Coverage Report

Created: 2025-12-31 07:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/pdf/pdf_optcontent.c
Line
Count
Source
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
91.5k
{
42
91.5k
    bool is_visible = true;
43
91.5k
    pdf_dict *D = NULL;
44
91.5k
    pdf_obj *BaseState = NULL;
45
91.5k
    pdf_array *OFF = NULL;
46
91.5k
    pdf_array *ON = NULL;
47
91.5k
    int code;
48
49
91.5k
    if (ctx->OCProperties == NULL)
50
5.15k
        return is_visible;
51
52
86.4k
    code = pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D);
53
86.4k
    if (code <= 0)
54
28
        goto cleanup;
55
56
86.4k
    code = pdfi_dict_knownget_type(ctx, D, "BaseState", PDF_NAME, &BaseState);
57
86.4k
    if (code < 0) {
58
0
        goto cleanup;
59
0
    }
60
86.4k
    if (code > 0) {
61
94
        if (pdfi_name_is((pdf_name *)BaseState, "OFF")) {
62
0
            is_visible = false;
63
0
        }
64
94
    }
65
66
86.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
86.4k
    if (is_visible) {
77
86.4k
        code = pdfi_dict_knownget_type(ctx, D, "OFF", PDF_ARRAY, (pdf_obj **)&OFF);
78
86.4k
        if (code < 0)
79
2.85k
            goto cleanup;
80
83.5k
        if (code > 0) {
81
82.2k
            if (pdfi_array_known(ctx, OFF, (pdf_obj *)ocdict, NULL))
82
79.5k
                is_visible = false;
83
82.2k
        }
84
83.5k
    }
85
86
87
86.4k
 cleanup:
88
86.4k
    pdfi_countdown(BaseState);
89
86.4k
    pdfi_countdown(D);
90
86.4k
    pdfi_countdown(OFF);
91
86.4k
    pdfi_countdown(ON);
92
86.4k
    return is_visible;
93
86.4k
}
94
95
/* Check Usage for an OCG */
96
static bool
97
pdfi_oc_check_OCG_usage(pdf_context *ctx, pdf_dict *ocdict)
98
110
{
99
110
    bool is_visible = true;
100
110
    int code;
101
110
    pdf_dict *Usage = NULL;
102
110
    pdf_dict *dict = NULL;
103
110
    pdf_obj *name = NULL;
104
105
    /* Check Usage to see if it has additional info */
106
110
    code = pdfi_dict_knownget_type(ctx, ocdict, "Usage", PDF_DICT, (pdf_obj **)&Usage);
107
110
    if (code <= 0) {
108
        /* No Usage, so we're done */
109
94
        goto cleanup;
110
94
    }
111
112
16
    if (ctx->args.printed) {
113
16
        code = pdfi_dict_knownget_type(ctx, Usage, "Print", PDF_DICT, (pdf_obj **)&dict);
114
16
        if (code <= 0)
115
0
            goto cleanup;
116
16
        code = pdfi_dict_knownget_type(ctx, dict, "PrintState", PDF_NAME, &name);
117
16
        if (code <= 0)
118
0
            goto cleanup;
119
16
    } 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
16
    if (pdfi_name_strcmp((pdf_name *)name, "OFF") == 0) {
128
0
        is_visible = false;
129
0
    }
130
131
110
 cleanup:
132
110
    pdfi_countdown(Usage);
133
110
    pdfi_countdown(dict);
134
110
    pdfi_countdown(name);
135
136
110
    return is_visible;
137
16
}
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
5.52k
{
148
5.52k
    int code;
149
5.52k
    pdf_dict *D_dict = NULL;
150
5.52k
    pdf_name *B = NULL;
151
152
5.52k
    code = pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D_dict);
153
5.52k
    if (code < 0 || D_dict == NULL)
154
198
        return true;
155
156
5.32k
    code = pdfi_dict_knownget_type(ctx, D_dict, "BaseState", PDF_NAME, (pdf_obj **)&B);
157
5.32k
    (void)pdfi_countdown(D_dict);
158
5.32k
    D_dict = NULL;
159
5.32k
    if (code < 0 || B == NULL)
160
5.32k
        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
