From 54d7093e80e419b8aa18ffe33a4a3ff4fda25140 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 15:23:25 +0000 Subject: Fix clipping error. When reverting the clip path handling, I made a mistake. We need to set up the clip before starting any local group to ensure correct nesting. --- pdf/pdf_interpret.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index 4f26f04a..9b2a788b 100644 --- a/pdf/pdf_interpret.c +++ b/pdf/pdf_interpret.c @@ -263,15 +263,15 @@ pdf_show_path(pdf_csi *csi, int doclose, int dofill, int dostroke, int even_odd) else bbox = fz_bound_path(path, NULL, gstate->ctm); - if (dofill || dostroke) - pdf_begin_group(csi, bbox); - if (csi->clip) { gstate->clip_depth++; fz_clip_path(csi->dev, path, NULL, csi->clip_even_odd, gstate->ctm); } + if (dofill || dostroke) + pdf_begin_group(csi, bbox); + if (dofill) { switch (gstate->fill.kind) -- cgit v1.2.3 From 819aed16c190263a853fabe07870f9da98070f80 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 16:40:18 +0000 Subject: Allow draw device to use a dynamic stack. Previously, we had a hardwired 96 element stack for clipping/group nesting etc. If this was exceeeded during rendering we would give an error. Now we allow for that stack to be extended dynamically at runtime. If the stack extension fails, we will give an error and die. --- draw/draw_device.c | 124 ++++++++++++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/draw/draw_device.c b/draw/draw_device.c index 0efe701a..165cb1a9 100644 --- a/draw/draw_device.c +++ b/draw/draw_device.c @@ -34,6 +34,21 @@ enum { FZ_DRAWDEV_FLAGS_TYPE3 = 1, }; +typedef struct fz_draw_stack_s fz_draw_stack; + +struct fz_draw_stack_s { + fz_bbox scissor; + fz_pixmap *dest; + fz_pixmap *mask; + fz_pixmap *shape; + int blendmode; + int luminosity; + float alpha; + fz_matrix ctm; + float xstep, ystep; + fz_rect area; +}; + struct fz_draw_device_s { fz_glyph_cache *cache; @@ -46,18 +61,9 @@ struct fz_draw_device_s int flags; int top; int blendmode; - struct { - fz_bbox scissor; - fz_pixmap *dest; - fz_pixmap *mask; - fz_pixmap *shape; - int blendmode; - int luminosity; - float alpha; - fz_matrix ctm; - float xstep, ystep; - fz_rect area; - } stack[STACK_SIZE]; + fz_draw_stack *stack; + int stack_max; + fz_draw_stack init_stack[STACK_SIZE]; }; #ifdef DUMP_GROUP_BLENDS @@ -88,6 +94,24 @@ static void dump_spaces(int x, const char *s) #endif +static void fz_grow_stack(fz_draw_device *dev) +{ + int max = dev->stack_max * 2; + fz_draw_stack *stack; + + if (dev->stack == &dev->init_stack[0]) + { + stack = fz_malloc(sizeof(*stack) * max); + memcpy(stack, dev->stack, sizeof(*stack) * dev->stack_max); + } + else + { + stack = fz_realloc(dev->stack, max, sizeof(*stack)); + } + dev->stack = stack; + dev->stack_max = max; +} + static void fz_knockout_begin(void *user) { fz_draw_device *dev = user; @@ -98,11 +122,8 @@ static void fz_knockout_begin(void *user) if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0) return; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); bbox = fz_bound_pixmap(dev->dest); bbox = fz_intersect_bbox(bbox, dev->scissor); @@ -159,11 +180,8 @@ static void fz_knockout_end(void *user) if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0) return; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (dev->top > 0) { @@ -324,11 +342,8 @@ fz_draw_clip_path(void *user, fz_path *path, fz_rect *rect, int even_odd, fz_mat fz_pixmap *mask, *dest, *shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); fz_reset_gel(dev->gel, dev->scissor); fz_flatten_fill_path(dev->gel, path, ctm, flatness); @@ -395,11 +410,8 @@ fz_draw_clip_stroke_path(void *user, fz_path *path, fz_rect *rect, fz_stroke_sta fz_pixmap *mask, *dest, *shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (linewidth * expansion < 0.1f) linewidth = 1 / expansion; @@ -608,11 +620,8 @@ fz_draw_clip_text(void *user, fz_text *text, fz_matrix ctm, int accumulate) /* If accumulate == 1 then this text object is the first (or only) in a sequence */ /* If accumulate == 2 then this text object is a continuation */ - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (accumulate == 0) { @@ -701,11 +710,8 @@ fz_draw_clip_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_ fz_pixmap *glyph; int i, x, y, gid; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); /* make the mask the exact size needed */ bbox = fz_round_rect(fz_bound_text(text, ctm)); @@ -1042,11 +1048,8 @@ fz_draw_clip_image_mask(void *user, fz_pixmap *image, fz_rect *rect, fz_matrix c fz_pixmap *scaled = NULL; int dx, dy; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); #ifdef DUMP_GROUP_BLENDS dump_spaces(dev->top, "Clip (image mask) begin\n"); @@ -1185,11 +1188,8 @@ fz_draw_begin_mask(void *user, fz_rect rect, int luminosity, fz_colorspace *colo fz_pixmap *shape = dev->shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); bbox = fz_round_rect(rect); bbox = fz_intersect_bbox(bbox, dev->scissor); @@ -1247,11 +1247,8 @@ fz_draw_end_mask(void *user) fz_bbox bbox; int luminosity; - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (dev->top > 0) { @@ -1304,7 +1301,7 @@ fz_draw_begin_group(void *user, fz_rect rect, int isolated, int knockout, int bl fz_bbox bbox; fz_pixmap *dest, *shape; - if (dev->top == STACK_SIZE) + if (dev->top == dev->stack_max) { fz_warn("assert: too many buffers on stack"); return; @@ -1434,11 +1431,8 @@ fz_draw_begin_tile(void *user, fz_rect area, fz_rect view, float xstep, float ys /* area, view, xstep, ystep are in pattern space */ /* ctm maps from pattern space to device space */ - if (dev->top == STACK_SIZE) - { - fz_warn("assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (dev->blendmode & FZ_BLEND_KNOCKOUT) fz_knockout_begin(dev); @@ -1524,6 +1518,8 @@ fz_draw_free_user(void *user) /* TODO: pop and free the stacks */ if (dev->top > 0) fz_warn("items left on stack in draw device: %d", dev->top); + if (dev->stack != &dev->init_stack[0]) + fz_free(dev->stack); fz_free_gel(dev->gel); fz_free(dev); } @@ -1540,6 +1536,8 @@ fz_new_draw_device(fz_glyph_cache *cache, fz_pixmap *dest) ddev->top = 0; ddev->blendmode = 0; ddev->flags = 0; + ddev->stack = &ddev->init_stack[0]; + ddev->stack_max = STACK_SIZE; ddev->scissor.x0 = dest->x; ddev->scissor.y0 = dest->y; -- cgit v1.2.3 From 37b1af3eeae78cfe312d81eb616d98158445cae6 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 17:04:46 +0000 Subject: Fix bug 692627: stack overflows in text handling. The existing code uses recursion for text span handling. With sufficiently many chained spans we get stack overflow. Simple fixes to use a loop. --- fitz/dev_text.c | 89 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/fitz/dev_text.c b/fitz/dev_text.c index 139c3eed..fb75c944 100644 --- a/fitz/dev_text.c +++ b/fitz/dev_text.c @@ -35,12 +35,17 @@ fz_new_text_span(void) void fz_free_text_span(fz_text_span *span) { - if (span->font) - fz_drop_font(span->font); - if (span->next) - fz_free_text_span(span->next); - fz_free(span->text); - fz_free(span); + fz_text_span *next; + + while (span) + { + if (span->font) + fz_drop_font(span->font); + next = span->next; + fz_free(span->text); + fz_free(span); + span = next; + } } static void @@ -154,32 +159,34 @@ fz_debug_text_span_xml(fz_text_span *span) char buf[10]; int c, n, k, i; - printf("\n", - span->font ? span->font->name : "NULL", span->size, span->wmode, span->eol); - - for (i = 0; i < span->len; i++) + while (span) { - printf("\ttext[i].c; - if (c < 128) - putchar(c); - else + printf("\n", + span->font ? span->font->name : "NULL", span->size, span->wmode, span->eol); + + for (i = 0; i < span->len; i++) { - n = runetochar(buf, &c); - for (k = 0; k < n; k++) - putchar(buf[k]); + printf("\ttext[i].c; + if (c < 128) + putchar(c); + else + { + n = runetochar(buf, &c); + for (k = 0; k < n; k++) + putchar(buf[k]); + } + printf("\" bbox=\"%d %d %d %d\" />\n", + span->text[i].bbox.x0, + span->text[i].bbox.y0, + span->text[i].bbox.x1, + span->text[i].bbox.y1); } - printf("\" bbox=\"%d %d %d %d\" />\n", - span->text[i].bbox.x0, - span->text[i].bbox.y0, - span->text[i].bbox.x1, - span->text[i].bbox.y1); - } - printf("\n"); + printf("\n"); - if (span->next) - fz_debug_text_span_xml(span->next); + span = span->next; + } } void @@ -188,24 +195,26 @@ fz_debug_text_span(fz_text_span *span) char buf[10]; int c, n, k, i; - for (i = 0; i < span->len; i++) + while (span) { - c = span->text[i].c; - if (c < 128) - putchar(c); - else + for (i = 0; i < span->len; i++) { - n = runetochar(buf, &c); - for (k = 0; k < n; k++) - putchar(buf[k]); + c = span->text[i].c; + if (c < 128) + putchar(c); + else + { + n = runetochar(buf, &c); + for (k = 0; k < n; k++) + putchar(buf[k]); + } } - } - if (span->eol) - putchar('\n'); + if (span->eol) + putchar('\n'); - if (span->next) - fz_debug_text_span(span->next); + span = span->next; + } } static void -- cgit v1.2.3 From db360fac468dc89b45c89bd30374b61b27c221b1 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 18:37:21 +0000 Subject: AES encryption tweak; strings can be 0 bytes long Do not emit a warning if AES strings are 0 bytes long. --- pdf/pdf_crypt.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pdf/pdf_crypt.c b/pdf/pdf_crypt.c index 4dd4c4e1..81ee5649 100644 --- a/pdf/pdf_crypt.c +++ b/pdf/pdf_crypt.c @@ -729,7 +729,11 @@ pdf_crypt_obj_imp(pdf_crypt *crypt, fz_obj *obj, unsigned char *key, int keylen) if (crypt->strf.method == PDF_CRYPT_AESV2 || crypt->strf.method == PDF_CRYPT_AESV3) { - if (n & 15 || n < 32) + if (n == 0) + { + /* Empty strings are permissible */ + } + else if (n & 15 || n < 32) fz_warn("invalid string length for aes encryption"); else { -- cgit v1.2.3 From 9b56c6c88522b7ff6c592c93ff8ec33ee158747a Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 18:38:44 +0000 Subject: Fix bug 692627; pdfclean -ggg of encrypted files fails. Encryption bakes the object numbers into the strings/streams. This means that our renumbering technique falls down; when we move an object to its new position, then come to read it, we find that it decrypts badly. The fix here is (as suggested by Zeniko) to avoid renumbering when encryption is in use. --- apps/pdfclean.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/pdfclean.c b/apps/pdfclean.c index dd85b1a4..f46c4b76 100644 --- a/apps/pdfclean.c +++ b/apps/pdfclean.c @@ -754,7 +754,10 @@ int main(int argc, char **argv) compactxref(); /* Make renumbering affect all indirect references and update xref */ - if (dogarbage >= 2) + /* Do not renumber objects if encryption is in use, as the object + * numbers are baked into the streams/strings, and we can't currently + * cope with moving them. See bug 692627. */ + if (dogarbage >= 2 && xref->crypt == NULL) renumberobjs(); writepdf(); -- cgit v1.2.3 From 2be04fd23c9650820fa5abd1196647ccf1b73921 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 19:05:59 +0000 Subject: Bug 692487: Make pdfclean keep internal links where possible. Take on a (cosmetically tweaked) version of Zenikos patch to allow pdfclean to keep link destinations that are in preserved pages. --- apps/pdfclean.c | 41 +++++++++++++++++++++++++++++++++++++++-- fitz/base_object.c | 12 ++++++++++++ fitz/fitz.h | 1 + 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/apps/pdfclean.c b/apps/pdfclean.c index f46c4b76..b436a9cb 100644 --- a/apps/pdfclean.c +++ b/apps/pdfclean.c @@ -278,16 +278,18 @@ static void renumberobjs(void) static void retainpages(int argc, char **argv) { fz_error error; - fz_obj *oldroot, *root, *pages, *kids, *countobj, *parent; + fz_obj *oldroot, *root, *pages, *kids, *countobj, *parent, *olddests; /* Load the old page tree */ error = pdf_load_page_tree(xref); if (error) die(fz_rethrow(error, "cannot load page tree")); - /* Keep only pages/type entry to avoid references to unretained pages */ + /* Keep only pages/type and (reduced) dest entries to avoid + * references to unretained pages */ oldroot = fz_dict_gets(xref->trailer, "Root"); pages = fz_dict_gets(oldroot, "Pages"); + olddests = pdf_load_name_tree(xref, "Dests"); root = fz_new_dict(2); fz_dict_puts(root, "Type", fz_dict_gets(oldroot, "Type")); @@ -359,6 +361,41 @@ static void retainpages(int argc, char **argv) fz_drop_obj(countobj); fz_dict_puts(pages, "Kids", kids); fz_drop_obj(kids); + + /* Also preserve the (partial) Dests name tree */ + if (olddests) + { + int i; + fz_obj *names = fz_new_dict(1); + fz_obj *dests = fz_new_dict(1); + fz_obj *names_list = fz_new_array(32); + + for (i = 0; i < fz_dict_len(olddests); i++) + { + fz_obj *key = fz_dict_get_key(olddests, i); + fz_obj *val = fz_dict_get_val(olddests, i); + fz_obj *key_str = fz_new_string(fz_to_name(key), strlen(fz_to_name(key))); + fz_obj *dest = fz_dict_gets(val, "D"); + + dest = fz_array_get(dest ? dest : val, 0); + if (fz_array_contains(fz_dict_gets(pages, "Kids"), dest)) + { + fz_array_push(names_list, key_str); + fz_array_push(names_list, val); + } + fz_drop_obj(key_str); + } + + root = fz_dict_gets(xref->trailer, "Root"); + fz_dict_puts(dests, "Names", names_list); + fz_dict_puts(names, "Dests", dests); + fz_dict_puts(root, "Names", names); + + fz_drop_obj(names); + fz_drop_obj(dests); + fz_drop_obj(names_list); + fz_drop_obj(olddests); + } } /* diff --git a/fitz/base_object.c b/fitz/base_object.c index 6881fffb..64fafad0 100644 --- a/fitz/base_object.c +++ b/fitz/base_object.c @@ -497,6 +497,18 @@ fz_array_insert(fz_obj *obj, fz_obj *item) } } +int +fz_array_contains(fz_obj *arr, fz_obj *obj) +{ + int i; + + for (i = 0; i < fz_array_len(arr); i++) + if (!fz_objcmp(fz_array_get(arr, i), obj)) + return 1; + + return 0; +} + /* dicts may only have names as keys! */ static int keyvalcmp(const void *ap, const void *bp) diff --git a/fitz/fitz.h b/fitz/fitz.h index a1235c05..ccaa98b8 100644 --- a/fitz/fitz.h +++ b/fitz/fitz.h @@ -403,6 +403,7 @@ fz_obj *fz_array_get(fz_obj *array, int i); void fz_array_put(fz_obj *array, int i, fz_obj *obj); void fz_array_push(fz_obj *array, fz_obj *obj); void fz_array_insert(fz_obj *array, fz_obj *obj); +int fz_array_contains(fz_obj *array, fz_obj *obj); int fz_dict_len(fz_obj *dict); fz_obj *fz_dict_get_key(fz_obj *dict, int idx); -- cgit v1.2.3 From 8f8794063127610db433c5dd0f6e7f5241308c6f Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 19:22:30 +0000 Subject: Bug 692478: Honour decode arrays for jpx images Add simple code to read decode array and apply it to a jpx image after loading. Solves bug. --- pdf/pdf_image.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pdf/pdf_image.c b/pdf/pdf_image.c index e1901100..50292092 100644 --- a/pdf/pdf_image.c +++ b/pdf/pdf_image.c @@ -329,6 +329,18 @@ pdf_load_jpx_image(fz_pixmap **imgp, pdf_xref *xref, fz_obj *dict) } } + obj = fz_dict_getsa(dict, "Decode", "D"); + if (obj) + { + float decode[FZ_MAX_COLORS * 2]; + int i; + + for (i = 0; i < img->n * 2; i++) + decode[i] = fz_to_real(fz_array_get(obj, i)); + + fz_decode_tile(img, decode); + } + *imgp = img; return fz_okay; } -- cgit v1.2.3 From 84037901d3838b85f7c4d61b191376ce2e32b909 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Tue, 15 Nov 2011 20:20:05 +0000 Subject: Bug 692424: make repair cope better with missing endobj Previously when parsing an object with a missing endobj, the code would consume the header of the following object. Here we amend the code to give up searching for an endobj if it finds an integer (presumed to be the start of the next object). We backtrack over that integer and carry on. --- pdf/pdf_repair.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pdf/pdf_repair.c b/pdf/pdf_repair.c index 067fe2cf..08adcb99 100644 --- a/pdf/pdf_repair.c +++ b/pdf/pdf_repair.c @@ -68,14 +68,20 @@ pdf_repair_obj(fz_stream *file, char *buf, int cap, int *stmofsp, int *stmlenp, while ( tok != PDF_TOK_STREAM && tok != PDF_TOK_ENDOBJ && tok != PDF_TOK_ERROR && - tok != PDF_TOK_EOF ) + tok != PDF_TOK_EOF && + tok != PDF_TOK_INT ) { error = pdf_lex(&tok, file, buf, cap, &len); if (error) return fz_rethrow(error, "cannot scan for endobj or stream token"); } - if (tok == PDF_TOK_STREAM) + if (tok == PDF_TOK_INT) + { + while (len-- > 0) + fz_unread_byte(file); + } + else if (tok == PDF_TOK_STREAM) { int c = fz_read_byte(file); if (c == '\r') { -- cgit v1.2.3 From 3f608056b53cf55ed76983d92c32e5a94da1427e Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 14 Nov 2011 20:23:41 +0100 Subject: Fix bug where app->doctitle wasn't pointing to an alloced string. --- apps/pdfapp.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/pdfapp.c b/apps/pdfapp.c index ab29fcb9..0ef61e2d 100644 --- a/apps/pdfapp.c +++ b/apps/pdfapp.c @@ -139,11 +139,6 @@ static void pdfapp_open_pdf(pdfapp_t *app, char *filename, int fd) app->outline = pdf_load_outline(app->xref); - app->doctitle = fz_strdup(filename); - if (strrchr(app->doctitle, '\\')) - app->doctitle = strrchr(app->doctitle, '\\') + 1; - if (strrchr(app->doctitle, '/')) - app->doctitle = strrchr(app->doctitle, '/') + 1; info = fz_dict_gets(app->xref->trailer, "Info"); if (info) { @@ -151,6 +146,15 @@ static void pdfapp_open_pdf(pdfapp_t *app, char *filename, int fd) if (obj) app->doctitle = pdf_to_utf8(obj); } + if (!app->doctitle) + { + app->doctitle = filename; + if (strrchr(app->doctitle, '\\')) + app->doctitle = strrchr(app->doctitle, '\\') + 1; + if (strrchr(app->doctitle, '/')) + app->doctitle = strrchr(app->doctitle, '/') + 1; + app->doctitle = fz_strdup(app->doctitle); + } /* * Start at first page -- cgit v1.2.3 From 7b0e83ea2e177f30bd2f9a17894217066deb7ea5 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 14 Nov 2011 20:24:54 +0100 Subject: Add searching to the high level document API in the iOS app. --- ios/document.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ios/document.h | 1 + 2 files changed, 92 insertions(+) diff --git a/ios/document.c b/ios/document.c index 6d30a0bc..b48e0d33 100644 --- a/ios/document.c +++ b/ios/document.c @@ -3,6 +3,8 @@ #include "xps/muxps.h" #include "document.h" +#include // for tolower() + struct document * open_document(char *filename) { @@ -137,6 +139,95 @@ 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 ((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, count = 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); + + len = textlen(text); + for (pos = 0; pos < len; pos++) { + int n = match(text, needle, pos); + if (n) { + // TODO: extract bbox(es) into a result list + printf("found a match at page %d, pos %d!\n", number, pos); + count++; + } + } + + fz_free_text_span(text); + return count; +} + void close_document(struct document *doc) { diff --git a/ios/document.h b/ios/document.h index 1f59ccdd..00805a5d 100644 --- a/ios/document.h +++ b/ios/document.h @@ -27,6 +27,7 @@ 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); void close_document(struct document *doc); #endif -- cgit v1.2.3 From 97ca1a0cbee19553bdc60bf18552738d6776da9f Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 15 Nov 2011 17:45:50 +0100 Subject: Add search buttons and search bar to iOS app. --- ios/document.c | 3 +- ios/main.m | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 186 insertions(+), 22 deletions(-) diff --git a/ios/document.c b/ios/document.c index b48e0d33..31e8cc57 100644 --- a/ios/document.c +++ b/ios/document.c @@ -192,7 +192,8 @@ static int match(fz_text_span *span, char *s, int n) { int start = n, c; - while ((c = *s++)) { + while (*s) { + s += chartorune(&c, s); if (c == ' ' && charat(span, n) == ' ') { while (charat(span, n) == ' ') n++; diff --git a/ios/main.m b/ios/main.m index 3a6a45f3..5dbbbbc0 100644 --- a/ios/main.m +++ b/ios/main.m @@ -1,4 +1,4 @@ -#import +#import #undef ABS #undef MIN @@ -12,6 +12,9 @@ #define GAP 20 #define INDICATOR_Y -44-24 +#define BUTTON_W (44*2+10-4) +#define SEARCH_W (width - GAP - BUTTON_W) +#define SLIDER_W (width - GAP - 24) static dispatch_queue_t queue; static fz_glyph_cache *glyphcache = NULL; @@ -56,7 +59,7 @@ static float screenScale = 1; - (int) number; @end -@interface MuDocumentController : UIViewController +@interface MuDocumentController : UIViewController { struct document *doc; NSString *key; @@ -66,7 +69,10 @@ static float screenScale = 1; UIScrollView *canvas; UILabel *indicator; UISlider *slider; - UIBarButtonItem *wrapper; // for slider + UISearchBar *searchBar; + char *searchText; + UINavigationBar *buttonBar; + UIBarButtonItem *sliderWrapper; int width; // current screen size int height; int current; // currently visible page @@ -76,6 +82,8 @@ static float screenScale = 1; - (void) createPageView: (int)number; - (void) gotoPage: (int)number animated: (BOOL)animated; - (void) onShowOutline: (id)sender; +- (void) onToggleSearch: (id)sender; +- (void) resetSearch; - (void) onSlide: (id)sender; - (void) onTap: (UITapGestureRecognizer*)sender; - (void) showNavigationBar; @@ -92,6 +100,22 @@ static float screenScale = 1; #pragma mark - +// We need a transparent UIToolbar to embed into other toolbars. +@interface FakeToolbar : UIToolbar {} @end +@implementation FakeToolbar +- (id) initWithFrame: (CGRect)frame +{ + [super initWithFrame: frame]; + [self setOpaque: NO]; + [self setTranslucent: YES]; + return self; +} +- (void) drawRect: (CGRect)r +{ + // don't draw a backdrop! +} +@end + static void showAlert(NSString *msg) { char msgbuf[160 * 30]; @@ -697,22 +721,17 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, 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; } @@ -752,16 +771,86 @@ 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]; + + [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]]; + + // Set up the button on the right side of the navigation bar + + UIToolbar *rightBar = nil; + UIBarButtonItem *outlineButton = nil; - wrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; - [self setToolbarItems: [NSArray arrayWithObjects: wrapper, nil]]; + UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemSearch + target:self action:@selector(onToggleSearch:)]; + [searchButton setStyle: UIBarButtonItemStyleBordered]; + + if (outline) { + outlineButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks + target:self action:@selector(onShowOutline:)]; + [outlineButton setStyle: UIBarButtonItemStyleBordered]; + rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,54*2-4,44)]; + [rightBar setItems: [NSArray arrayWithObjects: outlineButton, searchButton, nil]]; + } else { + rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,54-4,44)]; + [rightBar setItems: [NSArray arrayWithObjects: searchButton, nil]]; + } + + UIBarButtonItem *rightWrapper = [[UIBarButtonItem alloc] initWithCustomView: rightBar]; + [[self navigationItem] setRightBarButtonItem: rightWrapper]; + [rightWrapper release]; + [rightBar release]; + + [searchButton release]; + [outlineButton release]; + + // Set up the search bar and prev/next search buttons + + searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,44,SEARCH_W,44)]; + [searchBar setHidden: YES]; + [searchBar setTranslucent: YES]; + [searchBar setDelegate: self]; + [view addSubview: searchBar]; + + // We abuse UINavigationBar to get the same background as UISearchBar. + // We'd like to use UIToolbar, but that has a different backdrop than the search bar. + // Ideally we'd add these buttons to the search bar, but that can't be done without + // even more ugly hacks. + buttonBar = [[UINavigationBar alloc] initWithFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; + [buttonBar setHidden: YES]; + [buttonBar setTranslucent: YES]; + [view addSubview: buttonBar]; + + UIBarButtonItem *prevButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemRewind + target:self action:@selector(onSearchPrev:)]; + UIBarButtonItem *space = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemFixedSpace + target:nil action:nil]; + [space setWidth: 10]; + UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward + target:self action:@selector(onSearchNext:)]; + + // TODO: add activityindicator to search bar + + rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,BUTTON_W,44)]; + [rightBar setItems: [NSArray arrayWithObjects: prevButton, space, nextButton, nil]]; + [buttonBar addSubview: rightBar]; + [rightBar release]; + + [prevButton release]; + [space release]; + [nextButton release]; [self setView: view]; [view release]; @@ -773,7 +862,9 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [recycledPages release]; recycledPages = nil; [indicator release]; indicator = nil; [slider release]; slider = nil; - [wrapper release]; wrapper = nil; + [sliderWrapper release]; sliderWrapper = nil; + [searchBar release]; searchBar = nil; + [buttonBar release]; buttonBar = nil; [canvas release]; canvas = nil; } @@ -786,6 +877,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, close_document(self_doc); }); } + free(searchText); [outline release]; [key release]; [super dealloc]; @@ -807,7 +899,9 @@ 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,44,SEARCH_W,44)]; + [buttonBar setFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; [[self navigationController] setToolbarHidden: NO animated: animated]; } @@ -844,6 +938,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)]; @@ -852,6 +948,9 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[[self navigationController] toolbar] setAlpha: 0]; [indicator setAlpha: 0]; + [searchBar setAlpha: 0]; + [buttonBar setAlpha: 0]; + [UIView commitAnimations]; } } @@ -861,6 +960,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] setNavigationBarHidden: YES]; [[self navigationController] setToolbarHidden: YES]; [indicator setHidden: YES]; + [searchBar setHidden: YES]; + [buttonBar setHidden: YES]; } - (void) onShowOutline: (id)sender @@ -868,6 +969,65 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] pushViewController: outline animated: YES]; } +- (void) onToggleSearch: (id)sender +{ + if ([searchBar isHidden]) { + [searchBar becomeFirstResponder]; + [searchBar setAlpha: 0]; + [searchBar setHidden: NO]; + [buttonBar setAlpha: 0]; + [buttonBar setHidden: NO]; + [UIView beginAnimations: @"MuNavBar" context: NULL]; + [searchBar setAlpha: 1]; + [buttonBar setAlpha: 1]; + [UIView commitAnimations]; + } else { + [searchBar resignFirstResponder]; + [UIView beginAnimations: @"MuNavBar" context: NULL]; + [UIView setAnimationDelegate: self]; + [UIView setAnimationDidStopSelector: @selector(onHideSearchFinished)]; + [searchBar setAlpha: 0]; + [buttonBar setAlpha: 0]; + [UIView commitAnimations]; + } +} + +- (void) onHideSearchFinished +{ + [searchBar setHidden: YES]; + [buttonBar setHidden: YES]; +} + +- (void) resetSearch +{ + [searchBar resignFirstResponder]; + free(searchText); + searchText = strdup([[searchBar text] UTF8String]); +} + +- (void) onSearchPrev: (id)sender +{ + [self resetSearch]; + + dispatch_sync(queue, ^{}); + int n = search_page(doc, current, searchText); + printf("search prev for '%s' found %d matches\n", searchText, n); +} + +- (void) onSearchNext: (id)sender +{ + [self resetSearch]; + + dispatch_sync(queue, ^{}); + int n = search_page(doc, current, searchText); + printf("search next for '%s' found %d matches\n", searchText, n); +} + +- (void) searchBarSearchButtonClicked: (UISearchBar*)sender +{ + [self onSearchNext: sender]; +} + - (void) onSlide: (id)sender { int number = [slider value]; @@ -1008,7 +1168,10 @@ 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,44,SEARCH_W,44)]; + [buttonBar setFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; + [[[self navigationController] toolbar] setNeedsLayout]; // force layout! // use max_width so we don't clamp the content offset too early during animation -- cgit v1.2.3 From 6b166e7d69121aa321ec6173697c53c66fd17001 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Thu, 17 Nov 2011 21:48:20 +0100 Subject: Display search results and search in background thread. --- ios/document.c | 25 ++++-- ios/document.h | 3 + ios/main.m | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 240 insertions(+), 30 deletions(-) diff --git a/ios/document.c b/ios/document.c index 31e8cc57..9e46644c 100644 --- a/ios/document.c +++ b/ios/document.c @@ -209,24 +209,39 @@ match(fz_text_span *span, char *s, int n) int search_page(struct document *doc, int number, char *needle) { - int pos, len, count = 0; + 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++) { - int n = match(text, needle, pos); + n = match(text, needle, pos); if (n) { - // TODO: extract bbox(es) into a result list printf("found a match at page %d, pos %d!\n", number, pos); - count++; + 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 count; + return doc->hit_count; +} + +fz_bbox +search_result_bbox(struct document *doc, int i) +{ + return doc->hit_bbox[i]; } void diff --git a/ios/document.h b/ios/document.h index 00805a5d..95aa9703 100644 --- a/ios/document.h +++ b/ios/document.h @@ -20,6 +20,8 @@ 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); @@ -28,6 +30,7 @@ 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 diff --git a/ios/main.m b/ios/main.m index 5dbbbbc0..db6e1edd 100644 --- a/ios/main.m +++ b/ios/main.m @@ -12,7 +12,7 @@ #define GAP 20 #define INDICATOR_Y -44-24 -#define BUTTON_W (44*2+10-4) +#define BUTTON_W (44*2+12-4) #define SEARCH_W (width - GAP - BUTTON_W) #define SLIDER_W (width - GAP - 24) @@ -38,6 +38,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 { struct document *doc; @@ -45,6 +55,8 @@ static float screenScale = 1; UIActivityIndicatorView *loadingView; UIImageView *imageView; UIImageView *tileView; + MuHitView *hitView; + CGSize pageSize; CGRect tileFrame; float tileScale; BOOL cancel; @@ -56,6 +68,8 @@ static float screenScale = 1; - (void) loadTile; - (void) willRotate; - (void) resetZoomAnimated: (BOOL)animated; +- (void) showSearchResults: (int)count; +- (void) clearSearchResults; - (int) number; @end @@ -70,7 +84,8 @@ static float screenScale = 1; UILabel *indicator; UISlider *slider; UISearchBar *searchBar; - char *searchText; + UIBarButtonItem *nextButton, *prevButton; + int searchPage; UINavigationBar *buttonBar; UIBarButtonItem *sliderWrapper; int width; // current screen size @@ -84,6 +99,7 @@ static float screenScale = 1; - (void) onShowOutline: (id)sender; - (void) onToggleSearch: (id)sender; - (void) resetSearch; +- (void) showSearchResults: (int)count forPage: (int)number; - (void) onSlide: (id)sender; - (void) onTap: (UITapGestureRecognizer*)sender; - (void) showNavigationBar; @@ -194,6 +210,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; @@ -428,6 +451,51 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, #pragma mark - +@implementation MuHitView + +- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc +{ + self = [super init]; + if (self) { + [self setOpaque: NO]; + + pageSize = CGSizeMake(1,1); + + 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; +} + +- (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 @@ -468,6 +536,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]; @@ -480,6 +549,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 @@ -509,8 +602,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]; }); @@ -554,7 +649,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]; }); }); @@ -564,8 +659,14 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self setContentSize: imageView.frame.size]; + if (hitView) { + [hitView setPageSize: pageSize]; + [self bringSubviewToFront: hitView]; + } + [self layoutIfNeeded]; } + } - (void) willRotate @@ -601,6 +702,10 @@ 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 @@ -610,7 +715,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; @@ -640,7 +745,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; @@ -656,6 +761,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"); } @@ -690,6 +797,12 @@ 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 - @@ -818,6 +931,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,44,SEARCH_W,44)]; [searchBar setHidden: YES]; [searchBar setTranslucent: YES]; + [searchBar setPlaceholder: @"Search"]; [searchBar setDelegate: self]; [view addSubview: searchBar]; @@ -830,16 +944,20 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [buttonBar setTranslucent: YES]; [view addSubview: buttonBar]; - UIBarButtonItem *prevButton = [[UIBarButtonItem alloc] + prevButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemRewind target:self action:@selector(onSearchPrev:)]; + nextButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward + target:self action:@selector(onSearchNext:)]; + + [prevButton setEnabled: NO]; + [nextButton setEnabled: NO]; + UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemFixedSpace target:nil action:nil]; - [space setWidth: 10]; - UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward - target:self action:@selector(onSearchNext:)]; + [space setWidth: 12]; // TODO: add activityindicator to search bar @@ -848,9 +966,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [buttonBar addSubview: rightBar]; [rightBar release]; - [prevButton release]; [space release]; - [nextButton release]; [self setView: view]; [view release]; @@ -864,6 +980,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [slider release]; slider = nil; [sliderWrapper release]; sliderWrapper = nil; [searchBar release]; searchBar = nil; + [prevButton release]; prevButton = nil; + [nextButton release]; nextButton = nil; [buttonBar release]; buttonBar = nil; [canvas release]; canvas = nil; } @@ -877,7 +995,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, close_document(self_doc); }); } - free(searchText); [outline release]; [key release]; [super dealloc]; @@ -1000,27 +1117,84 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) resetSearch { + searchPage = -1; + for (MuPageView *view in [canvas subviews]) + [view clearSearchResults]; +} + +- (void) showSearchResults: (int)count forPage: (int)number +{ + printf("search found %d matches on page %d\n", count, 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]; - free(searchText); - searchText = strdup([[searchBar text] UTF8String]); + + 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]; + + 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_sync(dispatch_get_main_queue(), ^{ + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + [self showSearchResults: n forPage: i]; // this is why we don't async + free(needle); + }); + return; + } + } + dispatch_sync(dispatch_get_main_queue(), ^{ + printf("no search results found\n"); + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + UIAlertView *alert = [[UIAlertView alloc] + initWithTitle: @"Search Completed" + message: @"No matches found" + delegate: nil + cancelButtonTitle: @"Okay" + otherButtonTitles: nil]; + [alert show]; + [alert release]; + free(needle); + }); + }); } - (void) onSearchPrev: (id)sender { - [self resetSearch]; - - dispatch_sync(queue, ^{}); - int n = search_page(doc, current, searchText); - printf("search prev for '%s' found %d matches\n", searchText, n); + [self searchInDirection: -1]; } - (void) onSearchNext: (id)sender { - [self resetSearch]; - - dispatch_sync(queue, ^{}); - int n = search_page(doc, current, searchText); - printf("search next for '%s' found %d matches\n", searchText, n); + [self searchInDirection: 1]; } - (void) searchBarSearchButtonClicked: (UISearchBar*)sender @@ -1028,6 +1202,18 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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]; @@ -1094,6 +1280,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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 @@ -1118,6 +1308,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 -- cgit v1.2.3 From d20f587871d13f958c7a99f559584c7622ef8527 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 18 Nov 2011 16:24:16 +0100 Subject: Update About document and fix some search UI bugs in iOS app. --- ios/About.pdf | Bin 131735 -> 0 bytes ios/About.xps | Bin 0 -> 76673 bytes ios/MuPDF.xcodeproj/project.pbxproj | 8 ++++---- ios/document.c | 3 --- ios/main.m | 23 ++++++++++++----------- 5 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 ios/About.pdf create mode 100644 ios/About.xps diff --git a/ios/About.pdf b/ios/About.pdf deleted file mode 100644 index 763c467e..00000000 Binary files a/ios/About.pdf and /dev/null differ diff --git a/ios/About.xps b/ios/About.xps new file mode 100644 index 00000000..3fcec260 Binary files /dev/null and b/ios/About.xps differ diff --git a/ios/MuPDF.xcodeproj/project.pbxproj b/ios/MuPDF.xcodeproj/project.pbxproj index 61542780..56f6275a 100644 --- a/ios/MuPDF.xcodeproj/project.pbxproj +++ b/ios/MuPDF.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* 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 */; }; @@ -39,7 +39,7 @@ /* Begin PBXFileReference section */ 9644E99E146ACEC000E5B70A /* document.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = document.c; sourceTree = ""; }; 9644E99F146ACEC000E5B70A /* document.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = document.h; sourceTree = ""; }; - 9683F618145F4F84000E1607 /* About.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = About.pdf; sourceTree = ""; }; + 9668C8D81476A30200D7BA52 /* About.xps */ = {isa = PBXFileReference; lastKnownFileType = file; path = About.xps; sourceTree = ""; }; 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 = ""; }; 968F2E8F14539BEB0085264E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -86,7 +86,7 @@ 968F2E9214539BF10085264E /* Sources */ = { isa = PBXGroup; children = ( - 9683F618145F4F84000E1607 /* About.pdf */, + 9668C8D81476A30200D7BA52 /* About.xps */, 96BD2B36145AC485001CEBC3 /* Icon.png */, 96F2341414603FBA004A8A22 /* Icon@2x.png */, 96BD2B35145AC485001CEBC3 /* Icon-72.png */, @@ -212,8 +212,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; }; diff --git a/ios/document.c b/ios/document.c index 9e46644c..7f3b97ab 100644 --- a/ios/document.c +++ b/ios/document.c @@ -80,7 +80,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); @@ -89,7 +88,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); @@ -225,7 +223,6 @@ search_page(struct document *doc, int number, char *needle) for (pos = 0; pos < len; pos++) { n = match(text, needle, pos); if (n) { - printf("found a match at page %d, pos %d!\n", number, pos); 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)) diff --git a/ios/main.m b/ios/main.m index db6e1edd..52d901fe 100644 --- a/ios/main.m +++ b/ios/main.m @@ -1,4 +1,4 @@ -#import +#import #undef ABS #undef MIN @@ -364,7 +364,7 @@ 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]]; } @@ -455,11 +455,11 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (id) initWithSearchResults: (int)n forDocument: (struct document *)doc { - self = [super init]; + self = [super initWithFrame: CGRectMake(0,0,100,100)]; if (self) { [self setOpaque: NO]; - pageSize = CGSizeMake(1,1); + 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 @@ -476,6 +476,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (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 @@ -623,10 +625,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]; } @@ -659,11 +666,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self setContentSize: imageView.frame.size]; - if (hitView) { - [hitView setPageSize: pageSize]; - [self bringSubviewToFront: hitView]; - } - [self layoutIfNeeded]; } @@ -705,7 +707,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, if (hitView && imageView) [hitView setFrame: [imageView frame]]; - } - (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView @@ -1124,7 +1125,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) showSearchResults: (int)count forPage: (int)number { - printf("search found %d matches on page %d\n", count, number); + printf("search found match on page %d\n", number); searchPage = number; [self gotoPage: number animated: NO]; for (MuPageView *view in [canvas subviews]) -- cgit v1.2.3 From f0fa07986543bb3f3e076c4186b06ef8574ea106 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 19 Nov 2011 02:08:17 +0100 Subject: Tweak search UI and use iOS 5 features for better toolbar buttons. --- ios/MuPDF.xcodeproj/project.pbxproj | 4 +- ios/main.m | 179 +++++++++++++----------------------- 2 files changed, 64 insertions(+), 119 deletions(-) diff --git a/ios/MuPDF.xcodeproj/project.pbxproj b/ios/MuPDF.xcodeproj/project.pbxproj index 56f6275a..78e0c10c 100644 --- a/ios/MuPDF.xcodeproj/project.pbxproj +++ b/ios/MuPDF.xcodeproj/project.pbxproj @@ -309,7 +309,7 @@ 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)"; SDKROOT = iphoneos; @@ -335,7 +335,7 @@ 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)"; diff --git a/ios/main.m b/ios/main.m index 52d901fe..1708ef2a 100644 --- a/ios/main.m +++ b/ios/main.m @@ -12,8 +12,6 @@ #define GAP 20 #define INDICATOR_Y -44-24 -#define BUTTON_W (44*2+12-4) -#define SEARCH_W (width - GAP - BUTTON_W) #define SLIDER_W (width - GAP - 24) static dispatch_queue_t queue; @@ -84,10 +82,10 @@ static float screenScale = 1; UILabel *indicator; UISlider *slider; UISearchBar *searchBar; - UIBarButtonItem *nextButton, *prevButton; - int searchPage; - UINavigationBar *buttonBar; + UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton; UIBarButtonItem *sliderWrapper; + int searchPage; + int cancelSearch; int width; // current screen size int height; int current; // currently visible page @@ -97,7 +95,8 @@ static float screenScale = 1; - (void) createPageView: (int)number; - (void) gotoPage: (int)number animated: (BOOL)animated; - (void) onShowOutline: (id)sender; -- (void) onToggleSearch: (id)sender; +- (void) onShowSearch: (id)sender; +- (void) onCancelSearch: (id)sender; - (void) resetSearch; - (void) showSearchResults: (int)count forPage: (int)number; - (void) onSlide: (id)sender; @@ -116,22 +115,6 @@ static float screenScale = 1; #pragma mark - -// We need a transparent UIToolbar to embed into other toolbars. -@interface FakeToolbar : UIToolbar {} @end -@implementation FakeToolbar -- (id) initWithFrame: (CGRect)frame -{ - [super initWithFrame: frame]; - [self setOpaque: NO]; - [self setTranslucent: YES]; - return self; -} -- (void) drawRect: (CGRect)r -{ - // don't draw a backdrop! -} -@end - static void showAlert(NSString *msg) { char msgbuf[160 * 30]; @@ -897,54 +880,17 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]]; - // Set up the button on the right side of the navigation bar + // Set up the buttons on the navigation bar - UIToolbar *rightBar = nil; - UIBarButtonItem *outlineButton = nil; - - UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] + 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(onToggleSearch:)]; - [searchButton setStyle: UIBarButtonItemStyleBordered]; - - if (outline) { - outlineButton = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks - target:self action:@selector(onShowOutline:)]; - [outlineButton setStyle: UIBarButtonItemStyleBordered]; - rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,54*2-4,44)]; - [rightBar setItems: [NSArray arrayWithObjects: outlineButton, searchButton, nil]]; - } else { - rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,54-4,44)]; - [rightBar setItems: [NSArray arrayWithObjects: searchButton, nil]]; - } - - UIBarButtonItem *rightWrapper = [[UIBarButtonItem alloc] initWithCustomView: rightBar]; - [[self navigationItem] setRightBarButtonItem: rightWrapper]; - [rightWrapper release]; - [rightBar release]; - - [searchButton release]; - [outlineButton release]; - - // Set up the search bar and prev/next search buttons - - searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,44,SEARCH_W,44)]; - [searchBar setHidden: YES]; - [searchBar setTranslucent: YES]; - [searchBar setPlaceholder: @"Search"]; - [searchBar setDelegate: self]; - [view addSubview: searchBar]; - - // We abuse UINavigationBar to get the same background as UISearchBar. - // We'd like to use UIToolbar, but that has a different backdrop than the search bar. - // Ideally we'd add these buttons to the search bar, but that can't be done without - // even more ugly hacks. - buttonBar = [[UINavigationBar alloc] initWithFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; - [buttonBar setHidden: YES]; - [buttonBar setTranslucent: YES]; - [view addSubview: buttonBar]; - + target:self action:@selector(onShowSearch:)]; prevButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemRewind target:self action:@selector(onSearchPrev:)]; @@ -952,23 +898,24 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward target:self action:@selector(onSearchNext:)]; + // Set up the search bar and prev/next search buttons + float w = [[UIScreen mainScreen] bounds].size.width - 180; + + searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,w,32)]; + [searchBar setPlaceholder: @"Search"]; + [searchBar setDelegate: self]; + [searchBar setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + // HACK to make transparent background + [[searchBar.subviews objectAtIndex:0] removeFromSuperview]; + [prevButton setEnabled: NO]; [nextButton setEnabled: NO]; - UIBarButtonItem *space = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemFixedSpace - target:nil action:nil]; - [space setWidth: 12]; + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: searchButton, outlineButton, nil]]; // TODO: add activityindicator to search bar - rightBar = [[FakeToolbar alloc] initWithFrame: CGRectMake(0,0,BUTTON_W,44)]; - [rightBar setItems: [NSArray arrayWithObjects: prevButton, space, nextButton, nil]]; - [buttonBar addSubview: rightBar]; - [rightBar release]; - - [space release]; - [self setView: view]; [view release]; } @@ -981,9 +928,11 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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; - [buttonBar release]; buttonBar = nil; [canvas release]; canvas = nil; } @@ -1018,8 +967,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [canvas setContentOffset: CGPointMake(current * width, 0)]; [sliderWrapper setWidth: SLIDER_W]; - [searchBar setFrame: CGRectMake(0,44,SEARCH_W,44)]; - [buttonBar setFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; [[self navigationController] setToolbarHidden: NO animated: animated]; } @@ -1066,9 +1013,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[[self navigationController] toolbar] setAlpha: 0]; [indicator setAlpha: 0]; - [searchBar setAlpha: 0]; - [buttonBar setAlpha: 0]; - [UIView commitAnimations]; } } @@ -1078,8 +1022,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] setNavigationBarHidden: YES]; [[self navigationController] setToolbarHidden: YES]; [indicator setHidden: YES]; - [searchBar setHidden: YES]; - [buttonBar setHidden: YES]; } - (void) onShowOutline: (id)sender @@ -1087,33 +1029,24 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] pushViewController: outline animated: YES]; } -- (void) onToggleSearch: (id)sender +- (void) onShowSearch: (id)sender { - if ([searchBar isHidden]) { - [searchBar becomeFirstResponder]; - [searchBar setAlpha: 0]; - [searchBar setHidden: NO]; - [buttonBar setAlpha: 0]; - [buttonBar setHidden: NO]; - [UIView beginAnimations: @"MuNavBar" context: NULL]; - [searchBar setAlpha: 1]; - [buttonBar setAlpha: 1]; - [UIView commitAnimations]; - } else { - [searchBar resignFirstResponder]; - [UIView beginAnimations: @"MuNavBar" context: NULL]; - [UIView setAnimationDelegate: self]; - [UIView setAnimationDidStopSelector: @selector(onHideSearchFinished)]; - [searchBar setAlpha: 0]; - [buttonBar setAlpha: 0]; - [UIView commitAnimations]; - } + [[self navigationItem] setTitleView: searchBar]; + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: nextButton, prevButton, nil]]; + [[self navigationItem] setLeftBarButtonItem: cancelButton]; + [searchBar becomeFirstResponder]; } -- (void) onHideSearchFinished +- (void) onCancelSearch: (id)sender { - [searchBar setHidden: YES]; - [buttonBar setHidden: YES]; + cancelSearch = YES; + [searchBar resignFirstResponder]; + [[self navigationItem] setTitleView: nil]; + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: searchButton, outlineButton, nil]]; + [[self navigationItem] setLeftBarButtonItem: nil]; + [self resetSearch]; } - (void) resetSearch @@ -1157,27 +1090,41 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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_sync(dispatch_get_main_queue(), ^{ + 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]; - [self showSearchResults: n forPage: i]; // this is why we don't async + [searchField setEnabled: YES]; free(needle); }); return; } } - dispatch_sync(dispatch_get_main_queue(), ^{ + 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: @"Search Completed" - message: @"No matches found" + initWithTitle: @"No matches found for:" + message: [NSString stringWithUTF8String: needle] delegate: nil cancelButtonTitle: @"Okay" otherButtonTitles: nil]; @@ -1362,8 +1309,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, height = size.height; [sliderWrapper setWidth: SLIDER_W]; - [searchBar setFrame: CGRectMake(0,44,SEARCH_W,44)]; - [buttonBar setFrame: CGRectMake(SEARCH_W,44,BUTTON_W,44)]; [[[self navigationController] toolbar] setNeedsLayout]; // force layout! -- cgit v1.2.3 From 5821e0a73ea10182fc105f27bc45153f0b40a1a5 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 21 Nov 2011 13:53:43 +0100 Subject: Fix memory leaks in iOS app. The use of a set for visible pages was causing reference counting cycles. viewDidUnload is only called in low memory warning situations, not as part of the normal tear down. --- ios/main.m | 68 +++++++++++++++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/ios/main.m b/ios/main.m index 1708ef2a..9d200817 100644 --- a/ios/main.m +++ b/ios/main.m @@ -75,8 +75,6 @@ static float screenScale = 1; { struct document *doc; NSString *key; - NSMutableSet *visiblePages; - NSMutableSet *recycledPages; MuOutlineController *outline; UIScrollView *canvas; UILabel *indicator; @@ -170,15 +168,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; } @@ -845,9 +844,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]; @@ -880,11 +876,13 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]]; - // Set up the buttons on the navigation bar + // Set up the buttons on the navigation and search bar - outlineButton = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks - target:self action:@selector(onShowOutline:)]; + if (outline) { + outlineButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks + target:self action:@selector(onShowOutline:)]; + } cancelButton = [[UIBarButtonItem alloc] initWithTitle: @"Cancel" style: UIBarButtonItemStyleBordered target:self action:@selector(onCancelSearch:)]; @@ -898,7 +896,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward target:self action:@selector(onSearchNext:)]; - // Set up the search bar and prev/next search buttons float w = [[UIScreen mainScreen] bounds].size.width - 180; searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,w,32)]; @@ -920,10 +917,16 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [view release]; } -- (void) viewDidUnload +- (void) dealloc { - [visiblePages release]; visiblePages = nil; - [recycledPages release]; recycledPages = nil; + if (doc) { + struct document *self_doc = doc; // don't auto-retain self here! + dispatch_async(queue, ^{ + printf("close document\n"); + close_document(self_doc); + }); + } + [indicator release]; indicator = nil; [slider release]; slider = nil; [sliderWrapper release]; sliderWrapper = nil; @@ -934,17 +937,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [prevButton release]; prevButton = nil; [nextButton release]; nextButton = nil; [canvas release]; canvas = nil; -} -- (void) dealloc -{ - if (doc) { - struct document *self_doc = doc; // don't auto-retain self here! - dispatch_async(queue, ^{ - printf("close document\n"); - close_document(self_doc); - }); - } [outline release]; [key release]; [super dealloc]; @@ -1212,18 +1205,18 @@ 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]; @@ -1244,7 +1237,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]; } @@ -1273,7 +1265,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)]; @@ -1282,7 +1274,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)]; } @@ -1316,13 +1308,13 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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]; -- cgit v1.2.3 From 51b43a4230e1317df107e1c877d8599f15e44cfe Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 22 Nov 2011 16:51:51 +0100 Subject: Add password dialog to iOS app. --- ios/document.c | 20 ++++++++++++- ios/document.h | 2 ++ ios/main.m | 95 +++++++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/ios/document.c b/ios/document.c index 7f3b97ab..ffb92519 100644 --- a/ios/document.c +++ b/ios/document.c @@ -14,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"); @@ -45,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) { diff --git a/ios/document.h b/ios/document.h index 95aa9703..797b1e8f 100644 --- a/ios/document.h +++ b/ios/document.h @@ -25,6 +25,8 @@ struct document }; 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); diff --git a/ios/main.m b/ios/main.m index 9d200817..3949a18a 100644 --- a/ios/main.m +++ b/ios/main.m @@ -22,8 +22,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 @@ -89,7 +94,7 @@ static float screenScale = 1; 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; @@ -351,14 +356,76 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [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 @@ -792,31 +859,17 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, @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; - } - fz_outline *root = load_outline(doc); if (root) { NSMutableArray *titles = [[NSMutableArray alloc] init]; @@ -1119,7 +1172,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, initWithTitle: @"No matches found for:" message: [NSString stringWithUTF8String: needle] delegate: nil - cancelButtonTitle: @"Okay" + cancelButtonTitle: @"Close" otherButtonTitles: nil]; [alert show]; [alert release]; -- cgit v1.2.3 From 0ab03af67d5e35ab305e5915e38e71e2c7f6306a Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Thu, 24 Nov 2011 16:10:40 +0000 Subject: Bug 692506: Improve repairing by accepting broken dictionaries. Adopt Zenikos patch from bug 692506; if a dict fails to parse, then create an empty one and continue. The repaired document will be incomplete, but we may well get something useful out of it. --- pdf/pdf_repair.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pdf/pdf_repair.c b/pdf/pdf_repair.c index 08adcb99..dc376dc3 100644 --- a/pdf/pdf_repair.c +++ b/pdf/pdf_repair.c @@ -36,7 +36,10 @@ pdf_repair_obj(fz_stream *file, char *buf, int cap, int *stmofsp, int *stmlenp, /* Send NULL xref so we don't try to resolve references */ error = pdf_parse_dict(&dict, NULL, file, buf, cap); if (error) - return fz_rethrow(error, "cannot parse object"); + { + fz_catch(error, "cannot parse object - repair will be incomplete"); + dict = fz_new_dict(2); + } obj = fz_dict_gets(dict, "Type"); if (fz_is_name(obj) && !strcmp(fz_to_name(obj), "XRef")) -- cgit v1.2.3 From 085c8a05ad38d2b2ca00038ca4d4239786bc7bd0 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 22 Nov 2011 19:50:06 +0100 Subject: Resize searchBar manually instead of relying on automatic resizing. --- ios/main.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/main.m b/ios/main.m index 3949a18a..7b977928 100644 --- a/ios/main.m +++ b/ios/main.m @@ -13,6 +13,7 @@ #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; @@ -949,12 +950,9 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward target:self action:@selector(onSearchNext:)]; - float w = [[UIScreen mainScreen] bounds].size.width - 180; - - searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,w,32)]; + searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)]; [searchBar setPlaceholder: @"Search"]; [searchBar setDelegate: self]; - [searchBar setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; // HACK to make transparent background [[searchBar.subviews objectAtIndex:0] removeFromSuperview]; @@ -1013,6 +1011,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [canvas setContentOffset: CGPointMake(current * width, 0)]; [sliderWrapper setWidth: SLIDER_W]; + [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; [[self navigationController] setToolbarHidden: NO animated: animated]; } @@ -1354,6 +1353,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, height = size.height; [sliderWrapper setWidth: SLIDER_W]; + [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; [[[self navigationController] toolbar] setNeedsLayout]; // force layout! -- cgit v1.2.3 From df430bd236d2ffcca37a1558ab52be77e5119e22 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Tue, 22 Nov 2011 19:48:26 +0100 Subject: Prepare for App Store release. --- ios/Info.plist | 4 +-- ios/MuPDF.xcodeproj/project.pbxproj | 61 ++++++------------------------------- ios/build_libs.sh | 10 +++--- 3 files changed, 16 insertions(+), 59 deletions(-) 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 @@ CFBundleIconFiles CFBundleIdentifier - com.artifex.${PRODUCT_NAME:rfc1034identifier} + com.artifex.mupdf CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9 + 0.9.1 CFBundleSignature ???? CFBundleVersion diff --git a/ios/MuPDF.xcodeproj/project.pbxproj b/ios/MuPDF.xcodeproj/project.pbxproj index 78e0c10c..73fe67c6 100644 --- a/ios/MuPDF.xcodeproj/project.pbxproj +++ b/ios/MuPDF.xcodeproj/project.pbxproj @@ -13,14 +13,7 @@ 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 */; }; @@ -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 = ""; }; 96BD2B36145AC485001CEBC3 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; 96F2341414603FBA004A8A22 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = ""; }; @@ -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; }; @@ -118,26 +96,10 @@ name = Frameworks; sourceTree = ""; }; - 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 = ""; - }; 96A3B27414539BAD00D0A895 = { isa = PBXGroup; children = ( 968F2E9214539BF10085264E /* Sources */, - 968F2EB214539F230085264E /* Libraries */, 968F2E9A14539C880085264E /* Frameworks */, 968F2E9814539C880085264E /* Products */, ); @@ -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)", @@ -312,6 +267,7 @@ 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)", @@ -339,6 +295,7 @@ 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. -- cgit v1.2.3 From 7aeb37fc5be2388ad71d9eab04b539c2c49f024e Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Thu, 24 Nov 2011 20:27:47 +0000 Subject: Fix *STUPID* error in recent clipping changes. Once we've applied the clipping path, don't clip again on every subsequent path. --- pdf/pdf_interpret.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index 9b2a788b..752d9426 100644 --- a/pdf/pdf_interpret.c +++ b/pdf/pdf_interpret.c @@ -267,6 +267,7 @@ pdf_show_path(pdf_csi *csi, int doclose, int dofill, int dostroke, int even_odd) { gstate->clip_depth++; fz_clip_path(csi->dev, path, NULL, csi->clip_even_odd, gstate->ctm); + csi->clip = 0; } if (dofill || dostroke) -- cgit v1.2.3 From 6e14149d3e915f559f99276a525862e28d6f0478 Mon Sep 17 00:00:00 2001 From: Robin Watts Date: Thu, 24 Nov 2011 19:28:25 +0000 Subject: First cut at support for OCGs in mupdf (bug 692314) When opening a file, create a pdf_ocg_descriptor that lists the OCGs in a file. Add a new function to allow us to set the configuration in use (currently just the default one). This sets the states of the OCGs as appropriate. When decoding the file respect the states of the OCGs. This results in Invite.pdf rendering correctly. There is more to be done in this area (with automatic setting of OCGs by language/zoom level etc), but this is a good start. --- pdf/mupdf.h | 19 +++- pdf/pdf_interpret.c | 270 +++++++++++++++++++++++++++++++++++++++++++++++----- pdf/pdf_xref.c | 159 +++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+), 27 deletions(-) diff --git a/pdf/mupdf.h b/pdf/mupdf.h index 22e087f9..e03f6efc 100644 --- a/pdf/mupdf.h +++ b/pdf/mupdf.h @@ -45,6 +45,8 @@ char *pdf_from_ucs2(unsigned short *str); typedef struct pdf_xref_entry_s pdf_xref_entry; typedef struct pdf_crypt_s pdf_crypt; +typedef struct pdf_ocg_descriptor_s pdf_ocg_descriptor; +typedef struct pdf_ocg_entry_s pdf_ocg_entry; struct pdf_xref_entry_s { @@ -55,6 +57,20 @@ struct pdf_xref_entry_s int type; /* 0=unset (f)ree i(n)use (o)bjstm */ }; +struct pdf_ocg_entry_s +{ + int num; + int gen; + int state; +}; + +struct pdf_ocg_descriptor_s +{ + int len; + pdf_ocg_entry *ocgs; + fz_obj *intent; +}; + struct pdf_xref_s { fz_stream *file; @@ -63,6 +79,7 @@ struct pdf_xref_s int file_size; pdf_crypt *crypt; fz_obj *trailer; + pdf_ocg_descriptor *ocg; int len; pdf_xref_entry *table; @@ -474,7 +491,7 @@ void pdf_free_page(pdf_page *page); * Content stream parsing */ -fz_error pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *target); +fz_error pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *event); fz_error pdf_run_page(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm); fz_error pdf_run_glyph(pdf_xref *xref, fz_obj *resources, fz_buffer *contents, fz_device *dev, fz_matrix ctm); diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index 752d9426..55e66e61 100644 --- a/pdf/pdf_interpret.c +++ b/pdf/pdf_interpret.c @@ -67,7 +67,7 @@ struct pdf_csi_s pdf_xref *xref; /* usage mode for optional content groups */ - char *target; /* "View", "Print", "Export" */ + char *event; /* "View", "Print", "Export" */ /* interpreter stack */ fz_obj *obj; @@ -79,6 +79,7 @@ struct pdf_csi_s int xbalance; int in_text; + int in_hidden_ocg; /* path object state */ fz_path *path; @@ -102,24 +103,194 @@ static fz_error pdf_run_buffer(pdf_csi *csi, fz_obj *rdb, fz_buffer *contents); static fz_error pdf_run_xobject(pdf_csi *csi, fz_obj *resources, pdf_xobject *xobj, fz_matrix transform); static void pdf_show_pattern(pdf_csi *csi, pdf_pattern *pat, fz_rect area, int what); +static int +ocg_intents_include(pdf_ocg_descriptor *desc, char *name) +{ + int i, len; + + if (strcmp(name, "All") == 0) + return 1; + + /* In the absence of a specified intent, it's 'View' */ + if (desc->intent == NULL) + return (strcmp(name, "View") == 0); + + if (fz_is_name(desc->intent)) + { + char *intent = fz_to_name(desc->intent); + if (strcmp(intent, "All") == 0) + return 1; + return (strcmp(intent, name) == 0); + } + if (!fz_is_array(desc->intent)) + return 0; + + len = fz_array_len(desc->intent); + for (i=0; i < len; i++) + { + char *intent = fz_to_name(fz_array_get(desc->intent, i)); + if (strcmp(intent, "All") == 0) + return 1; + if (strcmp(intent, name) == 0) + return 1; + } + return 0; +} static int -pdf_is_hidden_ocg(fz_obj *xobj, char *target) +pdf_is_hidden_ocg(fz_obj *ocg, pdf_csi *csi, fz_obj *rdb) { - char target_state[16]; - fz_obj *obj; + char event_state[16]; + fz_obj *obj, *obj2; + char *type; + pdf_ocg_descriptor *desc = csi->xref->ocg; + + /* If no ocg descriptor, everything is visible */ + if (desc == NULL) + return 0; + + /* If we've been handed a name, look it up in the properties. */ + if (fz_is_name(ocg)) + { + ocg = fz_dict_gets(fz_dict_gets(rdb, "Properties"), fz_to_name(ocg)); + } + /* If we haven't been given an ocg at all, then we're visible */ + if (ocg == NULL) + return 0; + + fz_strlcpy(event_state, csi->event, sizeof event_state); + fz_strlcat(event_state, "State", sizeof event_state); + + type = fz_to_name(fz_dict_gets(ocg, "Type")); + + if (strcmp(type, "OCG") == 0) + { + /* An Optional Content Group */ + int num = fz_to_num(ocg); + int gen = fz_to_gen(ocg); + int len = desc->len; + int i; - fz_strlcpy(target_state, target, sizeof target_state); - fz_strlcat(target_state, "State", sizeof target_state); + for (i = 0; i < len; i++) + { + if (desc->ocgs[i].num == num && desc->ocgs[i].gen == gen) + { + if (desc->ocgs[i].state == 0) + return 1; /* If off, hidden */ + break; + } + } + + /* Check Intents; if our intent is not part of the set given + * by the current config, we should ignore it. */ + obj = fz_dict_gets(ocg, "Intent"); + if (fz_is_name(obj)) + { + /* If it doesn't match, it's hidden */ + if (ocg_intents_include(desc, fz_to_name(obj)) == 0) + return 1; + } + else if (fz_is_array(obj)) + { + int match = 0; + len = fz_array_len(obj); + for (i=0; i