1 | //======================================================================== |
2 | // |
3 | // Page.cc |
4 | // |
5 | // Copyright 1996-2007 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) 2005 Kristian Høgsberg <krh@redhat.com> |
17 | // Copyright (C) 2005 Jeff Muizelaar <jeff@infidigm.net> |
18 | // Copyright (C) 2005-2013, 2016-2023 Albert Astals Cid <aacid@kde.org> |
19 | // Copyright (C) 2006-2008 Pino Toscano <pino@kde.org> |
20 | // Copyright (C) 2006 Nickolay V. Shmyrev <nshmyrev@yandex.ru> |
21 | // Copyright (C) 2006 Scott Turner <scotty1024@mac.com> |
22 | // Copyright (C) 2006-2011, 2015 Carlos Garcia Campos <carlosgc@gnome.org> |
23 | // Copyright (C) 2007 Julien Rebetez <julienr@svn.gnome.org> |
24 | // Copyright (C) 2008 Iñigo Martínez <inigomartinez@gmail.com> |
25 | // Copyright (C) 2008 Brad Hards <bradh@kde.org> |
26 | // Copyright (C) 2008 Ilya Gorenbein <igorenbein@finjan.com> |
27 | // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it> |
28 | // Copyright (C) 2013, 2014 Thomas Freitag <Thomas.Freitag@alfa.de> |
29 | // Copyright (C) 2013 Jason Crain <jason@aquaticape.us> |
30 | // Copyright (C) 2013, 2017, 2023 Adrian Johnson <ajohnson@redneon.com> |
31 | // Copyright (C) 2015 Philipp Reinkemeier <philipp.reinkemeier@offis.de> |
32 | // Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de> |
33 | // Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de> |
34 | // Copyright (C) 2020, 2021 Nelson Benítez León <nbenitezl@gmail.com> |
35 | // Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com> |
36 | // |
37 | // To see a description of the changes please see the Changelog file that |
38 | // came with your tarball or type make ChangeLog if you are building from git |
39 | // |
40 | //======================================================================== |
41 | |
42 | #include <config.h> |
43 | |
44 | #include <cstddef> |
45 | #include <climits> |
46 | #include "GlobalParams.h" |
47 | #include "Object.h" |
48 | #include "Array.h" |
49 | #include "Dict.h" |
50 | #include "PDFDoc.h" |
51 | #include "XRef.h" |
52 | #include "Link.h" |
53 | #include "OutputDev.h" |
54 | #include "Gfx.h" |
55 | #include "GfxState.h" |
56 | #include "Annot.h" |
57 | #include "TextOutputDev.h" |
58 | #include "Form.h" |
59 | #include "Error.h" |
60 | #include "Page.h" |
61 | #include "Catalog.h" |
62 | #include "Form.h" |
63 | |
64 | //------------------------------------------------------------------------ |
65 | // PDFRectangle |
66 | //------------------------------------------------------------------------ |
67 | |
68 | void PDFRectangle::clipTo(PDFRectangle *rect) |
69 | { |
70 | if (x1 < rect->x1) { |
71 | x1 = rect->x1; |
72 | } else if (x1 > rect->x2) { |
73 | x1 = rect->x2; |
74 | } |
75 | if (x2 < rect->x1) { |
76 | x2 = rect->x1; |
77 | } else if (x2 > rect->x2) { |
78 | x2 = rect->x2; |
79 | } |
80 | if (y1 < rect->y1) { |
81 | y1 = rect->y1; |
82 | } else if (y1 > rect->y2) { |
83 | y1 = rect->y2; |
84 | } |
85 | if (y2 < rect->y1) { |
86 | y2 = rect->y1; |
87 | } else if (y2 > rect->y2) { |
88 | y2 = rect->y2; |
89 | } |
90 | } |
91 | |
92 | //------------------------------------------------------------------------ |
93 | // PageAttrs |
94 | //------------------------------------------------------------------------ |
95 | |
96 | PageAttrs::PageAttrs(PageAttrs *attrs, Dict *dict) |
97 | { |
98 | Object obj1; |
99 | PDFRectangle mBox; |
100 | const bool isPage = dict->is(type: "Page" ); |
101 | |
102 | // get old/default values |
103 | if (attrs) { |
104 | mediaBox = attrs->mediaBox; |
105 | cropBox = attrs->cropBox; |
106 | haveCropBox = attrs->haveCropBox; |
107 | rotate = attrs->rotate; |
108 | resources = attrs->resources.copy(); |
109 | } else { |
110 | // set default MediaBox to 8.5" x 11" -- this shouldn't be necessary |
111 | // but some (non-compliant) PDF files don't specify a MediaBox |
112 | mediaBox.x1 = 0; |
113 | mediaBox.y1 = 0; |
114 | mediaBox.x2 = 612; |
115 | mediaBox.y2 = 792; |
116 | cropBox.x1 = cropBox.y1 = cropBox.x2 = cropBox.y2 = 0; |
117 | haveCropBox = false; |
118 | rotate = 0; |
119 | resources.setToNull(); |
120 | } |
121 | |
122 | // media box |
123 | if (readBox(dict, key: "MediaBox" , box: &mBox)) { |
124 | mediaBox = mBox; |
125 | } |
126 | |
127 | // crop box |
128 | if (readBox(dict, key: "CropBox" , box: &cropBox)) { |
129 | haveCropBox = true; |
130 | } |
131 | if (!haveCropBox) { |
132 | cropBox = mediaBox; |
133 | } |
134 | |
135 | if (isPage) { |
136 | // cropBox can not be bigger than mediaBox |
137 | if (cropBox.x2 - cropBox.x1 > mediaBox.x2 - mediaBox.x1) { |
138 | cropBox.x1 = mediaBox.x1; |
139 | cropBox.x2 = mediaBox.x2; |
140 | } |
141 | if (cropBox.y2 - cropBox.y1 > mediaBox.y2 - mediaBox.y1) { |
142 | cropBox.y1 = mediaBox.y1; |
143 | cropBox.y2 = mediaBox.y2; |
144 | } |
145 | } |
146 | |
147 | // other boxes |
148 | bleedBox = cropBox; |
149 | readBox(dict, key: "BleedBox" , box: &bleedBox); |
150 | trimBox = cropBox; |
151 | readBox(dict, key: "TrimBox" , box: &trimBox); |
152 | artBox = cropBox; |
153 | readBox(dict, key: "ArtBox" , box: &artBox); |
154 | |
155 | // rotate |
156 | obj1 = dict->lookup(key: "Rotate" ); |
157 | if (obj1.isInt()) { |
158 | rotate = obj1.getInt(); |
159 | } |
160 | while (rotate < 0) { |
161 | rotate += 360; |
162 | } |
163 | while (rotate >= 360) { |
164 | rotate -= 360; |
165 | } |
166 | |
167 | // misc attributes |
168 | lastModified = dict->lookup(key: "LastModified" ); |
169 | boxColorInfo = dict->lookup(key: "BoxColorInfo" ); |
170 | group = dict->lookup(key: "Group" ); |
171 | metadata = dict->lookup(key: "Metadata" ); |
172 | pieceInfo = dict->lookup(key: "PieceInfo" ); |
173 | separationInfo = dict->lookup(key: "SeparationInfo" ); |
174 | |
175 | // resource dictionary |
176 | Object objResources = dict->lookup(key: "Resources" ); |
177 | if (objResources.isDict()) { |
178 | resources = std::move(objResources); |
179 | } |
180 | } |
181 | |
182 | PageAttrs::~PageAttrs() { } |
183 | |
184 | void PageAttrs::clipBoxes() |
185 | { |
186 | cropBox.clipTo(rect: &mediaBox); |
187 | bleedBox.clipTo(rect: &mediaBox); |
188 | trimBox.clipTo(rect: &mediaBox); |
189 | artBox.clipTo(rect: &mediaBox); |
190 | } |
191 | |
192 | bool PageAttrs::readBox(Dict *dict, const char *key, PDFRectangle *box) |
193 | { |
194 | PDFRectangle tmp; |
195 | double t; |
196 | Object obj1, obj2; |
197 | bool ok; |
198 | |
199 | obj1 = dict->lookup(key); |
200 | if (obj1.isArray() && obj1.arrayGetLength() == 4) { |
201 | ok = true; |
202 | obj2 = obj1.arrayGet(i: 0); |
203 | if (obj2.isNum()) { |
204 | tmp.x1 = obj2.getNum(); |
205 | } else { |
206 | ok = false; |
207 | } |
208 | obj2 = obj1.arrayGet(i: 1); |
209 | if (obj2.isNum()) { |
210 | tmp.y1 = obj2.getNum(); |
211 | } else { |
212 | ok = false; |
213 | } |
214 | obj2 = obj1.arrayGet(i: 2); |
215 | if (obj2.isNum()) { |
216 | tmp.x2 = obj2.getNum(); |
217 | } else { |
218 | ok = false; |
219 | } |
220 | obj2 = obj1.arrayGet(i: 3); |
221 | if (obj2.isNum()) { |
222 | tmp.y2 = obj2.getNum(); |
223 | } else { |
224 | ok = false; |
225 | } |
226 | if (tmp.x1 == 0 && tmp.x2 == 0 && tmp.y1 == 0 && tmp.y2 == 0) { |
227 | ok = false; |
228 | } |
229 | if (ok) { |
230 | if (tmp.x1 > tmp.x2) { |
231 | t = tmp.x1; |
232 | tmp.x1 = tmp.x2; |
233 | tmp.x2 = t; |
234 | } |
235 | if (tmp.y1 > tmp.y2) { |
236 | t = tmp.y1; |
237 | tmp.y1 = tmp.y2; |
238 | tmp.y2 = t; |
239 | } |
240 | *box = tmp; |
241 | } |
242 | } else { |
243 | ok = false; |
244 | } |
245 | return ok; |
246 | } |
247 | |
248 | //------------------------------------------------------------------------ |
249 | // Page |
250 | //------------------------------------------------------------------------ |
251 | |
252 | #define pageLocker() const std::scoped_lock locker(mutex) |
253 | |
254 | Page::Page(PDFDoc *docA, int numA, Object &&pageDict, Ref , PageAttrs *attrsA, Form *form) : pageRef(pageRefA) |
255 | { |
256 | ok = true; |
257 | doc = docA; |
258 | xref = doc->getXRef(); |
259 | num = numA; |
260 | duration = -1; |
261 | annots = nullptr; |
262 | structParents = -1; |
263 | |
264 | pageObj = std::move(pageDict); |
265 | |
266 | // get attributes |
267 | attrs = attrsA; |
268 | attrs->clipBoxes(); |
269 | |
270 | // transtion |
271 | trans = pageObj.dictLookupNF(key: "Trans" ).copy(); |
272 | if (!(trans.isRef() || trans.isDict() || trans.isNull())) { |
273 | error(category: errSyntaxError, pos: -1, msg: "Page transition object (page {0:d}) is wrong type ({1:s})" , num, trans.getTypeName()); |
274 | trans = Object(); |
275 | } |
276 | |
277 | // duration |
278 | const Object &tmp = pageObj.dictLookupNF(key: "Dur" ); |
279 | if (!(tmp.isNum() || tmp.isNull())) { |
280 | error(category: errSyntaxError, pos: -1, msg: "Page duration object (page {0:d}) is wrong type ({1:s})" , num, tmp.getTypeName()); |
281 | } else if (tmp.isNum()) { |
282 | duration = tmp.getNum(); |
283 | } |
284 | |
285 | // structParents |
286 | const Object &tmp2 = pageObj.dictLookup(key: "StructParents" ); |
287 | if (!(tmp2.isInt() || tmp2.isNull())) { |
288 | error(category: errSyntaxError, pos: -1, msg: "Page StructParents object (page {0:d}) is wrong type ({1:s})" , num, tmp2.getTypeName()); |
289 | } else if (tmp2.isInt()) { |
290 | structParents = tmp2.getInt(); |
291 | } |
292 | |
293 | // annotations |
294 | annotsObj = pageObj.dictLookupNF(key: "Annots" ).copy(); |
295 | if (!(annotsObj.isRef() || annotsObj.isArray() || annotsObj.isNull())) { |
296 | error(category: errSyntaxError, pos: -1, msg: "Page annotations object (page {0:d}) is wrong type ({1:s})" , num, annotsObj.getTypeName()); |
297 | goto err2; |
298 | } |
299 | |
300 | // contents |
301 | contents = pageObj.dictLookupNF(key: "Contents" ).copy(); |
302 | if (!(contents.isRef() || contents.isArray() || contents.isNull())) { |
303 | error(category: errSyntaxError, pos: -1, msg: "Page contents object (page {0:d}) is wrong type ({1:s})" , num, contents.getTypeName()); |
304 | goto err1; |
305 | } |
306 | |
307 | // thumb |
308 | thumb = pageObj.dictLookupNF(key: "Thumb" ).copy(); |
309 | if (!(thumb.isStream() || thumb.isNull() || thumb.isRef())) { |
310 | error(category: errSyntaxError, pos: -1, msg: "Page thumb object (page {0:d}) is wrong type ({1:s})" , num, thumb.getTypeName()); |
311 | thumb.setToNull(); |
312 | } |
313 | |
314 | // actions |
315 | actions = pageObj.dictLookupNF(key: "AA" ).copy(); |
316 | if (!(actions.isDict() || actions.isNull())) { |
317 | error(category: errSyntaxError, pos: -1, msg: "Page additional action object (page {0:d}) is wrong type ({1:s})" , num, actions.getTypeName()); |
318 | actions.setToNull(); |
319 | } |
320 | |
321 | return; |
322 | |
323 | err2: |
324 | annotsObj.setToNull(); |
325 | err1: |
326 | contents.setToNull(); |
327 | ok = false; |
328 | } |
329 | |
330 | Page::~Page() |
331 | { |
332 | delete attrs; |
333 | delete annots; |
334 | for (auto frmField : standaloneFields) { |
335 | delete frmField; |
336 | } |
337 | } |
338 | |
339 | Dict *Page::getResourceDict() |
340 | { |
341 | return attrs->getResourceDict(); |
342 | } |
343 | |
344 | Object *Page::getResourceDictObject() |
345 | { |
346 | return attrs->getResourceDictObject(); |
347 | } |
348 | |
349 | Dict *Page::getResourceDictCopy(XRef *xrefA) |
350 | { |
351 | pageLocker(); |
352 | Dict *dict = attrs->getResourceDict(); |
353 | return dict ? dict->copy(xrefA) : nullptr; |
354 | } |
355 | |
356 | void Page::replaceXRef(XRef *xrefA) |
357 | { |
358 | Dict *pageDict = pageObj.getDict()->copy(xrefA); |
359 | xref = xrefA; |
360 | trans = pageDict->lookupNF(key: "Trans" ).copy(); |
361 | annotsObj = pageDict->lookupNF(key: "Annots" ).copy(); |
362 | contents = pageDict->lookupNF(key: "Contents" ).copy(); |
363 | if (contents.isArray()) { |
364 | contents = Object(contents.getArray()->copy(xrefA)); |
365 | } |
366 | thumb = pageDict->lookupNF(key: "Thumb" ).copy(); |
367 | actions = pageDict->lookupNF(key: "AA" ).copy(); |
368 | Object resources = pageDict->lookup(key: "Resources" ); |
369 | if (resources.isDict()) { |
370 | attrs->replaceResource(obj1: std::move(resources)); |
371 | } |
372 | delete pageDict; |
373 | } |
374 | |
375 | /* Loads standalone fields into Page, should be called once per page only */ |
376 | void Page::loadStandaloneFields(Annots *annotations, Form *form) |
377 | { |
378 | /* Look for standalone annots, identified by being: 1) of type Widget |
379 | * 2) not referenced from the Catalog's Form Field array */ |
380 | for (Annot *annot : annots->getAnnots()) { |
381 | |
382 | if (annot->getType() != Annot::typeWidget || !annot->getHasRef()) { |
383 | continue; |
384 | } |
385 | |
386 | const Ref r = annot->getRef(); |
387 | if (form && form->findWidgetByRef(aref: r)) { |
388 | continue; // this annot is referenced inside Form, skip it |
389 | } |
390 | |
391 | std::set<int> parents; |
392 | FormField *field = Form::createFieldFromDict(obj: annot->getAnnotObj().copy(), docA: annot->getDoc(), aref: r, parent: nullptr, usedParents: &parents); |
393 | |
394 | if (field && field->getNumWidgets() == 1) { |
395 | |
396 | static_cast<AnnotWidget *>(annot)->setField(field); |
397 | |
398 | field->setStandAlone(true); |
399 | FormWidget *formWidget = field->getWidget(i: 0); |
400 | |
401 | if (!formWidget->getWidgetAnnotation()) { |
402 | formWidget->createWidgetAnnotation(); |
403 | } |
404 | |
405 | standaloneFields.push_back(x: field); |
406 | |
407 | } else if (field) { |
408 | delete field; |
409 | } |
410 | } |
411 | } |
412 | |
413 | Annots *Page::getAnnots(XRef *xrefA) |
414 | { |
415 | if (!annots) { |
416 | Object obj = getAnnotsObject(xrefA); |
417 | annots = new Annots(doc, num, &obj); |
418 | // Load standalone fields once for the page |
419 | loadStandaloneFields(annotations: annots, form: doc->getCatalog()->getForm()); |
420 | } |
421 | |
422 | return annots; |
423 | } |
424 | |
425 | bool Page::addAnnot(Annot *annot) |
426 | { |
427 | if (unlikely(xref->getEntry(pageRef.num)->type == xrefEntryFree)) { |
428 | // something very wrong happened if we're here |
429 | error(category: errInternal, pos: -1, msg: "Can not addAnnot to page with an invalid ref" ); |
430 | return false; |
431 | } |
432 | |
433 | const Ref annotRef = annot->getRef(); |
434 | |
435 | // Make sure we have annots before adding the new one |
436 | // even if it's an empty list so that we can safely |
437 | // call annots->appendAnnot(annot) |
438 | pageLocker(); |
439 | getAnnots(); |
440 | |
441 | if (annotsObj.isNull()) { |
442 | Ref annotsRef; |
443 | // page doesn't have annots array, |
444 | // we have to create it |
445 | |
446 | Array *annotsArray = new Array(xref); |
447 | annotsArray->add(elem: Object(annotRef)); |
448 | |
449 | annotsRef = xref->addIndirectObject(o: Object(annotsArray)); |
450 | annotsObj = Object(annotsRef); |
451 | pageObj.dictSet(key: "Annots" , val: Object(annotsRef)); |
452 | xref->setModifiedObject(o: &pageObj, r: pageRef); |
453 | } else { |
454 | Object obj1 = getAnnotsObject(); |
455 | if (obj1.isArray()) { |
456 | obj1.arrayAdd(elem: Object(annotRef)); |
457 | if (annotsObj.isRef()) { |
458 | xref->setModifiedObject(o: &obj1, r: annotsObj.getRef()); |
459 | } else { |
460 | xref->setModifiedObject(o: &pageObj, r: pageRef); |
461 | } |
462 | } |
463 | } |
464 | |
465 | // Popup annots are already handled by markup annots, |
466 | // so add to the list only Popup annots without a |
467 | // markup annotation associated. |
468 | if (annot->getType() != Annot::typePopup || !static_cast<AnnotPopup *>(annot)->hasParent()) { |
469 | annots->appendAnnot(annot); |
470 | } |
471 | annot->setPage(pageIndex: num, updateP: true); |
472 | |
473 | AnnotMarkup *annotMarkup = dynamic_cast<AnnotMarkup *>(annot); |
474 | if (annotMarkup) { |
475 | AnnotPopup * = annotMarkup->getPopup(); |
476 | if (annotPopup) { |
477 | addAnnot(annot: annotPopup); |
478 | } |
479 | } |
480 | |
481 | return true; |
482 | } |
483 | |
484 | void Page::removeAnnot(Annot *annot) |
485 | { |
486 | Ref annotRef = annot->getRef(); |
487 | |
488 | pageLocker(); |
489 | Object annArray = getAnnotsObject(); |
490 | if (annArray.isArray()) { |
491 | int idx = -1; |
492 | // Get annotation position |
493 | for (int i = 0; idx == -1 && i < annArray.arrayGetLength(); ++i) { |
494 | const Object &tmp = annArray.arrayGetNF(i); |
495 | if (tmp.isRef()) { |
496 | const Ref currAnnot = tmp.getRef(); |
497 | if (currAnnot == annotRef) { |
498 | idx = i; |
499 | } |
500 | } |
501 | } |
502 | |
503 | if (idx == -1) { |
504 | error(category: errInternal, pos: -1, msg: "Annotation doesn't belong to this page" ); |
505 | return; |
506 | } |
507 | annots->removeAnnot(annot); // Gracefully fails on popup windows |
508 | annArray.arrayRemove(i: idx); |
509 | |
510 | if (annotsObj.isRef()) { |
511 | xref->setModifiedObject(o: &annArray, r: annotsObj.getRef()); |
512 | } else { |
513 | xref->setModifiedObject(o: &pageObj, r: pageRef); |
514 | } |
515 | } |
516 | annot->removeReferencedObjects(); // Note: Might recurse in removeAnnot again |
517 | if (annArray.isArray()) { |
518 | xref->removeIndirectObject(r: annotRef); |
519 | } |
520 | annot->setPage(pageIndex: 0, updateP: false); |
521 | } |
522 | |
523 | std::unique_ptr<Links> Page::getLinks() |
524 | { |
525 | return std::make_unique<Links>(args: getAnnots()); |
526 | } |
527 | |
528 | std::unique_ptr<FormPageWidgets> Page::getFormWidgets() |
529 | { |
530 | auto frmPageWidgets = std::make_unique<FormPageWidgets>(args: getAnnots(), args&: num, args: doc->getCatalog()->getForm()); |
531 | frmPageWidgets->addWidgets(addedWidgets: standaloneFields, page: num); |
532 | |
533 | return frmPageWidgets; |
534 | } |
535 | |
536 | void Page::display(OutputDev *out, double hDPI, double vDPI, int rotate, bool useMediaBox, bool crop, bool printing, bool (*abortCheckCbk)(void *data), void *abortCheckCbkData, bool (*annotDisplayDecideCbk)(Annot *annot, void *user_data), |
537 | void *annotDisplayDecideCbkData, bool copyXRef) |
538 | { |
539 | displaySlice(out, hDPI, vDPI, rotate, useMediaBox, crop, sliceX: -1, sliceY: -1, sliceW: -1, sliceH: -1, printing, abortCheckCbk, abortCheckCbkData, annotDisplayDecideCbk, annotDisplayDecideCbkData, copyXRef); |
540 | } |
541 | |
542 | Gfx *Page::createGfx(OutputDev *out, double hDPI, double vDPI, int rotate, bool useMediaBox, bool crop, int sliceX, int sliceY, int sliceW, int sliceH, bool printing, bool (*abortCheckCbk)(void *data), void *abortCheckCbkData, XRef *xrefA) |
543 | { |
544 | const PDFRectangle *mediaBox, *cropBox; |
545 | PDFRectangle box; |
546 | Gfx *gfx; |
547 | |
548 | rotate += getRotate(); |
549 | if (rotate >= 360) { |
550 | rotate -= 360; |
551 | } else if (rotate < 0) { |
552 | rotate += 360; |
553 | } |
554 | |
555 | makeBox(hDPI, vDPI, rotate, useMediaBox, upsideDown: out->upsideDown(), sliceX, sliceY, sliceW, sliceH, box: &box, crop: &crop); |
556 | cropBox = getCropBox(); |
557 | mediaBox = getMediaBox(); |
558 | |
559 | if (globalParams->getPrintCommands()) { |
560 | printf(format: "***** MediaBox = ll:%g,%g ur:%g,%g\n" , mediaBox->x1, mediaBox->y1, mediaBox->x2, mediaBox->y2); |
561 | printf(format: "***** CropBox = ll:%g,%g ur:%g,%g\n" , cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2); |
562 | printf(format: "***** Rotate = %d\n" , attrs->getRotate()); |
563 | } |
564 | |
565 | if (!crop) { |
566 | crop = (box == *cropBox) && out->needClipToCropBox(); |
567 | } |
568 | gfx = new Gfx(doc, out, num, attrs->getResourceDict(), hDPI, vDPI, &box, crop ? cropBox : nullptr, rotate, abortCheckCbk, abortCheckCbkData, xrefA); |
569 | |
570 | return gfx; |
571 | } |
572 | |
573 | void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, int rotate, bool useMediaBox, bool crop, int sliceX, int sliceY, int sliceW, int sliceH, bool printing, bool (*abortCheckCbk)(void *data), void *abortCheckCbkData, |
574 | bool (*annotDisplayDecideCbk)(Annot *annot, void *user_data), void *annotDisplayDecideCbkData, bool copyXRef) |
575 | { |
576 | Gfx *gfx; |
577 | Annots *annotList; |
578 | |
579 | if (!out->checkPageSlice(page: this, hDPI, vDPI, rotate, useMediaBox, crop, sliceX, sliceY, sliceW, sliceH, printing, abortCheckCbk, abortCheckCbkData, annotDisplayDecideCbk, annotDisplayDecideCbkData)) { |
580 | return; |
581 | } |
582 | pageLocker(); |
583 | XRef *localXRef = (copyXRef) ? xref->copy() : xref; |
584 | if (copyXRef) { |
585 | replaceXRef(xrefA: localXRef); |
586 | } |
587 | |
588 | gfx = createGfx(out, hDPI, vDPI, rotate, useMediaBox, crop, sliceX, sliceY, sliceW, sliceH, printing, abortCheckCbk, abortCheckCbkData, xrefA: localXRef); |
589 | |
590 | Object obj = contents.fetch(xref: localXRef); |
591 | if (!obj.isNull()) { |
592 | gfx->saveState(); |
593 | gfx->display(obj: &obj); |
594 | gfx->restoreState(); |
595 | } else { |
596 | // empty pages need to call dump to do any setup required by the |
597 | // OutputDev |
598 | out->dump(); |
599 | } |
600 | |
601 | // draw annotations |
602 | annotList = getAnnots(); |
603 | |
604 | if (!annotList->getAnnots().empty()) { |
605 | if (globalParams->getPrintCommands()) { |
606 | printf(format: "***** Annotations\n" ); |
607 | } |
608 | for (Annot *annot : annots->getAnnots()) { |
609 | if ((annotDisplayDecideCbk && (*annotDisplayDecideCbk)(annot, annotDisplayDecideCbkData)) || !annotDisplayDecideCbk) { |
610 | annot->draw(gfx, printing); |
611 | } |
612 | } |
613 | out->dump(); |
614 | } |
615 | |
616 | delete gfx; |
617 | if (copyXRef) { |
618 | replaceXRef(xrefA: doc->getXRef()); |
619 | delete localXRef; |
620 | } |
621 | } |
622 | |
623 | void Page::display(Gfx *gfx) |
624 | { |
625 | Object obj = contents.fetch(xref); |
626 | if (!obj.isNull()) { |
627 | gfx->saveState(); |
628 | gfx->display(obj: &obj); |
629 | gfx->restoreState(); |
630 | } |
631 | } |
632 | |
633 | bool Page::loadThumb(unsigned char **data_out, int *width_out, int *height_out, int *rowstride_out) |
634 | { |
635 | unsigned int pixbufdatasize; |
636 | int width, height, bits; |
637 | Object obj1; |
638 | Dict *dict; |
639 | GfxColorSpace *colorSpace; |
640 | Stream *str; |
641 | GfxImageColorMap *colorMap; |
642 | |
643 | /* Get stream dict */ |
644 | pageLocker(); |
645 | Object fetched_thumb = thumb.fetch(xref); |
646 | if (!fetched_thumb.isStream()) { |
647 | return false; |
648 | } |
649 | |
650 | dict = fetched_thumb.streamGetDict(); |
651 | str = fetched_thumb.getStream(); |
652 | |
653 | if (!dict->lookupInt(key: "Width" , alt_key: "W" , value: &width)) { |
654 | return false; |
655 | } |
656 | if (!dict->lookupInt(key: "Height" , alt_key: "H" , value: &height)) { |
657 | return false; |
658 | } |
659 | if (!dict->lookupInt(key: "BitsPerComponent" , alt_key: "BPC" , value: &bits)) { |
660 | return false; |
661 | } |
662 | |
663 | /* Check for invalid dimensions and integer overflow. */ |
664 | if (width <= 0 || height <= 0) { |
665 | return false; |
666 | } |
667 | if (width > INT_MAX / 3 / height) { |
668 | return false; |
669 | } |
670 | pixbufdatasize = width * height * 3; |
671 | |
672 | /* Get color space */ |
673 | obj1 = dict->lookup(key: "ColorSpace" ); |
674 | if (obj1.isNull()) { |
675 | obj1 = dict->lookup(key: "CS" ); |
676 | } |
677 | // Just initialize some dummy GfxState for GfxColorSpace::parse. |
678 | // This will set a sRGB profile for ICC-based colorspaces. |
679 | auto pdfrectangle = std::make_shared<PDFRectangle>(); |
680 | auto state = std::make_shared<GfxState>(args: 72.0, args: 72.0, args: pdfrectangle.get(), args: 0, args: false); |
681 | colorSpace = GfxColorSpace::parse(res: nullptr, csObj: &obj1, out: nullptr, state: state.get()); |
682 | if (!colorSpace) { |
683 | fprintf(stderr, format: "Error: Cannot parse color space\n" ); |
684 | return false; |
685 | } |
686 | |
687 | obj1 = dict->lookup(key: "Decode" ); |
688 | if (obj1.isNull()) { |
689 | obj1 = dict->lookup(key: "D" ); |
690 | } |
691 | colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); |
692 | if (!colorMap->isOk()) { |
693 | fprintf(stderr, format: "Error: invalid colormap\n" ); |
694 | delete colorMap; |
695 | return false; |
696 | } |
697 | |
698 | if (data_out) { |
699 | unsigned char *pixbufdata = (unsigned char *)gmalloc(size: pixbufdatasize); |
700 | unsigned char *p = pixbufdata; |
701 | ImageStream *imgstr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); |
702 | imgstr->reset(); |
703 | for (int row = 0; row < height; ++row) { |
704 | for (int col = 0; col < width; ++col) { |
705 | unsigned char pix[gfxColorMaxComps]; |
706 | GfxRGB rgb; |
707 | |
708 | imgstr->getPixel(pix); |
709 | colorMap->getRGB(x: pix, rgb: &rgb); |
710 | |
711 | *p++ = colToByte(x: rgb.r); |
712 | *p++ = colToByte(x: rgb.g); |
713 | *p++ = colToByte(x: rgb.b); |
714 | } |
715 | } |
716 | *data_out = pixbufdata; |
717 | imgstr->close(); |
718 | delete imgstr; |
719 | } |
720 | |
721 | if (width_out) { |
722 | *width_out = width; |
723 | } |
724 | if (height_out) { |
725 | *height_out = height; |
726 | } |
727 | if (rowstride_out) { |
728 | *rowstride_out = width * 3; |
729 | } |
730 | |
731 | delete colorMap; |
732 | |
733 | return true; |
734 | } |
735 | |
736 | void Page::makeBox(double hDPI, double vDPI, int rotate, bool useMediaBox, bool upsideDown, double sliceX, double sliceY, double sliceW, double sliceH, PDFRectangle *box, bool *crop) |
737 | { |
738 | const PDFRectangle *mediaBox, *cropBox, *baseBox; |
739 | double kx, ky; |
740 | |
741 | mediaBox = getMediaBox(); |
742 | cropBox = getCropBox(); |
743 | if (sliceW >= 0 && sliceH >= 0) { |
744 | baseBox = useMediaBox ? mediaBox : cropBox; |
745 | kx = 72.0 / hDPI; |
746 | ky = 72.0 / vDPI; |
747 | if (rotate == 90) { |
748 | if (upsideDown) { |
749 | box->x1 = baseBox->x1 + ky * sliceY; |
750 | box->x2 = baseBox->x1 + ky * (sliceY + sliceH); |
751 | } else { |
752 | box->x1 = baseBox->x2 - ky * (sliceY + sliceH); |
753 | box->x2 = baseBox->x2 - ky * sliceY; |
754 | } |
755 | box->y1 = baseBox->y1 + kx * sliceX; |
756 | box->y2 = baseBox->y1 + kx * (sliceX + sliceW); |
757 | } else if (rotate == 180) { |
758 | box->x1 = baseBox->x2 - kx * (sliceX + sliceW); |
759 | box->x2 = baseBox->x2 - kx * sliceX; |
760 | if (upsideDown) { |
761 | box->y1 = baseBox->y1 + ky * sliceY; |
762 | box->y2 = baseBox->y1 + ky * (sliceY + sliceH); |
763 | } else { |
764 | box->y1 = baseBox->y2 - ky * (sliceY + sliceH); |
765 | box->y2 = baseBox->y2 - ky * sliceY; |
766 | } |
767 | } else if (rotate == 270) { |
768 | if (upsideDown) { |
769 | box->x1 = baseBox->x2 - ky * (sliceY + sliceH); |
770 | box->x2 = baseBox->x2 - ky * sliceY; |
771 | } else { |
772 | box->x1 = baseBox->x1 + ky * sliceY; |
773 | box->x2 = baseBox->x1 + ky * (sliceY + sliceH); |
774 | } |
775 | box->y1 = baseBox->y2 - kx * (sliceX + sliceW); |
776 | box->y2 = baseBox->y2 - kx * sliceX; |
777 | } else { |
778 | box->x1 = baseBox->x1 + kx * sliceX; |
779 | box->x2 = baseBox->x1 + kx * (sliceX + sliceW); |
780 | if (upsideDown) { |
781 | box->y1 = baseBox->y2 - ky * (sliceY + sliceH); |
782 | box->y2 = baseBox->y2 - ky * sliceY; |
783 | } else { |
784 | box->y1 = baseBox->y1 + ky * sliceY; |
785 | box->y2 = baseBox->y1 + ky * (sliceY + sliceH); |
786 | } |
787 | } |
788 | } else if (useMediaBox) { |
789 | *box = *mediaBox; |
790 | } else { |
791 | *box = *cropBox; |
792 | *crop = false; |
793 | } |
794 | } |
795 | |
796 | void Page::processLinks(OutputDev *out) |
797 | { |
798 | std::unique_ptr<Links> links = getLinks(); |
799 | for (AnnotLink *link : links->getLinks()) { |
800 | out->processLink(link); |
801 | } |
802 | } |
803 | |
804 | void Page::getDefaultCTM(double *ctm, double hDPI, double vDPI, int rotate, bool useMediaBox, bool upsideDown) |
805 | { |
806 | GfxState *state; |
807 | int i; |
808 | rotate += getRotate(); |
809 | if (rotate >= 360) { |
810 | rotate -= 360; |
811 | } else if (rotate < 0) { |
812 | rotate += 360; |
813 | } |
814 | state = new GfxState(hDPI, vDPI, useMediaBox ? getMediaBox() : getCropBox(), rotate, upsideDown); |
815 | for (i = 0; i < 6; ++i) { |
816 | ctm[i] = state->getCTM()[i]; |
817 | } |
818 | delete state; |
819 | } |
820 | |
821 | std::unique_ptr<LinkAction> Page::getAdditionalAction(PageAdditionalActionsType type) |
822 | { |
823 | Object additionalActionsObject = actions.fetch(xref: doc->getXRef()); |
824 | if (additionalActionsObject.isDict()) { |
825 | const char *key = (type == actionOpenPage ? "O" : type == actionClosePage ? "C" : nullptr); |
826 | |
827 | Object actionObject = additionalActionsObject.dictLookup(key); |
828 | if (actionObject.isDict()) { |
829 | return LinkAction::parseAction(obj: &actionObject, baseURI: doc->getCatalog()->getBaseURI()); |
830 | } |
831 | } |
832 | |
833 | return nullptr; |
834 | } |
835 | |