1 //========================================================================
2 //
3 // Gfx.cc
4 //
5 // Copyright 1996-2013 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 //========================================================================
10 //
11 // Modified under the Poppler project - http://poppler.freedesktop.org
12 //
13 // All changes made under the Poppler project to this file are licensed
14 // under GPL version 2 or later
15 //
16 // Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com>
17 // Copyright (C) 2005-2013, 2015-2021 Albert Astals Cid <aacid@kde.org>
18 // Copyright (C) 2006 Thorkild Stray <thorkild@ifi.uio.no>
19 // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
20 // Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org>
21 // Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
22 // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org>
23 // Copyright (C) 2007, 2011, 2017, 2021 Adrian Johnson <ajohnson@redneon.com>
24 // Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com>
25 // Copyright (C) 2007 Koji Otani <sho@bbr.jp>
26 // Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
27 // Copyright (C) 2008 Pino Toscano <pino@kde.org>
28 // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
29 // Copyright (C) 2008 Hib Eris <hib@hiberis.nl>
30 // Copyright (C) 2009 M Joonas Pihlaja <jpihlaja@cc.helsinki.fi>
31 // Copyright (C) 2009-2016, 2020 Thomas Freitag <Thomas.Freitag@alfa.de>
32 // Copyright (C) 2009 William Bader <williambader@hotmail.com>
33 // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
34 // Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com>
35 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
36 // Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de>
37 // Copyright (C) 2012 Even Rouault <even.rouault@mines-paris.org>
38 // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
39 // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com>
40 // Copyright (C) 2014 Jason Crain <jason@aquaticape.us>
41 // 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
42 // Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
43 // Copyright (C) 2018 Denis Onishchenko <denis.onischenko@gmail.com>
44 // Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
45 // Copyright (C) 2019-2021 Oliver Sander <oliver.sander@tu-dresden.de>
46 // Copyright (C) 2019 Volker Krause <vkrause@kde.org>
47 // Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
48 // Copyright (C) 2021 Steve Rosenhamer <srosenhamer@me.com>
49 //
50 // To see a description of the changes please see the Changelog file that
51 // came with your tarball or type make ChangeLog if you are building from git
52 //
53 //========================================================================
54 
55 #include <config.h>
56 
57 #include <cstdlib>
58 #include <cstdio>
59 #include <cstddef>
60 #include <cstring>
61 #include <cmath>
62 #include <memory>
63 #include "goo/gmem.h"
64 #include "goo/GooTimer.h"
65 #include "GlobalParams.h"
66 #include "CharTypes.h"
67 #include "Object.h"
68 #include "PDFDoc.h"
69 #include "Array.h"
70 #include "Annot.h"
71 #include "Dict.h"
72 #include "Stream.h"
73 #include "Lexer.h"
74 #include "Parser.h"
75 #include "GfxFont.h"
76 #include "GfxState.h"
77 #include "OutputDev.h"
78 #include "Page.h"
79 #include "Annot.h"
80 #include "Error.h"
81 #include "Gfx.h"
82 #include "ProfileData.h"
83 #include "Catalog.h"
84 #include "OptionalContent.h"
85 
86 // the MSVC math.h doesn't define this
87 #ifndef M_PI
88 #    define M_PI 3.14159265358979323846
89 #endif
90 
91 //------------------------------------------------------------------------
92 // constants
93 //------------------------------------------------------------------------
94 
95 // Max recursive depth for a function shading fill.
96 #define functionMaxDepth 6
97 
98 // Max delta allowed in any color component for a function shading fill.
99 #define functionColorDelta (dblToCol(1 / 256.0))
100 
101 // Max number of splits along the t axis for an axial shading fill.
102 #define axialMaxSplits 256
103 
104 // Max delta allowed in any color component for an axial shading fill.
105 #define axialColorDelta (dblToCol(1 / 256.0))
106 
107 // Max number of splits along the t axis for a radial shading fill.
108 #define radialMaxSplits 256
109 
110 // Max delta allowed in any color component for a radial shading fill.
111 #define radialColorDelta (dblToCol(1 / 256.0))
112 
113 // Max recursive depth for a Gouraud triangle shading fill.
114 //
115 // Triangles will be split at most gouraudMaxDepth times (each time into 4
116 // smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for
117 // every triangle.
118 #define gouraudMaxDepth 6
119 
120 // Max delta allowed in any color component for a Gouraud triangle
121 // shading fill.
122 #define gouraudColorDelta (dblToCol(3. / 256.0))
123 
124 // Gouraud triangle: if the three color parameters differ by at more than this percend of
125 // the total color parameter range, the triangle will be refined
126 #define gouraudParameterizedColorDelta 5e-3
127 
128 // Max recursive depth for a patch mesh shading fill.
129 #define patchMaxDepth 6
130 
131 // Max delta allowed in any color component for a patch mesh shading
132 // fill.
133 #define patchColorDelta (dblToCol((3. / 256.0)))
134 
135 //------------------------------------------------------------------------
136 // Operator table
137 //------------------------------------------------------------------------
138 
139 const Operator Gfx::opTab[] = {
140     { "\"", 3, { tchkNum, tchkNum, tchkString }, &Gfx::opMoveSetShowText },
141     { "'", 1, { tchkString }, &Gfx::opMoveShowText },
142     { "B", 0, { tchkNone }, &Gfx::opFillStroke },
143     { "B*", 0, { tchkNone }, &Gfx::opEOFillStroke },
144     { "BDC", 2, { tchkName, tchkProps }, &Gfx::opBeginMarkedContent },
145     { "BI", 0, { tchkNone }, &Gfx::opBeginImage },
146     { "BMC", 1, { tchkName }, &Gfx::opBeginMarkedContent },
147     { "BT", 0, { tchkNone }, &Gfx::opBeginText },
148     { "BX", 0, { tchkNone }, &Gfx::opBeginIgnoreUndef },
149     { "CS", 1, { tchkName }, &Gfx::opSetStrokeColorSpace },
150     { "DP", 2, { tchkName, tchkProps }, &Gfx::opMarkPoint },
151     { "Do", 1, { tchkName }, &Gfx::opXObject },
152     { "EI", 0, { tchkNone }, &Gfx::opEndImage },
153     { "EMC", 0, { tchkNone }, &Gfx::opEndMarkedContent },
154     { "ET", 0, { tchkNone }, &Gfx::opEndText },
155     { "EX", 0, { tchkNone }, &Gfx::opEndIgnoreUndef },
156     { "F", 0, { tchkNone }, &Gfx::opFill },
157     { "G", 1, { tchkNum }, &Gfx::opSetStrokeGray },
158     { "ID", 0, { tchkNone }, &Gfx::opImageData },
159     { "J", 1, { tchkInt }, &Gfx::opSetLineCap },
160     { "K", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeCMYKColor },
161     { "M", 1, { tchkNum }, &Gfx::opSetMiterLimit },
162     { "MP", 1, { tchkName }, &Gfx::opMarkPoint },
163     { "Q", 0, { tchkNone }, &Gfx::opRestore },
164     { "RG", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeRGBColor },
165     { "S", 0, { tchkNone }, &Gfx::opStroke },
166     { "SC", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetStrokeColor },
167     { "SCN",
168       -33,
169       { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
170         tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
171       &Gfx::opSetStrokeColorN },
172     { "T*", 0, { tchkNone }, &Gfx::opTextNextLine },
173     { "TD", 2, { tchkNum, tchkNum }, &Gfx::opTextMoveSet },
174     { "TJ", 1, { tchkArray }, &Gfx::opShowSpaceText },
175     { "TL", 1, { tchkNum }, &Gfx::opSetTextLeading },
176     { "Tc", 1, { tchkNum }, &Gfx::opSetCharSpacing },
177     { "Td", 2, { tchkNum, tchkNum }, &Gfx::opTextMove },
178     { "Tf", 2, { tchkName, tchkNum }, &Gfx::opSetFont },
179     { "Tj", 1, { tchkString }, &Gfx::opShowText },
180     { "Tm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetTextMatrix },
181     { "Tr", 1, { tchkInt }, &Gfx::opSetTextRender },
182     { "Ts", 1, { tchkNum }, &Gfx::opSetTextRise },
183     { "Tw", 1, { tchkNum }, &Gfx::opSetWordSpacing },
184     { "Tz", 1, { tchkNum }, &Gfx::opSetHorizScaling },
185     { "W", 0, { tchkNone }, &Gfx::opClip },
186     { "W*", 0, { tchkNone }, &Gfx::opEOClip },
187     { "b", 0, { tchkNone }, &Gfx::opCloseFillStroke },
188     { "b*", 0, { tchkNone }, &Gfx::opCloseEOFillStroke },
189     { "c", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo },
190     { "cm", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opConcat },
191     { "cs", 1, { tchkName }, &Gfx::opSetFillColorSpace },
192     { "d", 2, { tchkArray, tchkNum }, &Gfx::opSetDash },
193     { "d0", 2, { tchkNum, tchkNum }, &Gfx::opSetCharWidth },
194     { "d1", 6, { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetCacheDevice },
195     { "f", 0, { tchkNone }, &Gfx::opFill },
196     { "f*", 0, { tchkNone }, &Gfx::opEOFill },
197     { "g", 1, { tchkNum }, &Gfx::opSetFillGray },
198     { "gs", 1, { tchkName }, &Gfx::opSetExtGState },
199     { "h", 0, { tchkNone }, &Gfx::opClosePath },
200     { "i", 1, { tchkNum }, &Gfx::opSetFlat },
201     { "j", 1, { tchkInt }, &Gfx::opSetLineJoin },
202     { "k", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillCMYKColor },
203     { "l", 2, { tchkNum, tchkNum }, &Gfx::opLineTo },
204     { "m", 2, { tchkNum, tchkNum }, &Gfx::opMoveTo },
205     { "n", 0, { tchkNone }, &Gfx::opEndPath },
206     { "q", 0, { tchkNone }, &Gfx::opSave },
207     { "re", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opRectangle },
208     { "rg", 3, { tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillRGBColor },
209     { "ri", 1, { tchkName }, &Gfx::opSetRenderingIntent },
210     { "s", 0, { tchkNone }, &Gfx::opCloseStroke },
211     { "sc", -4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opSetFillColor },
212     { "scn",
213       -33,
214       { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
215         tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
216       &Gfx::opSetFillColorN },
217     { "sh", 1, { tchkName }, &Gfx::opShFill },
218     { "v", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo1 },
219     { "w", 1, { tchkNum }, &Gfx::opSetLineWidth },
220     { "y", 4, { tchkNum, tchkNum, tchkNum, tchkNum }, &Gfx::opCurveTo2 },
221 };
222 
223 #define numOps (sizeof(opTab) / sizeof(Operator))
224 
isSameGfxColor(const GfxColor & colorA,const GfxColor & colorB,unsigned int nComps,double delta)225 static inline bool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, unsigned int nComps, double delta)
226 {
227     for (unsigned int k = 0; k < nComps; ++k) {
228         if (abs(colorA.c[k] - colorB.c[k]) > delta) {
229             return false;
230         }
231     }
232     return true;
233 }
234 
235 //------------------------------------------------------------------------
236 // GfxResources
237 //------------------------------------------------------------------------
238 
GfxResources(XRef * xrefA,Dict * resDictA,GfxResources * nextA)239 GfxResources::GfxResources(XRef *xrefA, Dict *resDictA, GfxResources *nextA) : gStateCache(2), xref(xrefA)
240 {
241     Ref r;
242 
243     if (resDictA) {
244 
245         // build font dictionary
246         Dict *resDict = resDictA->copy(xref);
247         fonts = nullptr;
248         const Object &obj1 = resDict->lookupNF("Font");
249         if (obj1.isRef()) {
250             Object obj2 = obj1.fetch(xref);
251             if (obj2.isDict()) {
252                 r = obj1.getRef();
253                 fonts = new GfxFontDict(xref, &r, obj2.getDict());
254             }
255         } else if (obj1.isDict()) {
256             fonts = new GfxFontDict(xref, nullptr, obj1.getDict());
257         }
258 
259         // get XObject dictionary
260         xObjDict = resDict->lookup("XObject");
261 
262         // get color space dictionary
263         colorSpaceDict = resDict->lookup("ColorSpace");
264 
265         // get pattern dictionary
266         patternDict = resDict->lookup("Pattern");
267 
268         // get shading dictionary
269         shadingDict = resDict->lookup("Shading");
270 
271         // get graphics state parameter dictionary
272         gStateDict = resDict->lookup("ExtGState");
273 
274         // get properties dictionary
275         propertiesDict = resDict->lookup("Properties");
276 
277         delete resDict;
278     } else {
279         fonts = nullptr;
280         xObjDict.setToNull();
281         colorSpaceDict.setToNull();
282         patternDict.setToNull();
283         shadingDict.setToNull();
284         gStateDict.setToNull();
285         propertiesDict.setToNull();
286     }
287 
288     next = nextA;
289 }
290 
~GfxResources()291 GfxResources::~GfxResources()
292 {
293     delete fonts;
294 }
295 
doLookupFont(const char * name) const296 GfxFont *GfxResources::doLookupFont(const char *name) const
297 {
298     GfxFont *font;
299     const GfxResources *resPtr;
300 
301     for (resPtr = this; resPtr; resPtr = resPtr->next) {
302         if (resPtr->fonts) {
303             if ((font = resPtr->fonts->lookup(name)))
304                 return font;
305         }
306     }
307     error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name);
308     return nullptr;
309 }
310 
lookupFont(const char * name)311 GfxFont *GfxResources::lookupFont(const char *name)
312 {
313     return doLookupFont(name);
314 }
315 
lookupFont(const char * name) const316 const GfxFont *GfxResources::lookupFont(const char *name) const
317 {
318     return doLookupFont(name);
319 }
320 
lookupXObject(const char * name)321 Object GfxResources::lookupXObject(const char *name)
322 {
323     GfxResources *resPtr;
324 
325     for (resPtr = this; resPtr; resPtr = resPtr->next) {
326         if (resPtr->xObjDict.isDict()) {
327             Object obj = resPtr->xObjDict.dictLookup(name);
328             if (!obj.isNull())
329                 return obj;
330         }
331     }
332     error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
333     return Object(objNull);
334 }
335 
lookupXObjectNF(const char * name)336 Object GfxResources::lookupXObjectNF(const char *name)
337 {
338     GfxResources *resPtr;
339 
340     for (resPtr = this; resPtr; resPtr = resPtr->next) {
341         if (resPtr->xObjDict.isDict()) {
342             Object obj = resPtr->xObjDict.dictLookupNF(name).copy();
343             if (!obj.isNull())
344                 return obj;
345         }
346     }
347     error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
348     return Object(objNull);
349 }
350 
lookupMarkedContentNF(const char * name)351 Object GfxResources::lookupMarkedContentNF(const char *name)
352 {
353     GfxResources *resPtr;
354 
355     for (resPtr = this; resPtr; resPtr = resPtr->next) {
356         if (resPtr->propertiesDict.isDict()) {
357             Object obj = resPtr->propertiesDict.dictLookupNF(name).copy();
358             if (!obj.isNull())
359                 return obj;
360         }
361     }
362     error(errSyntaxError, -1, "Marked Content '{0:s}' is unknown", name);
363     return Object(objNull);
364 }
365 
lookupColorSpace(const char * name)366 Object GfxResources::lookupColorSpace(const char *name)
367 {
368     GfxResources *resPtr;
369 
370     for (resPtr = this; resPtr; resPtr = resPtr->next) {
371         if (resPtr->colorSpaceDict.isDict()) {
372             Object obj = resPtr->colorSpaceDict.dictLookup(name);
373             if (!obj.isNull()) {
374                 return obj;
375             }
376         }
377     }
378     return Object(objNull);
379 }
380 
lookupPattern(const char * name,OutputDev * out,GfxState * state)381 GfxPattern *GfxResources::lookupPattern(const char *name, OutputDev *out, GfxState *state)
382 {
383     GfxResources *resPtr;
384 
385     for (resPtr = this; resPtr; resPtr = resPtr->next) {
386         if (resPtr->patternDict.isDict()) {
387             Ref patternRef = Ref::INVALID();
388             Object obj = resPtr->patternDict.getDict()->lookup(name, &patternRef);
389             if (!obj.isNull()) {
390                 return GfxPattern::parse(resPtr, &obj, out, state, patternRef.num);
391             }
392         }
393     }
394     error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name);
395     return nullptr;
396 }
397 
lookupShading(const char * name,OutputDev * out,GfxState * state)398 GfxShading *GfxResources::lookupShading(const char *name, OutputDev *out, GfxState *state)
399 {
400     GfxResources *resPtr;
401     GfxShading *shading;
402 
403     for (resPtr = this; resPtr; resPtr = resPtr->next) {
404         if (resPtr->shadingDict.isDict()) {
405             Object obj = resPtr->shadingDict.dictLookup(name);
406             if (!obj.isNull()) {
407                 shading = GfxShading::parse(resPtr, &obj, out, state);
408                 return shading;
409             }
410         }
411     }
412     error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
413     return nullptr;
414 }
415 
lookupGState(const char * name)416 Object GfxResources::lookupGState(const char *name)
417 {
418     Object obj = lookupGStateNF(name);
419     if (obj.isNull())
420         return Object(objNull);
421 
422     if (!obj.isRef())
423         return obj;
424 
425     const Ref ref = obj.getRef();
426 
427     if (auto *item = gStateCache.lookup(ref)) {
428         return item->copy();
429     }
430 
431     auto *item = new Object { xref->fetch(ref) };
432     gStateCache.put(ref, item);
433     return item->copy();
434 }
435 
lookupGStateNF(const char * name)436 Object GfxResources::lookupGStateNF(const char *name)
437 {
438     GfxResources *resPtr;
439 
440     for (resPtr = this; resPtr; resPtr = resPtr->next) {
441         if (resPtr->gStateDict.isDict()) {
442             Object obj = resPtr->gStateDict.dictLookupNF(name).copy();
443             if (!obj.isNull()) {
444                 return obj;
445             }
446         }
447     }
448     error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
449     return Object(objNull);
450 }
451 
452 //------------------------------------------------------------------------
453 // Gfx
454 //------------------------------------------------------------------------
455 
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)456 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)
457     : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
458 {
459     int i;
460 
461     doc = docA;
462     xref = (xrefA == nullptr) ? doc->getXRef() : xrefA;
463     catalog = doc->getCatalog();
464     subPage = false;
465     mcStack = nullptr;
466     parser = nullptr;
467 
468     // start the resource stack
469     res = new GfxResources(xref, resDict, nullptr);
470 
471     // initialize
472     out = outA;
473     state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
474     out->initGfxState(state);
475     stackHeight = 1;
476     pushStateGuard();
477     fontChanged = false;
478     clip = clipNone;
479     ignoreUndef = 0;
480     out->startPage(pageNum, state, xref);
481     out->setDefaultCTM(state->getCTM());
482     out->updateAll(state);
483     for (i = 0; i < 6; ++i) {
484         baseMatrix[i] = state->getCTM()[i];
485     }
486     displayDepth = 0;
487     ocState = true;
488     parser = nullptr;
489     abortCheckCbk = abortCheckCbkA;
490     abortCheckCbkData = abortCheckCbkDataA;
491 
492     // set crop box
493     if (cropBox) {
494         state->moveTo(cropBox->x1, cropBox->y1);
495         state->lineTo(cropBox->x2, cropBox->y1);
496         state->lineTo(cropBox->x2, cropBox->y2);
497         state->lineTo(cropBox->x1, cropBox->y2);
498         state->closePath();
499         state->clip();
500         out->clip(state);
501         state->clearPath();
502     }
503 #ifdef USE_CMS
504     initDisplayProfile();
505 #endif
506 }
507 
Gfx(PDFDoc * docA,OutputDev * outA,Dict * resDict,const PDFRectangle * box,const PDFRectangle * cropBox,bool (* abortCheckCbkA)(void * data),void * abortCheckCbkDataA,Gfx * gfxA)508 Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, const PDFRectangle *box, const PDFRectangle *cropBox, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, Gfx *gfxA)
509     : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
510 {
511     int i;
512 
513     doc = docA;
514     if (gfxA) {
515         xref = gfxA->getXRef();
516         formsDrawing = gfxA->formsDrawing;
517         charProcDrawing = gfxA->charProcDrawing;
518     } else {
519         xref = doc->getXRef();
520     }
521     catalog = doc->getCatalog();
522     subPage = true;
523     mcStack = nullptr;
524     parser = nullptr;
525 
526     // start the resource stack
527     res = new GfxResources(xref, resDict, nullptr);
528 
529     // initialize
530     out = outA;
531     double hDPI = 72;
532     double vDPI = 72;
533     if (gfxA) {
534         hDPI = gfxA->getState()->getHDPI();
535         vDPI = gfxA->getState()->getVDPI();
536     }
537     state = new GfxState(hDPI, vDPI, box, 0, false);
538     stackHeight = 1;
539     pushStateGuard();
540     fontChanged = false;
541     clip = clipNone;
542     ignoreUndef = 0;
543     for (i = 0; i < 6; ++i) {
544         baseMatrix[i] = state->getCTM()[i];
545     }
546     displayDepth = 0;
547     ocState = true;
548     parser = nullptr;
549     abortCheckCbk = abortCheckCbkA;
550     abortCheckCbkData = abortCheckCbkDataA;
551 
552     // set crop box
553     if (cropBox) {
554         state->moveTo(cropBox->x1, cropBox->y1);
555         state->lineTo(cropBox->x2, cropBox->y1);
556         state->lineTo(cropBox->x2, cropBox->y2);
557         state->lineTo(cropBox->x1, cropBox->y2);
558         state->closePath();
559         state->clip();
560         out->clip(state);
561         state->clearPath();
562     }
563 #ifdef USE_CMS
564     initDisplayProfile();
565 #endif
566 }
567 
568 #ifdef USE_CMS
569 
570 #    include <lcms2.h>
571 
initDisplayProfile()572 void Gfx::initDisplayProfile()
573 {
574     Object catDict = xref->getCatalog();
575     if (catDict.isDict()) {
576         Object outputIntents = catDict.dictLookup("OutputIntents");
577         if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) {
578             Object firstElement = outputIntents.arrayGet(0);
579             if (firstElement.isDict()) {
580                 Object profile = firstElement.dictLookup("DestOutputProfile");
581                 if (profile.isStream()) {
582                     Stream *iccStream = profile.getStream();
583                     int length = 0;
584                     unsigned char *profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
585                     auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf, length));
586                     if (!hp) {
587                         error(errSyntaxWarning, -1, "read ICCBased color space profile error");
588                     } else {
589                         state->setDisplayProfile(hp);
590                     }
591                     gfree(profBuf);
592                 }
593             }
594         }
595     }
596 }
597 
598 #endif
599 
~Gfx()600 Gfx::~Gfx()
601 {
602     while (stateGuards.size()) {
603         popStateGuard();
604     }
605     if (!subPage) {
606         out->endPage();
607     }
608     // There shouldn't be more saves, but pop them if there were any
609     while (state->hasSaves()) {
610         error(errSyntaxError, -1, "Found state under last state guard. Popping.");
611         restoreState();
612     }
613     delete state;
614     while (res) {
615         popResources();
616     }
617     while (mcStack) {
618         popMarkedContent();
619     }
620 }
621 
display(Object * obj,bool topLevel)622 void Gfx::display(Object *obj, bool topLevel)
623 {
624     // check for excessive recursion
625     if (displayDepth > 100) {
626         return;
627     }
628 
629     if (obj->isArray()) {
630         for (int i = 0; i < obj->arrayGetLength(); ++i) {
631             Object obj2 = obj->arrayGet(i);
632             if (!obj2.isStream()) {
633                 error(errSyntaxError, -1, "Weird page contents");
634                 return;
635             }
636         }
637     } else if (!obj->isStream()) {
638         error(errSyntaxError, -1, "Weird page contents");
639         return;
640     }
641     parser = new Parser(xref, obj, false);
642     go(topLevel);
643     delete parser;
644     parser = nullptr;
645 }
646 
go(bool topLevel)647 void Gfx::go(bool topLevel)
648 {
649     Object obj;
650     Object args[maxArgs];
651     int numArgs, i;
652     int lastAbortCheck;
653 
654     // scan a sequence of objects
655     pushStateGuard();
656     updateLevel = 1; // make sure even empty pages trigger a call to dump()
657     lastAbortCheck = 0;
658     numArgs = 0;
659     obj = parser->getObj();
660     while (!obj.isEOF()) {
661         commandAborted = false;
662 
663         // got a command - execute it
664         if (obj.isCmd()) {
665             if (printCommands) {
666                 obj.print(stdout);
667                 for (i = 0; i < numArgs; ++i) {
668                     printf(" ");
669                     args[i].print(stdout);
670                 }
671                 printf("\n");
672                 fflush(stdout);
673             }
674             GooTimer *timer = nullptr;
675 
676             if (unlikely(profileCommands)) {
677                 timer = new GooTimer();
678             }
679 
680             // Run the operation
681             execOp(&obj, args, numArgs);
682 
683             // Update the profile information
684             if (unlikely(profileCommands)) {
685                 if (auto *const hash = out->getProfileHash()) {
686                     auto &data = (*hash)[obj.getCmd()];
687                     data.addElement(timer->getElapsed());
688                 }
689                 delete timer;
690             }
691             for (i = 0; i < numArgs; ++i)
692                 args[i].setToNull(); // Free memory early
693             numArgs = 0;
694 
695             // periodically update display
696             if (++updateLevel >= 20000) {
697                 out->dump();
698                 updateLevel = 0;
699                 lastAbortCheck = 0;
700             }
701 
702             // did the command throw an exception
703             if (commandAborted) {
704                 // don't propogate; recursive drawing comes from Form XObjects which
705                 // should probably be drawn in a separate context anyway for caching
706                 commandAborted = false;
707                 break;
708             }
709 
710             // check for an abort
711             if (abortCheckCbk) {
712                 if (updateLevel - lastAbortCheck > 10) {
713                     if ((*abortCheckCbk)(abortCheckCbkData)) {
714                         break;
715                     }
716                     lastAbortCheck = updateLevel;
717                 }
718             }
719 
720             // got an argument - save it
721         } else if (numArgs < maxArgs) {
722             args[numArgs++] = std::move(obj);
723             // too many arguments - something is wrong
724         } else {
725             error(errSyntaxError, getPos(), "Too many args in content stream");
726             if (printCommands) {
727                 printf("throwing away arg: ");
728                 obj.print(stdout);
729                 printf("\n");
730                 fflush(stdout);
731             }
732         }
733 
734         // grab the next object
735         obj = parser->getObj();
736     }
737 
738     // args at end with no command
739     if (numArgs > 0) {
740         error(errSyntaxError, getPos(), "Leftover args in content stream");
741         if (printCommands) {
742             printf("%d leftovers:", numArgs);
743             for (i = 0; i < numArgs; ++i) {
744                 printf(" ");
745                 args[i].print(stdout);
746             }
747             printf("\n");
748             fflush(stdout);
749         }
750     }
751 
752     popStateGuard();
753 
754     // update display
755     if (topLevel && updateLevel > 0) {
756         out->dump();
757     }
758 }
759 
execOp(Object * cmd,Object args[],int numArgs)760 void Gfx::execOp(Object *cmd, Object args[], int numArgs)
761 {
762     const Operator *op;
763     Object *argPtr;
764     int i;
765 
766     // find operator
767     const char *name = cmd->getCmd();
768     if (!(op = findOp(name))) {
769         if (ignoreUndef == 0)
770             error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
771         return;
772     }
773 
774     // type check args
775     argPtr = args;
776     if (op->numArgs >= 0) {
777         if (numArgs < op->numArgs) {
778             error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name);
779             commandAborted = true;
780             return;
781         }
782         if (numArgs > op->numArgs) {
783 #if 0
784       error(errSyntaxWarning, getPos(),
785 	    "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
786 #endif
787             argPtr += numArgs - op->numArgs;
788             numArgs = op->numArgs;
789         }
790     } else {
791         if (numArgs > -op->numArgs) {
792             error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
793             return;
794         }
795     }
796     for (i = 0; i < numArgs; ++i) {
797         if (!checkArg(&argPtr[i], op->tchk[i])) {
798             error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName());
799             return;
800         }
801     }
802 
803     // do it
804     (this->*op->func)(argPtr, numArgs);
805 }
806 
findOp(const char * name)807 const Operator *Gfx::findOp(const char *name)
808 {
809     int a, b, m, cmp;
810 
811     a = -1;
812     b = numOps;
813     cmp = 0; // make gcc happy
814     // invariant: opTab[a] < name < opTab[b]
815     while (b - a > 1) {
816         m = (a + b) / 2;
817         cmp = strcmp(opTab[m].name, name);
818         if (cmp < 0)
819             a = m;
820         else if (cmp > 0)
821             b = m;
822         else
823             a = b = m;
824     }
825     if (cmp != 0)
826         return nullptr;
827     return &opTab[a];
828 }
829 
checkArg(Object * arg,TchkType type)830 bool Gfx::checkArg(Object *arg, TchkType type)
831 {
832     switch (type) {
833     case tchkBool:
834         return arg->isBool();
835     case tchkInt:
836         return arg->isInt();
837     case tchkNum:
838         return arg->isNum();
839     case tchkString:
840         return arg->isString();
841     case tchkName:
842         return arg->isName();
843     case tchkArray:
844         return arg->isArray();
845     case tchkProps:
846         return arg->isDict() || arg->isName();
847     case tchkSCN:
848         return arg->isNum() || arg->isName();
849     case tchkNone:
850         return false;
851     }
852     return false;
853 }
854 
getPos()855 Goffset Gfx::getPos()
856 {
857     return parser ? parser->getPos() : -1;
858 }
859 
860 //------------------------------------------------------------------------
861 // graphics state operators
862 //------------------------------------------------------------------------
863 
opSave(Object args[],int numArgs)864 void Gfx::opSave(Object args[], int numArgs)
865 {
866     saveState();
867 }
868 
opRestore(Object args[],int numArgs)869 void Gfx::opRestore(Object args[], int numArgs)
870 {
871     restoreState();
872 }
873 
opConcat(Object args[],int numArgs)874 void Gfx::opConcat(Object args[], int numArgs)
875 {
876     state->concatCTM(args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
877     out->updateCTM(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
878     fontChanged = true;
879 }
880 
opSetDash(Object args[],int numArgs)881 void Gfx::opSetDash(Object args[], int numArgs)
882 {
883     Array *a;
884     int length;
885     double *dash;
886     int i;
887 
888     a = args[0].getArray();
889     length = a->getLength();
890     if (length == 0) {
891         dash = nullptr;
892     } else {
893         dash = (double *)gmallocn(length, sizeof(double));
894         for (i = 0; i < length; ++i) {
895             dash[i] = a->get(i).getNumWithDefaultValue(0);
896         }
897     }
898     state->setLineDash(dash, length, args[1].getNum());
899     out->updateLineDash(state);
900 }
901 
opSetFlat(Object args[],int numArgs)902 void Gfx::opSetFlat(Object args[], int numArgs)
903 {
904     state->setFlatness((int)args[0].getNum());
905     out->updateFlatness(state);
906 }
907 
opSetLineJoin(Object args[],int numArgs)908 void Gfx::opSetLineJoin(Object args[], int numArgs)
909 {
910     state->setLineJoin(args[0].getInt());
911     out->updateLineJoin(state);
912 }
913 
opSetLineCap(Object args[],int numArgs)914 void Gfx::opSetLineCap(Object args[], int numArgs)
915 {
916     state->setLineCap(args[0].getInt());
917     out->updateLineCap(state);
918 }
919 
opSetMiterLimit(Object args[],int numArgs)920 void Gfx::opSetMiterLimit(Object args[], int numArgs)
921 {
922     state->setMiterLimit(args[0].getNum());
923     out->updateMiterLimit(state);
924 }
925 
opSetLineWidth(Object args[],int numArgs)926 void Gfx::opSetLineWidth(Object args[], int numArgs)
927 {
928     state->setLineWidth(args[0].getNum());
929     out->updateLineWidth(state);
930 }
931 
opSetExtGState(Object args[],int numArgs)932 void Gfx::opSetExtGState(Object args[], int numArgs)
933 {
934     Object obj1, obj2;
935     GfxBlendMode mode;
936     bool haveFillOP;
937     GfxColor backdropColor;
938     bool haveBackdropColor;
939     bool alpha;
940     double opac;
941 
942     obj1 = res->lookupGState(args[0].getName());
943     if (obj1.isNull()) {
944         return;
945     }
946     if (!obj1.isDict()) {
947         error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type", args[0].getName());
948         return;
949     }
950     if (printCommands) {
951         printf("  gfx state dict: ");
952         obj1.print();
953         printf("\n");
954     }
955 
956     // parameters that are also set by individual PDF operators
957     obj2 = obj1.dictLookup("LW");
958     if (obj2.isNum()) {
959         opSetLineWidth(&obj2, 1);
960     }
961     obj2 = obj1.dictLookup("LC");
962     if (obj2.isInt()) {
963         opSetLineCap(&obj2, 1);
964     }
965     obj2 = obj1.dictLookup("LJ");
966     if (obj2.isInt()) {
967         opSetLineJoin(&obj2, 1);
968     }
969     obj2 = obj1.dictLookup("ML");
970     if (obj2.isNum()) {
971         opSetMiterLimit(&obj2, 1);
972     }
973     obj2 = obj1.dictLookup("D");
974     if (obj2.isArray() && obj2.arrayGetLength() == 2) {
975         Object args2[2];
976         args2[0] = obj2.arrayGet(0);
977         args2[1] = obj2.arrayGet(1);
978         if (args2[0].isArray() && args2[1].isNum()) {
979             opSetDash(args2, 2);
980         }
981     }
982 #if 0 //~ need to add a new version of GfxResources::lookupFont() that
983       //~ takes an indirect ref instead of a name
984   if (obj1.dictLookup("Font", &obj2)->isArray() &&
985       obj2.arrayGetLength() == 2) {
986     obj2.arrayGet(0, &args2[0]);
987     obj2.arrayGet(1, &args2[1]);
988     if (args2[0].isDict() && args2[1].isNum()) {
989       opSetFont(args2, 2);
990     }
991     args2[0].free();
992     args2[1].free();
993   }
994   obj2.free();
995 #endif
996     obj2 = obj1.dictLookup("FL");
997     if (obj2.isNum()) {
998         opSetFlat(&obj2, 1);
999     }
1000 
1001     // transparency support: blend mode, fill/stroke opacity
1002     obj2 = obj1.dictLookup("BM");
1003     if (!obj2.isNull()) {
1004         if (state->parseBlendMode(&obj2, &mode)) {
1005             state->setBlendMode(mode);
1006             out->updateBlendMode(state);
1007         } else {
1008             error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
1009         }
1010     }
1011     obj2 = obj1.dictLookup("ca");
1012     if (obj2.isNum()) {
1013         opac = obj2.getNum();
1014         state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
1015         out->updateFillOpacity(state);
1016     }
1017     obj2 = obj1.dictLookup("CA");
1018     if (obj2.isNum()) {
1019         opac = obj2.getNum();
1020         state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
1021         out->updateStrokeOpacity(state);
1022     }
1023 
1024     // fill/stroke overprint, overprint mode
1025     obj2 = obj1.dictLookup("op");
1026     if ((haveFillOP = obj2.isBool())) {
1027         state->setFillOverprint(obj2.getBool());
1028         out->updateFillOverprint(state);
1029     }
1030     obj2 = obj1.dictLookup("OP");
1031     if (obj2.isBool()) {
1032         state->setStrokeOverprint(obj2.getBool());
1033         out->updateStrokeOverprint(state);
1034         if (!haveFillOP) {
1035             state->setFillOverprint(obj2.getBool());
1036             out->updateFillOverprint(state);
1037         }
1038     }
1039     obj2 = obj1.dictLookup("OPM");
1040     if (obj2.isInt()) {
1041         state->setOverprintMode(obj2.getInt());
1042         out->updateOverprintMode(state);
1043     }
1044 
1045     // stroke adjust
1046     obj2 = obj1.dictLookup("SA");
1047     if (obj2.isBool()) {
1048         state->setStrokeAdjust(obj2.getBool());
1049         out->updateStrokeAdjust(state);
1050     }
1051 
1052     // transfer function
1053     obj2 = obj1.dictLookup("TR2");
1054     if (obj2.isNull()) {
1055         obj2 = obj1.dictLookup("TR");
1056     }
1057     if (obj2.isName("Default") || obj2.isName("Identity")) {
1058         Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr };
1059         state->setTransfer(funcs);
1060         out->updateTransfer(state);
1061     } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
1062         Function *funcs[4] = { nullptr, nullptr, nullptr, nullptr };
1063         for (int i = 0; i < 4; ++i) {
1064             Object obj3 = obj2.arrayGet(i);
1065             funcs[i] = Function::parse(&obj3);
1066             if (!funcs[i]) {
1067                 break;
1068             }
1069         }
1070         if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
1071             state->setTransfer(funcs);
1072             out->updateTransfer(state);
1073         } else {
1074             for (Function *f : funcs) {
1075                 delete f;
1076             }
1077         }
1078     } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
1079         Function *funcs[4];
1080         if ((funcs[0] = Function::parse(&obj2))) {
1081             funcs[1] = funcs[2] = funcs[3] = nullptr;
1082             state->setTransfer(funcs);
1083             out->updateTransfer(state);
1084         }
1085     } else if (!obj2.isNull()) {
1086         error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState");
1087     }
1088 
1089     // alpha is shape
1090     obj2 = obj1.dictLookup("AIS");
1091     if (obj2.isBool()) {
1092         state->setAlphaIsShape(obj2.getBool());
1093         out->updateAlphaIsShape(state);
1094     }
1095 
1096     // text knockout
1097     obj2 = obj1.dictLookup("TK");
1098     if (obj2.isBool()) {
1099         state->setTextKnockout(obj2.getBool());
1100         out->updateTextKnockout(state);
1101     }
1102 
1103     // soft mask
1104     obj2 = obj1.dictLookup("SMask");
1105     if (!obj2.isNull()) {
1106         if (obj2.isName("None")) {
1107             out->clearSoftMask(state);
1108         } else if (obj2.isDict()) {
1109             Object obj3 = obj2.dictLookup("S");
1110             if (obj3.isName("Alpha")) {
1111                 alpha = true;
1112             } else { // "Luminosity"
1113                 alpha = false;
1114             }
1115             Function *softMaskTransferFunc = nullptr;
1116             obj3 = obj2.dictLookup("TR");
1117             if (!obj3.isNull()) {
1118                 if (obj3.isName("Default") || obj3.isName("Identity")) {
1119                     // nothing
1120                 } else {
1121                     softMaskTransferFunc = Function::parse(&obj3);
1122                     if (softMaskTransferFunc == nullptr || softMaskTransferFunc->getInputSize() != 1 || softMaskTransferFunc->getOutputSize() != 1) {
1123                         error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState");
1124                         delete softMaskTransferFunc;
1125                         softMaskTransferFunc = nullptr;
1126                     }
1127                 }
1128             }
1129             obj3 = obj2.dictLookup("BC");
1130             if ((haveBackdropColor = obj3.isArray())) {
1131                 for (int &c : backdropColor.c) {
1132                     c = 0;
1133                 }
1134                 for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
1135                     Object obj4 = obj3.arrayGet(i);
1136                     if (obj4.isNum()) {
1137                         backdropColor.c[i] = dblToCol(obj4.getNum());
1138                     }
1139                 }
1140             }
1141             obj3 = obj2.dictLookup("G");
1142             if (obj3.isStream()) {
1143                 Object obj4 = obj3.streamGetDict()->lookup("Group");
1144                 if (obj4.isDict()) {
1145                     GfxColorSpace *blendingColorSpace = nullptr;
1146                     Object obj5 = obj4.dictLookup("CS");
1147                     if (!obj5.isNull()) {
1148                         blendingColorSpace = GfxColorSpace::parse(res, &obj5, out, state);
1149                     }
1150                     const bool isolated = obj4.dictLookup("I").getBoolWithDefaultValue(false);
1151                     const bool knockout = obj4.dictLookup("K").getBoolWithDefaultValue(false);
1152                     if (!haveBackdropColor) {
1153                         if (blendingColorSpace) {
1154                             blendingColorSpace->getDefaultColor(&backdropColor);
1155                         } else {
1156                             //~ need to get the parent or default color space (?)
1157                             for (int &c : backdropColor.c) {
1158                                 c = 0;
1159                             }
1160                         }
1161                     }
1162                     doSoftMask(&obj3, alpha, blendingColorSpace, isolated, knockout, softMaskTransferFunc, &backdropColor);
1163                     delete blendingColorSpace;
1164                 } else {
1165                     error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
1166                 }
1167             } else {
1168                 error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
1169             }
1170             delete softMaskTransferFunc;
1171         } else if (!obj2.isNull()) {
1172             error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
1173         }
1174     }
1175     obj2 = obj1.dictLookup("Font");
1176     if (obj2.isArray()) {
1177         GfxFont *font;
1178         if (obj2.arrayGetLength() == 2) {
1179             const Object &fargs0 = obj2.arrayGetNF(0);
1180             Object fargs1 = obj2.arrayGet(1);
1181             if (fargs0.isRef() && fargs1.isNum()) {
1182                 Object fobj = fargs0.fetch(xref);
1183                 if (fobj.isDict()) {
1184                     Ref r = fargs0.getRef();
1185                     font = GfxFont::makeFont(xref, args[0].getName(), r, fobj.getDict());
1186                     state->setFont(font, fargs1.getNum());
1187                     fontChanged = true;
1188                 }
1189             }
1190         } else {
1191             error(errSyntaxError, getPos(), "Number of args mismatch for /Font in ExtGState");
1192         }
1193     }
1194     obj2 = obj1.dictLookup("LW");
1195     if (obj2.isNum()) {
1196         opSetLineWidth(&obj2, 1);
1197     }
1198     obj2 = obj1.dictLookup("LC");
1199     if (obj2.isInt()) {
1200         opSetLineCap(&obj2, 1);
1201     }
1202     obj2 = obj1.dictLookup("LJ");
1203     if (obj2.isInt()) {
1204         opSetLineJoin(&obj2, 1);
1205     }
1206     obj2 = obj1.dictLookup("ML");
1207     if (obj2.isNum()) {
1208         opSetMiterLimit(&obj2, 1);
1209     }
1210     obj2 = obj1.dictLookup("D");
1211     if (obj2.isArray()) {
1212         if (obj2.arrayGetLength() == 2) {
1213             Object dargs[2];
1214 
1215             dargs[0] = obj2.arrayGetNF(0).copy();
1216             dargs[1] = obj2.arrayGet(1);
1217             if (dargs[0].isArray() && dargs[1].isInt()) {
1218                 opSetDash(dargs, 2);
1219             }
1220         } else {
1221             error(errSyntaxError, getPos(), "Number of args mismatch for /D in ExtGState");
1222         }
1223     }
1224     obj2 = obj1.dictLookup("RI");
1225     if (obj2.isName()) {
1226         opSetRenderingIntent(&obj2, 1);
1227     }
1228     obj2 = obj1.dictLookup("FL");
1229     if (obj2.isNum()) {
1230         opSetFlat(&obj2, 1);
1231     }
1232 }
1233 
doSoftMask(Object * str,bool alpha,GfxColorSpace * blendingColorSpace,bool isolated,bool knockout,Function * transferFunc,GfxColor * backdropColor)1234 void Gfx::doSoftMask(Object *str, bool alpha, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, Function *transferFunc, GfxColor *backdropColor)
1235 {
1236     Dict *dict, *resDict;
1237     double m[6], bbox[4];
1238     Object obj1;
1239     int i;
1240 
1241     // get stream dict
1242     dict = str->streamGetDict();
1243 
1244     // check form type
1245     obj1 = dict->lookup("FormType");
1246     if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
1247         error(errSyntaxError, getPos(), "Unknown form type");
1248     }
1249 
1250     // get bounding box
1251     obj1 = dict->lookup("BBox");
1252     if (!obj1.isArray()) {
1253         error(errSyntaxError, getPos(), "Bad form bounding box");
1254         return;
1255     }
1256     for (i = 0; i < 4; ++i) {
1257         Object obj2 = obj1.arrayGet(i);
1258         if (likely(obj2.isNum()))
1259             bbox[i] = obj2.getNum();
1260         else {
1261             error(errSyntaxError, getPos(), "Bad form bounding box (non number)");
1262             return;
1263         }
1264     }
1265 
1266     // get matrix
1267     obj1 = dict->lookup("Matrix");
1268     if (obj1.isArray()) {
1269         for (i = 0; i < 6; ++i) {
1270             Object obj2 = obj1.arrayGet(i);
1271             if (likely(obj2.isNum()))
1272                 m[i] = obj2.getNum();
1273             else
1274                 m[i] = 0;
1275         }
1276     } else {
1277         m[0] = 1;
1278         m[1] = 0;
1279         m[2] = 0;
1280         m[3] = 1;
1281         m[4] = 0;
1282         m[5] = 0;
1283     }
1284 
1285     // get resources
1286     obj1 = dict->lookup("Resources");
1287     resDict = obj1.isDict() ? obj1.getDict() : nullptr;
1288 
1289     // draw it
1290     drawForm(str, resDict, m, bbox, true, true, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor);
1291 }
1292 
opSetRenderingIntent(Object args[],int numArgs)1293 void Gfx::opSetRenderingIntent(Object args[], int numArgs)
1294 {
1295     state->setRenderingIntent(args[0].getName());
1296 }
1297 
1298 //------------------------------------------------------------------------
1299 // color operators
1300 //------------------------------------------------------------------------
1301 
opSetFillGray(Object args[],int numArgs)1302 void Gfx::opSetFillGray(Object args[], int numArgs)
1303 {
1304     GfxColor color;
1305     GfxColorSpace *colorSpace = nullptr;
1306 
1307     state->setFillPattern(nullptr);
1308     Object obj = res->lookupColorSpace("DefaultGray");
1309     if (!obj.isNull()) {
1310         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1311     }
1312     if (colorSpace == nullptr || colorSpace->getNComps() > 1) {
1313         delete colorSpace;
1314         colorSpace = state->copyDefaultGrayColorSpace();
1315     }
1316     state->setFillColorSpace(colorSpace);
1317     out->updateFillColorSpace(state);
1318     color.c[0] = dblToCol(args[0].getNum());
1319     state->setFillColor(&color);
1320     out->updateFillColor(state);
1321 }
1322 
opSetStrokeGray(Object args[],int numArgs)1323 void Gfx::opSetStrokeGray(Object args[], int numArgs)
1324 {
1325     GfxColor color;
1326     GfxColorSpace *colorSpace = nullptr;
1327 
1328     state->setStrokePattern(nullptr);
1329     Object obj = res->lookupColorSpace("DefaultGray");
1330     if (!obj.isNull()) {
1331         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1332     }
1333     if (colorSpace == nullptr) {
1334         colorSpace = state->copyDefaultGrayColorSpace();
1335     }
1336     state->setStrokeColorSpace(colorSpace);
1337     out->updateStrokeColorSpace(state);
1338     color.c[0] = dblToCol(args[0].getNum());
1339     state->setStrokeColor(&color);
1340     out->updateStrokeColor(state);
1341 }
1342 
opSetFillCMYKColor(Object args[],int numArgs)1343 void Gfx::opSetFillCMYKColor(Object args[], int numArgs)
1344 {
1345     GfxColor color;
1346     GfxColorSpace *colorSpace = nullptr;
1347     int i;
1348 
1349     Object obj = res->lookupColorSpace("DefaultCMYK");
1350     if (!obj.isNull()) {
1351         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1352     }
1353     if (colorSpace == nullptr) {
1354         colorSpace = state->copyDefaultCMYKColorSpace();
1355     }
1356     state->setFillPattern(nullptr);
1357     state->setFillColorSpace(colorSpace);
1358     out->updateFillColorSpace(state);
1359     for (i = 0; i < 4; ++i) {
1360         color.c[i] = dblToCol(args[i].getNum());
1361     }
1362     state->setFillColor(&color);
1363     out->updateFillColor(state);
1364 }
1365 
opSetStrokeCMYKColor(Object args[],int numArgs)1366 void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs)
1367 {
1368     GfxColor color;
1369     GfxColorSpace *colorSpace = nullptr;
1370     int i;
1371 
1372     state->setStrokePattern(nullptr);
1373     Object obj = res->lookupColorSpace("DefaultCMYK");
1374     if (!obj.isNull()) {
1375         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1376     }
1377     if (colorSpace == nullptr) {
1378         colorSpace = state->copyDefaultCMYKColorSpace();
1379     }
1380     state->setStrokeColorSpace(colorSpace);
1381     out->updateStrokeColorSpace(state);
1382     for (i = 0; i < 4; ++i) {
1383         color.c[i] = dblToCol(args[i].getNum());
1384     }
1385     state->setStrokeColor(&color);
1386     out->updateStrokeColor(state);
1387 }
1388 
opSetFillRGBColor(Object args[],int numArgs)1389 void Gfx::opSetFillRGBColor(Object args[], int numArgs)
1390 {
1391     GfxColorSpace *colorSpace = nullptr;
1392     GfxColor color;
1393     int i;
1394 
1395     state->setFillPattern(nullptr);
1396     Object obj = res->lookupColorSpace("DefaultRGB");
1397     if (!obj.isNull()) {
1398         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1399     }
1400     if (colorSpace == nullptr || colorSpace->getNComps() > 3) {
1401         delete colorSpace;
1402         colorSpace = state->copyDefaultRGBColorSpace();
1403     }
1404     state->setFillColorSpace(colorSpace);
1405     out->updateFillColorSpace(state);
1406     for (i = 0; i < 3; ++i) {
1407         color.c[i] = dblToCol(args[i].getNum());
1408     }
1409     state->setFillColor(&color);
1410     out->updateFillColor(state);
1411 }
1412 
opSetStrokeRGBColor(Object args[],int numArgs)1413 void Gfx::opSetStrokeRGBColor(Object args[], int numArgs)
1414 {
1415     GfxColorSpace *colorSpace = nullptr;
1416     GfxColor color;
1417     int i;
1418 
1419     state->setStrokePattern(nullptr);
1420     Object obj = res->lookupColorSpace("DefaultRGB");
1421     if (!obj.isNull()) {
1422         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1423     }
1424     if (colorSpace == nullptr) {
1425         colorSpace = state->copyDefaultRGBColorSpace();
1426     }
1427     state->setStrokeColorSpace(colorSpace);
1428     out->updateStrokeColorSpace(state);
1429     for (i = 0; i < 3; ++i) {
1430         color.c[i] = dblToCol(args[i].getNum());
1431     }
1432     state->setStrokeColor(&color);
1433     out->updateStrokeColor(state);
1434 }
1435 
opSetFillColorSpace(Object args[],int numArgs)1436 void Gfx::opSetFillColorSpace(Object args[], int numArgs)
1437 {
1438     GfxColorSpace *colorSpace;
1439     GfxColor color;
1440 
1441     Object obj = res->lookupColorSpace(args[0].getName());
1442     if (obj.isNull()) {
1443         colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
1444     } else {
1445         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1446     }
1447     if (colorSpace) {
1448         state->setFillPattern(nullptr);
1449         state->setFillColorSpace(colorSpace);
1450         out->updateFillColorSpace(state);
1451         colorSpace->getDefaultColor(&color);
1452         state->setFillColor(&color);
1453         out->updateFillColor(state);
1454     } else {
1455         error(errSyntaxError, getPos(), "Bad color space (fill)");
1456     }
1457 }
1458 
opSetStrokeColorSpace(Object args[],int numArgs)1459 void Gfx::opSetStrokeColorSpace(Object args[], int numArgs)
1460 {
1461     GfxColorSpace *colorSpace;
1462     GfxColor color;
1463 
1464     state->setStrokePattern(nullptr);
1465     Object obj = res->lookupColorSpace(args[0].getName());
1466     if (obj.isNull()) {
1467         colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
1468     } else {
1469         colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1470     }
1471     if (colorSpace) {
1472         state->setStrokeColorSpace(colorSpace);
1473         out->updateStrokeColorSpace(state);
1474         colorSpace->getDefaultColor(&color);
1475         state->setStrokeColor(&color);
1476         out->updateStrokeColor(state);
1477     } else {
1478         error(errSyntaxError, getPos(), "Bad color space (stroke)");
1479     }
1480 }
1481 
opSetFillColor(Object args[],int numArgs)1482 void Gfx::opSetFillColor(Object args[], int numArgs)
1483 {
1484     GfxColor color;
1485     int i;
1486 
1487     if (numArgs != state->getFillColorSpace()->getNComps()) {
1488         error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command");
1489         return;
1490     }
1491     state->setFillPattern(nullptr);
1492     for (i = 0; i < numArgs; ++i) {
1493         color.c[i] = dblToCol(args[i].getNum());
1494     }
1495     state->setFillColor(&color);
1496     out->updateFillColor(state);
1497 }
1498 
opSetStrokeColor(Object args[],int numArgs)1499 void Gfx::opSetStrokeColor(Object args[], int numArgs)
1500 {
1501     GfxColor color;
1502     int i;
1503 
1504     if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1505         error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command");
1506         return;
1507     }
1508     state->setStrokePattern(nullptr);
1509     for (i = 0; i < numArgs; ++i) {
1510         color.c[i] = dblToCol(args[i].getNum());
1511     }
1512     state->setStrokeColor(&color);
1513     out->updateStrokeColor(state);
1514 }
1515 
opSetFillColorN(Object args[],int numArgs)1516 void Gfx::opSetFillColorN(Object args[], int numArgs)
1517 {
1518     GfxColor color;
1519     GfxPattern *pattern;
1520     int i;
1521 
1522     if (state->getFillColorSpace()->getMode() == csPattern) {
1523         if (numArgs > 1) {
1524             if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder()->getNComps()) {
1525                 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
1526                 return;
1527             }
1528             for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
1529                 if (args[i].isNum()) {
1530                     color.c[i] = dblToCol(args[i].getNum());
1531                 } else {
1532                     color.c[i] = 0; // TODO Investigate if this is what Adobe does
1533                 }
1534             }
1535             state->setFillColor(&color);
1536             out->updateFillColor(state);
1537         }
1538         if (numArgs > 0) {
1539             if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) {
1540                 state->setFillPattern(pattern);
1541             }
1542         }
1543 
1544     } else {
1545         if (numArgs != state->getFillColorSpace()->getNComps()) {
1546             error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
1547             return;
1548         }
1549         state->setFillPattern(nullptr);
1550         for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
1551             if (args[i].isNum()) {
1552                 color.c[i] = dblToCol(args[i].getNum());
1553             } else {
1554                 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1555             }
1556         }
1557         state->setFillColor(&color);
1558         out->updateFillColor(state);
1559     }
1560 }
1561 
opSetStrokeColorN(Object args[],int numArgs)1562 void Gfx::opSetStrokeColorN(Object args[], int numArgs)
1563 {
1564     GfxColor color;
1565     GfxPattern *pattern;
1566     int i;
1567 
1568     if (state->getStrokeColorSpace()->getMode() == csPattern) {
1569         if (numArgs > 1) {
1570             if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())->getUnder()->getNComps()) {
1571                 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1572                 return;
1573             }
1574             for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
1575                 if (args[i].isNum()) {
1576                     color.c[i] = dblToCol(args[i].getNum());
1577                 } else {
1578                     color.c[i] = 0; // TODO Investigate if this is what Adobe does
1579                 }
1580             }
1581             state->setStrokeColor(&color);
1582             out->updateStrokeColor(state);
1583         }
1584         if (unlikely(numArgs <= 0)) {
1585             error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1586             return;
1587         }
1588         if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(args[numArgs - 1].getName(), out, state))) {
1589             state->setStrokePattern(pattern);
1590         }
1591 
1592     } else {
1593         if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1594             error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1595             return;
1596         }
1597         state->setStrokePattern(nullptr);
1598         for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
1599             if (args[i].isNum()) {
1600                 color.c[i] = dblToCol(args[i].getNum());
1601             } else {
1602                 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1603             }
1604         }
1605         state->setStrokeColor(&color);
1606         out->updateStrokeColor(state);
1607     }
1608 }
1609 
1610 //------------------------------------------------------------------------
1611 // path segment operators
1612 //------------------------------------------------------------------------
1613 
opMoveTo(Object args[],int numArgs)1614 void Gfx::opMoveTo(Object args[], int numArgs)
1615 {
1616     state->moveTo(args[0].getNum(), args[1].getNum());
1617 }
1618 
opLineTo(Object args[],int numArgs)1619 void Gfx::opLineTo(Object args[], int numArgs)
1620 {
1621     if (!state->isCurPt()) {
1622         error(errSyntaxError, getPos(), "No current point in lineto");
1623         return;
1624     }
1625     state->lineTo(args[0].getNum(), args[1].getNum());
1626 }
1627 
opCurveTo(Object args[],int numArgs)1628 void Gfx::opCurveTo(Object args[], int numArgs)
1629 {
1630     double x1, y1, x2, y2, x3, y3;
1631 
1632     if (!state->isCurPt()) {
1633         error(errSyntaxError, getPos(), "No current point in curveto");
1634         return;
1635     }
1636     x1 = args[0].getNum();
1637     y1 = args[1].getNum();
1638     x2 = args[2].getNum();
1639     y2 = args[3].getNum();
1640     x3 = args[4].getNum();
1641     y3 = args[5].getNum();
1642     state->curveTo(x1, y1, x2, y2, x3, y3);
1643 }
1644 
opCurveTo1(Object args[],int numArgs)1645 void Gfx::opCurveTo1(Object args[], int numArgs)
1646 {
1647     double x1, y1, x2, y2, x3, y3;
1648 
1649     if (!state->isCurPt()) {
1650         error(errSyntaxError, getPos(), "No current point in curveto1");
1651         return;
1652     }
1653     x1 = state->getCurX();
1654     y1 = state->getCurY();
1655     x2 = args[0].getNum();
1656     y2 = args[1].getNum();
1657     x3 = args[2].getNum();
1658     y3 = args[3].getNum();
1659     state->curveTo(x1, y1, x2, y2, x3, y3);
1660 }
1661 
opCurveTo2(Object args[],int numArgs)1662 void Gfx::opCurveTo2(Object args[], int numArgs)
1663 {
1664     double x1, y1, x2, y2, x3, y3;
1665 
1666     if (!state->isCurPt()) {
1667         error(errSyntaxError, getPos(), "No current point in curveto2");
1668         return;
1669     }
1670     x1 = args[0].getNum();
1671     y1 = args[1].getNum();
1672     x2 = args[2].getNum();
1673     y2 = args[3].getNum();
1674     x3 = x2;
1675     y3 = y2;
1676     state->curveTo(x1, y1, x2, y2, x3, y3);
1677 }
1678 
opRectangle(Object args[],int numArgs)1679 void Gfx::opRectangle(Object args[], int numArgs)
1680 {
1681     double x, y, w, h;
1682 
1683     x = args[0].getNum();
1684     y = args[1].getNum();
1685     w = args[2].getNum();
1686     h = args[3].getNum();
1687     state->moveTo(x, y);
1688     state->lineTo(x + w, y);
1689     state->lineTo(x + w, y + h);
1690     state->lineTo(x, y + h);
1691     state->closePath();
1692 }
1693 
opClosePath(Object args[],int numArgs)1694 void Gfx::opClosePath(Object args[], int numArgs)
1695 {
1696     if (!state->isCurPt()) {
1697         error(errSyntaxError, getPos(), "No current point in closepath");
1698         return;
1699     }
1700     state->closePath();
1701 }
1702 
1703 //------------------------------------------------------------------------
1704 // path painting operators
1705 //------------------------------------------------------------------------
1706 
opEndPath(Object args[],int numArgs)1707 void Gfx::opEndPath(Object args[], int numArgs)
1708 {
1709     doEndPath();
1710 }
1711 
opStroke(Object args[],int numArgs)1712 void Gfx::opStroke(Object args[], int numArgs)
1713 {
1714     if (!state->isCurPt()) {
1715         // error(errSyntaxError, getPos(), "No path in stroke");
1716         return;
1717     }
1718     if (state->isPath()) {
1719         if (ocState) {
1720             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1721                 doPatternStroke();
1722             } else {
1723                 out->stroke(state);
1724             }
1725         }
1726     }
1727     doEndPath();
1728 }
1729 
opCloseStroke(Object *,int)1730 void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/)
1731 {
1732     if (!state->isCurPt()) {
1733         // error(errSyntaxError, getPos(), "No path in closepath/stroke");
1734         return;
1735     }
1736     if (state->isPath()) {
1737         state->closePath();
1738         if (ocState) {
1739             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1740                 doPatternStroke();
1741             } else {
1742                 out->stroke(state);
1743             }
1744         }
1745     }
1746     doEndPath();
1747 }
1748 
opFill(Object args[],int numArgs)1749 void Gfx::opFill(Object args[], int numArgs)
1750 {
1751     if (!state->isCurPt()) {
1752         // error(errSyntaxError, getPos(), "No path in fill");
1753         return;
1754     }
1755     if (state->isPath()) {
1756         if (ocState) {
1757             if (state->getFillColorSpace()->getMode() == csPattern) {
1758                 doPatternFill(false);
1759             } else {
1760                 out->fill(state);
1761             }
1762         }
1763     }
1764     doEndPath();
1765 }
1766 
opEOFill(Object args[],int numArgs)1767 void Gfx::opEOFill(Object args[], int numArgs)
1768 {
1769     if (!state->isCurPt()) {
1770         // error(errSyntaxError, getPos(), "No path in eofill");
1771         return;
1772     }
1773     if (state->isPath()) {
1774         if (ocState) {
1775             if (state->getFillColorSpace()->getMode() == csPattern) {
1776                 doPatternFill(true);
1777             } else {
1778                 out->eoFill(state);
1779             }
1780         }
1781     }
1782     doEndPath();
1783 }
1784 
opFillStroke(Object args[],int numArgs)1785 void Gfx::opFillStroke(Object args[], int numArgs)
1786 {
1787     if (!state->isCurPt()) {
1788         // error(errSyntaxError, getPos(), "No path in fill/stroke");
1789         return;
1790     }
1791     if (state->isPath()) {
1792         if (ocState) {
1793             if (state->getFillColorSpace()->getMode() == csPattern) {
1794                 doPatternFill(false);
1795             } else {
1796                 out->fill(state);
1797             }
1798             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1799                 doPatternStroke();
1800             } else {
1801                 out->stroke(state);
1802             }
1803         }
1804     }
1805     doEndPath();
1806 }
1807 
opCloseFillStroke(Object args[],int numArgs)1808 void Gfx::opCloseFillStroke(Object args[], int numArgs)
1809 {
1810     if (!state->isCurPt()) {
1811         // error(errSyntaxError, getPos(), "No path in closepath/fill/stroke");
1812         return;
1813     }
1814     if (state->isPath()) {
1815         state->closePath();
1816         if (ocState) {
1817             if (state->getFillColorSpace()->getMode() == csPattern) {
1818                 doPatternFill(false);
1819             } else {
1820                 out->fill(state);
1821             }
1822             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1823                 doPatternStroke();
1824             } else {
1825                 out->stroke(state);
1826             }
1827         }
1828     }
1829     doEndPath();
1830 }
1831 
opEOFillStroke(Object args[],int numArgs)1832 void Gfx::opEOFillStroke(Object args[], int numArgs)
1833 {
1834     if (!state->isCurPt()) {
1835         // error(errSyntaxError, getPos(), "No path in eofill/stroke");
1836         return;
1837     }
1838     if (state->isPath()) {
1839         if (ocState) {
1840             if (state->getFillColorSpace()->getMode() == csPattern) {
1841                 doPatternFill(true);
1842             } else {
1843                 out->eoFill(state);
1844             }
1845             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1846                 doPatternStroke();
1847             } else {
1848                 out->stroke(state);
1849             }
1850         }
1851     }
1852     doEndPath();
1853 }
1854 
opCloseEOFillStroke(Object args[],int numArgs)1855 void Gfx::opCloseEOFillStroke(Object args[], int numArgs)
1856 {
1857     if (!state->isCurPt()) {
1858         // error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke");
1859         return;
1860     }
1861     if (state->isPath()) {
1862         state->closePath();
1863         if (ocState) {
1864             if (state->getFillColorSpace()->getMode() == csPattern) {
1865                 doPatternFill(true);
1866             } else {
1867                 out->eoFill(state);
1868             }
1869             if (state->getStrokeColorSpace()->getMode() == csPattern) {
1870                 doPatternStroke();
1871             } else {
1872                 out->stroke(state);
1873             }
1874         }
1875     }
1876     doEndPath();
1877 }
1878 
doPatternFill(bool eoFill)1879 void Gfx::doPatternFill(bool eoFill)
1880 {
1881     GfxPattern *pattern;
1882 
1883     // this is a bit of a kludge -- patterns can be really slow, so we
1884     // skip them if we're only doing text extraction, since they almost
1885     // certainly don't contain any text
1886     if (!out->needNonText()) {
1887         return;
1888     }
1889 
1890     if (!(pattern = state->getFillPattern())) {
1891         return;
1892     }
1893     switch (pattern->getType()) {
1894     case 1:
1895         doTilingPatternFill((GfxTilingPattern *)pattern, false, eoFill, false);
1896         break;
1897     case 2:
1898         doShadingPatternFill((GfxShadingPattern *)pattern, false, eoFill, false);
1899         break;
1900     default:
1901         error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType());
1902         break;
1903     }
1904 }
1905 
doPatternStroke()1906 void Gfx::doPatternStroke()
1907 {
1908     GfxPattern *pattern;
1909 
1910     // this is a bit of a kludge -- patterns can be really slow, so we
1911     // skip them if we're only doing text extraction, since they almost
1912     // certainly don't contain any text
1913     if (!out->needNonText()) {
1914         return;
1915     }
1916 
1917     if (!(pattern = state->getStrokePattern())) {
1918         return;
1919     }
1920     switch (pattern->getType()) {
1921     case 1:
1922         doTilingPatternFill((GfxTilingPattern *)pattern, true, false, false);
1923         break;
1924     case 2:
1925         doShadingPatternFill((GfxShadingPattern *)pattern, true, false, false);
1926         break;
1927     default:
1928         error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in stroke", pattern->getType());
1929         break;
1930     }
1931 }
1932 
doPatternText()1933 void Gfx::doPatternText()
1934 {
1935     GfxPattern *pattern;
1936 
1937     // this is a bit of a kludge -- patterns can be really slow, so we
1938     // skip them if we're only doing text extraction, since they almost
1939     // certainly don't contain any text
1940     if (!out->needNonText()) {
1941         return;
1942     }
1943 
1944     if (!(pattern = state->getFillPattern())) {
1945         return;
1946     }
1947     switch (pattern->getType()) {
1948     case 1:
1949         doTilingPatternFill((GfxTilingPattern *)pattern, false, false, true);
1950         break;
1951     case 2:
1952         doShadingPatternFill((GfxShadingPattern *)pattern, false, false, true);
1953         break;
1954     default:
1955         error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill", pattern->getType());
1956         break;
1957     }
1958 }
1959 
doPatternImageMask(Object * ref,Stream * str,int width,int height,bool invert,bool inlineImg)1960 void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg)
1961 {
1962     saveState();
1963 
1964     out->setSoftMaskFromImageMask(state, ref, str, width, height, invert, inlineImg, baseMatrix);
1965 
1966     state->clearPath();
1967     state->moveTo(0, 0);
1968     state->lineTo(1, 0);
1969     state->lineTo(1, 1);
1970     state->lineTo(0, 1);
1971     state->closePath();
1972     doPatternText();
1973 
1974     out->unsetSoftMaskFromImageMask(state, baseMatrix);
1975     restoreState();
1976 }
1977 
doTilingPatternFill(GfxTilingPattern * tPat,bool stroke,bool eoFill,bool text)1978 void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, bool stroke, bool eoFill, bool text)
1979 {
1980     GfxPatternColorSpace *patCS;
1981     GfxColorSpace *cs;
1982     GfxColor color;
1983     GfxState *savedState;
1984     double xMin, yMin, xMax, yMax, x, y, x1, y1;
1985     double cxMin, cyMin, cxMax, cyMax;
1986     int xi0, yi0, xi1, yi1, xi, yi;
1987     const double *ctm, *btm, *ptm;
1988     double m[6], ictm[6], m1[6], imb[6];
1989     double det;
1990     double xstep, ystep;
1991     int i;
1992 
1993     // get color space
1994     patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace() : state->getFillColorSpace());
1995 
1996     // construct a (pattern space) -> (current space) transform matrix
1997     ctm = state->getCTM();
1998     btm = baseMatrix;
1999     ptm = tPat->getMatrix();
2000     // iCTM = invert CTM
2001     det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
2002     if (fabs(det) < 0.000001) {
2003         error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
2004         return;
2005     }
2006     det = 1 / det;
2007     ictm[0] = ctm[3] * det;
2008     ictm[1] = -ctm[1] * det;
2009     ictm[2] = -ctm[2] * det;
2010     ictm[3] = ctm[0] * det;
2011     ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2012     ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2013     // m1 = PTM * BTM = PTM * base transform matrix
2014     m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
2015     m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
2016     m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
2017     m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
2018     m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
2019     m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
2020     // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2021     m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
2022     m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
2023     m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
2024     m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
2025     m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
2026     m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
2027 
2028     // construct a (device space) -> (pattern space) transform matrix
2029     det = m1[0] * m1[3] - m1[1] * m1[2];
2030     det = 1 / det;
2031     if (!std::isfinite(det)) {
2032         error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
2033         return;
2034     }
2035     imb[0] = m1[3] * det;
2036     imb[1] = -m1[1] * det;
2037     imb[2] = -m1[2] * det;
2038     imb[3] = m1[0] * det;
2039     imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
2040     imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
2041 
2042     // save current graphics state
2043     savedState = saveStateStack();
2044 
2045     // set underlying color space (for uncolored tiling patterns); set
2046     // various other parameters (stroke color, line width) to match
2047     // Adobe's behavior
2048     state->setFillPattern(nullptr);
2049     state->setStrokePattern(nullptr);
2050     if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
2051         state->setFillColorSpace(cs->copy());
2052         out->updateFillColorSpace(state);
2053         state->setStrokeColorSpace(cs->copy());
2054         out->updateStrokeColorSpace(state);
2055         if (stroke) {
2056             state->setFillColor(state->getStrokeColor());
2057         } else {
2058             state->setStrokeColor(state->getFillColor());
2059         }
2060         out->updateFillColor(state);
2061         out->updateStrokeColor(state);
2062     } else {
2063         cs = new GfxDeviceGrayColorSpace();
2064         state->setFillColorSpace(cs);
2065         cs->getDefaultColor(&color);
2066         state->setFillColor(&color);
2067         out->updateFillColorSpace(state);
2068         state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
2069         state->setStrokeColor(&color);
2070         out->updateStrokeColorSpace(state);
2071     }
2072     if (!stroke) {
2073         state->setLineWidth(0);
2074         out->updateLineWidth(state);
2075     }
2076 
2077     // clip to current path
2078     if (stroke) {
2079         state->clipToStrokePath();
2080         out->clipToStrokePath(state);
2081     } else if (!text) {
2082         state->clip();
2083         if (eoFill) {
2084             out->eoClip(state);
2085         } else {
2086             out->clip(state);
2087         }
2088     }
2089     state->clearPath();
2090 
2091     // get the clip region, check for empty
2092     state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
2093     if (cxMin > cxMax || cyMin > cyMax) {
2094         goto restore;
2095     }
2096 
2097     // transform clip region bbox to pattern space
2098     xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
2099     yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
2100     x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
2101     y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
2102     if (x1 < xMin) {
2103         xMin = x1;
2104     } else if (x1 > xMax) {
2105         xMax = x1;
2106     }
2107     if (y1 < yMin) {
2108         yMin = y1;
2109     } else if (y1 > yMax) {
2110         yMax = y1;
2111     }
2112     x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
2113     y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
2114     if (x1 < xMin) {
2115         xMin = x1;
2116     } else if (x1 > xMax) {
2117         xMax = x1;
2118     }
2119     if (y1 < yMin) {
2120         yMin = y1;
2121     } else if (y1 > yMax) {
2122         yMax = y1;
2123     }
2124     x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
2125     y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
2126     if (x1 < xMin) {
2127         xMin = x1;
2128     } else if (x1 > xMax) {
2129         xMax = x1;
2130     }
2131     if (y1 < yMin) {
2132         yMin = y1;
2133     } else if (y1 > yMax) {
2134         yMax = y1;
2135     }
2136 
2137     // draw the pattern
2138     //~ this should treat negative steps differently -- start at right/top
2139     //~ edge instead of left/bottom (?)
2140     xstep = fabs(tPat->getXStep());
2141     ystep = fabs(tPat->getYStep());
2142     if (unlikely(xstep == 0 || ystep == 0)) {
2143         goto restore;
2144     }
2145     if (tPat->getBBox()[0] < tPat->getBBox()[2]) {
2146         xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep);
2147         xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1;
2148     } else {
2149         xi0 = (int)ceil((xMin - tPat->getBBox()[0]) / xstep);
2150         xi1 = (int)floor((xMax - tPat->getBBox()[2]) / xstep) + 1;
2151     }
2152     if (tPat->getBBox()[1] < tPat->getBBox()[3]) {
2153         yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep);
2154         yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1;
2155     } else {
2156         yi0 = (int)ceil((yMin - tPat->getBBox()[1]) / ystep);
2157         yi1 = (int)floor((yMax - tPat->getBBox()[3]) / ystep) + 1;
2158     }
2159     for (i = 0; i < 4; ++i) {
2160         m1[i] = m[i];
2161     }
2162     m1[4] = m[4];
2163     m1[5] = m[5];
2164     {
2165         bool shouldDrawPattern = true;
2166         std::set<int>::iterator patternRefIt;
2167         const int patternRefNum = tPat->getPatternRefNum();
2168         if (patternRefNum != -1) {
2169             if (formsDrawing.find(patternRefNum) == formsDrawing.end()) {
2170                 patternRefIt = formsDrawing.insert(patternRefNum).first;
2171             } else {
2172                 shouldDrawPattern = false;
2173             }
2174         }
2175         if (shouldDrawPattern) {
2176             if (out->useTilingPatternFill() && out->tilingPatternFill(state, this, catalog, tPat, m1, xi0, yi0, xi1, yi1, xstep, ystep)) {
2177                 // do nothing
2178             } else {
2179                 out->updatePatternOpacity(state);
2180                 for (yi = yi0; yi < yi1; ++yi) {
2181                     for (xi = xi0; xi < xi1; ++xi) {
2182                         x = xi * xstep;
2183                         y = yi * ystep;
2184                         m1[4] = x * m[0] + y * m[2] + m[4];
2185                         m1[5] = x * m[1] + y * m[3] + m[5];
2186                         drawForm(tPat->getContentStream(), tPat->getResDict(), m1, tPat->getBBox());
2187                     }
2188                 }
2189                 out->clearPatternOpacity(state);
2190             }
2191             if (patternRefNum != -1) {
2192                 formsDrawing.erase(patternRefIt);
2193             }
2194         }
2195     }
2196 
2197     // restore graphics state
2198 restore:
2199     restoreStateStack(savedState);
2200 }
2201 
doShadingPatternFill(GfxShadingPattern * sPat,bool stroke,bool eoFill,bool text)2202 void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, bool stroke, bool eoFill, bool text)
2203 {
2204     GfxShading *shading;
2205     GfxState *savedState;
2206     const double *ctm, *btm, *ptm;
2207     double m[6], ictm[6], m1[6];
2208     double xMin, yMin, xMax, yMax;
2209     double det;
2210 
2211     shading = sPat->getShading();
2212 
2213     // save current graphics state
2214     savedState = saveStateStack();
2215 
2216     // clip to current path
2217     if (stroke) {
2218         state->clipToStrokePath();
2219         out->clipToStrokePath(state);
2220     } else if (!text) {
2221         state->clip();
2222         if (eoFill) {
2223             out->eoClip(state);
2224         } else {
2225             out->clip(state);
2226         }
2227     }
2228     state->clearPath();
2229 
2230     // construct a (pattern space) -> (current space) transform matrix
2231     ctm = state->getCTM();
2232     btm = baseMatrix;
2233     ptm = sPat->getMatrix();
2234     // iCTM = invert CTM
2235     det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
2236     if (fabs(det) < 0.000001) {
2237         error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill");
2238         restoreStateStack(savedState);
2239         return;
2240     }
2241     det = 1 / det;
2242     ictm[0] = ctm[3] * det;
2243     ictm[1] = -ctm[1] * det;
2244     ictm[2] = -ctm[2] * det;
2245     ictm[3] = ctm[0] * det;
2246     ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2247     ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2248     // m1 = PTM * BTM = PTM * base transform matrix
2249     m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
2250     m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
2251     m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
2252     m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
2253     m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
2254     m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
2255     // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2256     m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
2257     m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
2258     m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
2259     m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
2260     m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
2261     m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
2262 
2263     // set the new matrix
2264     state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
2265     out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
2266 
2267     // clip to bbox
2268     if (shading->getHasBBox()) {
2269         shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2270         state->moveTo(xMin, yMin);
2271         state->lineTo(xMax, yMin);
2272         state->lineTo(xMax, yMax);
2273         state->lineTo(xMin, yMax);
2274         state->closePath();
2275         state->clip();
2276         out->clip(state);
2277         state->clearPath();
2278     }
2279 
2280     // set the color space
2281     state->setFillColorSpace(shading->getColorSpace()->copy());
2282     out->updateFillColorSpace(state);
2283 
2284     // background color fill
2285     if (shading->getHasBackground()) {
2286         state->setFillColor(shading->getBackground());
2287         out->updateFillColor(state);
2288         state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2289         state->moveTo(xMin, yMin);
2290         state->lineTo(xMax, yMin);
2291         state->lineTo(xMax, yMax);
2292         state->lineTo(xMin, yMax);
2293         state->closePath();
2294         out->fill(state);
2295         state->clearPath();
2296     }
2297 
2298 #if 1 //~tmp: turn off anti-aliasing temporarily
2299     bool vaa = out->getVectorAntialias();
2300     if (vaa) {
2301         out->setVectorAntialias(false);
2302     }
2303 #endif
2304 
2305     // do shading type-specific operations
2306     switch (shading->getType()) {
2307     case 1:
2308         doFunctionShFill((GfxFunctionShading *)shading);
2309         break;
2310     case 2:
2311         doAxialShFill((GfxAxialShading *)shading);
2312         break;
2313     case 3:
2314         doRadialShFill((GfxRadialShading *)shading);
2315         break;
2316     case 4:
2317     case 5:
2318         doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2319         break;
2320     case 6:
2321     case 7:
2322         doPatchMeshShFill((GfxPatchMeshShading *)shading);
2323         break;
2324     }
2325 
2326 #if 1 //~tmp: turn off anti-aliasing temporarily
2327     if (vaa) {
2328         out->setVectorAntialias(true);
2329     }
2330 #endif
2331 
2332     // restore graphics state
2333     restoreStateStack(savedState);
2334 }
2335 
opShFill(Object args[],int numArgs)2336 void Gfx::opShFill(Object args[], int numArgs)
2337 {
2338     GfxShading *shading;
2339     GfxState *savedState;
2340     double xMin, yMin, xMax, yMax;
2341 
2342     if (!ocState) {
2343         return;
2344     }
2345 
2346     if (!(shading = res->lookupShading(args[0].getName(), out, state))) {
2347         return;
2348     }
2349 
2350     // save current graphics state
2351     savedState = saveStateStack();
2352 
2353     // clip to bbox
2354     if (shading->getHasBBox()) {
2355         shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2356         state->moveTo(xMin, yMin);
2357         state->lineTo(xMax, yMin);
2358         state->lineTo(xMax, yMax);
2359         state->lineTo(xMin, yMax);
2360         state->closePath();
2361         state->clip();
2362         out->clip(state);
2363         state->clearPath();
2364     }
2365 
2366     // set the color space
2367     state->setFillColorSpace(shading->getColorSpace()->copy());
2368     out->updateFillColorSpace(state);
2369 
2370 #if 1 //~tmp: turn off anti-aliasing temporarily
2371     bool vaa = out->getVectorAntialias();
2372     if (vaa) {
2373         out->setVectorAntialias(false);
2374     }
2375 #endif
2376 
2377     // do shading type-specific operations
2378     switch (shading->getType()) {
2379     case 1:
2380         doFunctionShFill((GfxFunctionShading *)shading);
2381         break;
2382     case 2:
2383         doAxialShFill((GfxAxialShading *)shading);
2384         break;
2385     case 3:
2386         doRadialShFill((GfxRadialShading *)shading);
2387         break;
2388     case 4:
2389     case 5:
2390         doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2391         break;
2392     case 6:
2393     case 7:
2394         doPatchMeshShFill((GfxPatchMeshShading *)shading);
2395         break;
2396     }
2397 
2398 #if 1 //~tmp: turn off anti-aliasing temporarily
2399     if (vaa) {
2400         out->setVectorAntialias(true);
2401     }
2402 #endif
2403 
2404     // restore graphics state
2405     restoreStateStack(savedState);
2406 
2407     delete shading;
2408 }
2409 
doFunctionShFill(GfxFunctionShading * shading)2410 void Gfx::doFunctionShFill(GfxFunctionShading *shading)
2411 {
2412     double x0, y0, x1, y1;
2413     GfxColor colors[4];
2414 
2415     if (out->useShadedFills(shading->getType()) && out->functionShadedFill(state, shading)) {
2416         return;
2417     }
2418 
2419     shading->getDomain(&x0, &y0, &x1, &y1);
2420     shading->getColor(x0, y0, &colors[0]);
2421     shading->getColor(x0, y1, &colors[1]);
2422     shading->getColor(x1, y0, &colors[2]);
2423     shading->getColor(x1, y1, &colors[3]);
2424     doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
2425 }
2426 
doFunctionShFill1(GfxFunctionShading * shading,double x0,double y0,double x1,double y1,GfxColor * colors,int depth)2427 void Gfx::doFunctionShFill1(GfxFunctionShading *shading, double x0, double y0, double x1, double y1, GfxColor *colors, int depth)
2428 {
2429     GfxColor fillColor;
2430     GfxColor color0M, color1M, colorM0, colorM1, colorMM;
2431     GfxColor colors2[4];
2432     double xM, yM;
2433     int nComps, i, j;
2434 
2435     nComps = shading->getColorSpace()->getNComps();
2436     const double *matrix = shading->getMatrix();
2437 
2438     // compare the four corner colors
2439     for (i = 0; i < 4; ++i) {
2440         for (j = 0; j < nComps; ++j) {
2441             if (abs(colors[i].c[j] - colors[(i + 1) & 3].c[j]) > functionColorDelta) {
2442                 break;
2443             }
2444         }
2445         if (j < nComps) {
2446             break;
2447         }
2448     }
2449 
2450     // center of the rectangle
2451     xM = 0.5 * (x0 + x1);
2452     yM = 0.5 * (y0 + y1);
2453 
2454     // the four corner colors are close (or we hit the recursive limit)
2455     // -- fill the rectangle; but require at least one subdivision
2456     // (depth==0) to avoid problems when the four outer corners of the
2457     // shaded region are the same color
2458     if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
2459 
2460         // use the center color
2461         shading->getColor(xM, yM, &fillColor);
2462         state->setFillColor(&fillColor);
2463         out->updateFillColor(state);
2464 
2465         // fill the rectangle
2466         state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4], x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
2467         state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4], x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
2468         state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4], x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
2469         state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4], x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
2470         state->closePath();
2471         out->fill(state);
2472         state->clearPath();
2473 
2474         // the four corner colors are not close enough -- subdivide the
2475         // rectangle
2476     } else {
2477 
2478         // colors[0]       colorM0       colors[2]
2479         //   (x0,y0)       (xM,y0)       (x1,y0)
2480         //         +----------+----------+
2481         //         |          |          |
2482         //         |    UL    |    UR    |
2483         // color0M |       colorMM       | color1M
2484         // (x0,yM) +----------+----------+ (x1,yM)
2485         //         |       (xM,yM)       |
2486         //         |    LL    |    LR    |
2487         //         |          |          |
2488         //         +----------+----------+
2489         // colors[1]       colorM1       colors[3]
2490         //   (x0,y1)       (xM,y1)       (x1,y1)
2491 
2492         shading->getColor(x0, yM, &color0M);
2493         shading->getColor(x1, yM, &color1M);
2494         shading->getColor(xM, y0, &colorM0);
2495         shading->getColor(xM, y1, &colorM1);
2496         shading->getColor(xM, yM, &colorMM);
2497 
2498         // upper-left sub-rectangle
2499         colors2[0] = colors[0];
2500         colors2[1] = color0M;
2501         colors2[2] = colorM0;
2502         colors2[3] = colorMM;
2503         doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
2504 
2505         // lower-left sub-rectangle
2506         colors2[0] = color0M;
2507         colors2[1] = colors[1];
2508         colors2[2] = colorMM;
2509         colors2[3] = colorM1;
2510         doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
2511 
2512         // upper-right sub-rectangle
2513         colors2[0] = colorM0;
2514         colors2[1] = colorMM;
2515         colors2[2] = colors[2];
2516         colors2[3] = color1M;
2517         doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
2518 
2519         // lower-right sub-rectangle
2520         colors2[0] = colorMM;
2521         colors2[1] = colorM1;
2522         colors2[2] = color1M;
2523         colors2[3] = colors[3];
2524         doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
2525     }
2526 }
2527 
doAxialShFill(GfxAxialShading * shading)2528 void Gfx::doAxialShFill(GfxAxialShading *shading)
2529 {
2530     double xMin, yMin, xMax, yMax;
2531     double x0, y0, x1, y1;
2532     double dx, dy, mul;
2533     bool dxZero, dyZero;
2534     double bboxIntersections[4];
2535     double tMin, tMax, tx, ty;
2536     double s[4], sMin, sMax, tmp;
2537     double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
2538     double t0, t1, tt;
2539     double ta[axialMaxSplits + 1];
2540     int next[axialMaxSplits + 1];
2541     GfxColor color0 = {}, color1 = {};
2542     int nComps;
2543     int i, j, k;
2544     bool needExtend = true;
2545 
2546     // get the clip region bbox
2547     state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2548 
2549     // compute min and max t values, based on the four corners of the
2550     // clip region bbox
2551     shading->getCoords(&x0, &y0, &x1, &y1);
2552     dx = x1 - x0;
2553     dy = y1 - y0;
2554     dxZero = fabs(dx) < 0.01;
2555     dyZero = fabs(dy) < 0.01;
2556     if (dxZero && dyZero) {
2557         tMin = tMax = 0;
2558     } else {
2559         mul = 1 / (dx * dx + dy * dy);
2560         bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
2561         bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
2562         bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
2563         bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
2564         std::sort(std::begin(bboxIntersections), std::end(bboxIntersections));
2565         tMin = bboxIntersections[0];
2566         tMax = bboxIntersections[3];
2567         if (tMin < 0 && !shading->getExtend0()) {
2568             tMin = 0;
2569         }
2570         if (tMax > 1 && !shading->getExtend1()) {
2571             tMax = 1;
2572         }
2573     }
2574 
2575     if (out->useShadedFills(shading->getType()) && out->axialShadedFill(state, shading, tMin, tMax)) {
2576         return;
2577     }
2578 
2579     // get the function domain
2580     t0 = shading->getDomain0();
2581     t1 = shading->getDomain1();
2582 
2583     // Traverse the t axis and do the shading.
2584     //
2585     // For each point (tx, ty) on the t axis, consider a line through
2586     // that point perpendicular to the t axis:
2587     //
2588     //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
2589     //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
2590     //
2591     // Then look at the intersection of this line with the bounding box
2592     // (xMin, yMin, xMax, yMax).  In the general case, there are four
2593     // intersection points:
2594     //
2595     //     s0 = (xMin - tx) / -dy
2596     //     s1 = (xMax - tx) / -dy
2597     //     s2 = (yMin - ty) / dx
2598     //     s3 = (yMax - ty) / dx
2599     //
2600     // and we want the middle two s values.
2601     //
2602     // In the case where dx = 0, take s0 and s1; in the case where dy =
2603     // 0, take s2 and s3.
2604     //
2605     // Each filled polygon is bounded by two of these line segments
2606     // perpdendicular to the t axis.
2607     //
2608     // The t axis is bisected into smaller regions until the color
2609     // difference across a region is small enough, and then the region
2610     // is painted with a single color.
2611 
2612     // set up: require at least one split to avoid problems when the two
2613     // ends of the t axis have the same color
2614     nComps = shading->getColorSpace()->getNComps();
2615     ta[0] = tMin;
2616     next[0] = axialMaxSplits / 2;
2617     ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
2618     next[axialMaxSplits / 2] = axialMaxSplits;
2619     ta[axialMaxSplits] = tMax;
2620 
2621     // compute the color at t = tMin
2622     if (tMin < 0) {
2623         tt = t0;
2624     } else if (tMin > 1) {
2625         tt = t1;
2626     } else {
2627         tt = t0 + (t1 - t0) * tMin;
2628     }
2629     shading->getColor(tt, &color0);
2630 
2631     if (out->useFillColorStop()) {
2632         // make sure we add stop color when t = tMin
2633         state->setFillColor(&color0);
2634         out->updateFillColorStop(state, 0);
2635     }
2636 
2637     // compute the coordinates of the point on the t axis at t = tMin;
2638     // then compute the intersection of the perpendicular line with the
2639     // bounding box
2640     tx = x0 + tMin * dx;
2641     ty = y0 + tMin * dy;
2642     if (dxZero && dyZero) {
2643         sMin = sMax = 0;
2644     } else if (dxZero) {
2645         sMin = (xMin - tx) / -dy;
2646         sMax = (xMax - tx) / -dy;
2647         if (sMin > sMax) {
2648             tmp = sMin;
2649             sMin = sMax;
2650             sMax = tmp;
2651         }
2652     } else if (dyZero) {
2653         sMin = (yMin - ty) / dx;
2654         sMax = (yMax - ty) / dx;
2655         if (sMin > sMax) {
2656             tmp = sMin;
2657             sMin = sMax;
2658             sMax = tmp;
2659         }
2660     } else {
2661         s[0] = (yMin - ty) / dx;
2662         s[1] = (yMax - ty) / dx;
2663         s[2] = (xMin - tx) / -dy;
2664         s[3] = (xMax - tx) / -dy;
2665         std::sort(std::begin(s), std::end(s));
2666         sMin = s[1];
2667         sMax = s[2];
2668     }
2669     ux0 = tx - sMin * dy;
2670     uy0 = ty + sMin * dx;
2671     vx0 = tx - sMax * dy;
2672     vy0 = ty + sMax * dx;
2673 
2674     i = 0;
2675     bool doneBBox1, doneBBox2;
2676     if (dxZero && dyZero) {
2677         doneBBox1 = doneBBox2 = true;
2678     } else {
2679         doneBBox1 = bboxIntersections[1] < tMin;
2680         doneBBox2 = bboxIntersections[2] > tMax;
2681     }
2682 
2683     // If output device doesn't support the extended mode required
2684     // we have to do it here
2685     needExtend = !out->axialShadedSupportExtend(state, shading);
2686 
2687     while (i < axialMaxSplits) {
2688 
2689         // bisect until color difference is small enough or we hit the
2690         // bisection limit
2691         const double previousStop = tt;
2692         j = next[i];
2693         while (j > i + 1) {
2694             if (ta[j] < 0) {
2695                 tt = t0;
2696             } else if (ta[j] > 1) {
2697                 tt = t1;
2698             } else {
2699                 tt = t0 + (t1 - t0) * ta[j];
2700             }
2701 
2702             // Try to determine whether the color map is constant between ta[i] and ta[j].
2703             // In the strict sense this question cannot be answered by sampling alone.
2704             // We try an educated guess in form of 2 samples.
2705             // See https://gitlab.freedesktop.org/poppler/poppler/issues/938 for a file where one sample was not enough.
2706 
2707             // The first test sample at 1.0 (i.e., ta[j]) is coded separately, because we may
2708             // want to reuse the color later
2709             shading->getColor(tt, &color1);
2710             bool isPatchOfConstantColor = isSameGfxColor(color1, color0, nComps, axialColorDelta);
2711 
2712             if (isPatchOfConstantColor) {
2713 
2714                 // Add more sample locations here if required
2715                 for (double l : { 0.5 }) {
2716                     GfxColor tmpColor;
2717                     double x = previousStop + l * (tt - previousStop);
2718                     shading->getColor(x, &tmpColor);
2719                     if (!isSameGfxColor(tmpColor, color0, nComps, axialColorDelta)) {
2720                         isPatchOfConstantColor = false;
2721                         break;
2722                     }
2723                 }
2724             }
2725 
2726             if (isPatchOfConstantColor) {
2727                 // in these two if what we guarantee is that if we are skipping lots of
2728                 // positions because the colors are the same, we still create a region
2729                 // with vertexs passing by bboxIntersections[1] and bboxIntersections[2]
2730                 // otherwise we can have empty regions that should really be painted
2731                 // like happened in bug 19896
2732                 // What we do to ensure that we pass a line through this points
2733                 // is making sure use the exact bboxIntersections[] value as one of the used ta[] values
2734                 if (!doneBBox1 && ta[i] < bboxIntersections[1] && ta[j] > bboxIntersections[1]) {
2735                     int teoricalj = (int)((bboxIntersections[1] - tMin) * axialMaxSplits / (tMax - tMin));
2736                     if (teoricalj <= i)
2737                         teoricalj = i + 1;
2738                     if (teoricalj < j) {
2739                         next[i] = teoricalj;
2740                         next[teoricalj] = j;
2741                     } else {
2742                         teoricalj = j;
2743                     }
2744                     ta[teoricalj] = bboxIntersections[1];
2745                     j = teoricalj;
2746                     doneBBox1 = true;
2747                 }
2748                 if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) {
2749                     int teoricalj = (int)((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin));
2750                     if (teoricalj <= i)
2751                         teoricalj = i + 1;
2752                     if (teoricalj < j) {
2753                         next[i] = teoricalj;
2754                         next[teoricalj] = j;
2755                     } else {
2756                         teoricalj = j;
2757                     }
2758                     ta[teoricalj] = bboxIntersections[2];
2759                     j = teoricalj;
2760                     doneBBox2 = true;
2761                 }
2762                 break;
2763             }
2764             k = (i + j) / 2;
2765             ta[k] = 0.5 * (ta[i] + ta[j]);
2766             next[i] = k;
2767             next[k] = j;
2768             j = k;
2769         }
2770 
2771         // use the average of the colors of the two sides of the region
2772         for (k = 0; k < nComps; ++k) {
2773             color0.c[k] = safeAverage(color0.c[k], color1.c[k]);
2774         }
2775 
2776         // compute the coordinates of the point on the t axis; then
2777         // compute the intersection of the perpendicular line with the
2778         // bounding box
2779         tx = x0 + ta[j] * dx;
2780         ty = y0 + ta[j] * dy;
2781         if (dxZero && dyZero) {
2782             sMin = sMax = 0;
2783         } else if (dxZero) {
2784             sMin = (xMin - tx) / -dy;
2785             sMax = (xMax - tx) / -dy;
2786             if (sMin > sMax) {
2787                 tmp = sMin;
2788                 sMin = sMax;
2789                 sMax = tmp;
2790             }
2791         } else if (dyZero) {
2792             sMin = (yMin - ty) / dx;
2793             sMax = (yMax - ty) / dx;
2794             if (sMin > sMax) {
2795                 tmp = sMin;
2796                 sMin = sMax;
2797                 sMax = tmp;
2798             }
2799         } else {
2800             s[0] = (yMin - ty) / dx;
2801             s[1] = (yMax - ty) / dx;
2802             s[2] = (xMin - tx) / -dy;
2803             s[3] = (xMax - tx) / -dy;
2804             std::sort(std::begin(s), std::end(s));
2805             sMin = s[1];
2806             sMax = s[2];
2807         }
2808         ux1 = tx - sMin * dy;
2809         uy1 = ty + sMin * dx;
2810         vx1 = tx - sMax * dy;
2811         vy1 = ty + sMax * dx;
2812 
2813         // set the color
2814         state->setFillColor(&color0);
2815         if (out->useFillColorStop())
2816             out->updateFillColorStop(state, (ta[j] - tMin) / (tMax - tMin));
2817         else
2818             out->updateFillColor(state);
2819 
2820         if (needExtend) {
2821             // fill the region
2822             state->moveTo(ux0, uy0);
2823             state->lineTo(vx0, vy0);
2824             state->lineTo(vx1, vy1);
2825             state->lineTo(ux1, uy1);
2826             state->closePath();
2827         }
2828 
2829         if (!out->useFillColorStop()) {
2830             out->fill(state);
2831             state->clearPath();
2832         }
2833 
2834         // set up for next region
2835         ux0 = ux1;
2836         uy0 = uy1;
2837         vx0 = vx1;
2838         vy0 = vy1;
2839         color0 = color1;
2840         i = next[i];
2841     }
2842 
2843     if (out->useFillColorStop()) {
2844         if (!needExtend) {
2845             state->moveTo(xMin, yMin);
2846             state->lineTo(xMin, yMax);
2847             state->lineTo(xMax, yMax);
2848             state->lineTo(xMax, yMin);
2849             state->closePath();
2850         }
2851         out->fill(state);
2852         state->clearPath();
2853     }
2854 }
2855 
getShadingColorRadialHelper(double t0,double t1,double t,GfxRadialShading * shading,GfxColor * color)2856 static inline void getShadingColorRadialHelper(double t0, double t1, double t, GfxRadialShading *shading, GfxColor *color)
2857 {
2858     if (t0 < t1) {
2859         if (t < t0) {
2860             shading->getColor(t0, color);
2861         } else if (t > t1) {
2862             shading->getColor(t1, color);
2863         } else {
2864             shading->getColor(t, color);
2865         }
2866     } else {
2867         if (t > t0) {
2868             shading->getColor(t0, color);
2869         } else if (t < t1) {
2870             shading->getColor(t1, color);
2871         } else {
2872             shading->getColor(t, color);
2873         }
2874     }
2875 }
2876 
doRadialShFill(GfxRadialShading * shading)2877 void Gfx::doRadialShFill(GfxRadialShading *shading)
2878 {
2879     double xMin, yMin, xMax, yMax;
2880     double x0, y0, r0, x1, y1, r1, t0, t1;
2881     int nComps;
2882     GfxColor colorA = {}, colorB = {}, colorC = {};
2883     double xa, ya, xb, yb, ra, rb;
2884     double ta, tb, sa, sb;
2885     double sz, xz, yz, sMin, sMax;
2886     bool enclosed;
2887     int ia, ib, k, n;
2888     double theta, alpha, angle, t;
2889     bool needExtend = true;
2890 
2891     // get the shading info
2892     shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
2893     t0 = shading->getDomain0();
2894     t1 = shading->getDomain1();
2895     nComps = shading->getColorSpace()->getNComps();
2896 
2897     // Compute the point at which r(s) = 0; check for the enclosed
2898     // circles case; and compute the angles for the tangent lines.
2899     if (x0 == x1 && y0 == y1) {
2900         enclosed = true;
2901         theta = 0; // make gcc happy
2902         sz = 0; // make gcc happy
2903     } else if (r0 == r1) {
2904         enclosed = false;
2905         theta = 0;
2906         sz = 0; // make gcc happy
2907     } else {
2908         sz = (r1 > r0) ? -r0 / (r1 - r0) : -r1 / (r0 - r1);
2909         xz = x0 + sz * (x1 - x0);
2910         yz = y0 + sz * (y1 - y0);
2911         enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
2912         const double theta_aux = sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz));
2913         if (likely(theta_aux != 0)) {
2914             theta = asin(r0 / theta_aux);
2915         } else {
2916             theta = 0;
2917         }
2918         if (r0 > r1) {
2919             theta = -theta;
2920         }
2921     }
2922     if (enclosed) {
2923         alpha = 0;
2924     } else {
2925         alpha = atan2(y1 - y0, x1 - x0);
2926     }
2927 
2928     // compute the (possibly extended) s range
2929     state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2930     if (enclosed) {
2931         sMin = 0;
2932         sMax = 1;
2933     } else {
2934         sMin = 1;
2935         sMax = 0;
2936         // solve for x(s) + r(s) = xMin
2937         if ((x1 + r1) - (x0 + r0) != 0) {
2938             sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
2939             if (sa < sMin) {
2940                 sMin = sa;
2941             } else if (sa > sMax) {
2942                 sMax = sa;
2943             }
2944         }
2945         // solve for x(s) - r(s) = xMax
2946         if ((x1 - r1) - (x0 - r0) != 0) {
2947             sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
2948             if (sa < sMin) {
2949                 sMin = sa;
2950             } else if (sa > sMax) {
2951                 sMax = sa;
2952             }
2953         }
2954         // solve for y(s) + r(s) = yMin
2955         if ((y1 + r1) - (y0 + r0) != 0) {
2956             sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
2957             if (sa < sMin) {
2958                 sMin = sa;
2959             } else if (sa > sMax) {
2960                 sMax = sa;
2961             }
2962         }
2963         // solve for y(s) - r(s) = yMax
2964         if ((y1 - r1) - (y0 - r0) != 0) {
2965             sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
2966             if (sa < sMin) {
2967                 sMin = sa;
2968             } else if (sa > sMax) {
2969                 sMax = sa;
2970             }
2971         }
2972         // check against sz
2973         if (r0 < r1) {
2974             if (sMin < sz) {
2975                 sMin = sz;
2976             }
2977         } else if (r0 > r1) {
2978             if (sMax > sz) {
2979                 sMax = sz;
2980             }
2981         }
2982         // check the 'extend' flags
2983         if (!shading->getExtend0() && sMin < 0) {
2984             sMin = 0;
2985         }
2986         if (!shading->getExtend1() && sMax > 1) {
2987             sMax = 1;
2988         }
2989     }
2990 
2991     if (out->useShadedFills(shading->getType()) && out->radialShadedFill(state, shading, sMin, sMax)) {
2992         return;
2993     }
2994 
2995     // compute the number of steps into which circles must be divided to
2996     // achieve a curve flatness of 0.1 pixel in device space for the
2997     // largest circle (note that "device space" is 72 dpi when generating
2998     // PostScript, hence the relatively small 0.1 pixel accuracy)
2999     const double *ctm = state->getCTM();
3000     t = fabs(ctm[0]);
3001     if (fabs(ctm[1]) > t) {
3002         t = fabs(ctm[1]);
3003     }
3004     if (fabs(ctm[2]) > t) {
3005         t = fabs(ctm[2]);
3006     }
3007     if (fabs(ctm[3]) > t) {
3008         t = fabs(ctm[3]);
3009     }
3010     if (r0 > r1) {
3011         t *= r0;
3012     } else {
3013         t *= r1;
3014     }
3015     if (t < 1) {
3016         n = 3;
3017     } else {
3018         const double tmp = 1 - 0.1 / t;
3019         if (unlikely(tmp == 1))
3020             n = 200;
3021         else
3022             n = (int)(M_PI / acos(tmp));
3023         if (n < 3) {
3024             n = 3;
3025         } else if (n > 200) {
3026             n = 200;
3027         }
3028     }
3029 
3030     // setup for the start circle
3031     ia = 0;
3032     sa = sMin;
3033     ta = t0 + sa * (t1 - t0);
3034     xa = x0 + sa * (x1 - x0);
3035     ya = y0 + sa * (y1 - y0);
3036     ra = r0 + sa * (r1 - r0);
3037     getShadingColorRadialHelper(t0, t1, ta, shading, &colorA);
3038 
3039     needExtend = !out->radialShadedSupportExtend(state, shading);
3040 
3041     // fill the circles
3042     while (ia < radialMaxSplits) {
3043 
3044         // go as far along the t axis (toward t1) as we can, such that the
3045         // color difference is within the tolerance (radialColorDelta) --
3046         // this uses bisection (between the current value, t, and t1),
3047         // limited to radialMaxSplits points along the t axis; require at
3048         // least one split to avoid problems when the innermost and
3049         // outermost colors are the same
3050         ib = radialMaxSplits;
3051         sb = sMax;
3052         tb = t0 + sb * (t1 - t0);
3053         getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3054         while (ib - ia > 1) {
3055             if (isSameGfxColor(colorB, colorA, nComps, radialColorDelta)) {
3056                 // The shading is not necessarily lineal so having two points with the
3057                 // same color does not mean all the areas in between have the same color too
3058                 int ic = ia + 1;
3059                 for (; ic <= ib; ic++) {
3060                     const double sc = sMin + ((double)ic / (double)radialMaxSplits) * (sMax - sMin);
3061                     const double tc = t0 + sc * (t1 - t0);
3062                     getShadingColorRadialHelper(t0, t1, tc, shading, &colorC);
3063                     if (!isSameGfxColor(colorC, colorA, nComps, radialColorDelta)) {
3064                         break;
3065                     }
3066                 }
3067                 ib = (ic > ia + 1) ? ic - 1 : ia + 1;
3068                 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3069                 tb = t0 + sb * (t1 - t0);
3070                 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3071                 break;
3072             }
3073             ib = (ia + ib) / 2;
3074             sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3075             tb = t0 + sb * (t1 - t0);
3076             getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3077         }
3078 
3079         // compute center and radius of the circle
3080         xb = x0 + sb * (x1 - x0);
3081         yb = y0 + sb * (y1 - y0);
3082         rb = r0 + sb * (r1 - r0);
3083 
3084         // use the average of the colors at the two circles
3085         for (k = 0; k < nComps; ++k) {
3086             colorA.c[k] = safeAverage(colorA.c[k], colorB.c[k]);
3087         }
3088         state->setFillColor(&colorA);
3089         if (out->useFillColorStop())
3090             out->updateFillColorStop(state, (sa - sMin) / (sMax - sMin));
3091         else
3092             out->updateFillColor(state);
3093 
3094         if (needExtend) {
3095             if (enclosed) {
3096                 // construct path for first circle (counterclockwise)
3097                 state->moveTo(xa + ra, ya);
3098                 for (k = 1; k < n; ++k) {
3099                     angle = ((double)k / (double)n) * 2 * M_PI;
3100                     state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3101                 }
3102                 state->closePath();
3103 
3104                 // construct and append path for second circle (clockwise)
3105                 state->moveTo(xb + rb, yb);
3106                 for (k = 1; k < n; ++k) {
3107                     angle = -((double)k / (double)n) * 2 * M_PI;
3108                     state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3109                 }
3110                 state->closePath();
3111             } else {
3112                 // construct the first subpath (clockwise)
3113                 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), ya + ra * sin(alpha + theta + 0.5 * M_PI));
3114                 for (k = 0; k < n; ++k) {
3115                     angle = alpha + theta + 0.5 * M_PI - ((double)k / (double)n) * (2 * theta + M_PI);
3116                     state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3117                 }
3118                 for (k = 0; k < n; ++k) {
3119                     angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta - M_PI);
3120                     state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3121                 }
3122                 state->closePath();
3123 
3124                 // construct the second subpath (counterclockwise)
3125                 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), ya + ra * sin(alpha + theta + 0.5 * M_PI));
3126                 for (k = 0; k < n; ++k) {
3127                     angle = alpha + theta + 0.5 * M_PI + ((double)k / (double)n) * (-2 * theta + M_PI);
3128                     state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3129                 }
3130                 for (k = 0; k < n; ++k) {
3131                     angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta + M_PI);
3132                     state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3133                 }
3134                 state->closePath();
3135             }
3136         }
3137 
3138         if (!out->useFillColorStop()) {
3139             // fill the path
3140             out->fill(state);
3141             state->clearPath();
3142         }
3143 
3144         // step to the next value of t
3145         ia = ib;
3146         sa = sb;
3147         ta = tb;
3148         xa = xb;
3149         ya = yb;
3150         ra = rb;
3151         colorA = colorB;
3152     }
3153 
3154     if (out->useFillColorStop()) {
3155         // make sure we add stop color when sb = sMax
3156         state->setFillColor(&colorA);
3157         out->updateFillColorStop(state, (sb - sMin) / (sMax - sMin));
3158 
3159         // fill the path
3160         state->moveTo(xMin, yMin);
3161         state->lineTo(xMin, yMax);
3162         state->lineTo(xMax, yMax);
3163         state->lineTo(xMax, yMin);
3164         state->closePath();
3165 
3166         out->fill(state);
3167         state->clearPath();
3168     }
3169 
3170     if (!needExtend)
3171         return;
3172 
3173     if (enclosed) {
3174         // extend the smaller circle
3175         if ((shading->getExtend0() && r0 <= r1) || (shading->getExtend1() && r1 < r0)) {
3176             if (r0 <= r1) {
3177                 ta = t0;
3178                 ra = r0;
3179                 xa = x0;
3180                 ya = y0;
3181             } else {
3182                 ta = t1;
3183                 ra = r1;
3184                 xa = x1;
3185                 ya = y1;
3186             }
3187             shading->getColor(ta, &colorA);
3188             state->setFillColor(&colorA);
3189             out->updateFillColor(state);
3190             state->moveTo(xa + ra, ya);
3191             for (k = 1; k < n; ++k) {
3192                 angle = ((double)k / (double)n) * 2 * M_PI;
3193                 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3194             }
3195             state->closePath();
3196             out->fill(state);
3197             state->clearPath();
3198         }
3199 
3200         // extend the larger circle
3201         if ((shading->getExtend0() && r0 > r1) || (shading->getExtend1() && r1 >= r0)) {
3202             if (r0 > r1) {
3203                 ta = t0;
3204                 ra = r0;
3205                 xa = x0;
3206                 ya = y0;
3207             } else {
3208                 ta = t1;
3209                 ra = r1;
3210                 xa = x1;
3211                 ya = y1;
3212             }
3213             shading->getColor(ta, &colorA);
3214             state->setFillColor(&colorA);
3215             out->updateFillColor(state);
3216             state->moveTo(xMin, yMin);
3217             state->lineTo(xMin, yMax);
3218             state->lineTo(xMax, yMax);
3219             state->lineTo(xMax, yMin);
3220             state->closePath();
3221             state->moveTo(xa + ra, ya);
3222             for (k = 1; k < n; ++k) {
3223                 angle = ((double)k / (double)n) * 2 * M_PI;
3224                 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3225             }
3226             state->closePath();
3227             out->fill(state);
3228             state->clearPath();
3229         }
3230     }
3231 }
3232 
doGouraudTriangleShFill(GfxGouraudTriangleShading * shading)3233 void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading)
3234 {
3235     double x0, y0, x1, y1, x2, y2;
3236     int i;
3237 
3238     if (out->useShadedFills(shading->getType())) {
3239         if (out->gouraudTriangleShadedFill(state, shading))
3240             return;
3241     }
3242 
3243     // preallocate a path (speed improvements)
3244     state->moveTo(0., 0.);
3245     state->lineTo(1., 0.);
3246     state->lineTo(0., 1.);
3247     state->closePath();
3248 
3249     GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();
3250 
3251     if (shading->isParameterized()) {
3252         // work with parameterized values:
3253         double color0, color1, color2;
3254         // a relative threshold:
3255         const double refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3256         for (i = 0; i < shading->getNTriangles(); ++i) {
3257             shading->getTriangle(i, &x0, &y0, &color0, &x1, &y1, &color1, &x2, &y2, &color2);
3258             gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, 0, shading, reusablePath);
3259         }
3260 
3261     } else {
3262         // this always produces output -- even for parameterized ranges.
3263         // But it ignores the parameterized color map (the function).
3264         //
3265         // Note that using this code in for parameterized shadings might be
3266         // correct in circumstances (namely if the function is linear in the actual
3267         // triangle), but in general, it will simply be wrong.
3268         GfxColor color0, color1, color2;
3269         for (i = 0; i < shading->getNTriangles(); ++i) {
3270             shading->getTriangle(i, &x0, &y0, &color0, &x1, &y1, &color1, &x2, &y2, &color2);
3271             gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, shading->getColorSpace()->getNComps(), 0, reusablePath);
3272         }
3273     }
3274 
3275     delete reusablePath;
3276 }
3277 
checkTrue(bool b,const char * message)3278 static inline void checkTrue(bool b, const char *message)
3279 {
3280     if (unlikely(!b)) {
3281         error(errSyntaxError, -1, message);
3282     }
3283 }
3284 
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)3285 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)
3286 {
3287     double x01, y01, x12, y12, x20, y20;
3288     GfxColor color01, color12, color20;
3289     int i;
3290 
3291     for (i = 0; i < nComps; ++i) {
3292         if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
3293             break;
3294         }
3295     }
3296     if (i == nComps || depth == gouraudMaxDepth) {
3297         state->setFillColor(color0);
3298         out->updateFillColor(state);
3299 
3300         path->reset();
3301         checkTrue(!path->isEnd(), "Path should not be at end");
3302         path->setCoord(x0, y0);
3303         path->next();
3304         checkTrue(!path->isEnd(), "Path should not be at end");
3305         path->setCoord(x1, y1);
3306         path->next();
3307         checkTrue(!path->isEnd(), "Path should not be at end");
3308         path->setCoord(x2, y2);
3309         path->next();
3310         checkTrue(!path->isEnd(), "Path should not be at end");
3311         path->setCoord(x0, y0);
3312         path->next();
3313         checkTrue(path->isEnd(), "Path should be at end");
3314         out->fill(state);
3315 
3316     } else {
3317         x01 = 0.5 * (x0 + x1);
3318         y01 = 0.5 * (y0 + y1);
3319         x12 = 0.5 * (x1 + x2);
3320         y12 = 0.5 * (y1 + y2);
3321         x20 = 0.5 * (x2 + x0);
3322         y20 = 0.5 * (y2 + y0);
3323         for (i = 0; i < nComps; ++i) {
3324             color01.c[i] = safeAverage(color0->c[i], color1->c[i]);
3325             color12.c[i] = safeAverage(color1->c[i], color2->c[i]);
3326             color20.c[i] = safeAverage(color2->c[i], color0->c[i]);
3327         }
3328         gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, x20, y20, &color20, nComps, depth + 1, path);
3329         gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, x12, y12, &color12, nComps, depth + 1, path);
3330         gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, x20, y20, &color20, nComps, depth + 1, path);
3331         gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, x2, y2, color2, nComps, depth + 1, path);
3332     }
3333 }
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)3334 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,
3335                               GfxState::ReusablePathIterator *path)
3336 {
3337     const double meanColor = (color0 + color1 + color2) / 3;
3338 
3339     const bool isFineEnough = fabs(color0 - meanColor) < refineColorThreshold && fabs(color1 - meanColor) < refineColorThreshold && fabs(color2 - meanColor) < refineColorThreshold;
3340 
3341     if (isFineEnough || depth == gouraudMaxDepth) {
3342         GfxColor color;
3343 
3344         shading->getParameterizedColor(meanColor, &color);
3345         state->setFillColor(&color);
3346         out->updateFillColor(state);
3347 
3348         path->reset();
3349         checkTrue(!path->isEnd(), "Path should not be at end");
3350         path->setCoord(x0, y0);
3351         path->next();
3352         checkTrue(!path->isEnd(), "Path should not be at end");
3353         path->setCoord(x1, y1);
3354         path->next();
3355         checkTrue(!path->isEnd(), "Path should not be at end");
3356         path->setCoord(x2, y2);
3357         path->next();
3358         checkTrue(!path->isEnd(), "Path should not be at end");
3359         path->setCoord(x0, y0);
3360         path->next();
3361         checkTrue(path->isEnd(), "Path should be at end");
3362         out->fill(state);
3363 
3364     } else {
3365         const double x01 = 0.5 * (x0 + x1);
3366         const double y01 = 0.5 * (y0 + y1);
3367         const double x12 = 0.5 * (x1 + x2);
3368         const double y12 = 0.5 * (y1 + y2);
3369         const double x20 = 0.5 * (x2 + x0);
3370         const double y20 = 0.5 * (y2 + y0);
3371         const double color01 = (color0 + color1) / 2.;
3372         const double color12 = (color1 + color2) / 2.;
3373         const double color20 = (color2 + color0) / 2.;
3374         ++depth;
3375         gouraudFillTriangle(x0, y0, color0, x01, y01, color01, x20, y20, color20, refineColorThreshold, depth, shading, path);
3376         gouraudFillTriangle(x01, y01, color01, x1, y1, color1, x12, y12, color12, refineColorThreshold, depth, shading, path);
3377         gouraudFillTriangle(x01, y01, color01, x12, y12, color12, x20, y20, color20, refineColorThreshold, depth, shading, path);
3378         gouraudFillTriangle(x20, y20, color20, x12, y12, color12, x2, y2, color2, refineColorThreshold, depth, shading, path);
3379     }
3380 }
3381 
doPatchMeshShFill(GfxPatchMeshShading * shading)3382 void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading)
3383 {
3384     int start, i;
3385 
3386     if (out->useShadedFills(shading->getType())) {
3387         if (out->patchMeshShadedFill(state, shading))
3388             return;
3389     }
3390 
3391     if (shading->getNPatches() > 128) {
3392         start = 3;
3393     } else if (shading->getNPatches() > 64) {
3394         start = 2;
3395     } else if (shading->getNPatches() > 16) {
3396         start = 1;
3397     } else {
3398         start = 0;
3399     }
3400     /*
3401      * Parameterized shadings take one parameter [t_0,t_e]
3402      * and map it into the color space.
3403      *
3404      * Consequently, all color values are stored as doubles.
3405      *
3406      * These color values are interpreted as parameters for parameterized
3407      * shadings and as colorspace entities otherwise.
3408      *
3409      * The only difference is that color space entities are stored into
3410      * DOUBLE arrays, not into arrays of type GfxColorComp.
3411      */
3412     const int colorComps = shading->getColorSpace()->getNComps();
3413     double refineColorThreshold;
3414     if (shading->isParameterized()) {
3415         refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3416 
3417     } else {
3418         refineColorThreshold = patchColorDelta;
3419     }
3420 
3421     for (i = 0; i < shading->getNPatches(); ++i) {
3422         fillPatch(shading->getPatch(i), colorComps, shading->isParameterized() ? 1 : colorComps, refineColorThreshold, start, shading);
3423     }
3424 }
3425 
fillPatch(const GfxPatch * patch,int colorComps,int patchColorComps,double refineColorThreshold,int depth,const GfxPatchMeshShading * shading)3426 void Gfx::fillPatch(const GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, const GfxPatchMeshShading *shading)
3427 {
3428     GfxPatch patch00, patch01, patch10, patch11;
3429     double xx[4][8], yy[4][8];
3430     double xxm, yym;
3431     int i;
3432 
3433     for (i = 0; i < patchColorComps; ++i) {
3434         // these comparisons are done in double arithmetics.
3435         //
3436         // For non-parameterized shadings, they are done in color space
3437         // components.
3438         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
3439             || 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) {
3440             break;
3441         }
3442     }
3443     if (i == patchColorComps || depth == patchMaxDepth) {
3444         GfxColor flatColor;
3445         if (shading->isParameterized()) {
3446             shading->getParameterizedColor(patch->color[0][0].c[0], &flatColor);
3447         } else {
3448             for (i = 0; i < colorComps; ++i) {
3449                 // simply cast to the desired type; that's all what is needed.
3450                 flatColor.c[i] = GfxColorComp(patch->color[0][0].c[i]);
3451             }
3452         }
3453         state->setFillColor(&flatColor);
3454         out->updateFillColor(state);
3455         state->moveTo(patch->x[0][0], patch->y[0][0]);
3456         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]);
3457         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]);
3458         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]);
3459         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]);
3460         state->closePath();
3461         out->fill(state);
3462         state->clearPath();
3463     } else {
3464         for (i = 0; i < 4; ++i) {
3465             xx[i][0] = patch->x[i][0];
3466             yy[i][0] = patch->y[i][0];
3467             xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
3468             yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
3469             xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
3470             yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
3471             xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
3472             yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
3473             xx[i][2] = 0.5 * (xx[i][1] + xxm);
3474             yy[i][2] = 0.5 * (yy[i][1] + yym);
3475             xx[i][5] = 0.5 * (xxm + xx[i][6]);
3476             yy[i][5] = 0.5 * (yym + yy[i][6]);
3477             xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
3478             yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
3479             xx[i][7] = patch->x[i][3];
3480             yy[i][7] = patch->y[i][3];
3481         }
3482         for (i = 0; i < 4; ++i) {
3483             patch00.x[0][i] = xx[0][i];
3484             patch00.y[0][i] = yy[0][i];
3485             patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
3486             patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
3487             xxm = 0.5 * (xx[1][i] + xx[2][i]);
3488             yym = 0.5 * (yy[1][i] + yy[2][i]);
3489             patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
3490             patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
3491             patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
3492             patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
3493             patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
3494             patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
3495             patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
3496             patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
3497             patch10.x[0][i] = patch00.x[3][i];
3498             patch10.y[0][i] = patch00.y[3][i];
3499             patch10.x[3][i] = xx[3][i];
3500             patch10.y[3][i] = yy[3][i];
3501         }
3502         for (i = 4; i < 8; ++i) {
3503             patch01.x[0][i - 4] = xx[0][i];
3504             patch01.y[0][i - 4] = yy[0][i];
3505             patch01.x[1][i - 4] = 0.5 * (xx[0][i] + xx[1][i]);
3506             patch01.y[1][i - 4] = 0.5 * (yy[0][i] + yy[1][i]);
3507             xxm = 0.5 * (xx[1][i] + xx[2][i]);
3508             yym = 0.5 * (yy[1][i] + yy[2][i]);
3509             patch11.x[2][i - 4] = 0.5 * (xx[2][i] + xx[3][i]);
3510             patch11.y[2][i - 4] = 0.5 * (yy[2][i] + yy[3][i]);
3511             patch01.x[2][i - 4] = 0.5 * (patch01.x[1][i - 4] + xxm);
3512             patch01.y[2][i - 4] = 0.5 * (patch01.y[1][i - 4] + yym);
3513             patch11.x[1][i - 4] = 0.5 * (xxm + patch11.x[2][i - 4]);
3514             patch11.y[1][i - 4] = 0.5 * (yym + patch11.y[2][i - 4]);
3515             patch01.x[3][i - 4] = 0.5 * (patch01.x[2][i - 4] + patch11.x[1][i - 4]);
3516             patch01.y[3][i - 4] = 0.5 * (patch01.y[2][i - 4] + patch11.y[1][i - 4]);
3517             patch11.x[0][i - 4] = patch01.x[3][i - 4];
3518             patch11.y[0][i - 4] = patch01.y[3][i - 4];
3519             patch11.x[3][i - 4] = xx[3][i];
3520             patch11.y[3][i - 4] = yy[3][i];
3521         }
3522         for (i = 0; i < patchColorComps; ++i) {
3523             patch00.color[0][0].c[i] = patch->color[0][0].c[i];
3524             patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + patch->color[0][1].c[i]) / 2.;
3525             patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
3526             patch01.color[0][1].c[i] = patch->color[0][1].c[i];
3527             patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + patch->color[1][1].c[i]) / 2.;
3528             patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
3529             patch11.color[1][1].c[i] = patch->color[1][1].c[i];
3530             patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + patch->color[1][0].c[i]) / 2.;
3531             patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
3532             patch10.color[1][0].c[i] = patch->color[1][0].c[i];
3533             patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + patch->color[0][0].c[i]) / 2.;
3534             patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
3535             patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + patch01.color[1][1].c[i]) / 2.;
3536             patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
3537             patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
3538             patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
3539         }
3540         fillPatch(&patch00, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3541         fillPatch(&patch10, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3542         fillPatch(&patch01, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3543         fillPatch(&patch11, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3544     }
3545 }
3546 
doEndPath()3547 void Gfx::doEndPath()
3548 {
3549     if (state->isCurPt() && clip != clipNone) {
3550         state->clip();
3551         if (clip == clipNormal) {
3552             out->clip(state);
3553         } else {
3554             out->eoClip(state);
3555         }
3556     }
3557     clip = clipNone;
3558     state->clearPath();
3559 }
3560 
3561 //------------------------------------------------------------------------
3562 // path clipping operators
3563 //------------------------------------------------------------------------
3564 
opClip(Object args[],int numArgs)3565 void Gfx::opClip(Object args[], int numArgs)
3566 {
3567     clip = clipNormal;
3568 }
3569 
opEOClip(Object args[],int numArgs)3570 void Gfx::opEOClip(Object args[], int numArgs)
3571 {
3572     clip = clipEO;
3573 }
3574 
3575 //------------------------------------------------------------------------
3576 // text object operators
3577 //------------------------------------------------------------------------
3578 
opBeginText(Object args[],int numArgs)3579 void Gfx::opBeginText(Object args[], int numArgs)
3580 {
3581     out->beginTextObject(state);
3582     state->setTextMat(1, 0, 0, 1, 0, 0);
3583     state->textMoveTo(0, 0);
3584     out->updateTextMat(state);
3585     out->updateTextPos(state);
3586     fontChanged = true;
3587 }
3588 
opEndText(Object args[],int numArgs)3589 void Gfx::opEndText(Object args[], int numArgs)
3590 {
3591     out->endTextObject(state);
3592 }
3593 
3594 //------------------------------------------------------------------------
3595 // text state operators
3596 //------------------------------------------------------------------------
3597 
opSetCharSpacing(Object args[],int numArgs)3598 void Gfx::opSetCharSpacing(Object args[], int numArgs)
3599 {
3600     state->setCharSpace(args[0].getNum());
3601     out->updateCharSpace(state);
3602 }
3603 
opSetFont(Object args[],int numArgs)3604 void Gfx::opSetFont(Object args[], int numArgs)
3605 {
3606     GfxFont *font;
3607 
3608     if (!(font = res->lookupFont(args[0].getName()))) {
3609         // unsetting the font (drawing no text) is better than using the
3610         // previous one and drawing random glyphs from it
3611         state->setFont(nullptr, args[1].getNum());
3612         fontChanged = true;
3613         return;
3614     }
3615     if (printCommands) {
3616         printf("  font: tag=%s name='%s' %g\n", font->getTag().c_str(), font->getName() ? font->getName()->c_str() : "???", args[1].getNum());
3617         fflush(stdout);
3618     }
3619 
3620     font->incRefCnt();
3621     state->setFont(font, args[1].getNum());
3622     fontChanged = true;
3623 }
3624 
opSetTextLeading(Object args[],int numArgs)3625 void Gfx::opSetTextLeading(Object args[], int numArgs)
3626 {
3627     state->setLeading(args[0].getNum());
3628 }
3629 
opSetTextRender(Object args[],int numArgs)3630 void Gfx::opSetTextRender(Object args[], int numArgs)
3631 {
3632     state->setRender(args[0].getInt());
3633     out->updateRender(state);
3634 }
3635 
opSetTextRise(Object args[],int numArgs)3636 void Gfx::opSetTextRise(Object args[], int numArgs)
3637 {
3638     state->setRise(args[0].getNum());
3639     out->updateRise(state);
3640 }
3641 
opSetWordSpacing(Object args[],int numArgs)3642 void Gfx::opSetWordSpacing(Object args[], int numArgs)
3643 {
3644     state->setWordSpace(args[0].getNum());
3645     out->updateWordSpace(state);
3646 }
3647 
opSetHorizScaling(Object args[],int numArgs)3648 void Gfx::opSetHorizScaling(Object args[], int numArgs)
3649 {
3650     state->setHorizScaling(args[0].getNum());
3651     out->updateHorizScaling(state);
3652     fontChanged = true;
3653 }
3654 
3655 //------------------------------------------------------------------------
3656 // text positioning operators
3657 //------------------------------------------------------------------------
3658 
opTextMove(Object args[],int numArgs)3659 void Gfx::opTextMove(Object args[], int numArgs)
3660 {
3661     double tx, ty;
3662 
3663     tx = state->getLineX() + args[0].getNum();
3664     ty = state->getLineY() + args[1].getNum();
3665     state->textMoveTo(tx, ty);
3666     out->updateTextPos(state);
3667 }
3668 
opTextMoveSet(Object args[],int numArgs)3669 void Gfx::opTextMoveSet(Object args[], int numArgs)
3670 {
3671     double tx, ty;
3672 
3673     tx = state->getLineX() + args[0].getNum();
3674     ty = args[1].getNum();
3675     state->setLeading(-ty);
3676     ty += state->getLineY();
3677     state->textMoveTo(tx, ty);
3678     out->updateTextPos(state);
3679 }
3680 
opSetTextMatrix(Object args[],int numArgs)3681 void Gfx::opSetTextMatrix(Object args[], int numArgs)
3682 {
3683     state->setTextMat(args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
3684     state->textMoveTo(0, 0);
3685     out->updateTextMat(state);
3686     out->updateTextPos(state);
3687     fontChanged = true;
3688 }
3689 
opTextNextLine(Object args[],int numArgs)3690 void Gfx::opTextNextLine(Object args[], int numArgs)
3691 {
3692     double tx, ty;
3693 
3694     tx = state->getLineX();
3695     ty = state->getLineY() - state->getLeading();
3696     state->textMoveTo(tx, ty);
3697     out->updateTextPos(state);
3698 }
3699 
3700 //------------------------------------------------------------------------
3701 // text string operators
3702 //------------------------------------------------------------------------
3703 
opShowText(Object args[],int numArgs)3704 void Gfx::opShowText(Object args[], int numArgs)
3705 {
3706     if (!state->getFont()) {
3707         error(errSyntaxError, getPos(), "No font in show");
3708         return;
3709     }
3710     if (fontChanged) {
3711         out->updateFont(state);
3712         fontChanged = false;
3713     }
3714     out->beginStringOp(state);
3715     doShowText(args[0].getString());
3716     out->endStringOp(state);
3717     if (!ocState) {
3718         doIncCharCount(args[0].getString());
3719     }
3720 }
3721 
opMoveShowText(Object args[],int numArgs)3722 void Gfx::opMoveShowText(Object args[], int numArgs)
3723 {
3724     double tx, ty;
3725 
3726     if (!state->getFont()) {
3727         error(errSyntaxError, getPos(), "No font in move/show");
3728         return;
3729     }
3730     if (fontChanged) {
3731         out->updateFont(state);
3732         fontChanged = false;
3733     }
3734     tx = state->getLineX();
3735     ty = state->getLineY() - state->getLeading();
3736     state->textMoveTo(tx, ty);
3737     out->updateTextPos(state);
3738     out->beginStringOp(state);
3739     doShowText(args[0].getString());
3740     out->endStringOp(state);
3741     if (!ocState) {
3742         doIncCharCount(args[0].getString());
3743     }
3744 }
3745 
opMoveSetShowText(Object args[],int numArgs)3746 void Gfx::opMoveSetShowText(Object args[], int numArgs)
3747 {
3748     double tx, ty;
3749 
3750     if (!state->getFont()) {
3751         error(errSyntaxError, getPos(), "No font in move/set/show");
3752         return;
3753     }
3754     if (fontChanged) {
3755         out->updateFont(state);
3756         fontChanged = false;
3757     }
3758     state->setWordSpace(args[0].getNum());
3759     state->setCharSpace(args[1].getNum());
3760     tx = state->getLineX();
3761     ty = state->getLineY() - state->getLeading();
3762     state->textMoveTo(tx, ty);
3763     out->updateWordSpace(state);
3764     out->updateCharSpace(state);
3765     out->updateTextPos(state);
3766     out->beginStringOp(state);
3767     doShowText(args[2].getString());
3768     out->endStringOp(state);
3769     if (ocState) {
3770         doIncCharCount(args[2].getString());
3771     }
3772 }
3773 
opShowSpaceText(Object args[],int numArgs)3774 void Gfx::opShowSpaceText(Object args[], int numArgs)
3775 {
3776     Array *a;
3777     int wMode;
3778     int i;
3779 
3780     if (!state->getFont()) {
3781         error(errSyntaxError, getPos(), "No font in show/space");
3782         return;
3783     }
3784     if (fontChanged) {
3785         out->updateFont(state);
3786         fontChanged = false;
3787     }
3788     out->beginStringOp(state);
3789     wMode = state->getFont()->getWMode();
3790     a = args[0].getArray();
3791     for (i = 0; i < a->getLength(); ++i) {
3792         Object obj = a->get(i);
3793         if (obj.isNum()) {
3794             // this uses the absolute value of the font size to match
3795             // Acrobat's behavior
3796             if (wMode) {
3797                 state->textShift(0, -obj.getNum() * 0.001 * state->getFontSize());
3798             } else {
3799                 state->textShift(-obj.getNum() * 0.001 * state->getFontSize() * state->getHorizScaling(), 0);
3800             }
3801             out->updateTextShift(state, obj.getNum());
3802         } else if (obj.isString()) {
3803             doShowText(obj.getString());
3804         } else {
3805             error(errSyntaxError, getPos(), "Element of show/space array must be number or string");
3806         }
3807     }
3808     out->endStringOp(state);
3809     if (!ocState) {
3810         a = args[0].getArray();
3811         for (i = 0; i < a->getLength(); ++i) {
3812             Object obj = a->get(i);
3813             if (obj.isString()) {
3814                 doIncCharCount(obj.getString());
3815             }
3816         }
3817     }
3818 }
3819 
doShowText(const GooString * s)3820 void Gfx::doShowText(const GooString *s)
3821 {
3822     GfxFont *font;
3823     int wMode;
3824     double riseX, riseY;
3825     CharCode code;
3826     const Unicode *u = nullptr;
3827     double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
3828     double originX, originY, tOriginX, tOriginY;
3829     double x0, y0, x1, y1;
3830     double tmp[4], newCTM[6];
3831     const double *oldCTM, *mat;
3832     Dict *resDict;
3833     Parser *oldParser;
3834     GfxState *savedState;
3835     const char *p;
3836     int render;
3837     bool patternFill;
3838     int len, n, uLen, nChars, nSpaces;
3839 
3840     font = state->getFont();
3841     wMode = font->getWMode();
3842 
3843     if (out->useDrawChar()) {
3844         out->beginString(state, s);
3845     }
3846 
3847     // if we're doing a pattern fill, set up clipping
3848     render = state->getRender();
3849     if (!(render & 1) && state->getFillColorSpace()->getMode() == csPattern) {
3850         patternFill = true;
3851         saveState();
3852         // disable fill, enable clipping, leave stroke unchanged
3853         if ((render ^ (render >> 1)) & 1) {
3854             render = 5;
3855         } else {
3856             render = 7;
3857         }
3858         state->setRender(render);
3859         out->updateRender(state);
3860     } else {
3861         patternFill = false;
3862     }
3863 
3864     state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
3865     x0 = state->getCurX() + riseX;
3866     y0 = state->getCurY() + riseY;
3867 
3868     // handle a Type 3 char
3869     if (font->getType() == fontType3 && out->interpretType3Chars()) {
3870         oldCTM = state->getCTM();
3871         mat = state->getTextMat();
3872         tmp[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
3873         tmp[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
3874         tmp[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
3875         tmp[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
3876         mat = font->getFontMatrix();
3877         newCTM[0] = mat[0] * tmp[0] + mat[1] * tmp[2];
3878         newCTM[1] = mat[0] * tmp[1] + mat[1] * tmp[3];
3879         newCTM[2] = mat[2] * tmp[0] + mat[3] * tmp[2];
3880         newCTM[3] = mat[2] * tmp[1] + mat[3] * tmp[3];
3881         newCTM[0] *= state->getFontSize();
3882         newCTM[1] *= state->getFontSize();
3883         newCTM[2] *= state->getFontSize();
3884         newCTM[3] *= state->getFontSize();
3885         newCTM[0] *= state->getHorizScaling();
3886         newCTM[1] *= state->getHorizScaling();
3887         curX = state->getCurX();
3888         curY = state->getCurY();
3889         oldParser = parser;
3890         p = s->c_str();
3891         len = s->getLength();
3892         while (len > 0) {
3893             n = font->getNextChar(p, len, &code, &u, &uLen, &dx, &dy, &originX, &originY);
3894             dx = dx * state->getFontSize() + state->getCharSpace();
3895             if (n == 1 && *p == ' ') {
3896                 dx += state->getWordSpace();
3897             }
3898             dx *= state->getHorizScaling();
3899             dy *= state->getFontSize();
3900             state->textTransformDelta(dx, dy, &tdx, &tdy);
3901             state->transform(curX + riseX, curY + riseY, &x, &y);
3902             savedState = saveStateStack();
3903             state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
3904             //~ the CTM concat values here are wrong (but never used)
3905             out->updateCTM(state, 1, 0, 0, 1, 0, 0);
3906             state->transformDelta(dx, dy, &ddx, &ddy);
3907             if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) {
3908                 Object charProc = ((Gfx8BitFont *)font)->getCharProcNF(code);
3909                 int refNum = -1;
3910                 if (charProc.isRef()) {
3911                     refNum = charProc.getRef().num;
3912                     charProc = charProc.fetch(((Gfx8BitFont *)font)->getCharProcs()->getXRef());
3913                 }
3914                 if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
3915                     pushResources(resDict);
3916                 }
3917                 if (charProc.isStream()) {
3918                     Object charProcResourcesObj = charProc.streamGetDict()->lookup("Resources");
3919                     if (charProcResourcesObj.isDict()) {
3920                         pushResources(charProcResourcesObj.getDict());
3921                     }
3922                     std::set<int>::iterator charProcDrawingIt;
3923                     bool displayCharProc = true;
3924                     if (refNum != -1) {
3925                         if (charProcDrawing.find(refNum) == charProcDrawing.end()) {
3926                             charProcDrawingIt = charProcDrawing.insert(refNum).first;
3927                         } else {
3928                             displayCharProc = false;
3929                             error(errSyntaxError, -1, "CharProc wants to draw a CharProc that is already being drawn");
3930                         }
3931                     }
3932                     if (displayCharProc) {
3933                         ++displayDepth;
3934                         display(&charProc, false);
3935                         --displayDepth;
3936 
3937                         if (refNum != -1) {
3938                             charProcDrawing.erase(charProcDrawingIt);
3939                         }
3940                     }
3941                     if (charProcResourcesObj.isDict()) {
3942                         popResources();
3943                     }
3944                 } else {
3945                     error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry");
3946                 }
3947                 out->endType3Char(state);
3948                 if (resDict) {
3949                     popResources();
3950                 }
3951             }
3952             restoreStateStack(savedState);
3953             // GfxState::restore() does *not* restore the current position,
3954             // so we deal with it here using (curX, curY) and (lineX, lineY)
3955             curX += tdx;
3956             curY += tdy;
3957             state->moveTo(curX, curY);
3958             // Call updateCTM with the identity transformation.  That way, the CTM is unchanged,
3959             // but any side effect that the method may have is triggered.  This is the case,
3960             // in particular, for the Splash backend.
3961             out->updateCTM(state, 1, 0, 0, 1, 0, 0);
3962             p += n;
3963             len -= n;
3964         }
3965         parser = oldParser;
3966 
3967     } else if (out->useDrawChar()) {
3968         p = s->c_str();
3969         len = s->getLength();
3970         while (len > 0) {
3971             n = font->getNextChar(p, len, &code, &u, &uLen, &dx, &dy, &originX, &originY);
3972             if (wMode) {
3973                 dx *= state->getFontSize();
3974                 dy = dy * state->getFontSize() + state->getCharSpace();
3975                 if (n == 1 && *p == ' ') {
3976                     dy += state->getWordSpace();
3977                 }
3978             } else {
3979                 dx = dx * state->getFontSize() + state->getCharSpace();
3980                 if (n == 1 && *p == ' ') {
3981                     dx += state->getWordSpace();
3982                 }
3983                 dx *= state->getHorizScaling();
3984                 dy *= state->getFontSize();
3985             }
3986             state->textTransformDelta(dx, dy, &tdx, &tdy);
3987             originX *= state->getFontSize();
3988             originY *= state->getFontSize();
3989             state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
3990             if (ocState)
3991                 out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY, tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
3992             state->shift(tdx, tdy);
3993             p += n;
3994             len -= n;
3995         }
3996     } else {
3997         dx = dy = 0;
3998         p = s->c_str();
3999         len = s->getLength();
4000         nChars = nSpaces = 0;
4001         while (len > 0) {
4002             n = font->getNextChar(p, len, &code, &u, &uLen, &dx2, &dy2, &originX, &originY);
4003             dx += dx2;
4004             dy += dy2;
4005             if (n == 1 && *p == ' ') {
4006                 ++nSpaces;
4007             }
4008             ++nChars;
4009             p += n;
4010             len -= n;
4011         }
4012         if (wMode) {
4013             dx *= state->getFontSize();
4014             dy = dy * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
4015         } else {
4016             dx = dx * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
4017             dx *= state->getHorizScaling();
4018             dy *= state->getFontSize();
4019         }
4020         state->textTransformDelta(dx, dy, &tdx, &tdy);
4021         if (ocState)
4022             out->drawString(state, s);
4023         state->shift(tdx, tdy);
4024     }
4025 
4026     if (out->useDrawChar()) {
4027         out->endString(state);
4028     }
4029 
4030     if (patternFill && ocState) {
4031         out->saveTextPos(state);
4032         // tell the OutputDev to do the clipping
4033         out->endTextObject(state);
4034         // set up a clipping bbox so doPatternText will work -- assume
4035         // that the text bounding box does not extend past the baseline in
4036         // any direction by more than twice the font size
4037         x1 = state->getCurX() + riseX;
4038         y1 = state->getCurY() + riseY;
4039         if (x0 > x1) {
4040             x = x0;
4041             x0 = x1;
4042             x1 = x;
4043         }
4044         if (y0 > y1) {
4045             y = y0;
4046             y0 = y1;
4047             y1 = y;
4048         }
4049         state->textTransformDelta(0, state->getFontSize(), &dx, &dy);
4050         state->textTransformDelta(state->getFontSize(), 0, &dx2, &dy2);
4051         dx = fabs(dx);
4052         dx2 = fabs(dx2);
4053         if (dx2 > dx) {
4054             dx = dx2;
4055         }
4056         dy = fabs(dy);
4057         dy2 = fabs(dy2);
4058         if (dy2 > dy) {
4059             dy = dy2;
4060         }
4061         state->clipToRect(x0 - 2 * dx, y0 - 2 * dy, x1 + 2 * dx, y1 + 2 * dy);
4062         // set render mode to fill-only
4063         state->setRender(0);
4064         out->updateRender(state);
4065         doPatternText();
4066         restoreState();
4067         out->restoreTextPos(state);
4068     }
4069 
4070     updateLevel += 10 * s->getLength();
4071 }
4072 
4073 // NB: this is only called when ocState is false.
doIncCharCount(const GooString * s)4074 void Gfx::doIncCharCount(const GooString *s)
4075 {
4076     if (out->needCharCount()) {
4077         out->incCharCount(s->getLength());
4078     }
4079 }
4080 
4081 //------------------------------------------------------------------------
4082 // XObject operators
4083 //------------------------------------------------------------------------
4084 
opXObject(Object args[],int numArgs)4085 void Gfx::opXObject(Object args[], int numArgs)
4086 {
4087     const char *name;
4088 
4089     if (!ocState && !out->needCharCount()) {
4090         return;
4091     }
4092     name = args[0].getName();
4093     Object obj1 = res->lookupXObject(name);
4094     if (obj1.isNull()) {
4095         return;
4096     }
4097     if (!obj1.isStream()) {
4098         error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
4099         return;
4100     }
4101 
4102 #ifdef OPI_SUPPORT
4103     Object opiDict = obj1.streamGetDict()->lookup("OPI");
4104     if (opiDict.isDict()) {
4105         out->opiBegin(state, opiDict.getDict());
4106     }
4107 #endif
4108     Object obj2 = obj1.streamGetDict()->lookup("Subtype");
4109     if (obj2.isName("Image")) {
4110         if (out->needNonText()) {
4111             Object refObj = res->lookupXObjectNF(name);
4112             doImage(&refObj, obj1.getStream(), false);
4113         }
4114     } else if (obj2.isName("Form")) {
4115         Object refObj = res->lookupXObjectNF(name);
4116         bool shouldDoForm = true;
4117         std::set<int>::iterator drawingFormIt;
4118         if (refObj.isRef()) {
4119             const int num = refObj.getRef().num;
4120             if (formsDrawing.find(num) == formsDrawing.end()) {
4121                 drawingFormIt = formsDrawing.insert(num).first;
4122             } else {
4123                 shouldDoForm = false;
4124             }
4125         }
4126         if (shouldDoForm) {
4127             if (out->useDrawForm() && refObj.isRef()) {
4128                 out->drawForm(refObj.getRef());
4129             } else {
4130                 Ref ref = refObj.isRef() ? refObj.getRef() : Ref::INVALID();
4131                 out->beginForm(ref);
4132                 doForm(&obj1);
4133                 out->endForm(ref);
4134             }
4135         }
4136         if (refObj.isRef() && shouldDoForm) {
4137             formsDrawing.erase(drawingFormIt);
4138         }
4139     } else if (obj2.isName("PS")) {
4140         Object obj3 = obj1.streamGetDict()->lookup("Level1");
4141         out->psXObject(obj1.getStream(), obj3.isStream() ? obj3.getStream() : nullptr);
4142     } else if (obj2.isName()) {
4143         error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
4144     } else {
4145         error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
4146     }
4147 #ifdef OPI_SUPPORT
4148     if (opiDict.isDict()) {
4149         out->opiEnd(state, opiDict.getDict());
4150     }
4151 #endif
4152 }
4153 
doImage(Object * ref,Stream * str,bool inlineImg)4154 void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
4155 {
4156     Dict *dict, *maskDict;
4157     int width, height;
4158     int bits, maskBits;
4159     bool interpolate;
4160     StreamColorSpaceMode csMode;
4161     bool mask;
4162     bool invert;
4163     GfxColorSpace *colorSpace, *maskColorSpace;
4164     bool haveColorKeyMask, haveExplicitMask, haveSoftMask;
4165     int maskColors[2 * gfxColorMaxComps] = {};
4166     int maskWidth, maskHeight;
4167     bool maskInvert;
4168     bool maskInterpolate;
4169     Stream *maskStr;
4170     int i, n;
4171 
4172     // get info from the stream
4173     bits = 0;
4174     csMode = streamCSNone;
4175     str->getImageParams(&bits, &csMode);
4176 
4177     // get stream dict
4178     dict = str->getDict();
4179 
4180     // check for optional content key
4181     if (ref) {
4182         const Object &objOC = dict->lookupNF("OC");
4183         if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&objOC)) {
4184             return;
4185         }
4186     }
4187 
4188     // get size
4189     Object obj1 = dict->lookup("Width");
4190     if (obj1.isNull()) {
4191         obj1 = dict->lookup("W");
4192     }
4193     if (obj1.isInt())
4194         width = obj1.getInt();
4195     else if (obj1.isReal())
4196         width = (int)obj1.getReal();
4197     else
4198         goto err1;
4199     obj1 = dict->lookup("Height");
4200     if (obj1.isNull()) {
4201         obj1 = dict->lookup("H");
4202     }
4203     if (obj1.isInt())
4204         height = obj1.getInt();
4205     else if (obj1.isReal())
4206         height = (int)obj1.getReal();
4207     else
4208         goto err1;
4209 
4210     if (width < 1 || height < 1)
4211         goto err1;
4212 
4213     // image interpolation
4214     obj1 = dict->lookup("Interpolate");
4215     if (obj1.isNull()) {
4216         obj1 = dict->lookup("I");
4217     }
4218     if (obj1.isBool())
4219         interpolate = obj1.getBool();
4220     else
4221         interpolate = false;
4222     maskInterpolate = false;
4223 
4224     // image or mask?
4225     obj1 = dict->lookup("ImageMask");
4226     if (obj1.isNull()) {
4227         obj1 = dict->lookup("IM");
4228     }
4229     mask = false;
4230     if (obj1.isBool())
4231         mask = obj1.getBool();
4232     else if (!obj1.isNull())
4233         goto err1;
4234 
4235     // bit depth
4236     if (bits == 0) {
4237         obj1 = dict->lookup("BitsPerComponent");
4238         if (obj1.isNull()) {
4239             obj1 = dict->lookup("BPC");
4240         }
4241         if (obj1.isInt()) {
4242             bits = obj1.getInt();
4243         } else if (mask) {
4244             bits = 1;
4245         } else {
4246             goto err1;
4247         }
4248     }
4249 
4250     // display a mask
4251     if (mask) {
4252 
4253         // check for inverted mask
4254         if (bits != 1)
4255             goto err1;
4256         invert = false;
4257         obj1 = dict->lookup("Decode");
4258         if (obj1.isNull()) {
4259             obj1 = dict->lookup("D");
4260         }
4261         if (obj1.isArray()) {
4262             Object obj2;
4263             obj2 = obj1.arrayGet(0);
4264             // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4265             // accepts [1.0 0.0] as well.
4266             if (obj2.isNum() && obj2.getNum() >= 0.9)
4267                 invert = true;
4268         } else if (!obj1.isNull()) {
4269             goto err1;
4270         }
4271 
4272         // if drawing is disabled, skip over inline image data
4273         if (!ocState || !out->needNonText()) {
4274             str->reset();
4275             n = height * ((width + 7) / 8);
4276             for (i = 0; i < n; ++i) {
4277                 str->getChar();
4278             }
4279             str->close();
4280 
4281             // draw it
4282         } else {
4283             if (state->getFillColorSpace()->getMode() == csPattern) {
4284                 doPatternImageMask(ref, str, width, height, invert, inlineImg);
4285             } else {
4286                 out->drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
4287             }
4288         }
4289     } else {
4290         if (bits == 0) {
4291             goto err1;
4292         }
4293 
4294         // get color space and color map
4295         obj1 = dict->lookup("ColorSpace");
4296         if (obj1.isNull()) {
4297             obj1 = dict->lookup("CS");
4298         }
4299         if (obj1.isName() && inlineImg) {
4300             Object obj2 = res->lookupColorSpace(obj1.getName());
4301             if (!obj2.isNull()) {
4302                 obj1 = std::move(obj2);
4303             }
4304         }
4305         if (!obj1.isNull()) {
4306             char *tempIntent = nullptr;
4307             Object objIntent = dict->lookup("Intent");
4308             if (objIntent.isName()) {
4309                 const char *stateIntent = state->getRenderingIntent();
4310                 if (stateIntent != nullptr) {
4311                     tempIntent = strdup(stateIntent);
4312                 }
4313                 state->setRenderingIntent(objIntent.getName());
4314             }
4315             colorSpace = GfxColorSpace::parse(res, &obj1, out, state);
4316             if (objIntent.isName()) {
4317                 state->setRenderingIntent(tempIntent);
4318                 free(tempIntent);
4319             }
4320         } else if (csMode == streamCSDeviceGray) {
4321             Object objCS = res->lookupColorSpace("DefaultGray");
4322             if (objCS.isNull()) {
4323                 colorSpace = new GfxDeviceGrayColorSpace();
4324             } else {
4325                 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4326             }
4327         } else if (csMode == streamCSDeviceRGB) {
4328             Object objCS = res->lookupColorSpace("DefaultRGB");
4329             if (objCS.isNull()) {
4330                 colorSpace = new GfxDeviceRGBColorSpace();
4331             } else {
4332                 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4333             }
4334         } else if (csMode == streamCSDeviceCMYK) {
4335             Object objCS = res->lookupColorSpace("DefaultCMYK");
4336             if (objCS.isNull()) {
4337                 colorSpace = new GfxDeviceCMYKColorSpace();
4338             } else {
4339                 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4340             }
4341         } else {
4342             colorSpace = nullptr;
4343         }
4344         if (!colorSpace) {
4345             goto err1;
4346         }
4347         obj1 = dict->lookup("Decode");
4348         if (obj1.isNull()) {
4349             obj1 = dict->lookup("D");
4350         }
4351         GfxImageColorMap colorMap(bits, &obj1, colorSpace);
4352         if (!colorMap.isOk()) {
4353             goto err1;
4354         }
4355 
4356         // get the mask
4357         bool haveMaskImage = false;
4358         haveColorKeyMask = haveExplicitMask = haveSoftMask = false;
4359         maskStr = nullptr; // make gcc happy
4360         maskWidth = maskHeight = 0; // make gcc happy
4361         maskInvert = false; // make gcc happy
4362         std::unique_ptr<GfxImageColorMap> maskColorMap;
4363         Object maskObj = dict->lookup("Mask");
4364         Object smaskObj = dict->lookup("SMask");
4365 
4366         if (maskObj.isStream()) {
4367             maskStr = maskObj.getStream();
4368             maskDict = maskObj.streamGetDict();
4369             // if Type is XObject and Subtype is Image
4370             // then the way the softmask is drawn will draw
4371             // correctly, if it falls through to the explicit
4372             // mask code then you get an error and no image
4373             // drawn because it expects maskDict to have an entry
4374             // of Mask or IM that is boolean...
4375             Object tobj = maskDict->lookup("Type");
4376             if (!tobj.isNull() && tobj.isName() && tobj.isName("XObject")) {
4377                 Object sobj = maskDict->lookup("Subtype");
4378                 if (!sobj.isNull() && sobj.isName() && sobj.isName("Image")) {
4379                     // ensure that this mask does not include an ImageMask entry
4380                     // which signifies the explicit mask
4381                     obj1 = maskDict->lookup("ImageMask");
4382                     if (obj1.isNull()) {
4383                         obj1 = maskDict->lookup("IM");
4384                     }
4385                     if (obj1.isNull() || !obj1.isBool())
4386                         haveMaskImage = true;
4387                 }
4388             }
4389         }
4390 
4391         if (smaskObj.isStream() || haveMaskImage) {
4392             // soft mask
4393             if (inlineImg) {
4394                 goto err1;
4395             }
4396             if (!haveMaskImage) {
4397                 maskStr = smaskObj.getStream();
4398                 maskDict = smaskObj.streamGetDict();
4399             }
4400             obj1 = maskDict->lookup("Width");
4401             if (obj1.isNull()) {
4402                 obj1 = maskDict->lookup("W");
4403             }
4404             if (!obj1.isInt()) {
4405                 goto err1;
4406             }
4407             maskWidth = obj1.getInt();
4408             obj1 = maskDict->lookup("Height");
4409             if (obj1.isNull()) {
4410                 obj1 = maskDict->lookup("H");
4411             }
4412             if (!obj1.isInt()) {
4413                 goto err1;
4414             }
4415             maskHeight = obj1.getInt();
4416             obj1 = maskDict->lookup("Interpolate");
4417             if (obj1.isNull()) {
4418                 obj1 = maskDict->lookup("I");
4419             }
4420             if (obj1.isBool())
4421                 maskInterpolate = obj1.getBool();
4422             else
4423                 maskInterpolate = false;
4424             obj1 = maskDict->lookup("BitsPerComponent");
4425             if (obj1.isNull()) {
4426                 obj1 = maskDict->lookup("BPC");
4427             }
4428             if (!obj1.isInt()) {
4429                 goto err1;
4430             }
4431             maskBits = obj1.getInt();
4432             obj1 = maskDict->lookup("ColorSpace");
4433             if (obj1.isNull()) {
4434                 obj1 = maskDict->lookup("CS");
4435             }
4436             if (obj1.isName()) {
4437                 Object obj2 = res->lookupColorSpace(obj1.getName());
4438                 if (!obj2.isNull()) {
4439                     obj1 = std::move(obj2);
4440                 }
4441             }
4442             // Here, we parse manually instead of using GfxColorSpace::parse,
4443             // since we explicitly need DeviceGray and not some DefaultGray color space
4444             if (!obj1.isName("DeviceGray") && !obj1.isName("G")) {
4445                 goto err1;
4446             }
4447             maskColorSpace = new GfxDeviceGrayColorSpace();
4448             obj1 = maskDict->lookup("Decode");
4449             if (obj1.isNull()) {
4450                 obj1 = maskDict->lookup("D");
4451             }
4452             maskColorMap = std::make_unique<GfxImageColorMap>(maskBits, &obj1, maskColorSpace);
4453             if (!maskColorMap->isOk()) {
4454                 goto err1;
4455             }
4456             // handle the Matte entry
4457             obj1 = maskDict->lookup("Matte");
4458             if (obj1.isArray()) {
4459                 if (obj1.getArray()->getLength() != colorSpace->getNComps()) {
4460                     error(errSyntaxError, -1, "Matte entry should have {0:d} components but has {1:d}", colorSpace->getNComps(), obj1.getArray()->getLength());
4461                 } else if (maskWidth != width || maskHeight != height) {
4462                     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);
4463                 } else {
4464                     GfxColor matteColor;
4465                     for (i = 0; i < colorSpace->getNComps(); i++) {
4466                         Object obj2 = obj1.getArray()->get(i);
4467                         if (!obj2.isNum()) {
4468                             error(errSyntaxError, -1, "Matte entry {0:d} should be a number but it's of type {1:d}", i, obj2.getType());
4469 
4470                             break;
4471                         }
4472                         matteColor.c[i] = dblToCol(obj2.getNum());
4473                     }
4474                     if (i == colorSpace->getNComps()) {
4475                         maskColorMap->setMatteColor(&matteColor);
4476                     }
4477                 }
4478             }
4479             haveSoftMask = true;
4480         } else if (maskObj.isArray()) {
4481             // color key mask
4482             for (i = 0; i < maskObj.arrayGetLength() && i < 2 * gfxColorMaxComps; ++i) {
4483                 obj1 = maskObj.arrayGet(i);
4484                 if (obj1.isInt()) {
4485                     maskColors[i] = obj1.getInt();
4486                 } else if (obj1.isReal()) {
4487                     error(errSyntaxError, -1, "Mask entry should be an integer but it's a real, trying to use it");
4488                     maskColors[i] = (int)obj1.getReal();
4489                 } else {
4490                     error(errSyntaxError, -1, "Mask entry should be an integer but it's of type {0:d}", obj1.getType());
4491                     goto err1;
4492                 }
4493             }
4494             haveColorKeyMask = true;
4495         } else if (maskObj.isStream()) {
4496             // explicit mask
4497             if (inlineImg) {
4498                 goto err1;
4499             }
4500 
4501             if (maskStr == nullptr) {
4502                 maskStr = maskObj.getStream();
4503                 maskDict = maskObj.streamGetDict();
4504             }
4505             obj1 = maskDict->lookup("Width");
4506             if (obj1.isNull()) {
4507                 obj1 = maskDict->lookup("W");
4508             }
4509             if (!obj1.isInt()) {
4510                 goto err1;
4511             }
4512             maskWidth = obj1.getInt();
4513             obj1 = maskDict->lookup("Height");
4514             if (obj1.isNull()) {
4515                 obj1 = maskDict->lookup("H");
4516             }
4517             if (!obj1.isInt()) {
4518                 goto err1;
4519             }
4520             maskHeight = obj1.getInt();
4521             obj1 = maskDict->lookup("Interpolate");
4522             if (obj1.isNull()) {
4523                 obj1 = maskDict->lookup("I");
4524             }
4525             if (obj1.isBool())
4526                 maskInterpolate = obj1.getBool();
4527             else
4528                 maskInterpolate = false;
4529 
4530             obj1 = maskDict->lookup("ImageMask");
4531             if (obj1.isNull()) {
4532                 obj1 = maskDict->lookup("IM");
4533             }
4534             if (!haveMaskImage && (!obj1.isBool() || !obj1.getBool())) {
4535                 goto err1;
4536             }
4537 
4538             maskInvert = false;
4539             obj1 = maskDict->lookup("Decode");
4540             if (obj1.isNull()) {
4541                 obj1 = maskDict->lookup("D");
4542             }
4543             if (obj1.isArray()) {
4544                 Object obj2 = obj1.arrayGet(0);
4545                 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4546                 // accepts [1.0 0.0] as well.
4547                 if (obj2.isNum() && obj2.getNum() >= 0.9) {
4548                     maskInvert = true;
4549                 }
4550             } else if (!obj1.isNull()) {
4551                 goto err1;
4552             }
4553 
4554             haveExplicitMask = true;
4555         }
4556 
4557         // if drawing is disabled, skip over inline image data
4558         if (!ocState || !out->needNonText()) {
4559             str->reset();
4560             n = height * ((width * colorMap.getNumPixelComps() * colorMap.getBits() + 7) / 8);
4561             for (i = 0; i < n; ++i) {
4562                 str->getChar();
4563             }
4564             str->close();
4565 
4566             // draw it
4567         } else {
4568             if (haveSoftMask) {
4569                 out->drawSoftMaskedImage(state, ref, str, width, height, &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap.get(), maskInterpolate);
4570             } else if (haveExplicitMask) {
4571                 out->drawMaskedImage(state, ref, str, width, height, &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
4572             } else {
4573                 out->drawImage(state, ref, str, width, height, &colorMap, interpolate, haveColorKeyMask ? maskColors : nullptr, inlineImg);
4574             }
4575         }
4576     }
4577 
4578     if ((i = width * height) > 1000) {
4579         i = 1000;
4580     }
4581     updateLevel += i;
4582 
4583     return;
4584 
4585 err1:
4586     error(errSyntaxError, getPos(), "Bad image parameters");
4587 }
4588 
checkTransparencyGroup(Dict * resDict)4589 bool Gfx::checkTransparencyGroup(Dict *resDict)
4590 {
4591     // check the effect of compositing objects as a group:
4592     // look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
4593     bool transpGroup = false;
4594     double opac;
4595 
4596     if (resDict == nullptr)
4597         return false;
4598     pushResources(resDict);
4599     Object extGStates = resDict->lookup("ExtGState");
4600     if (extGStates.isDict()) {
4601         Dict *dict = extGStates.getDict();
4602         for (int i = 0; i < dict->getLength() && !transpGroup; i++) {
4603             GfxBlendMode mode;
4604 
4605             Object obj1 = res->lookupGState(dict->getKey(i));
4606             if (obj1.isDict()) {
4607                 Object obj2 = obj1.dictLookup("BM");
4608                 if (!obj2.isNull()) {
4609                     if (state->parseBlendMode(&obj2, &mode)) {
4610                         if (mode != gfxBlendNormal)
4611                             transpGroup = true;
4612                     } else {
4613                         error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
4614                     }
4615                 }
4616                 obj2 = obj1.dictLookup("ca");
4617                 if (obj2.isNum()) {
4618                     opac = obj2.getNum();
4619                     opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4620                     if (opac != 1)
4621                         transpGroup = true;
4622                 }
4623                 obj2 = obj1.dictLookup("CA");
4624                 if (obj2.isNum()) {
4625                     opac = obj2.getNum();
4626                     opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4627                     if (opac != 1)
4628                         transpGroup = true;
4629                 }
4630                 // alpha is shape
4631                 obj2 = obj1.dictLookup("AIS");
4632                 if (!transpGroup && obj2.isBool()) {
4633                     transpGroup = obj2.getBool();
4634                 }
4635                 // soft mask
4636                 obj2 = obj1.dictLookup("SMask");
4637                 if (!transpGroup && !obj2.isNull()) {
4638                     if (!obj2.isName("None")) {
4639                         transpGroup = true;
4640                     }
4641                 }
4642             }
4643         }
4644     }
4645     popResources();
4646     return transpGroup;
4647 }
4648 
doForm(Object * str)4649 void Gfx::doForm(Object *str)
4650 {
4651     Dict *dict;
4652     bool transpGroup, isolated, knockout;
4653     GfxColorSpace *blendingColorSpace;
4654     double m[6], bbox[4];
4655     Dict *resDict;
4656     bool ocSaved;
4657     Object obj1;
4658     int i;
4659 
4660     // get stream dict
4661     dict = str->streamGetDict();
4662 
4663     // check form type
4664     obj1 = dict->lookup("FormType");
4665     if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
4666         error(errSyntaxError, getPos(), "Unknown form type");
4667     }
4668 
4669     // check for optional content key
4670     ocSaved = ocState;
4671     const Object &objOC = dict->lookupNF("OC");
4672     if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&objOC)) {
4673         if (out->needCharCount()) {
4674             ocState = false;
4675         } else {
4676             return;
4677         }
4678     }
4679 
4680     // get bounding box
4681     Object bboxObj = dict->lookup("BBox");
4682     if (!bboxObj.isArray()) {
4683         error(errSyntaxError, getPos(), "Bad form bounding box");
4684         ocState = ocSaved;
4685         return;
4686     }
4687     for (i = 0; i < 4; ++i) {
4688         obj1 = bboxObj.arrayGet(i);
4689         if (likely(obj1.isNum())) {
4690             bbox[i] = obj1.getNum();
4691         } else {
4692             error(errSyntaxError, getPos(), "Bad form bounding box value");
4693             return;
4694         }
4695     }
4696 
4697     // get matrix
4698     Object matrixObj = dict->lookup("Matrix");
4699     if (matrixObj.isArray()) {
4700         for (i = 0; i < 6; ++i) {
4701             obj1 = matrixObj.arrayGet(i);
4702             if (likely(obj1.isNum()))
4703                 m[i] = obj1.getNum();
4704             else
4705                 m[i] = 0;
4706         }
4707     } else {
4708         m[0] = 1;
4709         m[1] = 0;
4710         m[2] = 0;
4711         m[3] = 1;
4712         m[4] = 0;
4713         m[5] = 0;
4714     }
4715 
4716     // get resources
4717     Object resObj = dict->lookup("Resources");
4718     resDict = resObj.isDict() ? resObj.getDict() : nullptr;
4719 
4720     // check for a transparency group
4721     transpGroup = isolated = knockout = false;
4722     blendingColorSpace = nullptr;
4723     obj1 = dict->lookup("Group");
4724     if (obj1.isDict()) {
4725         Object obj2 = obj1.dictLookup("S");
4726         if (obj2.isName("Transparency")) {
4727             Object obj3 = obj1.dictLookup("CS");
4728             if (!obj3.isNull()) {
4729                 blendingColorSpace = GfxColorSpace::parse(res, &obj3, out, state);
4730             }
4731             obj3 = obj1.dictLookup("I");
4732             if (obj3.isBool()) {
4733                 isolated = obj3.getBool();
4734             }
4735             obj3 = obj1.dictLookup("K");
4736             if (obj3.isBool()) {
4737                 knockout = obj3.getBool();
4738             }
4739             transpGroup = isolated || out->checkTransparencyGroup(state, knockout) || checkTransparencyGroup(resDict);
4740         }
4741     }
4742 
4743     // draw it
4744     drawForm(str, resDict, m, bbox, transpGroup, false, blendingColorSpace, isolated, knockout);
4745 
4746     if (blendingColorSpace) {
4747         delete blendingColorSpace;
4748     }
4749 
4750     ocState = ocSaved;
4751 }
4752 
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)4753 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,
4754                    GfxColor *backdropColor)
4755 {
4756     Parser *oldParser;
4757     GfxState *savedState;
4758     double oldBaseMatrix[6];
4759     int i;
4760 
4761     // push new resources on stack
4762     pushResources(resDict);
4763 
4764     // save current graphics state
4765     savedState = saveStateStack();
4766 
4767     // kill any pre-existing path
4768     state->clearPath();
4769 
4770     // save current parser
4771     oldParser = parser;
4772 
4773     // set form transformation matrix
4774     state->concatCTM(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
4775     out->updateCTM(state, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
4776 
4777     // set form bounding box
4778     state->moveTo(bbox[0], bbox[1]);
4779     state->lineTo(bbox[2], bbox[1]);
4780     state->lineTo(bbox[2], bbox[3]);
4781     state->lineTo(bbox[0], bbox[3]);
4782     state->closePath();
4783     state->clip();
4784     out->clip(state);
4785     state->clearPath();
4786 
4787     if (softMask || transpGroup) {
4788         if (state->getBlendMode() != gfxBlendNormal) {
4789             state->setBlendMode(gfxBlendNormal);
4790             out->updateBlendMode(state);
4791         }
4792         if (state->getFillOpacity() != 1) {
4793             state->setFillOpacity(1);
4794             out->updateFillOpacity(state);
4795         }
4796         if (state->getStrokeOpacity() != 1) {
4797             state->setStrokeOpacity(1);
4798             out->updateStrokeOpacity(state);
4799         }
4800         out->clearSoftMask(state);
4801         out->beginTransparencyGroup(state, bbox, blendingColorSpace, isolated, knockout, softMask);
4802     }
4803 
4804     // set new base matrix
4805     for (i = 0; i < 6; ++i) {
4806         oldBaseMatrix[i] = baseMatrix[i];
4807         baseMatrix[i] = state->getCTM()[i];
4808     }
4809 
4810     GfxState *stateBefore = state;
4811 
4812     // draw the form
4813     ++displayDepth;
4814     display(str, false);
4815     --displayDepth;
4816 
4817     if (stateBefore != state) {
4818         if (state->isParentState(stateBefore)) {
4819             error(errSyntaxError, -1, "There's a form with more q than Q, trying to fix");
4820             while (stateBefore != state) {
4821                 restoreState();
4822             }
4823         } else {
4824             error(errSyntaxError, -1, "There's a form with more Q than q");
4825         }
4826     }
4827 
4828     if (softMask || transpGroup) {
4829         out->endTransparencyGroup(state);
4830     }
4831 
4832     // restore base matrix
4833     for (i = 0; i < 6; ++i) {
4834         baseMatrix[i] = oldBaseMatrix[i];
4835     }
4836 
4837     // restore parser
4838     parser = oldParser;
4839 
4840     // restore graphics state
4841     restoreStateStack(savedState);
4842 
4843     // pop resource stack
4844     popResources();
4845 
4846     if (softMask) {
4847         out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
4848     } else if (transpGroup) {
4849         out->paintTransparencyGroup(state, bbox);
4850     }
4851 
4852     return;
4853 }
4854 
4855 //------------------------------------------------------------------------
4856 // in-line image operators
4857 //------------------------------------------------------------------------
4858 
opBeginImage(Object args[],int numArgs)4859 void Gfx::opBeginImage(Object args[], int numArgs)
4860 {
4861     Stream *str;
4862     int c1, c2;
4863 
4864     // NB: this function is run even if ocState is false -- doImage() is
4865     // responsible for skipping over the inline image data
4866 
4867     // build dict/stream
4868     str = buildImageStream();
4869 
4870     // display the image
4871     if (str) {
4872         doImage(nullptr, str, true);
4873 
4874         // skip 'EI' tag
4875         c1 = str->getUndecodedStream()->getChar();
4876         c2 = str->getUndecodedStream()->getChar();
4877         while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
4878             c1 = c2;
4879             c2 = str->getUndecodedStream()->getChar();
4880         }
4881         delete str;
4882     }
4883 }
4884 
buildImageStream()4885 Stream *Gfx::buildImageStream()
4886 {
4887     Stream *str;
4888 
4889     // build dictionary
4890     Object dict(new Dict(xref));
4891     Object obj = parser->getObj();
4892     while (!obj.isCmd("ID") && !obj.isEOF()) {
4893         if (!obj.isName()) {
4894             error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
4895         } else {
4896             auto val = parser->getObj();
4897             if (val.isEOF() || val.isError()) {
4898                 break;
4899             }
4900             dict.dictAdd(obj.getName(), std::move(val));
4901         }
4902         obj = parser->getObj();
4903     }
4904     if (obj.isEOF()) {
4905         error(errSyntaxError, getPos(), "End of file in inline image");
4906         return nullptr;
4907     }
4908 
4909     // make stream
4910     if (parser->getStream()) {
4911         str = new EmbedStream(parser->getStream(), std::move(dict), false, 0, true);
4912         str = str->addFilters(str->getDict());
4913     } else {
4914         str = nullptr;
4915     }
4916 
4917     return str;
4918 }
4919 
opImageData(Object args[],int numArgs)4920 void Gfx::opImageData(Object args[], int numArgs)
4921 {
4922     error(errInternal, getPos(), "Got 'ID' operator");
4923 }
4924 
opEndImage(Object args[],int numArgs)4925 void Gfx::opEndImage(Object args[], int numArgs)
4926 {
4927     error(errInternal, getPos(), "Got 'EI' operator");
4928 }
4929 
4930 //------------------------------------------------------------------------
4931 // type 3 font operators
4932 //------------------------------------------------------------------------
4933 
opSetCharWidth(Object args[],int numArgs)4934 void Gfx::opSetCharWidth(Object args[], int numArgs)
4935 {
4936     out->type3D0(state, args[0].getNum(), args[1].getNum());
4937 }
4938 
opSetCacheDevice(Object args[],int numArgs)4939 void Gfx::opSetCacheDevice(Object args[], int numArgs)
4940 {
4941     out->type3D1(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
4942 }
4943 
4944 //------------------------------------------------------------------------
4945 // compatibility operators
4946 //------------------------------------------------------------------------
4947 
opBeginIgnoreUndef(Object args[],int numArgs)4948 void Gfx::opBeginIgnoreUndef(Object args[], int numArgs)
4949 {
4950     ++ignoreUndef;
4951 }
4952 
opEndIgnoreUndef(Object args[],int numArgs)4953 void Gfx::opEndIgnoreUndef(Object args[], int numArgs)
4954 {
4955     if (ignoreUndef > 0)
4956         --ignoreUndef;
4957 }
4958 
4959 //------------------------------------------------------------------------
4960 // marked content operators
4961 //------------------------------------------------------------------------
4962 
4963 enum GfxMarkedContentKind
4964 {
4965     gfxMCOptionalContent,
4966     gfxMCActualText,
4967     gfxMCOther
4968 };
4969 
4970 struct MarkedContentStack
4971 {
4972     GfxMarkedContentKind kind;
4973     bool ocSuppressed; // are we ignoring content based on OptionalContent?
4974     MarkedContentStack *next; // next object on stack
4975 };
4976 
popMarkedContent()4977 void Gfx::popMarkedContent()
4978 {
4979     MarkedContentStack *mc = mcStack;
4980     mcStack = mc->next;
4981     delete mc;
4982 }
4983 
pushMarkedContent()4984 void Gfx::pushMarkedContent()
4985 {
4986     MarkedContentStack *mc = new MarkedContentStack();
4987     mc->ocSuppressed = false;
4988     mc->kind = gfxMCOther;
4989     mc->next = mcStack;
4990     mcStack = mc;
4991 }
4992 
contentIsHidden()4993 bool Gfx::contentIsHidden()
4994 {
4995     MarkedContentStack *mc = mcStack;
4996     bool hidden = mc && mc->ocSuppressed;
4997     while (!hidden && mc && mc->next) {
4998         mc = mc->next;
4999         hidden = mc->ocSuppressed;
5000     }
5001     return hidden;
5002 }
5003 
opBeginMarkedContent(Object args[],int numArgs)5004 void Gfx::opBeginMarkedContent(Object args[], int numArgs)
5005 {
5006     // push a new stack entry
5007     pushMarkedContent();
5008 
5009     OCGs *contentConfig = catalog->getOptContentConfig();
5010     const char *name0 = args[0].getName();
5011     if (strncmp(name0, "OC", 2) == 0 && contentConfig) {
5012         if (numArgs >= 2) {
5013             if (args[1].isName()) {
5014                 const char *name1 = args[1].getName();
5015                 MarkedContentStack *mc = mcStack;
5016                 mc->kind = gfxMCOptionalContent;
5017                 Object markedContent = res->lookupMarkedContentNF(name1);
5018                 if (!markedContent.isNull()) {
5019                     bool visible = contentConfig->optContentIsVisible(&markedContent);
5020                     mc->ocSuppressed = !(visible);
5021                 } else {
5022                     error(errSyntaxError, getPos(), "DID NOT find {0:s}", name1);
5023                 }
5024             } else {
5025                 error(errSyntaxError, getPos(), "Unexpected MC Type: {0:d}", args[1].getType());
5026             }
5027         } else {
5028             error(errSyntaxError, getPos(), "insufficient arguments for Marked Content");
5029         }
5030     } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) {
5031         Object obj = args[1].dictLookup("ActualText");
5032         if (obj.isString()) {
5033             out->beginActualText(state, obj.getString());
5034             MarkedContentStack *mc = mcStack;
5035             mc->kind = gfxMCActualText;
5036         }
5037     }
5038 
5039     if (printCommands) {
5040         printf("  marked content: %s ", args[0].getName());
5041         if (numArgs == 2)
5042             args[1].print(stdout);
5043         printf("\n");
5044         fflush(stdout);
5045     }
5046     ocState = !contentIsHidden();
5047 
5048     if (numArgs == 2 && args[1].isDict()) {
5049         out->beginMarkedContent(args[0].getName(), args[1].getDict());
5050     } else if (numArgs == 1) {
5051         out->beginMarkedContent(args[0].getName(), nullptr);
5052     }
5053 }
5054 
opEndMarkedContent(Object args[],int numArgs)5055 void Gfx::opEndMarkedContent(Object args[], int numArgs)
5056 {
5057     if (!mcStack) {
5058         error(errSyntaxWarning, getPos(), "Mismatched EMC operator");
5059         return;
5060     }
5061 
5062     MarkedContentStack *mc = mcStack;
5063     GfxMarkedContentKind mcKind = mc->kind;
5064 
5065     // pop the stack
5066     popMarkedContent();
5067 
5068     if (mcKind == gfxMCActualText)
5069         out->endActualText(state);
5070     ocState = !contentIsHidden();
5071 
5072     out->endMarkedContent(state);
5073 }
5074 
opMarkPoint(Object args[],int numArgs)5075 void Gfx::opMarkPoint(Object args[], int numArgs)
5076 {
5077     if (printCommands) {
5078         printf("  mark point: %s ", args[0].getName());
5079         if (numArgs == 2)
5080             args[1].print(stdout);
5081         printf("\n");
5082         fflush(stdout);
5083     }
5084 
5085     if (numArgs == 2 && args[1].isDict()) {
5086         out->markPoint(args[0].getName(), args[1].getDict());
5087     } else {
5088         out->markPoint(args[0].getName());
5089     }
5090 }
5091 
5092 //------------------------------------------------------------------------
5093 // misc
5094 //------------------------------------------------------------------------
5095 
5096 struct GfxStackStateSaver
5097 {
GfxStackStateSaverGfxStackStateSaver5098     explicit GfxStackStateSaver(Gfx *gfxA) : gfx(gfxA) { gfx->saveState(); }
5099 
~GfxStackStateSaverGfxStackStateSaver5100     ~GfxStackStateSaver() { gfx->restoreState(); }
5101 
5102     GfxStackStateSaver(const GfxStackStateSaver &) = delete;
5103     GfxStackStateSaver &operator=(const GfxStackStateSaver &) = delete;
5104 
5105     Gfx *const gfx;
5106 };
5107 
drawAnnot(Object * str,AnnotBorder * border,AnnotColor * aColor,double xMin,double yMin,double xMax,double yMax,int rotate)5108 void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor, double xMin, double yMin, double xMax, double yMax, int rotate)
5109 {
5110     Dict *dict, *resDict;
5111     double formXMin, formYMin, formXMax, formYMax;
5112     double x, y, sx, sy, tx, ty;
5113     double m[6], bbox[4];
5114     GfxColor color;
5115     double *dash, *dash2;
5116     int dashLength;
5117     int i;
5118 
5119     // this function assumes that we are in the default user space,
5120     // i.e., baseMatrix = ctm
5121 
5122     // if the bounding box has zero width or height, don't draw anything
5123     // at all
5124     if (xMin == xMax || yMin == yMax) {
5125         return;
5126     }
5127 
5128     // saves gfx state and automatically restores it on return
5129     GfxStackStateSaver stackStateSaver(this);
5130 
5131     // Rotation around the topleft corner (for the NoRotate flag)
5132     if (rotate != 0) {
5133         const double angle_rad = rotate * M_PI / 180;
5134         const double c = cos(angle_rad);
5135         const double s = sin(angle_rad);
5136 
5137         // (xMin, yMax) is the pivot
5138         const double unrotateMTX[6] = { +c, -s, +s, +c, -c * xMin - s * yMax + xMin, -c * yMax + s * xMin + yMax };
5139 
5140         state->concatCTM(unrotateMTX[0], unrotateMTX[1], unrotateMTX[2], unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5141         out->updateCTM(state, unrotateMTX[0], unrotateMTX[1], unrotateMTX[2], unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5142     }
5143 
5144     // draw the appearance stream (if there is one)
5145     if (str->isStream()) {
5146 
5147         // get stream dict
5148         dict = str->streamGetDict();
5149 
5150         // get the form bounding box
5151         Object bboxObj = dict->lookup("BBox");
5152         if (!bboxObj.isArray()) {
5153             error(errSyntaxError, getPos(), "Bad form bounding box");
5154             return;
5155         }
5156         for (i = 0; i < 4; ++i) {
5157             Object obj1 = bboxObj.arrayGet(i);
5158             if (likely(obj1.isNum())) {
5159                 bbox[i] = obj1.getNum();
5160             } else {
5161                 error(errSyntaxError, getPos(), "Bad form bounding box value");
5162                 return;
5163             }
5164         }
5165 
5166         // get the form matrix
5167         Object matrixObj = dict->lookup("Matrix");
5168         if (matrixObj.isArray() && matrixObj.arrayGetLength() >= 6) {
5169             for (i = 0; i < 6; ++i) {
5170                 Object obj1 = matrixObj.arrayGet(i);
5171                 if (likely(obj1.isNum())) {
5172                     m[i] = obj1.getNum();
5173                 } else {
5174                     error(errSyntaxError, getPos(), "Bad form matrix");
5175                     return;
5176                 }
5177             }
5178         } else {
5179             m[0] = 1;
5180             m[1] = 0;
5181             m[2] = 0;
5182             m[3] = 1;
5183             m[4] = 0;
5184             m[5] = 0;
5185         }
5186 
5187         // transform the four corners of the form bbox to default user
5188         // space, and construct the transformed bbox
5189         x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
5190         y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
5191         formXMin = formXMax = x;
5192         formYMin = formYMax = y;
5193         x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
5194         y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
5195         if (x < formXMin) {
5196             formXMin = x;
5197         } else if (x > formXMax) {
5198             formXMax = x;
5199         }
5200         if (y < formYMin) {
5201             formYMin = y;
5202         } else if (y > formYMax) {
5203             formYMax = y;
5204         }
5205         x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
5206         y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
5207         if (x < formXMin) {
5208             formXMin = x;
5209         } else if (x > formXMax) {
5210             formXMax = x;
5211         }
5212         if (y < formYMin) {
5213             formYMin = y;
5214         } else if (y > formYMax) {
5215             formYMax = y;
5216         }
5217         x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
5218         y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
5219         if (x < formXMin) {
5220             formXMin = x;
5221         } else if (x > formXMax) {
5222             formXMax = x;
5223         }
5224         if (y < formYMin) {
5225             formYMin = y;
5226         } else if (y > formYMax) {
5227             formYMax = y;
5228         }
5229 
5230         // construct a mapping matrix, [sx 0  0], which maps the transformed
5231         //                             [0  sy 0]
5232         //                             [tx ty 1]
5233         // bbox to the annotation rectangle
5234         if (formXMin == formXMax) {
5235             // this shouldn't happen
5236             sx = 1;
5237         } else {
5238             sx = (xMax - xMin) / (formXMax - formXMin);
5239         }
5240         if (formYMin == formYMax) {
5241             // this shouldn't happen
5242             sy = 1;
5243         } else {
5244             sy = (yMax - yMin) / (formYMax - formYMin);
5245         }
5246         tx = -formXMin * sx + xMin;
5247         ty = -formYMin * sy + yMin;
5248 
5249         // the final transform matrix is (form matrix) * (mapping matrix)
5250         m[0] *= sx;
5251         m[1] *= sy;
5252         m[2] *= sx;
5253         m[3] *= sy;
5254         m[4] = m[4] * sx + tx;
5255         m[5] = m[5] * sy + ty;
5256 
5257         // get the resources
5258         Object resObj = dict->lookup("Resources");
5259         resDict = resObj.isDict() ? resObj.getDict() : nullptr;
5260 
5261         // draw it
5262         drawForm(str, resDict, m, bbox);
5263     }
5264 
5265     // draw the border
5266     if (border && border->getWidth() > 0 && (!aColor || aColor->getSpace() != AnnotColor::colorTransparent)) {
5267         if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
5268             state->setStrokePattern(nullptr);
5269             state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
5270             out->updateStrokeColorSpace(state);
5271         }
5272         double r, g, b;
5273         if (!aColor) {
5274             r = g = b = 0;
5275         } else if ((aColor->getSpace() == AnnotColor::colorRGB)) {
5276             const double *values = aColor->getValues();
5277             r = values[0];
5278             g = values[1];
5279             b = values[2];
5280         } else {
5281             error(errUnimplemented, -1, "AnnotColor different than RGB and Transparent not supported");
5282             r = g = b = 0;
5283         };
5284         color.c[0] = dblToCol(r);
5285         color.c[1] = dblToCol(g);
5286         color.c[2] = dblToCol(b);
5287         state->setStrokeColor(&color);
5288         out->updateStrokeColor(state);
5289         state->setLineWidth(border->getWidth());
5290         out->updateLineWidth(state);
5291         dashLength = border->getDashLength();
5292         dash = border->getDash();
5293         if (border->getStyle() == AnnotBorder::borderDashed && dashLength > 0) {
5294             dash2 = (double *)gmallocn(dashLength, sizeof(double));
5295             memcpy(dash2, dash, dashLength * sizeof(double));
5296             state->setLineDash(dash2, dashLength, 0);
5297             out->updateLineDash(state);
5298         }
5299         //~ this doesn't currently handle the beveled and engraved styles
5300         state->clearPath();
5301         state->moveTo(xMin, yMin);
5302         state->lineTo(xMax, yMin);
5303         if (border->getStyle() != AnnotBorder::borderUnderlined) {
5304             state->lineTo(xMax, yMax);
5305             state->lineTo(xMin, yMax);
5306             state->closePath();
5307         }
5308         out->stroke(state);
5309     }
5310 }
5311 
bottomGuard()5312 int Gfx::bottomGuard()
5313 {
5314     return stateGuards[stateGuards.size() - 1];
5315 }
5316 
pushStateGuard()5317 void Gfx::pushStateGuard()
5318 {
5319     stateGuards.push_back(stackHeight);
5320 }
5321 
popStateGuard()5322 void Gfx::popStateGuard()
5323 {
5324     while (stackHeight > bottomGuard() && state->hasSaves())
5325         restoreState();
5326     stateGuards.pop_back();
5327 }
5328 
saveState()5329 void Gfx::saveState()
5330 {
5331     out->saveState(state);
5332     state = state->save();
5333     stackHeight++;
5334 }
5335 
restoreState()5336 void Gfx::restoreState()
5337 {
5338     if (stackHeight <= bottomGuard() || !state->hasSaves()) {
5339         error(errSyntaxError, -1, "Restoring state when no valid states to pop");
5340         commandAborted = true;
5341         return;
5342     }
5343     state = state->restore();
5344     out->restoreState(state);
5345     stackHeight--;
5346 }
5347 
5348 // Create a new state stack, and initialize it with a copy of the
5349 // current state.
saveStateStack()5350 GfxState *Gfx::saveStateStack()
5351 {
5352     GfxState *oldState;
5353 
5354     out->saveState(state);
5355     oldState = state;
5356     state = state->copy(true);
5357     return oldState;
5358 }
5359 
5360 // Switch back to the previous state stack.
restoreStateStack(GfxState * oldState)5361 void Gfx::restoreStateStack(GfxState *oldState)
5362 {
5363     while (state->hasSaves()) {
5364         restoreState();
5365     }
5366     delete state;
5367     state = oldState;
5368     out->restoreState(state);
5369 }
5370 
pushResources(Dict * resDict)5371 void Gfx::pushResources(Dict *resDict)
5372 {
5373     res = new GfxResources(xref, resDict, res);
5374 }
5375 
popResources()5376 void Gfx::popResources()
5377 {
5378     GfxResources *resPtr;
5379 
5380     resPtr = res->getNext();
5381     delete res;
5382     res = resPtr;
5383 }
5384