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 | |
139 | static 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 | |
168 | static 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 | |
194 | static 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 | |
214 | static 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 | |
237 | static 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 | |
263 | static 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 | |
268 | static 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 | |
287 | AnnotBorderEffect::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 | |
315 | AnnotPath::AnnotPath() = default; |
316 | |
317 | AnnotPath::AnnotPath(Array *array) |
318 | { |
319 | parsePathArray(array); |
320 | } |
321 | |
322 | AnnotPath::AnnotPath(std::vector<AnnotCoord> &&coordsA) |
323 | { |
324 | coords = std::move(coordsA); |
325 | } |
326 | |
327 | AnnotPath::~AnnotPath() = default; |
328 | |
329 | double AnnotPath::getX(int coord) const |
330 | { |
331 | if (coord >= 0 && coord < getCoordsLength()) { |
332 | return coords[coord].getX(); |
333 | } |
334 | return 0; |
335 | } |
336 | |
337 | double AnnotPath::getY(int coord) const |
338 | { |
339 | if (coord >= 0 && coord < getCoordsLength()) { |
340 | return coords[coord].getY(); |
341 | } |
342 | return 0; |
343 | } |
344 | |
345 | AnnotCoord *AnnotPath::getCoord(int coord) |
346 | { |
347 | if (coord >= 0 && coord < getCoordsLength()) { |
348 | return &coords[coord]; |
349 | } |
350 | return nullptr; |
351 | } |
352 | |
353 | void 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 | |
390 | AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2) : coord1(x1, y1), coord2(x2, y2) { } |
391 | |
392 | AnnotCalloutLine::~AnnotCalloutLine() = default; |
393 | |
394 | //------------------------------------------------------------------------ |
395 | // AnnotCalloutMultiLine |
396 | //------------------------------------------------------------------------ |
397 | |
398 | AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, double y2, double x3, double y3) : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) { } |
399 | |
400 | AnnotCalloutMultiLine::~AnnotCalloutMultiLine() = default; |
401 | |
402 | //------------------------------------------------------------------------ |
403 | // AnnotQuadrilateral |
404 | //------------------------------------------------------------------------ |
405 | |
406 | AnnotQuadrilaterals::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 | |
439 | AnnotQuadrilaterals::AnnotQuadrilaterals(std::unique_ptr<AnnotQuadrilateral[]> &&quads, int quadsLength) |
440 | { |
441 | quadrilaterals = std::move(quads); |
442 | quadrilateralsLength = quadsLength; |
443 | } |
444 | |
445 | AnnotQuadrilaterals::~AnnotQuadrilaterals() = default; |
446 | |
447 | double AnnotQuadrilaterals::getX1(int quadrilateral) |
448 | { |
449 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
450 | return quadrilaterals[quadrilateral].coord1.getX(); |
451 | } |
452 | return 0; |
453 | } |
454 | |
455 | double AnnotQuadrilaterals::getY1(int quadrilateral) |
456 | { |
457 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
458 | return quadrilaterals[quadrilateral].coord1.getY(); |
459 | } |
460 | return 0; |
461 | } |
462 | |
463 | double AnnotQuadrilaterals::getX2(int quadrilateral) |
464 | { |
465 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
466 | return quadrilaterals[quadrilateral].coord2.getX(); |
467 | } |
468 | return 0; |
469 | } |
470 | |
471 | double AnnotQuadrilaterals::getY2(int quadrilateral) |
472 | { |
473 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
474 | return quadrilaterals[quadrilateral].coord2.getY(); |
475 | } |
476 | return 0; |
477 | } |
478 | |
479 | double AnnotQuadrilaterals::getX3(int quadrilateral) |
480 | { |
481 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
482 | return quadrilaterals[quadrilateral].coord3.getX(); |
483 | } |
484 | return 0; |
485 | } |
486 | |
487 | double AnnotQuadrilaterals::getY3(int quadrilateral) |
488 | { |
489 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
490 | return quadrilaterals[quadrilateral].coord3.getY(); |
491 | } |
492 | return 0; |
493 | } |
494 | |
495 | double AnnotQuadrilaterals::getX4(int quadrilateral) |
496 | { |
497 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
498 | return quadrilaterals[quadrilateral].coord4.getX(); |
499 | } |
500 | return 0; |
501 | } |
502 | |
503 | double AnnotQuadrilaterals::getY4(int quadrilateral) |
504 | { |
505 | if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) { |
506 | return quadrilaterals[quadrilateral].coord4.getY(); |
507 | } |
508 | return 0; |
509 | } |
510 | |
511 | AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral() = default; |
512 | |
513 | AnnotQuadrilaterals::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 | //------------------------------------------------------------------------ |
518 | AnnotBorder::AnnotBorder() |
519 | { |
520 | width = 1; |
521 | style = borderSolid; |
522 | } |
523 | |
524 | bool 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 | |
550 | AnnotBorder::~AnnotBorder() = default; |
551 | |
552 | //------------------------------------------------------------------------ |
553 | // AnnotBorderArray |
554 | //------------------------------------------------------------------------ |
555 | |
556 | AnnotBorderArray::AnnotBorderArray() |
557 | { |
558 | horizontalCorner = 0; |
559 | verticalCorner = 0; |
560 | } |
561 | |
562 | AnnotBorderArray::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 | |
609 | std::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 | |
621 | Object 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 | |
645 | AnnotBorderBS::AnnotBorderBS() { } |
646 | |
647 | AnnotBorderBS::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 | |
684 | const 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 | |
702 | std::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 | |
712 | Object 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 | |
732 | AnnotColor::AnnotColor() |
733 | { |
734 | length = 0; |
735 | } |
736 | |
737 | AnnotColor::AnnotColor(double gray) |
738 | { |
739 | length = 1; |
740 | |
741 | values[0] = gray; |
742 | } |
743 | |
744 | AnnotColor::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 | |
753 | AnnotColor::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. |
766 | AnnotColor::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 | |
793 | void 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 | |
811 | Object 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 | |
828 | DefaultAppearance::DefaultAppearance(Object &&fontNameA, double fontPtSizeA, std::unique_ptr<AnnotColor> &&fontColorA) : fontName(std::move(fontNameA)), fontPtSize(fontPtSizeA), fontColor(std::move(fontColorA)) { } |
829 | |
830 | DefaultAppearance::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 | |
864 | void DefaultAppearance::setFontName(Object &&fontNameA) |
865 | { |
866 | fontName = std::move(fontNameA); |
867 | } |
868 | |
869 | void DefaultAppearance::setFontPtSize(double fontPtSizeA) |
870 | { |
871 | fontPtSize = fontPtSizeA; |
872 | } |
873 | |
874 | void DefaultAppearance::setFontColor(std::unique_ptr<AnnotColor> fontColorA) |
875 | { |
876 | fontColor = std::move(fontColorA); |
877 | } |
878 | |
879 | std::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 | |
893 | AnnotIconFit::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 | |
951 | AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict) |
952 | { |
953 | assert(dict->isDict()); |
954 | doc = docA; |
955 | appearDict = dict->copy(); |
956 | } |
957 | |
958 | AnnotAppearance::~AnnotAppearance() { } |
959 | |
960 | Object 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 | |
992 | std::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 | |
1001 | int 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 |
1012 | bool 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 |
1035 | bool 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 |
1059 | void 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 |
1082 | void 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 | |
1097 | void 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 | |
1111 | AnnotAppearanceCharacs::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 | |
1172 | AnnotAppearanceCharacs::~AnnotAppearanceCharacs() = default; |
1173 | |
1174 | std::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 | |
1204 | AnnotAppearanceBBox::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 | |
1217 | void 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 | |
1231 | void 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 | |
1239 | double AnnotAppearanceBBox::getPageXMin() const |
1240 | { |
1241 | return origX + minX - borderWidth; |
1242 | } |
1243 | |
1244 | double AnnotAppearanceBBox::getPageYMin() const |
1245 | { |
1246 | return origY + minY - borderWidth; |
1247 | } |
1248 | |
1249 | double AnnotAppearanceBBox::getPageXMax() const |
1250 | { |
1251 | return origX + maxX + borderWidth; |
1252 | } |
1253 | |
1254 | double 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 | |
1265 | Annot::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 | |
1287 | Annot::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 | |
1297 | Annot::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 | |
1312 | void 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 | |
1436 | void 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 | |
1444 | void Annot::setRect(const PDFRectangle *rectA) |
1445 | { |
1446 | setRect(x1: rectA->x1, y1: rectA->y1, x2: rectA->x2, y2: rectA->y2); |
1447 | } |
1448 | |
1449 | void 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 | |
1477 | bool Annot::inRect(double x, double y) const |
1478 | { |
1479 | return rect->contains(x, y); |
1480 | } |
1481 | |
1482 | void 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 | |
1499 | void 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 | |
1516 | void 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 | |
1529 | void 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 | |
1542 | void Annot::setFlags(unsigned int new_flags) |
1543 | { |
1544 | annotLocker(); |
1545 | flags = new_flags; |
1546 | update(key: "F" , value: Object(int(flags))); |
1547 | } |
1548 | |
1549 | void 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 | |
1563 | void 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 | |
1577 | void 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 = 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 | |
1596 | void 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 | |
1616 | void 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 | |
1638 | double Annot::getXMin() |
1639 | { |
1640 | return rect->x1; |
1641 | } |
1642 | |
1643 | double Annot::getYMin() |
1644 | { |
1645 | return rect->y1; |
1646 | } |
1647 | |
1648 | double Annot::getXMax() |
1649 | { |
1650 | return rect->x2; |
1651 | } |
1652 | |
1653 | double Annot::getYMax() |
1654 | { |
1655 | return rect->y2; |
1656 | } |
1657 | |
1658 | void 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 | |
1669 | void Annot::removeReferencedObjects() |
1670 | { |
1671 | // Remove appearance streams (if any) |
1672 | invalidateAppearance(); |
1673 | } |
1674 | |
1675 | void Annot::incRefCnt() |
1676 | { |
1677 | refCnt++; |
1678 | } |
1679 | |
1680 | void Annot::decRefCnt() |
1681 | { |
1682 | if (--refCnt == 0) { |
1683 | delete this; |
1684 | } |
1685 | } |
1686 | |
1687 | Annot::~Annot() { } |
1688 | |
1689 | void 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 | |
1709 | void 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 | |
1716 | void 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. |
1735 | void 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. |
1748 | void 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>). |
1766 | void 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>). |
1779 | void 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 | |
1790 | void 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 | |
1806 | void 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 | |
1829 | void 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 | |
1845 | void 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 | |
1866 | void 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 | |
1879 | void 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 | |
1919 | double 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 | |
1933 | double 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 | |
1947 | Object 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 | |
1952 | Object 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 | |
1977 | Dict *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 | |
1999 | Object 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 | |
2015 | bool 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 | |
2033 | int 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 | |
2045 | void 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 | |
2057 | void 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 | |
2087 | Object Annot::getAppearance() const |
2088 | { |
2089 | return appearance.fetch(xref: doc->getXRef()); |
2090 | } |
2091 | |
2092 | //------------------------------------------------------------------------ |
2093 | // AnnotPopup |
2094 | //------------------------------------------------------------------------ |
2095 | |
2096 | 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 | |
2104 | 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 | |
2110 | AnnotPopup::() { } |
2111 | |
2112 | void AnnotPopup::(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 | |
2124 | void AnnotPopup::(Annot *parentA) |
2125 | { |
2126 | parentRef = parentA->getRef(); |
2127 | update(key: "Parent" , value: Object(parentRef)); |
2128 | } |
2129 | |
2130 | void AnnotPopup::(bool openA) |
2131 | { |
2132 | open = openA; |
2133 | update(key: "Open" , value: Object(open)); |
2134 | } |
2135 | |
2136 | //------------------------------------------------------------------------ |
2137 | // AnnotMarkup |
2138 | //------------------------------------------------------------------------ |
2139 | AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) |
2140 | { |
2141 | initialize(docA, dict: annotObj.getDict()); |
2142 | } |
2143 | |
2144 | AnnotMarkup::AnnotMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) |
2145 | { |
2146 | initialize(docA, dict: annotObj.getDict()); |
2147 | } |
2148 | |
2149 | AnnotMarkup::~AnnotMarkup() = default; |
2150 | |
2151 | void 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 = 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 | |
2208 | void 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 | |
2223 | void AnnotMarkup::(std::unique_ptr<AnnotPopup> &&) |
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 = 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 | |
2256 | void AnnotMarkup::setOpacity(double opacityA) |
2257 | { |
2258 | opacity = opacityA; |
2259 | update(key: "CA" , value: Object(opacity)); |
2260 | invalidateAppearance(); |
2261 | } |
2262 | |
2263 | void 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 | |
2274 | void 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 | |
2291 | AnnotText::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 | |
2300 | AnnotText::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 | |
2308 | AnnotText::~AnnotText() = default; |
2309 | |
2310 | void 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 | |
2387 | void AnnotText::setOpen(bool openA) |
2388 | { |
2389 | open = openA; |
2390 | update(key: "Open" , value: Object(open)); |
2391 | } |
2392 | |
2393 | void 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 \ |
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 | |
2653 | void 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 | //------------------------------------------------------------------------ |
2722 | AnnotLink::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 | |
2729 | AnnotLink::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 | |
2736 | AnnotLink::~AnnotLink() = default; |
2737 | |
2738 | void 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 | |
2794 | void 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 | //------------------------------------------------------------------------ |
2809 | const double AnnotFreeText::undefinedFontPtSize = 10.; |
2810 | |
2811 | AnnotFreeText::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 | |
2821 | AnnotFreeText::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 | |
2827 | AnnotFreeText::~AnnotFreeText() = default; |
2828 | |
2829 | void 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 | |
2912 | void AnnotFreeText::setContents(std::unique_ptr<GooString> &&new_content) |
2913 | { |
2914 | Annot::setContents(std::move(new_content)); |
2915 | invalidateAppearance(); |
2916 | } |
2917 | |
2918 | void 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 | |
2926 | void AnnotFreeText::setQuadding(VariableTextQuadding new_quadding) |
2927 | { |
2928 | quadding = new_quadding; |
2929 | update(key: "Q" , value: Object((int)quadding)); |
2930 | invalidateAppearance(); |
2931 | } |
2932 | |
2933 | void 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 | |
2948 | void 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 | |
2978 | void 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 | |
2993 | std::unique_ptr<DefaultAppearance> AnnotFreeText::getDefaultAppearance() const |
2994 | { |
2995 | return std::make_unique<DefaultAppearance>(args: appearanceString.get()); |
2996 | } |
2997 | |
2998 | static 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 | |
3020 | class HorizontalTextLayouter |
3021 | { |
3022 | public: |
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 | |
3136 | double 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 | |
3166 | struct 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 |
3175 | static 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 | |
3243 | void 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 | |
3363 | void 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 |
3381 | Object AnnotFreeText::getAppearanceResDict() |
3382 | { |
3383 | if (appearance.isNull()) { |
3384 | generateFreeTextAppearance(); |
3385 | } |
3386 | return Annot::getAppearanceResDict(); |
3387 | } |
3388 | |
3389 | //------------------------------------------------------------------------ |
3390 | // AnnotLine |
3391 | //------------------------------------------------------------------------ |
3392 | |
3393 | AnnotLine::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 | |
3401 | AnnotLine::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 | |
3407 | AnnotLine::~AnnotLine() = default; |
3408 | |
3409 | void 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 | |
3523 | void AnnotLine::setContents(std::unique_ptr<GooString> &&new_content) |
3524 | { |
3525 | Annot::setContents(std::move(new_content)); |
3526 | if (caption) { |
3527 | invalidateAppearance(); |
3528 | } |
3529 | } |
3530 | |
3531 | void 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 | |
3546 | void 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 | |
3559 | void 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 | |
3571 | void AnnotLine::setLeaderLineLength(double len) |
3572 | { |
3573 | leaderLineLength = len; |
3574 | update(key: "LL" , value: Object(len)); |
3575 | invalidateAppearance(); |
3576 | } |
3577 | |
3578 | void 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 | |
3588 | void AnnotLine::setCaption(bool new_cap) |
3589 | { |
3590 | caption = new_cap; |
3591 | update(key: "Cap" , value: Object(new_cap)); |
3592 | invalidateAppearance(); |
3593 | } |
3594 | |
3595 | void 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 | |
3608 | void 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 | |
3792 | void 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 |
3814 | Object AnnotLine::getAppearanceResDict() |
3815 | { |
3816 | if (appearance.isNull()) { |
3817 | generateLineAppearance(); |
3818 | } |
3819 | return Annot::getAppearanceResDict(); |
3820 | } |
3821 | |
3822 | //------------------------------------------------------------------------ |
3823 | // AnnotTextMarkup |
3824 | //------------------------------------------------------------------------ |
3825 | AnnotTextMarkup::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 | |
3854 | AnnotTextMarkup::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 | |
3861 | AnnotTextMarkup::~AnnotTextMarkup() = default; |
3862 | |
3863 | void 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 | |
3890 | void 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 | |
3916 | void 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 | |
3937 | bool 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 | |
3964 | void 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 | |
4132 | AnnotWidget::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 | |
4139 | AnnotWidget::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 | |
4146 | AnnotWidget::~AnnotWidget() = default; |
4147 | |
4148 | void 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 | |
4198 | std::unique_ptr<LinkAction> AnnotWidget::getAdditionalAction(AdditionalActionsType additionalActionType) |
4199 | { |
4200 | return ::getAdditionalAction(type: additionalActionType, additionalActions: &additionalActions, doc); |
4201 | } |
4202 | |
4203 | std::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 | |
4219 | bool 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? |
4265 | void 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. |
4494 | void 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. |
4513 | bool 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 = 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. |
4827 | bool 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 | |
5011 | void 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 | |
5109 | bool 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 | |
5133 | bool 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 | |
5179 | bool 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 | |
5210 | static 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 | |
5220 | bool 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 | |
5257 | void 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 | |
5300 | bool 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? |
5329 | static 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 | |
5353 | static void recursiveMergeDicts(Dict *primary, const Dict *secondary) |
5354 | { |
5355 | RefRecursionChecker alreadySeenDicts; |
5356 | recursiveMergeDicts(primary, secondary, alreadySeenDicts: &alreadySeenDicts); |
5357 | } |
5358 | |
5359 | void 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 | |
5454 | void 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 | |
5499 | void 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 | |
5521 | void AnnotWidget::invalidateAppearance() |
5522 | { |
5523 | updatedAppearanceStream = Ref::INVALID(); |
5524 | Annot::invalidateAppearance(); |
5525 | } |
5526 | |
5527 | //------------------------------------------------------------------------ |
5528 | // AnnotMovie |
5529 | //------------------------------------------------------------------------ |
5530 | AnnotMovie::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 | |
5541 | AnnotMovie::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 | |
5547 | AnnotMovie::~AnnotMovie() = default; |
5548 | |
5549 | void 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 | |
5576 | void 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 | //------------------------------------------------------------------------ |
5655 | AnnotScreen::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 | |
5663 | AnnotScreen::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 | |
5669 | AnnotScreen::~AnnotScreen() = default; |
5670 | |
5671 | void 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 | |
5698 | std::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 | //------------------------------------------------------------------------ |
5710 | AnnotStamp::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 | |
5717 | AnnotStamp::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 | |
5723 | AnnotStamp::~AnnotStamp() |
5724 | { |
5725 | delete stampImageHelper; |
5726 | } |
5727 | |
5728 | void 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 | |
5741 | void 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 | |
5762 | void 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 | |
5859 | void 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 | |
5883 | void 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 | |
5895 | void 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 | |
5919 | void 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 | //------------------------------------------------------------------------ |
5932 | AnnotGeometry::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 | |
5948 | AnnotGeometry::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 | |
5955 | AnnotGeometry::~AnnotGeometry() = default; |
5956 | |
5957 | void 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 | |
5994 | void 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 | |
6014 | void 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 | |
6027 | void 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 | //------------------------------------------------------------------------ |
6095 | AnnotPolygon::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 | |
6117 | AnnotPolygon::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 | |
6124 | AnnotPolygon::~AnnotPolygon() = default; |
6125 | |
6126 | void 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 | |
6202 | void 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 | |
6222 | void 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 | |
6236 | void 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 | |
6249 | void 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 | |
6262 | void 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 | |
6277 | void 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 | |
6352 | void 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 | //------------------------------------------------------------------------ |
6430 | AnnotCaret::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 | |
6438 | AnnotCaret::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 | |
6444 | AnnotCaret::~AnnotCaret() = default; |
6445 | |
6446 | void 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 | |
6467 | void 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 | //------------------------------------------------------------------------ |
6477 | AnnotInk::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 | |
6494 | AnnotInk::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 | |
6500 | AnnotInk::~AnnotInk() |
6501 | { |
6502 | freeInkList(); |
6503 | } |
6504 | |
6505 | void 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 | |
6534 | void 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 | |
6547 | void 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 | |
6560 | void 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 | |
6570 | void 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 | |
6582 | void 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 | //------------------------------------------------------------------------ |
6647 | AnnotFileAttachment::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 | |
6657 | AnnotFileAttachment::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 | |
6663 | AnnotFileAttachment::~AnnotFileAttachment() = default; |
6664 | |
6665 | void 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 | |
6795 | void 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 | //------------------------------------------------------------------------ |
6848 | AnnotSound::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 | |
6858 | AnnotSound::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 | |
6864 | AnnotSound::~AnnotSound() = default; |
6865 | |
6866 | void 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 | |
6947 | void 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 | //------------------------------------------------------------------------ |
6997 | Annot3D::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 | |
7006 | Annot3D::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 | |
7012 | Annot3D::~Annot3D() = default; |
7013 | |
7014 | void 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 | |
7022 | Annot3D::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 | //------------------------------------------------------------------------ |
7100 | AnnotRichMedia::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 | |
7109 | AnnotRichMedia::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 | |
7115 | AnnotRichMedia::~AnnotRichMedia() = default; |
7116 | |
7117 | void 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 | |
7130 | AnnotRichMedia::Content *AnnotRichMedia::getContent() const |
7131 | { |
7132 | return content.get(); |
7133 | } |
7134 | |
7135 | AnnotRichMedia::Settings *AnnotRichMedia::getSettings() const |
7136 | { |
7137 | return settings.get(); |
7138 | } |
7139 | |
7140 | AnnotRichMedia::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 | |
7153 | AnnotRichMedia::Settings::~Settings() = default; |
7154 | |
7155 | AnnotRichMedia::Activation *AnnotRichMedia::Settings::getActivation() const |
7156 | { |
7157 | return activation.get(); |
7158 | } |
7159 | |
7160 | AnnotRichMedia::Deactivation *AnnotRichMedia::Settings::getDeactivation() const |
7161 | { |
7162 | return deactivation.get(); |
7163 | } |
7164 | |
7165 | AnnotRichMedia::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 | |
7185 | AnnotRichMedia::Activation::Condition AnnotRichMedia::Activation::getCondition() const |
7186 | { |
7187 | return condition; |
7188 | } |
7189 | |
7190 | AnnotRichMedia::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 | |
7210 | AnnotRichMedia::Deactivation::Condition AnnotRichMedia::Deactivation::getCondition() const |
7211 | { |
7212 | return condition; |
7213 | } |
7214 | |
7215 | AnnotRichMedia::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 | |
7263 | AnnotRichMedia::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 | |
7280 | int AnnotRichMedia::Content::getConfigurationsCount() const |
7281 | { |
7282 | return nConfigurations; |
7283 | } |
7284 | |
7285 | AnnotRichMedia::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 | |
7294 | int AnnotRichMedia::Content::getAssetsCount() const |
7295 | { |
7296 | return nAssets; |
7297 | } |
7298 | |
7299 | AnnotRichMedia::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 | |
7308 | AnnotRichMedia::Asset::Asset() = default; |
7309 | |
7310 | AnnotRichMedia::Asset::~Asset() = default; |
7311 | |
7312 | const GooString *AnnotRichMedia::Asset::getName() const |
7313 | { |
7314 | return name.get(); |
7315 | } |
7316 | |
7317 | Object *AnnotRichMedia::Asset::getFileSpec() const |
7318 | { |
7319 | return const_cast<Object *>(&fileSpec); |
7320 | } |
7321 | |
7322 | AnnotRichMedia::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 | |
7389 | AnnotRichMedia::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 | |
7399 | int AnnotRichMedia::Configuration::getInstancesCount() const |
7400 | { |
7401 | return nInstances; |
7402 | } |
7403 | |
7404 | AnnotRichMedia::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 | |
7413 | const GooString *AnnotRichMedia::Configuration::getName() const |
7414 | { |
7415 | return name.get(); |
7416 | } |
7417 | |
7418 | AnnotRichMedia::Configuration::Type AnnotRichMedia::Configuration::getType() const |
7419 | { |
7420 | return type; |
7421 | } |
7422 | |
7423 | AnnotRichMedia::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 | |
7446 | AnnotRichMedia::Instance::~Instance() = default; |
7447 | |
7448 | AnnotRichMedia::Instance::Type AnnotRichMedia::Instance::getType() const |
7449 | { |
7450 | return type; |
7451 | } |
7452 | |
7453 | AnnotRichMedia::Params *AnnotRichMedia::Instance::getParams() const |
7454 | { |
7455 | return params.get(); |
7456 | } |
7457 | |
7458 | AnnotRichMedia::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 | |
7466 | AnnotRichMedia::Params::~Params() = default; |
7467 | |
7468 | const GooString *AnnotRichMedia::Params::getFlashVars() const |
7469 | { |
7470 | return flashVars.get(); |
7471 | } |
7472 | |
7473 | //------------------------------------------------------------------------ |
7474 | // Annots |
7475 | //------------------------------------------------------------------------ |
7476 | |
7477 | Annots::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 | |
7505 | void Annots::appendAnnot(Annot *annot) |
7506 | { |
7507 | if (annot && annot->isOk()) { |
7508 | annots.push_back(x: annot); |
7509 | annot->incRefCnt(); |
7510 | } |
7511 | } |
7512 | |
7513 | bool 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 | |
7526 | Annot *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 | |
7615 | Annot *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 | |
7625 | Annots::~Annots() |
7626 | { |
7627 | for (auto *annot : annots) { |
7628 | annot->decRefCnt(); |
7629 | } |
7630 | } |
7631 | |
7632 | //------------------------------------------------------------------------ |
7633 | // AnnotAppearanceBuilder |
7634 | //------------------------------------------------------------------------ |
7635 | |
7636 | AnnotAppearanceBuilder::AnnotAppearanceBuilder() : appearBuf(new GooString()) { } |
7637 | |
7638 | AnnotAppearanceBuilder::~AnnotAppearanceBuilder() |
7639 | { |
7640 | delete appearBuf; |
7641 | } |
7642 | |
7643 | void AnnotAppearanceBuilder::append(const char *text) |
7644 | { |
7645 | appearBuf->append(str: text); |
7646 | } |
7647 | |
7648 | void 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 | |
7657 | const GooString *AnnotAppearanceBuilder::buffer() const |
7658 | { |
7659 | return appearBuf; |
7660 | } |
7661 | |