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 | |
34 | OCGs::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 | |
117 | bool OCGs::hasOCGs() const |
118 | { |
119 | return !(optionalContentGroups.empty()); |
120 | } |
121 | |
122 | OptionalContentGroup *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 | |
128 | bool 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 | |
187 | bool 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 | |
234 | bool 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 | |
248 | bool 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 | |
262 | bool 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 | |
276 | bool 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 | |
292 | OptionalContentGroup::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 | |
329 | OptionalContentGroup::OptionalContentGroup(GooString *label) |
330 | { |
331 | m_name = label; |
332 | m_state = On; |
333 | } |
334 | |
335 | const GooString *OptionalContentGroup::getName() const |
336 | { |
337 | return m_name; |
338 | } |
339 | |
340 | void OptionalContentGroup::setRef(const Ref ref) |
341 | { |
342 | m_ref = ref; |
343 | } |
344 | |
345 | Ref OptionalContentGroup::getRef() const |
346 | { |
347 | return m_ref; |
348 | } |
349 | |
350 | OptionalContentGroup::~OptionalContentGroup() |
351 | { |
352 | delete m_name; |
353 | } |
354 | |