Coverage Report

Created: 2025-01-28 06:17

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