eBookReaderSwitch/mupdf/source/pdf/pdf-layer.c

892 lines
21 KiB
C
Raw Permalink Normal View History

#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 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;
const char *usage;
int num_ui_entries;
pdf_ocg_ui *ui;
};
/*
Get the number of layer
configurations defined in this document.
doc: The document in question.
*/
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);
if (pdf_mark_obj(ctx, o))
continue;
fz_try(ctx)
count += (pdf_is_array(ctx, o) ? count_entries(ctx, o) : 1);
fz_always(ctx)
pdf_unmark_obj(ctx, o);
fz_catch(ctx)
fz_rethrow(ctx);
}
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))
{
if (pdf_mark_obj(ctx, o))
continue;
fz_try(ctx)
ui = populate_ui(ctx, desc, ui, o, depth+1, rbgroups, locked);
fz_always(ctx)
pdf_unmark_obj(ctx, o);
fz_catch(ctx)
fz_rethrow(ctx);
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_dict_get_string(ctx, o, PDF_NAME(Name), NULL);
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));
if (!order)
order = pdf_dict_getp(ctx, ocprops, "D/Order");
count = count_entries(ctx, order);
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 = 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);
}
}
/*
Set the current configuration.
This updates the visibility of the optional content groups
within the document.
doc: The document in question.
config_num: A value in the 0..n-1 range, where n is the
value returned from pdf_count_layer_configs.
*/
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);
}
/*
Fetch the name (and
optionally creator) of the given layer config.
doc: The document in question.
config_num: A value in the 0..n-1 range, where n is the
value returned from pdf_count_layer_configs.
info: Pointer to structure to fill in. Pointers within
this structure may be set to NULL if no information is
available.
*/
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_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;
}
}
}
}
}
/*
Returns the number of entries in the
'UI' for this layer configuration.
doc: The document in question.
*/
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;
}
/*
Select a checkbox/radiobox
within the 'UI' for this layer configuration.
Selecting a UI entry that is a radiobox may disable
other UI entries.
doc: The document in question.
ui: A value in the 0..m-1 range, where m is the value
returned by pdf_count_layer_config_ui.
*/
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;
}
/*
Toggle a checkbox/radiobox
within the 'UI' for this layer configuration.
Toggling a UI entry that is a radiobox may disable
other UI entries.
doc: The document in question.
ui: A value in the 0..m-1 range, where m is the value
returned by pdf_count_layer_config_ui.
*/
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;
}
/*
Select a checkbox/radiobox
within the 'UI' for this layer configuration.
doc: The document in question.
ui: A value in the 0..m-1 range, where m is the value
returned by pdf_count_layer_config_ui.
*/
void pdf_deselect_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 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;
}
/*
Get the info for a given
entry in the layer config ui.
doc: The document in question.
ui: A value in the 0..m-1 range, where m is the value
returned by pdf_count_layer_config_ui.
info: Pointer to a structure to fill in with information
about the requested ui entry.
*/
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, 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_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 = 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;
}
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);
desc = fz_malloc_struct(ctx, pdf_ocg_descriptor);
desc->ocgs = NULL;
fz_try(ctx)
{
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)
{
fz_free(ctx, desc->ocgs);
fz_free(ctx, desc);
fz_rethrow(ctx);
}
pdf_select_layer_config(ctx, doc, 0);
}
/*
Write the current layer
config back into the document as the default state.
*/
void
pdf_set_layer_config_as_default(fz_context *ctx, pdf_document *doc)
{
pdf_obj *ocprops, *d, *order, *on, *configs, *rbgroups;
int k;
if (doc == NULL || doc->ocg == NULL)
return;
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));
}