diff options
-rw-r--r-- | apps/pdfapp.c | 9 | ||||
-rw-r--r-- | apps/pdfclean.c | 46 | ||||
-rw-r--r-- | draw/draw_device.c | 124 | ||||
-rw-r--r-- | fitz/base_object.c | 12 | ||||
-rw-r--r-- | fitz/dev_text.c | 77 | ||||
-rw-r--r-- | fitz/fitz.h | 1 | ||||
-rw-r--r-- | ios/About.pdf | bin | 131735 -> 0 bytes | |||
-rw-r--r-- | ios/About.xps | bin | 0 -> 76673 bytes | |||
-rw-r--r-- | ios/Info.plist | 4 | ||||
-rw-r--r-- | ios/MuPDF.xcodeproj/project.pbxproj | 73 | ||||
-rw-r--r-- | ios/build_libs.sh | 10 | ||||
-rw-r--r-- | ios/document.c | 128 | ||||
-rw-r--r-- | ios/document.h | 6 | ||||
-rw-r--r-- | ios/main.m | 492 | ||||
-rw-r--r-- | pdf/mupdf.h | 19 | ||||
-rw-r--r-- | pdf/pdf_crypt.c | 6 | ||||
-rw-r--r-- | pdf/pdf_image.c | 12 | ||||
-rw-r--r-- | pdf/pdf_interpret.c | 283 | ||||
-rw-r--r-- | pdf/pdf_repair.c | 21 | ||||
-rw-r--r-- | pdf/pdf_xref.c | 171 |
20 files changed, 1212 insertions, 282 deletions
diff --git a/apps/pdfapp.c b/apps/pdfapp.c index fed8a025..57f5e8e8 100644 --- a/apps/pdfapp.c +++ b/apps/pdfapp.c @@ -157,6 +157,15 @@ static void pdfapp_open_pdf(pdfapp_t *app, char *filename, int fd) if (obj) app->doctitle = pdf_to_utf8(ctx, 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(ctx, app->doctitle); + } /* * Start at first page diff --git a/apps/pdfclean.c b/apps/pdfclean.c index c77139ff..2c492951 100644 --- a/apps/pdfclean.c +++ b/apps/pdfclean.c @@ -287,7 +287,7 @@ static void renumberobjs(void) static void retainpages(int argc, char **argv) { - fz_obj *oldroot, *root, *pages, *kids, *countobj, *parent; + fz_obj *oldroot, *root, *pages, *kids, *countobj, *parent, *olddests; /* Load the old page tree */ fz_try(xref->ctx) @@ -299,9 +299,11 @@ static void retainpages(int argc, char **argv) die(fz_error_note(1, "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(ctx, 2); fz_dict_puts(root, "Type", fz_dict_gets(oldroot, "Type")); @@ -373,6 +375,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(ctx, 1); + fz_obj *dests = fz_new_dict(ctx, 1); + fz_obj *names_list = fz_new_array(ctx, 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(ctx, 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); + } } /* @@ -792,7 +829,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(); diff --git a/draw/draw_device.c b/draw/draw_device.c index 5407d618..7e40ef3f 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; @@ -47,18 +62,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 @@ -89,6 +95,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(dev->ctx, sizeof(*stack) * max); + memcpy(stack, dev->stack, sizeof(*stack) * dev->stack_max); + } + else + { + stack = fz_resize_array(dev->ctx, dev->stack, max, sizeof(*stack)); + } + dev->stack = stack; + dev->stack_max = max; +} + static void fz_knockout_begin(fz_draw_device *dev) { fz_bbox bbox; @@ -98,11 +122,8 @@ static void fz_knockout_begin(fz_draw_device *dev) if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0) return; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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); @@ -158,11 +179,8 @@ static void fz_knockout_end(fz_draw_device *dev) if ((dev->blendmode & FZ_BLEND_KNOCKOUT) == 0) return; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (dev->top > 0) { @@ -323,11 +341,8 @@ fz_draw_clip_path(fz_device *devp, fz_path *path, fz_rect *rect, int even_odd, f fz_pixmap *mask, *dest, *shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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); @@ -394,11 +409,8 @@ fz_draw_clip_stroke_path(fz_device *devp, fz_path *path, fz_rect *rect, fz_strok fz_pixmap *mask, *dest, *shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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; @@ -607,11 +619,8 @@ fz_draw_clip_text(fz_device *devp, 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(dev->ctx, "assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (accumulate == 0) { @@ -700,11 +709,8 @@ fz_draw_clip_stroke_text(fz_device *devp, fz_text *text, fz_stroke_state *stroke fz_pixmap *glyph; int i, x, y, gid; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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)); @@ -1041,11 +1047,8 @@ fz_draw_clip_image_mask(fz_device *devp, fz_pixmap *image, fz_rect *rect, fz_mat fz_pixmap *scaled = NULL; int dx, dy; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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"); @@ -1184,11 +1187,8 @@ fz_draw_begin_mask(fz_device *devp, fz_rect rect, int luminosity, fz_colorspace fz_pixmap *shape = dev->shape; fz_bbox bbox; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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); @@ -1246,11 +1246,8 @@ fz_draw_end_mask(fz_device *devp) fz_bbox bbox; int luminosity; - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "assert: too many buffers on stack"); - return; - } + if (dev->top == dev->stack_max) + fz_grow_stack(dev); if (dev->top > 0) { @@ -1303,7 +1300,7 @@ fz_draw_begin_group(fz_device *devp, fz_rect rect, int isolated, int knockout, i fz_bbox bbox; fz_pixmap *dest, *shape; - if (dev->top == STACK_SIZE) + if (dev->top == dev->stack_max) { fz_warn(dev->ctx, "assert: too many buffers on stack"); return; @@ -1433,11 +1430,8 @@ fz_draw_begin_tile(fz_device *devp, fz_rect area, fz_rect view, float xstep, flo /* area, view, xstep, ystep are in pattern space */ /* ctm maps from pattern space to device space */ - if (dev->top == STACK_SIZE) - { - fz_warn(dev->ctx, "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); @@ -1523,6 +1517,8 @@ fz_draw_free_user(fz_device *devp) /* TODO: pop and free the stacks */ if (dev->top > 0) fz_warn(dev->ctx, "items left on stack in draw device: %d", dev->top); + if (dev->stack != &dev->init_stack[0]) + fz_free(dev->ctx, dev->stack); fz_free_gel(dev->gel); fz_free(devp->ctx, dev); } @@ -1540,6 +1536,8 @@ fz_new_draw_device(fz_context *ctx, fz_glyph_cache *cache, fz_pixmap *dest) ddev->blendmode = 0; ddev->flags = 0; ddev->ctx = ctx; + ddev->stack = &ddev->init_stack[0]; + ddev->stack_max = STACK_SIZE; ddev->scissor.x0 = dest->x; ddev->scissor.y0 = dest->y; diff --git a/fitz/base_object.c b/fitz/base_object.c index b1feeb2c..2876f363 100644 --- a/fitz/base_object.c +++ b/fitz/base_object.c @@ -501,6 +501,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/dev_text.c b/fitz/dev_text.c index 2e63aa9d..bf304778 100644 --- a/fitz/dev_text.c +++ b/fitz/dev_text.c @@ -37,16 +37,15 @@ fz_free_text_span(fz_context *ctx, fz_text_span *span) { fz_text_span *next; - do + while (span) { - next = span->next; if (span->font) fz_drop_font(ctx, span->font); + next = span->next; fz_free(ctx, span->text); fz_free(ctx, span); span = next; } - while (span != NULL); } static void @@ -160,32 +159,34 @@ fz_debug_text_span_xml(fz_text_span *span) char buf[10]; int c, n, k, i; - printf("<span font=\"%s\" size=\"%g\" wmode=\"%d\" eol=\"%d\">\n", - span->font ? span->font->name : "NULL", span->size, span->wmode, span->eol); - - for (i = 0; i < span->len; i++) + while (span) { - printf("\t<char ucs=\""); - c = span->text[i].c; - if (c < 128) - putchar(c); - else + printf("<span font=\"%s\" size=\"%g\" wmode=\"%d\" eol=\"%d\">\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("\t<char ucs=\""); + c = span->text[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("</span>\n"); + printf("</span>\n"); - if (span->next) - fz_debug_text_span_xml(span->next); + span = span->next; + } } void @@ -194,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 diff --git a/fitz/fitz.h b/fitz/fitz.h index 0a3875c7..ae2c9105 100644 --- a/fitz/fitz.h +++ b/fitz/fitz.h @@ -478,6 +478,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); diff --git a/ios/About.pdf b/ios/About.pdf Binary files differdeleted file mode 100644 index 763c467e..00000000 --- a/ios/About.pdf +++ /dev/null diff --git a/ios/About.xps b/ios/About.xps Binary files differnew file mode 100644 index 00000000..3fcec260 --- /dev/null +++ b/ios/About.xps diff --git a/ios/Info.plist b/ios/Info.plist index 116bacee..85213b7c 100644 --- a/ios/Info.plist +++ b/ios/Info.plist @@ -11,7 +11,7 @@ <key>CFBundleIconFiles</key> <array/> <key>CFBundleIdentifier</key> - <string>com.artifex.${PRODUCT_NAME:rfc1034identifier}</string> + <string>com.artifex.mupdf</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> @@ -19,7 +19,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>0.9</string> + <string>0.9.1</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> diff --git a/ios/MuPDF.xcodeproj/project.pbxproj b/ios/MuPDF.xcodeproj/project.pbxproj index 61542780..73fe67c6 100644 --- a/ios/MuPDF.xcodeproj/project.pbxproj +++ b/ios/MuPDF.xcodeproj/project.pbxproj @@ -8,19 +8,12 @@ /* Begin PBXBuildFile section */ 9644E9A0146ACEC000E5B70A /* document.c in Sources */ = {isa = PBXBuildFile; fileRef = 9644E99E146ACEC000E5B70A /* document.c */; }; - 9683F619145F4F84000E1607 /* About.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 9683F618145F4F84000E1607 /* About.pdf */; }; + 9668C8D91476A30200D7BA52 /* About.xps in Resources */ = {isa = PBXBuildFile; fileRef = 9668C8D81476A30200D7BA52 /* About.xps */; }; 968F2E9C14539C880085264E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9B14539C880085264E /* UIKit.framework */; }; 968F2E9E14539C880085264E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9D14539C880085264E /* Foundation.framework */; }; 968F2EA014539C880085264E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2E9F14539C880085264E /* CoreGraphics.framework */; }; 968F2EB014539CDA0085264E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 968F2E9014539BEB0085264E /* main.m */; }; - 968F2EBB14539F350085264E /* libfitz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB314539F350085264E /* libfitz.a */; }; - 968F2EBC14539F350085264E /* libfreetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB414539F350085264E /* libfreetype.a */; }; - 968F2EBD14539F350085264E /* libjbig2dec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB514539F350085264E /* libjbig2dec.a */; }; - 968F2EBE14539F350085264E /* libjpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB614539F350085264E /* libjpeg.a */; }; - 968F2EBF14539F350085264E /* libmupdf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB714539F350085264E /* libmupdf.a */; }; - 968F2EC014539F350085264E /* libmuxps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB814539F350085264E /* libmuxps.a */; }; - 968F2EC114539F350085264E /* libopenjpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EB914539F350085264E /* libopenjpeg.a */; }; - 968F2EC214539F350085264E /* libz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968F2EBA14539F350085264E /* libz.a */; }; + 96A4739B147C1C3A003D757D /* libLibraries.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 968461E214642DB00012AE09 /* libLibraries.a */; }; 96BD2B38145AC485001CEBC3 /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 96BD2B35145AC485001CEBC3 /* Icon-72.png */; }; 96BD2B39145AC485001CEBC3 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 96BD2B36145AC485001CEBC3 /* Icon.png */; }; 96F2341514603FBA004A8A22 /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 96F2341414603FBA004A8A22 /* Icon@2x.png */; }; @@ -39,7 +32,7 @@ /* Begin PBXFileReference section */ 9644E99E146ACEC000E5B70A /* document.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = document.c; sourceTree = "<group>"; }; 9644E99F146ACEC000E5B70A /* document.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = document.h; sourceTree = "<group>"; }; - 9683F618145F4F84000E1607 /* About.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = About.pdf; sourceTree = "<group>"; }; + 9668C8D81476A30200D7BA52 /* About.xps */ = {isa = PBXFileReference; lastKnownFileType = file; path = About.xps; sourceTree = "<group>"; }; 968461E214642DB00012AE09 /* libLibraries.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLibraries.a; sourceTree = BUILT_PRODUCTS_DIR; }; 968F2E8E14539BEB0085264E /* build_libs.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_libs.sh; sourceTree = "<group>"; }; 968F2E8F14539BEB0085264E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; @@ -48,14 +41,6 @@ 968F2E9B14539C880085264E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 968F2E9D14539C880085264E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 968F2E9F14539C880085264E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; - 968F2EB314539F350085264E /* libfitz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfitz.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB414539F350085264E /* libfreetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfreetype.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB514539F350085264E /* libjbig2dec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjbig2dec.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB614539F350085264E /* libjpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libjpeg.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB714539F350085264E /* libmupdf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmupdf.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB814539F350085264E /* libmuxps.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmuxps.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EB914539F350085264E /* libopenjpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libopenjpeg.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 968F2EBA14539F350085264E /* libz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libz.a; sourceTree = BUILT_PRODUCTS_DIR; }; 96BD2B35145AC485001CEBC3 /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72.png"; sourceTree = "<group>"; }; 96BD2B36145AC485001CEBC3 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; }; 96F2341414603FBA004A8A22 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; }; @@ -69,14 +54,7 @@ 968F2E9C14539C880085264E /* UIKit.framework in Frameworks */, 968F2E9E14539C880085264E /* Foundation.framework in Frameworks */, 968F2EA014539C880085264E /* CoreGraphics.framework in Frameworks */, - 968F2EBB14539F350085264E /* libfitz.a in Frameworks */, - 968F2EBC14539F350085264E /* libfreetype.a in Frameworks */, - 968F2EBD14539F350085264E /* libjbig2dec.a in Frameworks */, - 968F2EBE14539F350085264E /* libjpeg.a in Frameworks */, - 968F2EBF14539F350085264E /* libmupdf.a in Frameworks */, - 968F2EC014539F350085264E /* libmuxps.a in Frameworks */, - 968F2EC114539F350085264E /* libopenjpeg.a in Frameworks */, - 968F2EC214539F350085264E /* libz.a in Frameworks */, + 96A4739B147C1C3A003D757D /* libLibraries.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,7 +64,7 @@ 968F2E9214539BF10085264E /* Sources */ = { isa = PBXGroup; children = ( - 9683F618145F4F84000E1607 /* About.pdf */, + 9668C8D81476A30200D7BA52 /* About.xps */, 96BD2B36145AC485001CEBC3 /* Icon.png */, 96F2341414603FBA004A8A22 /* Icon@2x.png */, 96BD2B35145AC485001CEBC3 /* Icon-72.png */, @@ -118,26 +96,10 @@ name = Frameworks; sourceTree = "<group>"; }; - 968F2EB214539F230085264E /* Libraries */ = { - isa = PBXGroup; - children = ( - 968F2EB314539F350085264E /* libfitz.a */, - 968F2EB414539F350085264E /* libfreetype.a */, - 968F2EB514539F350085264E /* libjbig2dec.a */, - 968F2EB614539F350085264E /* libjpeg.a */, - 968F2EB714539F350085264E /* libmupdf.a */, - 968F2EB814539F350085264E /* libmuxps.a */, - 968F2EB914539F350085264E /* libopenjpeg.a */, - 968F2EBA14539F350085264E /* libz.a */, - ); - name = Libraries; - sourceTree = "<group>"; - }; 96A3B27414539BAD00D0A895 = { isa = PBXGroup; children = ( 968F2E9214539BF10085264E /* Sources */, - 968F2EB214539F230085264E /* Libraries */, 968F2E9A14539C880085264E /* Frameworks */, 968F2E9814539C880085264E /* Products */, ); @@ -212,8 +174,8 @@ files = ( 96BD2B38145AC485001CEBC3 /* Icon-72.png in Resources */, 96BD2B39145AC485001CEBC3 /* Icon.png in Resources */, - 9683F619145F4F84000E1607 /* About.pdf in Resources */, 96F2341514603FBA004A8A22 /* Icon@2x.png in Resources */, + 9668C8D91476A30200D7BA52 /* About.xps in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -228,14 +190,7 @@ inputPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/libmupdf.a", - "$(DERIVED_FILE_DIR)/libfitz.a", - "$(DERIVED_FILE_DIR)/libmuxps.a", - "$(DERIVED_FILE_DIR)/libfreetype.a", - "$(DERIVED_FILE_DIR)/libjbig2dec.a", - "$(DERIVED_FILE_DIR)/libjpeg.a", - "$(DERIVED_FILE_DIR)/libopenjpeg.a", - "$(DERIVED_FILE_DIR)/libz.a", + "$(DERIVED_FILE_DIR)/libLibraries.a", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -269,9 +224,9 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = Libraries; SDKROOT = iphoneos; + SKIP_INSTALL = YES; }; name = Debug; }; @@ -279,9 +234,9 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; PRODUCT_NAME = Libraries; SDKROOT = iphoneos; + SKIP_INSTALL = YES; }; name = Release; }; @@ -290,7 +245,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -309,9 +264,10 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ..; INFOPLIST_FILE = Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -323,7 +279,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; COPY_PHASE_STRIP = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -335,10 +291,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ..; INFOPLIST_FILE = Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 4.3; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/ios/build_libs.sh b/ios/build_libs.sh index beea9cde..72c07ce0 100644 --- a/ios/build_libs.sh +++ b/ios/build_libs.sh @@ -24,10 +24,10 @@ export OUT=build/$build-$OS-$ARCHS echo Building libraries for $ARCHS. make -C .. libs || exit 1 -echo Copying files into $BUILT_PRODUCTS_DIR. - -mkdir -p "$BUILT_PRODUCTS_DIR" -cp ../$OUT/lib*.a $BUILT_PRODUCTS_DIR -ranlib $BUILT_PRODUCTS_DIR/lib*.a +echo Assembling final library in $TARGET_BUILD_DIR/. +mkdir -p "$TARGET_BUILD_DIR" +rm -f $TARGET_BUILD_DIR/libLibraries.a +ar cru $TARGET_BUILD_DIR/libLibraries.a ../$OUT/*.o +ranlib $TARGET_BUILD_DIR/libLibraries.a echo Done. diff --git a/ios/document.c b/ios/document.c index 6d30a0bc..ffb92519 100644 --- a/ios/document.c +++ b/ios/document.c @@ -3,6 +3,8 @@ #include "xps/muxps.h" #include "document.h" +#include <ctype.h> // for tolower() + struct document * open_document(char *filename) { @@ -12,7 +14,7 @@ open_document(char *filename) struct document *doc = fz_malloc(sizeof *doc); memset(doc, 0, sizeof *doc); doc->number = -1; - error = pdf_open_xref(&doc->pdf, filename, ""); + error = pdf_open_xref(&doc->pdf, filename, NULL); if (error) { fz_free(doc); fz_rethrow(error, "cannot open pdf document"); @@ -43,6 +45,24 @@ open_document(char *filename) } } +int +needs_password(struct document *doc) +{ + if (doc->pdf) { + return pdf_needs_password(doc->pdf); + } + return 0; +} + +int +authenticate_password(struct document *doc, char *password) +{ + if (doc->pdf) { + return pdf_authenticate_password(doc->pdf, password); + } + return 1; +} + fz_outline * load_outline(struct document *doc) { @@ -78,7 +98,6 @@ load_page(struct document *doc, int number) pdf_free_page(doc->pdf_page); } doc->pdf_page = NULL; -printf("load pdf page %d\n", number); error = pdf_load_page(&doc->pdf_page, doc->pdf, number); if (error) fz_catch(error, "cannot load page %d", number); @@ -87,7 +106,6 @@ printf("load pdf page %d\n", number); if (doc->xps_page) xps_free_page(doc->xps, doc->xps_page); doc->xps_page = NULL; -printf("load xps page %d\n", number); error = xps_load_page(&doc->xps_page, doc->xps, number); if (error) fz_catch(error, "cannot load page %d", number); @@ -137,6 +155,110 @@ draw_page(struct document *doc, int number, fz_device *dev, fz_matrix ctm) fz_flush_warnings(); } +static int +charat(fz_text_span *span, int idx) +{ + int ofs = 0; + while (span) { + if (idx < ofs + span->len) + return span->text[idx - ofs].c; + if (span->eol) { + if (idx == ofs + span->len) + return ' '; + ofs ++; + } + ofs += span->len; + span = span->next; + } + return 0; +} + +static fz_bbox +bboxat(fz_text_span *span, int idx) +{ + int ofs = 0; + while (span) { + if (idx < ofs + span->len) + return span->text[idx - ofs].bbox; + if (span->eol) { + if (idx == ofs + span->len) + return fz_empty_bbox; + ofs ++; + } + ofs += span->len; + span = span->next; + } + return fz_empty_bbox; +} + +static int +textlen(fz_text_span *span) +{ + int len = 0; + while (span) { + len += span->len; + if (span->eol) + len ++; + span = span->next; + } + return len; +} + +static int +match(fz_text_span *span, char *s, int n) +{ + int start = n, c; + while (*s) { + s += chartorune(&c, s); + if (c == ' ' && charat(span, n) == ' ') { + while (charat(span, n) == ' ') + n++; + } else { + if (tolower(c) != tolower(charat(span, n))) + return 0; + n++; + } + } + return n - start; +} + +int +search_page(struct document *doc, int number, char *needle) +{ + int pos, len, i, n; + + if (strlen(needle) == 0) + return 0; + + fz_text_span *text = fz_new_text_span(); + fz_device *dev = fz_new_text_device(text); + draw_page(doc, number, dev, fz_identity); + fz_free_device(dev); + + doc->hit_count = 0; + + len = textlen(text); + for (pos = 0; pos < len; pos++) { + n = match(text, needle, pos); + if (n) { + for (i = 0; i < n; i++) { + fz_bbox r = bboxat(text, pos + i); + if (!fz_is_empty_bbox(r) && doc->hit_count < nelem(doc->hit_bbox)) + doc->hit_bbox[doc->hit_count++] = r; + } + } + } + + fz_free_text_span(text); + return doc->hit_count; +} + +fz_bbox +search_result_bbox(struct document *doc, int i) +{ + return doc->hit_bbox[i]; +} + void close_document(struct document *doc) { diff --git a/ios/document.h b/ios/document.h index 1f59ccdd..797b1e8f 100644 --- a/ios/document.h +++ b/ios/document.h @@ -20,13 +20,19 @@ struct document int number; pdf_page *pdf_page; xps_page *xps_page; + fz_bbox hit_bbox[500]; + int hit_count; }; struct document *open_document(char *filename); +int needs_password(struct document *doc); +int authenticate_password(struct document *doc, char *password); fz_outline *load_outline(struct document *doc); int count_pages(struct document *doc); void measure_page(struct document *doc, int number, float *w, float *h); void draw_page(struct document *doc, int number, fz_device *dev, fz_matrix ctm); +int search_page(struct document *doc, int number, char *needle); +fz_bbox search_result_bbox(struct document *doc, int i); void close_document(struct document *doc); #endif @@ -12,6 +12,8 @@ #define GAP 20 #define INDICATOR_Y -44-24 +#define SLIDER_W (width - GAP - 24) +#define SEARCH_W (width - GAP - 170) static dispatch_queue_t queue; static fz_glyph_cache *glyphcache = NULL; @@ -21,8 +23,13 @@ static float screenScale = 1; { NSArray *files; NSTimer *timer; + struct document *_doc; // temporaries for juggling password dialog + NSString *_filename; } - (void) openDocument: (NSString*)filename; +- (void) askForPassword: (NSString*)prompt; +- (void) onPasswordOkay; +- (void) onPasswordCancel; - (void) reload; @end @@ -35,6 +42,16 @@ static float screenScale = 1; - (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages; @end +@interface MuHitView : UIView +{ + CGSize pageSize; + int hitCount; + CGRect hitRects[500]; +} +- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc; +- (void) setPageSize: (CGSize)s; +@end + @interface MuPageView : UIScrollView <UIScrollViewDelegate> { struct document *doc; @@ -42,6 +59,8 @@ static float screenScale = 1; UIActivityIndicatorView *loadingView; UIImageView *imageView; UIImageView *tileView; + MuHitView *hitView; + CGSize pageSize; CGRect tileFrame; float tileScale; BOOL cancel; @@ -53,29 +72,37 @@ static float screenScale = 1; - (void) loadTile; - (void) willRotate; - (void) resetZoomAnimated: (BOOL)animated; +- (void) showSearchResults: (int)count; +- (void) clearSearchResults; - (int) number; @end -@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UIGestureRecognizerDelegate> +@interface MuDocumentController : UIViewController <UIScrollViewDelegate, UISearchBarDelegate> { struct document *doc; NSString *key; - NSMutableSet *visiblePages; - NSMutableSet *recycledPages; MuOutlineController *outline; UIScrollView *canvas; UILabel *indicator; UISlider *slider; - UIBarButtonItem *wrapper; // for slider + UISearchBar *searchBar; + UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton; + UIBarButtonItem *sliderWrapper; + int searchPage; + int cancelSearch; int width; // current screen size int height; int current; // currently visible page int scroll_animating; // stop view updates during scrolling animations } -- (id) initWithFile: (NSString*)filename; +- (id) initWithFilename: (NSString*)nsfilename document: (struct document *)aDoc; - (void) createPageView: (int)number; - (void) gotoPage: (int)number animated: (BOOL)animated; - (void) onShowOutline: (id)sender; +- (void) onShowSearch: (id)sender; +- (void) onCancelSearch: (id)sender; +- (void) resetSearch; +- (void) showSearchResults: (int)count forPage: (int)number; - (void) onSlide: (id)sender; - (void) onTap: (UITapGestureRecognizer*)sender; - (void) showNavigationBar; @@ -147,15 +174,16 @@ static void releasePixmap(void *info, const void *data, size_t size) static UIImage *newImageWithPixmap(fz_pixmap *pix) { CGDataProviderRef cgdata = CGDataProviderCreateWithData(pix, pix->samples, pix->w * 4 * pix->h, releasePixmap); + CGColorSpaceRef cgcolor = CGColorSpaceCreateDeviceRGB(); CGImageRef cgimage = CGImageCreate(pix->w, pix->h, 8, 32, 4 * pix->w, - CGColorSpaceCreateDeviceRGB(), - kCGBitmapByteOrderDefault, + cgcolor, kCGBitmapByteOrderDefault, cgdata, NULL, NO, kCGRenderingIntentDefault); UIImage *image = [[UIImage alloc] initWithCGImage: cgimage scale: screenScale orientation: UIImageOrientationUp]; CGDataProviderRelease(cgdata); + CGColorSpaceRelease(cgcolor); CGImageRelease(cgimage); return image; } @@ -170,6 +198,13 @@ static CGSize fitPageToScreen(CGSize page, CGSize screen) return CGSizeMake(hscale, vscale); } +static CGSize measurePage(struct document *doc, int number) +{ + CGSize pageSize; + measure_page(doc, number, &pageSize.width, &pageSize.height); + return pageSize; +} + static UIImage *renderPage(struct document *doc, int number, CGSize screenSize) { CGSize pageSize; @@ -317,19 +352,81 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, { int row = [indexPath row]; if (row == 0) - [self openDocument: @"../MuPDF.app/About.pdf"]; + [self openDocument: @"../MuPDF.app/About.xps"]; else [self openDocument: [files objectAtIndex: row - 1]]; } -- (void) openDocument: (NSString*)filename +- (void) openDocument: (NSString*)nsfilename { - MuDocumentController *document = [[MuDocumentController alloc] initWithFile: filename]; + char filename[PATH_MAX]; + + dispatch_sync(queue, ^{}); + + strcpy(filename, [NSHomeDirectory() UTF8String]); + strcat(filename, "/Documents/"); + strcat(filename, [nsfilename UTF8String]); + + printf("open document '%s'\n", filename); + + _filename = [nsfilename retain]; + _doc = open_document(filename); + if (!_doc) { + showAlert(@"Cannot open document"); + return; + } + + if (needs_password(_doc)) + [self askForPassword: @"'%@' needs a password:"]; + else + [self onPasswordOkay]; +} + +- (void) askForPassword: (NSString*)prompt +{ + UIAlertView *passwordAlertView = [[UIAlertView alloc] + initWithTitle: @"Password Protected" + message: [NSString stringWithFormat: prompt, [_filename lastPathComponent]] + delegate: self + cancelButtonTitle: @"Cancel" + otherButtonTitles: @"Done", nil]; + [passwordAlertView setAlertViewStyle: UIAlertViewStyleSecureTextInput]; + [passwordAlertView show]; + [passwordAlertView release]; +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + char *password = (char*) [[[alertView textFieldAtIndex: 0] text] UTF8String]; + [alertView dismissWithClickedButtonIndex: buttonIndex animated: TRUE]; + if (buttonIndex == 1) { + if (authenticate_password(_doc, password)) + [self onPasswordOkay]; + else + [self askForPassword: @"Wrong password for '%@'. Try again:"]; + } else { + [self onPasswordCancel]; + } +} + +- (void) onPasswordOkay +{ + MuDocumentController *document = [[MuDocumentController alloc] initWithFilename: _filename document: _doc]; if (document) { [self setTitle: @"Library"]; [[self navigationController] pushViewController: document animated: YES]; [document release]; } + [_filename release]; + _doc = NULL; +} + +- (void) onPasswordCancel +{ + [_filename release]; + printf("close document (password cancel)\n"); + close_document(_doc); + _doc = NULL; } @end @@ -404,6 +501,53 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, #pragma mark - +@implementation MuHitView + +- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc +{ + self = [super initWithFrame: CGRectMake(0,0,100,100)]; + if (self) { + [self setOpaque: NO]; + + pageSize = CGSizeMake(100,100); + + for (int i = 0; i < n && i < nelem(hitRects); i++) { + fz_bbox bbox = search_result_bbox(doc, i); // this is thread-safe enough + hitRects[i].origin.x = bbox.x0; + hitRects[i].origin.y = bbox.y0; + hitRects[i].size.width = bbox.x1 - bbox.x0; + hitRects[i].size.height = bbox.y1 - bbox.y0; + } + hitCount = n; + } + return self; +} + +- (void) setPageSize: (CGSize)s +{ + pageSize = s; + // if page takes a long time to load we may have drawn at the initial (wrong) size + [self setNeedsDisplay]; +} + +- (void) drawRect: (CGRect)r +{ + CGSize scale = fitPageToScreen(pageSize, self.bounds.size); + + [[UIColor colorWithRed: 0.3 green: 0.3 blue: 1 alpha: 0.5] set]; + + for (int i = 0; i < hitCount; i++) { + CGRect rect = hitRects[i]; + rect.origin.x *= scale.width; + rect.origin.y *= scale.height; + rect.size.width *= scale.width; + rect.size.height *= scale.height; + UIRectFill(rect); + } +} + +@end + @implementation MuPageView - (id) initWithFrame: (CGRect)frame document: (struct document*)aDoc page: (int)aNumber @@ -444,6 +588,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, __block id block_self = self; // don't auto-retain self! dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; }); } else { + [hitView release]; [tileView release]; [loadingView release]; [imageView release]; @@ -456,6 +601,30 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, return number; } +- (void) showSearchResults: (int)count +{ + if (hitView) { + [hitView removeFromSuperview]; + [hitView release]; + hitView = nil; + } + hitView = [[MuHitView alloc] initWithSearchResults: count forDocument: doc]; + if (imageView) { + [hitView setFrame: [imageView frame]]; + [hitView setPageSize: pageSize]; + } + [self addSubview: hitView]; +} + +- (void) clearSearchResults +{ + if (hitView) { + [hitView removeFromSuperview]; + [hitView release]; + hitView = nil; + } +} + - (void) resetZoomAnimated: (BOOL)animated { // discard tile and any pending tile jobs @@ -485,8 +654,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, dispatch_async(queue, ^{ if (!cancel) { printf("render page %d\n", number); + CGSize size = measurePage(doc, number); UIImage *image = renderPage(doc, number, self.bounds.size); dispatch_async(dispatch_get_main_queue(), ^{ + pageSize = size; [self displayImage: image]; [image release]; }); @@ -504,10 +675,15 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, loadingView = nil; } + if (hitView) + [hitView setPageSize: pageSize]; + if (!imageView) { imageView = [[UIImageView alloc] initWithImage: image]; imageView.opaque = YES; [self addSubview: imageView]; + if (hitView) + [self bringSubviewToFront: hitView]; } else { [imageView setImage: image]; } @@ -530,7 +706,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, dispatch_async(queue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size); - if (fabs(scale.width - 1) > 0.1) + if (fabs(scale.width - 1) > 0.01) [self loadPage]; }); }); @@ -542,6 +718,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self layoutIfNeeded]; } + } - (void) willRotate @@ -577,6 +754,9 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, loadingView.frame = frameToCenter; else imageView.frame = frameToCenter; + + if (hitView && imageView) + [hitView setFrame: [imageView frame]]; } - (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView @@ -586,7 +766,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) loadTile { - CGSize pageSize = self.bounds.size; + CGSize screenSize = self.bounds.size; tileFrame.origin = self.contentOffset; tileFrame.size = self.bounds.size; @@ -616,7 +796,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, } printf("render tile\n"); - UIImage *image = renderTile(doc, number, pageSize, viewFrame, scale); + UIImage *image = renderTile(doc, number, screenSize, viewFrame, scale); dispatch_async(dispatch_get_main_queue(), ^{ isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale; @@ -632,6 +812,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, tileView = [[UIImageView alloc] initWithFrame: frame]; [tileView setImage: image]; [self addSubview: tileView]; + if (hitView) + [self bringSubviewToFront: hitView]; } else { printf("discard tile\n"); } @@ -666,53 +848,40 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [self loadTile]; } +- (void) scrollViewDidZoom: (UIScrollView*)scrollView +{ + if (hitView && imageView) + [hitView setFrame: [imageView frame]]; +} + @end #pragma mark - @implementation MuDocumentController -- (id) initWithFile: (NSString*)nsfilename +- (id) initWithFilename: (NSString*)filename document: (struct document *)aDoc { - char filename[PATH_MAX]; - self = [super init]; if (!self) return nil; - key = [nsfilename retain]; + key = [filename retain]; + doc = aDoc; dispatch_sync(queue, ^{}); - strcpy(filename, [NSHomeDirectory() UTF8String]); - strcat(filename, "/Documents/"); - strcat(filename, [nsfilename UTF8String]); - - printf("open document '%s'\n", filename); - - doc = open_document(filename); - if (!doc) { - showAlert(@"Cannot open document"); - [self release]; - return nil; - } - - NSMutableArray *titles = [[NSMutableArray alloc] init]; - NSMutableArray *pages = [[NSMutableArray alloc] init]; fz_outline *root = load_outline(doc); if (root) { + NSMutableArray *titles = [[NSMutableArray alloc] init]; + NSMutableArray *pages = [[NSMutableArray alloc] init]; flattenOutline(titles, pages, root, 0); + if ([titles count]) + outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages]; + [titles release]; + [pages release]; fz_free_outline(root); } - if ([titles count]) { - outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages]; - [[self navigationItem] setRightBarButtonItem: - [[UIBarButtonItem alloc] - initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks - target:self action:@selector(onShowOutline:)]]; - } - [titles release]; - [pages release]; return self; } @@ -729,9 +898,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [view setAutoresizesSubviews: YES]; - visiblePages = [[NSMutableSet alloc] init]; - recycledPages = [[NSMutableSet alloc] init]; - canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)]; [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [canvas setPagingEnabled: YES]; @@ -752,31 +918,56 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]]; [indicator setTextColor: [UIColor whiteColor]]; + [view addSubview: canvas]; + [view addSubview: indicator]; + slider = [[UISlider alloc] initWithFrame: CGRectZero]; [slider setMinimumValue: 0]; [slider setMaximumValue: count_pages(doc) - 1]; [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged]; - [view addSubview: canvas]; - [view addSubview: indicator]; + sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; - wrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; - [self setToolbarItems: [NSArray arrayWithObjects: wrapper, nil]]; + [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]]; + + // Set up the buttons on the navigation and search bar + + if (outline) { + outlineButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks + target:self action:@selector(onShowOutline:)]; + } + cancelButton = [[UIBarButtonItem alloc] + initWithTitle: @"Cancel" style: UIBarButtonItemStyleBordered + target:self action:@selector(onCancelSearch:)]; + searchButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemSearch + target:self action:@selector(onShowSearch:)]; + prevButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemRewind + target:self action:@selector(onSearchPrev:)]; + nextButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward + target:self action:@selector(onSearchNext:)]; + + searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)]; + [searchBar setPlaceholder: @"Search"]; + [searchBar setDelegate: self]; + // HACK to make transparent background + [[searchBar.subviews objectAtIndex:0] removeFromSuperview]; + + [prevButton setEnabled: NO]; + [nextButton setEnabled: NO]; + + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: searchButton, outlineButton, nil]]; + + // TODO: add activityindicator to search bar [self setView: view]; [view release]; } -- (void) viewDidUnload -{ - [visiblePages release]; visiblePages = nil; - [recycledPages release]; recycledPages = nil; - [indicator release]; indicator = nil; - [slider release]; slider = nil; - [wrapper release]; wrapper = nil; - [canvas release]; canvas = nil; -} - - (void) dealloc { if (doc) { @@ -786,6 +977,18 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, close_document(self_doc); }); } + + [indicator release]; indicator = nil; + [slider release]; slider = nil; + [sliderWrapper release]; sliderWrapper = nil; + [searchBar release]; searchBar = nil; + [outlineButton release]; outlineButton = nil; + [searchButton release]; searchButton = nil; + [cancelButton release]; cancelButton = nil; + [prevButton release]; prevButton = nil; + [nextButton release]; nextButton = nil; + [canvas release]; canvas = nil; + [outline release]; [key release]; [super dealloc]; @@ -807,7 +1010,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [canvas setContentSize: CGSizeMake(count_pages(doc) * width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; - [wrapper setWidth: width - GAP - 24]; + [sliderWrapper setWidth: SLIDER_W]; + [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; [[self navigationController] setToolbarHidden: NO animated: animated]; } @@ -844,6 +1048,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, - (void) hideNavigationBar { if (![[self navigationController] isNavigationBarHidden]) { + [searchBar resignFirstResponder]; + [UIView beginAnimations: @"MuNavBar" context: NULL]; [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)]; @@ -868,6 +1074,139 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [[self navigationController] pushViewController: outline animated: YES]; } +- (void) onShowSearch: (id)sender +{ + [[self navigationItem] setTitleView: searchBar]; + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: nextButton, prevButton, nil]]; + [[self navigationItem] setLeftBarButtonItem: cancelButton]; + [searchBar becomeFirstResponder]; +} + +- (void) onCancelSearch: (id)sender +{ + cancelSearch = YES; + [searchBar resignFirstResponder]; + [[self navigationItem] setTitleView: nil]; + [[self navigationItem] setRightBarButtonItems: + [NSArray arrayWithObjects: searchButton, outlineButton, nil]]; + [[self navigationItem] setLeftBarButtonItem: nil]; + [self resetSearch]; +} + +- (void) resetSearch +{ + searchPage = -1; + for (MuPageView *view in [canvas subviews]) + [view clearSearchResults]; +} + +- (void) showSearchResults: (int)count forPage: (int)number +{ + printf("search found match on page %d\n", number); + searchPage = number; + [self gotoPage: number animated: NO]; + for (MuPageView *view in [canvas subviews]) + if ([view number] == number) + [view showSearchResults: count]; + else + [view clearSearchResults]; +} + +- (void) searchInDirection: (int)dir +{ + UITextField *searchField; + char *needle; + int start; + + [searchBar resignFirstResponder]; + + if (searchPage == current) + start = current + dir; + else + start = current; + + needle = strdup([[searchBar text] UTF8String]); + + searchField = nil; + for (id view in [searchBar subviews]) + if ([view isKindOfClass: [UITextField class]]) + searchField = view; + + [prevButton setEnabled: NO]; + [nextButton setEnabled: NO]; + [searchField setEnabled: NO]; + + cancelSearch = NO; + + dispatch_async(queue, ^{ + for (int i = start; i >= 0 && i < count_pages(doc); i += dir) { + int n = search_page(doc, i, needle); + if (n) { + dispatch_async(dispatch_get_main_queue(), ^{ + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + [searchField setEnabled: YES]; + [self showSearchResults: n forPage: i]; + free(needle); + }); + return; + } + if (cancelSearch) { + dispatch_async(dispatch_get_main_queue(), ^{ + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + [searchField setEnabled: YES]; + free(needle); + }); + return; + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + printf("no search results found\n"); + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + [searchField setEnabled: YES]; + UIAlertView *alert = [[UIAlertView alloc] + initWithTitle: @"No matches found for:" + message: [NSString stringWithUTF8String: needle] + delegate: nil + cancelButtonTitle: @"Close" + otherButtonTitles: nil]; + [alert show]; + [alert release]; + free(needle); + }); + }); +} + +- (void) onSearchPrev: (id)sender +{ + [self searchInDirection: -1]; +} + +- (void) onSearchNext: (id)sender +{ + [self searchInDirection: 1]; +} + +- (void) searchBarSearchButtonClicked: (UISearchBar*)sender +{ + [self onSearchNext: sender]; +} + +- (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText +{ + [self resetSearch]; + if ([[searchBar text] length] > 0) { + [prevButton setEnabled: YES]; + [nextButton setEnabled: YES]; + } else { + [prevButton setEnabled: NO]; + [nextButton setEnabled: NO]; + } +} + - (void) onSlide: (id)sender { int number = [slider value]; @@ -918,22 +1257,26 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, count_pages(doc)]]; [slider setValue: current]; - // swap the page views in and out + // swap the distant page views out - for (MuPageView *view in visiblePages) { + NSMutableSet *invisiblePages = [[NSMutableSet alloc] init]; + for (MuPageView *view in [canvas subviews]) { if ([view number] != current) [view resetZoomAnimated: YES]; - if ([view number] < current - 2 || [view number] > current + 2) { - [recycledPages addObject: view]; - [view removeFromSuperview]; - } + if ([view number] < current - 2 || [view number] > current + 2) + [invisiblePages addObject: view]; } - [visiblePages minusSet: recycledPages]; - [recycledPages removeAllObjects]; // don't bother recycling them... + for (MuPageView *view in invisiblePages) + [view removeFromSuperview]; + [invisiblePages release]; // don't bother recycling them... [self createPageView: current]; [self createPageView: current - 1]; [self createPageView: current + 1]; + + // reset search results when page has flipped + if (current != searchPage) + [self resetSearch]; } - (void) createPageView: (int)number @@ -946,7 +1289,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, found = 1; if (!found) { MuPageView *view = [[MuPageView alloc] initWithFrame: CGRectMake(number * width, 0, width-GAP, height) document: doc page: number]; - [visiblePages addObject: view]; [canvas addSubview: view]; [view release]; } @@ -958,6 +1300,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, number = 0; if (number >= count_pages(doc)) number = count_pages(doc) - 1; + if (current == number) + return; if (animated) { // setContentOffset:animated: does not use the normal animation // framework. It also doesn't play nice with the tap gesture @@ -973,7 +1317,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)]; - for (MuPageView *view in visiblePages) + for (MuPageView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; @@ -982,7 +1326,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, [UIView commitAnimations]; } else { - for (MuPageView *view in visiblePages) + for (MuPageView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; } @@ -1008,20 +1352,22 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize, width = size.width; height = size.height; - [wrapper setWidth: width - GAP - 24]; + [sliderWrapper setWidth: SLIDER_W]; + [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; + [[[self navigationController] toolbar] setNeedsLayout]; // force layout! // use max_width so we don't clamp the content offset too early during animation [canvas setContentSize: CGSizeMake(count_pages(doc) * max_width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; - for (MuPageView *view in visiblePages) { + for (MuPageView *view in [canvas subviews]) { if ([view number] == current) { [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)]; [view willRotate]; } } - for (MuPageView *view in visiblePages) { + for (MuPageView *view in [canvas subviews]) { if ([view number] != current) { [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)]; [view willRotate]; diff --git a/pdf/mupdf.h b/pdf/mupdf.h index d3eb72e6..79560dab 100644 --- a/pdf/mupdf.h +++ b/pdf/mupdf.h @@ -45,6 +45,8 @@ char *pdf_from_ucs2(fz_context *ctx, 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_context *ctx; @@ -64,6 +80,7 @@ struct pdf_xref_s int file_size; pdf_crypt *crypt; fz_obj *trailer; + pdf_ocg_descriptor *ocg; int len; pdf_xref_entry *table; @@ -478,7 +495,7 @@ void pdf_free_page(fz_context *ctx, pdf_page *page); * Content stream parsing */ -void pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *target); +void pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *event); void pdf_run_page(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm); void pdf_run_glyph(pdf_xref *xref, fz_obj *resources, fz_buffer *contents, fz_device *dev, fz_matrix ctm); diff --git a/pdf/pdf_crypt.c b/pdf/pdf_crypt.c index 1ae16b37..98ad0159 100644 --- a/pdf/pdf_crypt.c +++ b/pdf/pdf_crypt.c @@ -722,7 +722,11 @@ pdf_crypt_obj_imp(fz_context *ctx, pdf_crypt *crypt, fz_obj *obj, unsigned char 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(ctx, "invalid string length for aes encryption"); else { diff --git a/pdf/pdf_image.c b/pdf/pdf_image.c index 438f4cef..849f703e 100644 --- a/pdf/pdf_image.c +++ b/pdf/pdf_image.c @@ -309,6 +309,18 @@ pdf_load_jpx_image(pdf_xref *xref, fz_obj *dict) img->mask = pdf_load_image_imp(xref, NULL, obj, NULL, 1); /* RJW: "cannot load image mask/softmask" */ } + + 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); + } } fz_catch(ctx) { diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index 85901539..0720f782 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 void pdf_run_buffer(pdf_csi *csi, fz_obj *rdb, fz_buffer *contents); static void 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_context *ctx, 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; + + 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; + } + } - fz_strlcpy(target_state, target, sizeof target_state); - fz_strlcat(target_state, "State", sizeof target_state); + /* 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<len; i++) { + match |= ocg_intents_include(desc, fz_to_name(fz_array_get(obj, i))); + if (match) + break; + } + /* If we don't match any, it's hidden */ + if (match == 0) + return 1; + } + else + { + /* If it doesn't match, it's hidden */ + if (ocg_intents_include(desc, "View") == 0) + return 1; + } + + /* FIXME: Currently we do a very simple check whereby we look + * at the Usage object (an Optional Content Usage Dictionary) + * and check to see if the corresponding 'event' key is on + * or off. + * + * Really we should only look at Usage dictionaries that + * correspond to entries in the AS list in the OCG config. + * Given that we don't handle Zoom or User, or Language + * dicts, this is not really a problem. */ + obj = fz_dict_gets(ocg, "Usage"); + if (!fz_is_dict(obj)) + return 0; + /* FIXME: Should look at Zoom (and return hidden if out of + * max/min range) */ + /* FIXME: Could provide hooks to the caller to check if + * User is appropriate - if not return hidden. */ + obj2 = fz_dict_gets(obj, csi->event); + if (strcmp(fz_to_name(fz_dict_gets(obj2, event_state)), "OFF") == 0) + { + return 1; + } + return 0; + } + else if (strcmp(type, "OCMD") == 0) + { + /* An Optional Content Membership Dictionary */ + char *name; + int combine, on; + + obj = fz_dict_gets(ocg, "VE"); + if (fz_is_array(obj)) { + /* FIXME: Calculate visibility from array */ + return 0; + } + name = fz_to_name(fz_dict_gets(ocg, "P")); + /* Set combine; Bit 0 set => AND, Bit 1 set => true means + * Off, otherwise true means On */ + if (strcmp(name, "AllOn") == 0) + { + combine = 1; + } + else if (strcmp(name, "AnyOff") == 0) + { + combine = 2; + } + else if (strcmp(name, "AllOff") == 0) + { + combine = 3; + } + else /* Assume it's the default (AnyOn) */ + { + combine = 0; + } - obj = fz_dict_gets(xobj, "OC"); - obj = fz_dict_gets(obj, "OCGs"); - if (fz_is_array(obj)) - obj = fz_array_get(obj, 0); - obj = fz_dict_gets(obj, "Usage"); - obj = fz_dict_gets(obj, target); - obj = fz_dict_gets(obj, target_state); - return !strcmp(fz_to_name(obj), "OFF"); + obj = fz_dict_gets(ocg, "OCGs"); + on = combine & 1; + if (fz_is_array(obj)) { + int i, len; + len = fz_array_len(obj); + for (i = 0; i < len; i++) + { + int hidden; + hidden = pdf_is_hidden_ocg(fz_array_get(obj, i), csi, rdb); + if ((combine & 1) == 0) + hidden = !hidden; + if (combine & 2) + on &= hidden; + else + on |= hidden; + } + } + else + { + on = pdf_is_hidden_ocg(obj, csi, rdb); + if ((combine & 1) == 0) + on = !on; + } + return !on; + } + /* No idea what sort of object this is - be visible */ + return 0; } /* @@ -172,6 +343,9 @@ pdf_show_shade(pdf_csi *csi, fz_shade *shd) pdf_gstate *gstate = csi->gstate + csi->gtop; fz_rect bbox; + if (csi->in_hidden_ocg > 0) + return; + bbox = fz_bound_shade(shd, gstate->ctm); pdf_begin_group(csi, bbox); @@ -187,6 +361,9 @@ pdf_show_image(pdf_csi *csi, fz_pixmap *image) pdf_gstate *gstate = csi->gstate + csi->gtop; fz_rect bbox; + if (csi->in_hidden_ocg > 0) + return; + bbox = fz_transform_rect(gstate->ctm, fz_unit_rect); if (image->mask) @@ -266,8 +443,12 @@ 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 (csi->in_hidden_ocg > 0) + dostroke = dofill = 0; + if (dofill || dostroke) pdf_begin_group(csi, bbox); @@ -369,6 +550,9 @@ pdf_flush_text(pdf_csi *csi) case 7: doclip = 1; break; } + if (csi->in_hidden_ocg > 0) + dostroke = dofill = 0; + bbox = fz_bound_text(text, gstate->ctm); pdf_begin_group(csi, bbox); @@ -656,14 +840,14 @@ pdf_init_gstate(pdf_gstate *gs, fz_matrix ctm) } static pdf_csi * -pdf_new_csi(pdf_xref *xref, fz_device *dev, fz_matrix ctm, char *target) +pdf_new_csi(pdf_xref *xref, fz_device *dev, fz_matrix ctm, char *event) { pdf_csi *csi; csi = fz_malloc(dev->ctx, sizeof(pdf_csi)); csi->xref = xref; csi->dev = dev; - csi->target = target; + csi->event = event; csi->top = 0; csi->obj = NULL; @@ -673,6 +857,7 @@ pdf_new_csi(pdf_xref *xref, fz_device *dev, fz_matrix ctm, char *target) csi->xbalance = 0; csi->in_text = 0; + csi->in_hidden_ocg = 0; csi->path = fz_new_path(xref->ctx); csi->clip = 0; @@ -1265,8 +1450,33 @@ pdf_run_extgstate(pdf_csi *csi, fz_obj *rdb, fz_obj *extgstate) * Operators */ -static void pdf_run_BDC(pdf_csi *csi) +static void pdf_run_BDC(pdf_csi *csi, fz_obj *rdb) { + fz_obj *ocg; + + /* If we are already in a hidden OCG, then we'll still be hidden - + * just increment the depth so we pop back to visibility when we've + * seen enough EDCs. */ + if (csi->in_hidden_ocg > 0) + { + csi->in_hidden_ocg++; + return; + } + + ocg = fz_dict_gets(fz_dict_gets(rdb, "Properties"), csi->name); + if (ocg == NULL) + { + /* No Properties array, or name not found in the properties + * means visible. */ + return; + } + if (strcmp(fz_to_name(fz_dict_gets(ocg, "Type")), "OCG") != 0) + { + /* Wrong type of property */ + return; + } + if (pdf_is_hidden_ocg(ocg, csi, rdb)) + csi->in_hidden_ocg++; } static void pdf_run_BI(pdf_csi *csi, fz_obj *rdb, fz_stream *file) @@ -1311,6 +1521,13 @@ static void pdf_run_B(pdf_csi *csi) static void pdf_run_BMC(pdf_csi *csi) { + /* If we are already in a hidden OCG, then we'll still be hidden - + * just increment the depth so we pop back to visibility when we've + * seen enough EDCs. */ + if (csi->in_hidden_ocg > 0) + { + csi->in_hidden_ocg++; + } } static void pdf_run_BT(pdf_csi *csi) @@ -1401,7 +1618,7 @@ static void pdf_run_Do(pdf_csi *csi, fz_obj *rdb) if (!fz_is_name(subtype)) fz_throw(ctx, "no XObject subtype specified"); - if (pdf_is_hidden_ocg(ctx, obj, csi->target)) + if (pdf_is_hidden_ocg(fz_dict_gets(obj, "OC"), csi, rdb)) return; if (!strcmp(fz_to_name(subtype), "Form") && fz_dict_gets(obj, "Subtype2")) @@ -1449,6 +1666,8 @@ static void pdf_run_Do(pdf_csi *csi, fz_obj *rdb) static void pdf_run_EMC(pdf_csi *csi) { + if (csi->in_hidden_ocg > 0) + csi->in_hidden_ocg--; } static void pdf_run_ET(pdf_csi *csi) @@ -1999,7 +2218,7 @@ pdf_run_keyword(pdf_csi *csi, fz_obj *rdb, fz_stream *file, char *buf) case A('\''): pdf_run_squote(csi); break; case A('B'): pdf_run_B(csi); break; case B('B','*'): pdf_run_Bstar(csi); break; - case C('B','D','C'): pdf_run_BDC(csi); break; + case C('B','D','C'): pdf_run_BDC(csi, rdb); break; case B('B','I'): pdf_run_BI(csi, rdb, file); /* RJW: "cannot draw inline image" */ @@ -2252,7 +2471,7 @@ pdf_run_buffer(pdf_csi *csi, fz_obj *rdb, fz_buffer *contents) } void -pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *target) +pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *event) { pdf_csi *csi; pdf_annot *annot; @@ -2262,7 +2481,7 @@ pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matri if (page->transparency) fz_begin_group(dev, fz_transform_rect(ctm, page->mediabox), 1, 0, 0, 1); - csi = pdf_new_csi(xref, dev, ctm, target); + csi = pdf_new_csi(xref, dev, ctm, event); fz_try(ctx) { pdf_run_buffer(csi, page->resources, page->contents); @@ -2286,18 +2505,18 @@ pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matri if (flags & (1 << 5)) /* NoView */ continue; - if (pdf_is_hidden_ocg(ctx, annot->obj, target)) - continue; - - csi = pdf_new_csi(xref, dev, ctm, target); - fz_try(ctx) - { - pdf_run_xobject(csi, page->resources, annot->ap, annot->matrix); - } - fz_catch(ctx) + csi = pdf_new_csi(xref, dev, ctm, event); + if (!pdf_is_hidden_ocg(fz_dict_gets(annot->obj, "OC"), csi, page->resources)) { - pdf_free_csi(csi); - fz_throw(ctx, "cannot parse annotation appearance stream"); + fz_try(ctx) + { + pdf_run_xobject(csi, page->resources, annot->ap, annot->matrix); + } + fz_catch(ctx) + { + pdf_free_csi(csi); + fz_throw(ctx, "cannot parse annotation appearance stream"); + } } pdf_free_csi(csi); } diff --git a/pdf/pdf_repair.c b/pdf/pdf_repair.c index 0a30f83b..abc443cc 100644 --- a/pdf/pdf_repair.c +++ b/pdf/pdf_repair.c @@ -33,8 +33,15 @@ pdf_repair_obj(fz_stream *file, char *buf, int cap, int *stmofsp, int *stmlenp, fz_obj *dict, *obj; /* Send NULL xref so we don't try to resolve references */ - dict = pdf_parse_dict(NULL, file, buf, cap); - /* RJW: "cannot parse object" */ + fz_try(ctx) + { + dict = pdf_parse_dict(NULL, file, buf, cap); + } + fz_catch(ctx) + { + /* Silently swallow the error */ + dict = fz_new_dict(ctx, 2); + } obj = fz_dict_gets(dict, "Type"); if (fz_is_name(obj) && !strcmp(fz_to_name(obj), "XRef")) @@ -66,13 +73,19 @@ 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 ) { tok = pdf_lex(file, buf, cap, &len); /* RJW: "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') { diff --git a/pdf/pdf_xref.c b/pdf/pdf_xref.c index cb538ae3..501db660 100644 --- a/pdf/pdf_xref.c +++ b/pdf/pdf_xref.c @@ -469,6 +469,165 @@ pdf_load_xref(pdf_xref *xref, char *buf, int bufsize) } } +void +pdf_ocg_set_config(pdf_xref *xref, int config) +{ + int i, j, len, len2; + pdf_ocg_descriptor *desc = xref->ocg; + fz_obj *obj, *cobj; + char *name; + + obj = fz_dict_gets(fz_dict_gets(xref->trailer, "Root"), "OCProperties"); + if (obj == NULL) + { + if (config == 0) + return; + else + fz_throw(xref->ctx, "Unknown OCG config (None known!)"); + } + if (config == 0) + { + cobj = fz_dict_gets(obj, "D"); + if (cobj == NULL) + fz_throw(xref->ctx, "No default OCG config"); + } + else + { + cobj = fz_array_get(fz_dict_gets(obj, "Configs"), config); + if (cobj == NULL) + fz_throw(xref->ctx, "Illegal OCG config"); + } + + if (desc->intent != NULL) + fz_drop_obj(desc->intent); + desc->intent = fz_dict_gets(cobj, "Intent"); + if (desc->intent != NULL) + fz_keep_obj(desc->intent); + + len = desc->len; + name = fz_to_name(fz_dict_gets(cobj, "BaseState")); + if (strcmp(name, "Unchanged") == 0) + { + /* Do nothing */ + } + else if (strcmp(name, "OFF") == 0) + { + for (i = 0; i < len; i++) + { + desc->ocgs[i].state = 0; + } + } + else /* Default to ON */ + { + for (i = 0; i < len; i++) + { + desc->ocgs[i].state = 1; + } + } + + obj = fz_dict_gets(cobj, "ON"); + len2 = fz_array_len(obj); + for (i = 0; i < len2; i++) + { + fz_obj *o = fz_array_get(obj, i); + int n = fz_to_num(o); + int g = fz_to_gen(o); + for (j=0; j < len; j++) + { + if (desc->ocgs[j].num == n && desc->ocgs[j].gen == g) + { + desc->ocgs[j].state = 1; + break; + } + } + } + + obj = fz_dict_gets(cobj, "OFF"); + len2 = fz_array_len(obj); + for (i = 0; i < len2; i++) + { + fz_obj *o = fz_array_get(obj, i); + int n = fz_to_num(o); + int g = fz_to_gen(o); + for (j=0; j < len; j++) + { + if (desc->ocgs[j].num == n && desc->ocgs[j].gen == g) + { + desc->ocgs[j].state = 0; + break; + } + } + } + + /* FIXME: Should make 'num configs' available in the descriptor. */ + /* FIXME: Should copy out 'Intent' here into the descriptor, and remove + * csi->intent in favour of that. */ + /* FIXME: Should copy 'AS' into the descriptor, and visibility + * decisions should respect it. */ + /* FIXME: Make 'Order' available via the descriptor (when we have an + * app that needs it) */ + /* FIXME: Make 'ListMode' available via the descriptor (when we have + * an app that needs it) */ + /* FIXME: Make 'RBGroups' available via the descriptor (when we have + * an app that needs it) */ + /* FIXME: Make 'Locked' available via the descriptor (when we have + * an app that needs it) */ +} + +static void +pdf_read_ocg(pdf_xref *xref) +{ + fz_obj *obj, *ocg; + int len, i; + pdf_ocg_descriptor * volatile desc; + fz_context *ctx = xref->ctx; + + obj = fz_dict_gets(fz_dict_gets(xref->trailer, "Root"), "OCProperties"); + if (obj == NULL) + return; + ocg = fz_dict_gets(obj, "OCGs"); + if (ocg == NULL || !fz_is_array(ocg)) + /* Not ever supposed to happen, but live with it. */ + return; + len = fz_array_len(ocg); + fz_try(ctx) + { + desc = fz_calloc(ctx, 1, sizeof(*desc)); + desc->len = len; + desc->ocgs = fz_calloc(ctx, len, sizeof(*desc->ocgs)); + desc->intent = NULL; + for (i=0; i < len; i++) + { + fz_obj *o = fz_array_get(ocg, i); + desc->ocgs[i].num = fz_to_num(o); + desc->ocgs[i].gen = fz_to_gen(o); + desc->ocgs[i].state = 0; + } + xref->ocg = desc; + } + fz_catch(ctx) + { + if (desc != NULL) + fz_free(ctx, desc->ocgs); + fz_free(ctx, desc); + fz_rethrow(ctx); + } + + pdf_ocg_set_config(xref, 0); +} + +static void +pdf_free_ocg(fz_context *ctx, pdf_ocg_descriptor *desc) +{ + if (desc == NULL) + return; + + if (desc->intent) + fz_drop_obj(desc->intent); + fz_free(ctx, desc->ocgs); + fz_free(ctx, desc); +} + /* * Initialize and load xref tables. * If password is not null, try to decrypt. @@ -591,6 +750,16 @@ pdf_open_xref_with_stream(fz_stream *file, char *password) fz_throw(ctx, "cannot open document"); } + fz_try(ctx) + { + pdf_read_ocg(xref); + } + fz_catch(ctx) + { + pdf_free_xref(xref); + fz_throw(ctx, "Broken Optional Content"); + } + return xref; } @@ -637,6 +806,8 @@ pdf_free_xref(pdf_xref *xref) if (xref->crypt) pdf_free_crypt(ctx, xref->crypt); + pdf_free_ocg(ctx, xref->ocg); + fz_free(ctx, xref); } |