| //======================================================================== |
| // |
| // Gfx.cc |
| // |
| // Copyright 1996-2013 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com> |
| // Copyright (C) 2005-2013, 2015-2022 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2006 Thorkild Stray <thorkild@ifi.uio.no> |
| // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com> |
| // Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net> |
| // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org> |
| // Copyright (C) 2007, 2011, 2017, 2021, 2023 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com> |
| // Copyright (C) 2007 Koji Otani <sho@bbr.jp> |
| // Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com> |
| // Copyright (C) 2008 Pino Toscano <pino@kde.org> |
| // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu> |
| // Copyright (C) 2008 Hib Eris <hib@hiberis.nl> |
| // Copyright (C) 2009 M Joonas Pihlaja <jpihlaja@cc.helsinki.fi> |
| // Copyright (C) 2009-2016, 2020 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2009 William Bader <williambader@hotmail.com> |
| // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu> |
| // Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com> |
| // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com> |
| // Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de> |
| // Copyright (C) 2012, 2024 Even Rouault <even.rouault@spatialys.com> |
| // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it> |
| // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com> |
| // Copyright (C) 2014 Jason Crain <jason@aquaticape.us> |
| // Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
| // Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de> |
| // Copyright (C) 2018 Denis Onishchenko <denis.onischenko@gmail.com> |
| // Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com> |
| // Copyright (C) 2019-2022 Oliver Sander <oliver.sander@tu-dresden.de> |
| // Copyright (C) 2019 Volker Krause <vkrause@kde.org> |
| // Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com> |
| // Copyright (C) 2021 Steve Rosenhamer <srosenhamer@me.com> |
| // Copyright (C) 2023 Anton Thomasson <antonthomasson@gmail.com> |
| // Copyright (C) 2024 Nelson Benítez León <nbenitezl@gmail.com> |
| // Copyright (C) 2024 Athul Raj Kollareth <krathul3152@gmail.com> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include <cstdlib> |
| #include <cstdio> |
| #include <cstddef> |
| #include <cstring> |
| #include <cmath> |
| #include <memory> |
| #include "goo/gmem.h" |
| #include "goo/GooTimer.h" |
| #include "GlobalParams.h" |
| #include "CharTypes.h" |
| #include "Object.h" |
| #include "PDFDoc.h" |
| #include "Array.h" |
| #include "Annot.h" |
| #include "Dict.h" |
| #include "Stream.h" |
| #include "Lexer.h" |
| #include "Parser.h" |
| #include "GfxFont.h" |
| #include "GfxState.h" |
| #include "OutputDev.h" |
| #include "Page.h" |
| #include "Annot.h" |
| #include "Error.h" |
| #include "Gfx.h" |
| #include "ProfileData.h" |
| #include "Catalog.h" |
| #include "OptionalContent.h" |
| |
| // the MSVC math.h doesn't define this |
| #ifndef M_PI |
| # define M_PI 3.14159265358979323846 |
| #endif |
| |
| //------------------------------------------------------------------------ |
| // constants |
| //------------------------------------------------------------------------ |
| |
| // Max recursive depth for a function shading fill. |
| #define functionMaxDepth 6 |
| |
| // Max delta allowed in any color component for a function shading fill. |
| #define functionColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max number of splits along the t axis for an axial shading fill. |
| #define axialMaxSplits 256 |
| |
| // Max delta allowed in any color component for an axial shading fill. |
| #define axialColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max number of splits along the t axis for a radial shading fill. |
| #define radialMaxSplits 256 |
| |
| // Max delta allowed in any color component for a radial shading fill. |
| #define radialColorDelta (dblToCol(1 / 256.0)) |
| |
| // Max recursive depth for a Gouraud triangle shading fill. |
| // |
| // Triangles will be split at most gouraudMaxDepth times (each time into 4 |
| // smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for |
| // every triangle. |
| #define gouraudMaxDepth 6 |
| |
| // Max delta allowed in any color component for a Gouraud triangle |
| // shading fill. |
| #define gouraudColorDelta (dblToCol(3. / 256.0)) |
| |
| // Gouraud triangle: if the three color parameters differ by at more than this percend of |
| // the total color parameter range, the triangle will be refined |
| #define gouraudParameterizedColorDelta 5e-3 |
| |
| // Max recursive depth for a patch mesh shading fill. |
| #define patchMaxDepth 6 |
| |
| // Max delta allowed in any color component for a patch mesh shading |
| // fill. |
| #define patchColorDelta (dblToCol((3. / 256.0))) |
| |
| //------------------------------------------------------------------------ |
| // Operator table |
| //------------------------------------------------------------------------ |
| |
| const Operator Gfx::opTab[] = { |
| { "\"", 3, { tchkNum, tchkNum, tchkString }, &Gfx::opMoveSetShowText }, |
| { "'", 1, { tchkString }, &Gfx::opMoveShowText }, |
| { "B", 0, { tchkNone }, &Gfx::opFillStroke }, |
| { "B*", 0, { tchkNone }, &Gfx::opEOFillStroke }, |
| { "BDC", 2, { tchkName, tchkProps }, &Gfx::opBeginMarkedContent }, |
| { "BI", 0, { tchkNone }, &Gfx::opBeginImage }, |
| { "BMC", 1, { tchkName }, &Gfx::opBeginMarkedContent }, |
| { "BT", 0, { tchkNone }, &Gfx::opBeginText }, |
| { "BX", 0, { tchkNone }, &Gfx::opBeginIgnoreUndef }, |
| { "CS", 1, { tchkName }, &Gfx::opSetStrokeColorSpace }, |
| { "DP", 2, { tchkName, tchkProps }, &Gfx::opMarkPoint }, |
| { "Do", 1, { tchkName }, &Gfx::opXObject }, |
| { "EI", 0, { tchkNone }, &Gfx::opEndImage }, |
| { "EMC", 0, { tchkNone }, &Gfx::opEndMarkedContent }, |
| { "ET", 0, { tchkNone }, &Gfx::opEndText }, |
| { "EX", 0, { tchkNone }, &Gfx::opEndIgnoreUndef }, |
| { "F", 0, { tchkNone }, &Gfx::opFill }, |
| { "G", 1, { tchkNum }, &Gfx::opSetStrokeGray }, |
| { "ID", 0, { tchkNone }, &Gfx::opImageData }, |
| { "J", 1, { tchkInt }, &Gfx::opSetLineCap }, |
| { "K", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeCMYKColor }, |
| { "M", 1, { tchkNum }, &Gfx::opSetMiterLimit }, |
| { "MP", 1, { tchkName }, &Gfx::opMarkPoint }, |
| { "Q", 0, { tchkNone }, &Gfx::opRestore }, |
| { "RG", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeRGBColor }, |
| { "S", 0, { tchkNone }, &Gfx::opStroke }, |
| { "SC", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeColor }, |
| { "SCN", |
| -33, |
| { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN }, |
| &Gfx::opSetStrokeColorN }, |
| { "T*", 0, { tchkNone }, &Gfx::opTextNextLine }, |
| { "TD", 2, { tchkNum, tchkNum }, &Gfx::opTextMoveSet }, |
| { "TJ", 1, { tchkArray }, &Gfx::opShowSpaceText }, |
| { "TL", 1, { tchkNum }, &Gfx::opSetTextLeading }, |
| { "Tc", 1, { tchkNum }, &Gfx::opSetCharSpacing }, |
| { "Td", 2, { tchkNum, tchkNum }, &Gfx::opTextMove }, |
| { "Tf", 2, { tchkName, tchkNum }, &Gfx::opSetFont }, |
| { "Tj", 1, { tchkString }, &Gfx::opShowText }, |
| { "Tm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetTextMatrix }, |
| { "Tr", 1, { tchkInt }, &Gfx::opSetTextRender }, |
| { "Ts", 1, { tchkNum }, &Gfx::opSetTextRise }, |
| { "Tw", 1, { tchkNum }, &Gfx::opSetWordSpacing }, |
| { "Tz", 1, { tchkNum }, &Gfx::opSetHorizScaling }, |
| { "W", 0, { tchkNone }, &Gfx::opClip }, |
| { "W*", 0, { tchkNone }, &Gfx::opEOClip }, |
| { "b", 0, { tchkNone }, &Gfx::opCloseFillStroke }, |
| { "b*", 0, { tchkNone }, &Gfx::opCloseEOFillStroke }, |
| { "c", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo }, |
| { "cm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opConcat }, |
| { "cs", 1, { tchkName }, &Gfx::opSetFillColorSpace }, |
| { "d", 2, { tchkArray, tchkNum }, &Gfx::opSetDash }, |
| { "d0", 2, { tchkNum, tchkNum }, &Gfx::opSetCharWidth }, |
| { "d1", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetCacheDevice }, |
| { "f", 0, { tchkNone }, &Gfx::opFill }, |
| { "f*", 0, { tchkNone }, &Gfx::opEOFill }, |
| { "g", 1, { tchkNum }, &Gfx::opSetFillGray }, |
| { "gs", 1, { tchkName }, &Gfx::opSetExtGState }, |
| { "h", 0, { tchkNone }, &Gfx::opClosePath }, |
| { "i", 1, { tchkNum }, &Gfx::opSetFlat }, |
| { "j", 1, { tchkInt }, &Gfx::opSetLineJoin }, |
| { "k", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillCMYKColor }, |
| { "l", 2, { tchkNum, tchkNum }, &Gfx::opLineTo }, |
| { "m", 2, { tchkNum, tchkNum }, &Gfx::opMoveTo }, |
| { "n", 0, { tchkNone }, &Gfx::opEndPath }, |
| { "q", 0, { tchkNone }, &Gfx::opSave }, |
| { "re", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opRectangle }, |
| { "rg", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillRGBColor }, |
| { "ri", 1, { tchkName }, &Gfx::opSetRenderingIntent }, |
| { "s", 0, { tchkNone }, &Gfx::opCloseStroke }, |
| { "sc", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillColor }, |
| { "scn", |
| -33, |
| { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, |
| tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN }, |
| &Gfx::opSetFillColorN }, |
| { "sh", 1, { tchkName }, &Gfx::opShFill }, |
| { "v", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo1 }, |
| { "w", 1, { tchkNum }, &Gfx::opSetLineWidth }, |
| { "y", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo2 }, |
| }; |
| |
| #define numOps (sizeof(opTab) / sizeof(Operator)) |
| |
| static inline bool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, unsigned int nComps, double delta) |
| { |
| for (unsigned int k = 0; k < nComps; ++k) { |
| if (abs(colorA.c[k] - colorB.c[k]) > delta) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| //------------------------------------------------------------------------ |
| // GfxResources |
| //------------------------------------------------------------------------ |
| |
| GfxResources::GfxResources(XRef *xrefA, Dict *resDictA, GfxResources *nextA) : gStateCache(2), xref(xrefA) |
| { |
| Ref r; |
| |
| if (resDictA) { |
| |
| // build font dictionary |
| Dict *resDict = resDictA->copy(xref); |
| fonts = nullptr; |
| const Object &obj1 = resDict->lookupNF("Font"); |
| if (obj1.isRef()) { |
| Object obj2 = obj1.fetch(xref); |
| if (obj2.isDict()) { |
| r = obj1.getRef(); |
| fonts = new GfxFontDict(xref, &r, obj2.getDict()); |
| } |
| } else if (obj1.isDict()) { |
| fonts = new GfxFontDict(xref, nullptr, obj1.getDict()); |
| } |
| |
| // get XObject dictionary |
| xObjDict = resDict->lookup("XObject"); |
| |
| // get color space dictionary |
| colorSpaceDict = resDict->lookup("ColorSpace"); |
| |
| // get pattern dictionary |
| patternDict = resDict->lookup("Pattern"); |
| |
| // get shading dictionary |
| shadingDict = resDict->lookup("Shading"); |
| |
| // get graphics state parameter dictionary |
| gStateDict = resDict->lookup("ExtGState"); |
| |
| // get properties dictionary |
| propertiesDict = resDict->lookup("Properties"); |
| |
| delete resDict; |
| } else { |
| fonts = nullptr; |
| xObjDict.setToNull(); |
| colorSpaceDict.setToNull(); |
| patternDict.setToNull(); |
| shadingDict.setToNull(); |
| gStateDict.setToNull(); |
| propertiesDict.setToNull(); |
| } |
| |
| next = nextA; |
| } |
| |
| GfxResources::~GfxResources() |
| { |
| delete fonts; |
| } |
| |
| std::shared_ptr<GfxFont> GfxResources::doLookupFont(const char *name) const |
| { |
| const GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->fonts) { |
| if (std::shared_ptr<GfxFont> font = resPtr->fonts->lookup(name)) { |
| return font; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name); |
| return nullptr; |
| } |
| |
| std::shared_ptr<GfxFont> GfxResources::lookupFont(const char *name) |
| { |
| return doLookupFont(name); |
| } |
| |
| std::shared_ptr<const GfxFont> GfxResources::lookupFont(const char *name) const |
| { |
| return doLookupFont(name); |
| } |
| |
| Object GfxResources::lookupXObject(const char *name) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->xObjDict.isDict()) { |
| Object obj = resPtr->xObjDict.dictLookup(name); |
| if (!obj.isNull()) { |
| return obj; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name); |
| return Object(objNull); |
| } |
| |
| Object GfxResources::lookupXObjectNF(const char *name) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->xObjDict.isDict()) { |
| Object obj = resPtr->xObjDict.dictLookupNF(name).copy(); |
| if (!obj.isNull()) { |
| return obj; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name); |
| return Object(objNull); |
| } |
| |
| Object GfxResources::lookupMarkedContentNF(const char *name) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->propertiesDict.isDict()) { |
| Object obj = resPtr->propertiesDict.dictLookupNF(name).copy(); |
| if (!obj.isNull()) { |
| return obj; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "Marked Content '{0:s}' is unknown", name); |
| return Object(objNull); |
| } |
| |
| Object GfxResources::lookupColorSpace(const char *name) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->colorSpaceDict.isDict()) { |
| Object obj = resPtr->colorSpaceDict.dictLookup(name); |
| if (!obj.isNull()) { |
| return obj; |
| } |
| } |
| } |
| return Object(objNull); |
| } |
| |
| GfxPattern *GfxResources::lookupPattern(const char *name, OutputDev *out, GfxState *state) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->patternDict.isDict()) { |
| Ref patternRef = Ref::INVALID(); |
| Object obj = resPtr->patternDict.getDict()->lookup(name, &patternRef); |
| if (!obj.isNull()) { |
| return GfxPattern::parse(resPtr, &obj, out, state, patternRef.num); |
| } |
| } |
| } |
| error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name); |
| return nullptr; |
| } |
| |
| GfxShading *GfxResources::lookupShading(const char *name, OutputDev *out, GfxState *state) |
| { |
| GfxResources *resPtr; |
| GfxShading *shading; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->shadingDict.isDict()) { |
| Object obj = resPtr->shadingDict.dictLookup(name); |
| if (!obj.isNull()) { |
| shading = GfxShading::parse(resPtr, &obj, out, state); |
| return shading; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name); |
| return nullptr; |
| } |
| |
| Object GfxResources::lookupGState(const char *name) |
| { |
| Object obj = lookupGStateNF(name); |
| if (obj.isNull()) { |
| return Object(objNull); |
| } |
| |
| if (!obj.isRef()) { |
| return obj; |
| } |
| |
| const Ref ref = obj.getRef(); |
| |
| if (auto *item = gStateCache.lookup(ref)) { |
| return item->copy(); |
| } |
| |
| auto *item = new Object { xref->fetch(ref) }; |
| gStateCache.put(ref, item); |
| return item->copy(); |
| } |
| |
| Object GfxResources::lookupGStateNF(const char *name) |
| { |
| GfxResources *resPtr; |
| |
| for (resPtr = this; resPtr; resPtr = resPtr->next) { |
| if (resPtr->gStateDict.isDict()) { |
| Object obj = resPtr->gStateDict.dictLookupNF(name).copy(); |
| if (!obj.isNull()) { |
| return obj; |
| } |
| } |
| } |
| error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name); |
| return Object(objNull); |
| } |
| |
| //------------------------------------------------------------------------ |
| // Gfx |
| //------------------------------------------------------------------------ |
| |
| Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict, double hDPI, double vDPI, const PDFRectangle *box, const PDFRectangle *cropBox, int rotate, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, XRef *xrefA) |
| : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands()) |
| { |
| int i; |
| |
| doc = docA; |
| xref = (xrefA == nullptr) ? doc->getXRef() : xrefA; |
| catalog = doc->getCatalog(); |
| subPage = false; |
| mcStack = nullptr; |
| parser = nullptr; |
| |
| // start the resource stack |
| res = new GfxResources(xref, resDict, nullptr); |
| |
| // initialize |
| out = outA; |
| state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown()); |
| out->initGfxState(state); |
| stackHeight = 1; |
| pushStateGuard(); |
| fontChanged = false; |
| clip = clipNone; |
| ignoreUndef = 0; |
| out->startPage(pageNum, state, xref); |
| out->setDefaultCTM(state->getCTM()); |
| out->updateAll(state); |
| for (i = 0; i < 6; ++i) { |
| baseMatrix[i] = state->getCTM()[i]; |
| } |
| displayDepth = 0; |
| ocState = true; |
| parser = nullptr; |
| abortCheckCbk = abortCheckCbkA; |
| abortCheckCbkData = abortCheckCbkDataA; |
| |
| // set crop box |
| if (cropBox) { |
| state->moveTo(cropBox->x1, cropBox->y1); |
| state->lineTo(cropBox->x2, cropBox->y1); |
| state->lineTo(cropBox->x2, cropBox->y2); |
| state->lineTo(cropBox->x1, cropBox->y2); |
| state->closePath(); |
| state->clip(); |
| out->clip(state); |
| state->clearPath(); |
| } |
| #ifdef USE_CMS |
| initDisplayProfile(); |
| #endif |
| } |
| |
| Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, const PDFRectangle *box, const PDFRectangle *cropBox, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, Gfx *gfxA) |
| : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands()) |
| { |
| int i; |
| |
| doc = docA; |
| if (gfxA) { |
| xref = gfxA->getXRef(); |
| formsDrawing = gfxA->formsDrawing; |
| charProcDrawing = gfxA->charProcDrawing; |
| } else { |
| xref = doc->getXRef(); |
| } |
| catalog = doc->getCatalog(); |
| subPage = true; |
| mcStack = nullptr; |
| parser = nullptr; |
| |
| // start the resource stack |
| res = new GfxResources(xref, resDict, nullptr); |
| |
| // initialize |
| out = outA; |
| double hDPI = 72; |
| double vDPI = 72; |
| if (gfxA) { |
| hDPI = gfxA->getState()->getHDPI(); |
| vDPI = gfxA->getState()->getVDPI(); |
| } |
| state = new GfxState(hDPI, vDPI, box, 0, false); |
| stackHeight = 1; |
| pushStateGuard(); |
| fontChanged = false; |
| clip = clipNone; |
| ignoreUndef = 0; |
| for (i = 0; i < 6; ++i) { |
| baseMatrix[i] = state->getCTM()[i]; |
| } |
| displayDepth = 0; |
| ocState = true; |
| parser = nullptr; |
| abortCheckCbk = abortCheckCbkA; |
| abortCheckCbkData = abortCheckCbkDataA; |
| |
| // set crop box |
| if (cropBox) { |
| state->moveTo(cropBox->x1, cropBox->y1); |
| state->lineTo(cropBox->x2, cropBox->y1); |
| state->lineTo(cropBox->x2, cropBox->y2); |
| state->lineTo(cropBox->x1, cropBox->y2); |
| state->closePath(); |
| state->clip(); |
| out->clip(state); |
| state->clearPath(); |
| } |
| #ifdef USE_CMS |
| initDisplayProfile(); |
| #endif |
| } |
| |
| #ifdef USE_CMS |
| |
| # include <lcms2.h> |
| |
| void Gfx::initDisplayProfile() |
| { |
| Object catDict = xref->getCatalog(); |
| if (catDict.isDict()) { |
| Object outputIntents = catDict.dictLookup("OutputIntents"); |
| if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) { |
| Object firstElement = outputIntents.arrayGet(0); |
| if (firstElement.isDict()) { |
| Object profile = firstElement.dictLookup("DestOutputProfile"); |
| if (profile.isStream()) { |
| Stream *iccStream = profile.getStream(); |
| const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536); |
| auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size())); |
| if (!hp) { |
| error(errSyntaxWarning, -1, "read ICCBased color space profile error"); |
| } else { |
| state->setDisplayProfile(hp); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| #endif |
| |
| Gfx::~Gfx() |
| { |
| while (stateGuards.size()) { |
| popStateGuard(); |
| } |
| if (!subPage) { |
| out->endPage(); |
| } |
| // There shouldn't be more saves, but pop them if there were any |
| while (state->hasSaves()) { |
| error(errSyntaxError, -1, "Found state under last state guard. Popping."); |
| restoreState(); |
| } |
| delete state; |
| while (res) { |
| popResources(); |
| } |
| while (mcStack) { |
| popMarkedContent(); |
| } |
| } |
| |
| void Gfx::display(Object *obj, bool topLevel) |
| { |
| // check for excessive recursion |
| if (displayDepth > 100) { |
| return; |
| } |
| |
| if (obj->isArray()) { |
| for (int i = 0; i < obj->arrayGetLength(); ++i) { |
| Object obj2 = obj->arrayGet(i); |
| if (!obj2.isStream()) { |
| error(errSyntaxError, -1, "Weird page contents"); |
| return; |
| } |
| } |
| } else if (!obj->isStream()) { |
| error(errSyntaxError, -1, "Weird page contents"); |
| return; |
| } |
| parser = new Parser(xref, obj, false); |
| go(topLevel); |
| delete parser; |
| parser = nullptr; |
| } |
| |
| void Gfx::go(bool topLevel) |
| { |
| Object obj; |
| Object args[maxArgs]; |
| int numArgs, i; |
| int lastAbortCheck; |
| |
| // scan a sequence of objects |
| pushStateGuard(); |
| updateLevel = 1; // make sure even empty pages trigger a call to dump() |
| lastAbortCheck = 0; |
| numArgs = 0; |
| obj = parser->getObj(); |
| while (!obj.isEOF()) { |
| commandAborted = false; |
| |
| // got a command - execute it |
| if (obj.isCmd()) { |
| if (printCommands) { |
| obj.print(stdout); |
| for (i = 0; i < numArgs; ++i) { |
| printf(" "); |
| args[i].print(stdout); |
| } |
| printf("\n"); |
| fflush(stdout); |
| } |
| GooTimer *timer = nullptr; |
| |
| if (unlikely(profileCommands)) { |
| timer = new GooTimer(); |
| } |
| |
| // Run the operation |
| execOp(&obj, args, numArgs); |
| |
| // Update the profile information |
| if (unlikely(profileCommands)) { |
| if (auto *const hash = out->getProfileHash()) { |
| auto &data = (*hash)[obj.getCmd()]; |
| data.addElement(timer->getElapsed()); |
| } |
| delete timer; |
| } |
| for (i = 0; i < numArgs; ++i) { |
| args[i].setToNull(); // Free memory early |
| } |
| numArgs = 0; |
| |
| // periodically update display |
| if (++updateLevel >= 20000) { |
| out->dump(); |
| updateLevel = 0; |
| lastAbortCheck = 0; |
| } |
| |
| // did the command throw an exception |
| if (commandAborted) { |
| // don't propogate; recursive drawing comes from Form XObjects which |
| // should probably be drawn in a separate context anyway for caching |
| commandAborted = false; |
| break; |
| } |
| |
| // check for an abort |
| if (abortCheckCbk) { |
| if (updateLevel - lastAbortCheck > 10) { |
| if ((*abortCheckCbk)(abortCheckCbkData)) { |
| break; |
| } |
| lastAbortCheck = updateLevel; |
| } |
| } |
| |
| // got an argument - save it |
| } else if (numArgs < maxArgs) { |
| args[numArgs++] = std::move(obj); |
| // too many arguments - something is wrong |
| } else { |
| error(errSyntaxError, getPos(), "Too many args in content stream"); |
| if (printCommands) { |
| printf("throwing away arg: "); |
| obj.print(stdout); |
| printf("\n"); |
| fflush(stdout); |
| } |
| } |
| |
| // grab the next object |
| obj = parser->getObj(); |
| } |
| |
| // args at end with no command |
| if (numArgs > 0) { |
| error(errSyntaxError, getPos(), "Leftover args in content stream"); |
| if (printCommands) { |
| printf("%d leftovers:", numArgs); |
| for (i = 0; i < numArgs; ++i) { |
| printf(" "); |
| args[i].print(stdout); |
| } |
| printf("\n"); |
| fflush(stdout); |
| } |
| } |
| |
| popStateGuard(); |
| |
| // update display |
| if (topLevel && updateLevel > 0) { |
| out->dump(); |
| } |
| } |
| |
| void Gfx::execOp(Object *cmd, Object args[], int numArgs) |
| { |
| const Operator *op; |
| Object *argPtr; |
| int i; |
| |
| // find operator |
| const char *name = cmd->getCmd(); |
| if (!(op = findOp(name))) { |
| if (ignoreUndef == 0) { |
| error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); |
| } |
| return; |
| } |
| |
| // type check args |
| argPtr = args; |
| if (op->numArgs >= 0) { |
| if (numArgs < op->numArgs) { |
| error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name); |
| commandAborted = true; |
| return; |
| } |
| if (numArgs > op->numArgs) { |
| #if 0 |
| error(errSyntaxWarning, getPos(), |
| "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); |
| #endif |
| argPtr += numArgs - op->numArgs; |
| numArgs = op->numArgs; |
| } |
| } else { |
| if (numArgs > -op->numArgs) { |
| error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); |
| return; |
| } |
| } |
| for (i = 0; i < numArgs; ++i) { |
| if (!checkArg(&argPtr[i], op->tchk[i])) { |
| error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName()); |
| return; |
| } |
| } |
| |
| // do it |
| (this->*op->func)(argPtr, numArgs); |
| } |
| |
| const Operator *Gfx::findOp(const char *name) |
| { |
| int a, b, m, cmp; |
| |
| a = -1; |
| b = numOps; |
| cmp = 0; // make gcc happy |
| // invariant: opTab[a] < name < opTab[b] |
| while (b - a > 1) { |
| m = (a + b) / 2; |
| cmp = strcmp(opTab[m].name, name); |
| if (cmp < 0) { |
| a = m; |
| } else if (cmp > 0) { |
| b = m; |
| } else { |
| a = b = m; |
| } |
| } |
| if (cmp != 0) { |
| return nullptr; |
| } |
| return &opTab[a]; |
| } |
| |
| bool Gfx::checkArg(Object *arg, TchkType type) |
| { |
| switch (type) { |
| case tchkBool: |
| return arg->isBool(); |
| case tchkInt: |
| return arg->isInt(); |
| case tchkNum: |
| return arg->isNum(); |
| case tchkString: |
| return arg->isString(); |
| case tchkName: |
| return arg->isName(); |
| case tchkArray: |
| return arg->isArray(); |
| case tchkProps: |
| return arg->isDict() || arg->isName(); |
| case tchkSCN: |
| return arg->isNum() || arg->isName(); |
| case tchkNone: |
| return false; |
| } |
| return false; |
| } |
| |
| Goffset Gfx::getPos() |
| { |
| return parser ? parser->getPos() : -1; |
| } |
| |
| //------------------------------------------------------------------------ |
| // graphics state operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opSave(Object args[], int numArgs) |
| { |
| saveState(); |
| } |
| |
| void Gfx::opRestore(Object args[], int numArgs) |
| { |
| restoreState(); |
| } |
| |
| void Gfx::opConcat(Object args[], int numArgs) |
| { |
| state->concatCTM(args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum()); |
| out->updateCTM(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum()); |
| fontChanged = true; |
| } |
| |
| void Gfx::opSetDash(Object args[], int numArgs) |
| { |
| const Array *a = args[0].getArray(); |
| int length = a->getLength(); |
| std::vector<double> dash(length); |
| for (int i = 0; i < length; ++i) { |
| dash[i] = a->get(i).getNumWithDefaultValue(0); |
| } |
| state->setLineDash(std::move(dash), args[1].getNum()); |
| out->updateLineDash(state); |
| } |
| |
| void Gfx::opSetFlat(Object args[], int numArgs) |
| { |
| state->setFlatness((int)args[0].getNum()); |
| out->updateFlatness(state); |
| } |
| |
| void Gfx::opSetLineJoin(Object args[], int numArgs) |
| { |
| state->setLineJoin(args[0].getInt()); |
| out->updateLineJoin(state); |
| } |
| |
| void Gfx::opSetLineCap(Object args[], int numArgs) |
| { |
| state->setLineCap(args[0].getInt()); |
| out->updateLineCap(state); |
| } |
| |
| void Gfx::opSetMiterLimit(Object args[], int numArgs) |
| { |
| state->setMiterLimit(args[0].getNum()); |
| out->updateMiterLimit(state); |
| } |
| |
| void Gfx::opSetLineWidth(Object args[], int numArgs) |
| { |
| state->setLineWidth(args[0].getNum()); |
| out->updateLineWidth(state); |
| } |
| |
| void Gfx::opSetExtGState(Object args[], int numArgs) |
| { |
| Object obj1, obj2; |
| GfxBlendMode mode; |
| bool haveFillOP; |
| GfxColor backdropColor; |
| bool haveBackdropColor; |
| bool alpha; |
| double opac; |
| |
| obj1 = res->lookupGState(args[0].getName()); |
| if (obj1.isNull()) { |
| return; |
| } |
| if (!obj1.isDict()) { |
| error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type", args[0].getName()); |
| return; |
| } |
| if (printCommands) { |
| printf(" gfx state dict: "); |
| obj1.print(); |
| printf("\n"); |
| } |
| |
| // parameters that are also set by individual PDF operators |
| obj2 = obj1.dictLookup("LW"); |
| if (obj2.isNum()) { |
| opSetLineWidth(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("LC"); |
| if (obj2.isInt()) { |
| opSetLineCap(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("LJ"); |
| if (obj2.isInt()) { |
| opSetLineJoin(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("ML"); |
| if (obj2.isNum()) { |
| opSetMiterLimit(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("D"); |
| if (obj2.isArray() && obj2.arrayGetLength() == 2) { |
| Object args2[2]; |
| args2[0] = obj2.arrayGet(0); |
| args2[1] = obj2.arrayGet(1); |
| if (args2[0].isArray() && args2[1].isNum()) { |
| opSetDash(args2, 2); |
| } |
| } |
| #if 0 //~ need to add a new version of GfxResources::lookupFont() that |
| //~ takes an indirect ref instead of a name |
| if (obj1.dictLookup("Font", &obj2)->isArray() && |
| obj2.arrayGetLength() == 2) { |
| obj2.arrayGet(0, &args2[0]); |
| obj2.arrayGet(1, &args2[1]); |
| if (args2[0].isDict() && args2[1].isNum()) { |
| opSetFont(args2, 2); |
| } |
| args2[0].free(); |
| args2[1].free(); |
| } |
| obj2.free(); |
| #endif |
| obj2 = obj1.dictLookup("FL"); |
| if (obj2.isNum()) { |
| opSetFlat(&obj2, 1); |
| } |
| |
| // transparency support: blend mode, fill/stroke opacity |
| obj2 = obj1.dictLookup("BM"); |
| if (!obj2.isNull()) { |
| if (state->parseBlendMode(&obj2, &mode)) { |
| state->setBlendMode(mode); |
| out->updateBlendMode(state); |
| } else { |
| error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState"); |
| } |
| } |
| obj2 = obj1.dictLookup("ca"); |
| if (obj2.isNum()) { |
| opac = obj2.getNum(); |
| state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac); |
| out->updateFillOpacity(state); |
| } |
| obj2 = obj1.dictLookup("CA"); |
| if (obj2.isNum()) { |
| opac = obj2.getNum(); |
| state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac); |
| out->updateStrokeOpacity(state); |
| } |
| |
| // fill/stroke overprint, overprint mode |
| obj2 = obj1.dictLookup("op"); |
| if ((haveFillOP = obj2.isBool())) { |
| state->setFillOverprint(obj2.getBool()); |
| out->updateFillOverprint(state); |
| } |
| obj2 = obj1.dictLookup("OP"); |
| if (obj2.isBool()) { |
| state->setStrokeOverprint(obj2.getBool()); |
| out->updateStrokeOverprint(state); |
| if (!haveFillOP) { |
| state->setFillOverprint(obj2.getBool()); |
| out->updateFillOverprint(state); |
| } |
| } |
| obj2 = obj1.dictLookup("OPM"); |
| if (obj2.isInt()) { |
| state->setOverprintMode(obj2.getInt()); |
| out->updateOverprintMode(state); |
| } |
| |
| // stroke adjust |
| obj2 = obj1.dictLookup("SA"); |
| if (obj2.isBool()) { |
| state->setStrokeAdjust(obj2.getBool()); |
| out->updateStrokeAdjust(state); |
| } |
| |
| // transfer function |
| obj2 = obj1.dictLookup("TR2"); |
| if (obj2.isNull()) { |
| obj2 = obj1.dictLookup("TR"); |
| } |
| if (obj2.isName("Default") || obj2.isName("Identity")) { |
| Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr }; |
| state->setTransfer(funcs); |
| out->updateTransfer(state); |
| } else if (obj2.isArray() && obj2.arrayGetLength() == 4) { |
| Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr }; |
| for (int i = 0; i < 4; ++i) { |
| Object obj3 = obj2.arrayGet(i); |
| funcs[i] = Function::parse(&obj3); |
| if (!funcs[i]) { |
| break; |
| } |
| } |
| if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) { |
| state->setTransfer(funcs); |
| out->updateTransfer(state); |
| } else { |
| for (Function *f : funcs) { |
| delete f; |
| } |
| } |
| } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) { |
| Function *funcs[4]; |
| if ((funcs[0] = Function::parse(&obj2))) { |
| funcs[1] = funcs[2] = funcs[3] = nullptr; |
| state->setTransfer(funcs); |
| out->updateTransfer(state); |
| } |
| } else if (!obj2.isNull()) { |
| error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState"); |
| } |
| |
| // alpha is shape |
| obj2 = obj1.dictLookup("AIS"); |
| if (obj2.isBool()) { |
| state->setAlphaIsShape(obj2.getBool()); |
| out->updateAlphaIsShape(state); |
| } |
| |
| // text knockout |
| obj2 = obj1.dictLookup("TK"); |
| if (obj2.isBool()) { |
| state->setTextKnockout(obj2.getBool()); |
| out->updateTextKnockout(state); |
| } |
| |
| // soft mask |
| obj2 = obj1.dictLookup("SMask"); |
| if (!obj2.isNull()) { |
| if (obj2.isName("None")) { |
| out->clearSoftMask(state); |
| } else if (obj2.isDict()) { |
| Object obj3 = obj2.dictLookup("S"); |
| if (obj3.isName("Alpha")) { |
| alpha = true; |
| } else { // "Luminosity" |
| alpha = false; |
| } |
| Function *softMaskTransferFunc = nullptr; |
| obj3 = obj2.dictLookup("TR"); |
| if (!obj3.isNull()) { |
| if (obj3.isName("Default") || obj3.isName("Identity")) { |
| // nothing |
| } else { |
| softMaskTransferFunc = Function::parse(&obj3); |
| if (softMaskTransferFunc == nullptr || softMaskTransferFunc->getInputSize() != 1 || softMaskTransferFunc->getOutputSize() != 1) { |
| error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState"); |
| delete softMaskTransferFunc; |
| softMaskTransferFunc = nullptr; |
| } |
| } |
| } |
| obj3 = obj2.dictLookup("BC"); |
| if ((haveBackdropColor = obj3.isArray())) { |
| for (int &c : backdropColor.c) { |
| c = 0; |
| } |
| for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { |
| Object obj4 = obj3.arrayGet(i); |
| if (obj4.isNum()) { |
| backdropColor.c[i] = dblToCol(obj4.getNum()); |
| } |
| } |
| } |
| obj3 = obj2.dictLookup("G"); |
| if (obj3.isStream()) { |
| Object obj4 = obj3.streamGetDict()->lookup("Group"); |
| if (obj4.isDict()) { |
| GfxColorSpace *blendingColorSpace = nullptr; |
| Object obj5 = obj4.dictLookup("CS"); |
| if (!obj5.isNull()) { |
| blendingColorSpace = GfxColorSpace::parse(res, &obj5, out, state); |
| } |
| const bool isolated = obj4.dictLookup("I").getBoolWithDefaultValue(false); |
| const bool knockout = obj4.dictLookup("K").getBoolWithDefaultValue(false); |
| if (!haveBackdropColor) { |
| if (blendingColorSpace) { |
| blendingColorSpace->getDefaultColor(&backdropColor); |
| } else { |
| //~ need to get the parent or default color space (?) |
| for (int &c : backdropColor.c) { |
| c = 0; |
| } |
| } |
| } |
| doSoftMask(&obj3, alpha, blendingColorSpace, isolated, knockout, softMaskTransferFunc, &backdropColor); |
| delete blendingColorSpace; |
| } else { |
| error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); |
| } |
| } else { |
| error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); |
| } |
| delete softMaskTransferFunc; |
| } else if (!obj2.isNull()) { |
| error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); |
| } |
| } |
| obj2 = obj1.dictLookup("Font"); |
| if (obj2.isArray()) { |
| if (obj2.arrayGetLength() == 2) { |
| const Object &fargs0 = obj2.arrayGetNF(0); |
| Object fargs1 = obj2.arrayGet(1); |
| if (fargs0.isRef() && fargs1.isNum()) { |
| Object fobj = fargs0.fetch(xref); |
| if (fobj.isDict()) { |
| Ref r = fargs0.getRef(); |
| std::shared_ptr<GfxFont> font = GfxFont::makeFont(xref, args[0].getName(), r, fobj.getDict()); |
| state->setFont(font, fargs1.getNum()); |
| fontChanged = true; |
| } |
| } |
| } else { |
| error(errSyntaxError, getPos(), "Number of args mismatch for /Font in ExtGState"); |
| } |
| } |
| obj2 = obj1.dictLookup("LW"); |
| if (obj2.isNum()) { |
| opSetLineWidth(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("LC"); |
| if (obj2.isInt()) { |
| opSetLineCap(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("LJ"); |
| if (obj2.isInt()) { |
| opSetLineJoin(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("ML"); |
| if (obj2.isNum()) { |
| opSetMiterLimit(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("D"); |
| if (obj2.isArray()) { |
| if (obj2.arrayGetLength() == 2) { |
| Object dargs[2]; |
| |
| dargs[0] = obj2.arrayGetNF(0).copy(); |
| dargs[1] = obj2.arrayGet(1); |
| if (dargs[0].isArray() && dargs[1].isInt()) { |
| opSetDash(dargs, 2); |
| } |
| } else { |
| error(errSyntaxError, getPos(), "Number of args mismatch for /D in ExtGState"); |
| } |
| } |
| obj2 = obj1.dictLookup("RI"); |
| if (obj2.isName()) { |
| opSetRenderingIntent(&obj2, 1); |
| } |
| obj2 = obj1.dictLookup("FL"); |
| if (obj2.isNum()) { |
| opSetFlat(&obj2, 1); |
| } |
| } |
| |
| void Gfx::doSoftMask(Object *str, bool alpha, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, Function *transferFunc, GfxColor *backdropColor) |
| { |
| Dict *dict, *resDict; |
| double m[6], bbox[4]; |
| Object obj1; |
| int i; |
| |
| // get stream dict |
| dict = str->streamGetDict(); |
| |
| // check form type |
| obj1 = dict->lookup("FormType"); |
| if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { |
| error(errSyntaxError, getPos(), "Unknown form type"); |
| } |
| |
| // get bounding box |
| obj1 = dict->lookup("BBox"); |
| if (!obj1.isArray()) { |
| error(errSyntaxError, getPos(), "Bad form bounding box"); |
| return; |
| } |
| for (i = 0; i < 4; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (likely(obj2.isNum())) { |
| bbox[i] = obj2.getNum(); |
| } else { |
| error(errSyntaxError, getPos(), "Bad form bounding box (non number)"); |
| return; |
| } |
| } |
| |
| // get matrix |
| obj1 = dict->lookup("Matrix"); |
| if (obj1.isArray()) { |
| for (i = 0; i < 6; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (likely(obj2.isNum())) { |
| m[i] = obj2.getNum(); |
| } else { |
| m[i] = 0; |
| } |
| } |
| } else { |
| m[0] = 1; |
| m[1] = 0; |
| m[2] = 0; |
| m[3] = 1; |
| m[4] = 0; |
| m[5] = 0; |
| } |
| |
| // get resources |
| obj1 = dict->lookup("Resources"); |
| resDict = obj1.isDict() ? obj1.getDict() : nullptr; |
| |
| // draw it |
| drawForm(str, resDict, m, bbox, true, true, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor); |
| } |
| |
| void Gfx::opSetRenderingIntent(Object args[], int numArgs) |
| { |
| state->setRenderingIntent(args[0].getName()); |
| } |
| |
| //------------------------------------------------------------------------ |
| // color operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opSetFillGray(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxColorSpace *colorSpace = nullptr; |
| |
| state->setFillPattern(nullptr); |
| Object obj = res->lookupColorSpace("DefaultGray"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr || colorSpace->getNComps() > 1) { |
| delete colorSpace; |
| colorSpace = state->copyDefaultGrayColorSpace(); |
| } |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| color.c[0] = dblToCol(args[0].getNum()); |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| |
| void Gfx::opSetStrokeGray(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxColorSpace *colorSpace = nullptr; |
| |
| state->setStrokePattern(nullptr); |
| Object obj = res->lookupColorSpace("DefaultGray"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr) { |
| colorSpace = state->copyDefaultGrayColorSpace(); |
| } |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| color.c[0] = dblToCol(args[0].getNum()); |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillCMYKColor(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxColorSpace *colorSpace = nullptr; |
| int i; |
| |
| Object obj = res->lookupColorSpace("DefaultCMYK"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr) { |
| colorSpace = state->copyDefaultCMYKColorSpace(); |
| } |
| state->setFillPattern(nullptr); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| for (i = 0; i < 4; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| |
| void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxColorSpace *colorSpace = nullptr; |
| int i; |
| |
| state->setStrokePattern(nullptr); |
| Object obj = res->lookupColorSpace("DefaultCMYK"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr) { |
| colorSpace = state->copyDefaultCMYKColorSpace(); |
| } |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| for (i = 0; i < 4; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillRGBColor(Object args[], int numArgs) |
| { |
| GfxColorSpace *colorSpace = nullptr; |
| GfxColor color; |
| int i; |
| |
| state->setFillPattern(nullptr); |
| Object obj = res->lookupColorSpace("DefaultRGB"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr || colorSpace->getNComps() > 3) { |
| delete colorSpace; |
| colorSpace = state->copyDefaultRGBColorSpace(); |
| } |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| for (i = 0; i < 3; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| |
| void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) |
| { |
| GfxColorSpace *colorSpace = nullptr; |
| GfxColor color; |
| int i; |
| |
| state->setStrokePattern(nullptr); |
| Object obj = res->lookupColorSpace("DefaultRGB"); |
| if (!obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace == nullptr) { |
| colorSpace = state->copyDefaultRGBColorSpace(); |
| } |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| for (i = 0; i < 3; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillColorSpace(Object args[], int numArgs) |
| { |
| GfxColorSpace *colorSpace; |
| GfxColor color; |
| |
| Object obj = res->lookupColorSpace(args[0].getName()); |
| if (obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &args[0], out, state); |
| } else { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace) { |
| state->setFillPattern(nullptr); |
| state->setFillColorSpace(colorSpace); |
| out->updateFillColorSpace(state); |
| colorSpace->getDefaultColor(&color); |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } else { |
| error(errSyntaxError, getPos(), "Bad color space (fill)"); |
| } |
| } |
| |
| void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) |
| { |
| GfxColorSpace *colorSpace; |
| GfxColor color; |
| |
| state->setStrokePattern(nullptr); |
| Object obj = res->lookupColorSpace(args[0].getName()); |
| if (obj.isNull()) { |
| colorSpace = GfxColorSpace::parse(res, &args[0], out, state); |
| } else { |
| colorSpace = GfxColorSpace::parse(res, &obj, out, state); |
| } |
| if (colorSpace) { |
| state->setStrokeColorSpace(colorSpace); |
| out->updateStrokeColorSpace(state); |
| colorSpace->getDefaultColor(&color); |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } else { |
| error(errSyntaxError, getPos(), "Bad color space (stroke)"); |
| } |
| } |
| |
| void Gfx::opSetFillColor(Object args[], int numArgs) |
| { |
| GfxColor color; |
| int i; |
| |
| if (numArgs != state->getFillColorSpace()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command"); |
| return; |
| } |
| state->setFillPattern(nullptr); |
| for (i = 0; i < numArgs; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| |
| void Gfx::opSetStrokeColor(Object args[], int numArgs) |
| { |
| GfxColor color; |
| int i; |
| |
| if (numArgs != state->getStrokeColorSpace()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command"); |
| return; |
| } |
| state->setStrokePattern(nullptr); |
| for (i = 0; i < numArgs; ++i) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| |
| void Gfx::opSetFillColorN(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxPattern *pattern; |
| int i; |
| |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| if (numArgs > 1) { |
| if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); |
| return; |
| } |
| for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| if (numArgs > 0) { |
| if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) { |
| state->setFillPattern(pattern); |
| } |
| } |
| |
| } else { |
| if (numArgs != state->getFillColorSpace()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); |
| return; |
| } |
| state->setFillPattern(nullptr); |
| for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setFillColor(&color); |
| out->updateFillColor(state); |
| } |
| } |
| |
| void Gfx::opSetStrokeColorN(Object args[], int numArgs) |
| { |
| GfxColor color; |
| GfxPattern *pattern; |
| int i; |
| |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| if (numArgs > 1) { |
| if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); |
| return; |
| } |
| for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| if (unlikely(numArgs <= 0)) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); |
| return; |
| } |
| if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) { |
| state->setStrokePattern(pattern); |
| } |
| |
| } else { |
| if (numArgs != state->getStrokeColorSpace()->getNComps()) { |
| error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); |
| return; |
| } |
| state->setStrokePattern(nullptr); |
| for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { |
| if (args[i].isNum()) { |
| color.c[i] = dblToCol(args[i].getNum()); |
| } else { |
| color.c[i] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| state->setStrokeColor(&color); |
| out->updateStrokeColor(state); |
| } |
| } |
| |
| //------------------------------------------------------------------------ |
| // path segment operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opMoveTo(Object args[], int numArgs) |
| { |
| state->moveTo(args[0].getNum(), args[1].getNum()); |
| } |
| |
| void Gfx::opLineTo(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| error(errSyntaxError, getPos(), "No current point in lineto"); |
| return; |
| } |
| state->lineTo(args[0].getNum(), args[1].getNum()); |
| } |
| |
| void Gfx::opCurveTo(Object args[], int numArgs) |
| { |
| double x1, y1, x2, y2, x3, y3; |
| |
| if (!state->isCurPt()) { |
| error(errSyntaxError, getPos(), "No current point in curveto"); |
| return; |
| } |
| x1 = args[0].getNum(); |
| y1 = args[1].getNum(); |
| x2 = args[2].getNum(); |
| y2 = args[3].getNum(); |
| x3 = args[4].getNum(); |
| y3 = args[5].getNum(); |
| state->curveTo(x1, y1, x2, y2, x3, y3); |
| } |
| |
| void Gfx::opCurveTo1(Object args[], int numArgs) |
| { |
| double x1, y1, x2, y2, x3, y3; |
| |
| if (!state->isCurPt()) { |
| error(errSyntaxError, getPos(), "No current point in curveto1"); |
| return; |
| } |
| x1 = state->getCurX(); |
| y1 = state->getCurY(); |
| x2 = args[0].getNum(); |
| y2 = args[1].getNum(); |
| x3 = args[2].getNum(); |
| y3 = args[3].getNum(); |
| state->curveTo(x1, y1, x2, y2, x3, y3); |
| } |
| |
| void Gfx::opCurveTo2(Object args[], int numArgs) |
| { |
| double x1, y1, x2, y2, x3, y3; |
| |
| if (!state->isCurPt()) { |
| error(errSyntaxError, getPos(), "No current point in curveto2"); |
| return; |
| } |
| x1 = args[0].getNum(); |
| y1 = args[1].getNum(); |
| x2 = args[2].getNum(); |
| y2 = args[3].getNum(); |
| x3 = x2; |
| y3 = y2; |
| state->curveTo(x1, y1, x2, y2, x3, y3); |
| } |
| |
| void Gfx::opRectangle(Object args[], int numArgs) |
| { |
| double x, y, w, h; |
| |
| x = args[0].getNum(); |
| y = args[1].getNum(); |
| w = args[2].getNum(); |
| h = args[3].getNum(); |
| state->moveTo(x, y); |
| state->lineTo(x + w, y); |
| state->lineTo(x + w, y + h); |
| state->lineTo(x, y + h); |
| state->closePath(); |
| } |
| |
| void Gfx::opClosePath(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| error(errSyntaxError, getPos(), "No current point in closepath"); |
| return; |
| } |
| state->closePath(); |
| } |
| |
| //------------------------------------------------------------------------ |
| // path painting operators |
| //------------------------------------------------------------------------ |
| |
| void Gfx::opEndPath(Object args[], int numArgs) |
| { |
| doEndPath(); |
| } |
| |
| void Gfx::opStroke(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| if (ocState) { |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in closepath/stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| state->closePath(); |
| if (ocState) { |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opFill(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in fill"); |
| return; |
| } |
| if (state->isPath()) { |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(false); |
| } else { |
| out->fill(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opEOFill(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in eofill"); |
| return; |
| } |
| if (state->isPath()) { |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(true); |
| } else { |
| out->eoFill(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opFillStroke(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in fill/stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(false); |
| } else { |
| out->fill(state); |
| } |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opCloseFillStroke(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in closepath/fill/stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| state->closePath(); |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(false); |
| } else { |
| out->fill(state); |
| } |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opEOFillStroke(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in eofill/stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(true); |
| } else { |
| out->eoFill(state); |
| } |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::opCloseEOFillStroke(Object args[], int numArgs) |
| { |
| if (!state->isCurPt()) { |
| // error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke"); |
| return; |
| } |
| if (state->isPath()) { |
| state->closePath(); |
| if (ocState) { |
| if (state->getFillColorSpace()->getMode() == csPattern) { |
| doPatternFill(true); |
| } else { |
| out->eoFill(state); |
| } |
| if (state->getStrokeColorSpace()->getMode() == csPattern) { |
| doPatternStroke(); |
| } else { |
| out->stroke(state); |
| } |
| } |
| } |
| doEndPath(); |
| } |
| |
| void Gfx::doPatternFill(bool eoFill) |
| { |
| GfxPattern *pattern; |
| |
| // this is a bit of a kludge -- patterns can be really slow, so we |
| // skip them if we're only doing text extraction, since they almost |
| // certainly don't contain any text |
| if (!out->needNonText()) { |
| return; |
| } |
| |
| if (!(pattern = state->getFillPattern())) { |
| return; |
| } |
| switch (pattern->getType()) { |
| case 1: |
| doTilingPatternFill((GfxTilingPattern *)pattern, false, eoFill, false); |
| break; |
| case 2: |
| doShadingPatternFill((GfxShadingPattern *)pattern, false, eoFill, false); |
| break; |
| default: |
| error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType()); |
| break; |
| } |
| } |
| |
| void Gfx::doPatternStroke() |
| { |
| GfxPattern *pattern; |
| |
| // this is a bit of a kludge -- patterns can be really slow, so we |
| // skip them if we're only doing text extraction, since they almost |
| // certainly don't contain any text |
| if (!out->needNonText()) { |
| return; |
| } |
| |
| if (!(pattern = state->getStrokePattern())) { |
| return; |
| } |
| switch (pattern->getType()) { |
| case 1: |
| doTilingPatternFill((GfxTilingPattern *)pattern, true, false, false); |
| break; |
| case 2: |
| doShadingPatternFill((GfxShadingPattern *)pattern, true, false, false); |
| break; |
| default: |
| error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in stroke", pattern->getType()); |
| break; |
| } |
| } |
| |
| void Gfx::doPatternText() |
| { |
| GfxPattern *pattern; |
| |
| // this is a bit of a kludge -- patterns can be really slow, so we |
| // skip them if we're only doing text extraction, since they almost |
| // certainly don't contain any text |
| if (!out->needNonText()) { |
| return; |
| } |
| |
| if (!(pattern = state->getFillPattern())) { |
| return; |
| } |
| switch (pattern->getType()) { |
| case 1: |
| doTilingPatternFill((GfxTilingPattern *)pattern, false, false, true); |
| break; |
| case 2: |
| doShadingPatternFill((GfxShadingPattern *)pattern, false, false, true); |
| break; |
| default: |
| error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType()); |
| break; |
| } |
| } |
| |
| void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg) |
| { |
| saveState(); |
| |
| out->setSoftMaskFromImageMask(state, ref, str, width, height, invert, inlineImg, baseMatrix); |
| |
| state->clearPath(); |
| state->moveTo(0, 0); |
| state->lineTo(1, 0); |
| state->lineTo(1, 1); |
| state->lineTo(0, 1); |
| state->closePath(); |
| doPatternText(); |
| |
| out->unsetSoftMaskFromImageMask(state, baseMatrix); |
| restoreState(); |
| } |
| |
| void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, bool stroke, bool eoFill, bool text) |
| { |
| GfxPatternColorSpace *patCS; |
| GfxColorSpace *cs; |
| GfxColor color; |
| GfxState *savedState; |
| double xMin, yMin, xMax, yMax, x, y, x1, y1; |
| double cxMin, cyMin, cxMax, cyMax; |
| int xi0, yi0, xi1, yi1, xi, yi; |
| const double *ctm, *btm, *ptm; |
| double m[6], ictm[6], m1[6], imb[6]; |
| double det; |
| double xstep, ystep; |
| int i; |
| |
| // get color space |
| patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace() : state->getFillColorSpace()); |
| |
| // construct a (pattern space) -> (current space) transform matrix |
| ctm = state->getCTM(); |
| btm = baseMatrix; |
| ptm = tPat->getMatrix(); |
| // iCTM = invert CTM |
| det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; |
| if (fabs(det) < 0.000001) { |
| error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); |
| return; |
| } |
| det = 1 / det; |
| ictm[0] = ctm[3] * det; |
| ictm[1] = -ctm[1] * det; |
| ictm[2] = -ctm[2] * det; |
| ictm[3] = ctm[0] * det; |
| ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; |
| ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; |
| // m1 = PTM * BTM = PTM * base transform matrix |
| m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2]; |
| m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3]; |
| m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2]; |
| m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3]; |
| m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4]; |
| m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5]; |
| // m = m1 * iCTM = (PTM * BTM) * (iCTM) |
| m[0] = m1[0] * ictm[0] + m1[1] * ictm[2]; |
| m[1] = m1[0] * ictm[1] + m1[1] * ictm[3]; |
| m[2] = m1[2] * ictm[0] + m1[3] * ictm[2]; |
| m[3] = m1[2] * ictm[1] + m1[3] * ictm[3]; |
| m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4]; |
| m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; |
| |
| // construct a (device space) -> (pattern space) transform matrix |
| det = m1[0] * m1[3] - m1[1] * m1[2]; |
| det = 1 / det; |
| if (!std::isfinite(det)) { |
| error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); |
| return; |
| } |
| imb[0] = m1[3] * det; |
| imb[1] = -m1[1] * det; |
| imb[2] = -m1[2] * det; |
| imb[3] = m1[0] * det; |
| imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det; |
| imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det; |
| |
| // save current graphics state |
| savedState = saveStateStack(); |
| |
| // set underlying color space (for uncolored tiling patterns); set |
| // various other parameters (stroke color, line width) to match |
| // Adobe's behavior |
| state->setFillPattern(nullptr); |
| state->setStrokePattern(nullptr); |
| if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) { |
| state->setFillColorSpace(cs->copy()); |
| out->updateFillColorSpace(state); |
| state->setStrokeColorSpace(cs->copy()); |
| out->updateStrokeColorSpace(state); |
| if (stroke) { |
| state->setFillColor(state->getStrokeColor()); |
| } else { |
| state->setStrokeColor(state->getFillColor()); |
| } |
| out->updateFillColor(state); |
| out->updateStrokeColor(state); |
| } else { |
| cs = new GfxDeviceGrayColorSpace(); |
| state->setFillColorSpace(cs); |
| cs->getDefaultColor(&color); |
| state->setFillColor(&color); |
| out->updateFillColorSpace(state); |
| state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); |
| state->setStrokeColor(&color); |
| out->updateStrokeColorSpace(state); |
| } |
| if (!stroke) { |
| state->setLineWidth(0); |
| out->updateLineWidth(state); |
| } |
| |
| // clip to current path |
| if (stroke) { |
| state->clipToStrokePath(); |
| out->clipToStrokePath(state); |
| } else if (!text) { |
| state->clip(); |
| if (eoFill) { |
| out->eoClip(state); |
| } else { |
| out->clip(state); |
| } |
| } |
| state->clearPath(); |
| |
| // get the clip region, check for empty |
| state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax); |
| if (cxMin > cxMax || cyMin > cyMax) { |
| goto restore; |
| } |
| |
| // transform clip region bbox to pattern space |
| xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4]; |
| yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5]; |
| x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4]; |
| y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4]; |
| y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4]; |
| y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5]; |
| if (x1 < xMin) { |
| xMin = x1; |
| } else if (x1 > xMax) { |
| xMax = x1; |
| } |
| if (y1 < yMin) { |
| yMin = y1; |
| } else if (y1 > yMax) { |
| yMax = y1; |
| } |
| |
| // draw the pattern |
| //~ this should treat negative steps differently -- start at right/top |
| //~ edge instead of left/bottom (?) |
| xstep = fabs(tPat->getXStep()); |
| ystep = fabs(tPat->getYStep()); |
| if (unlikely(xstep == 0 || ystep == 0)) { |
| goto restore; |
| } |
| if (tPat->getBBox()[0] < tPat->getBBox()[2]) { |
| xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep); |
| xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1; |
| } else { |
| xi0 = (int)ceil((xMin - tPat->getBBox()[0]) / xstep); |
| xi1 = (int)floor((xMax - tPat->getBBox()[2]) / xstep) + 1; |
| } |
| if (tPat->getBBox()[1] < tPat->getBBox()[3]) { |
| yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep); |
| yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1; |
| } else { |
| yi0 = (int)ceil((yMin - tPat->getBBox()[1]) / ystep); |
| yi1 = (int)floor((yMax - tPat->getBBox()[3]) / ystep) + 1; |
| } |
| for (i = 0; i < 4; ++i) { |
| m1[i] = m[i]; |
| } |
| m1[4] = m[4]; |
| m1[5] = m[5]; |
| { |
| bool shouldDrawPattern = true; |
| std::set<int>::iterator patternRefIt; |
| const int patternRefNum = tPat->getPatternRefNum(); |
| if (patternRefNum != -1) { |
| if (formsDrawing.find(patternRefNum) == formsDrawing.end()) { |
| patternRefIt = formsDrawing.insert(patternRefNum).first; |
| } else { |
| shouldDrawPattern = false; |
| } |
| } |
| if (shouldDrawPattern) { |
| if (out->useTilingPatternFill() && out->tilingPatternFill(state, this, catalog, tPat, m1, xi0, yi0, xi1, yi1, xstep, ystep)) { |
| // do nothing |
| } else { |
| out->updatePatternOpacity(state); |
| for (yi = yi0; yi < yi1; ++yi) { |
| for (xi = xi0; xi < xi1; ++xi) { |
| x = xi * xstep; |
| y = yi * ystep; |
| m1[4] = x * m[0] + y * m[2] + m[4]; |
| m1[5] = x * m[1] + y * m[3] + m[5]; |
| drawForm(tPat->getContentStream(), tPat->getResDict(), m1, tPat->getBBox()); |
| } |
| } |
| out->clearPatternOpacity(state); |
| } |
| if (patternRefNum != -1) { |
| formsDrawing.erase(patternRefIt); |
| } |
| } |
| } |
| |
| // restore graphics state |
| restore: |
| restoreStateStack(savedState); |
| } |
| |
| void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, bool stroke, bool eoFill, bool text) |
| { |
| GfxShading *shading; |
| GfxState *savedState; |
| const double *ctm, *btm, *ptm; |
| double m[6], ictm[6], m1[6]; |
| double xMin, yMin, xMax, yMax; |
| double det; |
| |
| shading = sPat->getShading(); |
| |
| // save current graphics state |
| savedState = saveStateStack(); |
| |
| // clip to current path |
| if (stroke) { |
| state->clipToStrokePath(); |
| out->clipToStrokePath(state); |
| } else if (!text) { |
| state->clip(); |
| if (eoFill) { |
| out->eoClip(state); |
| } else { |
| out->clip(state); |
| } |
| } |
| state->clearPath(); |
| |
| // construct a (pattern space) -> (current space) transform matrix |
| ctm = state->getCTM(); |
| btm = baseMatrix; |
| ptm = sPat->getMatrix(); |
| // iCTM = invert CTM |
| det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; |
| if (fabs(det) < 0.000001) { |
| error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill"); |
| restoreStateStack(savedState); |
| return; |
| } |
| det = 1 / det; |
| ictm[0] = ctm[3] * det; |
| ictm[1] = -ctm[1] * det; |
| ictm[2] = -ctm[2] * det; |
| ictm[3] = ctm[0] * det; |
| ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; |
| ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; |
| // m1 = PTM * BTM = PTM * base transform matrix |
| m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2]; |
| m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3]; |
| m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2]; |
| m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3]; |
| m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4]; |
| m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5]; |
| // m = m1 * iCTM = (PTM * BTM) * (iCTM) |
| m[0] = m1[0] * ictm[0] + m1[1] * ictm[2]; |
| m[1] = m1[0] * ictm[1] + m1[1] * ictm[3]; |
| m[2] = m1[2] * ictm[0] + m1[3] * ictm[2]; |
| m[3] = m1[2] * ictm[1] + m1[3] * ictm[3]; |
| m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4]; |
| m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; |
| |
| // set the new matrix |
| state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]); |
| out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]); |
| |
| // clip to bbox |
| if (shading->getHasBBox()) { |
| shading->getBBox(&xMin, &yMin, &xMax, &yMax); |
| state->moveTo(xMin, yMin); |
| state->lineTo(xMax, yMin); |
| state->lineTo(xMax, yMax); |
| state->lineTo(xMin, yMax); |
| state->closePath(); |
| state->clip(); |
| out->clip(state); |
| state->clearPath(); |
| } |
| |
| // set the color space |
| state->setFillColorSpace(shading->getColorSpace()->copy()); |
| out->updateFillColorSpace(state); |
| |
| // background color fill |
| if (shading->getHasBackground()) { |
| state->setFillColor(shading->getBackground()); |
| out->updateFillColor(state); |
| state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); |
| state->moveTo(xMin, yMin); |
| state->lineTo(xMax, yMin); |
| state->lineTo(xMax, yMax); |
| state->lineTo(xMin, yMax); |
| state->closePath(); |
| out->fill(state); |
| state->clearPath(); |
| } |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| bool vaa = out->getVectorAntialias(); |
| if (vaa) { |
| out->setVectorAntialias(false); |
| } |
| #endif |
| |
| // do shading type-specific operations |
| switch (shading->getType()) { |
| case GfxShading::FunctionBasedShading: |
| doFunctionShFill((GfxFunctionShading *)shading); |
| break; |
| case GfxShading::AxialShading: |
| doAxialShFill((GfxAxialShading *)shading); |
| break; |
| case GfxShading::RadialShading: |
| doRadialShFill((GfxRadialShading *)shading); |
| break; |
| case GfxShading::FreeFormGouraudShadedTriangleMesh: |
| case GfxShading::LatticeFormGouraudShadedTriangleMesh: |
| doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading); |
| break; |
| case GfxShading::CoonsPatchMesh: |
| case GfxShading::TensorProductPatchMesh: |
| 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 GfxShading::FunctionBasedShading: |
| doFunctionShFill((GfxFunctionShading *)shading); |
| break; |
| case GfxShading::AxialShading: |
| doAxialShFill((GfxAxialShading *)shading); |
| break; |
| case GfxShading::RadialShading: |
| doRadialShFill((GfxRadialShading *)shading); |
| break; |
| case GfxShading::FreeFormGouraudShadedTriangleMesh: |
| case GfxShading::LatticeFormGouraudShadedTriangleMesh: |
| doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading); |
| break; |
| case GfxShading::CoonsPatchMesh: |
| case GfxShading::TensorProductPatchMesh: |
| doPatchMeshShFill((GfxPatchMeshShading *)shading); |
| break; |
| } |
| |
| #if 1 //~tmp: turn off anti-aliasing temporarily |
| if (vaa) { |
| out->setVectorAntialias(true); |
| } |
| #endif |
| |
| // restore graphics state |
| restoreStateStack(savedState); |
| |
| delete shading; |
| } |
| |
| void Gfx::doFunctionShFill(GfxFunctionShading *shading) |
| { |
| double x0, y0, x1, y1; |
| GfxColor colors[4]; |
| |
| if (out->useShadedFills(shading->getType()) && out->functionShadedFill(state, shading)) { |
| return; |
| } |
| |
| shading->getDomain(&x0, &y0, &x1, &y1); |
| shading->getColor(x0, y0, &colors[0]); |
| shading->getColor(x0, y1, &colors[1]); |
| shading->getColor(x1, y0, &colors[2]); |
| shading->getColor(x1, y1, &colors[3]); |
| doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0); |
| } |
| |
| void Gfx::doFunctionShFill1(GfxFunctionShading *shading, double x0, double y0, double x1, double y1, GfxColor *colors, int depth) |
| { |
| GfxColor fillColor; |
| GfxColor color0M, color1M, colorM0, colorM1, colorMM; |
| GfxColor colors2[4]; |
| double xM, yM; |
| int nComps, i, j; |
| |
| nComps = shading->getColorSpace()->getNComps(); |
| const double *matrix = shading->getMatrix(); |
| |
| // compare the four corner colors |
| for (i = 0; i < 4; ++i) { |
| for (j = 0; j < nComps; ++j) { |
| if (abs(colors[i].c[j] - colors[(i + 1) & 3].c[j]) > functionColorDelta) { |
| break; |
| } |
| } |
| if (j < nComps) { |
| break; |
| } |
| } |
| |
| // center of the rectangle |
| xM = 0.5 * (x0 + x1); |
| yM = 0.5 * (y0 + y1); |
| |
| // the four corner colors are close (or we hit the recursive limit) |
| // -- fill the rectangle; but require at least one subdivision |
| // (depth==0) to avoid problems when the four outer corners of the |
| // shaded region are the same color |
| if ((i == 4 && depth > 0) || depth == functionMaxDepth) { |
| |
| // use the center color |
| shading->getColor(xM, yM, &fillColor); |
| state->setFillColor(&fillColor); |
| out->updateFillColor(state); |
| |
| // fill the rectangle |
| state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4], x0 * matrix[1] + y0 * matrix[3] + matrix[5]); |
| state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4], x1 * matrix[1] + y0 * matrix[3] + matrix[5]); |
| state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4], x1 * matrix[1] + y1 * matrix[3] + matrix[5]); |
| state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4], x0 * matrix[1] + y1 * matrix[3] + matrix[5]); |
| state->closePath(); |
| out->fill(state); |
| state->clearPath(); |
| |
| // the four corner colors are not close enough -- subdivide the |
| // rectangle |
| } else { |
| |
| // colors[0] colorM0 colors[2] |
| // (x0,y0) (xM,y0) (x1,y0) |
| // +----------+----------+ |
| // | | | |
| // | UL | UR | |
| // color0M | colorMM | color1M |
| // (x0,yM) +----------+----------+ (x1,yM) |
| // | (xM,yM) | |
| // | LL | LR | |
| // | | | |
| // +----------+----------+ |
| // colors[1] colorM1 colors[3] |
| // (x0,y1) (xM,y1) (x1,y1) |
| |
| shading->getColor(x0, yM, &color0M); |
| shading->getColor(x1, yM, &color1M); |
| shading->getColor(xM, y0, &colorM0); |
| shading->getColor(xM, y1, &colorM1); |
| shading->getColor(xM, yM, &colorMM); |
| |
| // upper-left sub-rectangle |
| colors2[0] = colors[0]; |
| colors2[1] = color0M; |
| colors2[2] = colorM0; |
| colors2[3] = colorMM; |
| doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1); |
| |
| // lower-left sub-rectangle |
| colors2[0] = color0M; |
| colors2[1] = colors[1]; |
| colors2[2] = colorMM; |
| colors2[3] = colorM1; |
| doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1); |
| |
| // upper-right sub-rectangle |
| colors2[0] = colorM0; |
| colors2[1] = colorMM; |
| colors2[2] = colors[2]; |
| colors2[3] = color1M; |
| doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1); |
| |
| // lower-right sub-rectangle |
| colors2[0] = colorMM; |
| colors2[1] = colorM1; |
| colors2[2] = color1M; |
| colors2[3] = colors[3]; |
| doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1); |
| } |
| } |
| |
| void Gfx::doAxialShFill(GfxAxialShading *shading) |
| { |
| double xMin, yMin, xMax, yMax; |
| double x0, y0, x1, y1; |
| double dx, dy, mul; |
| bool dxZero, dyZero; |
| double bboxIntersections[4]; |
| double tMin, tMax, tx, ty; |
| double s[4], sMin, sMax, tmp; |
| double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1; |
| double t0, t1, tt; |
| double ta[axialMaxSplits + 1]; |
| int next[axialMaxSplits + 1]; |
| GfxColor color0 = {}, color1 = {}; |
| int nComps; |
| int i, j, k; |
| bool needExtend = true; |
| |
| // get the clip region bbox |
| state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); |
| |
| // compute min and max t values, based on the four corners of the |
| // clip region bbox |
| shading->getCoords(&x0, &y0, &x1, &y1); |
| dx = x1 - x0; |
| dy = y1 - y0; |
| dxZero = fabs(dx) < 0.01; |
| dyZero = fabs(dy) < 0.01; |
| if (dxZero && dyZero) { |
| tMin = tMax = 0; |
| } else { |
| mul = 1 / (dx * dx + dy * dy); |
| bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; |
| bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; |
| bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; |
| bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; |
| std::sort(std::begin(bboxIntersections), std::end(bboxIntersections)); |
| tMin = bboxIntersections[0]; |
| tMax = bboxIntersections[3]; |
| if (tMin < 0 && !shading->getExtend0()) { |
| tMin = 0; |
| } |
| if (tMax > 1 && !shading->getExtend1()) { |
| tMax = 1; |
| } |
| } |
| |
| if (out->useShadedFills(shading->getType()) && out->axialShadedFill(state, shading, tMin, tMax)) { |
| return; |
| } |
| |
| // get the function domain |
| t0 = shading->getDomain0(); |
| t1 = shading->getDomain1(); |
| |
| // Traverse the t axis and do the shading. |
| // |
| // For each point (tx, ty) on the t axis, consider a line through |
| // that point perpendicular to the t axis: |
| // |
| // x(s) = tx + s * -dy --> s = (x - tx) / -dy |
| // y(s) = ty + s * dx --> s = (y - ty) / dx |
| // |
| // Then look at the intersection of this line with the bounding box |
| // (xMin, yMin, xMax, yMax). In the general case, there are four |
| // intersection points: |
| // |
| // s0 = (xMin - tx) / -dy |
| // s1 = (xMax - tx) / -dy |
| // s2 = (yMin - ty) / dx |
| // s3 = (yMax - ty) / dx |
| // |
| // and we want the middle two s values. |
| // |
| // In the case where dx = 0, take s0 and s1; in the case where dy = |
| // 0, take s2 and s3. |
| // |
| // Each filled polygon is bounded by two of these line segments |
| // perpdendicular to the t axis. |
| // |
| // The t axis is bisected into smaller regions until the color |
| // difference across a region is small enough, and then the region |
| // is painted with a single color. |
| |
| // set up: require at least one split to avoid problems when the two |
| // ends of the t axis have the same color |
| nComps = shading->getColorSpace()->getNComps(); |
| ta[0] = tMin; |
| next[0] = axialMaxSplits / 2; |
| ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax); |
| next[axialMaxSplits / 2] = axialMaxSplits; |
| ta[axialMaxSplits] = tMax; |
| |
| // compute the color at t = tMin |
| if (tMin < 0) { |
| tt = t0; |
| } else if (tMin > 1) { |
| tt = t1; |
| } else { |
| tt = t0 + (t1 - t0) * tMin; |
| } |
| shading->getColor(tt, &color0); |
| |
| if (out->useFillColorStop()) { |
| // make sure we add stop color when t = tMin |
| state->setFillColor(&color0); |
| out->updateFillColorStop(state, 0); |
| } |
| |
| // compute the coordinates of the point on the t axis at t = tMin; |
| // then compute the intersection of the perpendicular line with the |
| // bounding box |
| tx = x0 + tMin * dx; |
| ty = y0 + tMin * dy; |
| if (dxZero && dyZero) { |
| sMin = sMax = 0; |
| |