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