91.7k
{
174
91.7k
    bool is_visible, hit = false;
175
91.7k
    uint64_t i;
176
91.7k
    int code;
177
91.7k
    pdf_obj *val = NULL;
178
179
    /* Setup default */
180
91.7k
    switch (type) {
181
91.7k
    case P_AnyOn:
182
91.7k
    case P_AnyOff:
183
91.7k
        is_visible = false;
184
91.7k
        break;
185
0
    case P_AllOn:
186
0
    case P_AllOff:
187
0
        is_visible = true;
188
0
        break;
189
91.7k
    }
190
191
176k
    for (i=0; i<pdfi_array_size(array); i++) {
192
91.7k
        bool vis;
193
194
91.7k
        code = pdfi_array_get(ctx, array, i, &val);
195
91.7k
        if (code < 0) continue;
196
86.5k
        if (pdfi_type_of(val) != PDF_DICT) {
197
232
            dbgprintf1("WARNING: OCMD array contains item type %d, expected PDF_DICT or PDF_NULL\n", pdfi_type_of(val));
198
232
            pdfi_countdown(val);
199
232
            val = NULL;
200
232
            continue;
201
232
        }
202
203
86.3k
        hit = true;
204
86.3k
        vis = pdfi_get_default_OCG_val(ctx, (pdf_dict *)val);
205
86.3k
        switch (type) {
206
86.3k
        case P_AnyOn:
207
            /* visible if any is on */
208
86.3k
            if (vis) {
209
6.92k
                is_visible = true;
210
6.92k
                goto cleanup;
211
6.92k
            }
212
79.4k
            break;
213
79.4k
        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
86.3k
        }
235
79.4k
        pdfi_countdown(val);
236
79.4k
        val = NULL;
237
79.4k
    }
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
84.7k
    if (!hit)
247
5.36k
        is_visible = pdfi_oc_default_visibility(ctx);
248
249
91.7k
cleanup:
250
91.7k
    pdfi_countdown(val);
251
91.7k
    return is_visible;
252
84.7k
}
253
254
static bool
255
pdfi_oc_check_OCMD(pdf_context *ctx, pdf_dict *ocdict)
256
92.4k
{
257
92.4k
    bool is_visible = true;
258
92.4k
    int code;
259
92.4k
    pdf_obj *VE = NULL;
260
92.4k
    pdf_obj *obj = NULL;
261
92.4k
    pdf_obj *Pname = NULL;
262
92.4k
    pdf_dict *OCGs_dict = NULL; /* alias, don't need to free */
263
92.4k
    pdf_array *OCGs_array = NULL; /* alias, don't need to free */
264
92.4k
    pdf_dict *UsageDict = NULL, *StateDict = NULL;
265
92.4k
    pdf_obj *State = NULL;
266
92.4k
    ocmd_p_type Ptype = P_AnyOn;
267
268
    /* TODO: We don't support this, so log a warning and ignore */
269
92.4k
    code = pdfi_dict_knownget_type(ctx, ocdict, "VE", PDF_ARRAY, &VE);
270
92.4k
    if (code > 0) {
271
237
        dbgprintf("WARNING: OCMD contains VE, which is not supported (ignoring)\n");
272
237
    }
273
274
92.4k
    code = pdfi_dict_knownget(ctx, ocdict, "OCGs", &obj);
275
92.4k
    if (code <= 0)
276
313
        goto cleanup;
277
92.1k
    if (pdfi_type_of(obj) == PDF_ARRAY) {
278
91.7k
        OCGs_array = (pdf_array *)obj;
279
91.7k
    } else if (pdfi_type_of(obj) == PDF_DICT) {
280
248
        OCGs_dict = (pdf_dict *)obj;
281
248
    } else {
282
158
        is_visible = pdfi_oc_default_visibility(ctx);
283
158
        goto cleanup;
284
158
    }
285
286
91.9k
    code = pdfi_dict_knownget_type(ctx, ocdict, "P", PDF_NAME, &Pname);
287
91.9k
    if (code < 0)
288
0
        goto cleanup;
289
91.9k
    if (code == 0 || pdfi_name_is((pdf_name *)Pname, "AnyOn")) {
290
91.9k
        Ptype = P_AnyOn;
291
91.9k
    } 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
91.9k
    if (OCGs_dict) {
302
248
        switch (Ptype) {
303
248
        case P_AnyOn:
304
248
        case P_AllOn:
305
248
            is_visible = pdfi_get_default_OCG_val(ctx, OCGs_dict);
306
248
            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
248
        }
312
91.7k
    } else {
313
91.7k
        is_visible = pdfi_oc_check_OCMD_array(ctx, OCGs_array, Ptype);
314
91.7k
    }
315
316
91.9k
    if (OCGs_dict) {
317
248
        code = pdfi_dict_knownget_type(ctx, OCGs_dict, "Usage", PDF_DICT, (pdf_obj **)&UsageDict);
318
248
        if (code < 0)
319
0
            goto cleanup;
320
321
248
        if (UsageDict != NULL) {
322
115
            if (ctx->args.printed) {
323
115
                code = pdfi_dict_knownget_type(ctx, UsageDict, "Print", PDF_DICT, (pdf_obj **)&StateDict);
324
115
                if (code < 0)
325
0
                    goto cleanup;
326
115
                if (StateDict) {
327
36
                    code = pdfi_dict_knownget_type(ctx, StateDict, "PrintState", PDF_NAME, &State);
328
36
                    if (code < 0)
329
0
                        goto cleanup;
330
36
                }
331
115
            } 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
115
            if (State) {
342
36
                if (pdfi_name_is((const pdf_name *)State, "ON"))
343
36
                    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
36
            }
350
115
        }
351
248
    }
352
353
92.4k
 cleanup:
354
92.4k
    pdfi_countdown(State);
355
92.4k
    pdfi_countdown(StateDict);
356
92.4k
    pdfi_countdown(UsageDict);
357
92.4k
    pdfi_countdown(VE);
358
92.4k
    pdfi_countdown(obj);
359
92.4k
    pdfi_countdown(Pname);
360
361
92.4k
    return is_visible;
362
91.9k
}
363
364
static bool pdfi_must_check_usage(pdf_context *ctx, int64_t object)
365
5.00k
{
366
5.00k
    pdf_dict *D = NULL, *EventDict = NULL;
367
5.00k
    pdf_array *AS = NULL, *OCGs = NULL;
368
5.00k
    pdf_name *Event = NULL;
369
5.00k
    bool check = false;
370
5.00k
    int i, code = 0;
371
372
5.00k
    if (ctx->OCProperties != NULL) {
373
3.75k
        if (pdfi_dict_knownget_type(ctx, ctx->OCProperties, "D", PDF_DICT, (pdf_obj **)&D) > 0) {
374
3.74k
            if (pdfi_dict_knownget_type(ctx, D, "AS", PDF_ARRAY, (pdf_obj **)&AS) > 0) {
375
220
                for (i = 0; i < pdfi_array_size(AS); i++) {
376
220
                    code = pdfi_array_get(ctx, AS, i, (pdf_obj **)&EventDict);
377
220
                    if (code < 0)
378
0
                        goto exit;
379
220
                    if (pdfi_type_of(EventDict) == PDF_DICT) {
380
220
                        if (pdfi_dict_knownget_type(ctx, EventDict, "Event", PDF_NAME, (pdf_obj **)&Event) > 0) {
381
220
                            if (ctx->args.printed) {
382
220
                                if (Event->length == 5 && strncmp((const char *)Event->data, "Print", 5) == 0) {
383
110
                                    if (pdfi_dict_knownget_type(ctx, EventDict, "OCGs", PDF_ARRAY, (pdf_obj **)&OCGs) > 0) {
384
110
                                        pdfi_countdown(Event);
385
110
                                        Event = NULL;
386
110
                                        pdfi_countdown(EventDict);
387
110
                                        EventDict = NULL;
388
110
                                        break;
389
110
                                    }
390
110
                                }
391
220
                            } 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) > 0) {
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
110
                            pdfi_countdown(Event);
403
110
                            Event = NULL;
404
110
                        }
405
220
                    }
406
110
                    pdfi_countdown(EventDict);
407
110
                    EventDict = NULL;
408
110
                }
409
110
                pdfi_countdown(AS);
410
110
                AS = NULL;
411
110
                if (OCGs) {
412
110
                    pdf_obj *Group = NULL;
413
414
346
                    for (i = 0; i < pdfi_array_size(OCGs); i++) {
415
346
                        code = pdfi_array_get(ctx, OCGs, i, (pdf_obj **)&Group);
416
346
                        if (code < 0)
417
0
                            goto exit;
418
346
                        if (Group->object_num == object) {
419
110
                            pdfi_countdown(Group);
420
110
                            check = true;
421
110
                            goto exit;
422
110
                        }
423
236
                        pdfi_countdown(Group);
424
236
                        Group = NULL;
425
236
                    }
426
0
                    pdfi_countdown(OCGs);
427
0
                    OCGs = NULL;
428
0
                }
429
110
            }
430
3.63k
            pdfi_countdown(D);
431
3.63k
            D = NULL;
432
3.63k
        }
433
3.75k
    }
