blob: 2db12d8827d80046338a9f21eaeb36a2fb3189da [file] [log] [blame]
//========================================================================
//
// Gfx.cc
//
// Copyright 1996-2013 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) 2005 Jonathan Blandford <jrb@redhat.com>
// Copyright (C) 2005-2013, 2015-2022 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006 Thorkild Stray <thorkild@ifi.uio.no>
// Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org>
// Copyright (C) 2007, 2011, 2017, 2021, 2023 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com>
// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
// Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
// Copyright (C) 2008 Pino Toscano <pino@kde.org>
// Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
// Copyright (C) 2008 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2009 M Joonas Pihlaja <jpihlaja@cc.helsinki.fi>
// Copyright (C) 2009-2016, 2020 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2009 William Bader <williambader@hotmail.com>
// Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
// Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com>
// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
// Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de>
// Copyright (C) 2012, 2024 Even Rouault <even.rouault@spatialys.com>
// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com>
// Copyright (C) 2014 Jason Crain <jason@aquaticape.us>
// Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2018 Denis Onishchenko <denis.onischenko@gmail.com>
// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
// Copyright (C) 2019-2022 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2019 Volker Krause <vkrause@kde.org>
// Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
// Copyright (C) 2021 Steve Rosenhamer <srosenhamer@me.com>
// Copyright (C) 2023 Anton Thomasson <antonthomasson@gmail.com>
// Copyright (C) 2024 Nelson Benítez León <nbenitezl@gmail.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#include <cstdlib>
#include <cstdio>
#include <cstddef>
#include <cstring>
#include <cmath>
#include <memory>
#include "goo/gmem.h"
#include "goo/GooTimer.h"
#include "GlobalParams.h"
#include "CharTypes.h"
#include "Object.h"
#include "PDFDoc.h"
#include "Array.h"
#include "Annot.h"
#include "Dict.h"
#include "Stream.h"
#include "Lexer.h"
#include "Parser.h"
#include "GfxFont.h"
#include "GfxState.h"
#include "OutputDev.h"
#include "Page.h"
#include "Annot.h"
#include "Error.h"
#include "Gfx.h"
#include "ProfileData.h"
#include "Catalog.h"
#include "OptionalContent.h"
// the MSVC math.h doesn't define this
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
//------------------------------------------------------------------------
// constants
//------------------------------------------------------------------------
// Max recursive depth for a function shading fill.
#define functionMaxDepth 6
// Max delta allowed in any color component for a function shading fill.
#define functionColorDelta (dblToCol(1 / 256.0))
// Max number of splits along the t axis for an axial shading fill.
#define axialMaxSplits 256
// Max delta allowed in any color component for an axial shading fill.
#define axialColorDelta (dblToCol(1 / 256.0))
// Max number of splits along the t axis for a radial shading fill.
#define radialMaxSplits 256
// Max delta allowed in any color component for a radial shading fill.
#define radialColorDelta (dblToCol(1 / 256.0))
// Max recursive depth for a Gouraud triangle shading fill.
//
// Triangles will be split at most gouraudMaxDepth times (each time into 4
// smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for
// every triangle.
#define gouraudMaxDepth 6
// Max delta allowed in any color component for a Gouraud triangle
// shading fill.
#define gouraudColorDelta (dblToCol(3. / 256.0))
// Gouraud triangle: if the three color parameters differ by at more than this percend of
// the total color parameter range, the triangle will be refined
#define gouraudParameterizedColorDelta 5e-3
// Max recursive depth for a patch mesh shading fill.
#define patchMaxDepth 6
// Max delta allowed in any color component for a patch mesh shading
// fill.
#define patchColorDelta (dblToCol((3. / 256.0)))
//------------------------------------------------------------------------
// Operator table
//------------------------------------------------------------------------
const Operator Gfx::opTab[] = {
{ "\"", 3, { tchkNum, tchkNum, tchkString }, &Gfx::opMoveSetShowText },
{ "'", 1, { tchkString }, &Gfx::opMoveShowText },
{ "B", 0, { tchkNone }, &Gfx::opFillStroke },
{ "B*", 0, { tchkNone }, &Gfx::opEOFillStroke },
{ "BDC", 2, { tchkName, tchkProps }, &Gfx::opBeginMarkedContent },
{ "BI", 0, { tchkNone }, &Gfx::opBeginImage },
{ "BMC", 1, { tchkName }, &Gfx::opBeginMarkedContent },
{ "BT", 0, { tchkNone }, &Gfx::opBeginText },
{ "BX", 0, { tchkNone }, &Gfx::opBeginIgnoreUndef },
{ "CS", 1, { tchkName }, &Gfx::opSetStrokeColorSpace },
{ "DP", 2, { tchkName, tchkProps }, &Gfx::opMarkPoint },
{ "Do", 1, { tchkName }, &Gfx::opXObject },
{ "EI", 0, { tchkNone }, &Gfx::opEndImage },
{ "EMC", 0, { tchkNone }, &Gfx::opEndMarkedContent },
{ "ET", 0, { tchkNone }, &Gfx::opEndText },
{ "EX", 0, { tchkNone }, &Gfx::opEndIgnoreUndef },
{ "F", 0, { tchkNone }, &Gfx::opFill },
{ "G", 1, { tchkNum }, &Gfx::opSetStrokeGray },
{ "ID", 0, { tchkNone }, &Gfx::opImageData },
{ "J", 1, { tchkInt }, &Gfx::opSetLineCap },
{ "K", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeCMYKColor },
{ "M", 1, { tchkNum }, &Gfx::opSetMiterLimit },
{ "MP", 1, { tchkName }, &Gfx::opMarkPoint },
{ "Q", 0, { tchkNone }, &Gfx::opRestore },
{ "RG", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeRGBColor },
{ "S", 0, { tchkNone }, &Gfx::opStroke },
{ "SC", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeColor },
{ "SCN",
-33,
{ tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
&Gfx::opSetStrokeColorN },
{ "T*", 0, { tchkNone }, &Gfx::opTextNextLine },
{ "TD", 2, { tchkNum, tchkNum }, &Gfx::opTextMoveSet },
{ "TJ", 1, { tchkArray }, &Gfx::opShowSpaceText },
{ "TL", 1, { tchkNum }, &Gfx::opSetTextLeading },
{ "Tc", 1, { tchkNum }, &Gfx::opSetCharSpacing },
{ "Td", 2, { tchkNum, tchkNum }, &Gfx::opTextMove },
{ "Tf", 2, { tchkName, tchkNum }, &Gfx::opSetFont },
{ "Tj", 1, { tchkString }, &Gfx::opShowText },
{ "Tm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetTextMatrix },
{ "Tr", 1, { tchkInt }, &Gfx::opSetTextRender },
{ "Ts", 1, { tchkNum }, &Gfx::opSetTextRise },
{ "Tw", 1, { tchkNum }, &Gfx::opSetWordSpacing },
{ "Tz", 1, { tchkNum }, &Gfx::opSetHorizScaling },
{ "W", 0, { tchkNone }, &Gfx::opClip },
{ "W*", 0, { tchkNone }, &Gfx::opEOClip },
{ "b", 0, { tchkNone }, &Gfx::opCloseFillStroke },
{ "b*", 0, { tchkNone }, &Gfx::opCloseEOFillStroke },
{ "c", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo },
{ "cm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opConcat },
{ "cs", 1, { tchkName }, &Gfx::opSetFillColorSpace },
{ "d", 2, { tchkArray, tchkNum }, &Gfx::opSetDash },
{ "d0", 2, { tchkNum, tchkNum }, &Gfx::opSetCharWidth },
{ "d1", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetCacheDevice },
{ "f", 0, { tchkNone }, &Gfx::opFill },
{ "f*", 0, { tchkNone }, &Gfx::opEOFill },
{ "g", 1, { tchkNum }, &Gfx::opSetFillGray },
{ "gs", 1, { tchkName }, &Gfx::opSetExtGState },
{ "h", 0, { tchkNone }, &Gfx::opClosePath },
{ "i", 1, { tchkNum }, &Gfx::opSetFlat },
{ "j", 1, { tchkInt }, &Gfx::opSetLineJoin },
{ "k", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillCMYKColor },
{ "l", 2, { tchkNum, tchkNum }, &Gfx::opLineTo },
{ "m", 2, { tchkNum, tchkNum }, &Gfx::opMoveTo },
{ "n", 0, { tchkNone }, &Gfx::opEndPath },
{ "q", 0, { tchkNone }, &Gfx::opSave },
{ "re", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opRectangle },
{ "rg", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillRGBColor },
{ "ri", 1, { tchkName }, &Gfx::opSetRenderingIntent },
{ "s", 0, { tchkNone }, &Gfx::opCloseStroke },
{ "sc", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillColor },
{ "scn",
-33,
{ tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
&Gfx::opSetFillColorN },
{ "sh", 1, { tchkName }, &Gfx::opShFill },
{ "v", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo1 },
{ "w", 1, { tchkNum }, &Gfx::opSetLineWidth },
{ "y", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo2 },
};
#define numOps (sizeof(opTab) / sizeof(Operator))
static inline bool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, unsigned int nComps, double delta)
{
for (unsigned int k = 0; k < nComps; ++k) {
if (abs(colorA.c[k] - colorB.c[k]) > delta) {
return false;
}
}
return true;
}
//------------------------------------------------------------------------
// GfxResources
//------------------------------------------------------------------------
GfxResources::GfxResources(XRef *xrefA, Dict *resDictA, GfxResources *nextA) : gStateCache(2), xref(xrefA)
{
Ref r;
if (resDictA) {
// build font dictionary
Dict *resDict = resDictA->copy(xref);
fonts = nullptr;
const Object &obj1 = resDict->lookupNF("Font");
if (obj1.isRef()) {
Object obj2 = obj1.fetch(xref);
if (obj2.isDict()) {
r = obj1.getRef();
fonts = new GfxFontDict(xref, &r, obj2.getDict());
}
} else if (obj1.isDict()) {
fonts = new GfxFontDict(xref, nullptr, obj1.getDict());
}
// get XObject dictionary
xObjDict = resDict->lookup("XObject");
// get color space dictionary
colorSpaceDict = resDict->lookup("ColorSpace");
// get pattern dictionary
patternDict = resDict->lookup("Pattern");
// get shading dictionary
shadingDict = resDict->lookup("Shading");
// get graphics state parameter dictionary
gStateDict = resDict->lookup("ExtGState");
// get properties dictionary
propertiesDict = resDict->lookup("Properties");
delete resDict;
} else {
fonts = nullptr;
xObjDict.setToNull();
colorSpaceDict.setToNull();
patternDict.setToNull();
shadingDict.setToNull();
gStateDict.setToNull();
propertiesDict.setToNull();
}
next = nextA;
}
GfxResources::~GfxResources()
{
delete fonts;
}
std::shared_ptr<GfxFont> GfxResources::doLookupFont(const char *name) const
{
const GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->fonts) {
if (std::shared_ptr<GfxFont> font = resPtr->fonts->lookup(name)) {
return font;
}
}
}
error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name);
return nullptr;
}
std::shared_ptr<GfxFont> GfxResources::lookupFont(const char *name)
{
return doLookupFont(name);
}
std::shared_ptr<const GfxFont> GfxResources::lookupFont(const char *name) const
{
return doLookupFont(name);
}
Object GfxResources::lookupXObject(const char *name)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->xObjDict.isDict()) {
Object obj = resPtr->xObjDict.dictLookup(name);
if (!obj.isNull()) {
return obj;
}
}
}
error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
return Object(objNull);
}
Object GfxResources::lookupXObjectNF(const char *name)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->xObjDict.isDict()) {
Object obj = resPtr->xObjDict.dictLookupNF(name).copy();
if (!obj.isNull()) {
return obj;
}
}
}
error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
return Object(objNull);
}
Object GfxResources::lookupMarkedContentNF(const char *name)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->propertiesDict.isDict()) {
Object obj = resPtr->propertiesDict.dictLookupNF(name).copy();
if (!obj.isNull()) {
return obj;
}
}
}
error(errSyntaxError, -1, "Marked Content '{0:s}' is unknown", name);
return Object(objNull);
}
Object GfxResources::lookupColorSpace(const char *name)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->colorSpaceDict.isDict()) {
Object obj = resPtr->colorSpaceDict.dictLookup(name);
if (!obj.isNull()) {
return obj;
}
}
}
return Object(objNull);
}
GfxPattern *GfxResources::lookupPattern(const char *name, OutputDev *out, GfxState *state)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->patternDict.isDict()) {
Ref patternRef = Ref::INVALID();
Object obj = resPtr->patternDict.getDict()->lookup(name, &patternRef);
if (!obj.isNull()) {
return GfxPattern::parse(resPtr, &obj, out, state, patternRef.num);
}
}
}
error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name);
return nullptr;
}
GfxShading *GfxResources::lookupShading(const char *name, OutputDev *out, GfxState *state)
{
GfxResources *resPtr;
GfxShading *shading;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->shadingDict.isDict()) {
Object obj = resPtr->shadingDict.dictLookup(name);
if (!obj.isNull()) {
shading = GfxShading::parse(resPtr, &obj, out, state);
return shading;
}
}
}
error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
return nullptr;
}
Object GfxResources::lookupGState(const char *name)
{
Object obj = lookupGStateNF(name);
if (obj.isNull()) {
return Object(objNull);
}
if (!obj.isRef()) {
return obj;
}
const Ref ref = obj.getRef();
if (auto *item = gStateCache.lookup(ref)) {
return item->copy();
}
auto *item = new Object { xref->fetch(ref) };
gStateCache.put(ref, item);
return item->copy();
}
Object GfxResources::lookupGStateNF(const char *name)
{
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->gStateDict.isDict()) {
Object obj = resPtr->gStateDict.dictLookupNF(name).copy();
if (!obj.isNull()) {
return obj;
}
}
}
error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
return Object(objNull);
}
//------------------------------------------------------------------------
// Gfx
//------------------------------------------------------------------------
Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict, double hDPI, double vDPI, const PDFRectangle *box, const PDFRectangle *cropBox, int rotate, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, XRef *xrefA)
: printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
{
int i;
doc = docA;
xref = (xrefA == nullptr) ? doc->getXRef() : xrefA;
catalog = doc->getCatalog();
subPage = false;
mcStack = nullptr;
parser = nullptr;
// start the resource stack
res = new GfxResources(xref, resDict, nullptr);
// initialize
out = outA;
state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
out->initGfxState(state);
stackHeight = 1;
pushStateGuard();
fontChanged = false;
clip = clipNone;
ignoreUndef = 0;
out->startPage(pageNum, state, xref);
out->setDefaultCTM(state->getCTM());
out->updateAll(state);
for (i = 0; i < 6; ++i) {
baseMatrix[i] = state->getCTM()[i];
}
displayDepth = 0;
ocState = true;
parser = nullptr;
abortCheckCbk = abortCheckCbkA;
abortCheckCbkData = abortCheckCbkDataA;
// set crop box
if (cropBox) {
state->moveTo(cropBox->x1, cropBox->y1);
state->lineTo(cropBox->x2, cropBox->y1);
state->lineTo(cropBox->x2, cropBox->y2);
state->lineTo(cropBox->x1, cropBox->y2);
state->closePath();
state->clip();
out->clip(state);
state->clearPath();
}
#ifdef USE_CMS
initDisplayProfile();
#endif
}
Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, const PDFRectangle *box, const PDFRectangle *cropBox, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, Gfx *gfxA)
: printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
{
int i;
doc = docA;
if (gfxA) {
xref = gfxA->getXRef();
formsDrawing = gfxA->formsDrawing;
charProcDrawing = gfxA->charProcDrawing;
} else {
xref = doc->getXRef();
}
catalog = doc->getCatalog();
subPage = true;
mcStack = nullptr;
parser = nullptr;
// start the resource stack
res = new GfxResources(xref, resDict, nullptr);
// initialize
out = outA;
double hDPI = 72;
double vDPI = 72;
if (gfxA) {
hDPI = gfxA->getState()->getHDPI();
vDPI = gfxA->getState()->getVDPI();
}
state = new GfxState(hDPI, vDPI, box, 0, false);
stackHeight = 1;
pushStateGuard();
fontChanged = false;
clip = clipNone;
ignoreUndef = 0;
for (i = 0; i < 6; ++i) {
baseMatrix[i] = state->getCTM()[i];
}
displayDepth = 0;
ocState = true;
parser = nullptr;
abortCheckCbk = abortCheckCbkA;
abortCheckCbkData = abortCheckCbkDataA;
// set crop box
if (cropBox) {
state->moveTo(cropBox->x1, cropBox->y1);
state->lineTo(cropBox->x2, cropBox->y1);
state->lineTo(cropBox->x2, cropBox->y2);
state->lineTo(cropBox->x1, cropBox->y2);
state->closePath();
state->clip();
out->clip(state);
state->clearPath();
}
#ifdef USE_CMS
initDisplayProfile();
#endif
}
#ifdef USE_CMS
# include <lcms2.h>
void Gfx::initDisplayProfile()
{
Object catDict = xref->getCatalog();
if (catDict.isDict()) {
Object outputIntents = catDict.dictLookup("OutputIntents");
if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) {
Object firstElement = outputIntents.arrayGet(0);
if (firstElement.isDict()) {
Object profile = firstElement.dictLookup("DestOutputProfile");
if (profile.isStream()) {
Stream *iccStream = profile.getStream();
const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size()));
if (!hp) {
error(errSyntaxWarning, -1, "read ICCBased color space profile error");
} else {
state->setDisplayProfile(hp);
}
}
}
}
}
}
#endif
Gfx::~Gfx()
{
while (stateGuards.size()) {
popStateGuard();
}
if (!subPage) {
out->endPage();
}
// There shouldn't be more saves, but pop them if there were any
while (state->hasSaves()) {
error(errSyntaxError, -1, "Found state under last state guard. Popping.");
restoreState();
}
delete state;
while (res) {
popResources();
}
while (mcStack) {
popMarkedContent();
}
}
void Gfx::display(Object *obj, bool topLevel)
{
// check for excessive recursion
if (displayDepth > 100) {
return;
}
if (obj->isArray()) {
for (int i = 0; i < obj->arrayGetLength(); ++i) {
Object obj2 = obj->arrayGet(i);
if (!obj2.isStream()) {
error(errSyntaxError, -1, "Weird page contents");
return;
}
}
} else if (!obj->isStream()) {
error(errSyntaxError, -1, "Weird page contents");
return;
}
parser = new Parser(xref, obj, false);
go(topLevel);
delete parser;
parser = nullptr;
}
void Gfx::go(bool topLevel)
{
Object obj;
Object args[maxArgs];
int numArgs, i;
int lastAbortCheck;
// scan a sequence of objects
pushStateGuard();
updateLevel = 1; // make sure even empty pages trigger a call to dump()
lastAbortCheck = 0;
numArgs = 0;
obj = parser->getObj();
while (!obj.isEOF()) {
commandAborted = false;
// got a command - execute it
if (obj.isCmd()) {
if (printCommands) {
obj.print(stdout);
for (i = 0; i < numArgs; ++i) {
printf(" ");
args[i].print(stdout);
}
printf("\n");
fflush(stdout);
}
GooTimer *timer = nullptr;
if (unlikely(profileCommands)) {
timer = new GooTimer();
}
// Run the operation
execOp(&obj, args, numArgs);
// Update the profile information
if (unlikely(profileCommands)) {
if (auto *const hash = out->getProfileHash()) {
auto &data = (*hash)[obj.getCmd()];
data.addElement(timer->getElapsed());
}
delete timer;
}
for (i = 0; i < numArgs; ++i) {
args[i].setToNull(); // Free memory early
}
numArgs = 0;
// periodically update display
if (++updateLevel >= 20000) {
out->dump();
updateLevel = 0;
lastAbortCheck = 0;
}
// did the command throw an exception
if (commandAborted) {
// don't propogate; recursive drawing comes from Form XObjects which
// should probably be drawn in a separate context anyway for caching
commandAborted = false;
break;
}
// check for an abort
if (abortCheckCbk) {
if (updateLevel - lastAbortCheck > 10) {
if ((*abortCheckCbk)(abortCheckCbkData)) {
break;
}
lastAbortCheck = updateLevel;
}
}
// got an argument - save it
} else if (numArgs < maxArgs) {
args[numArgs++] = std::move(obj);
// too many arguments - something is wrong
} else {
error(errSyntaxError, getPos(), "Too many args in content stream");
if (printCommands) {
printf("throwing away arg: ");
obj.print(stdout);
printf("\n");
fflush(stdout);
}
}
// grab the next object
obj = parser->getObj();
}
// args at end with no command
if (numArgs > 0) {
error(errSyntaxError, getPos(), "Leftover args in content stream");
if (printCommands) {
printf("%d leftovers:", numArgs);
for (i = 0; i < numArgs; ++i) {
printf(" ");
args[i].print(stdout);
}
printf("\n");
fflush(stdout);
}
}
popStateGuard();
// update display
if (topLevel && updateLevel > 0) {
out->dump();
}
}
void Gfx::execOp(Object *cmd, Object args[], int numArgs)
{
const Operator *op;
Object *argPtr;
int i;
// find operator
const char *name = cmd->getCmd();
if (!(op = findOp(name))) {
if (ignoreUndef == 0) {
error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
}
return;
}
// type check args
argPtr = args;
if (op->numArgs >= 0) {
if (numArgs < op->numArgs) {
error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name);
commandAborted = true;
return;
}
if (numArgs > op->numArgs) {
#if 0
error(errSyntaxWarning, getPos(),
"Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
#endif
argPtr += numArgs - op->numArgs;
numArgs = op->numArgs;
}
} else {
if (numArgs > -op->numArgs) {
error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
return;
}
}
for (i = 0; i < numArgs; ++i) {
if (!checkArg(&argPtr[i], op->tchk[i])) {
error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName());
return;
}
}
// do it
(this->*op->func)(argPtr, numArgs);
}
const Operator *Gfx::findOp(const char *name)
{
int a, b, m, cmp;
a = -1;
b = numOps;
cmp = 0; // make gcc happy
// invariant: opTab[a] < name < opTab[b]
while (b - a > 1) {
m = (a + b) / 2;
cmp = strcmp(opTab[m].name, name);
if (cmp < 0) {
a = m;
} else if (cmp > 0) {
b = m;
} else {
a = b = m;
}
}
if (cmp != 0) {
return nullptr;
}
return &opTab[a];
}
bool Gfx::checkArg(Object *arg, TchkType type)
{
switch (type) {
case tchkBool:
return arg->isBool();
case tchkInt:
return arg->isInt();
case tchkNum:
return arg->isNum();
case tchkString:
return arg->isString();
case tchkName:
return arg->isName();
case tchkArray:
return arg->isArray();
case tchkProps:
return arg->isDict() || arg->isName();
case tchkSCN:
return arg->isNum() || arg->isName();
case tchkNone:
return false;
}
return false;
}
Goffset Gfx::getPos()
{
return parser ? parser->getPos() : -1;
}
//------------------------------------------------------------------------
// graphics state operators
//------------------------------------------------------------------------
void Gfx::opSave(Object args[], int numArgs)
{
saveState();
}
void Gfx::opRestore(Object args[], int numArgs)
{
restoreState();
}
void Gfx::opConcat(Object args[], int numArgs)
{
state->concatCTM(args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
out->updateCTM(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
fontChanged = true;
}
void Gfx::opSetDash(Object args[], int numArgs)
{
const Array *a = args[0].getArray();
int length = a->getLength();
std::vector<double> dash(length);
for (int i = 0; i < length; ++i) {
dash[i] = a->get(i).getNumWithDefaultValue(0);
}
state->setLineDash(std::move(dash), args[1].getNum());
out->updateLineDash(state);
}
void Gfx::opSetFlat(Object args[], int numArgs)
{
state->setFlatness((int)args[0].getNum());
out->updateFlatness(state);
}
void Gfx::opSetLineJoin(Object args[], int numArgs)
{
state->setLineJoin(args[0].getInt());
out->updateLineJoin(state);
}
void Gfx::opSetLineCap(Object args[], int numArgs)
{
state->setLineCap(args[0].getInt());
out->updateLineCap(state);
}
void Gfx::opSetMiterLimit(Object args[], int numArgs)
{
state->setMiterLimit(args[0].getNum());
out->updateMiterLimit(state);
}
void Gfx::opSetLineWidth(Object args[], int numArgs)
{
state->setLineWidth(args[0].getNum());
out->updateLineWidth(state);
}
void Gfx::opSetExtGState(Object args[], int numArgs)
{
Object obj1, obj2;
GfxBlendMode mode;
bool haveFillOP;
GfxColor backdropColor;
bool haveBackdropColor;
bool alpha;
double opac;
obj1 = res->lookupGState(args[0].getName());
if (obj1.isNull()) {
return;
}
if (!obj1.isDict()) {
error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type", args[0].getName());
return;
}
if (printCommands) {
printf(" gfx state dict: ");
obj1.print();
printf("\n");
}
// parameters that are also set by individual PDF operators
obj2 = obj1.dictLookup("LW");
if (obj2.isNum()) {
opSetLineWidth(&obj2, 1);
}
obj2 = obj1.dictLookup("LC");
if (obj2.isInt()) {
opSetLineCap(&obj2, 1);
}
obj2 = obj1.dictLookup("LJ");
if (obj2.isInt()) {
opSetLineJoin(&obj2, 1);
}
obj2 = obj1.dictLookup("ML");
if (obj2.isNum()) {
opSetMiterLimit(&obj2, 1);
}
obj2 = obj1.dictLookup("D");
if (obj2.isArray() && obj2.arrayGetLength() == 2) {
Object args2[2];
args2[0] = obj2.arrayGet(0);
args2[1] = obj2.arrayGet(1);
if (args2[0].isArray() && args2[1].isNum()) {
opSetDash(args2, 2);
}
}
#if 0 //~ need to add a new version of GfxResources::lookupFont() that
//~ takes an indirect ref instead of a name
if (obj1.dictLookup("Font", &obj2)->isArray() &&
obj2.arrayGetLength() == 2) {
obj2.arrayGet(0, &args2[0]);
obj2.arrayGet(1, &args2[1]);
if (args2[0].isDict() && args2[1].isNum()) {
opSetFont(args2, 2);
}
args2[0].free();
args2[1].free();
}
obj2.free();
#endif
obj2 = obj1.dictLookup("FL");
if (obj2.isNum()) {
opSetFlat(&obj2, 1);
}
// transparency support: blend mode, fill/stroke opacity
obj2 = obj1.dictLookup("BM");
if (!obj2.isNull()) {
if (state->parseBlendMode(&obj2, &mode)) {
state->setBlendMode(mode);
out->updateBlendMode(state);
} else {
error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
}
}
obj2 = obj1.dictLookup("ca");
if (obj2.isNum()) {
opac = obj2.getNum();
state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
out->updateFillOpacity(state);
}
obj2 = obj1.dictLookup("CA");
if (obj2.isNum()) {
opac = obj2.getNum();
state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
out->updateStrokeOpacity(state);
}
// fill/stroke overprint, overprint mode
obj2 = obj1.dictLookup("op");
if ((haveFillOP = obj2.isBool())) {
state->setFillOverprint(obj2.getBool());
out->updateFillOverprint(state);
}
obj2 = obj1.dictLookup("OP");
if (obj2.isBool()) {
state->setStrokeOverprint(obj2.getBool());
out->updateStrokeOverprint(state);
if (!haveFillOP) {
state->setFillOverprint(obj2.getBool());
out->updateFillOverprint(state);
}
}
obj2 = obj1.dictLookup("OPM");
if (obj2.isInt()) {
state->setOverprintMode(obj2.getInt());
out->updateOverprintMode(state);
}
// stroke adjust
obj2 = obj1.dictLookup("SA");
if (obj2.isBool()) {
state->setStrokeAdjust(obj2.getBool());
out->updateStrokeAdjust(state);
}
// transfer function
obj2 = obj1.dictLookup("TR2");
if (obj2.isNull()) {
obj2 = obj1.dictLookup("TR");
}
if (obj2.isName("Default") || obj2.isName("Identity")) {
Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr };
state->setTransfer(funcs);
out->updateTransfer(state);
} else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr };
for (int i = 0; i < 4; ++i) {
Object obj3 = obj2.arrayGet(i);
funcs[i] = Function::parse(&obj3);
if (!funcs[i]) {
break;
}
}
if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
state->setTransfer(funcs);
out->updateTransfer(state);
} else {
for (Function *f : funcs) {
delete f;
}
}
} else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
Function *funcs[4];
if ((funcs[0] = Function::parse(&obj2))) {
funcs[1] = funcs[2] = funcs[3] = nullptr;
state->setTransfer(funcs);
out->updateTransfer(state);
}
} else if (!obj2.isNull()) {
error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState");
}
// alpha is shape
obj2 = obj1.dictLookup("AIS");
if (obj2.isBool()) {
state->setAlphaIsShape(obj2.getBool());
out->updateAlphaIsShape(state);
}
// text knockout
obj2 = obj1.dictLookup("TK");
if (obj2.isBool()) {
state->setTextKnockout(obj2.getBool());
out->updateTextKnockout(state);
}
// soft mask
obj2 = obj1.dictLookup("SMask");
if (!obj2.isNull()) {
if (obj2.isName("None")) {
out->clearSoftMask(state);
} else if (obj2.isDict()) {
Object obj3 = obj2.dictLookup("S");
if (obj3.isName("Alpha")) {
alpha = true;
} else { // "Luminosity"
alpha = false;
}
Function *softMaskTransferFunc = nullptr;
obj3 = obj2.dictLookup("TR");
if (!obj3.isNull()) {
if (obj3.isName("Default") || obj3.isName("Identity")) {
// nothing
} else {
softMaskTransferFunc = Function::parse(&obj3);
if (softMaskTransferFunc == nullptr || softMaskTransferFunc->getInputSize() != 1 || softMaskTransferFunc->getOutputSize() != 1) {
error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState");
delete softMaskTransferFunc;
softMaskTransferFunc = nullptr;
}
}
}
obj3 = obj2.dictLookup("BC");
if ((haveBackdropColor = obj3.isArray())) {
for (int &c : backdropColor.c) {
c = 0;
}
for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
Object obj4 = obj3.arrayGet(i);
if (obj4.isNum()) {
backdropColor.c[i] = dblToCol(obj4.getNum());
}
}
}
obj3 = obj2.dictLookup("G");
if (obj3.isStream()) {
Object obj4 = obj3.streamGetDict()->lookup("Group");
if (obj4.isDict()) {
GfxColorSpace *blendingColorSpace = nullptr;
Object obj5 = obj4.dictLookup("CS");
if (!obj5.isNull()) {
blendingColorSpace = GfxColorSpace::parse(res, &obj5, out, state);
}
const bool isolated = obj4.dictLookup("I").getBoolWithDefaultValue(false);
const bool knockout = obj4.dictLookup("K").getBoolWithDefaultValue(false);
if (!haveBackdropColor) {
if (blendingColorSpace) {
blendingColorSpace->getDefaultColor(&backdropColor);
} else {
//~ need to get the parent or default color space (?)
for (int &c : backdropColor.c) {
c = 0;
}
}
}
doSoftMask(&obj3, alpha, blendingColorSpace, isolated, knockout, softMaskTransferFunc, &backdropColor);
delete blendingColorSpace;
} else {
error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
}
} else {
error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
}
delete softMaskTransferFunc;
} else if (!obj2.isNull()) {
error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
}
}
obj2 = obj1.dictLookup("Font");
if (obj2.isArray()) {
if (obj2.arrayGetLength() == 2) {
const Object &fargs0 = obj2.arrayGetNF(0);
Object fargs1 = obj2.arrayGet(1);
if (fargs0.isRef() && fargs1.isNum()) {
Object fobj = fargs0.fetch(xref);
if (fobj.isDict()) {
Ref r = fargs0.getRef();
std::shared_ptr<GfxFont> font = GfxFont::makeFont(xref, args[0].getName(), r, fobj.getDict());
state->setFont(font, fargs1.getNum());
fontChanged = true;
}
}
} else {
error(errSyntaxError, getPos(), "Number of args mismatch for /Font in ExtGState");
}
}
obj2 = obj1.dictLookup("LW");
if (obj2.isNum()) {
opSetLineWidth(&obj2, 1);
}
obj2 = obj1.dictLookup("LC");
if (obj2.isInt()) {
opSetLineCap(&obj2, 1);
}
obj2 = obj1.dictLookup("LJ");
if (obj2.isInt()) {
opSetLineJoin(&obj2, 1);
}
obj2 = obj1.dictLookup("ML");
if (obj2.isNum()) {
opSetMiterLimit(&obj2, 1);
}
obj2 = obj1.dictLookup("D");
if (obj2.isArray()) {
if (obj2.arrayGetLength() == 2) {
Object dargs[2];
dargs[0] = obj2.arrayGetNF(0).copy();
dargs[1] = obj2.arrayGet(1);
if (dargs[0].isArray() && dargs[1].isInt()) {
opSetDash(dargs, 2);
}
} else {
error(errSyntaxError, getPos(), "Number of args mismatch for /D in ExtGState");
}
}
obj2 = obj1.dictLookup("RI");
if (obj2.isName()) {
opSetRenderingIntent(&obj2, 1);
}
obj2 = obj1.dictLookup("FL");
if (obj2.isNum()) {
opSetFlat(&obj2, 1);
}
}
void Gfx::doSoftMask(Object *str, bool alpha, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, Function *transferFunc, GfxColor *backdropColor)
{
Dict *dict, *resDict;
double m[6], bbox[4];
Object obj1;
int i;
// get stream dict
dict = str->streamGetDict();
// check form type
obj1 = dict->lookup("FormType");
if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
error(errSyntaxError, getPos(), "Unknown form type");
}
// get bounding box
obj1 = dict->lookup("BBox");
if (!obj1.isArray()) {
error(errSyntaxError, getPos(), "Bad form bounding box");
return;
}
for (i = 0; i < 4; ++i) {
Object obj2 = obj1.arrayGet(i);
if (likely(obj2.isNum())) {
bbox[i] = obj2.getNum();
} else {
error(errSyntaxError, getPos(), "Bad form bounding box (non number)");
return;
}
}
// get matrix
obj1 = dict->lookup("Matrix");
if (obj1.isArray()) {
for (i = 0; i < 6; ++i) {
Object obj2 = obj1.arrayGet(i);
if (likely(obj2.isNum())) {
m[i] = obj2.getNum();
} else {
m[i] = 0;
}
}
} else {
m[0] = 1;
m[1] = 0;
m[2] = 0;
m[3] = 1;
m[4] = 0;
m[5] = 0;
}
// get resources
obj1 = dict->lookup("Resources");
resDict = obj1.isDict() ? obj1.getDict() : nullptr;
// draw it
drawForm(str, resDict, m, bbox, true, true, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor);
}
void Gfx::opSetRenderingIntent(Object args[], int numArgs)
{
state->setRenderingIntent(args[0].getName());
}
//------------------------------------------------------------------------
// color operators
//------------------------------------------------------------------------
void Gfx::opSetFillGray(Object args[], int numArgs)
{
GfxColor color;
GfxColorSpace *colorSpace = nullptr;
state->setFillPattern(nullptr);
Object obj = res->lookupColorSpace("DefaultGray");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr || colorSpace->getNComps() > 1) {
delete colorSpace;
colorSpace = state->copyDefaultGrayColorSpace();
}
state->setFillColorSpace(colorSpace);
out->updateFillColorSpace(state);
color.c[0] = dblToCol(args[0].getNum());
state->setFillColor(&color);
out->updateFillColor(state);
}
void Gfx::opSetStrokeGray(Object args[], int numArgs)
{
GfxColor color;
GfxColorSpace *colorSpace = nullptr;
state->setStrokePattern(nullptr);
Object obj = res->lookupColorSpace("DefaultGray");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr) {
colorSpace = state->copyDefaultGrayColorSpace();
}
state->setStrokeColorSpace(colorSpace);
out->updateStrokeColorSpace(state);
color.c[0] = dblToCol(args[0].getNum());
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
void Gfx::opSetFillCMYKColor(Object args[], int numArgs)
{
GfxColor color;
GfxColorSpace *colorSpace = nullptr;
int i;
Object obj = res->lookupColorSpace("DefaultCMYK");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr) {
colorSpace = state->copyDefaultCMYKColorSpace();
}
state->setFillPattern(nullptr);
state->setFillColorSpace(colorSpace);
out->updateFillColorSpace(state);
for (i = 0; i < 4; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setFillColor(&color);
out->updateFillColor(state);
}
void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs)
{
GfxColor color;
GfxColorSpace *colorSpace = nullptr;
int i;
state->setStrokePattern(nullptr);
Object obj = res->lookupColorSpace("DefaultCMYK");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr) {
colorSpace = state->copyDefaultCMYKColorSpace();
}
state->setStrokeColorSpace(colorSpace);
out->updateStrokeColorSpace(state);
for (i = 0; i < 4; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
void Gfx::opSetFillRGBColor(Object args[], int numArgs)
{
GfxColorSpace *colorSpace = nullptr;
GfxColor color;
int i;
state->setFillPattern(nullptr);
Object obj = res->lookupColorSpace("DefaultRGB");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr || colorSpace->getNComps() > 3) {
delete colorSpace;
colorSpace = state->copyDefaultRGBColorSpace();
}
state->setFillColorSpace(colorSpace);
out->updateFillColorSpace(state);
for (i = 0; i < 3; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setFillColor(&color);
out->updateFillColor(state);
}
void Gfx::opSetStrokeRGBColor(Object args[], int numArgs)
{
GfxColorSpace *colorSpace = nullptr;
GfxColor color;
int i;
state->setStrokePattern(nullptr);
Object obj = res->lookupColorSpace("DefaultRGB");
if (!obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace == nullptr) {
colorSpace = state->copyDefaultRGBColorSpace();
}
state->setStrokeColorSpace(colorSpace);
out->updateStrokeColorSpace(state);
for (i = 0; i < 3; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
void Gfx::opSetFillColorSpace(Object args[], int numArgs)
{
GfxColorSpace *colorSpace;
GfxColor color;
Object obj = res->lookupColorSpace(args[0].getName());
if (obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
} else {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace) {
state->setFillPattern(nullptr);
state->setFillColorSpace(colorSpace);
out->updateFillColorSpace(state);
colorSpace->getDefaultColor(&color);
state->setFillColor(&color);
out->updateFillColor(state);
} else {
error(errSyntaxError, getPos(), "Bad color space (fill)");
}
}
void Gfx::opSetStrokeColorSpace(Object args[], int numArgs)
{
GfxColorSpace *colorSpace;
GfxColor color;
state->setStrokePattern(nullptr);
Object obj = res->lookupColorSpace(args[0].getName());
if (obj.isNull()) {
colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
} else {
colorSpace = GfxColorSpace::parse(res, &obj, out, state);
}
if (colorSpace) {
state->setStrokeColorSpace(colorSpace);
out->updateStrokeColorSpace(state);
colorSpace->getDefaultColor(&color);
state->setStrokeColor(&color);
out->updateStrokeColor(state);
} else {
error(errSyntaxError, getPos(), "Bad color space (stroke)");
}
}
void Gfx::opSetFillColor(Object args[], int numArgs)
{
GfxColor color;
int i;
if (numArgs != state->getFillColorSpace()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command");
return;
}
state->setFillPattern(nullptr);
for (i = 0; i < numArgs; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setFillColor(&color);
out->updateFillColor(state);
}
void Gfx::opSetStrokeColor(Object args[], int numArgs)
{
GfxColor color;
int i;
if (numArgs != state->getStrokeColorSpace()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command");
return;
}
state->setStrokePattern(nullptr);
for (i = 0; i < numArgs; ++i) {
color.c[i] = dblToCol(args[i].getNum());
}
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
void Gfx::opSetFillColorN(Object args[], int numArgs)
{
GfxColor color;
GfxPattern *pattern;
int i;
if (state->getFillColorSpace()->getMode() == csPattern) {
if (numArgs > 1) {
if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
return;
}
for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
if (args[i].isNum()) {
color.c[i] = dblToCol(args[i].getNum());
} else {
color.c[i] = 0; // TODO Investigate if this is what Adobe does
}
}
state->setFillColor(&color);
out->updateFillColor(state);
}
if (numArgs > 0) {
if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) {
state->setFillPattern(pattern);
}
}
} else {
if (numArgs != state->getFillColorSpace()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
return;
}
state->setFillPattern(nullptr);
for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
if (args[i].isNum()) {
color.c[i] = dblToCol(args[i].getNum());
} else {
color.c[i] = 0; // TODO Investigate if this is what Adobe does
}
}
state->setFillColor(&color);
out->updateFillColor(state);
}
}
void Gfx::opSetStrokeColorN(Object args[], int numArgs)
{
GfxColor color;
GfxPattern *pattern;
int i;
if (state->getStrokeColorSpace()->getMode() == csPattern) {
if (numArgs > 1) {
if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
return;
}
for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
if (args[i].isNum()) {
color.c[i] = dblToCol(args[i].getNum());
} else {
color.c[i] = 0; // TODO Investigate if this is what Adobe does
}
}
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
if (unlikely(numArgs <= 0)) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
return;
}
if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) {
state->setStrokePattern(pattern);
}
} else {
if (numArgs != state->getStrokeColorSpace()->getNComps()) {
error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
return;
}
state->setStrokePattern(nullptr);
for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
if (args[i].isNum()) {
color.c[i] = dblToCol(args[i].getNum());
} else {
color.c[i] = 0; // TODO Investigate if this is what Adobe does
}
}
state->setStrokeColor(&color);
out->updateStrokeColor(state);
}
}
//------------------------------------------------------------------------
// path segment operators
//------------------------------------------------------------------------
void Gfx::opMoveTo(Object args[], int numArgs)
{
state->moveTo(args[0].getNum(), args[1].getNum());
}
void Gfx::opLineTo(Object args[], int numArgs)
{
if (!state->isCurPt()) {
error(errSyntaxError, getPos(), "No current point in lineto");
return;
}
state->lineTo(args[0].getNum(), args[1].getNum());
}
void Gfx::opCurveTo(Object args[], int numArgs)
{
double x1, y1, x2, y2, x3, y3;
if (!state->isCurPt()) {
error(errSyntaxError, getPos(), "No current point in curveto");
return;
}
x1 = args[0].getNum();
y1 = args[1].getNum();
x2 = args[2].getNum();
y2 = args[3].getNum();
x3 = args[4].getNum();
y3 = args[5].getNum();
state->curveTo(x1, y1, x2, y2, x3, y3);
}
void Gfx::opCurveTo1(Object args[], int numArgs)
{
double x1, y1, x2, y2, x3, y3;
if (!state->isCurPt()) {
error(errSyntaxError, getPos(), "No current point in curveto1");
return;
}
x1 = state->getCurX();
y1 = state->getCurY();
x2 = args[0].getNum();
y2 = args[1].getNum();
x3 = args[2].getNum();
y3 = args[3].getNum();
state->curveTo(x1, y1, x2, y2, x3, y3);
}
void Gfx::opCurveTo2(Object args[], int numArgs)
{
double x1, y1, x2, y2, x3, y3;
if (!state->isCurPt()) {
error(errSyntaxError, getPos(), "No current point in curveto2");
return;
}
x1 = args[0].getNum();
y1 = args[1].getNum();
x2 = args[2].getNum();
y2 = args[3].getNum();
x3 = x2;
y3 = y2;
state->curveTo(x1, y1, x2, y2, x3, y3);
}
void Gfx::opRectangle(Object args[], int numArgs)
{
double x, y, w, h;
x = args[0].getNum();
y = args[1].getNum();
w = args[2].getNum();
h = args[3].getNum();
state->moveTo(x, y);
state->lineTo(x + w, y);
state->lineTo(x + w, y + h);
state->lineTo(x, y + h);
state->closePath();
}
void Gfx::opClosePath(Object args[], int numArgs)
{
if (!state->isCurPt()) {
error(errSyntaxError, getPos(), "No current point in closepath");
return;
}
state->closePath();
}
//------------------------------------------------------------------------
// path painting operators
//------------------------------------------------------------------------
void Gfx::opEndPath(Object args[], int numArgs)
{
doEndPath();
}
void Gfx::opStroke(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in stroke");
return;
}
if (state->isPath()) {
if (ocState) {
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in closepath/stroke");
return;
}
if (state->isPath()) {
state->closePath();
if (ocState) {
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::opFill(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in fill");
return;
}
if (state->isPath()) {
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(false);
} else {
out->fill(state);
}
}
}
doEndPath();
}
void Gfx::opEOFill(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in eofill");
return;
}
if (state->isPath()) {
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(true);
} else {
out->eoFill(state);
}
}
}
doEndPath();
}
void Gfx::opFillStroke(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in fill/stroke");
return;
}
if (state->isPath()) {
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(false);
} else {
out->fill(state);
}
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::opCloseFillStroke(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in closepath/fill/stroke");
return;
}
if (state->isPath()) {
state->closePath();
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(false);
} else {
out->fill(state);
}
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::opEOFillStroke(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in eofill/stroke");
return;
}
if (state->isPath()) {
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(true);
} else {
out->eoFill(state);
}
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::opCloseEOFillStroke(Object args[], int numArgs)
{
if (!state->isCurPt()) {
// error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke");
return;
}
if (state->isPath()) {
state->closePath();
if (ocState) {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternFill(true);
} else {
out->eoFill(state);
}
if (state->getStrokeColorSpace()->getMode() == csPattern) {
doPatternStroke();
} else {
out->stroke(state);
}
}
}
doEndPath();
}
void Gfx::doPatternFill(bool eoFill)
{
GfxPattern *pattern;
// this is a bit of a kludge -- patterns can be really slow, so we
// skip them if we're only doing text extraction, since they almost
// certainly don't contain any text
if (!out->needNonText()) {
return;
}
if (!(pattern = state->getFillPattern())) {
return;
}
switch (pattern->getType()) {
case 1:
doTilingPatternFill((GfxTilingPattern *)pattern, false, eoFill, false);
break;
case 2:
doShadingPatternFill((GfxShadingPattern *)pattern, false, eoFill, false);
break;
default:
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType());
break;
}
}
void Gfx::doPatternStroke()
{
GfxPattern *pattern;
// this is a bit of a kludge -- patterns can be really slow, so we
// skip them if we're only doing text extraction, since they almost
// certainly don't contain any text
if (!out->needNonText()) {
return;
}
if (!(pattern = state->getStrokePattern())) {
return;
}
switch (pattern->getType()) {
case 1:
doTilingPatternFill((GfxTilingPattern *)pattern, true, false, false);
break;
case 2:
doShadingPatternFill((GfxShadingPattern *)pattern, true, false, false);
break;
default:
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in stroke", pattern->getType());
break;
}
}
void Gfx::doPatternText()
{
GfxPattern *pattern;
// this is a bit of a kludge -- patterns can be really slow, so we
// skip them if we're only doing text extraction, since they almost
// certainly don't contain any text
if (!out->needNonText()) {
return;
}
if (!(pattern = state->getFillPattern())) {
return;
}
switch (pattern->getType()) {
case 1:
doTilingPatternFill((GfxTilingPattern *)pattern, false, false, true);
break;
case 2:
doShadingPatternFill((GfxShadingPattern *)pattern, false, false, true);
break;
default:
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType());
break;
}
}
void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg)
{
saveState();
out->setSoftMaskFromImageMask(state, ref, str, width, height, invert, inlineImg, baseMatrix);
state->clearPath();
state->moveTo(0, 0);
state->lineTo(1, 0);
state->lineTo(1, 1);
state->lineTo(0, 1);
state->closePath();
doPatternText();
out->unsetSoftMaskFromImageMask(state, baseMatrix);
restoreState();
}
void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, bool stroke, bool eoFill, bool text)
{
GfxPatternColorSpace *patCS;
GfxColorSpace *cs;
GfxColor color;
GfxState *savedState;
double xMin, yMin, xMax, yMax, x, y, x1, y1;
double cxMin, cyMin, cxMax, cyMax;
int xi0, yi0, xi1, yi1, xi, yi;
const double *ctm, *btm, *ptm;
double m[6], ictm[6], m1[6], imb[6];
double det;
double xstep, ystep;
int i;
// get color space
patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace() : state->getFillColorSpace());
// construct a (pattern space) -> (current space) transform matrix
ctm = state->getCTM();
btm = baseMatrix;
ptm = tPat->getMatrix();
// iCTM = invert CTM
det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
if (fabs(det) < 0.000001) {
error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
return;
}
det = 1 / det;
ictm[0] = ctm[3] * det;
ictm[1] = -ctm[1] * det;
ictm[2] = -ctm[2] * det;
ictm[3] = ctm[0] * det;
ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
// m1 = PTM * BTM = PTM * base transform matrix
m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
// m = m1 * iCTM = (PTM * BTM) * (iCTM)
m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
// construct a (device space) -> (pattern space) transform matrix
det = m1[0] * m1[3] - m1[1] * m1[2];
det = 1 / det;
if (!std::isfinite(det)) {
error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
return;
}
imb[0] = m1[3] * det;
imb[1] = -m1[1] * det;
imb[2] = -m1[2] * det;
imb[3] = m1[0] * det;
imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
// save current graphics state
savedState = saveStateStack();
// set underlying color space (for uncolored tiling patterns); set
// various other parameters (stroke color, line width) to match
// Adobe's behavior
state->setFillPattern(nullptr);
state->setStrokePattern(nullptr);
if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
state->setFillColorSpace(cs->copy());
out->updateFillColorSpace(state);
state->setStrokeColorSpace(cs->copy());
out->updateStrokeColorSpace(state);
if (stroke) {
state->setFillColor(state->getStrokeColor());
} else {
state->setStrokeColor(state->getFillColor());
}
out->updateFillColor(state);
out->updateStrokeColor(state);
} else {
cs = new GfxDeviceGrayColorSpace();
state->setFillColorSpace(cs);
cs->getDefaultColor(&color);
state->setFillColor(&color);
out->updateFillColorSpace(state);
state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
state->setStrokeColor(&color);
out->updateStrokeColorSpace(state);
}
if (!stroke) {
state->setLineWidth(0);
out->updateLineWidth(state);
}
// clip to current path
if (stroke) {
state->clipToStrokePath();
out->clipToStrokePath(state);
} else if (!text) {
state->clip();
if (eoFill) {
out->eoClip(state);
} else {
out->clip(state);
}
}
state->clearPath();
// get the clip region, check for empty
state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
if (cxMin > cxMax || cyMin > cyMax) {
goto restore;
}
// transform clip region bbox to pattern space
xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
if (x1 < xMin) {
xMin = x1;
} else if (x1 > xMax) {
xMax = x1;
}
if (y1 < yMin) {
yMin = y1;
} else if (y1 > yMax) {
yMax = y1;
}
x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
if (x1 < xMin) {
xMin = x1;
} else if (x1 > xMax) {
xMax = x1;
}
if (y1 < yMin) {
yMin = y1;
} else if (y1 > yMax) {
yMax = y1;
}
x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
if (x1 < xMin) {
xMin = x1;
} else if (x1 > xMax) {
xMax = x1;
}
if (y1 < yMin) {
yMin = y1;
} else if (y1 > yMax) {
yMax = y1;
}
// draw the pattern
//~ this should treat negative steps differently -- start at right/top
//~ edge instead of left/bottom (?)
xstep = fabs(tPat->getXStep());
ystep = fabs(tPat->getYStep());
if (unlikely(xstep == 0 || ystep == 0)) {
goto restore;
}
if (tPat->getBBox()[0] < tPat->getBBox()[2]) {
xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep);
xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1;
} else {
xi0 = (int)ceil((xMin - tPat->getBBox()[0]) / xstep);
xi1 = (int)floor((xMax - tPat->getBBox()[2]) / xstep) + 1;
}
if (tPat->getBBox()[1] < tPat->getBBox()[3]) {
yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep);
yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1;
} else {
yi0 = (int)ceil((yMin - tPat->getBBox()[1]) / ystep);
yi1 = (int)floor((yMax - tPat->getBBox()[3]) / ystep) + 1;
}
for (i = 0; i < 4; ++i) {
m1[i] = m[i];
}
m1[4] = m[4];
m1[5] = m[5];
{
bool shouldDrawPattern = true;
std::set<int>::iterator patternRefIt;
const int patternRefNum = tPat->getPatternRefNum();
if (patternRefNum != -1) {
if (formsDrawing.find(patternRefNum) == formsDrawing.end()) {
patternRefIt = formsDrawing.insert(patternRefNum).first;
} else {
shouldDrawPattern = false;
}
}
if (shouldDrawPattern) {
if (out->useTilingPatternFill() && out->tilingPatternFill(state, this, catalog, tPat, m1, xi0, yi0, xi1, yi1, xstep, ystep)) {
// do nothing
} else {
out->updatePatternOpacity(state);
for (yi = yi0; yi < yi1; ++yi) {
for (xi = xi0; xi < xi1; ++xi) {
x = xi * xstep;
y = yi * ystep;
m1[4] = x * m[0] + y * m[2] + m[4];
m1[5] = x * m[1] + y * m[3] + m[5];
drawForm(tPat->getContentStream(), tPat->getResDict(), m1, tPat->getBBox());
}
}
out->clearPatternOpacity(state);
}
if (patternRefNum != -1) {
formsDrawing.erase(patternRefIt);
}
}
}
// restore graphics state
restore:
restoreStateStack(savedState);
}
void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, bool stroke, bool eoFill, bool text)
{
GfxShading *shading;
GfxState *savedState;
const double *ctm, *btm, *ptm;
double m[6], ictm[6], m1[6];
double xMin, yMin, xMax, yMax;
double det;
shading = sPat->getShading();
// save current graphics state
savedState = saveStateStack();
// clip to current path
if (stroke) {
state->clipToStrokePath();
out->clipToStrokePath(state);
} else if (!text) {
state->clip();
if (eoFill) {
out->eoClip(state);
} else {
out->clip(state);
}
}
state->clearPath();
// construct a (pattern space) -> (current space) transform matrix
ctm = state->getCTM();
btm = baseMatrix;
ptm = sPat->getMatrix();
// iCTM = invert CTM
det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
if (fabs(det) < 0.000001) {
error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill");
restoreStateStack(savedState);
return;
}
det = 1 / det;
ictm[0] = ctm[3] * det;
ictm[1] = -ctm[1] * det;
ictm[2] = -ctm[2] * det;
ictm[3] = ctm[0] * det;
ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
// m1 = PTM * BTM = PTM * base transform matrix
m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
// m = m1 * iCTM = (PTM * BTM) * (iCTM)
m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
// set the new matrix
state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
// clip to bbox
if (shading->getHasBBox()) {
shading->getBBox(&xMin, &yMin, &xMax, &yMax);
state->moveTo(xMin, yMin);
state->lineTo(xMax, yMin);
state->lineTo(xMax, yMax);
state->lineTo(xMin, yMax);
state->closePath();
state->clip();
out->clip(state);
state->clearPath();
}
// set the color space
state->setFillColorSpace(shading->getColorSpace()->copy());
out->updateFillColorSpace(state);
// background color fill
if (shading->getHasBackground()) {
state->setFillColor(shading->getBackground());
out->updateFillColor(state);
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
state->moveTo(xMin, yMin);
state->lineTo(xMax, yMin);
state->lineTo(xMax, yMax);
state->lineTo(xMin, yMax);
state->closePath();
out->fill(state);
state->clearPath();
}
#if 1 //~tmp: turn off anti-aliasing temporarily
bool vaa = out->getVectorAntialias();
if (vaa) {
out->setVectorAntialias(false);
}
#endif
// do shading type-specific operations
switch (shading->getType()) {
case 1:
doFunctionShFill((GfxFunctionShading *)shading);
break;
case 2:
doAxialShFill((GfxAxialShading *)shading);
break;
case 3:
doRadialShFill((GfxRadialShading *)shading);
break;
case 4:
case 5:
doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
break;
case 6:
case 7:
doPatchMeshShFill((GfxPatchMeshShading *)shading);
break;
}
#if 1 //~tmp: turn off anti-aliasing temporarily
if (vaa) {
out->setVectorAntialias(true);
}
#endif
// restore graphics state
restoreStateStack(savedState);
}
void Gfx::opShFill(Object args[], int numArgs)
{
GfxShading *shading;
GfxState *savedState;
double xMin, yMin, xMax, yMax;
if (!ocState) {
return;
}
if (!(shading = res->lookupShading(args[0].getName(), out, state))) {
return;
}
// save current graphics state
savedState = saveStateStack();
// clip to bbox
if (shading->getHasBBox()) {
shading->getBBox(&xMin, &yMin, &xMax, &yMax);
state->moveTo(xMin, yMin);
state->lineTo(xMax, yMin);
state->lineTo(xMax, yMax);
state->lineTo(xMin, yMax);
state->closePath();
state->clip();
out->clip(state);
state->clearPath();
}
// set the color space
state->setFillColorSpace(shading->getColorSpace()->copy());
out->updateFillColorSpace(state);
#if 1 //~tmp: turn off anti-aliasing temporarily
bool vaa = out->getVectorAntialias();
if (vaa) {
out->setVectorAntialias(false);
}
#endif
// do shading type-specific operations
switch (shading->getType()) {
case 1:
doFunctionShFill((GfxFunctionShading *)shading);
break;
case 2:
doAxialShFill((GfxAxialShading *)shading);
break;
case 3:
doRadialShFill((GfxRadialShading *)shading);
break;
case 4:
case 5:
doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
break;
case 6:
case 7:
doPatchMeshShFill((GfxPatchMeshShading *)shading);
break;
}
#if 1 //~tmp: turn off anti-aliasing temporarily
if (vaa) {
out->setVectorAntialias(true);
}
#endif
// restore graphics state
restoreStateStack(savedState);
delete shading;
}
void Gfx::doFunctionShFill(GfxFunctionShading *shading)
{
double x0, y0, x1, y1;
GfxColor colors[4];
if (out->useShadedFills(shading->getType()) && out->functionShadedFill(state, shading)) {
return;
}
shading->getDomain(&x0, &y0, &x1, &y1);
shading->getColor(x0, y0, &colors[0]);
shading->getColor(x0, y1, &colors[1]);
shading->getColor(x1, y0, &colors[2]);
shading->getColor(x1, y1, &colors[3]);
doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
}
void Gfx::doFunctionShFill1(GfxFunctionShading *shading, double x0, double y0, double x1, double y1, GfxColor *colors, int depth)
{
GfxColor fillColor;
GfxColor color0M, color1M, colorM0, colorM1, colorMM;
GfxColor colors2[4];
double xM, yM;
int nComps, i, j;
nComps = shading->getColorSpace()->getNComps();
const double *matrix = shading->getMatrix();
// compare the four corner colors
for (i = 0; i < 4; ++i) {
for (j = 0; j < nComps; ++j) {
if (abs(colors[i].c[j] - colors[(i + 1) & 3].c[j]) > functionColorDelta) {
break;
}
}
if (j < nComps) {
break;
}
}
// center of the rectangle
xM = 0.5 * (x0 + x1);
yM = 0.5 * (y0 + y1);
// the four corner colors are close (or we hit the recursive limit)
// -- fill the rectangle; but require at least one subdivision
// (depth==0) to avoid problems when the four outer corners of the
// shaded region are the same color
if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
// use the center color
shading->getColor(xM, yM, &fillColor);
state->setFillColor(&fillColor);
out->updateFillColor(state);
// fill the rectangle
state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4], x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4], x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4], x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4], x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
state->closePath();
out->fill(state);
state->clearPath();
// the four corner colors are not close enough -- subdivide the
// rectangle
} else {
// colors[0] colorM0 colors[2]
// (x0,y0) (xM,y0) (x1,y0)
// +----------+----------+
// | | |
// | UL | UR |
// color0M | colorMM | color1M
// (x0,yM) +----------+----------+ (x1,yM)
// | (xM,yM) |
// | LL | LR |
// | | |
// +----------+----------+
// colors[1] colorM1 colors[3]
// (x0,y1) (xM,y1) (x1,y1)
shading->getColor(x0, yM, &color0M);
shading->getColor(x1, yM, &color1M);
shading->getColor(xM, y0, &colorM0);
shading->getColor(xM, y1, &colorM1);
shading->getColor(xM, yM, &colorMM);
// upper-left sub-rectangle
colors2[0] = colors[0];
colors2[1] = color0M;
colors2[2] = colorM0;
colors2[3] = colorMM;
doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
// lower-left sub-rectangle
colors2[0] = color0M;
colors2[1] = colors[1];
colors2[2] = colorMM;
colors2[3] = colorM1;
doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
// upper-right sub-rectangle
colors2[0] = colorM0;
colors2[1] = colorMM;
colors2[2] = colors[2];
colors2[3] = color1M;
doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
// lower-right sub-rectangle
colors2[0] = colorMM;
colors2[1] = colorM1;
colors2[2] = color1M;
colors2[3] = colors[3];
doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
}
}
void Gfx::doAxialShFill(GfxAxialShading *shading)
{
double xMin, yMin, xMax, yMax;
double x0, y0, x1, y1;
double dx, dy, mul;
bool dxZero, dyZero;
double bboxIntersections[4];
double tMin, tMax, tx, ty;
double s[4], sMin, sMax, tmp;
double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
double t0, t1, tt;
double ta[axialMaxSplits + 1];
int next[axialMaxSplits + 1];
GfxColor color0 = {}, color1 = {};
int nComps;
int i, j, k;
bool needExtend = true;
// get the clip region bbox
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
// compute min and max t values, based on the four corners of the
// clip region bbox
shading->getCoords(&x0, &y0, &x1, &y1);
dx = x1 - x0;
dy = y1 - y0;
dxZero = fabs(dx) < 0.01;
dyZero = fabs(dy) < 0.01;
if (dxZero && dyZero) {
tMin = tMax = 0;
} else {
mul = 1 / (dx * dx + dy * dy);
bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
std::sort(std::begin(bboxIntersections), std::end(bboxIntersections));
tMin = bboxIntersections[0];
tMax = bboxIntersections[3];
if (tMin < 0 && !shading->getExtend0()) {
tMin = 0;
}
if (tMax > 1 && !shading->getExtend1()) {
tMax = 1;
}
}
if (out->useShadedFills(shading->getType()) && out->axialShadedFill(state, shading, tMin, tMax)) {
return;
}
// get the function domain
t0 = shading->getDomain0();
t1 = shading->getDomain1();
// Traverse the t axis and do the shading.
//
// For each point (tx, ty) on the t axis, consider a line through
// that point perpendicular to the t axis:
//
// x(s) = tx + s * -dy --> s = (x - tx) / -dy
// y(s) = ty + s * dx --> s = (y - ty) / dx
//
// Then look at the intersection of this line with the bounding box
// (xMin, yMin, xMax, yMax). In the general case, there are four
// intersection points:
//
// s0 = (xMin - tx) / -dy
// s1 = (xMax - tx) / -dy
// s2 = (yMin - ty) / dx
// s3 = (yMax - ty) / dx
//
// and we want the middle two s values.
//
// In the case where dx = 0, take s0 and s1; in the case where dy =
// 0, take s2 and s3.
//
// Each filled polygon is bounded by two of these line segments
// perpdendicular to the t axis.
//
// The t axis is bisected into smaller regions until the color
// difference across a region is small enough, and then the region
// is painted with a single color.
// set up: require at least one split to avoid problems when the two
// ends of the t axis have the same color
nComps = shading->getColorSpace()->getNComps();
ta[0] = tMin;
next[0] = axialMaxSplits / 2;
ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
next[axialMaxSplits / 2] = axialMaxSplits;
ta[axialMaxSplits] = tMax;
// compute the color at t = tMin
if (tMin < 0) {
tt = t0;
} else if (tMin > 1) {
tt = t1;
} else {
tt = t0 + (t1 - t0) * tMin;
}
shading->getColor(tt, &color0);
if (out->useFillColorStop()) {
// make sure we add stop color when t = tMin
state->setFillColor(&color0);
out->updateFillColorStop(state, 0);
}
// compute the coordinates of the point on the t axis at t = tMin;
// then compute the intersection of the perpendicular line with the
// bounding box
tx = x0 + tMin * dx;
ty = y0 + tMin * dy;
if (dxZero && dyZero) {
sMin = sMax = 0;
} else if (dxZero) {
sMin = (xMin - tx) / -dy;
sMax = (xMax - tx) / -dy;
if (sMin > sMax) {
tmp = sMin;
sMin = sMax;
sMax = tmp;
}
} else if (dyZero) {
sMin = (yMin - ty) / dx;
sMax = (yMax - ty) / dx;
if (sMin > sMax) {
tmp = sMin;
sMin = sMax;
sMax = tmp;
}
} else {
s[0] = (yMin - ty) / dx;
s[1] = (yMax - ty) / dx;
s[2] = (xMin - tx) / -dy;
s[3] = (xMax - tx) / -dy;
std::sort(std::begin(s), std::end(s));
sMin = s[1];
sMax = s[2];
}
ux0 = tx - sMin * dy;
uy0 = ty + sMin * dx;
vx0 = tx - sMax * dy;
vy0 = ty + sMax * dx;
i = 0;
bool doneBBox1, doneBBox2;
if (dxZero && dyZero) {
doneBBox1 = doneBBox2 = true;
} else {
doneBBox1 = bboxIntersections[1] < tMin;
doneBBox2 = bboxIntersections[2] > tMax;
}
// If output device doesn't support the extended mode required
// we have to do it here
needExtend = !out->axialShadedSupportExtend(state, shading);
while (i < axialMaxSplits) {
// bisect until color difference is small enough or we hit the
// bisection limit
const double previousStop = tt;
j = next[i];
while (j > i + 1) {
if (ta[j] < 0) {
tt = t0;
} else if (ta[j] > 1) {
tt = t1;
} else {
tt = t0 + (t1 - t0) * ta[j];
}
// Try to determine whether the color map is constant between ta[i] and ta[j].
// In the strict sense this question cannot be answered by sampling alone.
// We try an educated guess in form of 2 samples.
// See https://gitlab.freedesktop.org/poppler/poppler/issues/938 for a file where one sample was not enough.
// The first test sample at 1.0 (i.e., ta[j]) is coded separately, because we may
// want to reuse the color later
shading->getColor(tt, &color1);
bool isPatchOfConstantColor = isSameGfxColor(color1, color0, nComps, axialColorDelta);
if (isPatchOfConstantColor) {
// Add more sample locations here if required
for (double l : { 0.5 }) {
GfxColor tmpColor;
double x = previousStop + l * (tt - previousStop);
shading->getColor(x, &tmpColor);
if (!isSameGfxColor(tmpColor, color0, nComps, axialColorDelta)) {
isPatchOfConstantColor = false;
break;
}
}
}
if (isPatchOfConstantColor) {
// in these two if what we guarantee is that if we are skipping lots of
// positions because the colors are the same, we still create a region
// with vertexs passing by bboxIntersections[1] and bboxIntersections[2]
// otherwise we can have empty regions that should really be painted
// like happened in bug 19896
// What we do to ensure that we pass a line through this points
// is making sure use the exact bboxIntersections[] value as one of the used ta[] values
if (!doneBBox1 && ta[i] < bboxIntersections[1] && ta[j] > bboxIntersections[1]) {
int teoricalj = (int)((bboxIntersections[1] - tMin) * axialMaxSplits / (tMax - tMin));
if (teoricalj <= i) {
teoricalj = i + 1;
}
if (teoricalj < j) {
next[i] = teoricalj;
next[teoricalj] = j;
} else {
teoricalj = j;
}
ta[teoricalj] = bboxIntersections[1];
j = teoricalj;
doneBBox1 = true;
}
if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) {
int teoricalj = (int)((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin));
if (teoricalj <= i) {
teoricalj = i + 1;
}
if (teoricalj < j) {
next[i] = teoricalj;
next[teoricalj] = j;
} else {
teoricalj = j;
}
ta[teoricalj] = bboxIntersections[2];
j = teoricalj;
doneBBox2 = true;
}
break;
}
k = (i + j) / 2;
ta[k] = 0.5 * (ta[i] + ta[j]);
next[i] = k;
next[k] = j;
j = k;
}
// use the average of the colors of the two sides of the region
for (k = 0; k < nComps; ++k) {
color0.c[k] = safeAverage(color0.c[k], color1.c[k]);
}
// compute the coordinates of the point on the t axis; then
// compute the intersection of the perpendicular line with the
// bounding box
tx = x0 + ta[j] * dx;
ty = y0 + ta[j] * dy;
if (dxZero && dyZero) {
sMin = sMax = 0;
} else if (dxZero) {
sMin = (xMin - tx) / -dy;
sMax = (xMax - tx) / -dy;
if (sMin > sMax) {
tmp = sMin;
sMin = sMax;
sMax = tmp;
}
} else if (dyZero) {
sMin = (yMin - ty) / dx;
sMax = (yMax - ty) / dx;
if (sMin > sMax) {
tmp = sMin;
sMin = sMax;
sMax = tmp;
}
} else {
s[0] = (yMin - ty) / dx;
s[1] = (yMax - ty) / dx;
s[2] = (xMin - tx) / -dy;
s[3] = (xMax - tx) / -dy;
std::sort(std::begin(s), std::end(s));
sMin = s[1];
sMax = s[2];
}
ux1 = tx - sMin * dy;
uy1 = ty + sMin * dx;
vx1 = tx - sMax * dy;
vy1 = ty + sMax * dx;
// set the color
state->setFillColor(&color0);
if (out->useFillColorStop()) {
out->updateFillColorStop(state, (ta[j] - tMin) / (tMax - tMin));
} else {
out->updateFillColor(state);
}
if (needExtend) {
// fill the region
state->moveTo(ux0, uy0);
state->lineTo(vx0, vy0);
state->lineTo(vx1, vy1);
state->lineTo(ux1, uy1);
state->closePath();
}
if (!out->useFillColorStop()) {
out->fill(state);
state->clearPath();
}
// set up for next region
ux0 = ux1;
uy0 = uy1;
vx0 = vx1;
vy0 = vy1;
color0 = color1;
i = next[i];
}
if (out->useFillColorStop()) {
if (!needExtend) {
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
}
out->fill(state);
state->clearPath();
}
}
static inline void getShadingColorRadialHelper(double t0, double t1, double t, GfxRadialShading *shading, GfxColor *color)
{
if (t0 < t1) {
if (t < t0) {
shading->getColor(t0, color);
} else if (t > t1) {
shading->getColor(t1, color);
} else {
shading->getColor(t, color);
}
} else {
if (t > t0) {
shading->getColor(t0, color);
} else if (t < t1) {
shading->getColor(t1, color);
} else {
shading->getColor(t, color);
}
}
}
void Gfx::doRadialShFill(GfxRadialShading *shading)
{
double xMin, yMin, xMax, yMax;
double x0, y0, r0, x1, y1, r1, t0, t1;
int nComps;
GfxColor colorA = {}, colorB = {}, colorC = {};
double xa, ya, xb, yb, ra, rb;
double ta, tb, sa, sb;
double sz, xz, yz, sMin, sMax;
bool enclosed;
int ia, ib, k, n;
double theta, alpha, angle, t;
bool needExtend = true;
// get the shading info
shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
t0 = shading->getDomain0();
t1 = shading->getDomain1();
nComps = shading->getColorSpace()->getNComps();
// Compute the point at which r(s) = 0; check for the enclosed
// circles case; and compute the angles for the tangent lines.
if (x0 == x1 && y0 == y1) {
enclosed = true;
theta = 0; // make gcc happy
sz = 0; // make gcc happy
} else if (r0 == r1) {
enclosed = false;
theta = 0;
sz = 0; // make gcc happy
} else {
sz = (r1 > r0) ? -r0 / (r1 - r0) : -r1 / (r0 - r1);
xz = x0 + sz * (x1 - x0);
yz = y0 + sz * (y1 - y0);
enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
const double theta_aux = sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz));
if (likely(theta_aux != 0)) {
theta = asin(r0 / theta_aux);
} else {
theta = 0;
}
if (r0 > r1) {
theta = -theta;
}
}
if (enclosed) {
alpha = 0;
} else {
alpha = atan2(y1 - y0, x1 - x0);
}
// compute the (possibly extended) s range
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
if (enclosed) {
sMin = 0;
sMax = 1;
} else {
sMin = 1;
sMax = 0;
// solve for x(s) + r(s) = xMin
if ((x1 + r1) - (x0 + r0) != 0) {
sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
if (sa < sMin) {
sMin = sa;
} else if (sa > sMax) {
sMax = sa;
}
}
// solve for x(s) - r(s) = xMax
if ((x1 - r1) - (x0 - r0) != 0) {
sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
if (sa < sMin) {
sMin = sa;
} else if (sa > sMax) {
sMax = sa;
}
}
// solve for y(s) + r(s) = yMin
if ((y1 + r1) - (y0 + r0) != 0) {
sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
if (sa < sMin) {
sMin = sa;
} else if (sa > sMax) {
sMax = sa;
}
}
// solve for y(s) - r(s) = yMax
if ((y1 - r1) - (y0 - r0) != 0) {
sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
if (sa < sMin) {
sMin = sa;
} else if (sa > sMax) {
sMax = sa;
}
}
// check against sz
if (r0 < r1) {
if (sMin < sz) {
sMin = sz;
}
} else if (r0 > r1) {
if (sMax > sz) {
sMax = sz;
}
}
// check the 'extend' flags
if (!shading->getExtend0() && sMin < 0) {
sMin = 0;
}
if (!shading->getExtend1() && sMax > 1) {
sMax = 1;
}
}
if (out->useShadedFills(shading->getType()) && out->radialShadedFill(state, shading, sMin, sMax)) {
return;
}
// compute the number of steps into which circles must be divided to
// achieve a curve flatness of 0.1 pixel in device space for the
// largest circle (note that "device space" is 72 dpi when generating
// PostScript, hence the relatively small 0.1 pixel accuracy)
const double *ctm = state->getCTM();
t = fabs(ctm[0]);
if (fabs(ctm[1]) > t) {
t = fabs(ctm[1]);
}
if (fabs(ctm[2]) > t) {
t = fabs(ctm[2]);
}
if (fabs(ctm[3]) > t) {
t = fabs(ctm[3]);
}
if (r0 > r1) {
t *= r0;
} else {
t *= r1;
}
if (t < 1) {
n = 3;
} else {
const double tmp = 1 - 0.1 / t;
if (unlikely(tmp == 1)) {
n = 200;
} else {
n = (int)(M_PI / acos(tmp));
}
if (n < 3) {
n = 3;
} else if (n > 200) {
n = 200;
}
}
// setup for the start circle
ia = 0;
sa = sMin;
ta = t0 + sa * (t1 - t0);
xa = x0 + sa * (x1 - x0);
ya = y0 + sa * (y1 - y0);
ra = r0 + sa * (r1 - r0);
getShadingColorRadialHelper(t0, t1, ta, shading, &colorA);
needExtend = !out->radialShadedSupportExtend(state, shading);
// fill the circles
while (ia < radialMaxSplits) {
// go as far along the t axis (toward t1) as we can, such that the
// color difference is within the tolerance (radialColorDelta) --
// this uses bisection (between the current value, t, and t1),
// limited to radialMaxSplits points along the t axis; require at
// least one split to avoid problems when the innermost and
// outermost colors are the same
ib = radialMaxSplits;
sb = sMax;
tb = t0 + sb * (t1 - t0);
getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
while (ib - ia > 1) {
if (isSameGfxColor(colorB, colorA, nComps, radialColorDelta)) {
// The shading is not necessarily lineal so having two points with the
// same color does not mean all the areas in between have the same color too
int ic = ia + 1;
for (; ic <= ib; ic++) {
const double sc = sMin + ((double)ic / (double)radialMaxSplits) * (sMax - sMin);
const double tc = t0 + sc * (t1 - t0);
getShadingColorRadialHelper(t0, t1, tc, shading, &colorC);
if (!isSameGfxColor(colorC, colorA, nComps, radialColorDelta)) {
break;
}
}
ib = (ic > ia + 1) ? ic - 1 : ia + 1;
sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
tb = t0 + sb * (t1 - t0);
getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
break;
}
ib = (ia + ib) / 2;
sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
tb = t0 + sb * (t1 - t0);
getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
}
// compute center and radius of the circle
xb = x0 + sb * (x1 - x0);
yb = y0 + sb * (y1 - y0);
rb = r0 + sb * (r1 - r0);
// use the average of the colors at the two circles
for (k = 0; k < nComps; ++k) {
colorA.c[k] = safeAverage(colorA.c[k], colorB.c[k]);
}
state->setFillColor(&colorA);
if (out->useFillColorStop()) {
out->updateFillColorStop(state, (sa - sMin) / (sMax - sMin));
} else {
out->updateFillColor(state);
}
if (needExtend) {
if (enclosed) {
// construct path for first circle (counterclockwise)
state->moveTo(xa + ra, ya);
for (k = 1; k < n; ++k) {
angle = ((double)k / (double)n) * 2 * M_PI;
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
}
state->closePath();
// construct and append path for second circle (clockwise)
state->moveTo(xb + rb, yb);
for (k = 1; k < n; ++k) {
angle = -((double)k / (double)n) * 2 * M_PI;
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
}
state->closePath();
} else {
// construct the first subpath (clockwise)
state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), ya + ra * sin(alpha + theta + 0.5 * M_PI));
for (k = 0; k < n; ++k) {
angle = alpha + theta + 0.5 * M_PI - ((double)k / (double)n) * (2 * theta + M_PI);
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
}
for (k = 0; k < n; ++k) {
angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta - M_PI);
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
}
state->closePath();
// construct the second subpath (counterclockwise)
state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), ya + ra * sin(alpha + theta + 0.5 * M_PI));
for (k = 0; k < n; ++k) {
angle = alpha + theta + 0.5 * M_PI + ((double)k / (double)n) * (-2 * theta + M_PI);
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
}
for (k = 0; k < n; ++k) {
angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta + M_PI);
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
}
state->closePath();
}
}
if (!out->useFillColorStop()) {
// fill the path
out->fill(state);
state->clearPath();
}
// step to the next value of t
ia = ib;
sa = sb;
ta = tb;
xa = xb;
ya = yb;
ra = rb;
colorA = colorB;
}
if (out->useFillColorStop()) {
// make sure we add stop color when sb = sMax
state->setFillColor(&colorA);
out->updateFillColorStop(state, (sb - sMin) / (sMax - sMin));
// fill the path
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
out->fill(state);
state->clearPath();
}
if (!needExtend) {
return;
}
if (enclosed) {
// extend the smaller circle
if ((shading->getExtend0() && r0 <= r1) || (shading->getExtend1() && r1 < r0)) {
if (r0 <= r1) {
ta = t0;
ra = r0;
xa = x0;
ya = y0;
} else {
ta = t1;
ra = r1;
xa = x1;
ya = y1;
}
shading->getColor(ta, &colorA);
state->setFillColor(&colorA);
out->updateFillColor(state);
state->moveTo(xa + ra, ya);
for (k = 1; k < n; ++k) {
angle = ((double)k / (double)n) * 2 * M_PI;
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
}
state->closePath();
out->fill(state);
state->clearPath();
}
// extend the larger circle
if ((shading->getExtend0() && r0 > r1) || (shading->getExtend1() && r1 >= r0)) {
if (r0 > r1) {
ta = t0;
ra = r0;
xa = x0;
ya = y0;
} else {
ta = t1;
ra = r1;
xa = x1;
ya = y1;
}
shading->getColor(ta, &colorA);
state->setFillColor(&colorA);
out->updateFillColor(state);
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
state->moveTo(xa + ra, ya);
for (k = 1; k < n; ++k) {
angle = ((double)k / (double)n) * 2 * M_PI;
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
}
state->closePath();
out->fill(state);
state->clearPath();
}
}
}
void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading)
{
double x0, y0, x1, y1, x2, y2;
int i;
if (out->useShadedFills(shading->getType())) {
if (out->gouraudTriangleShadedFill(state, shading)) {
return;
}
}
// preallocate a path (speed improvements)
state->moveTo(0., 0.);
state->lineTo(1., 0.);
state->lineTo(0., 1.);
state->closePath();
GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();
if (shading->isParameterized()) {
// work with parameterized values:
double color0, color1, color2;
// a relative threshold:
const double refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
for (i = 0; i < shading->getNTriangles(); ++i) {
shading->getTriangle(i, &x0, &y0, &color0, &x1, &y1, &color1, &x2, &y2, &color2);
gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, 0, shading, reusablePath);
}
} else {
// this always produces output -- even for parameterized ranges.
// But it ignores the parameterized color map (the function).
//
// Note that using this code in for parameterized shadings might be
// correct in circumstances (namely if the function is linear in the actual
// triangle), but in general, it will simply be wrong.
GfxColor color0, color1, color2;
for (i = 0; i < shading->getNTriangles(); ++i) {
shading->getTriangle(i, &x0, &y0, &color0, &x1, &y1, &color1, &x2, &y2, &color2);
gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, shading->getColorSpace()->getNComps(), 0, reusablePath);
}
}
delete reusablePath;
}
static inline void checkTrue(bool b, const char *message)
{
if (unlikely(!b)) {
error(errSyntaxError, -1, message);
}
}
void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0, double x1, double y1, GfxColor *color1, double x2, double y2, GfxColor *color2, int nComps, int depth, GfxState::ReusablePathIterator *path)
{
double x01, y01, x12, y12, x20, y20;
GfxColor color01, color12, color20;
int i;
for (i = 0; i < nComps; ++i) {
if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
break;
}
}
if (i == nComps || depth == gouraudMaxDepth) {
state->setFillColor(color0);
out->updateFillColor(state);
path->reset();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x0, y0);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x1, y1);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x2, y2);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x0, y0);
path->next();
checkTrue(path->isEnd(), "Path should be at end");
out->fill(state);
} else {
x01 = 0.5 * (x0 + x1);
y01 = 0.5 * (y0 + y1);
x12 = 0.5 * (x1 + x2);
y12 = 0.5 * (y1 + y2);
x20 = 0.5 * (x2 + x0);
y20 = 0.5 * (y2 + y0);
for (i = 0; i < nComps; ++i) {
color01.c[i] = safeAverage(color0->c[i], color1->c[i]);
color12.c[i] = safeAverage(color1->c[i], color2->c[i]);
color20.c[i] = safeAverage(color2->c[i], color0->c[i]);
}
gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, x20, y20, &color20, nComps, depth + 1, path);
gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, x12, y12, &color12, nComps, depth + 1, path);
gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, x20, y20, &color20, nComps, depth + 1, path);
gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, x2, y2, color2, nComps, depth + 1, path);
}
}
void Gfx::gouraudFillTriangle(double x0, double y0, double color0, double x1, double y1, double color1, double x2, double y2, double color2, double refineColorThreshold, int depth, GfxGouraudTriangleShading *shading,
GfxState::ReusablePathIterator *path)
{
const double meanColor = (color0 + color1 + color2) / 3;
const bool isFineEnough = fabs(color0 - meanColor) < refineColorThreshold && fabs(color1 - meanColor) < refineColorThreshold && fabs(color2 - meanColor) < refineColorThreshold;
if (isFineEnough || depth == gouraudMaxDepth) {
GfxColor color;
shading->getParameterizedColor(meanColor, &color);
state->setFillColor(&color);
out->updateFillColor(state);
path->reset();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x0, y0);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x1, y1);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x2, y2);
path->next();
checkTrue(!path->isEnd(), "Path should not be at end");
path->setCoord(x0, y0);
path->next();
checkTrue(path->isEnd(), "Path should be at end");
out->fill(state);
} else {
const double x01 = 0.5 * (x0 + x1);
const double y01 = 0.5 * (y0 + y1);
const double x12 = 0.5 * (x1 + x2);
const double y12 = 0.5 * (y1 + y2);
const double x20 = 0.5 * (x2 + x0);
const double y20 = 0.5 * (y2 + y0);
const double color01 = (color0 + color1) / 2.;
const double color12 = (color1 + color2) / 2.;
const double color20 = (color2 + color0) / 2.;
++depth;
gouraudFillTriangle(x0, y0, color0, x01, y01, color01, x20, y20, color20, refineColorThreshold, depth, shading, path);
gouraudFillTriangle(x01, y01, color01, x1, y1, color1, x12, y12, color12, refineColorThreshold, depth, shading, path);
gouraudFillTriangle(x01, y01, color01, x12, y12, color12, x20, y20, color20, refineColorThreshold, depth, shading, path);
gouraudFillTriangle(x20, y20, color20, x12, y12, color12, x2, y2, color2, refineColorThreshold, depth, shading, path);
}
}
void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading)
{
int start, i;
if (out->useShadedFills(shading->getType())) {
if (out->patchMeshShadedFill(state, shading)) {
return;
}
}
if (shading->getNPatches() > 128) {
start = 3;
} else if (shading->getNPatches() > 64) {
start = 2;
} else if (shading->getNPatches() > 16) {
start = 1;
} else {
start = 0;
}
/*
* Parameterized shadings take one parameter [t_0,t_e]
* and map it into the color space.
*
* Consequently, all color values are stored as doubles.
*
* These color values are interpreted as parameters for parameterized
* shadings and as colorspace entities otherwise.
*
* The only difference is that color space entities are stored into
* DOUBLE arrays, not into arrays of type GfxColorComp.
*/
const int colorComps = shading->getColorSpace()->getNComps();
double refineColorThreshold;
if (shading->isParameterized()) {
refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
} else {
refineColorThreshold = patchColorDelta;
}
for (i = 0; i < shading->getNPatches(); ++i) {
fillPatch(shading->getPatch(i), colorComps, shading->isParameterized() ? 1 : colorComps, refineColorThreshold, start, shading);
}
}
void Gfx::fillPatch(const GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, const GfxPatchMeshShading *shading)
{
GfxPatch patch00, patch01, patch10, patch11;
double xx[4][8], yy[4][8];
double xxm, yym;
int i;
for (i = 0; i < patchColorComps; ++i) {
// these comparisons are done in double arithmetics.
//
// For non-parameterized shadings, they are done in color space
// components.
if (fabs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) > refineColorThreshold || fabs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) > refineColorThreshold
|| fabs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) > refineColorThreshold || fabs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) > refineColorThreshold) {
break;
}
}
if (i == patchColorComps || depth == patchMaxDepth) {
GfxColor flatColor;
if (shading->isParameterized()) {
shading->getParameterizedColor(patch->color[0][0].c[0], &flatColor);
} else {
for (i = 0; i < colorComps; ++i) {
// simply cast to the desired type; that's all what is needed.
flatColor.c[i] = GfxColorComp(patch->color[0][0].c[i]);
}
}
state->setFillColor(&flatColor);
out->updateFillColor(state);
state->moveTo(patch->x[0][0], patch->y[0][0]);
state->curveTo(patch->x[0][1], patch->y[0][1], patch->x[0][2], patch->y[0][2], patch->x[0][3], patch->y[0][3]);
state->curveTo(patch->x[1][3], patch->y[1][3], patch->x[2][3], patch->y[2][3], patch->x[3][3], patch->y[3][3]);
state->curveTo(patch->x[3][2], patch->y[3][2], patch->x[3][1], patch->y[3][1], patch->x[3][0], patch->y[3][0]);
state->curveTo(patch->x[2][0], patch->y[2][0], patch->x[1][0], patch->y[1][0], patch->x[0][0], patch->y[0][0]);
state->closePath();
out->fill(state);
state->clearPath();
} else {
for (i = 0; i < 4; ++i) {
xx[i][0] = patch->x[i][0];
yy[i][0] = patch->y[i][0];
xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
xx[i][2] = 0.5 * (xx[i][1] + xxm);
yy[i][2] = 0.5 * (yy[i][1] + yym);
xx[i][5] = 0.5 * (xxm + xx[i][6]);
yy[i][5] = 0.5 * (yym + yy[i][6]);
xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
xx[i][7] = patch->x[i][3];
yy[i][7] = patch->y[i][3];
}
for (i = 0; i < 4; ++i) {
patch00.x[0][i] = xx[0][i];
patch00.y[0][i] = yy[0][i];
patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
xxm = 0.5 * (xx[1][i] + xx[2][i]);
yym = 0.5 * (yy[1][i] + yy[2][i]);
patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
patch10.x[0][i] = patch00.x[3][i];
patch10.y[0][i] = patch00.y[3][i];
patch10.x[3][i] = xx[3][i];
patch10.y[3][i] = yy[3][i];
}
for (i = 4; i < 8; ++i) {
patch01.x[0][i - 4] = xx[0][i];
patch01.y[0][i - 4] = yy[0][i];
patch01.x[1][i - 4] = 0.5 * (xx[0][i] + xx[1][i]);
patch01.y[1][i - 4] = 0.5 * (yy[0][i] + yy[1][i]);
xxm = 0.5 * (xx[1][i] + xx[2][i]);
yym = 0.5 * (yy[1][i] + yy[2][i]);
patch11.x[2][i - 4] = 0.5 * (xx[2][i] + xx[3][i]);
patch11.y[2][i - 4] = 0.5 * (yy[2][i] + yy[3][i]);
patch01.x[2][i - 4] = 0.5 * (patch01.x[1][i - 4] + xxm);
patch01.y[2][i - 4] = 0.5 * (patch01.y[1][i - 4] + yym);
patch11.x[1][i - 4] = 0.5 * (xxm + patch11.x[2][i - 4]);
patch11.y[1][i - 4] = 0.5 * (yym + patch11.y[2][i - 4]);
patch01.x[3][i - 4] = 0.5 * (patch01.x[2][i - 4] + patch11.x[1][i - 4]);
patch01.y[3][i - 4] = 0.5 * (patch01.y[2][i - 4] + patch11.y[1][i - 4]);
patch11.x[0][i - 4] = patch01.x[3][i - 4];
patch11.y[0][i - 4] = patch01.y[3][i - 4];
patch11.x[3][i - 4] = xx[3][i];
patch11.y[3][i - 4] = yy[3][i];
}
for (i = 0; i < patchColorComps; ++i) {
patch00.color[0][0].c[i] = patch->color[0][0].c[i];
patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + patch->color[0][1].c[i]) / 2.;
patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
patch01.color[0][1].c[i] = patch->color[0][1].c[i];
patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + patch->color[1][1].c[i]) / 2.;
patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
patch11.color[1][1].c[i] = patch->color[1][1].c[i];
patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + patch->color[1][0].c[i]) / 2.;
patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
patch10.color[1][0].c[i] = patch->color[1][0].c[i];
patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + patch->color[0][0].c[i]) / 2.;
patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + patch01.color[1][1].c[i]) / 2.;
patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
}
fillPatch(&patch00, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
fillPatch(&patch10, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
fillPatch(&patch01, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
fillPatch(&patch11, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
}
}
void Gfx::doEndPath()
{
if (state->isCurPt() && clip != clipNone) {
state->clip();
if (clip == clipNormal) {
out->clip(state);
} else {
out->eoClip(state);
}
}
clip = clipNone;
state->clearPath();
}
//------------------------------------------------------------------------
// path clipping operators
//------------------------------------------------------------------------
void Gfx::opClip(Object args[], int numArgs)
{
clip = clipNormal;
}
void Gfx::opEOClip(Object args[], int numArgs)
{
clip = clipEO;
}
//------------------------------------------------------------------------
// text object operators
//------------------------------------------------------------------------
void Gfx::opBeginText(Object args[], int numArgs)
{
out->beginTextObject(state);
state->setTextMat(1, 0, 0, 1, 0, 0);
state->textMoveTo(0, 0);
out->updateTextMat(state);
out->updateTextPos(state);
fontChanged = true;
}
void Gfx::opEndText(Object args[], int numArgs)
{
out->endTextObject(state);
}
//------------------------------------------------------------------------
// text state operators
//------------------------------------------------------------------------
void Gfx::opSetCharSpacing(Object args[], int numArgs)
{
state->setCharSpace(args[0].getNum());
out->updateCharSpace(state);
}
void Gfx::opSetFont(Object args[], int numArgs)
{
std::shared_ptr<GfxFont> font;
if (!(font = res->lookupFont(args[0].getName()))) {
// unsetting the font (drawing no text) is better than using the
// previous one and drawing random glyphs from it
state->setFont(nullptr, args[1].getNum());
fontChanged = true;
return;
}
if (printCommands) {
printf(" font: tag=%s name='%s' %g\n", font->getTag().c_str(), font->getName() ? font->getName()->c_str() : "???", args[1].getNum());
fflush(stdout);
}
state->setFont(font, args[1].getNum());
fontChanged = true;
}
void Gfx::opSetTextLeading(Object args[], int numArgs)
{
state->setLeading(args[0].getNum());
}
void Gfx::opSetTextRender(Object args[], int numArgs)
{
state->setRender(args[0].getInt());
out->updateRender(state);
}
void Gfx::opSetTextRise(Object args[], int numArgs)
{
state->setRise(args[0].getNum());
out->updateRise(state);
}
void Gfx::opSetWordSpacing(Object args[], int numArgs)
{
state->setWordSpace(args[0].getNum());
out->updateWordSpace(state);
}
void Gfx::opSetHorizScaling(Object args[], int numArgs)
{
state->setHorizScaling(args[0].getNum());
out->updateHorizScaling(state);
fontChanged = true;
}
//------------------------------------------------------------------------
// text positioning operators
//------------------------------------------------------------------------
void Gfx::opTextMove(Object args[], int numArgs)
{
double tx, ty;
tx = state->getLineX() + args[0].getNum();
ty = state->getLineY() + args[1].getNum();
state->textMoveTo(tx, ty);
out->updateTextPos(state);
}
void Gfx::opTextMoveSet(Object args[], int numArgs)
{
double tx, ty;
tx = state->getLineX() + args[0].getNum();
ty = args[1].getNum();
state->setLeading(-ty);
ty += state->getLineY();
state->textMoveTo(tx, ty);
out->updateTextPos(state);
}
void Gfx::opSetTextMatrix(Object args[], int numArgs)
{
state->setTextMat(args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
state->textMoveTo(0, 0);
out->updateTextMat(state);
out->updateTextPos(state);
fontChanged = true;
}
void Gfx::opTextNextLine(Object args[], int numArgs)
{
double tx, ty;
tx = state->getLineX();
ty = state->getLineY() - state->getLeading();
state->textMoveTo(tx, ty);
out->updateTextPos(state);
}
//------------------------------------------------------------------------
// text string operators
//------------------------------------------------------------------------
void Gfx::opShowText(Object args[], int numArgs)
{
if (!state->getFont()) {
error(errSyntaxError, getPos(), "No font in show");
return;
}
if (fontChanged) {
out->updateFont(state);
fontChanged = false;
}
out->beginStringOp(state);
doShowText(args[0].getString());
out->endStringOp(state);
if (!ocState) {
doIncCharCount(args[0].getString());
}
}
void Gfx::opMoveShowText(Object args[], int numArgs)
{
double tx, ty;
if (!state->getFont()) {
error(errSyntaxError, getPos(), "No font in move/show");
return;
}
if (fontChanged) {
out->updateFont(state);
fontChanged = false;
}
tx = state->getLineX();
ty = state->getLineY() - state->getLeading();
state->textMoveTo(tx, ty);
out->updateTextPos(state);
out->beginStringOp(state);
doShowText(args[0].getString());
out->endStringOp(state);
if (!ocState) {
doIncCharCount(args[0].getString());
}
}
void Gfx::opMoveSetShowText(Object args[], int numArgs)
{
double tx, ty;
if (!state->getFont()) {
error(errSyntaxError, getPos(), "No font in move/set/show");
return;
}
if (fontChanged) {
out->updateFont(state);
fontChanged = false;
}
state->setWordSpace(args[0].getNum());
state->setCharSpace(args[1].getNum());
tx = state->getLineX();
ty = state->getLineY() - state->getLeading();
state->textMoveTo(tx, ty);
out->updateWordSpace(state);
out->updateCharSpace(state);
out->updateTextPos(state);
out->beginStringOp(state);
doShowText(args[2].getString());
out->endStringOp(state);
if (ocState) {
doIncCharCount(args[2].getString());
}
}
void Gfx::opShowSpaceText(Object args[], int numArgs)
{
Array *a;
int wMode;
int i;
if (!state->getFont()) {
error(errSyntaxError, getPos(), "No font in show/space");
return;
}
if (fontChanged) {
out->updateFont(state);
fontChanged = false;
}
out->beginStringOp(state);
wMode = state->getFont()->getWMode();
a = args[0].getArray();
for (i = 0; i < a->getLength(); ++i) {
Object obj = a->get(i);
if (obj.isNum()) {
// this uses the absolute value of the font size to match
// Acrobat's behavior
if (wMode) {
state->textShift(0, -obj.getNum() * 0.001 * state->getFontSize());
} else {
state->textShift(-obj.getNum() * 0.001 * state->getFontSize() * state->getHorizScaling(), 0);
}
out->updateTextShift(state, obj.getNum());
} else if (obj.isString()) {
doShowText(obj.getString());
} else {
error(errSyntaxError, getPos(), "Element of show/space array must be number or string");
}
}
out->endStringOp(state);
if (!ocState) {
a = args[0].getArray();
for (i = 0; i < a->getLength(); ++i) {
Object obj = a->get(i);
if (obj.isString()) {
doIncCharCount(obj.getString());
}
}
}
}
void Gfx::doShowText(const GooString *s)
{
int wMode;
double riseX, riseY;
CharCode code;
const Unicode *u = nullptr;
double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
double originX, originY, tOriginX, tOriginY;
double x0, y0, x1, y1;
double tmp[4], newCTM[6];
const double *oldCTM, *mat;
Dict *resDict;
Parser *oldParser;
GfxState *savedState;
const char *p;
int render;
bool patternFill;
int len, n, uLen, nChars, nSpaces;
GfxFont *const font = state->getFont().get();
wMode = font->getWMode();
if (out->useDrawChar()) {
out->beginString(state, s);
}
// if we're doing a pattern fill, set up clipping
render = state->getRender();
if (!(render & 1) && state->getFillColorSpace()->getMode() == csPattern) {
patternFill = true;
saveState();
// disable fill, enable clipping, leave stroke unchanged
if ((render ^ (render >> 1)) & 1) {
render = 5;
} else {
render = 7;
}
state->setRender(render);
out->updateRender(state);
} else {
patternFill = false;
}
state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
x0 = state->getCurX() + riseX;
y0 = state->getCurY() + riseY;
// handle a Type 3 char
if (font->getType() == fontType3 && out->interpretType3Chars()) {
oldCTM = state->getCTM();
mat = state->getTextMat();
tmp[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
tmp[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
tmp[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
tmp[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
mat = font->getFontMatrix();
newCTM[0] = mat[0] * tmp[0] + mat[1] * tmp[2];
newCTM[1] = mat[0] * tmp[1] + mat[1] * tmp[3];
newCTM[2] = mat[2] * tmp[0] + mat[3] * tmp[2];
newCTM[3] = mat[2] * tmp[1] + mat[3] * tmp[3];
newCTM[0] *= state->getFontSize();
newCTM[1] *= state->getFontSize();
newCTM[2] *= state->getFontSize();
newCTM[3] *= state->getFontSize();
newCTM[0] *= state->getHorizScaling();
newCTM[1] *= state->getHorizScaling();
curX = state->getCurX();
curY = state->getCurY();
oldParser = parser;
p = s->c_str();
len = s->getLength();
while (len > 0) {
n = font->getNextChar(p, len, &code, &u, &uLen, &dx, &dy, &originX, &originY);
dx = dx * state->getFontSize() + state->getCharSpace();
if (n == 1 && *p == ' ') {
dx += state->getWordSpace();
}
dx *= state->getHorizScaling();
dy *= state->getFontSize();
state->textTransformDelta(dx, dy, &tdx, &tdy);
state->transform(curX + riseX, curY + riseY, &x, &y);
savedState = saveStateStack();
state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
//~ the CTM concat values here are wrong (but never used)
out->updateCTM(state, 1, 0, 0, 1, 0, 0);
state->transformDelta(dx, dy, &ddx, &ddy);
if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) {
Object charProc = ((Gfx8BitFont *)font)->getCharProcNF(code);
int refNum = -1;
if (charProc.isRef()) {
refNum = charProc.getRef().num;
charProc = charProc.fetch(((Gfx8BitFont *)font)->getCharProcs()->getXRef());
}
if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
pushResources(resDict);
}
if (charProc.isStream()) {
Object charProcResourcesObj = charProc.streamGetDict()->lookup("Resources");
if (charProcResourcesObj.isDict()) {
pushResources(charProcResourcesObj.getDict());
}
std::set<int>::iterator charProcDrawingIt;
bool displayCharProc = true;
if (refNum != -1) {
if (charProcDrawing.find(refNum) == charProcDrawing.end()) {
charProcDrawingIt = charProcDrawing.insert(refNum).first;
} else {
displayCharProc = false;
error(errSyntaxError, -1, "CharProc wants to draw a CharProc that is already being drawn");
}
}
if (displayCharProc) {
++displayDepth;
display(&charProc, false);
--displayDepth;
if (refNum != -1) {
charProcDrawing.erase(charProcDrawingIt);
}
}
if (charProcResourcesObj.isDict()) {
popResources();
}
} else {
error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry");
}
out->endType3Char(state);
if (resDict) {
popResources();
}
}
restoreStateStack(savedState);
// GfxState::restore() does *not* restore the current position,
// so we deal with it here using (curX, curY) and (lineX, lineY)
curX += tdx;
curY += tdy;
state->moveTo(curX, curY);
// Call updateCTM with the identity transformation. That way, the CTM is unchanged,
// but any side effect that the method may have is triggered. This is the case,
// in particular, for the Splash backend.
out->updateCTM(state, 1, 0, 0, 1, 0, 0);
p += n;
len -= n;
}
parser = oldParser;
} else if (out->useDrawChar()) {
p = s->c_str();
len = s->getLength();
while (len > 0) {
n = font->getNextChar(p, len, &code, &u, &uLen, &dx, &dy, &originX, &originY);
if (wMode) {
dx *= state->getFontSize();
dy = dy * state->getFontSize() + state->getCharSpace();
if (n == 1 && *p == ' ') {
dy += state->getWordSpace();
}
} else {
dx = dx * state->getFontSize() + state->getCharSpace();
if (n == 1 && *p == ' ') {
dx += state->getWordSpace();
}
dx *= state->getHorizScaling();
dy *= state->getFontSize();
}
state->textTransformDelta(dx, dy, &tdx, &tdy);
originX *= state->getFontSize();
originY *= state->getFontSize();
state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
if (ocState) {
out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY, tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
}
state->shift(tdx, tdy);
p += n;
len -= n;
}
} else {
dx = dy = 0;
p = s->c_str();
len = s->getLength();
nChars = nSpaces = 0;
while (len > 0) {
n = font->getNextChar(p, len, &code, &u, &uLen, &dx2, &dy2, &originX, &originY);
dx += dx2;
dy += dy2;
if (n == 1 && *p == ' ') {
++nSpaces;
}
++nChars;
p += n;
len -= n;
}
if (wMode) {
dx *= state->getFontSize();
dy = dy * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
} else {
dx = dx * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
dx *= state->getHorizScaling();
dy *= state->getFontSize();
}
state->textTransformDelta(dx, dy, &tdx, &tdy);
if (ocState) {
out->drawString(state, s);
}
state->shift(tdx, tdy);
}
if (out->useDrawChar()) {
out->endString(state);
}
if (patternFill && ocState) {
out->saveTextPos(state);
// tell the OutputDev to do the clipping
out->endTextObject(state);
// set up a clipping bbox so doPatternText will work -- assume
// that the text bounding box does not extend past the baseline in
// any direction by more than twice the font size
x1 = state->getCurX() + riseX;
y1 = state->getCurY() + riseY;
if (x0 > x1) {
x = x0;
x0 = x1;
x1 = x;
}
if (y0 > y1) {
y = y0;
y0 = y1;
y1 = y;
}
state->textTransformDelta(0, state->getFontSize(), &dx, &dy);
state->textTransformDelta(state->getFontSize(), 0, &dx2, &dy2);
dx = fabs(dx);
dx2 = fabs(dx2);
if (dx2 > dx) {
dx = dx2;
}
dy = fabs(dy);
dy2 = fabs(dy2);
if (dy2 > dy) {
dy = dy2;
}
state->clipToRect(x0 - 2 * dx, y0 - 2 * dy, x1 + 2 * dx, y1 + 2 * dy);
// set render mode to fill-only
state->setRender(0);
out->updateRender(state);
doPatternText();
restoreState();
out->restoreTextPos(state);
}
updateLevel += 10 * s->getLength();
}
// NB: this is only called when ocState is false.
void Gfx::doIncCharCount(const GooString *s)
{
if (out->needCharCount()) {
out->incCharCount(s->getLength());
}
}
//------------------------------------------------------------------------
// XObject operators
//------------------------------------------------------------------------
void Gfx::opXObject(Object args[], int numArgs)
{
const char *name;
if (!ocState && !out->needCharCount()) {
return;
}
name = args[0].getName();
Object obj1 = res->lookupXObject(name);
if (obj1.isNull()) {
return;
}
if (!obj1.isStream()) {
error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
return;
}
#ifdef OPI_SUPPORT
Object opiDict = obj1.streamGetDict()->lookup("OPI");
if (opiDict.isDict()) {
out->opiBegin(state, opiDict.getDict());
}
#endif
Object obj2 = obj1.streamGetDict()->lookup("Subtype");
if (obj2.isName("Image")) {
if (out->needNonText()) {
Object refObj = res->lookupXObjectNF(name);
doImage(&refObj, obj1.getStream(), false);
}
} else if (obj2.isName("Form")) {
Object refObj = res->lookupXObjectNF(name);
bool shouldDoForm = true;
std::set<int>::iterator drawingFormIt;
if (refObj.isRef()) {
const int num = refObj.getRef().num;
if (formsDrawing.find(num) == formsDrawing.end()) {
drawingFormIt = formsDrawing.insert(num).first;
} else {
shouldDoForm = false;
}
}
if (shouldDoForm) {
if (out->useDrawForm() && refObj.isRef()) {
out->drawForm(refObj.getRef());
} else {
Ref ref = refObj.isRef() ? refObj.getRef() : Ref::INVALID();
out->beginForm(&obj1, ref);
doForm(&obj1);
out->endForm(&obj1, ref);
}
}
if (refObj.isRef() && shouldDoForm) {
formsDrawing.erase(drawingFormIt);
}
} else if (obj2.isName("PS")) {
Object obj3 = obj1.streamGetDict()->lookup("Level1");
out->psXObject(obj1.getStream(), obj3.isStream() ? obj3.getStream() : nullptr);
} else if (obj2.isName()) {
error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
} else {
error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
}
#ifdef OPI_SUPPORT
if (opiDict.isDict()) {
out->opiEnd(state, opiDict.getDict());
}
#endif
}
void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
{
Dict *dict, *maskDict;
int width, height;
int bits, maskBits;
bool interpolate;
StreamColorSpaceMode csMode;
bool mask;
bool invert;
GfxColorSpace *colorSpace, *maskColorSpace;
bool haveColorKeyMask, haveExplicitMask, haveSoftMask;
int maskColors[2 * gfxColorMaxComps] = {};
int maskWidth, maskHeight;
bool maskInvert;
bool maskInterpolate;
Stream *maskStr;
int i, n;
// get info from the stream
bits = 0;
csMode = streamCSNone;
str->getImageParams(&bits, &csMode);
// get stream dict
dict = str->getDict();
// check for optional content key
if (ref) {
const Object &objOC = dict->lookupNF("OC");
if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&objOC)) {
return;
}
}
const double *ctm = state->getCTM();
const double det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
// Detect singular matrix (non invertible) to avoid drawing Image in such case
const bool singular_matrix = fabs(det) < 0.000001;
// get size
Object obj1 = dict->lookup("Width");
if (obj1.isNull()) {
obj1 = dict->lookup("W");
}
if (obj1.isInt()) {
width = obj1.getInt();
} else if (obj1.isReal()) {
width = (int)obj1.getReal();
} else {
goto err1;
}
obj1 = dict->lookup("Height");
if (obj1.isNull()) {
obj1 = dict->lookup("H");
}
if (obj1.isInt()) {
height = obj1.getInt();
} else if (obj1.isReal()) {
height = (int)obj1.getReal();
} else {
goto err1;
}
if (width < 1 || height < 1 || width > INT_MAX / height) {
goto err1;
}
// image interpolation
obj1 = dict->lookup("Interpolate");
if (obj1.isNull()) {
obj1 = dict->lookup("I");
}
if (obj1.isBool()) {
interpolate = obj1.getBool();
} else {
interpolate = false;
}
maskInterpolate = false;
// image or mask?
obj1 = dict->lookup("ImageMask");
if (obj1.isNull()) {
obj1 = dict->lookup("IM");
}
mask = false;
if (obj1.isBool()) {
mask = obj1.getBool();
} else if (!obj1.isNull()) {
goto err1;
}
// bit depth
if (bits == 0) {
obj1 = dict->lookup("BitsPerComponent");
if (obj1.isNull()) {
obj1 = dict->lookup("BPC");
}
if (obj1.isInt()) {
bits = obj1.getInt();
} else if (mask) {
bits = 1;
} else {
goto err1;
}
}
// display a mask
if (mask) {
// check for inverted mask
if (bits != 1) {
goto err1;
}
invert = false;
obj1 = dict->lookup("Decode");
if (obj1.isNull()) {
obj1 = dict->lookup("D");
}
if (obj1.isArray()) {
Object obj2;
obj2 = obj1.arrayGet(0);
// Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
// accepts [1.0 0.0] as well.
if (obj2.isNum() && obj2.getNum() >= 0.9) {
invert = true;
}
} else if (!obj1.isNull()) {
goto err1;
}
// if drawing is disabled, skip over inline image data
if (!ocState || !out->needNonText()) {
str->reset();
n = height * ((width + 7) / 8);
for (i = 0; i < n; ++i) {
str->getChar();
}
str->close();
// draw it
} else {
if (state->getFillColorSpace()->getMode() == csPattern) {
doPatternImageMask(ref, str, width, height, invert, inlineImg);
} else {
out->drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
}
}
} else {
if (bits == 0) {
goto err1;
}
// get color space and color map
obj1 = dict->lookup("ColorSpace");
if (obj1.isNull()) {
obj1 = dict->lookup("CS");
}
if (obj1.isName() && inlineImg) {
Object obj2 = res->lookupColorSpace(obj1.getName());
if (!obj2.isNull()) {
obj1 = std::move(obj2);
}
}
if (!obj1.isNull()) {
char *tempIntent = nullptr;
Object objIntent = dict->lookup("Intent");
if (objIntent.isName()) {
const char *stateIntent = state->getRenderingIntent();
if (stateIntent != nullptr) {
tempIntent = strdup(stateIntent);
}
state->setRenderingIntent(objIntent.getName());
}
colorSpace = GfxColorSpace::parse(res, &obj1, out, state);
if (objIntent.isName()) {
state->setRenderingIntent(tempIntent);
free(tempIntent);
}
} else if (csMode == streamCSDeviceGray) {
Object objCS = res->lookupColorSpace("DefaultGray");
if (objCS.isNull()) {
colorSpace = new GfxDeviceGrayColorSpace();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
} else if (csMode == streamCSDeviceRGB) {
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
colorSpace = new GfxDeviceRGBColorSpace();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
} else if (csMode == streamCSDeviceCMYK) {
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
colorSpace = new GfxDeviceCMYKColorSpace();
} else {
colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
}
} else {
colorSpace = nullptr;
}
if (!colorSpace) {
goto err1;
}
obj1 = dict->lookup("Decode");
if (obj1.isNull()) {
obj1 = dict->lookup("D");
}
GfxImageColorMap colorMap(bits, &obj1, colorSpace);
if (!colorMap.isOk()) {
goto err1;
}
// get the mask
bool haveMaskImage = false;
haveColorKeyMask = haveExplicitMask = haveSoftMask = false;
maskStr = nullptr; // make gcc happy
maskWidth = maskHeight = 0; // make gcc happy
maskInvert = false; // make gcc happy
std::unique_ptr<GfxImageColorMap> maskColorMap;
Object maskObj = dict->lookup("Mask");
Object smaskObj = dict->lookup("SMask");
if (maskObj.isStream()) {
maskStr = maskObj.getStream();
maskDict = maskObj.streamGetDict();
// if Type is XObject and Subtype is Image
// then the way the softmask is drawn will draw
// correctly, if it falls through to the explicit
// mask code then you get an error and no image
// drawn because it expects maskDict to have an entry
// of Mask or IM that is boolean...
Object tobj = maskDict->lookup("Type");
if (!tobj.isNull() && tobj.isName() && tobj.isName("XObject")) {
Object sobj = maskDict->lookup("Subtype");
if (!sobj.isNull() && sobj.isName() && sobj.isName("Image")) {
// ensure that this mask does not include an ImageMask entry
// which signifies the explicit mask
obj1 = maskDict->lookup("ImageMask");
if (obj1.isNull()) {
obj1 = maskDict->lookup("IM");
}
if (obj1.isNull() || !obj1.isBool()) {
haveMaskImage = true;
}
}
}
}
if (smaskObj.isStream() || haveMaskImage) {
// soft mask
if (inlineImg) {
goto err1;
}
if (!haveMaskImage) {
maskStr = smaskObj.getStream();
maskDict = smaskObj.streamGetDict();
}
obj1 = maskDict->lookup("Width");
if (obj1.isNull()) {
obj1 = maskDict->lookup("W");
}
if (!obj1.isInt()) {
goto err1;
}
maskWidth = obj1.getInt();
obj1 = maskDict->lookup("Height");
if (obj1.isNull()) {
obj1 = maskDict->lookup("H");
}
if (!obj1.isInt()) {
goto err1;
}
maskHeight = obj1.getInt();
obj1 = maskDict->lookup("Interpolate");
if (obj1.isNull()) {
obj1 = maskDict->lookup("I");
}
if (obj1.isBool()) {
maskInterpolate = obj1.getBool();
} else {
maskInterpolate = false;
}
obj1 = maskDict->lookup("BitsPerComponent");
if (obj1.isNull()) {
obj1 = maskDict->lookup("BPC");
}
if (!obj1.isInt()) {
goto err1;
}
maskBits = obj1.getInt();
obj1 = maskDict->lookup("ColorSpace");
if (obj1.isNull()) {
obj1 = maskDict->lookup("CS");
}
if (obj1.isName()) {
Object obj2 = res->lookupColorSpace(obj1.getName());
if (!obj2.isNull()) {
obj1 = std::move(obj2);
}
}
// Here, we parse manually instead of using GfxColorSpace::parse,
// since we explicitly need DeviceGray and not some DefaultGray color space
if (!obj1.isName("DeviceGray") && !obj1.isName("G")) {
goto err1;
}
maskColorSpace = new GfxDeviceGrayColorSpace();
obj1 = maskDict->lookup("Decode");
if (obj1.isNull()) {
obj1 = maskDict->lookup("D");
}
maskColorMap = std::make_unique<GfxImageColorMap>(maskBits, &obj1, maskColorSpace);
if (!maskColorMap->isOk()) {
goto err1;
}
// handle the Matte entry
obj1 = maskDict->lookup("Matte");
if (obj1.isArray()) {
if (obj1.getArray()->getLength() != colorSpace->getNComps()) {
error(errSyntaxError, -1, "Matte entry should have {0:d} components but has {1:d}", colorSpace->getNComps(), obj1.getArray()->getLength());
} else if (maskWidth != width || maskHeight != height) {
error(errSyntaxError, -1, "Softmask with matte entry {0:d} x {1:d} must have same geometry as the image {2:d} x {3:d}", maskWidth, maskHeight, width, height);
} else {
GfxColor matteColor;
for (i = 0; i < colorSpace->getNComps(); i++) {
Object obj2 = obj1.getArray()->get(i);
if (!obj2.isNum()) {
error(errSyntaxError, -1, "Matte entry {0:d} should be a number but it's of type {1:d}", i, obj2.getType());
break;
}
matteColor.c[i] = dblToCol(obj2.getNum());
}
if (i == colorSpace->getNComps()) {
maskColorMap->setMatteColor(&matteColor);
}
}
}
haveSoftMask = true;
} else if (maskObj.isArray()) {
// color key mask
for (i = 0; i < maskObj.arrayGetLength() && i < 2 * gfxColorMaxComps; ++i) {
obj1 = maskObj.arrayGet(i);
if (obj1.isInt()) {
maskColors[i] = obj1.getInt();
} else if (obj1.isReal()) {
error(errSyntaxError, -1, "Mask entry should be an integer but it's a real, trying to use it");
maskColors[i] = (int)obj1.getReal();
} else {
error(errSyntaxError, -1, "Mask entry should be an integer but it's of type {0:d}", obj1.getType());
goto err1;
}
}
haveColorKeyMask = true;
} else if (maskObj.isStream()) {
// explicit mask
if (inlineImg) {
goto err1;
}
if (maskStr == nullptr) {
maskStr = maskObj.getStream();
maskDict = maskObj.streamGetDict();
}
obj1 = maskDict->lookup("Width");
if (obj1.isNull()) {
obj1 = maskDict->lookup("W");
}
if (!obj1.isInt()) {
goto err1;
}
maskWidth = obj1.getInt();
obj1 = maskDict->lookup("Height");
if (obj1.isNull()) {
obj1 = maskDict->lookup("H");
}
if (!obj1.isInt()) {
goto err1;
}
maskHeight = obj1.getInt();
obj1 = maskDict->lookup("Interpolate");
if (obj1.isNull()) {
obj1 = maskDict->lookup("I");
}
if (obj1.isBool()) {
maskInterpolate = obj1.getBool();
} else {
maskInterpolate = false;
}
obj1 = maskDict->lookup("ImageMask");
if (obj1.isNull()) {
obj1 = maskDict->lookup("IM");
}
if (!haveMaskImage && (!obj1.isBool() || !obj1.getBool())) {
goto err1;
}
maskInvert = false;
obj1 = maskDict->lookup("Decode");
if (obj1.isNull()) {
obj1 = maskDict->lookup("D");
}
if (obj1.isArray()) {
Object obj2 = obj1.arrayGet(0);
// Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
// accepts [1.0 0.0] as well.
if (obj2.isNum() && obj2.getNum() >= 0.9) {
maskInvert = true;
}
} else if (!obj1.isNull()) {
goto err1;
}
haveExplicitMask = true;
}
// if drawing is disabled, skip over inline image data
if (!ocState || !out->needNonText() || singular_matrix) {
str->reset();
n = height * ((width * colorMap.getNumPixelComps() * colorMap.getBits() + 7) / 8);
for (i = 0; i < n; ++i) {
str->getChar();
}
str->close();
// draw it
} else {
if (haveSoftMask) {
out->drawSoftMaskedImage(state, ref, str, width, height, &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap.get(), maskInterpolate);
} else if (haveExplicitMask) {
out->drawMaskedImage(state, ref, str, width, height, &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
} else {
out->drawImage(state, ref, str, width, height, &colorMap, interpolate, haveColorKeyMask ? maskColors : nullptr, inlineImg);
}
}
}
if ((i = width * height) > 1000) {
i = 1000;
}
updateLevel += i;
return;
err1:
error(errSyntaxError, getPos(), "Bad image parameters");
}
bool Gfx::checkTransparencyGroup(Dict *resDict)
{
// check the effect of compositing objects as a group:
// look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
bool transpGroup = false;
double opac;
if (resDict == nullptr) {
return false;
}
pushResources(resDict);
Object extGStates = resDict->lookup("ExtGState");
if (extGStates.isDict()) {
Dict *dict = extGStates.getDict();
for (int i = 0; i < dict->getLength() && !transpGroup; i++) {
GfxBlendMode mode;
Object obj1 = res->lookupGState(dict->getKey(i));
if (obj1.isDict()) {
Object obj2 = obj1.dictLookup("BM");
if (!obj2.isNull()) {
if (state->parseBlendMode(&obj2, &mode)) {
if (mode != gfxBlendNormal) {
transpGroup = true;
}
} else {
error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
}
}
obj2 = obj1.dictLookup("ca");
if (obj2.isNum()) {
opac = obj2.getNum();
opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
if (opac != 1) {
transpGroup = true;
}
}
obj2 = obj1.dictLookup("CA");
if (obj2.isNum()) {
opac = obj2.getNum();
opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
if (opac != 1) {
transpGroup = true;
}
}
// alpha is shape
obj2 = obj1.dictLookup("AIS");
if (!transpGroup && obj2.isBool()) {
transpGroup = obj2.getBool();
}
// soft mask
obj2 = obj1.dictLookup("SMask");
if (!transpGroup && !obj2.isNull()) {
if (!obj2.isName("None")) {
transpGroup = true;
}
}
}
}
}
popResources();
return transpGroup;
}
void Gfx::doForm(Object *str)
{
Dict *dict;
bool transpGroup, isolated, knockout;
GfxColorSpace *blendingColorSpace;
double m[6], bbox[4];
Dict *resDict;
bool ocSaved;
Object obj1;
int i;
// get stream dict
dict = str->streamGetDict();
// check form type
obj1 = dict->lookup("FormType");
if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
error(errSyntaxError, getPos(), "Unknown form type");
}
// check for optional content key
ocSaved = ocState;
const Object &objOC = dict->lookupNF("OC");
if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&objOC)) {
if (out->needCharCount()) {
ocState = false;
} else {
return;
}
}
// get bounding box
Object bboxObj = dict->lookup("BBox");
if (!bboxObj.isArray()) {
error(errSyntaxError, getPos(), "Bad form bounding box");
ocState = ocSaved;
return;
}
for (i = 0; i < 4; ++i) {
obj1 = bboxObj.arrayGet(i);
if (likely(obj1.isNum())) {
bbox[i] = obj1.getNum();
} else {
error(errSyntaxError, getPos(), "Bad form bounding box value");
return;
}
}
// get matrix
Object matrixObj = dict->lookup("Matrix");
if (matrixObj.isArray()) {
for (i = 0; i < 6; ++i) {
obj1 = matrixObj.arrayGet(i);
if (likely(obj1.isNum())) {
m[i] = obj1.getNum();
} else {
m[i] = 0;
}
}
} else {
m[0] = 1;
m[1] = 0;
m[2] = 0;
m[3] = 1;
m[4] = 0;
m[5] = 0;
}
// get resources
Object resObj = dict->lookup("Resources");
resDict = resObj.isDict() ? resObj.getDict() : nullptr;
// check for a transparency group
transpGroup = isolated = knockout = false;
blendingColorSpace = nullptr;
obj1 = dict->lookup("Group");
if (obj1.isDict()) {
Object obj2 = obj1.dictLookup("S");
if (obj2.isName("Transparency")) {
Object obj3 = obj1.dictLookup("CS");
if (!obj3.isNull()) {
blendingColorSpace = GfxColorSpace::parse(res, &obj3, out, state);
}
obj3 = obj1.dictLookup("I");
if (obj3.isBool()) {
isolated = obj3.getBool();
}
obj3 = obj1.dictLookup("K");
if (obj3.isBool()) {
knockout = obj3.getBool();
}
transpGroup = isolated || out->checkTransparencyGroup(state, knockout) || checkTransparencyGroup(resDict);
}
}
// draw it
drawForm(str, resDict, m, bbox, transpGroup, false, blendingColorSpace, isolated, knockout);
if (blendingColorSpace) {
delete blendingColorSpace;
}
ocState = ocSaved;
}
void Gfx::drawForm(Object *str, Dict *resDict, const double *matrix, const double *bbox, bool transpGroup, bool softMask, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, bool alpha, Function *transferFunc,
GfxColor *backdropColor)
{
Parser *oldParser;
GfxState *savedState;
double oldBaseMatrix[6];
int i;
// push new resources on stack
pushResources(resDict);
// save current graphics state
savedState = saveStateStack();
// kill any pre-existing path
state->clearPath();
// save current parser
oldParser = parser;
// set form transformation matrix
state->concatCTM(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
out->updateCTM(state, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
// set form bounding box
state->moveTo(bbox[0], bbox[1]);
state->lineTo(bbox[2], bbox[1]);
state->lineTo(bbox[2], bbox[3]);
state->lineTo(bbox[0], bbox[3]);
state->closePath();
state->clip();
out->clip(state);
state->clearPath();
if (softMask || transpGroup) {
if (state->getBlendMode() != gfxBlendNormal) {
state->setBlendMode(gfxBlendNormal);
out->updateBlendMode(state);
}
if (state->getFillOpacity() != 1) {
state->setFillOpacity(1);
out->updateFillOpacity(state);
}
if (state->getStrokeOpacity() != 1) {
state->setStrokeOpacity(1);
out->updateStrokeOpacity(state);
}
out->clearSoftMask(state);
out->beginTransparencyGroup(state, bbox, blendingColorSpace, isolated, knockout, softMask);
}
// set new base matrix
for (i = 0; i < 6; ++i) {
oldBaseMatrix[i] = baseMatrix[i];
baseMatrix[i] = state->getCTM()[i];
}
GfxState *stateBefore = state;
// draw the form
++displayDepth;
display(str, false);
--displayDepth;
if (stateBefore != state) {
if (state->isParentState(stateBefore)) {
error(errSyntaxError, -1, "There's a form with more q than Q, trying to fix");
while (stateBefore != state) {
restoreState();
}
} else {
error(errSyntaxError, -1, "There's a form with more Q than q");
}
}
if (softMask || transpGroup) {
out->endTransparencyGroup(state);
}
// restore base matrix
for (i = 0; i < 6; ++i) {
baseMatrix[i] = oldBaseMatrix[i];
}
// restore parser
parser = oldParser;
// restore graphics state
restoreStateStack(savedState);
// pop resource stack
popResources();
if (softMask) {
out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
} else if (transpGroup) {
out->paintTransparencyGroup(state, bbox);
}
return;
}
//------------------------------------------------------------------------
// in-line image operators
//------------------------------------------------------------------------
void Gfx::opBeginImage(Object args[], int numArgs)
{
Stream *str;
int c1, c2;
// NB: this function is run even if ocState is false -- doImage() is
// responsible for skipping over the inline image data
// build dict/stream
str = buildImageStream();
// display the image
if (str) {
doImage(nullptr, str, true);
// skip 'EI' tag
c1 = str->getUndecodedStream()->getChar();
c2 = str->getUndecodedStream()->getChar();
while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
c1 = c2;
c2 = str->getUndecodedStream()->getChar();
}
delete str;
}
}
Stream *Gfx::buildImageStream()
{
Stream *str;
// build dictionary
Object dict(new Dict(xref));
Object obj = parser->getObj();
while (!obj.isCmd("ID") && !obj.isEOF()) {
if (!obj.isName()) {
error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
} else {
auto val = parser->getObj();
if (val.isEOF() || val.isError()) {
break;
}
dict.dictAdd(obj.getName(), std::move(val));
}
obj = parser->getObj();
}
if (obj.isEOF()) {
error(errSyntaxError, getPos(), "End of file in inline image");
return nullptr;
}
// make stream
if (parser->getStream()) {
str = new EmbedStream(parser->getStream(), std::move(dict), false, 0, true);
str = str->addFilters(str->getDict());
} else {
str = nullptr;
}
return str;
}
void Gfx::opImageData(Object args[], int numArgs)
{
error(errInternal, getPos(), "Got 'ID' operator");
}
void Gfx::opEndImage(Object args[], int numArgs)
{
error(errInternal, getPos(), "Got 'EI' operator");
}
//------------------------------------------------------------------------
// type 3 font operators
//------------------------------------------------------------------------
void Gfx::opSetCharWidth(Object args[], int numArgs)
{
out->type3D0(state, args[0].getNum(), args[1].getNum());
}
void Gfx::opSetCacheDevice(Object args[], int numArgs)
{
out->type3D1(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
}
//------------------------------------------------------------------------
// compatibility operators
//------------------------------------------------------------------------
void Gfx::opBeginIgnoreUndef(Object args[], int numArgs)
{
++ignoreUndef;
}
void Gfx::opEndIgnoreUndef(Object args[], int numArgs)
{
if (ignoreUndef > 0) {
--ignoreUndef;
}
}
//------------------------------------------------------------------------
// marked content operators
//------------------------------------------------------------------------
enum GfxMarkedContentKind
{
gfxMCOptionalContent,
gfxMCActualText,
gfxMCOther
};
struct MarkedContentStack
{
GfxMarkedContentKind kind;
bool ocSuppressed; // are we ignoring content based on OptionalContent?
MarkedContentStack *next; // next object on stack
};
void Gfx::popMarkedContent()
{
MarkedContentStack *mc = mcStack;
mcStack = mc->next;
delete mc;
}
void Gfx::pushMarkedContent()
{
MarkedContentStack *mc = new MarkedContentStack();
mc->ocSuppressed = false;
mc->kind = gfxMCOther;
mc->next = mcStack;
mcStack = mc;
}
bool Gfx::contentIsHidden()
{
MarkedContentStack *mc = mcStack;
bool hidden = mc && mc->ocSuppressed;
while (!hidden && mc && mc->next) {
mc = mc->next;
hidden = mc->ocSuppressed;
}
return hidden;
}
void Gfx::opBeginMarkedContent(Object args[], int numArgs)
{
// push a new stack entry
pushMarkedContent();
OCGs *contentConfig = catalog->getOptContentConfig();
const char *name0 = args[0].getName();
if (strncmp(name0, "OC", 2) == 0 && contentConfig) {
if (numArgs >= 2) {
if (args[1].isName()) {
const char *name1 = args[1].getName();
MarkedContentStack *mc = mcStack;
mc->kind = gfxMCOptionalContent;
Object markedContent = res->lookupMarkedContentNF(name1);
if (!markedContent.isNull()) {
bool visible = contentConfig->optContentIsVisible(&markedContent);
mc->ocSuppressed = !(visible);
} else {
error(errSyntaxError, getPos(), "DID NOT find {0:s}", name1);
}
} else {
error(errSyntaxError, getPos(), "Unexpected MC Type: {0:d}", args[1].getType());
}
} else {
error(errSyntaxError, getPos(), "insufficient arguments for Marked Content");
}
} else if (args[0].isName("Span") && numArgs == 2) {
Object dictToUse;
if (args[1].isDict()) {
dictToUse = args[1].copy();
} else if (args[1].isName()) {
dictToUse = res->lookupMarkedContentNF(args[1].getName()).fetch(xref);
}
if (dictToUse.isDict()) {
Object obj = dictToUse.dictLookup("ActualText");
if (obj.isString()) {
out->beginActualText(state, obj.getString());
MarkedContentStack *mc = mcStack;
mc->kind = gfxMCActualText;
}
}
}
if (printCommands) {
printf(" marked content: %s ", args[0].getName());
if (numArgs == 2) {
args[1].print(stdout);
}
printf("\n");
fflush(stdout);
}
ocState = !contentIsHidden();
if (numArgs == 2 && args[1].isDict()) {
out->beginMarkedContent(args[0].getName(), args[1].getDict());
} else if (numArgs == 1) {
out->beginMarkedContent(args[0].getName(), nullptr);
}
}
void Gfx::opEndMarkedContent(Object args[], int numArgs)
{
if (!mcStack) {
error(errSyntaxWarning, getPos(), "Mismatched EMC operator");
return;
}
MarkedContentStack *mc = mcStack;
GfxMarkedContentKind mcKind = mc->kind;
// pop the stack
popMarkedContent();
if (mcKind == gfxMCActualText) {
out->endActualText(state);
}
ocState = !contentIsHidden();
out->endMarkedContent(state);
}
void Gfx::opMarkPoint(Object args[], int numArgs)
{
if (printCommands) {
printf(" mark point: %s ", args[0].getName());
if (numArgs == 2) {
args[1].print(stdout);
}
printf("\n");
fflush(stdout);
}
if (numArgs == 2 && args[1].isDict()) {
out->markPoint(args[0].getName(), args[1].getDict());
} else {
out->markPoint(args[0].getName());
}
}
//------------------------------------------------------------------------
// misc
//------------------------------------------------------------------------
struct GfxStackStateSaver
{
explicit GfxStackStateSaver(Gfx *gfxA) : gfx(gfxA) { gfx->saveState(); }
~GfxStackStateSaver() { gfx->restoreState(); }
GfxStackStateSaver(const GfxStackStateSaver &) = delete;
GfxStackStateSaver &operator=(const GfxStackStateSaver &) = delete;
Gfx *const gfx;
};
void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor, double xMin, double yMin, double xMax, double yMax, int rotate)
{
Dict *dict, *resDict;
double formXMin, formYMin, formXMax, formYMax;
double x, y, sx, sy, tx, ty;
double m[6], bbox[4];
GfxColor color;
int i;
// this function assumes that we are in the default user space,
// i.e., baseMatrix = ctm
// if the bounding box has zero width or height, don't draw anything
// at all
if (xMin == xMax || yMin == yMax) {
return;
}
// saves gfx state and automatically restores it on return
GfxStackStateSaver stackStateSaver(this);
// Rotation around the topleft corner (for the NoRotate flag)
if (rotate != 0) {
const double angle_rad = rotate * M_PI / 180;
const double c = cos(angle_rad);
const double s = sin(angle_rad);
// (xMin, yMax) is the pivot
const double unrotateMTX[6] = { +c, -s, +s, +c, -c * xMin - s * yMax + xMin, -c * yMax + s * xMin + yMax };
state->concatCTM(unrotateMTX[0], unrotateMTX[1], unrotateMTX[2], unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
out->updateCTM(state, unrotateMTX[0], unrotateMTX[1], unrotateMTX[2], unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
}
// draw the appearance stream (if there is one)
if (str->isStream()) {
// get stream dict
dict = str->streamGetDict();
// get the form bounding box
Object bboxObj = dict->lookup("BBox");
if (!bboxObj.isArray()) {
error(errSyntaxError, getPos(), "Bad form bounding box");
return;
}
for (i = 0; i < 4; ++i) {
Object obj1 = bboxObj.arrayGet(i);
if (likely(obj1.isNum())) {
bbox[i] = obj1.getNum();
} else {
error(errSyntaxError, getPos(), "Bad form bounding box value");
return;
}
}
// get the form matrix
Object matrixObj = dict->lookup("Matrix");
if (matrixObj.isArray() && matrixObj.arrayGetLength() >= 6) {
for (i = 0; i < 6; ++i) {
Object obj1 = matrixObj.arrayGet(i);
if (likely(obj1.isNum())) {
m[i] = obj1.getNum();
} else {
error(errSyntaxError, getPos(), "Bad form matrix");
return;
}
}
} else {
m[0] = 1;
m[1] = 0;
m[2] = 0;
m[3] = 1;
m[4] = 0;
m[5] = 0;
}
// transform the four corners of the form bbox to default user
// space, and construct the transformed bbox
x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
formXMin = formXMax = x;
formYMin = formYMax = y;
x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
if (x < formXMin) {
formXMin = x;
} else if (x > formXMax) {
formXMax = x;
}
if (y < formYMin) {
formYMin = y;
} else if (y > formYMax) {
formYMax = y;
}
x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
if (x < formXMin) {
formXMin = x;
} else if (x > formXMax) {
formXMax = x;
}
if (y < formYMin) {
formYMin = y;
} else if (y > formYMax) {
formYMax = y;
}
x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
if (x < formXMin) {
formXMin = x;
} else if (x > formXMax) {
formXMax = x;
}
if (y < formYMin) {
formYMin = y;
} else if (y > formYMax) {
formYMax = y;
}
// construct a mapping matrix, [sx 0 0], which maps the transformed
// [0 sy 0]
// [tx ty 1]
// bbox to the annotation rectangle
if (formXMin == formXMax) {
// this shouldn't happen
sx = 1;
} else {
sx = (xMax - xMin) / (formXMax - formXMin);
}
if (formYMin == formYMax) {
// this shouldn't happen
sy = 1;
} else {
sy = (yMax - yMin) / (formYMax - formYMin);
}
tx = -formXMin * sx + xMin;
ty = -formYMin * sy + yMin;
// the final transform matrix is (form matrix) * (mapping matrix)
m[0] *= sx;
m[1] *= sy;
m[2] *= sx;
m[3] *= sy;
m[4] = m[4] * sx + tx;
m[5] = m[5] * sy + ty;
// get the resources
Object resObj = dict->lookup("Resources");
resDict = resObj.isDict() ? resObj.getDict() : nullptr;
// draw it
drawForm(str, resDict, m, bbox);
}
// draw the border
if (border && border->getWidth() > 0 && (!aColor || aColor->getSpace() != AnnotColor::colorTransparent)) {
if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
state->setStrokePattern(nullptr);
state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
out->updateStrokeColorSpace(state);
}
double r, g, b;
if (!aColor) {
r = g = b = 0;
} else if ((aColor->getSpace() == AnnotColor::colorRGB)) {
const double *values = aColor->getValues();
r = values[0];
g = values[1];
b = values[2];
} else {
error(errUnimplemented, -1, "AnnotColor different than RGB and Transparent not supported");
r = g = b = 0;
};
color.c[0] = dblToCol(r);
color.c[1] = dblToCol(g);
color.c[2] = dblToCol(b);
state->setStrokeColor(&color);
out->updateStrokeColor(state);
state->setLineWidth(border->getWidth());
out->updateLineWidth(state);
const std::vector<double> &dash = border->getDash();
if (border->getStyle() == AnnotBorder::borderDashed && dash.size() > 0) {
std::vector<double> dash2 = dash;
state->setLineDash(std::move(dash2), 0);
out->updateLineDash(state);
}
//~ this doesn't currently handle the beveled and engraved styles
state->clearPath();
state->moveTo(xMin, yMin);
state->lineTo(xMax, yMin);
if (border->getStyle() != AnnotBorder::borderUnderlined) {
state->lineTo(xMax, yMax);
state->lineTo(xMin, yMax);
state->closePath();
}
out->stroke(state);
}
}
int Gfx::bottomGuard()
{
return stateGuards[stateGuards.size() - 1];
}
void Gfx::pushStateGuard()
{
stateGuards.push_back(stackHeight);
}
void Gfx::popStateGuard()
{
while (stackHeight > bottomGuard() && state->hasSaves()) {
restoreState();
}
stateGuards.pop_back();
}
void Gfx::saveState()
{
out->saveState(state);
state = state->save();
stackHeight++;
}
void Gfx::restoreState()
{
if (stackHeight <= bottomGuard() || !state->hasSaves()) {
error(errSyntaxError, -1, "Restoring state when no valid states to pop");
return;
}
state = state->restore();
out->restoreState(state);
stackHeight--;
clip = clipNone;
}
// Create a new state stack, and initialize it with a copy of the
// current state.
GfxState *Gfx::saveStateStack()
{
GfxState *oldState;
out->saveState(state);
oldState = state;
state = state->copy(true);
return oldState;
}
// Switch back to the previous state stack.
void Gfx::restoreStateStack(GfxState *oldState)
{
while (state->hasSaves()) {
restoreState();
}
delete state;
state = oldState;
out->restoreState(state);
}
void Gfx::pushResources(Dict *resDict)
{
res = new GfxResources(xref, resDict, res);
}
void Gfx::popResources()
{
GfxResources *resPtr;
resPtr = res->getNext();
delete res;
res = resPtr;
}