blob: 4f7a18c466b980245f0493dd7b171f48724b6ddb [file] [log] [blame]
//========================================================================
//
// 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, 2015-2018 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2007-2013, 2018 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, 2015 Tobias Koenig <tokoe@kdab.com>
// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
// Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2014, 2015 Marek Kasik <mkasik@redhat.com>
// Copyright (C) 2014 Jiri Slaby <jirislaby@gmail.com>
// Copyright (C) 2014 Anuj Khare <khareanuj18@gmail.com>
// Copyright (C) 2015 Petr Gajdos <pgajdos@suse.cz>
// Copyright (C) 2015 Philipp Reinkemeier <philipp.reinkemeier@offis.de>
// Copyright (C) 2015 Tamas Szekeres <szekerest@gmail.com>
// Copyright (C) 2017 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright 2018 Andre Heinecke <aheinecke@intevation.de>
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2018 Dileep Sankhla <sankhla.dileep96@gmail.com>
// Copyright (C) 2018 Tobias Deiminger <haxtibal@posteo.de>
// Copyright (C) 2018 Oliver Sander <oliver.sander@tu-dresden.de>
//
// 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>
#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>
#include <algorithm>
#define fieldFlagReadOnly 0x00000001
#define fieldFlagRequired 0x00000002
#define fieldFlagNoExport 0x00000004
#define fieldFlagMultiline 0x00001000
#define fieldFlagPassword 0x00002000
#define fieldFlagNoToggleToOff 0x00004000
#define fieldFlagRadio 0x00008000
#define fieldFlagPushbutton 0x00010000
#define fieldFlagCombo 0x00020000
#define fieldFlagEdit 0x00040000
#define fieldFlagSort 0x00080000
#define fieldFlagFileSelect 0x00100000
#define fieldFlagMultiSelect 0x00200000
#define fieldFlagDoNotSpellCheck 0x00400000
#define fieldFlagDoNotScroll 0x00800000
#define fieldFlagComb 0x01000000
#define fieldFlagRichText 0x02000000
#define fieldFlagRadiosInUnison 0x02000000
#define fieldFlagCommitOnSelChange 0x04000000
#define fieldQuadLeft 0
#define fieldQuadCenter 1
#define fieldQuadRight 2
// distance of Bezier control point from center for circle approximation
// = (4 * (sqrt(2) - 1) / 3) * r
#define bezierCircle 0.55228475
static AnnotLineEndingStyle parseAnnotLineEndingStyle(const GooString *string) {
if (string != nullptr) {
if (!string->cmp("Square")) {
return annotLineEndingSquare;
} else if (!string->cmp("Circle")) {
return annotLineEndingCircle;
} else if (!string->cmp("Diamond")) {
return annotLineEndingDiamond;
} else if (!string->cmp("OpenArrow")) {
return annotLineEndingOpenArrow;
} else if (!string->cmp("ClosedArrow")) {
return annotLineEndingClosedArrow;
} else if (!string->cmp("Butt")) {
return annotLineEndingButt;
} else if (!string->cmp("ROpenArrow")) {
return annotLineEndingROpenArrow;
} else if (!string->cmp("RClosedArrow")) {
return annotLineEndingRClosedArrow;
} else if (!string->cmp("Slash")) {
return annotLineEndingSlash;
} else {
return annotLineEndingNone;
}
} else {
return annotLineEndingNone;
}
}
static const char* convertAnnotLineEndingStyle(AnnotLineEndingStyle style) {
switch (style) {
case annotLineEndingSquare:
return "Square";
case annotLineEndingCircle:
return "Circle";
case annotLineEndingDiamond:
return "Diamond";
case annotLineEndingOpenArrow:
return "OpenArrow";
case annotLineEndingClosedArrow:
return "ClosedArrow";
case annotLineEndingButt:
return "Butt";
case annotLineEndingROpenArrow:
return "ROpenArrow";
case annotLineEndingRClosedArrow:
return "RClosedArrow";
case annotLineEndingSlash:
return "Slash";
default:
return "None";
}
}
static AnnotExternalDataType parseAnnotExternalData(Dict* dict) {
AnnotExternalDataType type;
Object obj1 = dict->lookup("Subtype");
if (obj1.isName()) {
const char *typeName = obj1.getName();
if (!strcmp(typeName, "Markup3D")) {
type = annotExternalDataMarkup3D;
} else {
type = annotExternalDataMarkupUnknown;
}
} else {
type = annotExternalDataMarkupUnknown;
}
return type;
}
static std::unique_ptr<PDFRectangle> parseDiffRectangle(Array *array, PDFRectangle *rect) {
if (array->getLength() == 4) {
// deltas
Object obj1;
double dx1 = (obj1 = array->get(0), obj1.isNum() ? obj1.getNum() : 0);
double dy1 = (obj1 = array->get(1), obj1.isNum() ? obj1.getNum() : 0);
double dx2 = (obj1 = array->get(2), obj1.isNum() ? obj1.getNum() : 0);
double dy2 = (obj1 = array->get(3), obj1.isNum() ? obj1.getNum() : 0);
// checking that the numbers are valid (i.e. >= 0),
// and that applying the differences still give us a valid rect
if (dx1 >= 0 && dy1 >= 0 && dx2 >= 0 && dy2
&& (rect->x2 - rect->x1 - dx1 - dx2) >= 0
&& (rect->y2 - rect->y1 - dy1 - dy2) >= 0) {
auto newRect = std::make_unique<PDFRectangle>();
newRect->x1 = rect->x1 + dx1;
newRect->y1 = rect->y1 + dy1;
newRect->x2 = rect->x2 - dx2;
newRect->y2 = rect->y2 - dy2;
return newRect;
}
}
return nullptr;
}
static LinkAction* getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc) {
LinkAction *linkAction = nullptr;
Object additionalActionsObject = additionalActions->fetch(doc->getXRef());
if (additionalActionsObject.isDict()) {
const char *key = (type == Annot::actionCursorEntering ? "E" :
type == Annot::actionCursorLeaving ? "X" :
type == Annot::actionMousePressed ? "D" :
type == Annot::actionMouseReleased ? "U" :
type == Annot::actionFocusIn ? "Fo" :
type == Annot::actionFocusOut ? "Bl" :
type == Annot::actionPageOpening ? "PO" :
type == Annot::actionPageClosing ? "PC" :
type == Annot::actionPageVisible ? "PV" :
type == Annot::actionPageInvisible ? "PI" : nullptr);
Object actionObject = additionalActionsObject.dictLookup(key);
if (actionObject.isDict())
linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
}
return linkAction;
}
static LinkAction* getFormAdditionalAction(Annot::FormAdditionalActionsType type, Object *additionalActions, PDFDoc *doc) {
LinkAction *linkAction = nullptr;
Object additionalActionsObject = additionalActions->fetch(doc->getXRef());
if (additionalActionsObject.isDict()) {
const char *key = (type == Annot::actionFieldModified ? "K" :
type == Annot::actionFormatField ? "F" :
type == Annot::actionValidateField ? "V" :
type == Annot::actionCalculateField ? "C" : nullptr);
Object actionObject = additionalActionsObject.dictLookup(key);
if (actionObject.isDict())
linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
}
return linkAction;
}
//------------------------------------------------------------------------
// AnnotBorderEffect
//------------------------------------------------------------------------
AnnotBorderEffect::AnnotBorderEffect(Dict *dict) {
Object obj1;
obj1 = dict->lookup("S");
if (obj1.isName()) {
const char *effectName = obj1.getName();
if (!strcmp(effectName, "C"))
effectType = borderEffectCloudy;
else
effectType = borderEffectNoEffect;
} else {
effectType = borderEffectNoEffect;
}
obj1 = dict->lookup("I");
if (obj1.isNum() && effectType == borderEffectCloudy) {
intensity = obj1.getNum();
} else {
intensity = 0;
}
}
//------------------------------------------------------------------------
// AnnotPath
//------------------------------------------------------------------------
AnnotPath::AnnotPath() = default;
AnnotPath::AnnotPath(Array *array) {
parsePathArray(array);
}
AnnotPath::AnnotPath(std::vector<AnnotCoord> &&coords) {
this->coords = std::move(coords);
}
AnnotPath::~AnnotPath() = default;
double AnnotPath::getX(int coord) const {
if (coord >= 0 && coord < getCoordsLength())
return coords[coord].getX();
return 0;
}
double AnnotPath::getY(int coord) const {
if (coord >= 0 && coord < getCoordsLength())
return coords[coord].getY();
return 0;
}
AnnotCoord *AnnotPath::getCoord(int coord) {
if (coord >= 0 && coord < getCoordsLength())
return &coords[coord];
return nullptr;
}
void AnnotPath::parsePathArray(Array *array) {
if (array->getLength() % 2) {
error(errSyntaxError, -1, "Bad Annot Path");
return;
}
const auto tempLength = array->getLength() / 2;
std::vector<AnnotCoord> tempCoords;
tempCoords.reserve(tempLength);
for (int i = 0; i < tempLength; i++) {
double x = 0, y = 0;
Object obj1 = array->get(i * 2);
if (obj1.isNum()) {
x = obj1.getNum();
} else {
return;
}
obj1 = array->get((i * 2) + 1);
if (obj1.isNum()) {
y = obj1.getNum();
} else {
return;
}
tempCoords.emplace_back(x, y);
}
coords = std::move(tempCoords);
}
//------------------------------------------------------------------------
// AnnotCalloutLine
//------------------------------------------------------------------------
AnnotCalloutLine::AnnotCalloutLine(double x1, double y1, double x2, double y2)
: coord1(x1, y1), coord2(x2, y2) {
}
//------------------------------------------------------------------------
// 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();
int quadsLength = 0;
double quadArray[8];
// default values
quadrilateralsLength = 0;
if ((arrayLength % 8) == 0) {
int i;
quadsLength = arrayLength / 8;
auto quads = std::make_unique<AnnotQuadrilateral[]>(quadsLength);
for (i = 0; i < quadsLength; i++) {
for (int j = 0; j < 8; j++) {
Object obj = array->get(i * 8 + j);
if (obj.isNum()) {
quadArray[j] = obj.getNum();
} else {
error (errSyntaxError, -1, "Invalid QuadPoint in annot");
return;
}
}
quads[i] = AnnotQuadrilateral(quadArray[0], quadArray[1],
quadArray[2], quadArray[3],
quadArray[4], quadArray[5],
quadArray[6], quadArray[7]);
}
quadrilateralsLength = quadsLength;
quadrilaterals = std::move(quads);
}
}
AnnotQuadrilaterals::AnnotQuadrilaterals(std::unique_ptr<AnnotQuadrilateral[]> &&quads, int quadsLength) {
quadrilaterals = std::move(quads);
quadrilateralsLength = quadsLength;
}
AnnotQuadrilaterals::~AnnotQuadrilaterals() = default;
double AnnotQuadrilaterals::getX1(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord1.getX();
return 0;
}
double AnnotQuadrilaterals::getY1(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord1.getY();
return 0;
}
double AnnotQuadrilaterals::getX2(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord2.getX();
return 0;
}
double AnnotQuadrilaterals::getY2(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord2.getY();
return 0;
}
double AnnotQuadrilaterals::getX3(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord3.getX();
return 0;
}
double AnnotQuadrilaterals::getY3(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord3.getY();
return 0;
}
double AnnotQuadrilaterals::getX4(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord4.getX();
return 0;
}
double AnnotQuadrilaterals::getY4(int quadrilateral) {
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength)
return quadrilaterals[quadrilateral].coord4.getY();
return 0;
}
AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral() = default;
AnnotQuadrilaterals::AnnotQuadrilateral::AnnotQuadrilateral(double x1, double y1,
double x2, double y2, double x3, double y3, double x4, double y4)
: coord1(x1, y1), coord2(x2, y2), coord3(x3, y3), coord4(x4, y4) {
}
//------------------------------------------------------------------------
// AnnotBorder
//------------------------------------------------------------------------
AnnotBorder::AnnotBorder() {
width = 1;
dashLength = 0;
dash = nullptr;
style = borderSolid;
}
GBool AnnotBorder::parseDashArray(Object *dashObj) {
GBool correct = gTrue;
const int tempLength = dashObj->arrayGetLength();
double *tempDash = (double *) gmallocn (tempLength, sizeof (double));
// TODO: check not all zero (Line Dash Pattern Page 217 PDF 8.1)
for (int i = 0; i < tempLength && i < DASH_LIMIT && correct; i++) {
const Object obj1 = dashObj->arrayGet(i);
if (obj1.isNum()) {
tempDash[i] = obj1.getNum();
correct = tempDash[i] >= 0;
} else {
correct = gFalse;
}
}
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.
obj1 = array->get(0);
if (obj1.isNum())
horizontalCorner = obj1.getNum();
else
correct = gFalse;
obj1 = array->get(1);
if (obj1.isNum())
verticalCorner = obj1.getNum();
else
correct = gFalse;
obj1 = array->get(2);
if (obj1.isNum())
width = obj1.getNum();
else
correct = gFalse;
if (arrayLength == 4) {
obj1 = array->get(3);
if (obj1.isArray())
correct = parseDashArray(&obj1);
else
correct = gFalse;
}
} else {
correct = gFalse;
}
if (!correct) {
width = 0;
}
}
Object AnnotBorderArray::writeToObject(XRef *xref) const {
Array *borderArray = new Array(xref);
borderArray->add(Object(horizontalCorner));
borderArray->add(Object(verticalCorner));
borderArray->add(Object(width));
if (dashLength > 0) {
Array *a = new Array(xref);
for (int i = 0; i < dashLength; i++)
a->add(Object(dash[i]));
borderArray->add(Object(a));
}
return Object(borderArray);
}
//------------------------------------------------------------------------
// AnnotBorderBS
//------------------------------------------------------------------------
AnnotBorderBS::AnnotBorderBS() {
}
AnnotBorderBS::AnnotBorderBS(Dict *dict) {
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
obj1 = dict->lookup("W");
obj2 = dict->lookup("S");
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;
}
if (style == borderDashed) {
obj1 = dict->lookup("D");
if (obj1.isArray())
parseDashArray(&obj1);
if (!dash) {
dashLength = 1;
dash = (double *) gmallocn (dashLength, sizeof (double));
dash[0] = 3;
}
}
}
const char *AnnotBorderBS::getStyleName() const {
switch (style) {
case borderSolid:
return "S";
case borderDashed:
return "D";
case borderBeveled:
return "B";
case borderInset:
return "I";
case borderUnderlined:
return "U";
}
return "S";
}
Object AnnotBorderBS::writeToObject(XRef *xref) const {
Dict *dict = new Dict(xref);
dict->set("W", Object(width));
dict->set("S", Object(objName, getStyleName()));
if (style == borderDashed && dashLength > 0) {
Array *a = new Array(xref);
for (int i = 0; i < dashLength; i++)
a->add(Object(dash[i]));
dict->set("D", Object(a));
}
return Object(dict);
}
//------------------------------------------------------------------------
// AnnotColor
//------------------------------------------------------------------------
AnnotColor::AnnotColor() {
length = 0;
}
AnnotColor::AnnotColor(double gray) {
length = 1;
values[0] = gray;
}
AnnotColor::AnnotColor(double r, double g, double b) {
length = 3;
values[0] = r;
values[1] = g;
values[2] = b;
}
AnnotColor::AnnotColor(double c, double m, double y, double k) {
length = 4;
values[0] = c;
values[1] = m;
values[2] = y;
values[3] = k;
}
// If <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 = array->get(i);
if (obj1.isNum()) {
values[i] = obj1.getNum();
if (values[i] < 0 || values[i] > 1)
values[i] = 0;
} else {
values[i] = 0;
}
}
if (adjust != 0)
adjustColor(adjust);
}
void AnnotColor::adjustColor(int adjust) {
int i;
if (length == 4) {
adjust = -adjust;
}
if (adjust > 0) {
for (i = 0; i < length; ++i) {
values[i] = 0.5 * values[i] + 0.5;
}
} else if (adjust < 0) {
for (i = 0; i < length; ++i) {
values[i] = 0.5 * values[i];
}
}
}
Object AnnotColor::writeToObject(XRef *xref) const {
if (length == 0) {
return Object(objNull); // Transparent (no color)
} else {
Array *a = new Array(xref);
for (int i = 0; i < length; ++i)
a->add( Object( values[i] ) );
return Object(a);
}
}
//------------------------------------------------------------------------
// DefaultAppearance
//------------------------------------------------------------------------
DefaultAppearance::DefaultAppearance(Object &&fontNameA, double fontPtSizeA, std::unique_ptr<AnnotColor> fontColorA) :
fontName(std::move(fontNameA)), fontPtSize(fontPtSizeA), fontColor(std::move(fontColorA)) {
}
DefaultAppearance::DefaultAppearance(GooString *da) {
fontPtSize = -1;
if (da) {
GooList * daToks = new GooList();
int i = FormFieldText::tokenizeDA(da, daToks, "Tf");
if (i >= 1) {
fontPtSize = gatof(( (GooString *)daToks->get(i-1) )->getCString());
}
if (i >= 2) {
// We are expecting a name, therefore the first letter should be '/'.
if (((const char*)daToks->get(i-2)) && ((const char*)daToks->get(i-2))[0] == '/') {
// The +1 is here to skip the leading '/'.
fontName = Object(objName, ((const char*)daToks->get(i-2))+1);
}
}
// Scan backwards: we are looking for the last set value
for (i = daToks->getLength()-1; i >= 0; --i) {
if (!fontColor) {
if (!((GooString *)daToks->get(i))->cmp("g") && i >= 1) {
fontColor = std::make_unique<AnnotColor>(gatof(( (GooString *)daToks->get(i-1) )->getCString()));
} else if (!((GooString *)daToks->get(i))->cmp("rg") && i >= 3) {
fontColor = std::make_unique<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 = std::make_unique<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<GooString>(daToks);
}
}
void DefaultAppearance::setFontName(Object &&fontNameA) {
fontName = std::move(fontNameA);
}
void DefaultAppearance::setFontPtSize(double fontPtSizeA) {
fontPtSize = fontPtSizeA;
}
void DefaultAppearance::setFontColor(std::unique_ptr<AnnotColor> fontColorA) {
fontColor = std::move(fontColorA);
}
GooString *DefaultAppearance::toAppearanceString() const {
AnnotAppearanceBuilder appearBuilder;
if (fontColor) {
appearBuilder.setDrawColor(fontColor.get(), gTrue);
}
appearBuilder.setTextFont(fontName, fontPtSize);
return appearBuilder.buffer()->copy();
}
//------------------------------------------------------------------------
// AnnotIconFit
//------------------------------------------------------------------------
AnnotIconFit::AnnotIconFit(Dict* dict) {
Object obj1;
obj1 = dict->lookup("SW");
if (obj1.isName()) {
const char *scaleName = obj1.getName();
if(!strcmp(scaleName, "B")) {
scaleWhen = scaleBigger;
} else if(!strcmp(scaleName, "S")) {
scaleWhen = scaleSmaller;
} else if(!strcmp(scaleName, "N")) {
scaleWhen = scaleNever;
} else {
scaleWhen = scaleAlways;
}
} else {
scaleWhen = scaleAlways;
}
obj1 = dict->lookup("S");
if (obj1.isName()) {
const char *scaleName = obj1.getName();
if(!strcmp(scaleName, "A")) {
scale = scaleAnamorphic;
} else {
scale = scaleProportional;
}
} else {
scale = scaleProportional;
}
obj1 = dict->lookup("A");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
Object obj2;
(obj2 = obj1.arrayGet(0), obj2.isNum() ? left = obj2.getNum() : left = 0);
(obj2 = obj1.arrayGet(1), obj2.isNum() ? bottom = obj2.getNum() : bottom = 0);
if (left < 0 || left > 1)
left = 0.5;
if (bottom < 0 || bottom > 1)
bottom = 0.5;
} else {
left = bottom = 0.5;
}
obj1 = dict->lookup("FB");
if (obj1.isBool()) {
fullyBounds = obj1.getBool();
} else {
fullyBounds = gFalse;
}
}
//------------------------------------------------------------------------
// AnnotAppearance
//------------------------------------------------------------------------
AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict) {
assert(dict->isDict());
doc = docA;
xref = docA->getXRef();
appearDict = dict->copy();
}
AnnotAppearance::~AnnotAppearance() {
}
Object AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state) {
Object apData;
// Obtain dictionary or stream associated to appearance type
switch (type) {
case appearRollover:
apData = appearDict.dictLookupNF("R");
if (apData.isNull())
apData = appearDict.dictLookupNF("N");
break;
case appearDown:
apData = appearDict.dictLookupNF("D");
if (apData.isNull())
apData = appearDict.dictLookupNF("N");
break;
case appearNormal:
apData = appearDict.dictLookupNF("N");
break;
}
Object res;
if (apData.isDict() && state)
res = apData.dictLookupNF(state);
else if (apData.isRef())
res = apData.copy();
return res;
}
std::unique_ptr<GooString> AnnotAppearance::getStateKey(int i) {
Object obj1 = appearDict.dictLookupNF("N");
if (obj1.isDict())
return std::make_unique<GooString>(obj1.dictGetKey(i));
return nullptr;
}
int AnnotAppearance::getNumStates() {
int res = 0;
Object obj1 = appearDict.dictLookupNF("N");
if (obj1.isDict())
res = obj1.dictGetLength();
return res;
}
// Test if stateObj (a Ref or a Dict) points to the specified stream
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);
if (obj1.isRef()) {
Ref r = obj1.getRef();
if (r.num == refToStream.num && r.gen == refToStream.gen) {
return gTrue;
}
}
}
}
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
obj1 = appearDict.dictLookupNF("N");
found = referencesStream(&obj1, refToStream);
if (found)
return gTrue;
obj1 = appearDict.dictLookupNF("R");
found = referencesStream(&obj1, refToStream);
if (found)
return gTrue;
obj1 = appearDict.dictLookupNF("D");
found = referencesStream(&obj1, refToStream);
return found;
}
// If this is the only annotation in the document that references the
// specified appearance stream, remove the appearance stream
void AnnotAppearance::removeStream(Ref refToStream) {
const int lastpage = doc->getNumPages();
for (int pg = 1; pg <= lastpage; ++pg) { // Scan all annotations in the document
Page *page = doc->getPage(pg);
if (!page) {
error(errSyntaxError, -1, "Failed check for shared annotation stream at page {0:d}", pg);
continue;
}
Annots *annots = page->getAnnots();
for (int i = 0; i < annots->getNumAnnots(); ++i) {
AnnotAppearance *annotAp = annots->getAnnot(i)->getAppearStreams();
if (annotAp && annotAp != this && annotAp->referencesStream(refToStream)) {
return; // Another annotation points to the stream -> Don't delete it
}
}
}
// TODO: stream resources (e.g. font), AP name tree
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);
if (obj2.isRef()) {
removeStream(obj2.getRef());
}
}
}
}
void AnnotAppearance::removeAllStreams() {
Object obj1;
obj1 = appearDict.dictLookupNF("N");
removeStateStreams(&obj1);
obj1 = appearDict.dictLookupNF("R");
removeStateStreams(&obj1);
obj1 = appearDict.dictLookupNF("D");
removeStateStreams(&obj1);
}
//------------------------------------------------------------------------
// AnnotAppearanceCharacs
//------------------------------------------------------------------------
AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict) {
Object obj1;
obj1 = dict->lookup("R");
if (obj1.isInt()) {
rotation = obj1.getInt();
} else {
rotation = 0;
}
obj1 = dict->lookup("BC");
if (obj1.isArray()) {
Array *colorComponents = obj1.getArray();
if (colorComponents->getLength() > 0) {
borderColor = std::make_unique<AnnotColor>(colorComponents);
}
}
obj1 = dict->lookup("BG");
if (obj1.isArray()) {
Array *colorComponents = obj1.getArray();
if (colorComponents->getLength() > 0) {
backColor = std::make_unique<AnnotColor>(colorComponents);
}
}
obj1 = dict->lookup("CA");
if (obj1.isString()) {
normalCaption = std::make_unique<GooString>(obj1.getString());
}
obj1 = dict->lookup("RC");
if (obj1.isString()) {
rolloverCaption = std::make_unique<GooString>(obj1.getString());
}
obj1 = dict->lookup("AC");
if (obj1.isString()) {
alternateCaption = std::make_unique<GooString>(obj1.getString());
}
obj1 = dict->lookup("IF");
if (obj1.isDict()) {
iconFit = std::make_unique<AnnotIconFit>(obj1.getDict());
}
obj1 = dict->lookup("TP");
if (obj1.isInt()) {
position = (AnnotAppearanceCharacsTextPos) obj1.getInt();
} else {
position = captionNoIcon;
}
}
AnnotAppearanceCharacs::~AnnotAppearanceCharacs() = default;
//------------------------------------------------------------------------
// AnnotAppearanceBBox
//------------------------------------------------------------------------
AnnotAppearanceBBox::AnnotAppearanceBBox(PDFRectangle *rect) {
origX = rect->x1;
origY = rect->y1;
borderWidth = 0;
// Initially set the same size as rect
minX = 0;
minY = 0;
maxX = rect->x2 - rect->x1;
maxY = rect->y2 - rect->y1;
}
void AnnotAppearanceBBox::extendTo(double x, double y) {
if (x < minX) {
minX = x;
} else if (x > maxX) {
maxX = x;
}
if (y < minY) {
minY = y;
} else if (y > maxY) {
maxY = y;
}
}
void AnnotAppearanceBBox::getBBoxRect(double bbox[4]) const {
bbox[0] = minX - borderWidth;
bbox[1] = minY - borderWidth;
bbox[2] = maxX + borderWidth;
bbox[3] = maxY + borderWidth;
}
double AnnotAppearanceBBox::getPageXMin() const {
return origX + minX - borderWidth;
}
double AnnotAppearanceBBox::getPageYMin() const {
return origY + minY - borderWidth;
}
double AnnotAppearanceBBox::getPageXMax() const {
return origX + maxX + borderWidth;
}
double AnnotAppearanceBBox::getPageYMax() const {
return origY + maxY + borderWidth;
}
//------------------------------------------------------------------------
// Annot
//------------------------------------------------------------------------
#define annotLocker() std::unique_lock<std::recursive_mutex> locker(mutex)
Annot::Annot(PDFDoc *docA, PDFRectangle *rectA) {
refCnt = 1;
flags = flagUnknown;
type = typeUnknown;
Array *a = new Array(docA->getXRef());
a->add(Object(rectA->x1));
a->add(Object(rectA->y1));
a->add(Object(rectA->x2));
a->add(Object(rectA->y2));
annotObj = Object(new Dict(docA->getXRef()));
annotObj.dictSet ("Type", Object(objName, "Annot"));
annotObj.dictSet ("Rect", Object(a));
ref = docA->getXRef()->addIndirectObject (&annotObj);
initialize (docA, annotObj.getDict());
}
Annot::Annot(PDFDoc *docA, Object *dictObject) {
refCnt = 1;
hasRef = false;
flags = flagUnknown;
type = typeUnknown;
annotObj = dictObject->copy();
initialize (docA, dictObject->getDict());
}
Annot::Annot(PDFDoc *docA, Object *dictObject, Object *obj) {
refCnt = 1;
if (obj->isRef()) {
hasRef = gTrue;
ref = obj->getRef();
} else {
hasRef = gFalse;
}
flags = flagUnknown;
type = typeUnknown;
annotObj = dictObject->copy();
initialize (docA, dictObject->getDict());
}
void Annot::initialize(PDFDoc *docA, Dict *dict) {
Object apObj, asObj, obj1;
ok = gTrue;
doc = docA;
xref = doc->getXRef();
appearance.setToNull();
//----- parse the rectangle
rect = std::make_unique<PDFRectangle>();
obj1 = dict->lookup("Rect");
if (obj1.isArray() && obj1.arrayGetLength() == 4) {
Object obj2;
(obj2 = obj1.arrayGet(0), obj2.isNum() ? rect->x1 = obj2.getNum() : rect->x1 = 0);
(obj2 = obj1.arrayGet(1), obj2.isNum() ? rect->y1 = obj2.getNum() : rect->y1 = 0);
(obj2 = obj1.arrayGet(2), obj2.isNum() ? rect->x2 = obj2.getNum() : rect->x2 = 1);
(obj2 = obj1.arrayGet(3), obj2.isNum() ? rect->y2 = obj2.getNum() : rect->y2 = 1);
if (rect->x1 > rect->x2) {
double t = rect->x1;
rect->x1 = rect->x2;
rect->x2 = t;
}
if (rect->y1 > rect->y2) {
double t = rect->y1;
rect->y1 = rect->y2;
rect->y2 = t;
}
} else {
rect->x1 = rect->y1 = 0;
rect->x2 = rect->y2 = 1;
error(errSyntaxError, -1, "Bad bounding box for annotation");
ok = gFalse;
}
obj1 = dict->lookup("Contents");
if (obj1.isString()) {
contents.reset(obj1.getString()->copy());
} else {
contents = std::make_unique<GooString>();
}
// Note: This value is overwritten by Annots ctor
obj1 = dict->lookupNF("P");
if (obj1.isRef()) {
Ref ref = obj1.getRef();
page = doc->getCatalog()->findPage (ref.num, ref.gen);
} else {
page = 0;
}
obj1 = dict->lookup("NM");
if (obj1.isString()) {
name.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("M");
if (obj1.isString()) {
modified.reset(obj1.getString()->copy());
}
//----- get the flags
obj1 = dict->lookup("F");
if (obj1.isInt()) {
flags |= obj1.getInt();
} else {
flags = flagUnknown;
}
//----- get the annotation appearance dictionary
apObj = dict->lookup("AP");
if (apObj.isDict()) {
appearStreams = std::make_unique<AnnotAppearance>(doc, &apObj);
}
//----- get the appearance state
asObj = dict->lookup("AS");
if (asObj.isName()) {
appearState = std::make_unique<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 = std::make_unique<GooString>("Off");
}
//----- get the annotation appearance
if (appearStreams) {
appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString());
}
//----- parse the border style
// According to the spec if neither the Border nor the BS entry is present,
// the border shall be drawn as a solid line with a width of 1 point. But acroread
// seems to ignore the Border entry for annots that can't have a BS entry. So, we only
// follow this rule for annots tha can have a BS entry.
obj1 = dict->lookup("Border");
if (obj1.isArray()) {
border = std::make_unique<AnnotBorderArray>(obj1.getArray());
}
obj1 = dict->lookup("C");
if (obj1.isArray()) {
color = std::make_unique<AnnotColor>(obj1.getArray());
}
obj1 = dict->lookup("StructParent");
if (obj1.isInt()) {
treeKey = obj1.getInt();
} else {
treeKey = 0;
}
oc = dict->lookupNF("OC");
}
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) {
if (x1 < x2) {
rect->x1 = x1;
rect->x2 = x2;
} else {
rect->x1 = x2;
rect->x2 = x1;
}
if (y1 < y2) {
rect->y1 = y1;
rect->y2 = y2;
} else {
rect->y1 = y2;
rect->y2 = y1;
}
Array *a = new Array(xref);
a->add(Object(rect->x1));
a->add(Object(rect->y1));
a->add(Object(rect->x2));
a->add(Object(rect->y2));
update("Rect", Object(a));
invalidateAppearance();
}
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) {
modified.reset(timeToDateString(nullptr));
annotObj.dictSet("M", Object(modified->copy()));
}
annotObj.dictSet(const_cast<char*>(key), std::move(value));
xref->setModifiedObject(&annotObj, ref);
}
void Annot::setContents(GooString *new_content) {
annotLocker();
if (new_content) {
contents = std::make_unique<GooString>(new_content);
//append the unicode marker <FE FF> if needed
if (!contents->hasUnicodeMarker()) {
contents->prependUnicodeMarker();
}
} else {
contents = std::make_unique<GooString>();
}
update ("Contents", Object(contents->copy()));
}
void Annot::setName(GooString *new_name) {
annotLocker();
if (new_name) {
name = std::make_unique<GooString>(new_name);
} else {
name = std::make_unique<GooString>();
}
update ("NM", Object(name->copy()));
}
void Annot::setModified(GooString *new_modified) {
annotLocker();
if (new_modified)
modified = std::make_unique<GooString>(new_modified);
else
modified = std::make_unique<GooString>();
update ("M", Object(modified->copy()));
}
void Annot::setFlags(Guint new_flags) {
annotLocker();
flags = new_flags;
update ("F", Object(int(flags)));
}
void Annot::setBorder(std::unique_ptr<AnnotBorder> &&new_border) {
annotLocker();
if (new_border) {
Object obj1 = new_border->writeToObject(xref);
update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", std::move(obj1));
border = std::move(new_border);
} else {
border = nullptr;
}
invalidateAppearance();
}
void Annot::setColor(std::unique_ptr<AnnotColor> &&new_color) {
annotLocker();
if (new_color) {
Object obj1 = new_color->writeToObject(xref);
update ("C", std::move(obj1));
color = std::move(new_color);
} else {
color = nullptr;
}
invalidateAppearance();
}
void Annot::setPage(int pageIndex, GBool updateP) {
annotLocker();
Page *pageobj = doc->getPage(pageIndex);
Object obj1(objNull);
if (pageobj) {
Ref pageRef = pageobj->getRef();
obj1 = Object(pageRef.num, pageRef.gen);
page = pageIndex;
} else {
page = 0;
}
if (updateP) {
update("P", std::move(obj1));
}
}
void Annot::setAppearanceState(const char *state) {
annotLocker();
if (!state)
return;
appearState = std::make_unique<GooString>(state);
appearBBox = nullptr;
update ("AS", Object(objName, state));
// The appearance state determines the current appearance stream
if (appearStreams) {
appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->getCString());
} else {
appearance.setToNull();
}
}
void Annot::invalidateAppearance() {
annotLocker();
if (appearStreams) { // Remove existing appearance streams
appearStreams->removeAllStreams();
}
appearStreams = nullptr;
appearState = nullptr;
appearBBox = nullptr;
appearance.setToNull();
Object obj2 = annotObj.dictLookup("AP");
if (!obj2.isNull())
update ("AP", Object(objNull)); // Remove AP
obj2 = annotObj.dictLookup("AS");
if (!obj2.isNull())
update ("AS", Object(objNull)); // Remove AS
}
double Annot::getXMin() {
return rect->x1;
}
double Annot::getYMin() {
return rect->y1;
}
double Annot::getXMax() {
return rect->x2;
}
double Annot::getYMax() {
return rect->y2;
}
void Annot::readArrayNum(Object *pdfArray, int key, double *value) {
Object valueObject = pdfArray->arrayGet(key);
if (valueObject.isNum()) {
*value = valueObject.getNum();
} else {
*value = 0;
ok = gFalse;
}
}
void Annot::removeReferencedObjects() {
// Remove appearance streams (if any)
invalidateAppearance();
}
void Annot::incRefCnt() {
refCnt++;
}
void Annot::decRefCnt() {
if (--refCnt == 0) {
delete this;
}
}
Annot::~Annot() {}
void AnnotAppearanceBuilder::setDrawColor(const AnnotColor *drawColor, GBool fill) {
const double *values = drawColor->getValues();
switch (drawColor->getSpace()) {
case AnnotColor::colorCMYK:
appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:.5f} {4:c}\n",
values[0], values[1], values[2], values[3],
fill ? 'k' : 'K');
break;
case AnnotColor::colorRGB:
appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:s}\n",
values[0], values[1], values[2],
fill ? "rg" : "RG");
break;
case AnnotColor::colorGray:
appearBuf->appendf("{0:.5f} {1:c}\n",
values[0],
fill ? 'g' : 'G');
break;
case AnnotColor::colorTransparent:
default:
break;
}
}
void AnnotAppearanceBuilder::setTextFont(const Object &fontName, double fontSize) {
if (fontName.isName() && strlen(fontName.getName()) > 0)
appearBuf->appendf("/{0:s} {1:.2f} Tf\n", fontName.getName(), fontSize);
}
void AnnotAppearanceBuilder::setLineStyleForBorder(const AnnotBorder *border) {
int i, dashLength;
double *dash;
switch (border->getStyle()) {
case AnnotBorder::borderDashed:
appearBuf->append("[");
dashLength = border->getDashLength();
dash = border->getDash();
for (i = 0; i < dashLength; ++i)
appearBuf->appendf(" {0:.2f}", dash[i]);
appearBuf->append(" ] 0 d\n");
break;
default:
appearBuf->append("[] 0 d\n");
break;
}
appearBuf->appendf("{0:.2f} w\n", border->getWidth());
}
// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
// If <fill> is true, the circle is filled; otherwise it is stroked.
void AnnotAppearanceBuilder::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 AnnotAppearanceBuilder::drawCircleTopLeft(double cx, double cy, double r) {
double r2;
r2 = r / sqrt(2.0);
appearBuf->appendf("{0:.2f} {1:.2f} m\n",
cx + r2, cy + r2);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
cx + (1 - bezierCircle) * r2,
cy + (1 + bezierCircle) * r2,
cx - (1 - bezierCircle) * r2,
cy + (1 + bezierCircle) * r2,
cx - r2,
cy + r2);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
cx - (1 + bezierCircle) * r2,
cy + (1 - bezierCircle) * r2,
cx - (1 + bezierCircle) * r2,
cy - (1 - bezierCircle) * r2,
cx - r2,
cy - r2);
appearBuf->append("S\n");
}
// Draw the bottom-right half of an (approximate) circle of radius <r>
// centered at (<cx>, <cy>).
void AnnotAppearanceBuilder::drawCircleBottomRight(double cx, double cy, double r) {
double r2;
r2 = r / sqrt(2.0);
appearBuf->appendf("{0:.2f} {1:.2f} m\n",
cx - r2, cy - r2);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
cx - (1 - bezierCircle) * r2,
cy - (1 + bezierCircle) * r2,
cx + (1 - bezierCircle) * r2,
cy - (1 + bezierCircle) * r2,
cx + r2,
cy - r2);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
cx + (1 + bezierCircle) * r2,
cy - (1 - bezierCircle) * r2,
cx + (1 + bezierCircle) * r2,
cy + (1 - bezierCircle) * r2,
cx + r2,
cy + r2);
appearBuf->append("S\n");
}
Object Annot::createForm(const GooString *appearBuf, double *bbox, GBool transparencyGroup, Dict *resDict) {
return createForm(appearBuf, bbox, transparencyGroup, resDict ? Object(resDict) : Object());
}
Object Annot::createForm(const GooString *appearBuf, double *bbox, GBool transparencyGroup, Object &&resDictObject) {
Dict *appearDict = new Dict(xref);
appearDict->set("Length", Object(appearBuf->getLength()));
appearDict->set("Subtype", Object(objName, "Form"));
Array *a = new Array(xref);
a->add(Object(bbox[0]));
a->add(Object(bbox[1]));
a->add(Object(bbox[2]));
a->add(Object(bbox[3]));
appearDict->set("BBox", Object(a));
if (transparencyGroup) {
Dict *d = new Dict(xref);
d->set("S", Object(objName, "Transparency"));
appearDict->set("Group", Object(d));
}
if (resDictObject.isDict())
appearDict->set("Resources", std::move(resDictObject));
Stream *mStream = new AutoFreeMemStream(copyString(appearBuf->getCString()), 0,
appearBuf->getLength(), Object(appearDict));
return Object(mStream);
}
Dict *Annot::createResourcesDict(const char *formName, Object &&formStream,
const char *stateName,
double opacity, const char *blendMode) {
Dict *gsDict = new Dict(xref);
if (opacity != 1) {
gsDict->set("CA", Object(opacity));
gsDict->set("ca", Object(opacity));
}
if (blendMode)
gsDict->set("BM", Object(objName, blendMode));
Dict *stateDict = new Dict(xref);
stateDict->set(stateName, Object(gsDict));
Dict *formDict = new Dict(xref);
formDict->set(formName, std::move(formStream));
Dict *resDict = new Dict(xref);
resDict->set("ExtGState", Object(stateDict));
resDict->set("XObject", Object(formDict));
return resDict;
}
Object Annot::getAppearanceResDict() {
Object obj1, obj2;
// Fetch appearance's resource dict (if any)
obj1 = appearance.fetch(xref);
if (obj1.isStream()) {
obj2 = obj1.streamGetDict()->lookup("Resources");
if (obj2.isDict()) {
return obj2;
}
}
return Object(objNull);
}
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 != nullptr);
if (flags & flagNoRotate) {
return (360 - pageobj->getRotate()) % 360;
} else {
return 0;
}
}
void Annot::draw(Gfx *gfx, GBool printing) {
annotLocker();
if (!isVisible (printing))
return;
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// AnnotPopup
//------------------------------------------------------------------------
AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
type = typePopup;
annotObj.dictSet ("Subtype", Object(objName, "Popup"));
initialize (docA, annotObj.getDict());
}
AnnotPopup::AnnotPopup(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typePopup;
initialize(docA, dictObject->getDict());
}
AnnotPopup::~AnnotPopup() {
}
void AnnotPopup::initialize(PDFDoc *docA, Dict *dict) {
parent = dict->lookupNF("Parent");
if (!parent.isRef()) {
parent.setToNull();
}
Object obj1 = dict->lookup("Open");
if (obj1.isBool()) {
open = obj1.getBool();
} else {
open = gFalse;
}
}
void AnnotPopup::setParent(Object *parentA) {
update ("Parent", parentA->copy());
}
void AnnotPopup::setParent(Annot *parentA) {
const Ref parentRef = parentA->getRef();
update ("Parent", Object(parentRef.num, parentRef.gen));
}
void AnnotPopup::setOpen(GBool openA) {
open = openA;
update ("Open", Object(open));
}
//------------------------------------------------------------------------
// AnnotMarkup
//------------------------------------------------------------------------
AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
initialize(docA, annotObj.getDict(), &annotObj);
}
AnnotMarkup::AnnotMarkup(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
initialize(docA, dictObject->getDict(), obj);
}
AnnotMarkup::~AnnotMarkup() = default;
void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict, Object *obj) {
Object obj1, obj2;
obj1 = dict->lookup("T");
if (obj1.isString()) {
label.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("Popup");
obj2 = dict->lookupNF("Popup");
if (obj1.isDict() && obj2.isRef()) {
popup = std::make_unique<AnnotPopup>(docA, &obj1, &obj2);
}
obj1 = dict->lookup("CA");
if (obj1.isNum()) {
opacity = obj1.getNum();
} else {
opacity = 1.0;
}
obj1 = dict->lookup("CreationDate");
if (obj1.isString()) {
date.reset(obj1.getString()->copy());
}
obj1 = dict->lookupNF("IRT");
if (obj1.isRef()) {
inReplyTo = obj1.getRef();
} else {
inReplyTo.num = 0;
inReplyTo.gen = 0;
}
obj1 = dict->lookup("Subj");
if (obj1.isString()) {
subject.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("RT");
if (obj1.isName()) {
const char *replyName = obj1.getName();
if (!strcmp(replyName, "R")) {
replyTo = replyTypeR;
} else if (!strcmp(replyName, "Group")) {
replyTo = replyTypeGroup;
} else {
replyTo = replyTypeR;
}
} else {
replyTo = replyTypeR;
}
obj1 = dict->lookup("ExData");
if (obj1.isDict()) {
exData = parseAnnotExternalData(obj1.getDict());
} else {
exData = annotExternalDataMarkupUnknown;
}
}
void AnnotMarkup::setLabel(GooString *new_label) {
if (new_label) {
label = std::make_unique<GooString>(new_label);
//append the unicode marker <FE FF> if needed
if (!label->hasUnicodeMarker()) {
label->prependUnicodeMarker();
}
} else {
label = std::make_unique<GooString>();
}
update ("T", Object(label->copy()));
}
void AnnotMarkup::setPopup(std::unique_ptr<AnnotPopup> &&new_popup) {
// If there exists an old popup annotation that is already
// associated with a page, then we need to remove that
// popup annotation from the page. Otherwise we would have
// dangling references to it.
if (popup && popup->getPageNum() != 0) {
Page *pageobj = doc->getPage(popup->getPageNum());
if (pageobj) {
pageobj->removeAnnot(popup.get());
}
}
if (new_popup) {
const Ref popupRef = new_popup->getRef();
update ("Popup", Object(popupRef.num, popupRef.gen));
new_popup->setParent(this);
popup = std::move(new_popup);
// If this annotation is already added to a page, then we
// add the new popup annotation to the same page.
if (page != 0) {
Page *pageobj = doc->getPage(page);
assert(pageobj != nullptr); // pageobj should exist in doc (see setPage())
pageobj->addAnnot(popup.get());
}
} else {
popup = nullptr;
}
}
void AnnotMarkup::setOpacity(double opacityA) {
opacity = opacityA;
update ("CA", Object(opacity));
invalidateAppearance();
}
void AnnotMarkup::setDate(GooString *new_date) {
if (new_date)
date = std::make_unique<GooString>(new_date);
else
date = std::make_unique<GooString>();
update ("CreationDate", Object(date->copy()));
}
void AnnotMarkup::removeReferencedObjects() {
Page *pageobj = doc->getPage(page);
assert(pageobj != nullptr); // We're called when removing an annot from a page
// Remove popup
if (popup) {
pageobj->removeAnnot(popup.get());
}
Annot::removeReferencedObjects();
}
//------------------------------------------------------------------------
// AnnotText
//------------------------------------------------------------------------
AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rect) :
AnnotMarkup(docA, rect) {
type = typeText;
flags |= flagNoZoom | flagNoRotate;
annotObj.dictSet ("Subtype", Object(objName, "Text"));
initialize (docA, annotObj.getDict());
}
AnnotText::AnnotText(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeText;
flags |= flagNoZoom | flagNoRotate;
initialize (docA, dictObject->getDict());
}
AnnotText::~AnnotText() = default;
void AnnotText::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
obj1 = dict->lookup("Open");
if (obj1.isBool())
open = obj1.getBool();
else
open = gFalse;
obj1 = dict->lookup("Name");
if (obj1.isName()) {
icon = std::make_unique<GooString>(obj1.getName());
} else {
icon = std::make_unique<GooString>("Note");
}
obj1 = dict->lookup("StateModel");
if (obj1.isString()) {
const GooString *modelName = obj1.getString();
Object obj2 = dict->lookup("State");
if (obj2.isString()) {
const GooString *stateName = obj2.getString();
if (!stateName->cmp("Marked")) {
state = stateMarked;
} else if (!stateName->cmp("Unmarked")) {
state = stateUnmarked;
} else if (!stateName->cmp("Accepted")) {
state = stateAccepted;
} else if (!stateName->cmp("Rejected")) {
state = stateRejected;
} else if (!stateName->cmp("Cancelled")) {
state = stateCancelled;
} else if (!stateName->cmp("Completed")) {
state = stateCompleted;
} else if (!stateName->cmp("None")) {
state = stateNone;
} else {
state = stateUnknown;
}
} else {
state = stateUnknown;
}
if (!modelName->cmp("Marked")) {
switch (state) {
case stateUnknown:
state = stateMarked;
break;
case stateAccepted:
case stateRejected:
case stateCancelled:
case stateCompleted:
case stateNone:
state = stateUnknown;
break;
default:
break;
}
} else if (!modelName->cmp("Review")) {
switch (state) {
case stateUnknown:
state = stateNone;
break;
case stateMarked:
case stateUnmarked:
state = stateUnknown;
break;
default:
break;
}
} else {
state = stateUnknown;
}
} else {
state = stateUnknown;
}
}
void AnnotText::setOpen(GBool openA) {
open = openA;
update ("Open", Object(open));
}
void AnnotText::setIcon(GooString *new_icon) {
if (new_icon && icon->cmp(new_icon) == 0)
return;
if (new_icon) {
icon = std::make_unique<GooString>(new_icon);
} else {
icon = std::make_unique<GooString>("Note");
}
update("Name", Object(objName, icon->getCString()));
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) {
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color)
appearBuilder.setDrawColor(color.get(), gTrue);
else
appearBuilder.append ("1 1 1 rg\n");
if (!icon->cmp("Note"))
appearBuilder.append (ANNOT_TEXT_AP_NOTE);
else if (!icon->cmp("Comment"))
appearBuilder.append (ANNOT_TEXT_AP_COMMENT);
else if (!icon->cmp("Key"))
appearBuilder.append (ANNOT_TEXT_AP_KEY);
else if (!icon->cmp("Help"))
appearBuilder.append (ANNOT_TEXT_AP_HELP);
else if (!icon->cmp("NewParagraph"))
appearBuilder.append (ANNOT_TEXT_AP_NEW_PARAGRAPH);
else if (!icon->cmp("Paragraph"))
appearBuilder.append (ANNOT_TEXT_AP_PARAGRAPH);
else if (!icon->cmp("Insert"))
appearBuilder.append (ANNOT_TEXT_AP_INSERT);
else if (!icon->cmp("Cross"))
appearBuilder.append (ANNOT_TEXT_AP_CROSS);
else if (!icon->cmp("Circle"))
appearBuilder.append (ANNOT_TEXT_AP_CIRCLE);
appearBuilder.append ("Q\n");
// Force 24x24 rectangle
PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2);
appearBBox = std::make_unique<AnnotAppearanceBBox>(&fixedRect);
double bbox[4];
appearBBox->getBBoxRect(bbox);
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(),
appearBBox->getPageXMin(), appearBBox->getPageYMin(),
appearBBox->getPageXMax(), appearBBox->getPageYMax(),
getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
//------------------------------------------------------------------------
// AnnotLink
//------------------------------------------------------------------------
AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
type = typeLink;
annotObj.dictSet ("Subtype", Object(objName, "Link"));
initialize (docA, annotObj.getDict());
}
AnnotLink::AnnotLink(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typeLink;
initialize (docA, dictObject->getDict());
}
AnnotLink::~AnnotLink() = default;
void AnnotLink::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
// look for destination
obj1 = dict->lookup("Dest");
if (!obj1.isNull()) {
action.reset(LinkAction::parseDest(&obj1));
// look for action
} else {
obj1 = dict->lookup("A");
if (obj1.isDict()) {
action.reset(LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()));
}
}
obj1 = dict->lookup("H");
if (obj1.isName()) {
const char *effect = obj1.getName();
if (!strcmp(effect, "N")) {
linkEffect = effectNone;
} else if (!strcmp(effect, "I")) {
linkEffect = effectInvert;
} else if (!strcmp(effect, "O")) {
linkEffect = effectOutline;
} else if (!strcmp(effect, "P")) {
linkEffect = effectPush;
} else {
linkEffect = effectInvert;
}
} else {
linkEffect = effectInvert;
}
/*
obj1 = dict->lookup("PA");
if (obj1.isDict()) {
uriAction = NULL;
} else {
uriAction = NULL;
}
obj1.free();
*/
obj1 = dict->lookup("QuadPoints");
if (obj1.isArray()) {
quadrilaterals = std::make_unique<AnnotQuadrilaterals>(obj1.getArray(), rect.get());
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
}
void AnnotLink::draw(Gfx *gfx, GBool printing) {
if (!isVisible (printing))
return;
annotLocker();
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, border.get(), color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// AnnotFreeText
//------------------------------------------------------------------------
AnnotFreeText::AnnotFreeText(PDFDoc *docA, PDFRectangle *rect, const DefaultAppearance &da) :
AnnotMarkup(docA, rect) {
type = typeFreeText;
GooString *daStr = da.toAppearanceString();
annotObj.dictSet ("Subtype", Object(objName, "FreeText"));
annotObj.dictSet("DA", Object(daStr));
initialize (docA, annotObj.getDict());
}
AnnotFreeText::AnnotFreeText(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeFreeText;
initialize(docA, dictObject->getDict());
}
AnnotFreeText::~AnnotFreeText() = default;
void AnnotFreeText::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
obj1 = dict->lookup("DA");
if (obj1.isString()) {
appearanceString.reset(obj1.getString()->copy());
} else {
appearanceString = std::make_unique<GooString>();
error(errSyntaxError, -1, "Bad appearance for annotation");
ok = gFalse;
}
obj1 = dict->lookup("Q");
if (obj1.isInt()) {
quadding = (AnnotFreeTextQuadding) obj1.getInt();
} else {
quadding = quaddingLeftJustified;
}
obj1 = dict->lookup("DS");
if (obj1.isString()) {
styleString.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("CL");
if (obj1.isArray() && obj1.arrayGetLength() >= 4) {
double x1, y1, x2, y2;
Object obj2;
(obj2 = obj1.arrayGet(0), obj2.isNum() ? x1 = obj2.getNum() : x1 = 0);
(obj2 = obj1.arrayGet(1), obj2.isNum() ? y1 = obj2.getNum() : y1 = 0);
(obj2 = obj1.arrayGet(2), obj2.isNum() ? x2 = obj2.getNum() : x2 = 0);
(obj2 = obj1.arrayGet(3), obj2.isNum() ? y2 = obj2.getNum() : y2 = 0);
if (obj1.arrayGetLength() == 6) {
double x3, y3;
(obj2 = obj1.arrayGet(4), obj2.isNum() ? x3 = obj2.getNum() : x3 = 0);
(obj2 = obj1.arrayGet(5), obj2.isNum() ? y3 = obj2.getNum() : y3 = 0);
calloutLine = std::make_unique<AnnotCalloutMultiLine>(x1, y1, x2, y2, x3, y3);
} else {
calloutLine = std::make_unique<AnnotCalloutLine>(x1, y1, x2, y2);
}
}
obj1 = dict->lookup("IT");
if (obj1.isName()) {
const char *intentName = obj1.getName();
if (!strcmp(intentName, "FreeText")) {
intent = intentFreeText;
} else if (!strcmp(intentName, "FreeTextCallout")) {
intent = intentFreeTextCallout;
} else if (!strcmp(intentName, "FreeTextTypeWriter")) {
intent = intentFreeTextTypeWriter;
} else {
intent = intentFreeText;
}
} else {
intent = intentFreeText;
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
obj1 = dict->lookup("BE");
if (obj1.isDict()) {
borderEffect = std::make_unique<AnnotBorderEffect>(obj1.getDict());
}
obj1 = dict->lookup("RD");
if (obj1.isArray()) {
rectangle = parseDiffRectangle(obj1.getArray(), rect.get());
}
obj1 = dict->lookup("LE");
if (obj1.isName()) {
GooString styleName(obj1.getName());
endStyle = parseAnnotLineEndingStyle(&styleName);
} else {
endStyle = annotLineEndingNone;
}
}
void AnnotFreeText::setContents(GooString *new_content) {
Annot::setContents(new_content);
invalidateAppearance();
}
void AnnotFreeText::setDefaultAppearance(const DefaultAppearance &da) {
appearanceString = std::unique_ptr<GooString>(da.toAppearanceString());
update ("DA", Object(appearanceString->copy()));
invalidateAppearance();
}
void AnnotFreeText::setQuadding(AnnotFreeTextQuadding new_quadding) {
quadding = new_quadding;
update ("Q", Object((int)quadding));
invalidateAppearance();
}
void AnnotFreeText::setStyleString(GooString *new_string) {
if (new_string) {
styleString = std::make_unique<GooString>(new_string);
//append the unicode marker <FE FF> if needed
if (!styleString->hasUnicodeMarker()) {
styleString->prependUnicodeMarker();
}
} else {
styleString = std::make_unique<GooString>();
}
update ("DS", Object(styleString->copy()));
}
void AnnotFreeText::setCalloutLine(AnnotCalloutLine *line) {
Object obj1;
if (line == nullptr) {
obj1.setToNull();
calloutLine = nullptr;
} else {
double x1 = line->getX1(), y1 = line->getY1();
double x2 = line->getX2(), y2 = line->getY2();
obj1 = Object( new Array(xref) );
obj1.arrayAdd( Object(x1) );
obj1.arrayAdd( Object(y1) );
obj1.arrayAdd( Object(x2) );
obj1.arrayAdd( Object(y2) );
AnnotCalloutMultiLine *mline = dynamic_cast<AnnotCalloutMultiLine*>(line);
if (mline) {
double x3 = mline->getX3(), y3 = mline->getY3();
obj1.arrayAdd( Object(x3) );
obj1.arrayAdd( Object(y3) );
calloutLine = std::make_unique<AnnotCalloutMultiLine>(x1, y1, x2, y2, x3, y3);
} else {
calloutLine = std::make_unique<AnnotCalloutLine>(x1, y1, x2, y2);
}
}
update("CL", std::move(obj1));
invalidateAppearance();
}
void AnnotFreeText::setIntent(AnnotFreeTextIntent new_intent) {
const char *intentName;
intent = new_intent;
if (new_intent == intentFreeText)
intentName = "FreeText";
else if (new_intent == intentFreeTextCallout)
intentName = "FreeTextCallout";
else // intentFreeTextTypeWriter
intentName = "FreeTextTypeWriter";
update ("IT", Object(objName, intentName));
}
std::unique_ptr<DefaultAppearance> AnnotFreeText::getDefaultAppearance() const {
return std::make_unique<DefaultAppearance>(appearanceString.get());
}
static GfxFont * createAnnotDrawFont(XRef * xref, Dict *fontResDict, const char* resourceName = "AnnotDrawFont", const char* fontname = "Helvetica")
{
const Ref dummyRef = { -1, -1 };
Dict *fontDict = new Dict(xref);
fontDict->add("BaseFont", Object(objName, fontname));
fontDict->add("Subtype", Object(objName, "Type0"));
fontDict->add("Encoding", Object(objName, "WinAnsiEncoding"));
Dict *fontsDict = new Dict(xref);
fontsDict->add(resourceName, Object(fontDict));
fontResDict->add("Font", Object(fontsDict));
return GfxFont::makeFont(xref, resourceName, dummyRef, fontDict);
}
void AnnotFreeText::generateFreeTextAppearance()
{
double borderWidth, ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
borderWidth = border->getWidth();
if (borderWidth > 0)
appearBuilder.setLineStyleForBorder(border.get());
// Box size
const double width = rect->x2 - rect->x1;
const double height = rect->y2 - rect->y1;
// Parse some properties from the appearance string
DefaultAppearance da{appearanceString.get()};
// Default values
if (!da.getFontName().isName())
da.setFontName(Object(objName, "AnnotDrawFont"));
if (da.getFontPtSize() <= 0)
da.setFontPtSize(10);
if (!da.getFontColor())
da.setFontColor(std::make_unique<AnnotColor>(0, 0, 0));
if (!contents)
contents = std::make_unique<GooString>();
// Draw box
GBool doFill = (color && color->getSpace() != AnnotColor::colorTransparent);
GBool doStroke = (borderWidth != 0);
if (doFill || doStroke) {
if (doStroke) {
appearBuilder.setDrawColor(da.getFontColor(), gFalse); // Border color: same as font color
}
appearBuilder.appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re\n", borderWidth/2, width-borderWidth, height-borderWidth);
if (doFill) {
appearBuilder.setDrawColor(color.get(), gTrue);
appearBuilder.append(doStroke ? "B\n" : "f\n");
} else {
appearBuilder.append("S\n");
}
}
// Setup text clipping
const double textmargin = borderWidth * 2;
const double textwidth = width - 2*textmargin;
appearBuilder.appendf ("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", textmargin, textwidth, height - 2*textmargin);
GfxFont *font = nullptr;
// look for font name in default resources
Form *form = doc->getCatalog()->getForm(); // form is owned by catalog, no need to clean it up
Object resourceObj;
if (form && form->getDefaultResourcesObj() && form->getDefaultResourcesObj()->isDict()) {
resourceObj = form->getDefaultResourcesObj()->copy(); // No real copy, but increment refcount of /DR Dict
Dict *resDict = resourceObj.getDict();
Object fontResources = resDict->lookup("Font"); // The 'Font' subdictionary
if (!fontResources.isDict()) {
error(errSyntaxWarning, -1, "Font subdictionary is not a dictionary");
} else {
// Get the font dictionary for the actual requested font
Object fontDictionary = fontResources.getDict()->lookupNF(da.getFontName().getName());
// Resolve reference, if necessary
Ref fontReference = {-1, -1};
if (fontDictionary.isRef()) {
fontReference = fontDictionary.getRef();
fontDictionary = fontDictionary.fetch(xref);
}
if (fontDictionary.isDict()) {
font = GfxFont::makeFont(xref, da.getFontName().getName(), fontReference, fontDictionary.getDict());
} else {
error(errSyntaxWarning, -1, "Font dictionary is not a dictionary");
}
}
}
// if fontname is not in in default resources, create a Helvetica fake font
if (!font) {
Dict *fontResDict = new Dict(xref);
resourceObj = Object(fontResDict);
font = createAnnotDrawFont(xref, fontResDict, da.getFontName().getName());
}
// Set font state
appearBuilder.setDrawColor(da.getFontColor(), gTrue);
appearBuilder.appendf ("BT 1 0 0 1 {0:.2f} {1:.2f} Tm\n", textmargin, height - textmargin - da.getFontPtSize() * font->getDescent());
appearBuilder.setTextFont(da.getFontName(), da.getFontPtSize());
int i = 0;
double xposPrev = 0;
while (i < contents->getLength()) {
GooString out;
double linewidth, xpos;
layoutText(contents.get(), &out, &i, font, &linewidth, textwidth/da.getFontPtSize(), nullptr, gFalse);
linewidth *= da.getFontPtSize();
switch (quadding) {
case quaddingCentered:
xpos = (textwidth - linewidth) / 2;
break;
case quaddingRightJustified:
xpos = textwidth - linewidth;
break;
default: // quaddingLeftJustified:
xpos = 0;
break;
}
appearBuilder.appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -da.getFontPtSize());
appearBuilder.writeString(out);
appearBuilder.append("Tj\n");
xposPrev = xpos;
}
font->decRefCnt();
appearBuilder.append ("ET Q\n");
double bbox[4];
bbox[0] = bbox[1] = 0;
bbox[2] = rect->x2 - rect->x1;
bbox[3] = rect->y2 - rect->y1;
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, std::move(resourceObj));
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, std::move(resourceObj));
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
void AnnotFreeText::draw(Gfx *gfx, GBool printing) {
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
generateFreeTextAppearance();
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
// Before retrieving the res dict, regenerate the appearance stream if needed,
// because AnnotFreeText::draw needs to store font info in the res dict
Object AnnotFreeText::getAppearanceResDict() {
if (appearance.isNull()) {
generateFreeTextAppearance();
}
return Annot::getAppearanceResDict();
}
//------------------------------------------------------------------------
// AnnotLine
//------------------------------------------------------------------------
AnnotLine::AnnotLine(PDFDoc *docA, PDFRectangle *rect) :
AnnotMarkup(docA, rect) {
type = typeLine;
annotObj.dictSet ("Subtype", Object(objName, "Line"));
initialize (docA, annotObj.getDict());
}
AnnotLine::AnnotLine(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeLine;
initialize(docA, dictObject->getDict());
}
AnnotLine::~AnnotLine() = default;
void AnnotLine::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
obj1 = dict->lookup("L");
if (obj1.isArray() && obj1.arrayGetLength() == 4) {
Object obj2;
double x1, y1, x2, y2;
(obj2 = obj1.arrayGet(0), obj2.isNum() ? x1 = obj2.getNum() : x1 = 0);
(obj2 = obj1.arrayGet(1), obj2.isNum() ? y1 = obj2.getNum() : y1 = 0);
(obj2 = obj1.arrayGet(2), obj2.isNum() ? x2 = obj2.getNum() : x2 = 0);
(obj2 = obj1.arrayGet(3), obj2.isNum() ? y2 = obj2.getNum() : y2 = 0);
coord1 = std::make_unique<AnnotCoord>(x1, y1);
coord2 = std::make_unique<AnnotCoord>(x2, y2);
} else {
coord1 = std::make_unique<AnnotCoord>();
coord2 = std::make_unique<AnnotCoord>();
}
obj1 = dict->lookup("LE");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
Object obj2;
obj2 = obj1.arrayGet(0);
if (obj2.isString())
startStyle = parseAnnotLineEndingStyle(obj2.getString());
else
startStyle = annotLineEndingNone;
obj2 = obj1.arrayGet(1);
if (obj2.isString())
endStyle = parseAnnotLineEndingStyle(obj2.getString());
else
endStyle = annotLineEndingNone;
} else {
startStyle = endStyle = annotLineEndingNone;
}
obj1 = dict->lookup("IC");
if (obj1.isArray()) {
interiorColor = std::make_unique<AnnotColor>(obj1.getArray());
}
obj1 = dict->lookup("LL");
if (obj1.isNum()) {
leaderLineLength = obj1.getNum();
} else {
leaderLineLength = 0;
}
obj1 = dict->lookup("LLE");
if (obj1.isNum()) {
leaderLineExtension = obj1.getNum();
if (leaderLineExtension < 0)
leaderLineExtension = 0;
} else {
leaderLineExtension = 0;
}
obj1 = dict->lookup("Cap");
if (obj1.isBool()) {
caption = obj1.getBool();
} else {
caption = gFalse;
}
obj1 = dict->lookup("IT");
if (obj1.isName()) {
const char *intentName = obj1.getName();
if(!strcmp(intentName, "LineArrow")) {
intent = intentLineArrow;
} else if(!strcmp(intentName, "LineDimension")) {
intent = intentLineDimension;
} else {
intent = intentLineArrow;
}
} else {
intent = intentLineArrow;
}
obj1 = dict->lookup("LLO");
if (obj1.isNum()) {
leaderLineOffset = obj1.getNum();
if (leaderLineOffset < 0)
leaderLineOffset = 0;
} else {
leaderLineOffset = 0;
}
obj1 = dict->lookup("CP");
if (obj1.isName()) {
const char *captionName = obj1.getName();
if(!strcmp(captionName, "Inline")) {
captionPos = captionPosInline;
} else if(!strcmp(captionName, "Top")) {
captionPos = captionPosTop;
} else {
captionPos = captionPosInline;
}
} else {
captionPos = captionPosInline;
}
obj1 = dict->lookup("Measure");
if (obj1.isDict()) {
measure = nullptr;
} else {
measure = nullptr;
}
obj1 = dict->lookup("CO");
if (obj1.isArray() && (obj1.arrayGetLength() == 2)) {
Object obj2;
obj2 = obj1.arrayGet(0);
captionTextHorizontal = obj2.isNum() ? obj2.getNum() : 0;
obj2 = obj1.arrayGet(1);
captionTextVertical = obj2.isNum() ? obj2.getNum() : 0;
} else {
captionTextHorizontal = captionTextVertical = 0;
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
}
void AnnotLine::setContents(GooString *new_content) {
Annot::setContents(new_content);
if (caption)
invalidateAppearance();
}
void AnnotLine::setVertices(double x1, double y1, double x2, double y2) {
coord1 = std::make_unique<AnnotCoord>(x1, y1);
coord2 = std::make_unique<AnnotCoord>(x2, y2);
Array *lArray = new Array(xref);
lArray->add( Object(x1) );
lArray->add( Object(y1) );
lArray->add( Object(x2) );
lArray->add( Object(y2) );
update("L", Object(lArray));
invalidateAppearance();
}
void AnnotLine::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) {
startStyle = start;
endStyle = end;
Array *leArray = new Array(xref);
leArray->add( Object(objName, convertAnnotLineEndingStyle( startStyle )) );
leArray->add( Object(objName, convertAnnotLineEndingStyle( endStyle )) );
update("LE", Object(leArray));
invalidateAppearance();
}
void AnnotLine::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color) {
if (new_color) {
Object obj1 = new_color->writeToObject(xref);
update ("IC", std::move(obj1));
interiorColor = std::move(new_color);
} else {
interiorColor = nullptr;
}
invalidateAppearance();
}
void AnnotLine::setLeaderLineLength(double len) {
leaderLineLength = len;
update ("LL", Object(len));
invalidateAppearance();
}
void AnnotLine::setLeaderLineExtension(double len) {
leaderLineExtension = len;
update ("LLE", Object(len));
// LL is required if LLE is present
update ("LL", Object(leaderLineLength));
invalidateAppearance();
}
void AnnotLine::setCaption(bool new_cap) {
caption = new_cap;
update ("Cap", Object(new_cap));
invalidateAppearance();
}
void AnnotLine::setIntent(AnnotLineIntent new_intent) {
const char *intentName;
intent = new_intent;
if (new_intent == intentLineArrow)
intentName = "LineArrow";
else // intentLineDimension
intentName = "LineDimension";
update ("IT", Object(objName, intentName));
}
void AnnotLine::generateLineAppearance()
{
double borderWidth, ca = opacity;
appearBBox = std::make_unique<AnnotAppearanceBBox>(rect.get());
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
appearBuilder.setLineStyleForBorder(border.get());
borderWidth = border->getWidth();
appearBBox->setBorderWidth(std::max(1., borderWidth));
const double x1 = coord1->getX();
const double y1 = coord1->getY();
const double x2 = coord2->getX();
const double y2 = coord2->getY();
// Main segment length
const double main_len = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
// Main segment becomes positive x direction, coord1 becomes (0,0)
Matrix matr;
const double angle = atan2(y2 - y1, x2 - x1);
matr.m[0] = matr.m[3] = cos(angle);
matr.m[1] = sin(angle);
matr.m[2] = -matr.m[1];
matr.m[4] = x1-rect->x1;
matr.m[5] = y1-rect->y1;
double tx, ty, captionwidth = 0, captionheight = 0;
AnnotLineCaptionPos actualCaptionPos = captionPos;
const double fontsize = 9;
const double captionhmargin = 2; // Left and right margin (inline caption only)
const double captionmaxwidth = main_len - 2 * captionhmargin;
Dict *fontResDict;
GfxFont *font;
// Calculate caption width and height
if (caption) {
fontResDict = new Dict(xref);
font = createAnnotDrawFont(xref, fontResDict);
int lines = 0;
int i = 0;
while (i < contents->getLength()) {
GooString out;
double linewidth;
layoutText(contents.get(), &out, &i, font, &linewidth, 0, nullptr, 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 = nullptr;
font = nullptr;
}
// Draw main segment
matr.transform (0, leaderLineLength, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
appearBBox->extendTo (tx, ty);
if (captionwidth != 0 && actualCaptionPos == captionPosInline) { // Break in the middle
matr.transform ((main_len-captionwidth)/2 - captionhmargin, leaderLineLength, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
matr.transform ((main_len+captionwidth)/2 + captionhmargin, leaderLineLength, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
}
matr.transform (main_len, leaderLineLength, &tx, &ty);
appearBuilder.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)
appearBuilder.appendf ("0 g BT /AnnotDrawFont {0:.2f} Tf\n", fontsize); // Font color: black
appearBuilder.appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} Tm\n",
matr.m[0], matr.m[1], matr.m[2], matr.m[3], tx, ty);
appearBuilder.appendf ("0 {0:.2f} Td\n", -fontsize * font->getDescent());
// Draw text
int i = 0;
double xposPrev = 0;
while (i < contents->getLength()) {
GooString out;
double linewidth, xpos;
layoutText(contents.get(), &out, &i, font, &linewidth, 0, nullptr, gFalse);
linewidth *= fontsize;
xpos = (captionwidth - linewidth) / 2;
appearBuilder.appendf("{0:.2f} {1:.2f} Td\n", xpos - xposPrev, -fontsize);
appearBuilder.writeString(out);
appearBuilder.append ("Tj\n");
xposPrev = xpos;
}
appearBuilder.append ("ET\n");
font->decRefCnt();
}
// Draw leader lines
double ll_len = fabs(leaderLineLength) + leaderLineExtension;
double sign = leaderLineLength >= 0 ? 1 : -1;
if (ll_len != 0) {
matr.transform (0, 0, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
appearBBox->extendTo (tx, ty);
matr.transform (0, sign*ll_len, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
appearBBox->extendTo (tx, ty);
matr.transform (main_len, 0, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", tx, ty);
appearBBox->extendTo (tx, ty);
matr.transform (main_len, sign*ll_len, &tx, &ty);
appearBuilder.appendf ("{0:.2f} {1:.2f} l S\n", tx, ty);
appearBBox->extendTo (tx, ty);
}
appearBuilder.append ("Q\n");
double bbox[4];
appearBBox->getBBoxRect(bbox);
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, fontResDict);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, fontResDict);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
void AnnotLine::draw(Gfx *gfx, GBool printing) {
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
generateLineAppearance();
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(),
appearBBox->getPageXMin(), appearBBox->getPageYMin(),
appearBBox->getPageXMax(), appearBBox->getPageYMax(),
getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
// Before retrieving the res dict, regenerate the appearance stream if needed,
// because AnnotLine::draw may need to store font info in the res dict
Object AnnotLine::getAppearanceResDict() {
if (appearance.isNull()) {
generateLineAppearance();
}
return Annot::getAppearanceResDict();
}
//------------------------------------------------------------------------
// AnnotTextMarkup
//------------------------------------------------------------------------
AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
AnnotMarkup(docA, rect) {
switch (subType) {
case typeHighlight:
annotObj.dictSet ("Subtype", Object(objName, "Highlight"));
break;
case typeUnderline:
annotObj.dictSet ("Subtype", Object(objName, "Underline"));
break;
case typeSquiggly:
annotObj.dictSet ("Subtype", Object(objName, "Squiggly"));
break;
case typeStrikeOut:
annotObj.dictSet ("Subtype", Object(objName, "StrikeOut"));
break;
default:
assert (0 && "Invalid subtype for AnnotTextMarkup\n");
}
// Store dummy quadrilateral with null coordinates
Array *quadPoints = new Array(doc->getXRef());
for (int i = 0; i < 4*2; ++i) {
quadPoints->add(Object(0.));
}
annotObj.dictSet ("QuadPoints", Object(quadPoints));
initialize(docA, annotObj.getDict());
}
AnnotTextMarkup::AnnotTextMarkup(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
// the real type will be read in initialize()
type = typeHighlight;
initialize(docA, dictObject->getDict());
}
AnnotTextMarkup::~AnnotTextMarkup() = default;
void AnnotTextMarkup::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
obj1 = dict->lookup("Subtype");
if (obj1.isName()) {
GooString typeName(obj1.getName());
if (!typeName.cmp("Highlight")) {
type = typeHighlight;
} else if (!typeName.cmp("Underline")) {
type = typeUnderline;
} else if (!typeName.cmp("Squiggly")) {
type = typeSquiggly;
} else if (!typeName.cmp("StrikeOut")) {
type = typeStrikeOut;
}
}
obj1 = dict->lookup("QuadPoints");
if (obj1.isArray()) {
quadrilaterals = std::make_unique<AnnotQuadrilaterals>(obj1.getArray(), rect.get());
} else {
error(errSyntaxError, -1, "Bad Annot Text Markup QuadPoints");
ok = gFalse;
}
}
void AnnotTextMarkup::setType(AnnotSubtype new_type) {
const char *typeName = nullptr; /* squelch bogus compiler warning */
switch (new_type) {
case typeHighlight:
typeName = "Highlight";
break;
case typeUnderline:
typeName = "Underline";
break;
case typeSquiggly:
typeName = "Squiggly";
break;
case typeStrikeOut:
typeName = "StrikeOut";
break;
default:
assert(!"Invalid subtype");
}
type = new_type;
update("Subtype", Object(objName, typeName));
invalidateAppearance();
}
void AnnotTextMarkup::setQuadrilaterals(AnnotQuadrilaterals *quadPoints) {
Array *a = new Array(xref);
for (int i = 0; i < quadPoints->getQuadrilateralsLength(); ++i) {
a->add(Object(quadPoints->getX1(i)));
a->add(Object(quadPoints->getY1(i)));
a->add(Object(quadPoints->getX2(i)));
a->add(Object(quadPoints->getY2(i)));
a->add(Object(quadPoints->getX3(i)));
a->add(Object(quadPoints->getY3(i)));
a->add(Object(quadPoints->getX4(i)));
a->add(Object(quadPoints->getY4(i)));
}
quadrilaterals = std::make_unique<AnnotQuadrilaterals>(a, rect.get());
annotObj.dictSet ("QuadPoints", Object(a));
invalidateAppearance();
}
void AnnotTextMarkup::draw(Gfx *gfx, GBool printing) {
double ca = 1;
int i;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull() || type == typeHighlight) {
GBool blendMultiply = gTrue;
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
/* Adjust BBox */
appearBBox = std::make_unique<AnnotAppearanceBBox>(rect.get());
for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
appearBBox->extendTo (quadrilaterals->getX1(i) - rect->x1, quadrilaterals->getY1(i) - rect->y1);
appearBBox->extendTo (quadrilaterals->getX2(i) - rect->x1, quadrilaterals->getY2(i) - rect->y1);
appearBBox->extendTo (quadrilaterals->getX3(i) - rect->x1, quadrilaterals->getY3(i) - rect->y1);
appearBBox->extendTo (quadrilaterals->getX4(i) - rect->x1, quadrilaterals->getY4(i) - rect->y1);
}
switch (type) {
case typeUnderline:
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
appearBuilder.append ("[] 0 d 1 w\n");
for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
double x3, y3, x4, y4;
x3 = quadrilaterals->getX3(i);
y3 = quadrilaterals->getY3(i);
x4 = quadrilaterals->getX4(i);
y4 = quadrilaterals->getY4(i);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", x3, y3);
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", x4, y4);
appearBuilder.append ("S\n");
}
break;
case typeStrikeOut:
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
blendMultiply = gFalse;
appearBuilder.append ("[] 0 d 1 w\n");
for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
double x1, y1, x2, y2;
double x3, y3, x4, y4;
x1 = quadrilaterals->getX1(i);
y1 = quadrilaterals->getY1(i);
x2 = quadrilaterals->getX2(i);
y2 = quadrilaterals->getY2(i);
x3 = quadrilaterals->getX3(i);
y3 = quadrilaterals->getY3(i);
x4 = quadrilaterals->getX4(i);
y4 = quadrilaterals->getY4(i);
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", (x1+x3)/2., (y1+y3)/2.);
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", (x2+x4)/2., (y2+y4)/2.);
appearBuilder.append ("S\n");
}
break;
case typeSquiggly:
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
appearBuilder.append ("[] 0 d 1 w\n");
for (i = 0; i < quadrilaterals->getQuadrilateralsLength(); ++i) {
double x1, y1, x2, y3;
double h6;
x1 = quadrilaterals->getX1(i);
y1 = quadrilaterals->getY1(i);
x2 = quadrilaterals->getX2(i);
y3 = quadrilaterals->getY3(i);
h6 = (y1 - y3) / 6.0;
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", x1, y3+h6);
bool down = false;
do {
down = !down; // Zigzag line
x1 += 2;
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", x1, y3 + (down ? 0 : h6));
} while (x1 < x2);
appearBuilder.append ("S\n");
}
break;
default:
case typeHighlight:
if (color)
appearBuilder.setDrawColor(color.get(), 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;
}
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", x3, y3);
appearBuilder.appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
x3 - h4, y3 + h4, x1 - h4, y1 - h4, x1, y1);
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", x2, y2);
appearBuilder.appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
x2 + h4, y2 - h4, x4 + h4, y4 + h4, x4, y4);
appearBuilder.append ("f\n");
}
appearBBox->setBorderWidth(biggestBorder);
break;
}
appearBuilder.append ("Q\n");
double bbox[4];
bbox[0] = appearBBox->getPageXMin();
bbox[1] = appearBBox->getPageYMin();
bbox[2] = appearBBox->getPageXMax();
bbox[3] = appearBBox->getPageYMax();
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", 1, blendMultiply ? "Multiply" : nullptr);
if (ca == 1) {
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
} else {
aStream = createForm(&appearBuf, bbox, gTrue, resDict);
Dict *resDict2 = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict2);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(),
appearBBox->getPageXMin(), appearBBox->getPageYMin(),
appearBBox->getPageXMax(), appearBBox->getPageYMax(),
getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
//------------------------------------------------------------------------
// AnnotWidget
//------------------------------------------------------------------------
AnnotWidget::AnnotWidget(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typeWidget;
field = nullptr;
initialize(docA, dictObject->getDict());
}
AnnotWidget::AnnotWidget(PDFDoc *docA, Object *dictObject, Object *obj, FormField *fieldA) :
Annot(docA, dictObject, obj) {
type = typeWidget;
field = fieldA;
initialize(docA, dictObject->getDict());
}
AnnotWidget::~AnnotWidget() = default;
void AnnotWidget::initialize(PDFDoc *docA, Dict *dict) {
Object obj1;
form = doc->getCatalog()->getForm();
obj1 = dict->lookup("H");
if (obj1.isName()) {
const char *modeName = obj1.getName();
if(!strcmp(modeName, "N")) {
mode = highlightModeNone;
} else if(!strcmp(modeName, "O")) {
mode = highlightModeOutline;
} else if(!strcmp(modeName, "P") || !strcmp(modeName, "T")) {
mode = highlightModePush;
} else {
mode = highlightModeInvert;
}
} else {
mode = highlightModeInvert;
}
obj1 = dict->lookup("MK");
if (obj1.isDict()) {
appearCharacs = std::make_unique<AnnotAppearanceCharacs>(obj1.getDict());
}
obj1 = dict->lookup("A");
if (obj1.isDict()) {
action.reset(LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()));
}
additionalActions = dict->lookupNF("AA");
obj1 = dict->lookup("Parent");
if (obj1.isDict()) {
parent = nullptr;
} else {
parent = nullptr;
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
}
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(const GooString *text, GooString *outBuf, int *i,
const 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;
if (!text) {
return;
}
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 {
const CharCodeToUnicode *ccToUnicode = font->getToUnicode();
if (!ccToUnicode) {
// This assumes an identity CMap.
outBuf->append((uChar >> 8) & 0xff);
outBuf->append(uChar & 0xff);
} else if (ccToUnicode->mapToCharCode(&uChar, &c, 1)) {
if (font->isCIDFont()) {
// TODO: This assumes an identity CMap. It should be extended to
// handle the general case.
outBuf->append((c >> 8) & 0xff);
outBuf->append(c & 0xff);
} else {
// 8-bit font
outBuf->append(c);
}
} else {
error(errSyntaxError, -1, "AnnotWidget::layoutText, cannot convert U+{0:04uX}", uChar);
}
}
// If we see a space, then we have a linebreak opportunity.
if (uChar == ' ') {
last_i1 = *i;
if (!spacePrev)
last_o1 = last_o2;
spacePrev = 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 != nullptr || charCount != nullptr) {
const char *s = outBuf->getCString();
int len = outBuf->getLength();
if (width != nullptr)
*width = 0.0;
if (charCount != nullptr)
*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 != nullptr)
*width += dx;
if (charCount != nullptr)
*charCount += 1;
s += n;
len -= n;
}
}
}
// Copy the given string to appearBuf, adding parentheses around it and
// escaping characters as appropriate.
void AnnotAppearanceBuilder::writeString(const GooString &str)
{
char c;
int i;
appearBuf->append('(');
for (i = 0; i < str.getLength(); ++i) {
c = str.getChar(i);
if (c == '(' || c == ')' || c == '\\') {
appearBuf->append('\\');
appearBuf->append(c);
} else if (c < 0x20) {
appearBuf->appendf("\\{0:03o}", (unsigned char)c);
} else {
appearBuf->append(c);
}
}
appearBuf->append(')');
}
// Draw the variable text or caption for a field.
bool AnnotAppearanceBuilder::drawText(const GooString *text, const GooString *da, const GfxResources *resources,
const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect,
GBool multiline, int comb, int quadding,
GBool txField, GBool forceZapfDingbats,
XRef *xref, bool *addedDingbatsResource,
GBool password) {
GooList *daToks;
GooString *tok;
GooString convertedText;
const 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
GfxFont *fontToFree = nullptr;
//~ if there is no MK entry, this should use the existing content stream,
//~ and only replace the marked content portion of it
//~ (this is only relevant for Tx fields)
// parse the default appearance string
tfPos = tmPos = -1;
if (da) {
daToks = new 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->push_back(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 = nullptr;
}
// force ZapfDingbats
if (forceZapfDingbats) {
assert(xref != nullptr);
assert(addedDingbatsResource != nullptr);
*addedDingbatsResource = false;
if (tfPos >= 0) {
tok = (GooString *)daToks->get(tfPos);
if (tok->cmp("/ZaDb")) {
tok->clear();
tok->append("/ZaDb");
}
}
}
// get the font and font size
font = nullptr;
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);
fontToFree = new Gfx8BitFont(xref, "ZaDb", r, new GooString("ZapfDingbats"), fontType1, r, d);
delete d;
font = fontToFree;
*addedDingbatsResource = true;
} else {
error(errSyntaxError, -1, "Unknown font in field's DA string");
}
}
} else {
error(errSyntaxError, -1, "Invalid font name in 'Tf' operator in field's DA string");
}
tok = (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<GooString>(daToks);
}
return false;
}
// get the border width
borderWidth = border ? border->getWidth() : 0;
// for a password field, replace all characters with asterisks
if (password) {
int len;
if (text->hasUnicodeMarker())
len = (text->getLength() - 2) / 2;
else
len = text->getLength();
GooString *newText = new GooString;
for (i = 0; i < len; ++i)
newText->append('*');
text = newText;
freeText = gTrue;
}
// 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()) {
Annot::layoutText(text, &convertedText, &i, font, &w, wMax / fontSize, nullptr,
forceZapfDingbats);
y -= fontSize;
}
// approximate the descender for the last line
if (y >= 0.33 * fontSize) {
break;
}
}
if (tfPos >= 0) {
tok = (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()) {
Annot::layoutText(text, &convertedText, &i, font, &w, wMax / fontSize, nullptr,
forceZapfDingbats);
w *= fontSize;
// compute text start position
switch (quadding) {
case quaddingLeftJustified:
default:
x = borderWidth + 2;
break;
case quaddingCentered:
x = (dx - w) / 2;
break;
case quaddingRightJustified:
x = dx - borderWidth - 2 - w;
break;
}
// draw the line
appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize);
writeString(convertedText);
appearBuf->append(" Tj\n");
// next line
xPrev = x;
}
// single-line text
} else {
//~ replace newlines with spaces? - what does Acrobat do?
// comb formatting
if (comb > 0) {
int charCount;
// compute comb spacing
w = (dx - 2 * borderWidth) / comb;
// compute font autosize
if (fontSize == 0) {
fontSize = dy - 2 * borderWidth;
if (w < fontSize) {
fontSize = w;
}
fontSize = floor(fontSize);
if (tfPos >= 0) {
tok = (GooString *)daToks->get(tfPos + 1);
tok->clear();
tok->appendf("{0:.2f}", fontSize);
}
}
i = 0;
Annot::layoutText(text, &convertedText, &i, font, nullptr, 0.0, &charCount,
forceZapfDingbats);
if (charCount > comb)
charCount = comb;
// compute starting text cell
switch (quadding) {
case quaddingLeftJustified:
default:
x = borderWidth;
break;
case quaddingCentered:
x = borderWidth + (comb - charCount) / 2 * 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
const 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(s, n);
writeString(charBuf);
appearBuf->append(" Tj\n");
i++;
s += n;
len -= n;
xPrev = x;
}
// regular (non-comb) formatting
} else {
i = 0;
Annot::layoutText(text, &convertedText, &i, font, &w, 0.0, nullptr,
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->append(" Tj\n");
}
}
// cleanup
appearBuf->append("ET\n");
appearBuf->append("Q\n");
if (txField) {
appearBuf->append("EMC\n");
}
if (daToks) {
deleteGooList<GooString>(daToks);
}
if (freeText) {
delete text;
}
if (fontToFree) {
fontToFree->decRefCnt();
}
return true;
}
// Draw the variable text or caption for a field.
bool AnnotAppearanceBuilder::drawListBox(const FormFieldChoice *fieldChoice, const AnnotBorder *border, const PDFRectangle *rect,
const GooString *da, const GfxResources *resources, int quadding) {
GooList *daToks;
GooString *tok;
GooString convertedText;
const 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->push_back(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 = nullptr;
}
// get the font and font size
font = nullptr;
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<GooString>(daToks);
}
return false;
}
// get the border width
borderWidth = border ? border->getWidth() : 0;
// compute font autosize
if (fontSize == 0) {
wMax = 0;
for (i = 0; i < fieldChoice->getNumChoices(); ++i) {
j = 0;
if (fieldChoice->getChoice(i) == nullptr) {
error(errSyntaxError, -1, "Invalid annotation listbox");
if (daToks) {
deleteGooList<GooString>(daToks);
}
return false;
}
Annot::layoutText(fieldChoice->getChoice(i), &convertedText, &j, font, &w, 0.0, nullptr, 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;
Annot::layoutText(fieldChoice->getChoice(i), &convertedText, &j, font, &w, 0.0, nullptr, 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->append(" Tj\n");
// cleanup
appearBuf->append("ET\n");
appearBuf->append("Q\n");
// next line
y -= 1.1 * fontSize;
}
if (daToks) {
deleteGooList<GooString>(daToks);
}
return true;
}
void AnnotAppearanceBuilder::drawFieldBorder(const FormField *field, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) {
int dashLength;
double *dash;
AnnotColor adjustedColor;
const double w = border->getWidth();
const AnnotColor *aColor = appearCharacs->getBorderColor();
if (!aColor)
aColor = appearCharacs->getBackColor();
if (!aColor)
return;
const double dx = rect->x2 - rect->x1;
const double dy = rect->y2 - rect->y1;
// radio buttons with no caption have a round border
const GBool hasCaption = appearCharacs->getNormalCaption() != nullptr;
if (field->getType() == formButton &&
static_cast<const 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");
// fallthrough
case AnnotBorder::borderSolid:
case AnnotBorder::borderUnderlined:
appearBuf->appendf("{0:.2f} w\n", w);
setDrawColor(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);
setDrawColor(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);
setDrawColor(&adjustedColor, gFalse);
drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w);
adjustedColor = AnnotColor(*aColor);
adjustedColor.adjustColor(border->getStyle() == AnnotBorder::borderBeveled ? -1 : 1);
setDrawColor(&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");
// fallthrough
case AnnotBorder::borderSolid:
appearBuf->appendf("{0:.2f} w\n", w);
setDrawColor(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);
setDrawColor(&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);
setDrawColor(&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);
setDrawColor(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);
}
}
bool AnnotAppearanceBuilder::drawFormField(const FormField *field, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const GooString *appearState, XRef *xref, bool *addedDingbatsResource)
{
// draw the field contents
switch (field->getType()) {
case formButton:
return drawFormFieldButton(static_cast<const FormFieldButton *>(field), resources, da, border, appearCharacs, rect, appearState, xref, addedDingbatsResource);
break;
case formText:
return drawFormFieldText(static_cast<const FormFieldText *>(field), form, resources, da, border, appearCharacs, rect);
case formChoice:
return drawFormFieldChoice(static_cast<const FormFieldChoice *>(field), form, resources, da, border, appearCharacs, rect);
break;
case formSignature:
//~unimp
break;
case formUndef:
default:
error(errSyntaxError, -1, "Unknown field type");
}
return false;
}
bool AnnotAppearanceBuilder::drawFormFieldButton(const FormFieldButton *field, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect, const GooString *appearState, XRef *xref, bool *addedDingbatsResource) {
const GooString *caption = nullptr;
if (appearCharacs)
caption = appearCharacs->getNormalCaption();
switch (field->getButtonType()) {
case formButtonRadio: {
//~ Acrobat doesn't draw a caption if there is no AP dict (?)
if (appearState && appearState->cmp("Off") != 0 &&
field->getState(appearState->getCString())) {
if (caption) {
return drawText(caption, da, resources, border, appearCharacs, rect, gFalse, 0, fieldQuadCenter, gFalse, gTrue, xref, addedDingbatsResource, gFalse);
} else if (appearCharacs) {
const AnnotColor *aColor = appearCharacs->getBorderColor();
if (aColor) {
const double dx = rect->x2 - rect->x1;
const double dy = rect->y2 - rect->y1;
setDrawColor(aColor, gTrue);
drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), gTrue);
}
return true;
}
}
}
break;
case formButtonPush:
if (caption)
return drawText(caption, da, resources, border, appearCharacs, rect, gFalse, 0, fieldQuadCenter, gFalse, gFalse, xref, addedDingbatsResource, gFalse);
break;
case formButtonCheck:
if (appearState && appearState->cmp("Off") != 0) {
if (!caption) {
GooString checkMark("3");
return drawText(&checkMark, da, resources, border, appearCharacs, rect, gFalse, 0, fieldQuadCenter, gFalse, gTrue, xref, addedDingbatsResource, gFalse);
} else {
return drawText(caption, da, resources, border, appearCharacs, rect, gFalse, 0, fieldQuadCenter, gFalse, gTrue, xref, addedDingbatsResource, gFalse);
}
}
break;
}
return true;
}
bool AnnotAppearanceBuilder::drawFormFieldText(const FormFieldText *fieldText, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) {
VariableTextQuadding quadding;
const GooString *contents;
contents = fieldText->getContent();
if (contents) {
quadding = fieldText->hasTextQuadding() ? fieldText->getTextQuadding() : form->getTextQuadding();
int comb = 0;
if (fieldText->isComb())
comb = fieldText->getMaxLen();
return drawText(contents, da, resources, border, appearCharacs, rect,
fieldText->isMultiline(), comb, quadding, gTrue, gFalse, nullptr, nullptr, fieldText->isPassword());
}
return true;
}
bool AnnotAppearanceBuilder::drawFormFieldChoice(const FormFieldChoice *fieldChoice, const Form *form, const GfxResources *resources, const GooString *da, const AnnotBorder *border, const AnnotAppearanceCharacs *appearCharacs, const PDFRectangle *rect) {
const GooString *selected;
VariableTextQuadding quadding;
quadding = fieldChoice->hasTextQuadding() ? fieldChoice->getTextQuadding() : form->getTextQuadding();
if (fieldChoice->isCombo()) {
selected = fieldChoice->getSelectedChoice();
if (selected) {
return drawText(selected, da, resources, border, appearCharacs, rect, gFalse, 0, quadding, gTrue, gFalse, nullptr, nullptr, gFalse);
//~ Acrobat draws a popup icon on the right side
}
// list box
} else {
return drawListBox(fieldChoice, border, rect, da, resources, quadding);
}
return true;
}
void AnnotWidget::generateFieldAppearance(bool *addedDingbatsResource) {
GfxResources *resources;
const GooString *da;
AnnotAppearanceBuilder appearBuilder;
// draw the background
if (appearCharacs) {
const AnnotColor *aColor = appearCharacs->getBackColor();
if (aColor) {
appearBuilder.setDrawColor(aColor, gTrue);
appearBuilder.appendf("0 0 {0:.2f} {1:.2f} re f\n",
rect->x2 - rect->x1, rect->y2 - rect->y1);
}
}
// draw the border
if (appearCharacs && border && border->getWidth() > 0)
appearBuilder.drawFieldBorder(field, border.get(), appearCharacs.get(), rect.get());
da = field->getDefaultAppearance();
if (!da)
da = form->getDefaultAppearance();
resources = form->getDefaultResources();
const bool success = appearBuilder.drawFormField(field, form, resources, da, border.get(), appearCharacs.get(), rect.get(), appearState.get(), xref, addedDingbatsResource);
if (!success && da != form->getDefaultAppearance()) {
da = form->getDefaultAppearance();
appearBuilder.drawFormField(field, form, resources, da, border.get(), appearCharacs.get(), rect.get(), appearState.get(), xref, addedDingbatsResource);
}
const GooString *appearBuf = appearBuilder.buffer();
// build the appearance stream dictionary
Dict *appearDict = new Dict(xref);
appearDict->add("Length", Object(appearBuf->getLength()));
appearDict->add("Subtype", Object(objName, "Form"));
Array *bbox = new Array(xref);
bbox->add(Object(0));
bbox->add(Object(0));
bbox->add(Object(rect->x2 - rect->x1));
bbox->add(Object(rect->y2 - rect->y1));
appearDict->add("BBox", Object(bbox));
// set the resource dictionary
Object *resDict = form->getDefaultResourcesObj();
if (resDict->isDict()) {
appearDict->add("Resources", resDict->copy());
}
// build the appearance stream
Stream *appearStream = new AutoFreeMemStream(copyString(appearBuf->getCString()), 0,
appearBuf->getLength(), Object(appearDict));
appearance = Object(appearStream);
}
void AnnotWidget::updateAppearanceStream()
{
// If this the first time updateAppearanceStream() is called on this widget,
// destroy the AP dictionary because we are going to create a new one.
if (updatedAppearanceStream.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
bool dummyAddDingbatsResource = false; // This is only update so if we didn't need to add
// the dingbats resource we should not need it now
generateFieldAppearance(&dummyAddDingbatsResource);
// Fetch the appearance stream we've just created
Object obj1 = appearance.fetch(xref);
// 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);
// Write the AP dictionary
obj1 = Object(new Dict(xref));
obj1.dictAdd("N", Object(updatedAppearanceStream.num, updatedAppearanceStream.gen));
// Update our internal pointers to the appearance dictionary
appearStreams = std::make_unique<AnnotAppearance>(doc, &obj1);
update("AP", std::move(obj1));
} else {
// Replace the existing appearance stream
xref->setModifiedObject(&obj1, updatedAppearanceStream);
}
}
void AnnotWidget::draw(Gfx *gfx, GBool printing) {
if (!isVisible (printing))
return;
annotLocker();
bool addDingbatsResource = false;
// Only construct the appearance stream when
// - annot doesn't have an AP or
// - NeedAppearances is true
if (field) {
if (appearance.isNull() || (form && form->getNeedAppearances()))
generateFieldAppearance(&addDingbatsResource);
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (addDingbatsResource) {
// We are forcing ZaDb but the font does not exist
// so create a fake one
Dict *fontDict = new Dict(gfx->getXRef());
fontDict->add("BaseFont", Object(objName, "ZapfDingbats"));
fontDict->add("Subtype", Object(objName, "Type1"));
Dict *fontsDict = new Dict(gfx->getXRef());
fontsDict->add("ZaDb", Object(fontDict));
Dict *dict = new Dict(gfx->getXRef());
dict->add("Font", Object(fontsDict));
gfx->pushResources(dict);
delete dict;
}
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
if (addDingbatsResource) {
gfx->popResources();
}
}
//------------------------------------------------------------------------
// AnnotMovie
//------------------------------------------------------------------------
AnnotMovie::AnnotMovie(PDFDoc *docA, PDFRectangle *rect, Movie *movieA) :
Annot(docA, rect) {
type = typeMovie;
annotObj.dictSet ("Subtype", Object(objName, "Movie"));
movie.reset(movieA->copy());
// TODO: create movie dict from movieA
initialize(docA, annotObj.getDict());
}
AnnotMovie::AnnotMovie(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typeMovie;
initialize(docA, dictObject->getDict());
}
AnnotMovie::~AnnotMovie() = default;
void AnnotMovie::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("T");
if (obj1.isString()) {
title.reset(obj1.getString()->copy());
}
Object movieDict = dict->lookup("Movie");
if (movieDict.isDict()) {
Object obj2 = dict->lookup("A");
if (obj2.isDict())
movie = std::make_unique<Movie>(&movieDict, &obj2);
else
movie = std::make_unique<Movie>(&movieDict);
if (!movie->isOk()) {
movie = nullptr;
ok = gFalse;
}
} else {
error(errSyntaxError, -1, "Bad Annot Movie");
ok = gFalse;
}
}
void AnnotMovie::draw(Gfx *gfx, GBool printing) {
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull() && movie->getShowPoster()) {
int width, height;
Object poster = movie->getPoster();
movie->getAspect(&width, &height);
if (width != -1 && height != -1 && !poster.isNone()) {
auto appearBuf = std::make_unique<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");
Dict *imgDict = new Dict(gfx->getXRef());
imgDict->set("MImg", std::move(poster));
Dict *resDict = new Dict(gfx->getXRef());
resDict->set("XObject", Object(imgDict));
Dict *formDict = new Dict(gfx->getXRef());
formDict->set("Length", Object(appearBuf->getLength()));
formDict->set("Subtype", Object(objName, "Form"));
formDict->set("Name", Object(objName, "FRM"));
Array *bboxArray = new Array(gfx->getXRef());
bboxArray->add(Object(0));
bboxArray->add(Object(0));
bboxArray->add(Object(width));
bboxArray->add(Object(height));
formDict->set("BBox", Object(bboxArray));
Array *matrix = new Array(gfx->getXRef());
matrix->add(Object(1));
matrix->add(Object(0));
matrix->add(Object(0));
matrix->add(Object(1));
matrix->add(Object(-width / 2));
matrix->add(Object(-height / 2));
formDict->set("Matrix", Object(matrix));
formDict->set("Resources", Object(resDict));
Stream *mStream = new AutoFreeMemStream(copyString(appearBuf->getCString()), 0,
appearBuf->getLength(), Object(formDict));
Dict *dict = new Dict(gfx->getXRef());
dict->set("FRM", Object(mStream));
Dict *resDict2 = new Dict(gfx->getXRef());
resDict2->set("XObject", Object(dict));
appearBuf = std::make_unique<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;
appearance = createForm(appearBuf.get(), bbox, gFalse, resDict2);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// AnnotScreen
//------------------------------------------------------------------------
AnnotScreen::AnnotScreen(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
type = typeScreen;
annotObj.dictSet ("Subtype", Object(objName, "Screen"));
initialize(docA, annotObj.getDict());
}
AnnotScreen::AnnotScreen(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typeScreen;
initialize(docA, dictObject->getDict());
}
AnnotScreen::~AnnotScreen() = default;
void AnnotScreen::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("T");
if (obj1.isString()) {
title.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("A");
if (obj1.isDict()) {
action.reset(LinkAction::parseAction(&obj1, doc->getCatalog()->getBaseURI()));
if (action && action->getKind() == actionRendition && page == 0) {
error (errSyntaxError, -1, "Invalid Rendition action: associated screen annotation without P");
action = nullptr;
ok = gFalse;
}
}
additionalActions = dict->lookupNF("AA");
obj1 = dict->lookup("MK");
if (obj1.isDict()) {
appearCharacs = std::make_unique<AnnotAppearanceCharacs>(obj1.getDict());
}
}
LinkAction* AnnotScreen::getAdditionalAction(AdditionalActionsType type)
{
if (type == actionFocusIn || type == actionFocusOut) // not defined for screen annotation
return nullptr;
return ::getAdditionalAction(type, &additionalActions, doc);
}
//------------------------------------------------------------------------
// AnnotStamp
//------------------------------------------------------------------------
AnnotStamp::AnnotStamp(PDFDoc *docA, PDFRectangle *rect) :
AnnotMarkup(docA, rect) {
type = typeStamp;
annotObj.dictSet ("Subtype", Object(objName, "Stamp"));
initialize(docA, annotObj.getDict());
}
AnnotStamp::AnnotStamp(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeStamp;
initialize(docA, dictObject->getDict());
}
AnnotStamp::~AnnotStamp() = default;
void AnnotStamp::initialize(PDFDoc *docA, Dict* dict) {
Object obj1 = dict->lookup("Name");
if (obj1.isName()) {
icon = std::make_unique<GooString>(obj1.getName());
} else {
icon = std::make_unique<GooString>("Draft");
}
}
void AnnotStamp::setIcon(GooString *new_icon) {
if (new_icon) {
icon = std::make_unique<GooString>(new_icon);
} else {
icon = std::make_unique<GooString>();
}
update("Name", Object(objName, icon->getCString()));
invalidateAppearance();
}
//------------------------------------------------------------------------
// AnnotGeometry
//------------------------------------------------------------------------
AnnotGeometry::AnnotGeometry(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
AnnotMarkup(docA, rect) {
switch (subType) {
case typeSquare:
annotObj.dictSet ("Subtype", Object(objName, "Square"));
break;
case typeCircle:
annotObj.dictSet ("Subtype", Object(objName, "Circle"));
break;
default:
assert (0 && "Invalid subtype for AnnotGeometry\n");
}
initialize(docA, annotObj.getDict());
}
AnnotGeometry::AnnotGeometry(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
// the real type will be read in initialize()
type = typeSquare;
initialize(docA, dictObject->getDict());
}
AnnotGeometry::~AnnotGeometry() = default;
void AnnotGeometry::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("Subtype");
if (obj1.isName()) {
GooString typeName(obj1.getName());
if (!typeName.cmp("Square")) {
type = typeSquare;
} else if (!typeName.cmp("Circle")) {
type = typeCircle;
}
}
obj1 = dict->lookup("IC");
if (obj1.isArray()) {
interiorColor = std::make_unique<AnnotColor>(obj1.getArray());
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
obj1 = dict->lookup("BE");
if (obj1.isDict()) {
borderEffect = std::make_unique<AnnotBorderEffect>(obj1.getDict());
}
obj1 = dict->lookup("RD");
if (obj1.isArray()) {
geometryRect = parseDiffRectangle(obj1.getArray(), rect.get());
}
}
void AnnotGeometry::setType(AnnotSubtype new_type) {
const char *typeName = nullptr; /* squelch bogus compiler warning */
switch (new_type) {
case typeSquare:
typeName = "Square";
break;
case typeCircle:
typeName = "Circle";
break;
default:
assert(!"Invalid subtype");
}
type = new_type;
update("Subtype", Object(objName, typeName));
invalidateAppearance();
}
void AnnotGeometry::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color) {
if (new_color) {
Object obj1 = new_color->writeToObject(xref);
update ("IC", std::move(obj1));
interiorColor = std::move(new_color);
} else {
interiorColor = nullptr;
}
invalidateAppearance();
}
void AnnotGeometry::draw(Gfx *gfx, GBool printing) {
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color)
appearBuilder.setDrawColor(color.get(), gFalse);
double borderWidth = border->getWidth();
appearBuilder.setLineStyleForBorder(border.get());
if (interiorColor)
appearBuilder.setDrawColor(interiorColor.get(), gTrue);
if (type == typeSquare) {
appearBuilder.appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re\n",
borderWidth / 2.0, borderWidth / 2.0,
(rect->x2 - rect->x1) - borderWidth,
(rect->y2 - rect->y1) - borderWidth);
} 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;
appearBuilder.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;
appearBuilder.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;
appearBuilder.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;
appearBuilder.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;
appearBuilder.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)
appearBuilder.append ("b\n");
else
appearBuilder.append ("S\n");
appearBuilder.append ("Q\n");
double bbox[4];
bbox[0] = bbox[1] = 0;
bbox[2] = rect->x2 - rect->x1;
bbox[3] = rect->y2 - rect->y1;
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// AnnotPolygon
//------------------------------------------------------------------------
AnnotPolygon::AnnotPolygon(PDFDoc *docA, PDFRectangle *rect, AnnotSubtype subType) :
AnnotMarkup(docA, rect) {
switch (subType) {
case typePolygon:
annotObj.dictSet ("Subtype", Object(objName, "Polygon"));
break;
case typePolyLine:
annotObj.dictSet ("Subtype", Object(objName, "PolyLine"));
break;
default:
assert (0 && "Invalid subtype for AnnotGeometry\n");
}
// Store dummy path with one null vertex only
Array *a = new Array(doc->getXRef());
a->add(Object(0.));
a->add(Object(0.));
annotObj.dictSet("Vertices", Object(a));
initialize(docA, annotObj.getDict());
}
AnnotPolygon::AnnotPolygon(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
// the real type will be read in initialize()
type = typePolygon;
initialize(docA, dictObject->getDict());
}
AnnotPolygon::~AnnotPolygon() = default;
void AnnotPolygon::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("Subtype");
if (obj1.isName()) {
GooString typeName(obj1.getName());
if (!typeName.cmp("Polygon")) {
type = typePolygon;
} else if (!typeName.cmp("PolyLine")) {
type = typePolyLine;
}
}
obj1 = dict->lookup("Vertices");
if (obj1.isArray()) {
vertices = std::make_unique<AnnotPath>(obj1.getArray());
} else {
vertices = std::make_unique<AnnotPath>();
error(errSyntaxError, -1, "Bad Annot Polygon Vertices");
ok = gFalse;
}
obj1 = dict->lookup("LE");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
Object obj2 = obj1.arrayGet(0);
if(obj2.isString())
startStyle = parseAnnotLineEndingStyle(obj2.getString());
else
startStyle = annotLineEndingNone;
obj2 = obj1.arrayGet(1);
if(obj2.isString())
endStyle = parseAnnotLineEndingStyle(obj2.getString());
else
endStyle = annotLineEndingNone;
} else {
startStyle = endStyle = annotLineEndingNone;
}
obj1 = dict->lookup("IC");
if (obj1.isArray()) {
interiorColor = std::make_unique<AnnotColor>(obj1.getArray());
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
obj1 = dict->lookup("BE");
if (obj1.isDict()) {
borderEffect = std::make_unique<AnnotBorderEffect>(obj1.getDict());
}
obj1 = dict->lookup("IT");
if (obj1.isName()) {
const char *intentName = obj1.getName();
if(!strcmp(intentName, "PolygonCloud")) {
intent = polygonCloud;
} else if(!strcmp(intentName, "PolyLineDimension")) {
intent = polylineDimension;
} else {
intent = polygonDimension;
}
} else {
intent = polygonCloud;
}
}
void AnnotPolygon::setType(AnnotSubtype new_type) {
const char *typeName = nullptr; /* squelch bogus compiler warning */
switch (new_type) {
case typePolygon:
typeName = "Polygon";
break;
case typePolyLine:
typeName = "PolyLine";
break;
default:
assert(!"Invalid subtype");
}
type = new_type;
update("Subtype", Object(objName, typeName));
invalidateAppearance();
}
void AnnotPolygon::setVertices(AnnotPath *path) {
Array *a = new Array(xref);
for (int i = 0; i < path->getCoordsLength(); i++) {
a->add(Object(path->getX(i)));
a->add(Object(path->getY(i)));
}
vertices = std::make_unique<AnnotPath>(a);
update("Vertices", Object(a));
invalidateAppearance();
}
void AnnotPolygon::setStartEndStyle(AnnotLineEndingStyle start, AnnotLineEndingStyle end) {
startStyle = start;
endStyle = end;
Array *a = new Array(xref);
a->add( Object(objName, convertAnnotLineEndingStyle( startStyle )) );
a->add( Object(objName, convertAnnotLineEndingStyle( endStyle )) );
update("LE", Object(a));
invalidateAppearance();
}
void AnnotPolygon::setInteriorColor(std::unique_ptr<AnnotColor> &&new_color) {
if (new_color) {
Object obj1 = new_color->writeToObject(xref);
update ("IC", std::move(obj1));
interiorColor = std::move(new_color);
}
invalidateAppearance();
}
void AnnotPolygon::setIntent(AnnotPolygonIntent new_intent) {
const char *intentName;
intent = new_intent;
if (new_intent == polygonCloud)
intentName = "PolygonCloud";
else if (new_intent == polylineDimension)
intentName = "PolyLineDimension";
else // polygonDimension
intentName = "PolygonDimension";
update ("IT", Object(objName, intentName));
}
void AnnotPolygon::draw(Gfx *gfx, GBool printing) {
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
appearBBox = std::make_unique<AnnotAppearanceBBox>(rect.get());
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
appearBuilder.setLineStyleForBorder(border.get());
appearBBox->setBorderWidth(std::max(1., border->getWidth()));
if (interiorColor) {
appearBuilder.setDrawColor(interiorColor.get(), gTrue);
}
if (vertices->getCoordsLength() != 0) {
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1);
appearBBox->extendTo (vertices->getX(0) - rect->x1, vertices->getY(0) - rect->y1);
for (int i = 1; i < vertices->getCoordsLength(); ++i) {
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1);
appearBBox->extendTo (vertices->getX(i) - rect->x1, vertices->getY(i) - rect->y1);
}
if (type == typePolygon) {
if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent) {
appearBuilder.append ("b\n");
} else {
appearBuilder.append ("s\n");
}
} else {
appearBuilder.append ("S\n");
}
}
appearBuilder.append ("Q\n");
double bbox[4];
appearBBox->getBBoxRect(bbox);
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(),
appearBBox->getPageXMin(), appearBBox->getPageYMin(),
appearBBox->getPageXMax(), appearBBox->getPageYMax(),
getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
//------------------------------------------------------------------------
// AnnotCaret
//------------------------------------------------------------------------
AnnotCaret::AnnotCaret(PDFDoc *docA, PDFRectangle *rect) :
AnnotMarkup(docA, rect) {
type = typeCaret;
annotObj.dictSet ("Subtype", Object(objName, "Caret"));
initialize(docA, annotObj.getDict());
}
AnnotCaret::AnnotCaret(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeCaret;
initialize(docA, dictObject->getDict());
}
AnnotCaret::~AnnotCaret() = default;
void AnnotCaret::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
symbol = symbolNone;
obj1 = dict->lookup("Sy");
if (obj1.isName()) {
GooString typeName(obj1.getName());
if (!typeName.cmp("P")) {
symbol = symbolP;
} else if (!typeName.cmp("None")) {
symbol = symbolNone;
}
}
obj1 = dict->lookup("RD");
if (obj1.isArray()) {
caretRect = parseDiffRectangle(obj1.getArray(), rect.get());
}
}
void AnnotCaret::setSymbol(AnnotCaretSymbol new_symbol) {
symbol = new_symbol;
update("Sy", Object(objName, new_symbol == symbolP ? "P" : "None"));
invalidateAppearance();
}
//------------------------------------------------------------------------
// AnnotInk
//------------------------------------------------------------------------
AnnotInk::AnnotInk(PDFDoc *docA, PDFRectangle *rect) :
AnnotMarkup(docA, rect) {
type = typeInk;
annotObj.dictSet ("Subtype", Object(objName, "Ink"));
// Store dummy path with one null vertex only
Array *inkList = new Array(doc->getXRef());
Array *vList = new Array(doc->getXRef());
vList->add(Object(0.));
vList->add(Object(0.));
inkList->add(Object(vList));
annotObj.dictSet("InkList", Object(inkList));
initialize(docA, annotObj.getDict());
}
AnnotInk::AnnotInk(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeInk;
initialize(docA, dictObject->getDict());
}
AnnotInk::~AnnotInk() {
freeInkList();
}
void AnnotInk::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("InkList");
if (obj1.isArray()) {
parseInkList(obj1.getArray());
} else {
inkListLength = 0;
inkList = nullptr;
error(errSyntaxError, -1, "Bad Annot Ink List");
ok = gFalse;
}
obj1 = dict->lookup("BS");
if (obj1.isDict()) {
border = std::make_unique<AnnotBorderBS>(obj1.getDict());
} else if (!border) {
border = std::make_unique<AnnotBorderBS>();
}
}
void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array) {
for (int i = 0; i < n_paths; ++i) {
AnnotPath *path = paths[i];
Array *a = new Array(xref);
for (int j = 0; j < path->getCoordsLength(); ++j) {
a->add(Object(path->getX(j)));
a->add(Object(path->getY(j)));
}
dest_array->add(Object(a));
}
}
void AnnotInk::parseInkList(Array *array) {
inkListLength = array->getLength();
inkList = (AnnotPath **) gmallocn ((inkListLength), sizeof(AnnotPath *));
memset(inkList, 0, inkListLength * sizeof(AnnotPath *));
for (int i = 0; i < inkListLength; i++) {
Object obj2 = array->get(i);
if (obj2.isArray())
inkList[i] = new AnnotPath(obj2.getArray());
}
}
void AnnotInk::freeInkList() {
if (inkList) {
for (int i = 0; i < inkListLength; ++i)
delete inkList[i];
gfree(inkList);
}
}
void AnnotInk::setInkList(AnnotPath **paths, int n_paths) {
freeInkList();
Array *a = new Array(xref);
writeInkList(paths, n_paths, a);
parseInkList(a);
annotObj.dictSet ("InkList", Object(a));
invalidateAppearance();
}
void AnnotInk::draw(Gfx *gfx, GBool printing) {
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
appearBBox = std::make_unique<AnnotAppearanceBBox>(rect.get());
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color) {
appearBuilder.setDrawColor(color.get(), gFalse);
}
appearBuilder.setLineStyleForBorder(border.get());
appearBBox->setBorderWidth(std::max(1., border->getWidth()));
for (int i = 0; i < inkListLength; ++i) {
const AnnotPath * path = inkList[i];
if (path && path->getCoordsLength() != 0) {
appearBuilder.appendf ("{0:.2f} {1:.2f} m\n", path->getX(0) - rect->x1, path->getY(0) - rect->y1);
appearBBox->extendTo (path->getX(0) - rect->x1, path->getY(0) - rect->y1);
for (int j = 1; j < path->getCoordsLength(); ++j) {
appearBuilder.appendf ("{0:.2f} {1:.2f} l\n", path->getX(j) - rect->x1, path->getY(j) - rect->y1);
appearBBox->extendTo (path->getX(j) - rect->x1, path->getY(j) - rect->y1);
}
appearBuilder.append ("S\n");
}
}
appearBuilder.append ("Q\n");
double bbox[4];
appearBBox->getBBoxRect(bbox);
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(),
appearBBox->getPageXMin(), appearBBox->getPageYMin(),
appearBBox->getPageXMax(), appearBBox->getPageYMax(),
getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
//------------------------------------------------------------------------
// AnnotFileAttachment
//------------------------------------------------------------------------
AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, PDFRectangle *rect, GooString *filename) :
AnnotMarkup(docA, rect) {
type = typeFileAttachment;
annotObj.dictSet("Subtype", Object(objName, "FileAttachment"));
annotObj.dictSet("FS", Object(filename->copy()));
initialize(docA, annotObj.getDict());
}
AnnotFileAttachment::AnnotFileAttachment(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeFileAttachment;
initialize(docA, dictObject->getDict());
}
AnnotFileAttachment::~AnnotFileAttachment() = default;
void AnnotFileAttachment::initialize(PDFDoc *docA, Dict* dict) {
Object obj1;
obj1 = dict->lookup("FS");
if (obj1.isDict() || obj1.isString()) {
file = obj1.copy();
} else {
error(errSyntaxError, -1, "Bad Annot File Attachment");
ok = gFalse;
}
obj1 = dict->lookup("Name");
if (obj1.isName()) {
name = std::make_unique<GooString>(obj1.getName());
} else {
name = std::make_unique<GooString>("PushPin");
}
}
#define ANNOT_FILE_ATTACHMENT_AP_PUSHPIN \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 5 4 m 6 5 l S\n" \
"2 w\n" \
"11 14 m 9 12 l 6 12 l 13 5 l 13 8 l 15 10 l 18 11 l 20 11 l 12 19 l 12\n" \
"17 l 11 14 l h\n" \
"11 14 m S\n" \
"3 w\n" \
"6 5 m 9 8 l S\n" \
"0.729412 0.741176 0.713725 RG 2 w\n" \
"5 5 m 6 6 l S\n" \
"2 w\n" \
"11 15 m 9 13 l 6 13 l 13 6 l 13 9 l 15 11 l 18 12 l 20 12 l 12 20 l 12\n" \
"18 l 11 15 l h\n" \
"11 15 m S\n" \
"3 w\n" \
"6 6 m 9 9 l S\n"
#define ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 16.645 12.035 m 12.418 7.707 l 10.902 6.559 6.402 11.203 8.09 12.562 c\n" \
"14.133 18.578 l 14.949 19.387 16.867 19.184 17.539 18.465 c 20.551\n" \
"15.23 l 21.191 14.66 21.336 12.887 20.426 12.102 c 13.18 4.824 l 12.18\n" \
"3.82 6.25 2.566 4.324 4.461 c 3 6.395 3.383 11.438 4.711 12.801 c 9.648\n" \
"17.887 l S\n" \
"0.729412 0.741176 0.713725 RG 16.645 13.035 m 12.418 8.707 l\n" \
"10.902 7.559 6.402 12.203 8.09 13.562 c\n" \
"14.133 19.578 l 14.949 20.387 16.867 20.184 17.539 19.465 c 20.551\n" \
"16.23 l 21.191 15.66 21.336 13.887 20.426 13.102 c 13.18 5.824 l 12.18\n" \
"4.82 6.25 3.566 4.324 5.461 c 3 7.395 3.383 12.438 4.711 13.801 c 9.648\n" \
"18.887 l S\n"
#define ANNOT_FILE_ATTACHMENT_AP_GRAPH \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 1 w\n" \
"1 J\n" \
"0 j\n" \
"[] 0.0 d\n" \
"4 M 18.5 15.5 m 18.5 13.086 l 16.086 15.5 l 18.5 15.5 l h\n" \
"18.5 15.5 m S\n" \
"7 7 m 10 11 l 13 9 l 18 15 l S\n" \
"0.729412 0.741176 0.713725 RG 7 8 m 10 12 l 13 10 l 18 16 l S\n" \
"18.5 16.5 m 18.5 14.086 l 16.086 16.5 l 18.5 16.5 l h\n" \
"18.5 16.5 m S\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 j\n" \
"3 19 m 3 3 l 21 3 l S\n" \
"0.729412 0.741176 0.713725 RG 3 20 m 3 4 l 21 4 l S\n"
#define ANNOT_FILE_ATTACHMENT_AP_TAG \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 0.999781 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M q 1 0 0 -1 0 24 cm\n" \
"8.492 8.707 m 8.492 9.535 7.82 10.207 6.992 10.207 c 6.164 10.207 5.492\n" \
"9.535 5.492 8.707 c 5.492 7.879 6.164 7.207 6.992 7.207 c 7.82 7.207\n" \
"8.492 7.879 8.492 8.707 c h\n" \
"8.492 8.707 m S Q\n" \
"2 w\n" \
"20.078 11.414 m 20.891 10.602 20.785 9.293 20.078 8.586 c 14.422 2.93 l\n" \
"13.715 2.223 12.301 2.223 11.594 2.93 c 3.816 10.707 l 3.109 11.414\n" \
"2.402 17.781 3.816 19.195 c 5.23 20.609 11.594 19.902 12.301 19.195 c\n" \
"20.078 11.414 l h\n" \
"20.078 11.414 m S\n" \
"0.729412 0.741176 0.713725 RG 20.078 12.414 m\n" \
"20.891 11.605 20.785 10.293 20.078 9.586 c 14.422 3.93 l\n" \
"13.715 3.223 12.301 3.223 11.594 3.93 c 3.816 11.707 l 3.109 12.414\n" \
"2.402 18.781 3.816 20.195 c 5.23 21.609 11.594 20.902 12.301 20.195 c\n" \
"20.078 12.414 l h\n" \
"20.078 12.414 m S\n" \
"0.533333 0.541176 0.521569 RG 1 w\n" \
"0 j\n" \
"11.949 13.184 m 16.191 8.941 l S\n" \
"0.729412 0.741176 0.713725 RG 11.949 14.184 m 16.191 9.941 l S\n" \
"0.533333 0.541176 0.521569 RG 14.07 6.82 m 9.828 11.062 l S\n" \
"0.729412 0.741176 0.713725 RG 14.07 7.82 m 9.828 12.062 l S\n" \
"0.533333 0.541176 0.521569 RG 6.93 15.141 m 8 20 14.27 20.5 16 20.5 c\n" \
"18.094 20.504 19.5 20 19.5 18 c 19.5 16.699 20.91 16.418 22.5 16.5 c S\n" \
"0.729412 0.741176 0.713725 RG 0.999781 w\n" \
"1 j\n" \
"q 1 0 0 -1 0 24 cm\n" \
"8.492 7.707 m 8.492 8.535 7.82 9.207 6.992 9.207 c 6.164 9.207 5.492\n" \
"8.535 5.492 7.707 c 5.492 6.879 6.164 6.207 6.992 6.207 c 7.82 6.207\n" \
"8.492 6.879 8.492 7.707 c h\n" \
"8.492 7.707 m S Q\n" \
"1 w\n" \
"0 j\n" \
"6.93 16.141 m 8 21 14.27 21.5 16 21.5 c 18.094 21.504 19.5 21 19.5 19 c\n" \
"19.5 17.699 20.91 17.418 22.5 17.5 c S\n"
void AnnotFileAttachment::draw(Gfx *gfx, GBool printing) {
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color)
appearBuilder.setDrawColor(color.get(), gTrue);
else
appearBuilder.append ("1 1 1 rg\n");
if (!name->cmp("PushPin"))
appearBuilder.append (ANNOT_FILE_ATTACHMENT_AP_PUSHPIN);
else if (!name->cmp("Paperclip"))
appearBuilder.append (ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP);
else if (!name->cmp("Graph"))
appearBuilder.append (ANNOT_FILE_ATTACHMENT_AP_GRAPH);
else if (!name->cmp("Tag"))
appearBuilder.append (ANNOT_FILE_ATTACHMENT_AP_TAG);
appearBuilder.append ("Q\n");
double bbox[4];
bbox[0] = bbox[1] = 0;
bbox[2] = bbox[3] = 24;
if (ca == 1) {
appearance = createForm (appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm (appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// AnnotSound
//------------------------------------------------------------------------
AnnotSound::AnnotSound(PDFDoc *docA, PDFRectangle *rect, Sound *soundA) :
AnnotMarkup(docA, rect) {
type = typeSound;
annotObj.dictSet ("Subtype", Object(objName, "Sound"));
annotObj.dictSet ("Sound", soundA->getObject()->copy());
initialize(docA, annotObj.getDict());
}
AnnotSound::AnnotSound(PDFDoc *docA, Object *dictObject, Object *obj) :
AnnotMarkup(docA, dictObject, obj) {
type = typeSound;
initialize(docA, dictObject->getDict());
}
AnnotSound::~AnnotSound() = default;
void AnnotSound::initialize(PDFDoc *docA, Dict* dict) {
Object obj1 = dict->lookup("Sound");
sound.reset(Sound::parseSound(&obj1));
if (!sound) {
error(errSyntaxError, -1, "Bad Annot Sound");
ok = gFalse;
}
obj1 = dict->lookup("Name");
if (obj1.isName()) {
name = std::make_unique<GooString>(obj1.getName());
} else {
name = std::make_unique<GooString>("Speaker");
}
}
#define ANNOT_SOUND_AP_SPEAKER \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"0 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 4 14 m 4.086 8.043 l 7 8 l 11 4 l 11 18 l 7 14 l 4 14 l h\n" \
"4 14 m S\n" \
"1 w\n" \
"1 J\n" \
"0 j\n" \
"13.699 15.398 m 14.699 13.398 14.699 9.398 13.699 7.398 c S\n" \
"18.199 19.398 m 21.199 17.398 21.199 5.398 18.199 3.398 c S\n" \
"16 17.398 m 18 16.398 18 7.398 16 5.398 c S\n" \
"0.729412 0.741176 0.713725 RG 2 w\n" \
"0 J\n" \
"1 j\n" \
"4 15 m 4.086 9.043 l 7 9 l 11 5 l 11 19 l 7 15 l 4 15 l h\n" \
"4 15 m S\n" \
"1 w\n" \
"1 J\n" \
"0 j\n" \
"13.699 16 m 14.699 14 14.699 10 13.699 8 c S\n" \
"18.199 20 m 21.199 18 21.199 6 18.199 4 c S\n" \
"16 18 m 18 17 18 8 16 6 c S\n"
#define ANNOT_SOUND_AP_MIC \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"0 j\n" \
"[] 0.0 d\n" \
"4 M 12 20 m 12 20 l 13.656 20 15 18.656 15 17 c 15 13 l 15 11.344 13.656 10\n" \
"12 10 c 12 10 l 10.344 10 9 11.344 9 13 c 9 17 l 9 18.656 10.344 20 12\n" \
"20 c h\n" \
"12 20 m S\n" \
"1 w\n" \
"17.5 14.5 m 17.5 11.973 l 17.5 8.941 15.047 6.5 12 6.5 c 8.953 6.5 6.5\n" \
"8.941 6.5 11.973 c 6.5 14.5 l S\n" \
"2 w\n" \
"0 J\n" \
"12 6.52 m 12 3 l S\n" \
"1 J\n" \
"8 3 m 16 3 l S\n" \
"0.729412 0.741176 0.713725 RG 12 21 m 12 21 l 13.656 21 15 19.656 15 18 c\n" \
"15 14 l 15 12.344 13.656 11 12 11 c 12 11 l 10.344 11 9 12.344 9 14 c\n" \
"9 18 l 9 19.656 10.344 21 12 21 c h\n" \
"12 21 m S\n" \
"1 w\n" \
"17.5 15.5 m 17.5 12.973 l 17.5 9.941 15.047 7.5 12 7.5 c 8.953 7.5 6.5\n" \
"9.941 6.5 12.973 c 6.5 15.5 l S\n" \
"2 w\n" \
"0 J\n" \
"12 7.52 m 12 4 l S\n" \
"1 J\n" \
"8 4 m 16 4 l S\n"
void AnnotSound::draw(Gfx *gfx, GBool printing) {
Object obj;
double ca = 1;
if (!isVisible (printing))
return;
annotLocker();
if (appearance.isNull()) {
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append ("q\n");
if (color)
appearBuilder.setDrawColor(color.get(), gTrue);
else
appearBuilder.append ("1 1 1 rg\n");
if (!name->cmp("Speaker"))
appearBuilder.append (ANNOT_SOUND_AP_SPEAKER);
else if (!name->cmp("Mic"))
appearBuilder.append (ANNOT_SOUND_AP_MIC);
appearBuilder.append ("Q\n");
double bbox[4];
bbox[0] = bbox[1] = 0;
bbox[2] = bbox[3] = 24;
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, gFalse, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, gTrue, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, gFalse, resDict);
}
}
// draw the appearance stream
obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(),
rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
//------------------------------------------------------------------------
// Annot3D
//------------------------------------------------------------------------
Annot3D::Annot3D(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
type = type3D;
annotObj.dictSet ("Subtype", Object(objName, "3D"));
initialize(docA, annotObj.getDict());
}
Annot3D::Annot3D(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = type3D;
initialize(docA, dictObject->getDict());
}
Annot3D::~Annot3D() = default;
void Annot3D::initialize(PDFDoc *docA, Dict* dict) {
Object obj1 = dict->lookup("3DA");
if (obj1.isDict()) {
activation = std::make_unique<Activation>(obj1.getDict());
}
}
Annot3D::Activation::Activation(Dict *dict) {
Object obj1;
obj1 = dict->lookup("A");
if (obj1.isName()) {
const char *name = obj1.getName();
if(!strcmp(name, "PO")) {
aTrigger = aTriggerPageOpened;
} else if(!strcmp(name, "PV")) {
aTrigger = aTriggerPageVisible;
} else if(!strcmp(name, "XA")) {
aTrigger = aTriggerUserAction;
} else {
aTrigger = aTriggerUnknown;
}
} else {
aTrigger = aTriggerUnknown;
}
obj1 = dict->lookup("AIS");
if (obj1.isName()) {
const char *name = obj1.getName();
if(!strcmp(name, "I")) {
aState = aStateEnabled;
} else if(!strcmp(name, "L")) {
aState = aStateDisabled;
} else {
aState = aStateUnknown;
}
} else {
aState = aStateUnknown;
}
obj1 = dict->lookup("D");
if (obj1.isName()) {
const char *name = obj1.getName();
if(!strcmp(name, "PC")) {
dTrigger = dTriggerPageClosed;
} else if(!strcmp(name, "PI")) {
dTrigger = dTriggerPageInvisible;
} else if(!strcmp(name, "XD")) {
dTrigger = dTriggerUserAction;
} else {
dTrigger = dTriggerUnknown;
}
} else {
dTrigger = dTriggerUnknown;
}
obj1 = dict->lookup("DIS");
if (obj1.isName()) {
const char *name = obj1.getName();
if(!strcmp(name, "U")) {
dState = dStateUninstantiaded;
} else if(!strcmp(name, "I")) {
dState = dStateInstantiated;
} else if(!strcmp(name, "L")) {
dState = dStateLive;
} else {
dState = dStateUnknown;
}
} else {
dState = dStateUnknown;
}
obj1 = dict->lookup("TB");
if (obj1.isBool()) {
displayToolbar = obj1.getBool();
} else {
displayToolbar = gTrue;
}
obj1 = dict->lookup("NP");
if (obj1.isBool()) {
displayNavigation = obj1.getBool();
} else {
displayNavigation = gFalse;
}
}
//------------------------------------------------------------------------
// AnnotRichMedia
//------------------------------------------------------------------------
AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, PDFRectangle *rect) :
Annot(docA, rect) {
type = typeRichMedia;
annotObj.dictSet ("Subtype", Object(objName, "RichMedia"));
initialize(docA, annotObj.getDict());
}
AnnotRichMedia::AnnotRichMedia(PDFDoc *docA, Object *dictObject, Object *obj) :
Annot(docA, dictObject, obj) {
type = typeRichMedia;
initialize(docA, dictObject->getDict());
}
AnnotRichMedia::~AnnotRichMedia() = default;
void AnnotRichMedia::initialize(PDFDoc *docA, Dict* dict) {
Object obj1 = dict->lookup("RichMediaContent");
if (obj1.isDict()) {
content = std::make_unique<AnnotRichMedia::Content>(obj1.getDict());
}
obj1 = dict->lookup("RichMediaSettings");
if (obj1.isDict()) {
settings = std::make_unique<AnnotRichMedia::Settings>(obj1.getDict());
}
}
AnnotRichMedia::Content* AnnotRichMedia::getContent() const {
return content.get();
}
AnnotRichMedia::Settings* AnnotRichMedia::getSettings() const {
return settings.get();
}
AnnotRichMedia::Settings::Settings(Dict *dict) {
Object obj1 = dict->lookup("Activation");
if (obj1.isDict()) {
activation = std::make_unique<AnnotRichMedia::Activation>(obj1.getDict());
}
obj1 = dict->lookup("Deactivation");
if (obj1.isDict()) {
deactivation = std::make_unique<AnnotRichMedia::Deactivation>(obj1.getDict());
}
}
AnnotRichMedia::Settings::~Settings() = default;
AnnotRichMedia::Activation* AnnotRichMedia::Settings::getActivation() const {
return activation.get();
}
AnnotRichMedia::Deactivation* AnnotRichMedia::Settings::getDeactivation() const {
return deactivation.get();
}
AnnotRichMedia::Activation::Activation(Dict *dict) {
Object obj1 = dict->lookup("Condition");
if (obj1.isName()) {
const char *name = obj1.getName();
if (!strcmp(name, "PO")) {
condition = conditionPageOpened;
} else if (!strcmp(name, "PV")) {
condition = conditionPageVisible;
} else if (!strcmp(name, "XA")) {
condition = conditionUserAction;
} else {
condition = conditionUserAction;
}
} else {
condition = conditionUserAction;
}
}
AnnotRichMedia::Activation::Condition AnnotRichMedia::Activation::getCondition() const {
return condition;
}
AnnotRichMedia::Deactivation::Deactivation(Dict *dict) {
Object obj1 = dict->lookup("Condition");
if (obj1.isName()) {
const char *name = obj1.getName();
if (!strcmp(name, "PC")) {
condition = conditionPageClosed;
} else if (!strcmp(name, "PI")) {
condition = conditionPageInvisible;
} else if (!strcmp(name, "XD")) {
condition = conditionUserAction;
} else {
condition = conditionUserAction;
}
} else {
condition = conditionUserAction;
}
}
AnnotRichMedia::Deactivation::Condition AnnotRichMedia::Deactivation::getCondition() const {
return condition;
}
AnnotRichMedia::Content::Content(Dict *dict) {
Object obj1 = dict->lookup("Configurations");
if (obj1.isArray()) {
nConfigurations = obj1.arrayGetLength();
configurations = (Configuration **)gmallocn(nConfigurations, sizeof(Configuration *));
for (int i = 0; i < nConfigurations; ++i) {
Object obj2 = obj1.arrayGet(i);
if (obj2.isDict()) {
configurations[i] = new AnnotRichMedia::Configuration(obj2.getDict());
} else {
configurations[i] = nullptr;
}
}
} else {
nConfigurations = 0;
configurations = nullptr;
}
nAssets = 0;
assets = nullptr;
obj1 = dict->lookup("Assets");
if (obj1.isDict()) {
Object obj2 = obj1.getDict()->lookup("Names");
if (obj2.isArray()) {
nAssets = obj2.arrayGetLength() / 2;
assets = (Asset **)gmallocn(nAssets, sizeof(Asset *));
int counter = 0;
for (int i = 0; i < nAssets; ++i) {
assets[counter] = new AnnotRichMedia::Asset;
Object objKey = obj2.arrayGet(i * 2);
assets[counter]->fileSpec = obj2.arrayGet(i * 2 + 1);
assets[counter]->name = std::make_unique<GooString>( objKey.getString() );
++counter;
}
}
}
}
AnnotRichMedia::Content::~Content() {
if (configurations) {
for (int i = 0; i < nConfigurations; ++i)
delete configurations[i];
gfree(configurations);
}
if (assets) {
for (int i = 0; i < nAssets; ++i)
delete assets[i];
gfree(assets);
}
}
int AnnotRichMedia::Content::getConfigurationsCount() const {
return nConfigurations;
}
AnnotRichMedia::Configuration* AnnotRichMedia::Content::getConfiguration(int index) const {
if (index < 0 || index >= nConfigurations)
return nullptr;
return configurations[index];
}
int AnnotRichMedia::Content::getAssetsCount() const {
return nAssets;
}
AnnotRichMedia::Asset* AnnotRichMedia::Content::getAsset(int index) const {
if (index < 0 || index >= nAssets)
return nullptr;
return assets[index];
}
AnnotRichMedia::Asset::Asset() = default;
AnnotRichMedia::Asset::~Asset() = default;
const GooString* AnnotRichMedia::Asset::getName() const {
return name.get();
}
Object* AnnotRichMedia::Asset::getFileSpec() const {
return const_cast<Object*>(&fileSpec);
}
AnnotRichMedia::Configuration::Configuration(Dict *dict)
{
Object obj1 = dict->lookup("Instances");
if (obj1.isArray()) {
nInstances = obj1.arrayGetLength();
instances = (Instance **)gmallocn(nInstances, sizeof(Instance *));
for (int i = 0; i < nInstances; ++i) {
Object obj2 = obj1.arrayGet(i);
if (obj2.isDict()) {
instances[i] = new AnnotRichMedia::Instance(obj2.getDict());
} else {
instances[i] = nullptr;
}
}
} else {
instances = nullptr;
}
obj1 = dict->lookup("Name");
if (obj1.isString()) {
name = std::make_unique<GooString>(obj1.getString());
}
obj1 = dict->lookup("Subtype");
if (obj1.isName()) {
const char *name = obj1.getName();
if (!strcmp(name, "3D")) {
type = type3D;
} else if (!strcmp(name, "Flash")) {
type = typeFlash;
} else if (!strcmp(name, "Sound")) {
type = typeSound;
} else if (!strcmp(name, "Video")) {
type = typeVideo;
} else {
// determine from first non null instance
type = typeFlash; // default in case all instances are null
if (instances && nInstances > 0) {
for (int i = 0; i < nInstances; ++i) {
AnnotRichMedia::Instance *instance = instances[i];
if (instance) {
switch (instance->getType()) {
case AnnotRichMedia::Instance::type3D:
type = type3D;
break;
case AnnotRichMedia::Instance::typeFlash:
type = typeFlash;
break;
case AnnotRichMedia::Instance::typeSound:
type = typeSound;
break;
case AnnotRichMedia::Instance::typeVideo:
type = typeVideo;
break;
}
// break the loop since we found the first non null instance
break;
}
}
}
}
}
}
AnnotRichMedia::Configuration::~Configuration()
{
if (instances) {
for (int i = 0; i < nInstances; ++i)
delete instances[i];
gfree(instances);
}
}
int AnnotRichMedia::Configuration::getInstancesCount() const {
return nInstances;
}
AnnotRichMedia::Instance* AnnotRichMedia::Configuration::getInstance(int index) const {
if (index < 0 || index >= nInstances)
return nullptr;
return instances[index];
}
const GooString* AnnotRichMedia::Configuration::getName() const {
return name.get();
}
AnnotRichMedia::Configuration::Type AnnotRichMedia::Configuration::getType() const {
return type;
}
AnnotRichMedia::Instance::Instance(Dict *dict)
{
Object obj1 = dict->lookup("Subtype");
const char *name = obj1.isName() ? obj1.getName() : "";
if (!strcmp(name, "3D")) {
type = type3D;
} else if (!strcmp(name, "Flash")) {
type = typeFlash;
} else if (!strcmp(name, "Sound")) {
type = typeSound;
} else if (!strcmp(name, "Video")) {
type = typeVideo;
} else {
type = typeFlash;
}
obj1 = dict->lookup("Params");
if (obj1.isDict()) {
params = std::make_unique<AnnotRichMedia::Params>(obj1.getDict());
}
}
AnnotRichMedia::Instance::~Instance() = default;
AnnotRichMedia::Instance::Type AnnotRichMedia::Instance::getType() const {
return type;
}
AnnotRichMedia::Params* AnnotRichMedia::Instance::getParams() const {
return params.get();
}
AnnotRichMedia::Params::Params(Dict *dict)
{
Object obj1 = dict->lookup("FlashVars");
if (obj1.isString()) {
flashVars = std::make_unique<GooString>(obj1.getString());
}
}
AnnotRichMedia::Params::~Params() = default;
const GooString* AnnotRichMedia::Params::getFlashVars() const {
return flashVars.get();
}
//------------------------------------------------------------------------
// Annots
//------------------------------------------------------------------------
Annots::Annots(PDFDoc *docA, int page, Object *annotsObj) {
Annot *annot;
int i;
doc = docA;
if (annotsObj->isArray()) {
for (i = 0; i < annotsObj->arrayGetLength(); ++i) {
//get the Ref to this annot and pass it to Annot constructor
//this way, it'll be possible for the annot to retrieve the corresponding
//form widget
Object obj1 = annotsObj->arrayGet(i);
if (obj1.isDict()) {
Object obj2 = annotsObj->arrayGetNF(i);
annot = createAnnot (&obj1, &obj2);
if (annot) {
if (annot->isOk()) {
annot->setPage(page, gFalse); // Don't change /P
appendAnnot(annot);
}
annot->decRefCnt();
}
}
}
}
}
void Annots::appendAnnot(Annot *annot) {
if (annot && annot->isOk()) {
annots.push_back(annot);
annot->incRefCnt();
}
}
GBool Annots::removeAnnot(Annot *annot) {
auto idx = std::find(annots.begin(), annots.end(), annot);
if (idx == annots.end()) {
return false;
} else {
annot->decRefCnt();
annots.erase(idx);
return true;
}
}
Annot *Annots::createAnnot(Object* dictObject, Object *obj) {
Annot *annot = nullptr;
Object obj1 = dictObject->dictLookup("Subtype");
if (obj1.isName()) {
const char *typeName = obj1.getName();
if (!strcmp(typeName, "Text")) {
annot = new AnnotText(doc, dictObject, obj);
} else if (!strcmp(typeName, "Link")) {
annot = new AnnotLink(doc, dictObject, obj);
} else if (!strcmp(typeName, "FreeText")) {
annot = new AnnotFreeText(doc, dictObject, obj);
} else if (!strcmp(typeName, "Line")) {
annot = new AnnotLine(doc, dictObject, obj);
} else if (!strcmp(typeName, "Square")) {
annot = new AnnotGeometry(doc, dictObject, obj);
} else if (!strcmp(typeName, "Circle")) {
annot = new AnnotGeometry(doc, dictObject, obj);
} else if (!strcmp(typeName, "Polygon")) {
annot = new AnnotPolygon(doc, dictObject, obj);
} else if (!strcmp(typeName, "PolyLine")) {
annot = new AnnotPolygon(doc, dictObject, obj);
} else if (!strcmp(typeName, "Highlight")) {
annot = new AnnotTextMarkup(doc, dictObject, obj);
} else if (!strcmp(typeName, "Underline")) {
annot = new AnnotTextMarkup(doc, dictObject, obj);
} else if (!strcmp(typeName, "Squiggly")) {
annot = new AnnotTextMarkup(doc, dictObject, obj);
} else if (!strcmp(typeName, "StrikeOut")) {
annot = new AnnotTextMarkup(doc, dictObject, obj);
} else if (!strcmp(typeName, "Stamp")) {
annot = new AnnotStamp(doc, dictObject, obj);
} else if (!strcmp(typeName, "Caret")) {
annot = new AnnotCaret(doc, dictObject, obj);
} else if (!strcmp(typeName, "Ink")) {
annot = new AnnotInk(doc, dictObject, obj);
} else if (!strcmp(typeName, "FileAttachment")) {
annot = new AnnotFileAttachment(doc, dictObject, obj);
} else if (!strcmp(typeName, "Sound")) {
annot = new AnnotSound(doc, dictObject, obj);
} else if(!strcmp(typeName, "Movie")) {
annot = new AnnotMovie(doc, dictObject, obj);
} else if(!strcmp(typeName, "Widget")) {
// Find the annot in forms
if (obj->isRef()) {
Form *form = doc->getCatalog()->getForm();
if (form) {
FormWidget *widget = form->findWidgetByRef(obj->getRef());
if (widget) {
annot = widget->getWidgetAnnotation();
annot->incRefCnt();
}
}
}
if (!annot)
annot = new AnnotWidget(doc, dictObject, obj);
} else if(!strcmp(typeName, "Screen")) {
annot = new AnnotScreen(doc, dictObject, obj);
} else if(!strcmp(typeName, "PrinterMark")) {
annot = new Annot(doc, dictObject, obj);
} else if (!strcmp(typeName, "TrapNet")) {
annot = new Annot(doc, dictObject, obj);
} else if (!strcmp(typeName, "Watermark")) {
annot = new Annot(doc, dictObject, obj);
} else if (!strcmp(typeName, "3D")) {
annot = new Annot3D(doc, dictObject, obj);
} else if (!strcmp(typeName, "RichMedia")) {
annot = new AnnotRichMedia(doc, dictObject, obj);
} else if (!strcmp(typeName, "Popup")) {
/* Popup annots are already handled by markup annots
* Here we only care about popup annots without a
* markup annotation associated
*/
Object obj2 = dictObject->dictLookup("Parent");
if (obj2.isNull())
annot = new AnnotPopup(doc, dictObject, obj);
else
annot = nullptr;
} else {
annot = new Annot(doc, dictObject, obj);
}
}
return annot;
}
Annot *Annots::findAnnot(Ref *ref) {
for (auto* annot : annots) {
if (annot->match(ref)) {
return annot;
}
}
return nullptr;
}
Annots::~Annots() {
for (auto* annot : annots) {
annot->decRefCnt();
}
}
//------------------------------------------------------------------------
// AnnotAppearanceBuilder
//------------------------------------------------------------------------
AnnotAppearanceBuilder::AnnotAppearanceBuilder()
: appearBuf(new GooString())
{
}
AnnotAppearanceBuilder::~AnnotAppearanceBuilder()
{
delete appearBuf;
}
void AnnotAppearanceBuilder::append(const char *text)
{
appearBuf->append(text);
}
void AnnotAppearanceBuilder::appendf(const char *fmt, ...) GOOSTRING_FORMAT
{
va_list argList;
va_start(argList, fmt);
appearBuf->appendfv(fmt, argList);
va_end(argList);
}
const GooString *AnnotAppearanceBuilder::buffer() const
{
return appearBuf;
}