blob: 47627ad3f6e053c1df1f08b3faf634df5a9bf4c4 [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-2019 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 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 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 Even Rouault <even.rouault@mines-paris.org>
// 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 Oliver Sander <oliver.sander@tu-dresden.de>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <math.h>
#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
//------------------------------------------------------------------------
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;
}
GfxFont *GfxResources::doLookupFont(const char *name) const
{
GfxFont *font;
const GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->fonts) {
if ((font = resPtr->fonts->lookup(name)))
return font;
}
}
error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name);
return nullptr;
}
GfxFont *GfxResources::lookupFont(const char *name) {
return doLookupFont(name);
}
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)
{
int i;
doc = docA;
xref = (xrefA == nullptr) ? doc->getXRef() : xrefA;
catalog = doc->getCatalog();
subPage = false;
printCommands = globalParams->getPrintCommands();
profileCommands = globalParams->getProfileCommands();
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());
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];
}
formDepth = 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)
{
int i;
doc = docA;
if (gfxA) {
xref = gfxA->getXRef();
formsDrawing = gfxA->formsDrawing;
charProcDrawing = gfxA->charProcDrawing;
} else {
xref = doc->getXRef();
}
catalog = doc->getCatalog();
subPage = true;
printCommands = globalParams->getPrintCommands();
profileCommands = globalParams->getProfileCommands();
mcStack = nullptr;
parser = nullptr;
// start the resource stack
res = new GfxResources(xref, resDict, nullptr);
// initialize
out = outA;
state = new GfxState(72, 72, box, 0, false);
stackHeight = 1;
pushStateGuard();
fontChanged = false;
clip = clipNone;
ignoreUndef = 0;
for (i = 0; i < 6; ++i) {
baseMatrix[i] = state->getCTM()[i];
}
formDepth = 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();
int length = 0;
unsigned char *profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf,length);
if (hp == nullptr) {
error(errSyntaxWarning, -1, "read ICCBased color space profile error");
} else {
state->setDisplayProfile(hp);
}
gfree(profBuf);
}
}
}
}
}
#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) {
int i;
if (obj->isArray()) {
for (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) {
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);
}
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) {
Array *a;
int length;
double *dash;
int i;
a = args[0].getArray();
length = a->getLength();
if (length == 0) {
dash = nullptr;
} else {
dash = (double *)gmallocn(length, sizeof(double));
bool dummyOk;
for (i = 0; i < length; ++i) {
const Object obj = a->get(i);
dash[i] = obj.getNum(&dummyOk);
}
}
state->setLineDash(dash, length, 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;
Function *funcs[4];
GfxColor backdropColor;
bool haveBackdropColor;
bool alpha, isolated, knockout;
double opac;
int i;
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")) {
funcs[0] = funcs[1] = funcs[2] = funcs[3] = nullptr;
state->setTransfer(funcs);
out->updateTransfer(state);
} else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
for (i = 0; i < 4; ++i) {
Object obj3 = obj2.arrayGet(i);
funcs[i] = Function::parse(&obj3);
if (!funcs[i]) {
break;
}
}
if (i == 4) {
state->setTransfer(funcs);
out->updateTransfer(state);
}
} else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
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;
}
funcs[0] = nullptr;
obj3 = obj2.dictLookup("TR");
if (!obj3.isNull()) {
if (obj3.isName("Default") ||
obj3.isName("Identity")) {
funcs[0] = nullptr;
} else {
funcs[0] = Function::parse(&obj3);
if (funcs[0] == nullptr ||
funcs[0]->getInputSize() != 1 ||
funcs[0]->getOutputSize() != 1) {
error(errSyntaxError, getPos(),
"Invalid transfer function in soft mask in ExtGState");
delete funcs[0];
funcs[0] = nullptr;
}
}
}
obj3 = obj2.dictLookup("BC");
if ((haveBackdropColor = obj3.isArray())) {
for (i = 0; i < gfxColorMaxComps; ++i) {
backdropColor.c[i] = 0;
}
for (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;
isolated = knockout = false;
Object obj5 = obj4.dictLookup("CS");
if (!obj5.isNull()) {
blendingColorSpace = GfxColorSpace::parse(res, &obj5, out, state);
}
obj5 = obj4.dictLookup("I");
if (obj5.isBool()) {
isolated = obj5.getBool();
}
obj5 = obj4.dictLookup("K");
if (obj5.isBool()) {
knockout = obj5.getBool();
}
if (!haveBackdropColor) {
if (blendingColorSpace) {
blendingColorSpace->getDefaultColor(&backdropColor);
} else {
//~ need to get the parent or default color space (?)
for (i = 0; i < gfxColorMaxComps; ++i) {
backdropColor.c[i] = 0;
}
}
}
doSoftMask(&obj3, alpha, blendingColorSpace,
isolated, knockout, funcs[0], &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 funcs[0];
} else if (!obj2.isNull()) {
error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
}
}
obj2 = obj1.dictLookup("Font");
if (obj2.isArray()) {
GfxFont *font;
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();
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;
// check for excessive recursion
if (formDepth > 20) {
return;
}
// 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
++formDepth;
drawForm(str, resDict, m, bbox, true, true,
blendingColorSpace, isolated, knockout,
alpha, transferFunc, backdropColor);
--formDepth;
}
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 = new GfxDeviceGrayColorSpace();
}
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 = new GfxDeviceGrayColorSpace();
}
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 = new GfxDeviceCMYKColorSpace();
}
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 = new GfxDeviceCMYKColorSpace();
}
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 = new GfxDeviceRGBColorSpace();
}
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 = new GfxDeviceRGBColorSpace();
}
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];
if (fabs(det) < 0.000001) {
error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
return;
}
det = 1 / det;
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->getContentStream(),
tPat->getMatrix(), tPat->getPaintType(), tPat->getTilingType(),
tPat->getResDict(), m1, tPat->getBBox(),
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);
}
}
static void bubbleSort(double array[])
{
for (int j = 0; j < 3; ++j) {
int kk = j;
for (int k = j + 1; k < 4; ++k) {
if (array[k] < array[kk]) {
kk = k;
}
}
double tmp = array[j];
array[j] = array[kk];
array[kk] = tmp;
}
}
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;
bubbleSort(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);