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