Coverage Report

Created: 2026-08-11 07:29

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