diff options
author | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 |
---|---|---|
committer | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 |
commit | 835e373b3eeaabcd0621ed6798ab500f37982fae (patch) | |
tree | dfa16b0e2e1b4956b38f693220eac4e607802133 /xpdf/Gfx.cc |
Diffstat (limited to 'xpdf/Gfx.cc')
-rw-r--r-- | xpdf/Gfx.cc | 5386 |
1 files changed, 5386 insertions, 0 deletions
diff --git a/xpdf/Gfx.cc b/xpdf/Gfx.cc new file mode 100644 index 0000000..1539b84 --- /dev/null +++ b/xpdf/Gfx.cc @@ -0,0 +1,5386 @@ +//======================================================================== +// +// Gfx.cc +// +// Copyright 1996-2016 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <string.h> +#include <math.h> +#include "gmem.h" +#include "gmempp.h" +#include "GString.h" +#include "GList.h" +#include "Trace.h" +#include "GlobalParams.h" +#include "CharTypes.h" +#include "Object.h" +#include "PDFDoc.h" +#include "Array.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 "OptionalContent.h" +#include "Error.h" +#include "TextString.h" +#include "Gfx.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)) + +// Number of splits along the t axis for an axial shading fill. +#define axialSplits 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. +#define gouraudMaxDepth 6 + +// Max delta allowed in any color component for a Gouraud triangle +// shading fill. +#define gouraudColorDelta (dblToCol(1 / 256.0)) + +// 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(1 / 256.0)) + +// Max errors (undefined operator, wrong number of args) allowed before +// giving up on a content stream. +#define contentStreamErrorLimit 500 + +//------------------------------------------------------------------------ +// Operator table +//------------------------------------------------------------------------ + +#ifdef _WIN32 // this works around a bug in the VC7 compiler +# pragma optimize("",off) +#endif + +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, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, 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, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, 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}, +}; + +#ifdef _WIN32 // this works around a bug in the VC7 compiler +# pragma optimize("",on) +#endif + +#define numOps (sizeof(opTab) / sizeof(Operator)) + +//------------------------------------------------------------------------ +// GfxResources +//------------------------------------------------------------------------ + +GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) { + Object obj1, obj2; + Ref r; + + if (resDict) { + valid = gTrue; + + // build font dictionary + fonts = NULL; + resDict->lookupNF("Font", &obj1); + if (obj1.isRef()) { + obj1.fetch(xref, &obj2); + if (obj2.isDict()) { + r = obj1.getRef(); + fonts = new GfxFontDict(xref, &r, obj2.getDict()); + } + obj2.free(); + } else if (obj1.isDict()) { + fonts = new GfxFontDict(xref, NULL, obj1.getDict()); + } + obj1.free(); + + // get XObject dictionary + resDict->lookup("XObject", &xObjDict); + + // get color space dictionary + resDict->lookup("ColorSpace", &colorSpaceDict); + + // get pattern dictionary + resDict->lookup("Pattern", &patternDict); + + // get shading dictionary + resDict->lookup("Shading", &shadingDict); + + // get graphics state parameter dictionary + resDict->lookup("ExtGState", &gStateDict); + + // get properties dictionary + resDict->lookup("Properties", &propsDict); + + } else { + valid = gFalse; + fonts = NULL; + xObjDict.initNull(); + colorSpaceDict.initNull(); + patternDict.initNull(); + shadingDict.initNull(); + gStateDict.initNull(); + propsDict.initNull(); + } + + next = nextA; +} + +GfxResources::~GfxResources() { + if (fonts) { + delete fonts; + } + xObjDict.free(); + colorSpaceDict.free(); + patternDict.free(); + shadingDict.free(); + gStateDict.free(); + propsDict.free(); +} + +GfxFont *GfxResources::lookupFont(char *name) { + GfxFont *font; + 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 NULL; +} + +GfxFont *GfxResources::lookupFontByRef(Ref ref) { + GfxFont *font; + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->fonts) { + if ((font = resPtr->fonts->lookupByRef(ref))) { + return font; + } + } + } + error(errSyntaxError, -1, "Unknown font ref {0:d}.{1:d}", ref.num, ref.gen); + return NULL; +} + +GBool GfxResources::lookupXObject(const char *name, Object *obj) { + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->xObjDict.isDict()) { + if (!resPtr->xObjDict.dictLookup(name, obj)->isNull()) + return gTrue; + obj->free(); + } + } + error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name); + return gFalse; +} + +GBool GfxResources::lookupXObjectNF(const char *name, Object *obj) { + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->xObjDict.isDict()) { + if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull()) + return gTrue; + obj->free(); + } + } + error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name); + return gFalse; +} + +void GfxResources::lookupColorSpace(const char *name, Object *obj, + GBool inherit) { + GfxResources *resPtr; + + //~ should also test for G, RGB, and CMYK - but only in inline images (?) + if (!strcmp(name, "DeviceGray") || + !strcmp(name, "DeviceRGB") || + !strcmp(name, "DeviceCMYK")) { + obj->initNull(); + return; + } + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->colorSpaceDict.isDict()) { + if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) { + return; + } + obj->free(); + } + if (!inherit && valid) { + break; + } + } + obj->initNull(); +} + +GfxPattern *GfxResources::lookupPattern(const char *name + ) { + GfxResources *resPtr; + GfxPattern *pattern; + Object objRef, obj; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->patternDict.isDict()) { + if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) { + resPtr->patternDict.dictLookupNF(name, &objRef); + pattern = GfxPattern::parse(&objRef, &obj + ); + objRef.free(); + obj.free(); + return pattern; + } + obj.free(); + } + } + error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name); + return NULL; +} + +GfxShading *GfxResources::lookupShading(const char *name + ) { + GfxResources *resPtr; + GfxShading *shading; + Object obj; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->shadingDict.isDict()) { + if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) { + shading = GfxShading::parse(&obj + ); + obj.free(); + return shading; + } + obj.free(); + } + } + error(errSyntaxError, -1, "Unknown shading '{0:s}'", name); + return NULL; +} + +GBool GfxResources::lookupGState(const char *name, Object *obj) { + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->gStateDict.isDict()) { + if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) { + return gTrue; + } + obj->free(); + } + } + error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name); + return gFalse; +} + +GBool GfxResources::lookupPropertiesNF(const char *name, Object *obj) { + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->propsDict.isDict()) { + if (!resPtr->propsDict.dictLookupNF(name, obj)->isNull()) { + return gTrue; + } + obj->free(); + } + } + error(errSyntaxError, -1, "Properties '{0:s}' is unknown", name); + return gFalse; +} + +//------------------------------------------------------------------------ +// Gfx +//------------------------------------------------------------------------ + +Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict, + double hDPI, double vDPI, PDFRectangle *box, + PDFRectangle *cropBox, int rotate, + GBool (*abortCheckCbkA)(void *data), + void *abortCheckCbkDataA) { + int i; + + doc = docA; + xref = doc->getXRef(); + subPage = gFalse; + printCommands = globalParams->getPrintCommands(); + defaultFont = NULL; + + // start the resource stack + res = new GfxResources(xref, resDict, NULL); + + // initialize + out = outA; + state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown()); + fontChanged = gFalse; + clip = clipNone; + ignoreUndef = 0; + out->startPage(pageNum, state); + out->setDefaultCTM(state->getCTM()); + out->updateAll(state); + for (i = 0; i < 6; ++i) { + baseMatrix[i] = state->getCTM()[i]; + } + formDepth = 0; + markedContentStack = new GList(); + ocState = gTrue; + parser = NULL; + contentStreamStack = new GList(); + 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(); + } +} + +Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, + PDFRectangle *box, PDFRectangle *cropBox, + GBool (*abortCheckCbkA)(void *data), + void *abortCheckCbkDataA) { + int i; + + doc = docA; + xref = doc->getXRef(); + subPage = gTrue; + printCommands = globalParams->getPrintCommands(); + defaultFont = NULL; + + // start the resource stack + res = new GfxResources(xref, resDict, NULL); + + // initialize + out = outA; + state = new GfxState(72, 72, box, 0, gFalse); + fontChanged = gFalse; + clip = clipNone; + ignoreUndef = 0; + for (i = 0; i < 6; ++i) { + baseMatrix[i] = state->getCTM()[i]; + } + formDepth = 0; + markedContentStack = new GList(); + ocState = gTrue; + parser = NULL; + contentStreamStack = new GList(); + 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(); + } +} + +Gfx::~Gfx() { + if (defaultFont) { + delete defaultFont; + } + if (!subPage) { + out->endPage(); + } + while (state->hasSaves()) { + restoreState(); + } + delete state; + while (res) { + popResources(); + } + deleteGList(markedContentStack, GfxMarkedContent); + delete contentStreamStack; +} + +void Gfx::display(Object *objRef, GBool topLevel) { + Object obj1, obj2; + int i; + + objRef->fetch(xref, &obj1); + if (obj1.isArray()) { + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGetNF(i, &obj2); + if (checkForContentStreamLoop(&obj2)) { + obj2.free(); + obj1.free(); + return; + } + obj2.free(); + } + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGet(i, &obj2); + if (!obj2.isStream()) { + error(errSyntaxError, -1, "Invalid object type for content stream"); + obj2.free(); + obj1.free(); + return; + } + obj2.free(); + } + contentStreamStack->append(&obj1); + } else if (obj1.isStream()) { + if (checkForContentStreamLoop(objRef)) { + obj1.free(); + return; + } + contentStreamStack->append(objRef); + } else { + error(errSyntaxError, -1, "Invalid object type for content stream"); + obj1.free(); + return; + } + parser = new Parser(xref, new Lexer(xref, &obj1), gFalse); + go(topLevel); + delete parser; + parser = NULL; + contentStreamStack->del(contentStreamStack->getLength() - 1); + obj1.free(); +} + +// If <ref> is already on contentStreamStack, i.e., if there is a loop +// in the content streams, report an error, and return true. +GBool Gfx::checkForContentStreamLoop(Object *ref) { + Object *objPtr; + Object obj1; + int i, j; + + if (ref->isRef()) { + for (i = 0; i < contentStreamStack->getLength(); ++i) { + objPtr = (Object *)contentStreamStack->get(i); + if (objPtr->isRef()) { + if (ref->getRefNum() == objPtr->getRefNum() && + ref->getRefGen() == objPtr->getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + return gTrue; + } + } else if (objPtr->isArray()) { + for (j = 0; j < objPtr->arrayGetLength(); ++j) { + objPtr->arrayGetNF(j, &obj1); + if (obj1.isRef()) { + if (ref->getRefNum() == obj1.getRefNum() && + ref->getRefGen() == obj1.getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + obj1.free(); + return gTrue; + } + } + obj1.free(); + } + } + } + } + return gFalse; +} + +void Gfx::go(GBool topLevel) { + Object obj; + Object args[maxArgs]; + GBool aborted; + int numArgs, i; + int errCount; + + // scan a sequence of objects + opCounter = 0; + aborted = gFalse; + errCount = 0; + numArgs = 0; + getContentObj(&obj); + while (!obj.isEOF()) { + + // check for an abort + ++opCounter; + if (abortCheckCbk && opCounter > 100) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + aborted = gTrue; + break; + } + opCounter = 0; + } + + // 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); + } + if (!execOp(&obj, args, numArgs)) { + ++errCount; + } + obj.free(); + for (i = 0; i < numArgs; ++i) + args[i].free(); + numArgs = 0; + + // check for too many errors + if (errCount > contentStreamErrorLimit) { + error(errSyntaxError, -1, + "Too many errors - giving up on this content stream"); + break; + } + + // got an argument - save it + } else if (numArgs < maxArgs) { + args[numArgs++] = 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); + } + obj.free(); + } + + // grab the next object + getContentObj(&obj); + } + obj.free(); + + // args at end with no command + if (numArgs > 0) { + if (!aborted) { + 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); + } + } + for (i = 0; i < numArgs; ++i) { + args[i].free(); + } + } +} + +void Gfx::getContentObj(Object *obj) { + parser->getObj(obj); + if (obj->isRef()) { + error(errSyntaxError, getPos(), "Indirect reference in content stream"); + obj->free(); + obj->initError(); + } +} + +// Returns true if successful, false on error. +GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) { + Operator *op; + char *name; + Object *argPtr; + int i; + + // find operator + name = cmd->getCmd(); + if (!(op = findOp(name))) { + if (ignoreUndef > 0) { + return gTrue; + } + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); + return gFalse; + } + + // 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); + return gFalse; + } + 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(errSyntaxWarning, getPos(), + "Too many ({0:d}) args to '{1:s}' operator", + numArgs, name); + } + } + 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 gFalse; + } + } + + // do it + (this->*op->func)(argPtr, numArgs); + + return gTrue; +} + +Operator *Gfx::findOp(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 NULL; + return &opTab[a]; +} + +GBool 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 gFalse; + } + return gFalse; +} + +GFileOffset 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 = gTrue; +} + +void Gfx::opSetDash(Object args[], int numArgs) { + Array *a; + int length; + Object obj; + double *dash; + int i; + + a = args[0].getArray(); + length = a->getLength(); + if (length == 0) { + dash = NULL; + } else { + dash = (double *)gmallocn(length, sizeof(double)); + for (i = 0; i < length; ++i) { + dash[i] = a->get(i, &obj)->getNum(); + obj.free(); + } + } + 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) { + int lineJoin; + + lineJoin = args[0].getInt(); + if (lineJoin < 0 || lineJoin > 2) { + lineJoin = 0; + } + state->setLineJoin(lineJoin); + out->updateLineJoin(state); +} + +void Gfx::opSetLineCap(Object args[], int numArgs) { + int lineCap; + + lineCap = args[0].getInt(); + if (lineCap < 0 || lineCap > 2) { + lineCap = 0; + } + state->setLineCap(lineCap); + 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, obj3, objRef3, obj4, obj5, backdropColorObj; + Object args2[2]; + GfxBlendMode mode; + GBool haveFillOP; + Function *funcs[4]; + GBool alpha, knockout; + double opac; + int i; + + if (!res->lookupGState(args[0].getName(), &obj1)) { + return; + } + if (!obj1.isDict()) { + error(errSyntaxError, getPos(), + "ExtGState '{0:s}' is wrong type", args[0].getName()); + obj1.free(); + return; + } + if (printCommands) { + printf(" gfx state dict: "); + obj1.print(); + printf("\n"); + } + + // parameters that are also set by individual PDF operators + if (obj1.dictLookup("LW", &obj2)->isNum()) { + opSetLineWidth(&obj2, 1); + } + obj2.free(); + if (obj1.dictLookup("LC", &obj2)->isInt()) { + opSetLineCap(&obj2, 1); + } + obj2.free(); + if (obj1.dictLookup("LJ", &obj2)->isInt()) { + opSetLineJoin(&obj2, 1); + } + obj2.free(); + if (obj1.dictLookup("ML", &obj2)->isNum()) { + opSetMiterLimit(&obj2, 1); + } + obj2.free(); + if (obj1.dictLookup("D", &obj2)->isArray() && + obj2.arrayGetLength() == 2) { + obj2.arrayGet(0, &args2[0]); + obj2.arrayGet(1, &args2[1]); + if (args2[0].isArray() && args2[1].isNum()) { + opSetDash(args2, 2); + } + args2[0].free(); + args2[1].free(); + } + obj2.free(); + if (obj1.dictLookup("FL", &obj2)->isNum()) { + opSetFlat(&obj2, 1); + } + obj2.free(); + if (obj1.dictLookup("RI", &obj2)->isName()) { + opSetRenderingIntent(&obj2, 1); + } + obj2.free(); + + // font + if (obj1.dictLookup("Font", &obj2)->isArray() && + obj2.arrayGetLength() == 2) { + obj2.arrayGetNF(0, &obj3); + obj2.arrayGetNF(1, &obj4); + if (obj3.isRef() && obj4.isNum()) { + doSetFont(res->lookupFontByRef(obj3.getRef()), obj4.getNum()); + } + obj3.free(); + obj4.free(); + } + obj2.free(); + + // transparency support: blend mode, fill/stroke opacity + if (!obj1.dictLookup("BM", &obj2)->isNull()) { + if (state->parseBlendMode(&obj2, &mode)) { + state->setBlendMode(mode); + out->updateBlendMode(state); + } else { + error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState"); + } + } + obj2.free(); + if (obj1.dictLookup("ca", &obj2)->isNum()) { + opac = obj2.getNum(); + state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac); + out->updateFillOpacity(state); + } + obj2.free(); + if (obj1.dictLookup("CA", &obj2)->isNum()) { + opac = obj2.getNum(); + state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac); + out->updateStrokeOpacity(state); + } + obj2.free(); + + // fill/stroke overprint, overprint mode + if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) { + if (!state->getIgnoreColorOps()) { + state->setFillOverprint(obj2.getBool()); + out->updateFillOverprint(state); + } else { + error(errSyntaxWarning, getPos(), "Ignoring overprint setting" + " in uncolored Type 3 char or tiling pattern"); + } + } + obj2.free(); + if (obj1.dictLookup("OP", &obj2)->isBool()) { + if (!state->getIgnoreColorOps()) { + state->setStrokeOverprint(obj2.getBool()); + out->updateStrokeOverprint(state); + if (!haveFillOP) { + state->setFillOverprint(obj2.getBool()); + out->updateFillOverprint(state); + } + } else { + error(errSyntaxWarning, getPos(), "Ignoring overprint setting" + " in uncolored Type 3 char or tiling pattern"); + } + } + obj2.free(); + if (obj1.dictLookup("OPM", &obj2)->isInt()) { + if (!state->getIgnoreColorOps()) { + state->setOverprintMode(obj2.getInt()); + out->updateOverprintMode(state); + } else { + error(errSyntaxWarning, getPos(), "Ignoring overprint setting" + " in uncolored Type 3 char or tiling pattern"); + } + } + obj2.free(); + + // stroke adjust + if (obj1.dictLookup("SA", &obj2)->isBool()) { + state->setStrokeAdjust(obj2.getBool()); + out->updateStrokeAdjust(state); + } + obj2.free(); + + // transfer function + if (obj1.dictLookup("TR2", &obj2)->isNull()) { + obj2.free(); + obj1.dictLookup("TR", &obj2); + } + if (!obj2.isNull()) { + if (!state->getIgnoreColorOps()) { + if (obj2.isName("Default") || + obj2.isName("Identity")) { + funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL; + state->setTransfer(funcs); + out->updateTransfer(state); + } else if (obj2.isArray() && obj2.arrayGetLength() == 4) { + for (i = 0; i < 4; ++i) { + obj2.arrayGet(i, &obj3); + funcs[i] = Function::parse(&obj3, 1, 1); + obj3.free(); + 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, 1, 1))) { + funcs[1] = funcs[2] = funcs[3] = NULL; + state->setTransfer(funcs); + out->updateTransfer(state); + } + } else { + error(errSyntaxError, getPos(), + "Invalid transfer function in ExtGState"); + } + } else { + error(errSyntaxWarning, getPos(), "Ignoring transfer function setting" + " in uncolored Type 3 char or tiling pattern"); + } + } + obj2.free(); + + // soft mask + if (!obj1.dictLookup("SMask", &obj2)->isNull()) { + if (obj2.isName("None")) { + out->clearSoftMask(state); + } else if (obj2.isDict()) { + obj2.dictLookup("S", &obj3); + if (obj3.isName("Alpha")) { + alpha = gTrue; + } else if (obj3.isName("Luminosity")) { + alpha = gFalse; + } else { + error(errSyntaxError, getPos(), + "Missing S (subtype) entry in soft mask"); + alpha = gFalse; + } + obj3.free(); + funcs[0] = NULL; + if (!obj2.dictLookup("TR", &obj3)->isNull()) { + if (obj3.isName("Default") || + obj3.isName("Identity")) { + funcs[0] = NULL; + } else { + if (!(funcs[0] = Function::parse(&obj3, 1, 1))) { + error(errSyntaxError, getPos(), + "Invalid transfer function in soft mask in ExtGState"); + delete funcs[0]; + funcs[0] = NULL; + } + } + } + obj3.free(); + obj2.dictLookup("BC", &backdropColorObj); + if (obj2.dictLookup("G", &obj3)->isStream()) { + if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) { + knockout = gFalse; + if (obj4.dictLookup("K", &obj5)->isBool()) { + knockout = obj5.getBool(); + } + obj5.free(); + obj2.dictLookupNF("G", &objRef3); + // it doesn't make sense for softmasks to be non-isolated, + // because they're blended with a backdrop color, rather + // than the original backdrop + doSoftMask(&obj3, &objRef3, alpha, gTrue, knockout, funcs[0], + &backdropColorObj); + objRef3.free(); + if (funcs[0]) { + delete funcs[0]; + } + } else { + error(errSyntaxError, getPos(), + "Invalid soft mask in ExtGState - missing group"); + } + obj4.free(); + } else { + error(errSyntaxError, getPos(), + "Invalid soft mask in ExtGState - missing group"); + } + obj3.free(); + backdropColorObj.free(); + } else if (!obj2.isNull()) { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); + } + } + obj2.free(); + + obj1.free(); +} + +void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha, + GBool isolated, GBool knockout, + Function *transferFunc, Object *backdropColorObj) { + Dict *dict, *resDict; + double m[6], bbox[4]; + Object obj1, obj2; + int i; + + // check for excessive recursion + if (formDepth > 20) { + return; + } + + // get stream dict + dict = str->streamGetDict(); + + // check form type + dict->lookup("FormType", &obj1); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + obj1.free(); + + // get bounding box + dict->lookup("BBox", &obj1); + if (!obj1.isArray()) { + obj1.free(); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + obj1.arrayGet(i, &obj2); + bbox[i] = obj2.getNum(); + obj2.free(); + } + obj1.free(); + + // get matrix + dict->lookup("Matrix", &obj1); + if (obj1.isArray()) { + for (i = 0; i < 6; ++i) { + obj1.arrayGet(i, &obj2); + m[i] = obj2.getNum(); + obj2.free(); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + obj1.free(); + + // get resources + dict->lookup("Resources", &obj1); + resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL; + + // draw it + ++formDepth; + drawForm(strRef, resDict, m, bbox, gTrue, gTrue, isolated, knockout, + alpha, transferFunc, backdropColorObj); + --formDepth; + + obj1.free(); +} + +void Gfx::opSetRenderingIntent(Object args[], int numArgs) { + GfxRenderingIntent ri; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring rendering intent setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + ri = parseRenderingIntent(args[0].getName()); + state->setRenderingIntent(ri); + out->updateRenderingIntent(state); +} + +GfxRenderingIntent Gfx::parseRenderingIntent(const char *name) { + if (!strcmp(name, "AbsoluteColorimetric")) { + return gfxRenderingIntentAbsoluteColorimetric; + } + if (!strcmp(name, "Saturation")) { + return gfxRenderingIntentSaturation; + } + if (!strcmp(name, "Perceptual")) { + return gfxRenderingIntentPerceptual; + } + return gfxRenderingIntentRelativeColorimetric; +} + +//------------------------------------------------------------------------ +// color operators +//------------------------------------------------------------------------ + +void Gfx::opSetFillGray(Object args[], int numArgs) { + GfxColor color; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setFillPattern(NULL); + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); + 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; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); + 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; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setFillPattern(NULL); + state->setFillColorSpace(GfxColorSpace::create(csDeviceCMYK)); + 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; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); + 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) { + GfxColor color; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setFillPattern(NULL); + state->setFillColorSpace(GfxColorSpace::create(csDeviceRGB)); + 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) { + GfxColor color; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); + 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) { + Object obj; + GfxColorSpace *colorSpace; + GfxColor color; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color space setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setFillPattern(NULL); + res->lookupColorSpace(args[0].getName(), &obj); + if (obj.isNull()) { + colorSpace = GfxColorSpace::parse(&args[0] + ); + } else { + colorSpace = GfxColorSpace::parse(&obj + ); + } + obj.free(); + if (colorSpace) { + 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) { + Object obj; + GfxColorSpace *colorSpace; + GfxColor color; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color space setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + state->setStrokePattern(NULL); + res->lookupColorSpace(args[0].getName(), &obj); + if (obj.isNull()) { + colorSpace = GfxColorSpace::parse(&args[0] + ); + } else { + colorSpace = GfxColorSpace::parse(&obj + ); + } + obj.free(); + 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)"); + } +} + +// Technically, per the PDF spec, the 'sc' operator can only be used +// with device, CIE, and Indexed color spaces. Some buggy PDF +// generators use it for DeviceN, so we also allow that (same as +// 'scn', minus the optional pattern name argument). +void Gfx::opSetFillColor(Object args[], int numArgs) { + GfxColor color; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), + "Incorrect number of arguments in 'sc' command"); + return; + } + state->setFillPattern(NULL); + for (i = 0; i < numArgs; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + out->updateFillColor(state); +} + +// Technically, per the PDF spec, the 'SC' operator can only be used +// with device, CIE, and Indexed color spaces. Some buggy PDF +// generators use it for DeviceN, so we also allow that (same as +// 'SCN', minus the optional pattern name argument). +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(NULL); + 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->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + if (state->getFillColorSpace()->getMode() == csPattern) { + if (numArgs == 0 || !args[numArgs-1].isName()) { + error(errSyntaxError, getPos(), + "Invalid arguments in 'scn' command"); + return; + } + 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()); + } + } + state->setFillColor(&color); + out->updateFillColor(state); + } + if ((pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { + state->setFillPattern(pattern); + } + + } else { + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), + "Incorrect number of arguments in 'scn' command"); + return; + } + state->setFillPattern(NULL); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setFillColor(&color); + out->updateFillColor(state); + } +} + +void Gfx::opSetStrokeColorN(Object args[], int numArgs) { + GfxColor color; + GfxPattern *pattern; + int i; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring color setting" + " in uncolored Type 3 char or tiling pattern"); + return; + } + if (state->getStrokeColorSpace()->getMode() == csPattern) { + if (numArgs == 0 || !args[numArgs-1].isName()) { + error(errSyntaxError, getPos(), + "Invalid arguments in 'SCN' command"); + return; + } + 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()); + } + } + state->setStrokeColor(&color); + out->updateStrokeColor(state); + } + if ((pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { + state->setStrokePattern(pattern); + } + + } else { + if (numArgs != state->getStrokeColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), + "Incorrect number of arguments in 'SCN' command"); + return; + } + state->setStrokePattern(NULL); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + 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(gFalse); + } 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(gTrue); + } 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(gFalse); + } 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(gFalse); + } 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(gTrue); + } 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(gTrue); + } else { + out->eoFill(state); + } + if (state->getStrokeColorSpace()->getMode() == csPattern) { + doPatternStroke(); + } else { + out->stroke(state); + } + } + } + doEndPath(); +} + +void Gfx::doPatternFill(GBool 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, gFalse, eoFill, gFalse); + break; + case 2: + doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill, gFalse); + 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, gTrue, gFalse, gFalse); + break; + case 2: + doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse, gFalse); + 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, gFalse, gFalse, gTrue); + break; + case 2: + doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, gFalse, gTrue); + 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, + GBool invert, GBool inlineImg, GBool interpolate) { + saveState(); + + out->setSoftMaskFromImageMask(state, ref, str, + width, height, invert, inlineImg, interpolate); + + state->clearPath(); + state->moveTo(0, 0); + state->lineTo(1, 0); + state->lineTo(1, 1); + state->lineTo(0, 1); + state->closePath(); + doPatternFill(gTrue); + state->clearPath(); + + restoreState(); +} + +void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, + GBool stroke, GBool eoFill, GBool text) { + GfxPatternColorSpace *patCS; + GfxColorSpace *cs; + GfxColor color; + GfxState *savedState; + double xMin, yMin, xMax, yMax, x, y, x1, y1, t; + double cxMin, cyMin, cxMax, cyMax; + int xi0, yi0, xi1, yi1, xi, yi; + double *ctm, *btm, *ptm; + double bbox[4], m[6], ictm[6], m1[6], imb[6]; + double det; + double xstep, ystep; + int abortCheckCounter, 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) <= 1e-10) { + 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) <= 1e-10) { + 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(NULL); + state->setStrokePattern(NULL); + state->setFillOverprint(gFalse); + state->setStrokeOverprint(gFalse); + state->setOverprintMode(0); + 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); + state->setIgnoreColorOps(gTrue); + } else { + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); + out->updateFillColorSpace(state); + state->getFillColorSpace()->getDefaultColor(&color); + state->setFillColor(&color); + out->updateFillColor(state); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); + out->updateStrokeColorSpace(state); + state->getStrokeColorSpace()->getDefaultColor(&color); + state->setStrokeColor(&color); + out->updateStrokeColor(state); + } + if (!stroke) { + state->setLineWidth(0); + out->updateLineWidth(state); + state->setLineDash(NULL, 0, 0); + out->updateLineDash(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 err; + } + + // 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 (?) + bbox[0] = tPat->getBBox()[0]; + bbox[1] = tPat->getBBox()[1]; + bbox[2] = tPat->getBBox()[2]; + bbox[3] = tPat->getBBox()[3]; + if (bbox[0] > bbox[2]) { + t = bbox[0]; bbox[0] = bbox[2]; bbox[2] = t; + } + if (bbox[1] > bbox[3]) { + t = bbox[1]; bbox[1] = bbox[3]; bbox[3] = t; + } + xstep = fabs(tPat->getXStep()); + ystep = fabs(tPat->getYStep()); + xi0 = (int)ceil((xMin - bbox[2]) / xstep); + xi1 = (int)floor((xMax - bbox[0]) / xstep) + 1; + yi0 = (int)ceil((yMin - bbox[3]) / ystep); + yi1 = (int)floor((yMax - bbox[1]) / ystep) + 1; + for (i = 0; i < 4; ++i) { + m1[i] = m[i]; + } + if (out->useTilingPatternFill()) { + m1[4] = m[4]; + m1[5] = m[5]; + out->tilingPatternFill(state, this, tPat->getContentStreamRef(), + tPat->getPaintType(), tPat->getTilingType(), + tPat->getResDict(), + m1, bbox, + xi0, yi0, xi1, yi1, xstep, ystep); + } else { + abortCheckCounter = 0; + for (yi = yi0; yi < yi1; ++yi) { + for (xi = xi0; xi < xi1; ++xi) { + if (abortCheckCbk) { + ++abortCheckCounter; + if (abortCheckCounter > 100) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + goto err; + } + abortCheckCounter = 0; + } + } + 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->getContentStreamRef(), tPat->getResDict(), + m1, bbox); + } + } + } + + // restore graphics state + err: + restoreStateStack(savedState); +} + +void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, + GBool stroke, GBool eoFill, GBool text) { + GfxShading *shading; + GfxState *savedState; + 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); + state->setFillOverprint(state->getStrokeOverprint()); + } 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) <= 1e-10) { + 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(); + } + + // perform the fill + doShFill(shading); + + // restore graphics state + restoreStateStack(savedState); +} + +void Gfx::opShFill(Object args[], int numArgs) { + GfxShading *shading; + GfxState *savedState; + double xMin, yMin, xMax, yMax; + + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring shaded fill" + " in uncolored Type 3 char or tiling pattern"); + return; + } + + if (!out->needNonText()) { + return; + } + + if (!ocState) { + return; + } + + if (!(shading = res->lookupShading(args[0].getName() + ))) { + 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); + + // perform the fill + doShFill(shading); + + // restore graphics state + restoreStateStack(savedState); + + delete shading; +} + +void Gfx::doShFill(GfxShading *shading) { + if (out->shadedFill(state, shading)) { + return; + } + + // 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; + } +} + +void Gfx::doFunctionShFill(GfxFunctionShading *shading) { + double x0, y0, x1, y1; + GfxColor colors[4]; + + 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 *matrix; + double xM, yM; + int nComps, i, j; + + nComps = shading->getColorSpace()->getNComps(); + 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; + GBool dxdyZero, horiz; + double tMin, tMax, tMinExt, tMaxExt, t, tx, ty; + double sMin, sMax, tmp; + double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1; + double t0, t1, tt; + GfxColor colors[axialSplits]; + int abortCheckCounter, nComps, i, j, k; + + // 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; + dxdyZero = fabs(dx) < 0.0001 && fabs(dy) < 0.0001; + horiz = fabs(dy) < fabs(dx); + if (dxdyZero) { + tMinExt = tMaxExt = 0; + tMin = tMax = 0; + } else { + mul = 1 / (dx * dx + dy * dy); + tMinExt = tMaxExt = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; + t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; + if (t < tMinExt) { + tMinExt = t; + } else if (t > tMaxExt) { + tMaxExt = t; + } + t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; + if (t < tMinExt) { + tMinExt = t; + } else if (t > tMaxExt) { + tMaxExt = t; + } + t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; + if (t < tMinExt) { + tMinExt = t; + } else if (t > tMaxExt) { + tMaxExt = t; + } + if ((tMin = tMinExt) < 0) { + tMin = 0; + } + if (!shading->getExtend0()) { + tMinExt = tMin; + } + if ((tMax = tMaxExt) > 1) { + tMax = 1; + } + if (!shading->getExtend1()) { + tMaxExt = tMax; + } + } + + // 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). For -1 < |dy/dx| < 1, look at the + // intersection with yMin, yMax: + // + // s0 = (yMin - ty) / dx + // s1 = (yMax - ty) / dx + // + // else look at the intersection with xMin, xMax: + // + // s0 = (xMin - tx) / -dy + // s1 = (xMax - tx) / -dy + // + // 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. + + // 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 (dxdyZero) { + sMin = sMax = 0; + } else { + if (horiz) { + sMin = (yMin - ty) / dx; + sMax = (yMax - ty) / dx; + } else { + sMin = (xMin - tx) / -dy; + sMax = (xMax - tx) / -dy; + } + if (sMin > sMax) { + tmp = sMin; sMin = sMax; sMax = tmp; + } + } + ux0 = tx - sMin * dy; + uy0 = ty + sMin * dx; + vx0 = tx - sMax * dy; + vy0 = ty + sMax * dx; + + // fill the extension at t0 + if (shading->getExtend0() && tMinExt < tMin) { + + // compute the color at t0 + shading->getColor(t0, &colors[0]); + + // compute the coordinates of the point on the t axis at t = + // tMinExt; then compute the intersection of the perpendicular + // line with the bounding box + tx = x0 + tMinExt * dx; + ty = y0 + tMinExt * dy; + if (dxdyZero) { + sMin = sMax = 0; + } else { + if (horiz) { + sMin = (yMin - ty) / dx; + sMax = (yMax - ty) / dx; + } else { + sMin = (xMin - tx) / -dy; + sMax = (xMax - tx) / -dy; + } + if (sMin > sMax) { + tmp = sMin; sMin = sMax; sMax = tmp; + } + } + ux1 = tx - sMin * dy; + uy1 = ty + sMin * dx; + vx1 = tx - sMax * dy; + vy1 = ty + sMax * dx; + + // set the color + state->setFillColor(&colors[0]); + out->updateFillColor(state); + + // fill the region + state->moveTo(ux1, uy1); + state->lineTo(vx1, vy1); + state->lineTo(vx0, vy0); + state->lineTo(ux0, uy0); + state->closePath(); + out->fill(state); + state->clearPath(); + } + + // traverse the t axis, splitting [tMin, tMax] into axialSplits regions + + // compute the color in the center of each region + for (i = 0; i < axialSplits; ++i) { + t = tMin + (tMax - tMin) * (i + 0.5) / axialSplits; + tt = t0 + (t1 - t0) * t; + shading->getColor(tt, &colors[i]); + } + + // each iteration draws one or more regions, starting at i -- + // if the colors are similar, it will combine regions i, i+1, ... + nComps = shading->getColorSpace()->getNComps(); + abortCheckCounter = 0; + i = 0; + while (i < axialSplits) { + + if (abortCheckCbk) { + ++abortCheckCounter; + if (abortCheckCounter > 100) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + break; + } + abortCheckCounter = 0; + } + } + + // check for similar colors + for (j = i + 1; j < axialSplits; ++j) { + for (k = 0; k < nComps; ++k) { + if (abs(colors[j].c[k] - colors[i].c[k]) > axialColorDelta) { + break; + } + } + if (k < nComps) { + break; + } + } + + // compute the coordinates of the point on the t axis; then + // compute the intersection of the perpendicular line with the + // bounding box + t = tMin + (tMax - tMin) * (double)j / axialSplits; + tx = x0 + t * dx; + ty = y0 + t * dy; + if (dxdyZero) { + sMin = sMax = 0; + } else { + if (horiz) { + sMin = (yMin - ty) / dx; + sMax = (yMax - ty) / dx; + } else { + sMin = (xMin - tx) / -dy; + sMax = (xMax - tx) / -dy; + } + if (sMin > sMax) { + tmp = sMin; sMin = sMax; sMax = tmp; + } + } + ux1 = tx - sMin * dy; + uy1 = ty + sMin * dx; + vx1 = tx - sMax * dy; + vy1 = ty + sMax * dx; + + // set the color + state->setFillColor(&colors[i]); + out->updateFillColor(state); + + // fill the region + state->moveTo(ux0, uy0); + state->lineTo(vx0, vy0); + state->lineTo(vx1, vy1); + state->lineTo(ux1, uy1); + state->closePath(); + out->fill(state); + state->clearPath(); + + // set up for next region + ux0 = ux1; + uy0 = uy1; + vx0 = vx1; + vy0 = vy1; + + i = j; + } + + // fill the extension at t1 + if (shading->getExtend1() && tMaxExt > tMax) { + + // compute the color at t1 + shading->getColor(t1, &colors[0]); + + // compute the coordinates of the point on the t axis at t = + // tMaxExt; then compute the intersection of the perpendicular + // line with the bounding box + tx = x0 + tMaxExt * dx; + ty = y0 + tMaxExt * dy; + if (dxdyZero) { + sMin = sMax = 0; + } else { + if (horiz) { + sMin = (yMin - ty) / dx; + sMax = (yMax - ty) / dx; + } else { + sMin = (xMin - tx) / -dy; + sMax = (xMax - tx) / -dy; + } + if (sMin > sMax) { + tmp = sMin; sMin = sMax; sMax = tmp; + } + } + ux1 = tx - sMin * dy; + uy1 = ty + sMin * dx; + vx1 = tx - sMax * dy; + vy1 = ty + sMax * dx; + + // set the color + state->setFillColor(&colors[0]); + out->updateFillColor(state); + + // fill the region + state->moveTo(ux0, uy0); + state->lineTo(vx0, vy0); + state->lineTo(vx1, vy1); + state->lineTo(ux1, uy1); + state->closePath(); + out->fill(state); + state->clearPath(); + } +} + +#if defined(__GNUC__) && !defined(__clang__) +// this function makes a lot of sin()/cos() calls, which are slow +// with glibc 2.16 and newer on x86; accuracy isn't terribly +// important here, so tell gcc to use the fast version +#pragma GCC optimize ("fast-math") +#endif + +void Gfx::doRadialShFill(GfxRadialShading *shading) { + double xMin, yMin, xMax, yMax; + double x0, y0, r0, x1, y1, r1, t0, t1; + int nComps; + GfxColor colorA, colorB; + double xa, ya, xb, yb, ra, rb; + double ta, tb, sa, sb; + double sMin, sMax, h; + double sLeft, sRight, sTop, sBottom, sZero, sDiag; + GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; + GBool haveSMin, haveSMax; + GBool enclosed; + double *ctm; + double theta, alpha, angle, t; + int abortCheckCounter, ia, ib, k, n; + + // 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. + h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + if (h == 0) { + enclosed = gTrue; + theta = 0; // make gcc happy + } else if (r1 - r0 == 0) { + enclosed = gFalse; + theta = 0; + } else if (fabs(r1 - r0) >= h - 0.0001) { + enclosed = gTrue; + theta = 0; // make gcc happy + } else { + enclosed = gFalse; + theta = asin((r1 - r0) / h); + } + 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 { + // solve x(sLeft) + r(sLeft) = xMin + if ((haveSLeft = fabs((x1 + r1) - (x0 + r0)) > 0.000001)) { + sLeft = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0)); + } else { + sLeft = 0; // make gcc happy + } + // solve x(sRight) - r(sRight) = xMax + if ((haveSRight = fabs((x1 - r1) - (x0 - r0)) > 0.000001)) { + sRight = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0)); + } else { + sRight = 0; // make gcc happy + } + // solve y(sBottom) + r(sBottom) = yMin + if ((haveSBottom = fabs((y1 + r1) - (y0 + r0)) > 0.000001)) { + sBottom = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0)); + } else { + sBottom = 0; // make gcc happy + } + // solve y(sTop) - r(sTop) = yMax + if ((haveSTop = fabs((y1 - r1) - (y0 - r0)) > 0.000001)) { + sTop = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0)); + } else { + sTop = 0; // make gcc happy + } + // solve r(sZero) = 0 + if ((haveSZero = fabs(r1 - r0) > 0.000001)) { + sZero = -r0 / (r1 - r0); + } else { + sZero = 0; // make gcc happy + } + // solve r(sDiag) = sqrt((xMax-xMin)^2 + (yMax-yMin)^2) + if (haveSZero) { + sDiag = (sqrt((xMax - xMin) * (xMax - xMin) + + (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0); + } else { + sDiag = 0; // make gcc happy + } + // compute sMin + if (shading->getExtend0()) { + sMin = 0; + haveSMin = gFalse; + if (x0 < x1 && haveSLeft && sLeft < 0) { + sMin = sLeft; + haveSMin = gTrue; + } else if (x0 > x1 && haveSRight && sRight < 0) { + sMin = sRight; + haveSMin = gTrue; + } + if (y0 < y1 && haveSBottom && sBottom < 0) { + if (!haveSMin || sBottom > sMin) { + sMin = sBottom; + haveSMin = gTrue; + } + } else if (y0 > y1 && haveSTop && sTop < 0) { + if (!haveSMin || sTop > sMin) { + sMin = sTop; + haveSMin = gTrue; + } + } + if (haveSZero && sZero <= 0) { + if (!haveSMin || sZero > sMin) { + sMin = sZero; + } + } + } else { + sMin = 0; + } + // compute sMax + if (shading->getExtend1()) { + sMax = 1; + haveSMax = gFalse; + if (x1 < x0 && haveSLeft && sLeft > 1) { + sMax = sLeft; + haveSMax = gTrue; + } else if (x1 > x0 && haveSRight && sRight > 1) { + sMax = sRight; + haveSMax = gTrue; + } + if (y1 < y0 && haveSBottom && sBottom > 1) { + if (!haveSMax || sBottom < sMax) { + sMax = sBottom; + haveSMax = gTrue; + } + } else if (y1 > y0 && haveSTop && sTop > 1) { + if (!haveSMax || sTop < sMax) { + sMax = sTop; + haveSMax = gTrue; + } + } + if (haveSZero && sDiag > 1) { + if (!haveSMax || sDiag < sMax) { + sMax = sDiag; + } + } + } else { + sMax = 1; + } + } + + // 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) + 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 { + n = (int)(M_PI / acos(1 - 0.1 / t)); + 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); + if (ta < t0) { + shading->getColor(t0, &colorA); + } else if (ta > t1) { + shading->getColor(t1, &colorA); + } else { + shading->getColor(ta, &colorA); + } + + // fill the circles + abortCheckCounter = 0; + while (ia < radialMaxSplits) { + + if (abortCheckCbk) { + ++abortCheckCounter; + if (abortCheckCounter > 100) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + break; + } + abortCheckCounter = 0; + } + } + + // 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); + if (tb < t0) { + shading->getColor(t0, &colorB); + } else if (tb > t1) { + shading->getColor(t1, &colorB); + } else { + shading->getColor(tb, &colorB); + } + while (ib - ia > 1) { + for (k = 0; k < nComps; ++k) { + if (abs(colorB.c[k] - colorA.c[k]) > radialColorDelta) { + break; + } + } + if (k == nComps && ib < radialMaxSplits) { + break; + } + ib = (ia + ib) / 2; + sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin); + tb = t0 + sb * (t1 - t0); + if (tb < t0) { + shading->getColor(t0, &colorB); + } else if (tb > t1) { + shading->getColor(t1, &colorB); + } else { + shading->getColor(tb, &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] = (colorA.c[k] + colorB.c[k]) / 2; + } + state->setFillColor(&colorA); + out->updateFillColor(state); + + 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(); + } + + // 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 (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(); + } + } +} + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC reset_options +#endif + +void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) { + double x0, y0, x1, y1, x2, y2; + double color0[gfxColorMaxComps]; + double color1[gfxColorMaxComps]; + double color2[gfxColorMaxComps]; + int abortCheckCounter, i; + + abortCheckCounter = 0; + for (i = 0; i < shading->getNTriangles(); ++i) { + if (abortCheckCbk) { + ++abortCheckCounter; + if (abortCheckCounter > 25) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + break; + } + abortCheckCounter = 0; + } + } + shading->getTriangle(i, &x0, &y0, color0, + &x1, &y1, color1, + &x2, &y2, color2); + gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, + shading, 0); + } +} + +void Gfx::gouraudFillTriangle(double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading, int depth) { + double dx0, dy0, dx1, dy1, dx2, dy2; + double x01, y01, x12, y12, x20, y20; + double color01[gfxColorMaxComps]; + double color12[gfxColorMaxComps]; + double color20[gfxColorMaxComps]; + GfxColor c0, c1, c2; + int nComps, i; + + // recursion ends when: + // (1) color difference is smaller than gouraudColorDelta; or + // (2) triangles are smaller than 0.5 pixel (note that "device + // space" is 72dpi when generating PostScript); or + // (3) max recursion depth (gouraudMaxDepth) is hit. + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(color0, &c0); + shading->getColor(color1, &c1); + shading->getColor(color2, &c2); + for (i = 0; i < nComps; ++i) { + if (abs(c0.c[i] - c1.c[i]) > gouraudColorDelta || + abs(c1.c[i] - c2.c[i]) > gouraudColorDelta) { + break; + } + } + state->transformDelta(x1 - x0, y1 - y0, &dx0, &dy0); + state->transformDelta(x2 - x1, y2 - y1, &dx1, &dy1); + state->transformDelta(x0 - x2, y0 - y2, &dx2, &dy2); + if (i == nComps || + depth == gouraudMaxDepth || + (fabs(dx0) < 0.5 && fabs(dy0) < 0.5 && + fabs(dx1) < 0.5 && fabs(dy1) < 0.5 && + fabs(dx2) < 0.5 && fabs(dy2) < 0.5)) { + state->setFillColor(&c0); + out->updateFillColor(state); + state->moveTo(x0, y0); + state->lineTo(x1, y1); + state->lineTo(x2, y2); + state->closePath(); + out->fill(state); + state->clearPath(); + } 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 < shading->getNComps(); ++i) { + color01[i] = 0.5 * (color0[i] + color1[i]); + color12[i] = 0.5 * (color1[i] + color2[i]); + color20[i] = 0.5 * (color2[i] + color0[i]); + } + gouraudFillTriangle(x0, y0, color0, x01, y01, color01, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x1, y1, color1, + x12, y12, color12, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x12, y12, color12, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x20, y20, color20, x12, y12, color12, + x2, y2, color2, shading, depth + 1); + } +} + +void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) { + int start, abortCheckCounter, i; + + if (shading->getNPatches() > 128) { + start = 3; + } else if (shading->getNPatches() > 64) { + start = 2; + } else if (shading->getNPatches() > 16) { + start = 1; + } else { + start = 0; + } + abortCheckCounter = 0; + for (i = 0; i < shading->getNPatches(); ++i) { + if (abortCheckCbk) { + ++abortCheckCounter; + if (abortCheckCounter > 25) { + if ((*abortCheckCbk)(abortCheckCbkData)) { + break; + } + abortCheckCounter = 0; + } + } + fillPatch(shading->getPatch(i), shading, start); + } +} + +void Gfx::fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth) { + GfxPatch patch00, patch01, patch10, patch11; + GfxColor c00, c01, c10, c11; + double xx[4][8], yy[4][8]; + double x, y, xMin, yMin, xMax, yMax, xxm, yym; + int nComps, i, j; + GBool stop; + + shading->getColor(patch->color[0][0], &c00); + stop = gFalse; + + // stop subdivision at max depth + if (depth == patchMaxDepth) { + stop = gTrue; + } + + // stop subdivision if colors are close enough + if (!stop) { + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(patch->color[0][1], &c01); + shading->getColor(patch->color[1][0], &c10); + shading->getColor(patch->color[1][1], &c11); + for (i = 0; i < nComps; ++i) { + if (abs(c00.c[i] - c01.c[i]) > patchColorDelta || + abs(c01.c[i] - c11.c[i]) > patchColorDelta || + abs(c11.c[i] - c10.c[i]) > patchColorDelta || + abs(c10.c[i] - c00.c[i]) > patchColorDelta) { + break; + } + } + if (i == nComps) { + stop = gTrue; + } + } + + // stop subdivision if patch is small enough + if (!stop) { + xMin = yMin = xMax = yMax = 0; + for (j = 0; j < 4; ++j) { + for (i = 0; i < 4; ++i) { + state->transformDelta(patch->x[i][j], patch->y[i][j], &x, &y); + if (i == 0 && j == 0) { + xMin = xMax = x; + yMin = yMax = y; + } else { + if (x < xMin) { + xMin = x; + } else if (x > xMax) { + xMax = x; + } + if (y < yMin) { + yMin = y; + } else if (y > yMax) { + yMax = y; + } + } + } + } + if (xMax - xMin < 1 && yMax - yMin < 1) { + stop = gTrue; + } + } + + // draw the patch + if (stop) { + state->setFillColor(&c00); + 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(); + + // subdivide the patch + } 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 < shading->getNComps(); ++i) { + patch00.color[0][0][i] = patch->color[0][0][i]; + patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] + + patch->color[0][1][i]); + patch01.color[0][0][i] = patch00.color[0][1][i]; + patch01.color[0][1][i] = patch->color[0][1][i]; + patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] + + patch->color[1][1][i]); + patch11.color[0][1][i] = patch01.color[1][1][i]; + patch11.color[1][1][i] = patch->color[1][1][i]; + patch11.color[1][0][i] = 0.5 * (patch->color[1][1][i] + + patch->color[1][0][i]); + patch10.color[1][1][i] = patch11.color[1][0][i]; + patch10.color[1][0][i] = patch->color[1][0][i]; + patch10.color[0][0][i] = 0.5 * (patch->color[1][0][i] + + patch->color[0][0][i]); + patch00.color[1][0][i] = patch10.color[0][0][i]; + patch00.color[1][1][i] = 0.5 * (patch00.color[1][0][i] + + patch01.color[1][1][i]); + patch01.color[1][0][i] = patch00.color[1][1][i]; + patch11.color[0][0][i] = patch00.color[1][1][i]; + patch10.color[0][1][i] = patch00.color[1][1][i]; + } + fillPatch(&patch00, shading, depth + 1); + fillPatch(&patch10, shading, depth + 1); + fillPatch(&patch01, shading, depth + 1); + fillPatch(&patch11, shading, depth + 1); + } +} + +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) { + state->setTextMat(1, 0, 0, 1, 0, 0); + state->textMoveTo(0, 0); + out->updateTextMat(state); + out->updateTextPos(state); + fontChanged = gTrue; +} + +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) { + doSetFont(res->lookupFont(args[0].getName()), args[1].getNum()); +} + +void Gfx::doSetFont(GfxFont *font, double size) { + if (!font) { + if (!defaultFont) { + defaultFont = GfxFont::makeDefaultFont(xref); + } + font = defaultFont; + } + if (printCommands) { + printf(" font: tag=%s name='%s' %g\n", + font->getTag()->getCString(), + font->getName() ? font->getName()->getCString() : "???", + size); + fflush(stdout); + } + state->setFont(font, size); + fontChanged = gTrue; +} + +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 = gTrue; +} + +//------------------------------------------------------------------------ +// 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 = gTrue; +} + +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 = gFalse; + } + if (ocState) { + out->beginStringOp(state); + doShowText(args[0].getString()); + out->endStringOp(state); + } else { + 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 = gFalse; + } + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + out->updateTextPos(state); + if (ocState) { + out->beginStringOp(state); + doShowText(args[0].getString()); + out->endStringOp(state); + } else { + 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 = gFalse; + } + 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); + if (ocState) { + out->beginStringOp(state); + doShowText(args[2].getString()); + out->endStringOp(state); + } else { + doIncCharCount(args[2].getString()); + } +} + +void Gfx::opShowSpaceText(Object args[], int numArgs) { + Array *a; + Object obj; + int wMode; + int i; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in show/space"); + return; + } + if (fontChanged) { + out->updateFont(state); + fontChanged = gFalse; + } + if (ocState) { + out->beginStringOp(state); + wMode = state->getFont()->getWMode(); + a = args[0].getArray(); + for (i = 0; i < a->getLength(); ++i) { + a->get(i, &obj); + if (obj.isNum()) { + 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"); + } + obj.free(); + } + out->endStringOp(state); + } else { + a = args[0].getArray(); + for (i = 0; i < a->getLength(); ++i) { + a->get(i, &obj); + if (obj.isString()) { + doIncCharCount(obj.getString()); + } + obj.free(); + } + } +} + +void Gfx::doShowText(GString *s) { + GfxFont *font; + int wMode; + double riseX, riseY; + CharCode code; + Unicode u[8]; + double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy; + double originX, originY, tOriginX, tOriginY; + double x0, y0, x1, y1; + double oldCTM[6], newCTM[6]; + double *mat; + Object charProcRef, charProc; + Dict *resDict; + Parser *oldParser; + GfxState *savedState; + char *p; + int render; + GBool patternFill; + int len, n, uLen, nChars, nSpaces, i; + + font = state->getFont(); + wMode = font->getWMode(); + + if (globalParams->isDroppedFont(font->getName() + ? font->getName()->getCString() : "")) { + doIncCharCount(s); + return; + } + + 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 = gTrue; + 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 = gFalse; + } + + 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()) { + mat = state->getCTM(); + for (i = 0; i < 6; ++i) { + oldCTM[i] = mat[i]; + } + mat = state->getTextMat(); + newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2]; + newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3]; + newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2]; + newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3]; + mat = font->getFontMatrix(); + newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2]; + newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3]; + newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2]; + newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[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->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + u, (int)(sizeof(u) / sizeof(Unicode)), &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 0 + // The PDF spec says: "the graphics state shall be inherited + // from the environment of the text-showing operator that caused + // the [Type 3] glyph description to be invoked". However, + // Acrobat apparently copies the fill color to the stroke color. + // It looks like Ghostscript does the same. (This is only + // relevant for uncached Type 3 glyphs.) Uncomment this block + // to make Xpdf mimic Adobe (in violation of the PDF spec). + state->setStrokeColorSpace(state->getFillColorSpace()->copy()); + out->updateStrokeColorSpace(state); + state->setStrokeColor(state->getFillColor()); + out->updateStrokeColor(state); +#endif + if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, + code, u, uLen)) { + ((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef); + charProcRef.fetch(xref, &charProc); + if ((resDict = ((Gfx8BitFont *)font)->getResources())) { + pushResources(resDict); + } + if (charProc.isStream()) { + display(&charProcRef, gFalse); + } else { + error(errSyntaxError, getPos(), + "Missing or bad Type3 CharProc entry"); + } + out->endType3Char(state); + if (resDict) { + popResources(); + } + charProc.free(); + charProcRef.free(); + } + restoreStateStack(savedState); + curX += tdx; + curY += tdy; + state->moveTo(curX, curY); + p += n; + len -= n; + } + parser = oldParser; + + } else if (out->useDrawChar()) { + p = s->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + u, (int)(sizeof(u) / sizeof(Unicode)), &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); + 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->getCString(); + len = s->getLength(); + nChars = nSpaces = 0; + while (len > 0) { + n = font->getNextChar(p, len, &code, + u, (int)(sizeof(u) / sizeof(Unicode)), &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); + out->drawString(state, s); + state->shift(tdx, tdy); + } + + if (out->useDrawChar()) { + out->endString(state); + } + + if (patternFill) { + 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); + } + + opCounter += 10 * s->getLength(); +} + +// NB: this is only called when ocState is false. +void Gfx::doIncCharCount(GString *s) { + if (out->needCharCount()) { + out->incCharCount(s->getLength()); + } +} + +//------------------------------------------------------------------------ +// XObject operators +//------------------------------------------------------------------------ + +void Gfx::opXObject(Object args[], int numArgs) { + char *name; + Object xObj, refObj, obj2, obj3; + GBool ocSaved, oc; +#if OPI_SUPPORT + Object opiDict; +#endif + + if (!ocState && !out->needCharCount()) { + return; + } + name = args[0].getName(); + // NB: we get both the reference and the object here, to make sure + // they refer to the same object. There's a problematic corner + // case: if the resource dict contains an entry for [name] with a + // reference to a nonexistent object ("/name 99999 0 R", where + // object 99999 doesn't exist), and a parent resource dict contains + // a valid entry with the same name, then lookupXObjectNF() will + // return "99999 0 R", but lookupXObject() will return the valid + // entry. This causes problems for doImage() and doForm(). + if (!res->lookupXObjectNF(name, &refObj)) { + return; + } + if (!refObj.fetch(xref, &xObj)) { + refObj.free(); + return; + } + if (!xObj.isStream()) { + error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name); + xObj.free(); + refObj.free(); + return; + } + + // check for optional content key + ocSaved = ocState; + xObj.streamGetDict()->lookupNF("OC", &obj2); + if (doc->getOptionalContent()->evalOCObject(&obj2, &oc)) { + ocState &= oc; + } + obj2.free(); + +#if USE_EXCEPTIONS + try { +#endif +#if OPI_SUPPORT + xObj.streamGetDict()->lookup("OPI", &opiDict); + if (opiDict.isDict()) { + out->opiBegin(state, opiDict.getDict()); + } +#endif + xObj.streamGetDict()->lookup("Subtype", &obj2); + if (obj2.isName("Image")) { + if (out->needNonText()) { + doImage(&refObj, xObj.getStream(), gFalse); + } + } else if (obj2.isName("Form")) { + if (out->useDrawForm() && refObj.isRef()) { + if (ocState) { + out->drawForm(refObj.getRef()); + } + } else { + doForm(&refObj, &xObj); + } + } else if (obj2.isName("PS")) { + if (ocState) { + xObj.streamGetDict()->lookup("Level1", &obj3); + out->psXObject(xObj.getStream(), + obj3.isStream() ? obj3.getStream() : (Stream *)NULL); + } + } 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"); + } + obj2.free(); +#if OPI_SUPPORT + if (opiDict.isDict()) { + out->opiEnd(state, opiDict.getDict()); + } + opiDict.free(); +#endif +#if USE_EXCEPTIONS + } catch (GMemException e) { + xObj.free(); + refObj.free(); + throw; + } +#endif + + ocState = ocSaved; + + xObj.free(); + refObj.free(); +} + +GBool Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { + Dict *dict, *maskDict; + int width, height; + int bits, maskBits; + StreamColorSpaceMode csMode; + GBool mask, invert; + GfxColorSpace *colorSpace, *maskColorSpace; + GfxImageColorMap *colorMap, *maskColorMap; + Object maskObj, smaskObj, maskRef; + GBool haveColorKeyMask, haveExplicitMask, haveSoftMask, haveMatte; + int maskColors[2*gfxColorMaxComps]; + int maskWidth, maskHeight; + GBool maskInvert; + Stream *maskStr; + double matte[gfxColorMaxComps]; + GBool interpolate; + GfxRenderingIntent riSaved; + Object obj1, obj2; + int i, n; + + // check for optional content + if (!ocState && !inlineImg) { + return gTrue; + } + + // images can have arbitrarily high compression ratios, but the + // data size is inherently limited + str->disableDecompressionBombChecking(); + + // get info from the stream + bits = 0; + csMode = streamCSNone; + str->getImageParams(&bits, &csMode); + + // get stream dict + dict = str->getDict(); + + // save the current rendering intent + riSaved = state->getRenderingIntent(); + + // get size + dict->lookup("Width", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("W", &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + width = obj1.getInt(); + obj1.free(); + if (width <= 0) { + goto err1; + } + dict->lookup("Height", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("H", &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + height = obj1.getInt(); + obj1.free(); + if (height <= 0) { + goto err1; + } + + // image or mask? + dict->lookup("ImageMask", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("IM", &obj1); + } + mask = gFalse; + if (obj1.isBool()) + mask = obj1.getBool(); + else if (!obj1.isNull()) + goto err2; + obj1.free(); + + // bit depth + if (bits == 0) { + dict->lookup("BitsPerComponent", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("BPC", &obj1); + } + if (obj1.isInt()) { + bits = obj1.getInt(); + if (bits < 1 || bits > 16) { + goto err2; + } + } else if (mask) { + bits = 1; + } else { + goto err2; + } + obj1.free(); + } + + // interpolate flag + dict->lookup("Interpolate", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("I", &obj1); + } + interpolate = obj1.isBool() && obj1.getBool(); + obj1.free(); + + // display a mask + if (mask) { + + // check for inverted mask + if (bits != 1) + goto err1; + invert = gFalse; + dict->lookup("Decode", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("D", &obj1); + } + if (obj1.isArray()) { + obj1.arrayGet(0, &obj2); + invert = obj2.isNum() && obj2.getNum() == 1; + obj2.free(); + } else if (!obj1.isNull()) { + goto err2; + } + obj1.free(); + + // if drawing is disabled, skip over inline image data + if (!ocState) { + str->disableDecompressionBombChecking(); + 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, + interpolate); + } else { + out->drawImageMask(state, ref, str, width, height, invert, inlineImg, + interpolate); + } + } + + } else { + + // rendering intent + if (dict->lookup("Intent", &obj1)->isName()) { + opSetRenderingIntent(&obj1, 1); + } + obj1.free(); + + // get color space and color map + dict->lookup("ColorSpace", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("CS", &obj1); + } + if (obj1.isName()) { + res->lookupColorSpace(obj1.getName(), &obj2); + if (!obj2.isNull()) { + obj1.free(); + obj1 = obj2; + } else { + obj2.free(); + } + } + if (!obj1.isNull()) { + colorSpace = GfxColorSpace::parse(&obj1 + ); + } else if (csMode == streamCSDeviceGray) { + colorSpace = GfxColorSpace::create(csDeviceGray); + } else if (csMode == streamCSDeviceRGB) { + colorSpace = GfxColorSpace::create(csDeviceRGB); + } else if (csMode == streamCSDeviceCMYK) { + colorSpace = GfxColorSpace::create(csDeviceCMYK); + } else { + colorSpace = NULL; + } + obj1.free(); + if (!colorSpace) { + goto err1; + } + if (colorSpace->getMode() == csPattern) { + error(errSyntaxError, getPos(), "Image with a Pattern color space"); + delete colorSpace; + goto err1; + } + dict->lookup("Decode", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("D", &obj1); + } + colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); + obj1.free(); + if (!colorMap->isOk()) { + delete colorMap; + goto err1; + } + + // get the mask + haveColorKeyMask = haveExplicitMask = haveSoftMask = haveMatte = gFalse; + maskStr = NULL; // make gcc happy + maskWidth = maskHeight = 0; // make gcc happy + maskInvert = gFalse; // make gcc happy + maskColorMap = NULL; // make gcc happy + dict->lookup("Mask", &maskObj); + dict->lookup("SMask", &smaskObj); + if (smaskObj.isStream()) { + // soft mask + if (inlineImg) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err1; + } + maskStr = smaskObj.getStream(); + maskStr->disableDecompressionBombChecking(); + maskDict = smaskObj.streamGetDict(); + maskDict->lookup("Width", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("W", &obj1); + } + if (!obj1.isInt()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskWidth = obj1.getInt(); + obj1.free(); + maskDict->lookup("Height", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("H", &obj1); + } + if (!obj1.isInt()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskHeight = obj1.getInt(); + obj1.free(); + if (maskWidth <= 0 || maskHeight <= 0) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err1; + } + maskDict->lookup("BitsPerComponent", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("BPC", &obj1); + } + if (!obj1.isInt()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskBits = obj1.getInt(); + obj1.free(); + if (maskBits < 1 || maskBits > 16) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err1; + } + maskDict->lookup("ColorSpace", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("CS", &obj1); + } + if (obj1.isName()) { + res->lookupColorSpace(obj1.getName(), &obj2); + if (!obj2.isNull()) { + obj1.free(); + obj1 = obj2; + } else { + obj2.free(); + } + } + if (!obj1.isName("DeviceGray")) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskColorSpace = new GfxDeviceGrayColorSpace(); + obj1.free(); + maskDict->lookup("Decode", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("D", &obj1); + } + maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); + obj1.free(); + if (!maskColorMap->isOk()) { + delete maskColorMap; + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err1; + } + if (maskDict->lookup("Matte", &obj1)->isArray()) { + if (obj1.arrayGetLength() == colorSpace->getNComps()) { + for (i = 0; i < obj1.arrayGetLength(); ++i) { + if (obj1.arrayGet(i, &obj2)->isNum()) { + matte[i] = obj2.getNum(); + } else { + error(errSyntaxError, getPos(), + "Invalid Matte entry in soft mask"); + matte[i] = 0; + } + obj2.free(); + } + haveMatte = gTrue; + } else { + error(errSyntaxError, getPos(), "Invalid Matte entry in soft mask"); + } + } + obj1.free(); + haveSoftMask = gTrue; + } else if (maskObj.isArray()) { + // color key mask + haveColorKeyMask = gTrue; + for (i = 0; + i+1 < maskObj.arrayGetLength() && i+1 < 2*gfxColorMaxComps; + i += 2) { + maskObj.arrayGet(i, &obj1); + if (!obj1.isInt()) { + obj1.free(); + haveColorKeyMask = gFalse; + break; + } + maskColors[i] = obj1.getInt(); + obj1.free(); + if (maskColors[i] < 0 || maskColors[i] >= (1 << bits)) { + haveColorKeyMask = gFalse; + break; + } + maskObj.arrayGet(i+1, &obj1); + if (!obj1.isInt()) { + obj1.free(); + haveColorKeyMask = gFalse; + break; + } + maskColors[i+1] = obj1.getInt(); + obj1.free(); + if (maskColors[i+1] < 0 || maskColors[i+1] >= (1 << bits) || + maskColors[i] > maskColors[i+1]) { + haveColorKeyMask = gFalse; + break; + } + } + } else if (maskObj.isStream()) { + // explicit mask + if (inlineImg) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err1; + } + maskStr = maskObj.getStream(); + maskStr->disableDecompressionBombChecking(); + maskDict = maskObj.streamGetDict(); + maskDict->lookup("Width", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("W", &obj1); + } + if (!obj1.isInt()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskWidth = obj1.getInt(); + obj1.free(); + maskDict->lookup("Height", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("H", &obj1); + } + if (!obj1.isInt()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskHeight = obj1.getInt(); + obj1.free(); + if (maskWidth <= 0 || maskHeight <= 0) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + maskDict->lookup("ImageMask", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("IM", &obj1); + } + if (!obj1.isBool() || !obj1.getBool()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + obj1.free(); + maskInvert = gFalse; + maskDict->lookup("Decode", &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup("D", &obj1); + } + if (obj1.isArray()) { + obj1.arrayGet(0, &obj2); + maskInvert = obj2.isNum() && obj2.getNum() == 1; + obj2.free(); + } else if (!obj1.isNull()) { + delete colorMap; + maskObj.free(); + smaskObj.free(); + goto err2; + } + obj1.free(); + haveExplicitMask = gTrue; + } + + // if drawing is disabled, skip over inline image data + if (state->getIgnoreColorOps() || !ocState) { + if (state->getIgnoreColorOps()) { + error(errSyntaxWarning, getPos(), "Ignoring image" + " in uncolored Type 3 char or tiling pattern"); + } + if (inlineImg) { + str->disableDecompressionBombChecking(); + str->reset(); + n = height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8); + str->discardChars(n); + str->close(); + } + + // draw it + } else { + if (haveSoftMask) { + dict->lookupNF("SMask", &maskRef); + out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, + &maskRef, maskStr, maskWidth, maskHeight, + maskColorMap, + haveMatte ? matte : (double *)NULL, + interpolate); + maskRef.free(); + delete maskColorMap; + } else if (haveExplicitMask) { + dict->lookupNF("Mask", &maskRef); + out->drawMaskedImage(state, ref, str, width, height, colorMap, + &maskRef, maskStr, maskWidth, maskHeight, + maskInvert, interpolate); + maskRef.free(); + } else { + out->drawImage(state, ref, str, width, height, colorMap, + haveColorKeyMask ? maskColors : (int *)NULL, inlineImg, + interpolate); + } + } + + delete colorMap; + maskObj.free(); + smaskObj.free(); + } + + // restore rendering intent + if (state->getRenderingIntent() != riSaved) { + state->setRenderingIntent(riSaved); + out->updateRenderingIntent(state); + } + + if ((i = width * height) > 1000) { + i = 1000; + } + opCounter += i; + + return gTrue; + + err2: + obj1.free(); + err1: + error(errSyntaxError, getPos(), "Bad image parameters"); + + // restore rendering intent + if (state->getRenderingIntent() != riSaved) { + state->setRenderingIntent(riSaved); + out->updateRenderingIntent(state); + } + + return gFalse; +} + +void Gfx::doForm(Object *strRef, Object *str) { + Dict *dict; + GBool transpGroup, isolated, knockout; + Object matrixObj, bboxObj; + double m[6], bbox[4]; + Object resObj; + Dict *resDict; + Object obj1, obj2, obj3; + int i; + + // check for excessive recursion + if (formDepth > 100) { + return; + } + + // check for optional content + if (!ocState && !out->needCharCount()) { + return; + } + + // get stream dict + dict = str->streamGetDict(); + + // check form type + dict->lookup("FormType", &obj1); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + obj1.free(); + + // get bounding box + dict->lookup("BBox", &bboxObj); + if (!(bboxObj.isArray() && bboxObj.arrayGetLength() == 4)) { + bboxObj.free(); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + bboxObj.arrayGet(i, &obj1); + if (!obj1.isNum()) { + bboxObj.free(); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + bbox[i] = obj1.getNum(); + obj1.free(); + } + bboxObj.free(); + + // get matrix + dict->lookup("Matrix", &matrixObj); + if (matrixObj.isArray() && matrixObj.arrayGetLength() == 6) { + for (i = 0; i < 6; ++i) { + matrixObj.arrayGet(i, &obj1); + if (obj1.isNum()) { + m[i] = obj1.getNum(); + } else { + m[i] = 0; + } + obj1.free(); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + matrixObj.free(); + + // get resources + dict->lookup("Resources", &resObj); + resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; + + // check for a transparency group + transpGroup = isolated = knockout = gFalse; + if (dict->lookup("Group", &obj1)->isDict()) { + if (obj1.dictLookup("S", &obj2)->isName("Transparency")) { + transpGroup = gTrue; + if (obj1.dictLookup("I", &obj3)->isBool()) { + isolated = obj3.getBool(); + } + obj3.free(); + if (obj1.dictLookup("K", &obj3)->isBool()) { + knockout = obj3.getBool(); + } + obj3.free(); + } + obj2.free(); + } + obj1.free(); + + // draw it + ++formDepth; + drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, isolated, knockout); + --formDepth; + + resObj.free(); +} + +void Gfx::drawForm(Object *strRef, Dict *resDict, + double *matrix, double *bbox, + GBool transpGroup, GBool softMask, + GBool isolated, GBool knockout, + GBool alpha, Function *transferFunc, + Object *backdropColorObj) { + Parser *oldParser; + GfxState *savedState; + GfxColorSpace *blendingColorSpace; + GfxColor backdropColor; + Object strObj, groupAttrsObj, csObj, obj1; + double oldBaseMatrix[6]; + int i; + + // push new resources on stack + pushResources(resDict); + + // save current graphics state + saveState(); + + // 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(); + + blendingColorSpace = NULL; + if (softMask || transpGroup) { + // get the blending color space + // NB: this must be done AFTER pushing the resource dictionary, + // so that any Default*** color spaces are available + strRef->fetch(xref, &strObj); + if (strObj.streamGetDict()->lookup("Group", &groupAttrsObj)->isDict()) { + if (!groupAttrsObj.dictLookup("CS", &csObj)->isNull()) { + blendingColorSpace = GfxColorSpace::parse(&csObj + ); + } + csObj.free(); + } + groupAttrsObj.free(); + strObj.free(); + + if (!out->beginTransparencyGroup(state, bbox, blendingColorSpace, + isolated, knockout, softMask)) { + transpGroup = gFalse; + } + + if (softMask || transpGroup) { + traceBegin(oldBaseMatrix, softMask ? "begin soft mask" : "begin t-group"); + 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); + } + } + + // set new base matrix + for (i = 0; i < 6; ++i) { + oldBaseMatrix[i] = baseMatrix[i]; + baseMatrix[i] = state->getCTM()[i]; + } + + // save the state stack -- this handles the case where the form + // contents have unbalanced q/Q operators + savedState = saveStateStack(); + + // draw the form + display(strRef, gFalse); + + restoreStateStack(savedState); + + 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 + restoreState(); + + // pop resource stack + popResources(); + + if (softMask) { + for (i = 0; i < gfxColorMaxComps; ++i) { + backdropColor.c[i] = 0; + } + if (backdropColorObj->isArray()) { + for (i = 0; + i < backdropColorObj->arrayGetLength() && i < gfxColorMaxComps; + ++i) { + backdropColorObj->arrayGet(i, &obj1); + if (obj1.isNum()) { + backdropColor.c[i] = dblToCol(obj1.getNum()); + } + obj1.free(); + } + } else if (blendingColorSpace) { + blendingColorSpace->getDefaultColor(&backdropColor); + } + //~ else: need to get the parent or default color space (?) + out->setSoftMask(state, bbox, alpha, transferFunc, &backdropColor); + traceEnd(oldBaseMatrix, "end soft mask"); + } else if (transpGroup) { + out->paintTransparencyGroup(state, bbox); + traceEnd(oldBaseMatrix, "end t-group"); + } + + if (blendingColorSpace) { + delete blendingColorSpace; + } + + return; +} + +void Gfx::takeContentStreamStack(Gfx *oldGfx) { + contentStreamStack->append(oldGfx->contentStreamStack); +} + +void Gfx::endOfPage() { + while (state->hasSaves()) { + restoreState(); + } + while (markedContentStack->getLength() > 0) { + opEndMarkedContent(NULL, 0); + } +} + +//------------------------------------------------------------------------ +// in-line image operators +//------------------------------------------------------------------------ + +void Gfx::opBeginImage(Object args[], int numArgs) { + Stream *str; + GBool haveLength; + int c1, c2, c3; + + // 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(&haveLength); + + // display the image + if (str) { + if (!doImage(NULL, str, gTrue)) { + delete str; + + // if we have the stream length, skip to end-of-stream and then + // skip 'EI' in the original stream + } else if (haveLength) { + while ((c1 = str->getChar()) != EOF) ; + delete str; + str = parser->getStream(); + c1 = str->getChar(); + c2 = str->getChar(); + c3 = str->lookChar(); + while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) { + c1 = c2; + c2 = str->getChar(); + c3 = str->lookChar(); + } + + // else, look for the 'EI' tag and skip it + } else { + c1 = str->getUndecodedStream()->getChar(); + c2 = str->getUndecodedStream()->getChar(); + c3 = str->getUndecodedStream()->lookChar(); + while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) { + c1 = c2; + c2 = str->getUndecodedStream()->getChar(); + c3 = str->getUndecodedStream()->lookChar(); + } + delete str; + } + } +} + +Stream *Gfx::buildImageStream(GBool *haveLength) { + Object dict; + Object obj, lengthObj; + char *key; + int length; + Stream *str; + + // build dictionary + dict.initDict(xref); + getContentObj(&obj); + while (!obj.isCmd("ID") && !obj.isEOF()) { + if (!obj.isName()) { + error(errSyntaxError, getPos(), + "Inline image dictionary key must be a name object"); + obj.free(); + } else { + key = copyString(obj.getName()); + obj.free(); + getContentObj(&obj); + if (obj.isEOF()) { + gfree(key); + break; + } + if (obj.isError()) { + gfree(key); + obj.free(); + } else { + dict.dictAdd(key, &obj); + } + } + getContentObj(&obj); + } + if (obj.isEOF()) { + error(errSyntaxError, getPos(), "End of file in inline image"); + obj.free(); + dict.free(); + return NULL; + } + obj.free(); + + // check for length field + length = 0; + *haveLength = gFalse; + if (!dict.dictLookup("Length", &lengthObj)->isInt()) { + lengthObj.free(); + dict.dictLookup("L", &lengthObj); + } + if (lengthObj.isInt()) { + length = lengthObj.getInt(); + *haveLength = gTrue; + } + lengthObj.free(); + + // make stream + if (!(str = parser->getStream())) { + error(errSyntaxError, getPos(), "Invalid inline image data"); + dict.free(); + return NULL; + } + str = new EmbedStream(str, &dict, *haveLength, (GFileOffset)length); + str = str->addFilters(&dict); + + 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) { + state->setIgnoreColorOps(gTrue); + 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 +//------------------------------------------------------------------------ + +void Gfx::opBeginMarkedContent(Object args[], int numArgs) { + GfxMarkedContent *mc; + Object obj; + GBool ocStateNew; + TextString *s; + GfxMarkedContentKind mcKind; + + if (printCommands) { + printf(" marked content: %s ", args[0].getName()); + if (numArgs == 2) { + args[1].print(stdout); + } + printf("\n"); + fflush(stdout); + } + mcKind = gfxMCOther; + if (args[0].isName("OC") && numArgs == 2 && args[1].isName() && + res->lookupPropertiesNF(args[1].getName(), &obj)) { + if (doc->getOptionalContent()->evalOCObject(&obj, &ocStateNew)) { + ocState &= ocStateNew; + } + obj.free(); + mcKind = gfxMCOptionalContent; + } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) { + if (args[1].dictLookup("ActualText", &obj)->isString()) { + s = new TextString(obj.getString()); + out->beginActualText(state, s->getUnicode(), s->getLength()); + delete s; + mcKind = gfxMCActualText; + } + obj.free(); + } + mc = new GfxMarkedContent(mcKind, ocState); + markedContentStack->append(mc); +} + +void Gfx::opEndMarkedContent(Object args[], int numArgs) { + GfxMarkedContent *mc; + GfxMarkedContentKind mcKind; + + if (markedContentStack->getLength() > 0) { + mc = (GfxMarkedContent *) + markedContentStack->del(markedContentStack->getLength() - 1); + mcKind = mc->kind; + delete mc; + if (mcKind == gfxMCOptionalContent) { + if (markedContentStack->getLength() > 0) { + mc = (GfxMarkedContent *) + markedContentStack->get(markedContentStack->getLength() - 1); + ocState = mc->ocState; + } else { + ocState = gTrue; + } + } else if (mcKind == gfxMCActualText) { + out->endActualText(state); + } + } else { + error(errSyntaxWarning, getPos(), "Mismatched EMC operator"); + } +} + +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); + } +} + +//------------------------------------------------------------------------ +// misc +//------------------------------------------------------------------------ + +void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, + double xMin, double yMin, double xMax, double yMax) { + Dict *dict, *resDict; + Object str, matrixObj, bboxObj, resObj, obj1; + double formXMin, formYMin, formXMax, formYMax; + double x, y, sx, sy, tx, ty; + double m[6], bbox[4]; + double *borderColor; + GfxColor color; + double *dash, *dash2; + int dashLength; + 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; + } + + // draw the appearance stream (if there is one) + strRef->fetch(xref, &str); + if (str.isStream()) { + + // get stream dict + dict = str.streamGetDict(); + + // get the form bounding box + dict->lookup("BBox", &bboxObj); + if (!bboxObj.isArray() || bboxObj.arrayGetLength() != 4) { + error(errSyntaxError, getPos(), "Bad form bounding box"); + bboxObj.free(); + str.free(); + return; + } + for (i = 0; i < 4; ++i) { + bboxObj.arrayGet(i, &obj1); + if (obj1.isNum()) { + bbox[i] = obj1.getNum(); + } else { + bbox[i] = 0; + } + obj1.free(); + } + bboxObj.free(); + + // get the form matrix + dict->lookup("Matrix", &matrixObj); + if (matrixObj.isArray()) { + for (i = 0; i < 6; ++i) { + matrixObj.arrayGet(i, &obj1); + m[i] = obj1.getNum(); + obj1.free(); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + matrixObj.free(); + + // 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 + dict->lookup("Resources", &resObj); + resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; + + // draw it + drawForm(strRef, resDict, m, bbox); + + resObj.free(); + } + str.free(); + + // draw the border + if (borderStyle && borderStyle->getWidth() > 0 && + borderStyle->getNumColorComps() > 0) { + borderColor = borderStyle->getColor(); + switch (borderStyle->getNumColorComps()) { + case 1: + if (state->getStrokeColorSpace()->getMode() != csDeviceGray) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); + out->updateStrokeColorSpace(state); + } + break; + case 3: + if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); + out->updateStrokeColorSpace(state); + } + break; + case 4: + if (state->getStrokeColorSpace()->getMode() != csDeviceCMYK) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); + out->updateStrokeColorSpace(state); + } + break; + } + color.c[0] = dblToCol(borderColor[0]); + color.c[1] = dblToCol(borderColor[1]); + color.c[2] = dblToCol(borderColor[2]); + color.c[3] = dblToCol(borderColor[3]); + state->setStrokeColor(&color); + out->updateStrokeColor(state); + state->setLineWidth(borderStyle->getWidth()); + out->updateLineWidth(state); + borderStyle->getDash(&dash, &dashLength); + if (borderStyle->getType() == annotBorderDashed && dashLength > 0) { + dash2 = (double *)gmallocn(dashLength, sizeof(double)); + memcpy(dash2, dash, dashLength * sizeof(double)); + state->setLineDash(dash2, dashLength, 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 (borderStyle->getType() != annotBorderUnderlined) { + state->lineTo(xMax, yMax); + state->lineTo(xMin, yMax); + state->closePath(); + } + out->stroke(state); + } +} + +void Gfx::saveState() { + out->saveState(state); + state = state->save(); +} + +void Gfx::restoreState() { + state = state->restore(); + out->restoreState(state); +} + +// 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(gTrue); + 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; +} |