diff options
Diffstat (limited to 'Build/source/libs/poppler/poppler-0.26.4/poppler/Annot.cc')
-rw-r--r-- | Build/source/libs/poppler/poppler-0.26.4/poppler/Annot.cc | 6839 |
1 files changed, 6839 insertions, 0 deletions
diff --git a/Build/source/libs/poppler/poppler-0.26.4/poppler/Annot.cc b/Build/source/libs/poppler/poppler-0.26.4/poppler/Annot.cc new file mode 100644 index 00000000000..d35cb9b2697 --- /dev/null +++ b/Build/source/libs/poppler/poppler-0.26.4/poppler/Annot.cc @@ -0,0 +1,6839 @@ +//======================================================================== +// +// 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 <scotty1024@mac.com> +// Copyright (C) 2007, 2008 Julien Rebetez <julienr@svn.gnome.org> +// Copyright (C) 2007-2013 Albert Astals Cid <aacid@kde.org> +// Copyright (C) 2007-2013 Carlos Garcia Campos <carlosgc@gnome.org> +// Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com> +// Copyright (C) 2007 Jeff Muizelaar <jeff@infidigm.net> +// Copyright (C) 2008, 2011 Pino Toscano <pino@kde.org> +// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu> +// Copyright (C) 2008 Hugo Mercier <hmercier31@gmail.com> +// Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com> +// Copyright (C) 2011, 2013 José Aliste <jaliste@src.gnome.org> +// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it> +// Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de> +// Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com> +// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de> +// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com> +// Copyright (C) 2014 Marek Kasik <mkasik@redhat.com> +// Copyright (C) 2014 Jiri Slaby <jirislaby@gmail.com> +// Copyright (C) 2014 Anuj Khare <khareanuj18@gmail.com> +// +// 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 <config.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include "goo/gmem.h" +#include "goo/gstrtod.h" +#include "GooList.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 <string.h> + +#if MULTITHREADED +# define annotLocker() MutexLocker locker(&mutex) +# define annotCondLocker(X) MutexLocker locker(&mutex, (X)) +#else +# define annotLocker() +# define annotCondLocker(X) +#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 + +AnnotLineEndingStyle parseAnnotLineEndingStyle(GooString *string) { + if (string != NULL) { + 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; + } +} + +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) { + Object obj1; + AnnotExternalDataType type; + + if (dict->lookup("Subtype", &obj1)->isName()) { + const char *typeName = obj1.getName(); + + if (!strcmp(typeName, "Markup3D")) { + type = annotExternalDataMarkup3D; + } else { + type = annotExternalDataMarkupUnknown; + } + } else { + type = annotExternalDataMarkupUnknown; + } + obj1.free(); + + return type; +} + +PDFRectangle *parseDiffRectangle(Array *array, PDFRectangle *rect) { + PDFRectangle *newRect = NULL; + if (array->getLength() == 4) { + // deltas + Object obj1; + double dx1 = (array->get(0, &obj1)->isNum() ? obj1.getNum() : 0); + obj1.free(); + double dy1 = (array->get(1, &obj1)->isNum() ? obj1.getNum() : 0); + obj1.free(); + double dx2 = (array->get(2, &obj1)->isNum() ? obj1.getNum() : 0); + obj1.free(); + double dy2 = (array->get(3, &obj1)->isNum() ? obj1.getNum() : 0); + obj1.free(); + + // 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) { + newRect = new PDFRectangle(); + newRect->x1 = rect->x1 + dx1; + newRect->y1 = rect->y1 + dy1; + newRect->x2 = rect->x2 - dx2; + newRect->y2 = rect->y2 - dy2; + } + } + return newRect; +} + +static LinkAction* getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc) { + Object additionalActionsObject; + LinkAction *linkAction = NULL; + + if (additionalActions->fetch(doc->getXRef(), &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 ? "BI" : + type == Annot::actionPageOpening ? "PO" : + type == Annot::actionPageClosing ? "PC" : + type == Annot::actionPageVisible ? "PV" : + type == Annot::actionPageInvisible ? "PI" : NULL); + + Object actionObject; + + if (additionalActionsObject.dictLookup(key, &actionObject)->isDict()) + linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI()); + actionObject.free(); + } + + additionalActionsObject.free(); + + return linkAction; +} + +static LinkAction* getFormAdditionalAction(Annot::FormAdditionalActionsType type, Object *additionalActions, PDFDoc *doc) { + Object additionalActionsObject; + LinkAction *linkAction = NULL; + + if (additionalActions->fetch(doc->getXRef(), &additionalActionsObject)->isDict()) { + const char *key = (type == Annot::actionFieldModified ? "K" : + type == Annot::actionFormatField ? "F" : + type == Annot::actionValidateField ? "V" : + type == Annot::actionCalculateField ? "C" : NULL); + + Object actionObject; + + if (additionalActionsObject.dictLookup(key, &actionObject)->isDict()) + linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI()); + actionObject.free(); + } + + additionalActionsObject.free(); + + return linkAction; +} + +//------------------------------------------------------------------------ +// AnnotBorderEffect +//------------------------------------------------------------------------ + +AnnotBorderEffect::AnnotBorderEffect(Dict *dict) { + Object obj1; + + if (dict->lookup("S", &obj1)->isName()) { + const char *effectName = obj1.getName(); + + if (!strcmp(effectName, "C")) + effectType = borderEffectCloudy; + else + effectType = borderEffectNoEffect; + } else { + effectType = borderEffectNoEffect; + } + obj1.free(); + + if ((dict->lookup("I", &obj1)->isNum()) && effectType == borderEffectCloudy) { + intensity = obj1.getNum(); + } else { + intensity = 0; + } + obj1.free(); +} + +//------------------------------------------------------------------------ +// AnnotPath +//------------------------------------------------------------------------ + +AnnotPath::AnnotPath() { + coords = NULL; + coordsLength = 0; +} + +AnnotPath::AnnotPath(Array *array) { + coords = NULL; + coordsLength = 0; + parsePathArray(array); +} + +AnnotPath::AnnotPath(AnnotCoord **coords, int coordsLength) { + this->coords = coords; + this->coordsLength = coordsLength; +} + +AnnotPath::~AnnotPath() { + if (coords) { + for (int i = 0; i < coordsLength; ++i) + delete coords[i]; + gfree(coords); + } +} + +double AnnotPath::getX(int coord) const { + if (coord >= 0 && coord < coordsLength) + return coords[coord]->getX(); + return 0; +} + +double AnnotPath::getY(int coord) const { + if (coord >= 0 && coord < coordsLength) + return coords[coord]->getY(); + return 0; +} + +AnnotCoord *AnnotPath::getCoord(int coord) const { + if (coord >= 0 && coord < coordsLength) + return coords[coord]; + return NULL; +} + +void AnnotPath::parsePathArray(Array *array) { + int tempLength; + AnnotCoord **tempCoords; + GBool correct = gTrue; + + if (array->getLength() % 2) { + error(errSyntaxError, -1, "Bad Annot Path"); + return; + } + + tempLength = array->getLength() / 2; + tempCoords = (AnnotCoord **) gmallocn (tempLength, sizeof(AnnotCoord *)); + memset(tempCoords, 0, tempLength * sizeof(AnnotCoord *)); + for (int i = 0; i < tempLength && correct; i++) { + Object obj1; + double x = 0, y = 0; + + if (array->get(i * 2, &obj1)->isNum()) { + x = obj1.getNum(); + } else { + correct = gFalse; + } + obj1.free(); + + if (array->get((i * 2) + 1, &obj1)->isNum()) { + y = obj1.getNum(); + } else { + correct = gFalse; + } + obj1.free(); + + if (!correct) { + for (int j = i - 1; j >= 0; j--) + delete tempCoords[j]; + gfree (tempCoords); + return; + } + + tempCoords[i] = new AnnotCoord(x, y); + } + + coords = tempCoords; + coordsLength = tempLength; +} + +//------------------------------------------------------------------------ +// AnnotCalloutLine +//------------------------------------------------------------------------ + +AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2) + : coord1(x1, y1), coord2(x2, y2) { +} + +//------------------------------------------------------------------------ +// AnnotCalloutMultiLine +//------------------------------------------------------------------------ + +AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, + double y2, double x3, double y3) + : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) { +} + +//------------------------------------------------------------------------ +// AnnotQuadrilateral +//------------------------------------------------------------------------ + +AnnotQuadrilaterals::AnnotQuadrilaterals(Array *array, PDFRectangle *rect) { + int arrayLength = array->getLength(); + GBool correct = gTrue; + int quadsLength = 0; + AnnotQuadrilateral **quads; + double quadArray[8]; + + // default values + quadrilaterals = NULL; + quadrilateralsLength = 0; + + if ((arrayLength % 8) == 0) { + int i; + + quadsLength = arrayLength / 8; + quads = (AnnotQuadrilateral **) gmallocn + ((quadsLength), sizeof(AnnotQuadrilateral *)); + memset(quads, 0, quadsLength * sizeof(AnnotQuadrilateral *)); + + for (i = 0; i < quadsLength; i++) { + for (int j = 0; j < 8; j++) { + Object obj; + if (array->get(i * 8 + j, &obj)->isNum()) { + quadArray[j] = obj.getNum(); + } else { + correct = gFalse; + obj.free(); + error (errSyntaxError, -1, "Invalid QuadPoint in annot"); + break; + } + obj.free(); + } + + if (!correct) + break; + + quads[i] = new AnnotQuadrilateral(quadArray[0], quadArray[1], + quadArray[2], quadArray[3], + quadArray[4], quadArray[5], + quadArray[6], quadArray[7]); + } + + if (correct) { + quadrilateralsLength = quadsLength; + quadrilaterals = quads; + } else { + for (int j = 0; j < i; j++) + delete quads[j]; + gfree (quads); + } + } +} + +AnnotQuadrilaterals::AnnotQuadrilaterals(AnnotQuadrilaterals::AnnotQuadrilateral **quads, int quadsLength) { + quadrilaterals = quads; + quadrilateralsLength = quadsLength; +} + +AnnotQuadrilaterals::~AnnotQuadrilaterals() { + if (quadrilaterals) { + for(int i = 0; i < quadrilateralsLength; i++) + delete quadrilaterals[i]; + + gfree (quadrilaterals); + } +} + +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(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 = NULL; + style = borderSolid; +} + +GBool AnnotBorder::parseDashArray(Object *dashObj) { + GBool correct = gTrue; + 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++) { + Object obj1; + + if (dashObj->arrayGet(i, &obj1)->isNum()) { + tempDash[i] = obj1.getNum(); + + correct = tempDash[i] >= 0; + obj1.free(); + } + } + + 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(); + + GBool correct = gTrue; + if (arrayLength == 3 || arrayLength == 4) { + // implementation note 81 in Appendix H. + + if (array->get(0, &obj1)->isNum()) + horizontalCorner = obj1.getNum(); + else + correct = gFalse; + obj1.free(); + + if (array->get(1, &obj1)->isNum()) + verticalCorner = obj1.getNum(); + else + correct = gFalse; + obj1.free(); + + if (array->get(2, &obj1)->isNum()) + width = obj1.getNum(); + else + correct = gFalse; + obj1.free(); + + if (arrayLength == 4) { + if (array->get(3, &obj1)->isArray()) + correct = parseDashArray(&obj1); + else + correct = gFalse; + obj1.free(); + } + } else { + correct = gFalse; + } + + if (!correct) { + width = 0; + } +} + +void AnnotBorderArray::writeToObject(XRef *xref, Object *obj1) const { + Object obj2; + + obj1->initArray(xref); + obj1->arrayAdd(obj2.initReal(horizontalCorner)); + obj1->arrayAdd(obj2.initReal(verticalCorner)); + obj1->arrayAdd(obj2.initReal(width)); + + if (dashLength > 0) { + Object obj3; + + obj1->arrayAdd(obj3.initArray(xref)); + for (int i = 0; i < dashLength; i++) + obj3.arrayAdd(obj2.initReal(dash[i])); + } +} + +//------------------------------------------------------------------------ +// AnnotBorderBS +//------------------------------------------------------------------------ + +AnnotBorderBS::AnnotBorderBS() { +} + +AnnotBorderBS::AnnotBorderBS(Dict *dict) { + Object obj1, obj2; + + // acroread 8 seems to need both W and S entries for + // any border to be drawn, even though the spec + // doesn't claim anything of that sort. We follow + // that behaviour by veryifying both entries exist + // otherwise we set the borderWidth to 0 + // --jrmuizel + dict->lookup("W", &obj1); + dict->lookup("S", &obj2); + if (obj1.isNum() && obj2.isName()) { + const char *styleName = obj2.getName(); + + width = obj1.getNum(); + + 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 { + width = 0; + } + obj2.free(); + obj1.free(); + + if (style == borderDashed) { + if (dict->lookup("D", &obj1)->isArray()) + parseDashArray(&obj1); + obj1.free(); + + 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"; +} + +void AnnotBorderBS::writeToObject(XRef *xref, Object *obj1) const { + Object obj2; + + obj1->initDict(xref); + obj1->dictSet("W", obj2.initReal(width)); + obj1->dictSet("S", obj2.initName(getStyleName())); + if (style == borderDashed && dashLength > 0) { + Object obj3; + + obj1->dictSet("D", obj3.initArray(xref)); + for (int i = 0; i < dashLength; i++) + obj3.arrayAdd(obj2.initReal(dash[i])); + } +} + +//------------------------------------------------------------------------ +// 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 <adjust> is +1, color is brightened; +// if <adjust> 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; + + if (array->get(i, &obj1)->isNum()) { + values[i] = obj1.getNum(); + + if (values[i] < 0 || values[i] > 1) + values[i] = 0; + } else { + values[i] = 0; + } + obj1.free(); + } + + 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]; + } + } +} + +void AnnotColor::writeToObject(XRef *xref, Object *obj1) const { + Object obj2; + int i; + + if (length == 0) { + obj1->initNull(); // Transparent (no color) + } else { + obj1->initArray(xref); + for (i = 0; i < length; ++i) + obj1->arrayAdd( obj2.initReal( values[i] ) ); + } +} + +//------------------------------------------------------------------------ +// AnnotIconFit +//------------------------------------------------------------------------ + +AnnotIconFit::AnnotIconFit(Dict* dict) { + Object obj1; + + if (dict->lookup("SW", &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.free(); + + if (dict->lookup("S", &obj1)->isName()) { + const char *scaleName = obj1.getName(); + + if(!strcmp(scaleName, "A")) { + scale = scaleAnamorphic; + } else { + scale = scaleProportional; + } + } else { + scale = scaleProportional; + } + obj1.free(); + + if (dict->lookup("A", &obj1)->isArray() && obj1.arrayGetLength() == 2) { + Object obj2; + (obj1.arrayGet(0, &obj2)->isNum() ? left = obj2.getNum() : left = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? bottom = obj2.getNum() : bottom = 0); + obj2.free(); + + if (left < 0 || left > 1) + left = 0.5; + + if (bottom < 0 || bottom > 1) + bottom = 0.5; + + } else { + left = bottom = 0.5; + } + obj1.free(); + + if (dict->lookup("FB", &obj1)->isBool()) { + fullyBounds = obj1.getBool(); + } else { + fullyBounds = gFalse; + } + obj1.free(); +} + +//------------------------------------------------------------------------ +// AnnotAppearance +//------------------------------------------------------------------------ + +AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict) { + assert(dict->isDict()); + doc = docA; + xref = docA->getXRef(); + dict->copy(&appearDict); +} + +AnnotAppearance::~AnnotAppearance() { + appearDict.free(); +} + +void AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state, Object *dest) { + Object apData, stream; + apData.initNull(); + + // Obtain dictionary or stream associated to appearance type + switch (type) { + case appearRollover: + if (appearDict.dictLookupNF("R", &apData)->isNull()) + appearDict.dictLookupNF("N", &apData); + break; + case appearDown: + if (appearDict.dictLookupNF("D", &apData)->isNull()) + appearDict.dictLookupNF("N", &apData); + break; + case appearNormal: + appearDict.dictLookupNF("N", &apData); + break; + } + + dest->initNull(); + if (apData.isDict() && state) + apData.dictLookupNF(state, dest); + else if (apData.isRef()) + apData.copy(dest); + apData.free(); +} + +GooString * AnnotAppearance::getStateKey(int i) { + Object obj1; + GooString * res = NULL; + if (appearDict.dictLookupNF("N", &obj1)->isDict()) + res = new GooString(obj1.dictGetKey(i)); + obj1.free(); + return res; +} + +int AnnotAppearance::getNumStates() { + Object obj1; + int res = 0; + if (appearDict.dictLookupNF("N", &obj1)->isDict()) + res = obj1.dictGetLength(); + obj1.free(); + return res; +} + +// Test if stateObj (a Ref or a Dict) points to the specified stream +GBool AnnotAppearance::referencesStream(Object *stateObj, Ref refToStream) { + if (stateObj->isRef()) { + Ref r = stateObj->getRef(); + if (r.num == refToStream.num && r.gen == refToStream.gen) { + return gTrue; + } + } else if (stateObj->isDict()) { // Test each value + const int size = stateObj->dictGetLength(); + for (int i = 0; i < size; ++i) { + Object obj1; + stateObj->dictGetValNF(i, &obj1); + if (obj1.isRef()) { + Ref r = obj1.getRef(); + if (r.num == refToStream.num && r.gen == refToStream.gen) { + return gTrue; + } + } + obj1.free(); + } + } + return gFalse; // Not found +} + +// Test if this AnnotAppearance references the specified stream +GBool AnnotAppearance::referencesStream(Ref refToStream) { + Object obj1; + GBool found; + + // Scan each state's ref/subdictionary + appearDict.dictLookupNF("N", &obj1); + found = referencesStream(&obj1, refToStream); + obj1.free(); + if (found) + return gTrue; + + appearDict.dictLookupNF("R", &obj1); + found = referencesStream(&obj1, refToStream); + obj1.free(); + if (found) + return gTrue; + + appearDict.dictLookupNF("D", &obj1); + found = referencesStream(&obj1, refToStream); + obj1.free(); + 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 + xref->removeIndirectObject(refToStream); +} + +// Removes stream if obj is a Ref, or removes pointed streams if obj is a Dict +void AnnotAppearance::removeStateStreams(Object *obj1) { + if (obj1->isRef()) { + removeStream(obj1->getRef()); + } else if (obj1->isDict()) { + const int size = obj1->dictGetLength(); + for (int i = 0; i < size; ++i) { + Object obj2; + obj1->dictGetValNF(i, &obj2); + if (obj2.isRef()) { + removeStream(obj2.getRef()); + } + obj2.free(); + } + } +} + +void AnnotAppearance::removeAllStreams() { + Object obj1; + appearDict.dictLookupNF("N", &obj1); + removeStateStreams(&obj1); + obj1.free(); + appearDict.dictLookupNF("R", &obj1); + removeStateStreams(&obj1); + obj1.free(); + appearDict.dictLookupNF("D", &obj1); + removeStateStreams(&obj1); + obj1.free(); +} + +//------------------------------------------------------------------------ +// AnnotAppearanceCharacs +//------------------------------------------------------------------------ + +AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) { + Object obj1; + + if (dict->lookup("R", &obj1)->isInt()) { + rotation = obj1.getInt(); + } else { + rotation = 0; + } + obj1.free(); + + if (dict->lookup("BC", &obj1)->isArray()) { + borderColor = new AnnotColor(obj1.getArray()); + } else { + borderColor = NULL; + } + obj1.free(); + + if (dict->lookup("BG", &obj1)->isArray()) { + backColor = new AnnotColor(obj1.getArray()); + } else { + backColor = NULL; + } + obj1.free(); + + if (dict->lookup("CA", &obj1)->isString()) { + normalCaption = new GooString(obj1.getString()); + } else { + normalCaption = NULL; + } + obj1.free(); + + if (dict->lookup("RC", &obj1)->isString()) { + rolloverCaption = new GooString(obj1.getString()); + } else { + rolloverCaption = NULL; + } + obj1.free(); + + if (dict->lookup("AC", &obj1)->isString()) { + alternateCaption = new GooString(obj1.getString()); + } else { + alternateCaption = NULL; + } + obj1.free(); + + if (dict->lookup("IF", &obj1)->isDict()) { + iconFit = new AnnotIconFit(obj1.getDict()); + } else { + iconFit = NULL; + } + obj1.free(); + + if (dict->lookup("TP", &obj1)->isInt()) { + position = (AnnotAppearanceCharacsTextPos) obj1.getInt(); + } else { + position = captionNoIcon; + } + obj1.free(); +} + +AnnotAppearanceCharacs::~AnnotAppearanceCharacs() { + if (borderColor) + delete borderColor; + + if (backColor) + delete backColor; + + if (normalCaption) + delete normalCaption; + + if (rolloverCaption) + delete rolloverCaption; + + if (alternateCaption) + delete alternateCaption; + + if (iconFit) + delete iconFit; +} + +//------------------------------------------------------------------------ +// 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 +//------------------------------------------------------------------------ + +Annot::Annot(PDFDoc *docA, PDFRectangle *rectA) { + Object obj1; + + refCnt = 1; + flags = flagUnknown; + type = typeUnknown; + + obj1.initArray (docA->getXRef()); + Object obj2; + obj1.arrayAdd (obj2.initReal (rectA->x1)); + obj1.arrayAdd (obj2.initReal (rectA->y1)); + obj1.arrayAdd (obj2.initReal (rectA->x2)); + obj1.arrayAdd (obj2.initReal (rectA->y2)); + obj2.free (); + + annotObj.initDict (docA->getXRef()); + annotObj.dictSet ("Type", obj2.initName ("Annot")); + annotObj.dictSet ("Rect", &obj1); + // obj1 is owned by the dict + + ref = docA->getXRef()->addIndirectObject (&annotObj); + + initialize (docA, annotObj.getDict()); +} + +Annot::Annot(PDFDoc *docA, Dict *dict) { + refCnt = 1; + hasRef = false; + flags = flagUnknown; + type = typeUnknown; + annotObj.initDict (dict); + initialize (docA, dict); +} + +Annot::Annot(PDFDoc *docA, Dict *dict, Object *obj) { + refCnt = 1; + if (obj->isRef()) { + hasRef = gTrue; + ref = obj->getRef(); + } else { + hasRef = gFalse; + } + flags = flagUnknown; + type = typeUnknown; + annotObj.initDict (dict); + initialize (docA, dict); +} + +void Annot::initialize(PDFDoc *docA, Dict *dict) { + Object apObj, asObj, obj1, obj2; + + ok = gTrue; + doc = docA; + xref = doc->getXRef(); + appearStreams = NULL; + appearBBox = NULL; + appearState = NULL; + appearBuf = NULL; + fontSize = 0; + + appearance.initNull(); + + //----- parse the rectangle + rect = new PDFRectangle(); + if (dict->lookup("Rect", &obj1)->isArray() && obj1.arrayGetLength() == 4) { + Object obj2; + (obj1.arrayGet(0, &obj2)->isNum() ? rect->x1 = obj2.getNum() : rect->x1 = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? rect->y1 = obj2.getNum() : rect->y1 = 0); + obj2.free(); + (obj1.arrayGet(2, &obj2)->isNum() ? rect->x2 = obj2.getNum() : rect->x2 = 1); + obj2.free(); + (obj1.arrayGet(3, &obj2)->isNum() ? rect->y2 = obj2.getNum() : rect->y2 = 1); + obj2.free(); + + 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 = gFalse; + } + obj1.free(); + + if (dict->lookup("Contents", &obj1)->isString()) { + contents = obj1.getString()->copy(); + } else { + contents = new GooString(); + } + obj1.free(); + + // Note: This value is overwritten by Annots ctor + if (dict->lookupNF("P", &obj1)->isRef()) { + Ref ref = obj1.getRef(); + + page = doc->getCatalog()->findPage (ref.num, ref.gen); + } else { + page = 0; + } + obj1.free(); + + if (dict->lookup("NM", &obj1)->isString()) { + name = obj1.getString()->copy(); + } else { + name = NULL; + } + obj1.free(); + + if (dict->lookup("M", &obj1)->isString()) { + modified = obj1.getString()->copy(); + } else { + modified = NULL; + } + obj1.free(); + + //----- get the flags + if (dict->lookup("F", &obj1)->isInt()) { + flags |= obj1.getInt(); + } else { + flags = flagUnknown; + } + obj1.free(); + + //----- get the annotation appearance dictionary + dict->lookup("AP", &apObj); + if (apObj.isDict()) { + appearStreams = new AnnotAppearance(doc, &apObj); + } + apObj.free(); + + //----- get the appearance state + dict->lookup("AS", &asObj); + if (asObj.isName()) { + appearState = new GooString(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 = new GooString("Off"); + } + asObj.free(); + + //----- get the annotation appearance + if (appearStreams) { + appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString(), &appearance); + } + + //----- 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. + if (dict->lookup("Border", &obj1)->isArray()) + border = new AnnotBorderArray(obj1.getArray()); + else + border = NULL; + obj1.free(); + + if (dict->lookup("C", &obj1)->isArray()) { + color = new AnnotColor(obj1.getArray()); + } else { + color = NULL; + } + obj1.free(); + + if (dict->lookup("StructParent", &obj1)->isInt()) { + treeKey = obj1.getInt(); + } else { + treeKey = 0; + } + obj1.free(); + + dict->lookupNF("OC", &oc); + +#if MULTITHREADED + gInitMutex(&mutex); +#endif +} + +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(PDFRectangle *rect) { + setRect(rect->x1, rect->y1, rect->x2, rect->y2); +} + +void Annot::setRect(double x1, double y1, double x2, double y2) { + Object obj1, obj2; + + 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; + } + + obj1.initArray (xref); + obj1.arrayAdd (obj2.initReal (rect->x1)); + obj1.arrayAdd (obj2.initReal (rect->y1)); + obj1.arrayAdd (obj2.initReal (rect->x2)); + obj1.arrayAdd (obj2.initReal (rect->y2)); + + update("Rect", &obj1); + invalidateAppearance(); +} + +GBool 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) { + delete modified; + modified = timeToDateString(NULL); + + Object obj1; + obj1.initString (modified->copy()); + annotObj.dictSet("M", &obj1); + } + + annotObj.dictSet(const_cast<char*>(key), value); + + xref->setModifiedObject(&annotObj, ref); +} + +void Annot::setContents(GooString *new_content) { + annotLocker(); + delete contents; + + if (new_content) { + contents = new GooString(new_content); + //append the unicode marker <FE FF> if needed + if (!contents->hasUnicodeMarker()) { + contents->insert(0, 0xff); + contents->insert(0, 0xfe); + } + } else { + contents = new GooString(); + } + + Object obj1; + obj1.initString(contents->copy()); + update ("Contents", &obj1); +} + +void Annot::setName(GooString *new_name) { + annotLocker(); + delete name; + + if (new_name) { + name = new GooString(new_name); + } else { + name = new GooString(); + } + + Object obj1; + obj1.initString(name->copy()); + update ("NM", &obj1); +} + +void Annot::setModified(GooString *new_modified) { + annotLocker(); + delete modified; + + if (new_modified) + modified = new GooString(new_modified); + else + modified = new GooString(); + + Object obj1; + obj1.initString(modified->copy()); + update ("M", &obj1); +} + +void Annot::setFlags(Guint new_flags) { + annotLocker(); + Object obj1; + flags = new_flags; + obj1.initInt(flags); + update ("F", &obj1); +} + +void Annot::setBorder(AnnotBorder *new_border) { + annotLocker(); + delete border; + + if (new_border) { + Object obj1; + new_border->writeToObject(xref, &obj1); + update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", &obj1); + border = new_border; + } else { + border = NULL; + } + invalidateAppearance(); +} + +void Annot::setColor(AnnotColor *new_color) { + annotLocker(); + delete color; + + if (new_color) { + Object obj1; + new_color->writeToObject(xref, &obj1); + update ("C", &obj1); + color = new_color; + } else { + color = NULL; + } + invalidateAppearance(); +} + +void Annot::setPage(int pageIndex, GBool updateP) { + annotLocker(); + Page *pageobj = doc->getPage(pageIndex); + Object obj1; + + if (pageobj) { + Ref pageRef = pageobj->getRef(); + obj1.initRef(pageRef.num, pageRef.gen); + page = pageIndex; + } else { + obj1.initNull(); + page = 0; + } + + if (updateP) { + update("P", &obj1); + } +} + +void Annot::setAppearanceState(const char *state) { + annotLocker(); + if (!state) + return; + + delete appearState; + appearState = new GooString(state); + + delete appearBBox; + appearBBox = NULL; + + Object obj1; + obj1.initName(state); + update ("AS", &obj1); + + // The appearance state determines the current appearance stream + appearance.free(); + if (appearStreams) { + appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString(), &appearance); + } else { + appearance.initNull(); + } +} + +void Annot::invalidateAppearance() { + annotLocker(); + if (appearStreams) { // Remove existing appearance streams + appearStreams->removeAllStreams(); + } + delete appearStreams; + appearStreams = NULL; + + delete appearState; + appearState = NULL; + + delete appearBBox; + appearBBox = NULL; + + appearance.free(); + appearance.initNull(); + + Object obj1, obj2; + obj1.initNull(); + if (!annotObj.dictLookup("AP", &obj2)->isNull()) + update ("AP", &obj1); // Remove AP + obj2.free(); + + if (!annotObj.dictLookup("AS", &obj2)->isNull()) + update ("AS", &obj1); // Remove AS + obj2.free(); +} + +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, &valueObject); + if (valueObject.isNum()) { + *value = valueObject.getNum(); + } else { + *value = 0; + ok = gFalse; + } + valueObject.free(); +} + +void Annot::removeReferencedObjects() { + // Remove appearance streams (if any) + invalidateAppearance(); +} + +void Annot::incRefCnt() { + annotLocker(); + refCnt++; +} + +void Annot::decRefCnt() { +#if MULTITHREADED + gLockMutex(&mutex); +#endif + if (--refCnt == 0) { +#if MULTITHREADED + gUnlockMutex(&mutex); +#endif + delete this; + return; + } +#if MULTITHREADED + gUnlockMutex(&mutex); +#endif +} + +Annot::~Annot() { + annotObj.free(); + + delete rect; + delete contents; + + if (name) + delete name; + + if (modified) + delete modified; + + delete appearStreams; + delete appearBBox; + appearance.free(); + + if (appearState) + delete appearState; + + if (border) + delete border; + + if (color) + delete color; + + oc.free(); + +#if MULTITHREADED + gDestroyMutex(&mutex); +#endif +} + +void Annot::setColor(AnnotColor *color, GBool fill) { + const double *values = color->getValues(); + + switch (color->getSpace()) { + case AnnotColor::colorCMYK: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", + values[0], values[1], values[2], values[3], + fill ? 'k' : 'K'); + break; + case AnnotColor::colorRGB: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", + values[0], values[1], values[2], + fill ? "rg" : "RG"); + break; + case AnnotColor::colorGray: + appearBuf->appendf("{0:.2f} {1:c}\n", + values[0], + fill ? 'g' : 'G'); + break; + case AnnotColor::colorTransparent: + default: + break; + } +} + +void Annot::setLineStyleForBorder(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 <r> centered at (<cx>, <cy>). +// If <fill> is true, the circle is filled; otherwise it is stroked. +void Annot::drawCircle(double cx, double cy, double r, GBool fill) { + appearBuf->appendf("{0:.2f} {1:.2f} m\n", + cx + r, cy); + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + cx + r, cy + bezierCircle * r, + cx + bezierCircle * r, cy + r, + cx, cy + r); + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + cx - bezierCircle * r, cy + r, + cx - r, cy + bezierCircle * r, + cx - r, cy); + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + cx - r, cy - bezierCircle * r, + cx - bezierCircle * r, cy - r, + cx, cy - r); + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + cx + bezierCircle * r, cy - r, + cx + r, cy - bezierCircle * r, + cx + r, cy); + appearBuf->append(fill ? "f\n" : "s\n"); +} + +// Draw the top-left half of an (approximate) circle of radius <r> +// centered at (<cx>, <cy>). +void Annot::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 <r> +// centered at (<cx>, <cy>). +void Annot::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 Annot::createForm(double *bbox, GBool transparencyGroup, Object *resDict, Object *aStream) { + Object obj1, obj2; + Object appearDict; + + appearDict.initDict(xref); + appearDict.dictSet("Length", obj1.initInt(appearBuf->getLength())); + appearDict.dictSet("Subtype", obj1.initName("Form")); + obj1.initArray(xref); + obj1.arrayAdd(obj2.initReal(bbox[0])); + obj1.arrayAdd(obj2.initReal(bbox[1])); + obj1.arrayAdd(obj2.initReal(bbox[2])); + obj1.arrayAdd(obj2.initReal(bbox[3])); + appearDict.dictSet("BBox", &obj1); + if (transparencyGroup) { + Object transDict; + transDict.initDict(xref); + transDict.dictSet("S", obj1.initName("Transparency")); + appearDict.dictSet("Group", &transDict); + } + if (resDict) + appearDict.dictSet("Resources", resDict); + + MemStream *mStream = new MemStream(copyString(appearBuf->getCString()), 0, + appearBuf->getLength(), &appearDict); + mStream->setNeedFree(gTrue); + aStream->initStream(mStream); +} + +void Annot::createResourcesDict(const char *formName, Object *formStream, + const char *stateName, + double opacity, const char *blendMode, + Object *resDict) { + Object gsDict, stateDict, formDict, obj1; + + gsDict.initDict(xref); + if (opacity != 1) { + gsDict.dictSet("CA", obj1.initReal(opacity)); + gsDict.dictSet("ca", obj1.initReal(opacity)); + } + if (blendMode) + gsDict.dictSet("BM", obj1.initName(blendMode)); + stateDict.initDict(xref); + stateDict.dictSet(stateName, &gsDict); + formDict.initDict(xref); + formDict.dictSet(formName, formStream); + + resDict->initDict(xref); + resDict->dictSet("ExtGState", &stateDict); + resDict->dictSet("XObject", &formDict); +} + +Object *Annot::getAppearanceResDict(Object *dest) { + Object obj1, obj2; + + dest->initNull(); // Default value + + // Fetch appearance's resource dict (if any) + appearance.fetch(xref, &obj1); + if (obj1.isStream()) { + obj1.streamGetDict()->lookup("Resources", &obj2); + if (obj2.isDict()) { + obj2.copy(dest); + } + obj2.free(); + } + obj1.free(); + + return dest; +} + +GBool Annot::isVisible(GBool printing) { + // check the flags + if ((flags & flagHidden) || + (printing && !(flags & flagPrint)) || + (!printing && (flags & flagNoView))) { + return gFalse; + } + + // check the OC + OCGs *optContentConfig = doc->getCatalog()->getOptContentConfig(); + if (optContentConfig) { + if (! optContentConfig->optContentIsVisible(&oc)) + return gFalse; + } + + return gTrue; +} + +int Annot::getRotation() const +{ + Page *pageobj = doc->getPage(page); + assert(pageobj != NULL); + + if (flags & flagNoRotate) { + return (360 - pageobj->getRotate()) % 360; + } else { + return 0; + } +} + +void Annot::draw(Gfx *gfx, GBool printing) { + Object obj; + + annotLocker(); + if (!isVisible (printing)) + return; + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotPopup +//------------------------------------------------------------------------ + +AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rect) : + Annot(docA, rect) { + Object obj1; + + type = typePopup; + + annotObj.dictSet ("Subtype", obj1.initName ("Popup")); + initialize (docA, annotObj.getDict()); +} + +AnnotPopup::AnnotPopup(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + type = typePopup; + initialize(docA, dict); +} + +AnnotPopup::~AnnotPopup() { + parent.free(); +} + +void AnnotPopup::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + if (!dict->lookupNF("Parent", &parent)->isRef()) { + parent.initNull(); + } + + if (dict->lookup("Open", &obj1)->isBool()) { + open = obj1.getBool(); + } else { + open = gFalse; + } + obj1.free(); +} + +void AnnotPopup::setParent(Object *parentA) { + parentA->copy(&parent); + update ("Parent", &parent); +} + +void AnnotPopup::setParent(Annot *parentA) { + Ref parentRef = parentA->getRef(); + parent.initRef(parentRef.num, parentRef.gen); + update ("Parent", &parent); +} + +void AnnotPopup::setOpen(GBool openA) { + Object obj1; + + open = openA; + obj1.initBool(open); + update ("Open", &obj1); +} + +//------------------------------------------------------------------------ +// AnnotMarkup +//------------------------------------------------------------------------ +AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rect) : + Annot(docA, rect) { + initialize(docA, annotObj.getDict(), &annotObj); +} + +AnnotMarkup::AnnotMarkup(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + initialize(docA, dict, obj); +} + +AnnotMarkup::~AnnotMarkup() { + if (label) + delete label; + + if (popup) + delete popup; + + if (date) + delete date; + + if (subject) + delete subject; +} + +void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict, Object *obj) { + Object obj1, obj2; + + if (dict->lookup("T", &obj1)->isString()) { + label = obj1.getString()->copy(); + } else { + label = NULL; + } + obj1.free(); + + if (dict->lookup("Popup", &obj1)->isDict() && dict->lookupNF("Popup", &obj2)->isRef()) { + popup = new AnnotPopup(docA, obj1.getDict(), &obj2); + } else { + popup = NULL; + } + obj1.free(); + + if (dict->lookup("CA", &obj1)->isNum()) { + opacity = obj1.getNum(); + } else { + opacity = 1.0; + } + obj1.free(); + + if (dict->lookup("CreationDate", &obj1)->isString()) { + date = obj1.getString()->copy(); + } else { + date = NULL; + } + obj1.free(); + + if (dict->lookupNF("IRT", &obj1)->isRef()) { + inReplyTo = obj1.getRef(); + } else { + inReplyTo.num = 0; + inReplyTo.gen = 0; + } + obj1.free(); + + if (dict->lookup("Subj", &obj1)->isString()) { + subject = obj1.getString()->copy(); + } else { + subject = NULL; + } + obj1.free(); + + if (dict->lookup("RT", &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.free(); + + if (dict->lookup("ExData", &obj1)->isDict()) { + exData = parseAnnotExternalData(obj1.getDict()); + } else { + exData = annotExternalDataMarkupUnknown; + } + obj1.free(); +} + +void AnnotMarkup::setLabel(GooString *new_label) { + delete label; + + if (new_label) { + label = new GooString(new_label); + //append the unicode marker <FE FF> if needed + if (!label->hasUnicodeMarker()) { + label->insert(0, 0xff); + label->insert(0, 0xfe); + } + } else { + label = new GooString(); + } + + Object obj1; + obj1.initString(label->copy()); + update ("T", &obj1); +} + +void AnnotMarkup::setPopup(AnnotPopup *new_popup) { + delete popup; + + if (new_popup) { + Object obj1; + Ref popupRef = new_popup->getRef(); + + obj1.initRef (popupRef.num, popupRef.gen); + update ("Popup", &obj1); + + new_popup->setParent(this); + popup = new_popup; + } else { + popup = NULL; + } +} + +void AnnotMarkup::setOpacity(double opacityA) { + Object obj1; + + opacity = opacityA; + obj1.initReal(opacity); + update ("CA", &obj1); + invalidateAppearance(); +} + +void AnnotMarkup::setDate(GooString *new_date) { + delete date; + + if (new_date) + date = new GooString(new_date); + else + date = new GooString(); + + Object obj1; + obj1.initString(date->copy()); + update ("CreationDate", &obj1); +} + +void AnnotMarkup::removeReferencedObjects() { + Page *pageobj = doc->getPage(page); + assert(pageobj != NULL); // We're called when removing an annot from a page + + // Remove popup + if (popup) { + pageobj->removeAnnot(popup); + } + + Annot::removeReferencedObjects(); +} + +//------------------------------------------------------------------------ +// AnnotText +//------------------------------------------------------------------------ + +AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rect) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeText; + flags |= flagNoZoom | flagNoRotate; + + annotObj.dictSet ("Subtype", obj1.initName ("Text")); + initialize (docA, annotObj.getDict()); +} + +AnnotText::AnnotText(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + + type = typeText; + flags |= flagNoZoom | flagNoRotate; + initialize (docA, dict); +} + +AnnotText::~AnnotText() { + delete icon; +} + +void AnnotText::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + if (dict->lookup("Open", &obj1)->isBool()) + open = obj1.getBool(); + else + open = gFalse; + obj1.free(); + + if (dict->lookup("Name", &obj1)->isName()) { + icon = new GooString(obj1.getName()); + } else { + icon = new GooString("Note"); + } + obj1.free(); + + if (dict->lookup("StateModel", &obj1)->isString()) { + Object obj2; + GooString *modelName = obj1.getString(); + + if (dict->lookup("State", &obj2)->isString()) { + 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; + } + obj2.free(); + + 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; + } + obj1.free(); +} + +void AnnotText::setOpen(GBool openA) { + Object obj1; + + open = openA; + obj1.initBool(open); + update ("Open", &obj1); +} + +void AnnotText::setIcon(GooString *new_icon) { + if (new_icon && icon->cmp(new_icon) == 0) + return; + + delete icon; + + if (new_icon) { + icon = new GooString (new_icon); + } else { + icon = new GooString("Note"); + } + + Object obj1; + obj1.initName (icon->getCString()); + update("Name", &obj1); + 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, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + ca = opacity; + + appearBuf = new GooString (); + + appearBuf->append ("q\n"); + if (color) + setColor(color, gTrue); + else + appearBuf->append ("1 1 1 rg\n"); + if (!icon->cmp("Note")) + appearBuf->append (ANNOT_TEXT_AP_NOTE); + else if (!icon->cmp("Comment")) + appearBuf->append (ANNOT_TEXT_AP_COMMENT); + else if (!icon->cmp("Key")) + appearBuf->append (ANNOT_TEXT_AP_KEY); + else if (!icon->cmp("Help")) + appearBuf->append (ANNOT_TEXT_AP_HELP); + else if (!icon->cmp("NewParagraph")) + appearBuf->append (ANNOT_TEXT_AP_NEW_PARAGRAPH); + else if (!icon->cmp("Paragraph")) + appearBuf->append (ANNOT_TEXT_AP_PARAGRAPH); + else if (!icon->cmp("Insert")) + appearBuf->append (ANNOT_TEXT_AP_INSERT); + else if (!icon->cmp("Cross")) + appearBuf->append (ANNOT_TEXT_AP_CROSS); + else if (!icon->cmp("Circle")) + appearBuf->append (ANNOT_TEXT_AP_CIRCLE); + appearBuf->append ("Q\n"); + + // Force 24x24 rectangle + PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2); + appearBBox = new AnnotAppearanceBBox(&fixedRect); + double bbox[4]; + appearBBox->getBBoxRect(bbox); + if (ca == 1) { + createForm(bbox, gFalse, NULL, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (appearBBox) { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + appearBBox->getPageXMin(), appearBBox->getPageYMin(), + appearBBox->getPageXMax(), appearBBox->getPageYMax(), + getRotation()); + } else { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + } + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotLink +//------------------------------------------------------------------------ +AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rect) : + Annot(docA, rect) { + Object obj1; + + type = typeLink; + annotObj.dictSet ("Subtype", obj1.initName ("Link")); + initialize (docA, annotObj.getDict()); +} + +AnnotLink::AnnotLink(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + + type = typeLink; + initialize (docA, dict); +} + +AnnotLink::~AnnotLink() { + delete action; + /* + if (uriAction) + delete uriAction; + */ + if (quadrilaterals) + delete quadrilaterals; +} + +void AnnotLink::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + action = NULL; + + // look for destination + if (!dict->lookup("Dest", &obj1)->isNull()) { + action = LinkAction::parseDest(&obj1); + // look for action + } else { + obj1.free(); + if (dict->lookup("A", &obj1)->isDict()) { + action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); + } + } + obj1.free(); + + if (dict->lookup("H", &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.free(); + /* + if (dict->lookup("PA", &obj1)->isDict()) { + uriAction = NULL; + } else { + uriAction = NULL; + } + obj1.free(); + */ + if (dict->lookup("QuadPoints", &obj1)->isArray()) { + quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect); + } else { + quadrilaterals = NULL; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); +} + +void AnnotLink::draw(Gfx *gfx, GBool printing) { + Object obj; + + if (!isVisible (printing)) + return; + + annotLocker(); + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, border, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotFreeText +//------------------------------------------------------------------------ +AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rect, GooString *da) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeFreeText; + + annotObj.dictSet ("Subtype", obj1.initName ("FreeText")); + + Object obj2; + obj2.initString (da->copy()); + annotObj.dictSet("DA", &obj2); + + initialize (docA, annotObj.getDict()); +} + +AnnotFreeText::AnnotFreeText(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeFreeText; + initialize(docA, dict); +} + +AnnotFreeText::~AnnotFreeText() { + delete appearanceString; + + if (styleString) + delete styleString; + + if (calloutLine) + delete calloutLine; + + if (borderEffect) + delete borderEffect; + + if (rectangle) + delete rectangle; +} + +void AnnotFreeText::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + if (dict->lookup("DA", &obj1)->isString()) { + appearanceString = obj1.getString()->copy(); + } else { + appearanceString = new GooString(); + error(errSyntaxError, -1, "Bad appearance for annotation"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("Q", &obj1)->isInt()) { + quadding = (AnnotFreeTextQuadding) obj1.getInt(); + } else { + quadding = quaddingLeftJustified; + } + obj1.free(); + + if (dict->lookup("DS", &obj1)->isString()) { + styleString = obj1.getString()->copy(); + } else { + styleString = NULL; + } + obj1.free(); + + if (dict->lookup("CL", &obj1)->isArray() && obj1.arrayGetLength() >= 4) { + double x1, y1, x2, y2; + Object obj2; + + (obj1.arrayGet(0, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0); + obj2.free(); + (obj1.arrayGet(2, &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0); + obj2.free(); + (obj1.arrayGet(3, &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0); + obj2.free(); + + if (obj1.arrayGetLength() == 6) { + double x3, y3; + (obj1.arrayGet(4, &obj2)->isNum() ? x3 = obj2.getNum() : x3 = 0); + obj2.free(); + (obj1.arrayGet(5, &obj2)->isNum() ? y3 = obj2.getNum() : y3 = 0); + obj2.free(); + calloutLine = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3); + } else { + calloutLine = new AnnotCalloutLine(x1, y1, x2, y2); + } + } else { + calloutLine = NULL; + } + obj1.free(); + + if (dict->lookup("IT", &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.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); + + if (dict->lookup("BE", &obj1)->isDict()) { + borderEffect = new AnnotBorderEffect(obj1.getDict()); + } else { + borderEffect = NULL; + } + obj1.free(); + + if (dict->lookup("RD", &obj1)->isArray()) { + rectangle = parseDiffRectangle(obj1.getArray(), rect); + } else { + rectangle = NULL; + } + obj1.free(); + + if (dict->lookup("LE", &obj1)->isName()) { + GooString styleName(obj1.getName()); + endStyle = parseAnnotLineEndingStyle(&styleName); + } else { + endStyle = annotLineEndingNone; + } + obj1.free(); +} + +void AnnotFreeText::setContents(GooString *new_content) { + Annot::setContents(new_content); + invalidateAppearance(); +} + +void AnnotFreeText::setAppearanceString(GooString *new_string) { + delete appearanceString; + + if (new_string) { + appearanceString = new GooString(new_string); + } else { + appearanceString = new GooString(); + } + + Object obj1; + obj1.initString(appearanceString->copy()); + update ("DA", &obj1); + invalidateAppearance(); +} + +void AnnotFreeText::setQuadding(AnnotFreeTextQuadding new_quadding) { + Object obj1; + quadding = new_quadding; + obj1.initInt((int)quadding); + update ("Q", &obj1); + invalidateAppearance(); +} + +void AnnotFreeText::setStyleString(GooString *new_string) { + delete styleString; + + if (new_string) { + styleString = new GooString(new_string); + //append the unicode marker <FE FF> if needed + if (!styleString->hasUnicodeMarker()) { + styleString->insert(0, 0xff); + styleString->insert(0, 0xfe); + } + } else { + styleString = new GooString(); + } + + Object obj1; + obj1.initString(styleString->copy()); + update ("DS", &obj1); +} + +void AnnotFreeText::setCalloutLine(AnnotCalloutLine *line) { + delete calloutLine; + + Object obj1; + if (line == NULL) { + obj1.initNull(); + calloutLine = NULL; + } else { + double x1 = line->getX1(), y1 = line->getY1(); + double x2 = line->getX2(), y2 = line->getY2(); + Object obj2; + obj1.initArray(xref); + obj1.arrayAdd( obj2.initReal(x1) ); + obj1.arrayAdd( obj2.initReal(y1) ); + obj1.arrayAdd( obj2.initReal(x2) ); + obj1.arrayAdd( obj2.initReal(y2) ); + + AnnotCalloutMultiLine *mline = dynamic_cast<AnnotCalloutMultiLine*>(line); + if (mline) { + double x3 = mline->getX3(), y3 = mline->getY3(); + obj1.arrayAdd( obj2.initReal(x3) ); + obj1.arrayAdd( obj2.initReal(y3) ); + calloutLine = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3); + } else { + calloutLine = new AnnotCalloutLine(x1, y1, x2, y2); + } + } + + update("CL", &obj1); + invalidateAppearance(); +} + +void AnnotFreeText::setIntent(AnnotFreeTextIntent new_intent) { + Object obj1; + + intent = new_intent; + if (new_intent == intentFreeText) + obj1.initName("FreeText"); + else if (new_intent == intentFreeTextCallout) + obj1.initName("FreeTextCallout"); + else // intentFreeTextTypeWriter + obj1.initName("FreeTextTypeWriter"); + update ("IT", &obj1); +} + +static GfxFont * createAnnotDrawFont(XRef * xref, Object *fontResDict) +{ + Ref dummyRef = { -1, -1 }; + + Object baseFontObj, subtypeObj, encodingObj; + baseFontObj.initName("Helvetica"); + subtypeObj.initName("Type0"); + encodingObj.initName("WinAnsiEncoding"); + + Object fontDictObj; + Dict *fontDict = new Dict(xref); + fontDict->decRef(); + fontDict->add(copyString("BaseFont"), &baseFontObj); + fontDict->add(copyString("Subtype"), &subtypeObj); + fontDict->add(copyString("Encoding"), &encodingObj); + fontDictObj.initDict(fontDict); + + Object fontsDictObj; + Dict *fontsDict = new Dict(xref); + fontsDict->decRef(); + fontsDict->add(copyString("AnnotDrawFont"), &fontDictObj); + fontsDictObj.initDict(fontsDict); + + Dict *dict = new Dict(xref); + dict->add(copyString("Font"), &fontsDictObj); + + fontResDict->initDict(dict); + return GfxFont::makeFont(xref, "AnnotDrawFont", dummyRef, fontDict); +} + +void AnnotFreeText::parseAppearanceString(GooString *da, double &fontsize, AnnotColor* &fontcolor) { + fontsize = -1; + fontcolor = NULL; + if (da) { + GooList * daToks = new GooList(); + int j, i = 0; + + // Tokenize + 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->append(new GooString(da, i, j - i)); + i = j; + } + } + + // Scan backwards: we are looking for the last set value + for (i = daToks->getLength()-1; i >= 0; --i) { + if (fontsize == -1) { + if (!((GooString *)daToks->get(i))->cmp("Tf") && i >= 2) { + // TODO: Font name + fontsize = gatof(( (GooString *)daToks->get(i-1) )->getCString()); + } + } + if (fontcolor == NULL) { + if (!((GooString *)daToks->get(i))->cmp("g") && i >= 1) { + fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-1) )->getCString())); + } else if (!((GooString *)daToks->get(i))->cmp("rg") && i >= 3) { + fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-3) )->getCString()), + gatof(( (GooString *)daToks->get(i-2) )->getCString()), + gatof(( (GooString *)daToks->get(i-1) )->getCString())); + } else if (!((GooString *)daToks->get(i))->cmp("k") && i >= 4) { + fontcolor = new AnnotColor(gatof(( (GooString *)daToks->get(i-4) )->getCString()), + gatof(( (GooString *)daToks->get(i-3) )->getCString()), + gatof(( (GooString *)daToks->get(i-2) )->getCString()), + gatof(( (GooString *)daToks->get(i-1) )->getCString())); + } + } + } + deleteGooList(daToks, GooString); + } +} + +void AnnotFreeText::generateFreeTextAppearance() +{ + double borderWidth, ca = opacity; + + appearBuf = new GooString (); + appearBuf->append ("q\n"); + + borderWidth = border->getWidth(); + if (borderWidth > 0) + setLineStyleForBorder(border); + + // Box size + const double width = rect->x2 - rect->x1; + const double height = rect->y2 - rect->y1; + + // Parse some properties from the appearance string + double fontsize; + AnnotColor *fontcolor; + parseAppearanceString(appearanceString, fontsize, fontcolor); + // Default values + if (fontsize <= 0) + fontsize = 10; + if (fontcolor == NULL) + fontcolor = new AnnotColor(0, 0, 0); // Black + if (!contents) + contents = new GooString (); + + // Draw box + GBool doFill = (color && color->getSpace() != AnnotColor::colorTransparent); + GBool doStroke = (borderWidth != 0); + if (doFill || doStroke) { + if (doStroke) { + setColor(fontcolor, gFalse); // Border color: same as font color + } + appearBuf->appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re\n", borderWidth/2, width-borderWidth, height-borderWidth); + if (doFill) { + setColor(color, gTrue); + appearBuf->append(doStroke ? "B\n" : "f\n"); + } else { + appearBuf->append("S\n"); + } + } + + // Setup text clipping + const double textmargin = borderWidth * 2; + const double textwidth = width - 2*textmargin; + appearBuf->appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", textmargin, textwidth, height - 2*textmargin); + + Object fontResDict; + GfxFont *font = createAnnotDrawFont(xref, &fontResDict); + + // Set font state + setColor(fontcolor, gTrue); + appearBuf->appendf ("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - fontsize * font->getDescent()); + appearBuf->appendf ("/AnnotDrawFont {0:.2f} Tf\n", fontsize); + + int i = 0; + double xposPrev = 0; + while (i < contents->getLength()) { + GooString out; + double linewidth, xpos; + layoutText(contents, &out, &i, font, &linewidth, textwidth/fontsize, NULL, gFalse); + linewidth *= fontsize; + switch (quadding) { + case quaddingCentered: + xpos = (textwidth - linewidth) / 2; + break; + case quaddingRightJustified: + xpos = textwidth - linewidth; + break; + default: // quaddingLeftJustified: + xpos = 0; + break; + } + appearBuf->appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize); + writeString(&out, appearBuf); + appearBuf->append("Tj\n"); + xposPrev = xpos; + } + + font->decRefCnt(); + delete fontcolor; + appearBuf->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) { + createForm(bbox, gFalse, &fontResDict, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, &fontResDict, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; +} + +void AnnotFreeText::draw(Gfx *gfx, GBool printing) { + Object obj; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + generateFreeTextAppearance(); + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +// 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(Object *dest) { + if (appearance.isNull()) { + generateFreeTextAppearance(); + } + return Annot::getAppearanceResDict(dest); +} + +//------------------------------------------------------------------------ +// AnnotLine +//------------------------------------------------------------------------ + +AnnotLine::AnnotLine(PDFDoc *docA, PDFRectangle *rect) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeLine; + annotObj.dictSet ("Subtype", obj1.initName ("Line")); + + initialize (docA, annotObj.getDict()); +} + +AnnotLine::AnnotLine(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeLine; + initialize(docA, dict); +} + +AnnotLine::~AnnotLine() { + delete coord1; + delete coord2; + + if (interiorColor) + delete interiorColor; + + if (measure) + delete measure; +} + +void AnnotLine::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + if (dict->lookup("L", &obj1)->isArray() && obj1.arrayGetLength() == 4) { + Object obj2; + double x1, y1, x2, y2; + + (obj1.arrayGet(0, &obj2)->isNum() ? x1 = obj2.getNum() : x1 = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? y1 = obj2.getNum() : y1 = 0); + obj2.free(); + (obj1.arrayGet(2, &obj2)->isNum() ? x2 = obj2.getNum() : x2 = 0); + obj2.free(); + (obj1.arrayGet(3, &obj2)->isNum() ? y2 = obj2.getNum() : y2 = 0); + obj2.free(); + + coord1 = new AnnotCoord(x1, y1); + coord2 = new AnnotCoord(x2, y2); + } else { + coord1 = new AnnotCoord(); + coord2 = new AnnotCoord(); + } + obj1.free(); + + if (dict->lookup("LE", &obj1)->isArray() && obj1.arrayGetLength() == 2) { + Object obj2; + + if(obj1.arrayGet(0, &obj2)->isString()) + startStyle = parseAnnotLineEndingStyle(obj2.getString()); + else + startStyle = annotLineEndingNone; + obj2.free(); + + if(obj1.arrayGet(1, &obj2)->isString()) + endStyle = parseAnnotLineEndingStyle(obj2.getString()); + else + endStyle = annotLineEndingNone; + obj2.free(); + + } else { + startStyle = endStyle = annotLineEndingNone; + } + obj1.free(); + + if (dict->lookup("IC", &obj1)->isArray()) { + interiorColor = new AnnotColor(obj1.getArray()); + } else { + interiorColor = NULL; + } + obj1.free(); + + if (dict->lookup("LL", &obj1)->isNum()) { + leaderLineLength = obj1.getNum(); + } else { + leaderLineLength = 0; + } + obj1.free(); + + if (dict->lookup("LLE", &obj1)->isNum()) { + leaderLineExtension = obj1.getNum(); + + if (leaderLineExtension < 0) + leaderLineExtension = 0; + } else { + leaderLineExtension = 0; + } + obj1.free(); + + if (dict->lookup("Cap", &obj1)->isBool()) { + caption = obj1.getBool(); + } else { + caption = gFalse; + } + obj1.free(); + + if (dict->lookup("IT", &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; + } + obj1.free(); + + if (dict->lookup("LLO", &obj1)->isNum()) { + leaderLineOffset = obj1.getNum(); + + if (leaderLineOffset < 0) + leaderLineOffset = 0; + } else { + leaderLineOffset = 0; + } + obj1.free(); + + if (dict->lookup("CP", &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.free(); + + if (dict->lookup("Measure", &obj1)->isDict()) { + measure = NULL; + } else { + measure = NULL; + } + obj1.free(); + + if ((dict->lookup("CO", &obj1)->isArray()) && (obj1.arrayGetLength() == 2)) { + Object obj2; + + (obj1.arrayGet(0, &obj2)->isNum() ? captionTextHorizontal = obj2.getNum() : + captionTextHorizontal = 0); + obj2.free(); + (obj1.arrayGet(1, &obj2)->isNum() ? captionTextVertical = obj2.getNum() : + captionTextVertical = 0); + obj2.free(); + } else { + captionTextHorizontal = captionTextVertical = 0; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); +} + +void AnnotLine::setContents(GooString *new_content) { + Annot::setContents(new_content); + if (caption) + invalidateAppearance(); +} + +void AnnotLine::setVertices(double x1, double y1, double x2, double y2) { + Object obj1, obj2; + + delete coord1; + coord1 = new AnnotCoord(x1, y1); + delete coord2; + coord2 = new AnnotCoord(x2, y2); + + obj1.initArray(xref); + obj1.arrayAdd( obj2.initReal(x1) ); + obj1.arrayAdd( obj2.initReal(y1) ); + obj1.arrayAdd( obj2.initReal(x2) ); + obj1.arrayAdd( obj2.initReal(y2) ); + + update("L", &obj1); + invalidateAppearance(); +} + +void AnnotLine::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) { + Object obj1, obj2; + + startStyle = start; + endStyle = end; + + obj1.initArray(xref); + obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( startStyle )) ); + obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( endStyle )) ); + + update("LE", &obj1); + invalidateAppearance(); +} + +void AnnotLine::setInteriorColor(AnnotColor *new_color) { + delete interiorColor; + + if (new_color) { + Object obj1; + new_color->writeToObject(xref, &obj1); + update ("IC", &obj1); + interiorColor = new_color; + } else { + interiorColor = NULL; + } + invalidateAppearance(); +} + +void AnnotLine::setLeaderLineLength(double len) { + Object obj1; + + leaderLineLength = len; + obj1.initReal(len); + update ("LL", &obj1); + invalidateAppearance(); +} + +void AnnotLine::setLeaderLineExtension(double len) { + Object obj1; + + leaderLineExtension = len; + obj1.initReal(len); + update ("LLE", &obj1); + + // LL is required if LLE is present + obj1.initReal(leaderLineLength); + update ("LL", &obj1); + invalidateAppearance(); +} + +void AnnotLine::setCaption(bool new_cap) { + Object obj1; + + caption = new_cap; + obj1.initBool(new_cap); + update ("Cap", &obj1); + invalidateAppearance(); +} + +void AnnotLine::setIntent(AnnotLineIntent new_intent) { + Object obj1; + + intent = new_intent; + if (new_intent == intentLineArrow) + obj1.initName("LineArrow"); + else // intentLineDimension + obj1.initName("LineDimension"); + update ("IT", &obj1); +} + +void AnnotLine::generateLineAppearance() +{ + double borderWidth, ca = opacity; + + appearBBox = new AnnotAppearanceBBox(rect); + appearBuf = new GooString (); + appearBuf->append ("q\n"); + if (color) { + setColor(color, gFalse); + } + + setLineStyleForBorder(border); + 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; + + Object fontResDict; + GfxFont *font; + + // Calculate caption width and height + if (caption) { + font = createAnnotDrawFont(xref, &fontResDict); + int lines = 0; + int i = 0; + while (i < contents->getLength()) { + GooString out; + double linewidth; + layoutText(contents, &out, &i, font, &linewidth, 0, NULL, gFalse); + 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.initNull(); + font = NULL; + } + + // Draw main segment + matr.transform (0, leaderLineLength, &tx, &ty); + appearBuf->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); + appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty); + + matr.transform ((main_len+captionwidth)/2 + captionhmargin, leaderLineLength, &tx, &ty); + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty); + } + + matr.transform (main_len, leaderLineLength, &tx, &ty); + appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty); + appearBBox->extendTo (tx, ty); + + // TODO: Line endings + + // 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) + appearBuf->appendf ("0 g BT /AnnotDrawFont {0:.2f} Tf\n", fontsize); // Font color: black + appearBuf->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); + appearBuf->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, &out, &i, font, &linewidth, 0, NULL, gFalse); + linewidth *= fontsize; + xpos = (captionwidth - linewidth) / 2; + appearBuf->appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize); + writeString(&out, appearBuf); + appearBuf->append ("Tj\n"); + xposPrev = xpos; + } + appearBuf->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); + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty); + appearBBox->extendTo (tx, ty); + matr.transform (0, sign*ll_len, &tx, &ty); + appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty); + appearBBox->extendTo (tx, ty); + + matr.transform (main_len, 0, &tx, &ty); + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", tx, ty); + appearBBox->extendTo (tx, ty); + matr.transform (main_len, sign*ll_len, &tx, &ty); + appearBuf->appendf ("{0:.2f} {1:.2f} l S\n", tx, ty); + appearBBox->extendTo (tx, ty); + } + + appearBuf->append ("Q\n"); + + double bbox[4]; + appearBBox->getBBoxRect(bbox); + if (ca == 1) { + createForm(bbox, gFalse, &fontResDict, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, &fontResDict, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; +} + +void AnnotLine::draw(Gfx *gfx, GBool printing) { + Object obj; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + generateLineAppearance(); + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (appearBBox) { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + appearBBox->getPageXMin(), appearBBox->getPageYMin(), + appearBBox->getPageXMax(), appearBBox->getPageYMax(), + getRotation()); + } else { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + } + obj.free(); +} + +// 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(Object *dest) { + if (appearance.isNull()) { + generateLineAppearance(); + } + return Annot::getAppearanceResDict(dest); +} + +//------------------------------------------------------------------------ +// AnnotTextMarkup +//------------------------------------------------------------------------ +AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) : + AnnotMarkup(docA, rect) { + Object obj1; + + switch (subType) { + case typeHighlight: + annotObj.dictSet ("Subtype", obj1.initName ("Highlight")); + break; + case typeUnderline: + annotObj.dictSet ("Subtype", obj1.initName ("Underline")); + break; + case typeSquiggly: + annotObj.dictSet ("Subtype", obj1.initName ("Squiggly")); + break; + case typeStrikeOut: + annotObj.dictSet ("Subtype", obj1.initName ("StrikeOut")); + break; + default: + assert (0 && "Invalid subtype for AnnotTextMarkup\n"); + } + + // Store dummy quadrilateral with null coordinates + Object obj2, obj3; + obj2.initArray (doc->getXRef()); + for (int i = 0; i < 4*2; ++i) { + obj2.arrayAdd (obj3.initReal (0)); + } + annotObj.dictSet ("QuadPoints", &obj2); + + initialize(docA, annotObj.getDict()); +} + +AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + // the real type will be read in initialize() + type = typeHighlight; + initialize(docA, dict); +} + +void AnnotTextMarkup::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + if (dict->lookup("Subtype", &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.free(); + + if(dict->lookup("QuadPoints", &obj1)->isArray()) { + quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect); + } else { + error(errSyntaxError, -1, "Bad Annot Text Markup QuadPoints"); + quadrilaterals = NULL; + ok = gFalse; + } + obj1.free(); +} + +AnnotTextMarkup::~AnnotTextMarkup() { + if(quadrilaterals) { + delete quadrilaterals; + } +} + +void AnnotTextMarkup::setType(AnnotSubtype new_type) { + Object obj1; + + switch (new_type) { + case typeHighlight: + obj1.initName("Highlight"); + break; + case typeUnderline: + obj1.initName("Underline"); + break; + case typeSquiggly: + obj1.initName("Squiggly"); + break; + case typeStrikeOut: + obj1.initName("StrikeOut"); + break; + default: + assert(!"Invalid subtype"); + } + + type = new_type; + update("Subtype", &obj1); + invalidateAppearance(); +} + +void AnnotTextMarkup::setQuadrilaterals(AnnotQuadrilaterals *quadPoints) { + Object obj1, obj2; + obj1.initArray (xref); + + for (int i = 0; i < quadPoints->getQuadrilateralsLength(); ++i) { + obj1.arrayAdd (obj2.initReal (quadPoints->getX1(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getY1(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getX2(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getY2(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getX3(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getY3(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getX4(i))); + obj1.arrayAdd (obj2.initReal (quadPoints->getY4(i))); + } + + delete quadrilaterals; + quadrilaterals = new AnnotQuadrilaterals(obj1.getArray(), rect); + + annotObj.dictSet ("QuadPoints", &obj1); + invalidateAppearance(); +} + +void AnnotTextMarkup::draw(Gfx *gfx, GBool printing) { + Object obj; + double ca = 1; + int i; + Object obj1, obj2; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull() || type == typeHighlight) { + GBool blendMultiply = gTrue; + ca = opacity; + + appearBuf = new GooString (); + appearBuf->append ("q\n"); + + /* Adjust BBox */ + delete appearBBox; + appearBBox = new AnnotAppearanceBBox(rect); + 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) { + setColor(color, gFalse); + } + appearBuf->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); + + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x3, y3); + appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x4, y4); + appearBuf->append ("S\n"); + } + break; + case typeStrikeOut: + if (color) { + setColor(color, gFalse); + } + blendMultiply = gFalse; + appearBuf->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); + + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", (x1+x3)/2., (y1+y3)/2.); + appearBuf->appendf ("{0:.2f} {1:.2f} l\n", (x2+x4)/2., (y2+y4)/2.); + appearBuf->append ("S\n"); + } + break; + case typeSquiggly: + if (color) { + setColor(color, gFalse); + } + appearBuf->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; + + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y3+h6); + bool down = false; + do { + down = !down; // Zigzag line + x1 += 2; + appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x1, y3 + (down ? 0 : h6)); + } while (x1 < x2); + appearBuf->append ("S\n"); + } + break; + default: + case typeHighlight: + appearance.free(); + + if (color) + setColor(color, gTrue); + + 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; + } + + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x3, y3); + appearBuf->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); + appearBuf->appendf ("{0:.2f} {1:.2f} l\n", x2, y2); + appearBuf->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); + appearBuf->append ("f\n"); + } + appearBBox->setBorderWidth(biggestBorder); + break; + } + appearBuf->append ("Q\n"); + + Object aStream, resDict; + double bbox[4]; + bbox[0] = appearBBox->getPageXMin(); + bbox[1] = appearBBox->getPageYMin(); + bbox[2] = appearBBox->getPageXMax(); + bbox[3] = appearBBox->getPageYMax(); + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", 1, blendMultiply ? "Multiply" : NULL, &resDict); + if (ca == 1) { + createForm(bbox, gFalse, &resDict, &appearance); + } else { + createForm(bbox, gTrue, &resDict, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (appearBBox) { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + appearBBox->getPageXMin(), appearBBox->getPageYMin(), + appearBBox->getPageXMax(), appearBBox->getPageYMax(), + getRotation()); + } else { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + } + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotWidget +//------------------------------------------------------------------------ + +AnnotWidget::AnnotWidget(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + type = typeWidget; + field = NULL; + initialize(docA, dict); +} + +AnnotWidget::AnnotWidget(PDFDoc *docA, Dict *dict, Object *obj, FormField *fieldA) : + Annot(docA, dict, obj) { + type = typeWidget; + field = fieldA; + initialize(docA, dict); +} + +AnnotWidget::~AnnotWidget() { + if (appearCharacs) + delete appearCharacs; + + if (action) + delete action; + + additionalActions.free(); + + if (parent) + delete parent; +} + +void AnnotWidget::initialize(PDFDoc *docA, Dict *dict) { + Object obj1; + + form = doc->getCatalog()->getForm(); + + if(dict->lookup("H", &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.free(); + + if(dict->lookup("MK", &obj1)->isDict()) { + appearCharacs = new AnnotAppearanceCharacs(obj1.getDict()); + } else { + appearCharacs = NULL; + } + obj1.free(); + + action = NULL; + if(dict->lookup("A", &obj1)->isDict()) { + action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); + } + obj1.free(); + + dict->lookupNF("AA", &additionalActions); + + if(dict->lookup("Parent", &obj1)->isDict()) { + parent = NULL; + } else { + parent = NULL; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } + obj1.free(); + + updatedAppearanceStream.num = updatedAppearanceStream.gen = -1; +} + +LinkAction* AnnotWidget::getAdditionalAction(AdditionalActionsType type) +{ + return ::getAdditionalAction(type, &additionalActions, doc); +} + +LinkAction* AnnotWidget::getFormAdditionalAction(FormAdditionalActionsType type) +{ + return ::getFormAdditionalAction(type, &additionalActions, doc); +} + +// 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(GooString *text, GooString *outBuf, int *i, + GfxFont *font, double *width, double widthLimit, + int *charCount, GBool noReencode) +{ + CharCode c; + Unicode uChar, *uAux; + double w = 0.0; + int uLen, n; + double dx, dy, ox, oy; + GBool unicode = text->hasUnicodeMarker(); + GBool 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 = gFalse; + 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 <CR><LF> 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 { + 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, 2)) { + ccToUnicode->decRefCnt(); + 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 { + ccToUnicode->decRefCnt(); + 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 = gTrue; + } else { + spacePrev = gFalse; + } + + // Compute width of character just output + if (outBuf->getLength() > last_o2) { + dx = 0.0; + font->getNextChar(outBuf->getCString() + 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 != NULL || charCount != NULL) { + char *s = outBuf->getCString(); + int len = outBuf->getLength(); + + if (width != NULL) + *width = 0.0; + if (charCount != NULL) + *charCount = 0; + + while (len > 0) { + dx = 0.0; + n = font->getNextChar(s, len, &c, &uAux, &uLen, &dx, &dy, &ox, &oy); + + if (n == 0) { + break; + } + + if (width != NULL) + *width += dx; + if (charCount != NULL) + *charCount += 1; + + s += n; + len -= n; + } + } +} + +// Copy the given string to appearBuf, adding parentheses around it and +// escaping characters as appropriate. +void Annot::writeString(GooString *str, GooString *appearBuf) +{ + 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. +void AnnotWidget::drawText(GooString *text, GooString *da, GfxResources *resources, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats, + GBool password) { + GooList *daToks; + GooString *tok, *convertedText; + GfxFont *font; + double dx, dy; + double fontSize, fontSize2, borderWidth, x, xPrev, y, w, wMax; + int tfPos, tmPos, i, j; + int rot; + GBool freeText = gFalse; // true if text should be freed before return + GBool freeFont = gFalse; + + //~ 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 GooList(); + 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->append(new GooString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GooString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GooString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // force ZapfDingbats + if (forceZapfDingbats) { + if (tfPos >= 0) { + tok = (GooString *)daToks->get(tfPos); + if (tok->cmp("/ZaDb")) { + tok->clear(); + tok->append("/ZaDb"); + } + } + } + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GooString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!resources || !(font = resources->lookupFont(tok->getCString() + 1))) { + if (forceZapfDingbats) { + // We are forcing ZaDb but the font does not exist + // so create a fake one + Ref r; // dummy Ref, it's not used at all in this codepath + r.num = -1; + r.gen = -1; + Dict *d = new Dict(xref); + font = new Gfx8BitFont(xref, "ZaDb", r, new GooString("ZapfDingbats"), fontType1, r, d); + delete d; + freeFont = gTrue; + addDingbatsResource = gTrue; + } 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 = (GooString *)daToks->get(tfPos + 1); + fontSize = gatof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + if (!font) { + if (daToks) { + deleteGooList(daToks, GooString); + } + return; + } + + // 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(); + + text = new GooString; + for (i = 0; i < len; ++i) + text->append('*'); + freeText = gTrue; + } + + convertedText = new GooString; + + // 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; + i = 0; + while (i < text->getLength()) { + layoutText(text, convertedText, &i, font, &w, wMax / fontSize, NULL, + forceZapfDingbats); + y -= fontSize; + } + // approximate the descender for the last line + if (y >= 0.33 * fontSize) { + break; + } + } + if (tfPos >= 0) { + tok = (GooString *)daToks->get(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 = (GooString *)daToks->get(tmPos + 4); + tok->clear(); + tok->append('0'); + tok = (GooString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.2f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GooString *)daToks->get(i))->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 + i = 0; + xPrev = 0; + while (i < text->getLength()) { + layoutText(text, convertedText, &i, font, &w, wMax / fontSize, NULL, + 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); + 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 = (GooString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.2f}", fontSize); + } + } + + i = 0; + layoutText(text, convertedText, &i, font, NULL, 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 * 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 = (GooString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.2f}", x); + tok = (GooString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.2f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GooString *)daToks->get(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 + char *s = convertedText->getCString(); + int len = convertedText->getLength(); + i = 0; + xPrev = w; // so that first character is placed properly + while (i < comb && len > 0) { + CharCode code; + Unicode *uAux; + int uLen, n; + double dx, dy, ox, oy; + + dx = 0.0; + n = font->getNextChar(s, len, &code, &uAux, &uLen, &dx, &dy, &ox, &oy); + 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 - dx); + appearBuf->appendf("{0:.2f} 0 Td\n", x - xPrev + w); + + GooString *charBuf = new GooString(s, n); + writeString(charBuf, appearBuf); + appearBuf->append(" Tj\n"); + delete charBuf; + + i++; + s += n; + len -= n; + xPrev = x; + } + + // regular (non-comb) formatting + } else { + i = 0; + layoutText(text, convertedText, &i, font, &w, 0.0, NULL, + forceZapfDingbats); + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * borderWidth; + fontSize2 = (dx - 4 - 2 * borderWidth) / w; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GooString *)daToks->get(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 = (GooString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.2f}", x); + tok = (GooString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.2f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GooString *)daToks->get(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 + writeString(convertedText, appearBuf); + appearBuf->append(" Tj\n"); + } + } + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + if (txField) { + appearBuf->append("EMC\n"); + } + if (daToks) { + deleteGooList(daToks, GooString); + } + if (freeText) { + delete text; + } + delete convertedText; + if (freeFont) { + font->decRefCnt(); + } +} + +// Draw the variable text or caption for a field. +void AnnotWidget::drawListBox(FormFieldChoice *fieldChoice, + GooString *da, GfxResources *resources, int quadding) { + GooList *daToks; + GooString *tok, *convertedText; + GfxFont *font; + double fontSize, fontSize2, 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 GooList(); + 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->append(new GooString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GooString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GooString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GooString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!resources || !(font = resources->lookupFont(tok->getCString() + 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 = (GooString *)daToks->get(tfPos + 1); + fontSize = gatof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + if (!font) { + if (daToks) { + deleteGooList(daToks, GooString); + } + return; + } + + convertedText = new GooString; + + // 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) == NULL) { + error(errSyntaxError, -1, "Invalid annotation listbox"); + if (daToks) { + deleteGooList(daToks, GooString); + } + delete convertedText; + return; + } + layoutText(fieldChoice->getChoice(i), convertedText, &j, font, &w, 0.0, NULL, gFalse); + if (w > wMax) { + wMax = w; + } + } + fontSize = rect->y2 - rect->y1 - 2 * borderWidth; + fontSize2 = (rect->x2 - rect->x1 - 4 - 2 * borderWidth) / wMax; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GooString *)daToks->get(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; + layoutText(fieldChoice->getChoice(i), convertedText, &j, font, &w, 0.0, NULL, gFalse); + 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 = (GooString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.2f}", x); + tok = (GooString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.2f}", y); + } + + // write the DA string + if (daToks) { + for (j = 0; j < daToks->getLength(); ++j) { + appearBuf->append((GooString *)daToks->get(j))->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); + appearBuf->append(" Tj\n"); + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + + // next line + y -= 1.1 * fontSize; + } + + if (daToks) { + deleteGooList(daToks, GooString); + } + + delete convertedText; +} + +void AnnotWidget::drawBorder() { + int dashLength; + double *dash; + AnnotColor adjustedColor; + double w = border->getWidth(); + + AnnotColor *aColor = appearCharacs->getBorderColor(); + if (!aColor) + aColor = appearCharacs->getBackColor(); + if (!aColor) + return; + + double dx = rect->x2 - rect->x1; + double dy = rect->y2 - rect->y1; + + // radio buttons with no caption have a round border + GBool hasCaption = appearCharacs->getNormalCaption() != NULL; + if (field->getType() == formButton && + static_cast<FormFieldButton*>(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"); + // fall through to the solid case + case AnnotBorder::borderSolid: + case AnnotBorder::borderUnderlined: + appearBuf->appendf("{0:.2f} w\n", w); + setColor(aColor, gFalse); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse); + break; + case AnnotBorder::borderBeveled: + case AnnotBorder::borderInset: + appearBuf->appendf("{0:.2f} w\n", 0.5 * w); + setColor(aColor, gFalse); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse); + adjustedColor = AnnotColor(*aColor); + adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? 1 : -1); + setColor(&adjustedColor, gFalse); + drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); + adjustedColor = AnnotColor(*aColor); + adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1); + setColor(&adjustedColor, gFalse); + 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"); + // fall through to the solid case + case AnnotBorder::borderSolid: + appearBuf->appendf("{0:.2f} w\n", w); + setColor(aColor, gFalse); + 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); + setColor(&adjustedColor, gTrue); + 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); + setColor(&adjustedColor, gTrue); + 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); + setColor(aColor, gFalse); + 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); + } +} + +void AnnotWidget::drawFormFieldButton(GfxResources *resources, GooString *da) { + GooString *caption = NULL; + if (appearCharacs) + caption = appearCharacs->getNormalCaption(); + + switch (static_cast<FormFieldButton *>(field)->getButtonType()) { + case formButtonRadio: { + //~ Acrobat doesn't draw a caption if there is no AP dict (?) + if (appearState && appearState->cmp("Off") != 0 && + static_cast<FormFieldButton *>(field)->getState(appearState->getCString())) { + if (caption) { + drawText(caption, da, resources, gFalse, 0, fieldQuadCenter, + gFalse, gTrue); + } else if (appearCharacs) { + AnnotColor *aColor = appearCharacs->getBorderColor(); + if (aColor) { + double dx = rect->x2 - rect->x1; + double dy = rect->y2 - rect->y1; + setColor(aColor, gTrue); + drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), gTrue); + } + } + } + } + break; + case formButtonPush: + if (caption) + drawText(caption, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gFalse); + break; + case formButtonCheck: + if (appearState && appearState->cmp("Off") != 0) { + if (!caption) { + GooString checkMark("3"); + drawText(&checkMark, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gTrue); + } else { + drawText(caption, da, resources, gFalse, 0, fieldQuadCenter, gFalse, gTrue); + } + } + break; + } +} + +void AnnotWidget::drawFormFieldText(GfxResources *resources, GooString *da) { + VariableTextQuadding quadding; + GooString *contents; + FormFieldText *fieldText = static_cast<FormFieldText *>(field); + + contents = fieldText->getContent(); + if (contents) { + quadding = field->hasTextQuadding() ? field->getTextQuadding() : form->getTextQuadding(); + + int comb = 0; + if (fieldText->isComb()) + comb = fieldText->getMaxLen(); + + drawText(contents, da, resources, + fieldText->isMultiline(), comb, quadding, gTrue, gFalse, fieldText->isPassword()); + } +} + +void AnnotWidget::drawFormFieldChoice(GfxResources *resources, GooString *da) { + GooString *selected; + VariableTextQuadding quadding; + FormFieldChoice *fieldChoice = static_cast<FormFieldChoice *>(field); + + quadding = field->hasTextQuadding() ? field->getTextQuadding() : form->getTextQuadding(); + + if (fieldChoice->isCombo()) { + selected = fieldChoice->getSelectedChoice(); + if (selected) { + drawText(selected, da, resources, gFalse, 0, quadding, gTrue, gFalse); + //~ Acrobat draws a popup icon on the right side + } + // list box + } else { + drawListBox(fieldChoice, da, resources, quadding); + } +} + +void AnnotWidget::generateFieldAppearance() { + Object appearDict, obj1, obj2; + GfxResources *resources; + MemStream *appearStream; + GooString *da; + + appearBuf = new GooString (); + + // draw the background + if (appearCharacs) { + AnnotColor *aColor = appearCharacs->getBackColor(); + if (aColor) { + setColor(aColor, gTrue); + appearBuf->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) + drawBorder(); + + da = field->getDefaultAppearance(); + if (!da) + da = form->getDefaultAppearance(); + + resources = form->getDefaultResources(); + + // draw the field contents + switch (field->getType()) { + case formButton: + drawFormFieldButton(resources, da); + break; + case formText: + drawFormFieldText(resources, da); + break; + case formChoice: + drawFormFieldChoice(resources, da); + break; + case formSignature: + //~unimp + break; + case formUndef: + default: + error(errSyntaxError, -1, "Unknown field type"); + } + + // build the appearance stream dictionary + appearDict.initDict(xref); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(xref); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(rect->x2 - rect->x1)); + obj1.arrayAdd(obj2.initReal(rect->y2 - rect->y1)); + appearDict.dictAdd(copyString("BBox"), &obj1); + + // set the resource dictionary + Object *resDict = form->getDefaultResourcesObj(); + if (resDict->isDict()) { + appearDict.dictAdd(copyString("Resources"), resDict->copy(&obj1)); + } + + // build the appearance stream + appearStream = new MemStream(copyString(appearBuf->getCString()), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); + delete appearBuf; + + appearStream->setNeedFree(gTrue); +} + +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.num == -1) { + 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 + generateFieldAppearance(); + + // Fetch the appearance stream we've just created + Object obj1; + appearance.fetch(xref, &obj1); + + // 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.num == -1) { + // Write the appearance stream + updatedAppearanceStream = xref->addIndirectObject(&obj1); + obj1.free(); + + // Write the AP dictionary + Object obj2; + obj1.initDict(xref); + obj1.dictAdd(copyString("N"), obj2.initRef(updatedAppearanceStream.num, updatedAppearanceStream.gen)); + update("AP", &obj1); + + // Update our internal pointers to the appearance dictionary + appearStreams = new AnnotAppearance(doc, &obj1); + } else { + // Replace the existing appearance stream + xref->setModifiedObject(&obj1, updatedAppearanceStream); + obj1.free(); + } +} + +void AnnotWidget::draw(Gfx *gfx, GBool printing) { + Object obj; + + if (!isVisible (printing)) + return; + + annotLocker(); + addDingbatsResource = gFalse; + + // Only construct the appearance stream when + // - annot doesn't have an AP or + // - NeedAppearances is true + if (field) { + if (appearance.isNull() || (form && form->getNeedAppearances())) + generateFieldAppearance(); + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (addDingbatsResource) { + // We are forcing ZaDb but the font does not exist + // so create a fake one + Object baseFontObj, subtypeObj; + baseFontObj.initName("ZapfDingbats"); + subtypeObj.initName("Type1"); + + Object fontDictObj; + Dict *fontDict = new Dict(gfx->getXRef()); + fontDict->decRef(); + fontDict->add(copyString("BaseFont"), &baseFontObj); + fontDict->add(copyString("Subtype"), &subtypeObj); + fontDictObj.initDict(fontDict); + + Object fontsDictObj; + Dict *fontsDict = new Dict(gfx->getXRef()); + fontsDict->decRef(); + fontsDict->add(copyString("ZaDb"), &fontDictObj); + fontsDictObj.initDict(fontsDict); + + Dict *dict = new Dict(gfx->getXRef()); + dict->add(copyString("Font"), &fontsDictObj); + gfx->pushResources(dict); + delete dict; + } + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + if (addDingbatsResource) { + gfx->popResources(); + } + obj.free(); +} + + +//------------------------------------------------------------------------ +// AnnotMovie +//------------------------------------------------------------------------ +AnnotMovie::AnnotMovie(PDFDoc *docA, PDFRectangle *rect, Movie *movieA) : + Annot(docA, rect) { + Object obj1; + + type = typeMovie; + annotObj.dictSet ("Subtype", obj1.initName ("Movie")); + + movie = movieA->copy(); + // TODO: create movie dict from movieA + + initialize(docA, annotObj.getDict()); +} + +AnnotMovie::AnnotMovie(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + type = typeMovie; + initialize(docA, dict); +} + +AnnotMovie::~AnnotMovie() { + if (title) + delete title; + delete movie; +} + +void AnnotMovie::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("T", &obj1)->isString()) { + title = obj1.getString()->copy(); + } else { + title = NULL; + } + obj1.free(); + + Object movieDict; + if (dict->lookup("Movie", &movieDict)->isDict()) { + Object obj2; + dict->lookup("A", &obj2); + if (obj2.isDict()) + movie = new Movie (&movieDict, &obj2); + else + movie = new Movie (&movieDict); + if (!movie->isOk()) { + delete movie; + movie = NULL; + ok = gFalse; + } + obj2.free(); + } else { + error(errSyntaxError, -1, "Bad Annot Movie"); + movie = NULL; + ok = gFalse; + } + movieDict.free(); +} + +void AnnotMovie::draw(Gfx *gfx, GBool printing) { + Object obj; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull() && movie->getShowPoster()) { + int width, height; + Object poster; + movie->getPoster(&poster); + movie->getAspect(&width, &height); + + if (width != -1 && height != -1 && !poster.isNone()) { + MemStream *mStream; + + appearBuf = new GooString (); + 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"); + + Object imgDict; + imgDict.initDict(gfx->getXRef()); + imgDict.dictSet ("MImg", &poster); + + Object resDict; + resDict.initDict(gfx->getXRef()); + resDict.dictSet ("XObject", &imgDict); + + Object formDict, obj1, obj2; + formDict.initDict(gfx->getXRef()); + formDict.dictSet("Length", obj1.initInt(appearBuf->getLength())); + formDict.dictSet("Subtype", obj1.initName("Form")); + formDict.dictSet("Name", obj1.initName("FRM")); + obj1.initArray(gfx->getXRef()); + obj1.arrayAdd(obj2.initInt(0)); + obj1.arrayAdd(obj2.initInt(0)); + obj1.arrayAdd(obj2.initInt(width)); + obj1.arrayAdd(obj2.initInt(height)); + formDict.dictSet("BBox", &obj1); + obj1.initArray(gfx->getXRef()); + obj1.arrayAdd(obj2.initInt(1)); + obj1.arrayAdd(obj2.initInt(0)); + obj1.arrayAdd(obj2.initInt(0)); + obj1.arrayAdd(obj2.initInt(1)); + obj1.arrayAdd(obj2.initInt(-width / 2)); + obj1.arrayAdd(obj2.initInt(-height / 2)); + formDict.dictSet("Matrix", &obj1); + formDict.dictSet("Resources", &resDict); + + Object aStream; + mStream = new MemStream(copyString(appearBuf->getCString()), 0, + appearBuf->getLength(), &formDict); + mStream->setNeedFree(gTrue); + aStream.initStream(mStream); + delete appearBuf; + + Object objDict; + objDict.initDict(gfx->getXRef()); + objDict.dictSet ("FRM", &aStream); + + resDict.initDict(gfx->getXRef()); + resDict.dictSet ("XObject", &objDict); + + appearBuf = new GooString (); + 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; + createForm(bbox, gFalse, &resDict, &appearance); + delete appearBuf; + } + poster.free(); + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotScreen +//------------------------------------------------------------------------ +AnnotScreen::AnnotScreen(PDFDoc *docA, PDFRectangle *rect) : + Annot(docA, rect) { + Object obj1; + + type = typeScreen; + + annotObj.dictSet ("Subtype", obj1.initName ("Screen")); + initialize(docA, annotObj.getDict()); +} + +AnnotScreen::AnnotScreen(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + type = typeScreen; + initialize(docA, dict); +} + +AnnotScreen::~AnnotScreen() { + if (title) + delete title; + if (appearCharacs) + delete appearCharacs; + if (action) + delete action; + + additionalActions.free(); +} + +void AnnotScreen::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + title = NULL; + if (dict->lookup("T", &obj1)->isString()) { + title = obj1.getString()->copy(); + } + obj1.free(); + + action = NULL; + if (dict->lookup("A", &obj1)->isDict()) { + action = LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()); + if (action->getKind() == actionRendition && page == 0) { + error (errSyntaxError, -1, "Invalid Rendition action: associated screen annotation without P"); + delete action; + action = NULL; + ok = gFalse; + } + } + obj1.free(); + + dict->lookupNF("AA", &additionalActions); + + appearCharacs = NULL; + if(dict->lookup("MK", &obj1)->isDict()) { + appearCharacs = new AnnotAppearanceCharacs(obj1.getDict()); + } + obj1.free(); +} + +LinkAction* AnnotScreen::getAdditionalAction(AdditionalActionsType type) +{ + if (type == actionFocusIn || type == actionFocusOut) // not defined for screen annotation + return NULL; + + return ::getAdditionalAction(type, &additionalActions, doc); +} + +//------------------------------------------------------------------------ +// AnnotStamp +//------------------------------------------------------------------------ +AnnotStamp::AnnotStamp(PDFDoc *docA, PDFRectangle *rect) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeStamp; + annotObj.dictSet ("Subtype", obj1.initName ("Stamp")); + initialize(docA, annotObj.getDict()); +} + +AnnotStamp::AnnotStamp(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeStamp; + initialize(docA, dict); +} + +AnnotStamp::~AnnotStamp() { + delete icon; +} + +void AnnotStamp::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("Name", &obj1)->isName()) { + icon = new GooString(obj1.getName()); + } else { + icon = new GooString("Draft"); + } + obj1.free(); + +} + +void AnnotStamp::setIcon(GooString *new_icon) { + delete icon; + + if (new_icon) { + icon = new GooString (new_icon); + } else { + icon = new GooString(); + } + + Object obj1; + obj1.initName (icon->getCString()); + update("Name", &obj1); + invalidateAppearance(); +} + +//------------------------------------------------------------------------ +// AnnotGeometry +//------------------------------------------------------------------------ +AnnotGeometry::AnnotGeometry(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) : + AnnotMarkup(docA, rect) { + Object obj1; + + switch (subType) { + case typeSquare: + annotObj.dictSet ("Subtype", obj1.initName ("Square")); + break; + case typeCircle: + annotObj.dictSet ("Subtype", obj1.initName ("Circle")); + break; + default: + assert (0 && "Invalid subtype for AnnotGeometry\n"); + } + + initialize(docA, annotObj.getDict()); +} + +AnnotGeometry::AnnotGeometry(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + // the real type will be read in initialize() + type = typeSquare; + initialize(docA, dict); +} + +AnnotGeometry::~AnnotGeometry() { + delete interiorColor; + delete borderEffect; + delete geometryRect; +} + +void AnnotGeometry::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("Subtype", &obj1)->isName()) { + GooString typeName(obj1.getName()); + if (!typeName.cmp("Square")) { + type = typeSquare; + } else if (!typeName.cmp("Circle")) { + type = typeCircle; + } + } + obj1.free(); + + if (dict->lookup("IC", &obj1)->isArray()) { + interiorColor = new AnnotColor(obj1.getArray()); + } else { + interiorColor = NULL; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); + + if (dict->lookup("BE", &obj1)->isDict()) { + borderEffect = new AnnotBorderEffect(obj1.getDict()); + } else { + borderEffect = NULL; + } + obj1.free(); + + geometryRect = NULL; + if (dict->lookup("RD", &obj1)->isArray()) { + geometryRect = parseDiffRectangle(obj1.getArray(), rect); + } + obj1.free(); + +} + +void AnnotGeometry::setType(AnnotSubtype new_type) { + Object obj1; + + switch (new_type) { + case typeSquare: + obj1.initName("Square"); + break; + case typeCircle: + obj1.initName("Circle"); + break; + default: + assert(!"Invalid subtype"); + } + + type = new_type; + update("Subtype", &obj1); + invalidateAppearance(); +} + +void AnnotGeometry::setInteriorColor(AnnotColor *new_color) { + delete interiorColor; + + if (new_color) { + Object obj1; + new_color->writeToObject(xref, &obj1); + update ("IC", &obj1); + interiorColor = new_color; + } else { + interiorColor = NULL; + } + invalidateAppearance(); +} + +void AnnotGeometry::draw(Gfx *gfx, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + ca = opacity; + + appearBuf = new GooString (); + appearBuf->append ("q\n"); + if (color) + setColor(color, gFalse); + + double borderWidth = border->getWidth(); + setLineStyleForBorder(border); + + if (interiorColor) + setColor(interiorColor, gTrue); + + if (type == typeSquare) { + appearBuf->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); + } else { + double width, height; + double b; + double x1, y1, x2, y2, x3, y3; + + width = rect->x2 - rect->x1; + height = rect->y2 - rect->y1; + b = borderWidth / 2.0; + + x1 = b; + y1 = height / 2.0; + appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y1); + + y1 += height / 4.0; + x2 = width / 4.0; + y2 = height - b; + x3 = width / 2.0; + y3 = y2; + appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + x1, y1, x2, y2, x3, y3); + x2 = width - b; + y2 = y1; + x1 = x3 + (width / 4.0); + y1 = y3; + x3 = x2; + y3 = height / 2.0; + appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + x1, y1, x2, y2, x3, y3); + + x2 = x1; + y2 = b; + x1 = x3; + y1 = height / 4.0; + x3 = width / 2.0; + y3 = b; + appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + x1, y1, x2, y2, x3, y3); + + x2 = b; + y2 = y1; + x1 = width / 4.0; + y1 = b; + x3 = b; + y3 = height / 2.0; + appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + x1, y1, x2, y2, x3, y3); + } + + if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) + appearBuf->append ("b\n"); + else + appearBuf->append ("S\n"); + + appearBuf->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) { + createForm(bbox, gFalse, NULL, &appearance); + } else { + Object aStream; + + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + Object resDict; + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotPolygon +//------------------------------------------------------------------------ +AnnotPolygon::AnnotPolygon(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) : + AnnotMarkup(docA, rect) { + Object obj1; + + switch (subType) { + case typePolygon: + annotObj.dictSet ("Subtype", obj1.initName ("Polygon")); + break; + case typePolyLine: + annotObj.dictSet ("Subtype", obj1.initName ("PolyLine")); + break; + default: + assert (0 && "Invalid subtype for AnnotGeometry\n"); + } + + // Store dummy path with one null vertex only + Object obj2, obj3; + obj2.initArray (doc->getXRef()); + obj2.arrayAdd (obj3.initReal (0)); + obj2.arrayAdd (obj3.initReal (0)); + annotObj.dictSet ("Vertices", &obj2); + + initialize(docA, annotObj.getDict()); +} + +AnnotPolygon::AnnotPolygon(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + // the real type will be read in initialize() + type = typePolygon; + initialize(docA, dict); +} + +AnnotPolygon::~AnnotPolygon() { + delete vertices; + + if (interiorColor) + delete interiorColor; + + if (borderEffect) + delete borderEffect; +} + +void AnnotPolygon::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("Subtype", &obj1)->isName()) { + GooString typeName(obj1.getName()); + if (!typeName.cmp("Polygon")) { + type = typePolygon; + } else if (!typeName.cmp("PolyLine")) { + type = typePolyLine; + } + } + obj1.free(); + + if (dict->lookup("Vertices", &obj1)->isArray()) { + vertices = new AnnotPath(obj1.getArray()); + } else { + vertices = new AnnotPath(); + error(errSyntaxError, -1, "Bad Annot Polygon Vertices"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("LE", &obj1)->isArray() && obj1.arrayGetLength() == 2) { + Object obj2; + + if(obj1.arrayGet(0, &obj2)->isString()) + startStyle = parseAnnotLineEndingStyle(obj2.getString()); + else + startStyle = annotLineEndingNone; + obj2.free(); + + if(obj1.arrayGet(1, &obj2)->isString()) + endStyle = parseAnnotLineEndingStyle(obj2.getString()); + else + endStyle = annotLineEndingNone; + obj2.free(); + + } else { + startStyle = endStyle = annotLineEndingNone; + } + obj1.free(); + + if (dict->lookup("IC", &obj1)->isArray()) { + interiorColor = new AnnotColor(obj1.getArray()); + } else { + interiorColor = NULL; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); + + if (dict->lookup("BE", &obj1)->isDict()) { + borderEffect = new AnnotBorderEffect(obj1.getDict()); + } else { + borderEffect = NULL; + } + obj1.free(); + + if (dict->lookup("IT", &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; + } + obj1.free(); +} + +void AnnotPolygon::setType(AnnotSubtype new_type) { + Object obj1; + + switch (new_type) { + case typePolygon: + obj1.initName("Polygon"); + break; + case typePolyLine: + obj1.initName("PolyLine"); + break; + default: + assert(!"Invalid subtype"); + } + + type = new_type; + update("Subtype", &obj1); + invalidateAppearance(); +} + +void AnnotPolygon::setVertices(AnnotPath *path) { + Object obj1, obj2; + delete vertices; + + obj1.initArray(xref); + + for (int i = 0; i < path->getCoordsLength(); i++) { + obj1.arrayAdd (obj2.initReal (path->getX(i))); + obj1.arrayAdd (obj2.initReal (path->getY(i))); + } + + vertices = new AnnotPath(obj1.getArray()); + + update("Vertices", &obj1); + invalidateAppearance(); +} + +void AnnotPolygon::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) { + Object obj1, obj2; + + startStyle = start; + endStyle = end; + + obj1.initArray(xref); + obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( startStyle )) ); + obj1.arrayAdd( obj2.initName(convertAnnotLineEndingStyle( endStyle )) ); + + update("LE", &obj1); + invalidateAppearance(); +} + +void AnnotPolygon::setInteriorColor(AnnotColor *new_color) { + delete interiorColor; + + if (new_color) { + Object obj1; + new_color->writeToObject(xref, &obj1); + update ("IC", &obj1); + interiorColor = new_color; + } else { + interiorColor = NULL; + } + invalidateAppearance(); +} + +void AnnotPolygon::setIntent(AnnotPolygonIntent new_intent) { + Object obj1; + + intent = new_intent; + if (new_intent == polygonCloud) + obj1.initName("PolygonCloud"); + else if (new_intent == polylineDimension) + obj1.initName("PolyLineDimension"); + else // polygonDimension + obj1.initName("PolygonDimension"); + update ("IT", &obj1); +} + +void AnnotPolygon::draw(Gfx *gfx, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + appearBBox = new AnnotAppearanceBBox(rect); + ca = opacity; + + appearBuf = new GooString (); + appearBuf->append ("q\n"); + + if (color) { + setColor(color, gFalse); + } + + setLineStyleForBorder(border); + appearBBox->setBorderWidth(std::max(1., border->getWidth())); + + if (interiorColor) { + setColor(interiorColor, gTrue); + } + + if (vertices->getCoordsLength() != 0) { + appearBuf->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) { + appearBuf->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 (type == typePolygon) { + if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) { + appearBuf->append ("b\n"); + } else { + appearBuf->append ("s\n"); + } + } else { + appearBuf->append ("S\n"); + } + } + + appearBuf->append ("Q\n"); + + double bbox[4]; + appearBBox->getBBoxRect(bbox); + if (ca == 1) { + createForm(bbox, gFalse, NULL, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (appearBBox) { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + appearBBox->getPageXMin(), appearBBox->getPageYMin(), + appearBBox->getPageXMax(), appearBBox->getPageYMax(), + getRotation()); + } else { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + } + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotCaret +//------------------------------------------------------------------------ +AnnotCaret::AnnotCaret(PDFDoc *docA, PDFRectangle *rect) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeCaret; + + annotObj.dictSet ("Subtype", obj1.initName ("Caret")); + initialize(docA, annotObj.getDict()); +} + +AnnotCaret::AnnotCaret(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeCaret; + initialize(docA, dict); +} + +AnnotCaret::~AnnotCaret() { + delete caretRect; +} + +void AnnotCaret::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + symbol = symbolNone; + if (dict->lookup("Sy", &obj1)->isName()) { + GooString typeName(obj1.getName()); + if (!typeName.cmp("P")) { + symbol = symbolP; + } else if (!typeName.cmp("None")) { + symbol = symbolNone; + } + } + obj1.free(); + + if (dict->lookup("RD", &obj1)->isArray()) { + caretRect = parseDiffRectangle(obj1.getArray(), rect); + } else { + caretRect = NULL; + } + obj1.free(); + +} + +void AnnotCaret::setSymbol(AnnotCaretSymbol new_symbol) { + Object obj1; + obj1.initName( new_symbol == symbolP ? "P" : "None" ); + symbol = new_symbol; + update("Sy", &obj1); + invalidateAppearance(); +} + +//------------------------------------------------------------------------ +// AnnotInk +//------------------------------------------------------------------------ +AnnotInk::AnnotInk(PDFDoc *docA, PDFRectangle *rect) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeInk; + + annotObj.dictSet ("Subtype", obj1.initName ("Ink")); + + // Store dummy path with one null vertex only + Object obj2, obj3, obj4; + obj2.initArray (doc->getXRef()); + obj2.arrayAdd (obj3.initArray (doc->getXRef())); + obj3.arrayAdd (obj4.initReal (0)); + obj3.arrayAdd (obj4.initReal (0)); + annotObj.dictSet ("InkList", &obj2); + + initialize(docA, annotObj.getDict()); +} + +AnnotInk::AnnotInk(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeInk; + initialize(docA, dict); +} + +AnnotInk::~AnnotInk() { + freeInkList(); +} + +void AnnotInk::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("InkList", &obj1)->isArray()) { + parseInkList(obj1.getArray()); + } else { + inkListLength = 0; + inkList = NULL; + error(errSyntaxError, -1, "Bad Annot Ink List"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("BS", &obj1)->isDict()) { + delete border; + border = new AnnotBorderBS(obj1.getDict()); + } else if (!border) { + border = new AnnotBorderBS(); + } + obj1.free(); +} + +void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array) { + Object obj1, obj2; + for (int i = 0; i < n_paths; ++i) { + AnnotPath *path = paths[i]; + obj1.initArray (xref); + for (int j = 0; j < path->getCoordsLength(); ++j) { + obj1.arrayAdd (obj2.initReal (path->getX(j))); + obj1.arrayAdd (obj2.initReal (path->getY(j))); + } + dest_array->add (&obj1); + } +} + +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; + if (array->get(i, &obj2)->isArray()) + inkList[i] = new AnnotPath(obj2.getArray()); + obj2.free(); + } +} + +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) { + Object obj1; + + freeInkList(); + + obj1.initArray (xref); + writeInkList(paths, n_paths, obj1.getArray()); + + parseInkList(obj1.getArray()); + annotObj.dictSet ("InkList", &obj1); + invalidateAppearance(); +} + +void AnnotInk::draw(Gfx *gfx, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + appearBBox = new AnnotAppearanceBBox(rect); + ca = opacity; + + appearBuf = new GooString (); + appearBuf->append ("q\n"); + + if (color) { + setColor(color, gFalse); + } + + setLineStyleForBorder(border); + appearBBox->setBorderWidth(std::max(1., border->getWidth())); + + for (int i = 0; i < inkListLength; ++i) { + const AnnotPath * path = inkList[i]; + if (path->getCoordsLength() != 0) { + appearBuf->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) { + appearBuf->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); + } + + appearBuf->append ("S\n"); + } + } + + appearBuf->append ("Q\n"); + + double bbox[4]; + appearBBox->getBBoxRect(bbox); + if (ca == 1) { + createForm(bbox, gFalse, NULL, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + if (appearBBox) { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + appearBBox->getPageXMin(), appearBBox->getPageYMin(), + appearBBox->getPageXMax(), appearBBox->getPageYMax(), + getRotation()); + } else { + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + } + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotFileAttachment +//------------------------------------------------------------------------ +AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, PDFRectangle *rect, GooString *filename) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeFileAttachment; + + annotObj.dictSet ("Subtype", obj1.initName ("FileAttachment")); + + Object obj2; + obj2.initString(filename->copy()); + annotObj.dictSet ("FS", &obj2); + + initialize(docA, annotObj.getDict()); +} + +AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeFileAttachment; + initialize(docA, dict); +} + +AnnotFileAttachment::~AnnotFileAttachment() { + file.free(); + + if (name) + delete name; +} + +void AnnotFileAttachment::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("FS", &obj1)->isDict() || dict->lookup("FS", &obj1)->isString()) { + obj1.copy(&file); + } else { + error(errSyntaxError, -1, "Bad Annot File Attachment"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("Name", &obj1)->isName()) { + name = new GooString(obj1.getName()); + } else { + name = new GooString("PushPin"); + } + obj1.free(); +} + +#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, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + ca = opacity; + + appearBuf = new GooString (); + + appearBuf->append ("q\n"); + if (color) + setColor(color, gTrue); + else + appearBuf->append ("1 1 1 rg\n"); + if (!name->cmp("PushPin")) + appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_PUSHPIN); + else if (!name->cmp("Paperclip")) + appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP); + else if (!name->cmp("Graph")) + appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_GRAPH); + else if (!name->cmp("Tag")) + appearBuf->append (ANNOT_FILE_ATTACHMENT_AP_TAG); + appearBuf->append ("Q\n"); + + double bbox[4]; + bbox[0] = bbox[1] = 0; + bbox[2] = bbox[3] = 24; + if (ca == 1) { + createForm (bbox, gFalse, NULL, &appearance); + } else { + Object aStream; + + createForm (bbox, gTrue, NULL, &aStream); + delete appearBuf; + + Object resDict; + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// AnnotSound +//------------------------------------------------------------------------ +AnnotSound::AnnotSound(PDFDoc *docA, PDFRectangle *rect, Sound *soundA) : + AnnotMarkup(docA, rect) { + Object obj1; + + type = typeSound; + + annotObj.dictSet ("Subtype", obj1.initName ("Sound")); + + Object obj2; + Stream *str = soundA->getStream(); + obj2.initStream (str); + str->incRef(); //FIXME: initStream should do this? + annotObj.dictSet ("Sound", &obj2); + + initialize(docA, annotObj.getDict()); +} + +AnnotSound::AnnotSound(PDFDoc *docA, Dict *dict, Object *obj) : + AnnotMarkup(docA, dict, obj) { + type = typeSound; + initialize(docA, dict); +} + +AnnotSound::~AnnotSound() { + delete sound; + + delete name; +} + +void AnnotSound::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + sound = Sound::parseSound(dict->lookup("Sound", &obj1)); + if (!sound) { + error(errSyntaxError, -1, "Bad Annot Sound"); + ok = gFalse; + } + obj1.free(); + + if (dict->lookup("Name", &obj1)->isName()) { + name = new GooString(obj1.getName()); + } else { + name = new GooString("Speaker"); + } + obj1.free(); +} + +#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, GBool printing) { + Object obj; + double ca = 1; + + if (!isVisible (printing)) + return; + + annotLocker(); + if (appearance.isNull()) { + ca = opacity; + + appearBuf = new GooString (); + + appearBuf->append ("q\n"); + if (color) + setColor(color, gTrue); + else + appearBuf->append ("1 1 1 rg\n"); + if (!name->cmp("Speaker")) + appearBuf->append (ANNOT_SOUND_AP_SPEAKER); + else if (!name->cmp("Mic")) + appearBuf->append (ANNOT_SOUND_AP_MIC); + appearBuf->append ("Q\n"); + + double bbox[4]; + bbox[0] = bbox[1] = 0; + bbox[2] = bbox[3] = 24; + if (ca == 1) { + createForm(bbox, gFalse, NULL, &appearance); + } else { + Object aStream, resDict; + + createForm(bbox, gTrue, NULL, &aStream); + delete appearBuf; + + appearBuf = new GooString ("/GS0 gs\n/Fm0 Do"); + createResourcesDict("Fm0", &aStream, "GS0", ca, NULL, &resDict); + createForm(bbox, gFalse, &resDict, &appearance); + } + delete appearBuf; + } + + // draw the appearance stream + appearance.fetch(gfx->getXRef(), &obj); + gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color, + rect->x1, rect->y1, rect->x2, rect->y2, getRotation()); + obj.free(); +} + +//------------------------------------------------------------------------ +// Annot3D +//------------------------------------------------------------------------ +Annot3D::Annot3D(PDFDoc *docA, PDFRectangle *rect) : + Annot(docA, rect) { + Object obj1; + + type = type3D; + + annotObj.dictSet ("Subtype", obj1.initName ("3D")); + + initialize(docA, annotObj.getDict()); +} + +Annot3D::Annot3D(PDFDoc *docA, Dict *dict, Object *obj) : + Annot(docA, dict, obj) { + type = type3D; + initialize(docA, dict); +} + +Annot3D::~Annot3D() { + if (activation) + delete activation; +} + +void Annot3D::initialize(PDFDoc *docA, Dict* dict) { + Object obj1; + + if (dict->lookup("3DA", &obj1)->isDict()) { + activation = new Activation(obj1.getDict()); + } else { + activation = NULL; + } + obj1.free(); +} + +Annot3D::Activation::Activation(Dict *dict) { + Object obj1; + + if (dict->lookup("A", &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.free(); + + if(dict->lookup("AIS", &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.free(); + + if(dict->lookup("D", &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.free(); + + if(dict->lookup("DIS", &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; + } + obj1.free(); + + if (dict->lookup("TB", &obj1)->isBool()) { + displayToolbar = obj1.getBool(); + } else { + displayToolbar = gTrue; + } + obj1.free(); + + if (dict->lookup("NP", &obj1)->isBool()) { + displayNavigation = obj1.getBool(); + } else { + displayNavigation = gFalse; + } + obj1.free(); +} + +//------------------------------------------------------------------------ +// Annots +//------------------------------------------------------------------------ + +Annots::Annots(PDFDoc *docA, int page, Object *annotsObj) { + Annot *annot; + Object obj1; + int i; + + doc = docA; + annots = NULL; + size = 0; + nAnnots = 0; + + 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 obj2; + if (annotsObj->arrayGet(i, &obj1)->isDict()) { + annotsObj->arrayGetNF(i, &obj2); + annot = createAnnot (obj1.getDict(), &obj2); + if (annot) { + if (annot->isOk()) { + annot->setPage(page, gFalse); // Don't change /P + appendAnnot(annot); + } + annot->decRefCnt(); + } + } + obj2.free(); + obj1.free(); + } + } +} + +void Annots::appendAnnot(Annot *annot) { + if (annot && annot->isOk()) { + if (nAnnots >= size) { + size += 16; + annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + } + annots[nAnnots++] = annot; + annot->incRefCnt(); + } +} + +GBool Annots::removeAnnot(Annot *annot) { + int idx = -1; + // Search annot and determine its index + for (int i = 0; idx == -1 && i < nAnnots; i++) { + if (annots[i] == annot) { + idx = i; + } + } + if (idx == -1) { + return gFalse; + } else { + --nAnnots; + memmove( annots + idx, annots + idx + 1, sizeof(annots[0]) * (nAnnots - idx) ); + annot->decRefCnt(); + return gTrue; + } +} + +Annot *Annots::createAnnot(Dict* dict, Object *obj) { + Annot *annot = NULL; + Object obj1; + + if (dict->lookup("Subtype", &obj1)->isName()) { + const char *typeName = obj1.getName(); + + if (!strcmp(typeName, "Text")) { + annot = new AnnotText(doc, dict, obj); + } else if (!strcmp(typeName, "Link")) { + annot = new AnnotLink(doc, dict, obj); + } else if (!strcmp(typeName, "FreeText")) { + annot = new AnnotFreeText(doc, dict, obj); + } else if (!strcmp(typeName, "Line")) { + annot = new AnnotLine(doc, dict, obj); + } else if (!strcmp(typeName, "Square")) { + annot = new AnnotGeometry(doc, dict, obj); + } else if (!strcmp(typeName, "Circle")) { + annot = new AnnotGeometry(doc, dict, obj); + } else if (!strcmp(typeName, "Polygon")) { + annot = new AnnotPolygon(doc, dict, obj); + } else if (!strcmp(typeName, "PolyLine")) { + annot = new AnnotPolygon(doc, dict, obj); + } else if (!strcmp(typeName, "Highlight")) { + annot = new AnnotTextMarkup(doc, dict, obj); + } else if (!strcmp(typeName, "Underline")) { + annot = new AnnotTextMarkup(doc, dict, obj); + } else if (!strcmp(typeName, "Squiggly")) { + annot = new AnnotTextMarkup(doc, dict, obj); + } else if (!strcmp(typeName, "StrikeOut")) { + annot = new AnnotTextMarkup(doc, dict, obj); + } else if (!strcmp(typeName, "Stamp")) { + annot = new AnnotStamp(doc, dict, obj); + } else if (!strcmp(typeName, "Caret")) { + annot = new AnnotCaret(doc, dict, obj); + } else if (!strcmp(typeName, "Ink")) { + annot = new AnnotInk(doc, dict, obj); + } else if (!strcmp(typeName, "FileAttachment")) { + annot = new AnnotFileAttachment(doc, dict, obj); + } else if (!strcmp(typeName, "Sound")) { + annot = new AnnotSound(doc, dict, obj); + } else if(!strcmp(typeName, "Movie")) { + annot = new AnnotMovie(doc, dict, 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, dict, obj); + } else if(!strcmp(typeName, "Screen")) { + annot = new AnnotScreen(doc, dict, obj); + } else if(!strcmp(typeName, "PrinterMark")) { + annot = new Annot(doc, dict, obj); + } else if (!strcmp(typeName, "TrapNet")) { + annot = new Annot(doc, dict, obj); + } else if (!strcmp(typeName, "Watermark")) { + annot = new Annot(doc, dict, obj); + } else if (!strcmp(typeName, "3D")) { + annot = new Annot3D(doc, dict, 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; + + if (dict->lookup("Parent", &obj2)->isNull()) + annot = new AnnotPopup(doc, dict, obj); + else + annot = NULL; + + obj2.free(); + } else { + annot = new Annot(doc, dict, obj); + } + } + obj1.free(); + + return annot; +} + +Annot *Annots::findAnnot(Ref *ref) { + int i; + + for (i = 0; i < nAnnots; ++i) { + if (annots[i]->match(ref)) { + return annots[i]; + } + } + return NULL; +} + + +Annots::~Annots() { + int i; + + for (i = 0; i < nAnnots; ++i) { + annots[i]->decRefCnt(); + } + gfree(annots); +} |