//======================================================================== // // Annot.cc // // Copyright 2000-2003 Glyph & Cog, LLC // //======================================================================== //======================================================================== // // Modified under the Poppler project - http://poppler.freedesktop.org // // All changes made under the Poppler project to this file are licensed // under GPL version 2 or later // // Copyright (C) 2006 Scott Turner // Copyright (C) 2007, 2008 Julien Rebetez // Copyright (C) 2007-2013, 2015-2020 Albert Astals Cid // Copyright (C) 2007-2013, 2018 Carlos Garcia Campos // Copyright (C) 2007, 2008 Iñigo Martínez // Copyright (C) 2007 Jeff Muizelaar // Copyright (C) 2008, 2011 Pino Toscano // Copyright (C) 2008 Michael Vrable // Copyright (C) 2008 Hugo Mercier // Copyright (C) 2009 Ilya Gorenbein // Copyright (C) 2011, 2013, 2019 José Aliste // Copyright (C) 2012, 2013 Fabio D'Urso // Copyright (C) 2012, 2013 Thomas Freitag // Copyright (C) 2012, 2015 Tobias Koenig // Copyright (C) 2013 Peter Breitenlohner // Copyright (C) 2013, 2017 Adrian Johnson // Copyright (C) 2014, 2015 Marek Kasik // Copyright (C) 2014 Jiri Slaby // Copyright (C) 2014 Anuj Khare // Copyright (C) 2015 Petr Gajdos // Copyright (C) 2015 Philipp Reinkemeier // Copyright (C) 2015 Tamas Szekeres // Copyright (C) 2017 Hans-Ulrich Jüttner // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich // Copyright 2018 Andre Heinecke // Copyright (C) 2018 Adam Reichold // Copyright (C) 2018 Dileep Sankhla // Copyright (C) 2018-2020 Tobias Deiminger // Copyright (C) 2018-2020 Oliver Sander // Copyright (C) 2019 Umang Malik // Copyright (C) 2019 João Netto // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by Technische Universität Dresden // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #include #include #include #include #include "goo/gmem.h" #include "goo/gstrtod.h" #include "Error.h" #include "Object.h" #include "Catalog.h" #include "Gfx.h" #include "Lexer.h" #include "PDFDoc.h" #include "Page.h" #include "Annot.h" #include "GfxFont.h" #include "CharCodeToUnicode.h" #include "PDFDocEncoding.h" #include "Form.h" #include "Error.h" #include "XRef.h" #include "Movie.h" #include "OptionalContent.h" #include "Sound.h" #include "FileSpec.h" #include "DateInfo.h" #include "Link.h" #include #include #ifndef M_PI # define M_PI 3.14159265358979323846 #endif #define fieldFlagReadOnly 0x00000001 #define fieldFlagRequired 0x00000002 #define fieldFlagNoExport 0x00000004 #define fieldFlagMultiline 0x00001000 #define fieldFlagPassword 0x00002000 #define fieldFlagNoToggleToOff 0x00004000 #define fieldFlagRadio 0x00008000 #define fieldFlagPushbutton 0x00010000 #define fieldFlagCombo 0x00020000 #define fieldFlagEdit 0x00040000 #define fieldFlagSort 0x00080000 #define fieldFlagFileSelect 0x00100000 #define fieldFlagMultiSelect 0x00200000 #define fieldFlagDoNotSpellCheck 0x00400000 #define fieldFlagDoNotScroll 0x00800000 #define fieldFlagComb 0x01000000 #define fieldFlagRichText 0x02000000 #define fieldFlagRadiosInUnison 0x02000000 #define fieldFlagCommitOnSelChange 0x04000000 #define fieldQuadLeft 0 #define fieldQuadCenter 1 #define fieldQuadRight 2 // distance of Bezier control point from center for circle approximation // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle 0.55228475 static AnnotLineEndingStyle parseAnnotLineEndingStyle(const GooString *string) { if (string != nullptr) { if (!string->cmp("Square")) { return annotLineEndingSquare; } else if (!string->cmp("Circle")) { return annotLineEndingCircle; } else if (!string->cmp("Diamond")) { return annotLineEndingDiamond; } else if (!string->cmp("OpenArrow")) { return annotLineEndingOpenArrow; } else if (!string->cmp("ClosedArrow")) { return annotLineEndingClosedArrow; } else if (!string->cmp("Butt")) { return annotLineEndingButt; } else if (!string->cmp("ROpenArrow")) { return annotLineEndingROpenArrow; } else if (!string->cmp("RClosedArrow")) { return annotLineEndingRClosedArrow; } else if (!string->cmp("Slash")) { return annotLineEndingSlash; } else { return annotLineEndingNone; } } else { return annotLineEndingNone; } } static const char *convertAnnotLineEndingStyle(AnnotLineEndingStyle style) { switch (style) { case annotLineEndingSquare: return "Square"; case annotLineEndingCircle: return "Circle"; case annotLineEndingDiamond: return "Diamond"; case annotLineEndingOpenArrow: return "OpenArrow"; case annotLineEndingClosedArrow: return "ClosedArrow"; case annotLineEndingButt: return "Butt"; case annotLineEndingROpenArrow: return "ROpenArrow"; case annotLineEndingRClosedArrow: return "RClosedArrow"; case annotLineEndingSlash: return "Slash"; default: return "None"; } } static AnnotExternalDataType parseAnnotExternalData(Dict *dict) { AnnotExternalDataType type; Object obj1 = dict->lookup("Subtype"); if (obj1.isName()) { const char *typeName = obj1.getName(); if (!strcmp(typeName, "Markup3D")) { type = annotExternalDataMarkup3D; } else { type = annotExternalDataMarkupUnknown; } } else { type = annotExternalDataMarkupUnknown; } return type; } static std::unique_ptr parseDiffRectangle(Array *array, PDFRectangle *rect) { if (array->getLength() == 4) { // deltas const double dx1 = array->get(0).getNumWithDefaultValue(0); const double dy1 = array->get(1).getNumWithDefaultValue(0); const double dx2 = array->get(2).getNumWithDefaultValue(0); const double dy2 = array->get(3).getNumWithDefaultValue(0); // checking that the numbers are valid (i.e. >= 0), // and that applying the differences still give us a valid rect if (dx1 >= 0 && dy1 >= 0 && dx2 >= 0 && dy2 && (rect->x2 - rect->x1 - dx1 - dx2) >= 0 && (rect->y2 - rect->y1 - dy1 - dy2) >= 0) { auto newRect = std::make_unique(); newRect->x1 = rect->x1 + dx1; newRect->y1 = rect->y1 + dy1; newRect->x2 = rect->x2 - dx2; newRect->y2 = rect->y2 - dy2; return newRect; } } return nullptr; } static std::unique_ptr getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc) { Object additionalActionsObject = additionalActions->fetch(doc->getXRef()); if (additionalActionsObject.isDict()) { const char *key = (type == Annot::actionCursorEntering ? "E" : type == Annot::actionCursorLeaving ? "X" : type == Annot::actionMousePressed ? "D" : type == Annot::actionMouseReleased ? "U" : type == Annot::actionFocusIn ? "Fo" : type == Annot::actionFocusOut ? "Bl" : type == Annot::actionPageOpening ? "PO" : type == Annot::actionPageClosing ? "PC" : type == Annot::actionPageVisible ? "PV" : type == Annot::actionPageInvisible ? "PI" : nullptr); Object actionObject = additionalActionsObject.dictLookup(key); if (actionObject.isDict()) return LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI()); } return nullptr; } static const char *getFormAdditionalActionKey(Annot::FormAdditionalActionsType type) { return (type == Annot::actionFieldModified ? "K" : type == Annot::actionFormatField ? "F" : type == Annot::actionValidateField ? "V" : type == Annot::actionCalculateField ? "C" : nullptr); } //------------------------------------------------------------------------ // AnnotBorderEffect //------------------------------------------------------------------------ AnnotBorderEffect::AnnotBorderEffect(Dict *dict) { Object obj1; obj1 = dict->lookup("S"); if (obj1.isName()) { const char *effectName = obj1.getName(); if (!strcmp(effectName, "C")) effectType = borderEffectCloudy; else effectType = borderEffectNoEffect; } else { effectType = borderEffectNoEffect; } if (effectType == borderEffectCloudy) { intensity = dict->lookup("I").getNumWithDefaultValue(0); } else { intensity = 0; } } //------------------------------------------------------------------------ // AnnotPath //------------------------------------------------------------------------ AnnotPath::AnnotPath() = default; AnnotPath::AnnotPath(Array *array) { parsePathArray(array); } AnnotPath::AnnotPath(std::vector &&coordsA) { coords = std::move(coordsA); } AnnotPath::~AnnotPath() = default; double AnnotPath::getX(int coord) const { if (coord >= 0 && coord < getCoordsLength()) return coords[coord].getX(); return 0; } double AnnotPath::getY(int coord) const { if (coord >= 0 && coord < getCoordsLength()) return coords[coord].getY(); return 0; } AnnotCoord *AnnotPath::getCoord(int coord) { if (coord >= 0 && coord < getCoordsLength()) return &coords[coord]; return nullptr; } void AnnotPath::parsePathArray(Array *array) { if (array->getLength() % 2) { error(errSyntaxError, -1, "Bad Annot Path"); return; } const auto tempLength = array->getLength() / 2; std::vector tempCoords; tempCoords.reserve(tempLength); for (int i = 0; i < tempLength; i++) { double x = 0, y = 0; Object obj1 = array->get(i * 2); if (obj1.isNum()) { x = obj1.getNum(); } else { return; } obj1 = array->get((i * 2) + 1); if (obj1.isNum()) { y = obj1.getNum(); } else { return; } tempCoords.emplace_back(x, y); } coords = std::move(tempCoords); } //------------------------------------------------------------------------ // AnnotCalloutLine //------------------------------------------------------------------------ AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2) : coord1(x1, y1), coord2(x2, y2) { } AnnotCalloutLine::~AnnotCalloutLine() = default; //------------------------------------------------------------------------ // AnnotCalloutMultiLine //------------------------------------------------------------------------ AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, double y2, double x3, double y3) : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) { } AnnotCalloutMultiLine::~AnnotCalloutMultiLine() = default; //------------------------------------------------------------------------ // AnnotQuadrilateral //------------------------------------------------------------------------ AnnotQuadrilaterals::AnnotQuadrilaterals(Array *array, PDFRectangle *rect) { int arrayLength = array->getLength(); int quadsLength = 0; double quadArray[8]; // default values quadrilateralsLength = 0; if ((arrayLength % 8) == 0) { int i; quadsLength = arrayLength / 8; auto quads = std::make_unique(quadsLength); for (i = 0; i < quadsLength; i++) { for (int j = 0; j < 8; j++) { Object obj = array->get(i * 8 + j); if (obj.isNum()) { quadArray[j] = obj.getNum(); } else { error(errSyntaxError, -1, "Invalid QuadPoint in annot"); return; } } quads[i] = AnnotQuadrilateral(quadArray[0], quadArray[1], quadArray[2], quadArray[3], quadArray[4], quadArray[5], quadArray[6], quadArray[7]); } quadrilateralsLength = quadsLength; quadrilaterals = std::move(quads); } } AnnotQuadrilaterals::AnnotQuadrilaterals(std::unique_ptr &&quads, int quadsLength) { quadrilaterals = std::move(quads); quadrilateralsLength = quadsLength; } AnnotQuadrilaterals::~AnnotQuadrilaterals() = default; double AnnotQuadrilaterals::getX1(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord1.getX(); return 0; } double AnnotQuadrilaterals::getY1(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord1.getY(); return 0; } double AnnotQuadrilaterals::getX2(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord2.getX(); return 0; } double AnnotQuadrilaterals::getY2(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord2.getY(); return 0; } double AnnotQuadrilaterals::getX3(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord3.getX(); return 0; } double AnnotQuadrilaterals::getY3(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord3.getY(); return 0; } double AnnotQuadrilaterals::getX4(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord4.getX(); return 0; } double AnnotQuadrilaterals::getY4(int quadrilateral) { if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) return quadrilaterals[quadrilateral].coord4.getY(); return 0; } AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral() = default; 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) { } //------------------------------------------------------------------------ // AnnotBorder //------------------------------------------------------------------------ AnnotBorder::AnnotBorder() { width = 1; dashLength = 0; dash = nullptr; style = borderSolid; } bool AnnotBorder::parseDashArray(Object *dashObj) { bool correct = true; const int tempLength = dashObj->arrayGetLength(); double *tempDash = (double *)gmallocn(tempLength, sizeof(double)); // TODO: check not all zero (Line Dash Pattern Page 217 PDF 8.1) for (int i = 0; i < tempLength && i < DASH_LIMIT && correct; i++) { const Object obj1 = dashObj->arrayGet(i); if (obj1.isNum()) { tempDash[i] = obj1.getNum(); correct = tempDash[i] >= 0; } else { correct = false; } } if (correct) { dashLength = tempLength; dash = tempDash; style = borderDashed; } else { gfree(tempDash); } return correct; } AnnotBorder::~AnnotBorder() { if (dash) gfree(dash); } //------------------------------------------------------------------------ // AnnotBorderArray //------------------------------------------------------------------------ AnnotBorderArray::AnnotBorderArray() { horizontalCorner = 0; verticalCorner = 0; } AnnotBorderArray::AnnotBorderArray(Array *array) { Object obj1; int arrayLength = array->getLength(); bool correct = true; if (arrayLength == 3 || arrayLength == 4) { // implementation note 81 in Appendix H. obj1 = array->get(0); if (obj1.isNum()) horizontalCorner = obj1.getNum(); else correct = false; obj1 = array->get(1); if (obj1.isNum()) verticalCorner = obj1.getNum(); else correct = false; obj1 = array->get(2); if (obj1.isNum()) width = obj1.getNum(); else correct = false; if (arrayLength == 4) { obj1 = array->get(3); if (obj1.isArray()) correct = parseDashArray(&obj1); else correct = false; } } else { correct = false; } if (!correct) { width = 0; } } Object AnnotBorderArray::writeToObject(XRef *xref) const { Array *borderArray = new Array(xref); borderArray->add(Object(horizontalCorner)); borderArray->add(Object(verticalCorner)); borderArray->add(Object(width)); if (dashLength > 0) { Array *a = new Array(xref); for (int i = 0; i < dashLength; i++) a->add(Object(dash[i])); borderArray->add(Object(a)); } return Object(borderArray); } //------------------------------------------------------------------------ // AnnotBorderBS //------------------------------------------------------------------------ AnnotBorderBS::AnnotBorderBS() { } AnnotBorderBS::AnnotBorderBS(Dict *dict) { // Border width (in points) Object obj1 = dict->lookup("W"); width = obj1.getNumWithDefaultValue(1.0); // Border style obj1 = dict->lookup("S"); if (obj1.isName()) { const char *styleName = obj1.getName(); if (!strcmp(styleName, "S")) { style = borderSolid; } else if (!strcmp(styleName, "D")) { style = borderDashed; } else if (!strcmp(styleName, "B")) { style = borderBeveled; } else if (!strcmp(styleName, "I")) { style = borderInset; } else if (!strcmp(styleName, "U")) { style = borderUnderlined; } else { style = borderSolid; } } else { style = borderSolid; } // Border dash style if (style == borderDashed) { obj1 = dict->lookup("D"); if (obj1.isArray()) parseDashArray(&obj1); if (!dash) { dashLength = 1; dash = (double *)gmallocn(dashLength, sizeof(double)); dash[0] = 3; } } } const char *AnnotBorderBS::getStyleName() const { switch (style) { case borderSolid: return "S"; case borderDashed: return "D"; case borderBeveled: return "B"; case borderInset: return "I"; case borderUnderlined: return "U"; } return "S"; } Object AnnotBorderBS::writeToObject(XRef *xref) const { Dict *dict = new Dict(xref); dict->set("W", Object(width)); dict->set("S", Object(objName, getStyleName())); if (style == borderDashed && dashLength > 0) { Array *a = new Array(xref); for (int i = 0; i < dashLength; i++) a->add(Object(dash[i])); dict->set("D", Object(a)); } return Object(dict); } //------------------------------------------------------------------------ // AnnotColor //------------------------------------------------------------------------ AnnotColor::AnnotColor() { length = 0; } AnnotColor::AnnotColor(double gray) { length = 1; values[0] = gray; } AnnotColor::AnnotColor(double r, double g, double b) { length = 3; values[0] = r; values[1] = g; values[2] = b; } AnnotColor::AnnotColor(double c, double m, double y, double k) { length = 4; values[0] = c; values[1] = m; values[2] = y; values[3] = k; } // If is +1, color is brightened; // if is -1, color is darkened; // otherwise color is not modified. AnnotColor::AnnotColor(Array *array, int adjust) { int i; length = array->getLength(); if (length > 4) length = 4; for (i = 0; i < length; i++) { Object obj1 = array->get(i); if (obj1.isNum()) { values[i] = obj1.getNum(); if (values[i] < 0 || values[i] > 1) values[i] = 0; } else { values[i] = 0; } } if (adjust != 0) adjustColor(adjust); } void AnnotColor::adjustColor(int adjust) { int i; if (length == 4) { adjust = -adjust; } if (adjust > 0) { for (i = 0; i < length; ++i) { values[i] = 0.5 * values[i] + 0.5; } } else if (adjust < 0) { for (i = 0; i < length; ++i) { values[i] = 0.5 * values[i]; } } } Object AnnotColor::writeToObject(XRef *xref) const { if (length == 0) { return Object(objNull); // Transparent (no color) } else { Array *a = new Array(xref); for (int i = 0; i < length; ++i) a->add(Object(values[i])); return Object(a); } } //------------------------------------------------------------------------ // DefaultAppearance //------------------------------------------------------------------------ DefaultAppearance::DefaultAppearance(Object &&fontNameA, double fontPtSizeA, std::unique_ptr fontColorA) : fontName(std::move(fontNameA)), fontPtSize(fontPtSizeA), fontColor(std::move(fontColorA)) { } DefaultAppearance::DefaultAppearance(GooString *da) { fontPtSize = -1; if (da) { std::vector *daToks = new std::vector(); int i = FormFieldText::tokenizeDA(da, daToks, "Tf"); if (i >= 1) { fontPtSize = gatof((*daToks)[i - 1]->c_str()); } if (i >= 2) { // We are expecting a name, therefore the first letter should be '/'. const GooString *fontToken = (*daToks)[i - 2]; if (fontToken && fontToken->getLength() > 1 && fontToken->getChar(0) == '/') { // The +1 is here to skip the leading '/'. fontName = Object(objName, fontToken->c_str() + 1); } } // Scan backwards: we are looking for the last set value for (i = daToks->size() - 1; i >= 0; --i) { if (!fontColor) { if (!((*daToks)[i])->cmp("g") && i >= 1) { fontColor = std::make_unique(gatof(((*daToks)[i - 1])->c_str())); } else if (!((*daToks)[i])->cmp("rg") && i >= 3) { fontColor = std::make_unique(gatof(((*daToks)[i - 3])->c_str()), gatof(((*daToks)[i - 2])->c_str()), gatof(((*daToks)[i - 1])->c_str())); } else if (!((*daToks)[i])->cmp("k") && i >= 4) { fontColor = std::make_unique(gatof(((*daToks)[i - 4])->c_str()), gatof(((*daToks)[i - 3])->c_str()), gatof(((*daToks)[i - 2])->c_str()), gatof(((*daToks)[i - 1])->c_str())); } } } for (auto entry : *daToks) { delete entry; } delete daToks; } } void DefaultAppearance::setFontName(Object &&fontNameA) { fontName = std::move(fontNameA); } void DefaultAppearance::setFontPtSize(double fontPtSizeA) { fontPtSize = fontPtSizeA; } void DefaultAppearance::setFontColor(std::unique_ptr fontColorA) { fontColor = std::move(fontColorA); } GooString *DefaultAppearance::toAppearanceString() const { AnnotAppearanceBuilder appearBuilder; if (fontColor) { appearBuilder.setDrawColor(fontColor.get(), true); } appearBuilder.setTextFont(fontName, fontPtSize); return appearBuilder.buffer()->copy(); } //------------------------------------------------------------------------ // AnnotIconFit //------------------------------------------------------------------------ AnnotIconFit::AnnotIconFit(Dict *dict) { Object obj1; obj1 = dict->lookup("SW"); if (obj1.isName()) { const char *scaleName = obj1.getName(); if (!strcmp(scaleName, "B")) { scaleWhen = scaleBigger; } else if (!strcmp(scaleName, "S")) { scaleWhen = scaleSmaller; } else if (!strcmp(scaleName, "N")) { scaleWhen = scaleNever; } else { scaleWhen = scaleAlways; } } else { scaleWhen = scaleAlways; } obj1 = dict->lookup("S"); if (obj1.isName()) { const char *scaleName = obj1.getName(); if (!strcmp(scaleName, "A")) { scale = scaleAnamorphic; } else { scale = scaleProportional; } } else { scale = scaleProportional; } obj1 = dict->lookup("A"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { left = obj1.arrayGet(0).getNumWithDefaultValue(0); bottom = obj1.arrayGet(1).getNumWithDefaultValue(0); if (left < 0 || left > 1) left = 0.5; if (bottom < 0 || bottom > 1) bottom = 0.5; } else { left = bottom = 0.5; } fullyBounds = dict->lookup("FB").getBoolWithDefaultValue(false); } //------------------------------------------------------------------------ // AnnotAppearance //------------------------------------------------------------------------ AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict) { assert(dict->isDict()); doc = docA; appearDict = dict->copy(); } AnnotAppearance::~AnnotAppearance() { } Object AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state) { Object apData; // Obtain dictionary or stream associated to appearance type switch (type) { case appearRollover: apData = appearDict.dictLookupNF("R").copy(); if (apData.isNull()) apData = appearDict.dictLookupNF("N").copy(); break; case appearDown: apData = appearDict.dictLookupNF("D").copy(); if (apData.isNull()) apData = appearDict.dictLookupNF("N").copy(); break; case appearNormal: apData = appearDict.dictLookupNF("N").copy(); break; } if (apData.isDict() && state) return apData.dictLookupNF(state).copy(); else if (apData.isRef()) return apData; return Object(); } std::unique_ptr AnnotAppearance::getStateKey(int i) { const Object &obj1 = appearDict.dictLookupNF("N"); if (obj1.isDict()) return std::make_unique(obj1.dictGetKey(i)); return nullptr; } int AnnotAppearance::getNumStates() { int res = 0; const Object &obj1 = appearDict.dictLookupNF("N"); if (obj1.isDict()) res = obj1.dictGetLength(); return res; } // Test if stateObj (a Ref or a Dict) points to the specified stream bool AnnotAppearance::referencesStream(const Object *stateObj, Ref refToStream) { if (stateObj->isRef()) { const Ref r = stateObj->getRef(); if (r == refToStream) { return true; } } else if (stateObj->isDict()) { // Test each value const int size = stateObj->dictGetLength(); for (int i = 0; i < size; ++i) { const Object &obj1 = stateObj->dictGetValNF(i); if (obj1.isRef()) { const Ref r = obj1.getRef(); if (r == refToStream) { return true; } } } } return false; // Not found } // Test if this AnnotAppearance references the specified stream bool AnnotAppearance::referencesStream(Ref refToStream) { bool found; // Scan each state's ref/subdictionary const Object &objN = appearDict.dictLookupNF("N"); found = referencesStream(&objN, refToStream); if (found) return true; const Object &objR = appearDict.dictLookupNF("R"); found = referencesStream(&objR, refToStream); if (found) return true; const Object &objD = appearDict.dictLookupNF("D"); found = referencesStream(&objD, refToStream); return found; } // If this is the only annotation in the document that references the // specified appearance stream, remove the appearance stream void AnnotAppearance::removeStream(Ref refToStream) { const int lastpage = doc->getNumPages(); for (int pg = 1; pg <= lastpage; ++pg) { // Scan all annotations in the document Page *page = doc->getPage(pg); if (!page) { error(errSyntaxError, -1, "Failed check for shared annotation stream at page {0:d}", pg); continue; } Annots *annots = page->getAnnots(); for (int i = 0; i < annots->getNumAnnots(); ++i) { AnnotAppearance *annotAp = annots->getAnnot(i)->getAppearStreams(); if (annotAp && annotAp != this && annotAp->referencesStream(refToStream)) { return; // Another annotation points to the stream -> Don't delete it } } } // TODO: stream resources (e.g. font), AP name tree doc->getXRef()->removeIndirectObject(refToStream); } // Removes stream if obj is a Ref, or removes pointed streams if obj is a Dict void AnnotAppearance::removeStateStreams(const Object *state) { if (state->isRef()) { removeStream(state->getRef()); } else if (state->isDict()) { const int size = state->dictGetLength(); for (int i = 0; i < size; ++i) { const Object &obj2 = state->dictGetValNF(i); if (obj2.isRef()) { removeStream(obj2.getRef()); } } } } void AnnotAppearance::removeAllStreams() { const Object &objN = appearDict.dictLookupNF("N"); removeStateStreams(&objN); const Object &objR = appearDict.dictLookupNF("R"); removeStateStreams(&objR); const Object &objD = appearDict.dictLookupNF("D"); removeStateStreams(&objD); } //------------------------------------------------------------------------ // AnnotAppearanceCharacs //------------------------------------------------------------------------ AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) { Object obj1; obj1 = dict->lookup("R"); if (obj1.isInt()) { rotation = obj1.getInt(); } else { rotation = 0; } obj1 = dict->lookup("BC"); if (obj1.isArray()) { Array *colorComponents = obj1.getArray(); if (colorComponents->getLength() > 0) { borderColor = std::make_unique(colorComponents); } } obj1 = dict->lookup("BG"); if (obj1.isArray()) { Array *colorComponents = obj1.getArray(); if (colorComponents->getLength() > 0) { backColor = std::make_unique(colorComponents); } } obj1 = dict->lookup("CA"); if (obj1.isString()) { normalCaption = std::make_unique(obj1.getString()); } obj1 = dict->lookup("RC"); if (obj1.isString()) { rolloverCaption = std::make_unique(obj1.getString()); } obj1 = dict->lookup("AC"); if (obj1.isString()) { alternateCaption = std::make_unique(obj1.getString()); } obj1 = dict->lookup("IF"); if (obj1.isDict()) { iconFit = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("TP"); if (obj1.isInt()) { position = (AnnotAppearanceCharacsTextPos)obj1.getInt(); } else { position = captionNoIcon; } } AnnotAppearanceCharacs::~AnnotAppearanceCharacs() = default; //------------------------------------------------------------------------ // AnnotAppearanceBBox //------------------------------------------------------------------------ AnnotAppearanceBBox::AnnotAppearanceBBox(PDFRectangle *rect) { origX = rect->x1; origY = rect->y1; borderWidth = 0; // Initially set the same size as rect minX = 0; minY = 0; maxX = rect->x2 - rect->x1; maxY = rect->y2 - rect->y1; } void AnnotAppearanceBBox::extendTo(double x, double y) { if (x < minX) { minX = x; } else if (x > maxX) { maxX = x; } if (y < minY) { minY = y; } else if (y > maxY) { maxY = y; } } void AnnotAppearanceBBox::getBBoxRect(double bbox[4]) const { bbox[0] = minX - borderWidth; bbox[1] = minY - borderWidth; bbox[2] = maxX + borderWidth; bbox[3] = maxY + borderWidth; } double AnnotAppearanceBBox::getPageXMin() const { return origX + minX - borderWidth; } double AnnotAppearanceBBox::getPageYMin() const { return origY + minY - borderWidth; } double AnnotAppearanceBBox::getPageXMax() const { return origX + maxX + borderWidth; } double AnnotAppearanceBBox::getPageYMax() const { return origY + maxY + borderWidth; } //------------------------------------------------------------------------ // Annot //------------------------------------------------------------------------ #define annotLocker() std::unique_lock locker(mutex) Annot::Annot(PDFDoc *docA, PDFRectangle *rectA) { refCnt = 1; flags = flagUnknown; type = typeUnknown; Array *a = new Array(docA->getXRef()); a->add(Object(rectA->x1)); a->add(Object(rectA->y1)); a->add(Object(rectA->x2)); a->add(Object(rectA->y2)); annotObj = Object(new Dict(docA->getXRef())); annotObj.dictSet("Type", Object(objName, "Annot")); annotObj.dictSet("Rect", Object(a)); ref = docA->getXRef()->addIndirectObject(&annotObj); initialize(docA, annotObj.getDict()); } Annot::Annot(PDFDoc *docA, Object &&dictObject) { refCnt = 1; hasRef = false; flags = flagUnknown; type = typeUnknown; annotObj = std::move(dictObject); initialize(docA, annotObj.getDict()); } Annot::Annot(PDFDoc *docA, Object &&dictObject, const Object *obj) { refCnt = 1; if (obj->isRef()) { hasRef = true; ref = obj->getRef(); } else { hasRef = false; } flags = flagUnknown; type = typeUnknown; annotObj = std::move(dictObject); initialize(docA, annotObj.getDict()); } void Annot::initialize(PDFDoc *docA, Dict *dict) { Object apObj, asObj, obj1; ok = true; doc = docA; appearance.setToNull(); //----- parse the rectangle rect = std::make_unique(); obj1 = dict->lookup("Rect"); if (obj1.isArray() && obj1.arrayGetLength() == 4) { rect->x1 = obj1.arrayGet(0).getNumWithDefaultValue(0); rect->y1 = obj1.arrayGet(1).getNumWithDefaultValue(0); rect->x2 = obj1.arrayGet(2).getNumWithDefaultValue(1); rect->y2 = obj1.arrayGet(3).getNumWithDefaultValue(1); if (rect->x1 > rect->x2) { double t = rect->x1; rect->x1 = rect->x2; rect->x2 = t; } if (rect->y1 > rect->y2) { double t = rect->y1; rect->y1 = rect->y2; rect->y2 = t; } } else { rect->x1 = rect->y1 = 0; rect->x2 = rect->y2 = 1; error(errSyntaxError, -1, "Bad bounding box for annotation"); ok = false; } obj1 = dict->lookup("Contents"); if (obj1.isString()) { contents.reset(obj1.getString()->copy()); } else { contents = std::make_unique(); } // Note: This value is overwritten by Annots ctor const Object &pObj = dict->lookupNF("P"); if (pObj.isRef()) { const Ref pRef = pObj.getRef(); page = doc->getCatalog()->findPage(pRef); } else { page = 0; } obj1 = dict->lookup("NM"); if (obj1.isString()) { name.reset(obj1.getString()->copy()); } obj1 = dict->lookup("M"); if (obj1.isString()) { modified.reset(obj1.getString()->copy()); } //----- get the flags obj1 = dict->lookup("F"); if (obj1.isInt()) { flags |= obj1.getInt(); } else { flags = flagUnknown; } //----- get the annotation appearance dictionary apObj = dict->lookup("AP"); if (apObj.isDict()) { appearStreams = std::make_unique(doc, &apObj); } //----- get the appearance state asObj = dict->lookup("AS"); if (asObj.isName()) { appearState = std::make_unique(asObj.getName()); } else if (appearStreams && appearStreams->getNumStates() != 0) { error(errSyntaxError, -1, "Invalid or missing AS value in annotation containing one or more appearance subdictionaries"); // AS value is required in this case, but if the // N dictionary contains only one entry // take it as default appearance. if (appearStreams->getNumStates() == 1) { appearState = appearStreams->getStateKey(0); } } if (!appearState) { appearState = std::make_unique("Off"); } //----- get the annotation appearance if (appearStreams) { appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str()); } //----- parse the border style // According to the spec if neither the Border nor the BS entry is present, // the border shall be drawn as a solid line with a width of 1 point. But acroread // seems to ignore the Border entry for annots that can't have a BS entry. So, we only // follow this rule for annots tha can have a BS entry. obj1 = dict->lookup("Border"); if (obj1.isArray()) { border = std::make_unique(obj1.getArray()); } obj1 = dict->lookup("C"); if (obj1.isArray()) { color = std::make_unique(obj1.getArray()); } obj1 = dict->lookup("StructParent"); if (obj1.isInt()) { treeKey = obj1.getInt(); } else { treeKey = 0; } oc = dict->lookupNF("OC").copy(); } void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const { *x1 = rect->x1; *y1 = rect->y1; *x2 = rect->x2; *y2 = rect->y2; } void Annot::setRect(const PDFRectangle *rectA) { setRect(rectA->x1, rectA->y1, rectA->x2, rectA->y2); } void Annot::setRect(double x1, double y1, double x2, double y2) { if (x1 < x2) { rect->x1 = x1; rect->x2 = x2; } else { rect->x1 = x2; rect->x2 = x1; } if (y1 < y2) { rect->y1 = y1; rect->y2 = y2; } else { rect->y1 = y2; rect->y2 = y1; } Array *a = new Array(doc->getXRef()); a->add(Object(rect->x1)); a->add(Object(rect->y1)); a->add(Object(rect->x2)); a->add(Object(rect->y2)); update("Rect", Object(a)); invalidateAppearance(); } bool Annot::inRect(double x, double y) const { return rect->contains(x, y); } void Annot::update(const char *key, Object &&value) { annotLocker(); /* Set M to current time, unless we are updating M itself */ if (strcmp(key, "M") != 0) { modified.reset(timeToDateString(nullptr)); annotObj.dictSet("M", Object(modified->copy())); } annotObj.dictSet(const_cast(key), std::move(value)); doc->getXRef()->setModifiedObject(&annotObj, ref); } void Annot::setContents(GooString *new_content) { annotLocker(); if (new_content) { contents = std::make_unique(new_content); // append the unicode marker if needed if (!contents->hasUnicodeMarker()) { contents->prependUnicodeMarker(); } } else { contents = std::make_unique(); } update("Contents", Object(contents->copy())); } void Annot::setName(GooString *new_name) { annotLocker(); if (new_name) { name = std::make_unique(new_name); } else { name = std::make_unique(); } update("NM", Object(name->copy())); } void Annot::setModified(GooString *new_modified) { annotLocker(); if (new_modified) { modified = std::make_unique(new_modified); update("M", Object(modified->copy())); } else { modified.reset(nullptr); update("M", Object(objNull)); } } void Annot::setFlags(unsigned int new_flags) { annotLocker(); flags = new_flags; update("F", Object(int(flags))); } void Annot::setBorder(std::unique_ptr &&new_border) { annotLocker(); if (new_border) { Object obj1 = new_border->writeToObject(doc->getXRef()); update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", std::move(obj1)); border = std::move(new_border); } else { border = nullptr; } invalidateAppearance(); } void Annot::setColor(std::unique_ptr &&new_color) { annotLocker(); if (new_color) { Object obj1 = new_color->writeToObject(doc->getXRef()); update("C", std::move(obj1)); color = std::move(new_color); } else { color = nullptr; } invalidateAppearance(); } void Annot::setPage(int pageIndex, bool updateP) { annotLocker(); Page *pageobj = doc->getPage(pageIndex); Object obj1(objNull); if (pageobj) { const Ref pageRef = pageobj->getRef(); obj1 = Object(pageRef); page = pageIndex; } else { page = 0; } if (updateP) { update("P", std::move(obj1)); } } void Annot::setAppearanceState(const char *state) { annotLocker(); if (!state) return; appearState = std::make_unique(state); appearBBox = nullptr; update("AS", Object(objName, state)); // The appearance state determines the current appearance stream if (appearStreams) { appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str()); } else { appearance.setToNull(); } } void Annot::invalidateAppearance() { annotLocker(); if (appearStreams) { // Remove existing appearance streams appearStreams->removeAllStreams(); } appearStreams = nullptr; appearState = nullptr; appearBBox = nullptr; appearance.setToNull(); Object obj2 = annotObj.dictLookup("AP"); if (!obj2.isNull()) update("AP", Object(objNull)); // Remove AP obj2 = annotObj.dictLookup("AS"); if (!obj2.isNull()) update("AS", Object(objNull)); // Remove AS } double Annot::getXMin() { return rect->x1; } double Annot::getYMin() { return rect->y1; } double Annot::getXMax() { return rect->x2; } double Annot::getYMax() { return rect->y2; } void Annot::readArrayNum(Object *pdfArray, int key, double *value) { Object valueObject = pdfArray->arrayGet(key); if (valueObject.isNum()) { *value = valueObject.getNum(); } else { *value = 0; ok = false; } } void Annot::removeReferencedObjects() { // Remove appearance streams (if any) invalidateAppearance(); } void Annot::incRefCnt() { refCnt++; } void Annot::decRefCnt() { if (--refCnt == 0) { delete this; } } Annot::~Annot() { } void AnnotAppearanceBuilder::setDrawColor(const AnnotColor *drawColor, bool fill) { const double *values = drawColor->getValues(); switch (drawColor->getSpace()) { case AnnotColor::colorCMYK: appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:.5f} {4:c}\n", values[0], values[1], values[2], values[3], fill ? 'k' : 'K'); break; case AnnotColor::colorRGB: appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:s}\n", values[0], values[1], values[2], fill ? "rg" : "RG"); break; case AnnotColor::colorGray: appearBuf->appendf("{0:.5f} {1:c}\n", values[0], fill ? 'g' : 'G'); break; case AnnotColor::colorTransparent: default: break; } } void AnnotAppearanceBuilder::setTextFont(const Object &fontName, double fontSize) { if (fontName.isName() && strlen(fontName.getName()) > 0) appearBuf->appendf("/{0:s} {1:.2f} Tf\n", fontName.getName(), fontSize); } void AnnotAppearanceBuilder::setLineStyleForBorder(const AnnotBorder *border) { int i, dashLength; double *dash; switch (border->getStyle()) { case AnnotBorder::borderDashed: appearBuf->append("["); dashLength = border->getDashLength(); dash = border->getDash(); for (i = 0; i < dashLength; ++i) appearBuf->appendf(" {0:.2f}", dash[i]); appearBuf->append(" ] 0 d\n"); break; default: appearBuf->append("[] 0 d\n"); break; } appearBuf->appendf("{0:.2f} w\n", border->getWidth()); } // Draw an (approximate) circle of radius centered at (, ). // If is true, the circle is filled; otherwise it is stroked. void AnnotAppearanceBuilder::drawCircle(double cx, double cy, double r, bool fill) { if (fill) drawEllipse(cx, cy, r, r, true, false); else drawEllipse(cx, cy, r, r, false, true); } // Draw an (approximate) ellipse of radius on x-axis and on y-axis, centered at (, ). // If is true, the ellipse is filled with current color for non-stroking operations. // If is true, the ellipse path ist stroked with current color and color space for stroking operations. // Path will be closed if either fill or stroke is true; otherwise it's left open. void AnnotAppearanceBuilder::drawEllipse(double cx, double cy, double rx, double ry, bool fill, bool stroke) { appearBuf->appendf("{0:.2f} {1:.2f} m\n", cx + rx, cy); appearBuf->appendf("{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); appearBuf->appendf("{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); appearBuf->appendf("{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); appearBuf->appendf("{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); if (!fill && stroke) appearBuf->append("s\n"); else if (fill && !stroke) appearBuf->append("f\n"); else if (fill && stroke) appearBuf->append("b\n"); } // Draw the top-left half of an (approximate) circle of radius // centered at (, ). void AnnotAppearanceBuilder::drawCircleTopLeft(double cx, double cy, double r) { double r2; r2 = r / sqrt(2.0); appearBuf->appendf("{0:.2f} {1:.2f} m\n", cx + r2, cy + r2); appearBuf->appendf("{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); appearBuf->appendf("{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); appearBuf->append("S\n"); } // Draw the bottom-right half of an (approximate) circle of radius // centered at (, ). void AnnotAppearanceBuilder::drawCircleBottomRight(double cx, double cy, double r) { double r2; r2 = r / sqrt(2.0); appearBuf->appendf("{0:.2f} {1:.2f} m\n", cx - r2, cy - r2); appearBuf->appendf("{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); appearBuf->appendf("{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); appearBuf->append("S\n"); } void AnnotAppearanceBuilder::drawLineEndSquare(double x, double y, double size, bool fill, const Matrix &m) { const double halfSize { size / 2. }; const double x1[3] { x - size, x - size, x }; const double y1[3] { y + halfSize, y - halfSize, y - halfSize }; double tx, ty; m.transform(x, y + halfSize, &tx, &ty); appendf("{0:.2f} {1:.2f} m\n", tx, ty); for (int i = 0; i < 3; i++) { m.transform(x1[i], y1[i], &tx, &ty); appendf("{0:.2f} {1:.2f} l\n", tx, ty); } appearBuf->append(fill ? "b\n" : "s\n"); } void AnnotAppearanceBuilder::drawLineEndCircle(double x, double y, double size, bool fill, const Matrix &m) { const double halfSize { size / 2. }; const double x1[4] { x, x - halfSize - bezierCircle * halfSize, x - size, x - halfSize + bezierCircle * halfSize }; const double x2[4] { x - halfSize + bezierCircle * halfSize, x - size, x - halfSize - bezierCircle * halfSize, x }; const double x3[4] { x - halfSize, x - size, x - halfSize, x }; const double y1[4] { y + bezierCircle * halfSize, y + halfSize, y - bezierCircle * halfSize, y - halfSize }; const double y2[4] { y + halfSize, y + bezierCircle * halfSize, y - halfSize, y - bezierCircle * halfSize }; const double y3[4] { y + halfSize, y, y - halfSize, y }; double tx[3]; double ty[3]; m.transform(x, y, &tx[0], &ty[0]); appearBuf->appendf("{0:.2f} {1:.2f} m\n", tx[0], ty[0]); for (int i = 0; i < 4; i++) { m.transform(x1[i], y1[i], &tx[0], &ty[0]); m.transform(x2[i], y2[i], &tx[1], &ty[1]); m.transform(x3[i], y3[i], &tx[2], &ty[2]); appearBuf->appendf("{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]); } appearBuf->append(fill ? "b\n" : "s\n"); } void AnnotAppearanceBuilder::drawLineEndDiamond(double x, double y, double size, bool fill, const Matrix &m) { const double halfSize { size / 2. }; const double x1[3] { x - halfSize, x - size, x - halfSize }; const double y1[3] { y + halfSize, y, y - halfSize }; double tx, ty; m.transform(x, y, &tx, &ty); appendf("{0:.2f} {1:.2f} m\n", tx, ty); for (int i = 0; i < 3; i++) { m.transform(x1[i], y1[i], &tx, &ty); appendf("{0:.2f} {1:.2f} l\n", tx, ty); } appearBuf->append(fill ? "b\n" : "s\n"); } void AnnotAppearanceBuilder::drawLineEndArrow(double x, double y, double size, int orientation, bool isOpen, bool fill, const Matrix &m) { const double alpha { M_PI / 6. }; const double xOffs { orientation * size }; const double yOffs { tan(alpha) * size }; double tx, ty; m.transform(x - xOffs, y + yOffs, &tx, &ty); appendf("{0:.2f} {1:.2f} m\n", tx, ty); m.transform(x, y, &tx, &ty); appendf("{0:.2f} {1:.2f} l\n", tx, ty); m.transform(x - xOffs, y - yOffs, &tx, &ty); appendf("{0:.2f} {1:.2f} l\n", tx, ty); if (isOpen) { appearBuf->append("S\n"); } else { appearBuf->append(fill ? "b\n" : "s\n"); } } void AnnotAppearanceBuilder::drawLineEndSlash(double x, double y, double size, const Matrix &m) { const double halfSize { size / 2. }; const double xOffs { cos(M_PI / 3.) * halfSize }; double tx, ty; m.transform(x - xOffs, y - halfSize, &tx, &ty); appendf("{0:.2f} {1:.2f} m\n", tx, ty); m.transform(x + xOffs, y + halfSize, &tx, &ty); appendf("{0:.2f} {1:.2f} l\n", tx, ty); appearBuf->append("S\n"); } void AnnotAppearanceBuilder::drawLineEnding(AnnotLineEndingStyle endingStyle, double x, double y, double size, bool fill, const Matrix &m) { switch (endingStyle) { case annotLineEndingSquare: drawLineEndSquare(x, y, size, fill, m); break; case annotLineEndingCircle: drawLineEndCircle(x, y, size, fill, m); break; case annotLineEndingDiamond: drawLineEndDiamond(x, y, size, fill, m); break; case annotLineEndingOpenArrow: drawLineEndArrow(x, y, size, 1, true, fill, m); break; case annotLineEndingClosedArrow: drawLineEndArrow(x, y, size, 1, false, fill, m); break; case annotLineEndingButt: { const double halfSize { size / 2. }; double tx, ty; m.transform(x, y + halfSize, &tx, &ty); appendf("{0:.2f} {1:.2f} m\n", tx, ty); m.transform(x, y - halfSize, &tx, &ty); appendf("{0:.2f} {1:.2f} l S\n", tx, ty); } break; case annotLineEndingROpenArrow: drawLineEndArrow(x, y, size, -1, true, fill, m); break; case annotLineEndingRClosedArrow: drawLineEndArrow(x, y, size, -1, false, fill, m); break; case annotLineEndingSlash: drawLineEndSlash(x, y, size, m); break; default: break; } } double AnnotAppearanceBuilder::lineEndingXShorten(AnnotLineEndingStyle endingStyle, double size) { switch (endingStyle) { case annotLineEndingCircle: case annotLineEndingClosedArrow: case annotLineEndingDiamond: case annotLineEndingSquare: return size; default: break; } return 0; } double AnnotAppearanceBuilder::lineEndingXExtendBBox(AnnotLineEndingStyle endingStyle, double size) { switch (endingStyle) { case annotLineEndingRClosedArrow: case annotLineEndingROpenArrow: return size; case annotLineEndingSlash: return cos(M_PI / 3.) * size / 2.; default: break; } return 0; } Object Annot::createForm(const GooString *appearBuf, double *bbox, bool transparencyGroup, Dict *resDict) { return createForm(appearBuf, bbox, transparencyGroup, resDict ? Object(resDict) : Object()); } Object Annot::createForm(const GooString *appearBuf, double *bbox, bool transparencyGroup, Object &&resDictObject) { Dict *appearDict = new Dict(doc->getXRef()); appearDict->set("Length", Object(appearBuf->getLength())); appearDict->set("Subtype", Object(objName, "Form")); Array *a = new Array(doc->getXRef()); a->add(Object(bbox[0])); a->add(Object(bbox[1])); a->add(Object(bbox[2])); a->add(Object(bbox[3])); appearDict->set("BBox", Object(a)); if (transparencyGroup) { Dict *d = new Dict(doc->getXRef()); d->set("S", Object(objName, "Transparency")); appearDict->set("Group", Object(d)); } if (resDictObject.isDict()) appearDict->set("Resources", std::move(resDictObject)); Stream *mStream = new AutoFreeMemStream(copyString(appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict)); return Object(mStream); } Dict *Annot::createResourcesDict(const char *formName, Object &&formStream, const char *stateName, double opacity, const char *blendMode) { Dict *gsDict = new Dict(doc->getXRef()); if (opacity != 1) { gsDict->set("CA", Object(opacity)); gsDict->set("ca", Object(opacity)); } if (blendMode) gsDict->set("BM", Object(objName, blendMode)); Dict *stateDict = new Dict(doc->getXRef()); stateDict->set(stateName, Object(gsDict)); Dict *formDict = new Dict(doc->getXRef()); formDict->set(formName, std::move(formStream)); Dict *resDict = new Dict(doc->getXRef()); resDict->set("ExtGState", Object(stateDict)); resDict->set("XObject", Object(formDict)); return resDict; } Object Annot::getAppearanceResDict() { Object obj1, obj2; // Fetch appearance's resource dict (if any) obj1 = appearance.fetch(doc->getXRef()); if (obj1.isStream()) { obj2 = obj1.streamGetDict()->lookup("Resources"); if (obj2.isDict()) { return obj2; } } return Object(objNull); } bool Annot::isVisible(bool printing) { // check the flags if ((flags & flagHidden) || (printing && !(flags & flagPrint)) || (!printing && (flags & flagNoView))) { return false; } // check the OC OCGs *optContentConfig = doc->getCatalog()->getOptContentConfig(); if (optContentConfig) { if (!optContentConfig->optContentIsVisible(&oc)) return false; } return true; } int Annot::getRotation() const { Page *pageobj = doc->getPage(page); assert(pageobj != nullptr); if (flags & flagNoRotate) { return (360 - pageobj->getRotate()) % 360; } else { return 0; } } void Annot::draw(Gfx *gfx, bool printing) { annotLocker(); if (!isVisible(printing)) return; // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // AnnotPopup //------------------------------------------------------------------------ AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { type = typePopup; annotObj.dictSet("Subtype", Object(objName, "Popup")); initialize(docA, annotObj.getDict()); } AnnotPopup::AnnotPopup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typePopup; initialize(docA, annotObj.getDict()); } AnnotPopup::~AnnotPopup() { } void AnnotPopup::initialize(PDFDoc *docA, Dict *dict) { const Object &parentObj = dict->lookupNF("Parent"); if (parentObj.isRef()) { parentRef = parentObj.getRef(); } else { parentRef = Ref::INVALID(); } open = dict->lookup("Open").getBoolWithDefaultValue(false); } void AnnotPopup::setParent(Annot *parentA) { parentRef = parentA->getRef(); update("Parent", Object(parentRef)); } void AnnotPopup::setOpen(bool openA) { open = openA; update("Open", Object(open)); } //------------------------------------------------------------------------ // AnnotMarkup //------------------------------------------------------------------------ AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { initialize(docA, annotObj.getDict()); } AnnotMarkup::AnnotMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { initialize(docA, annotObj.getDict()); } AnnotMarkup::~AnnotMarkup() = default; void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("T"); if (obj1.isString()) { label.reset(obj1.getString()->copy()); } Object popupObj = dict->lookup("Popup"); const Object &obj2 = dict->lookupNF("Popup"); if (popupObj.isDict() && obj2.isRef()) { popup = std::make_unique(docA, std::move(popupObj), &obj2); } opacity = dict->lookup("CA").getNumWithDefaultValue(1.0); obj1 = dict->lookup("CreationDate"); if (obj1.isString()) { date.reset(obj1.getString()->copy()); } const Object &irtObj = dict->lookupNF("IRT"); if (irtObj.isRef()) { inReplyTo = irtObj.getRef(); } else { inReplyTo = Ref::INVALID(); } obj1 = dict->lookup("Subj"); if (obj1.isString()) { subject.reset(obj1.getString()->copy()); } obj1 = dict->lookup("RT"); if (obj1.isName()) { const char *replyName = obj1.getName(); if (!strcmp(replyName, "R")) { replyTo = replyTypeR; } else if (!strcmp(replyName, "Group")) { replyTo = replyTypeGroup; } else { replyTo = replyTypeR; } } else { replyTo = replyTypeR; } obj1 = dict->lookup("ExData"); if (obj1.isDict()) { exData = parseAnnotExternalData(obj1.getDict()); } else { exData = annotExternalDataMarkupUnknown; } } void AnnotMarkup::setLabel(GooString *new_label) { if (new_label) { label = std::make_unique(new_label); // append the unicode marker if needed if (!label->hasUnicodeMarker()) { label->prependUnicodeMarker(); } } else { label = std::make_unique(); } update("T", Object(label->copy())); } void AnnotMarkup::setPopup(std::unique_ptr &&new_popup) { // If there exists an old popup annotation that is already // associated with a page, then we need to remove that // popup annotation from the page. Otherwise we would have // dangling references to it. if (popup && popup->getPageNum() != 0) { Page *pageobj = doc->getPage(popup->getPageNum()); if (pageobj) { pageobj->removeAnnot(popup.get()); } } if (new_popup) { const Ref popupRef = new_popup->getRef(); update("Popup", Object(popupRef)); new_popup->setParent(this); popup = std::move(new_popup); // If this annotation is already added to a page, then we // add the new popup annotation to the same page. if (page != 0) { Page *pageobj = doc->getPage(page); assert(pageobj != nullptr); // pageobj should exist in doc (see setPage()) pageobj->addAnnot(popup.get()); } } else { popup = nullptr; } } void AnnotMarkup::setOpacity(double opacityA) { opacity = opacityA; update("CA", Object(opacity)); invalidateAppearance(); } void AnnotMarkup::setDate(GooString *new_date) { if (new_date) { date = std::make_unique(new_date); update("CreationDate", Object(date->copy())); } else { date.reset(nullptr); update("CreationDate", Object(objNull)); } } void AnnotMarkup::removeReferencedObjects() { Page *pageobj = doc->getPage(page); assert(pageobj != nullptr); // We're called when removing an annot from a page // Remove popup if (popup) { pageobj->removeAnnot(popup.get()); } Annot::removeReferencedObjects(); } //------------------------------------------------------------------------ // AnnotText //------------------------------------------------------------------------ AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeText; flags |= flagNoZoom | flagNoRotate; annotObj.dictSet("Subtype", Object(objName, "Text")); initialize(docA, annotObj.getDict()); } AnnotText::AnnotText(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeText; flags |= flagNoZoom | flagNoRotate; initialize(docA, annotObj.getDict()); } AnnotText::~AnnotText() = default; void AnnotText::initialize(PDFDoc *docA, Dict *dict) { Object obj1; open = dict->lookup("Open").getBoolWithDefaultValue(false); obj1 = dict->lookup("Name"); if (obj1.isName()) { icon = std::make_unique(obj1.getName()); } else { icon = std::make_unique("Note"); } obj1 = dict->lookup("StateModel"); if (obj1.isString()) { const GooString *modelName = obj1.getString(); Object obj2 = dict->lookup("State"); if (obj2.isString()) { const GooString *stateName = obj2.getString(); if (!stateName->cmp("Marked")) { state = stateMarked; } else if (!stateName->cmp("Unmarked")) { state = stateUnmarked; } else if (!stateName->cmp("Accepted")) { state = stateAccepted; } else if (!stateName->cmp("Rejected")) { state = stateRejected; } else if (!stateName->cmp("Cancelled")) { state = stateCancelled; } else if (!stateName->cmp("Completed")) { state = stateCompleted; } else if (!stateName->cmp("None")) { state = stateNone; } else { state = stateUnknown; } } else { state = stateUnknown; } if (!modelName->cmp("Marked")) { switch (state) { case stateUnknown: state = stateMarked; break; case stateAccepted: case stateRejected: case stateCancelled: case stateCompleted: case stateNone: state = stateUnknown; break; default: break; } } else if (!modelName->cmp("Review")) { switch (state) { case stateUnknown: state = stateNone; break; case stateMarked: case stateUnmarked: state = stateUnknown; break; default: break; } } else { state = stateUnknown; } } else { state = stateUnknown; } } void AnnotText::setOpen(bool openA) { open = openA; update("Open", Object(open)); } void AnnotText::setIcon(GooString *new_icon) { if (new_icon && icon->cmp(new_icon) == 0) return; if (new_icon) { icon = std::make_unique(new_icon); } else { icon = std::make_unique("Note"); } update("Name", Object(objName, icon->c_str())); invalidateAppearance(); } #define ANNOT_TEXT_AP_NOTE \ "3.602 24 m 20.398 24 l 22.387 24 24 22.387 24 20.398 c 24 3.602 l 24\n" \ "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" \ "l 0 22.387 1.613 24 3.602 24 c h\n" \ "3.602 24 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "1.5 w\n" \ "0 j\n" \ "10 16 m 14 21 l S\n" \ "1.85625 w\n" \ "1 j\n" \ "15.07 20.523 m 15.07 19.672 14.379 18.977 13.523 18.977 c 12.672 18.977\n" \ "11.977 19.672 11.977 20.523 c 11.977 21.379 12.672 22.07 13.523 22.07 c\n" \ "14.379 22.07 15.07 21.379 15.07 20.523 c h\n" \ "15.07 20.523 m S\n" \ "1 w\n" \ "0 j\n" \ "6.5 13.5 m 15.5 13.5 l S\n" \ "6.5 10.5 m 13.5 10.5 l S\n" \ "6.801 7.5 m 15.5 7.5 l S\n" \ "0.729412 0.741176 0.713725 RG 2 w\n" \ "1 j\n" \ "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" \ "1.5 w\n" \ "0 j\n" \ "10 17 m 14 22 l S\n" \ "1.85625 w\n" \ "1 j\n" \ "15.07 21.523 m 15.07 20.672 14.379 19.977 13.523 19.977 c 12.672 19.977\n" \ "11.977 20.672 11.977 21.523 c 11.977 22.379 12.672 23.07 13.523 23.07 c\n" \ "14.379 23.07 15.07 22.379 15.07 21.523 c h\n" \ "15.07 21.523 m S\n" \ "1 w\n" \ "0 j\n" \ "6.5 14.5 m 15.5 14.5 l S\n" \ "6.5 11.5 m 13.5 11.5 l S\n" \ "6.801 8.5 m 15.5 8.5 l S\n" #define ANNOT_TEXT_AP_COMMENT \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "0 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "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" \ "5.637 20 8 20 c h\n" \ "8 20 m S\n" \ "0.729412 0.741176 0.713725 RG 8 21 m 16 21 l 18.363 21 20 19.215 20 17\n" \ "c 20 14 l 20 11.785 18.363 10\n" \ "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" \ "19.215 5.637 21 8 21 c h\n" \ "8 21 m S\n" #define ANNOT_TEXT_AP_KEY \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "0 j\n" \ "[] 0.0 d\n" \ "4 M 11.895 18.754 m 13.926 20.625 17.09 20.496 18.961 18.465 c 20.832\n" \ "16.434 20.699 13.27 18.668 11.398 c 17.164 10.016 15.043 9.746 13.281\n" \ "10.516 c 12.473 9.324 l 11.281 10.078 l 9.547 8.664 l 9.008 6.496 l\n" \ "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" \ "10.57 13.457 l 9.949 15.277 10.391 17.367 11.895 18.754 c h\n" \ "11.895 18.754 m S\n" \ "1.5 w\n" \ "16.059 15.586 m 16.523 15.078 17.316 15.043 17.824 15.512 c 18.332\n" \ "15.98 18.363 16.77 17.895 17.277 c 17.43 17.785 16.637 17.816 16.129\n" \ "17.352 c 15.621 16.883 15.59 16.094 16.059 15.586 c h\n" \ "16.059 15.586 m S\n" \ "0.729412 0.741176 0.713725 RG 2 w\n" \ "11.895 19.754 m 13.926 21.625 17.09 21.496 18.961 19.465 c 20.832\n" \ "17.434 20.699 14.27 18.668 12.398 c 17.164 11.016 15.043 10.746 13.281\n" \ "11.516 c 12.473 10.324 l 11.281 11.078 l 9.547 9.664 l 9.008 7.496 l\n" \ "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" \ "10.57 14.457 l 9.949 16.277 10.391 18.367 11.895 19.754 c h\n" \ "11.895 19.754 m S\n" \ "1.5 w\n" \ "16.059 16.586 m 16.523 16.078 17.316 16.043 17.824 16.512 c 18.332\n" \ "16.98 18.363 17.77 17.895 18.277 c 17.43 18.785 16.637 18.816 16.129\n" \ "18.352 c 15.621 17.883 15.59 17.094 16.059 16.586 c h\n" \ "16.059 16.586 m S\n" #define ANNOT_TEXT_AP_HELP \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2.5 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "14.328 18.559 15.195 17.406 c 16.062 16.254 16.242 14.723 15.664 13.398\n" \ "c S\n" \ "0 j\n" \ "12 8 m 12 12 16 11 16 15 c S\n" \ "1.539286 w\n" \ "1 j\n" \ "q 1 0 0 -0.999991 0 24 cm\n" \ "12.684 20.891 m 12.473 21.258 12.004 21.395 11.629 21.196 c 11.254\n" \ "20.992 11.105 20.531 11.297 20.149 c 11.488 19.77 11.945 19.61 12.332\n" \ "19.789 c 12.719 19.969 12.891 20.426 12.719 20.817 c S Q\n" \ "0.729412 0.741176 0.713725 RG 2.5 w\n" \ "8.289 17.488 m 9.109 19.539 11.438 20.535 13.488 19.711 c 15.539 18.891\n" \ "16.535 16.562 15.711 14.512 c 15.699 14.473 15.684 14.438 15.664 14.398\n" \ "c S\n" \ "0 j\n" \ "12 9 m 12 13 16 12 16 16 c S\n" \ "1.539286 w\n" \ "1 j\n" \ "q 1 0 0 -0.999991 0 24 cm\n" \ "12.684 19.891 m 12.473 20.258 12.004 20.395 11.629 20.195 c 11.254\n" \ "19.992 11.105 19.531 11.297 19.149 c 11.488 18.77 11.945 18.61 12.332\n" \ "18.789 c 12.719 18.969 12.891 19.426 12.719 19.817 c S Q\n" #define ANNOT_TEXT_AP_NEW_PARAGRAPH \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 4 w\n" \ "0 J\n" \ "2 j\n" \ "[] 0.0 d\n" \ "4 M q 1 0 0 -1 0 24 cm\n" \ "9.211 11.988 m 8.449 12.07 7.711 11.707 7.305 11.059 c 6.898 10.41\n" \ "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" \ "1.004413 w\n" \ "1 J\n" \ "1 j\n" \ "q 1 0 0 -0.991232 0 24 cm\n" \ "18.07 11.511 m 15.113 10.014 l 12.199 11.602 l 12.711 8.323 l 10.301\n" \ "6.045 l 13.574 5.517 l 14.996 2.522 l 16.512 5.474 l 19.801 5.899 l\n" \ "17.461 8.252 l 18.07 11.511 l h\n" \ "18.07 11.511 m S Q\n" \ "2 w\n" \ "0 j\n" \ "11 17 m 10 17 l 10 3 l S\n" \ "14 3 m 14 13 l S\n" \ "0.729412 0.741176 0.713725 RG 4 w\n" \ "0 J\n" \ "2 j\n" \ "q 1 0 0 -1 0 24 cm\n" \ "9.211 10.988 m 8.109 11.105 7.125 10.309 7.012 9.211 c 6.895 8.109\n" \ "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" \ "1.004413 w\n" \ "1 J\n" \ "1 j\n" \ "q 1 0 0 -0.991232 0 24 cm\n" \ "18.07 10.502 m 15.113 9.005 l 12.199 10.593 l 12.711 7.314 l 10.301\n" \ "5.036 l 13.574 4.508 l 14.996 1.513 l 16.512 4.465 l 19.801 4.891 l\n" \ "17.461 7.243 l 18.07 10.502 l h\n" \ "18.07 10.502 m S Q\n" \ "2 w\n" \ "0 j\n" \ "11 18 m 10 18 l 10 4 l S\n" \ "14 4 m 14 14 l S\n" #define ANNOT_TEXT_AP_PARAGRAPH \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "4 M 15 3 m 15 18 l 11 18 l 11 3 l S\n" \ "4 w\n" \ "q 1 0 0 -1 0 24 cm\n" \ "9.777 10.988 m 8.746 10.871 7.973 9.988 8 8.949 c 8.027 7.91 8.844\n" \ "7.066 9.879 7.004 c S Q\n" \ "0.729412 0.741176 0.713725 RG 2 w\n" \ "15 4 m 15 19 l 11 19 l 11 4 l S\n" \ "4 w\n" \ "q 1 0 0 -1 0 24 cm\n" \ "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" \ "9.879 6.004 c S Q\n" #define ANNOT_TEXT_AP_INSERT \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "0 j\n" \ "[] 0.0 d\n" \ "4 M 12 18.012 m 20 18 l S\n" \ "9 10 m 17 10 l S\n" \ "12 14.012 m 20 14 l S\n" \ "12 6.012 m 20 6.012 l S\n" \ "4 12 m 6 10 l 4 8 l S\n" \ "4 12 m 4 8 l S\n" \ "0.729412 0.741176 0.713725 RG 12 19.012 m 20 19 l S\n" \ "9 11 m 17 11 l S\n" \ "12 15.012 m 20 15 l S\n" \ "12 7.012 m 20 7.012 l S\n" \ "4 13 m 6 11 l 4 9 l S\n" \ "4 13 m 4 9 l S\n" #define ANNOT_TEXT_AP_CROSS \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2.5 w\n" \ "1 J\n" \ "0 j\n" \ "[] 0.0 d\n" \ "4 M 18 5 m 6 17 l S\n" \ "6 5 m 18 17 l S\n" \ "0.729412 0.741176 0.713725 RG 18 6 m 6 18 l S\n" \ "6 6 m 18 18 l S\n" #define ANNOT_TEXT_AP_CIRCLE \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2.5 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "15.641 7.859 19 12 19 c 16.141 19 19.5 15.641 19.5 11.5 c h\n" \ "19.5 11.5 m S\n" \ "0.729412 0.741176 0.713725 RG 19.5 12.5 m 19.5 8.359 16.141 5 12 5 c\n" \ "7.859 5 4.5 8.359 4.5 12.5 c 4.5\n" \ "16.641 7.859 20 12 20 c 16.141 20 19.5 16.641 19.5 12.5 c h\n" \ "19.5 12.5 m S\n" void AnnotText::draw(Gfx *gfx, bool printing) { double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) appearBuilder.setDrawColor(color.get(), true); else appearBuilder.append("1 1 1 rg\n"); if (!icon->cmp("Note")) appearBuilder.append(ANNOT_TEXT_AP_NOTE); else if (!icon->cmp("Comment")) appearBuilder.append(ANNOT_TEXT_AP_COMMENT); else if (!icon->cmp("Key")) appearBuilder.append(ANNOT_TEXT_AP_KEY); else if (!icon->cmp("Help")) appearBuilder.append(ANNOT_TEXT_AP_HELP); else if (!icon->cmp("NewParagraph")) appearBuilder.append(ANNOT_TEXT_AP_NEW_PARAGRAPH); else if (!icon->cmp("Paragraph")) appearBuilder.append(ANNOT_TEXT_AP_PARAGRAPH); else if (!icon->cmp("Insert")) appearBuilder.append(ANNOT_TEXT_AP_INSERT); else if (!icon->cmp("Cross")) appearBuilder.append(ANNOT_TEXT_AP_CROSS); else if (!icon->cmp("Circle")) appearBuilder.append(ANNOT_TEXT_AP_CIRCLE); appearBuilder.append("Q\n"); // Force 24x24 rectangle PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2); appearBBox = std::make_unique(&fixedRect); double bbox[4]; appearBBox->getBBoxRect(bbox); if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (appearBBox) { gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation()); } else { gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } } //------------------------------------------------------------------------ // AnnotLink //------------------------------------------------------------------------ AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { type = typeLink; annotObj.dictSet("Subtype", Object(objName, "Link")); initialize(docA, annotObj.getDict()); } AnnotLink::AnnotLink(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typeLink; initialize(docA, annotObj.getDict()); } AnnotLink::~AnnotLink() = default; void AnnotLink::initialize(PDFDoc *docA, Dict *dict) { Object obj1; // look for destination obj1 = dict->lookup("Dest"); if (!obj1.isNull()) { action = LinkAction::parseDest(&obj1); // look for action } else { obj1 = dict->lookup("A"); if (obj1.isDict()) { action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); } } obj1 = dict->lookup("H"); if (obj1.isName()) { const char *effect = obj1.getName(); if (!strcmp(effect, "N")) { linkEffect = effectNone; } else if (!strcmp(effect, "I")) { linkEffect = effectInvert; } else if (!strcmp(effect, "O")) { linkEffect = effectOutline; } else if (!strcmp(effect, "P")) { linkEffect = effectPush; } else { linkEffect = effectInvert; } } else { linkEffect = effectInvert; } /* obj1 = dict->lookup("PA"); if (obj1.isDict()) { uriAction = NULL; } else { uriAction = NULL; } obj1.free(); */ obj1 = dict->lookup("QuadPoints"); if (obj1.isArray()) { quadrilaterals = std::make_unique(obj1.getArray(), rect.get()); } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } } void AnnotLink::draw(Gfx *gfx, bool printing) { if (!isVisible(printing)) return; annotLocker(); // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, border.get(), color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // AnnotFreeText //------------------------------------------------------------------------ const double AnnotFreeText::undefinedFontPtSize = 10.; AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rectA, const DefaultAppearance &da) : AnnotMarkup(docA, rectA) { type = typeFreeText; GooString *daStr = da.toAppearanceString(); annotObj.dictSet("Subtype", Object(objName, "FreeText")); annotObj.dictSet("DA", Object(daStr)); initialize(docA, annotObj.getDict()); } AnnotFreeText::AnnotFreeText(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeFreeText; initialize(docA, annotObj.getDict()); } AnnotFreeText::~AnnotFreeText() = default; void AnnotFreeText::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("DA"); if (obj1.isString()) { appearanceString.reset(obj1.getString()->copy()); } else { appearanceString = std::make_unique(); error(errSyntaxWarning, -1, "Bad appearance for annotation"); } obj1 = dict->lookup("Q"); if (obj1.isInt()) { quadding = (AnnotFreeTextQuadding)obj1.getInt(); } else { quadding = quaddingLeftJustified; } obj1 = dict->lookup("DS"); if (obj1.isString()) { styleString.reset(obj1.getString()->copy()); } obj1 = dict->lookup("CL"); if (obj1.isArray() && obj1.arrayGetLength() >= 4) { const double x1 = obj1.arrayGet(0).getNumWithDefaultValue(0); const double y1 = obj1.arrayGet(1).getNumWithDefaultValue(0); const double x2 = obj1.arrayGet(2).getNumWithDefaultValue(0); const double y2 = obj1.arrayGet(3).getNumWithDefaultValue(0); if (obj1.arrayGetLength() == 6) { const double x3 = obj1.arrayGet(4).getNumWithDefaultValue(0); const double y3 = obj1.arrayGet(5).getNumWithDefaultValue(0); calloutLine = std::make_unique(x1, y1, x2, y2, x3, y3); } else { calloutLine = std::make_unique(x1, y1, x2, y2); } } obj1 = dict->lookup("IT"); if (obj1.isName()) { const char *intentName = obj1.getName(); if (!strcmp(intentName, "FreeText")) { intent = intentFreeText; } else if (!strcmp(intentName, "FreeTextCallout")) { intent = intentFreeTextCallout; } else if (!strcmp(intentName, "FreeTextTypeWriter")) { intent = intentFreeTextTypeWriter; } else { intent = intentFreeText; } } else { intent = intentFreeText; } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } obj1 = dict->lookup("BE"); if (obj1.isDict()) { borderEffect = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("RD"); if (obj1.isArray()) { rectangle = parseDiffRectangle(obj1.getArray(), rect.get()); } obj1 = dict->lookup("LE"); if (obj1.isName()) { GooString styleName(obj1.getName()); endStyle = parseAnnotLineEndingStyle(&styleName); } else { endStyle = annotLineEndingNone; } } void AnnotFreeText::setContents(GooString *new_content) { Annot::setContents(new_content); invalidateAppearance(); } void AnnotFreeText::setDefaultAppearance(const DefaultAppearance &da) { appearanceString = std::unique_ptr(da.toAppearanceString()); update("DA", Object(appearanceString->copy())); invalidateAppearance(); } void AnnotFreeText::setQuadding(AnnotFreeTextQuadding new_quadding) { quadding = new_quadding; update("Q", Object((int)quadding)); invalidateAppearance(); } void AnnotFreeText::setStyleString(GooString *new_string) { if (new_string) { styleString = std::make_unique(new_string); // append the unicode marker if needed if (!styleString->hasUnicodeMarker()) { styleString->prependUnicodeMarker(); } } else { styleString = std::make_unique(); } update("DS", Object(styleString->copy())); } void AnnotFreeText::setCalloutLine(AnnotCalloutLine *line) { Object obj1; if (line == nullptr) { obj1.setToNull(); calloutLine = nullptr; } else { double x1 = line->getX1(), y1 = line->getY1(); double x2 = line->getX2(), y2 = line->getY2(); obj1 = Object(new Array(doc->getXRef())); obj1.arrayAdd(Object(x1)); obj1.arrayAdd(Object(y1)); obj1.arrayAdd(Object(x2)); obj1.arrayAdd(Object(y2)); AnnotCalloutMultiLine *mline = dynamic_cast(line); if (mline) { double x3 = mline->getX3(), y3 = mline->getY3(); obj1.arrayAdd(Object(x3)); obj1.arrayAdd(Object(y3)); calloutLine = std::make_unique(x1, y1, x2, y2, x3, y3); } else { calloutLine = std::make_unique(x1, y1, x2, y2); } } update("CL", std::move(obj1)); invalidateAppearance(); } void AnnotFreeText::setIntent(AnnotFreeTextIntent new_intent) { const char *intentName; intent = new_intent; if (new_intent == intentFreeText) intentName = "FreeText"; else if (new_intent == intentFreeTextCallout) intentName = "FreeTextCallout"; else // intentFreeTextTypeWriter intentName = "FreeTextTypeWriter"; update("IT", Object(objName, intentName)); } std::unique_ptr AnnotFreeText::getDefaultAppearance() const { return std::make_unique(appearanceString.get()); } static GfxFont *createAnnotDrawFont(XRef *xref, Dict *fontResDict, const char *resourceName = "AnnotDrawFont", const char *fontname = "Helvetica") { const Ref dummyRef = { -1, -1 }; Dict *fontDict = new Dict(xref); fontDict->add("BaseFont", Object(objName, fontname)); fontDict->add("Subtype", Object(objName, "Type0")); fontDict->add("Encoding", Object(objName, "WinAnsiEncoding")); Dict *fontsDict = new Dict(xref); fontsDict->add(resourceName, Object(fontDict)); fontResDict->add("Font", Object(fontsDict)); return GfxFont::makeFont(xref, resourceName, dummyRef, fontDict); } void AnnotFreeText::generateFreeTextAppearance() { double borderWidth, ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); borderWidth = border->getWidth(); if (borderWidth > 0) appearBuilder.setLineStyleForBorder(border.get()); // Box size const double width = rect->x2 - rect->x1; const double height = rect->y2 - rect->y1; // Parse some properties from the appearance string DefaultAppearance da { appearanceString.get() }; // Default values if (!da.getFontName().isName()) da.setFontName(Object(objName, "AnnotDrawFont")); if (da.getFontPtSize() <= 0) da.setFontPtSize(undefinedFontPtSize); if (!da.getFontColor()) da.setFontColor(std::make_unique(0, 0, 0)); if (!contents) contents = std::make_unique(); // Draw box bool doFill = (color && color->getSpace() != AnnotColor::colorTransparent); bool doStroke = (borderWidth != 0); if (doFill || doStroke) { if (doStroke) { appearBuilder.setDrawColor(da.getFontColor(), false); // Border color: same as font color } appearBuilder.appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re\n", borderWidth / 2, width - borderWidth, height - borderWidth); if (doFill) { appearBuilder.setDrawColor(color.get(), true); appearBuilder.append(doStroke ? "B\n" : "f\n"); } else { appearBuilder.append("S\n"); } } // Setup text clipping const double textmargin = borderWidth * 2; const double textwidth = width - 2 * textmargin; appearBuilder.appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", textmargin, textwidth, height - 2 * textmargin); GfxFont *font = nullptr; // look for font name in the default resources Form *form = doc->getCatalog()->getForm(); // form is owned by catalog, no need to clean it up Object resourceObj; if (form && form->getDefaultResourcesObj() && form->getDefaultResourcesObj()->isDict()) { resourceObj = form->getDefaultResourcesObj()->copy(); // No real copy, but increment refcount of /DR Dict Dict *resDict = resourceObj.getDict(); Object fontResources = resDict->lookup("Font"); // The 'Font' subdictionary if (!fontResources.isDict()) { error(errSyntaxWarning, -1, "Font subdictionary is not a dictionary"); } else { // Get the font dictionary for the actual requested font Ref fontReference; Object fontDictionary = fontResources.getDict()->lookup(da.getFontName().getName(), &fontReference); if (fontDictionary.isDict()) { font = GfxFont::makeFont(doc->getXRef(), da.getFontName().getName(), fontReference, fontDictionary.getDict()); } else { error(errSyntaxWarning, -1, "Font dictionary is not a dictionary"); } } } // if fontname is not in the default resources, create a Helvetica fake font if (!font) { Dict *fontResDict = new Dict(doc->getXRef()); resourceObj = Object(fontResDict); font = createAnnotDrawFont(doc->getXRef(), fontResDict, da.getFontName().getName()); } // Set font state appearBuilder.setDrawColor(da.getFontColor(), true); appearBuilder.appendf("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - da.getFontPtSize() * font->getDescent()); appearBuilder.setTextFont(da.getFontName(), da.getFontPtSize()); int i = 0; double xposPrev = 0; while (i < contents->getLength()) { GooString out; double linewidth, xpos; layoutText(contents.get(), &out, &i, font, &linewidth, textwidth / da.getFontPtSize(), nullptr, false); linewidth *= da.getFontPtSize(); switch (quadding) { case quaddingCentered: xpos = (textwidth - linewidth) / 2; break; case quaddingRightJustified: xpos = textwidth - linewidth; break; default: // quaddingLeftJustified: xpos = 0; break; } appearBuilder.appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -da.getFontPtSize()); appearBuilder.writeString(out); appearBuilder.append("Tj\n"); xposPrev = xpos; } font->decRefCnt(); appearBuilder.append("ET Q\n"); double bbox[4]; bbox[0] = bbox[1] = 0; bbox[2] = rect->x2 - rect->x1; bbox[3] = rect->y2 - rect->y1; if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, std::move(resourceObj)); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, std::move(resourceObj)); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } void AnnotFreeText::draw(Gfx *gfx, bool printing) { if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { generateFreeTextAppearance(); } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } // Before retrieving the res dict, regenerate the appearance stream if needed, // because AnnotFreeText::draw needs to store font info in the res dict Object AnnotFreeText::getAppearanceResDict() { if (appearance.isNull()) { generateFreeTextAppearance(); } return Annot::getAppearanceResDict(); } //------------------------------------------------------------------------ // AnnotLine //------------------------------------------------------------------------ AnnotLine::AnnotLine(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeLine; annotObj.dictSet("Subtype", Object(objName, "Line")); initialize(docA, annotObj.getDict()); } AnnotLine::AnnotLine(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeLine; initialize(docA, annotObj.getDict()); } AnnotLine::~AnnotLine() = default; void AnnotLine::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("L"); if (obj1.isArray() && obj1.arrayGetLength() == 4) { const double x1 = obj1.arrayGet(0).getNumWithDefaultValue(0); const double y1 = obj1.arrayGet(1).getNumWithDefaultValue(0); const double x2 = obj1.arrayGet(2).getNumWithDefaultValue(0); const double y2 = obj1.arrayGet(3).getNumWithDefaultValue(0); coord1 = std::make_unique(x1, y1); coord2 = std::make_unique(x2, y2); } else { coord1 = std::make_unique(); coord2 = std::make_unique(); } obj1 = dict->lookup("LE"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { Object obj2; obj2 = obj1.arrayGet(0); if (obj2.isName()) { GooString leName(obj2.getName()); startStyle = parseAnnotLineEndingStyle(&leName); } else { startStyle = annotLineEndingNone; } obj2 = obj1.arrayGet(1); if (obj2.isName()) { GooString leName(obj2.getName()); endStyle = parseAnnotLineEndingStyle(&leName); } else { endStyle = annotLineEndingNone; } } else { startStyle = endStyle = annotLineEndingNone; } obj1 = dict->lookup("IC"); if (obj1.isArray()) { interiorColor = std::make_unique(obj1.getArray()); } leaderLineLength = dict->lookup("LL").getNumWithDefaultValue(0); leaderLineExtension = dict->lookup("LLE").getNumWithDefaultValue(0); if (leaderLineExtension < 0) leaderLineExtension = 0; caption = dict->lookup("Cap").getBoolWithDefaultValue(false); obj1 = dict->lookup("IT"); if (obj1.isName()) { const char *intentName = obj1.getName(); if (!strcmp(intentName, "LineArrow")) { intent = intentLineArrow; } else if (!strcmp(intentName, "LineDimension")) { intent = intentLineDimension; } else { intent = intentLineArrow; } } else { intent = intentLineArrow; } leaderLineOffset = dict->lookup("LLO").getNumWithDefaultValue(0); if (leaderLineOffset < 0) leaderLineOffset = 0; obj1 = dict->lookup("CP"); if (obj1.isName()) { const char *captionName = obj1.getName(); if (!strcmp(captionName, "Inline")) { captionPos = captionPosInline; } else if (!strcmp(captionName, "Top")) { captionPos = captionPosTop; } else { captionPos = captionPosInline; } } else { captionPos = captionPosInline; } obj1 = dict->lookup("Measure"); if (obj1.isDict()) { measure = nullptr; } else { measure = nullptr; } obj1 = dict->lookup("CO"); if (obj1.isArray() && (obj1.arrayGetLength() == 2)) { captionTextHorizontal = obj1.arrayGet(0).getNumWithDefaultValue(0); captionTextVertical = obj1.arrayGet(1).getNumWithDefaultValue(0); } else { captionTextHorizontal = captionTextVertical = 0; } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } } void AnnotLine::setContents(GooString *new_content) { Annot::setContents(new_content); if (caption) invalidateAppearance(); } void AnnotLine::setVertices(double x1, double y1, double x2, double y2) { coord1 = std::make_unique(x1, y1); coord2 = std::make_unique(x2, y2); Array *lArray = new Array(doc->getXRef()); lArray->add(Object(x1)); lArray->add(Object(y1)); lArray->add(Object(x2)); lArray->add(Object(y2)); update("L", Object(lArray)); invalidateAppearance(); } void AnnotLine::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) { startStyle = start; endStyle = end; Array *leArray = new Array(doc->getXRef()); leArray->add(Object(objName, convertAnnotLineEndingStyle(startStyle))); leArray->add(Object(objName, convertAnnotLineEndingStyle(endStyle))); update("LE", Object(leArray)); invalidateAppearance(); } void AnnotLine::setInteriorColor(std::unique_ptr &&new_color) { if (new_color) { Object obj1 = new_color->writeToObject(doc->getXRef()); update("IC", std::move(obj1)); interiorColor = std::move(new_color); } else { interiorColor = nullptr; } invalidateAppearance(); } void AnnotLine::setLeaderLineLength(double len) { leaderLineLength = len; update("LL", Object(len)); invalidateAppearance(); } void AnnotLine::setLeaderLineExtension(double len) { leaderLineExtension = len; update("LLE", Object(len)); // LL is required if LLE is present update("LL", Object(leaderLineLength)); invalidateAppearance(); } void AnnotLine::setCaption(bool new_cap) { caption = new_cap; update("Cap", Object(new_cap)); invalidateAppearance(); } void AnnotLine::setIntent(AnnotLineIntent new_intent) { const char *intentName; intent = new_intent; if (new_intent == intentLineArrow) intentName = "LineArrow"; else // intentLineDimension intentName = "LineDimension"; update("IT", Object(objName, intentName)); } void AnnotLine::generateLineAppearance() { double borderWidth, ca = opacity; bool fill = false; appearBBox = std::make_unique(rect.get()); AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) { appearBuilder.setDrawColor(color.get(), false); } if (interiorColor) { appearBuilder.setDrawColor(interiorColor.get(), true); fill = true; } appearBuilder.setLineStyleForBorder(border.get()); borderWidth = border->getWidth(); appearBBox->setBorderWidth(std::max(1., borderWidth)); const double x1 = coord1->getX(); const double y1 = coord1->getY(); const double x2 = coord2->getX(); const double y2 = coord2->getY(); // Main segment length const double main_len = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); // Main segment becomes positive x direction, coord1 becomes (0,0) Matrix matr; const double angle = atan2(y2 - y1, x2 - x1); matr.m[0] = matr.m[3] = cos(angle); matr.m[1] = sin(angle); matr.m[2] = -matr.m[1]; matr.m[4] = x1 - rect->x1; matr.m[5] = y1 - rect->y1; double tx, ty, captionwidth = 0, captionheight = 0; AnnotLineCaptionPos actualCaptionPos = captionPos; const double fontsize = 9; const double captionhmargin = 2; // Left and right margin (inline caption only) const double captionmaxwidth = main_len - 2 * captionhmargin; const double lineendingSize = std::min(6. * borderWidth, main_len / 2); Dict *fontResDict; GfxFont *font; // Calculate caption width and height if (caption) { fontResDict = new Dict(doc->getXRef()); font = createAnnotDrawFont(doc->getXRef(), fontResDict); int lines = 0; int i = 0; while (i < contents->getLength()) { GooString out; double linewidth; layoutText(contents.get(), &out, &i, font, &linewidth, 0, nullptr, false); linewidth *= fontsize; if (linewidth > captionwidth) { captionwidth = linewidth; } ++lines; } captionheight = lines * fontsize; // If text is longer than available space, turn into captionPosTop if (captionwidth > captionmaxwidth) { actualCaptionPos = captionPosTop; } } else { fontResDict = nullptr; font = nullptr; } // Draw main segment matr.transform(AnnotAppearanceBuilder::lineEndingXShorten(startStyle, lineendingSize), leaderLineLength, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", tx, ty); appearBBox->extendTo(tx, ty); if (captionwidth != 0 && actualCaptionPos == captionPosInline) { // Break in the middle matr.transform((main_len - captionwidth) / 2 - captionhmargin, leaderLineLength, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} l S\n", tx, ty); matr.transform((main_len + captionwidth) / 2 + captionhmargin, leaderLineLength, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", tx, ty); } matr.transform(main_len - AnnotAppearanceBuilder::lineEndingXShorten(endStyle, lineendingSize), leaderLineLength, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} l S\n", tx, ty); appearBBox->extendTo(tx, ty); if (startStyle != annotLineEndingNone) { const double extendX { -AnnotAppearanceBuilder::lineEndingXExtendBBox(startStyle, lineendingSize) }; appearBuilder.drawLineEnding(startStyle, 0, leaderLineLength, -lineendingSize, fill, matr); matr.transform(extendX, leaderLineLength + lineendingSize / 2., &tx, &ty); appearBBox->extendTo(tx, ty); matr.transform(extendX, leaderLineLength - lineendingSize / 2., &tx, &ty); appearBBox->extendTo(tx, ty); } if (endStyle != annotLineEndingNone) { const double extendX { AnnotAppearanceBuilder::lineEndingXExtendBBox(endStyle, lineendingSize) }; appearBuilder.drawLineEnding(endStyle, main_len, leaderLineLength, lineendingSize, fill, matr); matr.transform(main_len + extendX, leaderLineLength + lineendingSize / 2., &tx, &ty); appearBBox->extendTo(tx, ty); matr.transform(main_len + extendX, leaderLineLength - lineendingSize / 2., &tx, &ty); appearBBox->extendTo(tx, ty); } // Draw caption text if (caption) { double tlx = (main_len - captionwidth) / 2, tly; // Top-left coords if (actualCaptionPos == captionPosInline) { tly = leaderLineLength + captionheight / 2; } else { tly = leaderLineLength + captionheight + 2 * borderWidth; } tlx += captionTextHorizontal; tly += captionTextVertical; // Adjust bounding box matr.transform(tlx, tly - captionheight, &tx, &ty); appearBBox->extendTo(tx, ty); matr.transform(tlx + captionwidth, tly - captionheight, &tx, &ty); appearBBox->extendTo(tx, ty); matr.transform(tlx + captionwidth, tly, &tx, &ty); appearBBox->extendTo(tx, ty); matr.transform(tlx, tly, &tx, &ty); appearBBox->extendTo(tx, ty); // Setup text state (reusing transformed top-left coord) appearBuilder.appendf("0 g BT /AnnotDrawFont {0:.2f} Tf\n", fontsize); // Font color: black appearBuilder.appendf("{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); appearBuilder.appendf("0 {0:.2f} Td\n", -fontsize * font->getDescent()); // Draw text int i = 0; double xposPrev = 0; while (i < contents->getLength()) { GooString out; double linewidth, xpos; layoutText(contents.get(), &out, &i, font, &linewidth, 0, nullptr, false); linewidth *= fontsize; xpos = (captionwidth - linewidth) / 2; appearBuilder.appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize); appearBuilder.writeString(out); appearBuilder.append("Tj\n"); xposPrev = xpos; } appearBuilder.append("ET\n"); font->decRefCnt(); } // Draw leader lines double ll_len = fabs(leaderLineLength) + leaderLineExtension; double sign = leaderLineLength >= 0 ? 1 : -1; if (ll_len != 0) { matr.transform(0, 0, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", tx, ty); appearBBox->extendTo(tx, ty); matr.transform(0, sign * ll_len, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} l S\n", tx, ty); appearBBox->extendTo(tx, ty); matr.transform(main_len, 0, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", tx, ty); appearBBox->extendTo(tx, ty); matr.transform(main_len, sign * ll_len, &tx, &ty); appearBuilder.appendf("{0:.2f} {1:.2f} l S\n", tx, ty); appearBBox->extendTo(tx, ty); } appearBuilder.append("Q\n"); double bbox[4]; appearBBox->getBBoxRect(bbox); if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, fontResDict); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, fontResDict); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } void AnnotLine::draw(Gfx *gfx, bool printing) { if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { generateLineAppearance(); } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (appearBBox) { gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation()); } else { gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } } // Before retrieving the res dict, regenerate the appearance stream if needed, // because AnnotLine::draw may need to store font info in the res dict Object AnnotLine::getAppearanceResDict() { if (appearance.isNull()) { generateLineAppearance(); } return Annot::getAppearanceResDict(); } //------------------------------------------------------------------------ // AnnotTextMarkup //------------------------------------------------------------------------ AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA) { switch (subType) { case typeHighlight: annotObj.dictSet("Subtype", Object(objName, "Highlight")); break; case typeUnderline: annotObj.dictSet("Subtype", Object(objName, "Underline")); break; case typeSquiggly: annotObj.dictSet("Subtype", Object(objName, "Squiggly")); break; case typeStrikeOut: annotObj.dictSet("Subtype", Object(objName, "StrikeOut")); break; default: assert(0 && "Invalid subtype for AnnotTextMarkup\n"); } // Store dummy quadrilateral with null coordinates Array *quadPoints = new Array(doc->getXRef()); for (int i = 0; i < 4 * 2; ++i) { quadPoints->add(Object(0.)); } annotObj.dictSet("QuadPoints", Object(quadPoints)); initialize(docA, annotObj.getDict()); } AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { // the real type will be read in initialize() type = typeHighlight; initialize(docA, annotObj.getDict()); } AnnotTextMarkup::~AnnotTextMarkup() = default; void AnnotTextMarkup::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("Subtype"); if (obj1.isName()) { GooString typeName(obj1.getName()); if (!typeName.cmp("Highlight")) { type = typeHighlight; } else if (!typeName.cmp("Underline")) { type = typeUnderline; } else if (!typeName.cmp("Squiggly")) { type = typeSquiggly; } else if (!typeName.cmp("StrikeOut")) { type = typeStrikeOut; } } obj1 = dict->lookup("QuadPoints"); if (obj1.isArray()) { quadrilaterals = std::make_unique(obj1.getArray(), rect.get()); } else { error(errSyntaxError, -1, "Bad Annot Text Markup QuadPoints"); ok = false; } } void AnnotTextMarkup::setType(AnnotSubtype new_type) { const char *typeName = nullptr; /* squelch bogus compiler warning */ switch (new_type) { case typeHighlight: typeName = "Highlight"; break; case typeUnderline: typeName = "Underline"; break; case typeSquiggly: typeName = "Squiggly"; break; case typeStrikeOut: typeName = "StrikeOut"; break; default: assert(!"Invalid subtype"); } type = new_type; update("Subtype", Object(objName, typeName)); invalidateAppearance(); } void AnnotTextMarkup::setQuadrilaterals(AnnotQuadrilaterals *quadPoints) { Array *a = new Array(doc->getXRef()); for (int i = 0; i < quadPoints->getQuadrilateralsLength(); ++i) { a->add(Object(quadPoints->getX1(i))); a->add(Object(quadPoints->getY1(i))); a->add(Object(quadPoints->getX2(i))); a->add(Object(quadPoints->getY2(i))); a->add(Object(quadPoints->getX3(i))); a->add(Object(quadPoints->getY3(i))); a->add(Object(quadPoints->getX4(i))); a->add(Object(quadPoints->getY4(i))); } quadrilaterals = std::make_unique(a, rect.get()); annotObj.dictSet("QuadPoints", Object(a)); invalidateAppearance(); } bool AnnotTextMarkup::shouldCreateApperance(Gfx *gfx) const { if (appearance.isNull()) return true; // Adobe Reader seems to have a complex condition for when to use the // appearance stream of typeHighlight, which is "use it if it has a Resources dictionary with ExtGState" // this is reverse engineering of me editing a file by hand and checking what it does so the real // condition may be more or less complex if (type == typeHighlight) { XRef *xref = gfx->getXRef(); const Object fetchedApperance = appearance.fetch(xref); if (fetchedApperance.isStream()) { const Object resources = fetchedApperance.streamGetDict()->lookup("Resources"); if (resources.isDict()) { if (resources.dictLookup("ExtGState").isDict()) { return false; } } } return true; } return false; } void AnnotTextMarkup::draw(Gfx *gfx, bool printing) { double ca = 1; int i; if (!isVisible(printing)) return; annotLocker(); if (shouldCreateApperance(gfx)) { bool blendMultiply = true; ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); /* Adjust BBox */ appearBBox = std::make_unique(rect.get()); for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) { appearBBox->extendTo(quadrilaterals->getX1(i) - rect->x1, quadrilaterals->getY1(i) - rect->y1); appearBBox->extendTo(quadrilaterals->getX2(i) - rect->x1, quadrilaterals->getY2(i) - rect->y1); appearBBox->extendTo(quadrilaterals->getX3(i) - rect->x1, quadrilaterals->getY3(i) - rect->y1); appearBBox->extendTo(quadrilaterals->getX4(i) - rect->x1, quadrilaterals->getY4(i) - rect->y1); } switch (type) { case typeUnderline: if (color) { appearBuilder.setDrawColor(color.get(), false); } appearBuilder.append("[] 0 d 1 w\n"); for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) { double x3, y3, x4, y4; x3 = quadrilaterals->getX3(i); y3 = quadrilaterals->getY3(i); x4 = quadrilaterals->getX4(i); y4 = quadrilaterals->getY4(i); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", x3, y3); appearBuilder.appendf("{0:.2f} {1:.2f} l\n", x4, y4); appearBuilder.append("S\n"); } break; case typeStrikeOut: if (color) { appearBuilder.setDrawColor(color.get(), false); } blendMultiply = false; appearBuilder.append("[] 0 d 1 w\n"); for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) { double x1, y1, x2, y2; double x3, y3, x4, y4; x1 = quadrilaterals->getX1(i); y1 = quadrilaterals->getY1(i); x2 = quadrilaterals->getX2(i); y2 = quadrilaterals->getY2(i); x3 = quadrilaterals->getX3(i); y3 = quadrilaterals->getY3(i); x4 = quadrilaterals->getX4(i); y4 = quadrilaterals->getY4(i); appearBuilder.appendf("{0:.2f} {1:.2f} m\n", (x1 + x3) / 2., (y1 + y3) / 2.); appearBuilder.appendf("{0:.2f} {1:.2f} l\n", (x2 + x4) / 2., (y2 + y4) / 2.); appearBuilder.append("S\n"); } break; case typeSquiggly: if (color) { appearBuilder.setDrawColor(color.get(), false); } appearBuilder.append("[] 0 d 1 w\n"); for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) { double x1, y1, x2, y3; double h6; x1 = quadrilaterals->getX1(i); y1 = quadrilaterals->getY1(i); x2 = quadrilaterals->getX2(i); y3 = quadrilaterals->getY3(i); h6 = (y1 - y3) / 6.0; appearBuilder.appendf("{0:.2f} {1:.2f} m\n", x1, y3 + h6); bool down = false; do { down = !down; // Zigzag line x1 += 2; appearBuilder.appendf("{0:.2f} {1:.2f} l\n", x1, y3 + (down ? 0 : h6)); } while (x1 < x2); appearBuilder.append("S\n"); } break; default: case typeHighlight: if (color) appearBuilder.setDrawColor(color.get(), true); double biggestBorder = 0; for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) { double x1, y1, x2, y2, x3, y3, x4, y4; double h4; x1 = quadrilaterals->getX1(i); y1 = quadrilaterals->getY1(i); x2 = quadrilaterals->getX2(i); y2 = quadrilaterals->getY2(i); x3 = quadrilaterals->getX3(i); y3 = quadrilaterals->getY3(i); x4 = quadrilaterals->getX4(i); y4 = quadrilaterals->getY4(i); h4 = fabs(y1 - y3) / 4.0; if (h4 > biggestBorder) { biggestBorder = h4; } appearBuilder.appendf("{0:.2f} {1:.2f} m\n", x3, y3); appearBuilder.appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", x3 - h4, y3 + h4, x1 - h4, y1 - h4, x1, y1); appearBuilder.appendf("{0:.2f} {1:.2f} l\n", x2, y2); appearBuilder.appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", x2 + h4, y2 - h4, x4 + h4, y4 + h4, x4, y4); appearBuilder.append("f\n"); } appearBBox->setBorderWidth(biggestBorder); break; } appearBuilder.append("Q\n"); double bbox[4]; bbox[0] = appearBBox->getPageXMin(); bbox[1] = appearBBox->getPageYMin(); bbox[2] = appearBBox->getPageXMax(); bbox[3] = appearBBox->getPageYMax(); Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", 1, blendMultiply ? "Multiply" : nullptr); if (ca == 1) { appearance = createForm(&appearBuf, bbox, false, resDict); } else { aStream = createForm(&appearBuf, bbox, true, resDict); Dict *resDict2 = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict2); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (appearBBox) { gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation()); } else { gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } } //------------------------------------------------------------------------ // AnnotWidget //------------------------------------------------------------------------ AnnotWidget::AnnotWidget(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typeWidget; field = nullptr; initialize(docA, annotObj.getDict()); } AnnotWidget::AnnotWidget(PDFDoc *docA, Object *dictObject, Object *obj, FormField *fieldA) : Annot(docA, dictObject->copy(), obj) { type = typeWidget; field = fieldA; initialize(docA, dictObject->getDict()); } AnnotWidget::~AnnotWidget() = default; void AnnotWidget::initialize(PDFDoc *docA, Dict *dict) { Object obj1; form = doc->getCatalog()->getForm(); obj1 = dict->lookup("H"); if (obj1.isName()) { const char *modeName = obj1.getName(); if (!strcmp(modeName, "N")) { mode = highlightModeNone; } else if (!strcmp(modeName, "O")) { mode = highlightModeOutline; } else if (!strcmp(modeName, "P") || !strcmp(modeName, "T")) { mode = highlightModePush; } else { mode = highlightModeInvert; } } else { mode = highlightModeInvert; } obj1 = dict->lookup("MK"); if (obj1.isDict()) { appearCharacs = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("A"); if (obj1.isDict()) { action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); } additionalActions = dict->lookupNF("AA").copy(); obj1 = dict->lookup("Parent"); if (obj1.isDict()) { parent = nullptr; } else { parent = nullptr; } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } updatedAppearanceStream = Ref::INVALID(); } std::unique_ptr AnnotWidget::getAdditionalAction(AdditionalActionsType additionalActionType) { return ::getAdditionalAction(additionalActionType, &additionalActions, doc); } std::unique_ptr AnnotWidget::getFormAdditionalAction(FormAdditionalActionsType formAdditionalActionType) { Object additionalActionsObject = additionalActions.fetch(doc->getXRef()); if (additionalActionsObject.isDict()) { const char *key = getFormAdditionalActionKey(formAdditionalActionType); Object actionObject = additionalActionsObject.dictLookup(key); if (actionObject.isDict()) return LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI()); } return nullptr; } bool AnnotWidget::setFormAdditionalAction(FormAdditionalActionsType formAdditionalActionType, const GooString &js) { Object additionalActionsObject = additionalActions.fetch(doc->getXRef()); if (!additionalActionsObject.isDict()) { additionalActionsObject = Object(new Dict(doc->getXRef())); annotObj.dictSet("AA", additionalActionsObject.copy()); } additionalActionsObject.dictSet(getFormAdditionalActionKey(formAdditionalActionType), LinkJavaScript::createObject(doc->getXRef(), js)); if (additionalActions.isRef()) { doc->getXRef()->setModifiedObject(&additionalActionsObject, additionalActions.getRef()); } else if (hasRef) { doc->getXRef()->setModifiedObject(&annotObj, ref); } else { error(errInternal, -1, "AnnotWidget::setFormAdditionalAction, where neither additionalActions is ref nor annotobj itself is ref"); return false; } return true; } void AnnotWidget::setNewAppearance(Object &&newAppearance) { if (!newAppearance.isNull()) { appearStreams = std::make_unique(doc, &newAppearance); update("AP", std::move(newAppearance)); } if (appearStreams) appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str()); } // Grand unified handler for preparing text strings to be drawn into form // fields. Takes as input a text string (in PDFDocEncoding or UTF-16). // Converts some or all of this string to the appropriate encoding for the // specified font, and computes the width of the text. Can optionally stop // converting when a specified width has been reached, to perform line-breaking // for multi-line fields. // // Parameters: // text: input text string to convert // outBuf: buffer for writing re-encoded string // i: index at which to start converting; will be updated to point just after // last character processed // font: the font which will be used for display // width: computed width (unscaled by font size) will be stored here // widthLimit: if non-zero, stop converting to keep width under this value // (should be scaled down by font size) // charCount: count of number of characters will be stored here // noReencode: if set, do not try to translate the character encoding // (useful for Zapf Dingbats or other unusual encodings) // can only be used with simple fonts, not CID-keyed fonts // // TODO: Handle surrogate pairs in UTF-16. // Should be able to generate output for any CID-keyed font. // Doesn't handle vertical fonts--should it? void Annot::layoutText(const GooString *text, GooString *outBuf, int *i, const GfxFont *font, double *width, double widthLimit, int *charCount, bool noReencode) { CharCode c; Unicode uChar; const Unicode *uAux; double w = 0.0; int uLen, n; double dx, dy, ox, oy; if (width != nullptr) *width = 0.0; if (charCount != nullptr) *charCount = 0; if (!text) { return; } bool unicode = text->hasUnicodeMarker(); bool spacePrev; // previous character was a space // State for backtracking when more text has been processed than fits within // widthLimit. We track for both input (text) and output (outBuf) the offset // to the first character to discard. // // We keep track of several points: // 1 - end of previous completed word which fits // 2 - previous character which fit int last_i1, last_i2, last_o1, last_o2; if (unicode && text->getLength() % 2 != 0) { error(errSyntaxError, -1, "AnnotWidget::layoutText, bad unicode string"); return; } // skip Unicode marker on string if needed if (unicode && *i == 0) *i = 2; // Start decoding and copying characters, until either: // we reach the end of the string // we reach the maximum width // we reach a newline character // As we copy characters, keep track of the last full word to fit, so that we // can backtrack if we exceed the maximum width. last_i1 = last_i2 = *i; last_o1 = last_o2 = 0; spacePrev = false; outBuf->clear(); while (*i < text->getLength()) { last_i2 = *i; last_o2 = outBuf->getLength(); if (unicode) { uChar = (unsigned char)(text->getChar(*i)) << 8; uChar += (unsigned char)(text->getChar(*i + 1)); *i += 2; } else { if (noReencode) uChar = text->getChar(*i) & 0xff; else uChar = pdfDocEncoding[text->getChar(*i) & 0xff]; *i += 1; } // Explicit line break? if (uChar == '\r' || uChar == '\n') { // Treat a sequence as a single line break if (uChar == '\r' && *i < text->getLength()) { if (unicode && text->getChar(*i) == '\0' && text->getChar(*i + 1) == '\n') *i += 2; else if (!unicode && text->getChar(*i) == '\n') *i += 1; } break; } if (noReencode) { outBuf->append(uChar); } else { const CharCodeToUnicode *ccToUnicode = font->getToUnicode(); if (!ccToUnicode) { // This assumes an identity CMap. outBuf->append((uChar >> 8) & 0xff); outBuf->append(uChar & 0xff); } else if (ccToUnicode->mapToCharCode(&uChar, &c, 1)) { if (font->isCIDFont()) { // TODO: This assumes an identity CMap. It should be extended to // handle the general case. outBuf->append((c >> 8) & 0xff); outBuf->append(c & 0xff); } else { // 8-bit font outBuf->append(c); } } else { error(errSyntaxError, -1, "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar); } } // If we see a space, then we have a linebreak opportunity. if (uChar == ' ') { last_i1 = *i; if (!spacePrev) last_o1 = last_o2; spacePrev = true; } else { spacePrev = false; } // Compute width of character just output if (outBuf->getLength() > last_o2) { dx = 0.0; font->getNextChar(outBuf->c_str() + last_o2, outBuf->getLength() - last_o2, &c, &uAux, &uLen, &dx, &dy, &ox, &oy); w += dx; } // Current line over-full now? if (widthLimit > 0.0 && w > widthLimit) { if (last_o1 > 0) { // Back up to the previous word which fit, if there was a previous // word. *i = last_i1; outBuf->del(last_o1, outBuf->getLength() - last_o1); } else if (last_o2 > 0) { // Otherwise, back up to the previous character (in the only word on // this line) *i = last_i2; outBuf->del(last_o2, outBuf->getLength() - last_o2); } else { // Otherwise, we were trying to fit the first character; include it // anyway even if it overflows the space--no updates to make. } break; } } // If splitting input into lines because we reached the width limit, then // consume any remaining trailing spaces that would go on this line from the // input. If in doing so we reach a newline, consume that also. This code // won't run if we stopped at a newline above, since in that case w <= // widthLimit still. if (widthLimit > 0.0 && w > widthLimit) { if (unicode) { while (*i < text->getLength() && text->getChar(*i) == '\0' && text->getChar(*i + 1) == ' ') *i += 2; if (*i < text->getLength() && text->getChar(*i) == '\0' && text->getChar(*i + 1) == '\r') *i += 2; if (*i < text->getLength() && text->getChar(*i) == '\0' && text->getChar(*i + 1) == '\n') *i += 2; } else { while (*i < text->getLength() && text->getChar(*i) == ' ') *i += 1; if (*i < text->getLength() && text->getChar(*i) == '\r') *i += 1; if (*i < text->getLength() && text->getChar(*i) == '\n') *i += 1; } } // Compute the actual width and character count of the final string, based on // breakpoint, if this information is requested by the caller. if (width != nullptr || charCount != nullptr) { const char *s = outBuf->c_str(); int len = outBuf->getLength(); while (len > 0) { dx = 0.0; n = font->getNextChar(s, len, &c, &uAux, &uLen, &dx, &dy, &ox, &oy); if (n == 0) { break; } if (width != nullptr) *width += dx; if (charCount != nullptr) *charCount += 1; s += n; len -= n; } } } // Copy the given string to appearBuf, adding parentheses around it and // escaping characters as appropriate. void AnnotAppearanceBuilder::writeString(const GooString &str) { char c; int i; appearBuf->append('('); for (i = 0; i < str.getLength(); ++i) { c = str.getChar(i); if (c == '(' || c == ')' || c == '\\') { appearBuf->append('\\'); appearBuf->append(c); } else if (c < 0x20) { appearBuf->appendf("\\{0:03o}", (unsigned char)c); } else { appearBuf->append(c); } } appearBuf->append(')'); } // Draw the variable text or caption for a field. bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da, const GfxResources *resources, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, bool multiline, int comb, int quadding, bool txField, bool forceZapfDingbats, XRef *xref, bool *addedDingbatsResource, bool password) { std::vector *daToks; GooString *tok; GooString convertedText; const GfxFont *font; double dx, dy; double fontSize, borderWidth, x, xPrev, y, w, wMax; int tfPos, tmPos, j; int rot; bool freeText = false; // true if text should be freed before return GfxFont *fontToFree = nullptr; //~ if there is no MK entry, this should use the existing content stream, //~ and only replace the marked content portion of it //~ (this is only relevant for Tx fields) // parse the default appearance string tfPos = tmPos = -1; if (da) { daToks = new std::vector(); int i = 0; while (i < da->getLength()) { while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { ++i; } if (i < da->getLength()) { for (j = i + 1; j < da->getLength() && !Lexer::isSpace(da->getChar(j)); ++j) ; daToks->push_back(new GooString(da, i, j - i)); i = j; } } for (i = 2; i < (int)daToks->size(); ++i) { if (i >= 2 && !((*daToks)[i])->cmp("Tf")) { tfPos = i - 2; } else if (i >= 6 && !((*daToks)[i])->cmp("Tm")) { tmPos = i - 6; } } } else { daToks = nullptr; } // force ZapfDingbats if (forceZapfDingbats) { assert(xref != nullptr); assert(addedDingbatsResource != nullptr); *addedDingbatsResource = false; if (tfPos >= 0) { tok = (*daToks)[tfPos]; if (tok->cmp("/ZaDb")) { tok->clear(); tok->append("/ZaDb"); } } } // get the font and font size font = nullptr; fontSize = 0; if (tfPos >= 0) { tok = (*daToks)[tfPos]; if (tok->getLength() >= 1 && tok->getChar(0) == '/') { if (!resources || !(font = resources->lookupFont(tok->c_str() + 1))) { if (forceZapfDingbats) { // We are forcing ZaDb but the font does not exist // so create a fake one Ref r = Ref::INVALID(); // dummy Ref, it's not used at all in this codepath Dict *d = new Dict(xref); fontToFree = new Gfx8BitFont(xref, "ZaDb", r, new GooString("ZapfDingbats"), fontType1, r, d); delete d; font = fontToFree; *addedDingbatsResource = true; } else { error(errSyntaxError, -1, "Unknown font in field's DA string"); } } } else { error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string"); } tok = (*daToks)[tfPos + 1]; fontSize = gatof(tok->c_str()); } else { error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); } if (!font) { if (daToks) { for (auto entry : *daToks) { delete entry; } delete daToks; } return false; } // get the border width borderWidth = border ? border->getWidth() : 0; // for a password field, replace all characters with asterisks if (password) { int len; if (text->hasUnicodeMarker()) len = (text->getLength() - 2) / 2; else len = text->getLength(); GooString *newText = new GooString; for (int i = 0; i < len; ++i) newText->append('*'); text = newText; freeText = true; } // setup if (txField) { appearBuf->append("/Tx BMC\n"); } appearBuf->append("q\n"); rot = appearCharacs ? appearCharacs->getRotation() : 0; switch (rot) { case 90: appearBuf->appendf("0 1 -1 0 {0:.2f} 0 cm\n", rect->x2 - rect->x1); dx = rect->y2 - rect->y1; dy = rect->x2 - rect->x1; break; case 180: appearBuf->appendf("-1 0 0 -1 {0:.2f} {1:.2f} cm\n", rect->x2 - rect->x1, rect->y2 - rect->y1); dx = rect->x2 - rect->y2; dy = rect->y2 - rect->y1; break; case 270: appearBuf->appendf("0 -1 1 0 0 {0:.2f} cm\n", rect->y2 - rect->y1); dx = rect->y2 - rect->y1; dy = rect->x2 - rect->x1; break; default: // assume rot == 0 dx = rect->x2 - rect->x1; dy = rect->y2 - rect->y1; break; } appearBuf->append("BT\n"); // multi-line text if (multiline) { // note: the comb flag is ignored in multiline mode wMax = dx - 2 * borderWidth - 4; // compute font autosize if (fontSize == 0) { for (fontSize = 20; fontSize > 1; --fontSize) { y = dy - 3; int i = 0; while (i < text->getLength()) { Annot::layoutText(text, &convertedText, &i, font, &w, wMax / fontSize, nullptr, forceZapfDingbats); y -= fontSize; } // approximate the descender for the last line if (y >= 0.33 * fontSize) { break; } } if (tfPos >= 0) { tok = (*daToks)[tfPos + 1]; tok->clear(); tok->appendf("{0:.2f}", fontSize); } } // starting y coordinate // (note: each line of text starts with a Td operator that moves // down a line) y = dy - 3; // set the font matrix if (tmPos >= 0) { tok = (*daToks)[tmPos + 4]; tok->clear(); tok->append('0'); tok = (*daToks)[tmPos + 5]; tok->clear(); tok->appendf("{0:.2f}", y); } // write the DA string if (daToks) { for (const GooString *daTok : *daToks) { appearBuf->append(daTok)->append(' '); } } // write the font matrix (if not part of the DA string) if (tmPos < 0) { appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y); } // write a series of lines of text int i = 0; xPrev = 0; while (i < text->getLength()) { Annot::layoutText(text, &convertedText, &i, font, &w, wMax / fontSize, nullptr, forceZapfDingbats); w *= fontSize; // compute text start position switch (quadding) { case quaddingLeftJustified: default: x = borderWidth + 2; break; case quaddingCentered: x = (dx - w) / 2; break; case quaddingRightJustified: x = dx - borderWidth - 2 - w; break; } // draw the line appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); writeString(convertedText); appearBuf->append(" Tj\n"); // next line xPrev = x; } // single-line text } else { //~ replace newlines with spaces? - what does Acrobat do? // comb formatting if (comb > 0) { int charCount; // compute comb spacing w = (dx - 2 * borderWidth) / comb; // compute font autosize if (fontSize == 0) { fontSize = dy - 2 * borderWidth; if (w < fontSize) { fontSize = w; } fontSize = floor(fontSize); if (tfPos >= 0) { tok = (*daToks)[tfPos + 1]; tok->clear(); tok->appendf("{0:.2f}", fontSize); } } int i = 0; Annot::layoutText(text, &convertedText, &i, font, nullptr, 0.0, &charCount, forceZapfDingbats); if (charCount > comb) charCount = comb; // compute starting text cell switch (quadding) { case quaddingLeftJustified: default: x = borderWidth; break; case quaddingCentered: x = borderWidth + (comb - charCount) / 2.0 * w; break; case quaddingRightJustified: x = borderWidth + (comb - charCount) * w; break; } y = 0.5 * dy - 0.4 * fontSize; // set the font matrix if (tmPos >= 0) { tok = (*daToks)[tmPos + 4]; tok->clear(); tok->appendf("{0:.2f}", x); tok = (*daToks)[tmPos + 5]; tok->clear(); tok->appendf("{0:.2f}", y); } // write the DA string if (daToks) { for (i = 0; i < (int)daToks->size(); ++i) { appearBuf->append((*daToks)[i])->append(' '); } } // write the font matrix (if not part of the DA string) if (tmPos < 0) { appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); } // write the text string const char *s = convertedText.c_str(); int len = convertedText.getLength(); i = 0; xPrev = w; // so that first character is placed properly while (i < comb && len > 0) { CharCode code; const Unicode *uAux; int uLen, n; double char_dx, char_dy, ox, oy; char_dx = 0.0; n = font->getNextChar(s, len, &code, &uAux, &uLen, &char_dx, &char_dy, &ox, &oy); char_dx *= fontSize; // center each character within its cell, by advancing the text // position the appropriate amount relative to the start of the // previous character x = 0.5 * (w - char_dx); appearBuf->appendf("{0:.2f} 0 Td\n", x - xPrev + w); GooString charBuf(s, n); writeString(charBuf); appearBuf->append(" Tj\n"); i++; s += n; len -= n; xPrev = x; } // regular (non-comb) formatting } else { int ii = 0; Annot::layoutText(text, &convertedText, &ii, font, &w, 0.0, nullptr, forceZapfDingbats); // compute font autosize if (fontSize == 0) { fontSize = dy - 2 * borderWidth; if (w > 0) { const double fontSize2 = (dx - 4 - 2 * borderWidth) / w; if (fontSize2 < fontSize) { fontSize = fontSize2; } } fontSize = floor(fontSize); if (tfPos >= 0) { tok = (*daToks)[tfPos + 1]; tok->clear(); tok->appendf("{0:.2f}", fontSize); } } // compute text start position w *= fontSize; switch (quadding) { case quaddingLeftJustified: default: x = borderWidth + 2; break; case quaddingCentered: x = (dx - w) / 2; break; case quaddingRightJustified: x = dx - borderWidth - 2 - w; break; } y = 0.5 * dy - 0.4 * fontSize; // set the font matrix if (tmPos >= 0) { tok = (*daToks)[tmPos + 4]; tok->clear(); tok->appendf("{0:.2f}", x); tok = (*daToks)[tmPos + 5]; tok->clear(); tok->appendf("{0:.2f}", y); } // write the DA string if (daToks) { for (const GooString *daTok : *daToks) { appearBuf->append(daTok)->append(' '); } } // write the font matrix (if not part of the DA string) if (tmPos < 0) { appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); } // write the text string writeString(convertedText); appearBuf->append(" Tj\n"); } } // cleanup appearBuf->append("ET\n"); appearBuf->append("Q\n"); if (txField) { appearBuf->append("EMC\n"); } if (daToks) { for (auto entry : *daToks) { delete entry; } delete daToks; } if (freeText) { delete text; } if (fontToFree) { fontToFree->decRefCnt(); } return true; } // Draw the variable text or caption for a field. bool AnnotAppearanceBuilder::drawListBox(const FormFieldChoice *fieldChoice, const AnnotBorder *border, const PDFRectangle *rect, const GooString *da, const GfxResources *resources, int quadding) { std::vector *daToks; GooString *tok; GooString convertedText; const GfxFont *font; double fontSize, borderWidth, x, y, w, wMax; int tfPos, tmPos, i, j; //~ if there is no MK entry, this should use the existing content stream, //~ and only replace the marked content portion of it //~ (this is only relevant for Tx fields) // parse the default appearance string tfPos = tmPos = -1; if (da) { daToks = new std::vector(); i = 0; while (i < da->getLength()) { while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { ++i; } if (i < da->getLength()) { for (j = i + 1; j < da->getLength() && !Lexer::isSpace(da->getChar(j)); ++j) ; daToks->push_back(new GooString(da, i, j - i)); i = j; } } for (std::size_t k = 2; k < daToks->size(); ++k) { if (k >= 2 && !((*daToks)[k])->cmp("Tf")) { tfPos = k - 2; } else if (k >= 6 && !((*daToks)[k])->cmp("Tm")) { tmPos = k - 6; } } } else { daToks = nullptr; } // get the font and font size font = nullptr; fontSize = 0; if (tfPos >= 0) { tok = (*daToks)[tfPos]; if (tok->getLength() >= 1 && tok->getChar(0) == '/') { if (!resources || !(font = resources->lookupFont(tok->c_str() + 1))) { error(errSyntaxError, -1, "Unknown font in field's DA string"); } } else { error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string"); } tok = (*daToks)[tfPos + 1]; fontSize = gatof(tok->c_str()); } else { error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); } if (!font) { if (daToks) { for (auto entry : *daToks) { delete entry; } delete daToks; } return false; } // get the border width borderWidth = border ? border->getWidth() : 0; // compute font autosize if (fontSize == 0) { wMax = 0; for (i = 0; i < fieldChoice->getNumChoices(); ++i) { j = 0; if (fieldChoice->getChoice(i) == nullptr) { error(errSyntaxError, -1, "Invalid annotation listbox"); if (daToks) { for (auto entry : *daToks) { delete entry; } delete daToks; } return false; } Annot::layoutText(fieldChoice->getChoice(i), &convertedText, &j, font, &w, 0.0, nullptr, false); if (w > wMax) { wMax = w; } } fontSize = rect->y2 - rect->y1 - 2 * borderWidth; const double fontSize2 = (rect->x2 - rect->x1 - 4 - 2 * borderWidth) / wMax; if (fontSize2 < fontSize) { fontSize = fontSize2; } fontSize = floor(fontSize); if (tfPos >= 0) { tok = (*daToks)[tfPos + 1]; tok->clear(); tok->appendf("{0:.2f}", fontSize); } } // draw the text y = rect->y2 - rect->y1 - 1.1 * fontSize; for (i = fieldChoice->getTopIndex(); i < fieldChoice->getNumChoices(); ++i) { // setup appearBuf->append("q\n"); // draw the background if selected if (fieldChoice->isSelected(i)) { appearBuf->append("0 g f\n"); appearBuf->appendf("{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); } // setup appearBuf->append("BT\n"); // compute text width and start position j = 0; Annot::layoutText(fieldChoice->getChoice(i), &convertedText, &j, font, &w, 0.0, nullptr, false); w *= fontSize; switch (quadding) { case quaddingLeftJustified: default: x = borderWidth + 2; break; case quaddingCentered: x = (rect->x2 - rect->x1 - w) / 2; break; case quaddingRightJustified: x = rect->x2 - rect->x1 - borderWidth - 2 - w; break; } // set the font matrix if (tmPos >= 0) { tok = (*daToks)[tmPos + 4]; tok->clear(); tok->appendf("{0:.2f}", x); tok = (*daToks)[tmPos + 5]; tok->clear(); tok->appendf("{0:.2f}", y); } // write the DA string if (daToks) { for (const GooString *daTok : *daToks) { appearBuf->append(daTok)->append(' '); } } // write the font matrix (if not part of the DA string) if (tmPos < 0) { appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); } // change the text color if selected if (fieldChoice->isSelected(i)) { appearBuf->append("1 g\n"); } // write the text string writeString(convertedText); appearBuf->append(" Tj\n"); // cleanup appearBuf->append("ET\n"); appearBuf->append("Q\n"); // next line y -= 1.1 * fontSize; } if (daToks) { for (auto entry : *daToks) { delete entry; } delete daToks; } return true; } void AnnotAppearanceBuilder::drawFieldBorder(const FormField *field, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) { int dashLength; double *dash; AnnotColor adjustedColor; const double w = border->getWidth(); const AnnotColor *aColor = appearCharacs->getBorderColor(); if (!aColor) aColor = appearCharacs->getBackColor(); if (!aColor) return; const double dx = rect->x2 - rect->x1; const double dy = rect->y2 - rect->y1; // radio buttons with no caption have a round border const bool hasCaption = appearCharacs->getNormalCaption() != nullptr; if (field->getType() == formButton && static_cast(field)->getButtonType() == formButtonRadio && !hasCaption) { double r = 0.5 * (dx < dy ? dx : dy); switch (border->getStyle()) { case AnnotBorder::borderDashed: appearBuf->append("["); dashLength = border->getDashLength(); dash = border->getDash(); for (int i = 0; i < dashLength; ++i) { appearBuf->appendf(" {0:.2f}", dash[i]); } appearBuf->append("] 0 d\n"); // fallthrough case AnnotBorder::borderSolid: case AnnotBorder::borderUnderlined: appearBuf->appendf("{0:.2f} w\n", w); setDrawColor(aColor, false); drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, false); break; case AnnotBorder::borderBeveled: case AnnotBorder::borderInset: appearBuf->appendf("{0:.2f} w\n", 0.5 * w); setDrawColor(aColor, false); drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, false); adjustedColor = AnnotColor(*aColor); adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1); setDrawColor(&adjustedColor, false); drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); adjustedColor = AnnotColor(*aColor); adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1); setDrawColor(&adjustedColor, false); drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w); break; } } else { switch (border->getStyle()) { case AnnotBorder::borderDashed: appearBuf->append("["); dashLength = border->getDashLength(); dash = border->getDash(); for (int i = 0; i < dashLength; ++i) { appearBuf->appendf(" {0:.2f}", dash[i]); } appearBuf->append("] 0 d\n"); // fallthrough case AnnotBorder::borderSolid: appearBuf->appendf("{0:.2f} w\n", w); setDrawColor(aColor, false); appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", 0.5 * w, dx - w, dy - w); break; case AnnotBorder::borderBeveled: case AnnotBorder::borderInset: adjustedColor = AnnotColor(*aColor); adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1); setDrawColor(&adjustedColor, true); appearBuf->append("0 0 m\n"); appearBuf->appendf("0 {0:.2f} l\n", dy); appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w); appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); appearBuf->append("f\n"); adjustedColor = AnnotColor(*aColor); adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1); setDrawColor(&adjustedColor, true); appearBuf->append("0 0 m\n"); appearBuf->appendf("{0:.2f} 0 l\n", dx); appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w); appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); appearBuf->append("f\n"); break; case AnnotBorder::borderUnderlined: appearBuf->appendf("{0:.2f} w\n", w); setDrawColor(aColor, false); appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx); break; } // clip to the inside of the border appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", w, dx - 2 * w, dy - 2 * w); } } bool AnnotAppearanceBuilder::drawFormField(const FormField *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const GooString *appearState, XRef *xref, bool *addedDingbatsResource) { // draw the field contents switch (field->getType()) { case formButton: return drawFormFieldButton(static_cast(field), resources, da, border, appearCharacs, rect, appearState, xref, addedDingbatsResource); break; case formText: return drawFormFieldText(static_cast(field), form, resources, da, border, appearCharacs, rect); case formChoice: return drawFormFieldChoice(static_cast(field), form, resources, da, border, appearCharacs, rect); break; case formSignature: //~unimp break; case formUndef: default: error(errSyntaxError, -1, "Unknown field type"); } return false; } bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const GooString *appearState, XRef *xref, bool *addedDingbatsResource) { const GooString *caption = nullptr; if (appearCharacs) caption = appearCharacs->getNormalCaption(); switch (field->getButtonType()) { case formButtonRadio: { //~ Acrobat doesn't draw a caption if there is no AP dict (?) if (appearState && appearState->cmp("Off") != 0 && field->getState(appearState->c_str())) { if (caption) { return drawText(caption, da, resources, border, appearCharacs, rect, false, 0, fieldQuadCenter, false, true, xref, addedDingbatsResource, false); } else if (appearCharacs) { const AnnotColor *aColor = appearCharacs->getBorderColor(); if (aColor) { const double dx = rect->x2 - rect->x1; const double dy = rect->y2 - rect->y1; setDrawColor(aColor, true); drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), true); } return true; } } } break; case formButtonPush: if (caption) return drawText(caption, da, resources, border, appearCharacs, rect, false, 0, fieldQuadCenter, false, false, xref, addedDingbatsResource, false); break; case formButtonCheck: if (appearState && appearState->cmp("Off") != 0) { if (!caption) { GooString checkMark("3"); return drawText(&checkMark, da, resources, border, appearCharacs, rect, false, 0, fieldQuadCenter, false, true, xref, addedDingbatsResource, false); } else { return drawText(caption, da, resources, border, appearCharacs, rect, false, 0, fieldQuadCenter, false, true, xref, addedDingbatsResource, false); } } break; } return true; } bool AnnotAppearanceBuilder::drawFormFieldText(const FormFieldText *fieldText, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) { VariableTextQuadding quadding; const GooString *contents; contents = fieldText->getAppearanceContent(); if (contents) { quadding = fieldText->hasTextQuadding() ? fieldText->getTextQuadding() : form->getTextQuadding(); int comb = 0; if (fieldText->isComb()) comb = fieldText->getMaxLen(); return drawText(contents, da, resources, border, appearCharacs, rect, fieldText->isMultiline(), comb, quadding, true, false, nullptr, nullptr, fieldText->isPassword()); } return true; } bool AnnotAppearanceBuilder::drawFormFieldChoice(const FormFieldChoice *fieldChoice, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) { const GooString *selected; VariableTextQuadding quadding; quadding = fieldChoice->hasTextQuadding() ? fieldChoice->getTextQuadding() : form->getTextQuadding(); if (fieldChoice->isCombo()) { selected = fieldChoice->getSelectedChoice(); if (selected) { return drawText(selected, da, resources, border, appearCharacs, rect, false, 0, quadding, true, false, nullptr, nullptr, false); //~ Acrobat draws a popup icon on the right side } // list box } else { return drawListBox(fieldChoice, border, rect, da, resources, quadding); } return true; } void AnnotWidget::generateFieldAppearance(bool *addedDingbatsResource) { GfxResources *resources; const GooString *da; AnnotAppearanceBuilder appearBuilder; // draw the background if (appearCharacs) { const AnnotColor *aColor = appearCharacs->getBackColor(); if (aColor) { appearBuilder.setDrawColor(aColor, true); appearBuilder.appendf("0 0 {0:.2f} {1:.2f} re f\n", rect->x2 - rect->x1, rect->y2 - rect->y1); } } // draw the border if (appearCharacs && border && border->getWidth() > 0) appearBuilder.drawFieldBorder(field, border.get(), appearCharacs.get(), rect.get()); da = field->getDefaultAppearance(); if (!da) da = form->getDefaultAppearance(); resources = form->getDefaultResources(); const bool success = appearBuilder.drawFormField(field, form, resources, da, border.get(), appearCharacs.get(), rect.get(), appearState.get(), doc->getXRef(), addedDingbatsResource); if (!success && da != form->getDefaultAppearance()) { da = form->getDefaultAppearance(); appearBuilder.drawFormField(field, form, resources, da, border.get(), appearCharacs.get(), rect.get(), appearState.get(), doc->getXRef(), addedDingbatsResource); } const GooString *appearBuf = appearBuilder.buffer(); // build the appearance stream dictionary Dict *appearDict = new Dict(doc->getXRef()); appearDict->add("Length", Object(appearBuf->getLength())); appearDict->add("Subtype", Object(objName, "Form")); Array *bbox = new Array(doc->getXRef()); bbox->add(Object(0)); bbox->add(Object(0)); bbox->add(Object(rect->x2 - rect->x1)); bbox->add(Object(rect->y2 - rect->y1)); appearDict->add("BBox", Object(bbox)); // set the resource dictionary Object *resDict = form->getDefaultResourcesObj(); if (resDict->isDict()) { appearDict->add("Resources", resDict->copy()); } // build the appearance stream Stream *appearStream = new AutoFreeMemStream(copyString(appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict)); appearance = Object(appearStream); } void AnnotWidget::updateAppearanceStream() { // If this the first time updateAppearanceStream() is called on this widget, // destroy the AP dictionary because we are going to create a new one. if (updatedAppearanceStream == Ref::INVALID()) { invalidateAppearance(); // Delete AP dictionary and all referenced streams } // There's no need to create a new appearance stream if NeedAppearances is // set, because it will be ignored next time anyway. if (form && form->getNeedAppearances()) return; // Create the new appearance bool dummyAddDingbatsResource = false; // This is only update so if we didn't need to add // the dingbats resource we should not need it now generateFieldAppearance(&dummyAddDingbatsResource); // Fetch the appearance stream we've just created Object obj1 = appearance.fetch(doc->getXRef()); // If this the first time updateAppearanceStream() is called on this widget, // create a new AP dictionary containing the new appearance stream. // Otherwise, just update the stream we had created previously. if (updatedAppearanceStream == Ref::INVALID()) { // Write the appearance stream updatedAppearanceStream = doc->getXRef()->addIndirectObject(&obj1); // Write the AP dictionary obj1 = Object(new Dict(doc->getXRef())); obj1.dictAdd("N", Object(updatedAppearanceStream)); // Update our internal pointers to the appearance dictionary appearStreams = std::make_unique(doc, &obj1); update("AP", std::move(obj1)); } else { // Replace the existing appearance stream doc->getXRef()->setModifiedObject(&obj1, updatedAppearanceStream); } } void AnnotWidget::draw(Gfx *gfx, bool printing) { if (!isVisible(printing)) return; annotLocker(); bool addDingbatsResource = false; // Only construct the appearance stream when // - annot doesn't have an AP or // - NeedAppearances is true if (field && form) { if (appearance.isNull() || form->getNeedAppearances()) { generateFieldAppearance(&addDingbatsResource); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (addDingbatsResource) { // We are forcing ZaDb but the font does not exist // so create a fake one Dict *fontDict = new Dict(gfx->getXRef()); fontDict->add("BaseFont", Object(objName, "ZapfDingbats")); fontDict->add("Subtype", Object(objName, "Type1")); Dict *fontsDict = new Dict(gfx->getXRef()); fontsDict->add("ZaDb", Object(fontDict)); Dict *dict = new Dict(gfx->getXRef()); dict->add("Font", Object(fontsDict)); gfx->pushResources(dict); delete dict; } gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); if (addDingbatsResource) { gfx->popResources(); } } //------------------------------------------------------------------------ // AnnotMovie //------------------------------------------------------------------------ AnnotMovie::AnnotMovie(PDFDoc *docA, PDFRectangle *rectA, Movie *movieA) : Annot(docA, rectA) { type = typeMovie; annotObj.dictSet("Subtype", Object(objName, "Movie")); movie.reset(movieA->copy()); // TODO: create movie dict from movieA initialize(docA, annotObj.getDict()); } AnnotMovie::AnnotMovie(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typeMovie; initialize(docA, annotObj.getDict()); } AnnotMovie::~AnnotMovie() = default; void AnnotMovie::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("T"); if (obj1.isString()) { title.reset(obj1.getString()->copy()); } Object movieDict = dict->lookup("Movie"); if (movieDict.isDict()) { Object obj2 = dict->lookup("A"); if (obj2.isDict()) movie = std::make_unique(&movieDict, &obj2); else movie = std::make_unique(&movieDict); if (!movie->isOk()) { movie = nullptr; ok = false; } } else { error(errSyntaxError, -1, "Bad Annot Movie"); ok = false; } } void AnnotMovie::draw(Gfx *gfx, bool printing) { if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull() && movie->getShowPoster()) { int width, height; Object poster = movie->getPoster(); movie->getAspect(&width, &height); if (width != -1 && height != -1 && !poster.isNone()) { auto appearBuf = std::make_unique(); appearBuf->append("q\n"); appearBuf->appendf("{0:d} 0 0 {1:d} 0 0 cm\n", width, height); appearBuf->append("/MImg Do\n"); appearBuf->append("Q\n"); Dict *imgDict = new Dict(gfx->getXRef()); imgDict->set("MImg", std::move(poster)); Dict *resDict = new Dict(gfx->getXRef()); resDict->set("XObject", Object(imgDict)); Dict *formDict = new Dict(gfx->getXRef()); formDict->set("Length", Object(appearBuf->getLength())); formDict->set("Subtype", Object(objName, "Form")); formDict->set("Name", Object(objName, "FRM")); Array *bboxArray = new Array(gfx->getXRef()); bboxArray->add(Object(0)); bboxArray->add(Object(0)); bboxArray->add(Object(width)); bboxArray->add(Object(height)); formDict->set("BBox", Object(bboxArray)); Array *matrix = new Array(gfx->getXRef()); matrix->add(Object(1)); matrix->add(Object(0)); matrix->add(Object(0)); matrix->add(Object(1)); matrix->add(Object(-width / 2)); matrix->add(Object(-height / 2)); formDict->set("Matrix", Object(matrix)); formDict->set("Resources", Object(resDict)); Stream *mStream = new AutoFreeMemStream(copyString(appearBuf->c_str()), 0, appearBuf->getLength(), Object(formDict)); Dict *dict = new Dict(gfx->getXRef()); dict->set("FRM", Object(mStream)); Dict *resDict2 = new Dict(gfx->getXRef()); resDict2->set("XObject", Object(dict)); appearBuf = std::make_unique(); appearBuf->append("q\n"); appearBuf->appendf("0 0 {0:d} {1:d} re W n\n", width, height); appearBuf->append("q\n"); appearBuf->appendf("0 0 {0:d} {1:d} re W n\n", width, height); appearBuf->appendf("1 0 0 1 {0:d} {1:d} cm\n", width / 2, height / 2); appearBuf->append("/FRM Do\n"); appearBuf->append("Q\n"); appearBuf->append("Q\n"); double bbox[4]; bbox[0] = bbox[1] = 0; bbox[2] = width; bbox[3] = height; appearance = createForm(appearBuf.get(), bbox, false, resDict2); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // AnnotScreen //------------------------------------------------------------------------ AnnotScreen::AnnotScreen(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { type = typeScreen; annotObj.dictSet("Subtype", Object(objName, "Screen")); initialize(docA, annotObj.getDict()); } AnnotScreen::AnnotScreen(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typeScreen; initialize(docA, annotObj.getDict()); } AnnotScreen::~AnnotScreen() = default; void AnnotScreen::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("T"); if (obj1.isString()) { title.reset(obj1.getString()->copy()); } obj1 = dict->lookup("A"); if (obj1.isDict()) { action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); if (action && action->getKind() == actionRendition && page == 0) { error(errSyntaxError, -1, "Invalid Rendition action: associated screen annotation without P"); action = nullptr; ok = false; } } additionalActions = dict->lookupNF("AA").copy(); obj1 = dict->lookup("MK"); if (obj1.isDict()) { appearCharacs = std::make_unique(obj1.getDict()); } } std::unique_ptr AnnotScreen::getAdditionalAction(AdditionalActionsType additionalActionType) { if (additionalActionType == actionFocusIn || additionalActionType == actionFocusOut) // not defined for screen annotation return nullptr; return ::getAdditionalAction(additionalActionType, &additionalActions, doc); } //------------------------------------------------------------------------ // AnnotStamp //------------------------------------------------------------------------ AnnotStamp::AnnotStamp(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeStamp; annotObj.dictSet("Subtype", Object(objName, "Stamp")); initialize(docA, annotObj.getDict()); } AnnotStamp::AnnotStamp(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeStamp; initialize(docA, annotObj.getDict()); } AnnotStamp::~AnnotStamp() = default; void AnnotStamp::initialize(PDFDoc *docA, Dict *dict) { Object obj1 = dict->lookup("Name"); if (obj1.isName()) { icon = std::make_unique(obj1.getName()); } else { icon = std::make_unique("Draft"); } } void AnnotStamp::setIcon(GooString *new_icon) { if (new_icon) { icon = std::make_unique(new_icon); } else { icon = std::make_unique(); } update("Name", Object(objName, icon->c_str())); invalidateAppearance(); } //------------------------------------------------------------------------ // AnnotGeometry //------------------------------------------------------------------------ AnnotGeometry::AnnotGeometry(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA) { switch (subType) { case typeSquare: annotObj.dictSet("Subtype", Object(objName, "Square")); break; case typeCircle: annotObj.dictSet("Subtype", Object(objName, "Circle")); break; default: assert(0 && "Invalid subtype for AnnotGeometry\n"); } initialize(docA, annotObj.getDict()); } AnnotGeometry::AnnotGeometry(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { // the real type will be read in initialize() type = typeSquare; initialize(docA, annotObj.getDict()); } AnnotGeometry::~AnnotGeometry() = default; void AnnotGeometry::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("Subtype"); if (obj1.isName()) { GooString typeName(obj1.getName()); if (!typeName.cmp("Square")) { type = typeSquare; } else if (!typeName.cmp("Circle")) { type = typeCircle; } } obj1 = dict->lookup("IC"); if (obj1.isArray()) { interiorColor = std::make_unique(obj1.getArray()); } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } obj1 = dict->lookup("BE"); if (obj1.isDict()) { borderEffect = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("RD"); if (obj1.isArray()) { geometryRect = parseDiffRectangle(obj1.getArray(), rect.get()); } } void AnnotGeometry::setType(AnnotSubtype new_type) { const char *typeName = nullptr; /* squelch bogus compiler warning */ switch (new_type) { case typeSquare: typeName = "Square"; break; case typeCircle: typeName = "Circle"; break; default: assert(!"Invalid subtype"); } type = new_type; update("Subtype", Object(objName, typeName)); invalidateAppearance(); } void AnnotGeometry::setInteriorColor(std::unique_ptr &&new_color) { if (new_color) { Object obj1 = new_color->writeToObject(doc->getXRef()); update("IC", std::move(obj1)); interiorColor = std::move(new_color); } else { interiorColor = nullptr; } invalidateAppearance(); } void AnnotGeometry::draw(Gfx *gfx, bool printing) { double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { const bool fill = interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent; ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) appearBuilder.setDrawColor(color.get(), false); double borderWidth = border->getWidth(); appearBuilder.setLineStyleForBorder(border.get()); if (interiorColor) appearBuilder.setDrawColor(interiorColor.get(), true); if (type == typeSquare) { appearBuilder.appendf("{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); if (fill) { if (borderWidth > 0) { appearBuilder.append("b\n"); } else { appearBuilder.append("f\n"); } } else if (borderWidth > 0) { appearBuilder.append("S\n"); } } else { const double rx { (rect->x2 - rect->x1) / 2. }; const double ry { (rect->y2 - rect->y1) / 2. }; const double bwHalf { borderWidth / 2.0 }; appearBuilder.drawEllipse(rx, ry, rx - bwHalf, ry - bwHalf, fill, borderWidth > 0); } appearBuilder.append("Q\n"); double bbox[4]; bbox[0] = bbox[1] = 0; bbox[2] = rect->x2 - rect->x1; bbox[3] = rect->y2 - rect->y1; if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // AnnotPolygon //------------------------------------------------------------------------ AnnotPolygon::AnnotPolygon(PDFDoc *docA, PDFRectangle *rectA, AnnotSubtype subType) : AnnotMarkup(docA, rectA) { switch (subType) { case typePolygon: annotObj.dictSet("Subtype", Object(objName, "Polygon")); break; case typePolyLine: annotObj.dictSet("Subtype", Object(objName, "PolyLine")); break; default: assert(0 && "Invalid subtype for AnnotGeometry\n"); } // Store dummy path with one null vertex only Array *a = new Array(doc->getXRef()); a->add(Object(0.)); a->add(Object(0.)); annotObj.dictSet("Vertices", Object(a)); initialize(docA, annotObj.getDict()); } AnnotPolygon::AnnotPolygon(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { // the real type will be read in initialize() type = typePolygon; initialize(docA, annotObj.getDict()); } AnnotPolygon::~AnnotPolygon() = default; void AnnotPolygon::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("Subtype"); if (obj1.isName()) { GooString typeName(obj1.getName()); if (!typeName.cmp("Polygon")) { type = typePolygon; } else if (!typeName.cmp("PolyLine")) { type = typePolyLine; } } obj1 = dict->lookup("Vertices"); if (obj1.isArray()) { vertices = std::make_unique(obj1.getArray()); } else { vertices = std::make_unique(); error(errSyntaxError, -1, "Bad Annot Polygon Vertices"); ok = false; } obj1 = dict->lookup("LE"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { Object obj2 = obj1.arrayGet(0); if (obj2.isName()) { const GooString leName(obj2.getName()); startStyle = parseAnnotLineEndingStyle(&leName); } else { startStyle = annotLineEndingNone; } obj2 = obj1.arrayGet(1); if (obj2.isName()) { const GooString leName(obj2.getName()); endStyle = parseAnnotLineEndingStyle(&leName); } else { endStyle = annotLineEndingNone; } } else { startStyle = endStyle = annotLineEndingNone; } obj1 = dict->lookup("IC"); if (obj1.isArray()) { interiorColor = std::make_unique(obj1.getArray()); } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } obj1 = dict->lookup("BE"); if (obj1.isDict()) { borderEffect = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("IT"); if (obj1.isName()) { const char *intentName = obj1.getName(); if (!strcmp(intentName, "PolygonCloud")) { intent = polygonCloud; } else if (!strcmp(intentName, "PolyLineDimension")) { intent = polylineDimension; } else { intent = polygonDimension; } } else { intent = polygonCloud; } } void AnnotPolygon::setType(AnnotSubtype new_type) { const char *typeName = nullptr; /* squelch bogus compiler warning */ switch (new_type) { case typePolygon: typeName = "Polygon"; break; case typePolyLine: typeName = "PolyLine"; break; default: assert(!"Invalid subtype"); } type = new_type; update("Subtype", Object(objName, typeName)); invalidateAppearance(); } void AnnotPolygon::setVertices(AnnotPath *path) { Array *a = new Array(doc->getXRef()); for (int i = 0; i < path->getCoordsLength(); i++) { a->add(Object(path->getX(i))); a->add(Object(path->getY(i))); } vertices = std::make_unique(a); update("Vertices", Object(a)); invalidateAppearance(); } void AnnotPolygon::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) { startStyle = start; endStyle = end; Array *a = new Array(doc->getXRef()); a->add(Object(objName, convertAnnotLineEndingStyle(startStyle))); a->add(Object(objName, convertAnnotLineEndingStyle(endStyle))); update("LE", Object(a)); invalidateAppearance(); } void AnnotPolygon::setInteriorColor(std::unique_ptr &&new_color) { if (new_color) { Object obj1 = new_color->writeToObject(doc->getXRef()); update("IC", std::move(obj1)); interiorColor = std::move(new_color); } invalidateAppearance(); } void AnnotPolygon::setIntent(AnnotPolygonIntent new_intent) { const char *intentName; intent = new_intent; if (new_intent == polygonCloud) intentName = "PolygonCloud"; else if (new_intent == polylineDimension) intentName = "PolyLineDimension"; else // polygonDimension intentName = "PolygonDimension"; update("IT", Object(objName, intentName)); } void AnnotPolygon::generatePolyLineAppearance(AnnotAppearanceBuilder *appearBuilder) { const bool fill = (bool)interiorColor; const double x1 = vertices->getX(0); const double y1 = vertices->getY(0); const double x2 = vertices->getX(1); const double y2 = vertices->getY(1); const double x3 = vertices->getX(vertices->getCoordsLength() - 2); const double y3 = vertices->getY(vertices->getCoordsLength() - 2); const double x4 = vertices->getX(vertices->getCoordsLength() - 1); const double y4 = vertices->getY(vertices->getCoordsLength() - 1); const double len_1 = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); // length of last segment const double len_2 = sqrt((x4 - x3) * (x4 - x3) + (y4 - y3) * (y4 - y3)); // segments become positive x direction, coord1 becomes (0,0). Matrix matr1, matr2; const double angle1 = atan2(y2 - y1, x2 - x1); const double angle2 = atan2(y4 - y3, x4 - x3); matr1.m[0] = matr1.m[3] = cos(angle1); matr1.m[1] = sin(angle1); matr1.m[2] = -matr1.m[1]; matr1.m[4] = x1 - rect->x1; matr1.m[5] = y1 - rect->y1; matr2.m[0] = matr2.m[3] = cos(angle2); matr2.m[1] = sin(angle2); matr2.m[2] = -matr2.m[1]; matr2.m[4] = x3 - rect->x1; matr2.m[5] = y3 - rect->y1; const double lineEndingSize1 { std::min(6. * border->getWidth(), len_1 / 2) }; const double lineEndingSize2 { std::min(6. * border->getWidth(), len_2 / 2) }; if (vertices->getCoordsLength() != 0) { double tx, ty; matr1.transform(AnnotAppearanceBuilder::lineEndingXShorten(startStyle, lineEndingSize1), 0, &tx, &ty); appearBuilder->appendf("{0:.2f} {1:.2f} m\n", tx, ty); appearBBox->extendTo(tx, ty); for (int i = 1; i < vertices->getCoordsLength() - 1; ++i) { appearBuilder->appendf("{0:.2f} {1:.2f} l\n", vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1); appearBBox->extendTo(vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1); } if (vertices->getCoordsLength() > 1) { matr2.transform(len_2 - AnnotAppearanceBuilder::lineEndingXShorten(endStyle, lineEndingSize2), 0, &tx, &ty); appearBuilder->appendf("{0:.2f} {1:.2f} l S\n", tx, ty); appearBBox->extendTo(tx, ty); } } if (startStyle != annotLineEndingNone) { const double extendX { -AnnotAppearanceBuilder::lineEndingXExtendBBox(startStyle, lineEndingSize1) }; double tx, ty; appearBuilder->drawLineEnding(startStyle, 0, 0, -lineEndingSize1, fill, matr1); matr1.transform(extendX, lineEndingSize1 / 2., &tx, &ty); appearBBox->extendTo(tx, ty); matr1.transform(extendX, -lineEndingSize1 / 2., &tx, &ty); appearBBox->extendTo(tx, ty); } if (endStyle != annotLineEndingNone) { const double extendX { AnnotAppearanceBuilder::lineEndingXExtendBBox(endStyle, lineEndingSize2) }; double tx, ty; appearBuilder->drawLineEnding(endStyle, len_2, 0, lineEndingSize2, fill, matr2); matr2.transform(len_2 + extendX, lineEndingSize2 / 2., &tx, &ty); appearBBox->extendTo(tx, ty); matr2.transform(len_2 + extendX, -lineEndingSize2 / 2., &tx, &ty); appearBBox->extendTo(tx, ty); } } void AnnotPolygon::draw(Gfx *gfx, bool printing) { double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { appearBBox = std::make_unique(rect.get()); ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) { appearBuilder.setDrawColor(color.get(), false); } appearBuilder.setLineStyleForBorder(border.get()); appearBBox->setBorderWidth(std::max(1., border->getWidth())); if (interiorColor) { appearBuilder.setDrawColor(interiorColor.get(), true); } if (type == typePolyLine) { generatePolyLineAppearance(&appearBuilder); } else { if (vertices->getCoordsLength() != 0) { appearBuilder.appendf("{0:.2f} {1:.2f} m\n", vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1); appearBBox->extendTo(vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1); for (int i = 1; i < vertices->getCoordsLength(); ++i) { appearBuilder.appendf("{0:.2f} {1:.2f} l\n", vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1); appearBBox->extendTo(vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1); } const double borderWidth = border->getWidth(); if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) { if (borderWidth > 0) { appearBuilder.append("b\n"); } else { appearBuilder.append("f\n"); } } else if (borderWidth > 0) { appearBuilder.append("s\n"); } } } appearBuilder.append("Q\n"); double bbox[4]; appearBBox->getBBoxRect(bbox); if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (appearBBox) { gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation()); } else { gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } } //------------------------------------------------------------------------ // AnnotCaret //------------------------------------------------------------------------ AnnotCaret::AnnotCaret(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeCaret; annotObj.dictSet("Subtype", Object(objName, "Caret")); initialize(docA, annotObj.getDict()); } AnnotCaret::AnnotCaret(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeCaret; initialize(docA, annotObj.getDict()); } AnnotCaret::~AnnotCaret() = default; void AnnotCaret::initialize(PDFDoc *docA, Dict *dict) { Object obj1; symbol = symbolNone; obj1 = dict->lookup("Sy"); if (obj1.isName()) { GooString typeName(obj1.getName()); if (!typeName.cmp("P")) { symbol = symbolP; } else if (!typeName.cmp("None")) { symbol = symbolNone; } } obj1 = dict->lookup("RD"); if (obj1.isArray()) { caretRect = parseDiffRectangle(obj1.getArray(), rect.get()); } } void AnnotCaret::setSymbol(AnnotCaretSymbol new_symbol) { symbol = new_symbol; update("Sy", Object(objName, new_symbol == symbolP ? "P" : "None")); invalidateAppearance(); } //------------------------------------------------------------------------ // AnnotInk //------------------------------------------------------------------------ AnnotInk::AnnotInk(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA) { type = typeInk; annotObj.dictSet("Subtype", Object(objName, "Ink")); // Store dummy path with one null vertex only Array *inkListArray = new Array(doc->getXRef()); Array *vList = new Array(doc->getXRef()); vList->add(Object(0.)); vList->add(Object(0.)); inkListArray->add(Object(vList)); annotObj.dictSet("InkList", Object(inkListArray)); initialize(docA, annotObj.getDict()); } AnnotInk::AnnotInk(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeInk; initialize(docA, annotObj.getDict()); } AnnotInk::~AnnotInk() { freeInkList(); } void AnnotInk::initialize(PDFDoc *docA, Dict *dict) { Object obj1; obj1 = dict->lookup("InkList"); if (obj1.isArray()) { parseInkList(obj1.getArray()); } else { inkListLength = 0; inkList = nullptr; error(errSyntaxError, -1, "Bad Annot Ink List"); obj1 = dict->lookup("AP"); // Although InkList is required, it should be ignored // when there is an AP entry in the Annot, so do not fail // when that happens if (!obj1.isDict()) { ok = false; } } obj1 = dict->lookup("BS"); if (obj1.isDict()) { border = std::make_unique(obj1.getDict()); } else if (!border) { border = std::make_unique(); } } void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array) { for (int i = 0; i < n_paths; ++i) { AnnotPath *path = paths[i]; Array *a = new Array(doc->getXRef()); for (int j = 0; j < path->getCoordsLength(); ++j) { a->add(Object(path->getX(j))); a->add(Object(path->getY(j))); } dest_array->add(Object(a)); } } void AnnotInk::parseInkList(Array *array) { inkListLength = array->getLength(); inkList = (AnnotPath **)gmallocn((inkListLength), sizeof(AnnotPath *)); memset(inkList, 0, inkListLength * sizeof(AnnotPath *)); for (int i = 0; i < inkListLength; i++) { Object obj2 = array->get(i); if (obj2.isArray()) inkList[i] = new AnnotPath(obj2.getArray()); } } void AnnotInk::freeInkList() { if (inkList) { for (int i = 0; i < inkListLength; ++i) delete inkList[i]; gfree(inkList); } } void AnnotInk::setInkList(AnnotPath **paths, int n_paths) { freeInkList(); Array *a = new Array(doc->getXRef()); writeInkList(paths, n_paths, a); parseInkList(a); annotObj.dictSet("InkList", Object(a)); invalidateAppearance(); } void AnnotInk::draw(Gfx *gfx, bool printing) { double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { appearBBox = std::make_unique(rect.get()); ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) { appearBuilder.setDrawColor(color.get(), false); } appearBuilder.setLineStyleForBorder(border.get()); appearBBox->setBorderWidth(std::max(1., border->getWidth())); for (int i = 0; i < inkListLength; ++i) { const AnnotPath *path = inkList[i]; if (path && path->getCoordsLength() != 0) { appearBuilder.appendf("{0:.2f} {1:.2f} m\n", path->getX(0) - rect->x1, path->getY(0) - rect->y1); appearBBox->extendTo(path->getX(0) - rect->x1, path->getY(0) - rect->y1); for (int j = 1; j < path->getCoordsLength(); ++j) { appearBuilder.appendf("{0:.2f} {1:.2f} l\n", path->getX(j) - rect->x1, path->getY(j) - rect->y1); appearBBox->extendTo(path->getX(j) - rect->x1, path->getY(j) - rect->y1); } appearBuilder.append("S\n"); } } appearBuilder.append("Q\n"); double bbox[4]; appearBBox->getBBoxRect(bbox); if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); if (appearBBox) { gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation()); } else { gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } } //------------------------------------------------------------------------ // AnnotFileAttachment //------------------------------------------------------------------------ AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, PDFRectangle *rectA, GooString *filename) : AnnotMarkup(docA, rectA) { type = typeFileAttachment; annotObj.dictSet("Subtype", Object(objName, "FileAttachment")); annotObj.dictSet("FS", Object(filename->copy())); initialize(docA, annotObj.getDict()); } AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeFileAttachment; initialize(docA, annotObj.getDict()); } AnnotFileAttachment::~AnnotFileAttachment() = default; void AnnotFileAttachment::initialize(PDFDoc *docA, Dict *dict) { Object objFS = dict->lookup("FS"); if (objFS.isDict() || objFS.isString()) { file = std::move(objFS); } else { error(errSyntaxError, -1, "Bad Annot File Attachment"); ok = false; } Object objName = dict->lookup("Name"); if (objName.isName()) { name = std::make_unique(objName.getName()); } else { name = std::make_unique("PushPin"); } } #define ANNOT_FILE_ATTACHMENT_AP_PUSHPIN \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "4 M 5 4 m 6 5 l S\n" \ "2 w\n" \ "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" \ "17 l 11 14 l h\n" \ "11 14 m S\n" \ "3 w\n" \ "6 5 m 9 8 l S\n" \ "0.729412 0.741176 0.713725 RG 2 w\n" \ "5 5 m 6 6 l S\n" \ "2 w\n" \ "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" \ "18 l 11 15 l h\n" \ "11 15 m S\n" \ "3 w\n" \ "6 6 m 9 9 l S\n" #define ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "14.133 18.578 l 14.949 19.387 16.867 19.184 17.539 18.465 c 20.551\n" \ "15.23 l 21.191 14.66 21.336 12.887 20.426 12.102 c 13.18 4.824 l 12.18\n" \ "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" \ "17.887 l S\n" \ "0.729412 0.741176 0.713725 RG 16.645 13.035 m 12.418 8.707 l\n" \ "10.902 7.559 6.402 12.203 8.09 13.562 c\n" \ "14.133 19.578 l 14.949 20.387 16.867 20.184 17.539 19.465 c 20.551\n" \ "16.23 l 21.191 15.66 21.336 13.887 20.426 13.102 c 13.18 5.824 l 12.18\n" \ "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" \ "18.887 l S\n" #define ANNOT_FILE_ATTACHMENT_AP_GRAPH \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 1 w\n" \ "1 J\n" \ "0 j\n" \ "[] 0.0 d\n" \ "4 M 18.5 15.5 m 18.5 13.086 l 16.086 15.5 l 18.5 15.5 l h\n" \ "18.5 15.5 m S\n" \ "7 7 m 10 11 l 13 9 l 18 15 l S\n" \ "0.729412 0.741176 0.713725 RG 7 8 m 10 12 l 13 10 l 18 16 l S\n" \ "18.5 16.5 m 18.5 14.086 l 16.086 16.5 l 18.5 16.5 l h\n" \ "18.5 16.5 m S\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 j\n" \ "3 19 m 3 3 l 21 3 l S\n" \ "0.729412 0.741176 0.713725 RG 3 20 m 3 4 l 21 4 l S\n" #define ANNOT_FILE_ATTACHMENT_AP_TAG \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 0.999781 w\n" \ "1 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "4 M q 1 0 0 -1 0 24 cm\n" \ "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" \ "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" \ "8.492 7.879 8.492 8.707 c h\n" \ "8.492 8.707 m S Q\n" \ "2 w\n" \ "20.078 11.414 m 20.891 10.602 20.785 9.293 20.078 8.586 c 14.422 2.93 l\n" \ "13.715 2.223 12.301 2.223 11.594 2.93 c 3.816 10.707 l 3.109 11.414\n" \ "2.402 17.781 3.816 19.195 c 5.23 20.609 11.594 19.902 12.301 19.195 c\n" \ "20.078 11.414 l h\n" \ "20.078 11.414 m S\n" \ "0.729412 0.741176 0.713725 RG 20.078 12.414 m\n" \ "20.891 11.605 20.785 10.293 20.078 9.586 c 14.422 3.93 l\n" \ "13.715 3.223 12.301 3.223 11.594 3.93 c 3.816 11.707 l 3.109 12.414\n" \ "2.402 18.781 3.816 20.195 c 5.23 21.609 11.594 20.902 12.301 20.195 c\n" \ "20.078 12.414 l h\n" \ "20.078 12.414 m S\n" \ "0.533333 0.541176 0.521569 RG 1 w\n" \ "0 j\n" \ "11.949 13.184 m 16.191 8.941 l S\n" \ "0.729412 0.741176 0.713725 RG 11.949 14.184 m 16.191 9.941 l S\n" \ "0.533333 0.541176 0.521569 RG 14.07 6.82 m 9.828 11.062 l S\n" \ "0.729412 0.741176 0.713725 RG 14.07 7.82 m 9.828 12.062 l S\n" \ "0.533333 0.541176 0.521569 RG 6.93 15.141 m 8 20 14.27 20.5 16 20.5 c\n" \ "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" \ "0.729412 0.741176 0.713725 RG 0.999781 w\n" \ "1 j\n" \ "q 1 0 0 -1 0 24 cm\n" \ "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" \ "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" \ "8.492 6.879 8.492 7.707 c h\n" \ "8.492 7.707 m S Q\n" \ "1 w\n" \ "0 j\n" \ "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" \ "19.5 17.699 20.91 17.418 22.5 17.5 c S\n" void AnnotFileAttachment::draw(Gfx *gfx, bool printing) { double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) appearBuilder.setDrawColor(color.get(), true); else appearBuilder.append("1 1 1 rg\n"); if (!name->cmp("PushPin")) appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_PUSHPIN); else if (!name->cmp("Paperclip")) appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP); else if (!name->cmp("Graph")) appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_GRAPH); else if (!name->cmp("Tag")) appearBuilder.append(ANNOT_FILE_ATTACHMENT_AP_TAG); appearBuilder.append("Q\n"); double bbox[4]; bbox[0] = bbox[1] = 0; bbox[2] = bbox[3] = 24; if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream Object obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // AnnotSound //------------------------------------------------------------------------ AnnotSound::AnnotSound(PDFDoc *docA, PDFRectangle *rectA, Sound *soundA) : AnnotMarkup(docA, rectA) { type = typeSound; annotObj.dictSet("Subtype", Object(objName, "Sound")); annotObj.dictSet("Sound", soundA->getObject()->copy()); initialize(docA, annotObj.getDict()); } AnnotSound::AnnotSound(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj) { type = typeSound; initialize(docA, annotObj.getDict()); } AnnotSound::~AnnotSound() = default; void AnnotSound::initialize(PDFDoc *docA, Dict *dict) { Object obj1 = dict->lookup("Sound"); sound = Sound::parseSound(&obj1); if (!sound) { error(errSyntaxError, -1, "Bad Annot Sound"); ok = false; } obj1 = dict->lookup("Name"); if (obj1.isName()) { name = std::make_unique(obj1.getName()); } else { name = std::make_unique("Speaker"); } } #define ANNOT_SOUND_AP_SPEAKER \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "0 J\n" \ "1 j\n" \ "[] 0.0 d\n" \ "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" \ "4 14 m S\n" \ "1 w\n" \ "1 J\n" \ "0 j\n" \ "13.699 15.398 m 14.699 13.398 14.699 9.398 13.699 7.398 c S\n" \ "18.199 19.398 m 21.199 17.398 21.199 5.398 18.199 3.398 c S\n" \ "16 17.398 m 18 16.398 18 7.398 16 5.398 c S\n" \ "0.729412 0.741176 0.713725 RG 2 w\n" \ "0 J\n" \ "1 j\n" \ "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" \ "4 15 m S\n" \ "1 w\n" \ "1 J\n" \ "0 j\n" \ "13.699 16 m 14.699 14 14.699 10 13.699 8 c S\n" \ "18.199 20 m 21.199 18 21.199 6 18.199 4 c S\n" \ "16 18 m 18 17 18 8 16 6 c S\n" #define ANNOT_SOUND_AP_MIC \ "4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \ "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" \ "l 1 21.523 2.477 23 4.301 23 c h\n" \ "4.301 23 m f\n" \ "0.533333 0.541176 0.521569 RG 2 w\n" \ "1 J\n" \ "0 j\n" \ "[] 0.0 d\n" \ "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" \ "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" \ "20 c h\n" \ "12 20 m S\n" \ "1 w\n" \ "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" \ "8.941 6.5 11.973 c 6.5 14.5 l S\n" \ "2 w\n" \ "0 J\n" \ "12 6.52 m 12 3 l S\n" \ "1 J\n" \ "8 3 m 16 3 l S\n" \ "0.729412 0.741176 0.713725 RG 12 21 m 12 21 l 13.656 21 15 19.656 15 18 c\n" \ "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" \ "9 18 l 9 19.656 10.344 21 12 21 c h\n" \ "12 21 m S\n" \ "1 w\n" \ "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" \ "9.941 6.5 12.973 c 6.5 15.5 l S\n" \ "2 w\n" \ "0 J\n" \ "12 7.52 m 12 4 l S\n" \ "1 J\n" \ "8 4 m 16 4 l S\n" void AnnotSound::draw(Gfx *gfx, bool printing) { Object obj; double ca = 1; if (!isVisible(printing)) return; annotLocker(); if (appearance.isNull()) { ca = opacity; AnnotAppearanceBuilder appearBuilder; appearBuilder.append("q\n"); if (color) appearBuilder.setDrawColor(color.get(), true); else appearBuilder.append("1 1 1 rg\n"); if (!name->cmp("Speaker")) appearBuilder.append(ANNOT_SOUND_AP_SPEAKER); else if (!name->cmp("Mic")) appearBuilder.append(ANNOT_SOUND_AP_MIC); appearBuilder.append("Q\n"); double bbox[4]; bbox[0] = bbox[1] = 0; bbox[2] = bbox[3] = 24; if (ca == 1) { appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr); } else { Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr); GooString appearBuf("/GS0 gs\n/Fm0 Do"); Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr); appearance = createForm(&appearBuf, bbox, false, resDict); } } // draw the appearance stream obj = appearance.fetch(gfx->getXRef()); gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); } //------------------------------------------------------------------------ // Annot3D //------------------------------------------------------------------------ Annot3D::Annot3D(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { type = type3D; annotObj.dictSet("Subtype", Object(objName, "3D")); initialize(docA, annotObj.getDict()); } Annot3D::Annot3D(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = type3D; initialize(docA, annotObj.getDict()); } Annot3D::~Annot3D() = default; void Annot3D::initialize(PDFDoc *docA, Dict *dict) { Object obj1 = dict->lookup("3DA"); if (obj1.isDict()) { activation = std::make_unique(obj1.getDict()); } } Annot3D::Activation::Activation(Dict *dict) { Object obj1; obj1 = dict->lookup("A"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "PO")) { aTrigger = aTriggerPageOpened; } else if (!strcmp(name, "PV")) { aTrigger = aTriggerPageVisible; } else if (!strcmp(name, "XA")) { aTrigger = aTriggerUserAction; } else { aTrigger = aTriggerUnknown; } } else { aTrigger = aTriggerUnknown; } obj1 = dict->lookup("AIS"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "I")) { aState = aStateEnabled; } else if (!strcmp(name, "L")) { aState = aStateDisabled; } else { aState = aStateUnknown; } } else { aState = aStateUnknown; } obj1 = dict->lookup("D"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "PC")) { dTrigger = dTriggerPageClosed; } else if (!strcmp(name, "PI")) { dTrigger = dTriggerPageInvisible; } else if (!strcmp(name, "XD")) { dTrigger = dTriggerUserAction; } else { dTrigger = dTriggerUnknown; } } else { dTrigger = dTriggerUnknown; } obj1 = dict->lookup("DIS"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "U")) { dState = dStateUninstantiaded; } else if (!strcmp(name, "I")) { dState = dStateInstantiated; } else if (!strcmp(name, "L")) { dState = dStateLive; } else { dState = dStateUnknown; } } else { dState = dStateUnknown; } displayToolbar = dict->lookup("TB").getBoolWithDefaultValue(true); displayNavigation = dict->lookup("NP").getBoolWithDefaultValue(false); } //------------------------------------------------------------------------ // AnnotRichMedia //------------------------------------------------------------------------ AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA) { type = typeRichMedia; annotObj.dictSet("Subtype", Object(objName, "RichMedia")); initialize(docA, annotObj.getDict()); } AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj) { type = typeRichMedia; initialize(docA, annotObj.getDict()); } AnnotRichMedia::~AnnotRichMedia() = default; void AnnotRichMedia::initialize(PDFDoc *docA, Dict *dict) { Object obj1 = dict->lookup("RichMediaContent"); if (obj1.isDict()) { content = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("RichMediaSettings"); if (obj1.isDict()) { settings = std::make_unique(obj1.getDict()); } } AnnotRichMedia::Content *AnnotRichMedia::getContent() const { return content.get(); } AnnotRichMedia::Settings *AnnotRichMedia::getSettings() const { return settings.get(); } AnnotRichMedia::Settings::Settings(Dict *dict) { Object obj1 = dict->lookup("Activation"); if (obj1.isDict()) { activation = std::make_unique(obj1.getDict()); } obj1 = dict->lookup("Deactivation"); if (obj1.isDict()) { deactivation = std::make_unique(obj1.getDict()); } } AnnotRichMedia::Settings::~Settings() = default; AnnotRichMedia::Activation *AnnotRichMedia::Settings::getActivation() const { return activation.get(); } AnnotRichMedia::Deactivation *AnnotRichMedia::Settings::getDeactivation() const { return deactivation.get(); } AnnotRichMedia::Activation::Activation(Dict *dict) { Object obj1 = dict->lookup("Condition"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "PO")) { condition = conditionPageOpened; } else if (!strcmp(name, "PV")) { condition = conditionPageVisible; } else if (!strcmp(name, "XA")) { condition = conditionUserAction; } else { condition = conditionUserAction; } } else { condition = conditionUserAction; } } AnnotRichMedia::Activation::Condition AnnotRichMedia::Activation::getCondition() const { return condition; } AnnotRichMedia::Deactivation::Deactivation(Dict *dict) { Object obj1 = dict->lookup("Condition"); if (obj1.isName()) { const char *name = obj1.getName(); if (!strcmp(name, "PC")) { condition = conditionPageClosed; } else if (!strcmp(name, "PI")) { condition = conditionPageInvisible; } else if (!strcmp(name, "XD")) { condition = conditionUserAction; } else { condition = conditionUserAction; } } else { condition = conditionUserAction; } } AnnotRichMedia::Deactivation::Condition AnnotRichMedia::Deactivation::getCondition() const { return condition; } AnnotRichMedia::Content::Content(Dict *dict) { Object obj1 = dict->lookup("Configurations"); if (obj1.isArray()) { nConfigurations = obj1.arrayGetLength(); configurations = (Configuration **)gmallocn(nConfigurations, sizeof(Configuration *)); for (int i = 0; i < nConfigurations; ++i) { Object obj2 = obj1.arrayGet(i); if (obj2.isDict()) { configurations[i] = new AnnotRichMedia::Configuration(obj2.getDict()); } else { configurations[i] = nullptr; } } } else { nConfigurations = 0; configurations = nullptr; } nAssets = 0; assets = nullptr; obj1 = dict->lookup("Assets"); if (obj1.isDict()) { Object obj2 = obj1.getDict()->lookup("Names"); if (obj2.isArray()) { const int length = obj2.arrayGetLength() / 2; assets = (Asset **)gmallocn(length, sizeof(Asset *)); for (int i = 0; i < length; ++i) { Object objKey = obj2.arrayGet(2 * i); Object objVal = obj2.arrayGet(2 * i + 1); if (!objKey.isString() || objVal.isNull()) { error(errSyntaxError, -1, "Bad Annot Asset"); continue; } assets[nAssets] = new AnnotRichMedia::Asset; assets[nAssets]->name = std::make_unique(objKey.getString()); assets[nAssets]->fileSpec = std::move(objVal); ++nAssets; } } } } AnnotRichMedia::Content::~Content() { if (configurations) { for (int i = 0; i < nConfigurations; ++i) delete configurations[i]; gfree(configurations); } if (assets) { for (int i = 0; i < nAssets; ++i) delete assets[i]; gfree(assets); } } int AnnotRichMedia::Content::getConfigurationsCount() const { return nConfigurations; } AnnotRichMedia::Configuration *AnnotRichMedia::Content::getConfiguration(int index) const { if (index < 0 || index >= nConfigurations) return nullptr; return configurations[index]; } int AnnotRichMedia::Content::getAssetsCount() const { return nAssets; } AnnotRichMedia::Asset *AnnotRichMedia::Content::getAsset(int index) const { if (index < 0 || index >= nAssets) return nullptr; return assets[index]; } AnnotRichMedia::Asset::Asset() = default; AnnotRichMedia::Asset::~Asset() = default; const GooString *AnnotRichMedia::Asset::getName() const { return name.get(); } Object *AnnotRichMedia::Asset::getFileSpec() const { return const_cast(&fileSpec); } AnnotRichMedia::Configuration::Configuration(Dict *dict) { Object obj1 = dict->lookup("Instances"); if (obj1.isArray()) { nInstances = obj1.arrayGetLength(); instances = (Instance **)gmallocn(nInstances, sizeof(Instance *)); for (int i = 0; i < nInstances; ++i) { Object obj2 = obj1.arrayGet(i); if (obj2.isDict()) { instances[i] = new AnnotRichMedia::Instance(obj2.getDict()); } else { instances[i] = nullptr; } } } else { instances = nullptr; } obj1 = dict->lookup("Name"); if (obj1.isString()) { name = std::make_unique(obj1.getString()); } obj1 = dict->lookup("Subtype"); if (obj1.isName()) { const char *subtypeName = obj1.getName(); if (!strcmp(subtypeName, "3D")) { type = type3D; } else if (!strcmp(subtypeName, "Flash")) { type = typeFlash; } else if (!strcmp(subtypeName, "Sound")) { type = typeSound; } else if (!strcmp(subtypeName, "Video")) { type = typeVideo; } else { // determine from first non null instance type = typeFlash; // default in case all instances are null if (instances && nInstances > 0) { for (int i = 0; i < nInstances; ++i) { AnnotRichMedia::Instance *instance = instances[i]; if (instance) { switch (instance->getType()) { case AnnotRichMedia::Instance::type3D: type = type3D; break; case AnnotRichMedia::Instance::typeFlash: type = typeFlash; break; case AnnotRichMedia::Instance::typeSound: type = typeSound; break; case AnnotRichMedia::Instance::typeVideo: type = typeVideo; break; } // break the loop since we found the first non null instance break; } } } } } } AnnotRichMedia::Configuration::~Configuration() { if (instances) { for (int i = 0; i < nInstances; ++i) delete instances[i]; gfree(instances); } } int AnnotRichMedia::Configuration::getInstancesCount() const { return nInstances; } AnnotRichMedia::Instance *AnnotRichMedia::Configuration::getInstance(int index) const { if (index < 0 || index >= nInstances) return nullptr; return instances[index]; } const GooString *AnnotRichMedia::Configuration::getName() const { return name.get(); } AnnotRichMedia::Configuration::Type AnnotRichMedia::Configuration::getType() const { return type; } AnnotRichMedia::Instance::Instance(Dict *dict) { Object obj1 = dict->lookup("Subtype"); const char *name = obj1.isName() ? obj1.getName() : ""; if (!strcmp(name, "3D")) { type = type3D; } else if (!strcmp(name, "Flash")) { type = typeFlash; } else if (!strcmp(name, "Sound")) { type = typeSound; } else if (!strcmp(name, "Video")) { type = typeVideo; } else { type = typeFlash; } obj1 = dict->lookup("Params"); if (obj1.isDict()) { params = std::make_unique(obj1.getDict()); } } AnnotRichMedia::Instance::~Instance() = default; AnnotRichMedia::Instance::Type AnnotRichMedia::Instance::getType() const { return type; } AnnotRichMedia::Params *AnnotRichMedia::Instance::getParams() const { return params.get(); } AnnotRichMedia::Params::Params(Dict *dict) { Object obj1 = dict->lookup("FlashVars"); if (obj1.isString()) { flashVars = std::make_unique(obj1.getString()); } } AnnotRichMedia::Params::~Params() = default; const GooString *AnnotRichMedia::Params::getFlashVars() const { return flashVars.get(); } //------------------------------------------------------------------------ // Annots //------------------------------------------------------------------------ Annots::Annots(PDFDoc *docA, int page, Object *annotsObj) { Annot *annot; int i; doc = docA; if (annotsObj->isArray()) { for (i = 0; i < annotsObj->arrayGetLength(); ++i) { // get the Ref to this annot and pass it to Annot constructor // this way, it'll be possible for the annot to retrieve the corresponding // form widget Object obj1 = annotsObj->arrayGet(i); if (obj1.isDict()) { const Object &obj2 = annotsObj->arrayGetNF(i); annot = createAnnot(std::move(obj1), &obj2); if (annot) { if (annot->isOk()) { annot->setPage(page, false); // Don't change /P appendAnnot(annot); } annot->decRefCnt(); } } } } } void Annots::appendAnnot(Annot *annot) { if (annot && annot->isOk()) { annots.push_back(annot); annot->incRefCnt(); } } bool Annots::removeAnnot(Annot *annot) { auto idx = std::find(annots.begin(), annots.end(), annot); if (idx == annots.end()) { return false; } else { annot->decRefCnt(); annots.erase(idx); return true; } } Annot *Annots::createAnnot(Object &&dictObject, const Object *obj) { Annot *annot = nullptr; Object obj1 = dictObject.dictLookup("Subtype"); if (obj1.isName()) { const char *typeName = obj1.getName(); if (!strcmp(typeName, "Text")) { annot = new AnnotText(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Link")) { annot = new AnnotLink(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "FreeText")) { annot = new AnnotFreeText(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Line")) { annot = new AnnotLine(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Square")) { annot = new AnnotGeometry(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Circle")) { annot = new AnnotGeometry(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Polygon")) { annot = new AnnotPolygon(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "PolyLine")) { annot = new AnnotPolygon(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Highlight")) { annot = new AnnotTextMarkup(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Underline")) { annot = new AnnotTextMarkup(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Squiggly")) { annot = new AnnotTextMarkup(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "StrikeOut")) { annot = new AnnotTextMarkup(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Stamp")) { annot = new AnnotStamp(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Caret")) { annot = new AnnotCaret(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Ink")) { annot = new AnnotInk(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "FileAttachment")) { annot = new AnnotFileAttachment(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Sound")) { annot = new AnnotSound(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Movie")) { annot = new AnnotMovie(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Widget")) { // Find the annot in forms if (obj->isRef()) { Form *form = doc->getCatalog()->getForm(); if (form) { FormWidget *widget = form->findWidgetByRef(obj->getRef()); if (widget) { annot = widget->getWidgetAnnotation(); annot->incRefCnt(); } } } if (!annot) annot = new AnnotWidget(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Screen")) { annot = new AnnotScreen(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "PrinterMark")) { annot = new Annot(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "TrapNet")) { annot = new Annot(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Watermark")) { annot = new Annot(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "3D")) { annot = new Annot3D(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "RichMedia")) { annot = new AnnotRichMedia(doc, std::move(dictObject), obj); } else if (!strcmp(typeName, "Popup")) { /* Popup annots are already handled by markup annots * Here we only care about popup annots without a * markup annotation associated */ Object obj2 = dictObject.dictLookup("Parent"); if (obj2.isNull()) annot = new AnnotPopup(doc, std::move(dictObject), obj); else annot = nullptr; } else { annot = new Annot(doc, std::move(dictObject), obj); } } return annot; } Annot *Annots::findAnnot(Ref *ref) { for (auto *annot : annots) { if (annot->match(ref)) { return annot; } } return nullptr; } Annots::~Annots() { for (auto *annot : annots) { annot->decRefCnt(); } } //------------------------------------------------------------------------ // AnnotAppearanceBuilder //------------------------------------------------------------------------ AnnotAppearanceBuilder::AnnotAppearanceBuilder() : appearBuf(new GooString()) { } AnnotAppearanceBuilder::~AnnotAppearanceBuilder() { delete appearBuf; } void AnnotAppearanceBuilder::append(const char *text) { appearBuf->append(text); } void AnnotAppearanceBuilder::appendf(const char *fmt, ...) GOOSTRING_FORMAT { va_list argList; va_start(argList, fmt); appearBuf->appendfv(fmt, argList); va_end(argList); } const GooString *AnnotAppearanceBuilder::buffer() const { return appearBuf; }