summaryrefslogtreecommitdiff
path: root/platform/ios
diff options
context:
space:
mode:
authorPaul Gardiner <paul@pauls-mac-mini.site>2013-09-17 15:04:13 +0100
committerPaul Gardiner <paul@pauls-mac-mini.site>2013-09-17 15:08:40 +0100
commit43bcd8a7516bfbd455d81de7e00d5e139abce438 (patch)
tree71efd1fe71fb8dfd6dd4399547f9e10fa2b24d5d /platform/ios
parent0f6711fd4db36602034734b063cdaf30a396126c (diff)
downloadmupdf-43bcd8a7516bfbd455d81de7e00d5e139abce438.tar.xz
iOS: split the classes into separate files
Diffstat (limited to 'platform/ios')
-rw-r--r--platform/ios/Classes/MuAppDelegate.h18
-rw-r--r--platform/ios/Classes/MuAppDelegate.m98
-rw-r--r--platform/ios/Classes/MuDocumentController.h49
-rw-r--r--platform/ios/Classes/MuDocumentController.m575
-rw-r--r--platform/ios/Classes/MuHitView.h28
-rw-r--r--platform/ios/Classes/MuHitView.m94
-rw-r--r--platform/ios/Classes/MuLibraryController.h29
-rw-r--r--platform/ios/Classes/MuLibraryController.m225
-rw-r--r--platform/ios/Classes/MuOutlineController.h17
-rw-r--r--platform/ios/Classes/MuOutlineController.m76
-rw-r--r--platform/ios/Classes/MuPageView.h45
-rw-r--r--platform/ios/Classes/MuPageView.m462
-rw-r--r--platform/ios/MuPDF.xcodeproj/project.pbxproj57
-rw-r--r--platform/ios/common.h30
-rw-r--r--platform/ios/common.m50
-rw-r--r--platform/ios/main.m1642
16 files changed, 1850 insertions, 1645 deletions
diff --git a/platform/ios/Classes/MuAppDelegate.h b/platform/ios/Classes/MuAppDelegate.h
new file mode 100644
index 00000000..6131a328
--- /dev/null
+++ b/platform/ios/Classes/MuAppDelegate.h
@@ -0,0 +1,18 @@
+//
+// MuAppDelegate.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#import "MuLibraryController.h"
+
+@interface MuAppDelegate : NSObject <UIApplicationDelegate, UINavigationControllerDelegate>
+{
+ UIWindow *window;
+ UINavigationController *navigator;
+ MuLibraryController *library;
+}
+@end
diff --git a/platform/ios/Classes/MuAppDelegate.m b/platform/ios/Classes/MuAppDelegate.m
new file mode 100644
index 00000000..aaf0cff9
--- /dev/null
+++ b/platform/ios/Classes/MuAppDelegate.m
@@ -0,0 +1,98 @@
+//
+// MuAppDelegate.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#include "common.h"
+
+#import "MuAppDelegate.h"
+
+@implementation MuAppDelegate
+
+- (BOOL) application: (UIApplication*)application didFinishLaunchingWithOptions: (NSDictionary*)launchOptions
+{
+ NSString *filename;
+
+ queue = dispatch_queue_create("com.artifex.mupdf.queue", NULL);
+
+ // use at most 128M for resource cache
+ ctx = fz_new_context(NULL, NULL, 128<<20);
+
+ screenScale = [[UIScreen mainScreen] scale];
+
+ library = [[MuLibraryController alloc] initWithStyle: UITableViewStylePlain];
+
+ navigator = [[UINavigationController alloc] initWithRootViewController: library];
+ [[navigator navigationBar] setTranslucent: YES];
+ [[navigator toolbar] setTranslucent: YES];
+ [navigator setDelegate: self];
+
+ window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
+ [window setBackgroundColor: [UIColor scrollViewTexturedBackgroundColor]];
+ [window setRootViewController: navigator];
+ [window makeKeyAndVisible];
+
+ filename = [[NSUserDefaults standardUserDefaults] objectForKey: @"OpenDocumentKey"];
+ if (filename)
+ [library openDocument: filename];
+
+ filename = [launchOptions objectForKey: UIApplicationLaunchOptionsURLKey];
+ NSLog(@"urlkey = %@\n", filename);
+
+ return YES;
+}
+
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
+{
+ NSLog(@"openURL: %@\n", url);
+ if ([url isFileURL]) {
+ NSString *path = [url path];
+ NSString *dir = [NSString stringWithFormat: @"%@/Documents/", NSHomeDirectory()];
+ path = [path stringByReplacingOccurrencesOfString:@"/private" withString:@""];
+ path = [path stringByReplacingOccurrencesOfString:dir withString:@""];
+ NSLog(@"file relative path: %@\n", path);
+ [library openDocument:path];
+ return YES;
+ }
+ return NO;
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application
+{
+ printf("applicationDidEnterBackground!\n");
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application
+{
+ printf("applicationWillEnterForeground!\n");
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ printf("applicationDidBecomeActive!\n");
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ printf("applicationWillTerminate!\n");
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
+{
+ printf("applicationDidReceiveMemoryWarning\n");
+}
+
+- (void) dealloc
+{
+ dispatch_release(queue);
+ [library release];
+ [navigator release];
+ [window release];
+ [super dealloc];
+}
+
+@end
diff --git a/platform/ios/Classes/MuDocumentController.h b/platform/ios/Classes/MuDocumentController.h
new file mode 100644
index 00000000..f5d6115a
--- /dev/null
+++ b/platform/ios/Classes/MuDocumentController.h
@@ -0,0 +1,49 @@
+//
+// MuDocumentController.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#undef ABS
+#undef MIN
+#undef MAX
+
+#include "mupdf/fitz.h"
+
+#import "MuOutlineController.h"
+
+@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UISearchBarDelegate>
+{
+ fz_document *doc;
+ NSString *key;
+ MuOutlineController *outline;
+ UIScrollView *canvas;
+ UILabel *indicator;
+ UISlider *slider;
+ UISearchBar *searchBar;
+ UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton, *linkButton;
+ UIBarButtonItem *sliderWrapper;
+ int searchPage;
+ int cancelSearch;
+ int showLinks;
+ int width; // current screen size
+ int height;
+ int current; // currently visible page
+ int scroll_animating; // stop view updates during scrolling animations
+}
+- (id) initWithFilename: (NSString*)nsfilename document: (fz_document *)aDoc;
+- (void) createPageView: (int)number;
+- (void) gotoPage: (int)number animated: (BOOL)animated;
+- (void) onShowOutline: (id)sender;
+- (void) onShowSearch: (id)sender;
+- (void) onCancelSearch: (id)sender;
+- (void) resetSearch;
+- (void) showSearchResults: (int)count forPage: (int)number;
+- (void) onSlide: (id)sender;
+- (void) onTap: (UITapGestureRecognizer*)sender;
+- (void) showNavigationBar;
+- (void) hideNavigationBar;
+@end
diff --git a/platform/ios/Classes/MuDocumentController.m b/platform/ios/Classes/MuDocumentController.m
new file mode 100644
index 00000000..89ee0da8
--- /dev/null
+++ b/platform/ios/Classes/MuDocumentController.m
@@ -0,0 +1,575 @@
+//
+// MuDocumentController.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#include "common.h"
+#import "MuPageView.h"
+
+#import "MuDocumentController.h"
+
+#define GAP 20
+#define INDICATOR_Y -44-24
+#define SLIDER_W (width - GAP - 24)
+#define SEARCH_W (width - GAP - 170)
+
+static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_outline *outline, int level)
+{
+ char indent[8*4+1];
+ if (level > 8)
+ level = 8;
+ memset(indent, ' ', level * 4);
+ indent[level * 4] = 0;
+ while (outline)
+ {
+ if (outline->dest.kind == FZ_LINK_GOTO)
+ {
+ int page = outline->dest.ld.gotor.page;
+ if (page >= 0 && outline->title)
+ {
+ NSString *title = [NSString stringWithUTF8String: outline->title];
+ [titles addObject: [NSString stringWithFormat: @"%s%@", indent, title]];
+ [pages addObject: [NSNumber numberWithInt: page]];
+ }
+ }
+ flattenOutline(titles, pages, outline->down, level + 1);
+ outline = outline->next;
+ }
+}
+
+@implementation MuDocumentController
+
+- (id) initWithFilename: (NSString*)filename document: (fz_document *)aDoc
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ key = [filename retain];
+ doc = aDoc;
+
+ dispatch_sync(queue, ^{});
+
+ fz_outline *root = fz_load_outline(doc);
+ if (root) {
+ NSMutableArray *titles = [[NSMutableArray alloc] init];
+ NSMutableArray *pages = [[NSMutableArray alloc] init];
+ flattenOutline(titles, pages, root, 0);
+ if ([titles count])
+ outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages];
+ [titles release];
+ [pages release];
+ fz_free_outline(ctx, root);
+ }
+
+ return self;
+}
+
+- (void) loadView
+{
+ [[NSUserDefaults standardUserDefaults] setObject: key forKey: @"OpenDocumentKey"];
+
+ current = [[NSUserDefaults standardUserDefaults] integerForKey: key];
+ if (current < 0 || current >= fz_count_pages(doc))
+ current = 0;
+
+ UIView *view = [[UIView alloc] initWithFrame: CGRectZero];
+ [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
+ [view setAutoresizesSubviews: YES];
+
+ canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)];
+ [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
+ [canvas setPagingEnabled: YES];
+ [canvas setShowsHorizontalScrollIndicator: NO];
+ [canvas setShowsVerticalScrollIndicator: NO];
+ [canvas setDelegate: self];
+
+ [canvas addGestureRecognizer: [[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onTap:)] autorelease]];
+
+ scroll_animating = NO;
+
+ indicator = [[UILabel alloc] initWithFrame: CGRectZero];
+ [indicator setAutoresizingMask: UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
+ [indicator setText: @"0000 of 9999"];
+ [indicator sizeToFit];
+ [indicator setCenter: CGPointMake(0, INDICATOR_Y)];
+ [indicator setTextAlignment: UITextAlignmentCenter];
+ [indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]];
+ [indicator setTextColor: [UIColor whiteColor]];
+
+ [view addSubview: canvas];
+ [view addSubview: indicator];
+
+ slider = [[UISlider alloc] initWithFrame: CGRectZero];
+ [slider setMinimumValue: 0];
+ [slider setMaximumValue: fz_count_pages(doc) - 1];
+ [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged];
+
+ sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider];
+
+ [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]];
+
+ // Set up the buttons on the navigation and search bar
+
+ if (outline) {
+ outlineButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks
+ target:self action:@selector(onShowOutline:)];
+ }
+ linkButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemAction
+ target:self action:@selector(onToggleLinks:)];
+ cancelButton = [[UIBarButtonItem alloc]
+ initWithTitle: @"Cancel" style: UIBarButtonItemStyleBordered
+ target:self action:@selector(onCancelSearch:)];
+ searchButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemSearch
+ target:self action:@selector(onShowSearch:)];
+ prevButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemRewind
+ target:self action:@selector(onSearchPrev:)];
+ nextButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward
+ target:self action:@selector(onSearchNext:)];
+
+ searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)];
+ [searchBar setPlaceholder: @"Search"];
+ [searchBar setDelegate: self];
+ // HACK to make transparent background
+ [[searchBar.subviews objectAtIndex:0] removeFromSuperview];
+
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+
+ [[self navigationItem] setRightBarButtonItems:
+ [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
+
+ // TODO: add activityindicator to search bar
+
+ [self setView: view];
+ [view release];
+}
+
+- (void) dealloc
+{
+ if (doc) {
+ fz_document *self_doc = doc; // don't auto-retain self here!
+ dispatch_async(queue, ^{
+ printf("close document\n");
+ fz_close_document(self_doc);
+ });
+ }
+
+ [indicator release]; indicator = nil;
+ [slider release]; slider = nil;
+ [sliderWrapper release]; sliderWrapper = nil;
+ [searchBar release]; searchBar = nil;
+ [outlineButton release]; outlineButton = nil;
+ [searchButton release]; searchButton = nil;
+ [cancelButton release]; cancelButton = nil;
+ [prevButton release]; prevButton = nil;
+ [nextButton release]; nextButton = nil;
+ [canvas release]; canvas = nil;
+
+ [outline release];
+ [key release];
+ [super dealloc];
+}
+
+- (void) viewWillAppear: (BOOL)animated
+{
+ [self setTitle: [key lastPathComponent]];
+
+ [slider setValue: current];
+
+ [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
+
+ [[self navigationController] setToolbarHidden: NO animated: animated];
+}
+
+- (void) viewWillLayoutSubviews
+{
+ CGSize size = [canvas frame].size;
+ int max_width = fz_max(width, size.width);
+
+ width = size.width;
+ height = size.height;
+
+ [canvas setContentInset: UIEdgeInsetsZero];
+ [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
+ [canvas setContentOffset: CGPointMake(current * width, 0)];
+
+ [sliderWrapper setWidth: SLIDER_W];
+ [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)];
+
+ [[[self navigationController] toolbar] setNeedsLayout]; // force layout!
+
+ // use max_width so we don't clamp the content offset too early during animation
+ [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * max_width, height)];
+ [canvas setContentOffset: CGPointMake(current * width, 0)];
+
+ for (MuPageView *view in [canvas subviews]) {
+ if ([view number] == current) {
+ [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
+ [view willRotate];
+ }
+ }
+ for (MuPageView *view in [canvas subviews]) {
+ if ([view number] != current) {
+ [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
+ [view willRotate];
+ }
+ }
+}
+
+- (void) viewDidAppear: (BOOL)animated
+{
+ [self scrollViewDidScroll: canvas];
+}
+
+- (void) viewWillDisappear: (BOOL)animated
+{
+ [self setTitle: @"Resume"];
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"OpenDocumentKey"];
+ [[self navigationController] setToolbarHidden: YES animated: animated];
+}
+
+- (void) showNavigationBar
+{
+ if ([[self navigationController] isNavigationBarHidden]) {
+ [[self navigationController] setNavigationBarHidden: NO];
+ [[self navigationController] setToolbarHidden: NO];
+ [indicator setHidden: NO];
+
+ [UIView beginAnimations: @"MuNavBar" context: NULL];
+
+ [[[self navigationController] navigationBar] setAlpha: 1];
+ [[[self navigationController] toolbar] setAlpha: 1];
+ [indicator setAlpha: 1];
+
+ [UIView commitAnimations];
+ }
+}
+
+- (void) hideNavigationBar
+{
+ if (![[self navigationController] isNavigationBarHidden]) {
+ [searchBar resignFirstResponder];
+
+ [UIView beginAnimations: @"MuNavBar" context: NULL];
+ [UIView setAnimationDelegate: self];
+ [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)];
+
+ [[[self navigationController] navigationBar] setAlpha: 0];
+ [[[self navigationController] toolbar] setAlpha: 0];
+ [indicator setAlpha: 0];
+
+ [UIView commitAnimations];
+ }
+}
+
+- (void) onHideNavigationBarFinished
+{
+ [[self navigationController] setNavigationBarHidden: YES];
+ [[self navigationController] setToolbarHidden: YES];
+ [indicator setHidden: YES];
+}
+
+- (void) onShowOutline: (id)sender
+{
+ [[self navigationController] pushViewController: outline animated: YES];
+}
+
+- (void) onToggleLinks: (id)sender
+{
+ showLinks = !showLinks;
+ for (MuPageView *view in [canvas subviews])
+ {
+ if (showLinks)
+ [view showLinks];
+ else
+ [view hideLinks];
+ }
+}
+
+- (void) onShowSearch: (id)sender
+{
+ [[self navigationItem] setTitleView: searchBar];
+ [[self navigationItem] setRightBarButtonItems:
+ [NSArray arrayWithObjects: nextButton, prevButton, nil]];
+ [[self navigationItem] setLeftBarButtonItem: cancelButton];
+ [searchBar becomeFirstResponder];
+}
+
+- (void) onCancelSearch: (id)sender
+{
+ cancelSearch = YES;
+ [searchBar resignFirstResponder];
+ [[self navigationItem] setTitleView: nil];
+ [[self navigationItem] setRightBarButtonItems:
+ [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
+ [[self navigationItem] setLeftBarButtonItem: nil];
+ [self resetSearch];
+}
+
+- (void) resetSearch
+{
+ searchPage = -1;
+ for (MuPageView *view in [canvas subviews])
+ [view clearSearchResults];
+}
+
+- (void) showSearchResults: (int)count forPage: (int)number
+{
+ printf("search found match on page %d\n", number);
+ searchPage = number;
+ [self gotoPage: number animated: NO];
+ for (MuPageView *view in [canvas subviews])
+ if ([view number] == number)
+ [view showSearchResults: count];
+ else
+ [view clearSearchResults];
+}
+
+- (void) searchInDirection: (int)dir
+{
+ UITextField *searchField;
+ char *needle;
+ int start;
+
+ [searchBar resignFirstResponder];
+
+ if (searchPage == current)
+ start = current + dir;
+ else
+ start = current;
+
+ needle = strdup([[searchBar text] UTF8String]);
+
+ searchField = nil;
+ for (id view in [searchBar subviews])
+ if ([view isKindOfClass: [UITextField class]])
+ searchField = view;
+
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+ [searchField setEnabled: NO];
+
+ cancelSearch = NO;
+
+ dispatch_async(queue, ^{
+ for (int i = start; i >= 0 && i < fz_count_pages(doc); i += dir) {
+ int n = search_page(doc, i, needle, NULL);
+ if (n) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ [searchField setEnabled: YES];
+ [self showSearchResults: n forPage: i];
+ free(needle);
+ });
+ return;
+ }
+ if (cancelSearch) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ [searchField setEnabled: YES];
+ free(needle);
+ });
+ return;
+ }
+ }
+ dispatch_async(dispatch_get_main_queue(), ^{
+ printf("no search results found\n");
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ [searchField setEnabled: YES];
+ UIAlertView *alert = [[UIAlertView alloc]
+ initWithTitle: @"No matches found for:"
+ message: [NSString stringWithUTF8String: needle]
+ delegate: nil
+ cancelButtonTitle: @"Close"
+ otherButtonTitles: nil];
+ [alert show];
+ [alert release];
+ free(needle);
+ });
+ });
+}
+
+- (void) onSearchPrev: (id)sender
+{
+ [self searchInDirection: -1];
+}
+
+- (void) onSearchNext: (id)sender
+{
+ [self searchInDirection: 1];
+}
+
+- (void) searchBarSearchButtonClicked: (UISearchBar*)sender
+{
+ [self onSearchNext: sender];
+}
+
+- (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText
+{
+ [self resetSearch];
+ if ([[searchBar text] length] > 0) {
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ } else {
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+ }
+}
+
+- (void) onSlide: (id)sender
+{
+ int number = [slider value];
+ if ([slider isTracking])
+ [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
+ else
+ [self gotoPage: number animated: NO];
+}
+
+- (void) onTap: (UITapGestureRecognizer*)sender
+{
+ CGPoint p = [sender locationInView: canvas];
+ CGPoint ofs = [canvas contentOffset];
+ float x0 = (width - GAP) / 5;
+ float x1 = (width - GAP) - x0;
+ p.x -= ofs.x;
+ p.y -= ofs.y;
+ if (p.x < x0) {
+ [self gotoPage: current-1 animated: YES];
+ } else if (p.x > x1) {
+ [self gotoPage: current+1 animated: YES];
+ } else {
+ if ([[self navigationController] isNavigationBarHidden])
+ [self showNavigationBar];
+ else
+ [self hideNavigationBar];
+ }
+}
+
+- (void) scrollViewWillBeginDragging: (UIScrollView *)scrollView
+{
+ [self hideNavigationBar];
+}
+
+- (void) scrollViewDidScroll: (UIScrollView*)scrollview
+{
+ if (width == 0)
+ return; // not visible yet
+
+ if (scroll_animating)
+ return; // don't mess with layout during animations
+
+ float x = [canvas contentOffset].x + width * 0.5f;
+ current = x / width;
+
+ [[NSUserDefaults standardUserDefaults] setInteger: current forKey: key];
+
+ [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
+ [slider setValue: current];
+
+ // swap the distant page views out
+
+ NSMutableSet *invisiblePages = [[NSMutableSet alloc] init];
+ for (MuPageView *view in [canvas subviews]) {
+ if ([view number] != current)
+ [view resetZoomAnimated: YES];
+ if ([view number] < current - 2 || [view number] > current + 2)
+ [invisiblePages addObject: view];
+ }
+ for (MuPageView *view in invisiblePages)
+ [view removeFromSuperview];
+ [invisiblePages release]; // don't bother recycling them...
+
+ [self createPageView: current];
+ [self createPageView: current - 1];
+ [self createPageView: current + 1];
+
+ // reset search results when page has flipped
+ if (current != searchPage)
+ [self resetSearch];
+}
+
+- (void) createPageView: (int)number
+{
+ if (number < 0 || number >= fz_count_pages(doc))
+ return;
+ int found = 0;
+ for (MuPageView *view in [canvas subviews])
+ if ([view number] == number)
+ found = 1;
+ if (!found) {
+ MuPageView *view = [[MuPageView alloc] initWithFrame: CGRectMake(number * width, 0, width-GAP, height) document: doc page: number];
+ [canvas addSubview: view];
+ if (showLinks)
+ [view showLinks];
+ [view release];
+ }
+}
+
+- (void) gotoPage: (int)number animated: (BOOL)animated
+{
+ if (number < 0)
+ number = 0;
+ if (number >= fz_count_pages(doc))
+ number = fz_count_pages(doc) - 1;
+ if (current == number)
+ return;
+ if (animated) {
+ // setContentOffset:animated: does not use the normal animation
+ // framework. It also doesn't play nice with the tap gesture
+ // recognizer. So we do our own page flipping animation here.
+ // We must set the scroll_animating flag so that we don't create
+ // or remove subviews until after the animation, or they'll
+ // swoop in from origo during the animation.
+
+ scroll_animating = YES;
+ [UIView beginAnimations: @"MuScroll" context: NULL];
+ [UIView setAnimationDuration: 0.4];
+ [UIView setAnimationBeginsFromCurrentState: YES];
+ [UIView setAnimationDelegate: self];
+ [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)];
+
+ for (MuPageView *view in [canvas subviews])
+ [view resetZoomAnimated: NO];
+
+ [canvas setContentOffset: CGPointMake(number * width, 0)];
+ [slider setValue: number];
+ [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
+
+ [UIView commitAnimations];
+ } else {
+ for (MuPageView *view in [canvas subviews])
+ [view resetZoomAnimated: NO];
+ [canvas setContentOffset: CGPointMake(number * width, 0)];
+ }
+ current = number;
+}
+
+- (void) onGotoPageFinished
+{
+ scroll_animating = NO;
+ [self scrollViewDidScroll: canvas];
+}
+
+- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
+{
+ return YES;
+}
+
+- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation)o
+{
+ [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
+ [canvas setContentOffset: CGPointMake(current * width, 0)];
+}
+
+@end
diff --git a/platform/ios/Classes/MuHitView.h b/platform/ios/Classes/MuHitView.h
new file mode 100644
index 00000000..1be5dcb2
--- /dev/null
+++ b/platform/ios/Classes/MuHitView.h
@@ -0,0 +1,28 @@
+//
+// MuHitView.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#undef ABS
+#undef MIN
+#undef MAX
+
+#include "mupdf/fitz.h"
+
+@interface MuHitView : UIView
+{
+ CGSize pageSize;
+ int hitCount;
+ CGRect hitRects[500];
+ int linkPage[500];
+ char *linkUrl[500];
+ UIColor *color;
+}
+- (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc;
+- (id) initWithLinks: (fz_link*)links forDocument: (fz_document *)doc;
+- (void) setPageSize: (CGSize)s;
+@end
diff --git a/platform/ios/Classes/MuHitView.m b/platform/ios/Classes/MuHitView.m
new file mode 100644
index 00000000..be67601a
--- /dev/null
+++ b/platform/ios/Classes/MuHitView.m
@@ -0,0 +1,94 @@
+//
+// MuHitView.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import "common.h"
+#import "MuHitView.h"
+
+@implementation MuHitView
+
+- (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc
+{
+ self = [super initWithFrame: CGRectMake(0,0,100,100)];
+ if (self) {
+ [self setOpaque: NO];
+
+ color = [[UIColor colorWithRed: 0x25/255.0 green: 0x72/255.0 blue: 0xAC/255.0 alpha: 0.5] retain];
+
+ pageSize = CGSizeMake(100,100);
+
+ for (int i = 0; i < n && i < nelem(hitRects); i++) {
+ fz_rect bbox = search_result_bbox(doc, i); // this is thread-safe enough
+ hitRects[i].origin.x = bbox.x0;
+ hitRects[i].origin.y = bbox.y0;
+ hitRects[i].size.width = bbox.x1 - bbox.x0;
+ hitRects[i].size.height = bbox.y1 - bbox.y0;
+ }
+ hitCount = n;
+ }
+ return self;
+}
+
+- (id) initWithLinks: (fz_link*)link forDocument: (fz_document *)doc
+{
+ self = [super initWithFrame: CGRectMake(0,0,100,100)];
+ if (self) {
+ [self setOpaque: NO];
+
+ color = [[UIColor colorWithRed: 0xAC/255.0 green: 0x72/255.0 blue: 0x25/255.0 alpha: 0.5] retain];
+
+ pageSize = CGSizeMake(100,100);
+
+ while (link && hitCount < nelem(hitRects)) {
+ if (link->dest.kind == FZ_LINK_GOTO || link->dest.kind == FZ_LINK_URI) {
+ fz_rect bbox = link->rect;
+ hitRects[hitCount].origin.x = bbox.x0;
+ hitRects[hitCount].origin.y = bbox.y0;
+ hitRects[hitCount].size.width = bbox.x1 - bbox.x0;
+ hitRects[hitCount].size.height = bbox.y1 - bbox.y0;
+ linkPage[hitCount] = link->dest.kind == FZ_LINK_GOTO ? link->dest.ld.gotor.page : -1;
+ linkUrl[hitCount] = link->dest.kind == FZ_LINK_URI ? strdup(link->dest.ld.uri.uri) : nil;
+ hitCount++;
+ }
+ link = link->next;
+ }
+ }
+ return self;
+}
+
+- (void) setPageSize: (CGSize)s
+{
+ pageSize = s;
+ // if page takes a long time to load we may have drawn at the initial (wrong) size
+ [self setNeedsDisplay];
+}
+
+- (void) drawRect: (CGRect)r
+{
+ CGSize scale = fitPageToScreen(pageSize, self.bounds.size);
+
+ [color set];
+
+ for (int i = 0; i < hitCount; i++) {
+ CGRect rect = hitRects[i];
+ rect.origin.x *= scale.width;
+ rect.origin.y *= scale.height;
+ rect.size.width *= scale.width;
+ rect.size.height *= scale.height;
+ UIRectFill(rect);
+ }
+}
+
+- (void) dealloc
+{
+ int i;
+ [color release];
+ for (i = 0; i < hitCount; i++)
+ free(linkUrl[i]);
+ [super dealloc];
+}
+
+@end
diff --git a/platform/ios/Classes/MuLibraryController.h b/platform/ios/Classes/MuLibraryController.h
new file mode 100644
index 00000000..2bb7ff71
--- /dev/null
+++ b/platform/ios/Classes/MuLibraryController.h
@@ -0,0 +1,29 @@
+//
+// MuLibraryController.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#undef ABS
+#undef MIN
+#undef MAX
+
+#include "mupdf/fitz.h"
+
+@interface MuLibraryController : UITableViewController <UIActionSheetDelegate>
+{
+ NSArray *files;
+ NSTimer *timer;
+ fz_document *_doc; // temporaries for juggling password dialog
+ NSString *_filename;
+}
+- (void) openDocument: (NSString*)filename;
+- (void) askForPassword: (NSString*)prompt;
+- (void) onPasswordOkay;
+- (void) onPasswordCancel;
+- (void) reload;
+@end
+
diff --git a/platform/ios/Classes/MuLibraryController.m b/platform/ios/Classes/MuLibraryController.m
new file mode 100644
index 00000000..cc5a1ddd
--- /dev/null
+++ b/platform/ios/Classes/MuLibraryController.m
@@ -0,0 +1,225 @@
+//
+// MuLibraryController.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#include "common.h"
+#import "MuDocumentController.h"
+#import "MuLibraryController.h"
+
+static void showAlert(NSString *msg, NSString *filename)
+{
+ UIAlertView *alert = [[UIAlertView alloc]
+ initWithTitle: msg
+ message: filename
+ delegate: nil
+ cancelButtonTitle: @"Okay"
+ otherButtonTitles: nil];
+ [alert show];
+ [alert release];
+}
+
+@implementation MuLibraryController
+
+- (void) viewWillAppear: (BOOL)animated
+{
+ [self setTitle: @"PDF, XPS and CBZ Documents"];
+ [self reload];
+ printf("library viewWillAppear (starting reload timer)\n");
+ timer = [NSTimer timerWithTimeInterval: 3
+ target: self selector: @selector(reload) userInfo: nil
+ repeats: YES];
+ [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode];
+}
+
+- (void) viewWillDisappear: (BOOL)animated
+{
+ printf("library viewWillDisappear (stopping reload timer)\n");
+ [timer invalidate];
+ timer = nil;
+}
+
+- (void) reload
+{
+ if (files) {
+ [files release];
+ files = nil;
+ }
+
+ NSFileManager *fileman = [NSFileManager defaultManager];
+ NSString *docdir = [NSString stringWithFormat: @"%@/Documents", NSHomeDirectory()];
+ NSMutableArray *outfiles = [[NSMutableArray alloc] init];
+ NSDirectoryEnumerator *direnum = [fileman enumeratorAtPath:docdir];
+ NSString *file;
+ BOOL isdir;
+ while (file = [direnum nextObject]) {
+ NSString *filepath = [docdir stringByAppendingPathComponent:file];
+ NSLog(@"file %@\n", file);
+ if ([fileman fileExistsAtPath:filepath isDirectory:&isdir] && !isdir) {
+ [outfiles addObject:file];
+ }
+ }
+
+ files = outfiles;
+
+ [[self tableView] reloadData];
+}
+
+- (void) dealloc
+{
+ [files release];
+ [super dealloc];
+}
+
+- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
+{
+ return YES;
+}
+
+- (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
+{
+ return 1;
+}
+
+- (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
+{
+ return [files count];
+}
+
+- (void) actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
+{
+ if (buttonIndex == [actionSheet destructiveButtonIndex])
+ {
+ char filename[PATH_MAX];
+ int row = [actionSheet tag];
+
+ dispatch_sync(queue, ^{});
+
+ strcpy(filename, [NSHomeDirectory() UTF8String]);
+ strcat(filename, "/Documents/");
+ strcat(filename, [[files objectAtIndex: row - 1] UTF8String]);
+
+ printf("delete document '%s'\n", filename);
+
+ unlink(filename);
+
+ [self reload];
+ }
+}
+
+- (void) onTapDelete: (UIControl*)sender
+{
+ int row = [sender tag];
+ NSString *title = [NSString stringWithFormat: @"Delete %@?", [files objectAtIndex: row - 1]];
+ UIActionSheet *sheet = [[UIActionSheet alloc]
+ initWithTitle: title
+ delegate: self
+ cancelButtonTitle: @"Cancel"
+ destructiveButtonTitle: @"Delete"
+ otherButtonTitles: nil];
+ [sheet setTag: row];
+ [sheet showInView: [self tableView]];
+ [sheet release];
+}
+
+- (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
+{
+ static NSString *cellid = @"MuCellIdent";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
+ if (!cell)
+ cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellid] autorelease];
+ int row = [indexPath row];
+ [[cell textLabel] setText: [files objectAtIndex: row]];
+ [[cell textLabel] setFont: [UIFont systemFontOfSize: 20]];
+
+ UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
+ [deleteButton setImage: [UIImage imageNamed: @"x_alt_blue.png"] forState: UIControlStateNormal];
+ [deleteButton setFrame: CGRectMake(0, 0, 35, 35)];
+ [deleteButton addTarget: self action: @selector(onTapDelete:) forControlEvents: UIControlEventTouchUpInside];
+ [deleteButton setTag: row];
+ [cell setAccessoryView: deleteButton];
+
+ return cell;
+}
+
+- (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
+{
+ int row = [indexPath row];
+ [self openDocument: [files objectAtIndex: row]];
+}
+
+- (void) openDocument: (NSString*)nsfilename
+{
+ char filename[PATH_MAX];
+
+ dispatch_sync(queue, ^{});
+
+ strcpy(filename, [NSHomeDirectory() UTF8String]);
+ strcat(filename, "/Documents/");
+ strcat(filename, [nsfilename UTF8String]);
+
+ printf("open document '%s'\n", filename);
+
+ _filename = [nsfilename retain];
+ _doc = fz_open_document(ctx, filename);
+ if (!_doc) {
+ showAlert(@"Cannot open document", nsfilename);
+ return;
+ }
+
+ if (fz_needs_password(_doc))
+ [self askForPassword: @"'%@' needs a password:"];
+ else
+ [self onPasswordOkay];
+}
+
+- (void) askForPassword: (NSString*)prompt
+{
+ UIAlertView *passwordAlertView = [[UIAlertView alloc]
+ initWithTitle: @"Password Protected"
+ message: [NSString stringWithFormat: prompt, [_filename lastPathComponent]]
+ delegate: self
+ cancelButtonTitle: @"Cancel"
+ otherButtonTitles: @"Done", nil];
+ [passwordAlertView setAlertViewStyle: UIAlertViewStyleSecureTextInput];
+ [passwordAlertView show];
+ [passwordAlertView release];
+}
+
+- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
+{
+ char *password = (char*) [[[alertView textFieldAtIndex: 0] text] UTF8String];
+ [alertView dismissWithClickedButtonIndex: buttonIndex animated: TRUE];
+ if (buttonIndex == 1) {
+ if (fz_authenticate_password(_doc, password))
+ [self onPasswordOkay];
+ else
+ [self askForPassword: @"Wrong password for '%@'. Try again:"];
+ } else {
+ [self onPasswordCancel];
+ }
+}
+
+- (void) onPasswordOkay
+{
+ MuDocumentController *document = [[MuDocumentController alloc] initWithFilename: _filename document: _doc];
+ if (document) {
+ [self setTitle: @"Library"];
+ [[self navigationController] pushViewController: document animated: YES];
+ [document release];
+ }
+ [_filename release];
+ _doc = NULL;
+}
+
+- (void) onPasswordCancel
+{
+ [_filename release];
+ printf("close document (password cancel)\n");
+ fz_close_document(_doc);
+ _doc = NULL;
+}
+
+@end
diff --git a/platform/ios/Classes/MuOutlineController.h b/platform/ios/Classes/MuOutlineController.h
new file mode 100644
index 00000000..3af69848
--- /dev/null
+++ b/platform/ios/Classes/MuOutlineController.h
@@ -0,0 +1,17 @@
+//
+// MuOutlineController.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface MuOutlineController : UITableViewController
+{
+ id target;
+ NSMutableArray *titles;
+ NSMutableArray *pages;
+}
+- (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages;
+@end
diff --git a/platform/ios/Classes/MuOutlineController.m b/platform/ios/Classes/MuOutlineController.m
new file mode 100644
index 00000000..694fcef3
--- /dev/null
+++ b/platform/ios/Classes/MuOutlineController.m
@@ -0,0 +1,76 @@
+//
+// MuOutlineController.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import "MuOutlineController.h"
+
+@implementation MuOutlineController
+
+- (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages
+{
+ self = [super initWithStyle: UITableViewStylePlain];
+ if (self) {
+ [self setTitle: @"Table of Contents"];
+ target = aTarget; // only keep a weak reference, to avoid retain cycles
+ titles = [aTitles retain];
+ pages = [aPages retain];
+ [[self tableView] setSeparatorStyle: UITableViewCellSeparatorStyleNone];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [titles release];
+ [pages release];
+ [super dealloc];
+}
+
+- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
+{
+ return YES;
+}
+
+- (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
+{
+ return 1;
+}
+
+- (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
+{
+ return [titles count];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return 28;
+}
+
+- (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
+{
+ static NSString *cellid = @"MuCellIdent";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
+ if (!cell)
+ {
+ cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: cellid] autorelease];
+ [[cell textLabel] setFont: [UIFont systemFontOfSize: 16]];
+ [[cell detailTextLabel] setFont: [UIFont systemFontOfSize: 16]];
+ }
+ NSString *title = [titles objectAtIndex: [indexPath row]];
+ NSString *page = [pages objectAtIndex: [indexPath row]];
+ [[cell textLabel] setText: title];
+ [[cell detailTextLabel] setText: [NSString stringWithFormat: @"%d", [page intValue]+1]];
+ return cell;
+}
+
+- (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
+{
+ NSNumber *page = [pages objectAtIndex: [indexPath row]];
+ [target gotoPage: [page intValue] animated: NO];
+ [[self navigationController] popViewControllerAnimated: YES];
+}
+
+@end
diff --git a/platform/ios/Classes/MuPageView.h b/platform/ios/Classes/MuPageView.h
new file mode 100644
index 00000000..ab141ea8
--- /dev/null
+++ b/platform/ios/Classes/MuPageView.h
@@ -0,0 +1,45 @@
+//
+// MuPageView.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#undef ABS
+#undef MIN
+#undef MAX
+
+#include "mupdf/fitz.h"
+
+#include "MuHitView.h"
+
+@interface MuPageView : UIScrollView <UIScrollViewDelegate>
+{
+ fz_document *doc;
+ fz_page *page;
+ int number;
+ UIActivityIndicatorView *loadingView;
+ UIImageView *imageView;
+ UIImageView *tileView;
+ MuHitView *hitView;
+ MuHitView *linkView;
+ CGSize pageSize;
+ CGRect tileFrame;
+ float tileScale;
+ BOOL cancel;
+}
+- (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber;
+- (void) displayImage: (UIImage*)image;
+- (void) resizeImage;
+- (void) loadPage;
+- (void) loadTile;
+- (void) willRotate;
+- (void) resetZoomAnimated: (BOOL)animated;
+- (void) showSearchResults: (int)count;
+- (void) clearSearchResults;
+- (void) showLinks;
+- (void) hideLinks;
+- (int) number;
+@end
diff --git a/platform/ios/Classes/MuPageView.m b/platform/ios/Classes/MuPageView.m
new file mode 100644
index 00000000..53fe79b9
--- /dev/null
+++ b/platform/ios/Classes/MuPageView.m
@@ -0,0 +1,462 @@
+//
+// MuPageView.m
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#include "common.h"
+
+static CGSize measurePage(fz_document *doc, fz_page *page)
+{
+ CGSize pageSize;
+ fz_rect bounds;
+ fz_bound_page(doc, page, &bounds);
+ pageSize.width = bounds.x1 - bounds.x0;
+ pageSize.height = bounds.y1 - bounds.y0;
+ return pageSize;
+}
+
+static void releasePixmap(void *info, const void *data, size_t size)
+{
+ if (queue)
+ dispatch_async(queue, ^{
+ fz_drop_pixmap(ctx, info);
+ });
+ else
+ fz_drop_pixmap(ctx, info);
+}
+
+static UIImage *newImageWithPixmap(fz_pixmap *pix)
+{
+ unsigned char *samples = fz_pixmap_samples(ctx, pix);
+ int w = fz_pixmap_width(ctx, pix);
+ int h = fz_pixmap_height(ctx, pix);
+ CGDataProviderRef cgdata = CGDataProviderCreateWithData(pix, samples, w * 4 * h, releasePixmap);
+ CGColorSpaceRef cgcolor = CGColorSpaceCreateDeviceRGB();
+ CGImageRef cgimage = CGImageCreate(w, h, 8, 32, 4 * w,
+ cgcolor, kCGBitmapByteOrderDefault,
+ cgdata, NULL, NO, kCGRenderingIntentDefault);
+ UIImage *image = [[UIImage alloc]
+ initWithCGImage: cgimage
+ scale: screenScale
+ orientation: UIImageOrientationUp];
+ CGDataProviderRelease(cgdata);
+ CGColorSpaceRelease(cgcolor);
+ CGImageRelease(cgimage);
+ return image;
+}
+
+static UIImage *renderPage(fz_document *doc, fz_page *page, CGSize screenSize)
+{
+ CGSize pageSize;
+ fz_irect bbox;
+ fz_matrix ctm;
+ fz_device *dev;
+ fz_pixmap *pix;
+ CGSize scale;
+
+ screenSize.width *= screenScale;
+ screenSize.height *= screenScale;
+
+ pageSize = measurePage(doc, page);
+ scale = fitPageToScreen(pageSize, screenSize);
+ fz_scale(&ctm, scale.width, scale.height);
+ bbox = (fz_irect){0, 0, pageSize.width * scale.width, pageSize.height * scale.height};
+
+ pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox);
+ fz_clear_pixmap_with_value(ctx, pix, 255);
+
+ dev = fz_new_draw_device(ctx, pix);
+ fz_run_page(doc, page, dev, &ctm, NULL);
+ fz_free_device(dev);
+
+ return newImageWithPixmap(pix);
+}
+
+static UIImage *renderTile(fz_document *doc, fz_page *page, CGSize screenSize, CGRect tileRect, float zoom)
+{
+ CGSize pageSize;
+ fz_irect bbox;
+ fz_matrix ctm;
+ fz_device *dev;
+ fz_pixmap *pix;
+ CGSize scale;
+
+ screenSize.width *= screenScale;
+ screenSize.height *= screenScale;
+ tileRect.origin.x *= screenScale;
+ tileRect.origin.y *= screenScale;
+ tileRect.size.width *= screenScale;
+ tileRect.size.height *= screenScale;
+
+ pageSize = measurePage(doc, page);
+ scale = fitPageToScreen(pageSize, screenSize);
+ fz_scale(&ctm, scale.width * zoom, scale.height * zoom);
+
+ bbox.x0 = tileRect.origin.x;
+ bbox.y0 = tileRect.origin.y;
+ bbox.x1 = tileRect.origin.x + tileRect.size.width;
+ bbox.y1 = tileRect.origin.y + tileRect.size.height;
+
+ pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox);
+ fz_clear_pixmap_with_value(ctx, pix, 255);
+
+ dev = fz_new_draw_device(ctx, pix);
+ fz_run_page(doc, page, dev, &ctm, NULL);
+ fz_free_device(dev);
+
+ return newImageWithPixmap(pix);
+}
+
+#import "MuPageView.h"
+
+@implementation MuPageView
+
+- (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber
+{
+ self = [super initWithFrame: frame];
+ if (self) {
+ doc = aDoc;
+ number = aNumber;
+ cancel = NO;
+
+ [self setShowsVerticalScrollIndicator: NO];
+ [self setShowsHorizontalScrollIndicator: NO];
+ [self setDecelerationRate: UIScrollViewDecelerationRateFast];
+ [self setDelegate: self];
+
+ // zoomDidFinish/Begin events fire before bounce animation completes,
+ // making a mess when we rearrange views during the animation.
+ [self setBouncesZoom: NO];
+
+ [self resetZoomAnimated: NO];
+
+ // TODO: use a one shot timer to delay the display of this?
+ loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
+ [loadingView startAnimating];
+ [self addSubview: loadingView];
+
+ [self loadPage];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ // dealloc can trigger in background thread when the queued block is
+ // our last owner, and releases us on completion.
+ // Send the dealloc back to the main thread so we don't mess up UIKit.
+ if (dispatch_get_current_queue() != dispatch_get_main_queue()) {
+ __block id block_self = self; // don't auto-retain self!
+ dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; });
+ } else {
+ __block fz_page *block_page = page;
+ __block fz_document *block_doc = doc;
+ dispatch_async(queue, ^{
+ if (block_page)
+ fz_free_page(block_doc, block_page);
+ block_page = nil;
+ });
+ [linkView release];
+ [hitView release];
+ [tileView release];
+ [loadingView release];
+ [imageView release];
+ [super dealloc];
+ }
+}
+
+- (int) number
+{
+ return number;
+}
+
+- (void) showLinks
+{
+ if (!linkView) {
+ dispatch_async(queue, ^{
+ if (!page)
+ page = fz_load_page(doc, number);
+ fz_link *links = fz_load_links(doc, page);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ linkView = [[MuHitView alloc] initWithLinks: links forDocument: doc];
+ dispatch_async(queue, ^{
+ fz_drop_link(ctx, links);
+ });
+ if (imageView) {
+ [linkView setFrame: [imageView frame]];
+ [linkView setPageSize: pageSize];
+ }
+ [self addSubview: linkView];
+ });
+ });
+ }
+}
+
+- (void) hideLinks
+{
+ [linkView removeFromSuperview];
+ [linkView release];
+ linkView = nil;
+}
+
+- (void) showSearchResults: (int)count
+{
+ if (hitView) {
+ [hitView removeFromSuperview];
+ [hitView release];
+ hitView = nil;
+ }
+ hitView = [[MuHitView alloc] initWithSearchResults: count forDocument: doc];
+ if (imageView) {
+ [hitView setFrame: [imageView frame]];
+ [hitView setPageSize: pageSize];
+ }
+ [self addSubview: hitView];
+}
+
+- (void) clearSearchResults
+{
+ if (hitView) {
+ [hitView removeFromSuperview];
+ [hitView release];
+ hitView = nil;
+ }
+}
+
+- (void) resetZoomAnimated: (BOOL)animated
+{
+ // discard tile and any pending tile jobs
+ tileFrame = CGRectZero;
+ tileScale = 1;
+ if (tileView) {
+ [tileView removeFromSuperview];
+ [tileView release];
+ tileView = nil;
+ }
+
+ [self setMinimumZoomScale: 1];
+ [self setMaximumZoomScale: 5];
+ [self setZoomScale: 1 animated: animated];
+}
+
+- (void) removeFromSuperview
+{
+ cancel = YES;
+ [super removeFromSuperview];
+}
+
+- (void) loadPage
+{
+ if (number < 0 || number >= fz_count_pages(doc))
+ return;
+ dispatch_async(queue, ^{
+ if (!cancel) {
+ printf("render page %d\n", number);
+ if (!page)
+ page = fz_load_page(doc, number);
+ CGSize size = measurePage(doc, page);
+ UIImage *image = renderPage(doc, page, self.bounds.size);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ pageSize = size;
+ [self displayImage: image];
+ [image release];
+ });
+ } else {
+ printf("cancel page %d\n", number);
+ }
+ });
+}
+
+- (void) displayImage: (UIImage*)image
+{
+ if (loadingView) {
+ [loadingView removeFromSuperview];
+ [loadingView release];
+ loadingView = nil;
+ }
+
+ if (hitView)
+ [hitView setPageSize: pageSize];
+
+ if (!imageView) {
+ imageView = [[UIImageView alloc] initWithImage: image];
+ imageView.opaque = YES;
+ [self addSubview: imageView];
+ if (hitView)
+ [self bringSubviewToFront: hitView];
+ } else {
+ [imageView setImage: image];
+ }
+
+ [self resizeImage];
+}
+
+- (void) resizeImage
+{
+ if (imageView) {
+ CGSize imageSize = imageView.image.size;
+ CGSize scale = fitPageToScreen(imageSize, self.bounds.size);
+ if (fabs(scale.width - 1) > 0.1) {
+ CGRect frame = [imageView frame];
+ frame.size.width = imageSize.width * scale.width;
+ frame.size.height = imageSize.height * scale.height;
+ [imageView setFrame: frame];
+
+ printf("resized view; queuing up a reload (%d)\n", number);
+ dispatch_async(queue, ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size);
+ if (fabs(scale.width - 1) > 0.01)
+ [self loadPage];
+ });
+ });
+ } else {
+ [imageView sizeToFit];
+ }
+
+ [self setContentSize: imageView.frame.size];
+
+ [self layoutIfNeeded];
+ }
+
+}
+
+- (void) willRotate
+{
+ if (imageView) {
+ [self resetZoomAnimated: NO];
+ [self resizeImage];
+ }
+}
+
+- (void) layoutSubviews
+{
+ [super layoutSubviews];
+
+ // center the image as it becomes smaller than the size of the screen
+
+ CGSize boundsSize = self.bounds.size;
+ CGRect frameToCenter = loadingView ? loadingView.frame : imageView.frame;
+
+ // center horizontally
+ if (frameToCenter.size.width < boundsSize.width)
+ frameToCenter.origin.x = floor((boundsSize.width - frameToCenter.size.width) / 2);
+ else
+ frameToCenter.origin.x = 0;
+
+ // center vertically
+ if (frameToCenter.size.height < boundsSize.height)
+ frameToCenter.origin.y = floor((boundsSize.height - frameToCenter.size.height) / 2);
+ else
+ frameToCenter.origin.y = 0;
+
+ if (loadingView)
+ loadingView.frame = frameToCenter;
+ else
+ imageView.frame = frameToCenter;
+
+ if (hitView && imageView)
+ [hitView setFrame: [imageView frame]];
+}
+
+- (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView
+{
+ return imageView;
+}
+
+- (void) loadTile
+{
+ CGSize screenSize = self.bounds.size;
+
+ tileFrame.origin = self.contentOffset;
+ tileFrame.size = self.bounds.size;
+ tileFrame = CGRectIntersection(tileFrame, imageView.frame);
+ tileScale = self.zoomScale;
+
+ CGRect frame = tileFrame;
+ float scale = tileScale;
+
+ CGRect viewFrame = frame;
+ if (self.contentOffset.x < imageView.frame.origin.x)
+ viewFrame.origin.x = 0;
+ if (self.contentOffset.y < imageView.frame.origin.y)
+ viewFrame.origin.y = 0;
+
+ if (scale < 1.01)
+ return;
+
+ dispatch_async(queue, ^{
+ __block BOOL isValid;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
+ });
+ if (!isValid) {
+ printf("cancel tile\n");
+ return;
+ }
+
+ if (!page)
+ page = fz_load_page(doc, number);
+
+ printf("render tile\n");
+ UIImage *image = renderTile(doc, page, screenSize, viewFrame, scale);
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
+ if (isValid) {
+ tileFrame = CGRectZero;
+ tileScale = 1;
+ if (tileView) {
+ [tileView removeFromSuperview];
+ [tileView release];
+ tileView = nil;
+ }
+
+ tileView = [[UIImageView alloc] initWithFrame: frame];
+ [tileView setImage: image];
+ [self addSubview: tileView];
+ if (hitView)
+ [self bringSubviewToFront: hitView];
+ } else {
+ printf("discard tile\n");
+ }
+ [image release];
+ });
+ });
+}
+
+- (void) scrollViewDidScrollToTop:(UIScrollView *)scrollView { [self loadTile]; }
+- (void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self loadTile]; }
+- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self loadTile]; }
+- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
+{
+ if (!decelerate)
+ [self loadTile];
+}
+
+- (void) scrollViewWillBeginZooming: (UIScrollView*)scrollView withView: (UIView*)view
+{
+ // discard tile and any pending tile jobs
+ tileFrame = CGRectZero;
+ tileScale = 1;
+ if (tileView) {
+ [tileView removeFromSuperview];
+ [tileView release];
+ tileView = nil;
+ }
+}
+
+- (void) scrollViewDidEndZooming: (UIScrollView*)scrollView withView: (UIView*)view atScale: (float)scale
+{
+ [self loadTile];
+}
+
+- (void) scrollViewDidZoom: (UIScrollView*)scrollView
+{
+ if (hitView && imageView)
+ [hitView setFrame: [imageView frame]];
+}
+
+@end
diff --git a/platform/ios/MuPDF.xcodeproj/project.pbxproj b/platform/ios/MuPDF.xcodeproj/project.pbxproj
index dc3bb30a..40e1211b 100644
--- a/platform/ios/MuPDF.xcodeproj/project.pbxproj
+++ b/platform/ios/MuPDF.xcodeproj/project.pbxproj
@@ -28,6 +28,13 @@
96E1CDF21779A60700FCF717 /* iTunesArtwork.png in Resources */ = {isa = PBXBuildFile; fileRef = 96E1CDEB1779A60700FCF717 /* iTunesArtwork.png */; };
96E1CDF31779A60700FCF717 /* x_alt_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 96E1CDEC1779A60700FCF717 /* x_alt_blue.png */; };
96E1CDF41779A60700FCF717 /* x_alt_blue@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 96E1CDED1779A60700FCF717 /* x_alt_blue@2x.png */; };
+ DA1C68AB17E86A500061F586 /* MuLibraryController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C689917E85E8E0061F586 /* MuLibraryController.m */; };
+ DA1C68AD17E86A500061F586 /* MuOutlineController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C689D17E861020061F586 /* MuOutlineController.m */; };
+ DA1C68AF17E86A500061F586 /* MuHitView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C689F17E861870061F586 /* MuHitView.m */; };
+ DA1C68B117E86A500061F586 /* MuPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C68A517E863C70061F586 /* MuPageView.m */; };
+ DA1C68B317E86A500061F586 /* MuDocumentController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C68A717E864180061F586 /* MuDocumentController.m */; };
+ DA1C68B517E86A500061F586 /* MuAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C68A917E864CB0061F586 /* MuAppDelegate.m */; };
+ DA1C68C417E8969C0061F586 /* common.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C68C317E8969C0061F586 /* common.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -64,6 +71,20 @@
96E1CDEB1779A60700FCF717 /* iTunesArtwork.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iTunesArtwork.png; sourceTree = "<group>"; };
96E1CDEC1779A60700FCF717 /* x_alt_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = x_alt_blue.png; sourceTree = "<group>"; };
96E1CDED1779A60700FCF717 /* x_alt_blue@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "x_alt_blue@2x.png"; sourceTree = "<group>"; };
+ DA1C689817E85E8E0061F586 /* MuLibraryController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuLibraryController.h; path = Classes/MuLibraryController.h; sourceTree = "<group>"; };
+ DA1C689917E85E8E0061F586 /* MuLibraryController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuLibraryController.m; path = Classes/MuLibraryController.m; sourceTree = "<group>"; };
+ DA1C689C17E861020061F586 /* MuOutlineController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuOutlineController.h; path = Classes/MuOutlineController.h; sourceTree = "<group>"; };
+ DA1C689D17E861020061F586 /* MuOutlineController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuOutlineController.m; path = Classes/MuOutlineController.m; sourceTree = "<group>"; };
+ DA1C689E17E861870061F586 /* MuHitView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuHitView.h; path = Classes/MuHitView.h; sourceTree = "<group>"; };
+ DA1C689F17E861870061F586 /* MuHitView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuHitView.m; path = Classes/MuHitView.m; sourceTree = "<group>"; };
+ DA1C68A417E863C70061F586 /* MuPageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuPageView.h; path = Classes/MuPageView.h; sourceTree = "<group>"; };
+ DA1C68A517E863C70061F586 /* MuPageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuPageView.m; path = Classes/MuPageView.m; sourceTree = "<group>"; };
+ DA1C68A617E864180061F586 /* MuDocumentController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuDocumentController.h; path = Classes/MuDocumentController.h; sourceTree = "<group>"; };
+ DA1C68A717E864180061F586 /* MuDocumentController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuDocumentController.m; path = Classes/MuDocumentController.m; sourceTree = "<group>"; };
+ DA1C68A817E864CB0061F586 /* MuAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuAppDelegate.h; path = Classes/MuAppDelegate.h; sourceTree = "<group>"; };
+ DA1C68A917E864CB0061F586 /* MuAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuAppDelegate.m; path = Classes/MuAppDelegate.m; sourceTree = "<group>"; };
+ DA1C68C217E8968C0061F586 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
+ DA1C68C317E8969C0061F586 /* common.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = common.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -90,7 +111,10 @@
968E1E341779A54F0050CEA3 = {
isa = PBXGroup;
children = (
- 968E1E461779A54F0050CEA3 /* Files */,
+ DA1C68C317E8969C0061F586 /* common.m */,
+ DA1C68C217E8968C0061F586 /* common.h */,
+ DA1C689717E85E4A0061F586 /* Classes */,
+ 968E1E461779A54F0050CEA3 /* Resources */,
968E1E3F1779A54F0050CEA3 /* Frameworks */,
968E1E3E1779A54F0050CEA3 /* Products */,
);
@@ -121,7 +145,7 @@
name = Frameworks;
sourceTree = "<group>";
};
- 968E1E461779A54F0050CEA3 /* Files */ = {
+ 968E1E461779A54F0050CEA3 /* Resources */ = {
isa = PBXGroup;
children = (
96E1CDE71779A60700FCF717 /* Icon-72.png */,
@@ -137,9 +161,28 @@
968E1E541779A54F0050CEA3 /* Default@2x.png */,
968E1E561779A54F0050CEA3 /* Default-568h@2x.png */,
);
- name = Files;
+ name = Resources;
sourceTree = SOURCE_ROOT;
};
+ DA1C689717E85E4A0061F586 /* Classes */ = {
+ isa = PBXGroup;
+ children = (
+ DA1C689817E85E8E0061F586 /* MuLibraryController.h */,
+ DA1C689917E85E8E0061F586 /* MuLibraryController.m */,
+ DA1C689C17E861020061F586 /* MuOutlineController.h */,
+ DA1C689D17E861020061F586 /* MuOutlineController.m */,
+ DA1C689E17E861870061F586 /* MuHitView.h */,
+ DA1C689F17E861870061F586 /* MuHitView.m */,
+ DA1C68A417E863C70061F586 /* MuPageView.h */,
+ DA1C68A517E863C70061F586 /* MuPageView.m */,
+ DA1C68A617E864180061F586 /* MuDocumentController.h */,
+ DA1C68A717E864180061F586 /* MuDocumentController.m */,
+ DA1C68A817E864CB0061F586 /* MuAppDelegate.h */,
+ DA1C68A917E864CB0061F586 /* MuAppDelegate.m */,
+ );
+ name = Classes;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
@@ -255,7 +298,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DA1C68AB17E86A500061F586 /* MuLibraryController.m in Sources */,
+ DA1C68AD17E86A500061F586 /* MuOutlineController.m in Sources */,
+ DA1C68AF17E86A500061F586 /* MuHitView.m in Sources */,
+ DA1C68B117E86A500061F586 /* MuPageView.m in Sources */,
+ DA1C68B317E86A500061F586 /* MuDocumentController.m in Sources */,
+ DA1C68B517E86A500061F586 /* MuAppDelegate.m in Sources */,
96C8ED011779A88E00A30AF4 /* main.m in Sources */,
+ DA1C68C417E8969C0061F586 /* common.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -328,6 +378,7 @@
963AD8CD17D5FFD9000B0779 /* Release */,
);
defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
};
968E1E381779A54F0050CEA3 /* Build configuration list for PBXProject "MuPDF" */ = {
isa = XCConfigurationList;
diff --git a/platform/ios/common.h b/platform/ios/common.h
new file mode 100644
index 00000000..9642f009
--- /dev/null
+++ b/platform/ios/common.h
@@ -0,0 +1,30 @@
+//
+// common.h
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#ifndef MuPDF_common_h
+#define MuPDF_common_h
+
+#include <UIKit/UIKit.h>
+
+#undef ABS
+#undef MIN
+#undef MAX
+
+#include "mupdf/fitz.h"
+#include "dispatch/dispatch.h"
+
+extern fz_context *ctx;
+extern dispatch_queue_t queue;
+extern float screenScale;
+
+CGSize fitPageToScreen(CGSize page, CGSize screen);
+
+int search_page(fz_document *doc, int number, char *needle, fz_cookie *cookie);
+
+fz_rect search_result_bbox(fz_document *doc, int i);
+
+#endif
diff --git a/platform/ios/common.m b/platform/ios/common.m
new file mode 100644
index 00000000..78ccc1f5
--- /dev/null
+++ b/platform/ios/common.m
@@ -0,0 +1,50 @@
+//
+// common.c
+// MuPDF
+//
+// Copyright (c) 2013 Artifex Software, Inc. All rights reserved.
+//
+
+#include "common.h"
+
+fz_context *ctx;
+dispatch_queue_t queue;
+float screenScale = 1;
+
+CGSize fitPageToScreen(CGSize page, CGSize screen)
+{
+ float hscale = screen.width / page.width;
+ float vscale = screen.height / page.height;
+ float scale = fz_min(hscale, vscale);
+ hscale = floorf(page.width * scale) / page.width;
+ vscale = floorf(page.height * scale) / page.height;
+ return CGSizeMake(hscale, vscale);
+}
+
+static int hit_count = 0;
+static fz_rect hit_bbox[500];
+
+int search_page(fz_document *doc, int number, char *needle, fz_cookie *cookie)
+{
+ fz_page *page = fz_load_page(doc, number);
+
+ fz_text_sheet *sheet = fz_new_text_sheet(ctx);
+ fz_text_page *text = fz_new_text_page(ctx);
+ fz_device *dev = fz_new_text_device(ctx, sheet, text);
+ fz_run_page(doc, page, dev, &fz_identity, cookie);
+ fz_free_device(dev);
+
+ hit_count = fz_search_text_page(ctx, text, needle, hit_bbox, nelem(hit_bbox));
+
+ fz_free_text_page(ctx, text);
+ fz_free_text_sheet(ctx, sheet);
+ fz_free_page(doc, page);
+
+ return hit_count;
+}
+
+fz_rect search_result_bbox(fz_document *doc, int i)
+{
+ return hit_bbox[i];
+}
+
diff --git a/platform/ios/main.m b/platform/ios/main.m
index aa4eb730..516ced70 100644
--- a/platform/ios/main.m
+++ b/platform/ios/main.m
@@ -1,1647 +1,5 @@
#import <UIKit/UIKit.h>
-#undef ABS
-#undef MIN
-#undef MAX
-
-#include "mupdf/fitz.h"
-
-#define GAP 20
-#define INDICATOR_Y -44-24
-#define SLIDER_W (width - GAP - 24)
-#define SEARCH_W (width - GAP - 170)
-
-static dispatch_queue_t queue;
-static float screenScale = 1;
-static fz_context *ctx = NULL;
-
-@interface MuLibraryController : UITableViewController <UIActionSheetDelegate>
-{
- NSArray *files;
- NSTimer *timer;
- fz_document *_doc; // temporaries for juggling password dialog
- NSString *_filename;
-}
-- (void) openDocument: (NSString*)filename;
-- (void) askForPassword: (NSString*)prompt;
-- (void) onPasswordOkay;
-- (void) onPasswordCancel;
-- (void) reload;
-@end
-
-@interface MuOutlineController : UITableViewController
-{
- id target;
- NSMutableArray *titles;
- NSMutableArray *pages;
-}
-- (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages;
-@end
-
-@interface MuHitView : UIView
-{
- CGSize pageSize;
- int hitCount;
- CGRect hitRects[500];
- int linkPage[500];
- char *linkUrl[500];
- UIColor *color;
-}
-- (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc;
-- (id) initWithLinks: (fz_link*)links forDocument: (fz_document *)doc;
-- (void) setPageSize: (CGSize)s;
-@end
-
-@interface MuPageView : UIScrollView <UIScrollViewDelegate>
-{
- fz_document *doc;
- fz_page *page;
- int number;
- UIActivityIndicatorView *loadingView;
- UIImageView *imageView;
- UIImageView *tileView;
- MuHitView *hitView;
- MuHitView *linkView;
- CGSize pageSize;
- CGRect tileFrame;
- float tileScale;
- BOOL cancel;
-}
-- (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber;
-- (void) displayImage: (UIImage*)image;
-- (void) resizeImage;
-- (void) loadPage;
-- (void) loadTile;
-- (void) willRotate;
-- (void) resetZoomAnimated: (BOOL)animated;
-- (void) showSearchResults: (int)count;
-- (void) clearSearchResults;
-- (void) showLinks;
-- (void) hideLinks;
-- (int) number;
-@end
-
-@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UISearchBarDelegate>
-{
- fz_document *doc;
- NSString *key;
- MuOutlineController *outline;
- UIScrollView *canvas;
- UILabel *indicator;
- UISlider *slider;
- UISearchBar *searchBar;
- UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton, *linkButton;
- UIBarButtonItem *sliderWrapper;
- int searchPage;
- int cancelSearch;
- int showLinks;
- int width; // current screen size
- int height;
- int current; // currently visible page
- int scroll_animating; // stop view updates during scrolling animations
-}
-- (id) initWithFilename: (NSString*)nsfilename document: (fz_document *)aDoc;
-- (void) createPageView: (int)number;
-- (void) gotoPage: (int)number animated: (BOOL)animated;
-- (void) onShowOutline: (id)sender;
-- (void) onShowSearch: (id)sender;
-- (void) onCancelSearch: (id)sender;
-- (void) resetSearch;
-- (void) showSearchResults: (int)count forPage: (int)number;
-- (void) onSlide: (id)sender;
-- (void) onTap: (UITapGestureRecognizer*)sender;
-- (void) showNavigationBar;
-- (void) hideNavigationBar;
-@end
-
-@interface MuAppDelegate : NSObject <UIApplicationDelegate, UINavigationControllerDelegate>
-{
- UIWindow *window;
- UINavigationController *navigator;
- MuLibraryController *library;
-}
-@end
-
-#pragma mark -
-
-static int hit_count = 0;
-static fz_rect hit_bbox[500];
-
-static int
-search_page(fz_document *doc, int number, char *needle, fz_cookie *cookie)
-{
- fz_page *page = fz_load_page(doc, number);
-
- fz_text_sheet *sheet = fz_new_text_sheet(ctx);
- fz_text_page *text = fz_new_text_page(ctx);
- fz_device *dev = fz_new_text_device(ctx, sheet, text);
- fz_run_page(doc, page, dev, &fz_identity, cookie);
- fz_free_device(dev);
-
- hit_count = fz_search_text_page(ctx, text, needle, hit_bbox, nelem(hit_bbox));
-
- fz_free_text_page(ctx, text);
- fz_free_text_sheet(ctx, sheet);
- fz_free_page(doc, page);
-
- return hit_count;
-}
-
-static fz_rect
-search_result_bbox(fz_document *doc, int i)
-{
- return hit_bbox[i];
-}
-
-static void showAlert(NSString *msg, NSString *filename)
-{
- UIAlertView *alert = [[UIAlertView alloc]
- initWithTitle: msg
- message: filename
- delegate: nil
- cancelButtonTitle: @"Okay"
- otherButtonTitles: nil];
- [alert show];
- [alert release];
-}
-
-static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_outline *outline, int level)
-{
- char indent[8*4+1];
- if (level > 8)
- level = 8;
- memset(indent, ' ', level * 4);
- indent[level * 4] = 0;
- while (outline)
- {
- if (outline->dest.kind == FZ_LINK_GOTO)
- {
- int page = outline->dest.ld.gotor.page;
- if (page >= 0 && outline->title)
- {
- NSString *title = [NSString stringWithUTF8String: outline->title];
- [titles addObject: [NSString stringWithFormat: @"%s%@", indent, title]];
- [pages addObject: [NSNumber numberWithInt: page]];
- }
- }
- flattenOutline(titles, pages, outline->down, level + 1);
- outline = outline->next;
- }
-}
-
-static void releasePixmap(void *info, const void *data, size_t size)
-{
- if (queue)
- dispatch_async(queue, ^{
- fz_drop_pixmap(ctx, info);
- });
- else
- fz_drop_pixmap(ctx, info);
-}
-
-static UIImage *newImageWithPixmap(fz_pixmap *pix)
-{
- unsigned char *samples = fz_pixmap_samples(ctx, pix);
- int w = fz_pixmap_width(ctx, pix);
- int h = fz_pixmap_height(ctx, pix);
- CGDataProviderRef cgdata = CGDataProviderCreateWithData(pix, samples, w * 4 * h, releasePixmap);
- CGColorSpaceRef cgcolor = CGColorSpaceCreateDeviceRGB();
- CGImageRef cgimage = CGImageCreate(w, h, 8, 32, 4 * w,
- cgcolor, kCGBitmapByteOrderDefault,
- cgdata, NULL, NO, kCGRenderingIntentDefault);
- UIImage *image = [[UIImage alloc]
- initWithCGImage: cgimage
- scale: screenScale
- orientation: UIImageOrientationUp];
- CGDataProviderRelease(cgdata);
- CGColorSpaceRelease(cgcolor);
- CGImageRelease(cgimage);
- return image;
-}
-
-static CGSize fitPageToScreen(CGSize page, CGSize screen)
-{
- float hscale = screen.width / page.width;
- float vscale = screen.height / page.height;
- float scale = fz_min(hscale, vscale);
- hscale = floorf(page.width * scale) / page.width;
- vscale = floorf(page.height * scale) / page.height;
- return CGSizeMake(hscale, vscale);
-}
-
-static CGSize measurePage(fz_document *doc, fz_page *page)
-{
- CGSize pageSize;
- fz_rect bounds;
- fz_bound_page(doc, page, &bounds);
- pageSize.width = bounds.x1 - bounds.x0;
- pageSize.height = bounds.y1 - bounds.y0;
- return pageSize;
-}
-
-static UIImage *renderPage(fz_document *doc, fz_page *page, CGSize screenSize)
-{
- CGSize pageSize;
- fz_irect bbox;
- fz_matrix ctm;
- fz_device *dev;
- fz_pixmap *pix;
- CGSize scale;
-
- screenSize.width *= screenScale;
- screenSize.height *= screenScale;
-
- pageSize = measurePage(doc, page);
- scale = fitPageToScreen(pageSize, screenSize);
- fz_scale(&ctm, scale.width, scale.height);
- bbox = (fz_irect){0, 0, pageSize.width * scale.width, pageSize.height * scale.height};
-
- pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox);
- fz_clear_pixmap_with_value(ctx, pix, 255);
-
- dev = fz_new_draw_device(ctx, pix);
- fz_run_page(doc, page, dev, &ctm, NULL);
- fz_free_device(dev);
-
- return newImageWithPixmap(pix);
-}
-
-static UIImage *renderTile(fz_document *doc, fz_page *page, CGSize screenSize, CGRect tileRect, float zoom)
-{
- CGSize pageSize;
- fz_irect bbox;
- fz_matrix ctm;
- fz_device *dev;
- fz_pixmap *pix;
- CGSize scale;
-
- screenSize.width *= screenScale;
- screenSize.height *= screenScale;
- tileRect.origin.x *= screenScale;
- tileRect.origin.y *= screenScale;
- tileRect.size.width *= screenScale;
- tileRect.size.height *= screenScale;
-
- pageSize = measurePage(doc, page);
- scale = fitPageToScreen(pageSize, screenSize);
- fz_scale(&ctm, scale.width * zoom, scale.height * zoom);
-
- bbox.x0 = tileRect.origin.x;
- bbox.y0 = tileRect.origin.y;
- bbox.x1 = tileRect.origin.x + tileRect.size.width;
- bbox.y1 = tileRect.origin.y + tileRect.size.height;
-
- pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox);
- fz_clear_pixmap_with_value(ctx, pix, 255);
-
- dev = fz_new_draw_device(ctx, pix);
- fz_run_page(doc, page, dev, &ctm, NULL);
- fz_free_device(dev);
-
- return newImageWithPixmap(pix);
-}
-
-#pragma mark -
-
-@implementation MuLibraryController
-
-- (void) viewWillAppear: (BOOL)animated
-{
- [self setTitle: @"PDF, XPS and CBZ Documents"];
- [self reload];
- printf("library viewWillAppear (starting reload timer)\n");
- timer = [NSTimer timerWithTimeInterval: 3
- target: self selector: @selector(reload) userInfo: nil
- repeats: YES];
- [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode];
-}
-
-- (void) viewWillDisappear: (BOOL)animated
-{
- printf("library viewWillDisappear (stopping reload timer)\n");
- [timer invalidate];
- timer = nil;
-}
-
-- (void) reload
-{
- if (files) {
- [files release];
- files = nil;
- }
-
- NSFileManager *fileman = [NSFileManager defaultManager];
- NSString *docdir = [NSString stringWithFormat: @"%@/Documents", NSHomeDirectory()];
- NSMutableArray *outfiles = [[NSMutableArray alloc] init];
- NSDirectoryEnumerator *direnum = [fileman enumeratorAtPath:docdir];
- NSString *file;
- BOOL isdir;
- while (file = [direnum nextObject]) {
- NSString *filepath = [docdir stringByAppendingPathComponent:file];
- NSLog(@"file %@\n", file);
- if ([fileman fileExistsAtPath:filepath isDirectory:&isdir] && !isdir) {
- [outfiles addObject:file];
- }
- }
-
- files = outfiles;
-
- [[self tableView] reloadData];
-}
-
-- (void) dealloc
-{
- [files release];
- [super dealloc];
-}
-
-- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
-{
- return YES;
-}
-
-- (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
-{
- return 1;
-}
-
-- (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
-{
- return [files count];
-}
-
-- (void) actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
-{
- if (buttonIndex == [actionSheet destructiveButtonIndex])
- {
- char filename[PATH_MAX];
- int row = [actionSheet tag];
-
- dispatch_sync(queue, ^{});
-
- strcpy(filename, [NSHomeDirectory() UTF8String]);
- strcat(filename, "/Documents/");
- strcat(filename, [[files objectAtIndex: row - 1] UTF8String]);
-
- printf("delete document '%s'\n", filename);
-
- unlink(filename);
-
- [self reload];
- }
-}
-
-- (void) onTapDelete: (UIControl*)sender
-{
- int row = [sender tag];
- NSString *title = [NSString stringWithFormat: @"Delete %@?", [files objectAtIndex: row - 1]];
- UIActionSheet *sheet = [[UIActionSheet alloc]
- initWithTitle: title
- delegate: self
- cancelButtonTitle: @"Cancel"
- destructiveButtonTitle: @"Delete"
- otherButtonTitles: nil];
- [sheet setTag: row];
- [sheet showInView: [self tableView]];
- [sheet release];
-}
-
-- (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
-{
- static NSString *cellid = @"MuCellIdent";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
- if (!cell)
- cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellid] autorelease];
- int row = [indexPath row];
- [[cell textLabel] setText: [files objectAtIndex: row]];
- [[cell textLabel] setFont: [UIFont systemFontOfSize: 20]];
-
- UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
- [deleteButton setImage: [UIImage imageNamed: @"x_alt_blue.png"] forState: UIControlStateNormal];
- [deleteButton setFrame: CGRectMake(0, 0, 35, 35)];
- [deleteButton addTarget: self action: @selector(onTapDelete:) forControlEvents: UIControlEventTouchUpInside];
- [deleteButton setTag: row];
- [cell setAccessoryView: deleteButton];
-
- return cell;
-}
-
-- (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
-{
- int row = [indexPath row];
- [self openDocument: [files objectAtIndex: row]];
-}
-
-- (void) openDocument: (NSString*)nsfilename
-{
- char filename[PATH_MAX];
-
- dispatch_sync(queue, ^{});
-
- strcpy(filename, [NSHomeDirectory() UTF8String]);
- strcat(filename, "/Documents/");
- strcat(filename, [nsfilename UTF8String]);
-
- printf("open document '%s'\n", filename);
-
- _filename = [nsfilename retain];
- _doc = fz_open_document(ctx, filename);
- if (!_doc) {
- showAlert(@"Cannot open document", nsfilename);
- return;
- }
-
- if (fz_needs_password(_doc))
- [self askForPassword: @"'%@' needs a password:"];
- else
- [self onPasswordOkay];
-}
-
-- (void) askForPassword: (NSString*)prompt
-{
- UIAlertView *passwordAlertView = [[UIAlertView alloc]
- initWithTitle: @"Password Protected"
- message: [NSString stringWithFormat: prompt, [_filename lastPathComponent]]
- delegate: self
- cancelButtonTitle: @"Cancel"
- otherButtonTitles: @"Done", nil];
- [passwordAlertView setAlertViewStyle: UIAlertViewStyleSecureTextInput];
- [passwordAlertView show];
- [passwordAlertView release];
-}
-
-- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
-{
- char *password = (char*) [[[alertView textFieldAtIndex: 0] text] UTF8String];
- [alertView dismissWithClickedButtonIndex: buttonIndex animated: TRUE];
- if (buttonIndex == 1) {
- if (fz_authenticate_password(_doc, password))
- [self onPasswordOkay];
- else
- [self askForPassword: @"Wrong password for '%@'. Try again:"];
- } else {
- [self onPasswordCancel];
- }
-}
-
-- (void) onPasswordOkay
-{
- MuDocumentController *document = [[MuDocumentController alloc] initWithFilename: _filename document: _doc];
- if (document) {
- [self setTitle: @"Library"];
- [[self navigationController] pushViewController: document animated: YES];
- [document release];
- }
- [_filename release];
- _doc = NULL;
-}
-
-- (void) onPasswordCancel
-{
- [_filename release];
- printf("close document (password cancel)\n");
- fz_close_document(_doc);
- _doc = NULL;
-}
-
-@end
-
-#pragma mark -
-
-@implementation MuOutlineController
-
-- (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages
-{
- self = [super initWithStyle: UITableViewStylePlain];
- if (self) {
- [self setTitle: @"Table of Contents"];
- target = aTarget; // only keep a weak reference, to avoid retain cycles
- titles = [aTitles retain];
- pages = [aPages retain];
- [[self tableView] setSeparatorStyle: UITableViewCellSeparatorStyleNone];
- }
- return self;
-}
-
-- (void) dealloc
-{
- [titles release];
- [pages release];
- [super dealloc];
-}
-
-- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
-{
- return YES;
-}
-
-- (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
-{
- return 1;
-}
-
-- (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
-{
- return [titles count];
-}
-
-- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- return 28;
-}
-
-- (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
-{
- static NSString *cellid = @"MuCellIdent";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
- if (!cell)
- {
- cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: cellid] autorelease];
- [[cell textLabel] setFont: [UIFont systemFontOfSize: 16]];
- [[cell detailTextLabel] setFont: [UIFont systemFontOfSize: 16]];
- }
- NSString *title = [titles objectAtIndex: [indexPath row]];
- NSString *page = [pages objectAtIndex: [indexPath row]];
- [[cell textLabel] setText: title];
- [[cell detailTextLabel] setText: [NSString stringWithFormat: @"%d", [page intValue]+1]];
- return cell;
-}
-
-- (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
-{
- NSNumber *page = [pages objectAtIndex: [indexPath row]];
- [target gotoPage: [page intValue] animated: NO];
- [[self navigationController] popViewControllerAnimated: YES];
-}
-
-@end
-
-#pragma mark -
-
-@implementation MuHitView
-
-- (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc
-{
- self = [super initWithFrame: CGRectMake(0,0,100,100)];
- if (self) {
- [self setOpaque: NO];
-
- color = [[UIColor colorWithRed: 0x25/255.0 green: 0x72/255.0 blue: 0xAC/255.0 alpha: 0.5] retain];
-
- pageSize = CGSizeMake(100,100);
-
- for (int i = 0; i < n && i < nelem(hitRects); i++) {
- fz_rect bbox = search_result_bbox(doc, i); // this is thread-safe enough
- hitRects[i].origin.x = bbox.x0;
- hitRects[i].origin.y = bbox.y0;
- hitRects[i].size.width = bbox.x1 - bbox.x0;
- hitRects[i].size.height = bbox.y1 - bbox.y0;
- }
- hitCount = n;
- }
- return self;
-}
-
-- (id) initWithLinks: (fz_link*)link forDocument: (fz_document *)doc
-{
- self = [super initWithFrame: CGRectMake(0,0,100,100)];
- if (self) {
- [self setOpaque: NO];
-
- color = [[UIColor colorWithRed: 0xAC/255.0 green: 0x72/255.0 blue: 0x25/255.0 alpha: 0.5] retain];
-
- pageSize = CGSizeMake(100,100);
-
- while (link && hitCount < nelem(hitRects)) {
- if (link->dest.kind == FZ_LINK_GOTO || link->dest.kind == FZ_LINK_URI) {
- fz_rect bbox = link->rect;
- hitRects[hitCount].origin.x = bbox.x0;
- hitRects[hitCount].origin.y = bbox.y0;
- hitRects[hitCount].size.width = bbox.x1 - bbox.x0;
- hitRects[hitCount].size.height = bbox.y1 - bbox.y0;
- linkPage[hitCount] = link->dest.kind == FZ_LINK_GOTO ? link->dest.ld.gotor.page : -1;
- linkUrl[hitCount] = link->dest.kind == FZ_LINK_URI ? strdup(link->dest.ld.uri.uri) : nil;
- hitCount++;
- }
- link = link->next;
- }
- }
- return self;
-}
-
-- (void) setPageSize: (CGSize)s
-{
- pageSize = s;
- // if page takes a long time to load we may have drawn at the initial (wrong) size
- [self setNeedsDisplay];
-}
-
-- (void) drawRect: (CGRect)r
-{
- CGSize scale = fitPageToScreen(pageSize, self.bounds.size);
-
- [color set];
-
- for (int i = 0; i < hitCount; i++) {
- CGRect rect = hitRects[i];
- rect.origin.x *= scale.width;
- rect.origin.y *= scale.height;
- rect.size.width *= scale.width;
- rect.size.height *= scale.height;
- UIRectFill(rect);
- }
-}
-
-- (void) dealloc
-{
- int i;
- [color release];
- for (i = 0; i < hitCount; i++)
- free(linkUrl[i]);
- [super dealloc];
-}
-
-@end
-
-@implementation MuPageView
-
-- (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber
-{
- self = [super initWithFrame: frame];
- if (self) {
- doc = aDoc;
- number = aNumber;
- cancel = NO;
-
- [self setShowsVerticalScrollIndicator: NO];
- [self setShowsHorizontalScrollIndicator: NO];
- [self setDecelerationRate: UIScrollViewDecelerationRateFast];
- [self setDelegate: self];
-
- // zoomDidFinish/Begin events fire before bounce animation completes,
- // making a mess when we rearrange views during the animation.
- [self setBouncesZoom: NO];
-
- [self resetZoomAnimated: NO];
-
- // TODO: use a one shot timer to delay the display of this?
- loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
- [loadingView startAnimating];
- [self addSubview: loadingView];
-
- [self loadPage];
- }
- return self;
-}
-
-- (void) dealloc
-{
- // dealloc can trigger in background thread when the queued block is
- // our last owner, and releases us on completion.
- // Send the dealloc back to the main thread so we don't mess up UIKit.
- if (dispatch_get_current_queue() != dispatch_get_main_queue()) {
- __block id block_self = self; // don't auto-retain self!
- dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; });
- } else {
- __block fz_page *block_page = page;
- __block fz_document *block_doc = doc;
- dispatch_async(queue, ^{
- if (block_page)
- fz_free_page(block_doc, block_page);
- block_page = nil;
- });
- [linkView release];
- [hitView release];
- [tileView release];
- [loadingView release];
- [imageView release];
- [super dealloc];
- }
-}
-
-- (int) number
-{
- return number;
-}
-
-- (void) showLinks
-{
- if (!linkView) {
- dispatch_async(queue, ^{
- if (!page)
- page = fz_load_page(doc, number);
- fz_link *links = fz_load_links(doc, page);
- dispatch_async(dispatch_get_main_queue(), ^{
- linkView = [[MuHitView alloc] initWithLinks: links forDocument: doc];
- dispatch_async(queue, ^{
- fz_drop_link(ctx, links);
- });
- if (imageView) {
- [linkView setFrame: [imageView frame]];
- [linkView setPageSize: pageSize];
- }
- [self addSubview: linkView];
- });
- });
- }
-}
-
-- (void) hideLinks
-{
- [linkView removeFromSuperview];
- [linkView release];
- linkView = nil;
-}
-
-- (void) showSearchResults: (int)count
-{
- if (hitView) {
- [hitView removeFromSuperview];
- [hitView release];
- hitView = nil;
- }
- hitView = [[MuHitView alloc] initWithSearchResults: count forDocument: doc];
- if (imageView) {
- [hitView setFrame: [imageView frame]];
- [hitView setPageSize: pageSize];
- }
- [self addSubview: hitView];
-}
-
-- (void) clearSearchResults
-{
- if (hitView) {
- [hitView removeFromSuperview];
- [hitView release];
- hitView = nil;
- }
-}
-
-- (void) resetZoomAnimated: (BOOL)animated
-{
- // discard tile and any pending tile jobs
- tileFrame = CGRectZero;
- tileScale = 1;
- if (tileView) {
- [tileView removeFromSuperview];
- [tileView release];
- tileView = nil;
- }
-
- [self setMinimumZoomScale: 1];
- [self setMaximumZoomScale: 5];
- [self setZoomScale: 1 animated: animated];
-}
-
-- (void) removeFromSuperview
-{
- cancel = YES;
- [super removeFromSuperview];
-}
-
-- (void) loadPage
-{
- if (number < 0 || number >= fz_count_pages(doc))
- return;
- dispatch_async(queue, ^{
- if (!cancel) {
- printf("render page %d\n", number);
- if (!page)
- page = fz_load_page(doc, number);
- CGSize size = measurePage(doc, page);
- UIImage *image = renderPage(doc, page, self.bounds.size);
- dispatch_async(dispatch_get_main_queue(), ^{
- pageSize = size;
- [self displayImage: image];
- [image release];
- });
- } else {
- printf("cancel page %d\n", number);
- }
- });
-}
-
-- (void) displayImage: (UIImage*)image
-{
- if (loadingView) {
- [loadingView removeFromSuperview];
- [loadingView release];
- loadingView = nil;
- }
-
- if (hitView)
- [hitView setPageSize: pageSize];
-
- if (!imageView) {
- imageView = [[UIImageView alloc] initWithImage: image];
- imageView.opaque = YES;
- [self addSubview: imageView];
- if (hitView)
- [self bringSubviewToFront: hitView];
- } else {
- [imageView setImage: image];
- }
-
- [self resizeImage];
-}
-
-- (void) resizeImage
-{
- if (imageView) {
- CGSize imageSize = imageView.image.size;
- CGSize scale = fitPageToScreen(imageSize, self.bounds.size);
- if (fabs(scale.width - 1) > 0.1) {
- CGRect frame = [imageView frame];
- frame.size.width = imageSize.width * scale.width;
- frame.size.height = imageSize.height * scale.height;
- [imageView setFrame: frame];
-
- printf("resized view; queuing up a reload (%d)\n", number);
- dispatch_async(queue, ^{
- dispatch_async(dispatch_get_main_queue(), ^{
- CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size);
- if (fabs(scale.width - 1) > 0.01)
- [self loadPage];
- });
- });
- } else {
- [imageView sizeToFit];
- }
-
- [self setContentSize: imageView.frame.size];
-
- [self layoutIfNeeded];
- }
-
-}
-
-- (void) willRotate
-{
- if (imageView) {
- [self resetZoomAnimated: NO];
- [self resizeImage];
- }
-}
-
-- (void) layoutSubviews
-{
- [super layoutSubviews];
-
- // center the image as it becomes smaller than the size of the screen
-
- CGSize boundsSize = self.bounds.size;
- CGRect frameToCenter = loadingView ? loadingView.frame : imageView.frame;
-
- // center horizontally
- if (frameToCenter.size.width < boundsSize.width)
- frameToCenter.origin.x = floor((boundsSize.width - frameToCenter.size.width) / 2);
- else
- frameToCenter.origin.x = 0;
-
- // center vertically
- if (frameToCenter.size.height < boundsSize.height)
- frameToCenter.origin.y = floor((boundsSize.height - frameToCenter.size.height) / 2);
- else
- frameToCenter.origin.y = 0;
-
- if (loadingView)
- loadingView.frame = frameToCenter;
- else
- imageView.frame = frameToCenter;
-
- if (hitView && imageView)
- [hitView setFrame: [imageView frame]];
-}
-
-- (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView
-{
- return imageView;
-}
-
-- (void) loadTile
-{
- CGSize screenSize = self.bounds.size;
-
- tileFrame.origin = self.contentOffset;
- tileFrame.size = self.bounds.size;
- tileFrame = CGRectIntersection(tileFrame, imageView.frame);
- tileScale = self.zoomScale;
-
- CGRect frame = tileFrame;
- float scale = tileScale;
-
- CGRect viewFrame = frame;
- if (self.contentOffset.x < imageView.frame.origin.x)
- viewFrame.origin.x = 0;
- if (self.contentOffset.y < imageView.frame.origin.y)
- viewFrame.origin.y = 0;
-
- if (scale < 1.01)
- return;
-
- dispatch_async(queue, ^{
- __block BOOL isValid;
- dispatch_sync(dispatch_get_main_queue(), ^{
- isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
- });
- if (!isValid) {
- printf("cancel tile\n");
- return;
- }
-
- if (!page)
- page = fz_load_page(doc, number);
-
- printf("render tile\n");
- UIImage *image = renderTile(doc, page, screenSize, viewFrame, scale);
-
- dispatch_async(dispatch_get_main_queue(), ^{
- isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
- if (isValid) {
- tileFrame = CGRectZero;
- tileScale = 1;
- if (tileView) {
- [tileView removeFromSuperview];
- [tileView release];
- tileView = nil;
- }
-
- tileView = [[UIImageView alloc] initWithFrame: frame];
- [tileView setImage: image];
- [self addSubview: tileView];
- if (hitView)
- [self bringSubviewToFront: hitView];
- } else {
- printf("discard tile\n");
- }
- [image release];
- });
- });
-}
-
-- (void) scrollViewDidScrollToTop:(UIScrollView *)scrollView { [self loadTile]; }
-- (void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self loadTile]; }
-- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self loadTile]; }
-- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
-{
- if (!decelerate)
- [self loadTile];
-}
-
-- (void) scrollViewWillBeginZooming: (UIScrollView*)scrollView withView: (UIView*)view
-{
- // discard tile and any pending tile jobs
- tileFrame = CGRectZero;
- tileScale = 1;
- if (tileView) {
- [tileView removeFromSuperview];
- [tileView release];
- tileView = nil;
- }
-}
-
-- (void) scrollViewDidEndZooming: (UIScrollView*)scrollView withView: (UIView*)view atScale: (float)scale
-{
- [self loadTile];
-}
-
-- (void) scrollViewDidZoom: (UIScrollView*)scrollView
-{
- if (hitView && imageView)
- [hitView setFrame: [imageView frame]];
-}
-
-@end
-
-#pragma mark -
-
-@implementation MuDocumentController
-
-- (id) initWithFilename: (NSString*)filename document: (fz_document *)aDoc
-{
- self = [super init];
- if (!self)
- return nil;
-
- key = [filename retain];
- doc = aDoc;
-
- dispatch_sync(queue, ^{});
-
- fz_outline *root = fz_load_outline(doc);
- if (root) {
- NSMutableArray *titles = [[NSMutableArray alloc] init];
- NSMutableArray *pages = [[NSMutableArray alloc] init];
- flattenOutline(titles, pages, root, 0);
- if ([titles count])
- outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages];
- [titles release];
- [pages release];
- fz_free_outline(ctx, root);
- }
-
- return self;
-}
-
-- (void) loadView
-{
- [[NSUserDefaults standardUserDefaults] setObject: key forKey: @"OpenDocumentKey"];
-
- current = [[NSUserDefaults standardUserDefaults] integerForKey: key];
- if (current < 0 || current >= fz_count_pages(doc))
- current = 0;
-
- UIView *view = [[UIView alloc] initWithFrame: CGRectZero];
- [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
- [view setAutoresizesSubviews: YES];
-
- canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)];
- [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
- [canvas setPagingEnabled: YES];
- [canvas setShowsHorizontalScrollIndicator: NO];
- [canvas setShowsVerticalScrollIndicator: NO];
- [canvas setDelegate: self];
-
- [canvas addGestureRecognizer: [[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onTap:)] autorelease]];
-
- scroll_animating = NO;
-
- indicator = [[UILabel alloc] initWithFrame: CGRectZero];
- [indicator setAutoresizingMask: UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
- [indicator setText: @"0000 of 9999"];
- [indicator sizeToFit];
- [indicator setCenter: CGPointMake(0, INDICATOR_Y)];
- [indicator setTextAlignment: UITextAlignmentCenter];
- [indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]];
- [indicator setTextColor: [UIColor whiteColor]];
-
- [view addSubview: canvas];
- [view addSubview: indicator];
-
- slider = [[UISlider alloc] initWithFrame: CGRectZero];
- [slider setMinimumValue: 0];
- [slider setMaximumValue: fz_count_pages(doc) - 1];
- [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged];
-
- sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider];
-
- [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]];
-
- // Set up the buttons on the navigation and search bar
-
- if (outline) {
- outlineButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks
- target:self action:@selector(onShowOutline:)];
- }
- linkButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemAction
- target:self action:@selector(onToggleLinks:)];
- cancelButton = [[UIBarButtonItem alloc]
- initWithTitle: @"Cancel" style: UIBarButtonItemStyleBordered
- target:self action:@selector(onCancelSearch:)];
- searchButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemSearch
- target:self action:@selector(onShowSearch:)];
- prevButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemRewind
- target:self action:@selector(onSearchPrev:)];
- nextButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward
- target:self action:@selector(onSearchNext:)];
-
- searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)];
- [searchBar setPlaceholder: @"Search"];
- [searchBar setDelegate: self];
- // HACK to make transparent background
- [[searchBar.subviews objectAtIndex:0] removeFromSuperview];
-
- [prevButton setEnabled: NO];
- [nextButton setEnabled: NO];
-
- [[self navigationItem] setRightBarButtonItems:
- [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
-
- // TODO: add activityindicator to search bar
-
- [self setView: view];
- [view release];
-}
-
-- (void) dealloc
-{
- if (doc) {
- fz_document *self_doc = doc; // don't auto-retain self here!
- dispatch_async(queue, ^{
- printf("close document\n");
- fz_close_document(self_doc);
- });
- }
-
- [indicator release]; indicator = nil;
- [slider release]; slider = nil;
- [sliderWrapper release]; sliderWrapper = nil;
- [searchBar release]; searchBar = nil;
- [outlineButton release]; outlineButton = nil;
- [searchButton release]; searchButton = nil;
- [cancelButton release]; cancelButton = nil;
- [prevButton release]; prevButton = nil;
- [nextButton release]; nextButton = nil;
- [canvas release]; canvas = nil;
-
- [outline release];
- [key release];
- [super dealloc];
-}
-
-- (void) viewWillAppear: (BOOL)animated
-{
- [self setTitle: [key lastPathComponent]];
-
- [slider setValue: current];
-
- [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
-
- [[self navigationController] setToolbarHidden: NO animated: animated];
-}
-
-- (void) viewWillLayoutSubviews
-{
- CGSize size = [canvas frame].size;
- int max_width = fz_max(width, size.width);
-
- width = size.width;
- height = size.height;
-
- [canvas setContentInset: UIEdgeInsetsZero];
- [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
- [canvas setContentOffset: CGPointMake(current * width, 0)];
-
- [sliderWrapper setWidth: SLIDER_W];
- [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)];
-
- [[[self navigationController] toolbar] setNeedsLayout]; // force layout!
-
- // use max_width so we don't clamp the content offset too early during animation
- [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * max_width, height)];
- [canvas setContentOffset: CGPointMake(current * width, 0)];
-
- for (MuPageView *view in [canvas subviews]) {
- if ([view number] == current) {
- [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
- [view willRotate];
- }
- }
- for (MuPageView *view in [canvas subviews]) {
- if ([view number] != current) {
- [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
- [view willRotate];
- }
- }
-}
-
-- (void) viewDidAppear: (BOOL)animated
-{
- [self scrollViewDidScroll: canvas];
-}
-
-- (void) viewWillDisappear: (BOOL)animated
-{
- [self setTitle: @"Resume"];
- [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"OpenDocumentKey"];
- [[self navigationController] setToolbarHidden: YES animated: animated];
-}
-
-- (void) showNavigationBar
-{
- if ([[self navigationController] isNavigationBarHidden]) {
- [[self navigationController] setNavigationBarHidden: NO];
- [[self navigationController] setToolbarHidden: NO];
- [indicator setHidden: NO];
-
- [UIView beginAnimations: @"MuNavBar" context: NULL];
-
- [[[self navigationController] navigationBar] setAlpha: 1];
- [[[self navigationController] toolbar] setAlpha: 1];
- [indicator setAlpha: 1];
-
- [UIView commitAnimations];
- }
-}
-
-- (void) hideNavigationBar
-{
- if (![[self navigationController] isNavigationBarHidden]) {
- [searchBar resignFirstResponder];
-
- [UIView beginAnimations: @"MuNavBar" context: NULL];
- [UIView setAnimationDelegate: self];
- [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)];
-
- [[[self navigationController] navigationBar] setAlpha: 0];
- [[[self navigationController] toolbar] setAlpha: 0];
- [indicator setAlpha: 0];
-
- [UIView commitAnimations];
- }
-}
-
-- (void) onHideNavigationBarFinished
-{
- [[self navigationController] setNavigationBarHidden: YES];
- [[self navigationController] setToolbarHidden: YES];
- [indicator setHidden: YES];
-}
-
-- (void) onShowOutline: (id)sender
-{
- [[self navigationController] pushViewController: outline animated: YES];
-}
-
-- (void) onToggleLinks: (id)sender
-{
- showLinks = !showLinks;
- for (MuPageView *view in [canvas subviews])
- {
- if (showLinks)
- [view showLinks];
- else
- [view hideLinks];
- }
-}
-
-- (void) onShowSearch: (id)sender
-{
- [[self navigationItem] setTitleView: searchBar];
- [[self navigationItem] setRightBarButtonItems:
- [NSArray arrayWithObjects: nextButton, prevButton, nil]];
- [[self navigationItem] setLeftBarButtonItem: cancelButton];
- [searchBar becomeFirstResponder];
-}
-
-- (void) onCancelSearch: (id)sender
-{
- cancelSearch = YES;
- [searchBar resignFirstResponder];
- [[self navigationItem] setTitleView: nil];
- [[self navigationItem] setRightBarButtonItems:
- [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
- [[self navigationItem] setLeftBarButtonItem: nil];
- [self resetSearch];
-}
-
-- (void) resetSearch
-{
- searchPage = -1;
- for (MuPageView *view in [canvas subviews])
- [view clearSearchResults];
-}
-
-- (void) showSearchResults: (int)count forPage: (int)number
-{
- printf("search found match on page %d\n", number);
- searchPage = number;
- [self gotoPage: number animated: NO];
- for (MuPageView *view in [canvas subviews])
- if ([view number] == number)
- [view showSearchResults: count];
- else
- [view clearSearchResults];
-}
-
-- (void) searchInDirection: (int)dir
-{
- UITextField *searchField;
- char *needle;
- int start;
-
- [searchBar resignFirstResponder];
-
- if (searchPage == current)
- start = current + dir;
- else
- start = current;
-
- needle = strdup([[searchBar text] UTF8String]);
-
- searchField = nil;
- for (id view in [searchBar subviews])
- if ([view isKindOfClass: [UITextField class]])
- searchField = view;
-
- [prevButton setEnabled: NO];
- [nextButton setEnabled: NO];
- [searchField setEnabled: NO];
-
- cancelSearch = NO;
-
- dispatch_async(queue, ^{
- for (int i = start; i >= 0 && i < fz_count_pages(doc); i += dir) {
- int n = search_page(doc, i, needle, NULL);
- if (n) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [prevButton setEnabled: YES];
- [nextButton setEnabled: YES];
- [searchField setEnabled: YES];
- [self showSearchResults: n forPage: i];
- free(needle);
- });
- return;
- }
- if (cancelSearch) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [prevButton setEnabled: YES];
- [nextButton setEnabled: YES];
- [searchField setEnabled: YES];
- free(needle);
- });
- return;
- }
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- printf("no search results found\n");
- [prevButton setEnabled: YES];
- [nextButton setEnabled: YES];
- [searchField setEnabled: YES];
- UIAlertView *alert = [[UIAlertView alloc]
- initWithTitle: @"No matches found for:"
- message: [NSString stringWithUTF8String: needle]
- delegate: nil
- cancelButtonTitle: @"Close"
- otherButtonTitles: nil];
- [alert show];
- [alert release];
- free(needle);
- });
- });
-}
-
-- (void) onSearchPrev: (id)sender
-{
- [self searchInDirection: -1];
-}
-
-- (void) onSearchNext: (id)sender
-{
- [self searchInDirection: 1];
-}
-
-- (void) searchBarSearchButtonClicked: (UISearchBar*)sender
-{
- [self onSearchNext: sender];
-}
-
-- (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText
-{
- [self resetSearch];
- if ([[searchBar text] length] > 0) {
- [prevButton setEnabled: YES];
- [nextButton setEnabled: YES];
- } else {
- [prevButton setEnabled: NO];
- [nextButton setEnabled: NO];
- }
-}
-
-- (void) onSlide: (id)sender
-{
- int number = [slider value];
- if ([slider isTracking])
- [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
- else
- [self gotoPage: number animated: NO];
-}
-
-- (void) onTap: (UITapGestureRecognizer*)sender
-{
- CGPoint p = [sender locationInView: canvas];
- CGPoint ofs = [canvas contentOffset];
- float x0 = (width - GAP) / 5;
- float x1 = (width - GAP) - x0;
- p.x -= ofs.x;
- p.y -= ofs.y;
- if (p.x < x0) {
- [self gotoPage: current-1 animated: YES];
- } else if (p.x > x1) {
- [self gotoPage: current+1 animated: YES];
- } else {
- if ([[self navigationController] isNavigationBarHidden])
- [self showNavigationBar];
- else
- [self hideNavigationBar];
- }
-}
-
-- (void) scrollViewWillBeginDragging: (UIScrollView *)scrollView
-{
- [self hideNavigationBar];
-}
-
-- (void) scrollViewDidScroll: (UIScrollView*)scrollview
-{
- if (width == 0)
- return; // not visible yet
-
- if (scroll_animating)
- return; // don't mess with layout during animations
-
- float x = [canvas contentOffset].x + width * 0.5f;
- current = x / width;
-
- [[NSUserDefaults standardUserDefaults] setInteger: current forKey: key];
-
- [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
- [slider setValue: current];
-
- // swap the distant page views out
-
- NSMutableSet *invisiblePages = [[NSMutableSet alloc] init];
- for (MuPageView *view in [canvas subviews]) {
- if ([view number] != current)
- [view resetZoomAnimated: YES];
- if ([view number] < current - 2 || [view number] > current + 2)
- [invisiblePages addObject: view];
- }
- for (MuPageView *view in invisiblePages)
- [view removeFromSuperview];
- [invisiblePages release]; // don't bother recycling them...
-
- [self createPageView: current];
- [self createPageView: current - 1];
- [self createPageView: current + 1];
-
- // reset search results when page has flipped
- if (current != searchPage)
- [self resetSearch];
-}
-
-- (void) createPageView: (int)number
-{
- if (number < 0 || number >= fz_count_pages(doc))
- return;
- int found = 0;
- for (MuPageView *view in [canvas subviews])
- if ([view number] == number)
- found = 1;
- if (!found) {
- MuPageView *view = [[MuPageView alloc] initWithFrame: CGRectMake(number * width, 0, width-GAP, height) document: doc page: number];
- [canvas addSubview: view];
- if (showLinks)
- [view showLinks];
- [view release];
- }
-}
-
-- (void) gotoPage: (int)number animated: (BOOL)animated
-{
- if (number < 0)
- number = 0;
- if (number >= fz_count_pages(doc))
- number = fz_count_pages(doc) - 1;
- if (current == number)
- return;
- if (animated) {
- // setContentOffset:animated: does not use the normal animation
- // framework. It also doesn't play nice with the tap gesture
- // recognizer. So we do our own page flipping animation here.
- // We must set the scroll_animating flag so that we don't create
- // or remove subviews until after the animation, or they'll
- // swoop in from origo during the animation.
-
- scroll_animating = YES;
- [UIView beginAnimations: @"MuScroll" context: NULL];
- [UIView setAnimationDuration: 0.4];
- [UIView setAnimationBeginsFromCurrentState: YES];
- [UIView setAnimationDelegate: self];
- [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)];
-
- for (MuPageView *view in [canvas subviews])
- [view resetZoomAnimated: NO];
-
- [canvas setContentOffset: CGPointMake(number * width, 0)];
- [slider setValue: number];
- [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
-
- [UIView commitAnimations];
- } else {
- for (MuPageView *view in [canvas subviews])
- [view resetZoomAnimated: NO];
- [canvas setContentOffset: CGPointMake(number * width, 0)];
- }
- current = number;
-}
-
-- (void) onGotoPageFinished
-{
- scroll_animating = NO;
- [self scrollViewDidScroll: canvas];
-}
-
-- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
-{
- return YES;
-}
-
-- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation)o
-{
- [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
- [canvas setContentOffset: CGPointMake(current * width, 0)];
-}
-
-@end
-
-#pragma mark -
-
-@implementation MuAppDelegate
-
-- (BOOL) application: (UIApplication*)application didFinishLaunchingWithOptions: (NSDictionary*)launchOptions
-{
- NSString *filename;
-
- queue = dispatch_queue_create("com.artifex.mupdf.queue", NULL);
-
- // use at most 128M for resource cache
- ctx = fz_new_context(NULL, NULL, 128<<20);
-
- screenScale = [[UIScreen mainScreen] scale];
-
- library = [[MuLibraryController alloc] initWithStyle: UITableViewStylePlain];
-
- navigator = [[UINavigationController alloc] initWithRootViewController: library];
- [[navigator navigationBar] setTranslucent: YES];
- [[navigator toolbar] setTranslucent: YES];
- [navigator setDelegate: self];
-
- window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
- [window setBackgroundColor: [UIColor scrollViewTexturedBackgroundColor]];
- [window setRootViewController: navigator];
- [window makeKeyAndVisible];
-
- filename = [[NSUserDefaults standardUserDefaults] objectForKey: @"OpenDocumentKey"];
- if (filename)
- [library openDocument: filename];
-
- filename = [launchOptions objectForKey: UIApplicationLaunchOptionsURLKey];
- NSLog(@"urlkey = %@\n", filename);
-
- return YES;
-}
-
-- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
-{
- NSLog(@"openURL: %@\n", url);
- if ([url isFileURL]) {
- NSString *path = [url path];
- NSString *dir = [NSString stringWithFormat: @"%@/Documents/", NSHomeDirectory()];
- path = [path stringByReplacingOccurrencesOfString:@"/private" withString:@""];
- path = [path stringByReplacingOccurrencesOfString:dir withString:@""];
- NSLog(@"file relative path: %@\n", path);
- [library openDocument:path];
- return YES;
- }
- return NO;
-}
-
-- (void)applicationDidEnterBackground:(UIApplication *)application
-{
- printf("applicationDidEnterBackground!\n");
- [[NSUserDefaults standardUserDefaults] synchronize];
-}
-
-- (void)applicationWillEnterForeground:(UIApplication *)application
-{
- printf("applicationWillEnterForeground!\n");
-}
-
-- (void)applicationDidBecomeActive:(UIApplication *)application
-{
- printf("applicationDidBecomeActive!\n");
-}
-
-- (void)applicationWillTerminate:(UIApplication *)application
-{
- printf("applicationWillTerminate!\n");
- [[NSUserDefaults standardUserDefaults] synchronize];
-}
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
-{
- printf("applicationDidReceiveMemoryWarning\n");
-}
-
-- (void) dealloc
-{
- dispatch_release(queue);
- [library release];
- [navigator release];
- [window release];
- [super dealloc];
-}
-
-@end
-
-#pragma mark -
-
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];