Coverage Report

Created: 2024-05-20 06:23

/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
}