diff options
Diffstat (limited to 'ios')
-rw-r--r-- | ios/About.pdf | bin | 131735 -> 0 bytes | |||
-rw-r--r-- | ios/About.xps | bin | 0 -> 76673 bytes | |||
-rw-r--r-- | ios/Info.plist | 4 | ||||
-rw-r--r-- | ios/MuPDF.xcodeproj/project.pbxproj | 73 | ||||
-rw-r--r-- | ios/build_libs.sh | 10 | ||||
-rw-r--r-- | ios/document.c | 128 | ||||
-rw-r--r-- | ios/document.h | 6 | ||||
-rw-r--r-- | ios/main.m | 492 |
8 files changed, 572 insertions, 141 deletions
diff --git a/ios/About.pdf b/ios/About.pdf Binary files differdeleted file mode 100644 index 763c467e..00000000 --- a/ios/About.pdf +++ /dev/null diff --git a/ios/About.xps b/ios/About.xps Binary files differnew file mode 100644 index 00000000..3fcec260 --- /dev/null +++ b/ios/About.xps diff --git a/ios/Info.plist b/ios/Info.plist index 116bacee..85213b7c 100644 --- a/ios/Info.plist +++ b/ios/Info.plist @@ -11,7 +11,7 @@ <key>CFBundleIconFiles</key> <array/> <key>CFBundleIdentifier</key> - <string>com.artifex.${PRODUCT_NAME:rfc1034identifier}</string> + <string>com.artifex.mupdf</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> @@ -19,7 +19,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>0.9</string> + <string>0.9.1</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> diff --git a/ios/MuPDF.xcodeproj/project.pbxproj b/ios/MuPDF.xcodeproj/project.pbxproj index 61542780..73fe67c6 100644 --- a/ios/MuPDF.xcodeproj/project.pbxproj +++ b/ios/MuPDF.xcodeproj/project.pbxproj @@ -8,19 +8,12 @@ /* Begin PBXBuildFile section */ 9644E9A0146ACEC000E5B70A /* document.c in Sources */ = {isa = PBXBuildFile; fileRef = 9644E99E146ACEC000E5B70A /* document.c */; }; - 9683F619145F4F84000E1607 /* About.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 9683F618145F4F84000E1607 /* About.pdf */; }; + 9668C8D91476A30200D7BA52 /* About.xps in Resources */ = {isa = PBXBuildFile; fileRef = 9668C8D81476A30200D7BA52 /* About.xps */; }; 968F2E9C14539C880085264E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9B14539C880085264E /* UIKit.framework */; }; 968F2E9E14539C880085264E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9D14539C880085264E /* Foundation.framework */; }; 968F2EA014539C880085264E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9F14539C880085264E /* CoreGraphics.framework */; }; 968F2EB014539CDA0085264E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 968F2E9014539BEB0085264E /* main.m */; }; - 968F2EBB14539F350085264E /* libfitz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB314539F350085264E /* libfitz.a */; }; - 968F2EBC14539F350085264E /* libfreetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB414539F350085264E /* libfreetype.a */; }; - 968F2EBD14539F350085264E /* libjbig2dec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB514539F350085264E /* libjbig2dec.a */; }; - 968F2EBE14539F350085264E /* libjpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB614539F350085264E /* libjpeg.a */; }; - 968F2EBF14539F350085264E /* libmupdf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB714539F350085264E /* libmupdf.a */; }; - 968F2EC014539F350085264E /* libmuxps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB814539F350085264E /* libmuxps.a */; }; - 968F2EC114539F350085264E /* libopenjpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB914539F350085264E /* libopenjpeg.a */; }; - 968F2EC214539F350085264E /* libz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EBA14539F350085264E /* libz.a */; }; + 96A4739B147C1C3A003D757D /* libLibraries.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968461E214642DB00012AE09 /* libLibraries.a */; }; 96BD2B38145AC485001CEBC3 /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 96BD2B35145AC485001CEBC3 /* Icon-72.png */; }; 96BD2B39145AC485001CEBC3 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 96BD2B36145AC485001CEBC3 /* Icon.png */; }; 96F2341514603FBA004A8A22 /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 96F2341414603FBA004A8A22 /* Icon@2x.png */; }; @@ -39,7 +32,7 @@ /* Begin PBXFileReference section */ 9644E99E146ACEC000E5B70A /* document.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = document.c; sourceTree = "<group>"; }; 9644E99F146ACEC000E5B70A /* document.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = document.h; sourceTree = "<group>"; }; - 9683F618145F4F84000E1607 /* About.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = About.pdf; sourceTree = "<group>"; }; + 9668C8D81476A30200D7BA52 /* About.xps */ = {isa = PBXFileReference; lastKnownFileType = file; path = About.xps; sourceTree = "<group>"; }; 968461E214642DB00012AE09 /* libLibraries.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLibraries.a; sourceTree = BUILT_PRODUCTS_DIR; }; 968F2E8E14539BEB0085264E /* build_libs.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_libs.sh; sourceTree = "<group>"; }; 968F2E8F14539BEB0085264E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; @@ -48,14 +41,6 @@ 968F2E9B14539C880085264E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 968F2E9D14539C880085264E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 968F2E9F14539C880085264E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; - 968F2EB314539F350085264E /* libfitz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfitz.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB414539F350085264E /* libfreetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfreetype.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB514539F350085264E /* libjbig2dec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjbig2dec.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB614539F350085264E /* libjpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjpeg.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB714539F350085264E /* libmupdf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmupdf.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB814539F350085264E /* libmuxps.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmuxps.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB914539F350085264E /* libopenjpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libopenjpeg.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EBA14539F350085264E /* libz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libz.a; sourceTree = BUILT_PRODUCTS_DIR; }; 96BD2B35145AC485001CEBC3 /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72.png"; sourceTree = "<group>"; }; 96BD2B36145AC485001CEBC3 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; }; 96F2341414603FBA004A8A22 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; }; @@ -69,14 +54,7 @@ 968F2E9C14539C880085264E /* UIKit.framework in Frameworks */, 968F2E9E14539C880085264E /* Foundation.framework in Frameworks */, 968F2EA014539C880085264E /* CoreGraphics.framework in Frameworks */, - 968F2EBB14539F350085264E /* libfitz.a in Frameworks */, - 968F2EBC14539F350085264E /* libfreetype.a in Frameworks */, - 968F2EBD14539F350085264E /* libjbig2dec.a in Frameworks */, - 968F2EBE14539F350085264E /* libjpeg.a in Frameworks */, - 968F2EBF14539F350085264E /* libmupdf.a in Frameworks */, - 968F2EC014539F350085264E /* libmuxps.a in Frameworks */, - 968F2EC114539F350085264E /* libopenjpeg.a in Frameworks */, - 968F2EC214539F350085264E /* libz.a in Frameworks */, + 96A4739B147C1C3A003D757D /* libLibraries.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,7 +64,7 @@ 968F2E9214539BF10085264E /* Sources */ = { isa = PBXGroup; children = ( - 9683F618145F4F84000E1607 /* About.pdf */, + 9668C8D81476A30200D7BA52 /* About.xps */, 96BD2B36145AC485001CEBC3 /* Icon.png */, 96F2341414603FBA004A8A22 /* Icon@2x.png */, 96BD2B35145AC485001CEBC3 /* Icon-72.png */, @@ -118,26 +96,10 @@ name = Frameworks; sourceTree = "<group>"; }; - 968F2EB214539F230085264E /* Libraries */ = { - isa = PBXGroup; - children = ( - 968F2EB314539F350085264E /* libfitz.a */, - 968F2EB414539F350085264E /* libfreetype.a */, - 968F2EB514539F350085264E /* libjbig2dec.a */, - 968F2EB614539F350085264E /* libjpeg.a */, - 968F2EB714539F350085264E /* libmupdf.a */, - 968F2EB814539F350085264E /* libmuxps.a */, - 968F2EB914539F350085264E /* libopenjpeg.a */, - 968F2EBA14539F350085264E /* libz.a */, - ); - name = Libraries; - sourceTree = "<group>"; - }; 96A3B27414539BAD00D0A895 = { isa = PBXGroup; children = ( 968F2E9214539BF10085264E /* Sources */, - 968F2EB214539F230085264E /* Libraries */, 968F2E9A14539C880085264E /* Frameworks */, 968F2E9814539C880085264E /* Products */, ); @@ -212,8 +174,8 @@ files = ( 96BD2B38145AC485001CEBC3 /* Icon-72.png in Resources */, 96BD2B39145AC485001CEBC3 /* Icon.png in Resources */, - 9683F619145F4F84000E1607 /* About.pdf in Resources */, 96F2341514603FBA004A8A22 /* Icon@2x.png in Resources */, + 9668C8D91476A30200D7BA52 /* About.xps in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -228,14 +190,7 @@ inputPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/libmupdf.a", - "$(DERIVED_FILE_DIR)/libfitz.a", - "$(DERIVED_FILE_DIR)/libmuxps.a", - "$(DERIVED_FILE_DIR)/libfreetype.a", - "$(DERIVED_FILE_DIR)/libjbig2dec.a", - "$(DERIVED_FILE_DIR)/libjpeg.a", - "$(DERIVED_FILE_DIR)/libopenjpeg.a", - "$(DERIVED_FILE_DIR)/libz.a", + "$(DERIVED_FILE_DIR)/libLibraries.a", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -269,9 +224,9 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = Libraries; SDKROOT = iphoneos; + SKIP_INSTALL = YES; }; name = Debug; }; @@ -279,9 +234,9 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = Libraries; SDKROOT = iphoneos; + SKIP_INSTALL = YES; }; name = Release; }; @@ -290,7 +245,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -309,9 +264,10 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ..; INFOPLIST_FILE = Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -323,7 +279,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; COPY_PHASE_STRIP = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -335,10 +291,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ..; INFOPLIST_FILE = Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/ios/build_libs.sh b/ios/build_libs.sh index beea9cde..72c07ce0 100644 --- a/ios/build_libs.sh +++ b/ios/build_libs.sh @@ -24,10 +24,10 @@ export OUT=build/$build-$OS-$ARCHS echo Building libraries for $ARCHS. make -C .. libs || exit 1 -echo Copying files into $BUILT_PRODUCTS_DIR. - -mkdir -p "$BUILT_PRODUCTS_DIR" -cp ../$OUT/lib*.a $BUILT_PRODUCTS_DIR -ranlib $BUILT_PRODUCTS_DIR/lib*.a +echo Assembling final library in $TARGET_BUILD_DIR/. +mkdir -p "$TARGET_BUILD_DIR" +rm -f $TARGET_BUILD_DIR/libLibraries.a +ar cru $TARGET_BUILD_DIR/libLibraries.a ../$OUT/*.o +ranlib $TARGET_BUILD_DIR/libLibraries.a echo Done. diff --git a/ios/document.c b/ios/document.c index 6d30a0bc..ffb92519 100644 --- a/ios/document.c +++ b/ios/document.c @@ -3,6 +3,8 @@ #include "xps/muxps.h" #include "document.h" +#include <ctype.h> // for tolower() + struct document * open_document(char *filename) { @@ -12,7 +14,7 @@ open_document(char *filename) struct document *doc = fz_malloc(sizeof *doc); memset(doc, 0, sizeof *doc); doc->number = -1; - error = pdf_open_xref(&doc->pdf, filename, ""); + error = pdf_open_xref(&doc->pdf, filename, NULL); if (error) { fz_free(doc); fz_rethrow(error, "cannot open pdf document"); @@ -43,6 +45,24 @@ open_document(char *filename) } } +int +needs_password(struct document *doc) +{ + if (doc->pdf) { + return pdf_needs_password(doc->pdf); + } + return 0; +} + +int +authenticate_password(struct document *doc, char *password) +{ + if (doc->pdf) { + return pdf_authenticate_password(doc->pdf, password); + } + return 1; +} + fz_outline * load_outline(struct document *doc) { @@ -78,7 +98,6 @@ load_page(struct document *doc, int number) pdf_free_page(doc->pdf_page); } doc->pdf_page = NULL; -printf("load pdf page %d\n", number); error = pdf_load_page(&doc->pdf_page, doc->pdf, number); if (error) fz_catch(error, "cannot load page %d", number); @@ -87,7 +106,6 @@ printf("load pdf page %d\n", number); if (doc->xps_page) xps_free_page(doc->xps, doc->xps_page); doc->xps_page = NULL; -printf("load xps page %d\n", number); error = xps_load_page(&doc->xps_page, doc->xps, number); if (error) fz_catch(error, "cannot load page %d", number); @@ -137,6 +155,110 @@ draw_page(struct document *doc, int number, fz_device *dev, fz_matrix ctm) fz_flush_warnings(); } +static int +charat(fz_text_span *span, int idx) +{ + int ofs = 0; + while (span) { + if (idx < ofs + span->len) + return span->text[idx - ofs].c; + if (span->eol) { + if (idx == ofs + span->len) + return ' '; + ofs ++; + } + ofs += span->len; + span = span->next; + } + return 0; +} + +static fz_bbox +bboxat(fz_text_span *span, int idx) +{ + int ofs = 0; + while (span) { + if (idx < ofs + span->len) + return span->text[idx - ofs].bbox; + if (span->eol) { + if (idx == ofs + span->len) + return fz_empty_bbox; + ofs ++; + } + ofs += span->len; + span = span->next; + } + return fz_empty_bbox; +} + +static int +textlen(fz_text_span *span) +{ + int len = 0; + while (span) { + len += span->len; + if (span->eol) + len ++; + span = span->next; + } + return len; +} + +static int +match(fz_text_span *span, char *s, int n) +{ + int start = n, c; + while (*s) { + s += chartorune(&c, s); + if (c == ' ' && charat(span, n) == ' ') { + while (charat(span, n) == ' ') + n++; + } else { + if (tolower(c) != tolower(charat(span, n))) + return 0; + n++; + } + } + return n - start; +} + +int +search_page(struct document *doc, int number, char *needle) +{ + int pos, len, i, n; + + if (strlen(needle) == 0) + return 0; + + fz_text_span *text = fz_new_text_span(); + fz_device *dev = fz_new_text_device(text); + draw_page(doc, number, dev, fz_identity); + fz_free_device(dev); + + doc->hit_count = 0; + + len = textlen(text); + for (pos = 0; pos < len; pos++) { + n = match(text, needle, pos); + if (n) { + for (i = 0; i < n; i++) { + fz_bbox r = bboxat(text, pos + i); + if (!fz_is_empty_bbox(r) && doc->hit_count < nelem(doc->hit_bbox)) + doc->hit_bbox[doc->hit_count++] = r; + } + } + } + + fz_free_text_span(text); + return doc->hit_count; +} + +fz_bbox +search_result_bbox(struct document *doc, int i) +{ + return doc->hit_bbox[i]; +} + void close_document(struct document *doc) { diff --git a/ios/document.h b/ios/document.h index 1f59ccdd..797b1e8f 100644 --- a/ios/document.h +++ b/ios/document.h @@ -20,13 +20,19 @@ struct document int number; pdf_page *pdf_page; xps_page *xps_page; + fz_bbox hit_bbox[500]; + int hit_count; }; struct document *open_document(char *filename); +int needs_password(struct document *doc); +int authenticate_password(struct document *doc, char *password); fz_outline *load_outline(struct document *doc); int count_pages(struct document *doc); void measure_page(struct document *doc, int number, float *w, float *h); void draw_page(struct document *doc, int number, fz_device *dev, fz_matrix ctm); +int search_page(struct document *doc, int number, char *needle); +fz_bbox search_result_bbox(struct document *doc, int i); void close_document(struct document *doc); #endif @@ -12,6 +12,8 @@ #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 fz_glyph_cache *glyphcache = NULL; @@ -21,8 +23,13 @@ static float screenScale = 1; { NSArray *files; NSTimer *timer; + struct document *_doc; // temporaries for juggling password dialog + NSString *_filename; } - (void) openDocument: (NSString*)filename; +- (void) askForPassword: (NSString*)prompt; +- (void) onPasswordOkay; +- (void) onPasswordCancel; - (void) reload; @end @@ -35,6 +42,16 @@ static float screenScale = 1; - (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages; @end +@interface MuHitView : UIView +{ + CGSize pageSize; + int hitCount; + CGRect hitRects[500]; +} +- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc; +- (void) setPageSize: (CGSize)s; +@end + @interface MuPageView : UIScrollView <UIScrollViewDelegate> { struct document *doc; @@ -42,6 +59,8 @@ static float screenScale = 1; UIActivityIndicatorView *loadingView; UIImageView *imageView; UIImageView *tileView; + MuHitView *hitView; + CGSize pageSize; CGRect tileFrame; float tileScale; BOOL cancel; @@ -53,29 +72,37 @@ static float screenScale = 1; - (void) loadTile; - (void) willRotate; - (void) resetZoomAnimated: (BOOL)animated; +- (void) showSearchResults: (int)count; +- (void) clearSearchResults; - (int) number; @end -@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UIGestureRecognizerDelegate> +@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UISearchBarDelegate> { struct document *doc; NSString *key; - NSMutableSet *visiblePages; - NSMutableSet *recycledPages; MuOutlineController *outline; UIScrollView *canvas; UILabel *indicator; UISlider *slider; - UIBarButtonItem *wrapper; // for slider + UISearchBar *searchBar; + UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton; + UIBarButtonItem *sliderWrapper; + int searchPage; + int cancelSearch; int width; // current screen size int height; int current; // currently visible page int scroll_animating; // stop view updates during scrolling animations } -- (id) initWithFile: (NSString*)filename; +- (id) initWithFilename: (NSString*)nsfilename document: (struct 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; @@ -147,15 +174,16 @@ static void releasePixmap(void *info, const void *data, size_t size) static UIImage *newImageWithPixmap(fz_pixmap *pix) { CGDataProviderRef cgdata = CGDataProviderCreateWithData(pix, pix->samples, pix->w * 4 * pix->h, releasePixmap); + CGColorSpaceRef cgcolor = CGColorSpaceCreateDeviceRGB(); CGImageRef cgimage = CGImageCreate(pix->w, pix->h, 8, 32, 4 * pix->w, - CGColorSpaceCreateDeviceRGB(), - kCGBitmapByteOrderDefault, + cgcolor, kCGBitmapByteOrderDefault, cgdata, NULL, NO, kCGRenderingIntentDefault); UIImage *image = [[UIImage alloc] initWithCGImage: cgimage scale: screenScale orientation: UIImageOrientationUp]; CGDataProviderRelease(cgdata); + CGColorSpaceRelease(cgcolor); CGImageRelease(cgimage); return image; } @@ -170,6 +198,13 @@ static CGSize fitPageToScreen(CGSize page, CGSize screen) return CGSizeMake(hscale, vscale); } +static CGSize measurePage(struct document *doc, int number) +{ + CGSize pageSize; + measure_page(doc, number, &pageSize.width, &pageSize.height); + return pageSize; +} + static UIImage *renderPage(struct document *doc, int number, CGSize screenSize) { CGSize pageSize; @@ -317,19 +352,81 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, { int row = [indexPath row]; if (row == 0) - [self openDocument: @"../MuPDF.app/About.pdf"]; + [self openDocument: @"../MuPDF.app/About.xps"]; else [self openDocument: [files objectAtIndex: row - 1]]; } -- (void) openDocument: (NSString*)filename +- (void) openDocument: (NSString*)nsfilename { - MuDocumentController *document = [[MuDocumentController alloc] initWithFile: filename]; + 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 = open_document(filename); + if (!_doc) { + showAlert(@"Cannot open document"); + return; + } + + if (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 (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"); + close_document(_doc); + _doc = NULL; } @end @@ -404,6 +501,53 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, #pragma mark - +@implementation MuHitView + +- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc +{ + self = [super initWithFrame: CGRectMake(0,0,100,100)]; + if (self) { + [self setOpaque: NO]; + + pageSize = CGSizeMake(100,100); + + for (int i = 0; i < n && i < nelem(hitRects); i++) { + fz_bbox 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; +} + +- (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); + + [[UIColor colorWithRed: 0.3 green: 0.3 blue: 1 alpha: 0.5] 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); + } +} + +@end + @implementation MuPageView - (id) initWithFrame: (CGRect)frame document: (struct document*)aDoc page: (int)aNumber @@ -444,6 +588,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, __block id block_self = self; // don't auto-retain self! dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; }); } else { + [hitView release]; [tileView release]; [loadingView release]; [imageView release]; @@ -456,6 +601,30 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, return number; } +- (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 @@ -485,8 +654,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, dispatch_async(queue, ^{ if (!cancel) { printf("render page %d\n", number); + CGSize size = measurePage(doc, number); UIImage *image = renderPage(doc, number, self.bounds.size); dispatch_async(dispatch_get_main_queue(), ^{ + pageSize = size; [self displayImage: image]; [image release]; }); @@ -504,10 +675,15 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, 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]; } @@ -530,7 +706,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, dispatch_async(queue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size); - if (fabs(scale.width - 1) > 0.1) + if (fabs(scale.width - 1) > 0.01) [self loadPage]; }); }); @@ -542,6 +718,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self layoutIfNeeded]; } + } - (void) willRotate @@ -577,6 +754,9 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, loadingView.frame = frameToCenter; else imageView.frame = frameToCenter; + + if (hitView && imageView) + [hitView setFrame: [imageView frame]]; } - (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView @@ -586,7 +766,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) loadTile { - CGSize pageSize = self.bounds.size; + CGSize screenSize = self.bounds.size; tileFrame.origin = self.contentOffset; tileFrame.size = self.bounds.size; @@ -616,7 +796,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, } printf("render tile\n"); - UIImage *image = renderTile(doc, number, pageSize, viewFrame, scale); + UIImage *image = renderTile(doc, number, screenSize, viewFrame, scale); dispatch_async(dispatch_get_main_queue(), ^{ isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale; @@ -632,6 +812,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, tileView = [[UIImageView alloc] initWithFrame: frame]; [tileView setImage: image]; [self addSubview: tileView]; + if (hitView) + [self bringSubviewToFront: hitView]; } else { printf("discard tile\n"); } @@ -666,53 +848,40 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self loadTile]; } +- (void) scrollViewDidZoom: (UIScrollView*)scrollView +{ + if (hitView && imageView) + [hitView setFrame: [imageView frame]]; +} + @end #pragma mark - @implementation MuDocumentController -- (id) initWithFile: (NSString*)nsfilename +- (id) initWithFilename: (NSString*)filename document: (struct document *)aDoc { - char filename[PATH_MAX]; - self = [super init]; if (!self) return nil; - key = [nsfilename retain]; + key = [filename retain]; + doc = aDoc; dispatch_sync(queue, ^{}); - strcpy(filename, [NSHomeDirectory() UTF8String]); - strcat(filename, "/Documents/"); - strcat(filename, [nsfilename UTF8String]); - - printf("open document '%s'\n", filename); - - doc = open_document(filename); - if (!doc) { - showAlert(@"Cannot open document"); - [self release]; - return nil; - } - - NSMutableArray *titles = [[NSMutableArray alloc] init]; - NSMutableArray *pages = [[NSMutableArray alloc] init]; fz_outline *root = 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(root); } - if ([titles count]) { - outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages]; - [[self navigationItem] setRightBarButtonItem: - [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks - target:self action:@selector(onShowOutline:)]]; - } - [titles release]; - [pages release]; return self; } @@ -729,9 +898,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [view setAutoresizesSubviews: YES]; - visiblePages = [[NSMutableSet alloc] init]; - recycledPages = [[NSMutableSet alloc] init]; - canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)]; [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [canvas setPagingEnabled: YES]; @@ -752,31 +918,56 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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: count_pages(doc) - 1]; [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged]; - [view addSubview: canvas]; - [view addSubview: indicator]; + sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; - wrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; - [self setToolbarItems: [NSArray arrayWithObjects: wrapper, nil]]; + [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:)]; + } + 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, outlineButton, nil]]; + + // TODO: add activityindicator to search bar [self setView: view]; [view release]; } -- (void) viewDidUnload -{ - [visiblePages release]; visiblePages = nil; - [recycledPages release]; recycledPages = nil; - [indicator release]; indicator = nil; - [slider release]; slider = nil; - [wrapper release]; wrapper = nil; - [canvas release]; canvas = nil; -} - - (void) dealloc { if (doc) { @@ -786,6 +977,18 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, 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]; @@ -807,7 +1010,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [canvas setContentSize: CGSizeMake(count_pages(doc) * width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; - [wrapper setWidth: width - GAP - 24]; + [sliderWrapper setWidth: SLIDER_W]; + [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; [[self navigationController] setToolbarHidden: NO animated: animated]; } @@ -844,6 +1048,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) hideNavigationBar { if (![[self navigationController] isNavigationBarHidden]) { + [searchBar resignFirstResponder]; + [UIView beginAnimations: @"MuNavBar" context: NULL]; [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)]; @@ -868,6 +1074,139 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] pushViewController: outline animated: YES]; } +- (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, 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 < count_pages(doc); i += dir) { + int n = search_page(doc, i, needle); + 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]; @@ -918,22 +1257,26 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, count_pages(doc)]]; [slider setValue: current]; - // swap the page views in and out + // swap the distant page views out - for (MuPageView *view in visiblePages) { + 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) { - [recycledPages addObject: view]; - [view removeFromSuperview]; - } + if ([view number] < current - 2 || [view number] > current + 2) + [invisiblePages addObject: view]; } - [visiblePages minusSet: recycledPages]; - [recycledPages removeAllObjects]; // don't bother recycling them... + 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 @@ -946,7 +1289,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, found = 1; if (!found) { MuPageView *view = [[MuPageView alloc] initWithFrame: CGRectMake(number * width, 0, width-GAP, height) document: doc page: number]; - [visiblePages addObject: view]; [canvas addSubview: view]; [view release]; } @@ -958,6 +1300,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, number = 0; if (number >= count_pages(doc)) number = 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 @@ -973,7 +1317,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)]; - for (MuPageView *view in visiblePages) + for (MuPageView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; @@ -982,7 +1326,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [UIView commitAnimations]; } else { - for (MuPageView *view in visiblePages) + for (MuPageView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; } @@ -1008,20 +1352,22 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, width = size.width; height = size.height; - [wrapper setWidth: width - GAP - 24]; + [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(count_pages(doc) * max_width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; - for (MuPageView *view in visiblePages) { + 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 visiblePages) { + for (MuPageView *view in [canvas subviews]) { if ([view number] != current) { [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)]; [view willRotate]; |