434
5.00k
exit:
435
5.00k
    pdfi_countdown(OCGs);
436
5.00k
    pdfi_countdown(Event);
437
5.00k
    pdfi_countdown(EventDict);
438
5.00k
    pdfi_countdown(AS);
439
5.00k
    pdfi_countdown(D);
440
5.00k
    return check;
441
5.00k
}
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
97.9k
{
447
97.9k
    pdf_name *type = NULL;
448
97.9k
    bool is_visible = true;
449
97.9k
    int code;
450
451
    /* Type can be either OCMD or OCG.
452
     */
453
97.9k
    code = pdfi_dict_knownget_type(ctx, ocdict, "Type", PDF_NAME, (pdf_obj **)&type);
454
97.9k
    if (code <= 0)
455
80
        goto cleanup;
456
457
97.9k
    if (pdfi_name_is(type, "OCMD")) {
458
92.4k
        is_visible = pdfi_oc_check_OCMD(ctx, ocdict);
459
92.4k
    } else if (pdfi_name_is(type, "OCG")) {
460
5.00k
        is_visible = pdfi_get_default_OCG_val(ctx, ocdict);
461
5.00k
        if (pdfi_must_check_usage(ctx, ocdict->object_num))
462
110
            is_visible = pdfi_oc_check_OCG_usage(ctx, ocdict);
463
5.00k
    } else {
464
484
        char str[100];
465
484
        memcpy(str, (const char *)type->data, type->length < 100 ? type->length : 99);
466
484
        str[type->length < 100 ? type->length : 99] = '\0';
467
484
        dbgprintf1("WARNING: OC dict type is %s, expected OCG or OCMD\n", str);
468
484
    }
469
470
97.9k
 cleanup:
471
97.9k
    pdfi_countdown(type);
472
473
97.9k
    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
97.9k
    return is_visible;
478
97.9k
}
479
480
267k
#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
133k
{
489
133k
    byte *data;
490
133k
    pdfi_oc_levels_t *new;
491
492
133k
    *levels = NULL;
493
494
133k
    new = (pdfi_oc_levels_t *)gs_alloc_bytes(ctx->memory, sizeof(pdfi_oc_levels_t),
495
133k
                                             "pdfi_oc_levels_init (levels)");
496
133k
    if (!new)
497
0
        return_error(gs_error_VMerror);
498
499
133k
    data = (byte *)gs_alloc_bytes(ctx->memory, NUM_CONTENT_LEVELS, "pdfi_oc_levels_init (data)");
500
133k
    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
133k
    memset(data, 0, NUM_CONTENT_LEVELS);
505
506
133k
    new->flags = data;
507
133k
    new->num_off = 0;
508
133k
    new->max_flags = NUM_CONTENT_LEVELS;
509
133k
    *levels = new;
510
511
133k
    return 0;
512
133k
}
513
514
static int pdfi_oc_levels_free(pdf_context *ctx, pdfi_oc_levels_t *levels)
515
157k
{
516
157k
    if (!levels)
517
23.8k
        return 0;
518
133k
    gs_free_object(ctx->memory, levels->flags, "pdfi_oc_levels_free (flags)");
519
133k
    gs_free_object(ctx->memory, levels, "pdfi_oc_levels_free (levels)");
520
521
133k
    return 0;
522
157k
}
523
524
static int pdfi_oc_levels_set(pdf_context *ctx, pdfi_oc_levels_t *levels, uint64_t index)
525
79.4k
{
526
79.4k
    byte *new = NULL;
527
79.4k
    uint64_t newmax;
528
529
79.4k
    if (index > levels->max_flags - 1) {
530
        /* Expand the flags buffer */
531
2
        newmax = levels->max_flags + NUM_CONTENT_LEVELS;
532
2
        if (index > newmax)
533
0
            return_error(gs_error_Fatal); /* shouldn't happen */
534
2
        new = gs_alloc_bytes(ctx->memory, newmax, "pdfi_oc_levels_set (new data)");
535
2
        if (!new)
536
0
            return_error(gs_error_VMerror);
537
2
        memset(new, 0, newmax);
538
2
        memcpy(new, levels->flags, levels->max_flags);
539
2
        gs_free_object(ctx->memory, levels->flags, "pdfi_oc_levels_set (old data)");
540
2
        levels->flags = new;
541
2
        levels->max_flags += NUM_CONTENT_LEVELS;
542
2
    }
543
544
79.4k
    if (levels->flags[index] == 0)
545
79.4k
        levels->num_off ++;
546
79.4k
    levels->flags[index] = 1;
547
79.4k
    return 0;
548
79.4k
}
549
550
static int pdfi_oc_levels_clear(pdf_context *ctx, pdfi_oc_levels_t *levels, uint64_t index)
551
694k
{
552
694k
    if (index > levels->max_flags - 1)
553
823
        return -1;
554
693k
    if (levels->flags[index] != 0)
555
79.2k
        levels->num_off --;
556
693k
    levels->flags[index] = 0;
557
693k
    return 0;
558
694k
}
559
560
561
/* Test if content is turned off for this element.
562
 */
