summaryrefslogtreecommitdiff
path: root/pdf/pdf_interpret.c
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2011-11-24 19:28:25 +0000
committerRobin Watts <robin.watts@artifex.com>2011-11-25 19:10:53 +0000
commit6e14149d3e915f559f99276a525862e28d6f0478 (patch)
tree7caac4b663cb1ac0f1bbfad3a7f89317a644366f /pdf/pdf_interpret.c
parent7aeb37fc5be2388ad71d9eab04b539c2c49f024e (diff)
downloadmupdf-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.
Diffstat (limited to 'pdf/pdf_interpret.c')
-rw-r--r--pdf/pdf_interpret.c270
1 files changed, 244 insertions, 26 deletions
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");