Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/pdf/pdf-layer.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/pdf/pdf-layer.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,877 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +#include <string.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 n; + 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 +{ + int current; + int num_configs; + + int len; + pdf_ocg_entry *ocgs; + + pdf_obj *intent; + const char *usage; + + int num_ui_entries; + pdf_ocg_ui *ui; +}; + +int +pdf_count_layer_configs(fz_context *ctx, pdf_document *doc) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + return desc ? desc->num_configs : 0; +} + +int +pdf_count_layers(fz_context *ctx, pdf_document *doc) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + return desc ? desc->len : 0; +} + +const char * +pdf_layer_name(fz_context *ctx, pdf_document *doc, int layer) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + if (desc && layer >= 0 && layer < desc->len) + return pdf_dict_get_text_string(ctx, desc->ocgs[layer].obj, PDF_NAME(Name)); + fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index"); +} + +int +pdf_layer_is_enabled(fz_context *ctx, pdf_document *doc, int layer) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + if (desc && layer >= 0 && layer < desc->len) + return desc->ocgs[layer].state; + fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index"); +} + +void +pdf_enable_layer(fz_context *ctx, pdf_document *doc, int layer, int enabled) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + if (desc && layer >= 0 && layer < desc->len) + desc->ocgs[layer].state = enabled; + else + fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index"); +} + +static int +count_entries(fz_context *ctx, pdf_obj *obj, pdf_cycle_list *cycle_up) +{ + pdf_cycle_list cycle; + 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); + if (pdf_cycle(ctx, &cycle, cycle_up, o)) + continue; + count += (pdf_is_array(ctx, o) ? count_entries(ctx, o, &cycle) : 1); + } + return count; +} + +static pdf_ocg_ui * +get_ocg_ui(fz_context *ctx, pdf_ocg_descriptor *desc, int fill) +{ + if (fill == desc->num_ui_entries) + { + /* Number of layers changed while parsing; + * probably due to a repair. */ + int newsize = desc->num_ui_entries * 2; + if (newsize == 0) + newsize = 4; /* Arbitrary non-zero */ + desc->ui = fz_realloc_array(ctx, desc->ui, newsize, pdf_ocg_ui); + desc->num_ui_entries = newsize; + } + return &desc->ui[fill]; +} + +static int +ocgcmp(const void *a_, const void *b_) +{ + const pdf_ocg_entry *a = a_; + const pdf_ocg_entry *b = b_; + + return (b->n - a->n); +} + +static int +find_ocg(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *obj) +{ + int n = pdf_to_num(ctx, obj); + int l = 0; + int r = desc->len-1; + + if (n <= 0) + return -1; + + while (l <= r) + { + int m = (l + r) >> 1; + int c = desc->ocgs[m].n - n; + if (c < 0) + r = m - 1; + else if (c > 0) + l = m + 1; + else + return m; + } + return -1; +} + +static int +populate_ui(fz_context *ctx, pdf_ocg_descriptor *desc, int fill, pdf_obj *order, int depth, pdf_obj *rbgroups, pdf_obj *locked, + pdf_cycle_list *cycle_up) +{ + pdf_cycle_list cycle; + int len = pdf_array_len(ctx, order); + int i, j; + pdf_ocg_ui *ui; + + for (i = 0; i < len; i++) + { + pdf_obj *o = pdf_array_get(ctx, order, i); + if (pdf_is_array(ctx, o)) + { + if (pdf_cycle(ctx, &cycle, cycle_up, o)) + continue; + + fill = populate_ui(ctx, desc, fill, o, depth+1, rbgroups, locked, &cycle); + continue; + } + if (pdf_is_string(ctx, o)) + { + ui = get_ocg_ui(ctx, desc, fill++); + ui->depth = depth; + ui->ocg = -1; + ui->name = pdf_to_text_string(ctx, o); + ui->button_flags = PDF_LAYER_UI_LABEL; + ui->locked = 1; + continue; + } + + j = find_ocg(ctx, desc, o); + if (j < 0) + continue; /* OCG not found in main list! Just ignore it */ + ui = get_ocg_ui(ctx, desc, fill++); + ui->depth = depth; + ui->ocg = j; + ui->name = pdf_dict_get_text_string(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); + } + return fill; +} + +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)); + if (!order) + order = pdf_dict_getp(ctx, ocprops, "D/Order"); + count = count_entries(ctx, order, NULL); + rbgroups = pdf_dict_get(ctx, occg, PDF_NAME(RBGroups)); + if (!rbgroups) + rbgroups = pdf_dict_getp(ctx, ocprops, "D/RBGroups"); + locked = pdf_dict_get(ctx, occg, PDF_NAME(Locked)); + + desc->num_ui_entries = count; + if (desc->num_ui_entries == 0) + return; + + desc->ui = fz_malloc_struct_array(ctx, count, pdf_ocg_ui); + fz_try(ctx) + { + desc->num_ui_entries = populate_ui(ctx, desc, 0, order, 0, rbgroups, locked, NULL); + } + fz_catch(ctx) + { + drop_ui(ctx, desc); + fz_rethrow(ctx); + } +} + +void +pdf_select_layer_config(fz_context *ctx, pdf_document *doc, int config) +{ + pdf_ocg_descriptor *desc; + int i, j, len, len2; + pdf_obj *obj, *cobj; + pdf_obj *name; + + desc = pdf_read_ocg(ctx, doc); + + 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_ARGUMENT, "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_ARGUMENT, "Illegal Layer config"); + cobj = pdf_dict_get(ctx, obj, PDF_NAME(D)); + if (!cobj) + fz_throw(ctx, FZ_ERROR_FORMAT, "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_ocg_descriptor *desc; + pdf_obj *ocprops; + pdf_obj *obj; + + if (!info) + return; + + desc = pdf_read_ocg(ctx, doc); + + info->name = NULL; + info->creator = NULL; + + if (config_num < 0 || config_num >= desc->num_configs) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "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_ARGUMENT, "Invalid layer config number"); + + info->creator = pdf_dict_get_string(ctx, obj, PDF_NAME(Creator), NULL); + info->name = pdf_dict_get_string(ctx, obj, PDF_NAME(Name), NULL); +} + +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; + + drop_ui(ctx, desc); + 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) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + return desc ? desc->num_ui_entries : 0; +} + +void pdf_select_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + pdf_ocg_ui *entry; + + if (ui < 0 || ui >= desc->num_ui_entries) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry selected"); + + entry = &desc->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, desc->ocgs[entry->ocg].obj); + + desc->ocgs[entry->ocg].state = 1; +} + +void pdf_toggle_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + pdf_ocg_ui *entry; + int selected; + + if (ui < 0 || ui >= desc->num_ui_entries) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry toggled"); + + entry = &desc->ui[ui]; + if (entry->button_flags != PDF_LAYER_UI_RADIOBOX && + entry->button_flags != PDF_LAYER_UI_CHECKBOX) + return; + if (entry->locked) + return; + + selected = desc->ocgs[entry->ocg].state; + + if (entry->button_flags == PDF_LAYER_UI_RADIOBOX) + clear_radio_group(ctx, doc, desc->ocgs[entry->ocg].obj); + + desc->ocgs[entry->ocg].state = !selected; +} + +void pdf_deselect_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui) +{ + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + pdf_ocg_ui *entry; + + if (ui < 0 || ui >= desc->num_ui_entries) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry deselected"); + + entry = &desc->ui[ui]; + if (entry->button_flags != PDF_LAYER_UI_RADIOBOX && + entry->button_flags != PDF_LAYER_UI_CHECKBOX) + return; + if (entry->locked) + return; + + desc->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_descriptor *desc = pdf_read_ocg(ctx, doc); + pdf_ocg_ui *entry; + + if (!info) + return; + + info->depth = 0; + info->locked = 0; + info->selected = 0; + info->text = NULL; + info->type = 0; + + if (ui < 0 || ui >= desc->num_ui_entries) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry selected"); + + entry = &desc->ui[ui]; + info->type = entry->button_flags; + info->depth = entry->depth; + info->selected = desc->ocgs[entry->ocg].state; + info->locked = entry->locked; + info->text = entry->name; +} + +static int +ocg_intents_include(fz_context *ctx, pdf_ocg_descriptor *desc, const 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)) + { + const 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++) + { + const char *intent = pdf_array_get_name(ctx, desc->intent, i); + if (strcmp(intent, "All") == 0) + return 1; + if (strcmp(intent, name) == 0) + return 1; + } + return 0; +} + +static int +pdf_is_ocg_hidden_imp(fz_context *ctx, pdf_document *doc, pdf_obj *rdb, const char *usage, pdf_obj *ocg, pdf_cycle_list *cycle_up) +{ + pdf_cycle_list cycle; + pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc); + pdf_obj *obj, *obj2, *type; + char event_state[16]; + + /* If no usage, everything is visible */ + if (!usage) + return 0; + + /* If no ocg descriptor or no ocgs described, everything is visible */ + if (!desc || desc->len == 0) + 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; + + /* Avoid infinite recursions */ + if (pdf_cycle(ctx, &cycle, cycle_up, 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++) + { + /* Deliberately do NOT resolve here. Bug 702261. */ + if (!pdf_objcmp(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_array_get_name(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; + return default_value; + } + else if (pdf_name_eq(ctx, type, PDF_NAME(OCMD))) + { + /* An Optional Content Membership Dictionary */ + pdf_obj *name; + int combine, on = 0; + + 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; + } + + 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_ocg_hidden_imp(ctx, doc, rdb, usage, pdf_array_get(ctx, obj, i), &cycle); + if ((combine & 1) == 0) + hidden = !hidden; + if (combine & 2) + on &= hidden; + else + on |= hidden; + } + } + else + { + on = pdf_is_ocg_hidden_imp(ctx, doc, rdb, usage, obj, &cycle); + if ((combine & 1) == 0) + on = !on; + } + + return !on; + } + /* No idea what sort of object this is - be visible */ + return 0; +} + +int +pdf_is_ocg_hidden(fz_context *ctx, pdf_document *doc, pdf_obj *rdb, const char *usage, pdf_obj *ocg) +{ + return pdf_is_ocg_hidden_imp(ctx, doc, rdb, usage, ocg, NULL); +} + +pdf_ocg_descriptor * +pdf_read_ocg(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *prop, *ocgs, *configs; + int len, i, num_configs; + + if (doc->ocg) + return doc->ocg; + + fz_try(ctx) + { + prop = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)), PDF_NAME(OCProperties)); + + configs = pdf_dict_get(ctx, prop, PDF_NAME(Configs)); + num_configs = pdf_array_len(ctx, configs); + ocgs = pdf_dict_get(ctx, prop, PDF_NAME(OCGs)); + len = pdf_array_len(ctx, ocgs); + + doc->ocg = fz_malloc_struct(ctx, pdf_ocg_descriptor); + doc->ocg->ocgs = fz_calloc(ctx, len, sizeof(*doc->ocg->ocgs)); + doc->ocg->len = len; + doc->ocg->num_configs = num_configs; + + for (i = 0; i < len; i++) + { + pdf_obj *o = pdf_array_get(ctx, ocgs, i); + doc->ocg->ocgs[i].obj = pdf_keep_obj(ctx, o); + doc->ocg->ocgs[i].n = pdf_to_num(ctx, o); + doc->ocg->ocgs[i].state = 1; + } + qsort(doc->ocg->ocgs, len, sizeof(doc->ocg->ocgs[0]), ocgcmp); + + pdf_select_layer_config(ctx, doc, 0); + } + fz_catch(ctx) + { + pdf_drop_ocg(ctx, doc); + doc->ocg = NULL; + fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); + fz_rethrow_if(ctx, FZ_ERROR_SYSTEM); + fz_report_error(ctx); + fz_warn(ctx, "Ignoring broken Optional Content configuration"); + doc->ocg = fz_malloc_struct(ctx, pdf_ocg_descriptor); + } + + return doc->ocg; +} + +void +pdf_set_layer_config_as_default(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *ocprops, *d, *order, *on, *configs, *rbgroups; + int k; + + ocprops = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties"); + if (!ocprops) + return; + + /* All files with OCGs are required to have a D entry */ + d = pdf_dict_get(ctx, ocprops, PDF_NAME(D)); + if (d == NULL) + return; + + pdf_dict_put(ctx, d, PDF_NAME(BaseState), PDF_NAME(OFF)); + + /* We are about to delete RBGroups and Order, from D. These are + * both the underlying defaults for other configs, so copy the + * current values out to any config that doesn't have one + * already. */ + order = pdf_dict_get(ctx, d, PDF_NAME(Order)); + rbgroups = pdf_dict_get(ctx, d, PDF_NAME(RBGroups)); + configs = pdf_dict_get(ctx, ocprops, PDF_NAME(Configs)); + if (configs) + { + int len = pdf_array_len(ctx, configs); + for (k=0; k < len; k++) + { + pdf_obj *config = pdf_array_get(ctx, configs, k); + + if (order && !pdf_dict_get(ctx, config, PDF_NAME(Order))) + pdf_dict_put(ctx, config, PDF_NAME(Order), order); + if (rbgroups && !pdf_dict_get(ctx, config, PDF_NAME(RBGroups))) + pdf_dict_put(ctx, config, PDF_NAME(RBGroups), rbgroups); + } + } + + /* Offer all the layers in the UI */ + order = pdf_new_array(ctx, doc, 4); + on = pdf_new_array(ctx, doc, 4); + for (k = 0; k < doc->ocg->len; k++) + { + pdf_ocg_entry *s = &doc->ocg->ocgs[k]; + + pdf_array_push(ctx, order, s->obj); + if (s->state) + pdf_array_push(ctx, on, s->obj); + } + pdf_dict_put(ctx, d, PDF_NAME(Order), order); + pdf_dict_put(ctx, d, PDF_NAME(ON), on); + pdf_dict_del(ctx, d, PDF_NAME(OFF)); + pdf_dict_del(ctx, d, PDF_NAME(AS)); + pdf_dict_put(ctx, d, PDF_NAME(Intent), PDF_NAME(View)); + pdf_dict_del(ctx, d, PDF_NAME(Name)); + pdf_dict_del(ctx, d, PDF_NAME(Creator)); + pdf_dict_del(ctx, d, PDF_NAME(RBGroups)); + pdf_dict_del(ctx, d, PDF_NAME(Locked)); + + pdf_dict_del(ctx, ocprops, PDF_NAME(Configs)); +}
