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