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-2022 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, 2023 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, 2024 Even Rouault <even.rouault@spatialys.com>
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-2022 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// Copyright (C) 2023 Anton Thomasson <antonthomasson@gmail.com>
50// Copyright (C) 2024 Nelson Benítez León <nbenitezl@gmail.com>
51//
52// To see a description of the changes please see the Changelog file that
53// came with your tarball or type make ChangeLog if you are building from git
54//
55//========================================================================
56
57#include <config.h>
58
59#include <cstdlib>
60#include <cstdio>
61#include <cstddef>
62#include <cstring>
63#include <cmath>
64#include <memory>
65#include "goo/gmem.h"
66#include "goo/GooTimer.h"
67#include "GlobalParams.h"
68#include "CharTypes.h"
69#include "Object.h"
70#include "PDFDoc.h"
71#include "Array.h"
72#include "Annot.h"
73#include "Dict.h"
74#include "Stream.h"
75#include "Lexer.h"
76#include "Parser.h"
77#include "GfxFont.h"
78#include "GfxState.h"
79#include "OutputDev.h"
80#include "Page.h"
81#include "Annot.h"
82#include "Error.h"
83#include "Gfx.h"
84#include "ProfileData.h"
85#include "Catalog.h"
86#include "OptionalContent.h"
87
88// the MSVC math.h doesn't define this
89#ifndef M_PI
90# define M_PI 3.14159265358979323846
91#endif
92
93//------------------------------------------------------------------------
94// constants
95//------------------------------------------------------------------------
96
97// Max recursive depth for a function shading fill.
98#define functionMaxDepth 6
99
100// Max delta allowed in any color component for a function shading fill.
101#define functionColorDelta (dblToCol(1 / 256.0))
102
103// Max number of splits along the t axis for an axial shading fill.
104#define axialMaxSplits 256
105
106// Max delta allowed in any color component for an axial shading fill.
107#define axialColorDelta (dblToCol(1 / 256.0))
108
109// Max number of splits along the t axis for a radial shading fill.
110#define radialMaxSplits 256
111
112// Max delta allowed in any color component for a radial shading fill.
113#define radialColorDelta (dblToCol(1 / 256.0))
114
115// Max recursive depth for a Gouraud triangle shading fill.
116//
117// Triangles will be split at most gouraudMaxDepth times (each time into 4
118// smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for
119// every triangle.
120#define gouraudMaxDepth 6
121
122// Max delta allowed in any color component for a Gouraud triangle
123// shading fill.
124#define gouraudColorDelta (dblToCol(3. / 256.0))
125
126// Gouraud triangle: if the three color parameters differ by at more than this percend of
127// the total color parameter range, the triangle will be refined
128#define gouraudParameterizedColorDelta 5e-3
129
130// Max recursive depth for a patch mesh shading fill.
131#define patchMaxDepth 6
132
133// Max delta allowed in any color component for a patch mesh shading
134// fill.
135#define patchColorDelta (dblToCol((3. / 256.0)))
136
137//------------------------------------------------------------------------
138// Operator table
139//------------------------------------------------------------------------
140
141const Operator Gfx::opTab[] = {
142 { .name: "\"", .numArgs: 3, .tchk: { tchkNum, tchkNum, tchkString }, .func: &Gfx::opMoveSetShowText },
143 { .name: "'", .numArgs: 1, .tchk: { tchkString }, .func: &Gfx::opMoveShowText },
144 { .name: "B", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opFillStroke },
145 { .name: "B*", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEOFillStroke },
146 { .name: "BDC", .numArgs: 2, .tchk: { tchkName, tchkProps }, .func: &Gfx::opBeginMarkedContent },
147 { .name: "BI", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opBeginImage },
148 { .name: "BMC", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opBeginMarkedContent },
149 { .name: "BT", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opBeginText },
150 { .name: "BX", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opBeginIgnoreUndef },
151 { .name: "CS", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opSetStrokeColorSpace },
152 { .name: "DP", .numArgs: 2, .tchk: { tchkName, tchkProps }, .func: &Gfx::opMarkPoint },
153 { .name: "Do", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opXObject },
154 { .name: "EI", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEndImage },
155 { .name: "EMC", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEndMarkedContent },
156 { .name: "ET", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEndText },
157 { .name: "EX", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEndIgnoreUndef },
158 { .name: "F", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opFill },
159 { .name: "G", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetStrokeGray },
160 { .name: "ID", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opImageData },
161 { .name: "J", .numArgs: 1, .tchk: { tchkInt }, .func: &Gfx::opSetLineCap },
162 { .name: "K", .numArgs: 4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetStrokeCMYKColor },
163 { .name: "M", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetMiterLimit },
164 { .name: "MP", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opMarkPoint },
165 { .name: "Q", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opRestore },
166 { .name: "RG", .numArgs: 3, .tchk: { tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetStrokeRGBColor },
167 { .name: "S", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opStroke },
168 { .name: "SC", .numArgs: -4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetStrokeColor },
169 { .name: "SCN",
170 .numArgs: -33,
171 .tchk: { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
172 tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
173 .func: &Gfx::opSetStrokeColorN },
174 { .name: "T*", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opTextNextLine },
175 { .name: "TD", .numArgs: 2, .tchk: { tchkNum, tchkNum }, .func: &Gfx::opTextMoveSet },
176 { .name: "TJ", .numArgs: 1, .tchk: { tchkArray }, .func: &Gfx::opShowSpaceText },
177 { .name: "TL", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetTextLeading },
178 { .name: "Tc", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetCharSpacing },
179 { .name: "Td", .numArgs: 2, .tchk: { tchkNum, tchkNum }, .func: &Gfx::opTextMove },
180 { .name: "Tf", .numArgs: 2, .tchk: { tchkName, tchkNum }, .func: &Gfx::opSetFont },
181 { .name: "Tj", .numArgs: 1, .tchk: { tchkString }, .func: &Gfx::opShowText },
182 { .name: "Tm", .numArgs: 6, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetTextMatrix },
183 { .name: "Tr", .numArgs: 1, .tchk: { tchkInt }, .func: &Gfx::opSetTextRender },
184 { .name: "Ts", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetTextRise },
185 { .name: "Tw", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetWordSpacing },
186 { .name: "Tz", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetHorizScaling },
187 { .name: "W", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opClip },
188 { .name: "W*", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEOClip },
189 { .name: "b", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opCloseFillStroke },
190 { .name: "b*", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opCloseEOFillStroke },
191 { .name: "c", .numArgs: 6, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opCurveTo },
192 { .name: "cm", .numArgs: 6, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opConcat },
193 { .name: "cs", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opSetFillColorSpace },
194 { .name: "d", .numArgs: 2, .tchk: { tchkArray, tchkNum }, .func: &Gfx::opSetDash },
195 { .name: "d0", .numArgs: 2, .tchk: { tchkNum, tchkNum }, .func: &Gfx::opSetCharWidth },
196 { .name: "d1", .numArgs: 6, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetCacheDevice },
197 { .name: "f", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opFill },
198 { .name: "f*", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEOFill },
199 { .name: "g", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetFillGray },
200 { .name: "gs", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opSetExtGState },
201 { .name: "h", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opClosePath },
202 { .name: "i", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetFlat },
203 { .name: "j", .numArgs: 1, .tchk: { tchkInt }, .func: &Gfx::opSetLineJoin },
204 { .name: "k", .numArgs: 4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetFillCMYKColor },
205 { .name: "l", .numArgs: 2, .tchk: { tchkNum, tchkNum }, .func: &Gfx::opLineTo },
206 { .name: "m", .numArgs: 2, .tchk: { tchkNum, tchkNum }, .func: &Gfx::opMoveTo },
207 { .name: "n", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opEndPath },
208 { .name: "q", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opSave },
209 { .name: "re", .numArgs: 4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opRectangle },
210 { .name: "rg", .numArgs: 3, .tchk: { tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetFillRGBColor },
211 { .name: "ri", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opSetRenderingIntent },
212 { .name: "s", .numArgs: 0, .tchk: { tchkNone }, .func: &Gfx::opCloseStroke },
213 { .name: "sc", .numArgs: -4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opSetFillColor },
214 { .name: "scn",
215 .numArgs: -33,
216 .tchk: { tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN,
217 tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN },
218 .func: &Gfx::opSetFillColorN },
219 { .name: "sh", .numArgs: 1, .tchk: { tchkName }, .func: &Gfx::opShFill },
220 { .name: "v", .numArgs: 4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opCurveTo1 },
221 { .name: "w", .numArgs: 1, .tchk: { tchkNum }, .func: &Gfx::opSetLineWidth },
222 { .name: "y", .numArgs: 4, .tchk: { tchkNum, tchkNum, tchkNum, tchkNum }, .func: &Gfx::opCurveTo2 },
223};
224
225#define numOps (sizeof(opTab) / sizeof(Operator))
226
227static inline bool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, unsigned int nComps, double delta)
228{
229 for (unsigned int k = 0; k < nComps; ++k) {
230 if (abs(x: colorA.c[k] - colorB.c[k]) > delta) {
231 return false;
232 }
233 }
234 return true;
235}
236
237//------------------------------------------------------------------------
238// GfxResources
239//------------------------------------------------------------------------
240
241GfxResources::GfxResources(XRef *xrefA, Dict *resDictA, GfxResources *nextA) : gStateCache(2), xref(xrefA)
242{
243 Ref r;
244
245 if (resDictA) {
246
247 // build font dictionary
248 Dict *resDict = resDictA->copy(xrefA: xref);
249 fonts = nullptr;
250 const Object &obj1 = resDict->lookupNF(key: "Font");
251 if (obj1.isRef()) {
252 Object obj2 = obj1.fetch(xref);
253 if (obj2.isDict()) {
254 r = obj1.getRef();
255 fonts = new GfxFontDict(xref, &r, obj2.getDict());
256 }
257 } else if (obj1.isDict()) {
258 fonts = new GfxFontDict(xref, nullptr, obj1.getDict());
259 }
260
261 // get XObject dictionary
262 xObjDict = resDict->lookup(key: "XObject");
263
264 // get color space dictionary
265 colorSpaceDict = resDict->lookup(key: "ColorSpace");
266
267 // get pattern dictionary
268 patternDict = resDict->lookup(key: "Pattern");
269
270 // get shading dictionary
271 shadingDict = resDict->lookup(key: "Shading");
272
273 // get graphics state parameter dictionary
274 gStateDict = resDict->lookup(key: "ExtGState");
275
276 // get properties dictionary
277 propertiesDict = resDict->lookup(key: "Properties");
278
279 delete resDict;
280 } else {
281 fonts = nullptr;
282 xObjDict.setToNull();
283 colorSpaceDict.setToNull();
284 patternDict.setToNull();
285 shadingDict.setToNull();
286 gStateDict.setToNull();
287 propertiesDict.setToNull();
288 }
289
290 next = nextA;
291}
292
293GfxResources::~GfxResources()
294{
295 delete fonts;
296}
297
298std::shared_ptr<GfxFont> GfxResources::doLookupFont(const char *name) const
299{
300 const GfxResources *resPtr;
301
302 for (resPtr = this; resPtr; resPtr = resPtr->next) {
303 if (resPtr->fonts) {
304 if (std::shared_ptr<GfxFont> font = resPtr->fonts->lookup(tag: name)) {
305 return font;
306 }
307 }
308 }
309 error(category: errSyntaxError, pos: -1, msg: "Unknown font tag '{0:s}'", name);
310 return nullptr;
311}
312
313std::shared_ptr<GfxFont> GfxResources::lookupFont(const char *name)
314{
315 return doLookupFont(name);
316}
317
318std::shared_ptr<const GfxFont> GfxResources::lookupFont(const char *name) const
319{
320 return doLookupFont(name);
321}
322
323Object GfxResources::lookupXObject(const char *name)
324{
325 GfxResources *resPtr;
326
327 for (resPtr = this; resPtr; resPtr = resPtr->next) {
328 if (resPtr->xObjDict.isDict()) {
329 Object obj = resPtr->xObjDict.dictLookup(key: name);
330 if (!obj.isNull()) {
331 return obj;
332 }
333 }
334 }
335 error(category: errSyntaxError, pos: -1, msg: "XObject '{0:s}' is unknown", name);
336 return Object(objNull);
337}
338
339Object GfxResources::lookupXObjectNF(const char *name)
340{
341 GfxResources *resPtr;
342
343 for (resPtr = this; resPtr; resPtr = resPtr->next) {
344 if (resPtr->xObjDict.isDict()) {
345 Object obj = resPtr->xObjDict.dictLookupNF(key: name).copy();
346 if (!obj.isNull()) {
347 return obj;
348 }
349 }
350 }
351 error(category: errSyntaxError, pos: -1, msg: "XObject '{0:s}' is unknown", name);
352 return Object(objNull);
353}
354
355Object GfxResources::lookupMarkedContentNF(const char *name)
356{
357 GfxResources *resPtr;
358
359 for (resPtr = this; resPtr; resPtr = resPtr->next) {
360 if (resPtr->propertiesDict.isDict()) {
361 Object obj = resPtr->propertiesDict.dictLookupNF(key: name).copy();
362 if (!obj.isNull()) {
363 return obj;
364 }
365 }
366 }
367 error(category: errSyntaxError, pos: -1, msg: "Marked Content '{0:s}' is unknown", name);
368 return Object(objNull);
369}
370
371Object GfxResources::lookupColorSpace(const char *name)
372{
373 GfxResources *resPtr;
374
375 for (resPtr = this; resPtr; resPtr = resPtr->next) {
376 if (resPtr->colorSpaceDict.isDict()) {
377 Object obj = resPtr->colorSpaceDict.dictLookup(key: name);
378 if (!obj.isNull()) {
379 return obj;
380 }
381 }
382 }
383 return Object(objNull);
384}
385
386GfxPattern *GfxResources::lookupPattern(const char *name, OutputDev *out, GfxState *state)
387{
388 GfxResources *resPtr;
389
390 for (resPtr = this; resPtr; resPtr = resPtr->next) {
391 if (resPtr->patternDict.isDict()) {
392 Ref patternRef = Ref::INVALID();
393 Object obj = resPtr->patternDict.getDict()->lookup(key: name, returnRef: &patternRef);
394 if (!obj.isNull()) {
395 return GfxPattern::parse(res: resPtr, obj: &obj, out, state, patternRefNum: patternRef.num);
396 }
397 }
398 }
399 error(category: errSyntaxError, pos: -1, msg: "Unknown pattern '{0:s}'", name);
400 return nullptr;
401}
402
403GfxShading *GfxResources::lookupShading(const char *name, OutputDev *out, GfxState *state)
404{
405 GfxResources *resPtr;
406 GfxShading *shading;
407
408 for (resPtr = this; resPtr; resPtr = resPtr->next) {
409 if (resPtr->shadingDict.isDict()) {
410 Object obj = resPtr->shadingDict.dictLookup(key: name);
411 if (!obj.isNull()) {
412 shading = GfxShading::parse(res: resPtr, obj: &obj, out, state);
413 return shading;
414 }
415 }
416 }
417 error(category: errSyntaxError, pos: -1, msg: "ExtGState '{0:s}' is unknown", name);
418 return nullptr;
419}
420
421Object GfxResources::lookupGState(const char *name)
422{
423 Object obj = lookupGStateNF(name);
424 if (obj.isNull()) {
425 return Object(objNull);
426 }
427
428 if (!obj.isRef()) {
429 return obj;
430 }
431
432 const Ref ref = obj.getRef();
433
434 if (auto *item = gStateCache.lookup(key: ref)) {
435 return item->copy();
436 }
437
438 auto *item = new Object { xref->fetch(ref) };
439 gStateCache.put(key: ref, item);
440 return item->copy();
441}
442
443Object GfxResources::lookupGStateNF(const char *name)
444{
445 GfxResources *resPtr;
446
447 for (resPtr = this; resPtr; resPtr = resPtr->next) {
448 if (resPtr->gStateDict.isDict()) {
449 Object obj = resPtr->gStateDict.dictLookupNF(key: name).copy();
450 if (!obj.isNull()) {
451 return obj;
452 }
453 }
454 }
455 error(category: errSyntaxError, pos: -1, msg: "ExtGState '{0:s}' is unknown", name);
456 return Object(objNull);
457}
458
459//------------------------------------------------------------------------
460// Gfx
461//------------------------------------------------------------------------
462
463Gfx::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)
464 : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
465{
466 int i;
467
468 doc = docA;
469 xref = (xrefA == nullptr) ? doc->getXRef() : xrefA;
470 catalog = doc->getCatalog();
471 subPage = false;
472 mcStack = nullptr;
473 parser = nullptr;
474
475 // start the resource stack
476 res = new GfxResources(xref, resDict, nullptr);
477
478 // initialize
479 out = outA;
480 state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
481 out->initGfxState(state);
482 stackHeight = 1;
483 pushStateGuard();
484 fontChanged = false;
485 clip = clipNone;
486 ignoreUndef = 0;
487 out->startPage(pageNum, state, xref);
488 out->setDefaultCTM(state->getCTM());
489 out->updateAll(state);
490 for (i = 0; i < 6; ++i) {
491 baseMatrix[i] = state->getCTM()[i];
492 }
493 displayDepth = 0;
494 ocState = true;
495 parser = nullptr;
496 abortCheckCbk = abortCheckCbkA;
497 abortCheckCbkData = abortCheckCbkDataA;
498
499 // set crop box
500 if (cropBox) {
501 state->moveTo(x: cropBox->x1, y: cropBox->y1);
502 state->lineTo(x: cropBox->x2, y: cropBox->y1);
503 state->lineTo(x: cropBox->x2, y: cropBox->y2);
504 state->lineTo(x: cropBox->x1, y: cropBox->y2);
505 state->closePath();
506 state->clip();
507 out->clip(state);
508 state->clearPath();
509 }
510#ifdef USE_CMS
511 initDisplayProfile();
512#endif
513}
514
515Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, const PDFRectangle *box, const PDFRectangle *cropBox, bool (*abortCheckCbkA)(void *data), void *abortCheckCbkDataA, Gfx *gfxA)
516 : printCommands(globalParams->getPrintCommands()), profileCommands(globalParams->getProfileCommands())
517{
518 int i;
519
520 doc = docA;
521 if (gfxA) {
522 xref = gfxA->getXRef();
523 formsDrawing = gfxA->formsDrawing;
524 charProcDrawing = gfxA->charProcDrawing;
525 } else {
526 xref = doc->getXRef();
527 }
528 catalog = doc->getCatalog();
529 subPage = true;
530 mcStack = nullptr;
531 parser = nullptr;
532
533 // start the resource stack
534 res = new GfxResources(xref, resDict, nullptr);
535
536 // initialize
537 out = outA;
538 double hDPI = 72;
539 double vDPI = 72;
540 if (gfxA) {
541 hDPI = gfxA->getState()->getHDPI();
542 vDPI = gfxA->getState()->getVDPI();
543 }
544 state = new GfxState(hDPI, vDPI, box, 0, false);
545 stackHeight = 1;
546 pushStateGuard();
547 fontChanged = false;
548 clip = clipNone;
549 ignoreUndef = 0;
550 for (i = 0; i < 6; ++i) {
551 baseMatrix[i] = state->getCTM()[i];
552 }
553 displayDepth = 0;
554 ocState = true;
555 parser = nullptr;
556 abortCheckCbk = abortCheckCbkA;
557 abortCheckCbkData = abortCheckCbkDataA;
558
559 // set crop box
560 if (cropBox) {
561 state->moveTo(x: cropBox->x1, y: cropBox->y1);
562 state->lineTo(x: cropBox->x2, y: cropBox->y1);
563 state->lineTo(x: cropBox->x2, y: cropBox->y2);
564 state->lineTo(x: cropBox->x1, y: cropBox->y2);
565 state->closePath();
566 state->clip();
567 out->clip(state);
568 state->clearPath();
569 }
570#ifdef USE_CMS
571 initDisplayProfile();
572#endif
573}
574
575#ifdef USE_CMS
576
577# include <lcms2.h>
578
579void Gfx::initDisplayProfile()
580{
581 Object catDict = xref->getCatalog();
582 if (catDict.isDict()) {
583 Object outputIntents = catDict.dictLookup("OutputIntents");
584 if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) {
585 Object firstElement = outputIntents.arrayGet(0);
586 if (firstElement.isDict()) {
587 Object profile = firstElement.dictLookup("DestOutputProfile");
588 if (profile.isStream()) {
589 Stream *iccStream = profile.getStream();
590 const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
591 auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size()));
592 if (!hp) {
593 error(errSyntaxWarning, -1, "read ICCBased color space profile error");
594 } else {
595 state->setDisplayProfile(hp);
596 }
597 }
598 }
599 }
600 }
601}
602
603#endif
604
605Gfx::~Gfx()
606{
607 while (stateGuards.size()) {
608 popStateGuard();
609 }
610 if (!subPage) {
611 out->endPage();
612 }
613 // There shouldn't be more saves, but pop them if there were any
614 while (state->hasSaves()) {
615 error(category: errSyntaxError, pos: -1, msg: "Found state under last state guard. Popping.");
616 restoreState();
617 }
618 delete state;
619 while (res) {
620 popResources();
621 }
622 while (mcStack) {
623 popMarkedContent();
624 }
625}
626
627void Gfx::display(Object *obj, bool topLevel)
628{
629 // check for excessive recursion
630 if (displayDepth > 100) {
631 return;
632 }
633
634 if (obj->isArray()) {
635 for (int i = 0; i < obj->arrayGetLength(); ++i) {
636 Object obj2 = obj->arrayGet(i);
637 if (!obj2.isStream()) {
638 error(category: errSyntaxError, pos: -1, msg: "Weird page contents");
639 return;
640 }
641 }
642 } else if (!obj->isStream()) {
643 error(category: errSyntaxError, pos: -1, msg: "Weird page contents");
644 return;
645 }
646 parser = new Parser(xref, obj, false);
647 go(topLevel);
648 delete parser;
649 parser = nullptr;
650}
651
652void Gfx::go(bool topLevel)
653{
654 Object obj;
655 Object args[maxArgs];
656 int numArgs, i;
657 int lastAbortCheck;
658
659 // scan a sequence of objects
660 pushStateGuard();
661 updateLevel = 1; // make sure even empty pages trigger a call to dump()
662 lastAbortCheck = 0;
663 numArgs = 0;
664 obj = parser->getObj();
665 while (!obj.isEOF()) {
666 commandAborted = false;
667
668 // got a command - execute it
669 if (obj.isCmd()) {
670 if (printCommands) {
671 obj.print(stdout);
672 for (i = 0; i < numArgs; ++i) {
673 printf(format: " ");
674 args[i].print(stdout);
675 }
676 printf(format: "\n");
677 fflush(stdout);
678 }
679 GooTimer *timer = nullptr;
680
681 if (unlikely(profileCommands)) {
682 timer = new GooTimer();
683 }
684
685 // Run the operation
686 execOp(cmd: &obj, args, numArgs);
687
688 // Update the profile information
689 if (unlikely(profileCommands)) {
690 if (auto *const hash = out->getProfileHash()) {
691 auto &data = (*hash)[obj.getCmd()];
692 data.addElement(elapsed: timer->getElapsed());
693 }
694 delete timer;
695 }
696 for (i = 0; i < numArgs; ++i) {
697 args[i].setToNull(); // Free memory early
698 }
699 numArgs = 0;
700
701 // periodically update display
702 if (++updateLevel >= 20000) {
703 out->dump();
704 updateLevel = 0;
705 lastAbortCheck = 0;
706 }
707
708 // did the command throw an exception
709 if (commandAborted) {
710 // don't propogate; recursive drawing comes from Form XObjects which
711 // should probably be drawn in a separate context anyway for caching
712 commandAborted = false;
713 break;
714 }
715
716 // check for an abort
717 if (abortCheckCbk) {
718 if (updateLevel - lastAbortCheck > 10) {
719 if ((*abortCheckCbk)(abortCheckCbkData)) {
720 break;
721 }
722 lastAbortCheck = updateLevel;
723 }
724 }
725
726 // got an argument - save it
727 } else if (numArgs < maxArgs) {
728 args[numArgs++] = std::move(obj);
729 // too many arguments - something is wrong
730 } else {
731 error(category: errSyntaxError, pos: getPos(), msg: "Too many args in content stream");
732 if (printCommands) {
733 printf(format: "throwing away arg: ");
734 obj.print(stdout);
735 printf(format: "\n");
736 fflush(stdout);
737 }
738 }
739
740 // grab the next object
741 obj = parser->getObj();
742 }
743
744 // args at end with no command
745 if (numArgs > 0) {
746 error(category: errSyntaxError, pos: getPos(), msg: "Leftover args in content stream");
747 if (printCommands) {
748 printf(format: "%d leftovers:", numArgs);
749 for (i = 0; i < numArgs; ++i) {
750 printf(format: " ");
751 args[i].print(stdout);
752 }
753 printf(format: "\n");
754 fflush(stdout);
755 }
756 }
757
758 popStateGuard();
759
760 // update display
761 if (topLevel && updateLevel > 0) {
762 out->dump();
763 }
764}
765
766void Gfx::execOp(Object *cmd, Object args[], int numArgs)
767{
768 const Operator *op;
769 Object *argPtr;
770 int i;
771
772 // find operator
773 const char *name = cmd->getCmd();
774 if (!(op = findOp(name))) {
775 if (ignoreUndef == 0) {
776 error(category: errSyntaxError, pos: getPos(), msg: "Unknown operator '{0:s}'", name);
777 }
778 return;
779 }
780
781 // type check args
782 argPtr = args;
783 if (op->numArgs >= 0) {
784 if (numArgs < op->numArgs) {
785 error(category: errSyntaxError, pos: getPos(), msg: "Too few ({0:d}) args to '{1:s}' operator", numArgs, name);
786 commandAborted = true;
787 return;
788 }
789 if (numArgs > op->numArgs) {
790#if 0
791 error(errSyntaxWarning, getPos(),
792 "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
793#endif
794 argPtr += numArgs - op->numArgs;
795 numArgs = op->numArgs;
796 }
797 } else {
798 if (numArgs > -op->numArgs) {
799 error(category: errSyntaxError, pos: getPos(), msg: "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
800 return;
801 }
802 }
803 for (i = 0; i < numArgs; ++i) {
804 if (!checkArg(arg: &argPtr[i], type: op->tchk[i])) {
805 error(category: errSyntaxError, pos: getPos(), msg: "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName());
806 return;
807 }
808 }
809
810 // do it
811 (this->*op->func)(argPtr, numArgs);
812}
813
814const Operator *Gfx::findOp(const char *name)
815{
816 int a, b, m, cmp;
817
818 a = -1;
819 b = numOps;
820 cmp = 0; // make gcc happy
821 // invariant: opTab[a] < name < opTab[b]
822 while (b - a > 1) {
823 m = (a + b) / 2;
824 cmp = strcmp(s1: opTab[m].name, s2: name);
825 if (cmp < 0) {
826 a = m;
827 } else if (cmp > 0) {
828 b = m;
829 } else {
830 a = b = m;
831 }
832 }
833 if (cmp != 0) {
834 return nullptr;
835 }
836 return &opTab[a];
837}
838
839bool Gfx::checkArg(Object *arg, TchkType type)
840{
841 switch (type) {
842 case tchkBool:
843 return arg->isBool();
844 case tchkInt:
845 return arg->isInt();
846 case tchkNum:
847 return arg->isNum();
848 case tchkString:
849 return arg->isString();
850 case tchkName:
851 return arg->isName();
852 case tchkArray:
853 return arg->isArray();
854 case tchkProps:
855 return arg->isDict() || arg->isName();
856 case tchkSCN:
857 return arg->isNum() || arg->isName();
858 case tchkNone:
859 return false;
860 }
861 return false;
862}
863
864Goffset Gfx::getPos()
865{
866 return parser ? parser->getPos() : -1;
867}
868
869//------------------------------------------------------------------------
870// graphics state operators
871//------------------------------------------------------------------------
872
873void Gfx::opSave(Object args[], int numArgs)
874{
875 saveState();
876}
877
878void Gfx::opRestore(Object args[], int numArgs)
879{
880 restoreState();
881}
882
883void Gfx::opConcat(Object args[], int numArgs)
884{
885 state->concatCTM(a: args[0].getNum(), b: args[1].getNum(), c: args[2].getNum(), d: args[3].getNum(), e: args[4].getNum(), f: args[5].getNum());
886 out->updateCTM(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
887 fontChanged = true;
888}
889
890void Gfx::opSetDash(Object args[], int numArgs)
891{
892 const Array *a = args[0].getArray();
893 int length = a->getLength();
894 std::vector<double> dash(length);
895 for (int i = 0; i < length; ++i) {
896 dash[i] = a->get(i).getNumWithDefaultValue(defaultValue: 0);
897 }
898 state->setLineDash(dash: std::move(dash), start: args[1].getNum());
899 out->updateLineDash(state);
900}
901
902void Gfx::opSetFlat(Object args[], int numArgs)
903{
904 state->setFlatness((int)args[0].getNum());
905 out->updateFlatness(state);
906}
907
908void Gfx::opSetLineJoin(Object args[], int numArgs)
909{
910 state->setLineJoin(args[0].getInt());
911 out->updateLineJoin(state);
912}
913
914void Gfx::opSetLineCap(Object args[], int numArgs)
915{
916 state->setLineCap(args[0].getInt());
917 out->updateLineCap(state);
918}
919
920void Gfx::opSetMiterLimit(Object args[], int numArgs)
921{
922 state->setMiterLimit(args[0].getNum());
923 out->updateMiterLimit(state);
924}
925
926void Gfx::opSetLineWidth(Object args[], int numArgs)
927{
928 state->setLineWidth(args[0].getNum());
929 out->updateLineWidth(state);
930}
931
932void 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(name: args[0].getName());
943 if (obj1.isNull()) {
944 return;
945 }
946 if (!obj1.isDict()) {
947 error(category: errSyntaxError, pos: getPos(), msg: "ExtGState '{0:s}' is wrong type", args[0].getName());
948 return;
949 }
950 if (printCommands) {
951 printf(format: " gfx state dict: ");
952 obj1.print();
953 printf(format: "\n");
954 }
955
956 // parameters that are also set by individual PDF operators
957 obj2 = obj1.dictLookup(key: "LW");
958 if (obj2.isNum()) {
959 opSetLineWidth(args: &obj2, numArgs: 1);
960 }
961 obj2 = obj1.dictLookup(key: "LC");
962 if (obj2.isInt()) {
963 opSetLineCap(args: &obj2, numArgs: 1);
964 }
965 obj2 = obj1.dictLookup(key: "LJ");
966 if (obj2.isInt()) {
967 opSetLineJoin(args: &obj2, numArgs: 1);
968 }
969 obj2 = obj1.dictLookup(key: "ML");
970 if (obj2.isNum()) {
971 opSetMiterLimit(args: &obj2, numArgs: 1);
972 }
973 obj2 = obj1.dictLookup(key: "D");
974 if (obj2.isArray() && obj2.arrayGetLength() == 2) {
975 Object args2[2];
976 args2[0] = obj2.arrayGet(i: 0);
977 args2[1] = obj2.arrayGet(i: 1);
978 if (args2[0].isArray() && args2[1].isNum()) {
979 opSetDash(args: args2, numArgs: 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(key: "FL");
997 if (obj2.isNum()) {
998 opSetFlat(args: &obj2, numArgs: 1);
999 }
1000
1001 // transparency support: blend mode, fill/stroke opacity
1002 obj2 = obj1.dictLookup(key: "BM");
1003 if (!obj2.isNull()) {
1004 if (state->parseBlendMode(obj: &obj2, mode: &mode)) {
1005 state->setBlendMode(mode);
1006 out->updateBlendMode(state);
1007 } else {
1008 error(category: errSyntaxError, pos: getPos(), msg: "Invalid blend mode in ExtGState");
1009 }
1010 }
1011 obj2 = obj1.dictLookup(key: "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(key: "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(key: "op");
1026 if ((haveFillOP = obj2.isBool())) {
1027 state->setFillOverprint(obj2.getBool());
1028 out->updateFillOverprint(state);
1029 }
1030 obj2 = obj1.dictLookup(key: "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(key: "OPM");
1040 if (obj2.isInt()) {
1041 state->setOverprintMode(obj2.getInt());
1042 out->updateOverprintMode(state);
1043 }
1044
1045 // stroke adjust
1046 obj2 = obj1.dictLookup(key: "SA");
1047 if (obj2.isBool()) {
1048 state->setStrokeAdjust(obj2.getBool());
1049 out->updateStrokeAdjust(state);
1050 }
1051
1052 // transfer function
1053 obj2 = obj1.dictLookup(key: "TR2");
1054 if (obj2.isNull()) {
1055 obj2 = obj1.dictLookup(key: "TR");
1056 }
1057 if (obj2.isName(nameA: "Default") || obj2.isName(nameA: "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(funcObj: &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(funcObj: &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(category: errSyntaxError, pos: getPos(), msg: "Invalid transfer function in ExtGState");
1087 }
1088
1089 // alpha is shape
1090 obj2 = obj1.dictLookup(key: "AIS");
1091 if (obj2.isBool()) {
1092 state->setAlphaIsShape(obj2.getBool());
1093 out->updateAlphaIsShape(state);
1094 }
1095
1096 // text knockout
1097 obj2 = obj1.dictLookup(key: "TK");
1098 if (obj2.isBool()) {
1099 state->setTextKnockout(obj2.getBool());
1100 out->updateTextKnockout(state);
1101 }
1102
1103 // soft mask
1104 obj2 = obj1.dictLookup(key: "SMask");
1105 if (!obj2.isNull()) {
1106 if (obj2.isName(nameA: "None")) {
1107 out->clearSoftMask(state);
1108 } else if (obj2.isDict()) {
1109 Object obj3 = obj2.dictLookup(key: "S");
1110 if (obj3.isName(nameA: "Alpha")) {
1111 alpha = true;
1112 } else { // "Luminosity"
1113 alpha = false;
1114 }
1115 Function *softMaskTransferFunc = nullptr;
1116 obj3 = obj2.dictLookup(key: "TR");
1117 if (!obj3.isNull()) {
1118 if (obj3.isName(nameA: "Default") || obj3.isName(nameA: "Identity")) {
1119 // nothing
1120 } else {
1121 softMaskTransferFunc = Function::parse(funcObj: &obj3);
1122 if (softMaskTransferFunc == nullptr || softMaskTransferFunc->getInputSize() != 1 || softMaskTransferFunc->getOutputSize() != 1) {
1123 error(category: errSyntaxError, pos: getPos(), msg: "Invalid transfer function in soft mask in ExtGState");
1124 delete softMaskTransferFunc;
1125 softMaskTransferFunc = nullptr;
1126 }
1127 }
1128 }
1129 obj3 = obj2.dictLookup(key: "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(x: obj4.getNum());
1138 }
1139 }
1140 }
1141 obj3 = obj2.dictLookup(key: "G");
1142 if (obj3.isStream()) {
1143 Object obj4 = obj3.streamGetDict()->lookup(key: "Group");
1144 if (obj4.isDict()) {
1145 GfxColorSpace *blendingColorSpace = nullptr;
1146 Object obj5 = obj4.dictLookup(key: "CS");
1147 if (!obj5.isNull()) {
1148 blendingColorSpace = GfxColorSpace::parse(res, csObj: &obj5, out, state);
1149 }
1150 const bool isolated = obj4.dictLookup(key: "I").getBoolWithDefaultValue(defaultValue: false);
1151 const bool knockout = obj4.dictLookup(key: "K").getBoolWithDefaultValue(defaultValue: false);
1152 if (!haveBackdropColor) {
1153 if (blendingColorSpace) {
1154 blendingColorSpace->getDefaultColor(color: &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(str: &obj3, alpha, blendingColorSpace, isolated, knockout, transferFunc: softMaskTransferFunc, backdropColor: &backdropColor);
1163 delete blendingColorSpace;
1164 } else {
1165 error(category: errSyntaxError, pos: getPos(), msg: "Invalid soft mask in ExtGState - missing group");
1166 }
1167 } else {
1168 error(category: errSyntaxError, pos: getPos(), msg: "Invalid soft mask in ExtGState - missing group");
1169 }
1170 delete softMaskTransferFunc;
1171 } else if (!obj2.isNull()) {
1172 error(category: errSyntaxError, pos: getPos(), msg: "Invalid soft mask in ExtGState");
1173 }
1174 }
1175 obj2 = obj1.dictLookup(key: "Font");
1176 if (obj2.isArray()) {
1177 if (obj2.arrayGetLength() == 2) {
1178 const Object &fargs0 = obj2.arrayGetNF(i: 0);
1179 Object fargs1 = obj2.arrayGet(i: 1);
1180 if (fargs0.isRef() && fargs1.isNum()) {
1181 Object fobj = fargs0.fetch(xref);
1182 if (fobj.isDict()) {
1183 Ref r = fargs0.getRef();
1184 std::shared_ptr<GfxFont> font = GfxFont::makeFont(xref, tagA: args[0].getName(), idA: r, fontDict: fobj.getDict());
1185 state->setFont(fontA: font, fontSizeA: fargs1.getNum());
1186 fontChanged = true;
1187 }
1188 }
1189 } else {
1190 error(category: errSyntaxError, pos: getPos(), msg: "Number of args mismatch for /Font in ExtGState");
1191 }
1192 }
1193 obj2 = obj1.dictLookup(key: "LW");
1194 if (obj2.isNum()) {
1195 opSetLineWidth(args: &obj2, numArgs: 1);
1196 }
1197 obj2 = obj1.dictLookup(key: "LC");
1198 if (obj2.isInt()) {
1199 opSetLineCap(args: &obj2, numArgs: 1);
1200 }
1201 obj2 = obj1.dictLookup(key: "LJ");
1202 if (obj2.isInt()) {
1203 opSetLineJoin(args: &obj2, numArgs: 1);
1204 }
1205 obj2 = obj1.dictLookup(key: "ML");
1206 if (obj2.isNum()) {
1207 opSetMiterLimit(args: &obj2, numArgs: 1);
1208 }
1209 obj2 = obj1.dictLookup(key: "D");
1210 if (obj2.isArray()) {
1211 if (obj2.arrayGetLength() == 2) {
1212 Object dargs[2];
1213
1214 dargs[0] = obj2.arrayGetNF(i: 0).copy();
1215 dargs[1] = obj2.arrayGet(i: 1);
1216 if (dargs[0].isArray() && dargs[1].isInt()) {
1217 opSetDash(args: dargs, numArgs: 2);
1218 }
1219 } else {
1220 error(category: errSyntaxError, pos: getPos(), msg: "Number of args mismatch for /D in ExtGState");
1221 }
1222 }
1223 obj2 = obj1.dictLookup(key: "RI");
1224 if (obj2.isName()) {
1225 opSetRenderingIntent(args: &obj2, numArgs: 1);
1226 }
1227 obj2 = obj1.dictLookup(key: "FL");
1228 if (obj2.isNum()) {
1229 opSetFlat(args: &obj2, numArgs: 1);
1230 }
1231}
1232
1233void Gfx::doSoftMask(Object *str, bool alpha, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, Function *transferFunc, GfxColor *backdropColor)
1234{
1235 Dict *dict, *resDict;
1236 double m[6], bbox[4];
1237 Object obj1;
1238 int i;
1239
1240 // get stream dict
1241 dict = str->streamGetDict();
1242
1243 // check form type
1244 obj1 = dict->lookup(key: "FormType");
1245 if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
1246 error(category: errSyntaxError, pos: getPos(), msg: "Unknown form type");
1247 }
1248
1249 // get bounding box
1250 obj1 = dict->lookup(key: "BBox");
1251 if (!obj1.isArray()) {
1252 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box");
1253 return;
1254 }
1255 for (i = 0; i < 4; ++i) {
1256 Object obj2 = obj1.arrayGet(i);
1257 if (likely(obj2.isNum())) {
1258 bbox[i] = obj2.getNum();
1259 } else {
1260 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box (non number)");
1261 return;
1262 }
1263 }
1264
1265 // get matrix
1266 obj1 = dict->lookup(key: "Matrix");
1267 if (obj1.isArray()) {
1268 for (i = 0; i < 6; ++i) {
1269 Object obj2 = obj1.arrayGet(i);
1270 if (likely(obj2.isNum())) {
1271 m[i] = obj2.getNum();
1272 } else {
1273 m[i] = 0;
1274 }
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(key: "Resources");
1287 resDict = obj1.isDict() ? obj1.getDict() : nullptr;
1288
1289 // draw it
1290 drawForm(str, resDict, matrix: m, bbox, transpGroup: true, softMask: true, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor);
1291}
1292
1293void Gfx::opSetRenderingIntent(Object args[], int numArgs)
1294{
1295 state->setRenderingIntent(args[0].getName());
1296}
1297
1298//------------------------------------------------------------------------
1299// color operators
1300//------------------------------------------------------------------------
1301
1302void Gfx::opSetFillGray(Object args[], int numArgs)
1303{
1304 GfxColor color;
1305 GfxColorSpace *colorSpace = nullptr;
1306
1307 state->setFillPattern(nullptr);
1308 Object obj = res->lookupColorSpace(name: "DefaultGray");
1309 if (!obj.isNull()) {
1310 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[0].getNum());
1319 state->setFillColor(&color);
1320 out->updateFillColor(state);
1321}
1322
1323void Gfx::opSetStrokeGray(Object args[], int numArgs)
1324{
1325 GfxColor color;
1326 GfxColorSpace *colorSpace = nullptr;
1327
1328 state->setStrokePattern(nullptr);
1329 Object obj = res->lookupColorSpace(name: "DefaultGray");
1330 if (!obj.isNull()) {
1331 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[0].getNum());
1339 state->setStrokeColor(&color);
1340 out->updateStrokeColor(state);
1341}
1342
1343void Gfx::opSetFillCMYKColor(Object args[], int numArgs)
1344{
1345 GfxColor color;
1346 GfxColorSpace *colorSpace = nullptr;
1347 int i;
1348
1349 Object obj = res->lookupColorSpace(name: "DefaultCMYK");
1350 if (!obj.isNull()) {
1351 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[i].getNum());
1361 }
1362 state->setFillColor(&color);
1363 out->updateFillColor(state);
1364}
1365
1366void 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(name: "DefaultCMYK");
1374 if (!obj.isNull()) {
1375 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[i].getNum());
1384 }
1385 state->setStrokeColor(&color);
1386 out->updateStrokeColor(state);
1387}
1388
1389void 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(name: "DefaultRGB");
1397 if (!obj.isNull()) {
1398 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[i].getNum());
1408 }
1409 state->setFillColor(&color);
1410 out->updateFillColor(state);
1411}
1412
1413void 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(name: "DefaultRGB");
1421 if (!obj.isNull()) {
1422 colorSpace = GfxColorSpace::parse(res, csObj: &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(x: args[i].getNum());
1431 }
1432 state->setStrokeColor(&color);
1433 out->updateStrokeColor(state);
1434}
1435
1436void Gfx::opSetFillColorSpace(Object args[], int numArgs)
1437{
1438 GfxColorSpace *colorSpace;
1439 GfxColor color;
1440
1441 Object obj = res->lookupColorSpace(name: args[0].getName());
1442 if (obj.isNull()) {
1443 colorSpace = GfxColorSpace::parse(res, csObj: &args[0], out, state);
1444 } else {
1445 colorSpace = GfxColorSpace::parse(res, csObj: &obj, out, state);
1446 }
1447 if (colorSpace) {
1448 state->setFillPattern(nullptr);
1449 state->setFillColorSpace(colorSpace);
1450 out->updateFillColorSpace(state);
1451 colorSpace->getDefaultColor(color: &color);
1452 state->setFillColor(&color);
1453 out->updateFillColor(state);
1454 } else {
1455 error(category: errSyntaxError, pos: getPos(), msg: "Bad color space (fill)");
1456 }
1457}
1458
1459void Gfx::opSetStrokeColorSpace(Object args[], int numArgs)
1460{
1461 GfxColorSpace *colorSpace;
1462 GfxColor color;
1463
1464 state->setStrokePattern(nullptr);
1465 Object obj = res->lookupColorSpace(name: args[0].getName());
1466 if (obj.isNull()) {
1467 colorSpace = GfxColorSpace::parse(res, csObj: &args[0], out, state);
1468 } else {
1469 colorSpace = GfxColorSpace::parse(res, csObj: &obj, out, state);
1470 }
1471 if (colorSpace) {
1472 state->setStrokeColorSpace(colorSpace);
1473 out->updateStrokeColorSpace(state);
1474 colorSpace->getDefaultColor(color: &color);
1475 state->setStrokeColor(&color);
1476 out->updateStrokeColor(state);
1477 } else {
1478 error(category: errSyntaxError, pos: getPos(), msg: "Bad color space (stroke)");
1479 }
1480}
1481
1482void Gfx::opSetFillColor(Object args[], int numArgs)
1483{
1484 GfxColor color;
1485 int i;
1486
1487 if (numArgs != state->getFillColorSpace()->getNComps()) {
1488 error(category: errSyntaxError, pos: getPos(), msg: "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(x: args[i].getNum());
1494 }
1495 state->setFillColor(&color);
1496 out->updateFillColor(state);
1497}
1498
1499void Gfx::opSetStrokeColor(Object args[], int numArgs)
1500{
1501 GfxColor color;
1502 int i;
1503
1504 if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1505 error(category: errSyntaxError, pos: getPos(), msg: "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(x: args[i].getNum());
1511 }
1512 state->setStrokeColor(&color);
1513 out->updateStrokeColor(state);
1514}
1515
1516void 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(category: errSyntaxError, pos: getPos(), msg: "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(x: 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(name: args[numArgs - 1].getName(), out, state))) {
1540 state->setFillPattern(pattern);
1541 }
1542 }
1543
1544 } else {
1545 if (numArgs != state->getFillColorSpace()->getNComps()) {
1546 error(category: errSyntaxError, pos: getPos(), msg: "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(x: 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
1562void 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(category: errSyntaxError, pos: getPos(), msg: "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(x: 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(category: errSyntaxError, pos: getPos(), msg: "Incorrect number of arguments in 'SCN' command");
1586 return;
1587 }
1588 if (args[numArgs - 1].isName() && (pattern = res->lookupPattern(name: args[numArgs - 1].getName(), out, state))) {
1589 state->setStrokePattern(pattern);
1590 }
1591
1592 } else {
1593 if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1594 error(category: errSyntaxError, pos: getPos(), msg: "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(x: 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
1614void Gfx::opMoveTo(Object args[], int numArgs)
1615{
1616 state->moveTo(x: args[0].getNum(), y: args[1].getNum());
1617}
1618
1619void Gfx::opLineTo(Object args[], int numArgs)
1620{
1621 if (!state->isCurPt()) {
1622 error(category: errSyntaxError, pos: getPos(), msg: "No current point in lineto");
1623 return;
1624 }
1625 state->lineTo(x: args[0].getNum(), y: args[1].getNum());
1626}
1627
1628void Gfx::opCurveTo(Object args[], int numArgs)
1629{
1630 double x1, y1, x2, y2, x3, y3;
1631
1632 if (!state->isCurPt()) {
1633 error(category: errSyntaxError, pos: getPos(), msg: "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
1645void Gfx::opCurveTo1(Object args[], int numArgs)
1646{
1647 double x1, y1, x2, y2, x3, y3;
1648
1649 if (!state->isCurPt()) {
1650 error(category: errSyntaxError, pos: getPos(), msg: "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
1662void Gfx::opCurveTo2(Object args[], int numArgs)
1663{
1664 double x1, y1, x2, y2, x3, y3;
1665
1666 if (!state->isCurPt()) {
1667 error(category: errSyntaxError, pos: getPos(), msg: "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
1679void 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: x + w, y);
1689 state->lineTo(x: x + w, y: y + h);
1690 state->lineTo(x, y: y + h);
1691 state->closePath();
1692}
1693
1694void Gfx::opClosePath(Object args[], int numArgs)
1695{
1696 if (!state->isCurPt()) {
1697 error(category: errSyntaxError, pos: getPos(), msg: "No current point in closepath");
1698 return;
1699 }
1700 state->closePath();
1701}
1702
1703//------------------------------------------------------------------------
1704// path painting operators
1705//------------------------------------------------------------------------
1706
1707void Gfx::opEndPath(Object args[], int numArgs)
1708{
1709 doEndPath();
1710}
1711
1712void 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
1730void 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
1749void 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(eoFill: false);
1759 } else {
1760 out->fill(state);
1761 }
1762 }
1763 }
1764 doEndPath();
1765}
1766
1767void 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(eoFill: true);
1777 } else {
1778 out->eoFill(state);
1779 }
1780 }
1781 }
1782 doEndPath();
1783}
1784
1785void 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(eoFill: 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
1808void 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(eoFill: 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
1832void 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(eoFill: 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
1855void 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(eoFill: 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
1879void 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(tPat: (GfxTilingPattern *)pattern, stroke: false, eoFill, text: false);
1896 break;
1897 case 2:
1898 doShadingPatternFill(sPat: (GfxShadingPattern *)pattern, stroke: false, eoFill, text: false);
1899 break;
1900 default:
1901 error(category: errSyntaxError, pos: getPos(), msg: "Unknown pattern type ({0:d}) in fill", pattern->getType());
1902 break;
1903 }
1904}
1905
1906void 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(tPat: (GfxTilingPattern *)pattern, stroke: true, eoFill: false, text: false);
1923 break;
1924 case 2:
1925 doShadingPatternFill(sPat: (GfxShadingPattern *)pattern, stroke: true, eoFill: false, text: false);
1926 break;
1927 default:
1928 error(category: errSyntaxError, pos: getPos(), msg: "Unknown pattern type ({0:d}) in stroke", pattern->getType());
1929 break;
1930 }
1931}
1932
1933void 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(tPat: (GfxTilingPattern *)pattern, stroke: false, eoFill: false, text: true);
1950 break;
1951 case 2:
1952 doShadingPatternFill(sPat: (GfxShadingPattern *)pattern, stroke: false, eoFill: false, text: true);
1953 break;
1954 default:
1955 error(category: errSyntaxError, pos: getPos(), msg: "Unknown pattern type ({0:d}) in fill", pattern->getType());
1956 break;
1957 }
1958}
1959
1960void 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(x: 0, y: 0);
1968 state->lineTo(x: 1, y: 0);
1969 state->lineTo(x: 1, y: 1);
1970 state->lineTo(x: 0, y: 1);
1971 state->closePath();
1972 doPatternText();
1973
1974 out->unsetSoftMaskFromImageMask(state, baseMatrix);
1975 restoreState();
1976}
1977
1978void 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(x: det) < 0.000001) {
2003 error(category: errSyntaxError, pos: getPos(), msg: "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(x: det)) {
2032 error(category: errSyntaxError, pos: getPos(), msg: "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: &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(xMin: &cxMin, yMin: &cyMin, xMax: &cxMax, yMax: &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(x: tPat->getXStep());
2141 ystep = fabs(x: 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(x: (xMin - tPat->getBBox()[2]) / xstep);
2147 xi1 = (int)floor(x: (xMax - tPat->getBBox()[0]) / xstep) + 1;
2148 } else {
2149 xi0 = (int)ceil(x: (xMin - tPat->getBBox()[0]) / xstep);
2150 xi1 = (int)floor(x: (xMax - tPat->getBBox()[2]) / xstep) + 1;
2151 }
2152 if (tPat->getBBox()[1] < tPat->getBBox()[3]) {
2153 yi0 = (int)ceil(x: (yMin - tPat->getBBox()[3]) / ystep);
2154 yi1 = (int)floor(x: (yMax - tPat->getBBox()[1]) / ystep) + 1;
2155 } else {
2156 yi0 = (int)ceil(x: (yMin - tPat->getBBox()[1]) / ystep);
2157 yi1 = (int)floor(x: (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(x: patternRefNum) == formsDrawing.end()) {
2170 patternRefIt = formsDrawing.insert(x: 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(str: tPat->getContentStream(), resDict: tPat->getResDict(), matrix: m1, bbox: tPat->getBBox());
2187 }
2188 }
2189 out->clearPatternOpacity(state);
2190 }
2191 if (patternRefNum != -1) {
2192 formsDrawing.erase(position: patternRefIt);
2193 }
2194 }
2195 }
2196
2197 // restore graphics state
2198restore:
2199 restoreStateStack(oldState: savedState);
2200}
2201
2202void 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(x: det) < 0.000001) {
2237 error(category: errSyntaxError, pos: getPos(), msg: "Singular matrix in shading pattern fill");
2238 restoreStateStack(oldState: 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(a: m[0], b: m[1], c: m[2], d: m[3], e: m[4], f: 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(xMinA: &xMin, yMinA: &yMin, xMaxA: &xMax, yMaxA: &yMax);
2270 state->moveTo(x: xMin, y: yMin);
2271 state->lineTo(x: xMax, y: yMin);
2272 state->lineTo(x: xMax, y: yMax);
2273 state->lineTo(x: xMin, y: 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: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
2289 state->moveTo(x: xMin, y: yMin);
2290 state->lineTo(x: xMax, y: yMin);
2291 state->lineTo(x: xMax, y: yMax);
2292 state->lineTo(x: xMin, y: 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(shading: (GfxFunctionShading *)shading);
2309 break;
2310 case 2:
2311 doAxialShFill(shading: (GfxAxialShading *)shading);
2312 break;
2313 case 3:
2314 doRadialShFill(shading: (GfxRadialShading *)shading);
2315 break;
2316 case 4:
2317 case 5:
2318 doGouraudTriangleShFill(shading: (GfxGouraudTriangleShading *)shading);
2319 break;
2320 case 6:
2321 case 7:
2322 doPatchMeshShFill(shading: (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(oldState: savedState);
2334}
2335
2336void 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(name: 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(xMinA: &xMin, yMinA: &yMin, xMaxA: &xMax, yMaxA: &yMax);
2356 state->moveTo(x: xMin, y: yMin);
2357 state->lineTo(x: xMax, y: yMin);
2358 state->lineTo(x: xMax, y: yMax);
2359 state->lineTo(x: xMin, y: 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(shading: (GfxFunctionShading *)shading);
2381 break;
2382 case 2:
2383 doAxialShFill(shading: (GfxAxialShading *)shading);
2384 break;
2385 case 3:
2386 doRadialShFill(shading: (GfxRadialShading *)shading);
2387 break;
2388 case 4:
2389 case 5:
2390 doGouraudTriangleShFill(shading: (GfxGouraudTriangleShading *)shading);
2391 break;
2392 case 6:
2393 case 7:
2394 doPatchMeshShFill(shading: (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(oldState: savedState);
2406
2407 delete shading;
2408}
2409
2410void Gfx::doFunctionShFill(GfxFunctionShading *shading)
2411{
2412 double x0, y0, x1, y1;
2413 GfxColor colors[4];
2414
2415 if (out->useShadedFills(type: shading->getType()) && out->functionShadedFill(state, shading)) {
2416 return;
2417 }
2418
2419 shading->getDomain(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1);
2420 shading->getColor(x: x0, y: y0, color: &colors[0]);
2421 shading->getColor(x: x0, y: y1, color: &colors[1]);
2422 shading->getColor(x: x1, y: y0, color: &colors[2]);
2423 shading->getColor(x: x1, y: y1, color: &colors[3]);
2424 doFunctionShFill1(shading, x0, y0, x1, y1, colors, depth: 0);
2425}
2426
2427void 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(x: 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(x: xM, y: yM, color: &fillColor);
2462 state->setFillColor(&fillColor);
2463 out->updateFillColor(state);
2464
2465 // fill the rectangle
2466 state->moveTo(x: x0 * matrix[0] + y0 * matrix[2] + matrix[4], y: x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
2467 state->lineTo(x: x1 * matrix[0] + y0 * matrix[2] + matrix[4], y: x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
2468 state->lineTo(x: x1 * matrix[0] + y1 * matrix[2] + matrix[4], y: x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
2469 state->lineTo(x: x0 * matrix[0] + y1 * matrix[2] + matrix[4], y: 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(x: x0, y: yM, color: &color0M);
2493 shading->getColor(x: x1, y: yM, color: &color1M);
2494 shading->getColor(x: xM, y: y0, color: &colorM0);
2495 shading->getColor(x: xM, y: y1, color: &colorM1);
2496 shading->getColor(x: xM, y: yM, color: &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, x1: xM, y1: yM, colors: colors2, depth: 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, y0: yM, x1: xM, y1, colors: colors2, depth: 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, x0: xM, y0, x1, y1: yM, colors: colors2, depth: 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, x0: xM, y0: yM, x1, y1, colors: colors2, depth: depth + 1);
2525 }
2526}
2527
2528void 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: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
2548
2549 // compute min and max t values, based on the four corners of the
2550 // clip region bbox
2551 shading->getCoords(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1);
2552 dx = x1 - x0;
2553 dy = y1 - y0;
2554 dxZero = fabs(x: dx) < 0.01;
2555 dyZero = fabs(x: 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(first: std::begin(arr&: bboxIntersections), last: std::end(arr&: 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(type: 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(t: tt, color: &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(first: std::begin(arr&: s), last: std::end(arr&: 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(t: tt, color: &color1);
2710 bool isPatchOfConstantColor = isSameGfxColor(colorA: color1, colorB: 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(t: x, color: &tmpColor);
2719 if (!isSameGfxColor(colorA: tmpColor, colorB: 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 }
2739 if (teoricalj < j) {
2740 next[i] = teoricalj;
2741 next[teoricalj] = j;
2742 } else {
2743 teoricalj = j;
2744 }
2745 ta[teoricalj] = bboxIntersections[1];
2746 j = teoricalj;
2747 doneBBox1 = true;
2748 }
2749 if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) {
2750 int teoricalj = (int)((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin));
2751 if (teoricalj <= i) {
2752 teoricalj = i + 1;
2753 }
2754 if (teoricalj < j) {
2755 next[i] = teoricalj;
2756 next[teoricalj] = j;
2757 } else {
2758 teoricalj = j;
2759 }
2760 ta[teoricalj] = bboxIntersections[2];
2761 j = teoricalj;
2762 doneBBox2 = true;
2763 }
2764 break;
2765 }
2766 k = (i + j) / 2;
2767 ta[k] = 0.5 * (ta[i] + ta[j]);
2768 next[i] = k;
2769 next[k] = j;
2770 j = k;
2771 }
2772
2773 // use the average of the colors of the two sides of the region
2774 for (k = 0; k < nComps; ++k) {
2775 color0.c[k] = safeAverage(a: color0.c[k], b: color1.c[k]);
2776 }
2777
2778 // compute the coordinates of the point on the t axis; then
2779 // compute the intersection of the perpendicular line with the
2780 // bounding box
2781 tx = x0 + ta[j] * dx;
2782 ty = y0 + ta[j] * dy;
2783 if (dxZero && dyZero) {
2784 sMin = sMax = 0;
2785 } else if (dxZero) {
2786 sMin = (xMin - tx) / -dy;
2787 sMax = (xMax - tx) / -dy;
2788 if (sMin > sMax) {
2789 tmp = sMin;
2790 sMin = sMax;
2791 sMax = tmp;
2792 }
2793 } else if (dyZero) {
2794 sMin = (yMin - ty) / dx;
2795 sMax = (yMax - ty) / dx;
2796 if (sMin > sMax) {
2797 tmp = sMin;
2798 sMin = sMax;
2799 sMax = tmp;
2800 }
2801 } else {
2802 s[0] = (yMin - ty) / dx;
2803 s[1] = (yMax - ty) / dx;
2804 s[2] = (xMin - tx) / -dy;
2805 s[3] = (xMax - tx) / -dy;
2806 std::sort(first: std::begin(arr&: s), last: std::end(arr&: s));
2807 sMin = s[1];
2808 sMax = s[2];
2809 }
2810 ux1 = tx - sMin * dy;
2811 uy1 = ty + sMin * dx;
2812 vx1 = tx - sMax * dy;
2813 vy1 = ty + sMax * dx;
2814
2815 // set the color
2816 state->setFillColor(&color0);
2817 if (out->useFillColorStop()) {
2818 out->updateFillColorStop(state, (ta[j] - tMin) / (tMax - tMin));
2819 } else {
2820 out->updateFillColor(state);
2821 }
2822
2823 if (needExtend) {
2824 // fill the region
2825 state->moveTo(x: ux0, y: uy0);
2826 state->lineTo(x: vx0, y: vy0);
2827 state->lineTo(x: vx1, y: vy1);
2828 state->lineTo(x: ux1, y: uy1);
2829 state->closePath();
2830 }
2831
2832 if (!out->useFillColorStop()) {
2833 out->fill(state);
2834 state->clearPath();
2835 }
2836
2837 // set up for next region
2838 ux0 = ux1;
2839 uy0 = uy1;
2840 vx0 = vx1;
2841 vy0 = vy1;
2842 color0 = color1;
2843 i = next[i];
2844 }
2845
2846 if (out->useFillColorStop()) {
2847 if (!needExtend) {
2848 state->moveTo(x: xMin, y: yMin);
2849 state->lineTo(x: xMin, y: yMax);
2850 state->lineTo(x: xMax, y: yMax);
2851 state->lineTo(x: xMax, y: yMin);
2852 state->closePath();
2853 }
2854 out->fill(state);
2855 state->clearPath();
2856 }
2857}
2858
2859static inline void getShadingColorRadialHelper(double t0, double t1, double t, GfxRadialShading *shading, GfxColor *color)
2860{
2861 if (t0 < t1) {
2862 if (t < t0) {
2863 shading->getColor(t: t0, color);
2864 } else if (t > t1) {
2865 shading->getColor(t: t1, color);
2866 } else {
2867 shading->getColor(t, color);
2868 }
2869 } else {
2870 if (t > t0) {
2871 shading->getColor(t: t0, color);
2872 } else if (t < t1) {
2873 shading->getColor(t: t1, color);
2874 } else {
2875 shading->getColor(t, color);
2876 }
2877 }
2878}
2879
2880void Gfx::doRadialShFill(GfxRadialShading *shading)
2881{
2882 double xMin, yMin, xMax, yMax;
2883 double x0, y0, r0, x1, y1, r1, t0, t1;
2884 int nComps;
2885 GfxColor colorA = {}, colorB = {}, colorC = {};
2886 double xa, ya, xb, yb, ra, rb;
2887 double ta, tb, sa, sb;
2888 double sz, xz, yz, sMin, sMax;
2889 bool enclosed;
2890 int ia, ib, k, n;
2891 double theta, alpha, angle, t;
2892 bool needExtend = true;
2893
2894 // get the shading info
2895 shading->getCoords(x0A: &x0, y0A: &y0, r0A: &r0, x1A: &x1, y1A: &y1, r1A: &r1);
2896 t0 = shading->getDomain0();
2897 t1 = shading->getDomain1();
2898 nComps = shading->getColorSpace()->getNComps();
2899
2900 // Compute the point at which r(s) = 0; check for the enclosed
2901 // circles case; and compute the angles for the tangent lines.
2902 if (x0 == x1 && y0 == y1) {
2903 enclosed = true;
2904 theta = 0; // make gcc happy
2905 sz = 0; // make gcc happy
2906 } else if (r0 == r1) {
2907 enclosed = false;
2908 theta = 0;
2909 sz = 0; // make gcc happy
2910 } else {
2911 sz = (r1 > r0) ? -r0 / (r1 - r0) : -r1 / (r0 - r1);
2912 xz = x0 + sz * (x1 - x0);
2913 yz = y0 + sz * (y1 - y0);
2914 enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
2915 const double theta_aux = sqrt(x: (x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz));
2916 if (likely(theta_aux != 0)) {
2917 theta = asin(x: r0 / theta_aux);
2918 } else {
2919 theta = 0;
2920 }
2921 if (r0 > r1) {
2922 theta = -theta;
2923 }
2924 }
2925 if (enclosed) {
2926 alpha = 0;
2927 } else {
2928 alpha = atan2(y: y1 - y0, x: x1 - x0);
2929 }
2930
2931 // compute the (possibly extended) s range
2932 state->getUserClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
2933 if (enclosed) {
2934 sMin = 0;
2935 sMax = 1;
2936 } else {
2937 sMin = 1;
2938 sMax = 0;
2939 // solve for x(s) + r(s) = xMin
2940 if ((x1 + r1) - (x0 + r0) != 0) {
2941 sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
2942 if (sa < sMin) {
2943 sMin = sa;
2944 } else if (sa > sMax) {
2945 sMax = sa;
2946 }
2947 }
2948 // solve for x(s) - r(s) = xMax
2949 if ((x1 - r1) - (x0 - r0) != 0) {
2950 sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
2951 if (sa < sMin) {
2952 sMin = sa;
2953 } else if (sa > sMax) {
2954 sMax = sa;
2955 }
2956 }
2957 // solve for y(s) + r(s) = yMin
2958 if ((y1 + r1) - (y0 + r0) != 0) {
2959 sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
2960 if (sa < sMin) {
2961 sMin = sa;
2962 } else if (sa > sMax) {
2963 sMax = sa;
2964 }
2965 }
2966 // solve for y(s) - r(s) = yMax
2967 if ((y1 - r1) - (y0 - r0) != 0) {
2968 sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
2969 if (sa < sMin) {
2970 sMin = sa;
2971 } else if (sa > sMax) {
2972 sMax = sa;
2973 }
2974 }
2975 // check against sz
2976 if (r0 < r1) {
2977 if (sMin < sz) {
2978 sMin = sz;
2979 }
2980 } else if (r0 > r1) {
2981 if (sMax > sz) {
2982 sMax = sz;
2983 }
2984 }
2985 // check the 'extend' flags
2986 if (!shading->getExtend0() && sMin < 0) {
2987 sMin = 0;
2988 }
2989 if (!shading->getExtend1() && sMax > 1) {
2990 sMax = 1;
2991 }
2992 }
2993
2994 if (out->useShadedFills(type: shading->getType()) && out->radialShadedFill(state, shading, sMin, sMax)) {
2995 return;
2996 }
2997
2998 // compute the number of steps into which circles must be divided to
2999 // achieve a curve flatness of 0.1 pixel in device space for the
3000 // largest circle (note that "device space" is 72 dpi when generating
3001 // PostScript, hence the relatively small 0.1 pixel accuracy)
3002 const double *ctm = state->getCTM();
3003 t = fabs(x: ctm[0]);
3004 if (fabs(x: ctm[1]) > t) {
3005 t = fabs(x: ctm[1]);
3006 }
3007 if (fabs(x: ctm[2]) > t) {
3008 t = fabs(x: ctm[2]);
3009 }
3010 if (fabs(x: ctm[3]) > t) {
3011 t = fabs(x: ctm[3]);
3012 }
3013 if (r0 > r1) {
3014 t *= r0;
3015 } else {
3016 t *= r1;
3017 }
3018 if (t < 1) {
3019 n = 3;
3020 } else {
3021 const double tmp = 1 - 0.1 / t;
3022 if (unlikely(tmp == 1)) {
3023 n = 200;
3024 } else {
3025 n = (int)(M_PI / acos(x: tmp));
3026 }
3027 if (n < 3) {
3028 n = 3;
3029 } else if (n > 200) {
3030 n = 200;
3031 }
3032 }
3033
3034 // setup for the start circle
3035 ia = 0;
3036 sa = sMin;
3037 ta = t0 + sa * (t1 - t0);
3038 xa = x0 + sa * (x1 - x0);
3039 ya = y0 + sa * (y1 - y0);
3040 ra = r0 + sa * (r1 - r0);
3041 getShadingColorRadialHelper(t0, t1, t: ta, shading, color: &colorA);
3042
3043 needExtend = !out->radialShadedSupportExtend(state, shading);
3044
3045 // fill the circles
3046 while (ia < radialMaxSplits) {
3047
3048 // go as far along the t axis (toward t1) as we can, such that the
3049 // color difference is within the tolerance (radialColorDelta) --
3050 // this uses bisection (between the current value, t, and t1),
3051 // limited to radialMaxSplits points along the t axis; require at
3052 // least one split to avoid problems when the innermost and
3053 // outermost colors are the same
3054 ib = radialMaxSplits;
3055 sb = sMax;
3056 tb = t0 + sb * (t1 - t0);
3057 getShadingColorRadialHelper(t0, t1, t: tb, shading, color: &colorB);
3058 while (ib - ia > 1) {
3059 if (isSameGfxColor(colorA: colorB, colorB: colorA, nComps, radialColorDelta)) {
3060 // The shading is not necessarily lineal so having two points with the
3061 // same color does not mean all the areas in between have the same color too
3062 int ic = ia + 1;
3063 for (; ic <= ib; ic++) {
3064 const double sc = sMin + ((double)ic / (double)radialMaxSplits) * (sMax - sMin);
3065 const double tc = t0 + sc * (t1 - t0);
3066 getShadingColorRadialHelper(t0, t1, t: tc, shading, color: &colorC);
3067 if (!isSameGfxColor(colorA: colorC, colorB: colorA, nComps, radialColorDelta)) {
3068 break;
3069 }
3070 }
3071 ib = (ic > ia + 1) ? ic - 1 : ia + 1;
3072 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3073 tb = t0 + sb * (t1 - t0);
3074 getShadingColorRadialHelper(t0, t1, t: tb, shading, color: &colorB);
3075 break;
3076 }
3077 ib = (ia + ib) / 2;
3078 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3079 tb = t0 + sb * (t1 - t0);
3080 getShadingColorRadialHelper(t0, t1, t: tb, shading, color: &colorB);
3081 }
3082
3083 // compute center and radius of the circle
3084 xb = x0 + sb * (x1 - x0);
3085 yb = y0 + sb * (y1 - y0);
3086 rb = r0 + sb * (r1 - r0);
3087
3088 // use the average of the colors at the two circles
3089 for (k = 0; k < nComps; ++k) {
3090 colorA.c[k] = safeAverage(a: colorA.c[k], b: colorB.c[k]);
3091 }
3092 state->setFillColor(&colorA);
3093 if (out->useFillColorStop()) {
3094 out->updateFillColorStop(state, (sa - sMin) / (sMax - sMin));
3095 } else {
3096 out->updateFillColor(state);
3097 }
3098
3099 if (needExtend) {
3100 if (enclosed) {
3101 // construct path for first circle (counterclockwise)
3102 state->moveTo(x: xa + ra, y: ya);
3103 for (k = 1; k < n; ++k) {
3104 angle = ((double)k / (double)n) * 2 * M_PI;
3105 state->lineTo(x: xa + ra * cos(x: angle), y: ya + ra * sin(x: angle));
3106 }
3107 state->closePath();
3108
3109 // construct and append path for second circle (clockwise)
3110 state->moveTo(x: xb + rb, y: yb);
3111 for (k = 1; k < n; ++k) {
3112 angle = -((double)k / (double)n) * 2 * M_PI;
3113 state->lineTo(x: xb + rb * cos(x: angle), y: yb + rb * sin(x: angle));
3114 }
3115 state->closePath();
3116 } else {
3117 // construct the first subpath (clockwise)
3118 state->moveTo(x: xa + ra * cos(x: alpha + theta + 0.5 * M_PI), y: ya + ra * sin(x: alpha + theta + 0.5 * M_PI));
3119 for (k = 0; k < n; ++k) {
3120 angle = alpha + theta + 0.5 * M_PI - ((double)k / (double)n) * (2 * theta + M_PI);
3121 state->lineTo(x: xb + rb * cos(x: angle), y: yb + rb * sin(x: angle));
3122 }
3123 for (k = 0; k < n; ++k) {
3124 angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta - M_PI);
3125 state->lineTo(x: xa + ra * cos(x: angle), y: ya + ra * sin(x: angle));
3126 }
3127 state->closePath();
3128
3129 // construct the second subpath (counterclockwise)
3130 state->moveTo(x: xa + ra * cos(x: alpha + theta + 0.5 * M_PI), y: ya + ra * sin(x: alpha + theta + 0.5 * M_PI));
3131 for (k = 0; k < n; ++k) {
3132 angle = alpha + theta + 0.5 * M_PI + ((double)k / (double)n) * (-2 * theta + M_PI);
3133 state->lineTo(x: xb + rb * cos(x: angle), y: yb + rb * sin(x: angle));
3134 }
3135 for (k = 0; k < n; ++k) {
3136 angle = alpha - theta - 0.5 * M_PI + ((double)k / (double)n) * (2 * theta + M_PI);
3137 state->lineTo(x: xa + ra * cos(x: angle), y: ya + ra * sin(x: angle));
3138 }
3139 state->closePath();
3140 }
3141 }
3142
3143 if (!out->useFillColorStop()) {
3144 // fill the path
3145 out->fill(state);
3146 state->clearPath();
3147 }
3148
3149 // step to the next value of t
3150 ia = ib;
3151 sa = sb;
3152 ta = tb;
3153 xa = xb;
3154 ya = yb;
3155 ra = rb;
3156 colorA = colorB;
3157 }
3158
3159 if (out->useFillColorStop()) {
3160 // make sure we add stop color when sb = sMax
3161 state->setFillColor(&colorA);
3162 out->updateFillColorStop(state, (sb - sMin) / (sMax - sMin));
3163
3164 // fill the path
3165 state->moveTo(x: xMin, y: yMin);
3166 state->lineTo(x: xMin, y: yMax);
3167 state->lineTo(x: xMax, y: yMax);
3168 state->lineTo(x: xMax, y: yMin);
3169 state->closePath();
3170
3171 out->fill(state);
3172 state->clearPath();
3173 }
3174
3175 if (!needExtend) {
3176 return;
3177 }
3178
3179 if (enclosed) {
3180 // extend the smaller circle
3181 if ((shading->getExtend0() && r0 <= r1) || (shading->getExtend1() && r1 < r0)) {
3182 if (r0 <= r1) {
3183 ta = t0;
3184 ra = r0;
3185 xa = x0;
3186 ya = y0;
3187 } else {
3188 ta = t1;
3189 ra = r1;
3190 xa = x1;
3191 ya = y1;
3192 }
3193 shading->getColor(t: ta, color: &colorA);
3194 state->setFillColor(&colorA);
3195 out->updateFillColor(state);
3196 state->moveTo(x: xa + ra, y: ya);
3197 for (k = 1; k < n; ++k) {
3198 angle = ((double)k / (double)n) * 2 * M_PI;
3199 state->lineTo(x: xa + ra * cos(x: angle), y: ya + ra * sin(x: angle));
3200 }
3201 state->closePath();
3202 out->fill(state);
3203 state->clearPath();
3204 }
3205
3206 // extend the larger circle
3207 if ((shading->getExtend0() && r0 > r1) || (shading->getExtend1() && r1 >= r0)) {
3208 if (r0 > r1) {
3209 ta = t0;
3210 ra = r0;
3211 xa = x0;
3212 ya = y0;
3213 } else {
3214 ta = t1;
3215 ra = r1;
3216 xa = x1;
3217 ya = y1;
3218 }
3219 shading->getColor(t: ta, color: &colorA);
3220 state->setFillColor(&colorA);
3221 out->updateFillColor(state);
3222 state->moveTo(x: xMin, y: yMin);
3223 state->lineTo(x: xMin, y: yMax);
3224 state->lineTo(x: xMax, y: yMax);
3225 state->lineTo(x: xMax, y: yMin);
3226 state->closePath();
3227 state->moveTo(x: xa + ra, y: ya);
3228 for (k = 1; k < n; ++k) {
3229 angle = ((double)k / (double)n) * 2 * M_PI;
3230 state->lineTo(x: xa + ra * cos(x: angle), y: ya + ra * sin(x: angle));
3231 }
3232 state->closePath();
3233 out->fill(state);
3234 state->clearPath();
3235 }
3236 }
3237}
3238
3239void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading)
3240{
3241 double x0, y0, x1, y1, x2, y2;
3242 int i;
3243
3244 if (out->useShadedFills(type: shading->getType())) {
3245 if (out->gouraudTriangleShadedFill(state, shading)) {
3246 return;
3247 }
3248 }
3249
3250 // preallocate a path (speed improvements)
3251 state->moveTo(x: 0., y: 0.);
3252 state->lineTo(x: 1., y: 0.);
3253 state->lineTo(x: 0., y: 1.);
3254 state->closePath();
3255
3256 GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();
3257
3258 if (shading->isParameterized()) {
3259 // work with parameterized values:
3260 double color0, color1, color2;
3261 // a relative threshold:
3262 const double refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3263 for (i = 0; i < shading->getNTriangles(); ++i) {
3264 shading->getTriangle(i, x0: &x0, y0: &y0, color0: &color0, x1: &x1, y1: &y1, color1: &color1, x2: &x2, y2: &y2, color2: &color2);
3265 gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, depth: 0, shading, path: reusablePath);
3266 }
3267
3268 } else {
3269 // this always produces output -- even for parameterized ranges.
3270 // But it ignores the parameterized color map (the function).
3271 //
3272 // Note that using this code in for parameterized shadings might be
3273 // correct in circumstances (namely if the function is linear in the actual
3274 // triangle), but in general, it will simply be wrong.
3275 GfxColor color0, color1, color2;
3276 for (i = 0; i < shading->getNTriangles(); ++i) {
3277 shading->getTriangle(i, x0: &x0, y0: &y0, color0: &color0, x1: &x1, y1: &y1, color1: &color1, x2: &x2, y2: &y2, color2: &color2);
3278 gouraudFillTriangle(x0, y0, color0: &color0, x1, y1, color1: &color1, x2, y2, color2: &color2, nComps: shading->getColorSpace()->getNComps(), depth: 0, path: reusablePath);
3279 }
3280 }
3281
3282 delete reusablePath;
3283}
3284
3285static inline void checkTrue(bool b, const char *message)
3286{
3287 if (unlikely(!b)) {
3288 error(category: errSyntaxError, pos: -1, msg: message);
3289 }
3290}
3291
3292void 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)
3293{
3294 double x01, y01, x12, y12, x20, y20;
3295 GfxColor color01, color12, color20;
3296 int i;
3297
3298 for (i = 0; i < nComps; ++i) {
3299 if (abs(x: color0->c[i] - color1->c[i]) > gouraudColorDelta || abs(x: color1->c[i] - color2->c[i]) > gouraudColorDelta) {
3300 break;
3301 }
3302 }
3303 if (i == nComps || depth == gouraudMaxDepth) {
3304 state->setFillColor(color0);
3305 out->updateFillColor(state);
3306
3307 path->reset();
3308 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3309 path->setCoord(x: x0, y: y0);
3310 path->next();
3311 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3312 path->setCoord(x: x1, y: y1);
3313 path->next();
3314 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3315 path->setCoord(x: x2, y: y2);
3316 path->next();
3317 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3318 path->setCoord(x: x0, y: y0);
3319 path->next();
3320 checkTrue(b: path->isEnd(), message: "Path should be at end");
3321 out->fill(state);
3322
3323 } else {
3324 x01 = 0.5 * (x0 + x1);
3325 y01 = 0.5 * (y0 + y1);
3326 x12 = 0.5 * (x1 + x2);
3327 y12 = 0.5 * (y1 + y2);
3328 x20 = 0.5 * (x2 + x0);
3329 y20 = 0.5 * (y2 + y0);
3330 for (i = 0; i < nComps; ++i) {
3331 color01.c[i] = safeAverage(a: color0->c[i], b: color1->c[i]);
3332 color12.c[i] = safeAverage(a: color1->c[i], b: color2->c[i]);
3333 color20.c[i] = safeAverage(a: color2->c[i], b: color0->c[i]);
3334 }
3335 gouraudFillTriangle(x0, y0, color0, x1: x01, y1: y01, color1: &color01, x2: x20, y2: y20, color2: &color20, nComps, depth: depth + 1, path);
3336 gouraudFillTriangle(x0: x01, y0: y01, color0: &color01, x1, y1, color1, x2: x12, y2: y12, color2: &color12, nComps, depth: depth + 1, path);
3337 gouraudFillTriangle(x0: x01, y0: y01, color0: &color01, x1: x12, y1: y12, color1: &color12, x2: x20, y2: y20, color2: &color20, nComps, depth: depth + 1, path);
3338 gouraudFillTriangle(x0: x20, y0: y20, color0: &color20, x1: x12, y1: y12, color1: &color12, x2, y2, color2, nComps, depth: depth + 1, path);
3339 }
3340}
3341void 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,
3342 GfxState::ReusablePathIterator *path)
3343{
3344 const double meanColor = (color0 + color1 + color2) / 3;
3345
3346 const bool isFineEnough = fabs(x: color0 - meanColor) < refineColorThreshold && fabs(x: color1 - meanColor) < refineColorThreshold && fabs(x: color2 - meanColor) < refineColorThreshold;
3347
3348 if (isFineEnough || depth == gouraudMaxDepth) {
3349 GfxColor color;
3350
3351 shading->getParameterizedColor(t: meanColor, color: &color);
3352 state->setFillColor(&color);
3353 out->updateFillColor(state);
3354
3355 path->reset();
3356 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3357 path->setCoord(x: x0, y: y0);
3358 path->next();
3359 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3360 path->setCoord(x: x1, y: y1);
3361 path->next();
3362 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3363 path->setCoord(x: x2, y: y2);
3364 path->next();
3365 checkTrue(b: !path->isEnd(), message: "Path should not be at end");
3366 path->setCoord(x: x0, y: y0);
3367 path->next();
3368 checkTrue(b: path->isEnd(), message: "Path should be at end");
3369 out->fill(state);
3370
3371 } else {
3372 const double x01 = 0.5 * (x0 + x1);
3373 const double y01 = 0.5 * (y0 + y1);
3374 const double x12 = 0.5 * (x1 + x2);
3375 const double y12 = 0.5 * (y1 + y2);
3376 const double x20 = 0.5 * (x2 + x0);
3377 const double y20 = 0.5 * (y2 + y0);
3378 const double color01 = (color0 + color1) / 2.;
3379 const double color12 = (color1 + color2) / 2.;
3380 const double color20 = (color2 + color0) / 2.;
3381 ++depth;
3382 gouraudFillTriangle(x0, y0, color0, x1: x01, y1: y01, color1: color01, x2: x20, y2: y20, color2: color20, refineColorThreshold, depth, shading, path);
3383 gouraudFillTriangle(x0: x01, y0: y01, color0: color01, x1, y1, color1, x2: x12, y2: y12, color2: color12, refineColorThreshold, depth, shading, path);
3384 gouraudFillTriangle(x0: x01, y0: y01, color0: color01, x1: x12, y1: y12, color1: color12, x2: x20, y2: y20, color2: color20, refineColorThreshold, depth, shading, path);
3385 gouraudFillTriangle(x0: x20, y0: y20, color0: color20, x1: x12, y1: y12, color1: color12, x2, y2, color2, refineColorThreshold, depth, shading, path);
3386 }
3387}
3388
3389void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading)
3390{
3391 int start, i;
3392
3393 if (out->useShadedFills(type: shading->getType())) {
3394 if (out->patchMeshShadedFill(state, shading)) {
3395 return;
3396 }
3397 }
3398
3399 if (shading->getNPatches() > 128) {
3400 start = 3;
3401 } else if (shading->getNPatches() > 64) {
3402 start = 2;
3403 } else if (shading->getNPatches() > 16) {
3404 start = 1;
3405 } else {
3406 start = 0;
3407 }
3408 /*
3409 * Parameterized shadings take one parameter [t_0,t_e]
3410 * and map it into the color space.
3411 *
3412 * Consequently, all color values are stored as doubles.
3413 *
3414 * These color values are interpreted as parameters for parameterized
3415 * shadings and as colorspace entities otherwise.
3416 *
3417 * The only difference is that color space entities are stored into
3418 * DOUBLE arrays, not into arrays of type GfxColorComp.
3419 */
3420 const int colorComps = shading->getColorSpace()->getNComps();
3421 double refineColorThreshold;
3422 if (shading->isParameterized()) {
3423 refineColorThreshold = gouraudParameterizedColorDelta * (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3424
3425 } else {
3426 refineColorThreshold = patchColorDelta;
3427 }
3428
3429 for (i = 0; i < shading->getNPatches(); ++i) {
3430 fillPatch(patch: shading->getPatch(i), colorComps, patchColorComps: shading->isParameterized() ? 1 : colorComps, refineColorThreshold, depth: start, shading);
3431 }
3432}
3433
3434void Gfx::fillPatch(const GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, const GfxPatchMeshShading *shading)
3435{
3436 GfxPatch patch00, patch01, patch10, patch11;
3437 double xx[4][8], yy[4][8];
3438 double xxm, yym;
3439 int i;
3440
3441 for (i = 0; i < patchColorComps; ++i) {
3442 // these comparisons are done in double arithmetics.
3443 //
3444 // For non-parameterized shadings, they are done in color space
3445 // components.
3446 if (fabs(x: patch->color[0][0].c[i] - patch->color[0][1].c[i]) > refineColorThreshold || fabs(x: patch->color[0][1].c[i] - patch->color[1][1].c[i]) > refineColorThreshold
3447 || fabs(x: patch->color[1][1].c[i] - patch->color[1][0].c[i]) > refineColorThreshold || fabs(x: patch->color[1][0].c[i] - patch->color[0][0].c[i]) > refineColorThreshold) {
3448 break;
3449 }
3450 }
3451 if (i == patchColorComps || depth == patchMaxDepth) {
3452 GfxColor flatColor;
3453 if (shading->isParameterized()) {
3454 shading->getParameterizedColor(t: patch->color[0][0].c[0], color: &flatColor);
3455 } else {
3456 for (i = 0; i < colorComps; ++i) {
3457 // simply cast to the desired type; that's all what is needed.
3458 flatColor.c[i] = GfxColorComp(patch->color[0][0].c[i]);
3459 }
3460 }
3461 state->setFillColor(&flatColor);
3462 out->updateFillColor(state);
3463 state->moveTo(x: patch->x[0][0], y: patch->y[0][0]);
3464 state->curveTo(x1: patch->x[0][1], y1: patch->y[0][1], x2: patch->x[0][2], y2: patch->y[0][2], x3: patch->x[0][3], y3: patch->y[0][3]);
3465 state->curveTo(x1: patch->x[1][3], y1: patch->y[1][3], x2: patch->x[2][3], y2: patch->y[2][3], x3: patch->x[3][3], y3: patch->y[3][3]);
3466 state->curveTo(x1: patch->x[3][2], y1: patch->y[3][2], x2: patch->x[3][1], y2: patch->y[3][1], x3: patch->x[3][0], y3: patch->y[3][0]);
3467 state->curveTo(x1: patch->x[2][0], y1: patch->y[2][0], x2: patch->x[1][0], y2: patch->y[1][0], x3: patch->x[0][0], y3: patch->y[0][0]);
3468 state->closePath();
3469 out->fill(state);
3470 state->clearPath();
3471 } else {
3472 for (i = 0; i < 4; ++i) {
3473 xx[i][0] = patch->x[i][0];
3474 yy[i][0] = patch->y[i][0];
3475 xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
3476 yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
3477 xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
3478 yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
3479 xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
3480 yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
3481 xx[i][2] = 0.5 * (xx[i][1] + xxm);
3482 yy[i][2] = 0.5 * (yy[i][1] + yym);
3483 xx[i][5] = 0.5 * (xxm + xx[i][6]);
3484 yy[i][5] = 0.5 * (yym + yy[i][6]);
3485 xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
3486 yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
3487 xx[i][7] = patch->x[i][3];
3488 yy[i][7] = patch->y[i][3];
3489 }
3490 for (i = 0; i < 4; ++i) {
3491 patch00.x[0][i] = xx[0][i];
3492 patch00.y[0][i] = yy[0][i];
3493 patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
3494 patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
3495 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3496 yym = 0.5 * (yy[1][i] + yy[2][i]);
3497 patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
3498 patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
3499 patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
3500 patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
3501 patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
3502 patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
3503 patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
3504 patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
3505 patch10.x[0][i] = patch00.x[3][i];
3506 patch10.y[0][i] = patch00.y[3][i];
3507 patch10.x[3][i] = xx[3][i];
3508 patch10.y[3][i] = yy[3][i];
3509 }
3510 for (i = 4; i < 8; ++i) {
3511 patch01.x[0][i - 4] = xx[0][i];
3512 patch01.y[0][i - 4] = yy[0][i];
3513 patch01.x[1][i - 4] = 0.5 * (xx[0][i] + xx[1][i]);
3514 patch01.y[1][i - 4] = 0.5 * (yy[0][i] + yy[1][i]);
3515 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3516 yym = 0.5 * (yy[1][i] + yy[2][i]);
3517 patch11.x[2][i - 4] = 0.5 * (xx[2][i] + xx[3][i]);
3518 patch11.y[2][i - 4] = 0.5 * (yy[2][i] + yy[3][i]);
3519 patch01.x[2][i - 4] = 0.5 * (patch01.x[1][i - 4] + xxm);
3520 patch01.y[2][i - 4] = 0.5 * (patch01.y[1][i - 4] + yym);
3521 patch11.x[1][i - 4] = 0.5 * (xxm + patch11.x[2][i - 4]);
3522 patch11.y[1][i - 4] = 0.5 * (yym + patch11.y[2][i - 4]);
3523 patch01.x[3][i - 4] = 0.5 * (patch01.x[2][i - 4] + patch11.x[1][i - 4]);
3524 patch01.y[3][i - 4] = 0.5 * (patch01.y[2][i - 4] + patch11.y[1][i - 4]);
3525 patch11.x[0][i - 4] = patch01.x[3][i - 4];
3526 patch11.y[0][i - 4] = patch01.y[3][i - 4];
3527 patch11.x[3][i - 4] = xx[3][i];
3528 patch11.y[3][i - 4] = yy[3][i];
3529 }
3530 for (i = 0; i < patchColorComps; ++i) {
3531 patch00.color[0][0].c[i] = patch->color[0][0].c[i];
3532 patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + patch->color[0][1].c[i]) / 2.;
3533 patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
3534 patch01.color[0][1].c[i] = patch->color[0][1].c[i];
3535 patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + patch->color[1][1].c[i]) / 2.;
3536 patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
3537 patch11.color[1][1].c[i] = patch->color[1][1].c[i];
3538 patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + patch->color[1][0].c[i]) / 2.;
3539 patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
3540 patch10.color[1][0].c[i] = patch->color[1][0].c[i];
3541 patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + patch->color[0][0].c[i]) / 2.;
3542 patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
3543 patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + patch01.color[1][1].c[i]) / 2.;
3544 patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
3545 patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
3546 patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
3547 }
3548 fillPatch(patch: &patch00, colorComps, patchColorComps, refineColorThreshold, depth: depth + 1, shading);
3549 fillPatch(patch: &patch10, colorComps, patchColorComps, refineColorThreshold, depth: depth + 1, shading);
3550 fillPatch(patch: &patch01, colorComps, patchColorComps, refineColorThreshold, depth: depth + 1, shading);
3551 fillPatch(patch: &patch11, colorComps, patchColorComps, refineColorThreshold, depth: depth + 1, shading);
3552 }
3553}
3554
3555void Gfx::doEndPath()
3556{
3557 if (state->isCurPt() && clip != clipNone) {
3558 state->clip();
3559 if (clip == clipNormal) {
3560 out->clip(state);
3561 } else {
3562 out->eoClip(state);
3563 }
3564 }
3565 clip = clipNone;
3566 state->clearPath();
3567}
3568
3569//------------------------------------------------------------------------
3570// path clipping operators
3571//------------------------------------------------------------------------
3572
3573void Gfx::opClip(Object args[], int numArgs)
3574{
3575 clip = clipNormal;
3576}
3577
3578void Gfx::opEOClip(Object args[], int numArgs)
3579{
3580 clip = clipEO;
3581}
3582
3583//------------------------------------------------------------------------
3584// text object operators
3585//------------------------------------------------------------------------
3586
3587void Gfx::opBeginText(Object args[], int numArgs)
3588{
3589 out->beginTextObject(state);
3590 state->setTextMat(a: 1, b: 0, c: 0, d: 1, e: 0, f: 0);
3591 state->textMoveTo(tx: 0, ty: 0);
3592 out->updateTextMat(state);
3593 out->updateTextPos(state);
3594 fontChanged = true;
3595}
3596
3597void Gfx::opEndText(Object args[], int numArgs)
3598{
3599 out->endTextObject(state);
3600}
3601
3602//------------------------------------------------------------------------
3603// text state operators
3604//------------------------------------------------------------------------
3605
3606void Gfx::opSetCharSpacing(Object args[], int numArgs)
3607{
3608 state->setCharSpace(args[0].getNum());
3609 out->updateCharSpace(state);
3610}
3611
3612void Gfx::opSetFont(Object args[], int numArgs)
3613{
3614 std::shared_ptr<GfxFont> font;
3615
3616 if (!(font = res->lookupFont(name: args[0].getName()))) {
3617 // unsetting the font (drawing no text) is better than using the
3618 // previous one and drawing random glyphs from it
3619 state->setFont(fontA: nullptr, fontSizeA: args[1].getNum());
3620 fontChanged = true;
3621 return;
3622 }
3623 if (printCommands) {
3624 printf(format: " font: tag=%s name='%s' %g\n", font->getTag().c_str(), font->getName() ? font->getName()->c_str() : "???", args[1].getNum());
3625 fflush(stdout);
3626 }
3627
3628 state->setFont(fontA: font, fontSizeA: args[1].getNum());
3629 fontChanged = true;
3630}
3631
3632void Gfx::opSetTextLeading(Object args[], int numArgs)
3633{
3634 state->setLeading(args[0].getNum());
3635}
3636
3637void Gfx::opSetTextRender(Object args[], int numArgs)
3638{
3639 state->setRender(args[0].getInt());
3640 out->updateRender(state);
3641}
3642
3643void Gfx::opSetTextRise(Object args[], int numArgs)
3644{
3645 state->setRise(args[0].getNum());
3646 out->updateRise(state);
3647}
3648
3649void Gfx::opSetWordSpacing(Object args[], int numArgs)
3650{
3651 state->setWordSpace(args[0].getNum());
3652 out->updateWordSpace(state);
3653}
3654
3655void Gfx::opSetHorizScaling(Object args[], int numArgs)
3656{
3657 state->setHorizScaling(args[0].getNum());
3658 out->updateHorizScaling(state);
3659 fontChanged = true;
3660}
3661
3662//------------------------------------------------------------------------
3663// text positioning operators
3664//------------------------------------------------------------------------
3665
3666void Gfx::opTextMove(Object args[], int numArgs)
3667{
3668 double tx, ty;
3669
3670 tx = state->getLineX() + args[0].getNum();
3671 ty = state->getLineY() + args[1].getNum();
3672 state->textMoveTo(tx, ty);
3673 out->updateTextPos(state);
3674}
3675
3676void Gfx::opTextMoveSet(Object args[], int numArgs)
3677{
3678 double tx, ty;
3679
3680 tx = state->getLineX() + args[0].getNum();
3681 ty = args[1].getNum();
3682 state->setLeading(-ty);
3683 ty += state->getLineY();
3684 state->textMoveTo(tx, ty);
3685 out->updateTextPos(state);
3686}
3687
3688void Gfx::opSetTextMatrix(Object args[], int numArgs)
3689{
3690 state->setTextMat(a: args[0].getNum(), b: args[1].getNum(), c: args[2].getNum(), d: args[3].getNum(), e: args[4].getNum(), f: args[5].getNum());
3691 state->textMoveTo(tx: 0, ty: 0);
3692 out->updateTextMat(state);
3693 out->updateTextPos(state);
3694 fontChanged = true;
3695}
3696
3697void Gfx::opTextNextLine(Object args[], int numArgs)
3698{
3699 double tx, ty;
3700
3701 tx = state->getLineX();
3702 ty = state->getLineY() - state->getLeading();
3703 state->textMoveTo(tx, ty);
3704 out->updateTextPos(state);
3705}
3706
3707//------------------------------------------------------------------------
3708// text string operators
3709//------------------------------------------------------------------------
3710
3711void Gfx::opShowText(Object args[], int numArgs)
3712{
3713 if (!state->getFont()) {
3714 error(category: errSyntaxError, pos: getPos(), msg: "No font in show");
3715 return;
3716 }
3717 if (fontChanged) {
3718 out->updateFont(state);
3719 fontChanged = false;
3720 }
3721 out->beginStringOp(state);
3722 doShowText(s: args[0].getString());
3723 out->endStringOp(state);
3724 if (!ocState) {
3725 doIncCharCount(s: args[0].getString());
3726 }
3727}
3728
3729void Gfx::opMoveShowText(Object args[], int numArgs)
3730{
3731 double tx, ty;
3732
3733 if (!state->getFont()) {
3734 error(category: errSyntaxError, pos: getPos(), msg: "No font in move/show");
3735 return;
3736 }
3737 if (fontChanged) {
3738 out->updateFont(state);
3739 fontChanged = false;
3740 }
3741 tx = state->getLineX();
3742 ty = state->getLineY() - state->getLeading();
3743 state->textMoveTo(tx, ty);
3744 out->updateTextPos(state);
3745 out->beginStringOp(state);
3746 doShowText(s: args[0].getString());
3747 out->endStringOp(state);
3748 if (!ocState) {
3749 doIncCharCount(s: args[0].getString());
3750 }
3751}
3752
3753void Gfx::opMoveSetShowText(Object args[], int numArgs)
3754{
3755 double tx, ty;
3756
3757 if (!state->getFont()) {
3758 error(category: errSyntaxError, pos: getPos(), msg: "No font in move/set/show");
3759 return;
3760 }
3761 if (fontChanged) {
3762 out->updateFont(state);
3763 fontChanged = false;
3764 }
3765 state->setWordSpace(args[0].getNum());
3766 state->setCharSpace(args[1].getNum());
3767 tx = state->getLineX();
3768 ty = state->getLineY() - state->getLeading();
3769 state->textMoveTo(tx, ty);
3770 out->updateWordSpace(state);
3771 out->updateCharSpace(state);
3772 out->updateTextPos(state);
3773 out->beginStringOp(state);
3774 doShowText(s: args[2].getString());
3775 out->endStringOp(state);
3776 if (ocState) {
3777 doIncCharCount(s: args[2].getString());
3778 }
3779}
3780
3781void Gfx::opShowSpaceText(Object args[], int numArgs)
3782{
3783 Array *a;
3784 int wMode;
3785 int i;
3786
3787 if (!state->getFont()) {
3788 error(category: errSyntaxError, pos: getPos(), msg: "No font in show/space");
3789 return;
3790 }
3791 if (fontChanged) {
3792 out->updateFont(state);
3793 fontChanged = false;
3794 }
3795 out->beginStringOp(state);
3796 wMode = state->getFont()->getWMode();
3797 a = args[0].getArray();
3798 for (i = 0; i < a->getLength(); ++i) {
3799 Object obj = a->get(i);
3800 if (obj.isNum()) {
3801 // this uses the absolute value of the font size to match
3802 // Acrobat's behavior
3803 if (wMode) {
3804 state->textShift(tx: 0, ty: -obj.getNum() * 0.001 * state->getFontSize());
3805 } else {
3806 state->textShift(tx: -obj.getNum() * 0.001 * state->getFontSize() * state->getHorizScaling(), ty: 0);
3807 }
3808 out->updateTextShift(state, obj.getNum());
3809 } else if (obj.isString()) {
3810 doShowText(s: obj.getString());
3811 } else {
3812 error(category: errSyntaxError, pos: getPos(), msg: "Element of show/space array must be number or string");
3813 }
3814 }
3815 out->endStringOp(state);
3816 if (!ocState) {
3817 a = args[0].getArray();
3818 for (i = 0; i < a->getLength(); ++i) {
3819 Object obj = a->get(i);
3820 if (obj.isString()) {
3821 doIncCharCount(s: obj.getString());
3822 }
3823 }
3824 }
3825}
3826
3827void Gfx::doShowText(const GooString *s)
3828{
3829 int wMode;
3830 double riseX, riseY;
3831 CharCode code;
3832 const Unicode *u = nullptr;
3833 double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
3834 double originX, originY, tOriginX, tOriginY;
3835 double x0, y0, x1, y1;
3836 double tmp[4], newCTM[6];
3837 const double *oldCTM, *mat;
3838 Dict *resDict;
3839 Parser *oldParser;
3840 GfxState *savedState;
3841 const char *p;
3842 int render;
3843 bool patternFill;
3844 int len, n, uLen, nChars, nSpaces;
3845
3846 GfxFont *const font = state->getFont().get();
3847 wMode = font->getWMode();
3848
3849 if (out->useDrawChar()) {
3850 out->beginString(state, s);
3851 }
3852
3853 // if we're doing a pattern fill, set up clipping
3854 render = state->getRender();
3855 if (!(render & 1) && state->getFillColorSpace()->getMode() == csPattern) {
3856 patternFill = true;
3857 saveState();
3858 // disable fill, enable clipping, leave stroke unchanged
3859 if ((render ^ (render >> 1)) & 1) {
3860 render = 5;
3861 } else {
3862 render = 7;
3863 }
3864 state->setRender(render);
3865 out->updateRender(state);
3866 } else {
3867 patternFill = false;
3868 }
3869
3870 state->textTransformDelta(x1: 0, y1: state->getRise(), x2: &riseX, y2: &riseY);
3871 x0 = state->getCurX() + riseX;
3872 y0 = state->getCurY() + riseY;
3873
3874 // handle a Type 3 char
3875 if (font->getType() == fontType3 && out->interpretType3Chars()) {
3876 oldCTM = state->getCTM();
3877 mat = state->getTextMat();
3878 tmp[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
3879 tmp[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
3880 tmp[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
3881 tmp[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
3882 mat = font->getFontMatrix();
3883 newCTM[0] = mat[0] * tmp[0] + mat[1] * tmp[2];
3884 newCTM[1] = mat[0] * tmp[1] + mat[1] * tmp[3];
3885 newCTM[2] = mat[2] * tmp[0] + mat[3] * tmp[2];
3886 newCTM[3] = mat[2] * tmp[1] + mat[3] * tmp[3];
3887 newCTM[0] *= state->getFontSize();
3888 newCTM[1] *= state->getFontSize();
3889 newCTM[2] *= state->getFontSize();
3890 newCTM[3] *= state->getFontSize();
3891 newCTM[0] *= state->getHorizScaling();
3892 newCTM[1] *= state->getHorizScaling();
3893 curX = state->getCurX();
3894 curY = state->getCurY();
3895 oldParser = parser;
3896 p = s->c_str();
3897 len = s->getLength();
3898 while (len > 0) {
3899 n = font->getNextChar(s: p, len, code: &code, u: &u, uLen: &uLen, dx: &dx, dy: &dy, ox: &originX, oy: &originY);
3900 dx = dx * state->getFontSize() + state->getCharSpace();
3901 if (n == 1 && *p == ' ') {
3902 dx += state->getWordSpace();
3903 }
3904 dx *= state->getHorizScaling();
3905 dy *= state->getFontSize();
3906 state->textTransformDelta(x1: dx, y1: dy, x2: &tdx, y2: &tdy);
3907 state->transform(x1: curX + riseX, y1: curY + riseY, x2: &x, y2: &y);
3908 savedState = saveStateStack();
3909 state->setCTM(a: newCTM[0], b: newCTM[1], c: newCTM[2], d: newCTM[3], e: x, f: y);
3910 //~ the CTM concat values here are wrong (but never used)
3911 out->updateCTM(state, 1, 0, 0, 1, 0, 0);
3912 state->transformDelta(x1: dx, y1: dy, x2: &ddx, y2: &ddy);
3913 if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) {
3914 Object charProc = ((Gfx8BitFont *)font)->getCharProcNF(code);
3915 int refNum = -1;
3916 if (charProc.isRef()) {
3917 refNum = charProc.getRef().num;
3918 charProc = charProc.fetch(xref: ((Gfx8BitFont *)font)->getCharProcs()->getXRef());
3919 }
3920 if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
3921 pushResources(resDict);
3922 }
3923 if (charProc.isStream()) {
3924 Object charProcResourcesObj = charProc.streamGetDict()->lookup(key: "Resources");
3925 if (charProcResourcesObj.isDict()) {
3926 pushResources(resDict: charProcResourcesObj.getDict());
3927 }
3928 std::set<int>::iterator charProcDrawingIt;
3929 bool displayCharProc = true;
3930 if (refNum != -1) {
3931 if (charProcDrawing.find(x: refNum) == charProcDrawing.end()) {
3932 charProcDrawingIt = charProcDrawing.insert(x: refNum).first;
3933 } else {
3934 displayCharProc = false;
3935 error(category: errSyntaxError, pos: -1, msg: "CharProc wants to draw a CharProc that is already being drawn");
3936 }
3937 }
3938 if (displayCharProc) {
3939 ++displayDepth;
3940 display(obj: &charProc, topLevel: false);
3941 --displayDepth;
3942
3943 if (refNum != -1) {
3944 charProcDrawing.erase(position: charProcDrawingIt);
3945 }
3946 }
3947 if (charProcResourcesObj.isDict()) {
3948 popResources();
3949 }
3950 } else {
3951 error(category: errSyntaxError, pos: getPos(), msg: "Missing or bad Type3 CharProc entry");
3952 }
3953 out->endType3Char(state);
3954 if (resDict) {
3955 popResources();
3956 }
3957 }
3958 restoreStateStack(oldState: savedState);
3959 // GfxState::restore() does *not* restore the current position,
3960 // so we deal with it here using (curX, curY) and (lineX, lineY)
3961 curX += tdx;
3962 curY += tdy;
3963 state->moveTo(x: curX, y: curY);
3964 // Call updateCTM with the identity transformation. That way, the CTM is unchanged,
3965 // but any side effect that the method may have is triggered. This is the case,
3966 // in particular, for the Splash backend.
3967 out->updateCTM(state, 1, 0, 0, 1, 0, 0);
3968 p += n;
3969 len -= n;
3970 }
3971 parser = oldParser;
3972
3973 } else if (out->useDrawChar()) {
3974 p = s->c_str();
3975 len = s->getLength();
3976 while (len > 0) {
3977 n = font->getNextChar(s: p, len, code: &code, u: &u, uLen: &uLen, dx: &dx, dy: &dy, ox: &originX, oy: &originY);
3978 if (wMode) {
3979 dx *= state->getFontSize();
3980 dy = dy * state->getFontSize() + state->getCharSpace();
3981 if (n == 1 && *p == ' ') {
3982 dy += state->getWordSpace();
3983 }
3984 } else {
3985 dx = dx * state->getFontSize() + state->getCharSpace();
3986 if (n == 1 && *p == ' ') {
3987 dx += state->getWordSpace();
3988 }
3989 dx *= state->getHorizScaling();
3990 dy *= state->getFontSize();
3991 }
3992 state->textTransformDelta(x1: dx, y1: dy, x2: &tdx, y2: &tdy);
3993 originX *= state->getFontSize();
3994 originY *= state->getFontSize();
3995 state->textTransformDelta(x1: originX, y1: originY, x2: &tOriginX, y2: &tOriginY);
3996 if (ocState) {
3997 out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY, tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
3998 }
3999 state->shift(dx: tdx, dy: tdy);
4000 p += n;
4001 len -= n;
4002 }
4003 } else {
4004 dx = dy = 0;
4005 p = s->c_str();
4006 len = s->getLength();
4007 nChars = nSpaces = 0;
4008 while (len > 0) {
4009 n = font->getNextChar(s: p, len, code: &code, u: &u, uLen: &uLen, dx: &dx2, dy: &dy2, ox: &originX, oy: &originY);
4010 dx += dx2;
4011 dy += dy2;
4012 if (n == 1 && *p == ' ') {
4013 ++nSpaces;
4014 }
4015 ++nChars;
4016 p += n;
4017 len -= n;
4018 }
4019 if (wMode) {
4020 dx *= state->getFontSize();
4021 dy = dy * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
4022 } else {
4023 dx = dx * state->getFontSize() + nChars * state->getCharSpace() + nSpaces * state->getWordSpace();
4024 dx *= state->getHorizScaling();
4025 dy *= state->getFontSize();
4026 }
4027 state->textTransformDelta(x1: dx, y1: dy, x2: &tdx, y2: &tdy);
4028 if (ocState) {
4029 out->drawString(state, s);
4030 }
4031 state->shift(dx: tdx, dy: tdy);
4032 }
4033
4034 if (out->useDrawChar()) {
4035 out->endString(state);
4036 }
4037
4038 if (patternFill && ocState) {
4039 out->saveTextPos(state);
4040 // tell the OutputDev to do the clipping
4041 out->endTextObject(state);
4042 // set up a clipping bbox so doPatternText will work -- assume
4043 // that the text bounding box does not extend past the baseline in
4044 // any direction by more than twice the font size
4045 x1 = state->getCurX() + riseX;
4046 y1 = state->getCurY() + riseY;
4047 if (x0 > x1) {
4048 x = x0;
4049 x0 = x1;
4050 x1 = x;
4051 }
4052 if (y0 > y1) {
4053 y = y0;
4054 y0 = y1;
4055 y1 = y;
4056 }
4057 state->textTransformDelta(x1: 0, y1: state->getFontSize(), x2: &dx, y2: &dy);
4058 state->textTransformDelta(x1: state->getFontSize(), y1: 0, x2: &dx2, y2: &dy2);
4059 dx = fabs(x: dx);
4060 dx2 = fabs(x: dx2);
4061 if (dx2 > dx) {
4062 dx = dx2;
4063 }
4064 dy = fabs(x: dy);
4065 dy2 = fabs(x: dy2);
4066 if (dy2 > dy) {
4067 dy = dy2;
4068 }
4069 state->clipToRect(xMin: x0 - 2 * dx, yMin: y0 - 2 * dy, xMax: x1 + 2 * dx, yMax: y1 + 2 * dy);
4070 // set render mode to fill-only
4071 state->setRender(0);
4072 out->updateRender(state);
4073 doPatternText();
4074 restoreState();
4075 out->restoreTextPos(state);
4076 }
4077
4078 updateLevel += 10 * s->getLength();
4079}
4080
4081// NB: this is only called when ocState is false.
4082void Gfx::doIncCharCount(const GooString *s)
4083{
4084 if (out->needCharCount()) {
4085 out->incCharCount(s->getLength());
4086 }
4087}
4088
4089//------------------------------------------------------------------------
4090// XObject operators
4091//------------------------------------------------------------------------
4092
4093void Gfx::opXObject(Object args[], int numArgs)
4094{
4095 const char *name;
4096
4097 if (!ocState && !out->needCharCount()) {
4098 return;
4099 }
4100 name = args[0].getName();
4101 Object obj1 = res->lookupXObject(name);
4102 if (obj1.isNull()) {
4103 return;
4104 }
4105 if (!obj1.isStream()) {
4106 error(category: errSyntaxError, pos: getPos(), msg: "XObject '{0:s}' is wrong type", name);
4107 return;
4108 }
4109
4110#ifdef OPI_SUPPORT
4111 Object opiDict = obj1.streamGetDict()->lookup(key: "OPI");
4112 if (opiDict.isDict()) {
4113 out->opiBegin(state, opiDict: opiDict.getDict());
4114 }
4115#endif
4116 Object obj2 = obj1.streamGetDict()->lookup(key: "Subtype");
4117 if (obj2.isName(nameA: "Image")) {
4118 if (out->needNonText()) {
4119 Object refObj = res->lookupXObjectNF(name);
4120 doImage(ref: &refObj, str: obj1.getStream(), inlineImg: false);
4121 }
4122 } else if (obj2.isName(nameA: "Form")) {
4123 Object refObj = res->lookupXObjectNF(name);
4124 bool shouldDoForm = true;
4125 std::set<int>::iterator drawingFormIt;
4126 if (refObj.isRef()) {
4127 const int num = refObj.getRef().num;
4128 if (formsDrawing.find(x: num) == formsDrawing.end()) {
4129 drawingFormIt = formsDrawing.insert(x: num).first;
4130 } else {
4131 shouldDoForm = false;
4132 }
4133 }
4134 if (shouldDoForm) {
4135 if (out->useDrawForm() && refObj.isRef()) {
4136 out->drawForm(refObj.getRef());
4137 } else {
4138 Ref ref = refObj.isRef() ? refObj.getRef() : Ref::INVALID();
4139 out->beginForm(&obj1, ref);
4140 doForm(str: &obj1);
4141 out->endForm(&obj1, ref);
4142 }
4143 }
4144 if (refObj.isRef() && shouldDoForm) {
4145 formsDrawing.erase(position: drawingFormIt);
4146 }
4147 } else if (obj2.isName(nameA: "PS")) {
4148 Object obj3 = obj1.streamGetDict()->lookup(key: "Level1");
4149 out->psXObject(obj1.getStream(), obj3.isStream() ? obj3.getStream() : nullptr);
4150 } else if (obj2.isName()) {
4151 error(category: errSyntaxError, pos: getPos(), msg: "Unknown XObject subtype '{0:s}'", obj2.getName());
4152 } else {
4153 error(category: errSyntaxError, pos: getPos(), msg: "XObject subtype is missing or wrong type");
4154 }
4155#ifdef OPI_SUPPORT
4156 if (opiDict.isDict()) {
4157 out->opiEnd(state, opiDict: opiDict.getDict());
4158 }
4159#endif
4160}
4161
4162void Gfx::doImage(Object *ref, Stream *str, bool inlineImg)
4163{
4164 Dict *dict, *maskDict;
4165 int width, height;
4166 int bits, maskBits;
4167 bool interpolate;
4168 StreamColorSpaceMode csMode;
4169 bool mask;
4170 bool invert;
4171 GfxColorSpace *colorSpace, *maskColorSpace;
4172 bool haveColorKeyMask, haveExplicitMask, haveSoftMask;
4173 int maskColors[2 * gfxColorMaxComps] = {};
4174 int maskWidth, maskHeight;
4175 bool maskInvert;
4176 bool maskInterpolate;
4177 Stream *maskStr;
4178 int i, n;
4179
4180 // get info from the stream
4181 bits = 0;
4182 csMode = streamCSNone;
4183 str->getImageParams(&bits, &csMode);
4184
4185 // get stream dict
4186 dict = str->getDict();
4187
4188 // check for optional content key
4189 if (ref) {
4190 const Object &objOC = dict->lookupNF(key: "OC");
4191 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(dictRef: &objOC)) {
4192 return;
4193 }
4194 }
4195
4196 const double *ctm = state->getCTM();
4197 const double det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
4198 // Detect singular matrix (non invertible) to avoid drawing Image in such case
4199 const bool singular_matrix = fabs(x: det) < 0.000001;
4200
4201 // get size
4202 Object obj1 = dict->lookup(key: "Width");
4203 if (obj1.isNull()) {
4204 obj1 = dict->lookup(key: "W");
4205 }
4206 if (obj1.isInt()) {
4207 width = obj1.getInt();
4208 } else if (obj1.isReal()) {
4209 width = (int)obj1.getReal();
4210 } else {
4211 goto err1;
4212 }
4213 obj1 = dict->lookup(key: "Height");
4214 if (obj1.isNull()) {
4215 obj1 = dict->lookup(key: "H");
4216 }
4217 if (obj1.isInt()) {
4218 height = obj1.getInt();
4219 } else if (obj1.isReal()) {
4220 height = (int)obj1.getReal();
4221 } else {
4222 goto err1;
4223 }
4224
4225 if (width < 1 || height < 1 || width > INT_MAX / height) {
4226 goto err1;
4227 }
4228
4229 // image interpolation
4230 obj1 = dict->lookup(key: "Interpolate");
4231 if (obj1.isNull()) {
4232 obj1 = dict->lookup(key: "I");
4233 }
4234 if (obj1.isBool()) {
4235 interpolate = obj1.getBool();
4236 } else {
4237 interpolate = false;
4238 }
4239 maskInterpolate = false;
4240
4241 // image or mask?
4242 obj1 = dict->lookup(key: "ImageMask");
4243 if (obj1.isNull()) {
4244 obj1 = dict->lookup(key: "IM");
4245 }
4246 mask = false;
4247 if (obj1.isBool()) {
4248 mask = obj1.getBool();
4249 } else if (!obj1.isNull()) {
4250 goto err1;
4251 }
4252
4253 // bit depth
4254 if (bits == 0) {
4255 obj1 = dict->lookup(key: "BitsPerComponent");
4256 if (obj1.isNull()) {
4257 obj1 = dict->lookup(key: "BPC");
4258 }
4259 if (obj1.isInt()) {
4260 bits = obj1.getInt();
4261 } else if (mask) {
4262 bits = 1;
4263 } else {
4264 goto err1;
4265 }
4266 }
4267
4268 // display a mask
4269 if (mask) {
4270
4271 // check for inverted mask
4272 if (bits != 1) {
4273 goto err1;
4274 }
4275 invert = false;
4276 obj1 = dict->lookup(key: "Decode");
4277 if (obj1.isNull()) {
4278 obj1 = dict->lookup(key: "D");
4279 }
4280 if (obj1.isArray()) {
4281 Object obj2;
4282 obj2 = obj1.arrayGet(i: 0);
4283 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4284 // accepts [1.0 0.0] as well.
4285 if (obj2.isNum() && obj2.getNum() >= 0.9) {
4286 invert = true;
4287 }
4288 } else if (!obj1.isNull()) {
4289 goto err1;
4290 }
4291
4292 // if drawing is disabled, skip over inline image data
4293 if (!ocState || !out->needNonText()) {
4294 str->reset();
4295 n = height * ((width + 7) / 8);
4296 for (i = 0; i < n; ++i) {
4297 str->getChar();
4298 }
4299 str->close();
4300
4301 // draw it
4302 } else {
4303 if (state->getFillColorSpace()->getMode() == csPattern) {
4304 doPatternImageMask(ref, str, width, height, invert, inlineImg);
4305 } else {
4306 out->drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
4307 }
4308 }
4309 } else {
4310 if (bits == 0) {
4311 goto err1;
4312 }
4313
4314 // get color space and color map
4315 obj1 = dict->lookup(key: "ColorSpace");
4316 if (obj1.isNull()) {
4317 obj1 = dict->lookup(key: "CS");
4318 }
4319 if (obj1.isName() && inlineImg) {
4320 Object obj2 = res->lookupColorSpace(name: obj1.getName());
4321 if (!obj2.isNull()) {
4322 obj1 = std::move(obj2);
4323 }
4324 }
4325 if (!obj1.isNull()) {
4326 char *tempIntent = nullptr;
4327 Object objIntent = dict->lookup(key: "Intent");
4328 if (objIntent.isName()) {
4329 const char *stateIntent = state->getRenderingIntent();
4330 if (stateIntent != nullptr) {
4331 tempIntent = strdup(s: stateIntent);
4332 }
4333 state->setRenderingIntent(objIntent.getName());
4334 }
4335 colorSpace = GfxColorSpace::parse(res, csObj: &obj1, out, state);
4336 if (objIntent.isName()) {
4337 state->setRenderingIntent(tempIntent);
4338 free(ptr: tempIntent);
4339 }
4340 } else if (csMode == streamCSDeviceGray) {
4341 Object objCS = res->lookupColorSpace(name: "DefaultGray");
4342 if (objCS.isNull()) {
4343 colorSpace = new GfxDeviceGrayColorSpace();
4344 } else {
4345 colorSpace = GfxColorSpace::parse(res, csObj: &objCS, out, state);
4346 }
4347 } else if (csMode == streamCSDeviceRGB) {
4348 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
4349 if (objCS.isNull()) {
4350 colorSpace = new GfxDeviceRGBColorSpace();
4351 } else {
4352 colorSpace = GfxColorSpace::parse(res, csObj: &objCS, out, state);
4353 }
4354 } else if (csMode == streamCSDeviceCMYK) {
4355 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
4356 if (objCS.isNull()) {
4357 colorSpace = new GfxDeviceCMYKColorSpace();
4358 } else {
4359 colorSpace = GfxColorSpace::parse(res, csObj: &objCS, out, state);
4360 }
4361 } else {
4362 colorSpace = nullptr;
4363 }
4364 if (!colorSpace) {
4365 goto err1;
4366 }
4367 obj1 = dict->lookup(key: "Decode");
4368 if (obj1.isNull()) {
4369 obj1 = dict->lookup(key: "D");
4370 }
4371 GfxImageColorMap colorMap(bits, &obj1, colorSpace);
4372 if (!colorMap.isOk()) {
4373 goto err1;
4374 }
4375
4376 // get the mask
4377 bool haveMaskImage = false;
4378 haveColorKeyMask = haveExplicitMask = haveSoftMask = false;
4379 maskStr = nullptr; // make gcc happy
4380 maskWidth = maskHeight = 0; // make gcc happy
4381 maskInvert = false; // make gcc happy
4382 std::unique_ptr<GfxImageColorMap> maskColorMap;
4383 Object maskObj = dict->lookup(key: "Mask");
4384 Object smaskObj = dict->lookup(key: "SMask");
4385
4386 if (maskObj.isStream()) {
4387 maskStr = maskObj.getStream();
4388 maskDict = maskObj.streamGetDict();
4389 // if Type is XObject and Subtype is Image
4390 // then the way the softmask is drawn will draw
4391 // correctly, if it falls through to the explicit
4392 // mask code then you get an error and no image
4393 // drawn because it expects maskDict to have an entry
4394 // of Mask or IM that is boolean...
4395 Object tobj = maskDict->lookup(key: "Type");
4396 if (!tobj.isNull() && tobj.isName() && tobj.isName(nameA: "XObject")) {
4397 Object sobj = maskDict->lookup(key: "Subtype");
4398 if (!sobj.isNull() && sobj.isName() && sobj.isName(nameA: "Image")) {
4399 // ensure that this mask does not include an ImageMask entry
4400 // which signifies the explicit mask
4401 obj1 = maskDict->lookup(key: "ImageMask");
4402 if (obj1.isNull()) {
4403 obj1 = maskDict->lookup(key: "IM");
4404 }
4405 if (obj1.isNull() || !obj1.isBool()) {
4406 haveMaskImage = true;
4407 }
4408 }
4409 }
4410 }
4411
4412 if (smaskObj.isStream() || haveMaskImage) {
4413 // soft mask
4414 if (inlineImg) {
4415 goto err1;
4416 }
4417 if (!haveMaskImage) {
4418 maskStr = smaskObj.getStream();
4419 maskDict = smaskObj.streamGetDict();
4420 }
4421 obj1 = maskDict->lookup(key: "Width");
4422 if (obj1.isNull()) {
4423 obj1 = maskDict->lookup(key: "W");
4424 }
4425 if (!obj1.isInt()) {
4426 goto err1;
4427 }
4428 maskWidth = obj1.getInt();
4429 obj1 = maskDict->lookup(key: "Height");
4430 if (obj1.isNull()) {
4431 obj1 = maskDict->lookup(key: "H");
4432 }
4433 if (!obj1.isInt()) {
4434 goto err1;
4435 }
4436 maskHeight = obj1.getInt();
4437 obj1 = maskDict->lookup(key: "Interpolate");
4438 if (obj1.isNull()) {
4439 obj1 = maskDict->lookup(key: "I");
4440 }
4441 if (obj1.isBool()) {
4442 maskInterpolate = obj1.getBool();
4443 } else {
4444 maskInterpolate = false;
4445 }
4446 obj1 = maskDict->lookup(key: "BitsPerComponent");
4447 if (obj1.isNull()) {
4448 obj1 = maskDict->lookup(key: "BPC");
4449 }
4450 if (!obj1.isInt()) {
4451 goto err1;
4452 }
4453 maskBits = obj1.getInt();
4454 obj1 = maskDict->lookup(key: "ColorSpace");
4455 if (obj1.isNull()) {
4456 obj1 = maskDict->lookup(key: "CS");
4457 }
4458 if (obj1.isName()) {
4459 Object obj2 = res->lookupColorSpace(name: obj1.getName());
4460 if (!obj2.isNull()) {
4461 obj1 = std::move(obj2);
4462 }
4463 }
4464 // Here, we parse manually instead of using GfxColorSpace::parse,
4465 // since we explicitly need DeviceGray and not some DefaultGray color space
4466 if (!obj1.isName(nameA: "DeviceGray") && !obj1.isName(nameA: "G")) {
4467 goto err1;
4468 }
4469 maskColorSpace = new GfxDeviceGrayColorSpace();
4470 obj1 = maskDict->lookup(key: "Decode");
4471 if (obj1.isNull()) {
4472 obj1 = maskDict->lookup(key: "D");
4473 }
4474 maskColorMap = std::make_unique<GfxImageColorMap>(args&: maskBits, args: &obj1, args&: maskColorSpace);
4475 if (!maskColorMap->isOk()) {
4476 goto err1;
4477 }
4478 // handle the Matte entry
4479 obj1 = maskDict->lookup(key: "Matte");
4480 if (obj1.isArray()) {
4481 if (obj1.getArray()->getLength() != colorSpace->getNComps()) {
4482 error(category: errSyntaxError, pos: -1, msg: "Matte entry should have {0:d} components but has {1:d}", colorSpace->getNComps(), obj1.getArray()->getLength());
4483 } else if (maskWidth != width || maskHeight != height) {
4484 error(category: errSyntaxError, pos: -1, msg: "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);
4485 } else {
4486 GfxColor matteColor;
4487 for (i = 0; i < colorSpace->getNComps(); i++) {
4488 Object obj2 = obj1.getArray()->get(i);
4489 if (!obj2.isNum()) {
4490 error(category: errSyntaxError, pos: -1, msg: "Matte entry {0:d} should be a number but it's of type {1:d}", i, obj2.getType());
4491
4492 break;
4493 }
4494 matteColor.c[i] = dblToCol(x: obj2.getNum());
4495 }
4496 if (i == colorSpace->getNComps()) {
4497 maskColorMap->setMatteColor(&matteColor);
4498 }
4499 }
4500 }
4501 haveSoftMask = true;
4502 } else if (maskObj.isArray()) {
4503 // color key mask
4504 for (i = 0; i < maskObj.arrayGetLength() && i < 2 * gfxColorMaxComps; ++i) {
4505 obj1 = maskObj.arrayGet(i);
4506 if (obj1.isInt()) {
4507 maskColors[i] = obj1.getInt();
4508 } else if (obj1.isReal()) {
4509 error(category: errSyntaxError, pos: -1, msg: "Mask entry should be an integer but it's a real, trying to use it");
4510 maskColors[i] = (int)obj1.getReal();
4511 } else {
4512 error(category: errSyntaxError, pos: -1, msg: "Mask entry should be an integer but it's of type {0:d}", obj1.getType());
4513 goto err1;
4514 }
4515 }
4516 haveColorKeyMask = true;
4517 } else if (maskObj.isStream()) {
4518 // explicit mask
4519 if (inlineImg) {
4520 goto err1;
4521 }
4522
4523 if (maskStr == nullptr) {
4524 maskStr = maskObj.getStream();
4525 maskDict = maskObj.streamGetDict();
4526 }
4527 obj1 = maskDict->lookup(key: "Width");
4528 if (obj1.isNull()) {
4529 obj1 = maskDict->lookup(key: "W");
4530 }
4531 if (!obj1.isInt()) {
4532 goto err1;
4533 }
4534 maskWidth = obj1.getInt();
4535 obj1 = maskDict->lookup(key: "Height");
4536 if (obj1.isNull()) {
4537 obj1 = maskDict->lookup(key: "H");
4538 }
4539 if (!obj1.isInt()) {
4540 goto err1;
4541 }
4542 maskHeight = obj1.getInt();
4543 obj1 = maskDict->lookup(key: "Interpolate");
4544 if (obj1.isNull()) {
4545 obj1 = maskDict->lookup(key: "I");
4546 }
4547 if (obj1.isBool()) {
4548 maskInterpolate = obj1.getBool();
4549 } else {
4550 maskInterpolate = false;
4551 }
4552
4553 obj1 = maskDict->lookup(key: "ImageMask");
4554 if (obj1.isNull()) {
4555 obj1 = maskDict->lookup(key: "IM");
4556 }
4557 if (!haveMaskImage && (!obj1.isBool() || !obj1.getBool())) {
4558 goto err1;
4559 }
4560
4561 maskInvert = false;
4562 obj1 = maskDict->lookup(key: "Decode");
4563 if (obj1.isNull()) {
4564 obj1 = maskDict->lookup(key: "D");
4565 }
4566 if (obj1.isArray()) {
4567 Object obj2 = obj1.arrayGet(i: 0);
4568 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4569 // accepts [1.0 0.0] as well.
4570 if (obj2.isNum() && obj2.getNum() >= 0.9) {
4571 maskInvert = true;
4572 }
4573 } else if (!obj1.isNull()) {
4574 goto err1;
4575 }
4576
4577 haveExplicitMask = true;
4578 }
4579
4580 // if drawing is disabled, skip over inline image data
4581 if (!ocState || !out->needNonText() || singular_matrix) {
4582 str->reset();
4583 n = height * ((width * colorMap.getNumPixelComps() * colorMap.getBits() + 7) / 8);
4584 for (i = 0; i < n; ++i) {
4585 str->getChar();
4586 }
4587 str->close();
4588
4589 // draw it
4590 } else {
4591 if (haveSoftMask) {
4592 out->drawSoftMaskedImage(state, ref, str, width, height, colorMap: &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap: maskColorMap.get(), maskInterpolate);
4593 } else if (haveExplicitMask) {
4594 out->drawMaskedImage(state, ref, str, width, height, colorMap: &colorMap, interpolate, maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
4595 } else {
4596 out->drawImage(state, ref, str, width, height, colorMap: &colorMap, interpolate, maskColors: haveColorKeyMask ? maskColors : nullptr, inlineImg);
4597 }
4598 }
4599 }
4600
4601 if ((i = width * height) > 1000) {
4602 i = 1000;
4603 }
4604 updateLevel += i;
4605
4606 return;
4607
4608err1:
4609 error(category: errSyntaxError, pos: getPos(), msg: "Bad image parameters");
4610}
4611
4612bool Gfx::checkTransparencyGroup(Dict *resDict)
4613{
4614 // check the effect of compositing objects as a group:
4615 // look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
4616 bool transpGroup = false;
4617 double opac;
4618
4619 if (resDict == nullptr) {
4620 return false;
4621 }
4622 pushResources(resDict);
4623 Object extGStates = resDict->lookup(key: "ExtGState");
4624 if (extGStates.isDict()) {
4625 Dict *dict = extGStates.getDict();
4626 for (int i = 0; i < dict->getLength() && !transpGroup; i++) {
4627 GfxBlendMode mode;
4628
4629 Object obj1 = res->lookupGState(name: dict->getKey(i));
4630 if (obj1.isDict()) {
4631 Object obj2 = obj1.dictLookup(key: "BM");
4632 if (!obj2.isNull()) {
4633 if (state->parseBlendMode(obj: &obj2, mode: &mode)) {
4634 if (mode != gfxBlendNormal) {
4635 transpGroup = true;
4636 }
4637 } else {
4638 error(category: errSyntaxError, pos: getPos(), msg: "Invalid blend mode in ExtGState");
4639 }
4640 }
4641 obj2 = obj1.dictLookup(key: "ca");
4642 if (obj2.isNum()) {
4643 opac = obj2.getNum();
4644 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4645 if (opac != 1) {
4646 transpGroup = true;
4647 }
4648 }
4649 obj2 = obj1.dictLookup(key: "CA");
4650 if (obj2.isNum()) {
4651 opac = obj2.getNum();
4652 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4653 if (opac != 1) {
4654 transpGroup = true;
4655 }
4656 }
4657 // alpha is shape
4658 obj2 = obj1.dictLookup(key: "AIS");
4659 if (!transpGroup && obj2.isBool()) {
4660 transpGroup = obj2.getBool();
4661 }
4662 // soft mask
4663 obj2 = obj1.dictLookup(key: "SMask");
4664 if (!transpGroup && !obj2.isNull()) {
4665 if (!obj2.isName(nameA: "None")) {
4666 transpGroup = true;
4667 }
4668 }
4669 }
4670 }
4671 }
4672 popResources();
4673 return transpGroup;
4674}
4675
4676void Gfx::doForm(Object *str)
4677{
4678 Dict *dict;
4679 bool transpGroup, isolated, knockout;
4680 GfxColorSpace *blendingColorSpace;
4681 double m[6], bbox[4];
4682 Dict *resDict;
4683 bool ocSaved;
4684 Object obj1;
4685 int i;
4686
4687 // get stream dict
4688 dict = str->streamGetDict();
4689
4690 // check form type
4691 obj1 = dict->lookup(key: "FormType");
4692 if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
4693 error(category: errSyntaxError, pos: getPos(), msg: "Unknown form type");
4694 }
4695
4696 // check for optional content key
4697 ocSaved = ocState;
4698 const Object &objOC = dict->lookupNF(key: "OC");
4699 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(dictRef: &objOC)) {
4700 if (out->needCharCount()) {
4701 ocState = false;
4702 } else {
4703 return;
4704 }
4705 }
4706
4707 // get bounding box
4708 Object bboxObj = dict->lookup(key: "BBox");
4709 if (!bboxObj.isArray()) {
4710 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box");
4711 ocState = ocSaved;
4712 return;
4713 }
4714 for (i = 0; i < 4; ++i) {
4715 obj1 = bboxObj.arrayGet(i);
4716 if (likely(obj1.isNum())) {
4717 bbox[i] = obj1.getNum();
4718 } else {
4719 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box value");
4720 return;
4721 }
4722 }
4723
4724 // get matrix
4725 Object matrixObj = dict->lookup(key: "Matrix");
4726 if (matrixObj.isArray()) {
4727 for (i = 0; i < 6; ++i) {
4728 obj1 = matrixObj.arrayGet(i);
4729 if (likely(obj1.isNum())) {
4730 m[i] = obj1.getNum();
4731 } else {
4732 m[i] = 0;
4733 }
4734 }
4735 } else {
4736 m[0] = 1;
4737 m[1] = 0;
4738 m[2] = 0;
4739 m[3] = 1;
4740 m[4] = 0;
4741 m[5] = 0;
4742 }
4743
4744 // get resources
4745 Object resObj = dict->lookup(key: "Resources");
4746 resDict = resObj.isDict() ? resObj.getDict() : nullptr;
4747
4748 // check for a transparency group
4749 transpGroup = isolated = knockout = false;
4750 blendingColorSpace = nullptr;
4751 obj1 = dict->lookup(key: "Group");
4752 if (obj1.isDict()) {
4753 Object obj2 = obj1.dictLookup(key: "S");
4754 if (obj2.isName(nameA: "Transparency")) {
4755 Object obj3 = obj1.dictLookup(key: "CS");
4756 if (!obj3.isNull()) {
4757 blendingColorSpace = GfxColorSpace::parse(res, csObj: &obj3, out, state);
4758 }
4759 obj3 = obj1.dictLookup(key: "I");
4760 if (obj3.isBool()) {
4761 isolated = obj3.getBool();
4762 }
4763 obj3 = obj1.dictLookup(key: "K");
4764 if (obj3.isBool()) {
4765 knockout = obj3.getBool();
4766 }
4767 transpGroup = isolated || out->checkTransparencyGroup(state, knockout) || checkTransparencyGroup(resDict);
4768 }
4769 }
4770
4771 // draw it
4772 drawForm(str, resDict, matrix: m, bbox, transpGroup, softMask: false, blendingColorSpace, isolated, knockout);
4773
4774 if (blendingColorSpace) {
4775 delete blendingColorSpace;
4776 }
4777
4778 ocState = ocSaved;
4779}
4780
4781void 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,
4782 GfxColor *backdropColor)
4783{
4784 Parser *oldParser;
4785 GfxState *savedState;
4786 double oldBaseMatrix[6];
4787 int i;
4788
4789 // push new resources on stack
4790 pushResources(resDict);
4791
4792 // save current graphics state
4793 savedState = saveStateStack();
4794
4795 // kill any pre-existing path
4796 state->clearPath();
4797
4798 // save current parser
4799 oldParser = parser;
4800
4801 // set form transformation matrix
4802 state->concatCTM(a: matrix[0], b: matrix[1], c: matrix[2], d: matrix[3], e: matrix[4], f: matrix[5]);
4803 out->updateCTM(state, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
4804
4805 // set form bounding box
4806 state->moveTo(x: bbox[0], y: bbox[1]);
4807 state->lineTo(x: bbox[2], y: bbox[1]);
4808 state->lineTo(x: bbox[2], y: bbox[3]);
4809 state->lineTo(x: bbox[0], y: bbox[3]);
4810 state->closePath();
4811 state->clip();
4812 out->clip(state);
4813 state->clearPath();
4814
4815 if (softMask || transpGroup) {
4816 if (state->getBlendMode() != gfxBlendNormal) {
4817 state->setBlendMode(gfxBlendNormal);
4818 out->updateBlendMode(state);
4819 }
4820 if (state->getFillOpacity() != 1) {
4821 state->setFillOpacity(1);
4822 out->updateFillOpacity(state);
4823 }
4824 if (state->getStrokeOpacity() != 1) {
4825 state->setStrokeOpacity(1);
4826 out->updateStrokeOpacity(state);
4827 }
4828 out->clearSoftMask(state);
4829 out->beginTransparencyGroup(state, bbox, blendingColorSpace, isolated, knockout, softMask);
4830 }
4831
4832 // set new base matrix
4833 for (i = 0; i < 6; ++i) {
4834 oldBaseMatrix[i] = baseMatrix[i];
4835 baseMatrix[i] = state->getCTM()[i];
4836 }
4837
4838 GfxState *stateBefore = state;
4839
4840 // draw the form
4841 ++displayDepth;
4842 display(obj: str, topLevel: false);
4843 --displayDepth;
4844
4845 if (stateBefore != state) {
4846 if (state->isParentState(state: stateBefore)) {
4847 error(category: errSyntaxError, pos: -1, msg: "There's a form with more q than Q, trying to fix");
4848 while (stateBefore != state) {
4849 restoreState();
4850 }
4851 } else {
4852 error(category: errSyntaxError, pos: -1, msg: "There's a form with more Q than q");
4853 }
4854 }
4855
4856 if (softMask || transpGroup) {
4857 out->endTransparencyGroup(state);
4858 }
4859
4860 // restore base matrix
4861 for (i = 0; i < 6; ++i) {
4862 baseMatrix[i] = oldBaseMatrix[i];
4863 }
4864
4865 // restore parser
4866 parser = oldParser;
4867
4868 // restore graphics state
4869 restoreStateStack(oldState: savedState);
4870
4871 // pop resource stack
4872 popResources();
4873
4874 if (softMask) {
4875 out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
4876 } else if (transpGroup) {
4877 out->paintTransparencyGroup(state, bbox);
4878 }
4879
4880 return;
4881}
4882
4883//------------------------------------------------------------------------
4884// in-line image operators
4885//------------------------------------------------------------------------
4886
4887void Gfx::opBeginImage(Object args[], int numArgs)
4888{
4889 Stream *str;
4890 int c1, c2;
4891
4892 // NB: this function is run even if ocState is false -- doImage() is
4893 // responsible for skipping over the inline image data
4894
4895 // build dict/stream
4896 str = buildImageStream();
4897
4898 // display the image
4899 if (str) {
4900 doImage(ref: nullptr, str, inlineImg: true);
4901
4902 // skip 'EI' tag
4903 c1 = str->getUndecodedStream()->getChar();
4904 c2 = str->getUndecodedStream()->getChar();
4905 while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
4906 c1 = c2;
4907 c2 = str->getUndecodedStream()->getChar();
4908 }
4909 delete str;
4910 }
4911}
4912
4913Stream *Gfx::buildImageStream()
4914{
4915 Stream *str;
4916
4917 // build dictionary
4918 Object dict(new Dict(xref));
4919 Object obj = parser->getObj();
4920 while (!obj.isCmd(cmdA: "ID") && !obj.isEOF()) {
4921 if (!obj.isName()) {
4922 error(category: errSyntaxError, pos: getPos(), msg: "Inline image dictionary key must be a name object");
4923 } else {
4924 auto val = parser->getObj();
4925 if (val.isEOF() || val.isError()) {
4926 break;
4927 }
4928 dict.dictAdd(key: obj.getName(), val: std::move(val));
4929 }
4930 obj = parser->getObj();
4931 }
4932 if (obj.isEOF()) {
4933 error(category: errSyntaxError, pos: getPos(), msg: "End of file in inline image");
4934 return nullptr;
4935 }
4936
4937 // make stream
4938 if (parser->getStream()) {
4939 str = new EmbedStream(parser->getStream(), std::move(dict), false, 0, true);
4940 str = str->addFilters(dict: str->getDict());
4941 } else {
4942 str = nullptr;
4943 }
4944
4945 return str;
4946}
4947
4948void Gfx::opImageData(Object args[], int numArgs)
4949{
4950 error(category: errInternal, pos: getPos(), msg: "Got 'ID' operator");
4951}
4952
4953void Gfx::opEndImage(Object args[], int numArgs)
4954{
4955 error(category: errInternal, pos: getPos(), msg: "Got 'EI' operator");
4956}
4957
4958//------------------------------------------------------------------------
4959// type 3 font operators
4960//------------------------------------------------------------------------
4961
4962void Gfx::opSetCharWidth(Object args[], int numArgs)
4963{
4964 out->type3D0(state, args[0].getNum(), args[1].getNum());
4965}
4966
4967void Gfx::opSetCacheDevice(Object args[], int numArgs)
4968{
4969 out->type3D1(state, args[0].getNum(), args[1].getNum(), args[2].getNum(), args[3].getNum(), args[4].getNum(), args[5].getNum());
4970}
4971
4972//------------------------------------------------------------------------
4973// compatibility operators
4974//------------------------------------------------------------------------
4975
4976void Gfx::opBeginIgnoreUndef(Object args[], int numArgs)
4977{
4978 ++ignoreUndef;
4979}
4980
4981void Gfx::opEndIgnoreUndef(Object args[], int numArgs)
4982{
4983 if (ignoreUndef > 0) {
4984 --ignoreUndef;
4985 }
4986}
4987
4988//------------------------------------------------------------------------
4989// marked content operators
4990//------------------------------------------------------------------------
4991
4992enum GfxMarkedContentKind
4993{
4994 gfxMCOptionalContent,
4995 gfxMCActualText,
4996 gfxMCOther
4997};
4998
4999struct MarkedContentStack
5000{
5001 GfxMarkedContentKind kind;
5002 bool ocSuppressed; // are we ignoring content based on OptionalContent?
5003 MarkedContentStack *next; // next object on stack
5004};
5005
5006void Gfx::popMarkedContent()
5007{
5008 MarkedContentStack *mc = mcStack;
5009 mcStack = mc->next;
5010 delete mc;
5011}
5012
5013void Gfx::pushMarkedContent()
5014{
5015 MarkedContentStack *mc = new MarkedContentStack();
5016 mc->ocSuppressed = false;
5017 mc->kind = gfxMCOther;
5018 mc->next = mcStack;
5019 mcStack = mc;
5020}
5021
5022bool Gfx::contentIsHidden()
5023{
5024 MarkedContentStack *mc = mcStack;
5025 bool hidden = mc && mc->ocSuppressed;
5026 while (!hidden && mc && mc->next) {
5027 mc = mc->next;
5028 hidden = mc->ocSuppressed;
5029 }
5030 return hidden;
5031}
5032
5033void Gfx::opBeginMarkedContent(Object args[], int numArgs)
5034{
5035 // push a new stack entry
5036 pushMarkedContent();
5037
5038 OCGs *contentConfig = catalog->getOptContentConfig();
5039 const char *name0 = args[0].getName();
5040 if (strncmp(s1: name0, s2: "OC", n: 2) == 0 && contentConfig) {
5041 if (numArgs >= 2) {
5042 if (args[1].isName()) {
5043 const char *name1 = args[1].getName();
5044 MarkedContentStack *mc = mcStack;
5045 mc->kind = gfxMCOptionalContent;
5046 Object markedContent = res->lookupMarkedContentNF(name: name1);
5047 if (!markedContent.isNull()) {
5048 bool visible = contentConfig->optContentIsVisible(dictRef: &markedContent);
5049 mc->ocSuppressed = !(visible);
5050 } else {
5051 error(category: errSyntaxError, pos: getPos(), msg: "DID NOT find {0:s}", name1);
5052 }
5053 } else {
5054 error(category: errSyntaxError, pos: getPos(), msg: "Unexpected MC Type: {0:d}", args[1].getType());
5055 }
5056 } else {
5057 error(category: errSyntaxError, pos: getPos(), msg: "insufficient arguments for Marked Content");
5058 }
5059 } else if (args[0].isName(nameA: "Span") && numArgs == 2) {
5060 Object dictToUse;
5061 if (args[1].isDict()) {
5062 dictToUse = args[1].copy();
5063 } else if (args[1].isName()) {
5064 dictToUse = res->lookupMarkedContentNF(name: args[1].getName()).fetch(xref);
5065 }
5066
5067 if (dictToUse.isDict()) {
5068 Object obj = dictToUse.dictLookup(key: "ActualText");
5069 if (obj.isString()) {
5070 out->beginActualText(state, obj.getString());
5071 MarkedContentStack *mc = mcStack;
5072 mc->kind = gfxMCActualText;
5073 }
5074 }
5075 }
5076
5077 if (printCommands) {
5078 printf(format: " marked content: %s ", args[0].getName());
5079 if (numArgs == 2) {
5080 args[1].print(stdout);
5081 }
5082 printf(format: "\n");
5083 fflush(stdout);
5084 }
5085 ocState = !contentIsHidden();
5086
5087 if (numArgs == 2 && args[1].isDict()) {
5088 out->beginMarkedContent(name: args[0].getName(), properties: args[1].getDict());
5089 } else if (numArgs == 1) {
5090 out->beginMarkedContent(name: args[0].getName(), properties: nullptr);
5091 }
5092}
5093
5094void Gfx::opEndMarkedContent(Object args[], int numArgs)
5095{
5096 if (!mcStack) {
5097 error(category: errSyntaxWarning, pos: getPos(), msg: "Mismatched EMC operator");
5098 return;
5099 }
5100
5101 MarkedContentStack *mc = mcStack;
5102 GfxMarkedContentKind mcKind = mc->kind;
5103
5104 // pop the stack
5105 popMarkedContent();
5106
5107 if (mcKind == gfxMCActualText) {
5108 out->endActualText(state);
5109 }
5110 ocState = !contentIsHidden();
5111
5112 out->endMarkedContent(state);
5113}
5114
5115void Gfx::opMarkPoint(Object args[], int numArgs)
5116{
5117 if (printCommands) {
5118 printf(format: " mark point: %s ", args[0].getName());
5119 if (numArgs == 2) {
5120 args[1].print(stdout);
5121 }
5122 printf(format: "\n");
5123 fflush(stdout);
5124 }
5125
5126 if (numArgs == 2 && args[1].isDict()) {
5127 out->markPoint(name: args[0].getName(), properties: args[1].getDict());
5128 } else {
5129 out->markPoint(name: args[0].getName());
5130 }
5131}
5132
5133//------------------------------------------------------------------------
5134// misc
5135//------------------------------------------------------------------------
5136
5137struct GfxStackStateSaver
5138{
5139 explicit GfxStackStateSaver(Gfx *gfxA) : gfx(gfxA) { gfx->saveState(); }
5140
5141 ~GfxStackStateSaver() { gfx->restoreState(); }
5142
5143 GfxStackStateSaver(const GfxStackStateSaver &) = delete;
5144 GfxStackStateSaver &operator=(const GfxStackStateSaver &) = delete;
5145
5146 Gfx *const gfx;
5147};
5148
5149void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor, double xMin, double yMin, double xMax, double yMax, int rotate)
5150{
5151 Dict *dict, *resDict;
5152 double formXMin, formYMin, formXMax, formYMax;
5153 double x, y, sx, sy, tx, ty;
5154 double m[6], bbox[4];
5155 GfxColor color;
5156 int i;
5157
5158 // this function assumes that we are in the default user space,
5159 // i.e., baseMatrix = ctm
5160
5161 // if the bounding box has zero width or height, don't draw anything
5162 // at all
5163 if (xMin == xMax || yMin == yMax) {
5164 return;
5165 }
5166
5167 // saves gfx state and automatically restores it on return
5168 GfxStackStateSaver stackStateSaver(this);
5169
5170 // Rotation around the topleft corner (for the NoRotate flag)
5171 if (rotate != 0) {
5172 const double angle_rad = rotate * M_PI / 180;
5173 const double c = cos(x: angle_rad);
5174 const double s = sin(x: angle_rad);
5175
5176 // (xMin, yMax) is the pivot
5177 const double unrotateMTX[6] = { +c, -s, +s, +c, -c * xMin - s * yMax + xMin, -c * yMax + s * xMin + yMax };
5178
5179 state->concatCTM(a: unrotateMTX[0], b: unrotateMTX[1], c: unrotateMTX[2], d: unrotateMTX[3], e: unrotateMTX[4], f: unrotateMTX[5]);
5180 out->updateCTM(state, unrotateMTX[0], unrotateMTX[1], unrotateMTX[2], unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5181 }
5182
5183 // draw the appearance stream (if there is one)
5184 if (str->isStream()) {
5185
5186 // get stream dict
5187 dict = str->streamGetDict();
5188
5189 // get the form bounding box
5190 Object bboxObj = dict->lookup(key: "BBox");
5191 if (!bboxObj.isArray()) {
5192 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box");
5193 return;
5194 }
5195 for (i = 0; i < 4; ++i) {
5196 Object obj1 = bboxObj.arrayGet(i);
5197 if (likely(obj1.isNum())) {
5198 bbox[i] = obj1.getNum();
5199 } else {
5200 error(category: errSyntaxError, pos: getPos(), msg: "Bad form bounding box value");
5201 return;
5202 }
5203 }
5204
5205 // get the form matrix
5206 Object matrixObj = dict->lookup(key: "Matrix");
5207 if (matrixObj.isArray() && matrixObj.arrayGetLength() >= 6) {
5208 for (i = 0; i < 6; ++i) {
5209 Object obj1 = matrixObj.arrayGet(i);
5210 if (likely(obj1.isNum())) {
5211 m[i] = obj1.getNum();
5212 } else {
5213 error(category: errSyntaxError, pos: getPos(), msg: "Bad form matrix");
5214 return;
5215 }
5216 }
5217 } else {
5218 m[0] = 1;
5219 m[1] = 0;
5220 m[2] = 0;
5221 m[3] = 1;
5222 m[4] = 0;
5223 m[5] = 0;
5224 }
5225
5226 // transform the four corners of the form bbox to default user
5227 // space, and construct the transformed bbox
5228 x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
5229 y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
5230 formXMin = formXMax = x;
5231 formYMin = formYMax = y;
5232 x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
5233 y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
5234 if (x < formXMin) {
5235 formXMin = x;
5236 } else if (x > formXMax) {
5237 formXMax = x;
5238 }
5239 if (y < formYMin) {
5240 formYMin = y;
5241 } else if (y > formYMax) {
5242 formYMax = y;
5243 }
5244 x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
5245 y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
5246 if (x < formXMin) {
5247 formXMin = x;
5248 } else if (x > formXMax) {
5249 formXMax = x;
5250 }
5251 if (y < formYMin) {
5252 formYMin = y;
5253 } else if (y > formYMax) {
5254 formYMax = y;
5255 }
5256 x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
5257 y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
5258 if (x < formXMin) {
5259 formXMin = x;
5260 } else if (x > formXMax) {
5261 formXMax = x;
5262 }
5263 if (y < formYMin) {
5264 formYMin = y;
5265 } else if (y > formYMax) {
5266 formYMax = y;
5267 }
5268
5269 // construct a mapping matrix, [sx 0 0], which maps the transformed
5270 // [0 sy 0]
5271 // [tx ty 1]
5272 // bbox to the annotation rectangle
5273 if (formXMin == formXMax) {
5274 // this shouldn't happen
5275 sx = 1;
5276 } else {
5277 sx = (xMax - xMin) / (formXMax - formXMin);
5278 }
5279 if (formYMin == formYMax) {
5280 // this shouldn't happen
5281 sy = 1;
5282 } else {
5283 sy = (yMax - yMin) / (formYMax - formYMin);
5284 }
5285 tx = -formXMin * sx + xMin;
5286 ty = -formYMin * sy + yMin;
5287
5288 // the final transform matrix is (form matrix) * (mapping matrix)
5289 m[0] *= sx;
5290 m[1] *= sy;
5291 m[2] *= sx;
5292 m[3] *= sy;
5293 m[4] = m[4] * sx + tx;
5294 m[5] = m[5] * sy + ty;
5295
5296 // get the resources
5297 Object resObj = dict->lookup(key: "Resources");
5298 resDict = resObj.isDict() ? resObj.getDict() : nullptr;
5299
5300 // draw it
5301 drawForm(str, resDict, matrix: m, bbox);
5302 }
5303
5304 // draw the border
5305 if (border && border->getWidth() > 0 && (!aColor || aColor->getSpace() != AnnotColor::colorTransparent)) {
5306 if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
5307 state->setStrokePattern(nullptr);
5308 state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
5309 out->updateStrokeColorSpace(state);
5310 }
5311 double r, g, b;
5312 if (!aColor) {
5313 r = g = b = 0;
5314 } else if ((aColor->getSpace() == AnnotColor::colorRGB)) {
5315 const double *values = aColor->getValues();
5316 r = values[0];
5317 g = values[1];
5318 b = values[2];
5319 } else {
5320 error(category: errUnimplemented, pos: -1, msg: "AnnotColor different than RGB and Transparent not supported");
5321 r = g = b = 0;
5322 };
5323 color.c[0] = dblToCol(x: r);
5324 color.c[1] = dblToCol(x: g);
5325 color.c[2] = dblToCol(x: b);
5326 state->setStrokeColor(&color);
5327 out->updateStrokeColor(state);
5328 state->setLineWidth(border->getWidth());
5329 out->updateLineWidth(state);
5330 const std::vector<double> &dash = border->getDash();
5331 if (border->getStyle() == AnnotBorder::borderDashed && dash.size() > 0) {
5332 std::vector<double> dash2 = dash;
5333 state->setLineDash(dash: std::move(dash2), start: 0);
5334 out->updateLineDash(state);
5335 }
5336 //~ this doesn't currently handle the beveled and engraved styles
5337 state->clearPath();
5338 state->moveTo(x: xMin, y: yMin);
5339 state->lineTo(x: xMax, y: yMin);
5340 if (border->getStyle() != AnnotBorder::borderUnderlined) {
5341 state->lineTo(x: xMax, y: yMax);
5342 state->lineTo(x: xMin, y: yMax);
5343 state->closePath();
5344 }
5345 out->stroke(state);
5346 }
5347}
5348
5349int Gfx::bottomGuard()
5350{
5351 return stateGuards[stateGuards.size() - 1];
5352}
5353
5354void Gfx::pushStateGuard()
5355{
5356 stateGuards.push_back(x: stackHeight);
5357}
5358
5359void Gfx::popStateGuard()
5360{
5361 while (stackHeight > bottomGuard() && state->hasSaves()) {
5362 restoreState();
5363 }
5364 stateGuards.pop_back();
5365}
5366
5367void Gfx::saveState()
5368{
5369 out->saveState(state);
5370 state = state->save();
5371 stackHeight++;
5372}
5373
5374void Gfx::restoreState()
5375{
5376 if (stackHeight <= bottomGuard() || !state->hasSaves()) {
5377 error(category: errSyntaxError, pos: -1, msg: "Restoring state when no valid states to pop");
5378 return;
5379 }
5380 state = state->restore();
5381 out->restoreState(state);
5382 stackHeight--;
5383 clip = clipNone;
5384}
5385
5386// Create a new state stack, and initialize it with a copy of the
5387// current state.
5388GfxState *Gfx::saveStateStack()
5389{
5390 GfxState *oldState;
5391
5392 out->saveState(state);
5393 oldState = state;
5394 state = state->copy(copyPath: true);
5395 return oldState;
5396}
5397
5398// Switch back to the previous state stack.
5399void Gfx::restoreStateStack(GfxState *oldState)
5400{
5401 while (state->hasSaves()) {
5402 restoreState();
5403 }
5404 delete state;
5405 state = oldState;
5406 out->restoreState(state);
5407}
5408
5409void Gfx::pushResources(Dict *resDict)
5410{
5411 res = new GfxResources(xref, resDict, res);
5412}
5413
5414void Gfx::popResources()
5415{
5416 GfxResources *resPtr;
5417
5418 resPtr = res->getNext();
5419 delete res;
5420 res = resPtr;
5421}
5422

source code of poppler/poppler/Gfx.cc