563
bool pdfi_oc_is_off(pdf_context *ctx)
564
16.5M
{
565
16.5M
    pdfi_oc_levels_t *levels = (pdfi_oc_levels_t *)ctx->OFFlevels;
566
16.5M
    uint64_t num_off = levels->num_off;
567
568
16.5M
    return (num_off != 0);
569
16.5M
}
570
571
int pdfi_oc_init(pdf_context *ctx)
572
133k
{
573
133k
    int code;
574
575
133k
    ctx->BMClevel = 0;
576
133k
    if (ctx->OFFlevels) {
577
47.1k
        pdfi_oc_levels_free(ctx, ctx->OFFlevels);
578
47.1k
        ctx->OFFlevels = NULL;
579
47.1k
    }
580
133k
    code = pdfi_oc_levels_init(ctx, (pdfi_oc_levels_t **)&ctx->OFFlevels);
581
133k
    if (code < 0)
582
0
        return code;
583
584
133k
    return 0;
585
133k
}
586
587
int pdfi_oc_free(pdf_context *ctx)
588
110k
{
589
110k
    int code;
590
591
110k
    code = pdfi_oc_levels_free(ctx, (pdfi_oc_levels_t *)ctx->OFFlevels);
592
110k
    ctx->OFFlevels = NULL;
593
110k
    return code;
594
110k
}
595
596
int pdfi_op_MP(pdf_context *ctx)
597
3.15k
{
598
3.15k
    pdf_obj *o = NULL;
599
3.15k
    int code = 0;
600
601
3.15k
    if (pdfi_count_stack(ctx) < 1)
602
77
        return_error(gs_error_stackunderflow);
603
604
3.07k
    if (!ctx->device_state.writepdfmarks || !ctx->args.preservemarkedcontent) {
605
2.70k
        pdfi_pop(ctx, 1);
606
2.70k
        goto exit;
607
2.70k
    }
608
609
366
    o = ctx->stack_top[-1];
610
366
    pdfi_countup(o);
611
366
    pdfi_pop(ctx, 1);
612
613
366
    if (pdfi_type_of(o) != PDF_NAME) {
614
2
        code = gs_note_error(gs_error_typecheck);
615
2
        goto exit;
616
2
    }
617
618
364
    code = pdfi_pdfmark_from_objarray(ctx, &o, 1, NULL, "MP");
619
364
    ctx->BMClevel ++;
620
621
3.07k
exit:
622
3.07k
    pdfi_countdown(o);
623
3.07k
    return code;
624
364
}
625
626
int pdfi_op_DP(pdf_context *ctx, pdf_dict *stream_dict, pdf_dict *page_dict)
627
899
{
628
899
    pdf_name *properties = NULL;
629
899
    int code = 0;
630
899
    pdf_obj **objarray = NULL, *o = NULL;
631
632
899
    if (pdfi_count_stack(ctx) < 2) {
633
12
        pdfi_clearstack(ctx);
634
12
        return gs_note_error(gs_error_stackunderflow);
635
12
    }
636
637
887
    if (!ctx->device_state.writepdfmarks || !ctx->args.preservemarkedcontent) {
638
772
        pdfi_pop(ctx, 2); /* pop args */
639
772
        goto exit;
640
772
    }
641
642
115
    if (pdfi_type_of(ctx->stack_top[-2]) != PDF_NAME) {
643
1
        pdfi_pop(ctx, 2); /* pop args */
644
1
        code = gs_note_error(gs_error_typecheck);
645
1
        goto exit;
646
1
    }
647
648
114
    objarray = (pdf_obj **)gs_alloc_bytes(ctx->memory, 2 * sizeof(pdf_obj *), "pdfi_op_DP");
649
114
    if (objarray == NULL) {
650
0
        code = gs_note_error(gs_error_VMerror);
651
0
        goto exit;
652
0
    }
653
654
114
    objarray[0] = ctx->stack_top[-2];
655
114
    pdfi_countup(objarray[0]);
656
114
    o = ctx->stack_top[-1];
657
114
    pdfi_countup(o);
658
114
    pdfi_pop(ctx, 2); /* pop args */
659
660
114
    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
114
        case PDF_DICT:
672
114
            objarray[1] = o;
673
114
            break;
674
0
        default:
675
0
            code = gs_note_error(gs_error_VMerror);
676
0
            goto exit;
677
114
    }
