| //======================================================================== |
| // |
| // Gfx.cc |
| // |
| // Copyright 1996-2003 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com> |
| // Copyright (C) 2005-2010 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2006 Thorkild Stray <thorkild@ifi.uio.no> |
| // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com> |
| // Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net> |
| // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org> |
| // Copyright (C) 2007, 2011 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com> |
| // Copyright (C) 2007 Koji Otani <sho@bbr.jp> |
| // Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com> |
| // Copyright (C) 2008 Pino Toscano <pino@kde.org> |
| // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu> |
| // Copyright (C) 2008 Hib Eris <hib@hiberis.nl> |
| // Copyright (C) 2009 M Joonas Pihlaja <jpihlaja@cc.helsinki.fi> |
| // Copyright (C) 2009-2011 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2009 William Bader <williambader@hotmail.com> |
| // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu> |
| // Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com> |
| // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com> |
| // Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #ifdef USE_GCC_PRAGMAS |
| #pragma implementation |
| #endif |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <math.h> |
| #include "goo/gmem.h" |
| #include "goo/GooTimer.h" |
| #include "goo/GooHash.h" |
| #include "GlobalParams.h" |
| #include "CharTypes.h" |
| #include "Object.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 "Error.h" |
| #include "Gfx.h" |
| #include "ProfileData.h" |
| #include "Catalog.h" |
| #include "OptionalContent.h" |
| |
| // the MSVC math.h doesn't define this |
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
| |
| //------------------------------------------------------------------------ |
| // constants |
| //------------------------------------------------------------------------ |
| |
| // Max recursive depth for a function shading fill. |
| #define functionMaxDepth 6 |
| |
| // Max delta allowed in any color component for a function shading fill. |
| #define functionColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max number of splits along the t axis for an axial shading fill. |
| #define axialMaxSplits 256 |
| |
| // Max delta allowed in any color component for an axial shading fill. |
| #define axialColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max number of splits along the t axis for a radial shading fill. |
| #define radialMaxSplits 256 |
| |
| // Max delta allowed in any color component for a radial shading fill. |
| #define radialColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max recursive depth for a Gouraud triangle shading fill. |
| // |
| // Triangles will be split at most gouraudMaxDepth times (each time into 4 |
| // smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for |
| // every triangle. |
| #define gouraudMaxDepth 6 |
| |
| // Max delta allowed in any color component for a Gouraud triangle |
| // shading fill. |
| #define gouraudColorDelta (dblToCol(3. / 256.0)) |
| |
| // Gouraud triangle: if the three color parameters differ by at more than this percend of |
| // the total color parameter range, the triangle will be refined |
| #define gouraudParameterizedColorDelta 5e-3 |
| |
| // Max recursive depth for a patch mesh shading fill. |
| #define patchMaxDepth 6 |
| |
| // Max delta allowed in any color component for a patch mesh shading |
| // fill. |
| #define patchColorDelta (dblToCol((3. / 256.0))) |
| |
| //------------------------------------------------------------------------ |
| // Operator table |
| //------------------------------------------------------------------------ |
| |
| #ifdef _MSC_VER // 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}, |
| &Gfx::opSetStrokeColor}, |
| {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN}, |
| &Gfx::opSetStrokeColorN}, |
| {"T*", 0, {tchkNone}, |
| &Gfx::opTextNextLine}, |
| {"TD", 2, {tchkNum, tchkNum}, |
| &Gfx::opTextMoveSet}, |
| {"TJ", 1, {tchkArray}, |
| &Gfx::opShowSpaceText}, |
| {"TL", 1, {tchkNum}, |
| &Gfx::opSetTextLeading}, |
| {"Tc", 1, {tchkNum}, |
| &Gfx::opSetCharSpacing}, |
| {"Td", 2, {tchkNum, tchkNum}, |
| &Gfx::opTextMove}, |
| {"Tf", 2, {tchkName, tchkNum}, |
| &Gfx::opSetFont}, |
| {"Tj", 1, {tchkString}, |
| &Gfx::opShowText}, |
| {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, |
| tchkNum, tchkNum}, |
| &Gfx::opSetTextMatrix}, |
| {"Tr", 1, {tchkInt}, |
| &Gfx::opSetTextRender}, |
| {"Ts", 1, {tchkNum}, |
| &Gfx::opSetTextRise}, |
| {"Tw", 1, {tchkNum}, |
| &Gfx::opSetWordSpacing}, |
| {"Tz", 1, {tchkNum}, |
| &Gfx::opSetHorizScaling}, |
| {"W", 0, {tchkNone}, |
| &Gfx::opClip}, |
| {"W*", 0, {tchkNone}, |
| &Gfx::opEOClip}, |
| {"b", 0, {tchkNone}, |
| &Gfx::opCloseFillStroke}, |
| {"b*", 0, {tchkNone}, |
| &Gfx::opCloseEOFillStroke}, |
| {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum, |
| tchkNum, tchkNum}, |
| &Gfx::opCurveTo}, |
| {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, |
| tchkNum, tchkNum}, |
| &Gfx::opConcat}, |
| {"cs", 1, {tchkName}, |
| &Gfx::opSetFillColorSpace}, |
| {"d", 2, {tchkArray, tchkNum}, |
| &Gfx::opSetDash}, |
| {"d0", 2, {tchkNum, tchkNum}, |
| &Gfx::opSetCharWidth}, |
| {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum, |
| tchkNum, tchkNum}, |
| &Gfx::opSetCacheDevice}, |
| {"f", 0, {tchkNone}, |
| &Gfx::opFill}, |
| {"f*", 0, {tchkNone}, |
| &Gfx::opEOFill}, |
| {"g", 1, {tchkNum}, |
| &Gfx::opSetFillGray}, |
| {"gs", 1, {tchkName}, |
| &Gfx::opSetExtGState}, |
| {"h", 0, {tchkNone}, |
| &Gfx::opClosePath}, |
| {"i", 1, {tchkNum}, |
| &Gfx::opSetFlat}, |
| {"j", 1, {tchkInt}, |
| &Gfx::opSetLineJoin}, |
| {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, |
| &Gfx::opSetFillCMYKColor}, |
| {"l", 2, {tchkNum, tchkNum}, |
| &Gfx::opLineTo}, |
| {"m", 2, {tchkNum, tchkNum}, |
| &Gfx::opMoveTo}, |
| {"n", 0, {tchkNone}, |
| &Gfx::opEndPath}, |
| {"q", 0, {tchkNone}, |
| &Gfx::opSave}, |
| {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, |
| &Gfx::opRectangle}, |
| {"rg", 3, {tchkNum, tchkNum, tchkNum}, |
| &Gfx::opSetFillRGBColor}, |
| {"ri", 1, {tchkName}, |
| &Gfx::opSetRenderingIntent}, |
| {"s", 0, {tchkNone}, |
| &Gfx::opCloseStroke}, |
| {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, |
| &Gfx::opSetFillColor}, |
| {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN}, |
| &Gfx::opSetFillColorN}, |
| {"sh", 1, {tchkName}, |
| &Gfx::opShFill}, |
| {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, |
| &Gfx::opCurveTo1}, |
| {"w", 1, {tchkNum}, |
| &Gfx::opSetLineWidth}, |
| {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, |
| &Gfx::opCurveTo2}, |
| }; |
| |
| #ifdef _MSC_VER // this works around a bug in the VC7 compiler |
| # pragma optimize("",on) |
| #endif |
| |
| #define numOps (sizeof(opTab) / sizeof(Operator)) |
| |
| static inline GBool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, Guint nComps, double delta) { |
| for (Guint k = 0; k < nComps; ++k) { |
| if (abs(colorA.c[k] - colorB.c[k]) > delta) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| //------------------------------------------------------------------------ |
| // GfxResources |
| //------------------------------------------------------------------------ |
| |
| GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) : |
| gStateCache(2, xref) { |
| Object obj1, obj2; |
| Ref r; |
| |
| if (resDict) { |
| |
| // 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", &propertiesDict); |
| |
| } else { |
| fonts = NULL; |
| xObjDict.initNull(); |
| colorSpaceDict.initNull(); |
| patternDict.initNull(); |
| shadingDict.initNull(); |
| gStateDict.initNull(); |
| propertiesDict.initNull(); |
| } |
| |
| next = nextA; |
| } |
| |
| GfxResources::~GfxResources() { |
| if (fonts) { |
| delete fonts; |
| } |
| xObjDict.free(); |
| colorSpaceDict.free(); |
| patternDict.free(); |
| shadingDict.free(); |
| gStateDict.free(); |
| propertiesDict.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(-1, "Unknown font tag '%s'", name); |
| return NULL; |
| } |
| |
| GBool GfxResources::lookupXObject(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(-1, "XObject '%s' is unknown", name); |
| return gFalse; |
| } |
| |
| GBool GfxResources::lookupXObjectNF(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(-1, "XObject '%s' is unknown", name); |
| return gFalse; |
| } |
| |
| GBool GfxResources::lookupMarkedContentNF(char *name, Object *obj) { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->propertiesDict.isDict()) { |
| if (!resPtr->propertiesDict.dictLookupNF(name, obj)->isNull()) |
| return gTrue; |
| obj->free(); |
| } |
| } |
| error(-1, "Marked Content '%s' is unknown", name); |
| return gFalse; |
| } |
| |
| void GfxResources::lookupColorSpace(char *name, Object *obj) { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->colorSpaceDict.isDict()) { |
| if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) { |
| return; |
| } |
| obj->free(); |
| } |
| } |
| obj->initNull(); |
| } |
| |
| GfxPattern *GfxResources::lookupPattern(char *name, Gfx *gfx) { |
| GfxResources *resPtr; |
| GfxPattern *pattern; |
| Object obj; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->patternDict.isDict()) { |
| if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) { |
| pattern = GfxPattern::parse(&obj, gfx); |
| obj.free(); |
| return pattern; |
| } |
| obj.free(); |
| } |
| } |
| error(-1, "Unknown pattern '%s'", name); |
| return NULL; |
| } |
| |
| GfxShading *GfxResources::lookupShading(char *name, Gfx *gfx) { |
| 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, gfx); |
| obj.free(); |
| return shading; |
| } |
| obj.free(); |
| } |
| } |
| error(-1, "Unknown shading '%s'", name); |
| return NULL; |
| } |
| |
| GBool GfxResources::lookupGState(char *name, Object *obj) { |
| if (!lookupGStateNF(name, obj)) |
| return gFalse; |
| |
| if (!obj->isRef()) |
| return gTrue; |
| |
| const Ref ref = obj->getRef(); |
| if (!gStateCache.lookup(ref, obj)->isNull()) |
| return gTrue; |
| obj->free(); |
| |
| gStateCache.put(ref)->copy(obj); |
| return gTrue; |
| } |
| |
| GBool GfxResources::lookupGStateNF(char *name, Object *obj) { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->gStateDict.isDict()) { |
| if (!resPtr->gStateDict.dictLookupNF(name, obj)->isNull()) { |
| return gTrue; |
| } |
| obj->free(); |
| } |
| } |
| error(-1, "ExtGState '%s' is unknown", name); |
| return gFalse; |
| } |
| |
| //------------------------------------------------------------------------ |
| // Gfx |
| //------------------------------------------------------------------------ |
| |
| Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, Catalog *catalogA, |
| double hDPI, double vDPI, PDFRectangle *box, |
| PDFRectangle *cropBox, int rotate, |
| GBool (*abortCheckCbkA)(void *data), |
| void *abortCheckCbkDataA) |
| #ifdef USE_CMS |
| : iccColorSpaceCache(5) |
| #endif |
| { |
| int i; |
| |
| xref = xrefA; |
| catalog = catalogA; |
| subPage = gFalse; |
| printCommands = globalParams->getPrintCommands(); |
| profileCommands = globalParams->getProfileCommands(); |
| textHaveCSPattern = gFalse; |
| drawText = gFalse; |
| maskHaveCSPattern = gFalse; |
| mcStack = NULL; |
| parser = NULL; |
| |
| // start the resource stack |
| res = new GfxResources(xref, resDict, NULL); |
| |
| // initialize |
| out = outA; |
| state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown()); |
| stackHeight = 1; |
| pushStateGuard(); |
| 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; |
| 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(XRef *xrefA, OutputDev *outA, Dict *resDict, Catalog *catalogA, |
| PDFRectangle *box, PDFRectangle *cropBox, |
| GBool (*abortCheckCbkA)(void *data), |
| void *abortCheckCbkDataA) |
| #ifdef USE_CMS |
| : iccColorSpaceCache(5) |
| #endif |
| { |
| int i; |
| |
| xref = xrefA; |
| catalog = catalogA; |
| subPage = gTrue; |
| printCommands = globalParams->getPrintCommands(); |
| profileCommands = globalParams->getProfileCommands(); |
| textHaveCSPattern = gFalse; |
| drawText = gFalse; |
| maskHaveCSPattern = gFalse; |
| mcStack = NULL; |
| parser = NULL; |
| |
| // start the resource stack |
| res = new GfxResources(xref, resDict, NULL); |
| |
| // initialize |
| out = outA; |
| state = new GfxState(72, 72, box, 0, gFalse); |
| stackHeight = 1; |
| pushStateGuard(); |
| fontChanged = gFalse; |
| clip = clipNone; |
| ignoreUndef = 0; |
| for (i = 0; i < 6; ++i) { |
| baseMatrix[i] = state->getCTM()[i]; |
| } |
| formDepth = 0; |
| 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() { |
| while (stateGuards.size()) { |
| popStateGuard(); |
| } |
| // There shouldn't be more saves, but pop them if there were any |
| while (state->hasSaves()) { |
| error(-1, "Found state under last state guard. Popping."); |
| restoreState(); |
| } |
| if (!subPage) { |
| out->endPage(); |
| } |
| while (res) { |
| popResources(); |
| } |
| if (state) { |
| delete state; |
| } |
| while (mcStack) { |
| popMarkedContent(); |
| } |
| } |
| |
| void Gfx::display(Object *obj, GBool topLevel) { |
| Object obj2; |
| int i; |
| |
| if (obj->isArray()) { |
| for (i = 0; i < obj->arrayGetLength(); ++i) { |
| obj->arrayGet(i, &obj2); |
| if (!obj2.isStream()) { |
| error(-1, "Weird page contents"); |
| obj2.free(); |
| return; |
| } |
| obj2.free(); |
| } |
| } else if (!obj->isStream()) { |
| error(-1, "Weird page contents"); |
| return; |
| } |
| parser = new Parser(xref, new Lexer(xref, obj), gFalse); |
| go(topLevel); |
| delete parser; |
| parser = NULL; |
| } |
| |
| void Gfx::go(GBool topLevel) { |
| Object obj; |
| Object args[maxArgs]; |
| int numArgs, i; |
| int lastAbortCheck; |
| |
| // scan a sequence of objects |
| pushStateGuard(); |
| updateLevel = lastAbortCheck = 0; |
| numArgs = 0; |
| parser->getObj(&obj); |
| while (!obj.isEOF()) { |
| commandAborted = gFalse; |
| |
| // got a command - execute it |
| if (obj.isCmd()) { |
| if (printCommands) { |
| obj.print(stdout); |
| for (i = 0; i < numArgs; ++i) { |
| printf(" "); |
| args[i].print(stdout); |
| } |
| printf("\n"); |
| fflush(stdout); |
| } |
| GooTimer timer; |
| |
| // Run the operation |
| execOp(&obj, args, numArgs); |
| |
| // Update the profile information |
| if (profileCommands) { |
| GooHash *hash; |
| |
| hash = out->getProfileHash (); |
| if (hash) { |
| GooString *cmd_g; |
| ProfileData *data_p; |
| |
| cmd_g = new GooString (obj.getCmd()); |
| data_p = (ProfileData *)hash->lookup (cmd_g); |
| if (data_p == NULL) { |
| data_p = new ProfileData(); |
| hash->add (cmd_g, data_p); |
| } |
| |
| data_p->addElement(timer.getElapsed ()); |
| } |
| } |
| obj.free(); |
| for (i = 0; i < numArgs; ++i) |
| args[i].free(); |
| numArgs = 0; |
| |
| // periodically update display |
| if (++updateLevel >= 20000) { |
| out->dump(); |
| updateLevel = 0; |
| } |
| |
| // did the command throw an exception |
| if (commandAborted) { |
| // don't propogate; recursive drawing comes from Form XObjects which |
| // should probably be drawn in a separate context anyway for caching |
| commandAborted = gFalse; |
| break; |
| } |
| |
| // check for an abort |
| if (abortCheckCbk) { |
| if (updateLevel - lastAbortCheck > 10) { |
| if ((*abortCheckCbk)(abortCheckCbkData)) { |
| break; |
| } |
| lastAbortCheck = updateLevel; |
| } |
| } |
| |
| // got an argument - save it |
| } else if (numArgs < maxArgs) { |
| args[numArgs++] = obj; |
| |
| // too many arguments - something is wrong |
| } else { |
| error(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 |
| parser->getObj(&obj); |
| } |
| obj.free(); |
| |
| // args at end with no command |
| if (numArgs > 0) { |
| error(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(); |
| } |
| |
| popStateGuard(); |
| |
| // update display |
| if (topLevel && updateLevel > 0) { |
| out->dump(); |
| } |
| } |
| |
| void 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) |
| error(getPos(), "Unknown operator '%s'", name); |
| return; |
| } |
| |
| // type check args |
| argPtr = args; |
| if (op->numArgs >= 0) { |
| if (numArgs < op->numArgs) { |
| error(getPos(), "Too few (%d) args to '%s' operator", numArgs, name); |
| commandAborted = gTrue; |
| return; |
| } |
| if (numArgs > op->numArgs) { |
| #if 0 |
| error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name); |
| #endif |
| argPtr += numArgs - op->numArgs; |
| numArgs = op->numArgs; |
| } |
| } else { |
| if (numArgs > -op->numArgs) { |
| error(getPos(), "Too many (%d) args to '%s' operator", |
| numArgs, name); |
| return; |
| } |
| } |
| for (i = 0; i < numArgs; ++i) { |
| if (!checkArg(&argPtr[i], op->tchk[i])) { |
| error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)", |
| i, name, argPtr[i].getTypeName()); |
| return; |
| } |
| } |
| |
| // do it |
| (this->*op->func)(argPtr, numArgs); |
| } |
| |
| Operator *Gfx::findOp(char *name) { |
| int a, b, m, cmp; |
| |
| a = -1; |
| b = numOps; |
| // 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; |
| } |
| |
| int 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) { |
| state->setLineJoin(args[0].getInt()); |
| out->updateLineJoin(state); |
| } |
| |
| void Gfx::opSetLineCap(Object args[], int numArgs) { |
| state->setLineCap(args[0].getInt()); |
| out->updateLineCap(state); |
| } |
| |
| void Gfx::opSetMiterLimit(Object args[], int numArgs) { |
| state->setMiterLimit(args[0].getNum()); |
| out->updateMiterLimit(state); |
| } |
| |
| void Gfx::opSetLineWidth(Object args[], int numArgs) { |
| state->setLineWidth(args[0].getNum()); |
| out->updateLineWidth(state); |
| } |
| |
| void Gfx::opSetExtGState(Object args[], int numArgs) { |
| Object obj1, obj2, obj3, obj4, obj5; |
| GfxBlendMode mode; |
| GBool haveFillOP; |
| Function *funcs[4]; |
| GfxColor backdropColor; |
| GBool haveBackdropColor; |
| GfxColorSpace *blendingColorSpace; |
| GBool alpha, isolated, knockout; |
| int i; |
| |
| if (!res->lookupGState(args[0].getName(), &obj1)) { |
| return; |
| } |
| if (!obj1.isDict()) { |
| error(getPos(), "ExtGState '%s' is wrong type", args[0].getName()); |
| obj1.free(); |
| return; |
| } |
| if (printCommands) { |
| printf(" gfx state dict: "); |
| obj1.print(); |
| printf("\n"); |
| } |
| |
| // 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(getPos(), "Invalid blend mode in ExtGState"); |
| } |
| } |
| obj2.free(); |
| if (obj1.dictLookup("ca", &obj2)->isNum()) { |
| state->setFillOpacity(obj2.getNum()); |
| out->updateFillOpacity(state); |
| } |
| obj2.free(); |
| if (obj1.dictLookup("CA", &obj2)->isNum()) { |
| state->setStrokeOpacity(obj2.getNum()); |
| out->updateStrokeOpacity(state); |
| } |
| obj2.free(); |
| |
| // fill/stroke overprint |
| if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) { |
| state->setFillOverprint(obj2.getBool()); |
| out->updateFillOverprint(state); |
| } |
| obj2.free(); |
| if (obj1.dictLookup("OP", &obj2)->isBool()) { |
| state->setStrokeOverprint(obj2.getBool()); |
| out->updateStrokeOverprint(state); |
| if (!haveFillOP) { |
| state->setFillOverprint(obj2.getBool()); |
| out->updateFillOverprint(state); |
| } |
| } |
| obj2.free(); |
| if (obj1.dictLookup("OPM", &obj2)->isInt()) { |
| state->setOverprintMode(obj2.getInt()); |
| out->updateOverprintMode(state); |
| } |
| 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.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); |
| 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))) { |
| funcs[1] = funcs[2] = funcs[3] = NULL; |
| state->setTransfer(funcs); |
| out->updateTransfer(state); |
| } |
| } else if (!obj2.isNull()) { |
| error(getPos(), "Invalid transfer function in ExtGState"); |
| } |
| obj2.free(); |
| |
| // alpha is shape |
| if (obj1.dictLookup("AIS", &obj2)->isBool()) { |
| state->setAlphaIsShape(obj2.getBool()); |
| out->updateAlphaIsShape(state); |
| } |
| obj2.free(); |
| |
| // text knockout |
| if (obj1.dictLookup("TK", &obj2)->isBool()) { |
| state->setTextKnockout(obj2.getBool()); |
| out->updateTextKnockout(state); |
| } |
| obj2.free(); |
| |
| // soft mask |
| if (!obj1.dictLookup("SMask", &obj2)->isNull()) { |
| if (obj2.isName("None")) { |
| out->clearSoftMask(state); |
| } else if (obj2.isDict()) { |
| if (obj2.dictLookup("S", &obj3)->isName("Alpha")) { |
| alpha = gTrue; |
| } else { // "Luminosity" |
| alpha = gFalse; |
| } |
| obj3.free(); |
| funcs[0] = NULL; |
| if (!obj2.dictLookup("TR", &obj3)->isNull()) { |
| funcs[0] = Function::parse(&obj3); |
| if (funcs[0]->getInputSize() != 1 || |
| funcs[0]->getOutputSize() != 1) { |
| error(getPos(), |
| "Invalid transfer function in soft mask in ExtGState"); |
| delete funcs[0]; |
| funcs[0] = NULL; |
| } |
| } |
| obj3.free(); |
| if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) { |
| for (i = 0; i < gfxColorMaxComps; ++i) { |
| backdropColor.c[i] = 0; |
| } |
| for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { |
| obj3.arrayGet(i, &obj4); |
| if (obj4.isNum()) { |
| backdropColor.c[i] = dblToCol(obj4.getNum()); |
| } |
| obj4.free(); |
| } |
| } |
| obj3.free(); |
| if (obj2.dictLookup("G", &obj3)->isStream()) { |
| if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) { |
| blendingColorSpace = NULL; |
| isolated = knockout = gFalse; |
| if (!obj4.dictLookup("CS", &obj5)->isNull()) { |
| blendingColorSpace = GfxColorSpace::parse(&obj5, this); |
| } |
| obj5.free(); |
| if (obj4.dictLookup("I", &obj5)->isBool()) { |
| isolated = obj5.getBool(); |
| } |
| obj5.free(); |
| if (obj4.dictLookup("K", &obj5)->isBool()) { |
| knockout = obj5.getBool(); |
| } |
| obj5.free(); |
| if (!haveBackdropColor) { |
| if (blendingColorSpace) { |
| blendingColorSpace->getDefaultColor(&backdropColor); |
| } else { |
| //~ need to get the parent or default color space (?) |
| for (i = 0; i < gfxColorMaxComps; ++i) { |
| backdropColor.c[i] = 0; |
| } |
| } |
| } |
| doSoftMask(&obj3, alpha, blendingColorSpace, |
| isolated, knockout, funcs[0], &backdropColor); |
| if (funcs[0]) { |
| delete funcs[0]; |
| } |
| } else { |
| error(getPos(), "Invalid soft mask in ExtGState - missing group"); |
| } |
| obj4.free(); |
| } else { |
| error(getPos(), "Invalid soft mask in ExtGState - missing group"); |
| } |
| obj3.free(); |
| } else if (!obj2.isNull()) { |
| error(getPos(), "Invalid soft mask in ExtGState"); |
| } |
| } |
| obj2.free(); |
| if (obj1.dictLookup("Font", &obj2)->isArray()) { |
| GfxFont *font; |
| if (obj2.arrayGetLength() == 2) { |
| Object fargs0, fargs1; |
| |
| obj2.arrayGetNF(0,&fargs0); |
| obj2.arrayGet(1,&fargs1); |
| if (fargs0.isRef() && fargs1.isNum()) { |
| Object fobj; |
| Ref r; |
| |
| fargs0.fetch(xref, &fobj); |
| if (fobj.isDict()) { |
| r = fargs0.getRef(); |
| font = GfxFont::makeFont(xref,args[0].getName(),r,fobj.getDict()); |
| state->setFont(font,fargs1.getNum()); |
| fontChanged = gTrue; |
| } |
| fobj.free(); |
| } |
| fargs0.free(); |
| fargs1.free(); |
| } else { |
| error(getPos(), "Number of args mismatch for /Font in ExtGState"); |
| } |
| } |
| obj2.free(); |
| 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()) { |
| if (obj2.arrayGetLength() == 2) { |
| Object dargs[2]; |
| |
| obj2.arrayGetNF(0,&dargs[0]); |
| obj2.arrayGet(1,&dargs[1]); |
| if (dargs[0].isArray() && dargs[1].isInt()) { |
| opSetDash(dargs,2); |
| } |
| dargs[0].free(); |
| dargs[1].free(); |
| } else { |
| error(getPos(), "Number of args mismatch for /D in ExtGState"); |
| } |
| } |
| obj2.free(); |
| if (obj1.dictLookup("RI", &obj2)->isName()) { |
| opSetRenderingIntent(&obj2,1); |
| } |
| obj2.free(); |
| if (obj1.dictLookup("FL", &obj2)->isNum()) { |
| opSetFlat(&obj2,1); |
| } |
| obj2.free(); |
| |
| obj1.free(); |
| } |
| |
| void Gfx::doSoftMask(Object *str, GBool alpha, |
| GfxColorSpace *blendingColorSpace, |
| GBool isolated, GBool knockout, |
| Function *transferFunc, GfxColor *backdropColor) { |
| 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(getPos(), "Unknown form type"); |
| } |
| obj1.free(); |
| |
| // get bounding box |
| dict->lookup("BBox", &obj1); |
| if (!obj1.isArray()) { |
| obj1.free(); |
| error(getPos(), "Bad form bounding box"); |
| return; |
| } |
| for (i = 0; i < 4; ++i) { |
| obj1.arrayGet(i, &obj2); |
| if (likely(obj2.isNum())) bbox[i] = obj2.getNum(); |
| else { |
| obj2.free(); |
| obj1.free(); |
| error(getPos(), "Bad form bounding box (non number)"); |
| return; |
| } |
| obj2.free(); |
| } |
| obj1.free(); |
| |
| // get matrix |
| dict->lookup("Matrix", &obj1); |
| if (obj1.isArray()) { |
| for (i = 0; i < 6; ++i) { |
| obj1.arrayGet(i, &obj2); |
| if (likely(obj2.isNum())) m[i] = obj2.getNum(); |
| else m[i] = 0; |
| 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; |
| doForm1(str, resDict, m, bbox, gTrue, gTrue, |
| blendingColorSpace, isolated, knockout, |
| alpha, transferFunc, backdropColor); |
| --formDepth; |
| |
| if (blendingColorSpace) { |
| delete blendingColorSpace; |
| } |
| obj1.free(); |
| } |
| |
| void Gfx::opSetRenderingIntent(Object args[], int numArgs) { |
| } |
| |
| //------------------------------------------------------------------------ |
| // color operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opSetFillGray(Object args[], int numArgs) { |
| GfxColor color; |
| GfxColorSpace *colorSpace = NULL; |
| Object obj; |
| |
| if (textHaveCSPattern && drawText) { |
| GBool needFill = out->deviceHasTextClip(state); |
| out->endTextObject(state); |
| if (needFill) { |
| doPatternFill(gTrue); |
| } |
| out->restoreState(state); |
| } |
| state->setFillPattern(NULL); |
| res->lookupColorSpace("DefaultGray", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceGrayColorSpace(); |
| } |
| obj.free(); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| color.c[0] = dblToCol(args[0].getNum()); |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| if (textHaveCSPattern) { |
| out->beginTextObject(state); |
| out->updateRender(state); |
| out->updateTextMat(state); |
| out->updateTextPos(state); |
| textHaveCSPattern = gFalse; |
| } |
| } |
| |
| void Gfx::opSetStrokeGray(Object args[], int numArgs) { |
| GfxColor color; |
| GfxColorSpace *colorSpace = NULL; |
| Object obj; |
| |
| state->setStrokePattern(NULL); |
| res->lookupColorSpace("DefaultGray", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceGrayColorSpace(); |
| } |
| obj.free(); |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| color.c[0] = dblToCol(args[0].getNum()); |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillCMYKColor(Object args[], int numArgs) { |
| GfxColor color; |
| GfxColorSpace *colorSpace = NULL; |
| Object obj; |
| int i; |
| |
| if (textHaveCSPattern && drawText) { |
| GBool needFill = out->deviceHasTextClip(state); |
| out->endTextObject(state); |
| if (needFill) { |
| doPatternFill(gTrue); |
| } |
| out->restoreState(state); |
| } |
| res->lookupColorSpace("DefaultCMYK", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceCMYKColorSpace(); |
| } |
| obj.free(); |
| state->setFillPattern(NULL); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| for (i = 0; i < 4; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| if (textHaveCSPattern) { |
| out->beginTextObject(state); |
| out->updateRender(state); |
| out->updateTextMat(state); |
| out->updateTextPos(state); |
| textHaveCSPattern = gFalse; |
| } |
| } |
| |
| void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) { |
| GfxColor color; |
| GfxColorSpace *colorSpace = NULL; |
| Object obj; |
| int i; |
| |
| state->setStrokePattern(NULL); |
| res->lookupColorSpace("DefaultCMYK", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceCMYKColorSpace(); |
| } |
| obj.free(); |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| for (i = 0; i < 4; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillRGBColor(Object args[], int numArgs) { |
| Object obj; |
| GfxColorSpace *colorSpace = NULL; |
| GfxColor color; |
| int i; |
| |
| if (textHaveCSPattern && drawText) { |
| GBool needFill = out->deviceHasTextClip(state); |
| out->endTextObject(state); |
| if (needFill) { |
| doPatternFill(gTrue); |
| } |
| out->restoreState(state); |
| } |
| state->setFillPattern(NULL); |
| res->lookupColorSpace("DefaultRGB", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceRGBColorSpace(); |
| } |
| obj.free(); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| for (i = 0; i < 3; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| if (textHaveCSPattern) { |
| out->beginTextObject(state); |
| out->updateRender(state); |
| out->updateTextMat(state); |
| out->updateTextPos(state); |
| textHaveCSPattern = gFalse; |
| } |
| } |
| |
| void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) { |
| Object obj; |
| GfxColorSpace *colorSpace = NULL; |
| GfxColor color; |
| int i; |
| |
| state->setStrokePattern(NULL); |
| res->lookupColorSpace("DefaultRGB", &obj); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| if (colorSpace == NULL) { |
| colorSpace = new GfxDeviceRGBColorSpace(); |
| } |
| obj.free(); |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| for (i = 0; i < 3; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillColorSpace(Object args[], int numArgs) { |
| Object obj; |
| GfxColorSpace *colorSpace; |
| GfxColor color; |
| |
| res->lookupColorSpace(args[0].getName(), &obj); |
| if (obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&args[0], this); |
| } else { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| obj.free(); |
| if (colorSpace) { |
| if (textHaveCSPattern && drawText) { |
| GBool needFill = out->deviceHasTextClip(state); |
| out->endTextObject(state); |
| if (needFill) { |
| doPatternFill(gTrue); |
| } |
| out->restoreState(state); |
| } |
| state->setFillPattern(NULL); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| colorSpace->getDefaultColor(&color); |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| if (textHaveCSPattern) { |
| out->beginTextObject(state); |
| out->updateRender(state); |
| out->updateTextMat(state); |
| out->updateTextPos(state); |
| textHaveCSPattern = colorSpace->getMode() == csPattern; |
| } else if (drawText && out->supportTextCSPattern(state)) { |
| out->beginTextObject(state); |
| textHaveCSPattern = gTrue; |
| } |
| } else { |
| error(getPos(), "Bad color space (fill)"); |
| } |
| } |
| |
| void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) { |
| Object obj; |
| GfxColorSpace *colorSpace; |
| GfxColor color; |
| |
| state->setStrokePattern(NULL); |
| res->lookupColorSpace(args[0].getName(), &obj); |
| if (obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(&args[0], this); |
| } else { |
| colorSpace = GfxColorSpace::parse(&obj, this); |
| } |
| obj.free(); |
| if (colorSpace) { |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| colorSpace->getDefaultColor(&color); |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } else { |
| error(getPos(), "Bad color space (stroke)"); |
| } |
| } |
| |
| void Gfx::opSetFillColor(Object args[], int numArgs) { |
| GfxColor color; |
| int i; |
| |
| if (numArgs != state->getFillColorSpace()->getNComps()) { |
| error(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); |
| } |
| |
| void Gfx::opSetStrokeColor(Object args[], int numArgs) { |
| GfxColor color; |
| int i; |
| |
| if (numArgs != state->getStrokeColorSpace()->getNComps()) { |
| error(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->getFillColorSpace()->getMode() == csPattern) { |
| if (numArgs > 1) { |
| if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || |
| numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace()) |
| ->getUnder()->getNComps()) { |
| error(getPos(), "Incorrect number of arguments in 'scn' command"); |
| return; |
| } |
| for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| if (args[numArgs-1].isName() && |
| (pattern = res->lookupPattern(args[numArgs-1].getName(), this))) { |
| state->setFillPattern(pattern); |
| } |
| |
| } else { |
| if (numArgs != state->getFillColorSpace()->getNComps()) { |
| error(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()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| } |
| |
| void Gfx::opSetStrokeColorN(Object args[], int numArgs) { |
| GfxColor color; |
| GfxPattern *pattern; |
| int i; |
| |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| if (numArgs > 1) { |
| if (!((GfxPatternColorSpace *)state->getStrokeColorSpace()) |
| ->getUnder() || |
| numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace()) |
| ->getUnder()->getNComps()) { |
| error(getPos(), "Incorrect number of arguments in 'SCN' command"); |
| return; |
| } |
| for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| if (args[numArgs-1].isName() && |
| (pattern = res->lookupPattern(args[numArgs-1].getName(), this))) { |
| state->setStrokePattern(pattern); |
| } |
| |
| } else { |
| if (numArgs != state->getStrokeColorSpace()->getNComps()) { |
| error(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()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| } |
| |
| //------------------------------------------------------------------------ |
| // path segment operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opMoveTo(Object args[], int numArgs) { |
| state->moveTo(args[0].getNum(), args[1].getNum()); |
| } |
| |
| void Gfx::opLineTo(Object args[], int numArgs) { |
| if (!state->isCurPt()) { |
| error(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(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(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(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(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(getPos(), "No path in stroke"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) { |
| if (!state->isCurPt()) { |
| //error(getPos(), "No path in closepath/stroke"); |
| return; |
| } |
| state->closePath(); |
| if (state->isPath() && !contentIsHidden()) { |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opFill(Object args[], int numArgs) { |
| if (!state->isCurPt()) { |
| //error(getPos(), "No path in fill"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(gFalse); |
| } else { |
| out->fill(state); |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opEOFill(Object args[], int numArgs) { |
| if (!state->isCurPt()) { |
| //error(getPos(), "No path in eofill"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(gTrue); |
| } else { |
| out->eoFill(state); |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opFillStroke(Object args[], int numArgs) { |
| if (!state->isCurPt()) { |
| //error(getPos(), "No path in fill/stroke"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| 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(getPos(), "No path in closepath/fill/stroke"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| state->closePath(); |
| 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(getPos(), "No path in eofill/stroke"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| 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(getPos(), "No path in closepath/eofill/stroke"); |
| return; |
| } |
| if (state->isPath() && !contentIsHidden()) { |
| state->closePath(); |
| 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); |
| break; |
| case 2: |
| doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill); |
| break; |
| default: |
| error(getPos(), "Unimplemented pattern type (%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); |
| break; |
| case 2: |
| doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse); |
| break; |
| default: |
| error(getPos(), "Unimplemented pattern type (%d) in stroke", |
| pattern->getType()); |
| break; |
| } |
| } |
| |
| void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, |
| GBool stroke, GBool eoFill) { |
| GfxPatternColorSpace *patCS; |
| GfxColorSpace *cs; |
| GfxColor color; |
| GfxPath *savedPath; |
| double xMin, yMin, xMax, yMax, x, y, x1, y1; |
| double cxMin, cyMin, cxMax, cyMax; |
| int xi0, yi0, xi1, yi1, xi, yi; |
| double *ctm, *btm, *ptm; |
| double m[6], ictm[6], m1[6], imb[6]; |
| double det; |
| double xstep, ystep; |
| int i; |
| |
| // get color space |
| patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace() |
| : state->getFillColorSpace()); |
| |
| // construct a (pattern space) -> (current space) transform matrix |
| ctm = state->getCTM(); |
| btm = baseMatrix; |
| ptm = tPat->getMatrix(); |
| // iCTM = invert CTM |
| det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); |
| 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 = 1 / (m1[0] * m1[3] - m1[1] * m1[2]); |
| 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 |
| savedPath = state->getPath()->copy(); |
| saveState(); |
| |
| // set underlying color space (for uncolored tiling patterns); set |
| // various other parameters (stroke color, line width) to match |
| // Adobe's behavior |
| 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()); |
| } |
| } else { |
| cs = new GfxDeviceGrayColorSpace(); |
| state->setFillColorSpace(cs); |
| cs->getDefaultColor(&color); |
| state->setFillColor(&color); |
| out->updateFillColorSpace(state); |
| state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); |
| state->setStrokeColor(&color); |
| out->updateStrokeColorSpace(state); |
| } |
| state->setFillPattern(NULL); |
| out->updateFillColor(state); |
| state->setStrokePattern(NULL); |
| out->updateStrokeColor(state); |
| |
| // clip to current path |
| if (stroke) { |
| state->clipToStrokePath(); |
| out->clipToStrokePath(state); |
| } else if (!textHaveCSPattern && !maskHaveCSPattern) { |
| state->clip(); |
| if (eoFill) { |
| out->eoClip(state); |
| } else { |
| out->clip(state); |
| } |
| } |
| state->clearPath(); |
| state->setLineWidth(0); |
| out->updateLineWidth(state); |
| |
| // get the clip region, check for empty |
| state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax); |
| if (cxMin > cxMax || cyMin > cyMax) { |
| goto restore; |
| } |
| |
| // transform clip region bbox to pattern space |
| xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4]; |
| yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5]; |
| x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4]; |
| y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4]; |
| y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4]; |
| y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| |
| // draw the pattern |
| //~ this should treat negative steps differently -- start at right/top |
| //~ edge instead of left/bottom (?) |
| xstep = fabs(tPat->getXStep()); |
| ystep = fabs(tPat->getYStep()); |
| xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep); |
| xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1; |
| yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep); |
| yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1; |
| for (i = 0; i < 4; ++i) { |
| m1[i] = m[i]; |
| } |
| if (!contentIsHidden()) { |
| m1[4] = m[4]; |
| m1[5] = m[5]; |
| if (out->useTilingPatternFill() && |
| out->tilingPatternFill(state, catalog, tPat->getContentStream(), |
| tPat->getMatrix(), tPat->getPaintType(), tPat->getTilingType(), |
| tPat->getResDict(), m1, tPat->getBBox(), |
| xi0, yi0, xi1, yi1, xstep, ystep)) { |
| goto restore; |
| } else { |
| for (yi = yi0; yi < yi1; ++yi) { |
| for (xi = xi0; xi < xi1; ++xi) { |
| x = xi * xstep; |
| y = yi * ystep; |
| m1[4] = x * m[0] + y * m[2] + m[4]; |
| m1[5] = x * m[1] + y * m[3] + m[5]; |
| doForm1(tPat->getContentStream(), tPat->getResDict(), |
| m1, tPat->getBBox()); |
| } |
| } |
| } |
| } |
| |
| // restore graphics state |
| restore: |
| restoreState(); |
| state->setPath(savedPath); |
| } |
| |
| void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, |
| GBool stroke, GBool eoFill) { |
| GfxShading *shading; |
| GfxPath *savedPath; |
| 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 |
| savedPath = state->getPath()->copy(); |
| saveState(); |
| |
| // 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(); |
| if (!textHaveCSPattern && !maskHaveCSPattern) |
| out->clip(state); |
| state->setPath(savedPath->copy()); |
| } |
| |
| // clip to current path |
| if (stroke) { |
| state->clipToStrokePath(); |
| out->clipToStrokePath(state); |
| } else if (!textHaveCSPattern && !maskHaveCSPattern) { |
| state->clip(); |
| if (eoFill) { |
| out->eoClip(state); |
| } else { |
| out->clip(state); |
| } |
| } |
| |
| // 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); |
| if (!contentIsHidden()) |
| out->fill(state); |
| } |
| state->clearPath(); |
| |
| // construct a (pattern space) -> (current space) transform matrix |
| ctm = state->getCTM(); |
| btm = baseMatrix; |
| ptm = sPat->getMatrix(); |
| // iCTM = invert CTM |
| det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); |
| 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]); |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| GBool vaa = out->getVectorAntialias(); |
| if (vaa) { |
| out->setVectorAntialias(gFalse); |
| } |
| #endif |
| |
| // do shading type-specific operations |
| switch (shading->getType()) { |
| case 1: |
| doFunctionShFill((GfxFunctionShading *)shading); |
| break; |
| case 2: |
| doAxialShFill((GfxAxialShading *)shading); |
| break; |
| case 3: |
| doRadialShFill((GfxRadialShading *)shading); |
| break; |
| case 4: |
| case 5: |
| doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading); |
| break; |
| case 6: |
| case 7: |
| doPatchMeshShFill((GfxPatchMeshShading *)shading); |
| break; |
| } |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| if (vaa) { |
| out->setVectorAntialias(gTrue); |
| } |
| #endif |
| |
| // restore graphics state |
| restoreState(); |
| state->setPath(savedPath); |
| } |
| |
| void Gfx::opShFill(Object args[], int numArgs) { |
| GfxShading *shading; |
| GfxPath *savedPath; |
| double xMin, yMin, xMax, yMax; |
| |
| if (!(shading = res->lookupShading(args[0].getName(), this))) { |
| return; |
| } |
| |
| // save current graphics state |
| savedPath = state->getPath()->copy(); |
| saveState(); |
| |
| // clip to bbox |
| if (shading->getHasBBox()) { |
| shading->getBBox(&xMin, &yMin, &xMax, &yMax); |
| state->moveTo(xMin, yMin); |
| state->lineTo(xMax, yMin); |
| state->lineTo(xMax, yMax); |
| state->lineTo(xMin, yMax); |
| state->closePath(); |
| state->clip(); |
| out->clip(state); |
| state->clearPath(); |
| } |
| |
| // set the color space |
| state->setFillColorSpace(shading->getColorSpace()->copy()); |
| out->updateFillColorSpace(state); |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| GBool vaa = out->getVectorAntialias(); |
| if (vaa) { |
| out->setVectorAntialias(gFalse); |
| } |
| #endif |
| |
| // do shading type-specific operations |
| switch (shading->getType()) { |
| case 1: |
| doFunctionShFill((GfxFunctionShading *)shading); |
| break; |
| case 2: |
| doAxialShFill((GfxAxialShading *)shading); |
| break; |
| case 3: |
| doRadialShFill((GfxRadialShading *)shading); |
| break; |
| case 4: |
| case 5: |
| doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading); |
| break; |
| case 6: |
| case 7: |
| doPatchMeshShFill((GfxPatchMeshShading *)shading); |
| break; |
| } |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| if (vaa) { |
| out->setVectorAntialias(gTrue); |
| } |
| #endif |
| |
| // restore graphics state |
| restoreState(); |
| state->setPath(savedPath); |
| |
| delete shading; |
| } |
| |
| void Gfx::doFunctionShFill(GfxFunctionShading *shading) { |
| double x0, y0, x1, y1; |
| GfxColor colors[4]; |
| |
| if (out->useShadedFills( shading->getType() ) && |
| out->functionShadedFill(state, shading)) { |
| return; |
| } |
| |
| shading->getDomain(&x0, &y0, &x1, &y1); |
| shading->getColor(x0, y0, &colors[0]); |
| shading->getColor(x0, y1, &colors[1]); |
| shading->getColor(x1, y0, &colors[2]); |
| shading->getColor(x1, y1, &colors[3]); |
| doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0); |
| } |
| |
| void Gfx::doFunctionShFill1(GfxFunctionShading *shading, |
| double x0, double y0, |
| double x1, double y1, |
| GfxColor *colors, int depth) { |
| GfxColor fillColor; |
| GfxColor color0M, color1M, colorM0, colorM1, colorMM; |
| GfxColor colors2[4]; |
| double *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(); |
| if (!contentIsHidden()) |
| out->fill(state); |
| state->clearPath(); |
| |
| // the four corner colors are not close enough -- subdivide the |
| // rectangle |
| } else { |
| |
| // colors[0] colorM0 colors[2] |
| // (x0,y0) (xM,y0) (x1,y0) |
| // +----------+----------+ |
| // | | | |
| // | UL | UR | |
| // color0M | colorMM | color1M |
| // (x0,yM) +----------+----------+ (x1,yM) |
| // | (xM,yM) | |
| // | LL | LR | |
| // | | | |
| // +----------+----------+ |
| // colors[1] colorM1 colors[3] |
| // (x0,y1) (xM,y1) (x1,y1) |
| |
| shading->getColor(x0, yM, &color0M); |
| shading->getColor(x1, yM, &color1M); |
| shading->getColor(xM, y0, &colorM0); |
| shading->getColor(xM, y1, &colorM1); |
| shading->getColor(xM, yM, &colorMM); |
| |
| // upper-left sub-rectangle |
| colors2[0] = colors[0]; |
| colors2[1] = color0M; |
| colors2[2] = colorM0; |
| colors2[3] = colorMM; |
| doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1); |
| |
| // lower-left sub-rectangle |
| colors2[0] = color0M; |
| colors2[1] = colors[1]; |
| colors2[2] = colorMM; |
| colors2[3] = colorM1; |
| doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1); |
| |
| // upper-right sub-rectangle |
| colors2[0] = colorM0; |
| colors2[1] = colorMM; |
| colors2[2] = colors[2]; |
| colors2[3] = color1M; |
| doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1); |
| |
| // lower-right sub-rectangle |
| colors2[0] = colorMM; |
| colors2[1] = colorM1; |
| colors2[2] = color1M; |
| colors2[3] = colors[3]; |
| doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1); |
| } |
| } |
| |
| static void bubbleSort(double array[]) |
| { |
| for (int j = 0; j < 3; ++j) { |
| int kk = j; |
| for (int k = j + 1; k < 4; ++k) { |
| if (array[k] < array[kk]) { |
| kk = k; |
| } |
| } |
| double tmp = array[j]; |
| array[j] = array[kk]; |
| array[kk] = tmp; |
| } |
| } |
| |
| void Gfx::doAxialShFill(GfxAxialShading *shading) { |
| double xMin, yMin, xMax, yMax; |
| double x0, y0, x1, y1; |
| double dx, dy, mul; |
| GBool dxZero, dyZero; |
| double bboxIntersections[4]; |
| double tMin, tMax, tx, ty; |
| double s[4], sMin, sMax, tmp; |
| double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1; |
| double t0, t1, tt; |
| double ta[axialMaxSplits + 1]; |
| int next[axialMaxSplits + 1]; |
| GfxColor color0, color1; |
| int nComps; |
| int i, j, k; |
| GBool needExtend = gTrue; |
| |
| // get the clip region bbox |
| state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); |
| |
| // compute min and max t values, based on the four corners of the |
| // clip region bbox |
| shading->getCoords(&x0, &y0, &x1, &y1); |
| dx = x1 - x0; |
| dy = y1 - y0; |
| dxZero = fabs(dx) < 0.01; |
| dyZero = fabs(dy) < 0.01; |
| if (dxZero && dyZero) { |
| tMin = tMax = 0; |
| } else { |
| mul = 1 / (dx * dx + dy * dy); |
| bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; |
| bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; |
| bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; |
| bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; |
| bubbleSort(bboxIntersections); |
| tMin = bboxIntersections[0]; |
| tMax = bboxIntersections[3]; |
| if (tMin < 0 && !shading->getExtend0()) { |
| tMin = 0; |
| } |
| if (tMax > 1 && !shading->getExtend1()) { |
| tMax = 1; |
| } |
| } |
| |
| if (out->useShadedFills( shading->getType() ) && |
| out->axialShadedFill(state, shading, tMin, tMax)) { |
| return; |
| } |
| |
| // get the function domain |
| t0 = shading->getDomain0(); |
| t1 = shading->getDomain1(); |
| |
| // Traverse the t axis and do the shading. |
| // |
| // For each point (tx, ty) on the t axis, consider a line through |
| // that point perpendicular to the t axis: |
| // |
| // x(s) = tx + s * -dy --> s = (x - tx) / -dy |
| // y(s) = ty + s * dx --> s = (y - ty) / dx |
| // |
| // Then look at the intersection of this line with the bounding box |
| // (xMin, yMin, xMax, yMax). In the general case, there are four |
| // intersection points: |
| // |
| // s0 = (xMin - tx) / -dy |
| // s1 = (xMax - tx) / -dy |
| // s2 = (yMin - ty) / dx |
| // s3 = (yMax - ty) / dx |
| // |
| // and we want the middle two s values. |
| // |
| // In the case where dx = 0, take s0 and s1; in the case where dy = |
| // 0, take s2 and s3. |
| // |
| // Each filled polygon is bounded by two of these line segments |
| // perpdendicular to the t axis. |
| // |
| // The t axis is bisected into smaller regions until the color |
| // difference across a region is small enough, and then the region |
| // is painted with a single color. |
| |
| // set up: require at least one split to avoid problems when the two |
| // ends of the t axis have the same color |
| nComps = shading->getColorSpace()->getNComps(); |
| ta[0] = tMin; |
| next[0] = axialMaxSplits / 2; |
| ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax); |
| next[axialMaxSplits / 2] = axialMaxSplits; |
| ta[axialMaxSplits] = tMax; |
| |
| // compute the color at t = tMin |
| if (tMin < 0) { |
| tt = t0; |
| } else if (tMin > 1) { |
| tt = t1; |
| } else { |
| tt = t0 + (t1 - t0) * tMin; |
| } |
| shading->getColor(tt, &color0); |
| |
| if (out->useFillColorStop()) { |
| // make sure we add stop color when t = tMin |
| state->setFillColor(&color0); |
| out->updateFillColorStop(state, 0); |
| } |
| |
| // compute the coordinates of the point on the t axis at t = tMin; |
| // then compute the intersection of the perpendicular line with the |
| // bounding box |
| tx = x0 + tMin * dx; |
| ty = y0 + tMin * dy; |
| if (dxZero && dyZero) { |
| sMin = sMax = 0; |
| } else if (dxZero) { |
| sMin = (xMin - tx) / -dy; |
| sMax = (xMax - tx) / -dy; |
| if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; } |
| } else if (dyZero) { |
| sMin = (yMin - ty) / dx; |
| sMax = (yMax - ty) / dx; |
| if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; } |
| } else { |
| s[0] = (yMin - ty) / dx; |
| s[1] = (yMax - ty) / dx; |
| s[2] = (xMin - tx) / -dy; |
| s[3] = (xMax - tx) / -dy; |
| bubbleSort(s); |
| sMin = s[1]; |
| sMax = s[2]; |
| } |
| ux0 = tx - sMin * dy; |
| uy0 = ty + sMin * dx; |
| vx0 = tx - sMax * dy; |
| vy0 = ty + sMax * dx; |
| |
| i = 0; |
| bool doneBBox1, doneBBox2; |
| if (dxZero && dyZero) { |
| doneBBox1 = doneBBox2 = true; |
| } else { |
| doneBBox1 = bboxIntersections[1] < tMin; |
| doneBBox2 = bboxIntersections[2] > tMax; |
| } |
| |
| // If output device doesn't support the extended mode required |
| // we have to do it here |
| needExtend = !out->axialShadedSupportExtend(state, shading); |
| |
| while (i < axialMaxSplits) { |
| |
| // bisect until color difference is small enough or we hit the |
| // bisection limit |
| j = next[i]; |
| while (j > i + 1) { |
| if (ta[j] < 0) { |
| tt = t0; |
| } else if (ta[j] > 1) { |
| tt = t1; |
| } else { |
| tt = t0 + (t1 - t0) * ta[j]; |
| } |
| shading->getColor(tt, &color1); |
| if (isSameGfxColor(color1, color0, nComps, axialColorDelta)) { |
| // in these two if what we guarantee is that if we are skipping lots of |
| // positions because the colors are the same, we still create a region |
| // with vertexs passing by bboxIntersections[1] and bboxIntersections[2] |
| // otherwise we can have empty regions that should really be painted |
| // like happened in bug 19896 |
| // What we do to ensure that we pass a line through this points |
| // is making sure use the exact bboxIntersections[] value as one of the used ta[] values |
| if (!doneBBox1 && ta[i] < bboxIntersections[1] && ta[j] > bboxIntersections[1]) { |
| int teoricalj = (int) ((bboxIntersections[1] - tMin) * axialMaxSplits / (tMax - tMin)); |
| if (teoricalj <= i) teoricalj = i + 1; |
| if (teoricalj < j) { |
| next[i] = teoricalj; |
| next[teoricalj] = j; |
| } |
| else { |
| teoricalj = j; |
| } |
| ta[teoricalj] = bboxIntersections[1]; |
| j = teoricalj; |
| doneBBox1 = true; |
| } |
| if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) { |
| int teoricalj = (int) ((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin)); |
| if (teoricalj <= i) teoricalj = i + 1; |
| if (teoricalj < j) { |
| next[i] = teoricalj; |
| next[teoricalj] = j; |
| } |
| else { |
| teoricalj = j; |
| } |
| ta[teoricalj] = bboxIntersections[2]; |
| j = teoricalj; |
| doneBBox2 = true; |
| } |
| break; |
| } |
| k = (i + j) / 2; |
| ta[k] = 0.5 * (ta[i] + ta[j]); |
| next[i] = k; |
| next[k] = j; |
| j = k; |
| |