1//========================================================================
2//
3// OptionalContent.cc
4//
5// Copyright 2007 Brad Hards <bradh@kde.org>
6// Copyright 2008 Pino Toscano <pino@kde.org>
7// Copyright 2008, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
8// Copyright 2008, 2010, 2011, 2017-2019 Albert Astals Cid <aacid@kde.org>
9// Copyright 2008 Mark Kaplan <mkaplan@finjan.com>
10// Copyright 2018 Adam Reichold <adam.reichold@t-online.de>
11// Copyright 2019 Oliver Sander <oliver.sander@tu-dresden.de>
12//
13// Released under the GPL (version 2, or later, at your option)
14//
15//========================================================================
16
17#include <config.h>
18
19#include "goo/gmem.h"
20#include "goo/GooString.h"
21#include "Error.h"
22#include "OptionalContent.h"
23
24// Max depth of nested visibility expressions. This is used to catch
25// infinite loops in the visibility expression object structure.
26#define visibilityExprRecursionLimit 50
27
28// Max depth of nested display nodes. This is used to catch infinite
29// loops in the "Order" object structure.
30#define displayNodeRecursionLimit 50
31
32//------------------------------------------------------------------------
33
34OCGs::OCGs(Object *ocgObject, XRef *xref) : m_xref(xref)
35{
36 // we need to parse the dictionary here, and build optionalContentGroups
37 ok = true;
38
39 Object ocgList = ocgObject->dictLookup(key: "OCGs");
40 if (!ocgList.isArray()) {
41 error(category: errSyntaxError, pos: -1, msg: "Expected the optional content group list, but wasn't able to find it, or it isn't an Array");
42 ok = false;
43 return;
44 }
45
46 // we now enumerate over the ocgList, and build up the optionalContentGroups list.
47 for (int i = 0; i < ocgList.arrayGetLength(); ++i) {
48 Object ocgDict = ocgList.arrayGet(i);
49 if (!ocgDict.isDict()) {
50 break;
51 }
52 auto thisOptionalContentGroup = std::make_unique<OptionalContentGroup>(args: ocgDict.getDict());
53 const Object &ocgRef = ocgList.arrayGetNF(i);
54 if (!ocgRef.isRef()) {
55 break;
56 }
57 thisOptionalContentGroup->setRef(ocgRef.getRef());
58 // the default is ON - we change state later, depending on BaseState, ON and OFF
59 thisOptionalContentGroup->setState(OptionalContentGroup::On);
60 optionalContentGroups.emplace(args: ocgRef.getRef(), args: std::move(thisOptionalContentGroup));
61 }
62
63 Object defaultOcgConfig = ocgObject->dictLookup(key: "D");
64 if (!defaultOcgConfig.isDict()) {
65 error(category: errSyntaxError, pos: -1, msg: "Expected the default config, but wasn't able to find it, or it isn't a Dictionary");
66 ok = false;
67 return;
68 }
69
70 Object baseState = defaultOcgConfig.dictLookup(key: "BaseState");
71 if (baseState.isName(nameA: "OFF")) {
72 for (auto &group : optionalContentGroups) {
73 group.second->setState(OptionalContentGroup::Off);
74 }
75 }
76
77 Object on = defaultOcgConfig.dictLookup(key: "ON");
78 if (on.isArray()) {
79 // ON is an optional element
80 for (int i = 0; i < on.arrayGetLength(); ++i) {
81 const Object &reference = on.arrayGetNF(i);
82 if (!reference.isRef()) {
83 // there can be null entries
84 break;
85 }
86 OptionalContentGroup *group = findOcgByRef(ref: reference.getRef());
87 if (!group) {
88 error(category: errSyntaxWarning, pos: -1, msg: "Couldn't find group for reference");
89 break;
90 }
91 group->setState(OptionalContentGroup::On);
92 }
93 }
94
95 Object off = defaultOcgConfig.dictLookup(key: "OFF");
96 if (off.isArray()) {
97 // OFF is an optional element
98 for (int i = 0; i < off.arrayGetLength(); ++i) {
99 const Object &reference = off.arrayGetNF(i);
100 if (!reference.isRef()) {
101 // there can be null entries
102 break;
103 }
104 OptionalContentGroup *group = findOcgByRef(ref: reference.getRef());
105 if (!group) {
106 error(category: errSyntaxWarning, pos: -1, msg: "Couldn't find group for reference to set OFF");
107 break;
108 }
109 group->setState(OptionalContentGroup::Off);
110 }
111 }
112
113 order = defaultOcgConfig.dictLookup(key: "Order");
114 rbgroups = defaultOcgConfig.dictLookup(key: "RBGroups");
115}
116
117bool OCGs::hasOCGs() const
118{
119 return !(optionalContentGroups.empty());
120}
121
122OptionalContentGroup *OCGs::findOcgByRef(const Ref ref)
123{
124 const auto ocg = optionalContentGroups.find(x: ref);
125 return ocg != optionalContentGroups.end() ? ocg->second.get() : nullptr;
126}
127
128bool OCGs::optContentIsVisible(const Object *dictRef)
129{
130 Dict *dict;
131 bool result = true;
132
133 if (dictRef->isNull()) {
134 return result;
135 }
136
137 if (dictRef->isRef()) {
138 OptionalContentGroup *oc = findOcgByRef(ref: dictRef->getRef());
139 if (oc) {
140 return oc->getState() == OptionalContentGroup::On;
141 }
142 }
143
144 Object dictObj = dictRef->fetch(xref: m_xref);
145 if (!dictObj.isDict()) {
146 error(category: errSyntaxWarning, pos: -1, msg: "Unexpected oc reference target: {0:d}", dictObj.getType());
147 return result;
148 }
149 dict = dictObj.getDict();
150 Object dictType = dict->lookup(key: "Type");
151 if (dictType.isName(nameA: "OCMD")) {
152 Object ve = dict->lookup(key: "VE");
153 if (ve.isArray()) {
154 result = evalOCVisibilityExpr(expr: &ve, recursion: 0);
155 } else {
156 const Object &ocg = dict->lookupNF(key: "OCGs");
157 if (ocg.isArray()) {
158 Object policy = dict->lookup(key: "P");
159 if (policy.isName(nameA: "AllOn")) {
160 result = allOn(ocgArray: ocg.getArray());
161 } else if (policy.isName(nameA: "AllOff")) {
162 result = allOff(ocgArray: ocg.getArray());
163 } else if (policy.isName(nameA: "AnyOff")) {
164 result = anyOff(ocgArray: ocg.getArray());
165 } else if ((!policy.isName()) || (policy.isName(nameA: "AnyOn"))) {
166 // this is the default
167 result = anyOn(ocgArray: ocg.getArray());
168 }
169 } else if (ocg.isRef()) {
170 OptionalContentGroup *oc = findOcgByRef(ref: ocg.getRef());
171 if (oc && oc->getState() == OptionalContentGroup::Off) {
172 result = false;
173 } else {
174 result = true;
175 }
176 }
177 }
178 } else if (dictType.isName(nameA: "OCG") && dictRef->isRef()) {
179 OptionalContentGroup *oc = findOcgByRef(ref: dictRef->getRef());
180 if (oc && oc->getState() == OptionalContentGroup::Off) {
181 result = false;
182 }
183 }
184 return result;
185}
186
187bool OCGs::evalOCVisibilityExpr(const Object *expr, int recursion)
188{
189 OptionalContentGroup *ocg;
190 bool ret;
191
192 if (recursion > visibilityExprRecursionLimit) {
193 error(category: errSyntaxError, pos: -1, msg: "Loop detected in optional content visibility expression");
194 return true;
195 }
196 if (expr->isRef()) {
197 if ((ocg = findOcgByRef(ref: expr->getRef()))) {
198 return ocg->getState() == OptionalContentGroup::On;
199 }
200 }
201 Object expr2 = expr->fetch(xref: m_xref);
202 if (!expr2.isArray() || expr2.arrayGetLength() < 1) {
203 error(category: errSyntaxError, pos: -1, msg: "Invalid optional content visibility expression");
204 return true;
205 }
206 Object op = expr2.arrayGet(i: 0);
207 if (op.isName(nameA: "Not")) {
208 if (expr2.arrayGetLength() == 2) {
209 const Object &obj = expr2.arrayGetNF(i: 1);
210 ret = !evalOCVisibilityExpr(expr: &obj, recursion: recursion + 1);
211 } else {
212 error(category: errSyntaxError, pos: -1, msg: "Invalid optional content visibility expression");
213 ret = true;
214 }
215 } else if (op.isName(nameA: "And")) {
216 ret = true;
217 for (int i = 1; i < expr2.arrayGetLength() && ret; ++i) {
218 const Object &obj = expr2.arrayGetNF(i);
219 ret = evalOCVisibilityExpr(expr: &obj, recursion: recursion + 1);
220 }
221 } else if (op.isName(nameA: "Or")) {
222 ret = false;
223 for (int i = 1; i < expr2.arrayGetLength() && !ret; ++i) {
224 const Object &obj = expr2.arrayGetNF(i);
225 ret = evalOCVisibilityExpr(expr: &obj, recursion: recursion + 1);
226 }
227 } else {
228 error(category: errSyntaxError, pos: -1, msg: "Invalid optional content visibility expression");
229 ret = true;
230 }
231 return ret;
232}
233
234bool OCGs::allOn(Array *ocgArray)
235{
236 for (int i = 0; i < ocgArray->getLength(); ++i) {
237 const Object &ocgItem = ocgArray->getNF(i);
238 if (ocgItem.isRef()) {
239 OptionalContentGroup *oc = findOcgByRef(ref: ocgItem.getRef());
240 if (oc && oc->getState() == OptionalContentGroup::Off) {
241 return false;
242 }
243 }
244 }
245 return true;
246}
247
248bool OCGs::allOff(Array *ocgArray)
249{
250 for (int i = 0; i < ocgArray->getLength(); ++i) {
251 const Object &ocgItem = ocgArray->getNF(i);
252 if (ocgItem.isRef()) {
253 OptionalContentGroup *oc = findOcgByRef(ref: ocgItem.getRef());
254 if (oc && oc->getState() == OptionalContentGroup::On) {
255 return false;
256 }
257 }
258 }
259 return true;
260}
261
262bool OCGs::anyOn(Array *ocgArray)
263{
264 for (int i = 0; i < ocgArray->getLength(); ++i) {
265 const Object &ocgItem = ocgArray->getNF(i);
266 if (ocgItem.isRef()) {
267 OptionalContentGroup *oc = findOcgByRef(ref: ocgItem.getRef());
268 if (oc && oc->getState() == OptionalContentGroup::On) {
269 return true;
270 }
271 }
272 }
273 return false;
274}
275
276bool OCGs::anyOff(Array *ocgArray)
277{
278 for (int i = 0; i < ocgArray->getLength(); ++i) {
279 const Object &ocgItem = ocgArray->getNF(i);
280 if (ocgItem.isRef()) {
281 OptionalContentGroup *oc = findOcgByRef(ref: ocgItem.getRef());
282 if (oc && oc->getState() == OptionalContentGroup::Off) {
283 return true;
284 }
285 }
286 }
287 return false;
288}
289
290//------------------------------------------------------------------------
291
292OptionalContentGroup::OptionalContentGroup(Dict *ocgDict) : m_name(nullptr)
293{
294 Object ocgName = ocgDict->lookup(key: "Name");
295 if (!ocgName.isString()) {
296 error(category: errSyntaxWarning, pos: -1, msg: "Expected the name of the OCG, but wasn't able to find it, or it isn't a String");
297 } else {
298 m_name = new GooString(ocgName.getString());
299 }
300
301 viewState = printState = ocUsageUnset;
302 Object obj1 = ocgDict->lookup(key: "Usage");
303 if (obj1.isDict()) {
304 Object obj2 = obj1.dictLookup(key: "View");
305 if (obj2.isDict()) {
306 Object obj3 = obj2.dictLookup(key: "ViewState");
307 if (obj3.isName()) {
308 if (obj3.isName(nameA: "ON")) {
309 viewState = ocUsageOn;
310 } else {
311 viewState = ocUsageOff;
312 }
313 }
314 }
315 obj2 = obj1.dictLookup(key: "Print");
316 if (obj2.isDict()) {
317 Object obj3 = obj2.dictLookup(key: "PrintState");
318 if (obj3.isName()) {
319 if (obj3.isName(nameA: "ON")) {
320 printState = ocUsageOn;
321 } else {
322 printState = ocUsageOff;
323 }
324 }
325 }
326 }
327}
328
329OptionalContentGroup::OptionalContentGroup(GooString *label)
330{
331 m_name = label;
332 m_state = On;
333}
334
335const GooString *OptionalContentGroup::getName() const
336{
337 return m_name;
338}
339
340void OptionalContentGroup::setRef(const Ref ref)
341{
342 m_ref = ref;
343}
344
345Ref OptionalContentGroup::getRef() const
346{
347 return m_ref;
348}
349
350OptionalContentGroup::~OptionalContentGroup()
351{
352 delete m_name;
353}
354

source code of poppler/poppler/OptionalContent.cc