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