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