678
679
114
    code = pdfi_pdfmark_from_objarray(ctx, objarray, 2, NULL, "DP");
680
681
887
 exit:
682
887
    if (objarray != NULL) {
683
114
        pdfi_countdown(objarray[0]);
684
114
        gs_free_object(ctx->memory, objarray, "free pdfi_op_DP");
685
114
    }
686
887
    pdfi_countdown(o);
687
887
    pdfi_countdown(properties);
688
887
    return code;
689
114
}
690
691
/* begin marked content sequence */
692
int pdfi_op_BMC(pdf_context *ctx)
693
30.8k
{
694
30.8k
    pdf_obj *o = NULL;
695
30.8k
    int code = 0;
696
697
    /* This will also prevent us writing out an EMC if the BMC is in any way invalid */
698
30.8k
    ctx->BDCWasOC = true;
699
700
30.8k
    if (pdfi_count_stack(ctx) < 1)
701
18
        return_error(gs_error_stackunderflow);
702
703
30.8k
    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
28.2k
        ctx->BMClevel ++;
708
28.2k
        pdfi_pop(ctx, 1);
709
28.2k
        goto exit;
710
28.2k
    }
711
712
2.58k
    o = ctx->stack_top[-1];
713
2.58k
    pdfi_countup(o);
714
2.58k
    pdfi_pop(ctx, 1);
