blob: 95b068293a736cfdef95c1f1c39f8138ba86c389 [file] [log] [blame] [edit]
//========================================================================
//
// 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-2024 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, 2019 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-2020 Tobias Deiminger <haxtibal@posteo.de>
// Copyright (C) 2018-2020, 2022, 2024 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2019 Umang Malik <umang99m@gmail.com>
// Copyright (C) 2019 João Netto <joaonetto901@gmail.com>
// Copyright (C) 2020, 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden
// Copyright (C) 2020 Katarina Behrens <Katarina.Behrens@cib.de>
// Copyright (C) 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
// Copyright (C) 2020 Nelson Benítez León <nbenitezl@gmail.com>
// Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>.
// Copyright (C) 2021 Zachary Travis <ztravis@everlaw.com>
// Copyright (C) 2021 Mahmoud Ahmed Khalil <mahmoudkhalil11@gmail.com>
// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
// Copyright (C) 2022 Martin <martinbts@gmx.net>
// Copyright (C) 2022 Andreas Naumann <42870-ANaumann85@users.noreply.gitlab.freedesktop.org>
// Copyright (C) 2022, 2024 Erich E. Hoover <erich.e.hoover@gmail.com>
// Copyright (C) 2023, 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
// Copyright (C) 2024 Pratham Gandhi <ppg.1382@gmail.com>
// Copyright (C) 2024 Carsten Emde <ce@ceek.de>
// Copyright (C) 2024 Lucas Baudin <lucas.baudin@ensae.fr>
//
// 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 <cstdlib>
#include <cmath>
#include <cassert>
#include "goo/gmem.h"
#include "goo/gstrtod.h"
#include "Error.h"
#include "Object.h"
#include "Catalog.h"
#include "Gfx.h"
#include "Lexer.h"
#include "PDFDoc.h"
#include "Page.h"
#include "Annot.h"
#include "GfxFont.h"
#include "CharCodeToUnicode.h"
#include "PDFDocEncoding.h"
#include "Form.h"
#include "Error.h"
#include "XRef.h"
#include "Movie.h"
#include "OptionalContent.h"
#include "Sound.h"
#include "FileSpec.h"
#include "DateInfo.h"
#include "Link.h"
#include "UTF.h"
#include <cstring>
#include <algorithm>
#include "annot_stamp_approved.h"
#include "annot_stamp_as_is.h"
#include "annot_stamp_confidential.h"
#include "annot_stamp_departmental.h"
#include "annot_stamp_final.h"
#include "annot_stamp_for_comment.h"
#include "annot_stamp_experimental.h"
#include "annot_stamp_expired.h"
#include "annot_stamp_not_approved.h"
#include "annot_stamp_not_for_public_release.h"
#include "annot_stamp_sold.h"
#include "annot_stamp_top_secret.h"
#include "annot_stamp_for_public_release.h"
#include "annot_stamp_draft.h"
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
#define fieldFlagReadOnly 0x00000001
#define fieldFlagRequired 0x00000002
#define fieldFlagNoExport 0x00000004
#define fieldFlagMultiline 0x00001000
#define fieldFlagPassword 0x00002000
#define fieldFlagNoToggleToOff 0x00004000
#define fieldFlagRadio 0x00008000
#define fieldFlagPushbutton 0x00010000
#define fieldFlagCombo 0x00020000
#define fieldFlagEdit 0x00040000
#define fieldFlagSort 0x00080000
#define fieldFlagFileSelect 0x00100000
#define fieldFlagMultiSelect 0x00200000
#define fieldFlagDoNotSpellCheck 0x00400000
#define fieldFlagDoNotScroll 0x00800000
#define fieldFlagComb 0x01000000
#define fieldFlagRichText 0x02000000
#define fieldFlagRadiosInUnison 0x02000000
#define fieldFlagCommitOnSelChange 0x04000000
// 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
const double dx1 = array->get(0).getNumWithDefaultValue(0);
const double dy1 = array->get(1).getNumWithDefaultValue(0);
const double dx2 = array->get(2).getNumWithDefaultValue(0);
const double dy2 = array->get(3).getNumWithDefaultValue(0);
// checking that the numbers are valid (i.e. >= 0),
// and that applying the differences still give us a valid rect
if (dx1 >= 0 && dy1 >= 0 && dx2 >= 0 && dy2 && (rect->x2 - rect->x1 - dx1 - dx2) >= 0 && (rect->y2 - rect->y1 - dy1 - dy2) >= 0) {
auto newRect = std::make_unique<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 std::unique_ptr<LinkAction> getAdditionalAction(Annot::AdditionalActionsType type, Object *additionalActions, PDFDoc *doc)
{
Object additionalActionsObject = additionalActions->fetch(doc->getXRef());
if (additionalActionsObject.isDict()) {
const char *key = (type == Annot::actionCursorEntering ? "E"
: type == Annot::actionCursorLeaving ? "X"
: type == Annot::actionMousePressed ? "D"
: type == Annot::actionMouseReleased ? "U"
: type == Annot::actionFocusIn ? "Fo"
: type == Annot::actionFocusOut ? "Bl"
: type == Annot::actionPageOpening ? "PO"
: type == Annot::actionPageClosing ? "PC"
: type == Annot::actionPageVisible ? "PV"
: type == Annot::actionPageInvisible ? "PI"
: nullptr);
Object actionObject = additionalActionsObject.dictLookup(key);
if (actionObject.isDict()) {
return LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
}
}
return nullptr;
}
static const char *getFormAdditionalActionKey(Annot::FormAdditionalActionsType type)
{
return (type == Annot::actionFieldModified ? "K" : type == Annot::actionFormatField ? "F" : type == Annot::actionValidateField ? "V" : type == Annot::actionCalculateField ? "C" : nullptr);
}
static const char *determineFallbackFont(const std::string &tok, const char *defaultFallback)
{
// TODO: adjust these based on other example PDFs.
if (tok == "/ZaDb") {
return "ZapfDingbats";
} else if (tok == "/Cour") {
return "Courier";
} else if (tok == "/TiRo") {
return "TimesNewRoman";
} else if (tok == "/Helvetica-Bold") {
return "Helvetica-Bold";
}
return defaultFallback;
}
//------------------------------------------------------------------------
// AnnotBorderEffect
//------------------------------------------------------------------------
AnnotBorderEffect::AnnotBorderEffect(Dict *dict)
{
Object obj1;
obj1 = dict->lookup("S");
if (obj1.isName()) {
const char *effectName = obj1.getName();
if (!strcmp(effectName, "C")) {
effectType = borderEffectCloudy;
} else {
effectType = borderEffectNoEffect;
}
} else {
effectType = borderEffectNoEffect;
}
if (effectType == borderEffectCloudy) {
intensity = dict->lookup("I").getNumWithDefaultValue(0);
} else {
intensity = 0;
}
}
//------------------------------------------------------------------------
// AnnotPath
//------------------------------------------------------------------------
AnnotPath::AnnotPath() = default;
AnnotPath::AnnotPath(Array *array)
{
parsePathArray(array);
}
AnnotPath::AnnotPath(std::vector<AnnotCoord> &&coordsA)
{
coords = std::move(coordsA);
}
AnnotPath::~AnnotPath() = default;
double AnnotPath::getX(int coord) const
{
if (coord >= 0 && coord < getCoordsLength()) {
return coords[coord].getX();
}
return 0;
}
double AnnotPath::getY(int coord) const
{
if (coord >= 0 && coord < getCoordsLength()) {
return coords[coord].getY();
}
return 0;
}
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) { }
AnnotCalloutLine::~AnnotCalloutLine() = default;
//------------------------------------------------------------------------
// AnnotCalloutMultiLine
//------------------------------------------------------------------------
AnnotCalloutMultiLine::AnnotCalloutMultiLine(double x1, double y1, double x2, double y2, double x3, double y3) : AnnotCalloutLine(x1, y1, x2, y2), coord3(x3, y3) { }
AnnotCalloutMultiLine::~AnnotCalloutMultiLine() = default;
//------------------------------------------------------------------------
// AnnotQuadrilateral
//------------------------------------------------------------------------
AnnotQuadrilaterals::AnnotQuadrilaterals(Array *array, PDFRectangle *rect)
{
int arrayLength = array->getLength();
int quadsLength = 0;
double quadArray[8];
// default values
quadrilateralsLength = 0;
if ((arrayLength % 8) == 0) {
int i;
quadsLength = arrayLength / 8;
auto quads = std::make_unique<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) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord1.getX();
}
return 0;
}
double AnnotQuadrilaterals::getY1(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord1.getY();
}
return 0;
}
double AnnotQuadrilaterals::getX2(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord2.getX();
}
return 0;
}
double AnnotQuadrilaterals::getY2(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord2.getY();
}
return 0;
}
double AnnotQuadrilaterals::getX3(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord3.getX();
}
return 0;
}
double AnnotQuadrilaterals::getY3(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord3.getY();
}
return 0;
}
double AnnotQuadrilaterals::getX4(int quadrilateral) const
{
if (quadrilateral >= 0 && quadrilateral < quadrilateralsLength) {
return quadrilaterals[quadrilateral].coord4.getX();
}
return 0;
}
double AnnotQuadrilaterals::getY4(int quadrilateral) const
{
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;
style = borderSolid;
}
bool AnnotBorder::parseDashArray(Object *dashObj)
{
bool correct = true;
const int tempLength = dashObj->arrayGetLength();
std::vector<double> tempDash(tempLength);
// TODO: check not all zero (Line Dash Pattern Page 217 PDF 8.1)
for (int i = 0; i < tempLength && i < DASH_LIMIT && correct; i++) {
const Object obj1 = dashObj->arrayGet(i);
if (obj1.isNum()) {
tempDash[i] = obj1.getNum();
correct = tempDash[i] >= 0;
} else {
correct = false;
}
}
if (correct) {
dash = std::move(tempDash);
style = borderDashed;
}
return correct;
}
AnnotBorder::~AnnotBorder() = default;
//------------------------------------------------------------------------
// AnnotBorderArray
//------------------------------------------------------------------------
AnnotBorderArray::AnnotBorderArray()
{
horizontalCorner = 0;
verticalCorner = 0;
}
AnnotBorderArray::AnnotBorderArray(Array *array)
{
Object obj1;
int arrayLength = array->getLength();
bool correct = true;
if (arrayLength == 3 || arrayLength == 4) {
// implementation note 81 in Appendix H.
obj1 = array->get(0);
if (obj1.isNum()) {
horizontalCorner = obj1.getNum();
} else {
correct = false;
}
obj1 = array->get(1);
if (obj1.isNum()) {
verticalCorner = obj1.getNum();
} else {
correct = false;
}
obj1 = array->get(2);
if (obj1.isNum()) {
width = obj1.getNum();
} else {
correct = false;
}
if (arrayLength == 4) {
obj1 = array->get(3);
if (obj1.isArray()) {
correct = parseDashArray(&obj1);
} else {
correct = false;
}
}
} else {
correct = false;
}
if (!correct) {
width = 0;
}
}
std::unique_ptr<AnnotBorder> AnnotBorderArray::copy() const
{
AnnotBorderArray *res = new AnnotBorderArray();
res->type = type;
res->width = width;
res->dash = dash;
res->style = style;
res->horizontalCorner = horizontalCorner;
res->verticalCorner = verticalCorner;
return std::unique_ptr<AnnotBorder>(res);
}
Object AnnotBorderArray::writeToObject(XRef *xref) const
{
Array *borderArray = new Array(xref);
borderArray->add(Object(horizontalCorner));
borderArray->add(Object(verticalCorner));
borderArray->add(Object(width));
if (dash.size() > 0) {
Array *a = new Array(xref);
for (double d : dash) {
a->add(Object(d));
}
borderArray->add(Object(a));
}
return Object(borderArray);
}
//------------------------------------------------------------------------
// AnnotBorderBS
//------------------------------------------------------------------------
AnnotBorderBS::AnnotBorderBS() { }
AnnotBorderBS::AnnotBorderBS(Dict *dict)
{
// Border width (in points)
Object obj1 = dict->lookup("W");
width = obj1.getNumWithDefaultValue(1.0);
// Border style
obj1 = dict->lookup("S");
if (obj1.isName()) {
const char *styleName = obj1.getName();
if (!strcmp(styleName, "S")) {
style = borderSolid;
} else if (!strcmp(styleName, "D")) {
style = borderDashed;
} else if (!strcmp(styleName, "B")) {
style = borderBeveled;
} else if (!strcmp(styleName, "I")) {
style = borderInset;
} else if (!strcmp(styleName, "U")) {
style = borderUnderlined;
} else {
style = borderSolid;
}
} else {
style = borderSolid;
}
// Border dash style
if (style == borderDashed) {
obj1 = dict->lookup("D");
if (!obj1.isArray() || !parseDashArray(&obj1)) {
dash = { 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";
}
std::unique_ptr<AnnotBorder> AnnotBorderBS::copy() const
{
AnnotBorderBS *res = new AnnotBorderBS();
res->type = type;
res->width = width;
res->dash = dash;
res->style = style;
return std::unique_ptr<AnnotBorder>(res);
}
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 && dash.size() > 0) {
Array *a = new Array(xref);
for (double d : dash) {
a->add(Object(d));
}
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(const GooString *da)
{
fontPtSize = -1;
if (da) {
std::vector<std::string> daToks;
int i = FormFieldText::tokenizeDA(da->toStr(), &daToks, "Tf");
if (i >= 1) {
fontPtSize = gatof(daToks[i - 1].c_str());
}
if (i >= 2) {
// We are expecting a name, therefore the first letter should be '/'.
const std::string &fontToken = daToks[i - 2];
if (fontToken.size() > 1 && fontToken[0] == '/') {
// The +1 is here to skip the leading '/'.
fontName = Object(objName, fontToken.c_str() + 1);
}
}
// Scan backwards: we are looking for the last set value
for (i = daToks.size() - 1; i >= 0; --i) {
if (!fontColor) {
if (daToks[i] == "g" && i >= 1) {
fontColor = std::make_unique<AnnotColor>(gatof(daToks[i - 1].c_str()));
} else if (daToks[i] == "rg" && i >= 3) {
fontColor = std::make_unique<AnnotColor>(gatof(daToks[i - 3].c_str()), gatof(daToks[i - 2].c_str()), gatof(daToks[i - 1].c_str()));
} else if (daToks[i] == "k" && i >= 4) {
fontColor = std::make_unique<AnnotColor>(gatof(daToks[i - 4].c_str()), gatof(daToks[i - 3].c_str()), gatof(daToks[i - 2].c_str()), gatof(daToks[i - 1].c_str()));
}
}
}
}
}
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);
}
std::string DefaultAppearance::toAppearanceString() const
{
AnnotAppearanceBuilder appearBuilder;
if (fontColor) {
appearBuilder.setDrawColor(*fontColor, true);
}
appearBuilder.setTextFont(fontName, fontPtSize);
return appearBuilder.buffer()->toStr();
}
//------------------------------------------------------------------------
// AnnotIconFit
//------------------------------------------------------------------------
AnnotIconFit::AnnotIconFit(Dict *dict)
{
Object obj1;
obj1 = dict->lookup("SW");
if (obj1.isName()) {
const char *scaleName = obj1.getName();
if (!strcmp(scaleName, "B")) {
scaleWhen = scaleBigger;
} else if (!strcmp(scaleName, "S")) {
scaleWhen = scaleSmaller;
} else if (!strcmp(scaleName, "N")) {
scaleWhen = scaleNever;
} else {
scaleWhen = scaleAlways;
}
} else {
scaleWhen = scaleAlways;
}
obj1 = dict->lookup("S");
if (obj1.isName()) {
const char *scaleName = obj1.getName();
if (!strcmp(scaleName, "A")) {
scale = scaleAnamorphic;
} else {
scale = scaleProportional;
}
} else {
scale = scaleProportional;
}
obj1 = dict->lookup("A");
if (obj1.isArray() && obj1.arrayGetLength() == 2) {
left = obj1.arrayGet(0).getNumWithDefaultValue(0);
bottom = obj1.arrayGet(1).getNumWithDefaultValue(0);
if (left < 0 || left > 1) {
left = 0.5;
}
if (bottom < 0 || bottom > 1) {
bottom = 0.5;
}
} else {
left = bottom = 0.5;
}
fullyBounds = dict->lookup("FB").getBoolWithDefaultValue(false);
}
//------------------------------------------------------------------------
// AnnotAppearance
//------------------------------------------------------------------------
AnnotAppearance::AnnotAppearance(PDFDoc *docA, Object *dict)
{
assert(dict->isDict());
doc = docA;
appearDict = dict->copy();
}
AnnotAppearance::~AnnotAppearance() { }
Object AnnotAppearance::getAppearanceStream(AnnotAppearanceType type, const char *state)
{
Object apData;
// Obtain dictionary or stream associated to appearance type
switch (type) {
case appearRollover:
apData = appearDict.dictLookupNF("R").copy();
if (apData.isNull()) {
apData = appearDict.dictLookupNF("N").copy();
}
break;
case appearDown:
apData = appearDict.dictLookupNF("D").copy();
if (apData.isNull()) {
apData = appearDict.dictLookupNF("N").copy();
}
break;
case appearNormal:
apData = appearDict.dictLookupNF("N").copy();
break;
}
if (apData.isDict() && state) {
return apData.dictLookupNF(state).copy();
} else if (apData.isRef()) {
return apData;
}
return Object();
}
std::unique_ptr<GooString> AnnotAppearance::getStateKey(int i)
{
const Object &obj1 = appearDict.dictLookupNF("N");
if (obj1.isDict()) {
return std::make_unique<GooString>(obj1.dictGetKey(i));
}
return nullptr;
}
int AnnotAppearance::getNumStates()
{
int res = 0;
const Object &obj1 = appearDict.dictLookupNF("N");
if (obj1.isDict()) {
res = obj1.dictGetLength();
}
return res;
}
// Test if stateObj (a Ref or a Dict) points to the specified stream
bool AnnotAppearance::referencesStream(const Object *stateObj, Ref refToStream)
{
if (stateObj->isRef()) {
const Ref r = stateObj->getRef();
if (r == refToStream) {
return true;
}
} else if (stateObj->isDict()) { // Test each value
const int size = stateObj->dictGetLength();
for (int i = 0; i < size; ++i) {
const Object &obj1 = stateObj->dictGetValNF(i);
if (obj1.isRef()) {
const Ref r = obj1.getRef();
if (r == refToStream) {
return true;
}
}
}
}
return false; // Not found
}
// Test if this AnnotAppearance references the specified stream
bool AnnotAppearance::referencesStream(Ref refToStream)
{
bool found;
// Scan each state's ref/subdictionary
const Object &objN = appearDict.dictLookupNF("N");
found = referencesStream(&objN, refToStream);
if (found) {
return true;
}
const Object &objR = appearDict.dictLookupNF("R");
found = referencesStream(&objR, refToStream);
if (found) {
return true;
}
const Object &objD = appearDict.dictLookupNF("D");
found = referencesStream(&objD, refToStream);
return found;
}
// If this is the only annotation in the document that references the
// specified appearance stream, remove the appearance stream
void AnnotAppearance::removeStream(Ref refToStream)
{
const int lastpage = doc->getNumPages();
for (int pg = 1; pg <= lastpage; ++pg) { // Scan all annotations in the document
Page *page = doc->getPage(pg);
if (!page) {
error(errSyntaxError, -1, "Failed check for shared annotation stream at page {0:d}", pg);
continue;
}
Annots *annots = page->getAnnots();
for (Annot *annot : annots->getAnnots()) {
AnnotAppearance *annotAp = annot->getAppearStreams();
if (annotAp && annotAp != this && annotAp->referencesStream(refToStream)) {
return; // Another annotation points to the stream -> Don't delete it
}
}
}
// TODO: stream resources (e.g. font), AP name tree
doc->getXRef()->removeIndirectObject(refToStream);
}
// Removes stream if obj is a Ref, or removes pointed streams if obj is a Dict
void AnnotAppearance::removeStateStreams(const Object *state)
{
if (state->isRef()) {
removeStream(state->getRef());
} else if (state->isDict()) {
const int size = state->dictGetLength();
for (int i = 0; i < size; ++i) {
const Object &obj2 = state->dictGetValNF(i);
if (obj2.isRef()) {
removeStream(obj2.getRef());
}
}
}
}
void AnnotAppearance::removeAllStreams()
{
const Object &objN = appearDict.dictLookupNF("N");
removeStateStreams(&objN);
const Object &objR = appearDict.dictLookupNF("R");
removeStateStreams(&objR);
const Object &objD = appearDict.dictLookupNF("D");
removeStateStreams(&objD);
}
//------------------------------------------------------------------------
// AnnotAppearanceCharacs
//------------------------------------------------------------------------
AnnotAppearanceCharacs::AnnotAppearanceCharacs(Dict *dict)
{
Object obj1;
if (!dict) {
rotation = 0;
position = captionNoIcon;
return;
}
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;
std::unique_ptr<AnnotAppearanceCharacs> AnnotAppearanceCharacs::copy() const
{
AnnotAppearanceCharacs *res = new AnnotAppearanceCharacs(nullptr);
res->rotation = rotation;
if (borderColor) {
res->borderColor = std::make_unique<AnnotColor>(*borderColor);
}
if (backColor) {
res->backColor = std::make_unique<AnnotColor>(*backColor);
}
if (normalCaption) {
res->normalCaption = std::unique_ptr<GooString>(normalCaption->copy());
}
if (rolloverCaption) {
res->rolloverCaption = std::unique_ptr<GooString>(rolloverCaption->copy());
}
if (alternateCaption) {
res->alternateCaption = std::unique_ptr<GooString>(alternateCaption->copy());
}
if (iconFit) {
res->iconFit = std::make_unique<AnnotIconFit>(*iconFit);
}
res->position = position;
return std::unique_ptr<AnnotAppearanceCharacs>(res);
}
//------------------------------------------------------------------------
// 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() const std::scoped_lock locker(mutex)
Annot::Annot(PDFDoc *docA, PDFRectangle *rectA)
{
refCnt = 1;
flags = flagUnknown;
type = typeUnknown;
Array *a = new Array(docA->getXRef());
a->add(Object(rectA->x1));
a->add(Object(rectA->y1));
a->add(Object(rectA->x2));
a->add(Object(rectA->y2));
annotObj = Object(new Dict(docA->getXRef()));
annotObj.dictSet("Type", Object(objName, "Annot"));
annotObj.dictSet("Rect", Object(a));
ref = docA->getXRef()->addIndirectObject(annotObj);
initialize(docA, annotObj.getDict());
}
Annot::Annot(PDFDoc *docA, Object &&dictObject)
{
refCnt = 1;
hasRef = false;
flags = flagUnknown;
type = typeUnknown;
annotObj = std::move(dictObject);
initialize(docA, annotObj.getDict());
}
Annot::Annot(PDFDoc *docA, Object &&dictObject, const Object *obj)
{
refCnt = 1;
if (obj->isRef()) {
hasRef = true;
ref = obj->getRef();
} else {
hasRef = false;
}
flags = flagUnknown;
type = typeUnknown;
annotObj = std::move(dictObject);
initialize(docA, annotObj.getDict());
}
void Annot::initialize(PDFDoc *docA, Dict *dict)
{
Object apObj, asObj, obj1;
ok = true;
doc = docA;
appearance.setToNull();
//----- parse the rectangle
rect = std::make_unique<PDFRectangle>();
obj1 = dict->lookup("Rect");
if (obj1.isArray() && obj1.arrayGetLength() == 4) {
rect->x1 = obj1.arrayGet(0).getNumWithDefaultValue(0);
rect->y1 = obj1.arrayGet(1).getNumWithDefaultValue(0);
rect->x2 = obj1.arrayGet(2).getNumWithDefaultValue(1);
rect->y2 = obj1.arrayGet(3).getNumWithDefaultValue(1);
if (rect->x1 > rect->x2) {
double t = rect->x1;
rect->x1 = rect->x2;
rect->x2 = t;
}
if (rect->y1 > rect->y2) {
double t = rect->y1;
rect->y1 = rect->y2;
rect->y2 = t;
}
} else {
rect->x1 = rect->y1 = 0;
rect->x2 = rect->y2 = 1;
error(errSyntaxError, -1, "Bad bounding box for annotation");
ok = false;
}
obj1 = dict->lookup("Contents");
if (obj1.isString()) {
contents.reset(obj1.getString()->copy());
} else {
contents = std::make_unique<GooString>();
}
// Note: This value is overwritten by Annots ctor
const Object &pObj = dict->lookupNF("P");
if (pObj.isRef()) {
const Ref pRef = pObj.getRef();
page = doc->getCatalog()->findPage(pRef);
} else {
page = 0;
}
obj1 = dict->lookup("NM");
if (obj1.isString()) {
name.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("M");
if (obj1.isString()) {
modified.reset(obj1.getString()->copy());
}
//----- get the flags
obj1 = dict->lookup("F");
if (obj1.isInt()) {
flags |= obj1.getInt();
} else {
flags = flagUnknown;
}
//----- get the annotation appearance dictionary
apObj = dict->lookup("AP");
if (apObj.isDict()) {
appearStreams = std::make_unique<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->c_str());
}
//----- parse the border style
// According to the spec if neither the Border nor the BS entry is present,
// the border shall be drawn as a solid line with a width of 1 point. But acroread
// seems to ignore the Border entry for annots that can't have a BS entry. So, we only
// follow this rule for annots tha can have a BS entry.
obj1 = dict->lookup("Border");
if (obj1.isArray()) {
border = std::make_unique<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").copy();
}
void Annot::getRect(double *x1, double *y1, double *x2, double *y2) const
{
*x1 = rect->x1;
*y1 = rect->y1;
*x2 = rect->x2;
*y2 = rect->y2;
}
void Annot::setRect(const PDFRectangle &rectA)
{
setRect(rectA.x1, rectA.y1, rectA.x2, rectA.y2);
}
void Annot::setRect(double x1, double y1, double x2, double y2)
{
if (x1 < x2) {
rect->x1 = x1;
rect->x2 = x2;
} else {
rect->x1 = x2;
rect->x2 = x1;
}
if (y1 < y2) {
rect->y1 = y1;
rect->y2 = y2;
} else {
rect->y1 = y2;
rect->y2 = y1;
}
Array *a = new Array(doc->getXRef());
a->add(Object(rect->x1));
a->add(Object(rect->y1));
a->add(Object(rect->x2));
a->add(Object(rect->y2));
update("Rect", Object(a));
invalidateAppearance();
}
bool Annot::inRect(double x, double y) const
{
return rect->contains(x, y);
}
void Annot::update(const char *key, Object &&value)
{
annotLocker();
/* Set M to current time, unless we are updating M itself */
if (strcmp(key, "M") != 0) {
modified.reset(timeToDateString(nullptr));
annotObj.dictSet("M", Object(modified->copy()));
}
annotObj.dictSet(const_cast<char *>(key), std::move(value));
doc->getXRef()->setModifiedObject(&annotObj, ref);
hasBeenUpdated = true;
}
void Annot::setContents(std::unique_ptr<GooString> &&new_content)
{
annotLocker();
if (new_content) {
contents = std::move(new_content);
// append the unicode marker <FE FF> if needed
if (!hasUnicodeByteOrderMark(contents->toStr())) {
prependUnicodeByteOrderMark(contents->toNonConstStr());
}
} 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);
update("M", Object(modified->copy()));
} else {
modified.reset(nullptr);
update("M", Object(objNull));
}
}
void Annot::setFlags(unsigned int new_flags)
{
annotLocker();
flags = new_flags;
update("F", Object(int(flags)));
}
void Annot::setBorder(std::unique_ptr<AnnotBorder> &&new_border)
{
annotLocker();
if (new_border) {
Object obj1 = new_border->writeToObject(doc->getXRef());
update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", std::move(obj1));
border = std::move(new_border);
} else {
border = nullptr;
}
invalidateAppearance();
}
void Annot::setColor(std::unique_ptr<AnnotColor> &&new_color)
{
annotLocker();
if (new_color) {
Object obj1 = new_color->writeToObject(doc->getXRef());
update("C", std::move(obj1));
color = std::move(new_color);
} else {
color = nullptr;
}
invalidateAppearance();
}
void Annot::setPage(int pageIndex, bool updateP)
{
annotLocker();
Page *pageobj = doc->getPage(pageIndex);
Object obj1(objNull);
if (pageobj) {
const Ref pageRef = pageobj->getRef();
obj1 = Object(pageRef);
page = pageIndex;
} else {
page = 0;
}
if (updateP) {
update("P", std::move(obj1));
}
}
void Annot::setAppearanceState(const char *state)
{
annotLocker();
if (!state) {
return;
}
appearState = std::make_unique<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->c_str());
} else {
appearance.setToNull();
}
}
void Annot::invalidateAppearance()
{
annotLocker();
if (appearStreams) { // Remove existing appearance streams
appearStreams->removeAllStreams();
}
appearStreams = nullptr;
appearState = nullptr;
appearBBox = nullptr;
appearance.setToNull();
Object obj2 = annotObj.dictLookup("AP");
if (!obj2.isNull()) {
update("AP", Object(objNull)); // Remove AP
}
obj2 = annotObj.dictLookup("AS");
if (!obj2.isNull()) {
update("AS", Object(objNull)); // Remove AS
}
}
double Annot::getXMin()
{
return rect->x1;
}
double Annot::getYMin()
{
return rect->y1;
}
double Annot::getXMax()
{
return rect->x2;
}
double Annot::getYMax()
{
return rect->y2;
}
void Annot::readArrayNum(Object *pdfArray, int key, double *value)
{
Object valueObject = pdfArray->arrayGet(key);
if (valueObject.isNum()) {
*value = valueObject.getNum();
} else {
*value = 0;
ok = false;
}
}
void Annot::removeReferencedObjects()
{
// Remove appearance streams (if any)
invalidateAppearance();
}
void Annot::incRefCnt()
{
refCnt++;
}
void Annot::decRefCnt()
{
if (--refCnt == 0) {
delete this;
}
}
Annot::~Annot() { }
void AnnotAppearanceBuilder::setDrawColor(const AnnotColor &drawColor, bool fill)
{
const double *values = drawColor.getValues();
switch (drawColor.getSpace()) {
case AnnotColor::colorCMYK:
appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:.5f} {4:c}\n", values[0], values[1], values[2], values[3], fill ? 'k' : 'K');
break;
case AnnotColor::colorRGB:
appearBuf->appendf("{0:.5f} {1:.5f} {2:.5f} {3:s}\n", values[0], values[1], values[2], fill ? "rg" : "RG");
break;
case AnnotColor::colorGray:
appearBuf->appendf("{0:.5f} {1:c}\n", values[0], fill ? 'g' : 'G');
break;
case AnnotColor::colorTransparent:
default:
break;
}
}
void AnnotAppearanceBuilder::setTextFont(const Object &fontName, double fontSize)
{
if (fontName.isName() && strlen(fontName.getName()) > 0) {
appearBuf->appendf("/{0:s} {1:.2f} Tf\n", fontName.getName(), fontSize);
}
}
void AnnotAppearanceBuilder::setLineStyleForBorder(const AnnotBorder &border)
{
switch (border.getStyle()) {
case AnnotBorder::borderDashed:
appearBuf->append("[");
for (double dash : border.getDash()) {
appearBuf->appendf(" {0:.2f}", dash);
}
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, bool fill)
{
if (fill) {
drawEllipse(cx, cy, r, r, true, false);
} else {
drawEllipse(cx, cy, r, r, false, true);
}
}
// Draw an (approximate) ellipse of radius <rx> on x-axis and <ry> on y-axis, centered at (<cx>, <cy>).
// If <fill> is true, the ellipse is filled with current color for non-stroking operations.
// If <stroke> is true, the ellipse path ist stroked with current color and color space for stroking operations.
// Path will be closed if either fill or stroke is true; otherwise it's left open.
void AnnotAppearanceBuilder::drawEllipse(double cx, double cy, double rx, double ry, bool fill, bool stroke)
{
appearBuf->appendf("{0:.2f} {1:.2f} m\n", cx + rx, cy);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + rx, cy + bezierCircle * ry, cx + bezierCircle * rx, cy + ry, cx, cy + ry);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - bezierCircle * rx, cy + ry, cx - rx, cy + bezierCircle * ry, cx - rx, cy);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx - rx, cy - bezierCircle * ry, cx - bezierCircle * rx, cy - ry, cx, cy - ry);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", cx + bezierCircle * rx, cy - ry, cx + rx, cy - bezierCircle * ry, cx + rx, cy);
if (!fill && stroke) {
appearBuf->append("s\n");
} else if (fill && !stroke) {
appearBuf->append("f\n");
} else if (fill && stroke) {
appearBuf->append("b\n");
}
}
// Draw the top-left half of an (approximate) circle of radius <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");
}
void AnnotAppearanceBuilder::drawLineEndSquare(double x, double y, double size, bool fill, const Matrix &m)
{
const double halfSize { size / 2. };
const double x1[3] { x - size, x - size, x };
const double y1[3] { y + halfSize, y - halfSize, y - halfSize };
double tx, ty;
m.transform(x, y + halfSize, &tx, &ty);
appendf("{0:.2f} {1:.2f} m\n", tx, ty);
for (int i = 0; i < 3; i++) {
m.transform(x1[i], y1[i], &tx, &ty);
appendf("{0:.2f} {1:.2f} l\n", tx, ty);
}
appearBuf->append(fill ? "b\n" : "s\n");
}
void AnnotAppearanceBuilder::drawLineEndCircle(double x, double y, double size, bool fill, const Matrix &m)
{
const double halfSize { size / 2. };
const double x1[4] { x, x - halfSize - bezierCircle * halfSize, x - size, x - halfSize + bezierCircle * halfSize };
const double x2[4] { x - halfSize + bezierCircle * halfSize, x - size, x - halfSize - bezierCircle * halfSize, x };
const double x3[4] { x - halfSize, x - size, x - halfSize, x };
const double y1[4] { y + bezierCircle * halfSize, y + halfSize, y - bezierCircle * halfSize, y - halfSize };
const double y2[4] { y + halfSize, y + bezierCircle * halfSize, y - halfSize, y - bezierCircle * halfSize };
const double y3[4] { y + halfSize, y, y - halfSize, y };
double tx[3];
double ty[3];
m.transform(x, y, &tx[0], &ty[0]);
appearBuf->appendf("{0:.2f} {1:.2f} m\n", tx[0], ty[0]);
for (int i = 0; i < 4; i++) {
m.transform(x1[i], y1[i], &tx[0], &ty[0]);
m.transform(x2[i], y2[i], &tx[1], &ty[1]);
m.transform(x3[i], y3[i], &tx[2], &ty[2]);
appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", tx[0], ty[0], tx[1], ty[1], tx[2], ty[2]);
}
appearBuf->append(fill ? "b\n" : "s\n");
}
void AnnotAppearanceBuilder::drawLineEndDiamond(double x, double y, double size, bool fill, const Matrix &m)
{
const double halfSize { size / 2. };
const double x1[3] { x - halfSize, x - size, x - halfSize };
const double y1[3] { y + halfSize, y, y - halfSize };
double tx, ty;
m.transform(x, y, &tx, &ty);
appendf("{0:.2f} {1:.2f} m\n", tx, ty);
for (int i = 0; i < 3; i++) {
m.transform(x1[i], y1[i], &tx, &ty);
appendf("{0:.2f} {1:.2f} l\n", tx, ty);
}
appearBuf->append(fill ? "b\n" : "s\n");
}
void AnnotAppearanceBuilder::drawLineEndArrow(double x, double y, double size, int orientation, bool isOpen, bool fill, const Matrix &m)
{
const double alpha { M_PI / 6. };
const double xOffs { orientation * size };
const double yOffs { tan(alpha) * size };
double tx, ty;
m.transform(x - xOffs, y + yOffs, &tx, &ty);
appendf("{0:.2f} {1:.2f} m\n", tx, ty);
m.transform(x, y, &tx, &ty);
appendf("{0:.2f} {1:.2f} l\n", tx, ty);
m.transform(x - xOffs, y - yOffs, &tx, &ty);
appendf("{0:.2f} {1:.2f} l\n", tx, ty);
if (isOpen) {
appearBuf->append("S\n");
} else {
appearBuf->append(fill ? "b\n" : "s\n");
}
}
void AnnotAppearanceBuilder::drawLineEndSlash(double x, double y, double size, const Matrix &m)
{
const double halfSize { size / 2. };
const double xOffs { cos(M_PI / 3.) * halfSize };
double tx, ty;
m.transform(x - xOffs, y - halfSize, &tx, &ty);
appendf("{0:.2f} {1:.2f} m\n", tx, ty);
m.transform(x + xOffs, y + halfSize, &tx, &ty);
appendf("{0:.2f} {1:.2f} l\n", tx, ty);
appearBuf->append("S\n");
}
void AnnotAppearanceBuilder::drawLineEnding(AnnotLineEndingStyle endingStyle, double x, double y, double size, bool fill, const Matrix &m)
{
switch (endingStyle) {
case annotLineEndingSquare:
drawLineEndSquare(x, y, size, fill, m);
break;
case annotLineEndingCircle:
drawLineEndCircle(x, y, size, fill, m);
break;
case annotLineEndingDiamond:
drawLineEndDiamond(x, y, size, fill, m);
break;
case annotLineEndingOpenArrow:
drawLineEndArrow(x, y, size, 1, true, fill, m);
break;
case annotLineEndingClosedArrow:
drawLineEndArrow(x, y, size, 1, false, fill, m);
break;
case annotLineEndingButt: {
const double halfSize { size / 2. };
double tx, ty;
m.transform(x, y + halfSize, &tx, &ty);
appendf("{0:.2f} {1:.2f} m\n", tx, ty);
m.transform(x, y - halfSize, &tx, &ty);
appendf("{0:.2f} {1:.2f} l S\n", tx, ty);
} break;
case annotLineEndingROpenArrow:
drawLineEndArrow(x, y, size, -1, true, fill, m);
break;
case annotLineEndingRClosedArrow:
drawLineEndArrow(x, y, size, -1, false, fill, m);
break;
case annotLineEndingSlash:
drawLineEndSlash(x, y, size, m);
break;
default:
break;
}
}
double AnnotAppearanceBuilder::lineEndingXShorten(AnnotLineEndingStyle endingStyle, double size)
{
switch (endingStyle) {
case annotLineEndingCircle:
case annotLineEndingClosedArrow:
case annotLineEndingDiamond:
case annotLineEndingSquare:
return size;
default:
break;
}
return 0;
}
double AnnotAppearanceBuilder::lineEndingXExtendBBox(AnnotLineEndingStyle endingStyle, double size)
{
switch (endingStyle) {
case annotLineEndingRClosedArrow:
case annotLineEndingROpenArrow:
return size;
case annotLineEndingSlash:
return cos(M_PI / 3.) * size / 2.;
default:
break;
}
return 0;
}
Object Annot::createForm(const GooString *appearBuf, const double *bbox, bool transparencyGroup, Dict *resDict)
{
return createForm(appearBuf, bbox, transparencyGroup, resDict ? Object(resDict) : Object());
}
Object Annot::createForm(const GooString *appearBuf, const double *bbox, bool transparencyGroup, Object &&resDictObject)
{
Dict *appearDict = new Dict(doc->getXRef());
appearDict->set("Length", Object(appearBuf->getLength()));
appearDict->set("Subtype", Object(objName, "Form"));
Array *a = new Array(doc->getXRef());
a->add(Object(bbox[0]));
a->add(Object(bbox[1]));
a->add(Object(bbox[2]));
a->add(Object(bbox[3]));
appearDict->set("BBox", Object(a));
if (transparencyGroup) {
Dict *d = new Dict(doc->getXRef());
d->set("S", Object(objName, "Transparency"));
appearDict->set("Group", Object(d));
}
if (resDictObject.isDict()) {
appearDict->set("Resources", std::move(resDictObject));
}
Stream *mStream = new AutoFreeMemStream(copyString(appearBuf->c_str()), 0, appearBuf->getLength(), Object(appearDict));
return Object(mStream);
}
Dict *Annot::createResourcesDict(const char *formName, Object &&formStream, const char *stateName, double opacity, const char *blendMode)
{
Dict *gsDict = new Dict(doc->getXRef());
if (opacity != 1) {
gsDict->set("CA", Object(opacity));
gsDict->set("ca", Object(opacity));
}
if (blendMode) {
gsDict->set("BM", Object(objName, blendMode));
}
Dict *stateDict = new Dict(doc->getXRef());
stateDict->set(stateName, Object(gsDict));
Dict *formDict = new Dict(doc->getXRef());
formDict->set(formName, std::move(formStream));
Dict *resDict = new Dict(doc->getXRef());
resDict->set("ExtGState", Object(stateDict));
resDict->set("XObject", Object(formDict));
return resDict;
}
Object Annot::getAppearanceResDict()
{
Object obj1, obj2;
// Fetch appearance's resource dict (if any)
obj1 = appearance.fetch(doc->getXRef());
if (obj1.isStream()) {
obj2 = obj1.streamGetDict()->lookup("Resources");
if (obj2.isDict()) {
return obj2;
}
}
return Object(objNull);
}
bool Annot::isVisible(bool printing)
{
// check the flags
if ((flags & flagHidden) || (printing && !(flags & flagPrint)) || (!printing && (flags & flagNoView))) {
return false;
}
// check the OC
OCGs *optContentConfig = doc->getCatalog()->getOptContentConfig();
if (optContentConfig) {
if (!optContentConfig->optContentIsVisible(&oc)) {
return false;
}
}
return true;
}
int Annot::getRotation() const
{
Page *pageobj = doc->getPage(page);
assert(pageobj != nullptr);
if (flags & flagNoRotate) {
return (360 - pageobj->getRotate()) % 360;
} else {
return 0;
}
}
void Annot::draw(Gfx *gfx, bool printing)
{
annotLocker();
if (!isVisible(printing)) {
return;
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
void Annot::setNewAppearance(Object &&newAppearance)
{
setNewAppearance(std::move(newAppearance), false);
}
void Annot::setNewAppearance(Object &&newAppearance, bool keepAppearState)
{
if (newAppearance.isNull()) {
return;
}
annotLocker();
if (newAppearance.getType() == ObjType::objStream) {
Object oldAS = annotObj.dictLookup("AS");
invalidateAppearance();
appearance = std::move(newAppearance);
Ref updatedAppearanceStream = doc->getXRef()->addIndirectObject(appearance);
Object obj1 = Object(new Dict(doc->getXRef()));
obj1.dictAdd("N", Object(updatedAppearanceStream));
update("AP", std::move(obj1));
Object updatedAP = annotObj.dictLookup("AP");
appearStreams = std::make_unique<AnnotAppearance>(doc, &updatedAP);
if (keepAppearState && !oldAS.isNull()) {
appearState = std::make_unique<GooString>(oldAS.getName());
update("AS", std::move(oldAS));
} else {
update("AS", Object(objName, "N"));
}
} else {
appearStreams = std::make_unique<AnnotAppearance>(doc, &newAppearance);
update("AP", std::move(newAppearance));
if (appearStreams) {
appearance = appearStreams->getAppearanceStream(AnnotAppearance::appearNormal, appearState->c_str());
}
}
}
Object Annot::getAppearance() const
{
return appearance.fetch(doc->getXRef());
}
//------------------------------------------------------------------------
// AnnotPopup
//------------------------------------------------------------------------
AnnotPopup::AnnotPopup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
{
type = typePopup;
annotObj.dictSet("Subtype", Object(objName, "Popup"));
initialize(docA, annotObj.getDict());
}
AnnotPopup::AnnotPopup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
{
type = typePopup;
initialize(docA, annotObj.getDict());
}
AnnotPopup::~AnnotPopup() = default;
void AnnotPopup::initialize(PDFDoc *docA, Dict *dict)
{
const Object &parentObj = dict->lookupNF("Parent");
if (parentObj.isRef()) {
parentRef = parentObj.getRef();
} else {
parentRef = Ref::INVALID();
}
open = dict->lookup("Open").getBoolWithDefaultValue(false);
}
void AnnotPopup::setParent(Annot *parentA)
{
parentRef = parentA->getRef();
update("Parent", Object(parentRef));
}
void AnnotPopup::setOpen(bool openA)
{
open = openA;
update("Open", Object(open));
}
//------------------------------------------------------------------------
// AnnotMarkup
//------------------------------------------------------------------------
AnnotMarkup::AnnotMarkup(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA)
{
initialize(docA, annotObj.getDict());
}
AnnotMarkup::AnnotMarkup(PDFDoc *docA, Object &&dictObject, const Object *obj) : Annot(docA, std::move(dictObject), obj)
{
initialize(docA, annotObj.getDict());
}
AnnotMarkup::~AnnotMarkup() = default;
void AnnotMarkup::initialize(PDFDoc *docA, Dict *dict)
{
Object obj1;
obj1 = dict->lookup("T");
if (obj1.isString()) {
label.reset(obj1.getString()->copy());
}
Object popupObj = dict->lookup("Popup");
const Object &obj2 = dict->lookupNF("Popup");
if (popupObj.isDict() && obj2.isRef()) {
popup = std::make_unique<AnnotPopup>(docA, std::move(popupObj), &obj2);
}
opacity = dict->lookup("CA").getNumWithDefaultValue(1.0);
obj1 = dict->lookup("CreationDate");
if (obj1.isString()) {
date.reset(obj1.getString()->copy());
}
const Object &irtObj = dict->lookupNF("IRT");
if (irtObj.isRef()) {
inReplyTo = irtObj.getRef();
} else {
inReplyTo = Ref::INVALID();
}
obj1 = dict->lookup("Subj");
if (obj1.isString()) {
subject.reset(obj1.getString()->copy());
}
obj1 = dict->lookup("RT");
if (obj1.isName()) {
const char *replyName = obj1.getName();
if (!strcmp(replyName, "R")) {
replyTo = replyTypeR;
} else if (!strcmp(replyName, "Group")) {
replyTo = replyTypeGroup;
} else {
replyTo = replyTypeR;
}
} else {
replyTo = replyTypeR;
}
obj1 = dict->lookup("ExData");
if (obj1.isDict()) {
exData = parseAnnotExternalData(obj1.getDict());
} else {
exData = annotExternalDataMarkupUnknown;
}
}
void AnnotMarkup::setLabel(std::unique_ptr<GooString> &&new_label)
{
if (new_label) {
label = std::move(new_label);
// append the unicode marker <FE FF> if needed
if (!hasUnicodeByteOrderMark(label->toStr())) {
prependUnicodeByteOrderMark(label->toNonConstStr());
}
} 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));
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);
update("CreationDate", Object(date->copy()));
} else {
date.reset(nullptr);
update("CreationDate", Object(objNull));
}
}
void AnnotMarkup::removeReferencedObjects()
{
Page *pageobj = doc->getPage(page);
assert(pageobj != nullptr); // We're called when removing an annot from a page
// Remove popup
if (popup) {
pageobj->removeAnnot(popup.get());
}
Annot::removeReferencedObjects();
}
//------------------------------------------------------------------------
// AnnotText
//------------------------------------------------------------------------
AnnotText::AnnotText(PDFDoc *docA, PDFRectangle *rectA) : AnnotMarkup(docA, rectA)
{
type = typeText;
flags |= flagNoZoom | flagNoRotate;
annotObj.dictSet("Subtype", Object(objName, "Text"));
initialize(docA, annotObj.getDict());
}
AnnotText::AnnotText(PDFDoc *docA, Object &&dictObject, const Object *obj) : AnnotMarkup(docA, std::move(dictObject), obj)
{
type = typeText;
flags |= flagNoZoom | flagNoRotate;
initialize(docA, annotObj.getDict());
}
AnnotText::~AnnotText() = default;
void AnnotText::initialize(PDFDoc *docA, Dict *dict)
{
Object obj1;
open = dict->lookup("Open").getBoolWithDefaultValue(false);
obj1 = dict->lookup("Name");
if (obj1.isName()) {
icon = std::make_unique<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(bool openA)
{
open = openA;
update("Open", Object(open));
}
void AnnotText::setIcon(GooString *new_icon)
{
if (new_icon && icon->cmp(new_icon) == 0) {
return;
}
if (new_icon) {
icon = std::make_unique<GooString>(new_icon);
} else {
icon = std::make_unique<GooString>("Note");
}
update("Name", Object(objName, icon->c_str()));
invalidateAppearance();
}
#define ANNOT_TEXT_AP_NOTE \
"3.602 24 m 20.398 24 l 22.387 24 24 22.387 24 20.398 c 24 3.602 l 24\n" \
"1.613 22.387 0 20.398 0 c 3.602 0 l 1.613 0 0 1.613 0 3.602 c 0 20.398\n" \
"l 0 22.387 1.613 24 3.602 24 c h\n" \
"3.602 24 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 9 18 m 4 18 l 4 7 4 4 6 3 c 20 3 l 18 4 18 7 18 18 c 17 18 l S\n" \
"1.5 w\n" \
"0 j\n" \
"10 16 m 14 21 l S\n" \
"1.85625 w\n" \
"1 j\n" \
"15.07 20.523 m 15.07 19.672 14.379 18.977 13.523 18.977 c 12.672 18.977\n" \
"11.977 19.672 11.977 20.523 c 11.977 21.379 12.672 22.07 13.523 22.07 c\n" \
"14.379 22.07 15.07 21.379 15.07 20.523 c h\n" \
"15.07 20.523 m S\n" \
"1 w\n" \
"0 j\n" \
"6.5 13.5 m 15.5 13.5 l S\n" \
"6.5 10.5 m 13.5 10.5 l S\n" \
"6.801 7.5 m 15.5 7.5 l S\n" \
"0.729412 0.741176 0.713725 RG 2 w\n" \
"1 j\n" \
"9 19 m 4 19 l 4 8 4 5 6 4 c 20 4 l 18 5 18 8 18 19 c 17 19 l S\n" \
"1.5 w\n" \
"0 j\n" \
"10 17 m 14 22 l S\n" \
"1.85625 w\n" \
"1 j\n" \
"15.07 21.523 m 15.07 20.672 14.379 19.977 13.523 19.977 c 12.672 19.977\n" \
"11.977 20.672 11.977 21.523 c 11.977 22.379 12.672 23.07 13.523 23.07 c\n" \
"14.379 23.07 15.07 22.379 15.07 21.523 c h\n" \
"15.07 21.523 m S\n" \
"1 w\n" \
"0 j\n" \
"6.5 14.5 m 15.5 14.5 l S\n" \
"6.5 11.5 m 13.5 11.5 l S\n" \
"6.801 8.5 m 15.5 8.5 l S\n"
#define ANNOT_TEXT_AP_COMMENT \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"0 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 8 20 m 16 20 l 18.363 20 20 18.215 20 16 c 20 13 l 20 10.785 18.363 9\n" \
"16 9 c 13 9 l 8 3 l 8 9 l 8 9 l 5.637 9 4 10.785 4 13 c 4 16 l 4 18.215\n" \
"5.637 20 8 20 c h\n" \
"8 20 m S\n" \
"0.729412 0.741176 0.713725 RG 8 21 m 16 21 l 18.363 21 20 19.215 20 17\n" \
"c 20 14 l 20 11.785 18.363 10\n" \
"16 10 c 13 10 l 8 4 l 8 10 l 8 10 l 5.637 10 4 11.785 4 14 c 4 17 l 4\n" \
"19.215 5.637 21 8 21 c h\n" \
"8 21 m S\n"
#define ANNOT_TEXT_AP_KEY \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"0 j\n" \
"[] 0.0 d\n" \
"4 M 11.895 18.754 m 13.926 20.625 17.09 20.496 18.961 18.465 c 20.832\n" \
"16.434 20.699 13.27 18.668 11.398 c 17.164 10.016 15.043 9.746 13.281\n" \
"10.516 c 12.473 9.324 l 11.281 10.078 l 9.547 8.664 l 9.008 6.496 l\n" \
"7.059 6.059 l 6.34 4.121 l 5.543 3.668 l 3.375 4.207 l 2.938 6.156 l\n" \
"10.57 13.457 l 9.949 15.277 10.391 17.367 11.895 18.754 c h\n" \
"11.895 18.754 m S\n" \
"1.5 w\n" \
"16.059 15.586 m 16.523 15.078 17.316 15.043 17.824 15.512 c 18.332\n" \
"15.98 18.363 16.77 17.895 17.277 c 17.43 17.785 16.637 17.816 16.129\n" \
"17.352 c 15.621 16.883 15.59 16.094 16.059 15.586 c h\n" \
"16.059 15.586 m S\n" \
"0.729412 0.741176 0.713725 RG 2 w\n" \
"11.895 19.754 m 13.926 21.625 17.09 21.496 18.961 19.465 c 20.832\n" \
"17.434 20.699 14.27 18.668 12.398 c 17.164 11.016 15.043 10.746 13.281\n" \
"11.516 c 12.473 10.324 l 11.281 11.078 l 9.547 9.664 l 9.008 7.496 l\n" \
"7.059 7.059 l 6.34 5.121 l 5.543 4.668 l 3.375 5.207 l 2.938 7.156 l\n" \
"10.57 14.457 l 9.949 16.277 10.391 18.367 11.895 19.754 c h\n" \
"11.895 19.754 m S\n" \
"1.5 w\n" \
"16.059 16.586 m 16.523 16.078 17.316 16.043 17.824 16.512 c 18.332\n" \
"16.98 18.363 17.77 17.895 18.277 c 17.43 18.785 16.637 18.816 16.129\n" \
"18.352 c 15.621 17.883 15.59 17.094 16.059 16.586 c h\n" \
"16.059 16.586 m S\n"
#define ANNOT_TEXT_AP_HELP \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2.5 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 8.289 16.488 m 8.824 17.828 10.043 18.773 11.473 18.965 c 12.902 19.156\n" \
"14.328 18.559 15.195 17.406 c 16.062 16.254 16.242 14.723 15.664 13.398\n" \
"c S\n" \
"0 j\n" \
"12 8 m 12 12 16 11 16 15 c S\n" \
"1.539286 w\n" \
"1 j\n" \
"q 1 0 0 -0.999991 0 24 cm\n" \
"12.684 20.891 m 12.473 21.258 12.004 21.395 11.629 21.196 c 11.254\n" \
"20.992 11.105 20.531 11.297 20.149 c 11.488 19.77 11.945 19.61 12.332\n" \
"19.789 c 12.719 19.969 12.891 20.426 12.719 20.817 c S Q\n" \
"0.729412 0.741176 0.713725 RG 2.5 w\n" \
"8.289 17.488 m 9.109 19.539 11.438 20.535 13.488 19.711 c 15.539 18.891\n" \
"16.535 16.562 15.711 14.512 c 15.699 14.473 15.684 14.438 15.664 14.398\n" \
"c S\n" \
"0 j\n" \
"12 9 m 12 13 16 12 16 16 c S\n" \
"1.539286 w\n" \
"1 j\n" \
"q 1 0 0 -0.999991 0 24 cm\n" \
"12.684 19.891 m 12.473 20.258 12.004 20.395 11.629 20.195 c 11.254\n" \
"19.992 11.105 19.531 11.297 19.149 c 11.488 18.77 11.945 18.61 12.332\n" \
"18.789 c 12.719 18.969 12.891 19.426 12.719 19.817 c S Q\n"
#define ANNOT_TEXT_AP_NEW_PARAGRAPH \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 4 w\n" \
"0 J\n" \
"2 j\n" \
"[] 0.0 d\n" \
"4 M q 1 0 0 -1 0 24 cm\n" \
"9.211 11.988 m 8.449 12.07 7.711 11.707 7.305 11.059 c 6.898 10.41\n" \
"6.898 9.59 7.305 8.941 c 7.711 8.293 8.449 7.93 9.211 8.012 c S Q\n" \
"1.004413 w\n" \
"1 J\n" \
"1 j\n" \
"q 1 0 0 -0.991232 0 24 cm\n" \
"18.07 11.511 m 15.113 10.014 l 12.199 11.602 l 12.711 8.323 l 10.301\n" \
"6.045 l 13.574 5.517 l 14.996 2.522 l 16.512 5.474 l 19.801 5.899 l\n" \
"17.461 8.252 l 18.07 11.511 l h\n" \
"18.07 11.511 m S Q\n" \
"2 w\n" \
"0 j\n" \
"11 17 m 10 17 l 10 3 l S\n" \
"14 3 m 14 13 l S\n" \
"0.729412 0.741176 0.713725 RG 4 w\n" \
"0 J\n" \
"2 j\n" \
"q 1 0 0 -1 0 24 cm\n" \
"9.211 10.988 m 8.109 11.105 7.125 10.309 7.012 9.211 c 6.895 8.109\n" \
"7.691 7.125 8.789 7.012 c 8.93 6.996 9.07 6.996 9.211 7.012 c S Q\n" \
"1.004413 w\n" \
"1 J\n" \
"1 j\n" \
"q 1 0 0 -0.991232 0 24 cm\n" \
"18.07 10.502 m 15.113 9.005 l 12.199 10.593 l 12.711 7.314 l 10.301\n" \
"5.036 l 13.574 4.508 l 14.996 1.513 l 16.512 4.465 l 19.801 4.891 l\n" \
"17.461 7.243 l 18.07 10.502 l h\n" \
"18.07 10.502 m S Q\n" \
"2 w\n" \
"0 j\n" \
"11 18 m 10 18 l 10 4 l S\n" \
"14 4 m 14 14 l S\n"
#define ANNOT_TEXT_AP_PARAGRAPH \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 15 3 m 15 18 l 11 18 l 11 3 l S\n" \
"4 w\n" \
"q 1 0 0 -1 0 24 cm\n" \
"9.777 10.988 m 8.746 10.871 7.973 9.988 8 8.949 c 8.027 7.91 8.844\n" \
"7.066 9.879 7.004 c S Q\n" \
"0.729412 0.741176 0.713725 RG 2 w\n" \
"15 4 m 15 19 l 11 19 l 11 4 l S\n" \
"4 w\n" \
"q 1 0 0 -1 0 24 cm\n" \
"9.777 9.988 m 8.746 9.871 7.973 8.988 8 7.949 c 8.027 6.91 8.844 6.066\n" \
"9.879 6.004 c S Q\n"
#define ANNOT_TEXT_AP_INSERT \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2 w\n" \
"1 J\n" \
"0 j\n" \
"[] 0.0 d\n" \
"4 M 12 18.012 m 20 18 l S\n" \
"9 10 m 17 10 l S\n" \
"12 14.012 m 20 14 l S\n" \
"12 6.012 m 20 6.012 l S\n" \
"4 12 m 6 10 l 4 8 l S\n" \
"4 12 m 4 8 l S\n" \
"0.729412 0.741176 0.713725 RG 12 19.012 m 20 19 l S\n" \
"9 11 m 17 11 l S\n" \
"12 15.012 m 20 15 l S\n" \
"12 7.012 m 20 7.012 l S\n" \
"4 13 m 6 11 l 4 9 l S\n" \
"4 13 m 4 9 l S\n"
#define ANNOT_TEXT_AP_CROSS \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2.5 w\n" \
"1 J\n" \
"0 j\n" \
"[] 0.0 d\n" \
"4 M 18 5 m 6 17 l S\n" \
"6 5 m 18 17 l S\n" \
"0.729412 0.741176 0.713725 RG 18 6 m 6 18 l S\n" \
"6 6 m 18 18 l S\n"
#define ANNOT_TEXT_AP_CIRCLE \
"4.301 23 m 19.699 23 l 21.523 23 23 21.523 23 19.699 c 23 4.301 l 23\n" \
"2.477 21.523 1 19.699 1 c 4.301 1 l 2.477 1 1 2.477 1 4.301 c 1 19.699\n" \
"l 1 21.523 2.477 23 4.301 23 c h\n" \
"4.301 23 m f\n" \
"0.533333 0.541176 0.521569 RG 2.5 w\n" \
"1 J\n" \
"1 j\n" \
"[] 0.0 d\n" \
"4 M 19.5 11.5 m 19.5 7.359 16.141 4 12 4 c 7.859 4 4.5 7.359 4.5 11.5 c 4.5\n" \
"15.641 7.859 19 12 19 c 16.141 19 19.5 15.641 19.5 11.5 c h\n" \
"19.5 11.5 m S\n" \
"0.729412 0.741176 0.713725 RG 19.5 12.5 m 19.5 8.359 16.141 5 12 5 c\n" \
"7.859 5 4.5 8.359 4.5 12.5 c 4.5\n" \
"16.641 7.859 20 12 20 c 16.141 20 19.5 16.641 19.5 12.5 c h\n" \
"19.5 12.5 m S\n"
void AnnotText::draw(Gfx *gfx, bool printing)
{
double ca = 1;
if (!isVisible(printing)) {
return;
}
annotLocker();
if (appearance.isNull()) {
ca = opacity;
AnnotAppearanceBuilder appearBuilder;
appearBuilder.append("q\n");
if (color) {
appearBuilder.setDrawColor(*color, true);
} else {
appearBuilder.append("1 1 1 rg\n");
}
if (!icon->cmp("Note")) {
appearBuilder.append(ANNOT_TEXT_AP_NOTE);
} else if (!icon->cmp("Comment")) {
appearBuilder.append(ANNOT_TEXT_AP_COMMENT);
} else if (!icon->cmp("Key")) {
appearBuilder.append(ANNOT_TEXT_AP_KEY);
} else if (!icon->cmp("Help")) {
appearBuilder.append(ANNOT_TEXT_AP_HELP);
} else if (!icon->cmp("NewParagraph")) {
appearBuilder.append(ANNOT_TEXT_AP_NEW_PARAGRAPH);
} else if (!icon->cmp("Paragraph")) {
appearBuilder.append(ANNOT_TEXT_AP_PARAGRAPH);
} else if (!icon->cmp("Insert")) {
appearBuilder.append(ANNOT_TEXT_AP_INSERT);
} else if (!icon->cmp("Cross")) {
appearBuilder.append(ANNOT_TEXT_AP_CROSS);
} else if (!icon->cmp("Circle")) {
appearBuilder.append(ANNOT_TEXT_AP_CIRCLE);
}
appearBuilder.append("Q\n");
// Force 24x24 rectangle
PDFRectangle fixedRect(rect->x1, rect->y2 - 24, rect->x1 + 24, rect->y2);
appearBBox = std::make_unique<AnnotAppearanceBBox>(&fixedRect);
double bbox[4];
appearBBox->getBBoxRect(bbox);
if (ca == 1) {
appearance = createForm(appearBuilder.buffer(), bbox, false, nullptr);
} else {
Object aStream = createForm(appearBuilder.buffer(), bbox, true, nullptr);
GooString appearBuf("/GS0 gs\n/Fm0 Do");
Dict *resDict = createResourcesDict("Fm0", std::move(aStream), "GS0", ca, nullptr);
appearance = createForm(&appearBuf, bbox, false, resDict);
}
}
// draw the appearance stream
Object obj = appearance.fetch(gfx->getXRef());
if (appearBBox) {
gfx->drawAnnot(&obj, nullptr, color.get(), appearBBox->getPageXMin(), appearBBox->getPageYMin(), appearBBox->getPageXMax(), appearBBox->getPageYMax(), getRotation());
} else {
gfx->drawAnnot(&obj, nullptr, color.get(), rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
}
}
//------------------------------------------------------------------------
// AnnotLink
//------------------------------------------------------------------------
AnnotLink::AnnotLink(PDFDoc *docA, PDFRectangle *rectA) : Annot(docA, rectA