/src/ghostpdl/pdf/pdf_optcontent.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2019-2025 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | /* Optional Content routines */ |
17 | | |
18 | | #include "pdf_int.h" |
19 | | #include "pdf_stack.h" |
20 | | #include "pdf_misc.h" |
21 | | #include "pdf_font_types.h" |
22 | | #include "pdf_gstate.h" |
23 | | #include "pdf_dict.h" |
24 | | #include "pdf_array.h" |
25 | | #include "pdf_doc.h" |
26 | | #include "pdf_mark.h" |
27 | | #include "pdf_optcontent.h" |
28 | | #include "pdf_loop_detect.h" |
29 | | |
30 | | |
31 | | /* Find the default value for an ocdict, based on contents of OCProperties */ |
32 | | /* NOTE: the spec says that if BaseState is present, it won't be set to "OFF", |
33 | | * but this doesn't seem to be the case (Bug 691491). Also, the spec |
34 | | * says the ON and OFF arrays are redundant in certain cases. We just |
35 | | * look at everything anyway. |
36 | | * Default is going to be visible unless anything here indicates that it |
37 | | * should be turned off. |
38 | | */ |
39 | | static bool |
40 | | pdfi_get_default_OCG_val(pdf_context *ctx, pdf_dict *ocdict) |
41 | 74.0k | { |
42 | 74.0k | bool is_visible = true; |
43 | 74.0k | pdf_dict *D = NULL; |
44 | 74.0k | pdf_obj *BaseState = NULL; |
45 | 74.0k | pdf_array *OFF = NULL; |
46 | 74.0k | pdf_array *ON = NULL; |
47 | 74.0k | int code; |
48 | | |
49 | 74.0k | if (ctx->OCProperties == NULL) |
50 | 4.65k | return is_visible; |
51 | | |
52 | 69.4k | code = pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D); |
53 | 69.4k | if (code <= 0) |
54 | 4 | goto cleanup; |
55 | | |
56 | 69.4k | code = pdfi_dict_knownget_type(ctx, D, "BaseState", PDF_NAME, &BaseState); |
57 | 69.4k | if (code < 0) { |
58 | 0 | goto cleanup; |
59 | 0 | } |
60 | 69.4k | if (code > 0) { |
61 | 7 | if (pdfi_name_is((pdf_name *)BaseState, "OFF")) { |
62 | 0 | is_visible = false; |
63 | 0 | } |
64 | 7 | } |
65 | | |
66 | 69.4k | if (!is_visible) { |
67 | 0 | code = pdfi_dict_knownget_type(ctx, D, "ON", PDF_ARRAY, (pdf_obj **)&ON); |
68 | 0 | if (code < 0) |
69 | 0 | goto cleanup; |
70 | 0 | if (code > 0) { |
71 | 0 | if (pdfi_array_known(ctx, ON, (pdf_obj *)ocdict, NULL)) |
72 | 0 | is_visible = true; |
73 | 0 | } |
74 | 0 | } |
75 | | |
76 | 69.4k | if (is_visible) { |
77 | 69.4k | code = pdfi_dict_knownget_type(ctx, D, "OFF", PDF_ARRAY, (pdf_obj **)&OFF); |
78 | 69.4k | if (code < 0) |
79 | 2.27k | goto cleanup; |
80 | 67.1k | if (code > 0) { |
81 | 66.0k | if (pdfi_array_known(ctx, OFF, (pdf_obj *)ocdict, NULL)) |
82 | 63.7k | is_visible = false; |
83 | 66.0k | } |
84 | 67.1k | } |
85 | | |
86 | | |
87 | 69.4k | cleanup: |
88 | 69.4k | pdfi_countdown(BaseState); |
89 | 69.4k | pdfi_countdown(D); |
90 | 69.4k | pdfi_countdown(OFF); |
91 | 69.4k | pdfi_countdown(ON); |
92 | 69.4k | return is_visible; |
93 | 69.4k | } |
94 | | |
95 | | /* Check Usage for an OCG */ |
96 | | static bool |
97 | | pdfi_oc_check_OCG_usage(pdf_context *ctx, pdf_dict *ocdict) |
98 | 28 | { |
99 | 28 | bool is_visible = true; |
100 | 28 | int code; |
101 | 28 | pdf_dict *Usage = NULL; |
102 | 28 | pdf_dict *dict = NULL; |
103 | 28 | pdf_obj *name = NULL; |
104 | | |
105 | | /* Check Usage to see if it has additional info */ |
106 | 28 | code = pdfi_dict_knownget_type(ctx, ocdict, "Usage", PDF_DICT, (pdf_obj **)&Usage); |
107 | 28 | if (code <= 0) { |
108 | | /* No Usage, so we're done */ |
109 | 7 | goto cleanup; |
110 | 7 | } |
111 | | |
112 | 21 | if (ctx->args.printed) { |
113 | 21 | code = pdfi_dict_knownget_type(ctx, Usage, "Print", PDF_DICT, (pdf_obj **)&dict); |
114 | 21 | if (code <= 0) |
115 | 0 | goto cleanup; |
116 | 21 | code = pdfi_dict_knownget_type(ctx, dict, "PrintState", PDF_NAME, &name); |
117 | 21 | if (code <= 0) |
118 | 0 | goto cleanup; |
119 | 21 | } else { |
120 | 0 | code = pdfi_dict_knownget_type(ctx, Usage, "View", PDF_DICT, (pdf_obj **)&dict); |
121 | 0 | if (code <= 0) |
122 | 0 | goto cleanup; |
123 | 0 | code = pdfi_dict_knownget_type(ctx, dict, "ViewState", PDF_NAME, &name); |
124 | 0 | if (code <= 0) |
125 | 0 | goto cleanup; |
126 | 0 | } |
127 | 21 | if (pdfi_name_strcmp((pdf_name *)name, "OFF") == 0) { |
128 | 0 | is_visible = false; |
129 | 0 | } |
130 | | |
131 | 28 | cleanup: |
132 | 28 | pdfi_countdown(Usage); |
133 | 28 | pdfi_countdown(dict); |
134 | 28 | pdfi_countdown(name); |
135 | | |
136 | 28 | return is_visible; |
137 | 21 | } |
138 | | |
139 | | typedef enum { |
140 | | P_AnyOn, |
141 | | P_AllOn, |
142 | | P_AllOff, |
143 | | P_AnyOff |
144 | | } ocmd_p_type; |
145 | | |
146 | | static bool pdfi_oc_default_visibility(pdf_context *ctx) |
147 | 4.90k | { |
148 | 4.90k | int code; |
149 | 4.90k | pdf_dict *D_dict = NULL; |
150 | 4.90k | pdf_name *B = NULL; |
151 | | |
152 | 4.90k | code = pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D_dict); |
153 | 4.90k | if (code < 0 || D_dict == NULL) |
154 | 0 | return true; |
155 | | |
156 | 4.90k | code = pdfi_dict_knownget_type(ctx, D_dict, "BaseState", PDF_NAME, (pdf_obj **)&B); |
157 | 4.90k | (void)pdfi_countdown(D_dict); |
158 | 4.90k | D_dict = NULL; |
159 | 4.90k | if (code < 0 || B == NULL) |
160 | 4.90k | return true; |
161 | | |
162 | 0 | if (pdfi_name_is(B, "OFF")) { |
163 | 0 | (void)pdfi_countdown(B); |
164 | 0 | return false; |
165 | 0 | } |
166 | | |
167 | 0 | (void)pdfi_countdown(B); |
168 | 0 | return true; |
169 | 0 | } |
170 | | |
171 | | static bool |
172 | | pdfi_oc_check_OCMD_array(pdf_context *ctx, pdf_array *array, ocmd_p_type type) |
173 | 74.0k | { |
174 | 74.0k | bool is_visible, hit = false; |
175 | 74.0k | uint64_t i; |
176 | 74.0k | int code; |
177 | 74.0k | pdf_obj *val = NULL; |
178 | | |
179 | | /* Setup default */ |
180 | 74.0k | switch (type) { |
181 | 74.0k | case P_AnyOn: |
182 | 74.0k | case P_AnyOff: |
183 | 74.0k | is_visible = false; |
184 | 74.0k | break; |
185 | 0 | case P_AllOn: |
186 | 0 | case P_AllOff: |
187 | 0 | is_visible = true; |
188 | 0 | break; |
189 | 74.0k | } |
190 | | |
191 | 142k | for (i=0; i<pdfi_array_size(array); i++) { |
192 | 74.0k | bool vis; |
193 | | |
194 | 74.0k | code = pdfi_array_get(ctx, array, i, &val); |
195 | 74.0k | if (code < 0) continue; |
196 | 69.3k | if (pdfi_type_of(val) != PDF_DICT) { |
197 | 1 | dbgprintf1("WARNING: OCMD array contains item type %d, expected PDF_DICT or PDF_NULL\n", pdfi_type_of(val)); |
198 | 1 | pdfi_countdown(val); |
199 | 1 | val = NULL; |
200 | 1 | continue; |
201 | 1 | } |
202 | | |
203 | 69.3k | hit = true; |
204 | 69.3k | vis = pdfi_get_default_OCG_val(ctx, (pdf_dict *)val); |
205 | 69.3k | switch (type) { |
206 | 69.3k | case P_AnyOn: |
207 | | /* visible if any is on */ |
208 | 69.3k | if (vis) { |
209 | 5.69k | is_visible = true; |
210 | 5.69k | goto cleanup; |
211 | 5.69k | } |
212 | 63.6k | break; |
213 | 63.6k | case P_AllOn: |
214 | | /* visible if all on */ |
215 | 0 | if (!vis) { |
216 | 0 | is_visible = false; |
217 | 0 | goto cleanup; |
218 | 0 | } |
219 | 0 | break; |
220 | 0 | case P_AllOff: |
221 | | /* visible if all are off */ |
222 | 0 | if (vis) { |
223 | 0 | is_visible = false; |
224 | 0 | goto cleanup; |
225 | 0 | } |
226 | 0 | break; |
227 | 0 | case P_AnyOff: |
228 | | /* visible if any is off */ |
229 | 0 | if (!vis) { |
230 | 0 | is_visible = true; |
231 | 0 | goto cleanup; |
232 | 0 | } |
233 | 0 | break; |
234 | 69.3k | } |
235 | 63.6k | pdfi_countdown(val); |
236 | 63.6k | val = NULL; |
237 | 63.6k | } |
238 | | |
239 | | /* If the array was empty, or contained only null or deleted entries, then it has no effect |
240 | | * PDF Reference 1.7 p366, table 4.49, OCGs entry. I'm interpreting this to mean that we should use |
241 | | * the OCProperties 'D' dictionary, set the visibility state to the BaseState. Since this is an OCMD |
242 | | * I'm assuming that the ON and OFF arrays (which apply to Optional Content Groups) shouold not be |
243 | | * consulted. We need to cater for the fact that BaseState is optional (but default is ON). D is |
244 | | * specified as 'Required' but it seems best not to take any chances on that! |
245 | | */ |
246 | 68.3k | if (!hit) |
247 | 4.75k | is_visible = pdfi_oc_default_visibility(ctx); |
248 | | |
249 | 74.0k | cleanup: |
250 | 74.0k | pdfi_countdown(val); |
251 | 74.0k | return is_visible; |
252 | 68.3k | } |
253 | | |
254 | | static bool |
255 | | pdfi_oc_check_OCMD(pdf_context *ctx, pdf_dict *ocdict) |
256 | 74.7k | { |
257 | 74.7k | bool is_visible = true; |
258 | 74.7k | int code; |
259 | 74.7k | pdf_obj *VE = NULL; |
260 | 74.7k | pdf_obj *obj = NULL; |
261 | 74.7k | pdf_obj *Pname = NULL; |
262 | 74.7k | pdf_dict *OCGs_dict = NULL; /* alias, don't need to free */ |
263 | 74.7k | pdf_array *OCGs_array = NULL; /* alias, don't need to free */ |
264 | 74.7k | pdf_dict *UsageDict = NULL, *StateDict = NULL; |
265 | 74.7k | pdf_obj *State = NULL; |
266 | 74.7k | ocmd_p_type Ptype = P_AnyOn; |
267 | | |
268 | | /* TODO: We don't support this, so log a warning and ignore */ |
269 | 74.7k | code = pdfi_dict_knownget_type(ctx, ocdict, "VE", PDF_ARRAY, &VE); |
270 | 74.7k | if (code > 0) { |
271 | 241 | dbgprintf("WARNING: OCMD contains VE, which is not supported (ignoring)\n"); |
272 | 241 | } |
273 | | |
274 | 74.7k | code = pdfi_dict_knownget(ctx, ocdict, "OCGs", &obj); |
275 | 74.7k | if (code <= 0) |
276 | 310 | goto cleanup; |
277 | 74.4k | if (pdfi_type_of(obj) == PDF_ARRAY) { |
278 | 74.0k | OCGs_array = (pdf_array *)obj; |
279 | 74.0k | } else if (pdfi_type_of(obj) == PDF_DICT) { |
280 | 212 | OCGs_dict = (pdf_dict *)obj; |
281 | 212 | } else { |
282 | 146 | is_visible = pdfi_oc_default_visibility(ctx); |
283 | 146 | goto cleanup; |
284 | 146 | } |
285 | | |
286 | 74.2k | code = pdfi_dict_knownget_type(ctx, ocdict, "P", PDF_NAME, &Pname); |
287 | 74.2k | if (code < 0) |
288 | 0 | goto cleanup; |
289 | 74.2k | if (code == 0 || pdfi_name_is((pdf_name *)Pname, "AnyOn")) { |
290 | 74.2k | Ptype = P_AnyOn; |
291 | 74.2k | } else if (pdfi_name_is((pdf_name *)Pname, "AllOn")) { |
292 | 0 | Ptype = P_AllOn; |
293 | 0 | } else if (pdfi_name_is((pdf_name *)Pname, "AnyOff")) { |
294 | 0 | Ptype = P_AnyOff; |
295 | 0 | } else if (pdfi_name_is((pdf_name *)Pname, "AllOff")) { |
296 | 0 | Ptype = P_AllOff; |
297 | 0 | } else { |
298 | 0 | Ptype = P_AnyOn; |
299 | 0 | } |
300 | | |
301 | 74.2k | if (OCGs_dict) { |
302 | 212 | switch (Ptype) { |
303 | 212 | case P_AnyOn: |
304 | 212 | case P_AllOn: |
305 | 212 | is_visible = pdfi_get_default_OCG_val(ctx, OCGs_dict); |
306 | 212 | break; |
307 | 0 | case P_AllOff: |
308 | 0 | case P_AnyOff: |
309 | 0 | is_visible = !pdfi_get_default_OCG_val(ctx, OCGs_dict); |
310 | 0 | break; |
311 | 212 | } |
312 | 74.0k | } else { |
313 | 74.0k | is_visible = pdfi_oc_check_OCMD_array(ctx, OCGs_array, Ptype); |
314 | 74.0k | } |
315 | | |
316 | 74.2k | if (OCGs_dict) { |
317 | 212 | code = pdfi_dict_knownget_type(ctx, OCGs_dict, "Usage", PDF_DICT, (pdf_obj **)&UsageDict); |
318 | 212 | if (code < 0) |
319 | 0 | goto cleanup; |
320 | | |
321 | 212 | if (UsageDict != NULL) { |
322 | 100 | if (ctx->args.printed) { |
323 | 100 | code = pdfi_dict_knownget_type(ctx, UsageDict, "Print", PDF_DICT, (pdf_obj **)&StateDict); |
324 | 100 | if (code < 0) |
325 | 0 | goto cleanup; |
326 | 100 | if (StateDict) { |
327 | 25 | code = pdfi_dict_knownget_type(ctx, StateDict, "PrintState", PDF_NAME, &State); |
328 | 25 | if (code < 0) |
329 | 0 | goto cleanup; |
330 | 25 | } |
331 | 100 | } else { |
332 | 0 | code = pdfi_dict_knownget_type(ctx, UsageDict, "View", PDF_DICT, (pdf_obj **)&StateDict); |
333 | 0 | if (code < 0) |
334 | 0 | goto cleanup; |
335 | 0 | if (StateDict) { |
336 | 0 | code = pdfi_dict_knownget_type(ctx, StateDict, "ViewState", PDF_NAME, &State); |
337 | 0 | if (code < 0) |
338 | 0 | goto cleanup; |
339 | 0 | } |
340 | 0 | } |
341 | 100 | if (State) { |
342 | 25 | if (pdfi_name_is((const pdf_name *)State, "ON")) |
343 | 25 | is_visible = true; |
344 | 0 | else |
345 | 0 | if (pdfi_name_is((const pdf_name *)State, "OFF")) |
346 | 0 | is_visible = false; |
347 | 0 | else |
348 | 0 | pdfi_set_error(ctx, 0, NULL, E_PDF_BAD_VALUE, "pdfi_oc_check_OCMD", "Usage Dictionary State is neither ON nor OFF"); |
349 | 25 | } |
350 | 100 | } |
351 | 212 | } |
352 | | |
353 | 74.7k | cleanup: |
354 | 74.7k | pdfi_countdown(State); |
355 | 74.7k | pdfi_countdown(StateDict); |
356 | 74.7k | pdfi_countdown(UsageDict); |
357 | 74.7k | pdfi_countdown(VE); |
358 | 74.7k | pdfi_countdown(obj); |
359 | 74.7k | pdfi_countdown(Pname); |
360 | | |
361 | 74.7k | return is_visible; |
362 | 74.2k | } |
363 | | |
364 | | static bool pdfi_must_check_usage(pdf_context *ctx, int64_t object) |
365 | 4.56k | { |
366 | 4.56k | pdf_dict *D = NULL, *EventDict = NULL; |
367 | 4.56k | pdf_array *AS = NULL, *OCGs = NULL; |
368 | 4.56k | pdf_name *Event = NULL; |
369 | 4.56k | bool check = false; |
370 | 4.56k | int i, code = 0; |
371 | | |
372 | 4.56k | if (ctx->OCProperties != NULL) { |
373 | 3.21k | if (pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D)) { |
374 | 3.21k | if (pdfi_dict_knownget_type(ctx, D, "AS", PDF_ARRAY, (pdf_obj **)&AS)) { |
375 | 56 | for (i = 0; i < pdfi_array_size(AS); i++) { |
376 | 56 | code = pdfi_array_get(ctx, AS, i, (pdf_obj **)&EventDict); |
377 | 56 | if (code < 0) |
378 | 0 | goto exit; |
379 | 56 | if (pdfi_type_of(EventDict) == PDF_DICT) { |
380 | 56 | if (pdfi_dict_knownget_type(ctx, EventDict, "Event", PDF_NAME, (pdf_obj **)&Event)) { |
381 | 56 | if (ctx->args.printed) { |
382 | 56 | if (Event->length == 5 && strncmp((const char *)Event->data, "Print", 5) == 0) { |
383 | 28 | if (pdfi_dict_knownget_type(ctx, EventDict, "OCGs", PDF_ARRAY, (pdf_obj **)&OCGs)) { |
384 | 28 | pdfi_countdown(Event); |
385 | 28 | Event = NULL; |
386 | 28 | pdfi_countdown(EventDict); |
387 | 28 | EventDict = NULL; |
388 | 28 | break; |
389 | 28 | } |
390 | 28 | } |
391 | 56 | } else { |
392 | 0 | if (Event->length == 4 && strncmp((const char *)Event->data, "View", 4) == 0) { |
393 | 0 | if (pdfi_dict_knownget_type(ctx, EventDict, "OCGs", PDF_ARRAY, (pdf_obj **)&OCGs)) { |
394 | 0 | pdfi_countdown(Event); |
395 | 0 | Event = NULL; |
396 | 0 | pdfi_countdown(EventDict); |
397 | 0 | EventDict = NULL; |
398 | 0 | break; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | } |
402 | 28 | pdfi_countdown(Event); |
403 | 28 | Event = NULL; |
404 | 28 | } |
405 | 56 | } |
406 | 28 | pdfi_countdown(EventDict); |
407 | 28 | EventDict = NULL; |
408 | 28 | } |
409 | 28 | pdfi_countdown(AS); |
410 | 28 | AS = NULL; |
411 | 28 | if (OCGs) { |
412 | 28 | pdf_obj *Group = NULL; |
413 | | |
414 | 49 | for (i = 0; i < pdfi_array_size(OCGs); i++) { |
415 | 49 | code = pdfi_array_get(ctx, OCGs, i, (pdf_obj **)&Group); |
416 | 49 | if (code < 0) |
417 | 0 | goto exit; |
418 | 49 | if (Group->object_num == object) { |
419 | 28 | pdfi_countdown(Group); |
420 | 28 | check = true; |
421 | 28 | goto exit; |
422 | 28 | } |
423 | 21 | pdfi_countdown(Group); |
424 | 21 | Group = NULL; |
425 | 21 | } |
426 | 0 | pdfi_countdown(OCGs); |
427 | 0 | OCGs = NULL; |
428 | 0 | } |
429 | 28 | } |
430 | 3.18k | pdfi_countdown(D); |
431 | 3.18k | D = NULL; |
432 | 3.18k | } |
433 | 3.21k | } |
434 | 4.56k | exit: |
435 | 4.56k | pdfi_countdown(OCGs); |
436 | 4.56k | pdfi_countdown(Event); |
437 | 4.56k | pdfi_countdown(EventDict); |
438 | 4.56k | pdfi_countdown(AS); |
439 | 4.56k | pdfi_countdown(D); |
440 | 4.56k | return check; |
441 | 4.56k | } |
442 | | |
443 | | /* Check if an OCG or OCMD is visible, passing in OC dict */ |
444 | | bool |
445 | | pdfi_oc_is_ocg_visible(pdf_context *ctx, pdf_dict *ocdict) |
446 | 79.6k | { |
447 | 79.6k | pdf_name *type = NULL; |
448 | 79.6k | bool is_visible = true; |
449 | 79.6k | int code; |
450 | | |
451 | | /* Type can be either OCMD or OCG. |
452 | | */ |
453 | 79.6k | code = pdfi_dict_knownget_type(ctx, ocdict, "Type", PDF_NAME, (pdf_obj **)&type); |
454 | 79.6k | if (code <= 0) |
455 | 79 | goto cleanup; |
456 | | |
457 | 79.6k | if (pdfi_name_is(type, "OCMD")) { |
458 | 74.7k | is_visible = pdfi_oc_check_OCMD(ctx, ocdict); |
459 | 74.7k | } else if (pdfi_name_is(type, "OCG")) { |
460 | 4.56k | is_visible = pdfi_get_default_OCG_val(ctx, ocdict); |
461 | 4.56k | if (pdfi_must_check_usage(ctx, ocdict->object_num)) |
462 | 28 | is_visible = pdfi_oc_check_OCG_usage(ctx, ocdict); |
463 | 4.56k | } else { |
464 | 306 | char str[100]; |
465 | 306 | memcpy(str, (const char *)type->data, type->length < 100 ? type->length : 99); |
466 | 306 | str[type->length < 100 ? type->length : 99] = '\0'; |
467 | 306 | dbgprintf1("WARNING: OC dict type is %s, expected OCG or OCMD\n", str); |
468 | 306 | } |
469 | | |
470 | 79.6k | cleanup: |
471 | 79.6k | pdfi_countdown(type); |
472 | | |
473 | 79.6k | if (ctx->args.pdfdebug) { |
474 | 0 | outprintf(ctx->memory, "OCG: OC Dict %d %s visible\n", ocdict->object_num, |
475 | 0 | is_visible ? "IS" : "IS NOT"); |
476 | 0 | } |
477 | 79.6k | return is_visible; |
478 | 79.6k | } |
479 | | |
480 | 246k | #define NUM_CONTENT_LEVELS 100 |
481 | | typedef struct { |
482 | | byte *flags; |
483 | | uint64_t num_off; |
484 | | uint64_t max_flags; |
485 | | } pdfi_oc_levels_t; |
486 | | |
487 | | static int pdfi_oc_levels_init(pdf_context *ctx, pdfi_oc_levels_t **levels) |
488 | 123k | { |
489 | 123k | byte *data; |
490 | 123k | pdfi_oc_levels_t *new; |
491 | | |
492 | 123k | *levels = NULL; |
493 | | |
494 | 123k | new = (pdfi_oc_levels_t *)gs_alloc_bytes(ctx->memory, sizeof(pdfi_oc_levels_t), |
495 | 123k | "pdfi_oc_levels_init (levels)"); |
496 | 123k | if (!new) |
497 | 0 | return_error(gs_error_VMerror); |
498 | | |
499 | 123k | data = (byte *)gs_alloc_bytes(ctx->memory, NUM_CONTENT_LEVELS, "pdfi_oc_levels_init (data)"); |
500 | 123k | if (!data) { |
501 | 0 | gs_free_object(ctx->memory, new, "pdfi_oc_levels_init (levels (error))"); |
502 | 0 | return_error(gs_error_VMerror); |
503 | 0 | } |
504 | 123k | memset(data, 0, NUM_CONTENT_LEVELS); |
505 | | |
506 | 123k | new->flags = data; |
507 | 123k | new->num_off = 0; |
508 | 123k | new->max_flags = NUM_CONTENT_LEVELS; |
509 | 123k | *levels = new; |
510 | | |
511 | 123k | return 0; |
512 | 123k | } |
513 | | |
514 | | static int pdfi_oc_levels_free(pdf_context *ctx, pdfi_oc_levels_t *levels) |
515 | 149k | { |
516 | 149k | if (!levels) |
517 | 25.7k | return 0; |
518 | 123k | gs_free_object(ctx->memory, levels->flags, "pdfi_oc_levels_free (flags)"); |
519 | 123k | gs_free_object(ctx->memory, levels, "pdfi_oc_levels_free (levels)"); |
520 | | |
521 | 123k | return 0; |
522 | 149k | } |
523 | | |
524 | | static int pdfi_oc_levels_set(pdf_context *ctx, pdfi_oc_levels_t *levels, uint64_t index) |
525 | 63.6k | { |
526 | 63.6k | byte *new = NULL; |
527 | 63.6k | uint64_t newmax; |
528 | | |
529 | 63.6k | if (index > levels->max_flags - 1) { |
530 | | /* Expand the flags buffer */ |
531 | 1 | newmax = levels->max_flags + NUM_CONTENT_LEVELS; |
532 | 1 | if (index > newmax) |
533 | 0 | return_error(gs_error_Fatal); /* shouldn't happen */ |
534 | 1 | new = gs_alloc_bytes(ctx->memory, newmax, "pdfi_oc_levels_set (new data)"); |
535 | 1 | if (!new) |
536 | 0 | return_error(gs_error_VMerror); |
537 | 1 | memset(new, 0, newmax); |
538 | 1 | memcpy(new, levels->flags, levels->max_flags); |
539 | 1 | gs_free_object(ctx->memory, levels->flags, "pdfi_oc_levels_set (old data)"); |
540 | 1 | levels->flags = new; |
541 | 1 | levels->max_flags += NUM_CONTENT_LEVELS; |
542 | 1 | } |
543 | | |
544 | 63.6k | if (levels->flags[index] == 0) |
545 | 63.6k | levels->num_off ++; |
546 | 63.6k | levels->flags[index] = 1; |
547 | 63.6k | return 0; |
548 | 63.6k | } |
549 | | |
550 | | static int pdfi_oc_levels_clear(pdf_context *ctx, pdfi_oc_levels_t *levels, uint64_t index) |
551 | 596k | { |
552 | 596k | if (index > levels->max_flags - 1) |
553 | 273 | return -1; |
554 | 596k | if (levels->flags[index] != 0) |
555 | 63.5k | levels->num_off --; |
556 | 596k | levels->flags[index] = 0; |
557 | 596k | return 0; |
558 | 596k | } |
559 | | |
560 | | |
561 | | /* Test if content is turned off for this element. |
562 | | */ |
563 | | bool pdfi_oc_is_off(pdf_context *ctx) |
564 | 14.2M | { |
565 | 14.2M | pdfi_oc_levels_t *levels = (pdfi_oc_levels_t *)ctx->OFFlevels; |
566 | 14.2M | uint64_t num_off = levels->num_off; |
567 | | |
568 | 14.2M | return (num_off != 0); |
569 | 14.2M | } |
570 | | |
571 | | int pdfi_oc_init(pdf_context *ctx) |
572 | 123k | { |
573 | 123k | int code; |
574 | | |
575 | 123k | ctx->BMClevel = 0; |
576 | 123k | if (ctx->OFFlevels) { |
577 | 40.0k | pdfi_oc_levels_free(ctx, ctx->OFFlevels); |
578 | 40.0k | ctx->OFFlevels = NULL; |
579 | 40.0k | } |
580 | 123k | code = pdfi_oc_levels_init(ctx, (pdfi_oc_levels_t **)&ctx->OFFlevels); |
581 | 123k | if (code < 0) |
582 | 0 | return code; |
583 | | |
584 | 123k | return 0; |
585 | 123k | } |
586 | | |
587 | | int pdfi_oc_free(pdf_context *ctx) |
588 | 109k | { |
589 | 109k | int code; |
590 | | |
591 | 109k | code = pdfi_oc_levels_free(ctx, (pdfi_oc_levels_t *)ctx->OFFlevels); |
592 | 109k | ctx->OFFlevels = NULL; |
593 | 109k | return code; |
594 | 109k | } |
595 | | |
596 | | int pdfi_op_MP(pdf_context *ctx) |
597 | 3.18k | { |
598 | 3.18k | pdf_obj *o = NULL; |
599 | 3.18k | int code = 0; |
600 | | |
601 | 3.18k | if (pdfi_count_stack(ctx) < 1) |
602 | 89 | return_error(gs_error_stackunderflow); |
603 | | |
604 | 3.09k | if (!ctx->device_state.writepdfmarks || !ctx->args.preservemarkedcontent) { |
605 | 2.72k | pdfi_pop(ctx, 1); |
606 | 2.72k | goto exit; |
607 | 2.72k | } |
608 | | |
609 | 375 | o = ctx->stack_top[-1]; |
610 | 375 | pdfi_countup(o); |
611 | 375 | pdfi_pop(ctx, 1); |
612 | | |
613 | 375 | if (pdfi_type_of(o) != PDF_NAME) { |
614 | 2 | code = gs_note_error(gs_error_typecheck); |
615 | 2 | goto exit; |
616 | 2 | } |
617 | | |
618 | 373 | code = pdfi_pdfmark_from_objarray(ctx, &o, 1, NULL, "MP"); |
619 | 373 | ctx->BMClevel ++; |
620 | | |
621 | 3.09k | exit: |
622 | 3.09k | pdfi_countdown(o); |
623 | 3.09k | return code; |
624 | 373 | } |
625 | | |
626 | | int pdfi_op_DP(pdf_context *ctx, pdf_dict *stream_dict, pdf_dict *page_dict) |
627 | 905 | { |
628 | 905 | pdf_name *properties = NULL; |
629 | 905 | int code = 0; |
630 | 905 | pdf_obj **objarray = NULL, *o = NULL; |
631 | | |
632 | 905 | if (pdfi_count_stack(ctx) < 2) { |
633 | 15 | pdfi_clearstack(ctx); |
634 | 15 | return gs_note_error(gs_error_stackunderflow); |
635 | 15 | } |
636 | | |
637 | 890 | if (!ctx->device_state.writepdfmarks || !ctx->args.preservemarkedcontent) { |
638 | 767 | pdfi_pop(ctx, 2); /* pop args */ |
639 | 767 | goto exit; |
640 | 767 | } |
641 | | |
642 | 123 | if (pdfi_type_of(ctx->stack_top[-2]) != PDF_NAME) { |
643 | 0 | pdfi_pop(ctx, 2); /* pop args */ |
644 | 0 | code = gs_note_error(gs_error_typecheck); |
645 | 0 | goto exit; |
646 | 0 | } |
647 | | |
648 | 123 | objarray = (pdf_obj **)gs_alloc_bytes(ctx->memory, 2 * sizeof(pdf_obj *), "pdfi_op_DP"); |
649 | 123 | if (objarray == NULL) { |
650 | 0 | code = gs_note_error(gs_error_VMerror); |
651 | 0 | goto exit; |
652 | 0 | } |
653 | | |
654 | 123 | objarray[0] = ctx->stack_top[-2]; |
655 | 123 | pdfi_countup(objarray[0]); |
656 | 123 | o = ctx->stack_top[-1]; |
657 | 123 | pdfi_countup(o); |
658 | 123 | pdfi_pop(ctx, 2); /* pop args */ |
659 | | |
660 | 123 | switch (pdfi_type_of(o)) { |
661 | 0 | case PDF_NAME: |
662 | 0 | code = pdfi_find_resource(ctx, (unsigned char *)"Properties", (pdf_name *)o, stream_dict, page_dict, (pdf_obj **)&properties); |
663 | 0 | if(code < 0) |
664 | 0 | goto exit; |
665 | 0 | if (pdfi_type_of(properties) != PDF_DICT) { |
666 | 0 | code = gs_note_error(gs_error_typecheck); |
667 | 0 | goto exit; |
668 | 0 | } |
669 | 0 | objarray[1] = (pdf_obj *)properties; |
670 | 0 | break; |
671 | 123 | case PDF_DICT: |
672 | 123 | objarray[1] = o; |
673 | 123 | break; |
674 | 0 | default: |
675 | 0 | code = gs_note_error(gs_error_VMerror); |
676 | 0 | goto exit; |
677 | 123 | } |
678 | | |
679 | 123 | code = pdfi_pdfmark_from_objarray(ctx, objarray, 2, NULL, "DP"); |
680 | | |
681 | 890 | exit: |
682 | 890 | if (objarray != NULL) { |
683 | 123 | pdfi_countdown(objarray[0]); |
684 | 123 | gs_free_object(ctx->memory, objarray, "free pdfi_op_DP"); |
685 | 123 | } |
686 | 890 | pdfi_countdown(o); |
687 | 890 | pdfi_countdown(properties); |
688 | 890 | return code; |
689 | 123 | } |
690 | | |
691 | | /* begin marked content sequence */ |
692 | | int pdfi_op_BMC(pdf_context *ctx) |
693 | 26.0k | { |
694 | 26.0k | pdf_obj *o = NULL; |
695 | 26.0k | int code = 0; |
696 | | |
697 | | /* This will also prevent us writing out an EMC if the BMC is in any way invalid */ |
698 | 26.0k | ctx->BDCWasOC = true; |
699 | | |
700 | 26.0k | if (pdfi_count_stack(ctx) < 1) |
701 | 15 | return_error(gs_error_stackunderflow); |
702 | | |
703 | 26.0k | if (!ctx->device_state.writepdfmarks || !ctx->args.preservemarkedcontent) { |
704 | | /* Need to increment the BMCLevel anyway, as the EMC will count it down. |
705 | | * If we already have a BDC, then that effectively will turn it on. |
706 | | */ |
707 | 23.8k | ctx->BMClevel ++; |
708 | 23.8k | pdfi_pop(ctx, 1); |
709 | 23.8k | goto exit; |
710 | 23.8k | } |
711 | | |
712 | 2.14k | o = ctx->stack_top[-1]; |
713 | 2.14k | pdfi_countup(o); |
714 | 2.14k | pdfi_pop(ctx, 1); |
715 | | |
716 | 2.14k | if (pdfi_type_of(o) != PDF_NAME) { |
717 | 3 | code = gs_note_error(gs_error_typecheck); |
718 | 3 | goto exit; |
719 | 3 | } |
720 | | |
721 | 2.14k | ctx->BDCWasOC = false; |
722 | 2.14k | code = pdfi_pdfmark_from_objarray(ctx, &o, 1, NULL, "BMC"); |
723 | 2.14k | ctx->BMClevel ++; |
724 | | |
725 | 26.0k | exit: |
726 | 26.0k | pdfi_countdown(o); |
727 | 26.0k | return code; |
728 | 2.14k | } |
729 | | |
730 | | /* begin marked content sequence with property list */ |
731 | | int pdfi_op_BDC(pdf_context *ctx, pdf_dict *stream_dict, pdf_dict *page_dict) |
732 | 581k | { |
733 | 581k | pdf_name *tag = NULL; |
734 | 581k | pdf_name *properties = NULL; |
735 | 581k | pdf_dict *oc_dict = NULL; |
736 | 581k | int code = 0; |
737 | 581k | bool ocg_is_visible; |
738 | 581k | pdf_obj **objarray = NULL, *o = NULL; |
739 | 581k | pdf_indirect_ref *dictref = NULL; |
740 | | |
741 | | /* This will also prevent us writing out an EMC if the BDC is in any way invalid */ |
742 | 581k | ctx->BDCWasOC = true; |
743 | | |
744 | 581k | if (pdfi_count_stack(ctx) < 2) { |
745 | 9.71k | pdfi_clearstack(ctx); |
746 | 9.71k | return gs_note_error(gs_error_stackunderflow); |
747 | 9.71k | } |
748 | | |
749 | 571k | ctx->BMClevel ++; |
750 | | |
751 | 571k | tag = (pdf_name *)ctx->stack_top[-2]; |
752 | 571k | pdfi_countup(tag); |
753 | 571k | o = ctx->stack_top[-1]; |
754 | 571k | pdfi_countup(o); |
755 | 571k | pdfi_pop(ctx, 2); |
756 | | |
757 | 571k | if (pdfi_type_of(tag) != PDF_NAME) |
758 | 3.78k | goto exit; |
759 | | |
760 | 567k | if (!pdfi_name_is(tag, "OC")) |
761 | 382k | ctx->BDCWasOC = false; |
762 | | |
763 | 567k | if (ctx->device_state.writepdfmarks && ctx->args.preservemarkedcontent && (!ctx->BDCWasOC || ctx->device_state.WantsOptionalContent)) { |
764 | 52.3k | objarray = (pdf_obj **)gs_alloc_bytes(ctx->memory, 2 * sizeof(pdf_obj *), "pdfi_op_BDC"); |
765 | 52.3k | if (objarray == NULL) { |
766 | 0 | code = gs_note_error(gs_error_VMerror); |
767 | 0 | goto exit; |
768 | 0 | } |
769 | | |
770 | 52.3k | objarray[0] = (pdf_obj *)tag; |
771 | | |
772 | 52.3k | switch (pdfi_type_of(o)) { |
773 | 22.2k | case PDF_NAME: |
774 | 22.2k | code = pdfi_find_resource(ctx, (unsigned char *)"Properties", (pdf_name *)o, stream_dict, page_dict, (pdf_obj **)&oc_dict); |
775 | 22.2k | if(code < 0) |
776 | 11.1k | goto exit; |
777 | 11.1k | if (pdfi_type_of(oc_dict) != PDF_DICT) { |
778 | 129 | code = gs_note_error(gs_error_typecheck); |
779 | 129 | goto exit; |
780 | 129 | } |
781 | | /* If we are producing PDF/A we must not include any Metadata, as that |
782 | | * requires us to modify the XMP Metadata, which we don't know how to do. |
783 | | */ |
784 | 10.9k | if (ctx->args.PDFA > 0) { |
785 | 0 | uint64_t index = 0; |
786 | 0 | pdf_name *Key = NULL; |
787 | 0 | pdf_obj *Value = NULL; |
788 | |
|
789 | 0 | code = pdfi_dict_first(ctx, oc_dict, (pdf_obj **)&Key, &Value, &index); |
790 | 0 | if (code < 0) { |
791 | 0 | if (code == gs_error_undefined) |
792 | 0 | code = 0; |
793 | 0 | goto exit; |
794 | 0 | } |
795 | 0 | while (code >= 0) { |
796 | 0 | if (pdfi_name_is(Key, "Metadata")) { |
797 | 0 | pdfi_dict_delete_pair(ctx, oc_dict, Key); |
798 | 0 | } |
799 | 0 | pdfi_countdown(Key); |
800 | 0 | Key = NULL; |
801 | 0 | pdfi_countdown(Value); |
802 | 0 | Value = NULL; |
803 | |
|
804 | 0 | code = pdfi_dict_next(ctx, oc_dict, (pdf_obj **)&Key, &Value, &index); |
805 | 0 | if (code == gs_error_undefined) { |
806 | 0 | code = 0; |
807 | 0 | break; |
808 | 0 | } |
809 | 0 | } |
810 | 0 | } |
811 | 10.9k | if (pdfi_dict_entries(oc_dict) == 0) |
812 | 27 | goto exit; |
813 | | |
814 | 10.9k | code = pdfi_pdfmark_dict(ctx, oc_dict); |
815 | 10.9k | if (code < 0) |
816 | 65 | goto exit; |
817 | | |
818 | | /* Create an indirect ref for the dict */ |
819 | 10.8k | code = pdfi_object_alloc(ctx, PDF_INDIRECT, 0, (pdf_obj **)&dictref); |
820 | 10.8k | if (code < 0) goto exit; |
821 | 10.8k | pdfi_countup(dictref); |
822 | 10.8k | dictref->ref_object_num = oc_dict->object_num; |
823 | 10.8k | dictref->ref_generation_num = oc_dict->generation_num; |
824 | 10.8k | dictref->is_marking = true; |
825 | | |
826 | 10.8k | objarray[1] = (pdf_obj *)dictref; |
827 | 10.8k | break; |
828 | 30.0k | case PDF_DICT: |
829 | 30.0k | objarray[1] = o; |
830 | 30.0k | break; |
831 | 43 | default: |
832 | 43 | code = gs_note_error(gs_error_VMerror); |
833 | 43 | goto exit; |
834 | 52.3k | } |
835 | | |
836 | 40.9k | code = pdfi_pdfmark_from_objarray(ctx, objarray, 2, NULL, "BDC"); |
837 | 40.9k | goto exit; |
838 | 52.3k | } |
839 | | |
840 | 515k | if (pdfi_name_is(tag, "OC")) { |
841 | | /* Check if first arg is a name and handle it if so */ |
842 | | /* TODO: spec says it could also be an inline dict that we should be able to handle, |
843 | | * but I am just matching what gs does for now, and it doesn't handle that case. |
844 | | */ |
845 | 163k | properties = (pdf_name *)o; |
846 | 163k | if (pdfi_type_of(properties) != PDF_NAME) |
847 | 122 | goto exit; |
848 | | |
849 | 163k | code = pdfi_loop_detector_mark(ctx); |
850 | | /* If it's a name, look it up in Properties */ |
851 | 163k | code = pdfi_find_resource(ctx, (unsigned char *)"Properties", properties, |
852 | 163k | (pdf_dict *)stream_dict, page_dict, (pdf_obj **)&oc_dict); |
853 | 163k | (void)pdfi_loop_detector_cleartomark(ctx); |
854 | 163k | if (code != 0) |
855 | 82.8k | goto exit; |
856 | 80.9k | if (pdfi_type_of(oc_dict) != PDF_DICT) |
857 | 2.16k | goto exit; |
858 | | |
859 | 78.7k | ocg_is_visible = pdfi_oc_is_ocg_visible(ctx, oc_dict); |
860 | 78.7k | if (!ocg_is_visible) |
861 | 63.6k | code = pdfi_oc_levels_set(ctx, ctx->OFFlevels, ctx->BMClevel); |
862 | | |
863 | 78.7k | } |
864 | | |
865 | 571k | exit: |
866 | 571k | if (objarray != NULL) |
867 | 52.3k | gs_free_object(ctx->memory, objarray, "free pdfi_op_BDC"); |
868 | 571k | pdfi_countdown(dictref); |
869 | 571k | pdfi_countdown(o); |
870 | 571k | pdfi_countdown(tag); |
871 | 571k | pdfi_countdown(oc_dict); |
872 | 571k | return code; |
873 | 515k | } |
874 | | |
875 | | /* end marked content sequence */ |
876 | | int pdfi_op_EMC(pdf_context *ctx) |
877 | 596k | { |
878 | 596k | int code, code1 = 0; |
879 | | |
880 | 596k | if (ctx->device_state.writepdfmarks && ctx->args.preservemarkedcontent && (!ctx->BDCWasOC || ctx->device_state.WantsOptionalContent)) |
881 | 53.8k | code1 = pdfi_pdfmark_from_objarray(ctx, NULL, 0, NULL, "EMC"); |
882 | | |
883 | 596k | code = pdfi_oc_levels_clear(ctx, ctx->OFFlevels, ctx->BMClevel); |
884 | 596k | if (code == 0) |
885 | 596k | code = code1; |
886 | | |
887 | | /* TODO: Should we flag error on too many EMC? */ |
888 | 596k | if (ctx->BMClevel > 0) |
889 | 583k | ctx->BMClevel --; |
890 | 596k | return code; |
891 | 596k | } |