diff options
author | Robin Watts <robin.watts@artifex.com> | 2011-11-24 19:28:25 +0000 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2011-11-25 19:10:53 +0000 |
commit | 6e14149d3e915f559f99276a525862e28d6f0478 (patch) | |
tree | 7caac4b663cb1ac0f1bbfad3a7f89317a644366f | |
parent | 7aeb37fc5be2388ad71d9eab04b539c2c49f024e (diff) | |
download | mupdf-6e14149d3e915f559f99276a525862e28d6f0478.tar.xz |
First cut at support for OCGs in mupdf (bug 692314)
When opening a file, create a pdf_ocg_descriptor that lists the OCGs
in a file. Add a new function to allow us to set the configuration
in use (currently just the default one).
This sets the states of the OCGs as appropriate. When decoding the
file respect the states of the OCGs.
This results in Invite.pdf rendering correctly.
There is more to be done in this area (with automatic setting of
OCGs by language/zoom level etc), but this is a good start.
-rw-r--r-- | pdf/mupdf.h | 19 | ||||
-rw-r--r-- | pdf/pdf_interpret.c | 270 | ||||
-rw-r--r-- | pdf/pdf_xref.c | 159 |
3 files changed, 421 insertions, 27 deletions
diff --git a/pdf/mupdf.h b/pdf/mupdf.h index 22e087f9..e03f6efc 100644 --- a/pdf/mupdf.h +++ b/pdf/mupdf.h @@ -45,6 +45,8 @@ char *pdf_from_ucs2(unsigned short *str); typedef struct pdf_xref_entry_s pdf_xref_entry; typedef struct pdf_crypt_s pdf_crypt; +typedef struct pdf_ocg_descriptor_s pdf_ocg_descriptor; +typedef struct pdf_ocg_entry_s pdf_ocg_entry; struct pdf_xref_entry_s { @@ -55,6 +57,20 @@ struct pdf_xref_entry_s int type; /* 0=unset (f)ree i(n)use (o)bjstm */ }; +struct pdf_ocg_entry_s +{ + int num; + int gen; + int state; +}; + +struct pdf_ocg_descriptor_s +{ + int len; + pdf_ocg_entry *ocgs; + fz_obj *intent; +}; + struct pdf_xref_s { fz_stream *file; @@ -63,6 +79,7 @@ struct pdf_xref_s int file_size; pdf_crypt *crypt; fz_obj *trailer; + pdf_ocg_descriptor *ocg; int len; pdf_xref_entry *table; @@ -474,7 +491,7 @@ void pdf_free_page(pdf_page *page); * Content stream parsing */ -fz_error pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *target); +fz_error pdf_run_page_with_usage(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm, char *event); fz_error pdf_run_page(pdf_xref *xref, pdf_page *page, fz_device *dev, fz_matrix ctm); fz_error pdf_run_glyph(pdf_xref *xref, fz_obj *resources, fz_buffer *contents, fz_device *dev, fz_matrix ctm); diff --git a/pdf/pdf_interpret.c b/pdf/pdf_interpret.c index 752d9426..55e66e61 100644 --- a/pdf/pdf_interpret.c +++ b/pdf/pdf_interpret.c @@ -67,7 +67,7 @@ struct pdf_csi_s pdf_xref *xref; /* usage mode for optional content groups */ - char *target; /* "View", "Print", "Export" */ + char *event; /* "View", "Print", "Export" */ /* interpreter stack */ fz_obj *obj; @@ -79,6 +79,7 @@ struct pdf_csi_s int xbalance; int in_text; + int in_hidden_ocg; /* path object state */ fz_path *path; @@ -102,24 +103,194 @@ static fz_error pdf_run_buffer(pdf_csi *csi, fz_obj *rdb, fz_buffer *contents); static fz_error pdf_run_xobject(pdf_csi *csi, fz_obj *resources, pdf_xobject *xobj, fz_matrix transform); static void pdf_show_pattern(pdf_csi *csi, pdf_pattern *pat, fz_rect area, int what); +static int +ocg_intents_include(pdf_ocg_descriptor *desc, char *name) +{ + int i, len; + + if (strcmp(name, "All") == 0) + return 1; + + /* In the absence of a specified intent, it's 'View' */ + if (desc->intent == NULL) + return (strcmp(name, "View") == 0); + + if (fz_is_name(desc->intent)) + { + char *intent = fz_to_name(desc->intent); + if (strcmp(intent, "All") == 0) + return 1; + return (strcmp(intent, name) == 0); + } + if (!fz_is_array(desc->intent)) + return 0; + + len = fz_array_len(desc->intent); + for (i=0; i < len; i++) + { + char *intent = fz_to_name(fz_array_get(desc->intent, i)); + if (strcmp(intent, "All") == 0) + return 1; + if (strcmp(intent, name) == 0) + return 1; + } + return 0; +} static int -pdf_is_hidden_ocg(fz_obj *xobj, char *target) +pdf_is_hidden_ocg(fz_obj *ocg, pdf_csi *csi, fz_obj *rdb) { - char target_state[16]; - fz_obj *obj; + char event_state[16]; + fz_obj *obj, *obj2; + char *type; + pdf_ocg_descriptor *desc = csi->xref->ocg; + + /* If no ocg descriptor, everything is visible */ + if (desc == NULL) + return 0; + + /* If we've been handed a name, look it up in the properties. */ + if (fz_is_name(ocg)) + { + ocg = fz_dict_gets(fz_dict_gets(rdb, "Properties"), fz_to_name(ocg)); + } + /* If we haven't been given an ocg at all, then we're visible */ + if (ocg == NULL) + return 0; + + fz_strlcpy(event_state, csi->event, sizeof event_state); + fz_strlcat(event_state, "State", sizeof event_state); + + type = fz_to_name(fz_dict_gets(ocg, "Type")); + + if (strcmp(type, "OCG") == 0) + { + /* An Optional Content Group */ + int num = fz_to_num(ocg); + int gen = fz_to_gen(ocg); + int len = desc->len; + int i; - fz_strlcpy(target_state, target, sizeof target_state); - fz_strlcat(target_state, "State", sizeof target_state); + for (i = 0; i < len; i++) + { + if (desc->ocgs[i].num == num && desc->ocgs[i].gen == gen) + { + if (desc->ocgs[i].state == 0) + return 1; /* If off, hidden */ + break; + } + } + + /* Check Intents; if our intent is not part of the set given + * by the current config, we should ignore it. */ + obj = fz_dict_gets(ocg, "Intent"); + if (fz_is_name(obj)) + { + /* If it doesn't match, it's hidden */ + if (ocg_intents_include(desc, fz_to_name(obj)) == 0) + return 1; + } + else if (fz_is_array(obj)) + { + int match = 0; + len = fz_array_len(obj); + for (i=0; i<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; } /* @@ -174,6 +345,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); @@ -189,6 +363,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) @@ -270,6 +447,9 @@ pdf_show_path(pdf_csi *csi, int doclose, int dofill, int dostroke, int even_odd) csi->clip = 0; } + if (csi->in_hidden_ocg > 0) + dostroke = dofill = 0; + if (dofill || dostroke) pdf_begin_group(csi, bbox); @@ -370,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); @@ -653,14 +836,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(sizeof(pdf_csi)); csi->xref = xref; csi->dev = dev; - csi->target = target; + csi->event = event; csi->top = 0; csi->obj = NULL; @@ -670,6 +853,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(); csi->clip = 0; @@ -1258,8 +1442,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 fz_error pdf_run_BI(pdf_csi *csi, fz_obj *rdb, fz_stream *file) @@ -1308,6 +1517,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) @@ -1404,7 +1620,7 @@ static fz_error pdf_run_Do(pdf_csi *csi, fz_obj *rdb) if (!fz_is_name(subtype)) return fz_throw("no XObject subtype specified"); - if (pdf_is_hidden_ocg(obj, csi->target)) + if (pdf_is_hidden_ocg(fz_dict_gets(obj, "OC"), csi, rdb)) return fz_okay; if (!strcmp(fz_to_name(subtype), "Form") && fz_dict_gets(obj, "Subtype2")) @@ -1457,6 +1673,8 @@ static fz_error 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) @@ -2021,7 +2239,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'): error = pdf_run_BI(csi, rdb, file); if (error) @@ -2251,7 +2469,7 @@ pdf_run_buffer(pdf_csi *csi, fz_obj *rdb, fz_buffer *contents) } fz_error -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; fz_error error; @@ -2261,7 +2479,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); error = pdf_run_buffer(csi, page->resources, page->contents); pdf_free_csi(csi); if (error) @@ -2279,11 +2497,11 @@ 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(annot->obj, target)) - continue; - - csi = pdf_new_csi(xref, dev, ctm, target); - error = pdf_run_xobject(csi, page->resources, annot->ap, annot->matrix); + csi = pdf_new_csi(xref, dev, ctm, event); + if (!pdf_is_hidden_ocg(fz_dict_gets(annot->obj, "OC"), csi, page->resources)) + { + error = pdf_run_xobject(csi, page->resources, annot->ap, annot->matrix); + } pdf_free_csi(csi); if (error) return fz_rethrow(error, "cannot parse annotation appearance stream"); diff --git a/pdf/pdf_xref.c b/pdf/pdf_xref.c index bd25004e..a14ac6d9 100644 --- a/pdf/pdf_xref.c +++ b/pdf/pdf_xref.c @@ -508,6 +508,156 @@ pdf_load_xref(pdf_xref *xref, char *buf, int bufsize) return fz_okay; } +fz_error +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 fz_okay; + else + return fz_throw("Unknown OCG config (None known!)"); + } + if (config == 0) + { + cobj = fz_dict_gets(obj, "D"); + if (cobj == NULL) + return fz_throw("No default OCG config"); + } + else + { + cobj = fz_array_get(fz_dict_gets(obj, "Configs"), config); + if (cobj == NULL) + return fz_throw("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) */ + return fz_okay; +} + +static fz_error +pdf_read_ocg(pdf_xref *xref) +{ + fz_obj *obj, *ocg; + int len, i; + pdf_ocg_descriptor *desc; + + obj = fz_dict_gets(fz_dict_gets(xref->trailer, "Root"), "OCProperties"); + if (obj == NULL) + return fz_okay; + ocg = fz_dict_gets(obj, "OCGs"); + if (ocg == NULL || !fz_is_array(ocg)) + /* Not ever supposed to happen, but live with it. */ + return fz_okay; + len = fz_array_len(ocg); + desc = fz_malloc(sizeof(*desc)); + desc->len = len; + desc->ocgs = fz_calloc(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; + + return pdf_ocg_set_config(xref, 0); +} + +static void +pdf_free_ocg(pdf_ocg_descriptor *desc) +{ + if (desc == NULL) + return; + + if (desc->intent) + fz_drop_obj(desc->intent); + fz_free(desc->ocgs); + fz_free(desc); +} + /* * Initialize and load xref tables. * If password is not null, try to decrypt. @@ -632,6 +782,13 @@ pdf_open_xref_with_stream(pdf_xref **xrefp, fz_stream *file, char *password) } } + error = pdf_read_ocg(xref); + if (error) + { + pdf_free_xref(xref); + return fz_rethrow(error, "Broken Optional Content"); + } + *xrefp = xref; return fz_okay; } @@ -678,6 +835,8 @@ pdf_free_xref(pdf_xref *xref) if (xref->crypt) pdf_free_crypt(xref->crypt); + pdf_free_ocg(xref->ocg); + fz_free(xref); } |