summaryrefslogtreecommitdiff
path: root/source/pdf/pdf-layer.c
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2016-11-09 17:41:43 +0000
committerRobin Watts <robin.watts@artifex.com>2016-11-11 13:18:39 +0000
commitda50c6f300e565c8eedf4ce252c44b5ddbb95cee (patch)
treed63891b1494b45046973cbc1ab3b4435821fc3f1 /source/pdf/pdf-layer.c
parenta1d18b804019ad89841f289c465755d7feebe823 (diff)
downloadmupdf-da50c6f300e565c8eedf4ce252c44b5ddbb95cee.tar.xz
Add pdf_layer configuration API.
Add API to: * allow enumeration of layer configs (OCCDs) within PDF files. * allow selection of layer configs. * allow enumeration of the "UI" (or "Human readable") form of layer configs. * allow selection/toggling of entries in the UI.
Diffstat (limited to 'source/pdf/pdf-layer.c')
-rw-r--r--source/pdf/pdf-layer.c715
1 files changed, 715 insertions, 0 deletions
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);
+}