715
716
2.58k
    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.58k
    ctx->BDCWasOC = false;
722
2.58k
    code = pdfi_pdfmark_from_objarray(ctx, &o, 1, NULL, "BMC");
723
2.58k
    ctx->BMClevel ++;
724
725
30.8k
exit:
726
30.8k
    pdfi_countdown(o);
727
30.8k
    return code;
728
2.58k
}
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
675k
{
733
675k
    pdf_name *tag = NULL;
734
675k
    pdf_name *properties = NULL;
735
675k
    pdf_dict *oc_dict = NULL;
736
675k
    int code = 0;
737
675k
    bool ocg_is_visible;
738
675k
    pdf_obj **objarray = NULL, *o = NULL;
739
675k
    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
675k
    ctx->BDCWasOC = true;
743
744
675k
    if (pdfi_count_stack(ctx) < 2) {
745
9.86k
        pdfi_clearstack(ctx);
746
9.86k
        return gs_note_error(gs_error_stackunderflow);
747
9.86k
    }
748
749
665k
    ctx->BMClevel ++;
750
751
665k
    tag = (pdf_name *)ctx->stack_top[-2];
752
665k
    pdfi_countup(tag);
753
665k
    o = ctx->stack_top[-1];
754
665k
    pdfi_countup(o);
755
665k
    pdfi_pop(ctx, 2);
756
757
665k
    if (pdfi_type_of(tag) != PDF_NAME)
758
4.16k
        goto exit;
759
760
661k
    if (!pdfi_name_is(tag, "OC"))
761
446k
        ctx->BDCWasOC = false;
762
763
661k
    if (ctx->device_state.writepdfmarks && ctx->args.preservemarkedcontent && (!ctx->BDCWasOC || ctx->device_state.WantsOptionalContent)) {
764
60.5k
        objarray = (pdf_obj **)gs_alloc_bytes(ctx->memory, 2 * sizeof(pdf_obj *), "pdfi_op_BDC");
765
60.5k
        if (objarray == NULL) {
766
0
            code = gs_note_error(gs_error_VMerror);
767
0
            goto exit;
768
0
        }
769
770
60.5k
        objarray[0] = (pdf_obj *)tag;
771
772
60.5k
        switch (pdfi_type_of(o)) {
773
27.3k
            case PDF_NAME:
774
27.3k
                code = pdfi_find_resource(ctx, (unsigned char *)"Properties", (pdf_name *)o, stream_dict, page_dict, (pdf_obj **)&oc_dict);
775
27.3k
                if(code < 0)
776
12.0k
                    goto exit;
777
15.3k
                if (pdfi_type_of(oc_dict) != PDF_DICT) {
778
133
                    code = gs_note_error(gs_error_typecheck);
779
133
                    goto exit;
780
133
                }
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
15.1k
                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
15.1k
                if (pdfi_dict_entries(oc_dict) == 0)
812
27
                    goto exit;
813
814
15.1k
                code = pdfi_pdfmark_dict(ctx, oc_dict);
815
15.1k
                if (code < 0)
816
93
                    goto exit;
817
818
                /* Create an indirect ref for the dict */
819
15.0k
                code = pdfi_object_alloc(ctx, PDF_INDIRECT, 0, (pdf_obj **)&dictref);
820
15.0k
                if (code < 0) goto exit;
821
15.0k
                pdfi_countup(dictref);
822
15.0k
                dictref->ref_object_num = oc_dict->object_num;
823
15.0k
                dictref->ref_generation_num = oc_dict->generation_num;
824
15.0k
                dictref->is_marking = true;
825
826
15.0k
                objarray[1] = (pdf_obj *)dictref;
827
15.0k
                break;
828
33.0k
            case PDF_DICT:
829
33.0k
                objarray[1] = o;
830
33.0k
                break;
831
49
            default:
832
49
                code = gs_note_error(gs_error_VMerror);
833
49
                goto exit;
834
60.5k
        }
835
836
48.1k
        code = pdfi_pdfmark_from_objarray(ctx, objarray, 2, NULL, "BDC");
837
48.1k
        goto exit;
838
60.5k
    }
839
840
601k
    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
189k
        properties = (pdf_name *)o;
846
189k
        if (pdfi_type_of(properties) != PDF_NAME)
847
135
            goto exit;
848
849
189k
        code = pdfi_loop_detector_mark(ctx);
850
        /* If it's a name, look it up in Properties */
851
189k
        code = pdfi_find_resource(ctx, (unsigned char *)"Properties", properties,
852
189k
                                  (pdf_dict *)stream_dict, page_dict, (pdf_obj **)&oc_dict);
853
189k
        (void)pdfi_loop_detector_cleartomark(ctx);
854
189k
        if (code != 0)
855
89.4k
            goto exit;
856
99.9k
        if (pdfi_type_of(oc_dict) != PDF_DICT)
857
3.05k
            goto exit;
858
859
96.8k
        ocg_is_visible = pdfi_oc_is_ocg_visible(ctx, oc_dict);
860
96.8k
        if (!ocg_is_visible)
861
79.4k
            code = pdfi_oc_levels_set(ctx, ctx->OFFlevels, ctx->BMClevel);
862
863
96.8k
    }
864
865
665k
exit:
866
665k
    if (objarray != NULL)
867
60.5k
        gs_free_object(ctx->memory, objarray, "free pdfi_op_BDC");
868
665k
    pdfi_countdown(dictref);
869
665k
    pdfi_countdown(o);
870
665k
    pdfi_countdown(tag);
871
665k
    pdfi_countdown(oc_dict);
872
665k
    return code;
873
601k
}
874
875
/* end marked content sequence */
876
int pdfi_op_EMC(pdf_context *ctx)
877
694k
{
878
694k
    int code, code1 = 0;
879
880
694k
    if (ctx->device_state.writepdfmarks && ctx->args.preservemarkedcontent && (!ctx->BDCWasOC || ctx->device_state.WantsOptionalContent))
881
62.4k
        code1 = pdfi_pdfmark_from_objarray(ctx, NULL, 0, NULL, "EMC");
882
883
694k
    code = pdfi_oc_levels_clear(ctx, ctx->OFFlevels, ctx->BMClevel);
884
694k
    if (code == 0)
885
693k
        code = code1;
886
887
    /* TODO: Should we flag error on too many EMC? */
888
694k
    if (ctx->BMClevel > 0)
889
680k
        ctx->BMClevel --;
890
694k
    return code;
891
694k
}