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