1 //========================================================================
2 //
3 // OptionalContent.cc
4 //
5 // Copyright 2008-2013 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include "gmempp.h"
16 #include "GString.h"
17 #include "GList.h"
18 #include "Error.h"
19 #include "Object.h"
20 #include "PDFDoc.h"
21 #include "TextString.h"
22 #include "OptionalContent.h"
23
24 //------------------------------------------------------------------------
25
26 #define ocPolicyAllOn 1
27 #define ocPolicyAnyOn 2
28 #define ocPolicyAnyOff 3
29 #define ocPolicyAllOff 4
30
31 //------------------------------------------------------------------------
32
33 // Max depth of nested visibility expressions. This is used to catch
34 // infinite loops in the visibility expression object structure.
35 #define visibilityExprRecursionLimit 50
36
37 // Max depth of nested display nodes. This is used to catch infinite
38 // loops in the "Order" object structure.
39 #define displayNodeRecursionLimit 50
40
41 //------------------------------------------------------------------------
42
OptionalContent(PDFDoc * doc)43 OptionalContent::OptionalContent(PDFDoc *doc) {
44 Object *ocProps;
45 Object ocgList, defView, uad, obj1, obj2, obj3, obj4;
46 Ref ref1;
47 OptionalContentGroup *ocg;
48 int i, j;
49
50 xref = doc->getXRef();
51 ocgs = new GList();
52 display = NULL;
53
54 if ((ocProps = doc->getCatalog()->getOCProperties())->isDict()) {
55 if (ocProps->dictLookup("OCGs", &ocgList)->isArray()) {
56
57 //----- read the OCG list
58 for (i = 0; i < ocgList.arrayGetLength(); ++i) {
59 if (ocgList.arrayGetNF(i, &obj1)->isRef()) {
60 ref1 = obj1.getRef();
61 obj1.fetch(xref, &obj2);
62 if ((ocg = OptionalContentGroup::parse(&ref1, &obj2))) {
63 ocgs->append(ocg);
64 }
65 obj2.free();
66 }
67 obj1.free();
68 }
69
70 //----- read the default viewing OCCD
71 if (ocProps->dictLookup("D", &defView)->isDict()) {
72
73 //----- read the usage app dicts
74 if (defView.dictLookup("AS", &obj1)->isArray()) {
75 for (i = 0; i < obj1.arrayGetLength(); ++i) {
76 if (obj1.arrayGet(i, &uad)->isDict()) {
77 if (uad.dictLookup("Event", &obj2)->isName("View")) {
78 if (uad.dictLookup("OCGs", &obj3)->isArray()) {
79 for (j = 0; j < obj3.arrayGetLength(); ++j) {
80 if (obj3.arrayGetNF(j, &obj4)->isRef()) {
81 ref1 = obj4.getRef();
82 if ((ocg = findOCG(&ref1))) {
83 ocg->setInViewUsageAppDict();
84 }
85 }
86 obj4.free();
87 }
88 }
89 obj3.free();
90 }
91 obj2.free();
92 }
93 uad.free();
94 }
95 }
96 obj1.free();
97
98 //----- initial state from OCCD
99 if (defView.dictLookup("OFF", &obj1)->isArray()) {
100 for (i = 0; i < obj1.arrayGetLength(); ++i) {
101 if (obj1.arrayGetNF(i, &obj2)->isRef()) {
102 ref1 = obj2.getRef();
103 if ((ocg = findOCG(&ref1))) {
104 ocg->setState(gFalse);
105 } else {
106 error(errSyntaxError, -1,
107 "Invalid OCG reference in OFF array in default viewing OCCD");
108 }
109 }
110 obj2.free();
111 }
112 }
113 obj1.free();
114
115 //----- initial state from OCG usage dict
116 for (i = 0; i < ocgs->getLength(); ++i) {
117 ocg = (OptionalContentGroup *)ocgs->get(i);
118 if (ocg->getInViewUsageAppDict() &&
119 ocg->getViewState() != ocUsageUnset) {
120 ocg->setState(ocg->getViewState() == ocUsageOn);
121 }
122 }
123
124 //----- display order
125 if (defView.dictLookup("Order", &obj1)->isArray()) {
126 display = OCDisplayNode::parse(&obj1, this, xref);
127 }
128 obj1.free();
129
130 } else {
131 error(errSyntaxError, -1, "Missing or invalid default viewing OCCD");
132 }
133 defView.free();
134
135 }
136 ocgList.free();
137 }
138
139 if (!display) {
140 display = new OCDisplayNode();
141 }
142 }
143
~OptionalContent()144 OptionalContent::~OptionalContent() {
145 deleteGList(ocgs, OptionalContentGroup);
146 delete display;
147 }
148
getNumOCGs()149 int OptionalContent::getNumOCGs() {
150 return ocgs->getLength();
151 }
152
getOCG(int idx)153 OptionalContentGroup *OptionalContent::getOCG(int idx) {
154 return (OptionalContentGroup *)ocgs->get(idx);
155 }
156
findOCG(Ref * ref)157 OptionalContentGroup *OptionalContent::findOCG(Ref *ref) {
158 OptionalContentGroup *ocg;
159 int i;
160
161 for (i = 0; i < ocgs->getLength(); ++i) {
162 ocg = (OptionalContentGroup *)ocgs->get(i);
163 if (ocg->matches(ref)) {
164 return ocg;
165 }
166 }
167 return NULL;
168 }
169
evalOCObject(Object * obj,GBool * visible)170 GBool OptionalContent::evalOCObject(Object *obj, GBool *visible) {
171 OptionalContentGroup *ocg;
172 int policy;
173 Ref ref;
174 Object obj2, obj3, obj4, obj5;
175 GBool gotOCG;
176 int i;
177
178 if (obj->isNull()) {
179 return gFalse;
180 }
181 if (obj->isRef()) {
182 ref = obj->getRef();
183 if ((ocg = findOCG(&ref))) {
184 *visible = ocg->getState();
185 return gTrue;
186 }
187 }
188 obj->fetch(xref, &obj2);
189 if (!obj2.isDict("OCMD")) {
190 obj2.free();
191 return gFalse;
192 }
193 if (obj2.dictLookup("VE", &obj3)->isArray()) {
194 *visible = evalOCVisibilityExpr(&obj3, 0);
195 obj3.free();
196 } else {
197 obj3.free();
198 policy = ocPolicyAnyOn;
199 if (obj2.dictLookup("P", &obj3)->isName()) {
200 if (obj3.isName("AllOn")) {
201 policy = ocPolicyAllOn;
202 } else if (obj3.isName("AnyOn")) {
203 policy = ocPolicyAnyOn;
204 } else if (obj3.isName("AnyOff")) {
205 policy = ocPolicyAnyOff;
206 } else if (obj3.isName("AllOff")) {
207 policy = ocPolicyAllOff;
208 }
209 }
210 obj3.free();
211 obj2.dictLookupNF("OCGs", &obj3);
212 ocg = NULL;
213 if (obj3.isRef()) {
214 ref = obj3.getRef();
215 ocg = findOCG(&ref);
216 }
217 if (ocg) {
218 *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ?
219 ocg->getState() : !ocg->getState();
220 } else {
221 *visible = policy == ocPolicyAllOn || policy == ocPolicyAllOff;
222 if (!obj3.fetch(xref, &obj4)->isArray()) {
223 obj4.free();
224 obj3.free();
225 obj2.free();
226 return gFalse;
227 }
228 gotOCG = gFalse;
229 for (i = 0; i < obj4.arrayGetLength(); ++i) {
230 obj4.arrayGetNF(i, &obj5);
231 if (obj5.isRef()) {
232 ref = obj5.getRef();
233 if ((ocg = findOCG(&ref))) {
234 gotOCG = gTrue;
235 switch (policy) {
236 case ocPolicyAllOn:
237 *visible = *visible && ocg->getState();
238 break;
239 case ocPolicyAnyOn:
240 *visible = *visible || ocg->getState();
241 break;
242 case ocPolicyAnyOff:
243 *visible = *visible || !ocg->getState();
244 break;
245 case ocPolicyAllOff:
246 *visible = *visible && !ocg->getState();
247 break;
248 }
249 }
250 }
251 obj5.free();
252 }
253 if (!gotOCG) {
254 // if the "OCGs" array is "empty or contains references only
255 // to null or deleted objects", this OCMD doesn't have any
256 // effect
257 obj4.free();
258 obj3.free();
259 obj2.free();
260 return gFalse;
261 }
262 obj4.free();
263 }
264 obj3.free();
265 }
266 obj2.free();
267 return gTrue;
268 }
269
evalOCVisibilityExpr(Object * expr,int recursion)270 GBool OptionalContent::evalOCVisibilityExpr(Object *expr, int recursion) {
271 OptionalContentGroup *ocg;
272 Object expr2, op, obj;
273 Ref ref;
274 GBool ret;
275 int i;
276
277 if (recursion > visibilityExprRecursionLimit) {
278 error(errSyntaxError, -1,
279 "Loop detected in optional content visibility expression");
280 return gTrue;
281 }
282 if (expr->isRef()) {
283 ref = expr->getRef();
284 if ((ocg = findOCG(&ref))) {
285 return ocg->getState();
286 }
287 }
288 expr->fetch(xref, &expr2);
289 if (!expr2.isArray() || expr2.arrayGetLength() < 1) {
290 error(errSyntaxError, -1,
291 "Invalid optional content visibility expression");
292 expr2.free();
293 return gTrue;
294 }
295 expr2.arrayGet(0, &op);
296 if (op.isName("Not")) {
297 if (expr2.arrayGetLength() == 2) {
298 expr2.arrayGetNF(1, &obj);
299 ret = !evalOCVisibilityExpr(&obj, recursion + 1);
300 obj.free();
301 } else {
302 error(errSyntaxError, -1,
303 "Invalid optional content visibility expression");
304 ret = gTrue;
305 }
306 } else if (op.isName("And")) {
307 ret = gTrue;
308 for (i = 1; i < expr2.arrayGetLength() && ret; ++i) {
309 expr2.arrayGetNF(i, &obj);
310 ret = evalOCVisibilityExpr(&obj, recursion + 1);
311 obj.free();
312 }
313 } else if (op.isName("Or")) {
314 ret = gFalse;
315 for (i = 1; i < expr2.arrayGetLength() && !ret; ++i) {
316 expr2.arrayGetNF(i, &obj);
317 ret = evalOCVisibilityExpr(&obj, recursion + 1);
318 obj.free();
319 }
320 } else {
321 error(errSyntaxError, -1,
322 "Invalid optional content visibility expression");
323 ret = gTrue;
324 }
325 op.free();
326 expr2.free();
327 return ret;
328 }
329
330 //------------------------------------------------------------------------
331
parse(Ref * refA,Object * obj)332 OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) {
333 TextString *nameA;
334 Object obj1, obj2, obj3;
335 OCUsageState viewStateA, printStateA;
336
337 if (!obj->isDict()) {
338 return NULL;
339 }
340 if (!obj->dictLookup("Name", &obj1)->isString()) {
341 error(errSyntaxError, -1, "Missing or invalid Name in OCG");
342 obj1.free();
343 return NULL;
344 }
345 nameA = new TextString(obj1.getString());
346 obj1.free();
347
348 viewStateA = printStateA = ocUsageUnset;
349 if (obj->dictLookup("Usage", &obj1)->isDict()) {
350 if (obj1.dictLookup("View", &obj2)->isDict()) {
351 if (obj2.dictLookup("ViewState", &obj3)->isName()) {
352 if (obj3.isName("ON")) {
353 viewStateA = ocUsageOn;
354 } else {
355 viewStateA = ocUsageOff;
356 }
357 }
358 obj3.free();
359 }
360 obj2.free();
361 if (obj1.dictLookup("Print", &obj2)->isDict()) {
362 if (obj2.dictLookup("PrintState", &obj3)->isName()) {
363 if (obj3.isName("ON")) {
364 printStateA = ocUsageOn;
365 } else {
366 printStateA = ocUsageOff;
367 }
368 }
369 obj3.free();
370 }
371 obj2.free();
372 }
373 obj1.free();
374
375 return new OptionalContentGroup(refA, nameA, viewStateA, printStateA);
376 }
377
OptionalContentGroup(Ref * refA,TextString * nameA,OCUsageState viewStateA,OCUsageState printStateA)378 OptionalContentGroup::OptionalContentGroup(Ref *refA, TextString *nameA,
379 OCUsageState viewStateA,
380 OCUsageState printStateA) {
381 ref = *refA;
382 name = nameA;
383 viewState = viewStateA;
384 printState = printStateA;
385 state = gTrue;
386 inViewUsageAppDict = gFalse;
387 }
388
~OptionalContentGroup()389 OptionalContentGroup::~OptionalContentGroup() {
390 delete name;
391 }
392
matches(Ref * refA)393 GBool OptionalContentGroup::matches(Ref *refA) {
394 return refA->num == ref.num && refA->gen == ref.gen;
395 }
396
getName()397 Unicode *OptionalContentGroup::getName() {
398 return name->getUnicode();
399 }
400
getNameLength()401 int OptionalContentGroup::getNameLength() {
402 return name->getLength();
403 }
404
405 //------------------------------------------------------------------------
406
parse(Object * obj,OptionalContent * oc,XRef * xref,int recursion)407 OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc,
408 XRef *xref, int recursion) {
409 Object obj2, obj3;
410 Ref ref;
411 OptionalContentGroup *ocgA;
412 OCDisplayNode *node, *child;
413 int i;
414
415 if (recursion > displayNodeRecursionLimit) {
416 error(errSyntaxError, -1, "Loop detected in optional content order");
417 return NULL;
418 }
419 if (obj->isRef()) {
420 ref = obj->getRef();
421 if ((ocgA = oc->findOCG(&ref))) {
422 return new OCDisplayNode(ocgA);
423 }
424 }
425 obj->fetch(xref, &obj2);
426 if (!obj2.isArray()) {
427 obj2.free();
428 return NULL;
429 }
430 i = 0;
431 if (obj2.arrayGetLength() >= 1) {
432 if (obj2.arrayGet(0, &obj3)->isString()) {
433 node = new OCDisplayNode(obj3.getString());
434 i = 1;
435 } else {
436 node = new OCDisplayNode();
437 }
438 obj3.free();
439 } else {
440 node = new OCDisplayNode();
441 }
442 for (; i < obj2.arrayGetLength(); ++i) {
443 obj2.arrayGetNF(i, &obj3);
444 if ((child = OCDisplayNode::parse(&obj3, oc, xref, recursion + 1))) {
445 if (!child->ocg && !child->name && node->getNumChildren() > 0) {
446 if (child->getNumChildren() > 0) {
447 node->getChild(node->getNumChildren() - 1)->
448 addChildren(child->takeChildren());
449 }
450 delete child;
451 } else {
452 node->addChild(child);
453 }
454 }
455 obj3.free();
456 }
457 obj2.free();
458 return node;
459 }
460
OCDisplayNode()461 OCDisplayNode::OCDisplayNode() {
462 name = new TextString();
463 ocg = NULL;
464 parent = NULL;
465 children = NULL;
466 }
467
OCDisplayNode(GString * nameA)468 OCDisplayNode::OCDisplayNode(GString *nameA) {
469 name = new TextString(nameA);
470 ocg = NULL;
471 children = NULL;
472 }
473
OCDisplayNode(OptionalContentGroup * ocgA)474 OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) {
475 name = new TextString(ocgA->name);
476 ocg = ocgA;
477 children = NULL;
478 }
479
addChild(OCDisplayNode * child)480 void OCDisplayNode::addChild(OCDisplayNode *child) {
481 if (!children) {
482 children = new GList();
483 }
484 children->append(child);
485 child->parent = this;
486 }
487
addChildren(GList * childrenA)488 void OCDisplayNode::addChildren(GList *childrenA) {
489 int i;
490
491 if (!children) {
492 children = new GList();
493 }
494 children->append(childrenA);
495 for (i = 0; i < childrenA->getLength(); ++i) {
496 ((OCDisplayNode *)childrenA->get(i))->parent = this;
497 }
498 delete childrenA;
499 }
500
takeChildren()501 GList *OCDisplayNode::takeChildren() {
502 GList *childrenA;
503 int i;
504
505 childrenA = children;
506 children = NULL;
507 for (i = 0; i < childrenA->getLength(); ++i) {
508 ((OCDisplayNode *)childrenA->get(i))->parent = NULL;
509 }
510 return childrenA;
511 }
512
~OCDisplayNode()513 OCDisplayNode::~OCDisplayNode() {
514 delete name;
515 if (children) {
516 deleteGList(children, OCDisplayNode);
517 }
518 }
519
getName()520 Unicode *OCDisplayNode::getName() {
521 return name->getUnicode();
522 }
523
getNameLength()524 int OCDisplayNode::getNameLength() {
525 return name->getLength();
526 }
527
getNumChildren()528 int OCDisplayNode::getNumChildren() {
529 if (!children) {
530 return 0;
531 }
532 return children->getLength();
533 }
534
getChild(int idx)535 OCDisplayNode *OCDisplayNode::getChild(int idx) {
536 return (OCDisplayNode *)children->get(idx);
537 }
538