static UIImage *newImageWithPixmap(fz_pixmap *pix, CGDataProviderRef cgdata)
{
CGImageRef cgimage = CreateCGImageWithPixmap(pix, cgdata);
UIImage *image = [[UIImage alloc] initWithCGImage: cgimage scale: screenScale orientation: UIImageOrientationUp];
CGImageRelease(cgimage);
return image;
}
static NSArray *enumerateWidgetRects(fz_document *doc, fz_page *page)
{
pdf_document *idoc = pdf_specifics(ctx, doc);
pdf_widget *widget;
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:10];
if (!idoc)
return nil;
for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)page); widget; widget = pdf_next_widget(ctx, widget))
{
fz_rect rect;
pdf_bound_widget(ctx, widget, &rect);
[arr addObject:[NSValue valueWithCGRect:CGRectMake(
rect.x0,
rect.y0,
rect.x1-rect.x0,
rect.y1-rect.y0)]];
}
return [arr retain];
}
static NSArray *enumerateAnnotations(fz_document *doc, fz_page *page)
{
fz_annot *annot;
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:10];
for (annot = fz_first_annot(ctx, page); annot; annot = fz_next_annot(ctx, page, annot))
[arr addObject:[MuAnnotation annotFromAnnot:annot forPage:page]];
return [arr retain];
}
static NSArray *enumerateWords(fz_document *doc, fz_page *page)
{
fz_text_sheet *sheet = NULL;
fz_text_page *text = NULL;
fz_device *dev = NULL;
NSMutableArray *lns = [NSMutableArray array];
NSMutableArray *wds;
MuWord *word;
if (!lns)
return NULL;
fz_var(sheet);
fz_var(text);
fz_var(dev);
fz_try(ctx);
{
int b, l, c;
sheet = fz_new_text_sheet(ctx);
text = fz_new_text_page(ctx);
dev = fz_new_text_device(ctx, sheet, text);
fz_run_page(ctx, page, dev, &fz_identity, NULL);
fz_drop_device(ctx, dev);
dev = NULL;
for (b = 0; b < text->len; b++)
{
fz_text_block *block;
if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT)
continue;
block = text->blocks[b].u.text;
for (l = 0; l < block->len; l++)
{
fz_text_line *line = &block->lines[l];
fz_text_span *span;
wds = [NSMutableArray array];
if (!wds)
fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word array");
word = [MuWord word];
if (!word)
fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word");
for (span = line->first_span; span; span = span->next)
{
for (c = 0; c < span->len; c++)
{
fz_text_char *ch = &span->text[c];
fz_rect bbox;
CGRect rect;
fz_text_char_bbox(ctx, &bbox, span, c);
rect = CGRectMake(bbox.x0, bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
if (ch->c != ' ')
{
[word appendChar:ch->c withRect:rect];
}
else if (word.string.length > 0)
{
[wds addObject:word];
word = [MuWord word];
if (!word)
fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word");
}
}
}
if (word.string.length > 0)
[wds addObject:word];
if ([wds count] > 0)
[lns addObject:wds];
}
}
}
fz_always(ctx);
{
fz_drop_text_page(ctx, text);
fz_drop_text_sheet(ctx, sheet);
fz_drop_device(ctx, dev);
}
fz_catch(ctx)
{
lns = NULL;
}
return [lns retain];
}
static void addMarkupAnnot(fz_document *doc, fz_page *page, int type, NSArray *rects)
{
pdf_document *idoc;
fz_point *quadpts = NULL;
float color[3];
float alpha;
float line_height;
float line_thickness;
idoc = pdf_specifics(ctx, doc);
if (!idoc)
return;
switch (type)
{
case FZ_ANNOT_HIGHLIGHT:
color[0] = 1.0;
color[1] = 1.0;
color[2] = 0.0;
alpha = 0.5;
line_thickness = 1.0;
line_height = 0.5;
break;
case FZ_ANNOT_UNDERLINE:
color[0] = 0.0;
color[1] = 0.0;
color[2] = 1.0;
alpha = 1.0;
line_thickness = LINE_THICKNESS;
line_height = UNDERLINE_HEIGHT;
break;
case FZ_ANNOT_STRIKEOUT:
color[0] = 1.0;
color[1] = 0.0;
color[2] = 0.0;
alpha = 1.0;
line_thickness = LINE_THICKNESS;
line_height = STRIKE_HEIGHT;
break;
default:
return;
}
fz_var(quadpts);
fz_try(ctx)
{
int i;
pdf_annot *annot;
quadpts = fz_malloc_array(ctx, (int)rects.count * 4, sizeof(fz_point));
for (i = 0; i < rects.count; i++)
{
CGRect rect = [[rects objectAtIndex:i] CGRectValue];
float top = rect.origin.y;
float bot = top + rect.size.height;
float left = rect.origin.x;
float right = left + rect.size.width;
quadpts[i*4].x = left;
quadpts[i*4].y = bot;
quadpts[i*4+1].x = right;
quadpts[i*4+1].y = bot;
quadpts[i*4+2].x = right;
quadpts[i*4+2].y = top;
quadpts[i*4+3].x = left;
quadpts[i*4+3].y = top;
}
annot = pdf_create_annot(ctx, idoc, (pdf_page *)page, type);
pdf_set_markup_annot_quadpoints(ctx, idoc, annot, quadpts, (int)rects.count*4);
pdf_set_markup_appearance(ctx, idoc, annot, color, alpha, line_thickness, line_height);
}
fz_always(ctx)
{
fz_free(ctx, quadpts);
}
fz_catch(ctx)
{
printf("Annotation creation failed\n");
}
}
static void addInkAnnot(fz_document *doc, fz_page *page, NSArray *curves)
{
pdf_document *idoc;
fz_point *pts = NULL;
int *counts = NULL;
int total;
float color[3] = {1.0, 0.0, 0.0};
idoc = pdf_specifics(ctx, doc);
if (!idoc)
return;
fz_var(pts);
fz_var(counts);
fz_try(ctx)
{
int i, j, k, n;
pdf_annot *annot;
n = (int)curves.count;
counts = fz_malloc_array(ctx, n, sizeof(int));
total = 0;
for (i = 0; i < n; i++)
{
NSArray *curve = [curves objectAtIndex:i];
counts[i] = (int)curve.count;
total += (int)curve.count;
}
pts = fz_malloc_array(ctx, total, sizeof(fz_point));
k = 0;
for (i = 0; i < n; i++)
{
NSArray *curve = [curves objectAtIndex:i];
int count = counts[i];
for (j = 0; j < count; j++)
{
CGPoint pt = [[curve objectAtIndex:j] CGPointValue];
pts[k].x = pt.x;
pts[k].y = pt.y;
k++;
}
}
annot = pdf_create_annot(ctx, idoc, (pdf_page *)page, FZ_ANNOT_INK);
pdf_set_ink_annot_list(ctx, idoc, annot, pts, counts, n, color, INK_THICKNESS);
}
fz_always(ctx)
{
fz_free(ctx, pts);
fz_free(ctx, counts);
}
fz_catch(ctx)
{
printf("Annotation creation failed\n");
}
}
static void deleteAnnotation(fz_document *doc, fz_page *page, int index)
{
pdf_document *idoc = pdf_specifics(ctx, doc);
if (!idoc)
return;
fz_try(ctx)
{
int i;
fz_annot *annot = fz_first_annot(ctx, page);
for (i = 0; i < index && annot; i++)
annot = fz_next_annot(ctx, page, annot);
if (annot)
pdf_delete_annot(ctx, idoc, (pdf_page *)page, (pdf_annot *)annot);
}
fz_catch(ctx)
{
printf("Annotation deletion failed\n");
}
}
static int setFocussedWidgetText(fz_document *doc, fz_page *page, const char *text)
{
int accepted = 0;
fz_var(accepted);
fz_try(ctx)
{
pdf_document *idoc = pdf_specifics(ctx, doc);
if (idoc)
{
pdf_widget *focus = pdf_focused_widget(ctx, idoc);
if (focus)
{
accepted = pdf_text_widget_set_text(ctx, idoc, focus, (char *)text);
}
}
}
fz_catch(ctx)
{
accepted = 0;
}
return accepted;
}
static int setFocussedWidgetChoice(fz_document *doc, fz_page *page, const char *text)
{
int accepted = 0;
fz_var(accepted);
fz_try(ctx)
{
pdf_document *idoc = pdf_specifics(ctx, doc);
if (idoc)
{
pdf_widget *focus = pdf_focused_widget(ctx, idoc);
if (focus)
{
pdf_choice_widget_set_value(ctx, idoc, focus, 1, (char **)&text);
accepted = 1;
}
}
}
fz_catch(ctx)
{
accepted = 0;
}
return accepted;
}
static fz_display_list *create_page_list(fz_document *doc, fz_page *page)
{
fz_display_list *list = NULL;
fz_device *dev = NULL;
fz_var(dev);
fz_try(ctx)
{
list = fz_new_display_list(ctx);
dev = fz_new_list_device(ctx, list);
fz_run_page_contents(ctx, page, dev, &fz_identity, NULL);
}
fz_always(ctx)
{
fz_drop_device(ctx, dev);
}
fz_catch(ctx)
{
return NULL;
}
return list;
}
static fz_display_list *create_annot_list(fz_document *doc, fz_page *page)
{
fz_display_list *list = NULL;
fz_device *dev = NULL;
fz_var(dev);
fz_try(ctx)
{
fz_annot *annot;
pdf_document *idoc = pdf_specifics(ctx, doc);
if (idoc)
pdf_update_page(ctx, idoc, (pdf_page *)page);
list = fz_new_display_list(ctx);
dev = fz_new_list_device(ctx, list);
for (annot = fz_first_annot(ctx, page); annot; annot = fz_next_annot(ctx, page, annot))
fz_run_annot(ctx, page, annot, dev, &fz_identity, NULL);
}
fz_always(ctx)
{
fz_drop_device(ctx, dev);
}
fz_catch(ctx)
{
return NULL;
}
return list;
}
static fz_pixmap *renderPixmap(fz_document *doc, fz_display_list *page_list, fz_display_list *annot_list, CGSize pageSize, CGSize screenSize, CGRect tileRect, float zoom)
{
fz_irect bbox;
fz_rect rect;
fz_matrix ctm;
fz_device *dev = NULL;
fz_pixmap *pix = NULL;
CGSize scale;
screenSize.width *= screenScale;
screenSize.height *= screenScale;
tileRect.origin.x *= screenScale;
tileRect.origin.y *= screenScale;
tileRect.size.width *= screenScale;
tileRect.size.height *= screenScale;
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;
fz_rect_from_irect(&rect, &bbox);
fz_var(dev);
fz_var(pix);
fz_try(ctx)
{
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_display_list(ctx, page_list, dev, &ctm, &rect, NULL);
fz_run_display_list(ctx, annot_list, dev, &ctm, &rect, NULL);
}
fz_always(ctx)
{
fz_drop_device(ctx, dev);
}
fz_catch(ctx)
{
fz_drop_pixmap(ctx, pix);
return NULL;
}
return pix;
}
typedef struct rect_list_s rect_list;
struct rect_list_s
{
fz_rect rect;
rect_list *next;
};
static void drop_list(rect_list *list)
{
while (list)
{
rect_list *n = list->next;
fz_free(ctx, list);
list = n;
}
}
static rect_list *updatePage(fz_document *doc, fz_page *page)
{
rect_list *list = NULL;
fz_var(list);
fz_try(ctx)
{
pdf_document *idoc = pdf_specifics(ctx, doc);
if (idoc)
{
fz_annot *annot;
pdf_update_page(ctx, idoc, (pdf_page *)page);
while ((annot = (fz_annot *)pdf_poll_changed_annot(ctx, idoc, (pdf_page *)page)) != NULL)
{
rect_list *node = fz_malloc_struct(ctx, rect_list);
fz_bound_annot(ctx, page, annot, &node->rect);
node->next = list;
list = node;
}
}
}
fz_catch(ctx)
{
drop_list(list);
list = NULL;
}
return list;
}
static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_display_list *annot_list, fz_pixmap *pixmap, rect_list *rlist, CGSize pageSize, CGSize screenSize, CGRect tileRect, float zoom)
{
fz_irect bbox;
fz_rect rect;
fz_matrix ctm;
fz_device *dev = NULL;
CGSize scale;
screenSize.width *= screenScale;
screenSize.height *= screenScale;
tileRect.origin.x *= screenScale;
tileRect.origin.y *= screenScale;
tileRect.size.width *= screenScale;
tileRect.size.height *= screenScale;
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;
fz_rect_from_irect(&rect, &bbox);
fz_var(dev);
fz_try(ctx)
{
while (rlist)
{
fz_irect abox;
fz_rect arect = rlist->rect;
fz_transform_rect(&arect, &ctm);
fz_intersect_rect(&arect, &rect);
fz_round_rect(&abox, &arect);
if (!fz_is_empty_irect(&abox))
{
fz_clear_pixmap_rect_with_value(ctx, pixmap, 255, &abox);
dev = fz_new_draw_device_with_bbox(ctx, pixmap, &abox);
fz_run_display_list(ctx, page_list, dev, &ctm, &arect, NULL);
fz_run_display_list(ctx, annot_list, dev, &ctm, &arect, NULL);
fz_drop_device(ctx, dev);
dev = NULL;
}
rlist = rlist->next;
}
}
fz_always(ctx)
{
fz_drop_device(ctx, dev);
}
fz_catch(ctx)
{
}
}
@implementation MuPageViewNormal
{
MuDocRef *docRef;
fz_document *doc;
fz_page *page;
fz_display_list *page_list;
fz_display_list *annot_list;
int number;
UIActivityIndicatorView *loadingView;
fz_pixmap *image_pix;
CGDataProviderRef imageData;
UIImageView *imageView;
fz_pixmap *tile_pix;
CGDataProviderRef tileData;
UIImageView *tileView;
MuHitView *hitView;
MuHitView *linkView;
MuTextSelectView *textSelectView;
MuInkView *inkView;
MuAnnotSelectView *annotSelectView;
NSArray *widgetRects;
NSArray *annotations;
int selectedAnnotationIndex;
CGSize pageSize;
CGRect tileFrame;
float tileScale;
BOOL cancel;
id<MuDialogCreator> dialogCreator;
id<MuUpdater> updater;
}
- (void) ensurePageLoaded
{
if (page)
return;
fz_try(ctx)
{
fz_rect bounds;
page = fz_load_page(ctx, doc, number);
fz_bound_page(ctx, page, &bounds);
pageSize.width = bounds.x1 - bounds.x0;
pageSize.height = bounds.y1 - bounds.y0;
}
fz_catch(ctx)
{
return;
}
}
- (void) ensureDisplaylists
{
[self ensurePageLoaded];
if (!page)
return;
if (!page_list)
page_list = create_page_list(doc, page);
if (!annot_list)
annot_list = create_annot_list(doc, page);
}
-(id) initWithFrame:(CGRect)frame dialogCreator:(id<MuDialogCreator>)dia updater:(id<MuUpdater>)upd document:(MuDocRef *)aDoc page:(int)aNumber
{
self = [super initWithFrame: frame];
if (self) {
docRef = [aDoc retain];
doc = docRef->doc;
number = aNumber;
cancel = NO;
dialogCreator = dia;
updater = upd;
selectedAnnotationIndex = -1;
[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 (![NSThread isMainThread]) {
__block id block_self = self; // don't auto-retain self!
dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; });
} else {
__block fz_display_list *block_page_list = page_list;
__block fz_display_list *block_annot_list = annot_list;
__block fz_page *block_page = page;
// __block fz_document *block_doc = docRef->doc;
__block CGDataProviderRef block_tileData = tileData;
__block CGDataProviderRef block_imageData = imageData;
dispatch_async(queue, ^{
if (block_page_list)
fz_drop_display_list(ctx, block_page_list);
if (block_annot_list)
fz_drop_display_list(ctx, block_annot_list);
if (block_page)
fz_drop_page(ctx, block_page);
block_page = nil;
CGDataProviderRelease(block_tileData);
CGDataProviderRelease(block_imageData);
});
[docRef release];
[widgetRects release];
[linkView release];
[hitView release];
[textSelectView release];
[inkView release];
[annotSelectView release];
[tileView release];
[loadingView release];
[imageView release];
[super dealloc];
}
}
- (int) number
{
return number;
}
- (void) showLinks
{
if (!linkView) {
dispatch_async(queue, ^{
[self ensurePageLoaded];
fz_link *links = fz_load_links(ctx, 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) textSelectModeOn
{
dispatch_async(queue, ^{
[self ensurePageLoaded];
NSArray *words = enumerateWords(doc, page);
dispatch_sync(dispatch_get_main_queue(), ^{
textSelectView = [[MuTextSelectView alloc] initWithWords:words pageSize:pageSize];
[words release];
if (imageView)
[textSelectView setFrame:[imageView frame]];
[self addSubview:textSelectView];
});
});
}
- (void) inkModeOn
{
inkView = [[MuInkView alloc] initWithPageSize:pageSize];
if (imageView)
[inkView setFrame:[imageView frame]];
[self addSubview:inkView];
}
- (void) textSelectModeOff
{
[textSelectView removeFromSuperview];
[textSelectView release];
textSelectView = nil;
}
- (void) inkModeOff
{
[inkView removeFromSuperview];
[inkView release];
inkView = nil;
}
-(void) saveSelectionAsMarkup:(int)type
{
NSArray *rects = [textSelectView selectionRects];
if (rects.count == 0)
return;
[rects retain];
dispatch_async(queue, ^{
addMarkupAnnot(doc, page, type, rects);
[rects release];
dispatch_async(dispatch_get_main_queue(), ^{
[self update];
});
[self loadAnnotations];
});
[self textSelectModeOff];
}
-(void) saveInk
{
NSArray *curves = inkView.curves;
if (curves.count == 0)
return;
[curves retain];
dispatch_async(queue, ^{
addInkAnnot(doc, page, curves);
[curves release];
dispatch_async(dispatch_get_main_queue(), ^{
[self update];
});
[self loadAnnotations];
});
[self inkModeOff];
}
-(void) selectAnnotation:(int)i
{
selectedAnnotationIndex = i;
[annotSelectView removeFromSuperview];
[annotSelectView release];
annotSelectView = [[MuAnnotSelectView alloc] initWithAnnot:[annotations objectAtIndex:i] pageSize:pageSize];
[self addSubview:annotSelectView];
}
-(void) deselectAnnotation
{
selectedAnnotationIndex = -1;
[annotSelectView removeFromSuperview];
[annotSelectView release];
annotSelectView = nil;
}
-(void) deleteSelectedAnnotation
{
int index = selectedAnnotationIndex;
if (index >= 0)
{
dispatch_async(queue, ^{
deleteAnnotation(doc, page, index);
dispatch_async(dispatch_get_main_queue(), ^{
[self update];
});
[self loadAnnotations];
});
}
[self deselectAnnotation];
}
- (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) loadAnnotations
{
if (number < 0 || number >= fz_count_pages(ctx, doc))
return;
NSArray *annots = enumerateAnnotations(doc, page);
dispatch_async(dispatch_get_main_queue(), ^{
[annotations release];
annotations = annots;
});
}
- (void) loadPage
{
if (number < 0 || number >= fz_count_pages(ctx, doc))
return;
dispatch_async(queue, ^{
if (!cancel) {
printf("render page %d\n", number);
[self ensureDisplaylists];
CGSize scale = fitPageToScreen(pageSize, self.bounds.size);
CGRect rect = (CGRect){{0.0, 0.0},{pageSize.width * scale.width, pageSize.height * scale.height}};
image_pix = renderPixmap(doc, page_list, annot_list, pageSize, self.bounds.size, rect, 1.0);
CGDataProviderRelease(imageData);
imageData = CreateWrappedPixmap(image_pix);
UIImage *image = newImageWithPixmap(image_pix, imageData);
widgetRects = enumerateWidgetRects(doc, page);
[self loadAnnotations];
dispatch_async(dispatch_get_main_queue(), ^{
[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];
if (textSelectView)
[self bringSubviewToFront:textSelectView];
if (inkView)
[self bringSubviewToFront:inkView];
if (annotSelectView)
[self bringSubviewToFront:annotSelectView];
} 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 (imageView)
{
CGRect frm = [imageView frame];
if (hitView)
[hitView setFrame: frm];
if (linkView)
[linkView setFrame:frm];
if (textSelectView)
[textSelectView setFrame:frm];
if (inkView)
[inkView setFrame:frm];
if (annotSelectView)
[annotSelectView setFrame:frm];
}
}
- (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;
// Adjust viewFrame to be relative to imageView's origin
viewFrame.origin.x -= imageView.frame.origin.x;
viewFrame.origin.y -= imageView.frame.origin.y;
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;
}
[self ensureDisplaylists];
printf("render tile\n");
tile_pix = renderPixmap(doc, page_list, annot_list, pageSize, screenSize, viewFrame, scale);
CGDataProviderRelease(tileData);
tileData = CreateWrappedPixmap(tile_pix);
UIImage *image = newImageWithPixmap(tile_pix, tileData);
dispatch_async(dispatch_get_main_queue(), ^{
isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
if (isValid) {
if (tileView) {
[tileView removeFromSuperview];
[tileView release];
tileView = nil;
}
tileView = [[UIImageView alloc] initWithFrame: frame];
[tileView setImage: image];
[self addSubview: tileView];
if (hitView)
[self bringSubviewToFront: hitView];
if (linkView)
[self bringSubviewToFront:linkView];
if (textSelectView)
[self bringSubviewToFront:textSelectView];
if (inkView)
[self bringSubviewToFront:inkView];
if (annotSelectView)
[self bringSubviewToFront:annotSelectView];
} 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: (CGFloat)scale
{
[self loadTile];
}
- (void) scrollViewDidZoom: (UIScrollView*)scrollView
{
if (imageView)
{
CGRect frm = [imageView frame];
if (hitView)
[hitView setFrame: frm];
if (textSelectView)
[textSelectView setFrame:frm];
if (inkView)
[inkView setFrame:frm];
if (annotSelectView)
[annotSelectView setFrame:frm];
}
}
- (void) setScale:(float)scale {}
- (void) updatePageAndTileWithTileFrame:(CGRect)tframe tileScale:(float)tscale viewFrame:(CGRect)vframe
{
rect_list *rlist = updatePage(doc, page);
fz_drop_display_list(ctx, annot_list);
annot_list = create_annot_list(doc, page);
if (tile_pix)
{
updatePixmap(doc, page_list, annot_list, tile_pix, rlist, pageSize, self.bounds.size, vframe, tscale);
UIImage *timage = newImageWithPixmap(tile_pix, tileData);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL isValid = CGRectEqualToRect(tframe, tileFrame) && tscale == tileScale;
if (isValid)
[tileView setImage:timage];
[timage release];
});
}
CGSize fscale = fitPageToScreen(pageSize, self.bounds.size);
CGRect rect = (CGRect){{0.0, 0.0},{pageSize.width * fscale.width, pageSize.height * fscale.height}};
updatePixmap(doc, page_list, annot_list, image_pix, rlist, pageSize, self.bounds.size, rect, 1.0);
drop_list(rlist);
UIImage *image = newImageWithPixmap(image_pix, imageData);
dispatch_async(dispatch_get_main_queue(), ^{
[imageView setImage:image];
[image release];
});
}
- (void) update
{
CGRect tframe = tileFrame;
float tscale = tileScale;
CGRect vframe = tframe;
vframe.origin.x -= imageView.frame.origin.x;
vframe.origin.y -= imageView.frame.origin.y;
dispatch_async(queue, ^{
[self updatePageAndTileWithTileFrame:tframe tileScale:tscale viewFrame:vframe];
});
}
- (void) invokeTextDialog:(NSString *)text
{
[dialogCreator invokeTextDialog:text okayAction:^(NSString *newText) {
dispatch_async(queue, ^{
BOOL accepted = setFocussedWidgetText(doc, page, [newText UTF8String]);
if (accepted)
{
dispatch_async(dispatch_get_main_queue(), ^{
[updater update];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[self invokeTextDialog:newText];
});
}
});
}];
}
- (void) invokeChoiceDialog:(NSArray *)choices
{
[dialogCreator invokeChoiceDialog:choices okayAction:^(NSArray *selection) {
dispatch_async(queue, ^{
BOOL accepted = setFocussedWidgetChoice(doc, page, [[selection objectAtIndex:0] UTF8String]);
if (accepted)
{
dispatch_async(dispatch_get_main_queue(), ^{
[updater update];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
[self invokeChoiceDialog:choices];
});
}
});
}];
}
- (int) passTapToPage:(CGPoint)pt
{
pdf_document *idoc = pdf_specifics(ctx, doc);
pdf_ui_event event;
int changed = 0;
pdf_widget *focus;
char **opts = NULL;
char *text = NULL;
if (!idoc)
return 0;
fz_var(opts);
fz_var(text);
fz_try(ctx)
{
event.etype = PDF_EVENT_TYPE_POINTER;
event.event.pointer.pt.x = pt.x;
event.event.pointer.pt.y = pt.y;
event.event.pointer.ptype = PDF_POINTER_DOWN;
changed = pdf_pass_event(ctx, idoc, (pdf_page *)page, &event);
event.event.pointer.ptype = PDF_POINTER_UP;
changed |= pdf_pass_event(ctx, idoc, (pdf_page *)page, &event);
focus = pdf_focused_widget(ctx, idoc);
if (focus)
{
switch (pdf_widget_get_type(ctx, focus))
{
case PDF_WIDGET_TYPE_TEXT:
{
text = pdf_text_widget_text(ctx, idoc, focus);
NSString *stext = [[NSString stringWithUTF8String:text?text:""] retain];
dispatch_async(dispatch_get_main_queue(), ^{
[self invokeTextDialog:stext];
[stext release];
});
break;
}
case PDF_WIDGET_TYPE_LISTBOX:
case PDF_WIDGET_TYPE_COMBOBOX:
{
int nopts = pdf_choice_widget_options(ctx, idoc, focus, NULL);
opts = fz_malloc(ctx, nopts * sizeof(*opts));
(void)pdf_choice_widget_options(ctx, idoc, focus, opts);
NSMutableArray *arr = [[NSMutableArray arrayWithCapacity:nopts] retain];
for (int i = 0; i < nopts; i++)
{
NSString *utf8 = [NSString stringWithUTF8String:opts[i]];
// FIXME: temporary patch to handle the library not converting to utf8
if (utf8 == nil)
utf8 = [NSString stringWithCString:opts[i] encoding:NSASCIIStringEncoding];
if (utf8 != nil)
[arr addObject:utf8];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self invokeChoiceDialog:arr];
[arr release];
});
break;
}
case PDF_WIDGET_TYPE_SIGNATURE:
break;
default:
break;
}
}
}
fz_always(ctx)
{
fz_free(ctx, text);
fz_free(ctx, opts);
}
fz_catch(ctx)
{
}
return changed;
}
- (MuTapResult *) handleTap:(CGPoint)pt
{
CGPoint ipt = [self convertPoint:pt toView:imageView];
CGSize scale = fitPageToScreen(pageSize, imageView.bounds.size);
int i;
ipt.x /= scale.width;
ipt.y /= scale.height;
for (i = 0; i < annotations.count; i++)
{
MuAnnotation *annot = [annotations objectAtIndex:i];
if (annot.type != FZ_ANNOT_WIDGET && CGRectContainsPoint(annot.rect, ipt))
{
[self selectAnnotation:i];
return [[[MuTapResultAnnotation alloc] initWithAnnotation:annot] autorelease];
}
}
[self deselectAnnotation];
for (i = 0; i < widgetRects.count; i++)
{
CGRect r = [[widgetRects objectAtIndex:i] CGRectValue];
if (CGRectContainsPoint(r, ipt))
{
dispatch_async(queue, ^{
int changed = [self passTapToPage:ipt];
if (changed)
dispatch_async(dispatch_get_main_queue(), ^{
[updater update];
});
});
return [[[MuTapResultWidget alloc] init] autorelease];
}
}
if (linkView)
{
CGPoint lpt = [self convertPoint:pt toView:linkView];
return [linkView handleTap:lpt];
}
return nil;
}
@end