summaryrefslogtreecommitdiff
path: root/platform/ios/Classes/MuPageViewNormal.m
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/Classes/MuPageViewNormal.m')
-rw-r--r--platform/ios/Classes/MuPageViewNormal.m466
1 files changed, 466 insertions, 0 deletions
diff --git a/platform/ios/Classes/MuPageViewNormal.m b/platform/ios/Classes/MuPageViewNormal.m
new file mode 100644
index 00000000..8cec8f02
--- /dev/null
+++ b/platform/ios/Classes/MuPageViewNormal.m
@@ -0,0 +1,466 @@
+//
+// 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 "MuPageViewNormal.h"
+
+@implementation MuPageViewNormal
+
+- (id) initWithFrame: (CGRect)frame document: (MuDocRef *)aDoc page: (int)aNumber
+{
+ self = [super initWithFrame: frame];
+ if (self) {
+ docRef = [aDoc retain];
+ doc = docRef->doc;
+ 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 = docRef->doc;
+ dispatch_async(queue, ^{
+ if (block_page)
+ fz_free_page(block_doc, block_page);
+ block_page = nil;
+ });
+ [docRef release];
+ [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]];
+}
+
+- (void) setScale:(float)scale {}
+
+@end