comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24 #include "mupdf/pdf.h"
25
26 #include <string.h>
27
28 /*
29 Notes on OCGs etc.
30
31 PDF Documents may contain Optional Content Groups. Which of
32 these is shown at any given time is dependent on which
33 Optional Content Configuration Dictionary is in force at the
34 time.
35
36 A pdf_document, once loaded, contains some state saying which
37 OCGs are enabled/disabled, and which 'Intent' (or 'Intents')
38 a file is being used for. This information is held outside of
39 the actual PDF file.
40
41 An Intent (just 'View' or 'Design' or 'All', according to
42 PDF 2.0, but theoretically more) says which OCGs to consider
43 or ignore in calculating the visibility of content. The
44 Intent (or Intents, for there can be an array) is set by the
45 current OCCD.
46
47 When first loaded, we turn all OCGs on, then load the default
48 OCCD. This may turn some OCGs off, and sets the document Intent.
49
50 Callers can ask how many OCCDs there are, read the names/creators
51 for each, and then select any one of them. That updates which
52 OCGs are selected, and resets the Intent.
53
54 Once an OCCD has been selected, a caller can enumerate the
55 'displayable configuration'. This is a list of labels/radio
56 buttons/check buttons that can be used to enable/disable
57 given OCGs. The caller can then enable/disable OCGs by
58 asking to select (or toggle) given entries in that list.
59
60 Thus the handling of radio button groups, and 'locked'
61 elements is kept within the core of MuPDF.
62
63 Finally, the caller can set the 'usage' for a document. This
64 can be 'View', 'Print', or 'Export'.
65 */
66
67 typedef struct
68 {
69 pdf_obj *obj;
70 int n;
71 int state;
72 } pdf_ocg_entry;
73
74 typedef struct
75 {
76 int ocg;
77 const char *name;
78 int depth;
79 unsigned int button_flags : 2;
80 unsigned int locked : 1;
81 } pdf_ocg_ui;
82
83 struct pdf_ocg_descriptor
84 {
85 int current;
86 int num_configs;
87
88 int len;
89 pdf_ocg_entry *ocgs;
90
91 pdf_obj *intent;
92 const char *usage;
93
94 int num_ui_entries;
95 pdf_ocg_ui *ui;
96 };
97
98 int
99 pdf_count_layer_configs(fz_context *ctx, pdf_document *doc)
100 {
101 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
102 return desc ? desc->num_configs : 0;
103 }
104
105 int
106 pdf_count_layers(fz_context *ctx, pdf_document *doc)
107 {
108 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
109 return desc ? desc->len : 0;
110 }
111
112 const char *
113 pdf_layer_name(fz_context *ctx, pdf_document *doc, int layer)
114 {
115 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
116 if (desc && layer >= 0 && layer < desc->len)
117 return pdf_dict_get_text_string(ctx, desc->ocgs[layer].obj, PDF_NAME(Name));
118 fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index");
119 }
120
121 int
122 pdf_layer_is_enabled(fz_context *ctx, pdf_document *doc, int layer)
123 {
124 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
125 if (desc && layer >= 0 && layer < desc->len)
126 return desc->ocgs[layer].state;
127 fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index");
128 }
129
130 void
131 pdf_enable_layer(fz_context *ctx, pdf_document *doc, int layer, int enabled)
132 {
133 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
134 if (desc && layer >= 0 && layer < desc->len)
135 desc->ocgs[layer].state = enabled;
136 else
137 fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid layer index");
138 }
139
140 static int
141 count_entries(fz_context *ctx, pdf_obj *obj, pdf_cycle_list *cycle_up)
142 {
143 pdf_cycle_list cycle;
144 int len = pdf_array_len(ctx, obj);
145 int i;
146 int count = 0;
147
148 for (i = 0; i < len; i++)
149 {
150 pdf_obj *o = pdf_array_get(ctx, obj, i);
151 if (pdf_cycle(ctx, &cycle, cycle_up, o))
152 continue;
153 count += (pdf_is_array(ctx, o) ? count_entries(ctx, o, &cycle) : 1);
154 }
155 return count;
156 }
157
158 static pdf_ocg_ui *
159 get_ocg_ui(fz_context *ctx, pdf_ocg_descriptor *desc, int fill)
160 {
161 if (fill == desc->num_ui_entries)
162 {
163 /* Number of layers changed while parsing;
164 * probably due to a repair. */
165 int newsize = desc->num_ui_entries * 2;
166 if (newsize == 0)
167 newsize = 4; /* Arbitrary non-zero */
168 desc->ui = fz_realloc_array(ctx, desc->ui, newsize, pdf_ocg_ui);
169 desc->num_ui_entries = newsize;
170 }
171 return &desc->ui[fill];
172 }
173
174 static int
175 ocgcmp(const void *a_, const void *b_)
176 {
177 const pdf_ocg_entry *a = a_;
178 const pdf_ocg_entry *b = b_;
179
180 return (b->n - a->n);
181 }
182
183 static int
184 find_ocg(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *obj)
185 {
186 int n = pdf_to_num(ctx, obj);
187 int l = 0;
188 int r = desc->len-1;
189
190 if (n <= 0)
191 return -1;
192
193 while (l <= r)
194 {
195 int m = (l + r) >> 1;
196 int c = desc->ocgs[m].n - n;
197 if (c < 0)
198 r = m - 1;
199 else if (c > 0)
200 l = m + 1;
201 else
202 return m;
203 }
204 return -1;
205 }
206
207 static int
208 populate_ui(fz_context *ctx, pdf_ocg_descriptor *desc, int fill, pdf_obj *order, int depth, pdf_obj *rbgroups, pdf_obj *locked,
209 pdf_cycle_list *cycle_up)
210 {
211 pdf_cycle_list cycle;
212 int len = pdf_array_len(ctx, order);
213 int i, j;
214 pdf_ocg_ui *ui;
215
216 for (i = 0; i < len; i++)
217 {
218 pdf_obj *o = pdf_array_get(ctx, order, i);
219 if (pdf_is_array(ctx, o))
220 {
221 if (pdf_cycle(ctx, &cycle, cycle_up, o))
222 continue;
223
224 fill = populate_ui(ctx, desc, fill, o, depth+1, rbgroups, locked, &cycle);
225 continue;
226 }
227 if (pdf_is_string(ctx, o))
228 {
229 ui = get_ocg_ui(ctx, desc, fill++);
230 ui->depth = depth;
231 ui->ocg = -1;
232 ui->name = pdf_to_text_string(ctx, o);
233 ui->button_flags = PDF_LAYER_UI_LABEL;
234 ui->locked = 1;
235 continue;
236 }
237
238 j = find_ocg(ctx, desc, o);
239 if (j < 0)
240 continue; /* OCG not found in main list! Just ignore it */
241 ui = get_ocg_ui(ctx, desc, fill++);
242 ui->depth = depth;
243 ui->ocg = j;
244 ui->name = pdf_dict_get_text_string(ctx, o, PDF_NAME(Name));
245 ui->button_flags = pdf_array_contains(ctx, o, rbgroups) ? PDF_LAYER_UI_RADIOBOX : PDF_LAYER_UI_CHECKBOX;
246 ui->locked = pdf_array_contains(ctx, o, locked);
247 }
248 return fill;
249 }
250
251 static void
252 drop_ui(fz_context *ctx, pdf_ocg_descriptor *desc)
253 {
254 if (!desc)
255 return;
256
257 fz_free(ctx, desc->ui);
258 desc->ui = NULL;
259 }
260
261 static void
262 load_ui(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *ocprops, pdf_obj *occg)
263 {
264 pdf_obj *order;
265 pdf_obj *rbgroups;
266 pdf_obj *locked;
267 int count;
268
269 /* Count the number of entries */
270 order = pdf_dict_get(ctx, occg, PDF_NAME(Order));
271 if (!order)
272 order = pdf_dict_getp(ctx, ocprops, "D/Order");
273 count = count_entries(ctx, order, NULL);
274 rbgroups = pdf_dict_get(ctx, occg, PDF_NAME(RBGroups));
275 if (!rbgroups)
276 rbgroups = pdf_dict_getp(ctx, ocprops, "D/RBGroups");
277 locked = pdf_dict_get(ctx, occg, PDF_NAME(Locked));
278
279 desc->num_ui_entries = count;
280 if (desc->num_ui_entries == 0)
281 return;
282
283 desc->ui = fz_malloc_struct_array(ctx, count, pdf_ocg_ui);
284 fz_try(ctx)
285 {
286 desc->num_ui_entries = populate_ui(ctx, desc, 0, order, 0, rbgroups, locked, NULL);
287 }
288 fz_catch(ctx)
289 {
290 drop_ui(ctx, desc);
291 fz_rethrow(ctx);
292 }
293 }
294
295 void
296 pdf_select_layer_config(fz_context *ctx, pdf_document *doc, int config)
297 {
298 pdf_ocg_descriptor *desc;
299 int i, j, len, len2;
300 pdf_obj *obj, *cobj;
301 pdf_obj *name;
302
303 desc = pdf_read_ocg(ctx, doc);
304
305 obj = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)), PDF_NAME(OCProperties));
306 if (!obj)
307 {
308 if (config == 0)
309 return;
310 else
311 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Unknown Layer config (None known!)");
312 }
313
314 cobj = pdf_array_get(ctx, pdf_dict_get(ctx, obj, PDF_NAME(Configs)), config);
315 if (!cobj)
316 {
317 if (config != 0)
318 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Illegal Layer config");
319 cobj = pdf_dict_get(ctx, obj, PDF_NAME(D));
320 if (!cobj)
321 fz_throw(ctx, FZ_ERROR_FORMAT, "No default Layer config");
322 }
323
324 pdf_drop_obj(ctx, desc->intent);
325 desc->intent = pdf_keep_obj(ctx, pdf_dict_get(ctx, cobj, PDF_NAME(Intent)));
326
327 len = desc->len;
328 name = pdf_dict_get(ctx, cobj, PDF_NAME(BaseState));
329 if (pdf_name_eq(ctx, name, PDF_NAME(Unchanged)))
330 {
331 /* Do nothing */
332 }
333 else if (pdf_name_eq(ctx, name, PDF_NAME(OFF)))
334 {
335 for (i = 0; i < len; i++)
336 {
337 desc->ocgs[i].state = 0;
338 }
339 }
340 else /* Default to ON */
341 {
342 for (i = 0; i < len; i++)
343 {
344 desc->ocgs[i].state = 1;
345 }
346 }
347
348 obj = pdf_dict_get(ctx, cobj, PDF_NAME(ON));
349 len2 = pdf_array_len(ctx, obj);
350 for (i = 0; i < len2; i++)
351 {
352 pdf_obj *o = pdf_array_get(ctx, obj, i);
353 for (j=0; j < len; j++)
354 {
355 if (!pdf_objcmp_resolve(ctx, desc->ocgs[j].obj, o))
356 {
357 desc->ocgs[j].state = 1;
358 break;
359 }
360 }
361 }
362
363 obj = pdf_dict_get(ctx, cobj, PDF_NAME(OFF));
364 len2 = pdf_array_len(ctx, obj);
365 for (i = 0; i < len2; i++)
366 {
367 pdf_obj *o = pdf_array_get(ctx, obj, i);
368 for (j=0; j < len; j++)
369 {
370 if (!pdf_objcmp_resolve(ctx, desc->ocgs[j].obj, o))
371 {
372 desc->ocgs[j].state = 0;
373 break;
374 }
375 }
376 }
377
378 desc->current = config;
379
380 drop_ui(ctx, desc);
381 load_ui(ctx, desc, obj, cobj);
382 }
383
384 void
385 pdf_layer_config_info(fz_context *ctx, pdf_document *doc, int config_num, pdf_layer_config *info)
386 {
387 pdf_ocg_descriptor *desc;
388 pdf_obj *ocprops;
389 pdf_obj *obj;
390
391 if (!info)
392 return;
393
394 desc = pdf_read_ocg(ctx, doc);
395
396 info->name = NULL;
397 info->creator = NULL;
398
399 if (config_num < 0 || config_num >= desc->num_configs)
400 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Invalid layer config number");
401
402 ocprops = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties");
403 if (!ocprops)
404 return;
405
406 obj = pdf_dict_get(ctx, ocprops, PDF_NAME(Configs));
407 if (pdf_is_array(ctx, obj))
408 obj = pdf_array_get(ctx, obj, config_num);
409 else if (config_num == 0)
410 obj = pdf_dict_get(ctx, ocprops, PDF_NAME(D));
411 else
412 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Invalid layer config number");
413
414 info->creator = pdf_dict_get_string(ctx, obj, PDF_NAME(Creator), NULL);
415 info->name = pdf_dict_get_string(ctx, obj, PDF_NAME(Name), NULL);
416 }
417
418 void
419 pdf_drop_ocg(fz_context *ctx, pdf_document *doc)
420 {
421 pdf_ocg_descriptor *desc;
422 int i;
423
424 if (!doc)
425 return;
426 desc = doc->ocg;
427 if (!desc)
428 return;
429
430 drop_ui(ctx, desc);
431 pdf_drop_obj(ctx, desc->intent);
432 for (i = 0; i < desc->len; i++)
433 pdf_drop_obj(ctx, desc->ocgs[i].obj);
434 fz_free(ctx, desc->ocgs);
435 fz_free(ctx, desc);
436 }
437
438 static void
439 clear_radio_group(fz_context *ctx, pdf_document *doc, pdf_obj *ocg)
440 {
441 pdf_obj *rbgroups = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties/RBGroups");
442 int len, i;
443
444 len = pdf_array_len(ctx, rbgroups);
445 for (i = 0; i < len; i++)
446 {
447 pdf_obj *group = pdf_array_get(ctx, rbgroups, i);
448
449 if (pdf_array_contains(ctx, ocg, group))
450 {
451 int len2 = pdf_array_len(ctx, group);
452 int j;
453
454 for (j = 0; j < len2; j++)
455 {
456 pdf_obj *g = pdf_array_get(ctx, group, j);
457 int k;
458 for (k = 0; k < doc->ocg->len; k++)
459 {
460 pdf_ocg_entry *s = &doc->ocg->ocgs[k];
461
462 if (!pdf_objcmp_resolve(ctx, s->obj, g))
463 s->state = 0;
464 }
465 }
466 }
467 }
468 }
469
470 int pdf_count_layer_config_ui(fz_context *ctx, pdf_document *doc)
471 {
472 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
473 return desc ? desc->num_ui_entries : 0;
474 }
475
476 void pdf_select_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui)
477 {
478 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
479 pdf_ocg_ui *entry;
480
481 if (ui < 0 || ui >= desc->num_ui_entries)
482 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry selected");
483
484 entry = &desc->ui[ui];
485 if (entry->button_flags != PDF_LAYER_UI_RADIOBOX &&
486 entry->button_flags != PDF_LAYER_UI_CHECKBOX)
487 return;
488 if (entry->locked)
489 return;
490
491 if (entry->button_flags == PDF_LAYER_UI_RADIOBOX)
492 clear_radio_group(ctx, doc, desc->ocgs[entry->ocg].obj);
493
494 desc->ocgs[entry->ocg].state = 1;
495 }
496
497 void pdf_toggle_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui)
498 {
499 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
500 pdf_ocg_ui *entry;
501 int selected;
502
503 if (ui < 0 || ui >= desc->num_ui_entries)
504 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry toggled");
505
506 entry = &desc->ui[ui];
507 if (entry->button_flags != PDF_LAYER_UI_RADIOBOX &&
508 entry->button_flags != PDF_LAYER_UI_CHECKBOX)
509 return;
510 if (entry->locked)
511 return;
512
513 selected = desc->ocgs[entry->ocg].state;
514
515 if (entry->button_flags == PDF_LAYER_UI_RADIOBOX)
516 clear_radio_group(ctx, doc, desc->ocgs[entry->ocg].obj);
517
518 desc->ocgs[entry->ocg].state = !selected;
519 }
520
521 void pdf_deselect_layer_config_ui(fz_context *ctx, pdf_document *doc, int ui)
522 {
523 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
524 pdf_ocg_ui *entry;
525
526 if (ui < 0 || ui >= desc->num_ui_entries)
527 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry deselected");
528
529 entry = &desc->ui[ui];
530 if (entry->button_flags != PDF_LAYER_UI_RADIOBOX &&
531 entry->button_flags != PDF_LAYER_UI_CHECKBOX)
532 return;
533 if (entry->locked)
534 return;
535
536 desc->ocgs[entry->ocg].state = 0;
537 }
538
539 void
540 pdf_layer_config_ui_info(fz_context *ctx, pdf_document *doc, int ui, pdf_layer_config_ui *info)
541 {
542 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
543 pdf_ocg_ui *entry;
544
545 if (!info)
546 return;
547
548 info->depth = 0;
549 info->locked = 0;
550 info->selected = 0;
551 info->text = NULL;
552 info->type = 0;
553
554 if (ui < 0 || ui >= desc->num_ui_entries)
555 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Out of range UI entry selected");
556
557 entry = &desc->ui[ui];
558 info->type = entry->button_flags;
559 info->depth = entry->depth;
560 info->selected = desc->ocgs[entry->ocg].state;
561 info->locked = entry->locked;
562 info->text = entry->name;
563 }
564
565 static int
566 ocg_intents_include(fz_context *ctx, pdf_ocg_descriptor *desc, const char *name)
567 {
568 int i, len;
569
570 if (strcmp(name, "All") == 0)
571 return 1;
572
573 /* In the absence of a specified intent, it's 'View' */
574 if (!desc->intent)
575 return (strcmp(name, "View") == 0);
576
577 if (pdf_is_name(ctx, desc->intent))
578 {
579 const char *intent = pdf_to_name(ctx, desc->intent);
580 if (strcmp(intent, "All") == 0)
581 return 1;
582 return (strcmp(intent, name) == 0);
583 }
584 if (!pdf_is_array(ctx, desc->intent))
585 return 0;
586
587 len = pdf_array_len(ctx, desc->intent);
588 for (i=0; i < len; i++)
589 {
590 const char *intent = pdf_array_get_name(ctx, desc->intent, i);
591 if (strcmp(intent, "All") == 0)
592 return 1;
593 if (strcmp(intent, name) == 0)
594 return 1;
595 }
596 return 0;
597 }
598
599 static int
600 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)
601 {
602 pdf_cycle_list cycle;
603 pdf_ocg_descriptor *desc = pdf_read_ocg(ctx, doc);
604 pdf_obj *obj, *obj2, *type;
605 char event_state[16];
606
607 /* If no usage, everything is visible */
608 if (!usage)
609 return 0;
610
611 /* If no ocg descriptor or no ocgs described, everything is visible */
612 if (!desc || desc->len == 0)
613 return 0;
614
615 /* If we've been handed a name, look it up in the properties. */
616 if (pdf_is_name(ctx, ocg))
617 {
618 ocg = pdf_dict_get(ctx, pdf_dict_get(ctx, rdb, PDF_NAME(Properties)), ocg);
619 }
620 /* If we haven't been given an ocg at all, then we're visible */
621 if (!ocg)
622 return 0;
623
624 /* Avoid infinite recursions */
625 if (pdf_cycle(ctx, &cycle, cycle_up, ocg))
626 return 0;
627
628 fz_strlcpy(event_state, usage, sizeof event_state);
629 fz_strlcat(event_state, "State", sizeof event_state);
630
631 type = pdf_dict_get(ctx, ocg, PDF_NAME(Type));
632
633 if (pdf_name_eq(ctx, type, PDF_NAME(OCG)))
634 {
635 /* An Optional Content Group */
636 int default_value = 0;
637 int len = desc->len;
638 int i;
639 pdf_obj *es;
640
641 /* by default an OCG is visible, unless it's explicitly hidden */
642 for (i = 0; i < len; i++)
643 {
644 /* Deliberately do NOT resolve here. Bug 702261. */
645 if (!pdf_objcmp(ctx, desc->ocgs[i].obj, ocg))
646 {
647 default_value = !desc->ocgs[i].state;
648 break;
649 }
650 }
651
652 /* Check Intents; if our intent is not part of the set given
653 * by the current config, we should ignore it. */
654 obj = pdf_dict_get(ctx, ocg, PDF_NAME(Intent));
655 if (pdf_is_name(ctx, obj))
656 {
657 /* If it doesn't match, it's hidden */
658 if (ocg_intents_include(ctx, desc, pdf_to_name(ctx, obj)) == 0)
659 return 1;
660 }
661 else if (pdf_is_array(ctx, obj))
662 {
663 int match = 0;
664 len = pdf_array_len(ctx, obj);
665 for (i=0; i<len; i++) {
666 match |= ocg_intents_include(ctx, desc, pdf_array_get_name(ctx, obj, i));
667 if (match)
668 break;
669 }
670 /* If we don't match any, it's hidden */
671 if (match == 0)
672 return 1;
673 }
674 else
675 {
676 /* If it doesn't match, it's hidden */
677 if (ocg_intents_include(ctx, desc, "View") == 0)
678 return 1;
679 }
680
681 /* FIXME: Currently we do a very simple check whereby we look
682 * at the Usage object (an Optional Content Usage Dictionary)
683 * and check to see if the corresponding 'event' key is on
684 * or off.
685 *
686 * Really we should only look at Usage dictionaries that
687 * correspond to entries in the AS list in the OCG config.
688 * Given that we don't handle Zoom or User, or Language
689 * dicts, this is not really a problem. */
690 obj = pdf_dict_get(ctx, ocg, PDF_NAME(Usage));
691 if (!pdf_is_dict(ctx, obj))
692 return default_value;
693 /* FIXME: Should look at Zoom (and return hidden if out of
694 * max/min range) */
695 /* FIXME: Could provide hooks to the caller to check if
696 * User is appropriate - if not return hidden. */
697 obj2 = pdf_dict_gets(ctx, obj, usage);
698 es = pdf_dict_gets(ctx, obj2, event_state);
699 if (pdf_name_eq(ctx, es, PDF_NAME(OFF)))
700 return 1;
701 return default_value;
702 }
703 else if (pdf_name_eq(ctx, type, PDF_NAME(OCMD)))
704 {
705 /* An Optional Content Membership Dictionary */
706 pdf_obj *name;
707 int combine, on = 0;
708
709 obj = pdf_dict_get(ctx, ocg, PDF_NAME(VE));
710 if (pdf_is_array(ctx, obj)) {
711 /* FIXME: Calculate visibility from array */
712 return 0;
713 }
714 name = pdf_dict_get(ctx, ocg, PDF_NAME(P));
715 /* Set combine; Bit 0 set => AND, Bit 1 set => true means
716 * Off, otherwise true means On */
717 if (pdf_name_eq(ctx, name, PDF_NAME(AllOn)))
718 {
719 combine = 1;
720 }
721 else if (pdf_name_eq(ctx, name, PDF_NAME(AnyOff)))
722 {
723 combine = 2;
724 }
725 else if (pdf_name_eq(ctx, name, PDF_NAME(AllOff)))
726 {
727 combine = 3;
728 }
729 else /* Assume it's the default (AnyOn) */
730 {
731 combine = 0;
732 }
733
734 obj = pdf_dict_get(ctx, ocg, PDF_NAME(OCGs));
735 on = combine & 1;
736 if (pdf_is_array(ctx, obj)) {
737 int i, len;
738 len = pdf_array_len(ctx, obj);
739 for (i = 0; i < len; i++)
740 {
741 int hidden = pdf_is_ocg_hidden_imp(ctx, doc, rdb, usage, pdf_array_get(ctx, obj, i), &cycle);
742 if ((combine & 1) == 0)
743 hidden = !hidden;
744 if (combine & 2)
745 on &= hidden;
746 else
747 on |= hidden;
748 }
749 }
750 else
751 {
752 on = pdf_is_ocg_hidden_imp(ctx, doc, rdb, usage, obj, &cycle);
753 if ((combine & 1) == 0)
754 on = !on;
755 }
756
757 return !on;
758 }
759 /* No idea what sort of object this is - be visible */
760 return 0;
761 }
762
763 int
764 pdf_is_ocg_hidden(fz_context *ctx, pdf_document *doc, pdf_obj *rdb, const char *usage, pdf_obj *ocg)
765 {
766 return pdf_is_ocg_hidden_imp(ctx, doc, rdb, usage, ocg, NULL);
767 }
768
769 pdf_ocg_descriptor *
770 pdf_read_ocg(fz_context *ctx, pdf_document *doc)
771 {
772 pdf_obj *prop, *ocgs, *configs;
773 int len, i, num_configs;
774
775 if (doc->ocg)
776 return doc->ocg;
777
778 fz_try(ctx)
779 {
780 prop = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)), PDF_NAME(OCProperties));
781
782 configs = pdf_dict_get(ctx, prop, PDF_NAME(Configs));
783 num_configs = pdf_array_len(ctx, configs);
784 ocgs = pdf_dict_get(ctx, prop, PDF_NAME(OCGs));
785 len = pdf_array_len(ctx, ocgs);
786
787 doc->ocg = fz_malloc_struct(ctx, pdf_ocg_descriptor);
788 doc->ocg->ocgs = fz_calloc(ctx, len, sizeof(*doc->ocg->ocgs));
789 doc->ocg->len = len;
790 doc->ocg->num_configs = num_configs;
791
792 for (i = 0; i < len; i++)
793 {
794 pdf_obj *o = pdf_array_get(ctx, ocgs, i);
795 doc->ocg->ocgs[i].obj = pdf_keep_obj(ctx, o);
796 doc->ocg->ocgs[i].n = pdf_to_num(ctx, o);
797 doc->ocg->ocgs[i].state = 1;
798 }
799 qsort(doc->ocg->ocgs, len, sizeof(doc->ocg->ocgs[0]), ocgcmp);
800
801 pdf_select_layer_config(ctx, doc, 0);
802 }
803 fz_catch(ctx)
804 {
805 pdf_drop_ocg(ctx, doc);
806 doc->ocg = NULL;
807 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
808 fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
809 fz_report_error(ctx);
810 fz_warn(ctx, "Ignoring broken Optional Content configuration");
811 doc->ocg = fz_malloc_struct(ctx, pdf_ocg_descriptor);
812 }
813
814 return doc->ocg;
815 }
816
817 void
818 pdf_set_layer_config_as_default(fz_context *ctx, pdf_document *doc)
819 {
820 pdf_obj *ocprops, *d, *order, *on, *configs, *rbgroups;
821 int k;
822
823 ocprops = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/OCProperties");
824 if (!ocprops)
825 return;
826
827 /* All files with OCGs are required to have a D entry */
828 d = pdf_dict_get(ctx, ocprops, PDF_NAME(D));
829 if (d == NULL)
830 return;
831
832 pdf_dict_put(ctx, d, PDF_NAME(BaseState), PDF_NAME(OFF));
833
834 /* We are about to delete RBGroups and Order, from D. These are
835 * both the underlying defaults for other configs, so copy the
836 * current values out to any config that doesn't have one
837 * already. */
838 order = pdf_dict_get(ctx, d, PDF_NAME(Order));
839 rbgroups = pdf_dict_get(ctx, d, PDF_NAME(RBGroups));
840 configs = pdf_dict_get(ctx, ocprops, PDF_NAME(Configs));
841 if (configs)
842 {
843 int len = pdf_array_len(ctx, configs);
844 for (k=0; k < len; k++)
845 {
846 pdf_obj *config = pdf_array_get(ctx, configs, k);
847
848 if (order && !pdf_dict_get(ctx, config, PDF_NAME(Order)))
849 pdf_dict_put(ctx, config, PDF_NAME(Order), order);
850 if (rbgroups && !pdf_dict_get(ctx, config, PDF_NAME(RBGroups)))
851 pdf_dict_put(ctx, config, PDF_NAME(RBGroups), rbgroups);
852 }
853 }
854
855 /* Offer all the layers in the UI */
856 order = pdf_new_array(ctx, doc, 4);
857 on = pdf_new_array(ctx, doc, 4);
858 for (k = 0; k < doc->ocg->len; k++)
859 {
860 pdf_ocg_entry *s = &doc->ocg->ocgs[k];
861
862 pdf_array_push(ctx, order, s->obj);
863 if (s->state)
864 pdf_array_push(ctx, on, s->obj);
865 }
866 pdf_dict_put(ctx, d, PDF_NAME(Order), order);
867 pdf_dict_put(ctx, d, PDF_NAME(ON), on);
868 pdf_dict_del(ctx, d, PDF_NAME(OFF));
869 pdf_dict_del(ctx, d, PDF_NAME(AS));
870 pdf_dict_put(ctx, d, PDF_NAME(Intent), PDF_NAME(View));
871 pdf_dict_del(ctx, d, PDF_NAME(Name));
872 pdf_dict_del(ctx, d, PDF_NAME(Creator));
873 pdf_dict_del(ctx, d, PDF_NAME(RBGroups));
874 pdf_dict_del(ctx, d, PDF_NAME(Locked));
875
876 pdf_dict_del(ctx, ocprops, PDF_NAME(Configs));
877 }