1 | //======================================================================== |
2 | // |
3 | // Link.cc |
4 | // |
5 | // Copyright 1996-2003 Glyph & Cog, LLC |
6 | // |
7 | //======================================================================== |
8 | |
9 | //======================================================================== |
10 | // |
11 | // Modified under the Poppler project - http://poppler.freedesktop.org |
12 | // |
13 | // All changes made under the Poppler project to this file are licensed |
14 | // under GPL version 2 or later |
15 | // |
16 | // Copyright (C) 2006, 2008 Pino Toscano <pino@kde.org> |
17 | // Copyright (C) 2007, 2010, 2011 Carlos Garcia Campos <carlosgc@gnome.org> |
18 | // Copyright (C) 2008 Hugo Mercier <hmercier31@gmail.com> |
19 | // Copyright (C) 2008-2010, 2012-2014, 2016-2023 Albert Astals Cid <aacid@kde.org> |
20 | // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> |
21 | // Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com> |
22 | // Copyright (C) 2012 Tobias Koening <tobias.koenig@kdab.com> |
23 | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
24 | // Copyright (C) 2018 Intevation GmbH <intevation@intevation.de> |
25 | // Copyright (C) 2018, 2020 Adam Reichold <adam.reichold@t-online.de> |
26 | // Copyright (C) 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de> |
27 | // Copyright (C) 2020 Marek Kasik <mkasik@redhat.com> |
28 | // |
29 | // To see a description of the changes please see the Changelog file that |
30 | // came with your tarball or type make ChangeLog if you are building from git |
31 | // |
32 | //======================================================================== |
33 | |
34 | #include <config.h> |
35 | |
36 | #include <cstddef> |
37 | #include <cstring> |
38 | #include "goo/gmem.h" |
39 | #include "goo/GooString.h" |
40 | #include "Error.h" |
41 | #include "Object.h" |
42 | #include "Array.h" |
43 | #include "Dict.h" |
44 | #include "Link.h" |
45 | #include "Sound.h" |
46 | #include "FileSpec.h" |
47 | #include "Rendition.h" |
48 | #include "Annot.h" |
49 | |
50 | //------------------------------------------------------------------------ |
51 | // LinkAction |
52 | //------------------------------------------------------------------------ |
53 | LinkAction::LinkAction() = default; |
54 | |
55 | LinkAction::~LinkAction() = default; |
56 | |
57 | std::unique_ptr<LinkAction> LinkAction::parseDest(const Object *obj) |
58 | { |
59 | auto action = std::unique_ptr<LinkAction>(new LinkGoTo(obj)); |
60 | if (!action->isOk()) { |
61 | action.reset(); |
62 | } |
63 | return action; |
64 | } |
65 | |
66 | std::unique_ptr<LinkAction> LinkAction::parseAction(const Object *obj, const std::optional<std::string> &baseURI) |
67 | { |
68 | std::set<int> seenNextActions; |
69 | return parseAction(obj, baseURI, seenNextActions: &seenNextActions); |
70 | } |
71 | |
72 | std::unique_ptr<LinkAction> LinkAction::parseAction(const Object *obj, const std::optional<std::string> &baseURI, std::set<int> *seenNextActions) |
73 | { |
74 | |
75 | if (!obj->isDict()) { |
76 | error(category: errSyntaxWarning, pos: -1, msg: "parseAction: Bad annotation action for URI '{0:s}'" , baseURI ? baseURI->c_str() : "NULL" ); |
77 | return nullptr; |
78 | } |
79 | |
80 | std::unique_ptr<LinkAction> action; |
81 | Object obj2 = obj->dictLookup(key: "S" ); |
82 | |
83 | // GoTo action |
84 | if (obj2.isName(nameA: "GoTo" )) { |
85 | Object obj3 = obj->dictLookup(key: "D" ); |
86 | action = std::make_unique<LinkGoTo>(args: &obj3); |
87 | |
88 | // GoToR action |
89 | } else if (obj2.isName(nameA: "GoToR" )) { |
90 | Object obj3 = obj->dictLookup(key: "F" ); |
91 | Object obj4 = obj->dictLookup(key: "D" ); |
92 | action = std::make_unique<LinkGoToR>(args: &obj3, args: &obj4); |
93 | |
94 | // Launch action |
95 | } else if (obj2.isName(nameA: "Launch" )) { |
96 | action = std::make_unique<LinkLaunch>(args&: obj); |
97 | |
98 | // URI action |
99 | } else if (obj2.isName(nameA: "URI" )) { |
100 | Object obj3 = obj->dictLookup(key: "URI" ); |
101 | action = std::make_unique<LinkURI>(args: &obj3, args: baseURI); |
102 | |
103 | // Named action |
104 | } else if (obj2.isName(nameA: "Named" )) { |
105 | Object obj3 = obj->dictLookup(key: "N" ); |
106 | action = std::make_unique<LinkNamed>(args: &obj3); |
107 | |
108 | // Movie action |
109 | } else if (obj2.isName(nameA: "Movie" )) { |
110 | action = std::make_unique<LinkMovie>(args&: obj); |
111 | |
112 | // Rendition action |
113 | } else if (obj2.isName(nameA: "Rendition" )) { |
114 | action = std::make_unique<LinkRendition>(args&: obj); |
115 | |
116 | // Sound action |
117 | } else if (obj2.isName(nameA: "Sound" )) { |
118 | action = std::make_unique<LinkSound>(args&: obj); |
119 | |
120 | // JavaScript action |
121 | } else if (obj2.isName(nameA: "JavaScript" )) { |
122 | Object obj3 = obj->dictLookup(key: "JS" ); |
123 | action = std::make_unique<LinkJavaScript>(args: &obj3); |
124 | |
125 | // Set-OCG-State action |
126 | } else if (obj2.isName(nameA: "SetOCGState" )) { |
127 | action = std::make_unique<LinkOCGState>(args&: obj); |
128 | |
129 | // Hide action |
130 | } else if (obj2.isName(nameA: "Hide" )) { |
131 | action = std::make_unique<LinkHide>(args&: obj); |
132 | |
133 | // ResetForm action |
134 | } else if (obj2.isName(nameA: "ResetForm" )) { |
135 | action = std::make_unique<LinkResetForm>(args&: obj); |
136 | |
137 | // unknown action |
138 | } else if (obj2.isName()) { |
139 | action = std::make_unique<LinkUnknown>(args: obj2.getName()); |
140 | |
141 | // action is missing or wrong type |
142 | } else { |
143 | error(category: errSyntaxWarning, pos: -1, msg: "parseAction: Unknown annotation action object: URI = '{0:s}'" , baseURI ? baseURI->c_str() : "NULL" ); |
144 | action = nullptr; |
145 | } |
146 | |
147 | if (action && !action->isOk()) { |
148 | action.reset(); |
149 | return nullptr; |
150 | } |
151 | |
152 | if (!action) { |
153 | return nullptr; |
154 | } |
155 | |
156 | // parse the next actions |
157 | const Object nextObj = obj->dictLookup(key: "Next" ); |
158 | std::vector<std::unique_ptr<LinkAction>> actionList; |
159 | if (nextObj.isDict()) { |
160 | |
161 | // Prevent circles in the tree by checking the ref against used refs in |
162 | // our current tree branch. |
163 | const Object &nextRefObj = obj->dictLookupNF(key: "Next" ); |
164 | if (nextRefObj.isRef()) { |
165 | const Ref ref = nextRefObj.getRef(); |
166 | if (!seenNextActions->insert(x: ref.num).second) { |
167 | error(category: errSyntaxWarning, pos: -1, msg: "parseAction: Circular next actions detected." ); |
168 | return action; |
169 | } |
170 | } |
171 | |
172 | actionList.reserve(n: 1); |
173 | actionList.push_back(x: parseAction(obj: &nextObj, baseURI: {}, seenNextActions)); |
174 | } else if (nextObj.isArray()) { |
175 | const Array *a = nextObj.getArray(); |
176 | const int n = a->getLength(); |
177 | actionList.reserve(n: n); |
178 | for (int i = 0; i < n; ++i) { |
179 | const Object obj3 = a->get(i); |
180 | if (!obj3.isDict()) { |
181 | error(category: errSyntaxWarning, pos: -1, msg: "parseAction: Next array does not contain only dicts" ); |
182 | continue; |
183 | } |
184 | |
185 | // Similar circle check as above. |
186 | const Object &obj3Ref = a->getNF(i); |
187 | if (obj3Ref.isRef()) { |
188 | const Ref ref = obj3Ref.getRef(); |
189 | if (!seenNextActions->insert(x: ref.num).second) { |
190 | error(category: errSyntaxWarning, pos: -1, msg: "parseAction: Circular next actions detected in array." ); |
191 | return action; |
192 | } |
193 | } |
194 | |
195 | actionList.push_back(x: parseAction(obj: &obj3, baseURI: {}, seenNextActions)); |
196 | } |
197 | } |
198 | |
199 | action->nextActionList = std::move(actionList); |
200 | |
201 | return action; |
202 | } |
203 | |
204 | const std::vector<std::unique_ptr<LinkAction>> &LinkAction::nextActions() const |
205 | { |
206 | return nextActionList; |
207 | } |
208 | |
209 | //------------------------------------------------------------------------ |
210 | // LinkDest |
211 | //------------------------------------------------------------------------ |
212 | |
213 | LinkDest::LinkDest(const Array *a) |
214 | { |
215 | // initialize fields |
216 | left = bottom = right = top = zoom = 0; |
217 | changeLeft = changeTop = changeZoom = false; |
218 | ok = false; |
219 | |
220 | // get page |
221 | if (a->getLength() < 2) { |
222 | error(category: errSyntaxWarning, pos: -1, msg: "Annotation destination array is too short" ); |
223 | return; |
224 | } |
225 | const Object &obj0 = a->getNF(i: 0); |
226 | if (obj0.isInt()) { |
227 | pageNum = obj0.getInt() + 1; |
228 | pageIsRef = false; |
229 | } else if (obj0.isRef()) { |
230 | pageRef = obj0.getRef(); |
231 | pageIsRef = true; |
232 | } else { |
233 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination" ); |
234 | return; |
235 | } |
236 | |
237 | // get destination type |
238 | Object obj1 = a->get(i: 1); |
239 | |
240 | // XYZ link |
241 | if (obj1.isName(nameA: "XYZ" )) { |
242 | kind = destXYZ; |
243 | if (a->getLength() < 3) { |
244 | changeLeft = false; |
245 | } else { |
246 | Object obj2 = a->get(i: 2); |
247 | if (obj2.isNull()) { |
248 | changeLeft = false; |
249 | } else if (obj2.isNum()) { |
250 | changeLeft = true; |
251 | left = obj2.getNum(); |
252 | } else { |
253 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
254 | return; |
255 | } |
256 | } |
257 | if (a->getLength() < 4) { |
258 | changeTop = false; |
259 | } else { |
260 | Object obj2 = a->get(i: 3); |
261 | if (obj2.isNull()) { |
262 | changeTop = false; |
263 | } else if (obj2.isNum()) { |
264 | changeTop = true; |
265 | top = obj2.getNum(); |
266 | } else { |
267 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
268 | return; |
269 | } |
270 | } |
271 | if (a->getLength() < 5) { |
272 | changeZoom = false; |
273 | } else { |
274 | Object obj2 = a->get(i: 4); |
275 | if (obj2.isNull()) { |
276 | changeZoom = false; |
277 | } else if (obj2.isNum()) { |
278 | zoom = obj2.getNum(); |
279 | changeZoom = (zoom == 0) ? false : true; |
280 | } else { |
281 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
282 | return; |
283 | } |
284 | } |
285 | |
286 | // Fit link |
287 | } else if (obj1.isName(nameA: "Fit" )) { |
288 | kind = destFit; |
289 | |
290 | // FitH link |
291 | } else if (obj1.isName(nameA: "FitH" )) { |
292 | kind = destFitH; |
293 | if (a->getLength() < 3) { |
294 | changeTop = false; |
295 | } else { |
296 | Object obj2 = a->get(i: 2); |
297 | if (obj2.isNull()) { |
298 | changeTop = false; |
299 | } else if (obj2.isNum()) { |
300 | changeTop = true; |
301 | top = obj2.getNum(); |
302 | } else { |
303 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
304 | kind = destFit; |
305 | } |
306 | } |
307 | |
308 | // FitV link |
309 | } else if (obj1.isName(nameA: "FitV" )) { |
310 | if (a->getLength() < 3) { |
311 | error(category: errSyntaxWarning, pos: -1, msg: "Annotation destination array is too short" ); |
312 | return; |
313 | } |
314 | kind = destFitV; |
315 | Object obj2 = a->get(i: 2); |
316 | if (obj2.isNull()) { |
317 | changeLeft = false; |
318 | } else if (obj2.isNum()) { |
319 | changeLeft = true; |
320 | left = obj2.getNum(); |
321 | } else { |
322 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
323 | kind = destFit; |
324 | } |
325 | |
326 | // FitR link |
327 | } else if (obj1.isName(nameA: "FitR" )) { |
328 | if (a->getLength() < 6) { |
329 | error(category: errSyntaxWarning, pos: -1, msg: "Annotation destination array is too short" ); |
330 | return; |
331 | } |
332 | kind = destFitR; |
333 | Object obj2 = a->get(i: 2); |
334 | if (obj2.isNum()) { |
335 | left = obj2.getNum(); |
336 | } else { |
337 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
338 | kind = destFit; |
339 | } |
340 | obj2 = a->get(i: 3); |
341 | if (obj2.isNum()) { |
342 | bottom = obj2.getNum(); |
343 | } else { |
344 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
345 | kind = destFit; |
346 | } |
347 | obj2 = a->get(i: 4); |
348 | if (obj2.isNum()) { |
349 | right = obj2.getNum(); |
350 | } else { |
351 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
352 | kind = destFit; |
353 | } |
354 | obj2 = a->get(i: 5); |
355 | if (obj2.isNum()) { |
356 | top = obj2.getNum(); |
357 | } else { |
358 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
359 | kind = destFit; |
360 | } |
361 | |
362 | // FitB link |
363 | } else if (obj1.isName(nameA: "FitB" )) { |
364 | kind = destFitB; |
365 | |
366 | // FitBH link |
367 | } else if (obj1.isName(nameA: "FitBH" )) { |
368 | if (a->getLength() < 3) { |
369 | error(category: errSyntaxWarning, pos: -1, msg: "Annotation destination array is too short" ); |
370 | return; |
371 | } |
372 | kind = destFitBH; |
373 | Object obj2 = a->get(i: 2); |
374 | if (obj2.isNull()) { |
375 | changeTop = false; |
376 | } else if (obj2.isNum()) { |
377 | changeTop = true; |
378 | top = obj2.getNum(); |
379 | } else { |
380 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
381 | kind = destFit; |
382 | } |
383 | |
384 | // FitBV link |
385 | } else if (obj1.isName(nameA: "FitBV" )) { |
386 | if (a->getLength() < 3) { |
387 | error(category: errSyntaxWarning, pos: -1, msg: "Annotation destination array is too short" ); |
388 | return; |
389 | } |
390 | kind = destFitBV; |
391 | Object obj2 = a->get(i: 2); |
392 | if (obj2.isNull()) { |
393 | changeLeft = false; |
394 | } else if (obj2.isNum()) { |
395 | changeLeft = true; |
396 | left = obj2.getNum(); |
397 | } else { |
398 | error(category: errSyntaxWarning, pos: -1, msg: "Bad annotation destination position" ); |
399 | kind = destFit; |
400 | } |
401 | |
402 | // unknown link kind |
403 | } else { |
404 | error(category: errSyntaxWarning, pos: -1, msg: "Unknown annotation destination type" ); |
405 | return; |
406 | } |
407 | |
408 | ok = true; |
409 | } |
410 | |
411 | //------------------------------------------------------------------------ |
412 | // LinkGoTo |
413 | //------------------------------------------------------------------------ |
414 | |
415 | LinkGoTo::LinkGoTo(const Object *destObj) |
416 | { |
417 | // named destination |
418 | if (destObj->isName()) { |
419 | namedDest = std::make_unique<GooString>(args: destObj->getName()); |
420 | } else if (destObj->isString()) { |
421 | namedDest = std::unique_ptr<GooString>(destObj->getString()->copy()); |
422 | |
423 | // destination dictionary |
424 | } else if (destObj->isArray()) { |
425 | dest = std::make_unique<LinkDest>(args: destObj->getArray()); |
426 | if (!dest->isOk()) { |
427 | dest.reset(); |
428 | } |
429 | |
430 | // error |
431 | } else { |
432 | error(category: errSyntaxWarning, pos: -1, msg: "Illegal annotation destination" ); |
433 | } |
434 | } |
435 | |
436 | LinkGoTo::~LinkGoTo() = default; |
437 | |
438 | //------------------------------------------------------------------------ |
439 | // LinkGoToR |
440 | //------------------------------------------------------------------------ |
441 | |
442 | LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) |
443 | { |
444 | // get file name |
445 | Object obj1 = getFileSpecNameForPlatform(fileSpec: fileSpecObj); |
446 | if (obj1.isString()) { |
447 | fileName = std::unique_ptr<GooString>(obj1.getString()->copy()); |
448 | } |
449 | |
450 | // named destination |
451 | if (destObj->isName()) { |
452 | namedDest = std::make_unique<GooString>(args: destObj->getName()); |
453 | } else if (destObj->isString()) { |
454 | namedDest = std::unique_ptr<GooString>(destObj->getString()->copy()); |
455 | |
456 | // destination dictionary |
457 | } else if (destObj->isArray()) { |
458 | dest = std::make_unique<LinkDest>(args: destObj->getArray()); |
459 | if (!dest->isOk()) { |
460 | dest.reset(); |
461 | } |
462 | |
463 | // error |
464 | } else { |
465 | error(category: errSyntaxWarning, pos: -1, msg: "Illegal annotation destination" ); |
466 | } |
467 | } |
468 | |
469 | LinkGoToR::~LinkGoToR() = default; |
470 | |
471 | //------------------------------------------------------------------------ |
472 | // LinkLaunch |
473 | //------------------------------------------------------------------------ |
474 | |
475 | LinkLaunch::LinkLaunch(const Object *actionObj) |
476 | { |
477 | |
478 | if (actionObj->isDict()) { |
479 | Object obj1 = actionObj->dictLookup(key: "F" ); |
480 | if (!obj1.isNull()) { |
481 | Object obj3 = getFileSpecNameForPlatform(fileSpec: &obj1); |
482 | if (obj3.isString()) { |
483 | fileName = std::unique_ptr<GooString>(obj3.getString()->copy()); |
484 | } |
485 | } else { |
486 | #ifdef _WIN32 |
487 | obj1 = actionObj->dictLookup("Win" ); |
488 | #else |
489 | //~ This hasn't been defined by Adobe yet, so assume it looks |
490 | //~ just like the Win dictionary until they say otherwise. |
491 | obj1 = actionObj->dictLookup(key: "Unix" ); |
492 | #endif |
493 | if (obj1.isDict()) { |
494 | Object obj2 = obj1.dictLookup(key: "F" ); |
495 | Object obj3 = getFileSpecNameForPlatform(fileSpec: &obj2); |
496 | if (obj3.isString()) { |
497 | fileName = std::unique_ptr<GooString>(obj3.getString()->copy()); |
498 | } |
499 | obj2 = obj1.dictLookup(key: "P" ); |
500 | if (obj2.isString()) { |
501 | params = std::unique_ptr<GooString>(obj2.getString()->copy()); |
502 | } |
503 | } else { |
504 | error(category: errSyntaxWarning, pos: -1, msg: "Bad launch-type link action" ); |
505 | } |
506 | } |
507 | } |
508 | } |
509 | |
510 | LinkLaunch::~LinkLaunch() = default; |
511 | |
512 | //------------------------------------------------------------------------ |
513 | // LinkURI |
514 | //------------------------------------------------------------------------ |
515 | |
516 | LinkURI::LinkURI(const Object *uriObj, const std::optional<std::string> &baseURI) |
517 | { |
518 | hasURIFlag = false; |
519 | if (uriObj->isString()) { |
520 | hasURIFlag = true; |
521 | const std::string &uri2 = uriObj->getString()->toStr(); |
522 | size_t n = strcspn(s: uri2.c_str(), reject: "/:" ); |
523 | if (n < uri2.size() && uri2[n] == ':') { |
524 | // "http:..." etc. |
525 | uri = uri2; |
526 | } else if (!uri2.compare(pos: 0, n1: 4, s: "www." )) { |
527 | // "www.[...]" without the leading "http://" |
528 | uri = "http://" + uri2; |
529 | } else { |
530 | // relative URI |
531 | if (baseURI) { |
532 | uri = *baseURI; |
533 | if (uri.size() > 0) { |
534 | char c = uri.back(); |
535 | if (c != '/' && c != '?') { |
536 | uri += '/'; |
537 | } |
538 | } |
539 | if (uri2[0] == '/') { |
540 | uri.append(s: uri2.c_str() + 1, n: uri2.size() - 1); |
541 | } else { |
542 | uri += uri2; |
543 | } |
544 | } else { |
545 | uri = uri2; |
546 | } |
547 | } |
548 | } else { |
549 | error(category: errSyntaxWarning, pos: -1, msg: "Illegal URI-type link" ); |
550 | } |
551 | } |
552 | |
553 | LinkURI::~LinkURI() = default; |
554 | |
555 | //------------------------------------------------------------------------ |
556 | // LinkNamed |
557 | //------------------------------------------------------------------------ |
558 | |
559 | LinkNamed::LinkNamed(const Object *nameObj) |
560 | { |
561 | hasNameFlag = false; |
562 | if (nameObj->isName()) { |
563 | name = (nameObj->getName()) ? nameObj->getName() : "" ; |
564 | hasNameFlag = true; |
565 | } |
566 | } |
567 | |
568 | LinkNamed::~LinkNamed() = default; |
569 | |
570 | //------------------------------------------------------------------------ |
571 | // LinkMovie |
572 | //------------------------------------------------------------------------ |
573 | |
574 | LinkMovie::LinkMovie(const Object *obj) |
575 | { |
576 | annotRef = Ref::INVALID(); |
577 | hasAnnotTitleFlag = false; |
578 | |
579 | const Object &annotationObj = obj->dictLookupNF(key: "Annotation" ); |
580 | if (annotationObj.isRef()) { |
581 | annotRef = annotationObj.getRef(); |
582 | } |
583 | |
584 | Object tmp = obj->dictLookup(key: "T" ); |
585 | if (tmp.isString()) { |
586 | annotTitle = tmp.getString()->toStr(); |
587 | hasAnnotTitleFlag = true; |
588 | } |
589 | |
590 | if ((!hasAnnotTitleFlag) && (annotRef == Ref::INVALID())) { |
591 | error(category: errSyntaxError, pos: -1, msg: "Movie action is missing both the Annot and T keys" ); |
592 | } |
593 | |
594 | tmp = obj->dictLookup(key: "Operation" ); |
595 | if (tmp.isName()) { |
596 | const char *name = tmp.getName(); |
597 | |
598 | if (!strcmp(s1: name, s2: "Play" )) { |
599 | operation = operationTypePlay; |
600 | } else if (!strcmp(s1: name, s2: "Stop" )) { |
601 | operation = operationTypeStop; |
602 | } else if (!strcmp(s1: name, s2: "Pause" )) { |
603 | operation = operationTypePause; |
604 | } else if (!strcmp(s1: name, s2: "Resume" )) { |
605 | operation = operationTypeResume; |
606 | } |
607 | } |
608 | } |
609 | |
610 | LinkMovie::~LinkMovie() = default; |
611 | |
612 | //------------------------------------------------------------------------ |
613 | // LinkSound |
614 | //------------------------------------------------------------------------ |
615 | |
616 | LinkSound::LinkSound(const Object *soundObj) |
617 | { |
618 | volume = 1.0; |
619 | sync = false; |
620 | repeat = false; |
621 | mix = false; |
622 | sound = nullptr; |
623 | if (soundObj->isDict()) { |
624 | // volume |
625 | Object tmp = soundObj->dictLookup(key: "Volume" ); |
626 | if (tmp.isNum()) { |
627 | volume = tmp.getNum(); |
628 | } |
629 | // sync |
630 | tmp = soundObj->dictLookup(key: "Synchronous" ); |
631 | if (tmp.isBool()) { |
632 | sync = tmp.getBool(); |
633 | } |
634 | // repeat |
635 | tmp = soundObj->dictLookup(key: "Repeat" ); |
636 | if (tmp.isBool()) { |
637 | repeat = tmp.getBool(); |
638 | } |
639 | // mix |
640 | tmp = soundObj->dictLookup(key: "Mix" ); |
641 | if (tmp.isBool()) { |
642 | mix = tmp.getBool(); |
643 | } |
644 | // 'Sound' object |
645 | tmp = soundObj->dictLookup(key: "Sound" ); |
646 | sound = Sound::parseSound(obj: &tmp); |
647 | } |
648 | } |
649 | |
650 | LinkSound::~LinkSound() = default; |
651 | |
652 | //------------------------------------------------------------------------ |
653 | // LinkRendition |
654 | //------------------------------------------------------------------------ |
655 | |
656 | LinkRendition::LinkRendition(const Object *obj) |
657 | { |
658 | operation = NoRendition; |
659 | media = nullptr; |
660 | int operationCode = -1; |
661 | |
662 | screenRef = Ref::INVALID(); |
663 | |
664 | if (obj->isDict()) { |
665 | Object tmp = obj->dictLookup(key: "JS" ); |
666 | if (!tmp.isNull()) { |
667 | if (tmp.isString()) { |
668 | js = tmp.getString()->toStr(); |
669 | } else if (tmp.isStream()) { |
670 | Stream *stream = tmp.getStream(); |
671 | stream->fillString(s&: js); |
672 | } else { |
673 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid Rendition Action: JS not string or stream" ); |
674 | } |
675 | } |
676 | |
677 | tmp = obj->dictLookup(key: "OP" ); |
678 | if (tmp.isInt()) { |
679 | operationCode = tmp.getInt(); |
680 | if (js.empty() && (operationCode < 0 || operationCode > 4)) { |
681 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid Rendition Action: unrecognized operation valued: {0:d}" , operationCode); |
682 | } else { |
683 | // retrieve rendition object |
684 | Object renditionObj = obj->dictLookup(key: "R" ); |
685 | if (renditionObj.isDict()) { |
686 | media = new MediaRendition(&renditionObj); |
687 | } else if (operationCode == 0 || operationCode == 4) { |
688 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid Rendition Action: no R field with op = {0:d}" , operationCode); |
689 | renditionObj.setToNull(); |
690 | } |
691 | |
692 | const Object &anObj = obj->dictLookupNF(key: "AN" ); |
693 | if (anObj.isRef()) { |
694 | screenRef = anObj.getRef(); |
695 | } else if (operation >= 0 && operation <= 4) { |
696 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid Rendition Action: no AN field with op = {0:d}" , operationCode); |
697 | } |
698 | } |
699 | |
700 | switch (operationCode) { |
701 | case 0: |
702 | operation = PlayRendition; |
703 | break; |
704 | case 1: |
705 | operation = StopRendition; |
706 | break; |
707 | case 2: |
708 | operation = PauseRendition; |
709 | break; |
710 | case 3: |
711 | operation = ResumeRendition; |
712 | break; |
713 | case 4: |
714 | operation = PlayRendition; |
715 | break; |
716 | } |
717 | } else if (js == "" ) { |
718 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid Rendition action: no OP or JS field defined" ); |
719 | } |
720 | } |
721 | } |
722 | |
723 | LinkRendition::~LinkRendition() |
724 | { |
725 | delete media; |
726 | } |
727 | |
728 | //------------------------------------------------------------------------ |
729 | // LinkJavaScript |
730 | //------------------------------------------------------------------------ |
731 | |
732 | LinkJavaScript::LinkJavaScript(Object *jsObj) |
733 | { |
734 | isValid = false; |
735 | |
736 | if (jsObj->isString()) { |
737 | js = jsObj->getString()->toStr(); |
738 | isValid = true; |
739 | } else if (jsObj->isStream()) { |
740 | Stream *stream = jsObj->getStream(); |
741 | stream->fillString(s&: js); |
742 | isValid = true; |
743 | } |
744 | } |
745 | |
746 | LinkJavaScript::~LinkJavaScript() = default; |
747 | |
748 | Object LinkJavaScript::createObject(XRef *xref, const std::string &js) |
749 | { |
750 | Dict *linkDict = new Dict(xref); |
751 | linkDict->add(key: "S" , val: Object(objName, "JavaScript" )); |
752 | linkDict->add(key: "JS" , val: Object(new GooString(js))); |
753 | |
754 | return Object(linkDict); |
755 | } |
756 | |
757 | //------------------------------------------------------------------------ |
758 | // LinkOCGState |
759 | //------------------------------------------------------------------------ |
760 | LinkOCGState::LinkOCGState(const Object *obj) : isValid(true) |
761 | { |
762 | Object obj1 = obj->dictLookup(key: "State" ); |
763 | if (obj1.isArray()) { |
764 | StateList stList; |
765 | |
766 | for (int i = 0; i < obj1.arrayGetLength(); ++i) { |
767 | const Object &obj2 = obj1.arrayGetNF(i); |
768 | if (obj2.isName()) { |
769 | if (!stList.list.empty()) { |
770 | stateList.push_back(x: stList); |
771 | } |
772 | |
773 | const char *name = obj2.getName(); |
774 | stList.list.clear(); |
775 | if (!strcmp(s1: name, s2: "ON" )) { |
776 | stList.st = On; |
777 | } else if (!strcmp(s1: name, s2: "OFF" )) { |
778 | stList.st = Off; |
779 | } else if (!strcmp(s1: name, s2: "Toggle" )) { |
780 | stList.st = Toggle; |
781 | } else { |
782 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid name '{0:s}' in OCG Action state array" , name); |
783 | isValid = false; |
784 | } |
785 | } else if (obj2.isRef()) { |
786 | stList.list.push_back(x: obj2.getRef()); |
787 | } else { |
788 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid item in OCG Action State array" ); |
789 | isValid = false; |
790 | } |
791 | } |
792 | // Add the last group |
793 | if (!stList.list.empty()) { |
794 | stateList.push_back(x: stList); |
795 | } |
796 | } else { |
797 | error(category: errSyntaxWarning, pos: -1, msg: "Invalid OCGState action" ); |
798 | isValid = false; |
799 | } |
800 | |
801 | preserveRB = obj->dictLookup(key: "PreserveRB" ).getBoolWithDefaultValue(defaultValue: true); |
802 | } |
803 | |
804 | LinkOCGState::~LinkOCGState() = default; |
805 | |
806 | //------------------------------------------------------------------------ |
807 | // LinkHide |
808 | //------------------------------------------------------------------------ |
809 | |
810 | LinkHide::LinkHide(const Object *hideObj) |
811 | { |
812 | hasTargetNameFlag = false; |
813 | show = false; // Default |
814 | |
815 | if (hideObj->isDict()) { |
816 | const Object targetObj = hideObj->dictLookup(key: "T" ); |
817 | if (targetObj.isString()) { |
818 | targetName = targetObj.getString()->toStr(); |
819 | hasTargetNameFlag = true; |
820 | } |
821 | const Object shouldHide = hideObj->dictLookup(key: "H" ); |
822 | if (shouldHide.isBool()) { |
823 | show = !shouldHide.getBool(); |
824 | } |
825 | } |
826 | } |
827 | |
828 | LinkHide::~LinkHide() = default; |
829 | |
830 | //------------------------------------------------------------------------ |
831 | // LinkResetForm |
832 | //------------------------------------------------------------------------ |
833 | |
834 | LinkResetForm::LinkResetForm(const Object *obj) |
835 | { |
836 | Object obj1; |
837 | |
838 | exclude = false; |
839 | |
840 | obj1 = obj->dictLookup(key: "Fields" ); |
841 | if (obj1.isArray()) { |
842 | fields.resize(new_size: obj1.arrayGetLength()); |
843 | for (int i = 0; i < obj1.arrayGetLength(); ++i) { |
844 | const Object &obj2 = obj1.arrayGetNF(i); |
845 | if (obj2.isName()) { |
846 | fields[i] = std::string(obj2.getName()); |
847 | } else if (obj2.isString()) { |
848 | fields[i] = obj2.getString()->toStr(); |
849 | } else if (obj2.isRef()) { |
850 | fields[i] = std::to_string(val: obj2.getRef().num); |
851 | fields[i].append(s: " " ); |
852 | fields[i].append(str: std::to_string(val: obj2.getRef().gen)); |
853 | fields[i].append(s: " R" ); |
854 | } else { |
855 | error(category: errSyntaxWarning, pos: -1, msg: "LinkResetForm: unexpected Field type" ); |
856 | } |
857 | } |
858 | } |
859 | |
860 | obj1 = obj->dictLookup(key: "Flags" ); |
861 | if (obj1.isInt()) { |
862 | int flags = obj1.getInt(); |
863 | |
864 | if (flags & 0x1) { |
865 | exclude = true; |
866 | } |
867 | } |
868 | } |
869 | |
870 | LinkResetForm::~LinkResetForm() = default; |
871 | |
872 | //------------------------------------------------------------------------ |
873 | // LinkUnknown |
874 | //------------------------------------------------------------------------ |
875 | |
876 | LinkUnknown::LinkUnknown(const char *actionA) |
877 | { |
878 | action = std::string(actionA ? actionA : "" ); |
879 | } |
880 | |
881 | LinkUnknown::~LinkUnknown() = default; |
882 | |
883 | //------------------------------------------------------------------------ |
884 | // Links |
885 | //------------------------------------------------------------------------ |
886 | |
887 | Links::Links(Annots *annots) |
888 | { |
889 | if (!annots) { |
890 | return; |
891 | } |
892 | |
893 | for (Annot *annot : annots->getAnnots()) { |
894 | |
895 | if (annot->getType() != Annot::typeLink) { |
896 | continue; |
897 | } |
898 | |
899 | annot->incRefCnt(); |
900 | links.push_back(x: static_cast<AnnotLink *>(annot)); |
901 | } |
902 | } |
903 | |
904 | Links::~Links() |
905 | { |
906 | for (AnnotLink *link : links) { |
907 | link->decRefCnt(); |
908 | } |
909 | } |
910 | |