diff options
Diffstat (limited to 'source/pdf')
-rw-r--r-- | source/pdf/pdf-imp.h | 9 | ||||
-rw-r--r-- | source/pdf/pdf-interpret.c | 226 | ||||
-rw-r--r-- | source/pdf/pdf-layer.c | 715 | ||||
-rw-r--r-- | source/pdf/pdf-op-run.c | 4 | ||||
-rw-r--r-- | source/pdf/pdf-run.c | 20 | ||||
-rw-r--r-- | source/pdf/pdf-xref.c | 161 |
6 files changed, 746 insertions, 389 deletions
diff --git a/source/pdf/pdf-imp.h b/source/pdf/pdf-imp.h new file mode 100644 index 00000000..37605c51 --- /dev/null +++ b/source/pdf/pdf-imp.h @@ -0,0 +1,9 @@ +#ifndef MUPDF_PDF_IMP_H +#define MUPDF_PDF_IMP_H + +#include "mupdf/pdf.h" + +void pdf_read_ocg(fz_context *ctx, pdf_document *doc); +void pdf_drop_ocg(fz_context *ctx, pdf_document *doc); + +#endif diff --git a/source/pdf/pdf-interpret.c b/source/pdf/pdf-interpret.c index 58999373..4a8a2a46 100644 --- a/source/pdf/pdf-interpret.c +++ b/source/pdf/pdf-interpret.c @@ -81,220 +81,6 @@ load_font_or_hail_mary(fz_context *ctx, pdf_document *doc, pdf_obj *rdb, pdf_obj return desc; } -static int -ocg_intents_include(fz_context *ctx, 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) - return (strcmp(name, "View") == 0); - - if (pdf_is_name(ctx, desc->intent)) - { - char *intent = pdf_to_name(ctx, desc->intent); - if (strcmp(intent, "All") == 0) - return 1; - return (strcmp(intent, name) == 0); - } - if (!pdf_is_array(ctx, desc->intent)) - return 0; - - len = pdf_array_len(ctx, desc->intent); - for (i=0; i < len; i++) - { - char *intent = pdf_to_name(ctx, pdf_array_get(ctx, 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, pdf_ocg_descriptor *desc, pdf_obj *rdb, const char *event, pdf_obj *ocg) -{ - char event_state[16]; - pdf_obj *obj, *obj2, *type; - - /* Avoid infinite recursions */ - if (pdf_obj_marked(ctx, ocg)) - return 0; - - /* If no event, everything is visible */ - if (!event) - return 0; - - /* If no ocg descriptor, everything is visible */ - if (!desc) - return 0; - - /* If we've been handed a name, look it up in the properties. */ - if (pdf_is_name(ctx, ocg)) - { - ocg = pdf_dict_get(ctx, pdf_dict_get(ctx, rdb, PDF_NAME_Properties), ocg); - } - /* If we haven't been given an ocg at all, then we're visible */ - if (!ocg) - return 0; - - fz_strlcpy(event_state, event, sizeof event_state); - fz_strlcat(event_state, "State", sizeof event_state); - - type = pdf_dict_get(ctx, ocg, PDF_NAME_Type); - - if (pdf_name_eq(ctx, type, PDF_NAME_OCG)) - { - /* An Optional Content Group */ - int default_value = 0; - int num = pdf_to_num(ctx, ocg); - int len = desc->len; - int i; - pdf_obj *es; - - /* by default an OCG is visible, unless it's explicitly hidden */ - for (i = 0; i < len; i++) - { - if (desc->ocgs[i].num == num) - { - default_value = desc->ocgs[i].state == 0; - break; - } - } - - /* Check Intents; if our intent is not part of the set given - * by the current config, we should ignore it. */ - obj = pdf_dict_get(ctx, ocg, PDF_NAME_Intent); - if (pdf_is_name(ctx, obj)) - { - /* If it doesn't match, it's hidden */ - if (ocg_intents_include(ctx, desc, pdf_to_name(ctx, obj)) == 0) - return 1; - } - else if (pdf_is_array(ctx, obj)) - { - int match = 0; - len = pdf_array_len(ctx, obj); - for (i=0; i<len; i++) { - match |= ocg_intents_include(ctx, desc, pdf_to_name(ctx, pdf_array_get(ctx, 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(ctx, 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 = pdf_dict_get(ctx, ocg, PDF_NAME_Usage); - if (!pdf_is_dict(ctx, obj)) - return default_value; - /* 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 = pdf_dict_gets(ctx, obj, event); - es = pdf_dict_gets(ctx, obj2, event_state); - if (pdf_name_eq(ctx, es, PDF_NAME_OFF)) - { - return 1; - } - if (pdf_name_eq(ctx, es, PDF_NAME_ON)) - { - return 0; - } - return default_value; - } - else if (pdf_name_eq(ctx, type, PDF_NAME_OCMD)) - { - /* An Optional Content Membership Dictionary */ - pdf_obj *name; - int combine, on; - - obj = pdf_dict_get(ctx, ocg, PDF_NAME_VE); - if (pdf_is_array(ctx, obj)) { - /* FIXME: Calculate visibility from array */ - return 0; - } - name = pdf_dict_get(ctx, ocg, PDF_NAME_P); - /* Set combine; Bit 0 set => AND, Bit 1 set => true means - * Off, otherwise true means On */ - if (pdf_name_eq(ctx, name, PDF_NAME_AllOn)) - { - combine = 1; - } - else if (pdf_name_eq(ctx, name, PDF_NAME_AnyOff)) - { - combine = 2; - } - else if (pdf_name_eq(ctx, name, PDF_NAME_AllOff)) - { - combine = 3; - } - else /* Assume it's the default (AnyOn) */ - { - combine = 0; - } - - if (pdf_mark_obj(ctx, ocg)) - return 0; /* Should never happen */ - fz_try(ctx) - { - obj = pdf_dict_get(ctx, ocg, PDF_NAME_OCGs); - on = combine & 1; - if (pdf_is_array(ctx, obj)) { - int i, len; - len = pdf_array_len(ctx, obj); - for (i = 0; i < len; i++) - { - int hidden = pdf_is_hidden_ocg(ctx, desc, rdb, event, pdf_array_get(ctx, obj, i)); - if ((combine & 1) == 0) - hidden = !hidden; - if (combine & 2) - on &= hidden; - else - on |= hidden; - } - } - else - { - on = pdf_is_hidden_ocg(ctx, desc, rdb, event, obj); - if ((combine & 1) == 0) - on = !on; - } - } - fz_always(ctx) - { - pdf_unmark_obj(ctx, ocg); - } - fz_catch(ctx) - { - fz_rethrow(ctx); - } - return !on; - } - /* No idea what sort of object this is - be visible */ - return 0; -} - static fz_image * parse_inline_image(fz_context *ctx, pdf_csi *csi, fz_stream *stm) { @@ -530,7 +316,7 @@ pdf_process_Do(fz_context *ctx, pdf_processor *proc, pdf_csi *csi) if (!pdf_is_name(ctx, subtype)) fz_throw(ctx, FZ_ERROR_GENERIC, "no XObject subtype specified"); - if (pdf_is_hidden_ocg(ctx, csi->doc->ocg, csi->rdb, proc->event, pdf_dict_get(ctx, xobj, PDF_NAME_OC))) + if (pdf_is_hidden_ocg(ctx, csi->doc->ocg, csi->rdb, proc->usage, pdf_dict_get(ctx, xobj, PDF_NAME_OC))) return; if (pdf_name_eq(ctx, subtype, PDF_NAME_Form)) @@ -726,7 +512,7 @@ pdf_process_BDC(fz_context *ctx, pdf_processor *proc, pdf_csi *csi) if (!pdf_name_eq(ctx, pdf_dict_get(ctx, cooked, PDF_NAME_Type), PDF_NAME_OCG)) return; - if (pdf_is_hidden_ocg(ctx, csi->doc->ocg, csi->rdb, proc->event, cooked)) + if (pdf_is_hidden_ocg(ctx, csi->doc->ocg, csi->rdb, proc->usage, cooked)) ++proc->hidden; } @@ -1263,18 +1049,18 @@ pdf_process_annot(fz_context *ctx, pdf_processor *proc, pdf_document *doc, pdf_p if (flags & (PDF_ANNOT_IS_INVISIBLE | PDF_ANNOT_IS_HIDDEN)) return; - if (proc->event) + if (proc->usage) { - if (!strcmp(proc->event, "Print") && !(flags & PDF_ANNOT_IS_PRINT)) + if (!strcmp(proc->usage, "Print") && !(flags & PDF_ANNOT_IS_PRINT)) return; - if (!strcmp(proc->event, "View") && (flags & PDF_ANNOT_IS_NO_VIEW)) + if (!strcmp(proc->usage, "View") && (flags & PDF_ANNOT_IS_NO_VIEW)) return; } /* TODO: NoZoom and NoRotate */ /* XXX what resources, if any, to use for this check? */ - if (pdf_is_hidden_ocg(ctx, doc->ocg, NULL, proc->event, pdf_dict_get(ctx, annot->obj, PDF_NAME_OC))) + if (pdf_is_hidden_ocg(ctx, doc->ocg, NULL, proc->usage, pdf_dict_get(ctx, annot->obj, PDF_NAME_OC))) return; if (proc->op_q && proc->op_cm && proc->op_Do_form && proc->op_Q && annot->ap) diff --git a/source/pdf/pdf-layer.c b/source/pdf/pdf-layer.c new file mode 100644 index 00000000..70dfcd50 --- /dev/null +++ b/source/pdf/pdf-layer.c @@ -0,0 +1,715 @@ +#include "mupdf/fitz.h" +#include "pdf-imp.h" + +/* + Notes on OCGs etc. + + PDF Documents may contain Optional Content Groups. Which of + these is shown at any given time is dependent on which + Optional Content Configuration Dictionary is in force at the + time. + + A pdf_document, once loaded, contains some state saying which + OCGs are enabled/disabled, and which 'Intent' (or 'Intents') + a file is being used for. This information is held outside of + the actual PDF file. + + An Intent (just 'View' or 'Design' or 'All', according to + PDF 2.0, but theoretically more) says which OCGs to consider + or ignore in calculating the visibility of content. The + Intent (or Intents, for there can be an array) is set by the + current OCCD. + + When first loaded, we turn all OCGs on, then load the default + OCCD. This may turn some OCGs off, and sets the document Intent. + + Callers can ask how many OCCDs there are, read the names/creators + for each, and then select any one of them. That updates which + OCGs are selected, and resets the Intent. + + Once an OCCD has been selected, a caller can enumerate the + 'displayable configuration'. This is a list of labels/radio + buttons/check buttons that can be used to enable/disable + given OCGs. The caller can then enable/disable OCGs by + asking to select (or toggle) given entries in that list. + + Thus the handling of radio button groups, and 'locked' + elements is kept within the core of MuPDF. + + Finally, the caller can set the 'usage' for a document. This + can be 'View', 'Print', or 'Export'. +*/ + +typedef struct +{ + pdf_obj *obj; + int state; +} pdf_ocg_entry; + +typedef struct +{ + int ocg; + const char *name; + int depth; + unsigned int button_flags : 2; + unsigned int locked : 1; +} pdf_ocg_ui; + +struct pdf_ocg_descriptor_s +{ + int current; + int num_configs; + + int len; + pdf_ocg_entry *ocgs; + + pdf_obj *intent; + char *usage; + + int num_ui_entries; + pdf_ocg_ui *ui; +}; + +int +pdf_count_layer_configs(fz_context *ctx, pdf_document *doc) +{ + /* If no OCProperties, then no OCGs */ + if (!doc || !doc->ocg) + return 0; + return doc->ocg->num_configs; +} + + +static int +count_entries(fz_context *ctx, pdf_obj *obj) +{ + int len = pdf_array_len(ctx, obj); + int i; + int count = 0; + + for (i = 0; i < len; i++) + { + pdf_obj *o = pdf_array_get(ctx, obj, i); + count += (pdf_is_array(ctx, o) ? count_entries(ctx, o) : 1); + } + return count; +} + +static pdf_ocg_ui * +populate_ui(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_ocg_ui *ui, pdf_obj *order, int depth, pdf_obj *rbgroups, pdf_obj *locked) +{ + int len = pdf_array_len(ctx, order); + int i, j; + + for (i = 0; i < len; i++) + { + pdf_obj *o = pdf_array_get(ctx, order, i); + if (pdf_is_array(ctx, o)) + { + ui = populate_ui(ctx, desc, ui, o, depth+1, rbgroups, locked); + continue; + } + ui->depth = depth; + if (pdf_is_string(ctx, o)) + { + ui->ocg = -1; + ui->name = pdf_to_str_buf(ctx, o); + ui->button_flags = PDF_LAYER_UI_LABEL; + ui->locked = 1; + ui++; + continue; + } + + for (j = 0; j < desc->len; j++) + { + if (!pdf_objcmp_resolve(ctx, o, desc->ocgs[j].obj)) + break; + } + if (j == desc->len) + continue; /* OCG not found in main list! Just ignore it */ + ui->ocg = j; + ui->name = pdf_to_str_buf(ctx, pdf_dict_get(ctx, o, PDF_NAME_Name)); + ui->button_flags = pdf_array_contains(ctx, o, rbgroups) ? PDF_LAYER_UI_RADIOBOX : PDF_LAYER_UI_CHECKBOX; + ui->locked = pdf_array_contains(ctx, o, locked); + ui++; + } + return ui; +} + +static void +drop_ui(fz_context *ctx, pdf_ocg_descriptor *desc) +{ + if (!desc) + return; + + fz_free(ctx, desc->ui); + desc->ui = NULL; +} + +static void +load_ui(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *ocprops, pdf_obj *occg) +{ + pdf_obj *order; + pdf_obj *rbgroups; + pdf_obj *locked; + int count; + + /* Count the number of entries */ + order = pdf_dict_get(ctx, occg, PDF_NAME_Order); + count = count_entries(ctx, order); + rbgroups = pdf_dict_get(ctx, occg, PDF_NAME_RBGroups); + locked = pdf_dict_get(ctx, occg, PDF_NAME_Locked); + + desc->num_ui_entries = count; + desc->ui = Memento_label(fz_calloc(ctx, count, sizeof(pdf_ocg_ui)), "pdf_ocg_ui"); + fz_try(ctx) + { + (void)populate_ui(ctx, desc, desc->ui, order, 0, rbgroups, locked); + } + fz_catch(ctx) + { + drop_ui(ctx, desc); + fz_rethrow(ctx); + } +} + +void +pdf_select_layer_config(fz_context *ctx, pdf_document *doc, int config) +{ + int i, j, len, len2; + pdf_ocg_descriptor *desc = doc->ocg; + pdf_obj *obj, *cobj; + pdf_obj *name; + + obj = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root), PDF_NAME_OCProperties); + if (!obj) + { + if (config == 0) + return; + else + fz_throw(ctx, FZ_ERROR_GENERIC, "Unknown Layer config (None known!)"); + } + + cobj = pdf_array_get(ctx, pdf_dict_get(ctx, obj, PDF_NAME_Configs), config); + if (!cobj) + { + if (config != 0) + fz_throw(ctx, FZ_ERROR_GENERIC, "Illegal Layer config"); + cobj = pdf_dict_get(ctx, obj, PDF_NAME_D); + if (!cobj) + fz_throw(ctx, FZ_ERROR_GENERIC, "No default Layer config"); + } + + pdf_drop_obj(ctx, desc->intent); + desc->intent = pdf_keep_obj(ctx, pdf_dict_get(ctx, cobj, PDF_NAME_Intent)); + + len = desc->len; + name = pdf_dict_get(ctx, cobj, PDF_NAME_BaseState); + if (pdf_name_eq(ctx, name, PDF_NAME_Unchanged)) + { + /* Do nothing */ + } + else if (pdf_name_eq(ctx, name, PDF_NAME_OFF)) + { + 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 = pdf_dict_get(ctx, cobj, PDF_NAME_ON); + len2 = pdf_array_len(ctx, obj); + for (i = 0; i < len2; i++) + { + pdf_obj *o = pdf_array_get(ctx, obj, i); + for (j=0; j < len; j++) + { + if (!pdf_objcmp_resolve(ctx, desc->ocgs[j].obj, o)) + { + desc->ocgs[j].state = 1; + break; + } + } + } + + obj = pdf_dict_get(ctx, cobj, PDF_NAME_OFF); + len2 = pdf_array_len(ctx, obj); + for (i = 0; i < len2; i++) + { + pdf_obj *o = pdf_array_get(ctx, obj, i); + for (j=0; j < len; j++) + { + if (!pdf_objcmp_resolve(ctx, desc->ocgs[j].obj, o)) + { + desc->ocgs[j].state = 0; + break; + } + } + } + + desc->current = config; + + drop_ui(ctx, desc); + load_ui(ctx, desc, obj, cobj); +} + +void +pdf_layer_config_info(fz_context *ctx, pdf_document *doc, int config_num, pdf_layer_config *info) +{ + pdf_obj *ocprops; + pdf_obj *obj; + + if (!info) + return; + + info->name = NULL; + info->creator = NULL; + + if (doc == NULL || doc->ocg == NULL) + return; + if (config_num < 0 || config_num >= doc->ocg->num_configs) + fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid layer config number"); + + ocprops = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties"); + if (!ocprops) + return; + + obj = pdf_dict_get(ctx, ocprops, PDF_NAME_Configs); + if (pdf_is_array(ctx, obj)) + obj = pdf_array_get(ctx, obj, config_num); + else if (config_num == 0) + obj = pdf_dict_get(ctx, ocprops, PDF_NAME_D); + else + fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid layer config number"); + + info->creator = pdf_to_str_buf(ctx, pdf_dict_get(ctx, obj, PDF_NAME_Creator)); + info->name = pdf_to_str_buf(ctx, pdf_dict_get(ctx, obj, PDF_NAME_Name)); +} + +void +pdf_drop_ocg(fz_context *ctx, pdf_document *doc) +{ + pdf_ocg_descriptor *desc; + int i; + + if (!doc) + return; + desc = doc->ocg; + if (!desc) + return; + + pdf_drop_obj(ctx, desc->intent); + for (i = 0; i < desc->len; i++) + pdf_drop_obj(ctx, desc->ocgs[i].obj); + fz_free(ctx, desc->ocgs); + fz_free(ctx, desc); +} + +static void +clear_radio_group(fz_context *ctx, pdf_document *doc, pdf_obj *ocg) +{ + pdf_obj *rbgroups = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties/RBGroups"); + int len, i; + + len = pdf_array_len(ctx, rbgroups); + for (i = 0; i < len; i++) + { + pdf_obj *group = pdf_array_get(ctx, rbgroups, i); + + if (pdf_array_contains(ctx, ocg, group)) + { + int len2 = pdf_array_len(ctx, group); + int j; + + for (j = 0; j < len2; j++) + { + pdf_obj *g = pdf_array_get(ctx, group, j); + int k; + for (k = 0; k < doc->ocg->len; k++) + { + pdf_ocg_entry *s = &doc->ocg->ocgs[k]; + + if (!pdf_objcmp_resolve(ctx, s->obj, g)) + s->state = 0; + } + } + } + } +} + +int pdf_count_layer_config_ui(fz_context *ctx, pdf_document *doc) +{ + if (doc == NULL || doc->ocg == NULL) + return 0; + + return doc->ocg->num_ui_entries; +} + +void pdf_select_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_ui *entry; + + if (doc == NULL || doc->ocg == NULL) + return; + + if (ui < 0 || ui >= doc->ocg->num_ui_entries) + fz_throw(ctx, FZ_ERROR_GENERIC, "Out of range UI entry selected"); + + entry = &doc->ocg->ui[ui]; + if (entry->button_flags != PDF_LAYER_UI_RADIOBOX && + entry->button_flags != PDF_LAYER_UI_CHECKBOX) + return; + if (entry->locked) + return; + + if (entry->button_flags == PDF_LAYER_UI_RADIOBOX) + clear_radio_group(ctx, doc, doc->ocg->ocgs[entry->ocg].obj); + + doc->ocg->ocgs[entry->ocg].state = 1; +} + +void pdf_toggle_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_ui *entry; + int selected; + + if (doc == NULL || doc->ocg == NULL) + return; + + if (ui < 0 || ui >= doc->ocg->num_ui_entries) + fz_throw(ctx, FZ_ERROR_GENERIC, "Out of range UI entry toggled"); + + entry = &doc->ocg->ui[ui]; + if (entry->button_flags != PDF_LAYER_UI_RADIOBOX && + entry->button_flags != PDF_LAYER_UI_CHECKBOX) + return; + if (entry->locked) + return; + + selected = doc->ocg->ocgs[entry->ocg].state; + + if (entry->button_flags == PDF_LAYER_UI_RADIOBOX) + clear_radio_group(ctx, doc, doc->ocg->ocgs[entry->ocg].obj); + + doc->ocg->ocgs[entry->ocg].state = !selected; +} + +void pdf_deselect_layer_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_ui *entry; + + if (doc == NULL || doc->ocg == NULL) + return; + + if (ui < 0 || ui >= doc->ocg->num_ui_entries) + fz_throw(ctx, FZ_ERROR_GENERIC, "Out of range UI entry deselected"); + + entry = &doc->ocg->ui[ui]; + if (entry->button_flags != PDF_LAYER_UI_RADIOBOX && + entry->button_flags != PDF_LAYER_UI_CHECKBOX) + return; + if (entry->locked) + return; + + doc->ocg->ocgs[entry->ocg].state = 0; +} + +void +pdf_layer_config_ui_info(fz_context *ctx, pdf_document *doc, int ui, pdf_layer_config_ui *info) +{ + pdf_ocg_ui *entry; + + if (!info) + return; + + info->depth = 0; + info->locked = 0; + info->selected = 0; + info->text = NULL; + info->type = 0; + + if (doc == NULL || doc->ocg == NULL) + return; + + if (ui < 0 || ui >= doc->ocg->num_ui_entries) + fz_throw(ctx, FZ_ERROR_GENERIC, "Out of range UI entry selected"); + + entry = &doc->ocg->ui[ui]; + info->type = entry->button_flags; + info->depth = entry->depth; + info->selected = doc->ocg->ocgs[entry->ocg].state; + info->locked = entry->locked; + info->text = entry->name; +} + +static int +ocg_intents_include(fz_context *ctx, 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) + return (strcmp(name, "View") == 0); + + if (pdf_is_name(ctx, desc->intent)) + { + char *intent = pdf_to_name(ctx, desc->intent); + if (strcmp(intent, "All") == 0) + return 1; + return (strcmp(intent, name) == 0); + } + if (!pdf_is_array(ctx, desc->intent)) + return 0; + + len = pdf_array_len(ctx, desc->intent); + for (i=0; i < len; i++) + { + char *intent = pdf_to_name(ctx, pdf_array_get(ctx, desc->intent, i)); + if (strcmp(intent, "All") == 0) + return 1; + if (strcmp(intent, name) == 0) + return 1; + } + return 0; +} + +int +pdf_is_hidden_ocg(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *rdb, const char *usage, pdf_obj *ocg) +{ + char event_state[16]; + pdf_obj *obj, *obj2, *type; + + /* Avoid infinite recursions */ + if (pdf_obj_marked(ctx, ocg)) + return 0; + + /* If no usage, everything is visible */ + if (!usage) + return 0; + + /* If no ocg descriptor, everything is visible */ + if (!desc) + return 0; + + /* If we've been handed a name, look it up in the properties. */ + if (pdf_is_name(ctx, ocg)) + { + ocg = pdf_dict_get(ctx, pdf_dict_get(ctx, rdb, PDF_NAME_Properties), ocg); + } + /* If we haven't been given an ocg at all, then we're visible */ + if (!ocg) + return 0; + + fz_strlcpy(event_state, usage, sizeof event_state); + fz_strlcat(event_state, "State", sizeof event_state); + + type = pdf_dict_get(ctx, ocg, PDF_NAME_Type); + + if (pdf_name_eq(ctx, type, PDF_NAME_OCG)) + { + /* An Optional Content Group */ + int default_value = 0; + int len = desc->len; + int i; + pdf_obj *es; + + /* by default an OCG is visible, unless it's explicitly hidden */ + for (i = 0; i < len; i++) + { + if (!pdf_objcmp_resolve(ctx, desc->ocgs[i].obj, ocg)) + { + default_value = !desc->ocgs[i].state; + break; + } + } + + /* Check Intents; if our intent is not part of the set given + * by the current config, we should ignore it. */ + obj = pdf_dict_get(ctx, ocg, PDF_NAME_Intent); + if (pdf_is_name(ctx, obj)) + { + /* If it doesn't match, it's hidden */ + if (ocg_intents_include(ctx, desc, pdf_to_name(ctx, obj)) == 0) + return 1; + } + else if (pdf_is_array(ctx, obj)) + { + int match = 0; + len = pdf_array_len(ctx, obj); + for (i=0; i<len; i++) { + match |= ocg_intents_include(ctx, desc, pdf_to_name(ctx, pdf_array_get(ctx, 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(ctx, 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 = pdf_dict_get(ctx, ocg, PDF_NAME_Usage); + if (!pdf_is_dict(ctx, obj)) + return default_value; + /* 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 = pdf_dict_gets(ctx, obj, usage); + es = pdf_dict_gets(ctx, obj2, event_state); + if (pdf_name_eq(ctx, es, PDF_NAME_OFF)) + { + return 1; + } + if (pdf_name_eq(ctx, es, PDF_NAME_ON)) + { + return 0; + } + return default_value; + } + else if (pdf_name_eq(ctx, type, PDF_NAME_OCMD)) + { + /* An Optional Content Membership Dictionary */ + pdf_obj *name; + int combine, on; + + obj = pdf_dict_get(ctx, ocg, PDF_NAME_VE); + if (pdf_is_array(ctx, obj)) { + /* FIXME: Calculate visibility from array */ + return 0; + } + name = pdf_dict_get(ctx, ocg, PDF_NAME_P); + /* Set combine; Bit 0 set => AND, Bit 1 set => true means + * Off, otherwise true means On */ + if (pdf_name_eq(ctx, name, PDF_NAME_AllOn)) + { + combine = 1; + } + else if (pdf_name_eq(ctx, name, PDF_NAME_AnyOff)) + { + combine = 2; + } + else if (pdf_name_eq(ctx, name, PDF_NAME_AllOff)) + { + combine = 3; + } + else /* Assume it's the default (AnyOn) */ + { + combine = 0; + } + + if (pdf_mark_obj(ctx, ocg)) + return 0; /* Should never happen */ + fz_try(ctx) + { + obj = pdf_dict_get(ctx, ocg, PDF_NAME_OCGs); + on = combine & 1; + if (pdf_is_array(ctx, obj)) { + int i, len; + len = pdf_array_len(ctx, obj); + for (i = 0; i < len; i++) + { + int hidden = pdf_is_hidden_ocg(ctx, desc, rdb, usage, pdf_array_get(ctx, obj, i)); + if ((combine & 1) == 0) + hidden = !hidden; + if (combine & 2) + on &= hidden; + else + on |= hidden; + } + } + else + { + on = pdf_is_hidden_ocg(ctx, desc, rdb, usage, obj); + if ((combine & 1) == 0) + on = !on; + } + } + fz_always(ctx) + { + pdf_unmark_obj(ctx, ocg); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + return !on; + } + /* No idea what sort of object this is - be visible */ + return 0; +} + +void +pdf_read_ocg(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *obj, *ocg, *configs; + int len, i, num_configs; + pdf_ocg_descriptor *desc; + + fz_var(desc); + + obj = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root), PDF_NAME_OCProperties); + if (!obj) + return; + + configs = pdf_dict_get(ctx, obj, PDF_NAME_Configs); + if (configs == NULL) + num_configs = 1; + else if (!pdf_is_array(ctx, configs)) + fz_throw(ctx, FZ_ERROR_GENERIC, "Invalid Configs value"); + else + num_configs = pdf_array_len(ctx, configs); + + ocg = pdf_dict_get(ctx, obj, PDF_NAME_OCGs); + if (!ocg || !pdf_is_array(ctx, ocg)) + /* Not ever supposed to happen, but live with it. */ + return; + len = pdf_array_len(ctx, ocg); + fz_try(ctx) + { + desc = fz_malloc_struct(ctx, pdf_ocg_descriptor); + desc->num_configs = num_configs; + desc->len = len; + desc->ocgs = fz_calloc(ctx, len, sizeof(*desc->ocgs)); + desc->intent = NULL; + for (i=0; i < len; i++) + { + pdf_obj *o = pdf_array_get(ctx, ocg, i); + desc->ocgs[i].obj = pdf_keep_obj(ctx, o); + desc->ocgs[i].state = 1; + } + doc->ocg = desc; + } + fz_catch(ctx) + { + if (desc) + fz_free(ctx, desc->ocgs); + fz_free(ctx, desc); + fz_rethrow(ctx); + } + + pdf_select_layer_config(ctx, doc, 0); +} diff --git a/source/pdf/pdf-op-run.c b/source/pdf/pdf-op-run.c index 89bbf008..00d1957d 100644 --- a/source/pdf/pdf-op-run.c +++ b/source/pdf/pdf-op-run.c @@ -2018,11 +2018,11 @@ pdf_drop_run_processor(fz_context *ctx, pdf_processor *proc) } pdf_processor * -pdf_new_run_processor(fz_context *ctx, fz_device *dev, const fz_matrix *ctm, const char *event, pdf_gstate *gstate, int nested) +pdf_new_run_processor(fz_context *ctx, fz_device *dev, const fz_matrix *ctm, const char *usage, pdf_gstate *gstate, int nested) { pdf_run_processor *proc = pdf_new_processor(ctx, sizeof *proc); { - proc->super.event = event; + proc->super.usage = usage; proc->super.drop_processor = pdf_drop_run_processor; diff --git a/source/pdf/pdf-run.c b/source/pdf/pdf-run.c index f806b120..ba8b30b7 100644 --- a/source/pdf/pdf-run.c +++ b/source/pdf/pdf-run.c @@ -1,7 +1,7 @@ #include "mupdf/pdf.h" static void -pdf_run_annot_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_annot *annot, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie) +pdf_run_annot_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_annot *annot, fz_device *dev, const fz_matrix *ctm, const char *usage, fz_cookie *cookie) { fz_matrix local_ctm, page_ctm; fz_rect mediabox; @@ -10,7 +10,7 @@ pdf_run_annot_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf pdf_page_transform(ctx, page, &mediabox, &page_ctm); fz_concat(&local_ctm, &page_ctm, ctm); - proc = pdf_new_run_processor(ctx, dev, &local_ctm, event, NULL, 0); + proc = pdf_new_run_processor(ctx, dev, &local_ctm, usage, NULL, 0); fz_try(ctx) pdf_process_annot(ctx, proc, doc, page, annot, cookie); fz_always(ctx) @@ -19,7 +19,8 @@ pdf_run_annot_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf fz_rethrow(ctx); } -static void pdf_run_page_contents_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie) +static void +pdf_run_page_contents_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, const char *usage, fz_cookie *cookie) { fz_matrix local_ctm, page_ctm; pdf_obj *resources; @@ -36,7 +37,7 @@ static void pdf_run_page_contents_with_usage(fz_context *ctx, pdf_document *doc, if (page->transparency) fz_begin_group(ctx, dev, fz_transform_rect(&mediabox, &local_ctm), 1, 0, 0, 1); - proc = pdf_new_run_processor(ctx, dev, &local_ctm, event, NULL, 0); + proc = pdf_new_run_processor(ctx, dev, &local_ctm, usage, NULL, 0); fz_try(ctx) pdf_process_contents(ctx, proc, doc, resources, contents, cookie); fz_always(ctx) @@ -100,7 +101,8 @@ void pdf_run_annot(fz_context *ctx, pdf_annot *annot, fz_device *dev, const fz_m fz_throw(ctx, FZ_ERROR_TRYLATER, "incomplete rendering"); } -static void pdf_run_page_annots_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie) +static void +pdf_run_page_annots_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, const char *usage, fz_cookie *cookie) { pdf_annot *annot; @@ -122,12 +124,12 @@ static void pdf_run_page_annots_with_usage(fz_context *ctx, pdf_document *doc, p cookie->progress++; } - pdf_run_annot_with_usage(ctx, doc, page, annot, dev, ctm, event, cookie); + pdf_run_annot_with_usage(ctx, doc, page, annot, dev, ctm, usage, cookie); } } void -pdf_run_page_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, char *event, fz_cookie *cookie) +pdf_run_page_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, const fz_matrix *ctm, const char *usage, fz_cookie *cookie) { int nocache = !!(dev->hints & FZ_NO_CACHE); @@ -135,8 +137,8 @@ pdf_run_page_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_d pdf_mark_xref(ctx, doc); fz_try(ctx) { - pdf_run_page_contents_with_usage(ctx, doc, page, dev, ctm, event, cookie); - pdf_run_page_annots_with_usage(ctx, doc, page, dev, ctm, event, cookie); + pdf_run_page_contents_with_usage(ctx, doc, page, dev, ctm, usage, cookie); + pdf_run_page_annots_with_usage(ctx, doc, page, dev, ctm, usage, cookie); } fz_always(ctx) { diff --git a/source/pdf/pdf-xref.c b/source/pdf/pdf-xref.c index 41dd9984..1af2d391 100644 --- a/source/pdf/pdf-xref.c +++ b/source/pdf/pdf-xref.c @@ -1,4 +1,4 @@ -#include "mupdf/pdf.h" +#include "pdf-imp.h" #include "mupdf/fitz/document.h" #undef DEBUG_PROGESSIVE_ADVANCE @@ -241,7 +241,7 @@ pdf_xref_entry *pdf_get_populating_xref_entry(fz_context *ctx, pdf_document *doc * xref. */ pdf_xref_entry *pdf_get_xref_entry(fz_context *ctx, pdf_document *doc, int i) { - pdf_xref *xref; + pdf_xref *xref = NULL; pdf_xref_subsec *sub; int j; @@ -1277,161 +1277,6 @@ pdf_load_linear(fz_context *ctx, pdf_document *doc) } } -void -pdf_ocg_set_config(fz_context *ctx, pdf_document *doc, int config) -{ - int i, j, len, len2; - pdf_ocg_descriptor *desc = doc->ocg; - pdf_obj *obj, *cobj; - pdf_obj *name; - - obj = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root), PDF_NAME_OCProperties); - if (!obj) - { - if (config == 0) - return; - else - fz_throw(ctx, FZ_ERROR_GENERIC, "Unknown OCG config (None known!)"); - } - if (config == 0) - { - cobj = pdf_dict_get(ctx, obj, PDF_NAME_D); - if (!cobj) - fz_throw(ctx, FZ_ERROR_GENERIC, "No default OCG config"); - } - else - { - cobj = pdf_array_get(ctx, pdf_dict_get(ctx, obj, PDF_NAME_Configs), config); - if (!cobj) - fz_throw(ctx, FZ_ERROR_GENERIC, "Illegal OCG config"); - } - - pdf_drop_obj(ctx, desc->intent); - desc->intent = pdf_dict_get(ctx, cobj, PDF_NAME_Intent); - if (desc->intent) - pdf_keep_obj(ctx, desc->intent); - - len = desc->len; - name = pdf_dict_get(ctx, cobj, PDF_NAME_BaseState); - if (pdf_name_eq(ctx, name, PDF_NAME_Unchanged)) - { - /* Do nothing */ - } - else if (pdf_name_eq(ctx, name, PDF_NAME_OFF)) - { - 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 = pdf_dict_get(ctx, cobj, PDF_NAME_ON); - len2 = pdf_array_len(ctx, obj); - for (i = 0; i < len2; i++) - { - pdf_obj *o = pdf_array_get(ctx, obj, i); - int n = pdf_to_num(ctx, o); - for (j=0; j < len; j++) - { - if (desc->ocgs[j].num == n) - { - desc->ocgs[j].state = 1; - break; - } - } - } - - obj = pdf_dict_get(ctx, cobj, PDF_NAME_OFF); - len2 = pdf_array_len(ctx, obj); - for (i = 0; i < len2; i++) - { - pdf_obj *o = pdf_array_get(ctx, obj, i); - int n = pdf_to_num(ctx, o); - for (j=0; j < len; j++) - { - if (desc->ocgs[j].num == n) - { - 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(fz_context *ctx, pdf_document *doc) -{ - pdf_obj *obj, *ocg; - int len, i; - pdf_ocg_descriptor *desc; - - fz_var(desc); - - obj = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root), PDF_NAME_OCProperties); - if (!obj) - return; - ocg = pdf_dict_get(ctx, obj, PDF_NAME_OCGs); - if (!ocg || !pdf_is_array(ctx, ocg)) - /* Not ever supposed to happen, but live with it. */ - return; - len = pdf_array_len(ctx, ocg); - fz_try(ctx) - { - desc = fz_malloc_struct(ctx, pdf_ocg_descriptor); - desc->len = len; - desc->ocgs = fz_calloc(ctx, len, sizeof(*desc->ocgs)); - desc->intent = NULL; - for (i=0; i < len; i++) - { - pdf_obj *o = pdf_array_get(ctx, ocg, i); - desc->ocgs[i].num = pdf_to_num(ctx, o); - desc->ocgs[i].state = 1; - } - doc->ocg = desc; - } - fz_catch(ctx) - { - if (desc) - fz_free(ctx, desc->ocgs); - fz_free(ctx, desc); - fz_rethrow(ctx); - } - - pdf_ocg_set_config(ctx, doc, 0); -} - -static void -pdf_drop_ocg(fz_context *ctx, pdf_ocg_descriptor *desc) -{ - if (!desc) - return; - - pdf_drop_obj(ctx, desc->intent); - fz_free(ctx, desc->ocgs); - fz_free(ctx, desc); -} - /* * Initialize and load xref tables. * If password is not null, try to decrypt. @@ -1631,7 +1476,7 @@ pdf_drop_document_imp(fz_context *ctx, pdf_document *doc) } fz_free(ctx, doc->type3_fonts); - pdf_drop_ocg(ctx, doc->ocg); + pdf_drop_ocg(ctx, doc); pdf_empty_store(ctx, doc); |