summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2011-11-25 19:42:34 +0000
committerRobin Watts <robin.watts@artifex.com>2011-11-25 19:42:34 +0000
commitbdf5c8848a2de071c8380fab86a1a49215ed5ee7 (patch)
tree8ff2e40bca4f337e5db112284992e6be67cea820
parent5f6c8d94faecc0bd87113a138befe554ab2172b2 (diff)
parent6e14149d3e915f559f99276a525862e28d6f0478 (diff)
downloadmupdf-bdf5c8848a2de071c8380fab86a1a49215ed5ee7.tar.xz
Merge branch 'master' into context
-rw-r--r--apps/pdfapp.c9
-rw-r--r--apps/pdfclean.c46
-rw-r--r--draw/draw_device.c124
-rw-r--r--fitz/base_object.c12
-rw-r--r--fitz/dev_text.c77
-rw-r--r--fitz/fitz.h1
-rw-r--r--ios/About.pdfbin131735 -> 0 bytes
-rw-r--r--ios/About.xpsbin0 -> 76673 bytes
-rw-r--r--ios/Info.plist4
-rw-r--r--ios/MuPDF.xcodeproj/project.pbxproj73
-rw-r--r--ios/build_libs.sh10
-rw-r--r--ios/document.c128
-rw-r--r--ios/document.h6
-rw-r--r--ios/main.m492
-rw-r--r--pdf/mupdf.h19
-rw-r--r--pdf/pdf_crypt.c6
-rw-r--r--pdf/pdf_image.c12
-rw-r--r--pdf/pdf_interpret.c283
-rw-r--r--pdf/pdf_repair.c21
-rw-r--r--pdf/pdf_xref.c171
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
deleted file mode 100644
index 763c467e..00000000
--- a/ios/About.pdf
+++ /dev/null
Binary files differ
diff --git a/ios/About.xps b/ios/About.xps
new file mode 100644
index 00000000..3fcec260
--- /dev/null
+++ b/ios/About.xps
Binary files differ
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
diff --git a/ios/main.m b/ios/main.m
index 3a6a45f3..7b977928 100644
--- a/ios/main.m
+++ b/ios/main.m
@@ -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);
}