1//========================================================================
2//
3// Annot.cc
4//
5// Copyright 2000-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 Scott Turner <scotty1024@mac.com>
17// Copyright (C) 2007, 2008 Julien Rebetez <julienr@svn.gnome.org>
18// Copyright (C) 2007-2013, 2015-2024 Albert Astals Cid <aacid@kde.org>
19// Copyright (C) 2007-2013, 2018 Carlos Garcia Campos <carlosgc@gnome.org>
20// Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com>
21// Copyright (C) 2007 Jeff Muizelaar <jeff@infidigm.net>
22// Copyright (C) 2008, 2011 Pino Toscano <pino@kde.org>
23// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
24// Copyright (C) 2008 Hugo Mercier <hmercier31@gmail.com>
25// Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
26// Copyright (C) 2011, 2013, 2019 José Aliste <jaliste@src.gnome.org>
27// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
28// Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
29// Copyright (C) 2012, 2015 Tobias Koenig <tokoe@kdab.com>
30// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
31// Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
32// Copyright (C) 2014, 2015 Marek Kasik <mkasik@redhat.com>
33// Copyright (C) 2014 Jiri Slaby <jirislaby@gmail.com>
34// Copyright (C) 2014 Anuj Khare <khareanuj18@gmail.com>
35// Copyright (C) 2015 Petr Gajdos <pgajdos@suse.cz>
36// Copyright (C) 2015 Philipp Reinkemeier <philipp.reinkemeier@offis.de>
37// Copyright (C) 2015 Tamas Szekeres <szekerest@gmail.com>
38// Copyright (C) 2017 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
39// 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
40// Copyright 2018 Andre Heinecke <aheinecke@intevation.de>
41// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
42// Copyright (C) 2018 Dileep Sankhla <sankhla.dileep96@gmail.com>
43// Copyright (C) 2018-2020 Tobias Deiminger <haxtibal@posteo.de>
44// Copyright (C) 2018-2020, 2022, 2024 Oliver Sander <oliver.sander@tu-dresden.de>
45// Copyright (C) 2019 Umang Malik <umang99m@gmail.com>
46// Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
47// Copyright (C) 2020, 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden
48// Copyright (C) 2020 Katarina Behrens <Katarina.Behrens@cib.de>
49// Copyright (C) 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
50// Copyright (C) 2020 Nelson Benítez León <nbenitezl@gmail.com>
51// Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>.
52// Copyright (C) 2021 Zachary Travis <ztravis@everlaw.com>
53// Copyright (C) 2021 Mahmoud Ahmed Khalil <mahmoudkhalil11@gmail.com>
54// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
55// Copyright (C) 2022 Martin <martinbts@gmx.net>
56// Copyright (C) 2022 Andreas Naumann <42870-ANaumann85@users.noreply.gitlab.freedesktop.org>
57// Copyright (C) 2022, 2024 Erich E. Hoover <erich.e.hoover@gmail.com>
58// Copyright (C) 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
59//
60// To see a description of the changes please see the Changelog file that
61// came with your tarball or type make ChangeLog if you are building from git
62//
63//========================================================================
64
65#include <config.h>
66
67#include <cstdlib>
68#include <cmath>
69#include <cassert>
70#include "goo/gmem.h"
71#include "goo/gstrtod.h"
72#include "Error.h"
73#include "Object.h"
74#include "Catalog.h"
75#include "Gfx.h"
76#include "Lexer.h"
77#include "PDFDoc.h"
78#include "Page.h"
79#include "Annot.h"
80#include "GfxFont.h"
81#include "CharCodeToUnicode.h"
82#include "PDFDocEncoding.h"
83#include "Form.h"
84#include "Error.h"
85#include "XRef.h"
86#include "Movie.h"
87#include "OptionalContent.h"
88#include "Sound.h"
89#include "FileSpec.h"
90#include "DateInfo.h"
91#include "Link.h"
92#include "UTF.h"
93#include <cstring>
94#include <algorithm>
95
96#include "annot_stamp_approved.h"
97#include "annot_stamp_as_is.h"
98#include "annot_stamp_confidential.h"
99#include "annot_stamp_departmental.h"
100#include "annot_stamp_final.h"
101#include "annot_stamp_for_comment.h"
102#include "annot_stamp_experimental.h"
103#include "annot_stamp_expired.h"
104#include "annot_stamp_not_approved.h"
105#include "annot_stamp_not_for_public_release.h"
106#include "annot_stamp_sold.h"
107#include "annot_stamp_top_secret.h"
108#include "annot_stamp_for_public_release.h"
109#include "annot_stamp_draft.h"
110
111#ifndef M_PI
112# define M_PI 3.14159265358979323846
113#endif
114
115#define fieldFlagReadOnly 0x00000001
116#define fieldFlagRequired 0x00000002
117#define fieldFlagNoExport 0x00000004
118#define fieldFlagMultiline 0x00001000
119#define fieldFlagPassword 0x00002000
120#define fieldFlagNoToggleToOff 0x00004000
121#define fieldFlagRadio 0x00008000
122#define fieldFlagPushbutton 0x00010000
123#define fieldFlagCombo 0x00020000
124#define fieldFlagEdit 0x00040000
125#define fieldFlagSort 0x00080000
126#define fieldFlagFileSelect 0x00100000
127#define fieldFlagMultiSelect 0x00200000
128#define fieldFlagDoNotSpellCheck 0x00400000
129#define fieldFlagDoNotScroll 0x00800000
130#define fieldFlagComb 0x01000000
131#define fieldFlagRichText 0x02000000
132#define fieldFlagRadiosInUnison 0x02000000
133#define fieldFlagCommitOnSelChange 0x04000000
134
135// distance of Bezier control point from center for circle approximation
136// = (4 * (sqrt(2) - 1) / 3) * r
137#define bezierCircle 0.55228475
138
139static AnnotLineEndingStyle parseAnnotLineEndingStyle(const GooString *string)
140{
141 if (string != nullptr) {
142 if (!string->cmp(sA: "Square")) {
143 return annotLineEndingSquare;
144 } else if (!string->cmp(sA: "Circle")) {
145 return annotLineEndingCircle;
146 } else if (!string->cmp(sA: "Diamond")) {
147 return annotLineEndingDiamond;
148 } else if (!string->cmp(sA: "OpenArrow")) {
149 return annotLineEndingOpenArrow;
150 } else if (!string->cmp(sA: "ClosedArrow")) {
151 return annotLineEndingClosedArrow;
152 } else if (!string->cmp(sA: "Butt")) {
153 return annotLineEndingButt;
154 } else if (!string->cmp(sA: "ROpenArrow")) {
155 return annotLineEndingROpenArrow;
156 } else if (!string->cmp(sA: "RClosedArrow")) {
157 return annotLineEndingRClosedArrow;
158 } else if (!string->cmp(sA: "Slash")) {
159 return annotLineEndingSlash;
160 } else {
161 return annotLineEndingNone;
162 }
163 } else {
164 return annotLineEndingNone;
165 }
166}
167
168static const char *convertAnnotLineEndingStyle(AnnotLineEndingStyle style)
169{
170 switch (style) {
171 case annotLineEndingSquare:
172 return "Square";
173 case annotLineEndingCircle:
174 return "Circle";
175 case annotLineEndingDiamond:
176 return "Diamond";
177 case annotLineEndingOpenArrow:
178 return "OpenArrow";
179 case annotLineEndingClosedArrow:
180 return "ClosedArrow";
181 case annotLineEndingButt:
182 return "Butt";
183 case annotLineEndingROpenArrow:
184 return "ROpenArrow";
185 case annotLineEndingRClosedArrow:
186 return "RClosedArrow";
187 case annotLineEndingSlash:
188 return "Slash";
189 default:
190 return "None";
191 }
192}
193
194static AnnotExternalDataType parseAnnotExternalData(Dict *dict)
195{
196 AnnotExternalDataType type;
197
198 Object obj1 = dict->lookup(key: "Subtype");
199 if (obj1.isName()) {
200 const char *typeName = obj1.getName();
201
202 if (!strcmp(s1: typeName, s2: "Markup3D")) {
203 type = annotExternalDataMarkup3D;
204 } else {
205 type = annotExternalDataMarkupUnknown;
206 }
207 } else {
208 type = annotExternalDataMarkupUnknown;
209 }
210
211 return type;
212}
213
214static std::unique_ptr<PDFRectangle> parseDiffRectangle(Array *array, PDFRectangle *rect)
215{
216 if (array->getLength() == 4) {
217 // deltas
218 const double dx1 = array->get(i: 0).getNumWithDefaultValue(defaultValue: 0);
219 const double dy1 = array->get(i: 1).getNumWithDefaultValue(defaultValue: 0);
220 const double dx2 = array->get(i: 2).getNumWithDefaultValue(defaultValue: 0);
221 const double dy2 = array->get(i: 3).getNumWithDefaultValue(defaultValue: 0);
222
223 // checking that the numbers are valid (i.e. >= 0),
224 // and that applying the differences still give us a valid rect
225 if (dx1 >= 0 && dy1 >= 0 && dx2 >= 0 && dy2 && (rect->x2 - rect->x1 - dx1 - dx2) >= 0 && (rect->y2 - rect->y1 - dy1 - dy2) >= 0) {
226 auto newRect = std::make_unique<PDFRectangle>();
227 newRect->x1 = rect->x1 + dx1;
228 newRect->y1 = rect->y1 + dy1;
229 newRect->x2 = rect->x2 - dx2;
230 newRect->y2 = rect->y2 - dy2;
231 return newRect;
232 }
233 }
234 return nullptr;
235}
236
237static std::unique_ptr<LinkAction> getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc)
238{
239 Object additionalActionsObject = additionalActions->fetch(xref: doc->getXRef());
240
241 if (additionalActionsObject.isDict()) {
242 const char *key = (type == Annot::actionCursorEntering ? "E"
243 : type == Annot::actionCursorLeaving ? "X"
244 : type == Annot::actionMousePressed ? "D"
245 : type == Annot::actionMouseReleased ? "U"
246 : type == Annot::actionFocusIn ? "Fo"
247 : type == Annot::actionFocusOut ? "Bl"
248 : type == Annot::actionPageOpening ? "PO"
249 : type == Annot::actionPageClosing ? "PC"
250 : type == Annot::actionPageVisible ? "PV"
251 : type == Annot::actionPageInvisible ? "PI"
252 : nullptr);
253
254 Object actionObject = additionalActionsObject.dictLookup(key);
255 if (actionObject.isDict()) {
256 return LinkAction::parseAction(obj: &actionObject, baseURI: doc->getCatalog()->getBaseURI());
257 }
258 }
259
260 return nullptr;
261}
262
263static const char *getFormAdditionalActionKey(Annot::FormAdditionalActionsType type)
264{
265 return (type == Annot::actionFieldModified ? "K" : type == Annot::actionFormatField ? "F" : type == Annot::actionValidateField ? "V" : type == Annot::actionCalculateField ? "C" : nullptr);
266}
267
268static const char *determineFallbackFont(const std::string &tok, const char *defaultFallback)
269{
270 // TODO: adjust these based on other example PDFs.
271 if (tok == "/ZaDb") {
272 return "ZapfDingbats";
273 } else if (tok == "/Cour") {
274 return "Courier";
275 } else if (tok == "/TiRo") {
276 return "TimesNewRoman";
277 } else if (tok == "/Helvetica-Bold") {
278 return "Helvetica-Bold";
279 }
280 return defaultFallback;
281}
282
283//------------------------------------------------------------------------
284// AnnotBorderEffect
285//------------------------------------------------------------------------
286
287AnnotBorderEffect::AnnotBorderEffect(Dict *dict)
288{
289 Object obj1;
290
291 obj1 = dict->lookup(key: "S");
292 if (obj1.isName()) {
293 const char *effectName = obj1.getName();
294
295 if (!strcmp(s1: effectName, s2: "C")) {
296 effectType = borderEffectCloudy;
297 } else {
298 effectType = borderEffectNoEffect;
299 }
300 } else {
301 effectType = borderEffectNoEffect;
302 }
303
304 if (effectType == borderEffectCloudy) {
305 intensity = dict->lookup(key: "I").getNumWithDefaultValue(defaultValue: 0);
306 } else {
307 intensity = 0;
308 }
309}
310
311//------------------------------------------------------------------------
312// AnnotPath
313//------------------------------------------------------------------------
314
315AnnotPath::AnnotPath() = default;
316
317AnnotPath::AnnotPath(Array *array)
318{
319 parsePathArray(array);
320}
321
322AnnotPath::AnnotPath(std::vector<AnnotCoord> &&coordsA)
323{
324 coords = std::move(coordsA);
325}
326
327AnnotPath::~AnnotPath() = default;
328
329double AnnotPath::getX(int coord) const
330{
331 if (coord >= 0 && coord < getCoordsLength()) {
332 return coords[coord].getX();
333 }
334 return 0;
335}
336
337double AnnotPath::getY(int coord) const
338{
339 if (coord >= 0 && coord < getCoordsLength()) {
340 return coords[coord].getY();
341 }
342 return 0;
343}
344
345AnnotCoord *AnnotPath::getCoord(int coord)
346{
347 if (coord >= 0 && coord < getCoordsLength()) {
348 return &coords[coord];
349 }
350 return nullptr;
351}
352
353void AnnotPath::parsePathArray(Array *array)
354{
355 if (array->getLength() % 2) {
356 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Path");
357 return;
358 }
359
360 const auto tempLength = array->getLength() / 2;
361 std::vector<AnnotCoord> tempCoords;
362 tempCoords.reserve(n: tempLength);
363 for (int i = 0; i < tempLength; i++) {
364 double x = 0, y = 0;
365
366 Object obj1 = array->get(i: i * 2);
367 if (obj1.isNum()) {
368 x = obj1.getNum();
369 } else {
370 return;
371 }
372
373 obj1 = array->get(i: (i * 2) + 1);
374 if (obj1.isNum()) {
375 y = obj1.getNum();
376 } else {
377 return;
378 }
379
380 tempCoords.emplace_back(args&: x, args&: y);
381 }
382
383 coords = std::move(tempCoords);
384}
385
386//------------------------------------------------------------------------
387// AnnotCalloutLine
388//------------------------------------------------------------------------
389
390AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2) : coord1(x1, y1), coord2(x2, y2) { }
391
392AnnotCalloutLine::~AnnotCalloutLine() = default;
393
394//------------------------------------------------------------------------
395// AnnotCalloutMultiLine
396//------------------------------------------------------------------------
397
398AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, double y2, double x3, double y3) : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) { }
399
400AnnotCalloutMultiLine::~AnnotCalloutMultiLine() = default;
401
402//------------------------------------------------------------------------
403// AnnotQuadrilateral
404//------------------------------------------------------------------------
405
406AnnotQuadrilaterals::AnnotQuadrilaterals(Array *array, PDFRectangle *rect)
407{
408 int arrayLength = array->getLength();
409 int quadsLength = 0;
410 double quadArray[8];
411
412 // default values
413 quadrilateralsLength = 0;
414
415 if ((arrayLength % 8) == 0) {
416 int i;
417
418 quadsLength = arrayLength / 8;
419 auto quads = std::make_unique<AnnotQuadrilateral[]>(num: quadsLength);
420 for (i = 0; i < quadsLength; i++) {
421 for (int j = 0; j < 8; j++) {
422 Object obj = array->get(i: i * 8 + j);
423 if (obj.isNum()) {
424 quadArray[j] = obj.getNum();
425 } else {
426 error(category: errSyntaxError, pos: -1, msg: "Invalid QuadPoint in annot");
427 return;
428 }
429 }
430
431 quads[i] = AnnotQuadrilateral(quadArray[0], quadArray[1], quadArray[2], quadArray[3], quadArray[4], quadArray[5], quadArray[6], quadArray[7]);
432 }
433
434 quadrilateralsLength = quadsLength;
435 quadrilaterals = std::move(quads);
436 }
437}
438
439AnnotQuadrilaterals::AnnotQuadrilaterals(std::unique_ptr<AnnotQuadrilateral[]> &&quads, int quadsLength)
440{
441 quadrilaterals = std::move(quads);
442 quadrilateralsLength = quadsLength;
443}
444
445AnnotQuadrilaterals::~AnnotQuadrilaterals() = default;
446
447double AnnotQuadrilaterals::getX1(int quadrilateral)
448{
449 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
450 return quadrilaterals[quadrilateral].coord1.getX();
451 }
452 return 0;
453}
454
455double AnnotQuadrilaterals::getY1(int quadrilateral)
456{
457 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
458 return quadrilaterals[quadrilateral].coord1.getY();
459 }
460 return 0;
461}
462
463double AnnotQuadrilaterals::getX2(int quadrilateral)
464{
465 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
466 return quadrilaterals[quadrilateral].coord2.getX();
467 }
468 return 0;
469}
470
471double AnnotQuadrilaterals::getY2(int quadrilateral)
472{
473 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
474 return quadrilaterals[quadrilateral].coord2.getY();
475 }
476 return 0;
477}
478
479double AnnotQuadrilaterals::getX3(int quadrilateral)
480{
481 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
482 return quadrilaterals[quadrilateral].coord3.getX();
483 }
484 return 0;
485}
486
487double AnnotQuadrilaterals::getY3(int quadrilateral)
488{
489 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
490 return quadrilaterals[quadrilateral].coord3.getY();
491 }
492 return 0;
493}
494
495double AnnotQuadrilaterals::getX4(int quadrilateral)
496{
497 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
498 return quadrilaterals[quadrilateral].coord4.getX();
499 }
500 return 0;
501}
502
503double AnnotQuadrilaterals::getY4(int quadrilateral)
504{
505 if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
506 return quadrilaterals[quadrilateral].coord4.getY();
507 }
508 return 0;
509}
510
511AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral() = default;
512
513AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) : coord1(x1, y1), coord2(x2, y2), coord3(x3, y3), coord4(x4, y4) { }
514
515//------------------------------------------------------------------------
516// AnnotBorder
517//------------------------------------------------------------------------
518AnnotBorder::AnnotBorder()
519{
520 width = 1;
521 style = borderSolid;
522}
523
524bool AnnotBorder::parseDashArray(Object *dashObj)
525{
526 bool correct = true;
527 const int tempLength = dashObj->arrayGetLength();
528 std::vector<double> tempDash(tempLength);
529
530 // TODO: check not all zero (Line Dash Pattern Page 217 PDF 8.1)
531 for (int i = 0; i < tempLength && i < DASH_LIMIT && correct; i++) {
532 const Object obj1 = dashObj->arrayGet(i);
533 if (obj1.isNum()) {
534 tempDash[i] = obj1.getNum();
535
536 correct = tempDash[i] >= 0;
537 } else {
538 correct = false;
539 }
540 }
541
542 if (correct) {
543 dash = std::move(tempDash);
544 style = borderDashed;
545 }
546
547 return correct;
548}
549
550AnnotBorder::~AnnotBorder() = default;
551
552//------------------------------------------------------------------------
553// AnnotBorderArray
554//------------------------------------------------------------------------
555
556AnnotBorderArray::AnnotBorderArray()
557{
558 horizontalCorner = 0;
559 verticalCorner = 0;
560}
561
562AnnotBorderArray::AnnotBorderArray(Array *array)
563{
564 Object obj1;
565 int arrayLength = array->getLength();
566
567 bool correct = true;
568 if (arrayLength == 3 || arrayLength == 4) {
569 // implementation note 81 in Appendix H.
570
571 obj1 = array->get(i: 0);
572 if (obj1.isNum()) {
573 horizontalCorner = obj1.getNum();
574 } else {
575 correct = false;
576 }
577
578 obj1 = array->get(i: 1);
579 if (obj1.isNum()) {
580 verticalCorner = obj1.getNum();
581 } else {
582 correct = false;
583 }
584
585 obj1 = array->get(i: 2);
586 if (obj1.isNum()) {
587 width = obj1.getNum();
588 } else {
589 correct = false;
590 }
591
592 if (arrayLength == 4) {
593 obj1 = array->get(i: 3);
594 if (obj1.isArray()) {
595 correct = parseDashArray(dashObj: &obj1);
596 } else {
597 correct = false;
598 }
599 }
600 } else {
601 correct = false;
602 }
603
604 if (!correct) {
605 width = 0;
606 }
607}
608
609std::unique_ptr<AnnotBorder> AnnotBorderArray::copy() const
610{
611 AnnotBorderArray *res = new AnnotBorderArray();
612 res->type = type;
613 res->width = width;
614 res->dash = dash;
615 res->style = style;
616 res->horizontalCorner = horizontalCorner;
617 res->verticalCorner = verticalCorner;
618 return std::unique_ptr<AnnotBorder>(res);
619}
620
621Object AnnotBorderArray::writeToObject(XRef *xref) const
622{
623 Array *borderArray = new Array(xref);
624 borderArray->add(elem: Object(horizontalCorner));
625 borderArray->add(elem: Object(verticalCorner));
626 borderArray->add(elem: Object(width));
627
628 if (dash.size() > 0) {
629 Array *a = new Array(xref);
630
631 for (double d : dash) {
632 a->add(elem: Object(d));
633 }
634
635 borderArray->add(elem: Object(a));
636 }
637
638 return Object(borderArray);
639}
640
641//------------------------------------------------------------------------
642// AnnotBorderBS
643//------------------------------------------------------------------------
644
645AnnotBorderBS::AnnotBorderBS() { }
646
647AnnotBorderBS::AnnotBorderBS(Dict *dict)
648{
649 // Border width (in points)
650 Object obj1 = dict->lookup(key: "W");
651 width = obj1.getNumWithDefaultValue(defaultValue: 1.0);
652
653 // Border style
654 obj1 = dict->lookup(key: "S");
655 if (obj1.isName()) {
656 const char *styleName = obj1.getName();
657
658 if (!strcmp(s1: styleName, s2: "S")) {
659 style = borderSolid;
660 } else if (!strcmp(s1: styleName, s2: "D")) {
661 style = borderDashed;
662 } else if (!strcmp(s1: styleName, s2: "B")) {
663 style = borderBeveled;
664 } else if (!strcmp(s1: styleName, s2: "I")) {
665 style = borderInset;
666 } else if (!strcmp(s1: styleName, s2: "U")) {
667 style = borderUnderlined;
668 } else {
669 style = borderSolid;
670 }
671 } else {
672 style = borderSolid;
673 }
674
675 // Border dash style
676 if (style == borderDashed) {
677 obj1 = dict->lookup(key: "D");
678 if (!obj1.isArray() || !parseDashArray(dashObj: &obj1)) {
679 dash = { 3 };
680 }
681 }
682}
683
684const char *AnnotBorderBS::getStyleName() const
685{
686 switch (style) {
687 case borderSolid:
688 return "S";
689 case borderDashed:
690 return "D";
691 case borderBeveled:
692 return "B";
693 case borderInset:
694 return "I";
695 case borderUnderlined:
696 return "U";
697 }
698
699 return "S";
700}
701
702std::unique_ptr<AnnotBorder> AnnotBorderBS::copy() const
703{
704 AnnotBorderBS *res = new AnnotBorderBS();
705 res->type = type;
706 res->width = width;
707 res->dash = dash;
708 res->style = style;
709 return std::unique_ptr<AnnotBorder>(res);
710}
711
712Object AnnotBorderBS::writeToObject(XRef *xref) const
713{
714 Dict *dict = new Dict(xref);
715 dict->set(key: "W", val: Object(width));
716 dict->set(key: "S", val: Object(objName, getStyleName()));
717 if (style == borderDashed && dash.size() > 0) {
718 Array *a = new Array(xref);
719
720 for (double d : dash) {
721 a->add(elem: Object(d));
722 }
723 dict->set(key: "D", val: Object(a));
724 }
725 return Object(dict);
726}
727
728//------------------------------------------------------------------------
729// AnnotColor
730//------------------------------------------------------------------------
731
732AnnotColor::AnnotColor()
733{
734 length = 0;
735}
736
737AnnotColor::AnnotColor(double gray)
738{
739 length = 1;
740
741 values[0] = gray;
742}
743
744AnnotColor::AnnotColor(double r, double g, double b)
745{
746 length = 3;
747
748 values[0] = r;
749 values[1] = g;
750 values[2] = b;
751}
752
753AnnotColor::AnnotColor(double c, double m, double y, double k)
754{
755 length = 4;
756
757 values[0] = c;
758 values[1] = m;
759 values[2] = y;
760 values[3] = k;
761}
762
763// If <adjust> is +1, color is brightened;
764// if <adjust> is -1, color is darkened;
765// otherwise color is not modified.
766AnnotColor::AnnotColor(Array *array, int adjust)
767{
768 int i;
769
770 length = array->getLength();
771 if (length > 4) {
772 length = 4;
773 }
774
775 for (i = 0; i < length; i++) {
776 Object obj1 = array->get(i);
777 if (obj1.isNum()) {
778 values[i] = obj1.getNum();
779
780 if (values[i] < 0 || values[i] > 1) {
781 values[i] = 0;
782 }
783 } else {
784 values[i] = 0;
785 }
786 }
787
788 if (adjust != 0) {
789 adjustColor(adjust);
790 }
791}
792
793void AnnotColor::adjustColor(int adjust)
794{
795 int i;
796
797 if (length == 4) {
798 adjust = -adjust;
799 }
800 if (adjust > 0) {
801 for (i = 0; i < length; ++i) {
802 values[i] = 0.5 * values[i] + 0.5;
803 }
804 } else if (adjust < 0) {
805 for (i = 0; i < length; ++i) {
806 values[i] = 0.5 * values[i];
807 }
808 }
809}
810
811Object AnnotColor::writeToObject(XRef *xref) const
812{
813 if (length == 0) {
814 return Object(objNull); // Transparent (no color)
815 } else {
816 Array *a = new Array(xref);
817 for (int i = 0; i < length; ++i) {
818 a->add(elem: Object(values[i]));
819 }
820 return Object(a);
821 }
822}
823
824//------------------------------------------------------------------------
825// DefaultAppearance
826//------------------------------------------------------------------------
827
828DefaultAppearance::DefaultAppearance(Object &&fontNameA, double fontPtSizeA, std::unique_ptr<AnnotColor> &&fontColorA) : fontName(std::move(fontNameA)), fontPtSize(fontPtSizeA), fontColor(std::move(fontColorA)) { }
829
830DefaultAppearance::DefaultAppearance(const GooString *da)
831{
832 fontPtSize = -1;
833
834 if (da) {
835 std::vector<std::string> daToks;
836 int i = FormFieldText::tokenizeDA(daString: da->toStr(), daToks: &daToks, searchTok: "Tf");
837
838 if (i >= 1) {
839 fontPtSize = gatof(nptr: daToks[i - 1].c_str());
840 }
841 if (i >= 2) {
842 // We are expecting a name, therefore the first letter should be '/'.
843 const std::string &fontToken = daToks[i - 2];
844 if (fontToken.size() > 1 && fontToken[0] == '/') {
845 // The +1 is here to skip the leading '/'.
846 fontName = Object(objName, fontToken.c_str() + 1);
847 }
848 }
849 // Scan backwards: we are looking for the last set value
850 for (i = daToks.size() - 1; i >= 0; --i) {
851 if (!fontColor) {
852 if (daToks[i] == "g" && i >= 1) {
853 fontColor = std::make_unique<AnnotColor>(args: gatof(nptr: daToks[i - 1].c_str()));
854 } else if (daToks[i] == "rg" && i >= 3) {
855 fontColor = std::make_unique<AnnotColor>(args: gatof(nptr: daToks[i - 3].c_str()), args: gatof(nptr: daToks[i - 2].c_str()), args: gatof(nptr: daToks[i - 1].c_str()));
856 } else if (daToks[i] == "k" && i >= 4) {
857 fontColor = std::make_unique<AnnotColor>(args: gatof(nptr: daToks[i - 4].c_str()), args: gatof(nptr: daToks[i - 3].c_str()), args: gatof(nptr: daToks[i - 2].c_str()), args: gatof(nptr: daToks[i - 1].c_str()));
858 }
859 }
860 }
861 }
862}
863
864void DefaultAppearance::setFontName(Object &&fontNameA)
865{
866 fontName = std::move(fontNameA);
867}
868
869void DefaultAppearance::setFontPtSize(double fontPtSizeA)
870{
871 fontPtSize = fontPtSizeA;
872}
873
874void DefaultAppearance::setFontColor(std::unique_ptr<AnnotColor> fontColorA)
875{
876 fontColor = std::move(fontColorA);
877}
878
879std::string DefaultAppearance::toAppearanceString() const
880{
881 AnnotAppearanceBuilder appearBuilder;
882 if (fontColor) {
883 appearBuilder.setDrawColor(color: fontColor.get(), fill: true);
884 }
885 appearBuilder.setTextFont(fontName, fontSize: fontPtSize);
886 return appearBuilder.buffer()->toStr();
887}
888
889//------------------------------------------------------------------------
890// AnnotIconFit
891//------------------------------------------------------------------------
892
893AnnotIconFit::AnnotIconFit(Dict *dict)
894{
895 Object obj1;
896
897 obj1 = dict->lookup(key: "SW");
898 if (obj1.isName()) {
899 const char *scaleName = obj1.getName();
900
901 if (!strcmp(s1: scaleName, s2: "B")) {
902 scaleWhen = scaleBigger;
903 } else if (!strcmp(s1: scaleName, s2: "S")) {
904 scaleWhen = scaleSmaller;
905 } else if (!strcmp(s1: scaleName, s2: "N")) {
906 scaleWhen = scaleNever;
907 } else {
908 scaleWhen = scaleAlways;
909 }
910 } else {
911 scaleWhen = scaleAlways;
912 }
913
914 obj1 = dict->lookup(key: "S");
915 if (obj1.isName()) {
916 const char *scaleName = obj1.getName();
917
918 if (!strcmp(s1: scaleName, s2: "A")) {
919 scale = scaleAnamorphic;
920 } else {
921 scale = scaleProportional;
922 }
923 } else {
924 scale = scaleProportional;
925 }
926
927 obj1 = dict->lookup(key: "A");
928 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
929 left = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
930 bottom = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
931
932 if (left < 0 || left > 1) {
933 left = 0.5;
934 }
935
936 if (bottom < 0 || bottom > 1) {
937 bottom = 0.5;
938 }
939
940 } else {
941 left = bottom = 0.5;
942 }
943
944 fullyBounds = dict->lookup(key: "FB").getBoolWithDefaultValue(defaultValue: false);
945}
946
947//------------------------------------------------------------------------
948// AnnotAppearance
949//------------------------------------------------------------------------
950
951AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict)
952{
953 assert(dict->isDict());
954 doc = docA;
955 appearDict = dict->copy();
956}
957
958AnnotAppearance::~AnnotAppearance() { }
959
960Object AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state)
961{
962 Object apData;
963
964 // Obtain dictionary or stream associated to appearance type
965 switch (type) {
966 case appearRollover:
967 apData = appearDict.dictLookupNF(key: "R").copy();
968 if (apData.isNull()) {
969 apData = appearDict.dictLookupNF(key: "N").copy();
970 }
971 break;
972 case appearDown:
973 apData = appearDict.dictLookupNF(key: "D").copy();
974 if (apData.isNull()) {
975 apData = appearDict.dictLookupNF(key: "N").copy();
976 }
977 break;
978 case appearNormal:
979 apData = appearDict.dictLookupNF(key: "N").copy();
980 break;
981 }
982
983 if (apData.isDict() && state) {
984 return apData.dictLookupNF(key: state).copy();
985 } else if (apData.isRef()) {
986 return apData;
987 }
988
989 return Object();
990}
991
992std::unique_ptr<GooString> AnnotAppearance::getStateKey(int i)
993{
994 const Object &obj1 = appearDict.dictLookupNF(key: "N");
995 if (obj1.isDict()) {
996 return std::make_unique<GooString>(args: obj1.dictGetKey(i));
997 }
998 return nullptr;
999}
1000
1001int AnnotAppearance::getNumStates()
1002{
1003 int res = 0;
1004 const Object &obj1 = appearDict.dictLookupNF(key: "N");
1005 if (obj1.isDict()) {
1006 res = obj1.dictGetLength();
1007 }
1008 return res;
1009}
1010
1011// Test if stateObj (a Ref or a Dict) points to the specified stream
1012bool AnnotAppearance::referencesStream(const Object *stateObj, Ref refToStream)
1013{
1014 if (stateObj->isRef()) {
1015 const Ref r = stateObj->getRef();
1016 if (r == refToStream) {
1017 return true;
1018 }
1019 } else if (stateObj->isDict()) { // Test each value
1020 const int size = stateObj->dictGetLength();
1021 for (int i = 0; i < size; ++i) {
1022 const Object &obj1 = stateObj->dictGetValNF(i);
1023 if (obj1.isRef()) {
1024 const Ref r = obj1.getRef();
1025 if (r == refToStream) {
1026 return true;
1027 }
1028 }
1029 }
1030 }
1031 return false; // Not found
1032}
1033
1034// Test if this AnnotAppearance references the specified stream
1035bool AnnotAppearance::referencesStream(Ref refToStream)
1036{
1037 bool found;
1038
1039 // Scan each state's ref/subdictionary
1040 const Object &objN = appearDict.dictLookupNF(key: "N");
1041 found = referencesStream(stateObj: &objN, refToStream);
1042 if (found) {
1043 return true;
1044 }
1045
1046 const Object &objR = appearDict.dictLookupNF(key: "R");
1047 found = referencesStream(stateObj: &objR, refToStream);
1048 if (found) {
1049 return true;
1050 }
1051
1052 const Object &objD = appearDict.dictLookupNF(key: "D");
1053 found = referencesStream(stateObj: &objD, refToStream);
1054 return found;
1055}
1056
1057// If this is the only annotation in the document that references the
1058// specified appearance stream, remove the appearance stream
1059void AnnotAppearance::removeStream(Ref refToStream)
1060{
1061 const int lastpage = doc->getNumPages();
1062 for (int pg = 1; pg <= lastpage; ++pg) { // Scan all annotations in the document
1063 Page *page = doc->getPage(page: pg);
1064 if (!page) {
1065 error(category: errSyntaxError, pos: -1, msg: "Failed check for shared annotation stream at page {0:d}", pg);
1066 continue;
1067 }
1068 Annots *annots = page->getAnnots();
1069 for (Annot *annot : annots->getAnnots()) {
1070 AnnotAppearance *annotAp = annot->getAppearStreams();
1071 if (annotAp && annotAp != this && annotAp->referencesStream(refToStream)) {
1072 return; // Another annotation points to the stream -> Don't delete it
1073 }
1074 }
1075 }
1076
1077 // TODO: stream resources (e.g. font), AP name tree
1078 doc->getXRef()->removeIndirectObject(r: refToStream);
1079}
1080
1081// Removes stream if obj is a Ref, or removes pointed streams if obj is a Dict
1082void AnnotAppearance::removeStateStreams(const Object *state)
1083{
1084 if (state->isRef()) {
1085 removeStream(refToStream: state->getRef());
1086 } else if (state->isDict()) {
1087 const int size = state->dictGetLength();
1088 for (int i = 0; i < size; ++i) {
1089 const Object &obj2 = state->dictGetValNF(i);
1090 if (obj2.isRef()) {
1091 removeStream(refToStream: obj2.getRef());
1092 }
1093 }
1094 }
1095}
1096
1097void AnnotAppearance::removeAllStreams()
1098{
1099 const Object &objN = appearDict.dictLookupNF(key: "N");
1100 removeStateStreams(state: &objN);
1101 const Object &objR = appearDict.dictLookupNF(key: "R");
1102 removeStateStreams(state: &objR);
1103 const Object &objD = appearDict.dictLookupNF(key: "D");
1104 removeStateStreams(state: &objD);
1105}
1106
1107//------------------------------------------------------------------------
1108// AnnotAppearanceCharacs
1109//------------------------------------------------------------------------
1110
1111AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict)
1112{
1113 Object obj1;
1114
1115 if (!dict) {
1116 rotation = 0;
1117 position = captionNoIcon;
1118 return;
1119 }
1120
1121 obj1 = dict->lookup(key: "R");
1122 if (obj1.isInt()) {
1123 rotation = obj1.getInt();
1124 } else {
1125 rotation = 0;
1126 }
1127
1128 obj1 = dict->lookup(key: "BC");
1129 if (obj1.isArray()) {
1130 Array *colorComponents = obj1.getArray();
1131 if (colorComponents->getLength() > 0) {
1132 borderColor = std::make_unique<AnnotColor>(args&: colorComponents);
1133 }
1134 }
1135
1136 obj1 = dict->lookup(key: "BG");
1137 if (obj1.isArray()) {
1138 Array *colorComponents = obj1.getArray();
1139 if (colorComponents->getLength() > 0) {
1140 backColor = std::make_unique<AnnotColor>(args&: colorComponents);
1141 }
1142 }
1143
1144 obj1 = dict->lookup(key: "CA");
1145 if (obj1.isString()) {
1146 normalCaption = std::make_unique<GooString>(args: obj1.getString());
1147 }
1148
1149 obj1 = dict->lookup(key: "RC");
1150 if (obj1.isString()) {
1151 rolloverCaption = std::make_unique<GooString>(args: obj1.getString());
1152 }
1153
1154 obj1 = dict->lookup(key: "AC");
1155 if (obj1.isString()) {
1156 alternateCaption = std::make_unique<GooString>(args: obj1.getString());
1157 }
1158
1159 obj1 = dict->lookup(key: "IF");
1160 if (obj1.isDict()) {
1161 iconFit = std::make_unique<AnnotIconFit>(args: obj1.getDict());
1162 }
1163
1164 obj1 = dict->lookup(key: "TP");
1165 if (obj1.isInt()) {
1166 position = (AnnotAppearanceCharacsTextPos)obj1.getInt();
1167 } else {
1168 position = captionNoIcon;
1169 }
1170}
1171
1172AnnotAppearanceCharacs::~AnnotAppearanceCharacs() = default;
1173
1174std::unique_ptr<AnnotAppearanceCharacs> AnnotAppearanceCharacs::copy() const
1175{
1176 AnnotAppearanceCharacs *res = new AnnotAppearanceCharacs(nullptr);
1177 res->rotation = rotation;
1178 if (borderColor) {
1179 res->borderColor = std::make_unique<AnnotColor>(args&: *borderColor);
1180 }
1181 if (backColor) {
1182 res->backColor = std::make_unique<AnnotColor>(args&: *backColor);
1183 }
1184 if (normalCaption) {
1185 res->normalCaption = std::unique_ptr<GooString>(normalCaption->copy());
1186 }
1187 if (rolloverCaption) {
1188 res->rolloverCaption = std::unique_ptr<GooString>(rolloverCaption->copy());
1189 }
1190 if (alternateCaption) {
1191 res->alternateCaption = std::unique_ptr<GooString>(alternateCaption->copy());
1192 }
1193 if (iconFit) {
1194 res->iconFit = std::make_unique<AnnotIconFit>(args&: *iconFit);
1195 }
1196 res->position = position;
1197 return std::unique_ptr<AnnotAppearanceCharacs>(res);
1198}
1199
1200//------------------------------------------------------------------------
1201// AnnotAppearanceBBox
1202//------------------------------------------------------------------------
1203
1204AnnotAppearanceBBox::AnnotAppearanceBBox(PDFRectangle *rect)
1205{
1206 origX = rect->x1;
1207 origY = rect->y1;
1208 borderWidth = 0;
1209
1210 // Initially set the same size as rect
1211 minX = 0;
1212 minY = 0;
1213 maxX = rect->x2 - rect->x1;
1214 maxY = rect->y2 - rect->y1;
1215}
1216
1217void AnnotAppearanceBBox::extendTo(double x, double y)
1218{
1219 if (x < minX) {
1220 minX = x;
1221 } else if (x > maxX) {
1222 maxX = x;
1223 }
1224 if (y < minY) {
1225 minY = y;
1226 } else if (y > maxY) {
1227 maxY = y;
1228 }
1229}
1230
1231void AnnotAppearanceBBox::getBBoxRect(double bbox[4]) const
1232{
1233 bbox[0] = minX - borderWidth;
1234 bbox[1] = minY - borderWidth;
1235 bbox[2] = maxX + borderWidth;
1236 bbox[3] = maxY + borderWidth;
1237}
1238
1239double AnnotAppearanceBBox::getPageXMin() const
1240{
1241 return origX + minX - borderWidth;
1242}
1243
1244double AnnotAppearanceBBox::getPageYMin() const
1245{
1246 return origY + minY - borderWidth;
1247}
1248
1249double AnnotAppearanceBBox::getPageXMax() const
1250{
1251 return origX + maxX + borderWidth;
1252}
1253
1254double AnnotAppearanceBBox::getPageYMax() const
1255{
1256 return origY + maxY + borderWidth;
1257}
1258
1259//------------------------------------------------------------------------
1260// Annot
1261//------------------------------------------------------------------------
1262
1263#define annotLocker() const std::scoped_lock locker(mutex)
1264
1265Annot::Annot(PDFDoc *docA, PDFRectangle *rectA)
1266{
1267
1268 refCnt = 1;
1269 flags = flagUnknown;
1270 type = typeUnknown;
1271
1272 Array *a = new Array(docA->getXRef());
1273 a->add(elem: Object(rectA->x1));
1274 a->add(elem: Object(rectA->y1));
1275 a->add(elem: Object(rectA->x2));
1276 a->add(elem: Object(rectA->y2));
1277
1278 annotObj = Object(new Dict(docA->getXRef()));
1279 annotObj.dictSet(key: "Type", val: Object(objName, "Annot"));
1280 annotObj.dictSet(key: "Rect", val: Object(a));
1281
1282 ref = docA->getXRef()->addIndirectObject(o: annotObj);
1283
1284 initialize(docA, dict: annotObj.getDict());
1285}
1286
1287Annot::Annot(PDFDoc *docA, Object &&dictObject)
1288{
1289 refCnt = 1;
1290 hasRef = false;
1291 flags = flagUnknown;
1292 type = typeUnknown;
1293 annotObj = std::move(dictObject);
1294 initialize(docA, dict: annotObj.getDict());
1295}
1296
1297Annot::Annot(PDFDoc *docA, Object &&dictObject, const Object *obj)
1298{
1299 refCnt = 1;
1300 if (obj->isRef()) {
1301 hasRef = true;
1302 ref = obj->getRef();
1303 } else {
1304 hasRef = false;
1305 }
1306 flags = flagUnknown;
1307 type = typeUnknown;
1308 annotObj = std::move(dictObject);
1309 initialize(docA, dict: annotObj.getDict());
1310}
1311
1312void Annot::initialize(PDFDoc *docA, Dict *dict)
1313{
1314 Object apObj, asObj, obj1;
1315
1316 ok = true;
1317 doc = docA;
1318
1319 appearance.setToNull();
1320
1321 //----- parse the rectangle
1322 rect = std::make_unique<PDFRectangle>();
1323 obj1 = dict->lookup(key: "Rect");
1324 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
1325 rect->x1 = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
1326 rect->y1 = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
1327 rect->x2 = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
1328 rect->y2 = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 1);
1329
1330 if (rect->x1 > rect->x2) {
1331 double t = rect->x1;
1332 rect->x1 = rect->x2;
1333 rect->x2 = t;
1334 }
1335
1336 if (rect->y1 > rect->y2) {
1337 double t = rect->y1;
1338 rect->y1 = rect->y2;
1339 rect->y2 = t;
1340 }
1341 } else {
1342 rect->x1 = rect->y1 = 0;
1343 rect->x2 = rect->y2 = 1;
1344 error(category: errSyntaxError, pos: -1, msg: "Bad bounding box for annotation");
1345 ok = false;
1346 }
1347
1348 obj1 = dict->lookup(key: "Contents");
1349 if (obj1.isString()) {
1350 contents.reset(p: obj1.getString()->copy());
1351 } else {
1352 contents = std::make_unique<GooString>();
1353 }
1354
1355 // Note: This value is overwritten by Annots ctor
1356 const Object &pObj = dict->lookupNF(key: "P");
1357 if (pObj.isRef()) {
1358 const Ref pRef = pObj.getRef();
1359
1360 page = doc->getCatalog()->findPage(pageRef: pRef);
1361 } else {
1362 page = 0;
1363 }
1364
1365 obj1 = dict->lookup(key: "NM");
1366 if (obj1.isString()) {
1367 name.reset(p: obj1.getString()->copy());
1368 }
1369
1370 obj1 = dict->lookup(key: "M");
1371 if (obj1.isString()) {
1372 modified.reset(p: obj1.getString()->copy());
1373 }
1374
1375 //----- get the flags
1376 obj1 = dict->lookup(key: "F");
1377 if (obj1.isInt()) {
1378 flags |= obj1.getInt();
1379 } else {
1380 flags = flagUnknown;
1381 }
1382
1383 //----- get the annotation appearance dictionary
1384 apObj = dict->lookup(key: "AP");
1385 if (apObj.isDict()) {
1386 appearStreams = std::make_unique<AnnotAppearance>(args&: doc, args: &apObj);
1387 }
1388
1389 //----- get the appearance state
1390 asObj = dict->lookup(key: "AS");
1391 if (asObj.isName()) {
1392 appearState = std::make_unique<GooString>(args: asObj.getName());
1393 } else if (appearStreams && appearStreams->getNumStates() != 0) {
1394 error(category: errSyntaxError, pos: -1, msg: "Invalid or missing AS value in annotation containing one or more appearance subdictionaries");
1395 // AS value is required in this case, but if the
1396 // N dictionary contains only one entry
1397 // take it as default appearance.
1398 if (appearStreams->getNumStates() == 1) {
1399 appearState = appearStreams->getStateKey(i: 0);
1400 }
1401 }
1402 if (!appearState) {
1403 appearState = std::make_unique<GooString>(args: "Off");
1404 }
1405
1406 //----- get the annotation appearance
1407 if (appearStreams) {
1408 appearance = appearStreams->getAppearanceStream(type: AnnotAppearance::appearNormal, state: appearState->c_str());
1409 }
1410
1411 //----- parse the border style
1412 // According to the spec if neither the Border nor the BS entry is present,
1413 // the border shall be drawn as a solid line with a width of 1 point. But acroread
1414 // seems to ignore the Border entry for annots that can't have a BS entry. So, we only
1415 // follow this rule for annots tha can have a BS entry.
1416 obj1 = dict->lookup(key: "Border");
1417 if (obj1.isArray()) {
1418 border = std::make_unique<AnnotBorderArray>(args: obj1.getArray());
1419 }
1420
1421 obj1 = dict->lookup(key: "C");
1422 if (obj1.isArray()) {
1423 color = std::make_unique<AnnotColor>(args: obj1.getArray());
1424 }
1425
1426 obj1 = dict->lookup(key: "StructParent");
1427 if (obj1.isInt()) {
1428 treeKey = obj1.getInt();
1429 } else {
1430 treeKey = 0;
1431 }
1432
1433 oc = dict->lookupNF(key: "OC").copy();
1434}
1435
1436void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const
1437{
1438 *x1 = rect->x1;
1439 *y1 = rect->y1;
1440 *x2 = rect->x2;
1441 *y2 = rect->y2;
1442}
1443
1444void Annot::setRect(const PDFRectangle *rectA)
1445{
1446 setRect(x1: rectA->x1, y1: rectA->y1, x2: rectA->x2, y2: rectA->y2);
1447}
1448
1449void Annot::setRect(double x1, double y1, double x2, double y2)
1450{
1451 if (x1 < x2) {
1452 rect->x1 = x1;
1453 rect->x2 = x2;
1454 } else {
1455 rect->x1 = x2;
1456 rect->x2 = x1;
1457 }
1458
1459 if (y1 < y2) {
1460 rect->y1 = y1;
1461 rect->y2 = y2;
1462 } else {
1463 rect->y1 = y2;
1464 rect->y2 = y1;
1465 }
1466
1467 Array *a = new Array(doc->getXRef());
1468 a->add(elem: Object(rect->x1));
1469 a->add(elem: Object(rect->y1));
1470 a->add(elem: Object(rect->x2));
1471 a->add(elem: Object(rect->y2));
1472
1473 update(key: "Rect", value: Object(a));
1474 invalidateAppearance();
1475}
1476
1477bool Annot::inRect(double x, double y) const
1478{
1479 return rect->contains(x, y);
1480}
1481
1482void Annot::update(const char *key, Object &&value)
1483{
1484 annotLocker();
1485 /* Set M to current time, unless we are updating M itself */
1486 if (strcmp(s1: key, s2: "M") != 0) {
1487 modified.reset(p: timeToDateString(timeA: nullptr));
1488
1489 annotObj.dictSet(key: "M", val: Object(modified->copy()));
1490 }
1491
1492 annotObj.dictSet(key: const_cast<char *>(key), val: std::move(value));
1493
1494 doc->getXRef()->setModifiedObject(o: &annotObj, r: ref);
1495
1496 hasBeenUpdated = true;
1497}
1498
1499void Annot::setContents(std::unique_ptr<GooString> &&new_content)
1500{
1501 annotLocker();
1502
1503 if (new_content) {
1504 contents = std::move(new_content);
1505 // append the unicode marker <FE FF> if needed
1506 if (!hasUnicodeByteOrderMark(s: contents->toStr())) {
1507 prependUnicodeByteOrderMark(s&: contents->toNonConstStr());
1508 }
1509 } else {
1510 contents = std::make_unique<GooString>();
1511 }
1512
1513 update(key: "Contents", value: Object(contents->copy()));
1514}
1515
1516void Annot::setName(GooString *new_name)
1517{
1518 annotLocker();
1519
1520 if (new_name) {
1521 name = std::make_unique<GooString>(args&: new_name);
1522 } else {
1523 name = std::make_unique<GooString>();
1524 }
1525
1526 update(key: "NM", value: Object(name->copy()));
1527}
1528
1529void Annot::setModified(GooString *new_modified)
1530{
1531 annotLocker();
1532
1533 if (new_modified) {
1534 modified = std::make_unique<GooString>(args&: new_modified);
1535 update(key: "M", value: Object(modified->copy()));
1536 } else {
1537 modified.reset(p: nullptr);
1538 update(key: "M", value: Object(objNull));
1539 }
1540}
1541
1542void Annot::setFlags(unsigned int new_flags)
1543{
1544 annotLocker();
1545 flags = new_flags;
1546 update(key: "F", value: Object(int(flags)));
1547}
1548
1549void Annot::setBorder(std::unique_ptr<AnnotBorder> &&new_border)
1550{
1551 annotLocker();
1552
1553 if (new_border) {
1554 Object obj1 = new_border->writeToObject(xref: doc->getXRef());
1555 update(key: new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", value: std::move(obj1));
1556 border = std::move(new_border);
1557 } else {
1558 border = nullptr;
1559 }
1560 invalidateAppearance();
1561}
1562
1563void Annot::setColor(std::unique_ptr<AnnotColor> &&new_color)
1564{
1565 annotLocker();
1566
1567 if (new_color) {
1568 Object obj1 = new_color->writeToObject(xref: doc->getXRef());
1569 update(key: "C", value: std::move(obj1));
1570 color = std::move(new_color);
1571 } else {
1572 color = nullptr;
1573 }
1574 invalidateAppearance();
1575}
1576
1577void Annot::setPage(int pageIndex, bool updateP)
1578{
1579 annotLocker();
1580 Page *pageobj = doc->getPage(page: pageIndex);
1581 Object obj1(objNull);
1582
1583 if (pageobj) {
1584 const Ref pageRef = pageobj->getRef();
1585 obj1 = Object(pageRef);
1586 page = pageIndex;
1587 } else {
1588 page = 0;
1589 }
1590
1591 if (updateP) {
1592 update(key: "P", value: std::move(obj1));
1593 }
1594}
1595
1596void Annot::setAppearanceState(const char *state)
1597{
1598 annotLocker();
1599 if (!state) {
1600 return;
1601 }
1602
1603 appearState = std::make_unique<GooString>(args&: state);
1604 appearBBox = nullptr;
1605
1606 update(key: "AS", value: Object(objName, state));
1607
1608 // The appearance state determines the current appearance stream
1609 if (appearStreams) {
1610 appearance = appearStreams->getAppearanceStream(type: AnnotAppearance::appearNormal, state: appearState->c_str());
1611 } else {
1612 appearance.setToNull();
1613 }
1614}
1615
1616void Annot::invalidateAppearance()
1617{
1618 annotLocker();
1619 if (appearStreams) { // Remove existing appearance streams
1620 appearStreams->removeAllStreams();
1621 }
1622 appearStreams = nullptr;
1623 appearState = nullptr;
1624 appearBBox = nullptr;
1625 appearance.setToNull();
1626
1627 Object obj2 = annotObj.dictLookup(key: "AP");
1628 if (!obj2.isNull()) {
1629 update(key: "AP", value: Object(objNull)); // Remove AP
1630 }
1631
1632 obj2 = annotObj.dictLookup(key: "AS");
1633 if (!obj2.isNull()) {
1634 update(key: "AS", value: Object(objNull)); // Remove AS
1635 }
1636}
1637
1638double Annot::getXMin()
1639{
1640 return rect->x1;
1641}
1642
1643double Annot::getYMin()
1644{
1645 return rect->y1;
1646}
1647
1648double Annot::getXMax()
1649{
1650 return rect->x2;
1651}
1652
1653double Annot::getYMax()
1654{
1655 return rect->y2;
1656}
1657
1658void Annot::readArrayNum(Object *pdfArray, int key, double *value)
1659{
1660 Object valueObject = pdfArray->arrayGet(i: key);
1661 if (valueObject.isNum()) {
1662 *value = valueObject.getNum();
1663 } else {
1664 *value = 0;
1665 ok = false;
1666 }
1667}
1668
1669void Annot::removeReferencedObjects()
1670{
1671 // Remove appearance streams (if any)
1672 invalidateAppearance();
1673}
1674
1675void Annot::incRefCnt()
1676{
1677 refCnt++;
1678}
1679
1680void Annot::decRefCnt()
1681{
1682 if (--refCnt == 0) {
1683 delete this;
1684 }
1685}
1686
1687Annot::~Annot() { }
1688
1689void AnnotAppearanceBuilder::setDrawColor(const AnnotColor *drawColor, bool fill)
1690{
1691 const double *values = drawColor->getValues();
1692
1693 switch (drawColor->getSpace()) {
1694 case AnnotColor::colorCMYK:
1695 appearBuf->appendf(fmt: "{0:.5f} {1:.5f} {2:.5f} {3:.5f} {4:c}\n", values[0], values[1], values[2], values[3], fill ? 'k' : 'K');
1696 break;
1697 case AnnotColor::colorRGB:
1698 appearBuf->appendf(fmt: "{0:.5f} {1:.5f} {2:.5f} {3:s}\n", values[0], values[1], values[2], fill ? "rg" : "RG");
1699 break;
1700 case AnnotColor::colorGray:
1701 appearBuf->appendf(fmt: "{0:.5f} {1:c}\n", values[0], fill ? 'g' : 'G');
1702 break;
1703 case AnnotColor::colorTransparent:
1704 default:
1705 break;
1706 }
1707}
1708
1709void AnnotAppearanceBuilder::setTextFont(const Object &fontName, double fontSize)
1710{
1711 if (fontName.isName() && strlen(s: fontName.getName()) > 0) {
1712 appearBuf->appendf(fmt: "/{0:s} {1:.2f} Tf\n", fontName.getName(), fontSize);
1713 }
1714}
1715
1716void AnnotAppearanceBuilder::setLineStyleForBorder(const AnnotBorder *border)
1717{
1718 switch (border->getStyle()) {
1719 case AnnotBorder::borderDashed:
1720 appearBuf->append(str: "[");
1721 for (double dash : border->getDash()) {
1722 appearBuf->appendf(fmt: " {0:.2f}", dash);
1723 }
1724 appearBuf->append(str: " ] 0 d\n");
1725 break;
1726 default:
1727 appearBuf->append(str: "[] 0 d\n");
1728 break;
1729 }
1730 appearBuf->appendf(fmt: "{0:.2f} w\n", border->getWidth());
1731}
1732
1733// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
1734// If <fill> is true, the circle is filled; otherwise it is stroked.
1735void AnnotAppearanceBuilder::drawCircle(double cx, double cy, double r, bool fill)
1736{
1737 if (fill) {
1738 drawEllipse(cx, cy, rx: r, ry: r, fill: true, stroke: false);
1739 } else {
1740 drawEllipse(cx, cy, rx: r, ry: r, fill: false, stroke: true);
1741 }
1742}
1743
1744// Draw an (approximate) ellipse of radius <rx> on x-axis and <ry> on y-axis, centered at (<cx>, <cy>).
1745// If <fill> is true, the ellipse is filled with current color for non-stroking operations.
1746// If <stroke> is true, the ellipse path ist stroked with current color and color space for stroking operations.
1747// Path will be closed if either fill or stroke is true; otherwise it's left open.
1748void AnnotAppearanceBuilder::drawEllipse(double cx, double cy, double rx, double ry, bool fill, bool stroke)
1749{
1750 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} m\n", cx + rx, cy);
1751 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + rx, cy + bezierCircle * ry, cx + bezierCircle * rx, cy + ry, cx, cy + ry);
1752 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - bezierCircle * rx, cy + ry, cx - rx, cy + bezierCircle * ry, cx - rx, cy);
1753 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - rx, cy - bezierCircle * ry, cx - bezierCircle * rx, cy - ry, cx, cy - ry);
1754 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + bezierCircle * rx, cy - ry, cx + rx, cy - bezierCircle * ry, cx + rx, cy);
1755 if (!fill && stroke) {
1756 appearBuf->append(str: "s\n");
1757 } else if (fill && !stroke) {
1758 appearBuf->append(str: "f\n");
1759 } else if (fill && stroke) {
1760 appearBuf->append(str: "b\n");
1761 }
1762}
1763
1764// Draw the top-left half of an (approximate) circle of radius <r>
1765// centered at (<cx>, <cy>).
1766void AnnotAppearanceBuilder::drawCircleTopLeft(double cx, double cy, double r)
1767{
1768 double r2;
1769
1770 r2 = r / sqrt(x: 2.0);
1771 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} m\n", cx + r2, cy + r2);
1772 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - r2, cy + r2);
1773 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - (1 + bezierCircle) * r2, cy + (1 - bezierCircle) * r2, cx - (1 + bezierCircle) * r2, cy - (1 - bezierCircle) * r2, cx - r2, cy - r2);
1774 appearBuf->append(str: "S\n");
1775}
1776
1777// Draw the bottom-right half of an (approximate) circle of radius <r>
1778// centered at (<cx>, <cy>).
1779void AnnotAppearanceBuilder::drawCircleBottomRight(double cx, double cy, double r)
1780{
1781 double r2;
1782
1783 r2 = r / sqrt(x: 2.0);
1784 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} m\n", cx - r2, cy - r2);
1785 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + r2, cy - r2);
1786 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + (1 + bezierCircle) * r2, cy - (1 - bezierCircle) * r2, cx + (1 + bezierCircle) * r2, cy + (1 - bezierCircle) * r2, cx + r2, cy + r2);
1787 appearBuf->append(str: "S\n");
1788}
1789
1790void AnnotAppearanceBuilder::drawLineEndSquare(double x, double y, double size, bool fill, const Matrix &m)
1791{
1792 const double halfSize { size / 2. };
1793 const double x1[3] { x - size, x - size, x };
1794 const double y1[3] { y + halfSize, y - halfSize, y - halfSize };
1795 double tx, ty;
1796
1797 m.transform(x, y: y + halfSize, tx: &tx, ty: &ty);
1798 appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
1799 for (int i = 0; i < 3; i++) {
1800 m.transform(x: x1[i], y: y1[i], tx: &tx, ty: &ty);
1801 appendf(fmt: "{0:.2f} {1:.2f} l\n", tx, ty);
1802 }
1803 appearBuf->append(str: fill ? "b\n" : "s\n");
1804}
1805
1806void AnnotAppearanceBuilder::drawLineEndCircle(double x, double y, double size, bool fill, const Matrix &m)
1807{
1808 const double halfSize { size / 2. };
1809 const double x1[4] { x, x - halfSize - bezierCircle * halfSize, x - size, x - halfSize + bezierCircle * halfSize };
1810 const double x2[4] { x - halfSize + bezierCircle * halfSize, x - size, x - halfSize - bezierCircle * halfSize, x };
1811 const double x3[4] { x - halfSize, x - size, x - halfSize, x };
1812 const double y1[4] { y + bezierCircle * halfSize, y + halfSize, y - bezierCircle * halfSize, y - halfSize };
1813 const double y2[4] { y + halfSize, y + bezierCircle * halfSize, y - halfSize, y - bezierCircle * halfSize };
1814 const double y3[4] { y + halfSize, y, y - halfSize, y };
1815 double tx[3];
1816 double ty[3];
1817
1818 m.transform(x, y, tx: &tx[0], ty: &ty[0]);
1819 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} m\n", tx[0], ty[0]);
1820 for (int i = 0; i < 4; i++) {
1821 m.transform(x: x1[i], y: y1[i], tx: &tx[0], ty: &ty[0]);
1822 m.transform(x: x2[i], y: y2[i], tx: &tx[1], ty: &ty[1]);
1823 m.transform(x: x3[i], y: y3[i], tx: &tx[2], ty: &ty[2]);
1824 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", tx[0], ty[0], tx[1], ty[1], tx[2], ty[2]);
1825 }
1826 appearBuf->append(str: fill ? "b\n" : "s\n");
1827}
1828
1829void AnnotAppearanceBuilder::drawLineEndDiamond(double x, double y, double size, bool fill, const Matrix &m)
1830{
1831 const double halfSize { size / 2. };
1832 const double x1[3] { x - halfSize, x - size, x - halfSize };
1833 const double y1[3] { y + halfSize, y, y - halfSize };
1834 double tx, ty;
1835
1836 m.transform(x, y, tx: &tx, ty: &ty);
1837 appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
1838 for (int i = 0; i < 3; i++) {
1839 m.transform(x: x1[i], y: y1[i], tx: &tx, ty: &ty);
1840 appendf(fmt: "{0:.2f} {1:.2f} l\n", tx, ty);
1841 }
1842 appearBuf->append(str: fill ? "b\n" : "s\n");
1843}
1844
1845void AnnotAppearanceBuilder::drawLineEndArrow(double x, double y, double size, int orientation, bool isOpen, bool fill, const Matrix &m)
1846{
1847 const double alpha { M_PI / 6. };
1848 const double xOffs { orientation * size };
1849 const double yOffs { tan(x: alpha) * size };
1850 double tx, ty;
1851
1852 m.transform(x: x - xOffs, y: y + yOffs, tx: &tx, ty: &ty);
1853 appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
1854 m.transform(x, y, tx: &tx, ty: &ty);
1855 appendf(fmt: "{0:.2f} {1:.2f} l\n", tx, ty);
1856 m.transform(x: x - xOffs, y: y - yOffs, tx: &tx, ty: &ty);
1857 appendf(fmt: "{0:.2f} {1:.2f} l\n", tx, ty);
1858
1859 if (isOpen) {
1860 appearBuf->append(str: "S\n");
1861 } else {
1862 appearBuf->append(str: fill ? "b\n" : "s\n");
1863 }
1864}
1865
1866void AnnotAppearanceBuilder::drawLineEndSlash(double x, double y, double size, const Matrix &m)
1867{
1868 const double halfSize { size / 2. };
1869 const double xOffs { cos(M_PI / 3.) * halfSize };
1870 double tx, ty;
1871
1872 m.transform(x: x - xOffs, y: y - halfSize, tx: &tx, ty: &ty);
1873 appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
1874 m.transform(x: x + xOffs, y: y + halfSize, tx: &tx, ty: &ty);
1875 appendf(fmt: "{0:.2f} {1:.2f} l\n", tx, ty);
1876 appearBuf->append(str: "S\n");
1877}
1878
1879void AnnotAppearanceBuilder::drawLineEnding(AnnotLineEndingStyle endingStyle, double x, double y, double size, bool fill, const Matrix &m)
1880{
1881 switch (endingStyle) {
1882 case annotLineEndingSquare:
1883 drawLineEndSquare(x, y, size, fill, m);
1884 break;
1885 case annotLineEndingCircle:
1886 drawLineEndCircle(x, y, size, fill, m);
1887 break;
1888 case annotLineEndingDiamond:
1889 drawLineEndDiamond(x, y, size, fill, m);
1890 break;
1891 case annotLineEndingOpenArrow:
1892 drawLineEndArrow(x, y, size, orientation: 1, isOpen: true, fill, m);
1893 break;
1894 case annotLineEndingClosedArrow:
1895 drawLineEndArrow(x, y, size, orientation: 1, isOpen: false, fill, m);
1896 break;
1897 case annotLineEndingButt: {
1898 const double halfSize { size / 2. };
1899 double tx, ty;
1900 m.transform(x, y: y + halfSize, tx: &tx, ty: &ty);
1901 appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
1902 m.transform(x, y: y - halfSize, tx: &tx, ty: &ty);
1903 appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
1904 } break;
1905 case annotLineEndingROpenArrow:
1906 drawLineEndArrow(x, y, size, orientation: -1, isOpen: true, fill, m);
1907 break;
1908 case annotLineEndingRClosedArrow:
1909 drawLineEndArrow(x, y, size, orientation: -1, isOpen: false, fill, m);
1910 break;
1911 case annotLineEndingSlash:
1912 drawLineEndSlash(x, y, size, m);
1913 break;
1914 default:
1915 break;
1916 }
1917}
1918
1919double AnnotAppearanceBuilder::lineEndingXShorten(AnnotLineEndingStyle endingStyle, double size)
1920{
1921 switch (endingStyle) {
1922 case annotLineEndingCircle:
1923 case annotLineEndingClosedArrow:
1924 case annotLineEndingDiamond:
1925 case annotLineEndingSquare:
1926 return size;
1927 default:
1928 break;
1929 }
1930 return 0;
1931}
1932
1933double AnnotAppearanceBuilder::lineEndingXExtendBBox(AnnotLineEndingStyle endingStyle, double size)
1934{
1935 switch (endingStyle) {
1936 case annotLineEndingRClosedArrow:
1937 case annotLineEndingROpenArrow:
1938 return size;
1939 case annotLineEndingSlash:
1940 return cos(M_PI / 3.) * size / 2.;
1941 default:
1942 break;
1943 }
1944 return 0;
1945}
1946
1947Object Annot::createForm(const GooString *appearBuf, const double *bbox, bool transparencyGroup, Dict *resDict)
1948{
1949 return createForm(appearBuf, bbox, transparencyGroup, resDictObject: resDict ? Object(resDict) : Object());
1950}
1951
1952Object Annot::createForm(const GooString *appearBuf, const double *bbox, bool transparencyGroup, Object &&resDictObject)
1953{
1954 Dict *appearDict = new Dict(doc->getXRef());
1955 appearDict->set(key: "Length", val: Object(appearBuf->getLength()));
1956 appearDict->set(key: "Subtype", val: Object(objName, "Form"));
1957
1958 Array *a = new Array(doc->getXRef());
1959 a->add(elem: Object(bbox[0]));
1960 a->add(elem: Object(bbox[1]));
1961 a->add(elem: Object(bbox[2]));
1962 a->add(elem: Object(bbox[3]));
1963 appearDict->set(key: "BBox", val: Object(a));
1964 if (transparencyGroup) {
1965 Dict *d = new Dict(doc->getXRef());
1966 d->set(key: "S", val: Object(objName, "Transparency"));
1967 appearDict->set(key: "Group", val: Object(d));
1968 }
1969 if (resDictObject.isDict()) {
1970 appearDict->set(key: "Resources", val: std::move(resDictObject));
1971 }
1972
1973 Stream *mStream = new AutoFreeMemStream(copyString(s: appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict));
1974 return Object(mStream);
1975}
1976
1977Dict *Annot::createResourcesDict(const char *formName, Object &&formStream, const char *stateName, double opacity, const char *blendMode)
1978{
1979 Dict *gsDict = new Dict(doc->getXRef());
1980 if (opacity != 1) {
1981 gsDict->set(key: "CA", val: Object(opacity));
1982 gsDict->set(key: "ca", val: Object(opacity));
1983 }
1984 if (blendMode) {
1985 gsDict->set(key: "BM", val: Object(objName, blendMode));
1986 }
1987 Dict *stateDict = new Dict(doc->getXRef());
1988 stateDict->set(key: stateName, val: Object(gsDict));
1989 Dict *formDict = new Dict(doc->getXRef());
1990 formDict->set(key: formName, val: std::move(formStream));
1991
1992 Dict *resDict = new Dict(doc->getXRef());
1993 resDict->set(key: "ExtGState", val: Object(stateDict));
1994 resDict->set(key: "XObject", val: Object(formDict));
1995
1996 return resDict;
1997}
1998
1999Object Annot::getAppearanceResDict()
2000{
2001 Object obj1, obj2;
2002
2003 // Fetch appearance's resource dict (if any)
2004 obj1 = appearance.fetch(xref: doc->getXRef());
2005 if (obj1.isStream()) {
2006 obj2 = obj1.streamGetDict()->lookup(key: "Resources");
2007 if (obj2.isDict()) {
2008 return obj2;
2009 }
2010 }
2011
2012 return Object(objNull);
2013}
2014
2015bool Annot::isVisible(bool printing)
2016{
2017 // check the flags
2018 if ((flags & flagHidden) || (printing && !(flags & flagPrint)) || (!printing && (flags & flagNoView))) {
2019 return false;
2020 }
2021
2022 // check the OC
2023 OCGs *optContentConfig = doc->getCatalog()->getOptContentConfig();
2024 if (optContentConfig) {
2025 if (!optContentConfig->optContentIsVisible(dictRef: &oc)) {
2026 return false;
2027 }
2028 }
2029
2030 return true;
2031}
2032
2033int Annot::getRotation() const
2034{
2035 Page *pageobj = doc->getPage(page);
2036 assert(pageobj != nullptr);
2037
2038 if (flags & flagNoRotate) {
2039 return (360 - pageobj->getRotate()) % 360;
2040 } else {
2041 return 0;
2042 }
2043}
2044
2045void Annot::draw(Gfx *gfx, bool printing)
2046{
2047 annotLocker();
2048 if (!isVisible(printing)) {
2049 return;
2050 }
2051
2052 // draw the appearance stream
2053 Object obj = appearance.fetch(xref: gfx->getXRef());
2054 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
2055}
2056
2057void Annot::setNewAppearance(Object &&newAppearance)
2058{
2059 if (newAppearance.isNull()) {
2060 return;
2061 }
2062
2063 annotLocker();
2064 if (newAppearance.getType() == ObjType::objStream) {
2065 invalidateAppearance();
2066 appearance = std::move(newAppearance);
2067
2068 Ref updatedAppearanceStream = doc->getXRef()->addIndirectObject(o: appearance);
2069
2070 Object obj1 = Object(new Dict(doc->getXRef()));
2071 obj1.dictAdd(key: "N", val: Object(updatedAppearanceStream));
2072 update(key: "AP", value: std::move(obj1));
2073 update(key: "AS", value: Object(objName, "N"));
2074
2075 Object updatedAP = annotObj.dictLookup(key: "AP");
2076 appearStreams = std::make_unique<AnnotAppearance>(args&: doc, args: &updatedAP);
2077 } else {
2078 appearStreams = std::make_unique<AnnotAppearance>(args&: doc, args: &newAppearance);
2079 update(key: "AP", value: std::move(newAppearance));
2080
2081 if (appearStreams) {
2082 appearance = appearStreams->getAppearanceStream(type: AnnotAppearance::appearNormal, state: appearState->c_str());
2083 }
2084 }
2085}
2086
2087Object Annot::getAppearance() const
2088{
2089 return appearance.fetch(xref: doc->getXRef());
2090}
2091
2092//------------------------------------------------------------------------
2093// AnnotPopup
2094//------------------------------------------------------------------------
2095
2096AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
2097{
2098 type = typePopup;
2099
2100 annotObj.dictSet(key: "Subtype", val: Object(objName, "Popup"));
2101 initialize(docA, dict: annotObj.getDict());
2102}
2103
2104AnnotPopup::AnnotPopup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
2105{
2106 type = typePopup;
2107 initialize(docA, dict: annotObj.getDict());
2108}
2109
2110AnnotPopup::~AnnotPopup() { }
2111
2112void AnnotPopup::initialize(PDFDoc *docA, Dict *dict)
2113{
2114 const Object &parentObj = dict->lookupNF(key: "Parent");
2115 if (parentObj.isRef()) {
2116 parentRef = parentObj.getRef();
2117 } else {
2118 parentRef = Ref::INVALID();
2119 }
2120
2121 open = dict->lookup(key: "Open").getBoolWithDefaultValue(defaultValue: false);
2122}
2123
2124void AnnotPopup::setParent(Annot *parentA)
2125{
2126 parentRef = parentA->getRef();
2127 update(key: "Parent", value: Object(parentRef));
2128}
2129
2130void AnnotPopup::setOpen(bool openA)
2131{
2132 open = openA;
2133 update(key: "Open", value: Object(open));
2134}
2135
2136//------------------------------------------------------------------------
2137// AnnotMarkup
2138//------------------------------------------------------------------------
2139AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
2140{
2141 initialize(docA, dict: annotObj.getDict());
2142}
2143
2144AnnotMarkup::AnnotMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
2145{
2146 initialize(docA, dict: annotObj.getDict());
2147}
2148
2149AnnotMarkup::~AnnotMarkup() = default;
2150
2151void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict)
2152{
2153 Object obj1;
2154
2155 obj1 = dict->lookup(key: "T");
2156 if (obj1.isString()) {
2157 label.reset(p: obj1.getString()->copy());
2158 }
2159
2160 Object popupObj = dict->lookup(key: "Popup");
2161 const Object &obj2 = dict->lookupNF(key: "Popup");
2162 if (popupObj.isDict() && obj2.isRef()) {
2163 popup = std::make_unique<AnnotPopup>(args&: docA, args: std::move(popupObj), args: &obj2);
2164 }
2165
2166 opacity = dict->lookup(key: "CA").getNumWithDefaultValue(defaultValue: 1.0);
2167
2168 obj1 = dict->lookup(key: "CreationDate");
2169 if (obj1.isString()) {
2170 date.reset(p: obj1.getString()->copy());
2171 }
2172
2173 const Object &irtObj = dict->lookupNF(key: "IRT");
2174 if (irtObj.isRef()) {
2175 inReplyTo = irtObj.getRef();
2176 } else {
2177 inReplyTo = Ref::INVALID();
2178 }
2179
2180 obj1 = dict->lookup(key: "Subj");
2181 if (obj1.isString()) {
2182 subject.reset(p: obj1.getString()->copy());
2183 }
2184
2185 obj1 = dict->lookup(key: "RT");
2186 if (obj1.isName()) {
2187 const char *replyName = obj1.getName();
2188
2189 if (!strcmp(s1: replyName, s2: "R")) {
2190 replyTo = replyTypeR;
2191 } else if (!strcmp(s1: replyName, s2: "Group")) {
2192 replyTo = replyTypeGroup;
2193 } else {
2194 replyTo = replyTypeR;
2195 }
2196 } else {
2197 replyTo = replyTypeR;
2198 }
2199
2200 obj1 = dict->lookup(key: "ExData");
2201 if (obj1.isDict()) {
2202 exData = parseAnnotExternalData(dict: obj1.getDict());
2203 } else {
2204 exData = annotExternalDataMarkupUnknown;
2205 }
2206}
2207
2208void AnnotMarkup::setLabel(std::unique_ptr<GooString> &&new_label)
2209{
2210 if (new_label) {
2211 label = std::move(new_label);
2212 // append the unicode marker <FE FF> if needed
2213 if (!hasUnicodeByteOrderMark(s: label->toStr())) {
2214 prependUnicodeByteOrderMark(s&: label->toNonConstStr());
2215 }
2216 } else {
2217 label = std::make_unique<GooString>();
2218 }
2219
2220 update(key: "T", value: Object(label->copy()));
2221}
2222
2223void AnnotMarkup::setPopup(std::unique_ptr<AnnotPopup> &&new_popup)
2224{
2225 // If there exists an old popup annotation that is already
2226 // associated with a page, then we need to remove that
2227 // popup annotation from the page. Otherwise we would have
2228 // dangling references to it.
2229 if (popup && popup->getPageNum() != 0) {
2230 Page *pageobj = doc->getPage(page: popup->getPageNum());
2231 if (pageobj) {
2232 pageobj->removeAnnot(annot: popup.get());
2233 }
2234 }
2235
2236 if (new_popup) {
2237 const Ref popupRef = new_popup->getRef();
2238 update(key: "Popup", value: Object(popupRef));
2239
2240 new_popup->setParent(this);
2241 popup = std::move(new_popup);
2242
2243 // If this annotation is already added to a page, then we
2244 // add the new popup annotation to the same page.
2245 if (page != 0) {
2246 Page *pageobj = doc->getPage(page);
2247 assert(pageobj != nullptr); // pageobj should exist in doc (see setPage())
2248
2249 pageobj->addAnnot(annot: popup.get());
2250 }
2251 } else {
2252 popup = nullptr;
2253 }
2254}
2255
2256void AnnotMarkup::setOpacity(double opacityA)
2257{
2258 opacity = opacityA;
2259 update(key: "CA", value: Object(opacity));
2260 invalidateAppearance();
2261}
2262
2263void AnnotMarkup::setDate(GooString *new_date)
2264{
2265 if (new_date) {
2266 date = std::make_unique<GooString>(args&: new_date);
2267 update(key: "CreationDate", value: Object(date->copy()));
2268 } else {
2269 date.reset(p: nullptr);
2270 update(key: "CreationDate", value: Object(objNull));
2271 }
2272}
2273
2274void AnnotMarkup::removeReferencedObjects()
2275{
2276 Page *pageobj = doc->getPage(page);
2277 assert(pageobj != nullptr); // We're called when removing an annot from a page
2278
2279 // Remove popup
2280 if (popup) {
2281 pageobj->removeAnnot(annot: popup.get());
2282 }
2283
2284 Annot::removeReferencedObjects();
2285}
2286
2287//------------------------------------------------------------------------
2288// AnnotText
2289//------------------------------------------------------------------------
2290
2291AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
2292{
2293 type = typeText;
2294 flags |= flagNoZoom | flagNoRotate;
2295
2296 annotObj.dictSet(key: "Subtype", val: Object(objName, "Text"));
2297 initialize(docA, dict: annotObj.getDict());
2298}
2299
2300AnnotText::AnnotText(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
2301{
2302
2303 type = typeText;
2304 flags |= flagNoZoom | flagNoRotate;
2305 initialize(docA, dict: annotObj.getDict());
2306}
2307
2308AnnotText::~AnnotText() = default;
2309
2310void AnnotText::initialize(PDFDoc *docA, Dict *dict)
2311{
2312 Object obj1;
2313
2314 open = dict->lookup(key: "Open").getBoolWithDefaultValue(defaultValue: false);
2315
2316 obj1 = dict->lookup(key: "Name");
2317 if (obj1.isName()) {
2318 icon = std::make_unique<GooString>(args: obj1.getName());
2319 } else {
2320 icon = std::make_unique<GooString>(args: "Note");
2321 }
2322
2323 obj1 = dict->lookup(key: "StateModel");
2324 if (obj1.isString()) {
2325 const GooString *modelName = obj1.getString();
2326
2327 Object obj2 = dict->lookup(key: "State");
2328 if (obj2.isString()) {
2329 const GooString *stateName = obj2.getString();
2330
2331 if (!stateName->cmp(sA: "Marked")) {
2332 state = stateMarked;
2333 } else if (!stateName->cmp(sA: "Unmarked")) {
2334 state = stateUnmarked;
2335 } else if (!stateName->cmp(sA: "Accepted")) {
2336 state = stateAccepted;
2337 } else if (!stateName->cmp(sA: "Rejected")) {
2338 state = stateRejected;
2339 } else if (!stateName->cmp(sA: "Cancelled")) {
2340 state = stateCancelled;
2341 } else if (!stateName->cmp(sA: "Completed")) {
2342 state = stateCompleted;
2343 } else if (!stateName->cmp(sA: "None")) {
2344 state = stateNone;
2345 } else {
2346 state = stateUnknown;
2347 }
2348 } else {
2349 state = stateUnknown;
2350 }
2351
2352 if (!modelName->cmp(sA: "Marked")) {
2353 switch (state) {
2354 case stateUnknown:
2355 state = stateMarked;
2356 break;
2357 case stateAccepted:
2358 case stateRejected:
2359 case stateCancelled:
2360 case stateCompleted:
2361 case stateNone:
2362 state = stateUnknown;
2363 break;
2364 default:
2365 break;
2366 }
2367 } else if (!modelName->cmp(sA: "Review")) {
2368 switch (state) {
2369 case stateUnknown:
2370 state = stateNone;
2371 break;
2372 case stateMarked:
2373 case stateUnmarked:
2374 state = stateUnknown;
2375 break;
2376 default:
2377 break;
2378 }
2379 } else {
2380 state = stateUnknown;
2381 }
2382 } else {
2383 state = stateUnknown;
2384 }
2385}
2386
2387void AnnotText::setOpen(bool openA)
2388{
2389 open = openA;
2390 update(key: "Open", value: Object(open));
2391}
2392
2393void AnnotText::setIcon(GooString *new_icon)
2394{
2395 if (new_icon && icon->cmp(str: new_icon) == 0) {
2396 return;
2397 }
2398
2399 if (new_icon) {
2400 icon = std::make_unique<GooString>(args&: new_icon);
2401 } else {
2402 icon = std::make_unique<GooString>(args: "Note");
2403 }
2404
2405 update(key: "Name", value: Object(objName, icon->c_str()));
2406 invalidateAppearance();
2407}
2408
2409#define ANNOT_TEXT_AP_NOTE \
2410 "3.602 24 m 20.398 24 l 22.387 24 24 22.387 24 20.398 c 24 3.602 l 24\n" \
2411 "1.613 22.387 0 20.398 0 c 3.602 0 l 1.613 0 0 1.613 0 3.602 c 0 20.398\n" \
2412 "l 0 22.387 1.613 24 3.602 24 c h\n" \
2413 "3.602 24 m f\n" \
2414 "0.533333 0.541176 0.521569 RG 2 w\n" \
2415 "1 J\n" \
2416 "1 j\n" \
2417 "[] 0.0 d\n" \
2418 "4 M 9 18 m 4 18 l 4 7 4 4 6 3 c 20 3 l 18 4 18 7 18 18 c 17 18 l S\n" \
2419 "1.5 w\n" \
2420 "0 j\n" \
2421 "10 16 m 14 21 l S\n" \
2422 "1.85625 w\n" \
2423 "1 j\n" \
2424 "15.07 20.523 m 15.07 19.672 14.379 18.977 13.523 18.977 c 12.672 18.977\n" \
2425 "11.977 19.672 11.977 20.523 c 11.977 21.379 12.672 22.07 13.523 22.07 c\n" \
2426 "14.379 22.07 15.07 21.379 15.07 20.523 c h\n" \
2427 "15.07 20.523 m S\n" \
2428 "1 w\n" \
2429 "0 j\n" \
2430 "6.5 13.5 m 15.5 13.5 l S\n" \
2431 "6.5 10.5 m 13.5 10.5 l S\n" \
2432 "6.801 7.5 m 15.5 7.5 l S\n" \
2433 "0.729412 0.741176 0.713725 RG 2 w\n" \
2434 "1 j\n" \
2435 "9 19 m 4 19 l 4 8 4 5 6 4 c 20 4 l 18 5 18 8 18 19 c 17 19 l S\n" \
2436 "1.5 w\n" \
2437 "0 j\n" \
2438 "10 17 m 14 22 l S\n" \
2439 "1.85625 w\n" \
2440 "1 j\n" \
2441 "15.07 21.523 m 15.07 20.672 14.379 19.977 13.523 19.977 c 12.672 19.977\n" \
2442 "11.977 20.672 11.977 21.523 c 11.977 22.379 12.672 23.07 13.523 23.07 c\n" \
2443 "14.379 23.07 15.07 22.379 15.07 21.523 c h\n" \
2444 "15.07 21.523 m S\n" \
2445 "1 w\n" \
2446 "0 j\n" \
2447 "6.5 14.5 m 15.5 14.5 l S\n" \
2448 "6.5 11.5 m 13.5 11.5 l S\n" \
2449 "6.801 8.5 m 15.5 8.5 l S\n"
2450
2451#define ANNOT_TEXT_AP_COMMENT \
2452 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2453 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2454 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2455 "4.301 23 m f\n" \
2456 "0.533333 0.541176 0.521569 RG 2 w\n" \
2457 "0 J\n" \
2458 "1 j\n" \
2459 "[] 0.0 d\n" \
2460 "4 M 8 20 m 16 20 l 18.363 20 20 18.215 20 16 c 20 13 l 20 10.785 18.363 9\n" \
2461 "16 9 c 13 9 l 8 3 l 8 9 l 8 9 l 5.637 9 4 10.785 4 13 c 4 16 l 4 18.215\n" \
2462 "5.637 20 8 20 c h\n" \
2463 "8 20 m S\n" \
2464 "0.729412 0.741176 0.713725 RG 8 21 m 16 21 l 18.363 21 20 19.215 20 17\n" \
2465 "c 20 14 l 20 11.785 18.363 10\n" \
2466 "16 10 c 13 10 l 8 4 l 8 10 l 8 10 l 5.637 10 4 11.785 4 14 c 4 17 l 4\n" \
2467 "19.215 5.637 21 8 21 c h\n" \
2468 "8 21 m S\n"
2469
2470#define ANNOT_TEXT_AP_KEY \
2471 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2472 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2473 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2474 "4.301 23 m f\n" \
2475 "0.533333 0.541176 0.521569 RG 2 w\n" \
2476 "1 J\n" \
2477 "0 j\n" \
2478 "[] 0.0 d\n" \
2479 "4 M 11.895 18.754 m 13.926 20.625 17.09 20.496 18.961 18.465 c 20.832\n" \
2480 "16.434 20.699 13.27 18.668 11.398 c 17.164 10.016 15.043 9.746 13.281\n" \
2481 "10.516 c 12.473 9.324 l 11.281 10.078 l 9.547 8.664 l 9.008 6.496 l\n" \
2482 "7.059 6.059 l 6.34 4.121 l 5.543 3.668 l 3.375 4.207 l 2.938 6.156 l\n" \
2483 "10.57 13.457 l 9.949 15.277 10.391 17.367 11.895 18.754 c h\n" \
2484 "11.895 18.754 m S\n" \
2485 "1.5 w\n" \
2486 "16.059 15.586 m 16.523 15.078 17.316 15.043 17.824 15.512 c 18.332\n" \
2487 "15.98 18.363 16.77 17.895 17.277 c 17.43 17.785 16.637 17.816 16.129\n" \
2488 "17.352 c 15.621 16.883 15.59 16.094 16.059 15.586 c h\n" \
2489 "16.059 15.586 m S\n" \
2490 "0.729412 0.741176 0.713725 RG 2 w\n" \
2491 "11.895 19.754 m 13.926 21.625 17.09 21.496 18.961 19.465 c 20.832\n" \
2492 "17.434 20.699 14.27 18.668 12.398 c 17.164 11.016 15.043 10.746 13.281\n" \
2493 "11.516 c 12.473 10.324 l 11.281 11.078 l 9.547 9.664 l 9.008 7.496 l\n" \
2494 "7.059 7.059 l 6.34 5.121 l 5.543 4.668 l 3.375 5.207 l 2.938 7.156 l\n" \
2495 "10.57 14.457 l 9.949 16.277 10.391 18.367 11.895 19.754 c h\n" \
2496 "11.895 19.754 m S\n" \
2497 "1.5 w\n" \
2498 "16.059 16.586 m 16.523 16.078 17.316 16.043 17.824 16.512 c 18.332\n" \
2499 "16.98 18.363 17.77 17.895 18.277 c 17.43 18.785 16.637 18.816 16.129\n" \
2500 "18.352 c 15.621 17.883 15.59 17.094 16.059 16.586 c h\n" \
2501 "16.059 16.586 m S\n"
2502
2503#define ANNOT_TEXT_AP_HELP \
2504 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2505 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2506 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2507 "4.301 23 m f\n" \
2508 "0.533333 0.541176 0.521569 RG 2.5 w\n" \
2509 "1 J\n" \
2510 "1 j\n" \
2511 "[] 0.0 d\n" \
2512 "4 M 8.289 16.488 m 8.824 17.828 10.043 18.773 11.473 18.965 c 12.902 19.156\n" \
2513 "14.328 18.559 15.195 17.406 c 16.062 16.254 16.242 14.723 15.664 13.398\n" \
2514 "c S\n" \
2515 "0 j\n" \
2516 "12 8 m 12 12 16 11 16 15 c S\n" \
2517 "1.539286 w\n" \
2518 "1 j\n" \
2519 "q 1 0 0 -0.999991 0 24 cm\n" \
2520 "12.684 20.891 m 12.473 21.258 12.004 21.395 11.629 21.196 c 11.254\n" \
2521 "20.992 11.105 20.531 11.297 20.149 c 11.488 19.77 11.945 19.61 12.332\n" \
2522 "19.789 c 12.719 19.969 12.891 20.426 12.719 20.817 c S Q\n" \
2523 "0.729412 0.741176 0.713725 RG 2.5 w\n" \
2524 "8.289 17.488 m 9.109 19.539 11.438 20.535 13.488 19.711 c 15.539 18.891\n" \
2525 "16.535 16.562 15.711 14.512 c 15.699 14.473 15.684 14.438 15.664 14.398\n" \
2526 "c S\n" \
2527 "0 j\n" \
2528 "12 9 m 12 13 16 12 16 16 c S\n" \
2529 "1.539286 w\n" \
2530 "1 j\n" \
2531 "q 1 0 0 -0.999991 0 24 cm\n" \
2532 "12.684 19.891 m 12.473 20.258 12.004 20.395 11.629 20.195 c 11.254\n" \
2533 "19.992 11.105 19.531 11.297 19.149 c 11.488 18.77 11.945 18.61 12.332\n" \
2534 "18.789 c 12.719 18.969 12.891 19.426 12.719 19.817 c S Q\n"
2535
2536#define ANNOT_TEXT_AP_NEW_PARAGRAPH \
2537 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2538 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2539 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2540 "4.301 23 m f\n" \
2541 "0.533333 0.541176 0.521569 RG 4 w\n" \
2542 "0 J\n" \
2543 "2 j\n" \
2544 "[] 0.0 d\n" \
2545 "4 M q 1 0 0 -1 0 24 cm\n" \
2546 "9.211 11.988 m 8.449 12.07 7.711 11.707 7.305 11.059 c 6.898 10.41\n" \
2547 "6.898 9.59 7.305 8.941 c 7.711 8.293 8.449 7.93 9.211 8.012 c S Q\n" \
2548 "1.004413 w\n" \
2549 "1 J\n" \
2550 "1 j\n" \
2551 "q 1 0 0 -0.991232 0 24 cm\n" \
2552 "18.07 11.511 m 15.113 10.014 l 12.199 11.602 l 12.711 8.323 l 10.301\n" \
2553 "6.045 l 13.574 5.517 l 14.996 2.522 l 16.512 5.474 l 19.801 5.899 l\n" \
2554 "17.461 8.252 l 18.07 11.511 l h\n" \
2555 "18.07 11.511 m S Q\n" \
2556 "2 w\n" \
2557 "0 j\n" \
2558 "11 17 m 10 17 l 10 3 l S\n" \
2559 "14 3 m 14 13 l S\n" \
2560 "0.729412 0.741176 0.713725 RG 4 w\n" \
2561 "0 J\n" \
2562 "2 j\n" \
2563 "q 1 0 0 -1 0 24 cm\n" \
2564 "9.211 10.988 m 8.109 11.105 7.125 10.309 7.012 9.211 c 6.895 8.109\n" \
2565 "7.691 7.125 8.789 7.012 c 8.93 6.996 9.07 6.996 9.211 7.012 c S Q\n" \
2566 "1.004413 w\n" \
2567 "1 J\n" \
2568 "1 j\n" \
2569 "q 1 0 0 -0.991232 0 24 cm\n" \
2570 "18.07 10.502 m 15.113 9.005 l 12.199 10.593 l 12.711 7.314 l 10.301\n" \
2571 "5.036 l 13.574 4.508 l 14.996 1.513 l 16.512 4.465 l 19.801 4.891 l\n" \
2572 "17.461 7.243 l 18.07 10.502 l h\n" \
2573 "18.07 10.502 m S Q\n" \
2574 "2 w\n" \
2575 "0 j\n" \
2576 "11 18 m 10 18 l 10 4 l S\n" \
2577 "14 4 m 14 14 l S\n"
2578
2579#define ANNOT_TEXT_AP_PARAGRAPH \
2580 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2581 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2582 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2583 "4.301 23 m f\n" \
2584 "0.533333 0.541176 0.521569 RG 2 w\n" \
2585 "1 J\n" \
2586 "1 j\n" \
2587 "[] 0.0 d\n" \
2588 "4 M 15 3 m 15 18 l 11 18 l 11 3 l S\n" \
2589 "4 w\n" \
2590 "q 1 0 0 -1 0 24 cm\n" \
2591 "9.777 10.988 m 8.746 10.871 7.973 9.988 8 8.949 c 8.027 7.91 8.844\n" \
2592 "7.066 9.879 7.004 c S Q\n" \
2593 "0.729412 0.741176 0.713725 RG 2 w\n" \
2594 "15 4 m 15 19 l 11 19 l 11 4 l S\n" \
2595 "4 w\n" \
2596 "q 1 0 0 -1 0 24 cm\n" \
2597 "9.777 9.988 m 8.746 9.871 7.973 8.988 8 7.949 c 8.027 6.91 8.844 6.066\n" \
2598 "9.879 6.004 c S Q\n"
2599
2600#define ANNOT_TEXT_AP_INSERT \
2601 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2602 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2603 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2604 "4.301 23 m f\n" \
2605 "0.533333 0.541176 0.521569 RG 2 w\n" \
2606 "1 J\n" \
2607 "0 j\n" \
2608 "[] 0.0 d\n" \
2609 "4 M 12 18.012 m 20 18 l S\n" \
2610 "9 10 m 17 10 l S\n" \
2611 "12 14.012 m 20 14 l S\n" \
2612 "12 6.012 m 20 6.012 l S\n" \
2613 "4 12 m 6 10 l 4 8 l S\n" \
2614 "4 12 m 4 8 l S\n" \
2615 "0.729412 0.741176 0.713725 RG 12 19.012 m 20 19 l S\n" \
2616 "9 11 m 17 11 l S\n" \
2617 "12 15.012 m 20 15 l S\n" \
2618 "12 7.012 m 20 7.012 l S\n" \
2619 "4 13 m 6 11 l 4 9 l S\n" \
2620 "4 13 m 4 9 l S\n"
2621
2622#define ANNOT_TEXT_AP_CROSS \
2623 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2624 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2625 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2626 "4.301 23 m f\n" \
2627 "0.533333 0.541176 0.521569 RG 2.5 w\n" \
2628 "1 J\n" \
2629 "0 j\n" \
2630 "[] 0.0 d\n" \
2631 "4 M 18 5 m 6 17 l S\n" \
2632 "6 5 m 18 17 l S\n" \
2633 "0.729412 0.741176 0.713725 RG 18 6 m 6 18 l S\n" \
2634 "6 6 m 18 18 l S\n"
2635
2636#define ANNOT_TEXT_AP_CIRCLE \
2637 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
2638 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
2639 "l 1 21.523 2.477 23 4.301 23 c h\n" \
2640 "4.301 23 m f\n" \
2641 "0.533333 0.541176 0.521569 RG 2.5 w\n" \
2642 "1 J\n" \
2643 "1 j\n" \
2644 "[] 0.0 d\n" \
2645 "4 M 19.5 11.5 m 19.5 7.359 16.141 4 12 4 c 7.859 4 4.5 7.359 4.5 11.5 c 4.5\n" \
2646 "15.641 7.859 19 12 19 c 16.141 19 19.5 15.641 19.5 11.5 c h\n" \
2647 "19.5 11.5 m S\n" \
2648 "0.729412 0.741176 0.713725 RG 19.5 12.5 m 19.5 8.359 16.141 5 12 5 c\n" \
2649 "7.859 5 4.5 8.359 4.5 12.5 c 4.5\n" \
2650 "16.641 7.859 20 12 20 c 16.141 20 19.5 16.641 19.5 12.5 c h\n" \
2651 "19.5 12.5 m S\n"
2652
2653void AnnotText::draw(Gfx *gfx, bool printing)
2654{
2655 double ca = 1;
2656
2657 if (!isVisible(printing)) {
2658 return;
2659 }
2660
2661 annotLocker();
2662 if (appearance.isNull()) {
2663 ca = opacity;
2664
2665 AnnotAppearanceBuilder appearBuilder;
2666
2667 appearBuilder.append(text: "q\n");
2668 if (color) {
2669 appearBuilder.setDrawColor(drawColor: color.get(), fill: true);
2670 } else {
2671 appearBuilder.append(text: "1 1 1 rg\n");
2672 }
2673 if (!icon->cmp(sA: "Note")) {
2674 appearBuilder.append(ANNOT_TEXT_AP_NOTE);
2675 } else if (!icon->cmp(sA: "Comment")) {
2676 appearBuilder.append(ANNOT_TEXT_AP_COMMENT);
2677 } else if (!icon->cmp(sA: "Key")) {
2678 appearBuilder.append(ANNOT_TEXT_AP_KEY);
2679 } else if (!icon->cmp(sA: "Help")) {
2680 appearBuilder.append(ANNOT_TEXT_AP_HELP);
2681 } else if (!icon->cmp(sA: "NewParagraph")) {
2682 appearBuilder.append(ANNOT_TEXT_AP_NEW_PARAGRAPH);
2683 } else if (!icon->cmp(sA: "Paragraph")) {
2684 appearBuilder.append(ANNOT_TEXT_AP_PARAGRAPH);
2685 } else if (!icon->cmp(sA: "Insert")) {
2686 appearBuilder.append(ANNOT_TEXT_AP_INSERT);
2687 } else if (!icon->cmp(sA: "Cross")) {
2688 appearBuilder.append(ANNOT_TEXT_AP_CROSS);
2689 } else if (!icon->cmp(sA: "Circle")) {
2690 appearBuilder.append(ANNOT_TEXT_AP_CIRCLE);
2691 }
2692 appearBuilder.append(text: "Q\n");
2693
2694 // Force 24x24 rectangle
2695 PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2);
2696 appearBBox = std::make_unique<AnnotAppearanceBBox>(args: &fixedRect);
2697 double bbox[4];
2698 appearBBox->getBBoxRect(bbox);
2699 if (ca == 1) {
2700 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
2701 } else {
2702 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
2703
2704 GooString appearBuf("/GS0 gs\n/Fm0 Do");
2705 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
2706 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
2707 }
2708 }
2709
2710 // draw the appearance stream
2711 Object obj = appearance.fetch(xref: gfx->getXRef());
2712 if (appearBBox) {
2713 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
2714 } else {
2715 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
2716 }
2717}
2718
2719//------------------------------------------------------------------------
2720// AnnotLink
2721//------------------------------------------------------------------------
2722AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
2723{
2724 type = typeLink;
2725 annotObj.dictSet(key: "Subtype", val: Object(objName, "Link"));
2726 initialize(docA, dict: annotObj.getDict());
2727}
2728
2729AnnotLink::AnnotLink(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
2730{
2731
2732 type = typeLink;
2733 initialize(docA, dict: annotObj.getDict());
2734}
2735
2736AnnotLink::~AnnotLink() = default;
2737
2738void AnnotLink::initialize(PDFDoc *docA, Dict *dict)
2739{
2740 Object obj1;
2741
2742 // look for destination
2743 obj1 = dict->lookup(key: "Dest");
2744 if (!obj1.isNull()) {
2745 action = LinkAction::parseDest(obj: &obj1);
2746 // look for action
2747 } else {
2748 obj1 = dict->lookup(key: "A");
2749 if (obj1.isDict()) {
2750 action = LinkAction::parseAction(obj: &obj1, baseURI: doc->getCatalog()->getBaseURI());
2751 }
2752 }
2753
2754 obj1 = dict->lookup(key: "H");
2755 if (obj1.isName()) {
2756 const char *effect = obj1.getName();
2757
2758 if (!strcmp(s1: effect, s2: "N")) {
2759 linkEffect = effectNone;
2760 } else if (!strcmp(s1: effect, s2: "I")) {
2761 linkEffect = effectInvert;
2762 } else if (!strcmp(s1: effect, s2: "O")) {
2763 linkEffect = effectOutline;
2764 } else if (!strcmp(s1: effect, s2: "P")) {
2765 linkEffect = effectPush;
2766 } else {
2767 linkEffect = effectInvert;
2768 }
2769 } else {
2770 linkEffect = effectInvert;
2771 }
2772 /*
2773 obj1 = dict->lookup("PA");
2774 if (obj1.isDict()) {
2775 uriAction = NULL;
2776 } else {
2777 uriAction = NULL;
2778 }
2779 obj1.free();
2780 */
2781 obj1 = dict->lookup(key: "QuadPoints");
2782 if (obj1.isArray()) {
2783 quadrilaterals = std::make_unique<AnnotQuadrilaterals>(args: obj1.getArray(), args: rect.get());
2784 }
2785
2786 obj1 = dict->lookup(key: "BS");
2787 if (obj1.isDict()) {
2788 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
2789 } else if (!border) {
2790 border = std::make_unique<AnnotBorderBS>();
2791 }
2792}
2793
2794void AnnotLink::draw(Gfx *gfx, bool printing)
2795{
2796 if (!isVisible(printing)) {
2797 return;
2798 }
2799
2800 annotLocker();
2801 // draw the appearance stream
2802 Object obj = appearance.fetch(xref: gfx->getXRef());
2803 gfx->drawAnnot(str: &obj, border: border.get(), aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
2804}
2805
2806//------------------------------------------------------------------------
2807// AnnotFreeText
2808//------------------------------------------------------------------------
2809const double AnnotFreeText::undefinedFontPtSize = 10.;
2810
2811AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
2812{
2813 type = typeFreeText;
2814
2815 annotObj.dictSet(key: "Subtype", val: Object(objName, "FreeText"));
2816 annotObj.dictSet(key: "DA", val: Object(new GooString()));
2817
2818 initialize(docA, dict: annotObj.getDict());
2819}
2820
2821AnnotFreeText::AnnotFreeText(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
2822{
2823 type = typeFreeText;
2824 initialize(docA, dict: annotObj.getDict());
2825}
2826
2827AnnotFreeText::~AnnotFreeText() = default;
2828
2829void AnnotFreeText::initialize(PDFDoc *docA, Dict *dict)
2830{
2831 Object obj1;
2832
2833 obj1 = dict->lookup(key: "DA");
2834 if (obj1.isString()) {
2835 appearanceString.reset(p: obj1.getString()->copy());
2836 } else {
2837 appearanceString = std::make_unique<GooString>();
2838 error(category: errSyntaxWarning, pos: -1, msg: "Bad appearance for annotation");
2839 }
2840
2841 obj1 = dict->lookup(key: "Q");
2842 if (obj1.isInt()) {
2843 quadding = (VariableTextQuadding)obj1.getInt();
2844 } else {
2845 quadding = VariableTextQuadding::leftJustified;
2846 }
2847
2848 obj1 = dict->lookup(key: "DS");
2849 if (obj1.isString()) {
2850 styleString.reset(p: obj1.getString()->copy());
2851 }
2852
2853 obj1 = dict->lookup(key: "CL");
2854 if (obj1.isArray() && obj1.arrayGetLength() >= 4) {
2855 const double x1 = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
2856 const double y1 = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
2857 const double x2 = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
2858 const double y2 = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 0);
2859
2860 if (obj1.arrayGetLength() == 6) {
2861 const double x3 = obj1.arrayGet(i: 4).getNumWithDefaultValue(defaultValue: 0);
2862 const double y3 = obj1.arrayGet(i: 5).getNumWithDefaultValue(defaultValue: 0);
2863 calloutLine = std::make_unique<AnnotCalloutMultiLine>(args: x1, args: y1, args: x2, args: y2, args: x3, args: y3);
2864 } else {
2865 calloutLine = std::make_unique<AnnotCalloutLine>(args: x1, args: y1, args: x2, args: y2);
2866 }
2867 }
2868
2869 obj1 = dict->lookup(key: "IT");
2870 if (obj1.isName()) {
2871 const char *intentName = obj1.getName();
2872
2873 if (!strcmp(s1: intentName, s2: "FreeText")) {
2874 intent = intentFreeText;
2875 } else if (!strcmp(s1: intentName, s2: "FreeTextCallout")) {
2876 intent = intentFreeTextCallout;
2877 } else if (!strcmp(s1: intentName, s2: "FreeTextTypeWriter")) {
2878 intent = intentFreeTextTypeWriter;
2879 } else {
2880 intent = intentFreeText;
2881 }
2882 } else {
2883 intent = intentFreeText;
2884 }
2885
2886 obj1 = dict->lookup(key: "BS");
2887 if (obj1.isDict()) {
2888 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
2889 } else if (!border) {
2890 border = std::make_unique<AnnotBorderBS>();
2891 }
2892
2893 obj1 = dict->lookup(key: "BE");
2894 if (obj1.isDict()) {
2895 borderEffect = std::make_unique<AnnotBorderEffect>(args: obj1.getDict());
2896 }
2897
2898 obj1 = dict->lookup(key: "RD");
2899 if (obj1.isArray()) {
2900 rectangle = parseDiffRectangle(array: obj1.getArray(), rect: rect.get());
2901 }
2902
2903 obj1 = dict->lookup(key: "LE");
2904 if (obj1.isName()) {
2905 GooString styleName(obj1.getName());
2906 endStyle = parseAnnotLineEndingStyle(string: &styleName);
2907 } else {
2908 endStyle = annotLineEndingNone;
2909 }
2910}
2911
2912void AnnotFreeText::setContents(std::unique_ptr<GooString> &&new_content)
2913{
2914 Annot::setContents(std::move(new_content));
2915 invalidateAppearance();
2916}
2917
2918void AnnotFreeText::setDefaultAppearance(const DefaultAppearance &da)
2919{
2920 appearanceString = std::make_unique<GooString>(args: da.toAppearanceString());
2921
2922 update(key: "DA", value: Object(appearanceString->copy()));
2923 invalidateAppearance();
2924}
2925
2926void AnnotFreeText::setQuadding(VariableTextQuadding new_quadding)
2927{
2928 quadding = new_quadding;
2929 update(key: "Q", value: Object((int)quadding));
2930 invalidateAppearance();
2931}
2932
2933void AnnotFreeText::setStyleString(GooString *new_string)
2934{
2935 if (new_string) {
2936 styleString = std::make_unique<GooString>(args&: new_string);
2937 // append the unicode marker <FE FF> if needed
2938 if (!hasUnicodeByteOrderMark(s: styleString->toStr())) {
2939 prependUnicodeByteOrderMark(s&: styleString->toNonConstStr());
2940 }
2941 } else {
2942 styleString = std::make_unique<GooString>();
2943 }
2944
2945 update(key: "DS", value: Object(styleString->copy()));
2946}
2947
2948void AnnotFreeText::setCalloutLine(AnnotCalloutLine *line)
2949{
2950 Object obj1;
2951 if (line == nullptr) {
2952 obj1.setToNull();
2953 calloutLine = nullptr;
2954 } else {
2955 double x1 = line->getX1(), y1 = line->getY1();
2956 double x2 = line->getX2(), y2 = line->getY2();
2957 obj1 = Object(new Array(doc->getXRef()));
2958 obj1.arrayAdd(elem: Object(x1));
2959 obj1.arrayAdd(elem: Object(y1));
2960 obj1.arrayAdd(elem: Object(x2));
2961 obj1.arrayAdd(elem: Object(y2));
2962
2963 AnnotCalloutMultiLine *mline = dynamic_cast<AnnotCalloutMultiLine *>(line);
2964 if (mline) {
2965 double x3 = mline->getX3(), y3 = mline->getY3();
2966 obj1.arrayAdd(elem: Object(x3));
2967 obj1.arrayAdd(elem: Object(y3));
2968 calloutLine = std::make_unique<AnnotCalloutMultiLine>(args&: x1, args&: y1, args&: x2, args&: y2, args&: x3, args&: y3);
2969 } else {
2970 calloutLine = std::make_unique<AnnotCalloutLine>(args&: x1, args&: y1, args&: x2, args&: y2);
2971 }
2972 }
2973
2974 update(key: "CL", value: std::move(obj1));
2975 invalidateAppearance();
2976}
2977
2978void AnnotFreeText::setIntent(AnnotFreeTextIntent new_intent)
2979{
2980 const char *intentName;
2981
2982 intent = new_intent;
2983 if (new_intent == intentFreeText) {
2984 intentName = "FreeText";
2985 } else if (new_intent == intentFreeTextCallout) {
2986 intentName = "FreeTextCallout";
2987 } else { // intentFreeTextTypeWriter
2988 intentName = "FreeTextTypeWriter";
2989 }
2990 update(key: "IT", value: Object(objName, intentName));
2991}
2992
2993std::unique_ptr<DefaultAppearance> AnnotFreeText::getDefaultAppearance() const
2994{
2995 return std::make_unique<DefaultAppearance>(args: appearanceString.get());
2996}
2997
2998static std::unique_ptr<GfxFont> createAnnotDrawFont(XRef *xref, Dict *fontParentDict, const char *resourceName = "AnnotDrawFont", const char *fontname = "Helvetica")
2999{
3000 const Ref dummyRef = { .num: -1, .gen: -1 };
3001
3002 Dict *fontDict = new Dict(xref);
3003 fontDict->add(key: "BaseFont", val: Object(objName, fontname));
3004 fontDict->add(key: "Subtype", val: Object(objName, "Type1"));
3005 if (strcmp(s1: fontname, s2: "ZapfDingbats") && strcmp(s1: fontname, s2: "Symbol")) {
3006 fontDict->add(key: "Encoding", val: Object(objName, "WinAnsiEncoding"));
3007 }
3008
3009 Object fontsDictObj = fontParentDict->lookup(key: "Font");
3010 if (!fontsDictObj.isDict()) {
3011 fontsDictObj = Object(new Dict(xref));
3012 fontParentDict->add(key: "Font", val: fontsDictObj.copy()); // This is not a copy it's a ref
3013 }
3014
3015 fontsDictObj.dictSet(key: resourceName, val: Object(fontDict));
3016
3017 return GfxFont::makeFont(xref, tagA: resourceName, idA: dummyRef, fontDict);
3018}
3019
3020class HorizontalTextLayouter
3021{
3022public:
3023 HorizontalTextLayouter() = default;
3024
3025 HorizontalTextLayouter(const GooString *text, const Form *form, const GfxFont *font, std::optional<double> availableWidth, const bool noReencode)
3026 {
3027 int i = 0;
3028 double blockWidth;
3029 bool newFontNeeded = false;
3030 GooString outputText;
3031 const bool isUnicode = hasUnicodeByteOrderMark(s: text->toStr());
3032 int charCount;
3033
3034 Annot::layoutText(text, outBuf: &outputText, i: &i, font: *font, width: &blockWidth, widthLimit: availableWidth ? *availableWidth : 0.0, charCount: &charCount, noReencode, newFontNeeded: !noReencode ? &newFontNeeded : nullptr);
3035 data.emplace_back(args: outputText.toStr(), args: std::string(), args&: blockWidth, args&: charCount);
3036 if (availableWidth) {
3037 *availableWidth -= blockWidth;
3038 }
3039
3040 while (newFontNeeded && (!availableWidth || *availableWidth > 0 || (isUnicode && i == 2) || (!isUnicode && i == 0))) {
3041 if (!form) {
3042 // There's no fonts to look for, so just skip the characters
3043 i += isUnicode ? 2 : 1;
3044 error(category: errSyntaxError, pos: -1, msg: "HorizontalTextLayouter, found character that the font can't represent");
3045 newFontNeeded = false;
3046 } else {
3047 Unicode uChar;
3048 if (isUnicode) {
3049 uChar = (unsigned char)(text->getChar(i)) << 8;
3050 uChar += (unsigned char)(text->getChar(i: i + 1));
3051 } else {
3052 uChar = pdfDocEncoding[text->getChar(i) & 0xff];
3053 }
3054 const std::string auxFontName = form->getFallbackFontForChar(uChar, fontToEmulate: *font);
3055 if (!auxFontName.empty()) {
3056 std::shared_ptr<GfxFont> auxFont = form->getDefaultResources()->lookupFont(name: auxFontName.c_str());
3057
3058 // Here we just layout one char, we don't know if the one afterwards can be layouted with the original font
3059 GooString auxContents = GooString(text->toStr().substr(pos: i, n: isUnicode ? 2 : 1));
3060 if (isUnicode) {
3061 prependUnicodeByteOrderMark(s&: auxContents.toNonConstStr());
3062 }
3063 int auxI = 0;
3064 Annot::layoutText(text: &auxContents, outBuf: &outputText, i: &auxI, font: *auxFont, width: &blockWidth, widthLimit: availableWidth ? *availableWidth : 0.0, charCount: &charCount, noReencode: false, newFontNeeded: &newFontNeeded);
3065 assert(!newFontNeeded);
3066 if (availableWidth) {
3067 *availableWidth -= blockWidth;
3068 }
3069 // layoutText will always at least layout one character even if it doesn't fit in
3070 // the given space which makes sense (except in the case of switching fonts, so we control if we ran out of space here manually)
3071 // we also need to allow the character if we have not layouted anything yet because otherwise we will end up in an infinite loop
3072 // because it is assumed we at least layout one character
3073 if (!availableWidth || *availableWidth > 0 || (isUnicode && i == 2) || (!isUnicode && i == 0)) {
3074 i += isUnicode ? 2 : 1;
3075 data.emplace_back(args: outputText.toStr(), args: auxFontName, args&: blockWidth, args&: charCount);
3076 }
3077 } else {
3078 error(category: errSyntaxError, pos: -1, msg: "HorizontalTextLayouter, couldn't find a font for character U+{0:04uX}", uChar);
3079 newFontNeeded = false;
3080 i += isUnicode ? 2 : 1;
3081 }
3082 }
3083 // Now layout the rest of the text with the original font
3084 if (!availableWidth || *availableWidth > 0) {
3085 Annot::layoutText(text, outBuf: &outputText, i: &i, font: *font, width: &blockWidth, widthLimit: availableWidth ? *availableWidth : 0.0, charCount: &charCount, noReencode: false, newFontNeeded: &newFontNeeded);
3086 if (availableWidth) {
3087 *availableWidth -= blockWidth;
3088 }
3089 // layoutText will always at least layout one character even if it doesn't fit in
3090 // the given space which makes sense (except in the case of switching fonts, so we control if we ran out of space here manually)
3091 if (!availableWidth || *availableWidth > 0) {
3092 data.emplace_back(args: outputText.toStr(), args: std::string(), args&: blockWidth, args&: charCount);
3093 } else {
3094 i -= isUnicode ? 2 : 1;
3095 }
3096 }
3097 }
3098 consumedText = i;
3099 }
3100
3101 HorizontalTextLayouter(const HorizontalTextLayouter &) = delete;
3102 HorizontalTextLayouter &operator=(const HorizontalTextLayouter &) = delete;
3103
3104 double totalWidth() const
3105 {
3106 double totalWidth = 0;
3107 for (const Data &d : data) {
3108 totalWidth += d.width;
3109 }
3110 return totalWidth;
3111 }
3112
3113 int totalCharCount() const
3114 {
3115 int total = 0;
3116 for (const Data &d : data) {
3117 total += d.charCount;
3118 }
3119 return total;
3120 }
3121
3122 struct Data
3123 {
3124 Data(const std::string &t, const std::string &fName, double w, int cc) : text(t), fontName(fName), width(w), charCount(cc) { }
3125
3126 const std::string text;
3127 const std::string fontName;
3128 const double width;
3129 const int charCount;
3130 };
3131
3132 std::vector<Data> data;
3133 int consumedText;
3134};
3135
3136double Annot::calculateFontSize(const Form *form, const GfxFont *font, const GooString *text, double wMax, double hMax, const bool forceZapfDingbats)
3137{
3138 const bool isUnicode = hasUnicodeByteOrderMark(s: text->toStr());
3139 double fontSize;
3140
3141 for (fontSize = 20; fontSize > 1; --fontSize) {
3142 const double availableWidthInFontSize = wMax / fontSize;
3143 double y = hMax - 3;
3144 int i = 0;
3145 while (i < text->getLength()) {
3146 GooString lineText(text->toStr().substr(pos: i));
3147 if (!hasUnicodeByteOrderMark(s: lineText.toStr()) && isUnicode) {
3148 prependUnicodeByteOrderMark(s&: lineText.toNonConstStr());
3149 }
3150 const HorizontalTextLayouter textLayouter(&lineText, form, font, availableWidthInFontSize, forceZapfDingbats);
3151 y -= fontSize;
3152 if (i == 0) {
3153 i += textLayouter.consumedText;
3154 } else {
3155 i += textLayouter.consumedText - (isUnicode ? 2 : 0);
3156 }
3157 }
3158 // approximate the descender for the last line
3159 if (y >= 0.33 * fontSize) {
3160 break;
3161 }
3162 }
3163 return fontSize;
3164}
3165
3166struct DrawMultiLineTextResult
3167{
3168 std::string text;
3169 int nLines = 0;
3170};
3171
3172// if fontName is empty it is assumed it is sent from the outside
3173// so for text that is in font no Tf is added and for text that is in the aux fonts
3174// a pair of q/Q is added
3175static DrawMultiLineTextResult drawMultiLineText(const GooString &text, double availableWidth, const Form *form, const GfxFont &font, const std::string &fontName, double fontSize, VariableTextQuadding quadding, double borderWidth)
3176{
3177 DrawMultiLineTextResult result;
3178 int i = 0;
3179 double xPosPrev = 0;
3180 const double availableTextWidthInFontPtSize = availableWidth / fontSize;
3181 while (i < text.getLength()) {
3182 GooString lineText(text.toStr().substr(pos: i));
3183 if (!hasUnicodeByteOrderMark(s: lineText.toStr()) && hasUnicodeByteOrderMark(s: text.toStr())) {
3184 prependUnicodeByteOrderMark(s&: lineText.toNonConstStr());
3185 }
3186 const HorizontalTextLayouter textLayouter(&lineText, form, &font, availableTextWidthInFontPtSize, false);
3187
3188 const double totalWidth = textLayouter.totalWidth() * fontSize;
3189
3190 auto calculateX = [quadding, availableWidth, totalWidth, borderWidth] {
3191 switch (quadding) {
3192 case VariableTextQuadding::centered:
3193 return (availableWidth - totalWidth) / 2;
3194 break;
3195 case VariableTextQuadding::rightJustified:
3196 return availableWidth - totalWidth - borderWidth;
3197 break;
3198 default: // VariableTextQuadding::lLeftJustified:
3199 return borderWidth;
3200 break;
3201 }
3202 };
3203 const double xPos = calculateX();
3204
3205 AnnotAppearanceBuilder builder;
3206 bool first = true;
3207 double prevBlockWidth = 0;
3208 for (const HorizontalTextLayouter::Data &d : textLayouter.data) {
3209 const std::string &fName = d.fontName.empty() ? fontName : d.fontName;
3210 if (!fName.empty()) {
3211 if (fontName.empty()) {
3212 builder.append(text: " q\n");
3213 }
3214 builder.appendf(fmt: "/{0:s} {1:.2f} Tf\n", fName.c_str(), fontSize);
3215 }
3216
3217 const double yDiff = first ? -fontSize : 0;
3218 const double xDiff = first ? xPos - xPosPrev : prevBlockWidth;
3219
3220 builder.appendf(fmt: "{0:.2f} {1:.2f} Td\n", xDiff, yDiff);
3221 builder.writeString(str: d.text);
3222 builder.append(text: " Tj\n");
3223 first = false;
3224 prevBlockWidth = d.width * fontSize;
3225
3226 if (!fName.empty() && fontName.empty()) {
3227 builder.append(text: " Q\n");
3228 }
3229 }
3230 xPosPrev = xPos + totalWidth - prevBlockWidth;
3231
3232 result.text += builder.buffer()->toStr();
3233 result.nLines += 1;
3234 if (i == 0) {
3235 i += textLayouter.consumedText;
3236 } else {
3237 i += textLayouter.consumedText - (hasUnicodeByteOrderMark(s: text.toStr()) ? 2 : 0);
3238 }
3239 }
3240 return result;
3241}
3242
3243void AnnotFreeText::generateFreeTextAppearance()
3244{
3245 double borderWidth, ca = opacity;
3246
3247 AnnotAppearanceBuilder appearBuilder;
3248 appearBuilder.append(text: "q\n");
3249
3250 borderWidth = border->getWidth();
3251 if (borderWidth > 0) {
3252 appearBuilder.setLineStyleForBorder(border.get());
3253 }
3254
3255 // Box size
3256 const double width = rect->x2 - rect->x1;
3257 const double height = rect->y2 - rect->y1;
3258
3259 // Parse some properties from the appearance string
3260 DefaultAppearance da { appearanceString.get() };
3261
3262 // Default values
3263 if (!da.getFontName().isName()) {
3264 da.setFontName(Object(objName, "AnnotDrawFont"));
3265 }
3266 if (da.getFontPtSize() <= 0) {
3267 da.setFontPtSize(undefinedFontPtSize);
3268 }
3269 if (!da.getFontColor()) {
3270 da.setFontColor(std::make_unique<AnnotColor>(args: 0, args: 0, args: 0));
3271 }
3272 if (!contents) {
3273 contents = std::make_unique<GooString>();
3274 }
3275
3276 // Draw box
3277 bool doFill = (color && color->getSpace() != AnnotColor::colorTransparent);
3278 bool doStroke = (borderWidth != 0);
3279 if (doFill || doStroke) {
3280 if (doStroke) {
3281 appearBuilder.setDrawColor(drawColor: da.getFontColor(), fill: false); // Border color: same as font color
3282 }
3283 appearBuilder.appendf(fmt: "{0:.2f} {0:.2f} {1:.2f} {2:.2f} re\n", borderWidth / 2, width - borderWidth, height - borderWidth);
3284 if (doFill) {
3285 appearBuilder.setDrawColor(drawColor: color.get(), fill: true);
3286 appearBuilder.append(text: doStroke ? "B\n" : "f\n");
3287 } else {
3288 appearBuilder.append(text: "S\n");
3289 }
3290 }
3291
3292 // Setup text clipping
3293 const double textmargin = borderWidth * 2;
3294 const double textwidth = width - 2 * textmargin;
3295 appearBuilder.appendf(fmt: "{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", textmargin, textwidth, height - 2 * textmargin);
3296
3297 std::unique_ptr<const GfxFont> font = nullptr;
3298
3299 // look for font name in the default resources
3300 Form *form = doc->getCatalog()->getForm(); // form is owned by catalog, no need to clean it up
3301
3302 Object resourceObj;
3303 if (form && form->getDefaultResourcesObj() && form->getDefaultResourcesObj()->isDict()) {
3304 resourceObj = form->getDefaultResourcesObj()->copy(); // No real copy, but increment refcount of /DR Dict
3305
3306 Dict *resDict = resourceObj.getDict();
3307 Object fontResources = resDict->lookup(key: "Font"); // The 'Font' subdictionary
3308
3309 if (!fontResources.isDict()) {
3310 error(category: errSyntaxWarning, pos: -1, msg: "Font subdictionary is not a dictionary");
3311 } else {
3312 // Get the font dictionary for the actual requested font
3313 Ref fontReference;
3314 Object fontDictionary = fontResources.getDict()->lookup(key: da.getFontName().getName(), returnRef: &fontReference);
3315
3316 if (fontDictionary.isDict()) {
3317 font = GfxFont::makeFont(xref: doc->getXRef(), tagA: da.getFontName().getName(), idA: fontReference, fontDict: fontDictionary.getDict());
3318 } else {
3319 error(category: errSyntaxWarning, pos: -1, msg: "Font dictionary is not a dictionary");
3320 }
3321 }
3322 }
3323
3324 // if fontname is not in the default resources, create a Helvetica fake font
3325 if (!font) {
3326 Dict *fontResDict = new Dict(doc->getXRef());
3327 resourceObj = Object(fontResDict);
3328 font = createAnnotDrawFont(xref: doc->getXRef(), fontParentDict: fontResDict, resourceName: da.getFontName().getName());
3329 }
3330
3331 // Set font state
3332 appearBuilder.setDrawColor(drawColor: da.getFontColor(), fill: true);
3333 appearBuilder.appendf(fmt: "BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin);
3334 const DrawMultiLineTextResult textCommands = drawMultiLineText(text: *contents, availableWidth: textwidth, form, font: *font, fontName: da.getFontName().getName(), fontSize: da.getFontPtSize(), quadding, borderWidth: 0 /*borderWidth*/);
3335 appearBuilder.append(text: textCommands.text.c_str());
3336 appearBuilder.append(text: "ET Q\n");
3337
3338 double bbox[4];
3339 bbox[0] = bbox[1] = 0;
3340 bbox[2] = rect->x2 - rect->x1;
3341 bbox[3] = rect->y2 - rect->y1;
3342
3343 Object newAppearance;
3344 if (ca == 1) {
3345 newAppearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDictObject: std::move(resourceObj));
3346 } else {
3347 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDictObject: std::move(resourceObj));
3348
3349 GooString appearBuf("/GS0 gs\n/Fm0 Do");
3350 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
3351 newAppearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
3352 }
3353 if (hasBeenUpdated) {
3354 // We should technically do this for all annots but AnnotFreeText
3355 // is particularly special since we're potentially embeddeing a font so we really need
3356 // to set the AP and not let other renderers guess it from the contents
3357 setNewAppearance(std::move(newAppearance));
3358 } else {
3359 appearance = std::move(newAppearance);
3360 }
3361}
3362
3363void AnnotFreeText::draw(Gfx *gfx, bool printing)
3364{
3365 if (!isVisible(printing)) {
3366 return;
3367 }
3368
3369 annotLocker();
3370 if (appearance.isNull()) {
3371 generateFreeTextAppearance();
3372 }
3373
3374 // draw the appearance stream
3375 Object obj = appearance.fetch(xref: gfx->getXRef());
3376 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
3377}
3378
3379// Before retrieving the res dict, regenerate the appearance stream if needed,
3380// because AnnotFreeText::draw needs to store font info in the res dict
3381Object AnnotFreeText::getAppearanceResDict()
3382{
3383 if (appearance.isNull()) {
3384 generateFreeTextAppearance();
3385 }
3386 return Annot::getAppearanceResDict();
3387}
3388
3389//------------------------------------------------------------------------
3390// AnnotLine
3391//------------------------------------------------------------------------
3392
3393AnnotLine::AnnotLine(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
3394{
3395 type = typeLine;
3396 annotObj.dictSet(key: "Subtype", val: Object(objName, "Line"));
3397
3398 initialize(docA, dict: annotObj.getDict());
3399}
3400
3401AnnotLine::AnnotLine(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
3402{
3403 type = typeLine;
3404 initialize(docA, dict: annotObj.getDict());
3405}
3406
3407AnnotLine::~AnnotLine() = default;
3408
3409void AnnotLine::initialize(PDFDoc *docA, Dict *dict)
3410{
3411 Object obj1;
3412
3413 obj1 = dict->lookup(key: "L");
3414 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
3415 const double x1 = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
3416 const double y1 = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
3417 const double x2 = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
3418 const double y2 = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 0);
3419
3420 coord1 = std::make_unique<AnnotCoord>(args: x1, args: y1);
3421 coord2 = std::make_unique<AnnotCoord>(args: x2, args: y2);
3422 } else {
3423 coord1 = std::make_unique<AnnotCoord>();
3424 coord2 = std::make_unique<AnnotCoord>();
3425 }
3426
3427 obj1 = dict->lookup(key: "LE");
3428 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
3429 Object obj2;
3430
3431 obj2 = obj1.arrayGet(i: 0);
3432 if (obj2.isName()) {
3433 GooString leName(obj2.getName());
3434 startStyle = parseAnnotLineEndingStyle(string: &leName);
3435 } else {
3436 startStyle = annotLineEndingNone;
3437 }
3438
3439 obj2 = obj1.arrayGet(i: 1);
3440 if (obj2.isName()) {
3441 GooString leName(obj2.getName());
3442 endStyle = parseAnnotLineEndingStyle(string: &leName);
3443 } else {
3444 endStyle = annotLineEndingNone;
3445 }
3446
3447 } else {
3448 startStyle = endStyle = annotLineEndingNone;
3449 }
3450
3451 obj1 = dict->lookup(key: "IC");
3452 if (obj1.isArray()) {
3453 interiorColor = std::make_unique<AnnotColor>(args: obj1.getArray());
3454 }
3455
3456 leaderLineLength = dict->lookup(key: "LL").getNumWithDefaultValue(defaultValue: 0);
3457
3458 leaderLineExtension = dict->lookup(key: "LLE").getNumWithDefaultValue(defaultValue: 0);
3459 if (leaderLineExtension < 0) {
3460 leaderLineExtension = 0;
3461 }
3462
3463 caption = dict->lookup(key: "Cap").getBoolWithDefaultValue(defaultValue: false);
3464
3465 obj1 = dict->lookup(key: "IT");
3466 if (obj1.isName()) {
3467 const char *intentName = obj1.getName();
3468
3469 if (!strcmp(s1: intentName, s2: "LineArrow")) {
3470 intent = intentLineArrow;
3471 } else if (!strcmp(s1: intentName, s2: "LineDimension")) {
3472 intent = intentLineDimension;
3473 } else {
3474 intent = intentLineArrow;
3475 }
3476 } else {
3477 intent = intentLineArrow;
3478 }
3479
3480 leaderLineOffset = dict->lookup(key: "LLO").getNumWithDefaultValue(defaultValue: 0);
3481 if (leaderLineOffset < 0) {
3482 leaderLineOffset = 0;
3483 }
3484
3485 obj1 = dict->lookup(key: "CP");
3486 if (obj1.isName()) {
3487 const char *captionName = obj1.getName();
3488
3489 if (!strcmp(s1: captionName, s2: "Inline")) {
3490 captionPos = captionPosInline;
3491 } else if (!strcmp(s1: captionName, s2: "Top")) {
3492 captionPos = captionPosTop;
3493 } else {
3494 captionPos = captionPosInline;
3495 }
3496 } else {
3497 captionPos = captionPosInline;
3498 }
3499
3500 obj1 = dict->lookup(key: "Measure");
3501 if (obj1.isDict()) {
3502 measure = nullptr;
3503 } else {
3504 measure = nullptr;
3505 }
3506
3507 obj1 = dict->lookup(key: "CO");
3508 if (obj1.isArray() && (obj1.arrayGetLength() == 2)) {
3509 captionTextHorizontal = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
3510 captionTextVertical = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
3511 } else {
3512 captionTextHorizontal = captionTextVertical = 0;
3513 }
3514
3515 obj1 = dict->lookup(key: "BS");
3516 if (obj1.isDict()) {
3517 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
3518 } else if (!border) {
3519 border = std::make_unique<AnnotBorderBS>();
3520 }
3521}
3522
3523void AnnotLine::setContents(std::unique_ptr<GooString> &&new_content)
3524{
3525 Annot::setContents(std::move(new_content));
3526 if (caption) {
3527 invalidateAppearance();
3528 }
3529}
3530
3531void AnnotLine::setVertices(double x1, double y1, double x2, double y2)
3532{
3533 coord1 = std::make_unique<AnnotCoord>(args&: x1, args&: y1);
3534 coord2 = std::make_unique<AnnotCoord>(args&: x2, args&: y2);
3535
3536 Array *lArray = new Array(doc->getXRef());
3537 lArray->add(elem: Object(x1));
3538 lArray->add(elem: Object(y1));
3539 lArray->add(elem: Object(x2));
3540 lArray->add(elem: Object(y2));
3541
3542 update(key: "L", value: Object(lArray));
3543 invalidateAppearance();
3544}
3545
3546void AnnotLine::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end)
3547{
3548 startStyle = start;
3549 endStyle = end;
3550
3551 Array *leArray = new Array(doc->getXRef());
3552 leArray->add(elem: Object(objName, convertAnnotLineEndingStyle(style: startStyle)));
3553 leArray->add(elem: Object(objName, convertAnnotLineEndingStyle(style: endStyle)));
3554
3555 update(key: "LE", value: Object(leArray));
3556 invalidateAppearance();
3557}
3558
3559void AnnotLine::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color)
3560{
3561 if (new_color) {
3562 Object obj1 = new_color->writeToObject(xref: doc->getXRef());
3563 update(key: "IC", value: std::move(obj1));
3564 interiorColor = std::move(new_color);
3565 } else {
3566 interiorColor = nullptr;
3567 }
3568 invalidateAppearance();
3569}
3570
3571void AnnotLine::setLeaderLineLength(double len)
3572{
3573 leaderLineLength = len;
3574 update(key: "LL", value: Object(len));
3575 invalidateAppearance();
3576}
3577
3578void AnnotLine::setLeaderLineExtension(double len)
3579{
3580 leaderLineExtension = len;
3581 update(key: "LLE", value: Object(len));
3582
3583 // LL is required if LLE is present
3584 update(key: "LL", value: Object(leaderLineLength));
3585 invalidateAppearance();
3586}
3587
3588void AnnotLine::setCaption(bool new_cap)
3589{
3590 caption = new_cap;
3591 update(key: "Cap", value: Object(new_cap));
3592 invalidateAppearance();
3593}
3594
3595void AnnotLine::setIntent(AnnotLineIntent new_intent)
3596{
3597 const char *intentName;
3598
3599 intent = new_intent;
3600 if (new_intent == intentLineArrow) {
3601 intentName = "LineArrow";
3602 } else { // intentLineDimension
3603 intentName = "LineDimension";
3604 }
3605 update(key: "IT", value: Object(objName, intentName));
3606}
3607
3608void AnnotLine::generateLineAppearance()
3609{
3610 double borderWidth, ca = opacity;
3611 bool fill = false;
3612
3613 appearBBox = std::make_unique<AnnotAppearanceBBox>(args: rect.get());
3614 AnnotAppearanceBuilder appearBuilder;
3615 appearBuilder.append(text: "q\n");
3616 if (color) {
3617 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
3618 }
3619 if (interiorColor) {
3620 appearBuilder.setDrawColor(drawColor: interiorColor.get(), fill: true);
3621 fill = true;
3622 }
3623 appearBuilder.setLineStyleForBorder(border.get());
3624 borderWidth = border->getWidth();
3625 appearBBox->setBorderWidth(std::max(a: 1., b: borderWidth));
3626
3627 const double x1 = coord1->getX();
3628 const double y1 = coord1->getY();
3629 const double x2 = coord2->getX();
3630 const double y2 = coord2->getY();
3631
3632 // Main segment length
3633 const double main_len = sqrt(x: (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
3634
3635 // Main segment becomes positive x direction, coord1 becomes (0,0)
3636 Matrix matr;
3637 const double angle = atan2(y: y2 - y1, x: x2 - x1);
3638 matr.m[0] = matr.m[3] = cos(x: angle);
3639 matr.m[1] = sin(x: angle);
3640 matr.m[2] = -matr.m[1];
3641 matr.m[4] = x1 - rect->x1;
3642 matr.m[5] = y1 - rect->y1;
3643
3644 double tx, ty, captionwidth = 0, captionheight = 0;
3645 AnnotLineCaptionPos actualCaptionPos = captionPos;
3646 const double fontsize = 9;
3647 const double captionhmargin = 2; // Left and right margin (inline caption only)
3648 const double captionmaxwidth = main_len - 2 * captionhmargin;
3649 const double lineendingSize = std::min(a: 6. * borderWidth, b: main_len / 2);
3650
3651 Dict *fontResDict;
3652 std::unique_ptr<const GfxFont> font;
3653
3654 // Calculate caption width and height
3655 if (caption) {
3656 fontResDict = new Dict(doc->getXRef());
3657 font = createAnnotDrawFont(xref: doc->getXRef(), fontParentDict: fontResDict);
3658 int lines = 0;
3659 int i = 0;
3660 while (i < contents->getLength()) {
3661 GooString out;
3662 double linewidth;
3663 layoutText(text: contents.get(), outBuf: &out, i: &i, font: *font, width: &linewidth, widthLimit: 0, charCount: nullptr, noReencode: false);
3664 linewidth *= fontsize;
3665 if (linewidth > captionwidth) {
3666 captionwidth = linewidth;
3667 }
3668 ++lines;
3669 }
3670 captionheight = lines * fontsize;
3671 // If text is longer than available space, turn into captionPosTop
3672 if (captionwidth > captionmaxwidth) {
3673 actualCaptionPos = captionPosTop;
3674 }
3675 } else {
3676 fontResDict = nullptr;
3677 font = nullptr;
3678 }
3679
3680 // Draw main segment
3681 matr.transform(x: AnnotAppearanceBuilder::lineEndingXShorten(endingStyle: startStyle, size: lineendingSize), y: leaderLineLength, tx: &tx, ty: &ty);
3682 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
3683 appearBBox->extendTo(x: tx, y: ty);
3684
3685 if (captionwidth != 0 && actualCaptionPos == captionPosInline) { // Break in the middle
3686 matr.transform(x: (main_len - captionwidth) / 2 - captionhmargin, y: leaderLineLength, tx: &tx, ty: &ty);
3687 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
3688
3689 matr.transform(x: (main_len + captionwidth) / 2 + captionhmargin, y: leaderLineLength, tx: &tx, ty: &ty);
3690 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
3691 }
3692
3693 matr.transform(x: main_len - AnnotAppearanceBuilder::lineEndingXShorten(endingStyle: endStyle, size: lineendingSize), y: leaderLineLength, tx: &tx, ty: &ty);
3694 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
3695 appearBBox->extendTo(x: tx, y: ty);
3696
3697 if (startStyle != annotLineEndingNone) {
3698 const double extendX { -AnnotAppearanceBuilder::lineEndingXExtendBBox(endingStyle: startStyle, size: lineendingSize) };
3699 appearBuilder.drawLineEnding(endingStyle: startStyle, x: 0, y: leaderLineLength, size: -lineendingSize, fill, m: matr);
3700 matr.transform(x: extendX, y: leaderLineLength + lineendingSize / 2., tx: &tx, ty: &ty);
3701 appearBBox->extendTo(x: tx, y: ty);
3702 matr.transform(x: extendX, y: leaderLineLength - lineendingSize / 2., tx: &tx, ty: &ty);
3703 appearBBox->extendTo(x: tx, y: ty);
3704 }
3705
3706 if (endStyle != annotLineEndingNone) {
3707 const double extendX { AnnotAppearanceBuilder::lineEndingXExtendBBox(endingStyle: endStyle, size: lineendingSize) };
3708 appearBuilder.drawLineEnding(endingStyle: endStyle, x: main_len, y: leaderLineLength, size: lineendingSize, fill, m: matr);
3709 matr.transform(x: main_len + extendX, y: leaderLineLength + lineendingSize / 2., tx: &tx, ty: &ty);
3710 appearBBox->extendTo(x: tx, y: ty);
3711 matr.transform(x: main_len + extendX, y: leaderLineLength - lineendingSize / 2., tx: &tx, ty: &ty);
3712 appearBBox->extendTo(x: tx, y: ty);
3713 }
3714
3715 // Draw caption text
3716 if (caption) {
3717 double tlx = (main_len - captionwidth) / 2, tly; // Top-left coords
3718 if (actualCaptionPos == captionPosInline) {
3719 tly = leaderLineLength + captionheight / 2;
3720 } else {
3721 tly = leaderLineLength + captionheight + 2 * borderWidth;
3722 }
3723
3724 tlx += captionTextHorizontal;
3725 tly += captionTextVertical;
3726
3727 // Adjust bounding box
3728 matr.transform(x: tlx, y: tly - captionheight, tx: &tx, ty: &ty);
3729 appearBBox->extendTo(x: tx, y: ty);
3730 matr.transform(x: tlx + captionwidth, y: tly - captionheight, tx: &tx, ty: &ty);
3731 appearBBox->extendTo(x: tx, y: ty);
3732 matr.transform(x: tlx + captionwidth, y: tly, tx: &tx, ty: &ty);
3733 appearBBox->extendTo(x: tx, y: ty);
3734 matr.transform(x: tlx, y: tly, tx: &tx, ty: &ty);
3735 appearBBox->extendTo(x: tx, y: ty);
3736
3737 // Setup text state (reusing transformed top-left coord)
3738 appearBuilder.appendf(fmt: "0 g BT /AnnotDrawFont {0:.2f} Tf\n", fontsize); // Font color: black
3739 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} Tm\n", matr.m[0], matr.m[1], matr.m[2], matr.m[3], tx, ty);
3740 appearBuilder.appendf(fmt: "0 {0:.2f} Td\n", -fontsize * font->getDescent());
3741 // Draw text
3742 int i = 0;
3743 double xposPrev = 0;
3744 while (i < contents->getLength()) {
3745 GooString out;
3746 double linewidth, xpos;
3747 layoutText(text: contents.get(), outBuf: &out, i: &i, font: *font, width: &linewidth, widthLimit: 0, charCount: nullptr, noReencode: false);
3748 linewidth *= fontsize;
3749 xpos = (captionwidth - linewidth) / 2;
3750 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize);
3751 appearBuilder.writeString(str: out.toStr());
3752 appearBuilder.append(text: "Tj\n");
3753 xposPrev = xpos;
3754 }
3755 appearBuilder.append(text: "ET\n");
3756 }
3757
3758 // Draw leader lines
3759 double ll_len = fabs(x: leaderLineLength) + leaderLineExtension;
3760 double sign = leaderLineLength >= 0 ? 1 : -1;
3761 if (ll_len != 0) {
3762 matr.transform(x: 0, y: 0, tx: &tx, ty: &ty);
3763 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
3764 appearBBox->extendTo(x: tx, y: ty);
3765 matr.transform(x: 0, y: sign * ll_len, tx: &tx, ty: &ty);
3766 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
3767 appearBBox->extendTo(x: tx, y: ty);
3768
3769 matr.transform(x: main_len, y: 0, tx: &tx, ty: &ty);
3770 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
3771 appearBBox->extendTo(x: tx, y: ty);
3772 matr.transform(x: main_len, y: sign * ll_len, tx: &tx, ty: &ty);
3773 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
3774 appearBBox->extendTo(x: tx, y: ty);
3775 }
3776
3777 appearBuilder.append(text: "Q\n");
3778
3779 double bbox[4];
3780 appearBBox->getBBoxRect(bbox);
3781 if (ca == 1) {
3782 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: fontResDict);
3783 } else {
3784 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: fontResDict);
3785
3786 GooString appearBuf("/GS0 gs\n/Fm0 Do");
3787 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
3788 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
3789 }
3790}
3791
3792void AnnotLine::draw(Gfx *gfx, bool printing)
3793{
3794 if (!isVisible(printing)) {
3795 return;
3796 }
3797
3798 annotLocker();
3799 if (appearance.isNull()) {
3800 generateLineAppearance();
3801 }
3802
3803 // draw the appearance stream
3804 Object obj = appearance.fetch(xref: gfx->getXRef());
3805 if (appearBBox) {
3806 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
3807 } else {
3808 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
3809 }
3810}
3811
3812// Before retrieving the res dict, regenerate the appearance stream if needed,
3813// because AnnotLine::draw may need to store font info in the res dict
3814Object AnnotLine::getAppearanceResDict()
3815{
3816 if (appearance.isNull()) {
3817 generateLineAppearance();
3818 }
3819 return Annot::getAppearanceResDict();
3820}
3821
3822//------------------------------------------------------------------------
3823// AnnotTextMarkup
3824//------------------------------------------------------------------------
3825AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA)
3826{
3827 switch (subType) {
3828 case typeHighlight:
3829 annotObj.dictSet(key: "Subtype", val: Object(objName, "Highlight"));
3830 break;
3831 case typeUnderline:
3832 annotObj.dictSet(key: "Subtype", val: Object(objName, "Underline"));
3833 break;
3834 case typeSquiggly:
3835 annotObj.dictSet(key: "Subtype", val: Object(objName, "Squiggly"));
3836 break;
3837 case typeStrikeOut:
3838 annotObj.dictSet(key: "Subtype", val: Object(objName, "StrikeOut"));
3839 break;
3840 default:
3841 assert(0 && "Invalid subtype for AnnotTextMarkup\n");
3842 }
3843
3844 // Store dummy quadrilateral with null coordinates
3845 Array *quadPoints = new Array(doc->getXRef());
3846 for (int i = 0; i < 4 * 2; ++i) {
3847 quadPoints->add(elem: Object(0.));
3848 }
3849 annotObj.dictSet(key: "QuadPoints", val: Object(quadPoints));
3850
3851 initialize(docA, dict: annotObj.getDict());
3852}
3853
3854AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
3855{
3856 // the real type will be read in initialize()
3857 type = typeHighlight;
3858 initialize(docA, dict: annotObj.getDict());
3859}
3860
3861AnnotTextMarkup::~AnnotTextMarkup() = default;
3862
3863void AnnotTextMarkup::initialize(PDFDoc *docA, Dict *dict)
3864{
3865 Object obj1;
3866
3867 obj1 = dict->lookup(key: "Subtype");
3868 if (obj1.isName()) {
3869 GooString typeName(obj1.getName());
3870 if (!typeName.cmp(sA: "Highlight")) {
3871 type = typeHighlight;
3872 } else if (!typeName.cmp(sA: "Underline")) {
3873 type = typeUnderline;
3874 } else if (!typeName.cmp(sA: "Squiggly")) {
3875 type = typeSquiggly;
3876 } else if (!typeName.cmp(sA: "StrikeOut")) {
3877 type = typeStrikeOut;
3878 }
3879 }
3880
3881 obj1 = dict->lookup(key: "QuadPoints");
3882 if (obj1.isArray()) {
3883 quadrilaterals = std::make_unique<AnnotQuadrilaterals>(args: obj1.getArray(), args: rect.get());
3884 } else {
3885 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Text Markup QuadPoints");
3886 ok = false;
3887 }
3888}
3889
3890void AnnotTextMarkup::setType(AnnotSubtype new_type)
3891{
3892 const char *typeName = nullptr; /* squelch bogus compiler warning */
3893
3894 switch (new_type) {
3895 case typeHighlight:
3896 typeName = "Highlight";
3897 break;
3898 case typeUnderline:
3899 typeName = "Underline";
3900 break;
3901 case typeSquiggly:
3902 typeName = "Squiggly";
3903 break;
3904 case typeStrikeOut:
3905 typeName = "StrikeOut";
3906 break;
3907 default:
3908 assert(!"Invalid subtype");
3909 }
3910
3911 type = new_type;
3912 update(key: "Subtype", value: Object(objName, typeName));
3913 invalidateAppearance();
3914}
3915
3916void AnnotTextMarkup::setQuadrilaterals(AnnotQuadrilaterals *quadPoints)
3917{
3918 Array *a = new Array(doc->getXRef());
3919
3920 for (int i = 0; i < quadPoints->getQuadrilateralsLength(); ++i) {
3921 a->add(elem: Object(quadPoints->getX1(quadrilateral: i)));
3922 a->add(elem: Object(quadPoints->getY1(quadrilateral: i)));
3923 a->add(elem: Object(quadPoints->getX2(quadrilateral: i)));
3924 a->add(elem: Object(quadPoints->getY2(quadrilateral: i)));
3925 a->add(elem: Object(quadPoints->getX3(quadrilateral: i)));
3926 a->add(elem: Object(quadPoints->getY3(quadrilateral: i)));
3927 a->add(elem: Object(quadPoints->getX4(quadrilateral: i)));
3928 a->add(elem: Object(quadPoints->getY4(quadrilateral: i)));
3929 }
3930
3931 quadrilaterals = std::make_unique<AnnotQuadrilaterals>(args&: a, args: rect.get());
3932
3933 annotObj.dictSet(key: "QuadPoints", val: Object(a));
3934 invalidateAppearance();
3935}
3936
3937bool AnnotTextMarkup::shouldCreateApperance(Gfx *gfx) const
3938{
3939 if (appearance.isNull()) {
3940 return true;
3941 }
3942
3943 // Adobe Reader seems to have a complex condition for when to use the
3944 // appearance stream of typeHighlight, which is "use it if it has a Resources dictionary with ExtGState"
3945 // this is reverse engineering of me editing a file by hand and checking what it does so the real
3946 // condition may be more or less complex
3947 if (type == typeHighlight) {
3948 XRef *xref = gfx->getXRef();
3949 const Object fetchedApperance = appearance.fetch(xref);
3950 if (fetchedApperance.isStream()) {
3951 const Object resources = fetchedApperance.streamGetDict()->lookup(key: "Resources");
3952 if (resources.isDict()) {
3953 if (resources.dictLookup(key: "ExtGState").isDict()) {
3954 return false;
3955 }
3956 }
3957 }
3958 return true;
3959 }
3960
3961 return false;
3962}
3963
3964void AnnotTextMarkup::draw(Gfx *gfx, bool printing)
3965{
3966 double ca = 1;
3967 int i;
3968
3969 if (!isVisible(printing)) {
3970 return;
3971 }
3972
3973 annotLocker();
3974 if (shouldCreateApperance(gfx)) {
3975 bool blendMultiply = true;
3976 ca = opacity;
3977
3978 AnnotAppearanceBuilder appearBuilder;
3979 appearBuilder.append(text: "q\n");
3980
3981 /* Adjust BBox */
3982 appearBBox = std::make_unique<AnnotAppearanceBBox>(args: rect.get());
3983 for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
3984 appearBBox->extendTo(x: quadrilaterals->getX1(quadrilateral: i) - rect->x1, y: quadrilaterals->getY1(quadrilateral: i) - rect->y1);
3985 appearBBox->extendTo(x: quadrilaterals->getX2(quadrilateral: i) - rect->x1, y: quadrilaterals->getY2(quadrilateral: i) - rect->y1);
3986 appearBBox->extendTo(x: quadrilaterals->getX3(quadrilateral: i) - rect->x1, y: quadrilaterals->getY3(quadrilateral: i) - rect->y1);
3987 appearBBox->extendTo(x: quadrilaterals->getX4(quadrilateral: i) - rect->x1, y: quadrilaterals->getY4(quadrilateral: i) - rect->y1);
3988 }
3989
3990 switch (type) {
3991 case typeUnderline:
3992 if (color) {
3993 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
3994 }
3995 appearBuilder.append(text: "[] 0 d 1 w\n");
3996 // use a borderwidth, which is consistent with the line width
3997 appearBBox->setBorderWidth(1.0);
3998
3999 for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
4000 double x3, y3, x4, y4;
4001
4002 x3 = quadrilaterals->getX3(quadrilateral: i);
4003 y3 = quadrilaterals->getY3(quadrilateral: i);
4004 x4 = quadrilaterals->getX4(quadrilateral: i);
4005 y4 = quadrilaterals->getY4(quadrilateral: i);
4006
4007 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", x3, y3);
4008 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", x4, y4);
4009 appearBuilder.append(text: "S\n");
4010 }
4011 break;
4012 case typeStrikeOut:
4013 if (color) {
4014 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
4015 }
4016 blendMultiply = false;
4017 appearBuilder.append(text: "[] 0 d 1 w\n");
4018
4019 for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
4020 double x1, y1, x2, y2;
4021 double x3, y3, x4, y4;
4022
4023 x1 = quadrilaterals->getX1(quadrilateral: i);
4024 y1 = quadrilaterals->getY1(quadrilateral: i);
4025 x2 = quadrilaterals->getX2(quadrilateral: i);
4026 y2 = quadrilaterals->getY2(quadrilateral: i);
4027
4028 x3 = quadrilaterals->getX3(quadrilateral: i);
4029 y3 = quadrilaterals->getY3(quadrilateral: i);
4030 x4 = quadrilaterals->getX4(quadrilateral: i);
4031 y4 = quadrilaterals->getY4(quadrilateral: i);
4032
4033 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", (x1 + x3) / 2., (y1 + y3) / 2.);
4034 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", (x2 + x4) / 2., (y2 + y4) / 2.);
4035 appearBuilder.append(text: "S\n");
4036 }
4037 break;
4038 case typeSquiggly:
4039 if (color) {
4040 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
4041 }
4042 appearBuilder.append(text: "[] 0 d 1 w\n");
4043
4044 for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
4045 double x1, y1, x2, y3;
4046 double h6;
4047
4048 x1 = quadrilaterals->getX1(quadrilateral: i);
4049 y1 = quadrilaterals->getY1(quadrilateral: i);
4050 x2 = quadrilaterals->getX2(quadrilateral: i);
4051 y3 = quadrilaterals->getY3(quadrilateral: i);
4052 h6 = (y1 - y3) / 6.0;
4053
4054 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", x1, y3 + h6);
4055 bool down = false;
4056 do {
4057 down = !down; // Zigzag line
4058 x1 += 2;
4059 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", x1, y3 + (down ? 0 : h6));
4060 } while (x1 < x2);
4061 appearBuilder.append(text: "S\n");
4062 }
4063 break;
4064 default:
4065 case typeHighlight:
4066 if (color) {
4067 appearBuilder.setDrawColor(drawColor: color.get(), fill: true);
4068 }
4069
4070 double biggestBorder = 0;
4071 for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
4072 double x1, y1, x2, y2, x3, y3, x4, y4;
4073 double h4;
4074
4075 x1 = quadrilaterals->getX1(quadrilateral: i);
4076 y1 = quadrilaterals->getY1(quadrilateral: i);
4077 x2 = quadrilaterals->getX2(quadrilateral: i);
4078 y2 = quadrilaterals->getY2(quadrilateral: i);
4079 x3 = quadrilaterals->getX3(quadrilateral: i);
4080 y3 = quadrilaterals->getY3(quadrilateral: i);
4081 x4 = quadrilaterals->getX4(quadrilateral: i);
4082 y4 = quadrilaterals->getY4(quadrilateral: i);
4083 h4 = fabs(x: y1 - y3) / 4.0;
4084
4085 if (h4 > biggestBorder) {
4086 biggestBorder = h4;
4087 }
4088
4089 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", x3, y3);
4090 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", x3 - h4, y3 + h4, x1 - h4, y1 - h4, x1, y1);
4091 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", x2, y2);
4092 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", x2 + h4, y2 - h4, x4 + h4, y4 + h4, x4, y4);
4093 appearBuilder.append(text: "f\n");
4094 }
4095 appearBBox->setBorderWidth(biggestBorder);
4096 break;
4097 }
4098 appearBuilder.append(text: "Q\n");
4099
4100 double bbox[4];
4101 bbox[0] = appearBBox->getPageXMin();
4102 bbox[1] = appearBBox->getPageYMin();
4103 bbox[2] = appearBBox->getPageXMax();
4104 bbox[3] = appearBBox->getPageYMax();
4105 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
4106
4107 GooString appearBuf("/GS0 gs\n/Fm0 Do");
4108 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: 1, blendMode: blendMultiply ? "Multiply" : nullptr);
4109 if (ca == 1) {
4110 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
4111 } else {
4112 aStream = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: true, resDict);
4113
4114 Dict *resDict2 = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
4115 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict: resDict2);
4116 }
4117 }
4118
4119 // draw the appearance stream
4120 Object obj = appearance.fetch(xref: gfx->getXRef());
4121 if (appearBBox) {
4122 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
4123 } else {
4124 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
4125 }
4126}
4127
4128//------------------------------------------------------------------------
4129// AnnotWidget
4130//------------------------------------------------------------------------
4131
4132AnnotWidget::AnnotWidget(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
4133{
4134 type = typeWidget;
4135 field = nullptr;
4136 initialize(docA, dict: annotObj.getDict());
4137}
4138
4139AnnotWidget::AnnotWidget(PDFDoc *docA, Object *dictObject, Object *obj, FormField *fieldA) : Annot(docA, dictObject->copy(), obj)
4140{
4141 type = typeWidget;
4142 field = fieldA;
4143 initialize(docA, dict: dictObject->getDict());
4144}
4145
4146AnnotWidget::~AnnotWidget() = default;
4147
4148void AnnotWidget::initialize(PDFDoc *docA, Dict *dict)
4149{
4150 Object obj1;
4151
4152 form = doc->getCatalog()->getForm();
4153
4154 obj1 = dict->lookup(key: "H");
4155 if (obj1.isName()) {
4156 const char *modeName = obj1.getName();
4157
4158 if (!strcmp(s1: modeName, s2: "N")) {
4159 mode = highlightModeNone;
4160 } else if (!strcmp(s1: modeName, s2: "O")) {
4161 mode = highlightModeOutline;
4162 } else if (!strcmp(s1: modeName, s2: "P") || !strcmp(s1: modeName, s2: "T")) {
4163 mode = highlightModePush;
4164 } else {
4165 mode = highlightModeInvert;
4166 }
4167 } else {
4168 mode = highlightModeInvert;
4169 }
4170
4171 obj1 = dict->lookup(key: "MK");
4172 if (obj1.isDict()) {
4173 appearCharacs = std::make_unique<AnnotAppearanceCharacs>(args: obj1.getDict());
4174 }
4175
4176 obj1 = dict->lookup(key: "A");
4177 if (obj1.isDict()) {
4178 action = LinkAction::parseAction(obj: &obj1, baseURI: doc->getCatalog()->getBaseURI());
4179 }
4180
4181 additionalActions = dict->lookupNF(key: "AA").copy();
4182
4183 obj1 = dict->lookup(key: "Parent");
4184 if (obj1.isDict()) {
4185 parent = nullptr;
4186 } else {
4187 parent = nullptr;
4188 }
4189
4190 obj1 = dict->lookup(key: "BS");
4191 if (obj1.isDict()) {
4192 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
4193 }
4194
4195 updatedAppearanceStream = Ref::INVALID();
4196}
4197
4198std::unique_ptr<LinkAction> AnnotWidget::getAdditionalAction(AdditionalActionsType additionalActionType)
4199{
4200 return ::getAdditionalAction(type: additionalActionType, additionalActions: &additionalActions, doc);
4201}
4202
4203std::unique_ptr<LinkAction> AnnotWidget::getFormAdditionalAction(FormAdditionalActionsType formAdditionalActionType)
4204{
4205 Object additionalActionsObject = additionalActions.fetch(xref: doc->getXRef());
4206
4207 if (additionalActionsObject.isDict()) {
4208 const char *key = getFormAdditionalActionKey(type: formAdditionalActionType);
4209
4210 Object actionObject = additionalActionsObject.dictLookup(key);
4211 if (actionObject.isDict()) {
4212 return LinkAction::parseAction(obj: &actionObject, baseURI: doc->getCatalog()->getBaseURI());
4213 }
4214 }
4215
4216 return nullptr;
4217}
4218
4219bool AnnotWidget::setFormAdditionalAction(FormAdditionalActionsType formAdditionalActionType, const std::string &js)
4220{
4221 Object additionalActionsObject = additionalActions.fetch(xref: doc->getXRef());
4222
4223 if (!additionalActionsObject.isDict()) {
4224 additionalActionsObject = Object(new Dict(doc->getXRef()));
4225 annotObj.dictSet(key: "AA", val: additionalActionsObject.copy());
4226 }
4227
4228 additionalActionsObject.dictSet(key: getFormAdditionalActionKey(type: formAdditionalActionType), val: LinkJavaScript::createObject(xref: doc->getXRef(), js));
4229
4230 if (additionalActions.isRef()) {
4231 doc->getXRef()->setModifiedObject(o: &additionalActionsObject, r: additionalActions.getRef());
4232 } else if (hasRef) {
4233 doc->getXRef()->setModifiedObject(o: &annotObj, r: ref);
4234 } else {
4235 error(category: errInternal, pos: -1, msg: "AnnotWidget::setFormAdditionalAction, where neither additionalActions is ref nor annotobj itself is ref");
4236 return false;
4237 }
4238 return true;
4239}
4240
4241// Grand unified handler for preparing text strings to be drawn into form
4242// fields. Takes as input a text string (in PDFDocEncoding or UTF-16).
4243// Converts some or all of this string to the appropriate encoding for the
4244// specified font, and computes the width of the text. Can optionally stop
4245// converting when a specified width has been reached, to perform line-breaking
4246// for multi-line fields.
4247//
4248// Parameters:
4249// text: input text string to convert
4250// outBuf: buffer for writing re-encoded string
4251// i: index at which to start converting; will be updated to point just after
4252// last character processed
4253// font: the font which will be used for display
4254// width: computed width (unscaled by font size) will be stored here
4255// widthLimit: if non-zero, stop converting to keep width under this value
4256// (should be scaled down by font size)
4257// charCount: count of number of characters will be stored here
4258// noReencode: if set, do not try to translate the character encoding
4259// (useful for Zapf Dingbats or other unusual encodings)
4260// can only be used with simple fonts, not CID-keyed fonts
4261//
4262// TODO: Handle surrogate pairs in UTF-16.
4263// Should be able to generate output for any CID-keyed font.
4264// Doesn't handle vertical fonts--should it?
4265void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont &font, double *width, double widthLimit, int *charCount, bool noReencode, bool *newFontNeeded)
4266{
4267 CharCode c;
4268 Unicode uChar;
4269 const Unicode *uAux;
4270 double w = 0.0;
4271 int uLen, n;
4272 double dx, dy, ox, oy;
4273
4274 if (newFontNeeded) {
4275 *newFontNeeded = false;
4276 }
4277 if (width != nullptr) {
4278 *width = 0.0;
4279 }
4280 if (charCount != nullptr) {
4281 *charCount = 0;
4282 }
4283
4284 if (!text) {
4285 return;
4286 }
4287 bool unicode = hasUnicodeByteOrderMark(s: text->toStr());
4288 bool spacePrev; // previous character was a space
4289
4290 // State for backtracking when more text has been processed than fits within
4291 // widthLimit. We track for both input (text) and output (outBuf) the offset
4292 // to the first character to discard.
4293 //
4294 // We keep track of several points:
4295 // 1 - end of previous completed word which fits
4296 // 2 - previous character which fit
4297 int last_i1, last_i2, last_o1, last_o2;
4298
4299 if (unicode && text->getLength() % 2 != 0) {
4300 error(category: errSyntaxError, pos: -1, msg: "AnnotWidget::layoutText, bad unicode string");
4301 return;
4302 }
4303
4304 // skip Unicode marker on string if needed
4305 if (unicode && *i == 0) {
4306 *i = 2;
4307 }
4308
4309 // Start decoding and copying characters, until either:
4310 // we reach the end of the string
4311 // we reach the maximum width
4312 // we reach a newline character
4313 // As we copy characters, keep track of the last full word to fit, so that we
4314 // can backtrack if we exceed the maximum width.
4315 last_i1 = last_i2 = *i;
4316 last_o1 = last_o2 = 0;
4317 spacePrev = false;
4318 outBuf->clear();
4319
4320 while (*i < text->getLength()) {
4321 last_i2 = *i;
4322 last_o2 = outBuf->getLength();
4323
4324 if (unicode) {
4325 uChar = (unsigned char)(text->getChar(i: *i)) << 8;
4326 uChar += (unsigned char)(text->getChar(i: *i + 1));
4327 *i += 2;
4328 } else {
4329 if (noReencode) {
4330 uChar = text->getChar(i: *i) & 0xff;
4331 } else {
4332 uChar = pdfDocEncoding[text->getChar(i: *i) & 0xff];
4333 }
4334 *i += 1;
4335 }
4336
4337 // Explicit line break?
4338 if (uChar == '\r' || uChar == '\n') {
4339 // Treat a <CR><LF> sequence as a single line break
4340 if (uChar == '\r' && *i < text->getLength()) {
4341 if (unicode && text->getChar(i: *i) == '\0' && text->getChar(i: *i + 1) == '\n') {
4342 *i += 2;
4343 } else if (!unicode && text->getChar(i: *i) == '\n') {
4344 *i += 1;
4345 }
4346 }
4347
4348 break;
4349 }
4350
4351 if (noReencode) {
4352 outBuf->append(c: uChar);
4353 } else {
4354 const CharCodeToUnicode *ccToUnicode = font.getToUnicode();
4355 if (!ccToUnicode) {
4356 // This assumes an identity CMap.
4357 outBuf->append(c: (uChar >> 8) & 0xff);
4358 outBuf->append(c: uChar & 0xff);
4359 } else if (ccToUnicode->mapToCharCode(u: &uChar, c: &c, usize: 1)) {
4360 if (font.isCIDFont()) {
4361 auto cidFont = static_cast<const GfxCIDFont *>(&font);
4362 if (c < cidFont->getCIDToGIDLen()) {
4363 const int glyph = cidFont->getCIDToGID()[c];
4364 if (glyph > 0 || c == 0) {
4365 outBuf->append(c: (c >> 8) & 0xff);
4366 outBuf->append(c: c & 0xff);
4367 } else {
4368 if (newFontNeeded) {
4369 *newFontNeeded = true;
4370 *i -= unicode ? 2 : 1;
4371 break;
4372 }
4373 outBuf->append(c: (c >> 8) & 0xff);
4374 outBuf->append(c: c & 0xff);
4375 error(category: errSyntaxError, pos: -1, msg: "AnnotWidget::layoutText, font doesn't have glyph for charcode U+{0:04uX}", c);
4376 }
4377 } else {
4378 // TODO: This assumes an identity CMap. It should be extended to
4379 // handle the general case.
4380 outBuf->append(c: (c >> 8) & 0xff);
4381 outBuf->append(c: c & 0xff);
4382 }
4383 } else {
4384 // 8-bit font
4385 outBuf->append(c);
4386 }
4387 } else {
4388 if (newFontNeeded) {
4389 *newFontNeeded = true;
4390 *i -= unicode ? 2 : 1;
4391 break;
4392 } else {
4393 error(category: errSyntaxError, pos: -1, msg: "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar);
4394 }
4395 }
4396 }
4397
4398 // If we see a space, then we have a linebreak opportunity.
4399 if (uChar == ' ') {
4400 last_i1 = *i;
4401 if (!spacePrev) {
4402 last_o1 = last_o2;
4403 }
4404 spacePrev = true;
4405 } else {
4406 spacePrev = false;
4407 }
4408
4409 // Compute width of character just output
4410 if (outBuf->getLength() > last_o2) {
4411 dx = 0.0;
4412 font.getNextChar(s: outBuf->c_str() + last_o2, len: outBuf->getLength() - last_o2, code: &c, u: &uAux, uLen: &uLen, dx: &dx, dy: &dy, ox: &ox, oy: &oy);
4413 w += dx;
4414 }
4415
4416 // Current line over-full now?
4417 if (widthLimit > 0.0 && w > widthLimit) {
4418 if (last_o1 > 0) {
4419 // Back up to the previous word which fit, if there was a previous
4420 // word.
4421 *i = last_i1;
4422 outBuf->del(i: last_o1, n: outBuf->getLength() - last_o1);
4423 } else if (last_o2 > 0) {
4424 // Otherwise, back up to the previous character (in the only word on
4425 // this line)
4426 *i = last_i2;
4427 outBuf->del(i: last_o2, n: outBuf->getLength() - last_o2);
4428 } else {
4429 // Otherwise, we were trying to fit the first character; include it
4430 // anyway even if it overflows the space--no updates to make.
4431 }
4432 break;
4433 }
4434 }
4435
4436 // If splitting input into lines because we reached the width limit, then
4437 // consume any remaining trailing spaces that would go on this line from the
4438 // input. If in doing so we reach a newline, consume that also. This code
4439 // won't run if we stopped at a newline above, since in that case w <=
4440 // widthLimit still.
4441 if (widthLimit > 0.0 && w > widthLimit) {
4442 if (unicode) {
4443 while (*i < text->getLength() && text->getChar(i: *i) == '\0' && text->getChar(i: *i + 1) == ' ') {
4444 *i += 2;
4445 }
4446 if (*i < text->getLength() && text->getChar(i: *i) == '\0' && text->getChar(i: *i + 1) == '\r') {
4447 *i += 2;
4448 }
4449 if (*i < text->getLength() && text->getChar(i: *i) == '\0' && text->getChar(i: *i + 1) == '\n') {
4450 *i += 2;
4451 }
4452 } else {
4453 while (*i < text->getLength() && text->getChar(i: *i) == ' ') {
4454 *i += 1;
4455 }
4456 if (*i < text->getLength() && text->getChar(i: *i) == '\r') {
4457 *i += 1;
4458 }
4459 if (*i < text->getLength() && text->getChar(i: *i) == '\n') {
4460 *i += 1;
4461 }
4462 }
4463 }
4464
4465 // Compute the actual width and character count of the final string, based on
4466 // breakpoint, if this information is requested by the caller.
4467 if (width != nullptr || charCount != nullptr) {
4468 const char *s = outBuf->c_str();
4469 int len = outBuf->getLength();
4470
4471 while (len > 0) {
4472 dx = 0.0;
4473 n = font.getNextChar(s, len, code: &c, u: &uAux, uLen: &uLen, dx: &dx, dy: &dy, ox: &ox, oy: &oy);
4474
4475 if (n == 0) {
4476 break;
4477 }
4478
4479 if (width != nullptr) {
4480 *width += dx;
4481 }
4482 if (charCount != nullptr) {
4483 *charCount += 1;
4484 }
4485
4486 s += n;
4487 len -= n;
4488 }
4489 }
4490}
4491
4492// Copy the given string to appearBuf, adding parentheses around it and
4493// escaping characters as appropriate.
4494void AnnotAppearanceBuilder::writeString(const std::string &str)
4495{
4496 appearBuf->append(c: '(');
4497
4498 for (const char c : str) {
4499 if (c == '(' || c == ')' || c == '\\') {
4500 appearBuf->append(c: '\\');
4501 appearBuf->append(c);
4502 } else if (c < 0x20) {
4503 appearBuf->appendf(fmt: "\\{0:03o}", (unsigned char)c);
4504 } else {
4505 appearBuf->append(c);
4506 }
4507 }
4508
4509 appearBuf->append(c: ')');
4510}
4511
4512// Draw the variable text or caption for a field.
4513bool AnnotAppearanceBuilder::drawText(const GooString *text, const Form *form, const GooString *da, const GfxResources *resources, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect,
4514 const VariableTextQuadding quadding, XRef *xref, Dict *resourcesDict, const int flags, const int nCombs)
4515{
4516 const bool forceZapfDingbats = flags & ForceZapfDingbatsDrawTextFlag;
4517
4518 std::vector<std::string> daToks;
4519 const GfxFont *font;
4520 double fontSize;
4521 int tfPos, tmPos;
4522 bool freeText = false; // true if text should be freed before return
4523 std::unique_ptr<const GfxFont> fontToFree = nullptr;
4524
4525 //~ if there is no MK entry, this should use the existing content stream,
4526 //~ and only replace the marked content portion of it
4527 //~ (this is only relevant for Tx fields)
4528
4529 // parse the default appearance string
4530 tfPos = tmPos = -1;
4531 if (da) {
4532 FormFieldText::tokenizeDA(daString: da->toStr(), daToks: &daToks, searchTok: nullptr /*searchTok*/);
4533 for (int i = 2; i < (int)daToks.size(); ++i) {
4534 if (i >= 2 && daToks[i] == "Tf") {
4535 tfPos = i - 2;
4536 } else if (i >= 6 && daToks[i] == "Tm") {
4537 tmPos = i - 6;
4538 }
4539 }
4540 }
4541
4542 // get the font and font size
4543 font = nullptr;
4544 fontSize = 0;
4545 if (tfPos >= 0) {
4546 std::string &tok = daToks[tfPos];
4547 if (forceZapfDingbats) {
4548 assert(xref != nullptr);
4549 if (tok != "/ZaDb") {
4550 tok = "/ZaDb";
4551 }
4552 }
4553 if (tok.size() >= 1 && tok[0] == '/') {
4554 if (!resources || !(font = resources->lookupFont(name: tok.c_str() + 1).get())) {
4555 if (xref != nullptr && resourcesDict != nullptr) {
4556 const char *fallback = determineFallbackFont(tok, defaultFallback: forceZapfDingbats ? "ZapfDingbats" : "Helvetica");
4557 // The font variable sometimes points to an object that needs to be deleted
4558 // and sometimes not, depending on whether the call to lookupFont above fails.
4559 // When the code path right here is taken, the destructor of fontToFree
4560 // (which is a std::unique_ptr) will delete the font object at the end of this method.
4561 fontToFree = createAnnotDrawFont(xref, fontParentDict: resourcesDict, resourceName: tok.c_str() + 1, fontname: fallback);
4562 font = fontToFree.get();
4563 } else {
4564 error(category: errSyntaxError, pos: -1, msg: "Unknown font in field's DA string");
4565 }
4566 }
4567 } else {
4568 error(category: errSyntaxError, pos: -1, msg: "Invalid font name in 'Tf' operator in field's DA string");
4569 }
4570 fontSize = gatof(nptr: daToks[tfPos + 1].c_str());
4571 } else {
4572 error(category: errSyntaxError, pos: -1, msg: "Missing 'Tf' operator in field's DA string");
4573 }
4574 if (!font) {
4575 return false;
4576 }
4577
4578 if (tmPos < 0) {
4579 // Add fake Tm to the DA tokens
4580 tmPos = daToks.size();
4581 daToks.insert(position: daToks.end(), l: { "1", "0", "0", "1", "0", "0", "Tm" });
4582 }
4583
4584 // get the border width
4585 const double borderWidth = border ? border->getWidth() : 0;
4586
4587 // for a password field, replace all characters with asterisks
4588 if (flags & TurnTextToStarsDrawTextFlag) {
4589 int len;
4590 if (hasUnicodeByteOrderMark(s: text->toStr())) {
4591 len = (text->getLength() - 2) / 2;
4592 } else {
4593 len = text->getLength();
4594 }
4595
4596 GooString *newText = new GooString;
4597 for (int i = 0; i < len; ++i) {
4598 newText->append(c: '*');
4599 }
4600 text = newText;
4601 freeText = true;
4602 }
4603
4604 // setup
4605 if (flags & EmitMarkedContentDrawTextFlag) {
4606 appearBuf->append(str: "/Tx BMC\n");
4607 }
4608 appearBuf->append(str: "q\n");
4609 auto calculateDxDy = [this, appearCharacs, rect]() -> std::tuple<double, double> {
4610 const int rot = appearCharacs ? appearCharacs->getRotation() : 0;
4611 switch (rot) {
4612 case 90:
4613 appearBuf->appendf(fmt: "0 1 -1 0 {0:.2f} 0 cm\n", rect->x2 - rect->x1);
4614 return { rect->y2 - rect->y1, rect->x2 - rect->x1 };
4615
4616 case 180:
4617 appearBuf->appendf(fmt: "-1 0 0 -1 {0:.2f} {1:.2f} cm\n", rect->x2 - rect->x1, rect->y2 - rect->y1);
4618 return { rect->x2 - rect->y2, rect->y2 - rect->y1 };
4619
4620 case 270:
4621 appearBuf->appendf(fmt: "0 -1 1 0 0 {0:.2f} cm\n", rect->y2 - rect->y1);
4622 return { rect->y2 - rect->y1, rect->x2 - rect->x1 };
4623
4624 default: // assume rot == 0
4625 return { rect->x2 - rect->x1, rect->y2 - rect->y1 };
4626 }
4627 };
4628 const auto dxdy = calculateDxDy();
4629 const double dx = std::get<0>(t: dxdy);
4630 const double dy = std::get<1>(t: dxdy);
4631 appearBuf->append(str: "BT\n");
4632 // multi-line text
4633 if (flags & MultilineDrawTextFlag) {
4634 // note: comb is ignored in multiline mode as mentioned in the spec
4635
4636 const double wMax = dx - 2 * borderWidth - 4;
4637
4638 // compute font autosize
4639 if (fontSize == 0) {
4640 fontSize = Annot::calculateFontSize(form, font, text, wMax, hMax: dy, forceZapfDingbats);
4641 daToks[tfPos + 1] = GooString().appendf(fmt: "{0:.2f}", fontSize)->toStr();
4642 }
4643
4644 // starting y coordinate
4645 // (note: each line of text starts with a Td operator that moves
4646 // down a line)
4647 const double y = dy - 3;
4648
4649 // set the font matrix
4650 daToks[tmPos + 4] = "0";
4651 daToks[tmPos + 5] = GooString().appendf(fmt: "{0:.2f}", y)->toStr();
4652
4653 // write the DA string
4654 for (const std::string &daTok : daToks) {
4655 appearBuf->append(str: daTok)->append(c: ' ');
4656 }
4657
4658 const DrawMultiLineTextResult textCommands = drawMultiLineText(text: *text, availableWidth: dx, form, font: *font, fontName: std::string(), fontSize, quadding, borderWidth: borderWidth + 2);
4659 appearBuf->append(str: textCommands.text);
4660
4661 // single-line text
4662 } else {
4663 //~ replace newlines with spaces? - what does Acrobat do?
4664
4665 // comb formatting
4666 if (nCombs > 0) {
4667 // compute comb spacing
4668 const double w = (dx - 2 * borderWidth) / nCombs;
4669
4670 // compute font autosize
4671 if (fontSize == 0) {
4672 fontSize = dy - 2 * borderWidth;
4673 if (w < fontSize) {
4674 fontSize = w;
4675 }
4676 fontSize = floor(x: fontSize);
4677 daToks[tfPos + 1] = GooString().appendf(fmt: "{0:.2f}", fontSize)->toStr();
4678 }
4679
4680 const HorizontalTextLayouter textLayouter(text, form, font, {}, forceZapfDingbats);
4681
4682 const int charCount = std::min(a: textLayouter.totalCharCount(), b: nCombs);
4683
4684 // compute starting text cell
4685 auto calculateX = [quadding, borderWidth, nCombs, charCount, w] {
4686 switch (quadding) {
4687 case VariableTextQuadding::leftJustified:
4688 default:
4689 return borderWidth;
4690 case VariableTextQuadding::centered:
4691 return borderWidth + (nCombs - charCount) / 2.0 * w;
4692 case VariableTextQuadding::rightJustified:
4693 return borderWidth + (nCombs - charCount) * w;
4694 }
4695 };
4696 const double x = calculateX();
4697 const double y = 0.5 * dy - 0.4 * fontSize;
4698
4699 // set the font matrix
4700 daToks[tmPos + 4] = GooString().appendf(fmt: "{0:.2f}", x)->toStr();
4701 daToks[tmPos + 5] = GooString().appendf(fmt: "{0:.2f}", y)->toStr();
4702
4703 // write the DA string
4704 for (const std::string &daTok : daToks) {
4705 appearBuf->append(str: daTok)->append(c: ' ');
4706 }
4707
4708 // write the text string
4709 int i = 0;
4710 double xPrev = w; // so that first character is placed properly
4711 for (const HorizontalTextLayouter::Data &d : textLayouter.data) {
4712 const char *s = d.text.c_str();
4713 int len = d.text.size();
4714 while (i < nCombs && len > 0) {
4715 CharCode code;
4716 const Unicode *uAux;
4717 int uLen, n;
4718 double char_dx, char_dy, ox, oy;
4719
4720 const GfxFont *currentFont = font;
4721 if (!d.fontName.empty()) {
4722 appearBuf->append(str: " q\n");
4723 appearBuf->appendf(fmt: "/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize);
4724 currentFont = form->getDefaultResources()->lookupFont(name: d.fontName.c_str()).get();
4725 }
4726
4727 char_dx = 0.0;
4728 n = currentFont->getNextChar(s, len, code: &code, u: &uAux, uLen: &uLen, dx: &char_dx, dy: &char_dy, ox: &ox, oy: &oy);
4729 char_dx *= fontSize;
4730
4731 // center each character within its cell, by advancing the text
4732 // position the appropriate amount relative to the start of the
4733 // previous character
4734 const double combX = 0.5 * (w - char_dx);
4735 appearBuf->appendf(fmt: "{0:.2f} 0 Td\n", combX - xPrev + w);
4736
4737 GooString charBuf(s, n);
4738 writeString(str: charBuf.toStr());
4739 appearBuf->append(str: " Tj\n");
4740
4741 if (!d.fontName.empty()) {
4742 appearBuf->append(str: " Q\n");
4743 }
4744
4745 i++;
4746 s += n;
4747 len -= n;
4748 xPrev = combX;
4749 }
4750 }
4751
4752 // regular (non-comb) formatting
4753 } else {
4754 const HorizontalTextLayouter textLayouter(text, form, font, {}, forceZapfDingbats);
4755
4756 const double usedWidthUnscaled = textLayouter.totalWidth();
4757
4758 // compute font autosize
4759 if (fontSize == 0) {
4760 fontSize = dy - 2 * borderWidth;
4761 if (usedWidthUnscaled > 0) {
4762 const double fontSize2 = (dx - 4 - 2 * borderWidth) / usedWidthUnscaled;
4763 if (fontSize2 < fontSize) {
4764 fontSize = fontSize2;
4765 }
4766 }
4767 fontSize = floor(x: fontSize);
4768 daToks[tfPos + 1] = GooString().appendf(fmt: "{0:.2f}", fontSize)->toStr();
4769 }
4770
4771 // compute text start position
4772 const double usedWidth = usedWidthUnscaled * fontSize;
4773 auto calculateX = [quadding, borderWidth, dx, usedWidth] {
4774 switch (quadding) {
4775 case VariableTextQuadding::leftJustified:
4776 default:
4777 return borderWidth + 2;
4778 case VariableTextQuadding::centered:
4779 return (dx - usedWidth) / 2;
4780 case VariableTextQuadding::rightJustified:
4781 return dx - borderWidth - 2 - usedWidth;
4782 }
4783 };
4784 const double x = calculateX();
4785 const double y = 0.5 * dy - 0.4 * fontSize;
4786
4787 // set the font matrix
4788 daToks[tmPos + 4] = GooString().appendf(fmt: "{0:.2f}", x)->toStr();
4789 daToks[tmPos + 5] = GooString().appendf(fmt: "{0:.2f}", y)->toStr();
4790
4791 // write the DA string
4792 for (const std::string &daTok : daToks) {
4793 appearBuf->append(str: daTok)->append(c: ' ');
4794 }
4795 // This newline is not neeed at all but it makes for easier reading
4796 // and our auto tests "wrongly" assume it will be there, so add it anyway
4797 appearBuf->append(str: "\n");
4798
4799 // write the text strings
4800 for (const HorizontalTextLayouter::Data &d : textLayouter.data) {
4801 if (!d.fontName.empty()) {
4802 appearBuf->append(str: " q\n");
4803 appearBuf->appendf(fmt: "/{0:s} {1:.2f} Tf\n", d.fontName.c_str(), fontSize);
4804 }
4805 writeString(str: d.text);
4806 appearBuf->append(str: " Tj\n");
4807 if (!d.fontName.empty()) {
4808 appearBuf->append(str: " Q\n");
4809 }
4810 }
4811 }
4812 }
4813 // cleanup
4814 appearBuf->append(str: "ET\n");
4815 appearBuf->append(str: "Q\n");
4816 if (flags & EmitMarkedContentDrawTextFlag) {
4817 appearBuf->append(str: "EMC\n");
4818 }
4819 if (freeText) {
4820 delete text;
4821 }
4822
4823 return true;
4824}
4825
4826// Draw the variable text or caption for a field.
4827bool AnnotAppearanceBuilder::drawListBox(const FormFieldChoice *fieldChoice, const AnnotBorder *border, const PDFRectangle *rect, const GooString *da, const GfxResources *resources, VariableTextQuadding quadding, XRef *xref,
4828 Dict *resourcesDict)
4829{
4830 std::vector<GooString *> daToks;
4831 GooString *tok;
4832 GooString convertedText;
4833 const GfxFont *font;
4834 double fontSize, borderWidth, x, y;
4835 int tfPos, tmPos, i, j;
4836 std::unique_ptr<const GfxFont> fontToFree;
4837
4838 //~ if there is no MK entry, this should use the existing content stream,
4839 //~ and only replace the marked content portion of it
4840 //~ (this is only relevant for Tx fields)
4841
4842 // parse the default appearance string
4843 tfPos = tmPos = -1;
4844 if (da) {
4845 i = 0;
4846 while (i < da->getLength()) {
4847 while (i < da->getLength() && Lexer::isSpace(c: da->getChar(i))) {
4848 ++i;
4849 }
4850 if (i < da->getLength()) {
4851 for (j = i + 1; j < da->getLength() && !Lexer::isSpace(c: da->getChar(i: j)); ++j) {
4852 ;
4853 }
4854 daToks.push_back(x: new GooString(da, i, j - i));
4855 i = j;
4856 }
4857 }
4858 for (std::size_t k = 2; k < daToks.size(); ++k) {
4859 if (k >= 2 && !(daToks[k])->cmp(sA: "Tf")) {
4860 tfPos = k - 2;
4861 } else if (k >= 6 && !(daToks[k])->cmp(sA: "Tm")) {
4862 tmPos = k - 6;
4863 }
4864 }
4865 }
4866
4867 // get the font and font size
4868 font = nullptr;
4869 fontSize = 0;
4870 if (tfPos >= 0) {
4871 tok = daToks[tfPos];
4872 if (tok->getLength() >= 1 && tok->getChar(i: 0) == '/') {
4873 if (!resources || !(font = resources->lookupFont(name: tok->c_str() + 1).get())) {
4874 if (xref != nullptr && resourcesDict != nullptr) {
4875 const char *fallback = determineFallbackFont(tok: tok->toStr(), defaultFallback: "Helvetica");
4876 // The font variable sometimes points to an object that needs to be deleted
4877 // and sometimes not, depending on whether the call to lookupFont above fails.
4878 // When the code path right here is taken, the destructor of fontToFree
4879 // (which is a std::unique_ptr) will delete the font object at the end of this method.
4880 fontToFree = createAnnotDrawFont(xref, fontParentDict: resourcesDict, resourceName: tok->c_str() + 1, fontname: fallback);
4881 font = fontToFree.get();
4882 } else {
4883 error(category: errSyntaxError, pos: -1, msg: "Unknown font in field's DA string");
4884 }
4885 }
4886 } else {
4887 error(category: errSyntaxError, pos: -1, msg: "Invalid font name in 'Tf' operator in field's DA string");
4888 }
4889 tok = daToks[tfPos + 1];
4890 fontSize = gatof(nptr: tok->c_str());
4891 } else {
4892 error(category: errSyntaxError, pos: -1, msg: "Missing 'Tf' operator in field's DA string");
4893 }
4894 if (!font) {
4895 for (auto entry : daToks) {
4896 delete entry;
4897 }
4898 return false;
4899 }
4900
4901 // get the border width
4902 borderWidth = border ? border->getWidth() : 0;
4903
4904 // compute font autosize
4905 if (fontSize == 0) {
4906 double wMax = 0;
4907 for (i = 0; i < fieldChoice->getNumChoices(); ++i) {
4908 j = 0;
4909 if (fieldChoice->getChoice(i) == nullptr) {
4910 error(category: errSyntaxError, pos: -1, msg: "Invalid annotation listbox");
4911 for (auto entry : daToks) {
4912 delete entry;
4913 }
4914 return false;
4915 }
4916 double w;
4917 Annot::layoutText(text: fieldChoice->getChoice(i), outBuf: &convertedText, i: &j, font: *font, width: &w, widthLimit: 0.0, charCount: nullptr, noReencode: false);
4918 if (w > wMax) {
4919 wMax = w;
4920 }
4921 }
4922 fontSize = rect->y2 - rect->y1 - 2 * borderWidth;
4923 const double fontSize2 = (rect->x2 - rect->x1 - 4 - 2 * borderWidth) / wMax;
4924 if (fontSize2 < fontSize) {
4925 fontSize = fontSize2;
4926 }
4927 fontSize = floor(x: fontSize);
4928 if (tfPos >= 0) {
4929 tok = daToks[tfPos + 1];
4930 tok->clear();
4931 tok->appendf(fmt: "{0:.2f}", fontSize);
4932 }
4933 }
4934 // draw the text
4935 y = rect->y2 - rect->y1 - 1.1 * fontSize;
4936 for (i = fieldChoice->getTopIndex(); i < fieldChoice->getNumChoices(); ++i) {
4937 // setup
4938 appearBuf->append(str: "q\n");
4939
4940 // draw the background if selected
4941 if (fieldChoice->isSelected(i)) {
4942 appearBuf->append(str: "0 g f\n");
4943 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n", borderWidth, y - 0.2 * fontSize, rect->x2 - rect->x1 - 2 * borderWidth, 1.1 * fontSize);
4944 }
4945
4946 // setup
4947 appearBuf->append(str: "BT\n");
4948
4949 // compute text width and start position
4950 j = 0;
4951 double w;
4952 Annot::layoutText(text: fieldChoice->getChoice(i), outBuf: &convertedText, i: &j, font: *font, width: &w, widthLimit: 0.0, charCount: nullptr, noReencode: false);
4953 w *= fontSize;
4954 switch (quadding) {
4955 case VariableTextQuadding::leftJustified:
4956 default:
4957 x = borderWidth + 2;
4958 break;
4959 case VariableTextQuadding::centered:
4960 x = (rect->x2 - rect->x1 - w) / 2;
4961 break;
4962 case VariableTextQuadding::rightJustified:
4963 x = rect->x2 - rect->x1 - borderWidth - 2 - w;
4964 break;
4965 }
4966
4967 // set the font matrix
4968 if (tmPos >= 0) {
4969 tok = daToks[tmPos + 4];
4970 tok->clear();
4971 tok->appendf(fmt: "{0:.2f}", x);
4972 tok = daToks[tmPos + 5];
4973 tok->clear();
4974 tok->appendf(fmt: "{0:.2f}", y);
4975 }
4976
4977 // write the DA string
4978 for (const GooString *daTok : daToks) {
4979 appearBuf->append(str: daTok)->append(c: ' ');
4980 }
4981
4982 // write the font matrix (if not part of the DA string)
4983 if (tmPos < 0) {
4984 appearBuf->appendf(fmt: "1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
4985 }
4986
4987 // change the text color if selected
4988 if (fieldChoice->isSelected(i)) {
4989 appearBuf->append(str: "1 g\n");
4990 }
4991
4992 // write the text string
4993 writeString(str: convertedText.toStr());
4994 appearBuf->append(str: " Tj\n");
4995
4996 // cleanup
4997 appearBuf->append(str: "ET\n");
4998 appearBuf->append(str: "Q\n");
4999
5000 // next line
5001 y -= 1.1 * fontSize;
5002 }
5003
5004 for (auto entry : daToks) {
5005 delete entry;
5006 }
5007
5008 return true;
5009}
5010
5011void AnnotAppearanceBuilder::drawFieldBorder(const FormField *field, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect)
5012{
5013 AnnotColor adjustedColor;
5014 const double w = border->getWidth();
5015
5016 const AnnotColor *aColor = appearCharacs->getBorderColor();
5017 if (!aColor) {
5018 aColor = appearCharacs->getBackColor();
5019 }
5020 if (!aColor) {
5021 return;
5022 }
5023
5024 const double dx = rect->x2 - rect->x1;
5025 const double dy = rect->y2 - rect->y1;
5026
5027 // radio buttons with no caption have a round border
5028 const bool hasCaption = appearCharacs->getNormalCaption() != nullptr;
5029 if (field->getType() == formButton && static_cast<const FormFieldButton *>(field)->getButtonType() == formButtonRadio && !hasCaption) {
5030 double r = 0.5 * (dx < dy ? dx : dy);
5031 switch (border->getStyle()) {
5032 case AnnotBorder::borderDashed:
5033 appearBuf->append(str: "[");
5034 for (double dash : border->getDash()) {
5035 appearBuf->appendf(fmt: " {0:.2f}", dash);
5036 }
5037 appearBuf->append(str: "] 0 d\n");
5038 // fallthrough
5039 case AnnotBorder::borderSolid:
5040 case AnnotBorder::borderUnderlined:
5041 appearBuf->appendf(fmt: "{0:.2f} w\n", w);
5042 setDrawColor(drawColor: aColor, fill: false);
5043 drawCircle(cx: 0.5 * dx, cy: 0.5 * dy, r: r - 0.5 * w, fill: false);
5044 break;
5045 case AnnotBorder::borderBeveled:
5046 case AnnotBorder::borderInset:
5047 appearBuf->appendf(fmt: "{0:.2f} w\n", 0.5 * w);
5048 setDrawColor(drawColor: aColor, fill: false);
5049 drawCircle(cx: 0.5 * dx, cy: 0.5 * dy, r: r - 0.25 * w, fill: false);
5050 adjustedColor = AnnotColor(*aColor);
5051 adjustedColor.adjustColor(adjust: border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1);
5052 setDrawColor(drawColor: &adjustedColor, fill: false);
5053 drawCircleTopLeft(cx: 0.5 * dx, cy: 0.5 * dy, r: r - 0.75 * w);
5054 adjustedColor = AnnotColor(*aColor);
5055 adjustedColor.adjustColor(adjust: border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1);
5056 setDrawColor(drawColor: &adjustedColor, fill: false);
5057 drawCircleBottomRight(cx: 0.5 * dx, cy: 0.5 * dy, r: r - 0.75 * w);
5058 break;
5059 }
5060 } else {
5061 switch (border->getStyle()) {
5062 case AnnotBorder::borderDashed:
5063 appearBuf->append(str: "[");
5064 for (double dash : border->getDash()) {
5065 appearBuf->appendf(fmt: " {0:.2f}", dash);
5066 }
5067 appearBuf->append(str: "] 0 d\n");
5068 // fallthrough
5069 case AnnotBorder::borderSolid:
5070 appearBuf->appendf(fmt: "{0:.2f} w\n", w);
5071 setDrawColor(drawColor: aColor, fill: false);
5072 appearBuf->appendf(fmt: "{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", 0.5 * w, dx - w, dy - w);
5073 break;
5074 case AnnotBorder::borderBeveled:
5075 case AnnotBorder::borderInset:
5076 adjustedColor = AnnotColor(*aColor);
5077 adjustedColor.adjustColor(adjust: border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1);
5078 setDrawColor(drawColor: &adjustedColor, fill: true);
5079 appearBuf->append(str: "0 0 m\n");
5080 appearBuf->appendf(fmt: "0 {0:.2f} l\n", dy);
5081 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", dx, dy);
5082 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", dx - w, dy - w);
5083 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", w, dy - w);
5084 appearBuf->appendf(fmt: "{0:.2f} {0:.2f} l\n", w);
5085 appearBuf->append(str: "f\n");
5086 adjustedColor = AnnotColor(*aColor);
5087 adjustedColor.adjustColor(adjust: border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1);
5088 setDrawColor(drawColor: &adjustedColor, fill: true);
5089 appearBuf->append(str: "0 0 m\n");
5090 appearBuf->appendf(fmt: "{0:.2f} 0 l\n", dx);
5091 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", dx, dy);
5092 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", dx - w, dy - w);
5093 appearBuf->appendf(fmt: "{0:.2f} {1:.2f} l\n", dx - w, w);
5094 appearBuf->appendf(fmt: "{0:.2f} {0:.2f} l\n", w);
5095 appearBuf->append(str: "f\n");
5096 break;
5097 case AnnotBorder::borderUnderlined:
5098 appearBuf->appendf(fmt: "{0:.2f} w\n", w);
5099 setDrawColor(drawColor: aColor, fill: false);
5100 appearBuf->appendf(fmt: "0 0 m {0:.2f} 0 l s\n", dx);
5101 break;
5102 }
5103
5104 // clip to the inside of the border
5105 appearBuf->appendf(fmt: "{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", w, dx - 2 * w, dy - 2 * w);
5106 }
5107}
5108
5109bool AnnotAppearanceBuilder::drawFormField(const FormField *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect,
5110 const GooString *appearState, XRef *xref, Dict *resourcesDict)
5111{
5112 // draw the field contents
5113 switch (field->getType()) {
5114 case formButton:
5115 return drawFormFieldButton(field: static_cast<const FormFieldButton *>(field), form, resources, da, border, appearCharacs, rect, appearState, xref, resourcesDict);
5116 break;
5117 case formText:
5118 return drawFormFieldText(fieldText: static_cast<const FormFieldText *>(field), form, resources, da, border, appearCharacs, rect, xref, resourcesDict);
5119 case formChoice:
5120 return drawFormFieldChoice(fieldChoice: static_cast<const FormFieldChoice *>(field), form, resources, da, border, appearCharacs, rect, xref, resourcesDict);
5121 break;
5122 case formSignature:
5123 return drawSignatureFieldText(field: static_cast<const FormFieldSignature *>(field), form, resources, da, border, appearCharacs, rect, xref, resourcesDict);
5124 break;
5125 case formUndef:
5126 default:
5127 error(category: errSyntaxError, pos: -1, msg: "Unknown field type");
5128 }
5129
5130 return false;
5131}
5132
5133bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs,
5134 const PDFRectangle *rect, const GooString *appearState, XRef *xref, Dict *resourcesDict)
5135{
5136 const GooString *caption = nullptr;
5137 if (appearCharacs) {
5138 caption = appearCharacs->getNormalCaption();
5139 }
5140
5141 switch (field->getButtonType()) {
5142 case formButtonRadio: {
5143 //~ Acrobat doesn't draw a caption if there is no AP dict (?)
5144 if (appearState && appearState->cmp(sA: "Off") != 0 && field->getState(state: appearState->c_str())) {
5145 if (caption) {
5146 return drawText(text: caption, form, da, resources, border, appearCharacs, rect, quadding: VariableTextQuadding::centered, xref, resourcesDict, flags: ForceZapfDingbatsDrawTextFlag);
5147 } else if (appearCharacs) {
5148 const AnnotColor *aColor = appearCharacs->getBorderColor();
5149 if (aColor) {
5150 const double dx = rect->x2 - rect->x1;
5151 const double dy = rect->y2 - rect->y1;
5152 setDrawColor(drawColor: aColor, fill: true);
5153 drawCircle(cx: 0.5 * dx, cy: 0.5 * dy, r: 0.2 * (dx < dy ? dx : dy), fill: true);
5154 }
5155 return true;
5156 }
5157 }
5158 } break;
5159 case formButtonPush:
5160 if (caption) {
5161 return drawText(text: caption, form, da, resources, border, appearCharacs, rect, quadding: VariableTextQuadding::centered, xref, resourcesDict);
5162 }
5163 break;
5164 case formButtonCheck:
5165 if (appearState && appearState->cmp(sA: "Off") != 0) {
5166 if (!caption) {
5167 GooString checkMark("3");
5168 return drawText(text: &checkMark, form, da, resources, border, appearCharacs, rect, quadding: VariableTextQuadding::centered, xref, resourcesDict, flags: ForceZapfDingbatsDrawTextFlag);
5169 } else {
5170 return drawText(text: caption, form, da, resources, border, appearCharacs, rect, quadding: VariableTextQuadding::centered, xref, resourcesDict, flags: ForceZapfDingbatsDrawTextFlag);
5171 }
5172 }
5173 break;
5174 }
5175
5176 return true;
5177}
5178
5179bool AnnotAppearanceBuilder::drawFormFieldText(const FormFieldText *fieldText, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs,
5180 const PDFRectangle *rect, XRef *xref, Dict *resourcesDict)
5181{
5182 VariableTextQuadding quadding;
5183 const GooString *contents;
5184
5185 contents = fieldText->getAppearanceContent();
5186 if (contents) {
5187 if (fieldText->hasTextQuadding()) {
5188 quadding = fieldText->getTextQuadding();
5189 } else if (form) {
5190 quadding = form->getTextQuadding();
5191 } else {
5192 quadding = VariableTextQuadding::leftJustified;
5193 }
5194
5195 const int nCombs = fieldText->isComb() ? fieldText->getMaxLen() : 0;
5196
5197 int flags = EmitMarkedContentDrawTextFlag;
5198 if (fieldText->isMultiline()) {
5199 flags = flags | MultilineDrawTextFlag;
5200 }
5201 if (fieldText->isPassword()) {
5202 flags = flags | TurnTextToStarsDrawTextFlag;
5203 }
5204 return drawText(text: contents, form, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, flags, nCombs);
5205 }
5206
5207 return true;
5208}
5209
5210static void setChildDictEntryValue(Dict *parentDict, const char *childDictName, const char *childDictEntryName, const Ref childDictEntryValue, XRef *xref)
5211{
5212 Object childDictionaryObj = parentDict->lookup(key: childDictName);
5213 if (!childDictionaryObj.isDict()) {
5214 childDictionaryObj = Object(new Dict(xref));
5215 parentDict->set(key: childDictName, val: childDictionaryObj.copy());
5216 }
5217 childDictionaryObj.dictSet(key: childDictEntryName, val: Object(childDictEntryValue));
5218}
5219
5220bool AnnotAppearanceBuilder::drawSignatureFieldText(const FormFieldSignature *field, const Form *form, const GfxResources *resources, const GooString *_da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs,
5221 const PDFRectangle *rect, XRef *xref, Dict *resourcesDict)
5222{
5223 const GooString &contents = field->getCustomAppearanceContent();
5224 if (contents.toStr().empty()) {
5225 return false;
5226 }
5227
5228 if (field->getImageResource() != Ref::INVALID()) {
5229 const double width = rect->x2 - rect->x1;
5230 const double height = rect->y2 - rect->y1;
5231 static const char *imageResourceId = "SigImg";
5232 setChildDictEntryValue(parentDict: resourcesDict, childDictName: "XObject", childDictEntryName: imageResourceId, childDictEntryValue: field->getImageResource(), xref);
5233 Matrix matrix = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
5234 matrix.scale(sx: width, sy: height);
5235 static const char *IMG_TMPL = "\nq {0:.1g} {1:.1g} {2:.1g} {3:.1g} {4:.1g} {5:.1g} cm /{6:s} Do Q\n";
5236 const std::unique_ptr<GooString> imgBuffer = GooString::format(fmt: IMG_TMPL, matrix.m[0], matrix.m[1], matrix.m[2], matrix.m[3], matrix.m[4], matrix.m[5], imageResourceId);
5237 append(text: imgBuffer->c_str());
5238 }
5239
5240 const GooString &leftText = field->getCustomAppearanceLeftContent();
5241 if (leftText.toStr().empty()) {
5242 drawSignatureFieldText(text: contents, form, da: DefaultAppearance(_da), border, rect, xref, resourcesDict, leftMargin: 0, centerVertically: false /* don't center vertically */, centerHorizontally: false /* don't center horizontally */);
5243 } else {
5244 DefaultAppearance daLeft(_da);
5245 daLeft.setFontPtSize(field->getCustomAppearanceLeftFontSize());
5246 const double halfWidth = (rect->x2 - rect->x1) / 2;
5247 PDFRectangle rectLeft(rect->x1, rect->y1, rect->x1 + halfWidth, rect->y2);
5248 drawSignatureFieldText(text: leftText, form, da: daLeft, border, rect: &rectLeft, xref, resourcesDict, leftMargin: 0, centerVertically: true /* center vertically */, centerHorizontally: true /* center horizontally */);
5249
5250 PDFRectangle rectRight(rectLeft.x2, rect->y1, rect->x2, rect->y2);
5251 drawSignatureFieldText(text: contents, form, da: DefaultAppearance(_da), border, rect: &rectRight, xref, resourcesDict, leftMargin: halfWidth, centerVertically: true /* center vertically */, centerHorizontally: false /* don't center horizontally */);
5252 }
5253
5254 return true;
5255}
5256
5257void AnnotAppearanceBuilder::drawSignatureFieldText(const GooString &text, const Form *form, const DefaultAppearance &da, const AnnotBorder *border, const PDFRectangle *rect, XRef *xref, Dict *resourcesDict, double leftMargin,
5258 bool centerVertically, bool centerHorizontally)
5259{
5260 double borderWidth = 0;
5261 append(text: "q\n");
5262
5263 if (border) {
5264 borderWidth = border->getWidth();
5265 if (borderWidth > 0) {
5266 setLineStyleForBorder(border);
5267 }
5268 }
5269
5270 // Box size
5271 const double width = rect->x2 - rect->x1;
5272 const double height = rect->y2 - rect->y1;
5273 const double textmargin = borderWidth * 2;
5274 const double textwidth = width - 2 * textmargin;
5275
5276 // create a Helvetica fake font
5277 std::shared_ptr<const GfxFont> font = form ? form->getDefaultResources()->lookupFont(name: da.getFontName().getName()) : nullptr;
5278 if (!font) {
5279 font = createAnnotDrawFont(xref, fontParentDict: resourcesDict, resourceName: da.getFontName().getName());
5280 }
5281
5282 // Setup text clipping
5283 appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} re W n\n", leftMargin + textmargin, textmargin, textwidth, height - 2 * textmargin);
5284 setDrawColor(drawColor: da.getFontColor(), fill: true);
5285 const DrawMultiLineTextResult textCommands =
5286 drawMultiLineText(text, availableWidth: textwidth, form, font: *font, fontName: da.getFontName().getName(), fontSize: da.getFontPtSize(), quadding: centerHorizontally ? VariableTextQuadding::centered : VariableTextQuadding::leftJustified, borderWidth: 0 /*borderWidth*/);
5287
5288 double yDelta = height - textmargin;
5289 if (centerVertically) {
5290 const double outTextHeight = textCommands.nLines * da.getFontPtSize();
5291 if (outTextHeight < height) {
5292 yDelta = height - (height - outTextHeight) / 2;
5293 }
5294 }
5295 appendf(fmt: "BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", leftMargin + textmargin, yDelta);
5296 append(text: textCommands.text.c_str());
5297 append(text: "ET Q\n");
5298}
5299
5300bool AnnotAppearanceBuilder::drawFormFieldChoice(const FormFieldChoice *fieldChoice, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs,
5301 const PDFRectangle *rect, XRef *xref, Dict *resourcesDict)
5302{
5303 const GooString *selected;
5304 VariableTextQuadding quadding;
5305
5306 if (fieldChoice->hasTextQuadding()) {
5307 quadding = fieldChoice->getTextQuadding();
5308 } else if (form) {
5309 quadding = form->getTextQuadding();
5310 } else {
5311 quadding = VariableTextQuadding::leftJustified;
5312 }
5313
5314 if (fieldChoice->isCombo()) {
5315 selected = fieldChoice->getSelectedChoice();
5316 if (selected) {
5317 return drawText(text: selected, form, da, resources, border, appearCharacs, rect, quadding, xref, resourcesDict, flags: EmitMarkedContentDrawTextFlag);
5318 //~ Acrobat draws a popup icon on the right side
5319 }
5320 // list box
5321 } else {
5322 return drawListBox(fieldChoice, border, rect, da, resources, quadding, xref, resourcesDict);
5323 }
5324
5325 return true;
5326}
5327
5328// Should we also merge Arrays?
5329static void recursiveMergeDicts(Dict *primary, const Dict *secondary, RefRecursionChecker *alreadySeenDicts)
5330{
5331 for (int i = 0; i < secondary->getLength(); ++i) {
5332 const char *key = secondary->getKey(i);
5333 if (!primary->hasKey(key)) {
5334 primary->add(key, val: secondary->lookup(key).deepCopy());
5335 } else {
5336 Ref primaryRef;
5337 Object primaryObj = primary->lookup(key, returnRef: &primaryRef);
5338 if (primaryObj.isDict()) {
5339 Ref secondaryRef;
5340 Object secondaryObj = secondary->lookup(key, returnRef: &secondaryRef);
5341 if (secondaryObj.isDict()) {
5342 if (!alreadySeenDicts->insert(ref: primaryRef) || !alreadySeenDicts->insert(ref: secondaryRef)) {
5343 // bad PDF
5344 return;
5345 }
5346 recursiveMergeDicts(primary: primaryObj.getDict(), secondary: secondaryObj.getDict(), alreadySeenDicts);
5347 }
5348 }
5349 }
5350 }
5351}
5352
5353static void recursiveMergeDicts(Dict *primary, const Dict *secondary)
5354{
5355 RefRecursionChecker alreadySeenDicts;
5356 recursiveMergeDicts(primary, secondary, alreadySeenDicts: &alreadySeenDicts);
5357}
5358
5359void AnnotWidget::generateFieldAppearance()
5360{
5361 const GooString *da;
5362
5363 AnnotAppearanceBuilder appearBuilder;
5364
5365 // draw the background
5366 if (appearCharacs) {
5367 const AnnotColor *aColor = appearCharacs->getBackColor();
5368 if (aColor) {
5369 appearBuilder.setDrawColor(drawColor: aColor, fill: true);
5370 appearBuilder.appendf(fmt: "0 0 {0:.2f} {1:.2f} re f\n", rect->x2 - rect->x1, rect->y2 - rect->y1);
5371 }
5372 }
5373
5374 // draw the border
5375 if (appearCharacs && border && border->getWidth() > 0) {
5376 appearBuilder.drawFieldBorder(field, border: border.get(), appearCharacs: appearCharacs.get(), rect: rect.get());
5377 }
5378
5379 da = field->getDefaultAppearance();
5380 if (!da && form) {
5381 da = form->getDefaultAppearance();
5382 }
5383
5384 Dict *appearDict = new Dict(doc->getXRef());
5385
5386 // Let's init resourcesDictObj and resources.
5387 // In PDF 1.2, an additional entry in the field dictionary, DR, was defined.
5388 // Beginning with PDF 1.5, this entry is obsolete.
5389 // And yet Acrobat Reader seems to be taking a field's DR into account.
5390 Object resourcesDictObj;
5391 const GfxResources *resources = nullptr;
5392 GfxResources *resourcesToFree = nullptr;
5393 if (field->getObj() && field->getObj()->isDict()) {
5394 // Let's use a field's resource dictionary.
5395 resourcesDictObj = field->getObj()->dictLookup(key: "DR");
5396 if (resourcesDictObj.isDict()) {
5397 if (form && form->getDefaultResourcesObj()->isDict()) {
5398 resourcesDictObj = resourcesDictObj.deepCopy();
5399 recursiveMergeDicts(primary: resourcesDictObj.getDict(), secondary: form->getDefaultResourcesObj()->getDict());
5400 }
5401 resourcesToFree = new GfxResources(doc->getXRef(), resourcesDictObj.getDict(), nullptr);
5402 resources = resourcesToFree;
5403 }
5404 }
5405 if (!resourcesDictObj.isDict()) {
5406 // No luck with a field's resource dictionary. Let's use an AcroForm's resource dictionary.
5407 if (form && form->getDefaultResourcesObj()->isDict()) {
5408 resourcesDictObj = form->getDefaultResourcesObj()->deepCopy();
5409 resources = form->getDefaultResources();
5410 }
5411 }
5412 if (!resourcesDictObj.isDict()) {
5413 resourcesDictObj = Object(new Dict(doc->getXRef()));
5414 }
5415
5416 const bool success = appearBuilder.drawFormField(field, form, resources, da, border: border.get(), appearCharacs: appearCharacs.get(), rect: rect.get(), appearState: appearState.get(), xref: doc->getXRef(), resourcesDict: resourcesDictObj.getDict());
5417 if (!success && form && da != form->getDefaultAppearance()) {
5418 da = form->getDefaultAppearance();
5419 appearBuilder.drawFormField(field, form, resources, da, border: border.get(), appearCharacs: appearCharacs.get(), rect: rect.get(), appearState: appearState.get(), xref: doc->getXRef(), resourcesDict: resourcesDictObj.getDict());
5420 }
5421
5422 const GooString *appearBuf = appearBuilder.buffer();
5423 // fill the appearance stream dictionary
5424 appearDict->add(key: "Length", val: Object(appearBuf->getLength()));
5425 appearDict->add(key: "Subtype", val: Object(objName, "Form"));
5426 Array *bbox = new Array(doc->getXRef());
5427 bbox->add(elem: Object(0));
5428 bbox->add(elem: Object(0));
5429 bbox->add(elem: Object(rect->x2 - rect->x1));
5430 bbox->add(elem: Object(rect->y2 - rect->y1));
5431 appearDict->add(key: "BBox", val: Object(bbox));
5432
5433 // set the resource dictionary
5434 if (resourcesDictObj.getDict()->getLength() > 0) {
5435 appearDict->set(key: "Resources", val: std::move(resourcesDictObj));
5436 }
5437
5438 // build the appearance stream
5439 Stream *appearStream = new AutoFreeMemStream(copyString(s: appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict));
5440 if (hasBeenUpdated) {
5441 // We should technically do this for all annots but AnnotFreeText
5442 // forms are particularly special since we're potentially embeddeing a font so we really need
5443 // to set the AP and not let other renderers guess it from the contents
5444 setNewAppearance(Object(appearStream));
5445 } else {
5446 appearance = Object(appearStream);
5447 }
5448
5449 if (resourcesToFree) {
5450 delete resourcesToFree;
5451 }
5452}
5453
5454void AnnotWidget::updateAppearanceStream()
5455{
5456 // If this the first time updateAppearanceStream() is called on this widget,
5457 // destroy the AP dictionary because we are going to create a new one.
5458 if (updatedAppearanceStream == Ref::INVALID()) {
5459 invalidateAppearance(); // Delete AP dictionary and all referenced streams
5460 }
5461
5462 // There's no need to create a new appearance stream if NeedAppearances is
5463 // set, because it will be ignored next time anyway.
5464 // except if signature type; most readers can't figure out how to create an
5465 // appearance for those and thus renders nothing.
5466 if (form && form->getNeedAppearances()) {
5467 if (field->getType() != FormFieldType::formSignature) {
5468 return;
5469 }
5470 }
5471
5472 // Create the new appearance
5473 generateFieldAppearance();
5474
5475 // Fetch the appearance stream we've just created
5476 Object obj1 = appearance.fetch(xref: doc->getXRef());
5477
5478 // If this the first time updateAppearanceStream() is called on this widget,
5479 // create a new AP dictionary containing the new appearance stream.
5480 // Otherwise, just update the stream we had created previously.
5481 if (updatedAppearanceStream == Ref::INVALID()) {
5482 // Write the appearance stream
5483 updatedAppearanceStream = doc->getXRef()->addIndirectObject(o: obj1);
5484
5485 // Write the AP dictionary
5486 obj1 = Object(new Dict(doc->getXRef()));
5487 obj1.dictAdd(key: "N", val: Object(updatedAppearanceStream));
5488
5489 // Update our internal pointers to the appearance dictionary
5490 appearStreams = std::make_unique<AnnotAppearance>(args&: doc, args: &obj1);
5491
5492 update(key: "AP", value: std::move(obj1));
5493 } else {
5494 // Replace the existing appearance stream
5495 doc->getXRef()->setModifiedObject(o: &obj1, r: updatedAppearanceStream);
5496 }
5497}
5498
5499void AnnotWidget::draw(Gfx *gfx, bool printing)
5500{
5501 if (!isVisible(printing)) {
5502 return;
5503 }
5504
5505 annotLocker();
5506
5507 // Only construct the appearance stream when
5508 // - annot doesn't have an AP or
5509 // - NeedAppearances is true and it isn't a Signature. There isn't enough data in our objects to generate it for signatures
5510 if (field) {
5511 if (appearance.isNull() || (field->getType() != FormFieldType::formSignature && form && form->getNeedAppearances())) {
5512 generateFieldAppearance();
5513 }
5514 }
5515
5516 // draw the appearance stream
5517 Object obj = appearance.fetch(xref: gfx->getXRef());
5518 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
5519}
5520
5521void AnnotWidget::invalidateAppearance()
5522{
5523 updatedAppearanceStream = Ref::INVALID();
5524 Annot::invalidateAppearance();
5525}
5526
5527//------------------------------------------------------------------------
5528// AnnotMovie
5529//------------------------------------------------------------------------
5530AnnotMovie::AnnotMovie(PDFDoc *docA, PDFRectangle *rectA, Movie *movieA) : Annot(docA, rectA)
5531{
5532 type = typeMovie;
5533 annotObj.dictSet(key: "Subtype", val: Object(objName, "Movie"));
5534
5535 movie = movieA->copy();
5536 // TODO: create movie dict from movieA
5537
5538 initialize(docA, dict: annotObj.getDict());
5539}
5540
5541AnnotMovie::AnnotMovie(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
5542{
5543 type = typeMovie;
5544 initialize(docA, dict: annotObj.getDict());
5545}
5546
5547AnnotMovie::~AnnotMovie() = default;
5548
5549void AnnotMovie::initialize(PDFDoc *docA, Dict *dict)
5550{
5551 Object obj1;
5552
5553 obj1 = dict->lookup(key: "T");
5554 if (obj1.isString()) {
5555 title.reset(p: obj1.getString()->copy());
5556 }
5557
5558 Object movieDict = dict->lookup(key: "Movie");
5559 if (movieDict.isDict()) {
5560 Object obj2 = dict->lookup(key: "A");
5561 if (obj2.isDict()) {
5562 movie = std::make_unique<Movie>(args: &movieDict, args: &obj2);
5563 } else {
5564 movie = std::make_unique<Movie>(args: &movieDict);
5565 }
5566 if (!movie->isOk()) {
5567 movie = nullptr;
5568 ok = false;
5569 }
5570 } else {
5571 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Movie");
5572 ok = false;
5573 }
5574}
5575
5576void AnnotMovie::draw(Gfx *gfx, bool printing)
5577{
5578 if (!isVisible(printing)) {
5579 return;
5580 }
5581
5582 annotLocker();
5583 if (appearance.isNull() && movie->getShowPoster()) {
5584 int width, height;
5585 Object poster = movie->getPoster();
5586 movie->getAspect(widthA: &width, heightA: &height);
5587
5588 if (width != -1 && height != -1 && !poster.isNone()) {
5589 auto appearBuf = std::make_unique<GooString>();
5590 appearBuf->append(str: "q\n");
5591 appearBuf->appendf(fmt: "{0:d} 0 0 {1:d} 0 0 cm\n", width, height);
5592 appearBuf->append(str: "/MImg Do\n");
5593 appearBuf->append(str: "Q\n");
5594
5595 Dict *imgDict = new Dict(gfx->getXRef());
5596 imgDict->set(key: "MImg", val: std::move(poster));
5597
5598 Dict *resDict = new Dict(gfx->getXRef());
5599 resDict->set(key: "XObject", val: Object(imgDict));
5600
5601 Dict *formDict = new Dict(gfx->getXRef());
5602 formDict->set(key: "Length", val: Object(appearBuf->getLength()));
5603 formDict->set(key: "Subtype", val: Object(objName, "Form"));
5604 formDict->set(key: "Name", val: Object(objName, "FRM"));
5605 Array *bboxArray = new Array(gfx->getXRef());
5606 bboxArray->add(elem: Object(0));
5607 bboxArray->add(elem: Object(0));
5608 bboxArray->add(elem: Object(width));
5609 bboxArray->add(elem: Object(height));
5610 formDict->set(key: "BBox", val: Object(bboxArray));
5611 Array *matrix = new Array(gfx->getXRef());
5612 matrix->add(elem: Object(1));
5613 matrix->add(elem: Object(0));
5614 matrix->add(elem: Object(0));
5615 matrix->add(elem: Object(1));
5616 matrix->add(elem: Object(-width / 2));
5617 matrix->add(elem: Object(-height / 2));
5618 formDict->set(key: "Matrix", val: Object(matrix));
5619 formDict->set(key: "Resources", val: Object(resDict));
5620
5621 Stream *mStream = new AutoFreeMemStream(copyString(s: appearBuf->c_str()), 0, appearBuf->getLength(), Object(formDict));
5622
5623 Dict *dict = new Dict(gfx->getXRef());
5624 dict->set(key: "FRM", val: Object(mStream));
5625
5626 Dict *resDict2 = new Dict(gfx->getXRef());
5627 resDict2->set(key: "XObject", val: Object(dict));
5628
5629 appearBuf = std::make_unique<GooString>();
5630 appearBuf->append(str: "q\n");
5631 appearBuf->appendf(fmt: "0 0 {0:d} {1:d} re W n\n", width, height);
5632 appearBuf->append(str: "q\n");
5633 appearBuf->appendf(fmt: "0 0 {0:d} {1:d} re W n\n", width, height);
5634 appearBuf->appendf(fmt: "1 0 0 1 {0:d} {1:d} cm\n", width / 2, height / 2);
5635 appearBuf->append(str: "/FRM Do\n");
5636 appearBuf->append(str: "Q\n");
5637 appearBuf->append(str: "Q\n");
5638
5639 double bbox[4];
5640 bbox[0] = bbox[1] = 0;
5641 bbox[2] = width;
5642 bbox[3] = height;
5643 appearance = createForm(appearBuf: appearBuf.get(), bbox, transparencyGroup: false, resDict: resDict2);
5644 }
5645 }
5646
5647 // draw the appearance stream
5648 Object obj = appearance.fetch(xref: gfx->getXRef());
5649 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
5650}
5651
5652//------------------------------------------------------------------------
5653// AnnotScreen
5654//------------------------------------------------------------------------
5655AnnotScreen::AnnotScreen(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
5656{
5657 type = typeScreen;
5658
5659 annotObj.dictSet(key: "Subtype", val: Object(objName, "Screen"));
5660 initialize(docA, dict: annotObj.getDict());
5661}
5662
5663AnnotScreen::AnnotScreen(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
5664{
5665 type = typeScreen;
5666 initialize(docA, dict: annotObj.getDict());
5667}
5668
5669AnnotScreen::~AnnotScreen() = default;
5670
5671void AnnotScreen::initialize(PDFDoc *docA, Dict *dict)
5672{
5673 Object obj1;
5674
5675 obj1 = dict->lookup(key: "T");
5676 if (obj1.isString()) {
5677 title.reset(p: obj1.getString()->copy());
5678 }
5679
5680 obj1 = dict->lookup(key: "A");
5681 if (obj1.isDict()) {
5682 action = LinkAction::parseAction(obj: &obj1, baseURI: doc->getCatalog()->getBaseURI());
5683 if (action && action->getKind() == actionRendition && page == 0) {
5684 error(category: errSyntaxError, pos: -1, msg: "Invalid Rendition action: associated screen annotation without P");
5685 action = nullptr;
5686 ok = false;
5687 }
5688 }
5689
5690 additionalActions = dict->lookupNF(key: "AA").copy();
5691
5692 obj1 = dict->lookup(key: "MK");
5693 if (obj1.isDict()) {
5694 appearCharacs = std::make_unique<AnnotAppearanceCharacs>(args: obj1.getDict());
5695 }
5696}
5697
5698std::unique_ptr<LinkAction> AnnotScreen::getAdditionalAction(AdditionalActionsType additionalActionType)
5699{
5700 if (additionalActionType == actionFocusIn || additionalActionType == actionFocusOut) { // not defined for screen annotation
5701 return nullptr;
5702 }
5703
5704 return ::getAdditionalAction(type: additionalActionType, additionalActions: &additionalActions, doc);
5705}
5706
5707//------------------------------------------------------------------------
5708// AnnotStamp
5709//------------------------------------------------------------------------
5710AnnotStamp::AnnotStamp(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
5711{
5712 type = typeStamp;
5713 annotObj.dictSet(key: "Subtype", val: Object(objName, "Stamp"));
5714 initialize(docA, dict: annotObj.getDict());
5715}
5716
5717AnnotStamp::AnnotStamp(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
5718{
5719 type = typeStamp;
5720 initialize(docA, dict: annotObj.getDict());
5721}
5722
5723AnnotStamp::~AnnotStamp()
5724{
5725 delete stampImageHelper;
5726}
5727
5728void AnnotStamp::initialize(PDFDoc *docA, Dict *dict)
5729{
5730 Object obj1 = dict->lookup(key: "Name");
5731 if (obj1.isName()) {
5732 icon = std::make_unique<GooString>(args: obj1.getName());
5733 } else {
5734 icon = std::make_unique<GooString>(args: "Draft");
5735 }
5736
5737 stampImageHelper = nullptr;
5738 updatedAppearanceStream = Ref::INVALID();
5739}
5740
5741void AnnotStamp::generateStampCustomAppearance()
5742{
5743 Ref imgRef = stampImageHelper->getRef();
5744 const std::string imgStrName = "X" + std::to_string(val: imgRef.num);
5745
5746 AnnotAppearanceBuilder appearBuilder;
5747 appearBuilder.append(text: "q\n");
5748 appearBuilder.append(text: "/GS0 gs\n");
5749 appearBuilder.appendf(fmt: "{0:.3f} 0 0 {1:.3f} 0 0 cm\n", rect->x2 - rect->x1, rect->y2 - rect->y1);
5750 appearBuilder.append(text: "/");
5751 appearBuilder.append(text: imgStrName.c_str());
5752 appearBuilder.append(text: " Do\n");
5753 appearBuilder.append(text: "Q\n");
5754
5755 Dict *resDict = createResourcesDict(formName: imgStrName.c_str(), formStream: Object(imgRef), stateName: "GS0", opacity, blendMode: nullptr);
5756
5757 const double bboxArray[4] = { 0, 0, rect->x2 - rect->x1, rect->y2 - rect->y1 };
5758 const GooString *appearBuf = appearBuilder.buffer();
5759 appearance = createForm(appearBuf, bbox: bboxArray, transparencyGroup: false, resDict);
5760}
5761
5762void AnnotStamp::generateStampDefaultAppearance()
5763{
5764 Dict *extGStateDict = nullptr;
5765 AnnotAppearanceBuilder defaultAppearanceBuilder;
5766
5767 double stampUnscaledWidth;
5768 double stampUnscaledHeight;
5769 const char *stampCode;
5770 if (!icon->cmp(sA: "Approved")) {
5771 stampUnscaledWidth = ANNOT_STAMP_APPROVED_WIDTH;
5772 stampUnscaledHeight = ANNOT_STAMP_APPROVED_HEIGHT;
5773 stampCode = ANNOT_STAMP_APPROVED;
5774 extGStateDict = getApprovedStampExtGStateDict(doc);
5775 } else if (!icon->cmp(sA: "AsIs")) {
5776 stampUnscaledWidth = ANNOT_STAMP_AS_IS_WIDTH;
5777 stampUnscaledHeight = ANNOT_STAMP_AS_IS_HEIGHT;
5778 stampCode = ANNOT_STAMP_AS_IS;
5779 extGStateDict = getAsIsStampExtGStateDict(doc);
5780 } else if (!icon->cmp(sA: "Confidential")) {
5781 stampUnscaledWidth = ANNOT_STAMP_CONFIDENTIAL_WIDTH;
5782 stampUnscaledHeight = ANNOT_STAMP_CONFIDENTIAL_HEIGHT;
5783 stampCode = ANNOT_STAMP_CONFIDENTIAL;
5784 extGStateDict = getConfidentialStampExtGStateDict(doc);
5785 } else if (!icon->cmp(sA: "Final")) {
5786 stampUnscaledWidth = ANNOT_STAMP_FINAL_WIDTH;
5787 stampUnscaledHeight = ANNOT_STAMP_FINAL_HEIGHT;
5788 stampCode = ANNOT_STAMP_FINAL;
5789 extGStateDict = getFinalStampExtGStateDict(doc);
5790 } else if (!icon->cmp(sA: "Experimental")) {
5791 stampUnscaledWidth = ANNOT_STAMP_EXPERIMENTAL_WIDTH;
5792 stampUnscaledHeight = ANNOT_STAMP_EXPERIMENTAL_HEIGHT;
5793 stampCode = ANNOT_STAMP_EXPERIMENTAL;
5794 extGStateDict = getExperimentalStampExtGStateDict(doc);
5795 } else if (!icon->cmp(sA: "Expired")) {
5796 stampUnscaledWidth = ANNOT_STAMP_EXPIRED_WIDTH;
5797 stampUnscaledHeight = ANNOT_STAMP_EXPIRED_HEIGHT;
5798 stampCode = ANNOT_STAMP_EXPIRED;
5799 extGStateDict = getExpiredStampExtGStateDict(doc);
5800 } else if (!icon->cmp(sA: "NotApproved")) {
5801 stampUnscaledWidth = ANNOT_STAMP_NOT_APPROVED_WIDTH;
5802 stampUnscaledHeight = ANNOT_STAMP_NOT_APPROVED_HEIGHT;
5803 stampCode = ANNOT_STAMP_NOT_APPROVED;
5804 extGStateDict = getNotApprovedStampExtGStateDict(doc);
5805 } else if (!icon->cmp(sA: "NotForPublicRelease")) {
5806 stampUnscaledWidth = ANNOT_STAMP_NOT_FOR_PUBLIC_RELEASE_WIDTH;
5807 stampUnscaledHeight = ANNOT_STAMP_NOT_FOR_PUBLIC_RELEASE_HEIGHT;
5808 stampCode = ANNOT_STAMP_NOT_FOR_PUBLIC_RELEASE;
5809 extGStateDict = getNotForPublicReleaseStampExtGStateDict(doc);
5810 } else if (!icon->cmp(sA: "Sold")) {
5811 stampUnscaledWidth = ANNOT_STAMP_SOLD_WIDTH;
5812 stampUnscaledHeight = ANNOT_STAMP_SOLD_HEIGHT;
5813 stampCode = ANNOT_STAMP_SOLD;
5814 extGStateDict = getSoldStampExtGStateDict(doc);
5815 } else if (!icon->cmp(sA: "Departmental")) {
5816 stampUnscaledWidth = ANNOT_STAMP_DEPARTMENTAL_WIDTH;
5817 stampUnscaledHeight = ANNOT_STAMP_DEPARTMENTAL_HEIGHT;
5818 stampCode = ANNOT_STAMP_DEPARTMENTAL;
5819 extGStateDict = getDepartmentalStampExtGStateDict(doc);
5820 } else if (!icon->cmp(sA: "ForComment")) {
5821 stampUnscaledWidth = ANNOT_STAMP_FOR_COMMENT_WIDTH;
5822 stampUnscaledHeight = ANNOT_STAMP_FOR_COMMENT_HEIGHT;
5823 stampCode = ANNOT_STAMP_FOR_COMMENT;
5824 extGStateDict = getForCommentStampExtGStateDict(doc);
5825 } else if (!icon->cmp(sA: "ForPublicRelease")) {
5826 stampUnscaledWidth = ANNOT_STAMP_FOR_PUBLIC_RELEASE_WIDTH;
5827 stampUnscaledHeight = ANNOT_STAMP_FOR_PUBLIC_RELEASE_HEIGHT;
5828 stampCode = ANNOT_STAMP_FOR_PUBLIC_RELEASE;
5829 extGStateDict = getForPublicReleaseStampExtGStateDict(doc);
5830 } else if (!icon->cmp(sA: "TopSecret")) {
5831 stampUnscaledWidth = ANNOT_STAMP_TOP_SECRET_WIDTH;
5832 stampUnscaledHeight = ANNOT_STAMP_TOP_SECRET_HEIGHT;
5833 stampCode = ANNOT_STAMP_TOP_SECRET;
5834 extGStateDict = getTopSecretStampExtGStateDict(doc);
5835 } else {
5836 stampUnscaledWidth = ANNOT_STAMP_DRAFT_WIDTH;
5837 stampUnscaledHeight = ANNOT_STAMP_DRAFT_HEIGHT;
5838 stampCode = ANNOT_STAMP_DRAFT;
5839 extGStateDict = getDraftStampExtGStateDict(doc);
5840 }
5841
5842 const double bboxArray[4] = { 0, 0, rect->x2 - rect->x1, rect->y2 - rect->y1 };
5843 const std::unique_ptr<GooString> scale = GooString::format(fmt: "{0:.6g} 0 0 {1:.6g} 0 0 cm\nq\n", bboxArray[2] / stampUnscaledWidth, bboxArray[3] / stampUnscaledHeight);
5844 defaultAppearanceBuilder.append(text: scale->c_str());
5845 defaultAppearanceBuilder.append(text: stampCode);
5846 defaultAppearanceBuilder.append(text: "Q\n");
5847
5848 Dict *resDict = new Dict(doc->getXRef());
5849 resDict->add(key: "ExtGState", val: Object(extGStateDict));
5850
5851 Object aStream = createForm(appearBuf: defaultAppearanceBuilder.buffer(), bbox: bboxArray, transparencyGroup: true, resDict);
5852
5853 AnnotAppearanceBuilder appearanceBuilder;
5854 appearanceBuilder.append(text: "/GS0 gs\n/Fm0 Do");
5855 resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity, blendMode: nullptr);
5856 appearance = createForm(appearBuf: appearanceBuilder.buffer(), bbox: bboxArray, transparencyGroup: false, resDict);
5857}
5858
5859void AnnotStamp::draw(Gfx *gfx, bool printing)
5860{
5861 if (!isVisible(printing)) {
5862 return;
5863 }
5864
5865 annotLocker();
5866 if (appearance.isNull()) {
5867 if (stampImageHelper != nullptr) {
5868 generateStampCustomAppearance();
5869 } else {
5870 generateStampDefaultAppearance();
5871 }
5872 }
5873
5874 // draw the appearance stream
5875 Object obj = appearance.fetch(xref: gfx->getXRef());
5876 if (appearBBox) {
5877 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
5878 } else {
5879 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
5880 }
5881}
5882
5883void AnnotStamp::setIcon(GooString *new_icon)
5884{
5885 if (new_icon) {
5886 icon = std::make_unique<GooString>(args&: new_icon);
5887 } else {
5888 icon = std::make_unique<GooString>();
5889 }
5890
5891 update(key: "Name", value: Object(objName, icon->c_str()));
5892 invalidateAppearance();
5893}
5894
5895void AnnotStamp::setCustomImage(AnnotStampImageHelper *stampImageHelperA)
5896{
5897 if (!stampImageHelperA) {
5898 return;
5899 }
5900
5901 annotLocker();
5902 clearCustomImage();
5903
5904 stampImageHelper = stampImageHelperA;
5905 generateStampCustomAppearance();
5906
5907 if (updatedAppearanceStream == Ref::INVALID()) {
5908 updatedAppearanceStream = doc->getXRef()->addIndirectObject(o: appearance);
5909 } else {
5910 Object obj1 = appearance.fetch(xref: doc->getXRef());
5911 doc->getXRef()->setModifiedObject(o: &obj1, r: updatedAppearanceStream);
5912 }
5913
5914 Object obj1 = Object(new Dict(doc->getXRef()));
5915 obj1.dictAdd(key: "N", val: Object(updatedAppearanceStream));
5916 update(key: "AP", value: std::move(obj1));
5917}
5918
5919void AnnotStamp::clearCustomImage()
5920{
5921 if (stampImageHelper != nullptr) {
5922 stampImageHelper->removeAnnotStampImageObject();
5923 delete stampImageHelper;
5924 stampImageHelper = nullptr;
5925 invalidateAppearance();
5926 }
5927}
5928
5929//------------------------------------------------------------------------
5930// AnnotGeometry
5931//------------------------------------------------------------------------
5932AnnotGeometry::AnnotGeometry(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA)
5933{
5934 switch (subType) {
5935 case typeSquare:
5936 annotObj.dictSet(key: "Subtype", val: Object(objName, "Square"));
5937 break;
5938 case typeCircle:
5939 annotObj.dictSet(key: "Subtype", val: Object(objName, "Circle"));
5940 break;
5941 default:
5942 assert(0 && "Invalid subtype for AnnotGeometry\n");
5943 }
5944
5945 initialize(docA, dict: annotObj.getDict());
5946}
5947
5948AnnotGeometry::AnnotGeometry(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
5949{
5950 // the real type will be read in initialize()
5951 type = typeSquare;
5952 initialize(docA, dict: annotObj.getDict());
5953}
5954
5955AnnotGeometry::~AnnotGeometry() = default;
5956
5957void AnnotGeometry::initialize(PDFDoc *docA, Dict *dict)
5958{
5959 Object obj1;
5960
5961 obj1 = dict->lookup(key: "Subtype");
5962 if (obj1.isName()) {
5963 GooString typeName(obj1.getName());
5964 if (!typeName.cmp(sA: "Square")) {
5965 type = typeSquare;
5966 } else if (!typeName.cmp(sA: "Circle")) {
5967 type = typeCircle;
5968 }
5969 }
5970
5971 obj1 = dict->lookup(key: "IC");
5972 if (obj1.isArray()) {
5973 interiorColor = std::make_unique<AnnotColor>(args: obj1.getArray());
5974 }
5975
5976 obj1 = dict->lookup(key: "BS");
5977 if (obj1.isDict()) {
5978 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
5979 } else if (!border) {
5980 border = std::make_unique<AnnotBorderBS>();
5981 }
5982
5983 obj1 = dict->lookup(key: "BE");
5984 if (obj1.isDict()) {
5985 borderEffect = std::make_unique<AnnotBorderEffect>(args: obj1.getDict());
5986 }
5987
5988 obj1 = dict->lookup(key: "RD");
5989 if (obj1.isArray()) {
5990 geometryRect = parseDiffRectangle(array: obj1.getArray(), rect: rect.get());
5991 }
5992}
5993
5994void AnnotGeometry::setType(AnnotSubtype new_type)
5995{
5996 const char *typeName = nullptr; /* squelch bogus compiler warning */
5997
5998 switch (new_type) {
5999 case typeSquare:
6000 typeName = "Square";
6001 break;
6002 case typeCircle:
6003 typeName = "Circle";
6004 break;
6005 default:
6006 assert(!"Invalid subtype");
6007 }
6008
6009 type = new_type;
6010 update(key: "Subtype", value: Object(objName, typeName));
6011 invalidateAppearance();
6012}
6013
6014void AnnotGeometry::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color)
6015{
6016 if (new_color) {
6017 Object obj1 = new_color->writeToObject(xref: doc->getXRef());
6018 update(key: "IC", value: std::move(obj1));
6019 interiorColor = std::move(new_color);
6020 } else {
6021 interiorColor = nullptr;
6022 update(key: "IC", value: Object(objNull));
6023 }
6024 invalidateAppearance();
6025}
6026
6027void AnnotGeometry::draw(Gfx *gfx, bool printing)
6028{
6029 double ca = 1;
6030
6031 if (!isVisible(printing)) {
6032 return;
6033 }
6034
6035 annotLocker();
6036 if (appearance.isNull()) {
6037 const bool fill = interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent;
6038 ca = opacity;
6039
6040 AnnotAppearanceBuilder appearBuilder;
6041 appearBuilder.append(text: "q\n");
6042 if (color) {
6043 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
6044 }
6045
6046 double borderWidth = border->getWidth();
6047 appearBuilder.setLineStyleForBorder(border.get());
6048
6049 if (interiorColor) {
6050 appearBuilder.setDrawColor(drawColor: interiorColor.get(), fill: true);
6051 }
6052
6053 if (type == typeSquare) {
6054 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} {2:.2f} {3:.2f} re\n", borderWidth / 2.0, borderWidth / 2.0, (rect->x2 - rect->x1) - borderWidth, (rect->y2 - rect->y1) - borderWidth);
6055 if (fill) {
6056 if (borderWidth > 0) {
6057 appearBuilder.append(text: "b\n");
6058 } else {
6059 appearBuilder.append(text: "f\n");
6060 }
6061 } else if (borderWidth > 0) {
6062 appearBuilder.append(text: "S\n");
6063 }
6064 } else {
6065 const double rx { (rect->x2 - rect->x1) / 2. };
6066 const double ry { (rect->y2 - rect->y1) / 2. };
6067 const double bwHalf { borderWidth / 2.0 };
6068 appearBuilder.drawEllipse(cx: rx, cy: ry, rx: rx - bwHalf, ry: ry - bwHalf, fill, stroke: borderWidth > 0);
6069 }
6070 appearBuilder.append(text: "Q\n");
6071
6072 double bbox[4];
6073 bbox[0] = bbox[1] = 0;
6074 bbox[2] = rect->x2 - rect->x1;
6075 bbox[3] = rect->y2 - rect->y1;
6076 if (ca == 1) {
6077 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
6078 } else {
6079 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
6080
6081 GooString appearBuf("/GS0 gs\n/Fm0 Do");
6082 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
6083 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
6084 }
6085 }
6086
6087 // draw the appearance stream
6088 Object obj = appearance.fetch(xref: gfx->getXRef());
6089 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
6090}
6091
6092//------------------------------------------------------------------------
6093// AnnotPolygon
6094//------------------------------------------------------------------------
6095AnnotPolygon::AnnotPolygon(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA)
6096{
6097 switch (subType) {
6098 case typePolygon:
6099 annotObj.dictSet(key: "Subtype", val: Object(objName, "Polygon"));
6100 break;
6101 case typePolyLine:
6102 annotObj.dictSet(key: "Subtype", val: Object(objName, "PolyLine"));
6103 break;
6104 default:
6105 assert(0 && "Invalid subtype for AnnotGeometry\n");
6106 }
6107
6108 // Store dummy path with one null vertex only
6109 Array *a = new Array(doc->getXRef());
6110 a->add(elem: Object(0.));
6111 a->add(elem: Object(0.));
6112 annotObj.dictSet(key: "Vertices", val: Object(a));
6113
6114 initialize(docA, dict: annotObj.getDict());
6115}
6116
6117AnnotPolygon::AnnotPolygon(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
6118{
6119 // the real type will be read in initialize()
6120 type = typePolygon;
6121 initialize(docA, dict: annotObj.getDict());
6122}
6123
6124AnnotPolygon::~AnnotPolygon() = default;
6125
6126void AnnotPolygon::initialize(PDFDoc *docA, Dict *dict)
6127{
6128 Object obj1;
6129
6130 obj1 = dict->lookup(key: "Subtype");
6131 if (obj1.isName()) {
6132 GooString typeName(obj1.getName());
6133 if (!typeName.cmp(sA: "Polygon")) {
6134 type = typePolygon;
6135 } else if (!typeName.cmp(sA: "PolyLine")) {
6136 type = typePolyLine;
6137 }
6138 }
6139
6140 obj1 = dict->lookup(key: "Vertices");
6141 if (obj1.isArray()) {
6142 vertices = std::make_unique<AnnotPath>(args: obj1.getArray());
6143 } else {
6144 vertices = std::make_unique<AnnotPath>();
6145 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Polygon Vertices");
6146 ok = false;
6147 }
6148
6149 obj1 = dict->lookup(key: "LE");
6150 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
6151 Object obj2 = obj1.arrayGet(i: 0);
6152 if (obj2.isName()) {
6153 const GooString leName(obj2.getName());
6154 startStyle = parseAnnotLineEndingStyle(string: &leName);
6155 } else {
6156 startStyle = annotLineEndingNone;
6157 }
6158 obj2 = obj1.arrayGet(i: 1);
6159 if (obj2.isName()) {
6160 const GooString leName(obj2.getName());
6161 endStyle = parseAnnotLineEndingStyle(string: &leName);
6162 } else {
6163 endStyle = annotLineEndingNone;
6164 }
6165 } else {
6166 startStyle = endStyle = annotLineEndingNone;
6167 }
6168
6169 obj1 = dict->lookup(key: "IC");
6170 if (obj1.isArray()) {
6171 interiorColor = std::make_unique<AnnotColor>(args: obj1.getArray());
6172 }
6173
6174 obj1 = dict->lookup(key: "BS");
6175 if (obj1.isDict()) {
6176 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
6177 } else if (!border) {
6178 border = std::make_unique<AnnotBorderBS>();
6179 }
6180
6181 obj1 = dict->lookup(key: "BE");
6182 if (obj1.isDict()) {
6183 borderEffect = std::make_unique<AnnotBorderEffect>(args: obj1.getDict());
6184 }
6185
6186 obj1 = dict->lookup(key: "IT");
6187 if (obj1.isName()) {
6188 const char *intentName = obj1.getName();
6189
6190 if (!strcmp(s1: intentName, s2: "PolygonCloud")) {
6191 intent = polygonCloud;
6192 } else if (!strcmp(s1: intentName, s2: "PolyLineDimension")) {
6193 intent = polylineDimension;
6194 } else {
6195 intent = polygonDimension;
6196 }
6197 } else {
6198 intent = polygonCloud;
6199 }
6200}
6201
6202void AnnotPolygon::setType(AnnotSubtype new_type)
6203{
6204 const char *typeName = nullptr; /* squelch bogus compiler warning */
6205
6206 switch (new_type) {
6207 case typePolygon:
6208 typeName = "Polygon";
6209 break;
6210 case typePolyLine:
6211 typeName = "PolyLine";
6212 break;
6213 default:
6214 assert(!"Invalid subtype");
6215 }
6216
6217 type = new_type;
6218 update(key: "Subtype", value: Object(objName, typeName));
6219 invalidateAppearance();
6220}
6221
6222void AnnotPolygon::setVertices(AnnotPath *path)
6223{
6224 Array *a = new Array(doc->getXRef());
6225 for (int i = 0; i < path->getCoordsLength(); i++) {
6226 a->add(elem: Object(path->getX(coord: i)));
6227 a->add(elem: Object(path->getY(coord: i)));
6228 }
6229
6230 vertices = std::make_unique<AnnotPath>(args&: a);
6231
6232 update(key: "Vertices", value: Object(a));
6233 invalidateAppearance();
6234}
6235
6236void AnnotPolygon::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end)
6237{
6238 startStyle = start;
6239 endStyle = end;
6240
6241 Array *a = new Array(doc->getXRef());
6242 a->add(elem: Object(objName, convertAnnotLineEndingStyle(style: startStyle)));
6243 a->add(elem: Object(objName, convertAnnotLineEndingStyle(style: endStyle)));
6244
6245 update(key: "LE", value: Object(a));
6246 invalidateAppearance();
6247}
6248
6249void AnnotPolygon::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color)
6250{
6251 if (new_color) {
6252 Object obj1 = new_color->writeToObject(xref: doc->getXRef());
6253 update(key: "IC", value: std::move(obj1));
6254 interiorColor = std::move(new_color);
6255 } else {
6256 interiorColor = nullptr;
6257 update(key: "IC", value: Object(objNull));
6258 }
6259 invalidateAppearance();
6260}
6261
6262void AnnotPolygon::setIntent(AnnotPolygonIntent new_intent)
6263{
6264 const char *intentName;
6265
6266 intent = new_intent;
6267 if (new_intent == polygonCloud) {
6268 intentName = "PolygonCloud";
6269 } else if (new_intent == polylineDimension) {
6270 intentName = "PolyLineDimension";
6271 } else { // polygonDimension
6272 intentName = "PolygonDimension";
6273 }
6274 update(key: "IT", value: Object(objName, intentName));
6275}
6276
6277void AnnotPolygon::generatePolyLineAppearance(AnnotAppearanceBuilder *appearBuilder)
6278{
6279 const bool fill = (bool)interiorColor;
6280 const double x1 = vertices->getX(coord: 0);
6281 const double y1 = vertices->getY(coord: 0);
6282 const double x2 = vertices->getX(coord: 1);
6283 const double y2 = vertices->getY(coord: 1);
6284 const double x3 = vertices->getX(coord: vertices->getCoordsLength() - 2);
6285 const double y3 = vertices->getY(coord: vertices->getCoordsLength() - 2);
6286 const double x4 = vertices->getX(coord: vertices->getCoordsLength() - 1);
6287 const double y4 = vertices->getY(coord: vertices->getCoordsLength() - 1);
6288
6289 const double len_1 = sqrt(x: (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
6290 // length of last segment
6291 const double len_2 = sqrt(x: (x4 - x3) * (x4 - x3) + (y4 - y3) * (y4 - y3));
6292
6293 // segments become positive x direction, coord1 becomes (0,0).
6294 Matrix matr1, matr2;
6295 const double angle1 = atan2(y: y2 - y1, x: x2 - x1);
6296 const double angle2 = atan2(y: y4 - y3, x: x4 - x3);
6297
6298 matr1.m[0] = matr1.m[3] = cos(x: angle1);
6299 matr1.m[1] = sin(x: angle1);
6300 matr1.m[2] = -matr1.m[1];
6301 matr1.m[4] = x1 - rect->x1;
6302 matr1.m[5] = y1 - rect->y1;
6303
6304 matr2.m[0] = matr2.m[3] = cos(x: angle2);
6305 matr2.m[1] = sin(x: angle2);
6306 matr2.m[2] = -matr2.m[1];
6307 matr2.m[4] = x3 - rect->x1;
6308 matr2.m[5] = y3 - rect->y1;
6309
6310 const double lineEndingSize1 { std::min(a: 6. * border->getWidth(), b: len_1 / 2) };
6311 const double lineEndingSize2 { std::min(a: 6. * border->getWidth(), b: len_2 / 2) };
6312
6313 if (vertices->getCoordsLength() != 0) {
6314 double tx, ty;
6315 matr1.transform(x: AnnotAppearanceBuilder::lineEndingXShorten(endingStyle: startStyle, size: lineEndingSize1), y: 0, tx: &tx, ty: &ty);
6316 appearBuilder->appendf(fmt: "{0:.2f} {1:.2f} m\n", tx, ty);
6317 appearBBox->extendTo(x: tx, y: ty);
6318
6319 for (int i = 1; i < vertices->getCoordsLength() - 1; ++i) {
6320 appearBuilder->appendf(fmt: "{0:.2f} {1:.2f} l\n", vertices->getX(coord: i) - rect->x1, vertices->getY(coord: i) - rect->y1);
6321 appearBBox->extendTo(x: vertices->getX(coord: i) - rect->x1, y: vertices->getY(coord: i) - rect->y1);
6322 }
6323
6324 if (vertices->getCoordsLength() > 1) {
6325 matr2.transform(x: len_2 - AnnotAppearanceBuilder::lineEndingXShorten(endingStyle: endStyle, size: lineEndingSize2), y: 0, tx: &tx, ty: &ty);
6326 appearBuilder->appendf(fmt: "{0:.2f} {1:.2f} l S\n", tx, ty);
6327 appearBBox->extendTo(x: tx, y: ty);
6328 }
6329 }
6330
6331 if (startStyle != annotLineEndingNone) {
6332 const double extendX { -AnnotAppearanceBuilder::lineEndingXExtendBBox(endingStyle: startStyle, size: lineEndingSize1) };
6333 double tx, ty;
6334 appearBuilder->drawLineEnding(endingStyle: startStyle, x: 0, y: 0, size: -lineEndingSize1, fill, m: matr1);
6335 matr1.transform(x: extendX, y: lineEndingSize1 / 2., tx: &tx, ty: &ty);
6336 appearBBox->extendTo(x: tx, y: ty);
6337 matr1.transform(x: extendX, y: -lineEndingSize1 / 2., tx: &tx, ty: &ty);
6338 appearBBox->extendTo(x: tx, y: ty);
6339 }
6340
6341 if (endStyle != annotLineEndingNone) {
6342 const double extendX { AnnotAppearanceBuilder::lineEndingXExtendBBox(endingStyle: endStyle, size: lineEndingSize2) };
6343 double tx, ty;
6344 appearBuilder->drawLineEnding(endingStyle: endStyle, x: len_2, y: 0, size: lineEndingSize2, fill, m: matr2);
6345 matr2.transform(x: len_2 + extendX, y: lineEndingSize2 / 2., tx: &tx, ty: &ty);
6346 appearBBox->extendTo(x: tx, y: ty);
6347 matr2.transform(x: len_2 + extendX, y: -lineEndingSize2 / 2., tx: &tx, ty: &ty);
6348 appearBBox->extendTo(x: tx, y: ty);
6349 }
6350}
6351
6352void AnnotPolygon::draw(Gfx *gfx, bool printing)
6353{
6354 double ca = 1;
6355
6356 if (!isVisible(printing)) {
6357 return;
6358 }
6359
6360 annotLocker();
6361 if (appearance.isNull()) {
6362 appearBBox = std::make_unique<AnnotAppearanceBBox>(args: rect.get());
6363 ca = opacity;
6364
6365 AnnotAppearanceBuilder appearBuilder;
6366 appearBuilder.append(text: "q\n");
6367
6368 if (color) {
6369 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
6370 }
6371
6372 appearBuilder.setLineStyleForBorder(border.get());
6373 appearBBox->setBorderWidth(std::max(a: 1., b: border->getWidth()));
6374
6375 if (interiorColor) {
6376 appearBuilder.setDrawColor(drawColor: interiorColor.get(), fill: true);
6377 }
6378
6379 if (type == typePolyLine) {
6380 generatePolyLineAppearance(appearBuilder: &appearBuilder);
6381 } else {
6382 if (vertices->getCoordsLength() != 0) {
6383 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", vertices->getX(coord: 0) - rect->x1, vertices->getY(coord: 0) - rect->y1);
6384 appearBBox->extendTo(x: vertices->getX(coord: 0) - rect->x1, y: vertices->getY(coord: 0) - rect->y1);
6385
6386 for (int i = 1; i < vertices->getCoordsLength(); ++i) {
6387 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", vertices->getX(coord: i) - rect->x1, vertices->getY(coord: i) - rect->y1);
6388 appearBBox->extendTo(x: vertices->getX(coord: i) - rect->x1, y: vertices->getY(coord: i) - rect->y1);
6389 }
6390
6391 const double borderWidth = border->getWidth();
6392 if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) {
6393 if (borderWidth > 0) {
6394 appearBuilder.append(text: "b\n");
6395 } else {
6396 appearBuilder.append(text: "f\n");
6397 }
6398 } else if (borderWidth > 0) {
6399 appearBuilder.append(text: "s\n");
6400 }
6401 }
6402 }
6403 appearBuilder.append(text: "Q\n");
6404
6405 double bbox[4];
6406 appearBBox->getBBoxRect(bbox);
6407 if (ca == 1) {
6408 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
6409 } else {
6410 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
6411
6412 GooString appearBuf("/GS0 gs\n/Fm0 Do");
6413 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
6414 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
6415 }
6416 }
6417
6418 // draw the appearance stream
6419 Object obj = appearance.fetch(xref: gfx->getXRef());
6420 if (appearBBox) {
6421 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
6422 } else {
6423 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
6424 }
6425}
6426
6427//------------------------------------------------------------------------
6428// AnnotCaret
6429//------------------------------------------------------------------------
6430AnnotCaret::AnnotCaret(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
6431{
6432 type = typeCaret;
6433
6434 annotObj.dictSet(key: "Subtype", val: Object(objName, "Caret"));
6435 initialize(docA, dict: annotObj.getDict());
6436}
6437
6438AnnotCaret::AnnotCaret(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
6439{
6440 type = typeCaret;
6441 initialize(docA, dict: annotObj.getDict());
6442}
6443
6444AnnotCaret::~AnnotCaret() = default;
6445
6446void AnnotCaret::initialize(PDFDoc *docA, Dict *dict)
6447{
6448 Object obj1;
6449
6450 symbol = symbolNone;
6451 obj1 = dict->lookup(key: "Sy");
6452 if (obj1.isName()) {
6453 GooString typeName(obj1.getName());
6454 if (!typeName.cmp(sA: "P")) {
6455 symbol = symbolP;
6456 } else if (!typeName.cmp(sA: "None")) {
6457 symbol = symbolNone;
6458 }
6459 }
6460
6461 obj1 = dict->lookup(key: "RD");
6462 if (obj1.isArray()) {
6463 caretRect = parseDiffRectangle(array: obj1.getArray(), rect: rect.get());
6464 }
6465}
6466
6467void AnnotCaret::setSymbol(AnnotCaretSymbol new_symbol)
6468{
6469 symbol = new_symbol;
6470 update(key: "Sy", value: Object(objName, new_symbol == symbolP ? "P" : "None"));
6471 invalidateAppearance();
6472}
6473
6474//------------------------------------------------------------------------
6475// AnnotInk
6476//------------------------------------------------------------------------
6477AnnotInk::AnnotInk(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
6478{
6479 type = typeInk;
6480
6481 annotObj.dictSet(key: "Subtype", val: Object(objName, "Ink"));
6482
6483 // Store dummy path with one null vertex only
6484 Array *inkListArray = new Array(doc->getXRef());
6485 Array *vList = new Array(doc->getXRef());
6486 vList->add(elem: Object(0.));
6487 vList->add(elem: Object(0.));
6488 inkListArray->add(elem: Object(vList));
6489 annotObj.dictSet(key: "InkList", val: Object(inkListArray));
6490
6491 initialize(docA, dict: annotObj.getDict());
6492}
6493
6494AnnotInk::AnnotInk(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
6495{
6496 type = typeInk;
6497 initialize(docA, dict: annotObj.getDict());
6498}
6499
6500AnnotInk::~AnnotInk()
6501{
6502 freeInkList();
6503}
6504
6505void AnnotInk::initialize(PDFDoc *docA, Dict *dict)
6506{
6507 Object obj1;
6508
6509 obj1 = dict->lookup(key: "InkList");
6510 if (obj1.isArray()) {
6511 parseInkList(src_array: obj1.getArray());
6512 } else {
6513 inkListLength = 0;
6514 inkList = nullptr;
6515 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Ink List");
6516
6517 obj1 = dict->lookup(key: "AP");
6518 // Although InkList is required, it should be ignored
6519 // when there is an AP entry in the Annot, so do not fail
6520 // when that happens
6521 if (!obj1.isDict()) {
6522 ok = false;
6523 }
6524 }
6525
6526 obj1 = dict->lookup(key: "BS");
6527 if (obj1.isDict()) {
6528 border = std::make_unique<AnnotBorderBS>(args: obj1.getDict());
6529 } else if (!border) {
6530 border = std::make_unique<AnnotBorderBS>();
6531 }
6532}
6533
6534void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array)
6535{
6536 for (int i = 0; i < n_paths; ++i) {
6537 AnnotPath *path = paths[i];
6538 Array *a = new Array(doc->getXRef());
6539 for (int j = 0; j < path->getCoordsLength(); ++j) {
6540 a->add(elem: Object(path->getX(coord: j)));
6541 a->add(elem: Object(path->getY(coord: j)));
6542 }
6543 dest_array->add(elem: Object(a));
6544 }
6545}
6546
6547void AnnotInk::parseInkList(Array *array)
6548{
6549 inkListLength = array->getLength();
6550 inkList = (AnnotPath **)gmallocn(count: (inkListLength), size: sizeof(AnnotPath *));
6551 memset(s: inkList, c: 0, n: inkListLength * sizeof(AnnotPath *));
6552 for (int i = 0; i < inkListLength; i++) {
6553 Object obj2 = array->get(i);
6554 if (obj2.isArray()) {
6555 inkList[i] = new AnnotPath(obj2.getArray());
6556 }
6557 }
6558}
6559
6560void AnnotInk::freeInkList()
6561{
6562 if (inkList) {
6563 for (int i = 0; i < inkListLength; ++i) {
6564 delete inkList[i];
6565 }
6566 gfree(p: inkList);
6567 }
6568}
6569
6570void AnnotInk::setInkList(AnnotPath **paths, int n_paths)
6571{
6572 freeInkList();
6573
6574 Array *a = new Array(doc->getXRef());
6575 writeInkList(paths, n_paths, dest_array: a);
6576
6577 parseInkList(array: a);
6578 annotObj.dictSet(key: "InkList", val: Object(a));
6579 invalidateAppearance();
6580}
6581
6582void AnnotInk::draw(Gfx *gfx, bool printing)
6583{
6584 double ca = 1;
6585
6586 if (!isVisible(printing)) {
6587 return;
6588 }
6589
6590 annotLocker();
6591 if (appearance.isNull()) {
6592 appearBBox = std::make_unique<AnnotAppearanceBBox>(args: rect.get());
6593 ca = opacity;
6594
6595 AnnotAppearanceBuilder appearBuilder;
6596 appearBuilder.append(text: "q\n");
6597
6598 if (color) {
6599 appearBuilder.setDrawColor(drawColor: color.get(), fill: false);
6600 }
6601
6602 appearBuilder.setLineStyleForBorder(border.get());
6603 appearBBox->setBorderWidth(std::max(a: 1., b: border->getWidth()));
6604
6605 for (int i = 0; i < inkListLength; ++i) {
6606 const AnnotPath *path = inkList[i];
6607 if (path && path->getCoordsLength() != 0) {
6608 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} m\n", path->getX(coord: 0) - rect->x1, path->getY(coord: 0) - rect->y1);
6609 appearBBox->extendTo(x: path->getX(coord: 0) - rect->x1, y: path->getY(coord: 0) - rect->y1);
6610
6611 for (int j = 1; j < path->getCoordsLength(); ++j) {
6612 appearBuilder.appendf(fmt: "{0:.2f} {1:.2f} l\n", path->getX(coord: j) - rect->x1, path->getY(coord: j) - rect->y1);
6613 appearBBox->extendTo(x: path->getX(coord: j) - rect->x1, y: path->getY(coord: j) - rect->y1);
6614 }
6615
6616 appearBuilder.append(text: "S\n");
6617 }
6618 }
6619
6620 appearBuilder.append(text: "Q\n");
6621
6622 double bbox[4];
6623 appearBBox->getBBoxRect(bbox);
6624 if (ca == 1) {
6625 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
6626 } else {
6627 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
6628
6629 GooString appearBuf("/GS0 gs\n/Fm0 Do");
6630 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
6631 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
6632 }
6633 }
6634
6635 // draw the appearance stream
6636 Object obj = appearance.fetch(xref: gfx->getXRef());
6637 if (appearBBox) {
6638 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: appearBBox->getPageXMin(), yMin: appearBBox->getPageYMin(), xMax: appearBBox->getPageXMax(), yMax: appearBBox->getPageYMax(), rotate: getRotation());
6639 } else {
6640 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
6641 }
6642}
6643
6644//------------------------------------------------------------------------
6645// AnnotFileAttachment
6646//------------------------------------------------------------------------
6647AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, PDFRectangle *rectA, GooString *filename) : AnnotMarkup(docA, rectA)
6648{
6649 type = typeFileAttachment;
6650
6651 annotObj.dictSet(key: "Subtype", val: Object(objName, "FileAttachment"));
6652 annotObj.dictSet(key: "FS", val: Object(filename->copy()));
6653
6654 initialize(docA, dict: annotObj.getDict());
6655}
6656
6657AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
6658{
6659 type = typeFileAttachment;
6660 initialize(docA, dict: annotObj.getDict());
6661}
6662
6663AnnotFileAttachment::~AnnotFileAttachment() = default;
6664
6665void AnnotFileAttachment::initialize(PDFDoc *docA, Dict *dict)
6666{
6667 Object objFS = dict->lookup(key: "FS");
6668 if (objFS.isDict() || objFS.isString()) {
6669 file = std::move(objFS);
6670 } else {
6671 error(category: errSyntaxError, pos: -1, msg: "Bad Annot File Attachment");
6672 ok = false;
6673 }
6674
6675 Object objName = dict->lookup(key: "Name");
6676 if (objName.isName()) {
6677 name = std::make_unique<GooString>(args: objName.getName());
6678 } else {
6679 name = std::make_unique<GooString>(args: "PushPin");
6680 }
6681}
6682
6683#define ANNOT_FILE_ATTACHMENT_AP_PUSHPIN \
6684 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6685 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6686 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6687 "4.301 23 m f\n" \
6688 "0.533333 0.541176 0.521569 RG 2 w\n" \
6689 "1 J\n" \
6690 "1 j\n" \
6691 "[] 0.0 d\n" \
6692 "4 M 5 4 m 6 5 l S\n" \
6693 "2 w\n" \
6694 "11 14 m 9 12 l 6 12 l 13 5 l 13 8 l 15 10 l 18 11 l 20 11 l 12 19 l 12\n" \
6695 "17 l 11 14 l h\n" \
6696 "11 14 m S\n" \
6697 "3 w\n" \
6698 "6 5 m 9 8 l S\n" \
6699 "0.729412 0.741176 0.713725 RG 2 w\n" \
6700 "5 5 m 6 6 l S\n" \
6701 "2 w\n" \
6702 "11 15 m 9 13 l 6 13 l 13 6 l 13 9 l 15 11 l 18 12 l 20 12 l 12 20 l 12\n" \
6703 "18 l 11 15 l h\n" \
6704 "11 15 m S\n" \
6705 "3 w\n" \
6706 "6 6 m 9 9 l S\n"
6707
6708#define ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP \
6709 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6710 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6711 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6712 "4.301 23 m f\n" \
6713 "0.533333 0.541176 0.521569 RG 2 w\n" \
6714 "1 J\n" \
6715 "1 j\n" \
6716 "[] 0.0 d\n" \
6717 "4 M 16.645 12.035 m 12.418 7.707 l 10.902 6.559 6.402 11.203 8.09 12.562 c\n" \
6718 "14.133 18.578 l 14.949 19.387 16.867 19.184 17.539 18.465 c 20.551\n" \
6719 "15.23 l 21.191 14.66 21.336 12.887 20.426 12.102 c 13.18 4.824 l 12.18\n" \
6720 "3.82 6.25 2.566 4.324 4.461 c 3 6.395 3.383 11.438 4.711 12.801 c 9.648\n" \
6721 "17.887 l S\n" \
6722 "0.729412 0.741176 0.713725 RG 16.645 13.035 m 12.418 8.707 l\n" \
6723 "10.902 7.559 6.402 12.203 8.09 13.562 c\n" \
6724 "14.133 19.578 l 14.949 20.387 16.867 20.184 17.539 19.465 c 20.551\n" \
6725 "16.23 l 21.191 15.66 21.336 13.887 20.426 13.102 c 13.18 5.824 l 12.18\n" \
6726 "4.82 6.25 3.566 4.324 5.461 c 3 7.395 3.383 12.438 4.711 13.801 c 9.648\n" \
6727 "18.887 l S\n"
6728
6729#define ANNOT_FILE_ATTACHMENT_AP_GRAPH \
6730 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6731 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6732 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6733 "4.301 23 m f\n" \
6734 "0.533333 0.541176 0.521569 RG 1 w\n" \
6735 "1 J\n" \
6736 "0 j\n" \
6737 "[] 0.0 d\n" \
6738 "4 M 18.5 15.5 m 18.5 13.086 l 16.086 15.5 l 18.5 15.5 l h\n" \
6739 "18.5 15.5 m S\n" \
6740 "7 7 m 10 11 l 13 9 l 18 15 l S\n" \
6741 "0.729412 0.741176 0.713725 RG 7 8 m 10 12 l 13 10 l 18 16 l S\n" \
6742 "18.5 16.5 m 18.5 14.086 l 16.086 16.5 l 18.5 16.5 l h\n" \
6743 "18.5 16.5 m S\n" \
6744 "0.533333 0.541176 0.521569 RG 2 w\n" \
6745 "1 j\n" \
6746 "3 19 m 3 3 l 21 3 l S\n" \
6747 "0.729412 0.741176 0.713725 RG 3 20 m 3 4 l 21 4 l S\n"
6748
6749#define ANNOT_FILE_ATTACHMENT_AP_TAG \
6750 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6751 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6752 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6753 "4.301 23 m f\n" \
6754 "0.533333 0.541176 0.521569 RG 0.999781 w\n" \
6755 "1 J\n" \
6756 "1 j\n" \
6757 "[] 0.0 d\n" \
6758 "4 M q 1 0 0 -1 0 24 cm\n" \
6759 "8.492 8.707 m 8.492 9.535 7.82 10.207 6.992 10.207 c 6.164 10.207 5.492\n" \
6760 "9.535 5.492 8.707 c 5.492 7.879 6.164 7.207 6.992 7.207 c 7.82 7.207\n" \
6761 "8.492 7.879 8.492 8.707 c h\n" \
6762 "8.492 8.707 m S Q\n" \
6763 "2 w\n" \
6764 "20.078 11.414 m 20.891 10.602 20.785 9.293 20.078 8.586 c 14.422 2.93 l\n" \
6765 "13.715 2.223 12.301 2.223 11.594 2.93 c 3.816 10.707 l 3.109 11.414\n" \
6766 "2.402 17.781 3.816 19.195 c 5.23 20.609 11.594 19.902 12.301 19.195 c\n" \
6767 "20.078 11.414 l h\n" \
6768 "20.078 11.414 m S\n" \
6769 "0.729412 0.741176 0.713725 RG 20.078 12.414 m\n" \
6770 "20.891 11.605 20.785 10.293 20.078 9.586 c 14.422 3.93 l\n" \
6771 "13.715 3.223 12.301 3.223 11.594 3.93 c 3.816 11.707 l 3.109 12.414\n" \
6772 "2.402 18.781 3.816 20.195 c 5.23 21.609 11.594 20.902 12.301 20.195 c\n" \
6773 "20.078 12.414 l h\n" \
6774 "20.078 12.414 m S\n" \
6775 "0.533333 0.541176 0.521569 RG 1 w\n" \
6776 "0 j\n" \
6777 "11.949 13.184 m 16.191 8.941 l S\n" \
6778 "0.729412 0.741176 0.713725 RG 11.949 14.184 m 16.191 9.941 l S\n" \
6779 "0.533333 0.541176 0.521569 RG 14.07 6.82 m 9.828 11.062 l S\n" \
6780 "0.729412 0.741176 0.713725 RG 14.07 7.82 m 9.828 12.062 l S\n" \
6781 "0.533333 0.541176 0.521569 RG 6.93 15.141 m 8 20 14.27 20.5 16 20.5 c\n" \
6782 "18.094 20.504 19.5 20 19.5 18 c 19.5 16.699 20.91 16.418 22.5 16.5 c S\n" \
6783 "0.729412 0.741176 0.713725 RG 0.999781 w\n" \
6784 "1 j\n" \
6785 "q 1 0 0 -1 0 24 cm\n" \
6786 "8.492 7.707 m 8.492 8.535 7.82 9.207 6.992 9.207 c 6.164 9.207 5.492\n" \
6787 "8.535 5.492 7.707 c 5.492 6.879 6.164 6.207 6.992 6.207 c 7.82 6.207\n" \
6788 "8.492 6.879 8.492 7.707 c h\n" \
6789 "8.492 7.707 m S Q\n" \
6790 "1 w\n" \
6791 "0 j\n" \
6792 "6.93 16.141 m 8 21 14.27 21.5 16 21.5 c 18.094 21.504 19.5 21 19.5 19 c\n" \
6793 "19.5 17.699 20.91 17.418 22.5 17.5 c S\n"
6794
6795void AnnotFileAttachment::draw(Gfx *gfx, bool printing)
6796{
6797 double ca = 1;
6798
6799 if (!isVisible(printing)) {
6800 return;
6801 }
6802
6803 annotLocker();
6804 if (appearance.isNull()) {
6805 ca = opacity;
6806
6807 AnnotAppearanceBuilder appearBuilder;
6808
6809 appearBuilder.append(text: "q\n");
6810 if (color) {
6811 appearBuilder.setDrawColor(drawColor: color.get(), fill: true);
6812 } else {
6813 appearBuilder.append(text: "1 1 1 rg\n");
6814 }
6815 if (!name->cmp(sA: "PushPin")) {
6816 appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_PUSHPIN);
6817 } else if (!name->cmp(sA: "Paperclip")) {
6818 appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP);
6819 } else if (!name->cmp(sA: "Graph")) {
6820 appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_GRAPH);
6821 } else if (!name->cmp(sA: "Tag")) {
6822 appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_TAG);
6823 }
6824 appearBuilder.append(text: "Q\n");
6825
6826 double bbox[4];
6827 bbox[0] = bbox[1] = 0;
6828 bbox[2] = bbox[3] = 24;
6829 if (ca == 1) {
6830 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
6831 } else {
6832 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
6833
6834 GooString appearBuf("/GS0 gs\n/Fm0 Do");
6835 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
6836 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
6837 }
6838 }
6839
6840 // draw the appearance stream
6841 Object obj = appearance.fetch(xref: gfx->getXRef());
6842 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
6843}
6844
6845//------------------------------------------------------------------------
6846// AnnotSound
6847//------------------------------------------------------------------------
6848AnnotSound::AnnotSound(PDFDoc *docA, PDFRectangle *rectA, Sound *soundA) : AnnotMarkup(docA, rectA)
6849{
6850 type = typeSound;
6851
6852 annotObj.dictSet(key: "Subtype", val: Object(objName, "Sound"));
6853 annotObj.dictSet(key: "Sound", val: soundA->getObject()->copy());
6854
6855 initialize(docA, dict: annotObj.getDict());
6856}
6857
6858AnnotSound::AnnotSound(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
6859{
6860 type = typeSound;
6861 initialize(docA, dict: annotObj.getDict());
6862}
6863
6864AnnotSound::~AnnotSound() = default;
6865
6866void AnnotSound::initialize(PDFDoc *docA, Dict *dict)
6867{
6868 Object obj1 = dict->lookup(key: "Sound");
6869
6870 sound = Sound::parseSound(obj: &obj1);
6871 if (!sound) {
6872 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Sound");
6873 ok = false;
6874 }
6875
6876 obj1 = dict->lookup(key: "Name");
6877 if (obj1.isName()) {
6878 name = std::make_unique<GooString>(args: obj1.getName());
6879 } else {
6880 name = std::make_unique<GooString>(args: "Speaker");
6881 }
6882}
6883
6884#define ANNOT_SOUND_AP_SPEAKER \
6885 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6886 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6887 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6888 "4.301 23 m f\n" \
6889 "0.533333 0.541176 0.521569 RG 2 w\n" \
6890 "0 J\n" \
6891 "1 j\n" \
6892 "[] 0.0 d\n" \
6893 "4 M 4 14 m 4.086 8.043 l 7 8 l 11 4 l 11 18 l 7 14 l 4 14 l h\n" \
6894 "4 14 m S\n" \
6895 "1 w\n" \
6896 "1 J\n" \
6897 "0 j\n" \
6898 "13.699 15.398 m 14.699 13.398 14.699 9.398 13.699 7.398 c S\n" \
6899 "18.199 19.398 m 21.199 17.398 21.199 5.398 18.199 3.398 c S\n" \
6900 "16 17.398 m 18 16.398 18 7.398 16 5.398 c S\n" \
6901 "0.729412 0.741176 0.713725 RG 2 w\n" \
6902 "0 J\n" \
6903 "1 j\n" \
6904 "4 15 m 4.086 9.043 l 7 9 l 11 5 l 11 19 l 7 15 l 4 15 l h\n" \
6905 "4 15 m S\n" \
6906 "1 w\n" \
6907 "1 J\n" \
6908 "0 j\n" \
6909 "13.699 16 m 14.699 14 14.699 10 13.699 8 c S\n" \
6910 "18.199 20 m 21.199 18 21.199 6 18.199 4 c S\n" \
6911 "16 18 m 18 17 18 8 16 6 c S\n"
6912
6913#define ANNOT_SOUND_AP_MIC \
6914 "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
6915 "2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
6916 "l 1 21.523 2.477 23 4.301 23 c h\n" \
6917 "4.301 23 m f\n" \
6918 "0.533333 0.541176 0.521569 RG 2 w\n" \
6919 "1 J\n" \
6920 "0 j\n" \
6921 "[] 0.0 d\n" \
6922 "4 M 12 20 m 12 20 l 13.656 20 15 18.656 15 17 c 15 13 l 15 11.344 13.656 10\n" \
6923 "12 10 c 12 10 l 10.344 10 9 11.344 9 13 c 9 17 l 9 18.656 10.344 20 12\n" \
6924 "20 c h\n" \
6925 "12 20 m S\n" \
6926 "1 w\n" \
6927 "17.5 14.5 m 17.5 11.973 l 17.5 8.941 15.047 6.5 12 6.5 c 8.953 6.5 6.5\n" \
6928 "8.941 6.5 11.973 c 6.5 14.5 l S\n" \
6929 "2 w\n" \
6930 "0 J\n" \
6931 "12 6.52 m 12 3 l S\n" \
6932 "1 J\n" \
6933 "8 3 m 16 3 l S\n" \
6934 "0.729412 0.741176 0.713725 RG 12 21 m 12 21 l 13.656 21 15 19.656 15 18 c\n" \
6935 "15 14 l 15 12.344 13.656 11 12 11 c 12 11 l 10.344 11 9 12.344 9 14 c\n" \
6936 "9 18 l 9 19.656 10.344 21 12 21 c h\n" \
6937 "12 21 m S\n" \
6938 "1 w\n" \
6939 "17.5 15.5 m 17.5 12.973 l 17.5 9.941 15.047 7.5 12 7.5 c 8.953 7.5 6.5\n" \
6940 "9.941 6.5 12.973 c 6.5 15.5 l S\n" \
6941 "2 w\n" \
6942 "0 J\n" \
6943 "12 7.52 m 12 4 l S\n" \
6944 "1 J\n" \
6945 "8 4 m 16 4 l S\n"
6946
6947void AnnotSound::draw(Gfx *gfx, bool printing)
6948{
6949 Object obj;
6950 double ca = 1;
6951
6952 if (!isVisible(printing)) {
6953 return;
6954 }
6955
6956 annotLocker();
6957 if (appearance.isNull()) {
6958 ca = opacity;
6959
6960 AnnotAppearanceBuilder appearBuilder;
6961
6962 appearBuilder.append(text: "q\n");
6963 if (color) {
6964 appearBuilder.setDrawColor(drawColor: color.get(), fill: true);
6965 } else {
6966 appearBuilder.append(text: "1 1 1 rg\n");
6967 }
6968 if (!name->cmp(sA: "Speaker")) {
6969 appearBuilder.append(ANNOT_SOUND_AP_SPEAKER);
6970 } else if (!name->cmp(sA: "Mic")) {
6971 appearBuilder.append(ANNOT_SOUND_AP_MIC);
6972 }
6973 appearBuilder.append(text: "Q\n");
6974
6975 double bbox[4];
6976 bbox[0] = bbox[1] = 0;
6977 bbox[2] = bbox[3] = 24;
6978 if (ca == 1) {
6979 appearance = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: false, resDict: nullptr);
6980 } else {
6981 Object aStream = createForm(appearBuf: appearBuilder.buffer(), bbox, transparencyGroup: true, resDict: nullptr);
6982
6983 GooString appearBuf("/GS0 gs\n/Fm0 Do");
6984 Dict *resDict = createResourcesDict(formName: "Fm0", formStream: std::move(aStream), stateName: "GS0", opacity: ca, blendMode: nullptr);
6985 appearance = createForm(appearBuf: &appearBuf, bbox, transparencyGroup: false, resDict);
6986 }
6987 }
6988
6989 // draw the appearance stream
6990 obj = appearance.fetch(xref: gfx->getXRef());
6991 gfx->drawAnnot(str: &obj, border: nullptr, aColor: color.get(), xMin: rect->x1, yMin: rect->y1, xMax: rect->x2, yMax: rect->y2, rotate: getRotation());
6992}
6993
6994//------------------------------------------------------------------------
6995// Annot3D
6996//------------------------------------------------------------------------
6997Annot3D::Annot3D(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
6998{
6999 type = type3D;
7000
7001 annotObj.dictSet(key: "Subtype", val: Object(objName, "3D"));
7002
7003 initialize(docA, dict: annotObj.getDict());
7004}
7005
7006Annot3D::Annot3D(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
7007{
7008 type = type3D;
7009 initialize(docA, dict: annotObj.getDict());
7010}
7011
7012Annot3D::~Annot3D() = default;
7013
7014void Annot3D::initialize(PDFDoc *docA, Dict *dict)
7015{
7016 Object obj1 = dict->lookup(key: "3DA");
7017 if (obj1.isDict()) {
7018 activation = std::make_unique<Activation>(args: obj1.getDict());
7019 }
7020}
7021
7022Annot3D::Activation::Activation(Dict *dict)
7023{
7024 Object obj1;
7025
7026 obj1 = dict->lookup(key: "A");
7027 if (obj1.isName()) {
7028 const char *name = obj1.getName();
7029
7030 if (!strcmp(s1: name, s2: "PO")) {
7031 aTrigger = aTriggerPageOpened;
7032 } else if (!strcmp(s1: name, s2: "PV")) {
7033 aTrigger = aTriggerPageVisible;
7034 } else if (!strcmp(s1: name, s2: "XA")) {
7035 aTrigger = aTriggerUserAction;
7036 } else {
7037 aTrigger = aTriggerUnknown;
7038 }
7039 } else {
7040 aTrigger = aTriggerUnknown;
7041 }
7042
7043 obj1 = dict->lookup(key: "AIS");
7044 if (obj1.isName()) {
7045 const char *name = obj1.getName();
7046
7047 if (!strcmp(s1: name, s2: "I")) {
7048 aState = aStateEnabled;
7049 } else if (!strcmp(s1: name, s2: "L")) {
7050 aState = aStateDisabled;
7051 } else {
7052 aState = aStateUnknown;
7053 }
7054 } else {
7055 aState = aStateUnknown;
7056 }
7057
7058 obj1 = dict->lookup(key: "D");
7059 if (obj1.isName()) {
7060 const char *name = obj1.getName();
7061
7062 if (!strcmp(s1: name, s2: "PC")) {
7063 dTrigger = dTriggerPageClosed;
7064 } else if (!strcmp(s1: name, s2: "PI")) {
7065 dTrigger = dTriggerPageInvisible;
7066 } else if (!strcmp(s1: name, s2: "XD")) {
7067 dTrigger = dTriggerUserAction;
7068 } else {
7069 dTrigger = dTriggerUnknown;
7070 }
7071 } else {
7072 dTrigger = dTriggerUnknown;
7073 }
7074
7075 obj1 = dict->lookup(key: "DIS");
7076 if (obj1.isName()) {
7077 const char *name = obj1.getName();
7078
7079 if (!strcmp(s1: name, s2: "U")) {
7080 dState = dStateUninstantiaded;
7081 } else if (!strcmp(s1: name, s2: "I")) {
7082 dState = dStateInstantiated;
7083 } else if (!strcmp(s1: name, s2: "L")) {
7084 dState = dStateLive;
7085 } else {
7086 dState = dStateUnknown;
7087 }
7088 } else {
7089 dState = dStateUnknown;
7090 }
7091
7092 displayToolbar = dict->lookup(key: "TB").getBoolWithDefaultValue(defaultValue: true);
7093
7094 displayNavigation = dict->lookup(key: "NP").getBoolWithDefaultValue(defaultValue: false);
7095}
7096
7097//------------------------------------------------------------------------
7098// AnnotRichMedia
7099//------------------------------------------------------------------------
7100AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
7101{
7102 type = typeRichMedia;
7103
7104 annotObj.dictSet(key: "Subtype", val: Object(objName, "RichMedia"));
7105
7106 initialize(docA, dict: annotObj.getDict());
7107}
7108
7109AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
7110{
7111 type = typeRichMedia;
7112 initialize(docA, dict: annotObj.getDict());
7113}
7114
7115AnnotRichMedia::~AnnotRichMedia() = default;
7116
7117void AnnotRichMedia::initialize(PDFDoc *docA, Dict *dict)
7118{
7119 Object obj1 = dict->lookup(key: "RichMediaContent");
7120 if (obj1.isDict()) {
7121 content = std::make_unique<AnnotRichMedia::Content>(args: obj1.getDict());
7122 }
7123
7124 obj1 = dict->lookup(key: "RichMediaSettings");
7125 if (obj1.isDict()) {
7126 settings = std::make_unique<AnnotRichMedia::Settings>(args: obj1.getDict());
7127 }
7128}
7129
7130AnnotRichMedia::Content *AnnotRichMedia::getContent() const
7131{
7132 return content.get();
7133}
7134
7135AnnotRichMedia::Settings *AnnotRichMedia::getSettings() const
7136{
7137 return settings.get();
7138}
7139
7140AnnotRichMedia::Settings::Settings(Dict *dict)
7141{
7142 Object obj1 = dict->lookup(key: "Activation");
7143 if (obj1.isDict()) {
7144 activation = std::make_unique<AnnotRichMedia::Activation>(args: obj1.getDict());
7145 }
7146
7147 obj1 = dict->lookup(key: "Deactivation");
7148 if (obj1.isDict()) {
7149 deactivation = std::make_unique<AnnotRichMedia::Deactivation>(args: obj1.getDict());
7150 }
7151}
7152
7153AnnotRichMedia::Settings::~Settings() = default;
7154
7155AnnotRichMedia::Activation *AnnotRichMedia::Settings::getActivation() const
7156{
7157 return activation.get();
7158}
7159
7160AnnotRichMedia::Deactivation *AnnotRichMedia::Settings::getDeactivation() const
7161{
7162 return deactivation.get();
7163}
7164
7165AnnotRichMedia::Activation::Activation(Dict *dict)
7166{
7167 Object obj1 = dict->lookup(key: "Condition");
7168 if (obj1.isName()) {
7169 const char *name = obj1.getName();
7170
7171 if (!strcmp(s1: name, s2: "PO")) {
7172 condition = conditionPageOpened;
7173 } else if (!strcmp(s1: name, s2: "PV")) {
7174 condition = conditionPageVisible;
7175 } else if (!strcmp(s1: name, s2: "XA")) {
7176 condition = conditionUserAction;
7177 } else {
7178 condition = conditionUserAction;
7179 }
7180 } else {
7181 condition = conditionUserAction;
7182 }
7183}
7184
7185AnnotRichMedia::Activation::Condition AnnotRichMedia::Activation::getCondition() const
7186{
7187 return condition;
7188}
7189
7190AnnotRichMedia::Deactivation::Deactivation(Dict *dict)
7191{
7192 Object obj1 = dict->lookup(key: "Condition");
7193 if (obj1.isName()) {
7194 const char *name = obj1.getName();
7195
7196 if (!strcmp(s1: name, s2: "PC")) {
7197 condition = conditionPageClosed;
7198 } else if (!strcmp(s1: name, s2: "PI")) {
7199 condition = conditionPageInvisible;
7200 } else if (!strcmp(s1: name, s2: "XD")) {
7201 condition = conditionUserAction;
7202 } else {
7203 condition = conditionUserAction;
7204 }
7205 } else {
7206 condition = conditionUserAction;
7207 }
7208}
7209
7210AnnotRichMedia::Deactivation::Condition AnnotRichMedia::Deactivation::getCondition() const
7211{
7212 return condition;
7213}
7214
7215AnnotRichMedia::Content::Content(Dict *dict)
7216{
7217 Object obj1 = dict->lookup(key: "Configurations");
7218 if (obj1.isArray()) {
7219 nConfigurations = obj1.arrayGetLength();
7220
7221 configurations = (Configuration **)gmallocn(count: nConfigurations, size: sizeof(Configuration *));
7222
7223 for (int i = 0; i < nConfigurations; ++i) {
7224 Object obj2 = obj1.arrayGet(i);
7225 if (obj2.isDict()) {
7226 configurations[i] = new AnnotRichMedia::Configuration(obj2.getDict());
7227 } else {
7228 configurations[i] = nullptr;
7229 }
7230 }
7231 } else {
7232 nConfigurations = 0;
7233 configurations = nullptr;
7234 }
7235
7236 nAssets = 0;
7237 assets = nullptr;
7238 obj1 = dict->lookup(key: "Assets");
7239 if (obj1.isDict()) {
7240 Object obj2 = obj1.getDict()->lookup(key: "Names");
7241 if (obj2.isArray()) {
7242 const int length = obj2.arrayGetLength() / 2;
7243
7244 assets = (Asset **)gmallocn(count: length, size: sizeof(Asset *));
7245 for (int i = 0; i < length; ++i) {
7246 Object objKey = obj2.arrayGet(i: 2 * i);
7247 Object objVal = obj2.arrayGet(i: 2 * i + 1);
7248
7249 if (!objKey.isString() || objVal.isNull()) {
7250 error(category: errSyntaxError, pos: -1, msg: "Bad Annot Asset");
7251 continue;
7252 }
7253
7254 assets[nAssets] = new AnnotRichMedia::Asset;
7255 assets[nAssets]->name = std::make_unique<GooString>(args: objKey.getString());
7256 assets[nAssets]->fileSpec = std::move(objVal);
7257 ++nAssets;
7258 }
7259 }
7260 }
7261}
7262
7263AnnotRichMedia::Content::~Content()
7264{
7265 if (configurations) {
7266 for (int i = 0; i < nConfigurations; ++i) {
7267 delete configurations[i];
7268 }
7269 gfree(p: configurations);
7270 }
7271
7272 if (assets) {
7273 for (int i = 0; i < nAssets; ++i) {
7274 delete assets[i];
7275 }
7276 gfree(p: assets);
7277 }
7278}
7279
7280int AnnotRichMedia::Content::getConfigurationsCount() const
7281{
7282 return nConfigurations;
7283}
7284
7285AnnotRichMedia::Configuration *AnnotRichMedia::Content::getConfiguration(int index) const
7286{
7287 if (index < 0 || index >= nConfigurations) {
7288 return nullptr;
7289 }
7290
7291 return configurations[index];
7292}
7293
7294int AnnotRichMedia::Content::getAssetsCount() const
7295{
7296 return nAssets;
7297}
7298
7299AnnotRichMedia::Asset *AnnotRichMedia::Content::getAsset(int index) const
7300{
7301 if (index < 0 || index >= nAssets) {
7302 return nullptr;
7303 }
7304
7305 return assets[index];
7306}
7307
7308AnnotRichMedia::Asset::Asset() = default;
7309
7310AnnotRichMedia::Asset::~Asset() = default;
7311
7312const GooString *AnnotRichMedia::Asset::getName() const
7313{
7314 return name.get();
7315}
7316
7317Object *AnnotRichMedia::Asset::getFileSpec() const
7318{
7319 return const_cast<Object *>(&fileSpec);
7320}
7321
7322AnnotRichMedia::Configuration::Configuration(Dict *dict)
7323{
7324 Object obj1 = dict->lookup(key: "Instances");
7325 if (obj1.isArray()) {
7326 nInstances = obj1.arrayGetLength();
7327
7328 instances = (Instance **)gmallocn(count: nInstances, size: sizeof(Instance *));
7329
7330 for (int i = 0; i < nInstances; ++i) {
7331 Object obj2 = obj1.arrayGet(i);
7332 if (obj2.isDict()) {
7333 instances[i] = new AnnotRichMedia::Instance(obj2.getDict());
7334 } else {
7335 instances[i] = nullptr;
7336 }
7337 }
7338 } else {
7339 instances = nullptr;
7340 }
7341
7342 obj1 = dict->lookup(key: "Name");
7343 if (obj1.isString()) {
7344 name = std::make_unique<GooString>(args: obj1.getString());
7345 }
7346
7347 obj1 = dict->lookup(key: "Subtype");
7348 if (obj1.isName()) {
7349 const char *subtypeName = obj1.getName();
7350
7351 if (!strcmp(s1: subtypeName, s2: "3D")) {
7352 type = type3D;
7353 } else if (!strcmp(s1: subtypeName, s2: "Flash")) {
7354 type = typeFlash;
7355 } else if (!strcmp(s1: subtypeName, s2: "Sound")) {
7356 type = typeSound;
7357 } else if (!strcmp(s1: subtypeName, s2: "Video")) {
7358 type = typeVideo;
7359 } else {
7360 // determine from first non null instance
7361 type = typeFlash; // default in case all instances are null
7362 if (instances && nInstances > 0) {
7363 for (int i = 0; i < nInstances; ++i) {
7364 AnnotRichMedia::Instance *instance = instances[i];
7365 if (instance) {
7366 switch (instance->getType()) {
7367 case AnnotRichMedia::Instance::type3D:
7368 type = type3D;
7369 break;
7370 case AnnotRichMedia::Instance::typeFlash:
7371 type = typeFlash;
7372 break;
7373 case AnnotRichMedia::Instance::typeSound:
7374 type = typeSound;
7375 break;
7376 case AnnotRichMedia::Instance::typeVideo:
7377 type = typeVideo;
7378 break;
7379 }
7380 // break the loop since we found the first non null instance
7381 break;
7382 }
7383 }
7384 }
7385 }
7386 }
7387}
7388
7389AnnotRichMedia::Configuration::~Configuration()
7390{
7391 if (instances) {
7392 for (int i = 0; i < nInstances; ++i) {
7393 delete instances[i];
7394 }
7395 gfree(p: instances);
7396 }
7397}
7398
7399int AnnotRichMedia::Configuration::getInstancesCount() const
7400{
7401 return nInstances;
7402}
7403
7404AnnotRichMedia::Instance *AnnotRichMedia::Configuration::getInstance(int index) const
7405{
7406 if (index < 0 || index >= nInstances) {
7407 return nullptr;
7408 }
7409
7410 return instances[index];
7411}
7412
7413const GooString *AnnotRichMedia::Configuration::getName() const
7414{
7415 return name.get();
7416}
7417
7418AnnotRichMedia::Configuration::Type AnnotRichMedia::Configuration::getType() const
7419{
7420 return type;
7421}
7422
7423AnnotRichMedia::Instance::Instance(Dict *dict)
7424{
7425 Object obj1 = dict->lookup(key: "Subtype");
7426 const char *name = obj1.isName() ? obj1.getName() : "";
7427
7428 if (!strcmp(s1: name, s2: "3D")) {
7429 type = type3D;
7430 } else if (!strcmp(s1: name, s2: "Flash")) {
7431 type = typeFlash;
7432 } else if (!strcmp(s1: name, s2: "Sound")) {
7433 type = typeSound;
7434 } else if (!strcmp(s1: name, s2: "Video")) {
7435 type = typeVideo;
7436 } else {
7437 type = typeFlash;
7438 }
7439
7440 obj1 = dict->lookup(key: "Params");
7441 if (obj1.isDict()) {
7442 params = std::make_unique<AnnotRichMedia::Params>(args: obj1.getDict());
7443 }
7444}
7445
7446AnnotRichMedia::Instance::~Instance() = default;
7447
7448AnnotRichMedia::Instance::Type AnnotRichMedia::Instance::getType() const
7449{
7450 return type;
7451}
7452
7453AnnotRichMedia::Params *AnnotRichMedia::Instance::getParams() const
7454{
7455 return params.get();
7456}
7457
7458AnnotRichMedia::Params::Params(Dict *dict)
7459{
7460 Object obj1 = dict->lookup(key: "FlashVars");
7461 if (obj1.isString()) {
7462 flashVars = std::make_unique<GooString>(args: obj1.getString());
7463 }
7464}
7465
7466AnnotRichMedia::Params::~Params() = default;
7467
7468const GooString *AnnotRichMedia::Params::getFlashVars() const
7469{
7470 return flashVars.get();
7471}
7472
7473//------------------------------------------------------------------------
7474// Annots
7475//------------------------------------------------------------------------
7476
7477Annots::Annots(PDFDoc *docA, int page, Object *annotsObj)
7478{
7479 Annot *annot;
7480 int i;
7481
7482 doc = docA;
7483
7484 if (annotsObj->isArray()) {
7485 for (i = 0; i < annotsObj->arrayGetLength(); ++i) {
7486 // get the Ref to this annot and pass it to Annot constructor
7487 // this way, it'll be possible for the annot to retrieve the corresponding
7488 // form widget
7489 Object obj1 = annotsObj->arrayGet(i);
7490 if (obj1.isDict()) {
7491 const Object &obj2 = annotsObj->arrayGetNF(i);
7492 annot = createAnnot(dictObject: std::move(obj1), obj: &obj2);
7493 if (annot) {
7494 if (annot->isOk()) {
7495 annot->setPage(pageIndex: page, updateP: false); // Don't change /P
7496 appendAnnot(annot);
7497 }
7498 annot->decRefCnt();
7499 }
7500 }
7501 }
7502 }
7503}
7504
7505void Annots::appendAnnot(Annot *annot)
7506{
7507 if (annot && annot->isOk()) {
7508 annots.push_back(x: annot);
7509 annot->incRefCnt();
7510 }
7511}
7512
7513bool Annots::removeAnnot(Annot *annot)
7514{
7515 auto idx = std::find(first: annots.begin(), last: annots.end(), val: annot);
7516
7517 if (idx == annots.end()) {
7518 return false;
7519 } else {
7520 annot->decRefCnt();
7521 annots.erase(position: idx);
7522 return true;
7523 }
7524}
7525
7526Annot *Annots::createAnnot(Object &&dictObject, const Object *obj)
7527{
7528 Annot *annot = nullptr;
7529 Object obj1 = dictObject.dictLookup(key: "Subtype");
7530 if (obj1.isName()) {
7531 const char *typeName = obj1.getName();
7532
7533 if (!strcmp(s1: typeName, s2: "Text")) {
7534 annot = new AnnotText(doc, std::move(dictObject), obj);
7535 } else if (!strcmp(s1: typeName, s2: "Link")) {
7536 annot = new AnnotLink(doc, std::move(dictObject), obj);
7537 } else if (!strcmp(s1: typeName, s2: "FreeText")) {
7538 annot = new AnnotFreeText(doc, std::move(dictObject), obj);
7539 } else if (!strcmp(s1: typeName, s2: "Line")) {
7540 annot = new AnnotLine(doc, std::move(dictObject), obj);
7541 } else if (!strcmp(s1: typeName, s2: "Square")) {
7542 annot = new AnnotGeometry(doc, std::move(dictObject), obj);
7543 } else if (!strcmp(s1: typeName, s2: "Circle")) {
7544 annot = new AnnotGeometry(doc, std::move(dictObject), obj);
7545 } else if (!strcmp(s1: typeName, s2: "Polygon")) {
7546 annot = new AnnotPolygon(doc, std::move(dictObject), obj);
7547 } else if (!strcmp(s1: typeName, s2: "PolyLine")) {
7548 annot = new AnnotPolygon(doc, std::move(dictObject), obj);
7549 } else if (!strcmp(s1: typeName, s2: "Highlight")) {
7550 annot = new AnnotTextMarkup(doc, std::move(dictObject), obj);
7551 } else if (!strcmp(s1: typeName, s2: "Underline")) {
7552 annot = new AnnotTextMarkup(doc, std::move(dictObject), obj);
7553 } else if (!strcmp(s1: typeName, s2: "Squiggly")) {
7554 annot = new AnnotTextMarkup(doc, std::move(dictObject), obj);
7555 } else if (!strcmp(s1: typeName, s2: "StrikeOut")) {
7556 annot = new AnnotTextMarkup(doc, std::move(dictObject), obj);
7557 } else if (!strcmp(s1: typeName, s2: "Stamp")) {
7558 annot = new AnnotStamp(doc, std::move(dictObject), obj);
7559 } else if (!strcmp(s1: typeName, s2: "Caret")) {
7560 annot = new AnnotCaret(doc, std::move(dictObject), obj);
7561 } else if (!strcmp(s1: typeName, s2: "Ink")) {
7562 annot = new AnnotInk(doc, std::move(dictObject), obj);
7563 } else if (!strcmp(s1: typeName, s2: "FileAttachment")) {
7564 annot = new AnnotFileAttachment(doc, std::move(dictObject), obj);
7565 } else if (!strcmp(s1: typeName, s2: "Sound")) {
7566 annot = new AnnotSound(doc, std::move(dictObject), obj);
7567 } else if (!strcmp(s1: typeName, s2: "Movie")) {
7568 annot = new AnnotMovie(doc, std::move(dictObject), obj);
7569 } else if (!strcmp(s1: typeName, s2: "Widget")) {
7570 // Find the annot in forms
7571 if (obj->isRef()) {
7572 Form *form = doc->getCatalog()->getForm();
7573 if (form) {
7574 FormWidget *widget = form->findWidgetByRef(aref: obj->getRef());
7575 if (widget) {
7576 annot = widget->getWidgetAnnotation();
7577 annot->incRefCnt();
7578 }
7579 }
7580 }
7581 if (!annot) {
7582 annot = new AnnotWidget(doc, std::move(dictObject), obj);
7583 }
7584 } else if (!strcmp(s1: typeName, s2: "Screen")) {
7585 annot = new AnnotScreen(doc, std::move(dictObject), obj);
7586 } else if (!strcmp(s1: typeName, s2: "PrinterMark")) {
7587 annot = new Annot(doc, std::move(dictObject), obj);
7588 } else if (!strcmp(s1: typeName, s2: "TrapNet")) {
7589 annot = new Annot(doc, std::move(dictObject), obj);
7590 } else if (!strcmp(s1: typeName, s2: "Watermark")) {
7591 annot = new Annot(doc, std::move(dictObject), obj);
7592 } else if (!strcmp(s1: typeName, s2: "3D")) {
7593 annot = new Annot3D(doc, std::move(dictObject), obj);
7594 } else if (!strcmp(s1: typeName, s2: "RichMedia")) {
7595 annot = new AnnotRichMedia(doc, std::move(dictObject), obj);
7596 } else if (!strcmp(s1: typeName, s2: "Popup")) {
7597 /* Popup annots are already handled by markup annots
7598 * Here we only care about popup annots without a
7599 * markup annotation associated
7600 */
7601 Object obj2 = dictObject.dictLookup(key: "Parent");
7602 if (obj2.isNull()) {
7603 annot = new AnnotPopup(doc, std::move(dictObject), obj);
7604 } else {
7605 annot = nullptr;
7606 }
7607 } else {
7608 annot = new Annot(doc, std::move(dictObject), obj);
7609 }
7610 }
7611
7612 return annot;
7613}
7614
7615Annot *Annots::findAnnot(Ref *ref)
7616{
7617 for (auto *annot : annots) {
7618 if (annot->match(refA: ref)) {
7619 return annot;
7620 }
7621 }
7622 return nullptr;
7623}
7624
7625Annots::~Annots()
7626{
7627 for (auto *annot : annots) {
7628 annot->decRefCnt();
7629 }
7630}
7631
7632//------------------------------------------------------------------------
7633// AnnotAppearanceBuilder
7634//------------------------------------------------------------------------
7635
7636AnnotAppearanceBuilder::AnnotAppearanceBuilder() : appearBuf(new GooString()) { }
7637
7638AnnotAppearanceBuilder::~AnnotAppearanceBuilder()
7639{
7640 delete appearBuf;
7641}
7642
7643void AnnotAppearanceBuilder::append(const char *text)
7644{
7645 appearBuf->append(str: text);
7646}
7647
7648void AnnotAppearanceBuilder::appendf(const char *fmt, ...) GOOSTRING_FORMAT
7649{
7650 va_list argList;
7651
7652 va_start(argList, fmt);
7653 appearBuf->appendfv(fmt, argList);
7654 va_end(argList);
7655}
7656
7657const GooString *AnnotAppearanceBuilder::buffer() const
7658{
7659 return appearBuf;
7660}
7661

source code of poppler/poppler/Annot.cc