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