1//========================================================================
2//
3// SplashOutputDev.cc
4//
5// Copyright 2003 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 Takashi Iwai <tiwai@suse.de>
17// Copyright (C) 2006 Stefan Schweizer <genstef@gentoo.org>
18// Copyright (C) 2006-2022 Albert Astals Cid <aacid@kde.org>
19// Copyright (C) 2006 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
20// Copyright (C) 2006 Scott Turner <scotty1024@mac.com>
21// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
22// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
23// Copyright (C) 2009-2016, 2020, 2022, 2023 Thomas Freitag <Thomas.Freitag@alfa.de>
24// Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
25// Copyright (C) 2009, 2014-2016, 2019 William Bader <williambader@hotmail.com>
26// Copyright (C) 2010 Patrick Spendrin <ps_ml@gmx.de>
27// Copyright (C) 2010 Brian Cameron <brian.cameron@oracle.com>
28// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
29// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
30// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
31// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
32// Copyright (C) 2011, 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
33// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
34// Copyright (C) 2013 Li Junling <lijunling@sina.com>
35// Copyright (C) 2014 Ed Porras <ed@moto-research.com>
36// Copyright (C) 2014 Richard PALO <richard@netbsd.org>
37// Copyright (C) 2015 Tamas Szekeres <szekerest@gmail.com>
38// Copyright (C) 2015 Kenji Uno <ku@digitaldolphins.jp>
39// Copyright (C) 2016 Takahiro Hashimoto <kenya888.en@gmail.com>
40// Copyright (C) 2017, 2021, 2024 Even Rouault <even.rouault@spatialys.com>
41// Copyright (C) 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 Stefan Brüns <stefan.bruens@rwth-aachen.de>
43// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
44// Copyright (C) 2019 Christian Persch <chpe@src.gnome.org>
45// Copyright (C) 2020-2022 Oliver Sander <oliver.sander@tu-dresden.de>
46//
47// To see a description of the changes please see the Changelog file that
48// came with your tarball or type make ChangeLog if you are building from git
49//
50//========================================================================
51
52#include <config.h>
53
54#include <cstring>
55#include <cmath>
56#include <vector>
57#include "goo/gfile.h"
58#include "GlobalParams.h"
59#include "Error.h"
60#include "Object.h"
61#include "Gfx.h"
62#include "GfxFont.h"
63#include "Page.h"
64#include "PDFDoc.h"
65#include "Link.h"
66#include "FontEncodingTables.h"
67#include "fofi/FoFiTrueType.h"
68#include "splash/SplashBitmap.h"
69#include "splash/SplashGlyphBitmap.h"
70#include "splash/SplashPattern.h"
71#include "splash/SplashScreen.h"
72#include "splash/SplashPath.h"
73#include "splash/SplashState.h"
74#include "splash/SplashErrorCodes.h"
75#include "splash/SplashFontEngine.h"
76#include "splash/SplashFont.h"
77#include "splash/SplashFontFile.h"
78#include "splash/SplashFontFileID.h"
79#include "splash/SplashMath.h"
80#include "splash/Splash.h"
81#include "SplashOutputDev.h"
82#include <algorithm>
83
84static const double s_minLineWidth = 0.0;
85
86static inline void convertGfxColor(SplashColorPtr dest, const SplashColorMode colorMode, const GfxColorSpace *colorSpace, const GfxColor *src)
87{
88 SplashColor color;
89 GfxGray gray;
90 GfxRGB rgb;
91 GfxCMYK cmyk;
92 GfxColor deviceN;
93
94 // make gcc happy
95 color[0] = color[1] = color[2] = 0;
96 color[3] = 0;
97 switch (colorMode) {
98 case splashModeMono1:
99 case splashModeMono8:
100 colorSpace->getGray(color: src, gray: &gray);
101 color[0] = colToByte(x: gray);
102 break;
103 case splashModeXBGR8:
104 color[3] = 255;
105 // fallthrough
106 case splashModeBGR8:
107 case splashModeRGB8:
108 colorSpace->getRGB(color: src, rgb: &rgb);
109 color[0] = colToByte(x: rgb.r);
110 color[1] = colToByte(x: rgb.g);
111 color[2] = colToByte(x: rgb.b);
112 break;
113 case splashModeCMYK8:
114 colorSpace->getCMYK(color: src, cmyk: &cmyk);
115 color[0] = colToByte(x: cmyk.c);
116 color[1] = colToByte(x: cmyk.m);
117 color[2] = colToByte(x: cmyk.y);
118 color[3] = colToByte(x: cmyk.k);
119 break;
120 case splashModeDeviceN8:
121 colorSpace->getDeviceN(color: src, deviceN: &deviceN);
122 for (int i = 0; i < SPOT_NCOMPS + 4; i++) {
123 color[i] = colToByte(x: deviceN.c[i]);
124 }
125 break;
126 }
127 splashColorCopy(dest, src: color);
128}
129
130// Copy a color according to the color mode.
131// Use convertGfxShortColor() below when the destination is a bitmap
132// to avoid overwriting cells.
133// Calling this in SplashGouraudPattern::getParameterizedColor() fixes bug 90570.
134// Use convertGfxColor() above when the destination is an array of SPOT_NCOMPS+4 bytes,
135// to ensure that everything is initialized.
136
137static inline void convertGfxShortColor(SplashColorPtr dest, const SplashColorMode colorMode, const GfxColorSpace *colorSpace, const GfxColor *src)
138{
139 switch (colorMode) {
140 case splashModeMono1:
141 case splashModeMono8: {
142 GfxGray gray;
143 colorSpace->getGray(color: src, gray: &gray);
144 dest[0] = colToByte(x: gray);
145 } break;
146 case splashModeXBGR8:
147 dest[3] = 255;
148 // fallthrough
149 case splashModeBGR8:
150 case splashModeRGB8: {
151 GfxRGB rgb;
152 colorSpace->getRGB(color: src, rgb: &rgb);
153 dest[0] = colToByte(x: rgb.r);
154 dest[1] = colToByte(x: rgb.g);
155 dest[2] = colToByte(x: rgb.b);
156 } break;
157 case splashModeCMYK8: {
158 GfxCMYK cmyk;
159 colorSpace->getCMYK(color: src, cmyk: &cmyk);
160 dest[0] = colToByte(x: cmyk.c);
161 dest[1] = colToByte(x: cmyk.m);
162 dest[2] = colToByte(x: cmyk.y);
163 dest[3] = colToByte(x: cmyk.k);
164 } break;
165 case splashModeDeviceN8: {
166 GfxColor deviceN;
167 colorSpace->getDeviceN(color: src, deviceN: &deviceN);
168 for (int i = 0; i < SPOT_NCOMPS + 4; i++) {
169 dest[i] = colToByte(x: deviceN.c[i]);
170 }
171 } break;
172 }
173}
174
175//------------------------------------------------------------------------
176// SplashGouraudPattern
177//------------------------------------------------------------------------
178SplashGouraudPattern::SplashGouraudPattern(bool bDirectColorTranslationA, GfxState *stateA, GfxGouraudTriangleShading *shadingA)
179{
180 state = stateA;
181 shading = shadingA;
182 bDirectColorTranslation = bDirectColorTranslationA;
183 gfxMode = shadingA->getColorSpace()->getMode();
184}
185
186SplashGouraudPattern::~SplashGouraudPattern() { }
187
188void SplashGouraudPattern::getNonParametrizedTriangle(int i, SplashColorMode mode, double *x0, double *y0, SplashColorPtr color0, double *x1, double *y1, SplashColorPtr color1, double *x2, double *y2, SplashColorPtr color2)
189{
190 GfxColor c0, c1, c2;
191 shading->getTriangle(i, x0, y0, color0: &c0, x1, y1, color1: &c1, x2, y2, color2: &c2);
192
193 const GfxColorSpace *srcColorSpace = shading->getColorSpace();
194 convertGfxColor(dest: color0, colorMode: mode, colorSpace: srcColorSpace, src: &c0);
195 convertGfxColor(dest: color1, colorMode: mode, colorSpace: srcColorSpace, src: &c1);
196 convertGfxColor(dest: color2, colorMode: mode, colorSpace: srcColorSpace, src: &c2);
197}
198
199void SplashGouraudPattern::getParameterizedColor(double colorinterp, SplashColorMode mode, SplashColorPtr dest)
200{
201 GfxColor src;
202 shading->getParameterizedColor(t: colorinterp, color: &src);
203
204 if (bDirectColorTranslation) {
205 const int colorComps = splashColorModeNComps[mode];
206 for (int m = 0; m < colorComps; ++m) {
207 dest[m] = colToByte(x: src.c[m]);
208 }
209 } else {
210 GfxColorSpace *srcColorSpace = shading->getColorSpace();
211 convertGfxShortColor(dest, colorMode: mode, colorSpace: srcColorSpace, src: &src);
212 }
213}
214
215//------------------------------------------------------------------------
216// SplashFunctionPattern
217//------------------------------------------------------------------------
218
219SplashFunctionPattern::SplashFunctionPattern(SplashColorMode colorModeA, GfxState *stateA, GfxFunctionShading *shadingA)
220{
221 Matrix ctm;
222 SplashColor defaultColor;
223 GfxColor srcColor;
224 const double *matrix = shadingA->getMatrix();
225
226 shading = shadingA;
227 state = stateA;
228 colorMode = colorModeA;
229
230 state->getCTM(m: &ctm);
231
232 double a1 = ctm.m[0];
233 double b1 = ctm.m[1];
234 double c1 = ctm.m[2];
235 double d1 = ctm.m[3];
236
237 ctm.m[0] = matrix[0] * a1 + matrix[1] * c1;
238 ctm.m[1] = matrix[0] * b1 + matrix[1] * d1;
239 ctm.m[2] = matrix[2] * a1 + matrix[3] * c1;
240 ctm.m[3] = matrix[2] * b1 + matrix[3] * d1;
241 ctm.m[4] = matrix[4] * a1 + matrix[5] * c1 + ctm.m[4];
242 ctm.m[5] = matrix[4] * b1 + matrix[5] * d1 + ctm.m[5];
243 ctm.invertTo(other: &ictm);
244
245 gfxMode = shadingA->getColorSpace()->getMode();
246 shadingA->getColorSpace()->getDefaultColor(color: &srcColor);
247 shadingA->getDomain(x0A: &xMin, y0A: &yMin, x1A: &xMax, y1A: &yMax);
248 convertGfxColor(dest: defaultColor, colorMode: colorModeA, colorSpace: shadingA->getColorSpace(), src: &srcColor);
249}
250
251SplashFunctionPattern::~SplashFunctionPattern() { }
252
253bool SplashFunctionPattern::getColor(int x, int y, SplashColorPtr c)
254{
255 GfxColor gfxColor;
256 double xc, yc;
257
258 ictm.transform(x, y, tx: &xc, ty: &yc);
259 if (xc < xMin || xc > xMax || yc < yMin || yc > yMax) {
260 return false;
261 }
262 shading->getColor(x: xc, y: yc, color: &gfxColor);
263 convertGfxColor(dest: c, colorMode, colorSpace: shading->getColorSpace(), src: &gfxColor);
264 return true;
265}
266
267//------------------------------------------------------------------------
268// SplashUnivariatePattern
269//------------------------------------------------------------------------
270
271SplashUnivariatePattern::SplashUnivariatePattern(SplashColorMode colorModeA, GfxState *stateA, GfxUnivariateShading *shadingA)
272{
273 Matrix ctm;
274 double xMin, yMin, xMax, yMax;
275
276 shading = shadingA;
277 state = stateA;
278 colorMode = colorModeA;
279
280 state->getCTM(m: &ctm);
281 ctm.invertTo(other: &ictm);
282
283 // get the function domain
284 t0 = shading->getDomain0();
285 t1 = shading->getDomain1();
286 dt = t1 - t0;
287
288 stateA->getUserClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
289 shadingA->setupCache(ctm: &ctm, xMin, yMin, xMax, yMax);
290 gfxMode = shadingA->getColorSpace()->getMode();
291}
292
293SplashUnivariatePattern::~SplashUnivariatePattern() { }
294
295bool SplashUnivariatePattern::getColor(int x, int y, SplashColorPtr c)
296{
297 GfxColor gfxColor;
298 double xc, yc, t;
299
300 ictm.transform(x, y, tx: &xc, ty: &yc);
301 if (!getParameter(xs: xc, ys: yc, t: &t)) {
302 return false;
303 }
304
305 const int filled = shading->getColor(t, color: &gfxColor);
306 if (unlikely(filled < shading->getColorSpace()->getNComps())) {
307 for (int i = filled; i < shading->getColorSpace()->getNComps(); ++i) {
308 gfxColor.c[i] = 0;
309 }
310 }
311 convertGfxColor(dest: c, colorMode, colorSpace: shading->getColorSpace(), src: &gfxColor);
312 return true;
313}
314
315bool SplashUnivariatePattern::testPosition(int x, int y)
316{
317 double xc, yc, t;
318
319 ictm.transform(x, y, tx: &xc, ty: &yc);
320 if (!getParameter(xs: xc, ys: yc, t: &t)) {
321 return false;
322 }
323 return (t0 < t1) ? (t > t0 && t < t1) : (t > t1 && t < t0);
324}
325
326//------------------------------------------------------------------------
327// SplashRadialPattern
328//------------------------------------------------------------------------
329#define RADIAL_EPSILON (1. / 1024 / 1024)
330
331SplashRadialPattern::SplashRadialPattern(SplashColorMode colorModeA, GfxState *stateA, GfxRadialShading *shadingA) : SplashUnivariatePattern(colorModeA, stateA, shadingA)
332{
333 SplashColor defaultColor;
334 GfxColor srcColor;
335
336 shadingA->getCoords(x0A: &x0, y0A: &y0, r0A: &r0, x1A: &dx, y1A: &dy, r1A: &dr);
337 dx -= x0;
338 dy -= y0;
339 dr -= r0;
340 a = dx * dx + dy * dy - dr * dr;
341 if (fabs(x: a) > RADIAL_EPSILON) {
342 inva = 1.0 / a;
343 }
344 shadingA->getColorSpace()->getDefaultColor(color: &srcColor);
345 convertGfxColor(dest: defaultColor, colorMode: colorModeA, colorSpace: shadingA->getColorSpace(), src: &srcColor);
346}
347
348SplashRadialPattern::~SplashRadialPattern() { }
349
350bool SplashRadialPattern::getParameter(double xs, double ys, double *t)
351{
352 double b, c, s0, s1;
353
354 // We want to solve this system of equations:
355 //
356 // 1. (x - xc(s))^2 + (y -yc(s))^2 = rc(s)^2
357 // 2. xc(s) = x0 + s * (x1 - xo)
358 // 3. yc(s) = y0 + s * (y1 - yo)
359 // 4. rc(s) = r0 + s * (r1 - ro)
360 //
361 // To simplify the system a little, we translate
362 // our coordinates to have the origin in (x0,y0)
363
364 xs -= x0;
365 ys -= y0;
366
367 // Then we have to solve the equation:
368 // A*s^2 - 2*B*s + C = 0
369 // where
370 // A = dx^2 + dy^2 - dr^2
371 // B = xs*dx + ys*dy + r0*dr
372 // C = xs^2 + ys^2 - r0^2
373
374 b = xs * dx + ys * dy + r0 * dr;
375 c = xs * xs + ys * ys - r0 * r0;
376
377 if (fabs(x: a) <= RADIAL_EPSILON) {
378 // A is 0, thus the equation simplifies to:
379 // -2*B*s + C = 0
380 // If B is 0, we can either have no solution or an indeterminate
381 // equation, thus we behave as if we had an invalid solution
382 if (fabs(x: b) <= RADIAL_EPSILON) {
383 return false;
384 }
385
386 s0 = s1 = 0.5 * c / b;
387 } else {
388 double d;
389
390 d = b * b - a * c;
391 if (d < 0) {
392 return false;
393 }
394
395 d = sqrt(x: d);
396 s0 = b + d;
397 s1 = b - d;
398
399 // If A < 0, one of the two solutions will have negative radius,
400 // thus it will be ignored. Otherwise we know that s1 <= s0
401 // (because d >=0 implies b - d <= b + d), so if both are valid it
402 // will be the true solution.
403 s0 *= inva;
404 s1 *= inva;
405 }
406
407 if (r0 + s0 * dr >= 0) {
408 if (0 <= s0 && s0 <= 1) {
409 *t = t0 + dt * s0;
410 return true;
411 } else if (s0 < 0 && shading->getExtend0()) {
412 *t = t0;
413 return true;
414 } else if (s0 > 1 && shading->getExtend1()) {
415 *t = t1;
416 return true;
417 }
418 }
419
420 if (r0 + s1 * dr >= 0) {
421 if (0 <= s1 && s1 <= 1) {
422 *t = t0 + dt * s1;
423 return true;
424 } else if (s1 < 0 && shading->getExtend0()) {
425 *t = t0;
426 return true;
427 } else if (s1 > 1 && shading->getExtend1()) {
428 *t = t1;
429 return true;
430 }
431 }
432
433 return false;
434}
435
436#undef RADIAL_EPSILON
437
438//------------------------------------------------------------------------
439// SplashAxialPattern
440//------------------------------------------------------------------------
441
442SplashAxialPattern::SplashAxialPattern(SplashColorMode colorModeA, GfxState *stateA, GfxAxialShading *shadingA) : SplashUnivariatePattern(colorModeA, stateA, shadingA)
443{
444 SplashColor defaultColor;
445 GfxColor srcColor;
446
447 shadingA->getCoords(x0A: &x0, y0A: &y0, x1A: &x1, y1A: &y1);
448 dx = x1 - x0;
449 dy = y1 - y0;
450 const double mul_denominator = (dx * dx + dy * dy);
451 if (unlikely(mul_denominator == 0)) {
452 mul = 0;
453 } else {
454 mul = 1 / mul_denominator;
455 }
456 shadingA->getColorSpace()->getDefaultColor(color: &srcColor);
457 convertGfxColor(dest: defaultColor, colorMode: colorModeA, colorSpace: shadingA->getColorSpace(), src: &srcColor);
458}
459
460SplashAxialPattern::~SplashAxialPattern() { }
461
462bool SplashAxialPattern::getParameter(double xc, double yc, double *t)
463{
464 double s;
465
466 xc -= x0;
467 yc -= y0;
468
469 s = (xc * dx + yc * dy) * mul;
470 if (0 <= s && s <= 1) {
471 *t = t0 + dt * s;
472 } else if (s < 0 && shading->getExtend0()) {
473 *t = t0;
474 } else if (s > 1 && shading->getExtend1()) {
475 *t = t1;
476 } else {
477 return false;
478 }
479
480 return true;
481}
482
483//------------------------------------------------------------------------
484// Type 3 font cache size parameters
485#define type3FontCacheAssoc 8
486#define type3FontCacheMaxSets 8
487#define type3FontCacheSize (128 * 1024)
488
489//------------------------------------------------------------------------
490// Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result.
491static inline unsigned char div255(int x)
492{
493 return (unsigned char)((x + (x >> 8) + 0x80) >> 8);
494}
495
496//------------------------------------------------------------------------
497// Blend functions
498//------------------------------------------------------------------------
499
500static void splashOutBlendMultiply(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
501{
502 int i;
503
504 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
505 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
506 dest[i] = 255 - dest[i];
507 src[i] = 255 - src[i];
508 }
509 }
510 {
511 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
512 blend[i] = (dest[i] * src[i]) / 255;
513 }
514 }
515 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
516 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
517 dest[i] = 255 - dest[i];
518 src[i] = 255 - src[i];
519 blend[i] = 255 - blend[i];
520 }
521 }
522}
523
524static void splashOutBlendScreen(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
525{
526 int i;
527
528 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
529 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
530 dest[i] = 255 - dest[i];
531 src[i] = 255 - src[i];
532 }
533 }
534 {
535 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
536 blend[i] = dest[i] + src[i] - (dest[i] * src[i]) / 255;
537 }
538 }
539 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
540 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
541 dest[i] = 255 - dest[i];
542 src[i] = 255 - src[i];
543 blend[i] = 255 - blend[i];
544 }
545 }
546}
547
548static void splashOutBlendOverlay(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
549{
550 int i;
551
552 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
553 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
554 dest[i] = 255 - dest[i];
555 src[i] = 255 - src[i];
556 }
557 }
558 {
559 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
560 blend[i] = dest[i] < 0x80 ? (src[i] * 2 * dest[i]) / 255 : 255 - 2 * ((255 - src[i]) * (255 - dest[i])) / 255;
561 }
562 }
563 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
564 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
565 dest[i] = 255 - dest[i];
566 src[i] = 255 - src[i];
567 blend[i] = 255 - blend[i];
568 }
569 }
570}
571
572static void splashOutBlendDarken(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
573{
574 int i;
575
576 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
577 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
578 dest[i] = 255 - dest[i];
579 src[i] = 255 - src[i];
580 }
581 }
582 {
583 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
584 blend[i] = dest[i] < src[i] ? dest[i] : src[i];
585 }
586 }
587 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
588 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
589 dest[i] = 255 - dest[i];
590 src[i] = 255 - src[i];
591 blend[i] = 255 - blend[i];
592 }
593 }
594}
595
596static void splashOutBlendLighten(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
597{
598 int i;
599
600 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
601 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
602 dest[i] = 255 - dest[i];
603 src[i] = 255 - src[i];
604 }
605 }
606 {
607 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
608 blend[i] = dest[i] > src[i] ? dest[i] : src[i];
609 }
610 }
611 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
612 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
613 dest[i] = 255 - dest[i];
614 src[i] = 255 - src[i];
615 blend[i] = 255 - blend[i];
616 }
617 }
618}
619
620static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
621{
622 int i, x;
623
624 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
625 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
626 dest[i] = 255 - dest[i];
627 src[i] = 255 - src[i];
628 }
629 }
630 {
631 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
632 if (src[i] == 255) {
633 blend[i] = 255;
634 } else {
635 x = (dest[i] * 255) / (255 - src[i]);
636 blend[i] = x <= 255 ? x : 255;
637 }
638 }
639 }
640 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
641 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
642 dest[i] = 255 - dest[i];
643 src[i] = 255 - src[i];
644 blend[i] = 255 - blend[i];
645 }
646 }
647}
648
649static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
650{
651 int i, x;
652
653 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
654 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
655 dest[i] = 255 - dest[i];
656 src[i] = 255 - src[i];
657 }
658 }
659 {
660 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
661 if (src[i] == 0) {
662 blend[i] = 0;
663 } else {
664 x = ((255 - dest[i]) * 255) / src[i];
665 blend[i] = x <= 255 ? 255 - x : 0;
666 }
667 }
668 }
669 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
670 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
671 dest[i] = 255 - dest[i];
672 src[i] = 255 - src[i];
673 blend[i] = 255 - blend[i];
674 }
675 }
676}
677
678static void splashOutBlendHardLight(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
679{
680 int i;
681
682 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
683 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
684 dest[i] = 255 - dest[i];
685 src[i] = 255 - src[i];
686 }
687 }
688 {
689 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
690 blend[i] = src[i] < 0x80 ? (dest[i] * 2 * src[i]) / 255 : 255 - 2 * ((255 - dest[i]) * (255 - src[i])) / 255;
691 }
692 }
693 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
694 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
695 dest[i] = 255 - dest[i];
696 src[i] = 255 - src[i];
697 blend[i] = 255 - blend[i];
698 }
699 }
700}
701
702static void splashOutBlendSoftLight(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
703{
704 int i, x;
705
706 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
707 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
708 dest[i] = 255 - dest[i];
709 src[i] = 255 - src[i];
710 }
711 }
712 {
713 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
714 if (src[i] < 0x80) {
715 blend[i] = dest[i] - (255 - 2 * src[i]) * dest[i] * (255 - dest[i]) / (255 * 255);
716 } else {
717 if (dest[i] < 0x40) {
718 x = (((((16 * dest[i] - 12 * 255) * dest[i]) / 255) + 4 * 255) * dest[i]) / 255;
719 } else {
720 x = (int)sqrt(x: 255.0 * dest[i]);
721 }
722 blend[i] = dest[i] + (2 * src[i] - 255) * (x - dest[i]) / 255;
723 }
724 }
725 }
726 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
727 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
728 dest[i] = 255 - dest[i];
729 src[i] = 255 - src[i];
730 blend[i] = 255 - blend[i];
731 }
732 }
733}
734
735static void splashOutBlendDifference(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
736{
737 int i;
738
739 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
740 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
741 dest[i] = 255 - dest[i];
742 src[i] = 255 - src[i];
743 }
744 }
745 {
746 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
747 blend[i] = dest[i] < src[i] ? src[i] - dest[i] : dest[i] - src[i];
748 }
749 }
750 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
751 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
752 dest[i] = 255 - dest[i];
753 src[i] = 255 - src[i];
754 blend[i] = 255 - blend[i];
755 }
756 }
757 if (cm == splashModeDeviceN8) {
758 for (i = 4; i < splashColorModeNComps[cm]; ++i) {
759 if (dest[i] == 0 && src[i] == 0) {
760 blend[i] = 0;
761 }
762 }
763 }
764}
765
766static void splashOutBlendExclusion(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
767{
768 int i;
769
770 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
771 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
772 dest[i] = 255 - dest[i];
773 src[i] = 255 - src[i];
774 }
775 }
776 {
777 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
778 blend[i] = dest[i] + src[i] - (2 * dest[i] * src[i]) / 255;
779 }
780 }
781 if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
782 for (i = 0; i < splashColorModeNComps[cm]; ++i) {
783 dest[i] = 255 - dest[i];
784 src[i] = 255 - src[i];
785 blend[i] = 255 - blend[i];
786 }
787 }
788 if (cm == splashModeDeviceN8) {
789 for (i = 4; i < splashColorModeNComps[cm]; ++i) {
790 if (dest[i] == 0 && src[i] == 0) {
791 blend[i] = 0;
792 }
793 }
794 }
795}
796
797static int getLum(int r, int g, int b)
798{
799 // (int)(0.3 * r + 0.59 * g + 0.11 * b) =
800 // (int)(256 / 256 * 0.3 * r + 256 / 256 * 0.59 * g + 256 / 256 * 0.11 * b)
801 // (int)((77 * r + 151 * g + 28 * b) / 256) = // round!
802 return (int)((r * 77 + g * 151 + b * 28 + 0x80) >> 8);
803}
804
805static int getSat(int r, int g, int b)
806{
807 int rgbMin = std::min(l: { r, g, b });
808 int rgbMax = std::max(l: { r, g, b });
809
810 return rgbMax - rgbMin;
811}
812
813static void clipColor(int rIn, int gIn, int bIn, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut)
814{
815 int lum = getLum(r: rIn, g: gIn, b: bIn);
816 int rgbMin = std::min(l: { rIn, bIn, gIn });
817 int rgbMax = std::max(l: { rIn, bIn, gIn });
818
819 if (rgbMin < 0) {
820 *rOut = (unsigned char)std::clamp(val: lum + ((rIn - lum) * lum) / (lum - rgbMin), lo: 0, hi: 255);
821 *gOut = (unsigned char)std::clamp(val: lum + ((gIn - lum) * lum) / (lum - rgbMin), lo: 0, hi: 255);
822 *bOut = (unsigned char)std::clamp(val: lum + ((bIn - lum) * lum) / (lum - rgbMin), lo: 0, hi: 255);
823 } else if (rgbMax > 255) {
824 *rOut = (unsigned char)std::clamp(val: lum + ((rIn - lum) * (255 - lum)) / (rgbMax - lum), lo: 0, hi: 255);
825 *gOut = (unsigned char)std::clamp(val: lum + ((gIn - lum) * (255 - lum)) / (rgbMax - lum), lo: 0, hi: 255);
826 *bOut = (unsigned char)std::clamp(val: lum + ((bIn - lum) * (255 - lum)) / (rgbMax - lum), lo: 0, hi: 255);
827 } else {
828 *rOut = rIn;
829 *gOut = gIn;
830 *bOut = bIn;
831 }
832}
833
834static void setLum(unsigned char rIn, unsigned char gIn, unsigned char bIn, int lum, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut)
835{
836 int d;
837
838 d = lum - getLum(r: rIn, g: gIn, b: bIn);
839 clipColor(rIn: rIn + d, gIn: gIn + d, bIn: bIn + d, rOut, gOut, bOut);
840}
841
842static void setSat(unsigned char rIn, unsigned char gIn, unsigned char bIn, int sat, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut)
843{
844 int rgbMin, rgbMid, rgbMax;
845 unsigned char *minOut, *midOut, *maxOut;
846
847 if (rIn < gIn) {
848 rgbMin = rIn;
849 minOut = rOut;
850 rgbMid = gIn;
851 midOut = gOut;
852 } else {
853 rgbMin = gIn;
854 minOut = gOut;
855 rgbMid = rIn;
856 midOut = rOut;
857 }
858 if (bIn > rgbMid) {
859 rgbMax = bIn;
860 maxOut = bOut;
861 } else if (bIn > rgbMin) {
862 rgbMax = rgbMid;
863 maxOut = midOut;
864 rgbMid = bIn;
865 midOut = bOut;
866 } else {
867 rgbMax = rgbMid;
868 maxOut = midOut;
869 rgbMid = rgbMin;
870 midOut = minOut;
871 rgbMin = bIn;
872 minOut = bOut;
873 }
874 if (rgbMax > rgbMin) {
875 *midOut = (unsigned char)std::clamp(val: ((rgbMid - rgbMin) * sat) / (rgbMax - rgbMin), lo: 0, hi: 255);
876 *maxOut = (unsigned char)std::clamp(val: sat, lo: 0, hi: 255);
877 } else {
878 *midOut = *maxOut = 0;
879 }
880 *minOut = 0;
881}
882
883static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
884{
885 unsigned char r0, g0, b0;
886 unsigned char r1, g1, b1;
887 int i;
888 SplashColor src2, dest2;
889
890 switch (cm) {
891 case splashModeMono1:
892 case splashModeMono8:
893 blend[0] = dest[0];
894 break;
895 case splashModeXBGR8:
896 src[3] = 255;
897 // fallthrough
898 case splashModeRGB8:
899 case splashModeBGR8:
900 setSat(rIn: src[0], gIn: src[1], bIn: src[2], sat: getSat(r: dest[0], g: dest[1], b: dest[2]), rOut: &r0, gOut: &g0, bOut: &b0);
901 setLum(rIn: r0, gIn: g0, bIn: b0, lum: getLum(r: dest[0], g: dest[1], b: dest[2]), rOut: &blend[0], gOut: &blend[1], bOut: &blend[2]);
902 break;
903 case splashModeCMYK8:
904 case splashModeDeviceN8:
905 for (i = 0; i < 4; i++) {
906 // convert to additive
907 src2[i] = 0xff - src[i];
908 dest2[i] = 0xff - dest[i];
909 }
910 // NB: inputs have already been converted to additive mode
911 setSat(rIn: src2[0], gIn: src2[1], bIn: src2[2], sat: getSat(r: dest2[0], g: dest2[1], b: dest2[2]), rOut: &r0, gOut: &g0, bOut: &b0);
912 setLum(rIn: r0, gIn: g0, bIn: b0, lum: getLum(r: dest2[0], g: dest2[1], b: dest2[2]), rOut: &r1, gOut: &g1, bOut: &b1);
913 blend[0] = r1;
914 blend[1] = g1;
915 blend[2] = b1;
916 blend[3] = dest2[3];
917 for (i = 0; i < 4; i++) {
918 // convert back to subtractive
919 blend[i] = 0xff - blend[i];
920 }
921 break;
922 }
923}
924
925static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
926{
927 unsigned char r0, g0, b0;
928 unsigned char r1, g1, b1;
929 int i;
930 SplashColor src2, dest2;
931
932 switch (cm) {
933 case splashModeMono1:
934 case splashModeMono8:
935 blend[0] = dest[0];
936 break;
937 case splashModeXBGR8:
938 src[3] = 255;
939 // fallthrough
940 case splashModeRGB8:
941 case splashModeBGR8:
942 setSat(rIn: dest[0], gIn: dest[1], bIn: dest[2], sat: getSat(r: src[0], g: src[1], b: src[2]), rOut: &r0, gOut: &g0, bOut: &b0);
943 setLum(rIn: r0, gIn: g0, bIn: b0, lum: getLum(r: dest[0], g: dest[1], b: dest[2]), rOut: &blend[0], gOut: &blend[1], bOut: &blend[2]);
944 break;
945 case splashModeCMYK8:
946 case splashModeDeviceN8:
947 for (i = 0; i < 4; i++) {
948 // convert to additive
949 src2[i] = 0xff - src[i];
950 dest2[i] = 0xff - dest[i];
951 }
952 setSat(rIn: dest2[0], gIn: dest2[1], bIn: dest2[2], sat: getSat(r: src2[0], g: src2[1], b: src2[2]), rOut: &r0, gOut: &g0, bOut: &b0);
953 setLum(rIn: r0, gIn: g0, bIn: b0, lum: getLum(r: dest2[0], g: dest2[1], b: dest2[2]), rOut: &r1, gOut: &g1, bOut: &b1);
954 blend[0] = r1;
955 blend[1] = g1;
956 blend[2] = b1;
957 blend[3] = dest2[3];
958 for (i = 0; i < 4; i++) {
959 // convert back to subtractive
960 blend[i] = 0xff - blend[i];
961 }
962 break;
963 }
964}
965
966static void splashOutBlendColor(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
967{
968 unsigned char r, g, b;
969 int i;
970 SplashColor src2, dest2;
971
972 switch (cm) {
973 case splashModeMono1:
974 case splashModeMono8:
975 blend[0] = dest[0];
976 break;
977 case splashModeXBGR8:
978 src[3] = 255;
979 // fallthrough
980 case splashModeRGB8:
981 case splashModeBGR8:
982 setLum(rIn: src[0], gIn: src[1], bIn: src[2], lum: getLum(r: dest[0], g: dest[1], b: dest[2]), rOut: &blend[0], gOut: &blend[1], bOut: &blend[2]);
983 break;
984 case splashModeCMYK8:
985 case splashModeDeviceN8:
986 for (i = 0; i < 4; i++) {
987 // convert to additive
988 src2[i] = 0xff - src[i];
989 dest2[i] = 0xff - dest[i];
990 }
991 setLum(rIn: src2[0], gIn: src2[1], bIn: src2[2], lum: getLum(r: dest2[0], g: dest2[1], b: dest2[2]), rOut: &r, gOut: &g, bOut: &b);
992 blend[0] = r;
993 blend[1] = g;
994 blend[2] = b;
995 blend[3] = dest2[3];
996 for (i = 0; i < 4; i++) {
997 // convert back to subtractive
998 blend[i] = 0xff - blend[i];
999 }
1000 break;
1001 }
1002}
1003
1004static void splashOutBlendLuminosity(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm)
1005{
1006 unsigned char r, g, b;
1007 int i;
1008 SplashColor src2, dest2;
1009
1010 switch (cm) {
1011 case splashModeMono1:
1012 case splashModeMono8:
1013 blend[0] = dest[0];
1014 break;
1015 case splashModeXBGR8:
1016 src[3] = 255;
1017 // fallthrough
1018 case splashModeRGB8:
1019 case splashModeBGR8:
1020 setLum(rIn: dest[0], gIn: dest[1], bIn: dest[2], lum: getLum(r: src[0], g: src[1], b: src[2]), rOut: &blend[0], gOut: &blend[1], bOut: &blend[2]);
1021 break;
1022 case splashModeCMYK8:
1023 case splashModeDeviceN8:
1024 for (i = 0; i < 4; i++) {
1025 // convert to additive
1026 src2[i] = 0xff - src[i];
1027 dest2[i] = 0xff - dest[i];
1028 }
1029 setLum(rIn: dest2[0], gIn: dest2[1], bIn: dest2[2], lum: getLum(r: src2[0], g: src2[1], b: src2[2]), rOut: &r, gOut: &g, bOut: &b);
1030 blend[0] = r;
1031 blend[1] = g;
1032 blend[2] = b;
1033 blend[3] = src2[3];
1034 for (i = 0; i < 4; i++) {
1035 // convert back to subtractive
1036 blend[i] = 0xff - blend[i];
1037 }
1038 break;
1039 }
1040}
1041
1042// NB: This must match the GfxBlendMode enum defined in GfxState.h.
1043static const SplashBlendFunc splashOutBlendFuncs[] = { nullptr,
1044 &splashOutBlendMultiply,
1045 &splashOutBlendScreen,
1046 &splashOutBlendOverlay,
1047 &splashOutBlendDarken,
1048 &splashOutBlendLighten,
1049 &splashOutBlendColorDodge,
1050 &splashOutBlendColorBurn,
1051 &splashOutBlendHardLight,
1052 &splashOutBlendSoftLight,
1053 &splashOutBlendDifference,
1054 &splashOutBlendExclusion,
1055 &splashOutBlendHue,
1056 &splashOutBlendSaturation,
1057 &splashOutBlendColor,
1058 &splashOutBlendLuminosity };
1059
1060//------------------------------------------------------------------------
1061// SplashOutFontFileID
1062//------------------------------------------------------------------------
1063
1064class SplashOutFontFileID : public SplashFontFileID
1065{
1066public:
1067 explicit SplashOutFontFileID(const Ref *rA) { r = *rA; }
1068
1069 ~SplashOutFontFileID() override;
1070
1071 bool matches(SplashFontFileID *id) override { return ((SplashOutFontFileID *)id)->r == r; }
1072
1073private:
1074 Ref r;
1075};
1076
1077SplashOutFontFileID::~SplashOutFontFileID() = default;
1078
1079//------------------------------------------------------------------------
1080// T3FontCache
1081//------------------------------------------------------------------------
1082
1083struct T3FontCacheTag
1084{
1085 unsigned short code;
1086 unsigned short mru; // valid bit (0x8000) and MRU index
1087};
1088
1089class T3FontCache
1090{
1091public:
1092 T3FontCache(const Ref *fontID, double m11A, double m12A, double m21A, double m22A, int glyphXA, int glyphYA, int glyphWA, int glyphHA, bool validBBoxA, bool aa);
1093 ~T3FontCache();
1094 T3FontCache(const T3FontCache &) = delete;
1095 T3FontCache &operator=(const T3FontCache &) = delete;
1096 bool matches(const Ref *idA, double m11A, double m12A, double m21A, double m22A) { return fontID == *idA && m11 == m11A && m12 == m12A && m21 == m21A && m22 == m22A; }
1097
1098 Ref fontID; // PDF font ID
1099 double m11, m12, m21, m22; // transform matrix
1100 int glyphX, glyphY; // pixel offset of glyph bitmaps
1101 int glyphW, glyphH; // size of glyph bitmaps, in pixels
1102 bool validBBox; // false if the bbox was [0 0 0 0]
1103 int glyphSize; // size of glyph bitmaps, in bytes
1104 int cacheSets; // number of sets in cache
1105 int cacheAssoc; // cache associativity (glyphs per set)
1106 unsigned char *cacheData; // glyph pixmap cache
1107 T3FontCacheTag *cacheTags; // cache tags, i.e., char codes
1108};
1109
1110T3FontCache::T3FontCache(const Ref *fontIDA, double m11A, double m12A, double m21A, double m22A, int glyphXA, int glyphYA, int glyphWA, int glyphHA, bool validBBoxA, bool aa)
1111{
1112
1113 fontID = *fontIDA;
1114 m11 = m11A;
1115 m12 = m12A;
1116 m21 = m21A;
1117 m22 = m22A;
1118 glyphX = glyphXA;
1119 glyphY = glyphYA;
1120 glyphW = glyphWA;
1121 glyphH = glyphHA;
1122 validBBox = validBBoxA;
1123 // sanity check for excessively large glyphs (which most likely
1124 // indicate an incorrect BBox)
1125 if (glyphW > INT_MAX / glyphH || glyphW <= 0 || glyphH <= 0 || glyphW * glyphH > 100000) {
1126 glyphW = glyphH = 100;
1127 validBBox = false;
1128 }
1129 if (aa) {
1130 glyphSize = glyphW * glyphH;
1131 } else {
1132 glyphSize = ((glyphW + 7) >> 3) * glyphH;
1133 }
1134 cacheAssoc = type3FontCacheAssoc;
1135 for (cacheSets = type3FontCacheMaxSets; cacheSets > 1 && cacheSets * cacheAssoc * glyphSize > type3FontCacheSize; cacheSets >>= 1) {
1136 ;
1137 }
1138 if (glyphSize < 10485760 / cacheAssoc / cacheSets) {
1139 cacheData = (unsigned char *)gmallocn_checkoverflow(count: cacheSets * cacheAssoc, size: glyphSize);
1140 } else {
1141 error(category: errSyntaxWarning, pos: -1,
1142 msg: "Not creating cacheData for T3FontCache, it asked for too much memory.\n"
1143 " This could teoretically result in wrong rendering,\n"
1144 " but most probably the document is bogus.\n"
1145 " Please report a bug if you think the rendering may be wrong because of this.");
1146 cacheData = nullptr;
1147 }
1148 if (cacheData != nullptr) {
1149 cacheTags = (T3FontCacheTag *)gmallocn(count: cacheSets * cacheAssoc, size: sizeof(T3FontCacheTag));
1150 for (int i = 0; i < cacheSets * cacheAssoc; ++i) {
1151 cacheTags[i].mru = i & (cacheAssoc - 1);
1152 }
1153 } else {
1154 cacheTags = nullptr;
1155 }
1156}
1157
1158T3FontCache::~T3FontCache()
1159{
1160 gfree(p: cacheData);
1161 gfree(p: cacheTags);
1162}
1163
1164struct T3GlyphStack
1165{
1166 unsigned short code; // character code
1167
1168 bool haveDx; // set after seeing a d0/d1 operator
1169 bool doNotCache; // set if we see a gsave/grestore before
1170 // the d0/d1
1171
1172 //----- cache info
1173 T3FontCache *cache; // font cache for the current font
1174 T3FontCacheTag *cacheTag; // pointer to cache tag for the glyph
1175 unsigned char *cacheData; // pointer to cache data for the glyph
1176
1177 //----- saved state
1178 SplashBitmap *origBitmap;
1179 Splash *origSplash;
1180 double origCTM4, origCTM5;
1181
1182 T3GlyphStack *next; // next object on stack
1183};
1184
1185//------------------------------------------------------------------------
1186// SplashTransparencyGroup
1187//------------------------------------------------------------------------
1188
1189struct SplashTransparencyGroup
1190{
1191 int tx, ty; // translation coordinates
1192 SplashBitmap *tBitmap; // bitmap for transparency group
1193 SplashBitmap *softmask; // bitmap for softmasks
1194 GfxColorSpace *blendingColorSpace;
1195 bool isolated;
1196
1197 //----- for knockout
1198 SplashBitmap *shape;
1199 bool knockout;
1200 SplashCoord knockoutOpacity;
1201 bool fontAA;
1202
1203 //----- saved state
1204 SplashBitmap *origBitmap;
1205 Splash *origSplash;
1206
1207 SplashTransparencyGroup *next;
1208};
1209
1210//------------------------------------------------------------------------
1211// SplashOutputDev
1212//------------------------------------------------------------------------
1213
1214SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA, bool reverseVideoA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA)
1215{
1216 colorMode = colorModeA;
1217 bitmapRowPad = bitmapRowPadA;
1218 bitmapTopDown = bitmapTopDownA;
1219 fontAntialias = true;
1220 vectorAntialias = true;
1221 overprintPreview = overprintPreviewA;
1222 enableFreeType = true;
1223 enableFreeTypeHinting = false;
1224 enableSlightHinting = false;
1225 setupScreenParams(hDPI: 72.0, vDPI: 72.0);
1226 reverseVideo = reverseVideoA;
1227 if (paperColorA != nullptr) {
1228 splashColorCopy(dest: paperColor, src: paperColorA);
1229 } else {
1230 splashClearColor(dest: paperColor);
1231 }
1232 skipHorizText = false;
1233 skipRotatedText = false;
1234 keepAlphaChannel = paperColorA == nullptr;
1235
1236 doc = nullptr;
1237
1238 bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown);
1239 splash = new Splash(bitmap, vectorAntialias, &screenParams);
1240 splash->setMinLineWidth(s_minLineWidth);
1241 splash->setThinLineMode(thinLineMode);
1242 splash->clear(color: paperColor, alpha: 0);
1243
1244 fontEngine = nullptr;
1245
1246 nT3Fonts = 0;
1247 t3GlyphStack = nullptr;
1248
1249 font = nullptr;
1250 needFontUpdate = false;
1251 textClipPath = nullptr;
1252 transpGroupStack = nullptr;
1253 xref = nullptr;
1254}
1255
1256void SplashOutputDev::setupScreenParams(double hDPI, double vDPI)
1257{
1258 screenParams.size = -1;
1259 screenParams.dotRadius = -1;
1260 screenParams.gamma = (SplashCoord)1.0;
1261 screenParams.blackThreshold = (SplashCoord)0.0;
1262 screenParams.whiteThreshold = (SplashCoord)1.0;
1263
1264 // use clustered dithering for resolution >= 300 dpi
1265 // (compare to 299.9 to avoid floating point issues)
1266 if (hDPI > 299.9 && vDPI > 299.9) {
1267 screenParams.type = splashScreenStochasticClustered;
1268 if (screenParams.size < 0) {
1269 screenParams.size = 64;
1270 }
1271 if (screenParams.dotRadius < 0) {
1272 screenParams.dotRadius = 2;
1273 }
1274 } else {
1275 screenParams.type = splashScreenDispersed;
1276 if (screenParams.size < 0) {
1277 screenParams.size = 4;
1278 }
1279 }
1280}
1281
1282SplashOutputDev::~SplashOutputDev()
1283{
1284 int i;
1285
1286 for (i = 0; i < nT3Fonts; ++i) {
1287 delete t3FontCache[i];
1288 }
1289 if (fontEngine) {
1290 delete fontEngine;
1291 }
1292 if (splash) {
1293 delete splash;
1294 }
1295 if (bitmap) {
1296 delete bitmap;
1297 }
1298 delete textClipPath;
1299}
1300
1301void SplashOutputDev::startDoc(PDFDoc *docA)
1302{
1303 int i;
1304
1305 doc = docA;
1306 if (fontEngine) {
1307 delete fontEngine;
1308 }
1309 fontEngine = new SplashFontEngine(enableFreeType, enableFreeTypeHinting, enableSlightHinting, getFontAntialias() && colorMode != splashModeMono1);
1310 for (i = 0; i < nT3Fonts; ++i) {
1311 delete t3FontCache[i];
1312 }
1313 nT3Fonts = 0;
1314}
1315
1316void SplashOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA)
1317{
1318 int w, h;
1319 SplashCoord mat[6];
1320 SplashColor color;
1321
1322 xref = xrefA;
1323 if (state) {
1324 setupScreenParams(hDPI: state->getHDPI(), vDPI: state->getVDPI());
1325 w = (int)(state->getPageWidth() + 0.5);
1326 if (w <= 0) {
1327 w = 1;
1328 }
1329 h = (int)(state->getPageHeight() + 0.5);
1330 if (h <= 0) {
1331 h = 1;
1332 }
1333 } else {
1334 w = h = 1;
1335 }
1336 SplashThinLineMode thinLineMode = splashThinLineDefault;
1337 if (splash) {
1338 thinLineMode = splash->getThinLineMode();
1339 delete splash;
1340 splash = nullptr;
1341 }
1342 if (!bitmap || w != bitmap->getWidth() || h != bitmap->getHeight()) {
1343 if (bitmap) {
1344 delete bitmap;
1345 bitmap = nullptr;
1346 }
1347 bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown);
1348 if (!bitmap->getDataPtr()) {
1349 delete bitmap;
1350 w = h = 1;
1351 bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown);
1352 }
1353 }
1354 splash = new Splash(bitmap, vectorAntialias, &screenParams);
1355 splash->setThinLineMode(thinLineMode);
1356 splash->setMinLineWidth(s_minLineWidth);
1357 if (state) {
1358 const double *ctm = state->getCTM();
1359 mat[0] = (SplashCoord)ctm[0];
1360 mat[1] = (SplashCoord)ctm[1];
1361 mat[2] = (SplashCoord)ctm[2];
1362 mat[3] = (SplashCoord)ctm[3];
1363 mat[4] = (SplashCoord)ctm[4];
1364 mat[5] = (SplashCoord)ctm[5];
1365 splash->setMatrix(mat);
1366 }
1367 switch (colorMode) {
1368 case splashModeMono1:
1369 case splashModeMono8:
1370 color[0] = 0;
1371 break;
1372 case splashModeXBGR8:
1373 color[3] = 255;
1374 // fallthrough
1375 case splashModeRGB8:
1376 case splashModeBGR8:
1377 color[0] = color[1] = color[2] = 0;
1378 break;
1379 case splashModeCMYK8:
1380 color[0] = color[1] = color[2] = color[3] = 0;
1381 break;
1382 case splashModeDeviceN8:
1383 splashClearColor(dest: color);
1384 break;
1385 }
1386 splash->setStrokePattern(new SplashSolidColor(color));
1387 splash->setFillPattern(new SplashSolidColor(color));
1388 splash->setLineCap(splashLineCapButt);
1389 splash->setLineJoin(splashLineJoinMiter);
1390 splash->setLineDash(lineDash: {}, lineDashPhase: 0);
1391 splash->setMiterLimit(10);
1392 splash->setFlatness(1);
1393 // the SA parameter supposedly defaults to false, but Acrobat
1394 // apparently hardwires it to true
1395 splash->setStrokeAdjust(true);
1396 splash->clear(color: paperColor, alpha: 0);
1397}
1398
1399void SplashOutputDev::endPage()
1400{
1401 if (colorMode != splashModeMono1 && !keepAlphaChannel) {
1402 splash->compositeBackground(color: paperColor);
1403 }
1404}
1405
1406void SplashOutputDev::saveState(GfxState *state)
1407{
1408 splash->saveState();
1409 if (t3GlyphStack && !t3GlyphStack->haveDx) {
1410 t3GlyphStack->doNotCache = true;
1411 error(category: errSyntaxWarning, pos: -1, msg: "Save (q) operator before d0/d1 in Type 3 glyph");
1412 }
1413}
1414
1415void SplashOutputDev::restoreState(GfxState *state)
1416{
1417 splash->restoreState();
1418 needFontUpdate = true;
1419 if (t3GlyphStack && !t3GlyphStack->haveDx) {
1420 t3GlyphStack->doNotCache = true;
1421 error(category: errSyntaxWarning, pos: -1, msg: "Restore (Q) operator before d0/d1 in Type 3 glyph");
1422 }
1423}
1424
1425void SplashOutputDev::updateAll(GfxState *state)
1426{
1427 updateLineDash(state);
1428 updateLineJoin(state);
1429 updateLineCap(state);
1430 updateLineWidth(state);
1431 updateFlatness(state);
1432 updateMiterLimit(state);
1433 updateStrokeAdjust(state);
1434 updateFillColorSpace(state);
1435 updateFillColor(state);
1436 updateStrokeColorSpace(state);
1437 updateStrokeColor(state);
1438 needFontUpdate = true;
1439}
1440
1441void SplashOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32)
1442{
1443 SplashCoord mat[6];
1444
1445 const double *ctm = state->getCTM();
1446 mat[0] = (SplashCoord)ctm[0];
1447 mat[1] = (SplashCoord)ctm[1];
1448 mat[2] = (SplashCoord)ctm[2];
1449 mat[3] = (SplashCoord)ctm[3];
1450 mat[4] = (SplashCoord)ctm[4];
1451 mat[5] = (SplashCoord)ctm[5];
1452 splash->setMatrix(mat);
1453}
1454
1455void SplashOutputDev::updateLineDash(GfxState *state)
1456{
1457 double dashStart;
1458
1459 const std::vector<double> &dashPattern = state->getLineDash(start: &dashStart);
1460
1461 std::vector<SplashCoord> dash(dashPattern.size());
1462 for (std::vector<double>::size_type i = 0; i < dashPattern.size(); ++i) {
1463 dash[i] = (SplashCoord)dashPattern[i];
1464 if (dash[i] < 0) {
1465 dash[i] = 0;
1466 }
1467 }
1468 splash->setLineDash(lineDash: std::move(dash), lineDashPhase: (SplashCoord)dashStart);
1469}
1470
1471void SplashOutputDev::updateFlatness(GfxState *state)
1472{
1473#if 0 // Acrobat ignores the flatness setting, and always renders curves
1474 // with a fairly small flatness value
1475 splash->setFlatness(state->getFlatness());
1476#endif
1477}
1478
1479void SplashOutputDev::updateLineJoin(GfxState *state)
1480{
1481 splash->setLineJoin(state->getLineJoin());
1482}
1483
1484void SplashOutputDev::updateLineCap(GfxState *state)
1485{
1486 splash->setLineCap(state->getLineCap());
1487}
1488
1489void SplashOutputDev::updateMiterLimit(GfxState *state)
1490{
1491 splash->setMiterLimit(state->getMiterLimit());
1492}
1493
1494void SplashOutputDev::updateLineWidth(GfxState *state)
1495{
1496 splash->setLineWidth(state->getLineWidth());
1497}
1498
1499void SplashOutputDev::updateStrokeAdjust(GfxState * /*state*/)
1500{
1501#if 0 // the SA parameter supposedly defaults to false, but Acrobat
1502 // apparently hardwires it to true
1503 splash->setStrokeAdjust(state->getStrokeAdjust());
1504#endif
1505}
1506
1507void SplashOutputDev::updateFillColorSpace(GfxState *state)
1508{
1509 if (colorMode == splashModeDeviceN8) {
1510 state->getFillColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
1511 }
1512}
1513
1514void SplashOutputDev::updateStrokeColorSpace(GfxState *state)
1515{
1516 if (colorMode == splashModeDeviceN8) {
1517 state->getStrokeColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
1518 }
1519}
1520
1521void SplashOutputDev::updateFillColor(GfxState *state)
1522{
1523 GfxGray gray;
1524 GfxRGB rgb;
1525 GfxCMYK cmyk;
1526 GfxColor deviceN;
1527
1528 switch (colorMode) {
1529 case splashModeMono1:
1530 case splashModeMono8:
1531 state->getFillGray(gray: &gray);
1532 splash->setFillPattern(getColor(gray));
1533 break;
1534 case splashModeXBGR8:
1535 case splashModeRGB8:
1536 case splashModeBGR8:
1537 state->getFillRGB(rgb: &rgb);
1538 splash->setFillPattern(getColor(rgb: &rgb));
1539 break;
1540 case splashModeCMYK8:
1541 state->getFillCMYK(cmyk: &cmyk);
1542 splash->setFillPattern(getColor(cmyk: &cmyk));
1543 break;
1544 case splashModeDeviceN8:
1545 state->getFillDeviceN(deviceN: &deviceN);
1546 splash->setFillPattern(getColor(deviceN: &deviceN));
1547 break;
1548 }
1549}
1550
1551void SplashOutputDev::updateStrokeColor(GfxState *state)
1552{
1553 GfxGray gray;
1554 GfxRGB rgb;
1555 GfxCMYK cmyk;
1556 GfxColor deviceN;
1557
1558 switch (colorMode) {
1559 case splashModeMono1:
1560 case splashModeMono8:
1561 state->getStrokeGray(gray: &gray);
1562 splash->setStrokePattern(getColor(gray));
1563 break;
1564 case splashModeXBGR8:
1565 case splashModeRGB8:
1566 case splashModeBGR8:
1567 state->getStrokeRGB(rgb: &rgb);
1568 splash->setStrokePattern(getColor(rgb: &rgb));
1569 break;
1570 case splashModeCMYK8:
1571 state->getStrokeCMYK(cmyk: &cmyk);
1572 splash->setStrokePattern(getColor(cmyk: &cmyk));
1573 break;
1574 case splashModeDeviceN8:
1575 state->getStrokeDeviceN(deviceN: &deviceN);
1576 splash->setStrokePattern(getColor(deviceN: &deviceN));
1577 break;
1578 }
1579}
1580
1581SplashPattern *SplashOutputDev::getColor(GfxGray gray)
1582{
1583 SplashColor color;
1584
1585 if (reverseVideo) {
1586 gray = gfxColorComp1 - gray;
1587 }
1588 color[0] = colToByte(x: gray);
1589 return new SplashSolidColor(color);
1590}
1591
1592SplashPattern *SplashOutputDev::getColor(GfxRGB *rgb)
1593{
1594 GfxColorComp r, g, b;
1595 SplashColor color;
1596
1597 if (reverseVideo) {
1598 r = gfxColorComp1 - rgb->r;
1599 g = gfxColorComp1 - rgb->g;
1600 b = gfxColorComp1 - rgb->b;
1601 } else {
1602 r = rgb->r;
1603 g = rgb->g;
1604 b = rgb->b;
1605 }
1606 color[0] = colToByte(x: r);
1607 color[1] = colToByte(x: g);
1608 color[2] = colToByte(x: b);
1609 if (colorMode == splashModeXBGR8) {
1610 color[3] = 255;
1611 }
1612 return new SplashSolidColor(color);
1613}
1614
1615SplashPattern *SplashOutputDev::getColor(GfxCMYK *cmyk)
1616{
1617 SplashColor color;
1618
1619 color[0] = colToByte(x: cmyk->c);
1620 color[1] = colToByte(x: cmyk->m);
1621 color[2] = colToByte(x: cmyk->y);
1622 color[3] = colToByte(x: cmyk->k);
1623 return new SplashSolidColor(color);
1624}
1625
1626SplashPattern *SplashOutputDev::getColor(GfxColor *deviceN)
1627{
1628 SplashColor color;
1629
1630 for (int i = 0; i < 4 + SPOT_NCOMPS; i++) {
1631 color[i] = colToByte(x: deviceN->c[i]);
1632 }
1633 return new SplashSolidColor(color);
1634}
1635
1636void SplashOutputDev::getMatteColor(SplashColorMode colorMode, GfxImageColorMap *colorMap, const GfxColor *matteColorIn, SplashColor matteColor)
1637{
1638 GfxGray gray;
1639 GfxRGB rgb;
1640 GfxCMYK cmyk;
1641 GfxColor deviceN;
1642
1643 switch (colorMode) {
1644 case splashModeMono1:
1645 case splashModeMono8:
1646 colorMap->getColorSpace()->getGray(color: matteColorIn, gray: &gray);
1647 matteColor[0] = colToByte(x: gray);
1648 break;
1649 case splashModeRGB8:
1650 case splashModeBGR8:
1651 colorMap->getColorSpace()->getRGB(color: matteColorIn, rgb: &rgb);
1652 matteColor[0] = colToByte(x: rgb.r);
1653 matteColor[1] = colToByte(x: rgb.g);
1654 matteColor[2] = colToByte(x: rgb.b);
1655 break;
1656 case splashModeXBGR8:
1657 colorMap->getColorSpace()->getRGB(color: matteColorIn, rgb: &rgb);
1658 matteColor[0] = colToByte(x: rgb.r);
1659 matteColor[1] = colToByte(x: rgb.g);
1660 matteColor[2] = colToByte(x: rgb.b);
1661 matteColor[3] = 255;
1662 break;
1663 case splashModeCMYK8:
1664 colorMap->getColorSpace()->getCMYK(color: matteColorIn, cmyk: &cmyk);
1665 matteColor[0] = colToByte(x: cmyk.c);
1666 matteColor[1] = colToByte(x: cmyk.m);
1667 matteColor[2] = colToByte(x: cmyk.y);
1668 matteColor[3] = colToByte(x: cmyk.k);
1669 break;
1670 case splashModeDeviceN8:
1671 colorMap->getColorSpace()->getDeviceN(color: matteColorIn, deviceN: &deviceN);
1672 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
1673 matteColor[cp] = colToByte(x: deviceN.c[cp]);
1674 }
1675 break;
1676 }
1677}
1678
1679void SplashOutputDev::setOverprintMask(GfxColorSpace *colorSpace, bool overprintFlag, int overprintMode, const GfxColor *singleColor, bool grayIndexed)
1680{
1681 unsigned int mask;
1682 GfxCMYK cmyk;
1683 bool additive = false;
1684 int i;
1685
1686 if (colorSpace->getMode() == csIndexed) {
1687 setOverprintMask(colorSpace: ((GfxIndexedColorSpace *)colorSpace)->getBase(), overprintFlag, overprintMode, singleColor, grayIndexed);
1688 return;
1689 }
1690 if (overprintFlag && overprintPreview) {
1691 mask = colorSpace->getOverprintMask();
1692 if (singleColor && overprintMode && colorSpace->getMode() == csDeviceCMYK) {
1693 colorSpace->getCMYK(color: singleColor, cmyk: &cmyk);
1694 if (cmyk.c == 0) {
1695 mask &= ~1;
1696 }
1697 if (cmyk.m == 0) {
1698 mask &= ~2;
1699 }
1700 if (cmyk.y == 0) {
1701 mask &= ~4;
1702 }
1703 if (cmyk.k == 0) {
1704 mask &= ~8;
1705 }
1706 }
1707 if (grayIndexed && colorSpace->getMode() != csDeviceN) {
1708 mask &= ~7;
1709 } else if (colorSpace->getMode() == csSeparation) {
1710 GfxSeparationColorSpace *deviceSep = (GfxSeparationColorSpace *)colorSpace;
1711 additive = deviceSep->getName()->cmp(sA: "All") != 0 && mask == 0x0f && !deviceSep->isNonMarking();
1712 } else if (colorSpace->getMode() == csDeviceN) {
1713 GfxDeviceNColorSpace *deviceNCS = (GfxDeviceNColorSpace *)colorSpace;
1714 additive = mask == 0x0f && !deviceNCS->isNonMarking();
1715 for (i = 0; i < deviceNCS->getNComps() && additive; i++) {
1716 if (deviceNCS->getColorantName(i) == "Cyan") {
1717 additive = false;
1718 } else if (deviceNCS->getColorantName(i) == "Magenta") {
1719 additive = false;
1720 } else if (deviceNCS->getColorantName(i) == "Yellow") {
1721 additive = false;
1722 } else if (deviceNCS->getColorantName(i) == "Black") {
1723 additive = false;
1724 }
1725 }
1726 }
1727 } else {
1728 mask = 0xffffffff;
1729 }
1730 splash->setOverprintMask(overprintMask: mask, additive);
1731}
1732
1733void SplashOutputDev::updateBlendMode(GfxState *state)
1734{
1735 splash->setBlendFunc(splashOutBlendFuncs[state->getBlendMode()]);
1736}
1737
1738void SplashOutputDev::updateFillOpacity(GfxState *state)
1739{
1740 splash->setFillAlpha((SplashCoord)state->getFillOpacity());
1741 if (transpGroupStack != nullptr && (SplashCoord)state->getFillOpacity() < transpGroupStack->knockoutOpacity) {
1742 transpGroupStack->knockoutOpacity = (SplashCoord)state->getFillOpacity();
1743 }
1744}
1745
1746void SplashOutputDev::updateStrokeOpacity(GfxState *state)
1747{
1748 splash->setStrokeAlpha((SplashCoord)state->getStrokeOpacity());
1749 if (transpGroupStack != nullptr && (SplashCoord)state->getStrokeOpacity() < transpGroupStack->knockoutOpacity) {
1750 transpGroupStack->knockoutOpacity = (SplashCoord)state->getStrokeOpacity();
1751 }
1752}
1753
1754void SplashOutputDev::updatePatternOpacity(GfxState *state)
1755{
1756 splash->setPatternAlpha(strokeAlpha: (SplashCoord)state->getStrokeOpacity(), fillAlpha: (SplashCoord)state->getFillOpacity());
1757}
1758
1759void SplashOutputDev::clearPatternOpacity(GfxState *state)
1760{
1761 splash->clearPatternAlpha();
1762}
1763
1764void SplashOutputDev::updateFillOverprint(GfxState *state)
1765{
1766 splash->setFillOverprint(state->getFillOverprint());
1767}
1768
1769void SplashOutputDev::updateStrokeOverprint(GfxState *state)
1770{
1771 splash->setStrokeOverprint(state->getStrokeOverprint());
1772}
1773
1774void SplashOutputDev::updateOverprintMode(GfxState *state)
1775{
1776 splash->setOverprintMode(state->getOverprintMode());
1777}
1778
1779void SplashOutputDev::updateTransfer(GfxState *state)
1780{
1781 Function **transfer;
1782 unsigned char red[256], green[256], blue[256], gray[256];
1783 double x, y;
1784 int i;
1785
1786 transfer = state->getTransfer();
1787 if (transfer[0] && transfer[0]->getInputSize() == 1 && transfer[0]->getOutputSize() == 1) {
1788 if (transfer[1] && transfer[1]->getInputSize() == 1 && transfer[1]->getOutputSize() == 1 && transfer[2] && transfer[2]->getInputSize() == 1 && transfer[2]->getOutputSize() == 1 && transfer[3] && transfer[3]->getInputSize() == 1
1789 && transfer[3]->getOutputSize() == 1) {
1790 for (i = 0; i < 256; ++i) {
1791 x = i / 255.0;
1792 transfer[0]->transform(in: &x, out: &y);
1793 red[i] = (unsigned char)(y * 255.0 + 0.5);
1794 transfer[1]->transform(in: &x, out: &y);
1795 green[i] = (unsigned char)(y * 255.0 + 0.5);
1796 transfer[2]->transform(in: &x, out: &y);
1797 blue[i] = (unsigned char)(y * 255.0 + 0.5);
1798 transfer[3]->transform(in: &x, out: &y);
1799 gray[i] = (unsigned char)(y * 255.0 + 0.5);
1800 }
1801 } else {
1802 for (i = 0; i < 256; ++i) {
1803 x = i / 255.0;
1804 transfer[0]->transform(in: &x, out: &y);
1805 red[i] = green[i] = blue[i] = gray[i] = (unsigned char)(y * 255.0 + 0.5);
1806 }
1807 }
1808 } else {
1809 for (i = 0; i < 256; ++i) {
1810 red[i] = green[i] = blue[i] = gray[i] = (unsigned char)i;
1811 }
1812 }
1813 splash->setTransfer(red, green, blue, gray);
1814}
1815
1816void SplashOutputDev::updateFont(GfxState * /*state*/)
1817{
1818 needFontUpdate = true;
1819}
1820
1821void SplashOutputDev::doUpdateFont(GfxState *state)
1822{
1823 GfxFontType fontType;
1824 SplashOutFontFileID *id = nullptr;
1825 SplashFontFile *fontFile;
1826 SplashFontSrc *fontsrc = nullptr;
1827 const double *textMat;
1828 double m11, m12, m21, m22, fontSize;
1829 int faceIndex = 0;
1830 SplashCoord mat[4];
1831 bool recreateFont = false;
1832 bool doAdjustFontMatrix = false;
1833
1834 needFontUpdate = false;
1835 font = nullptr;
1836
1837 GfxFont *const gfxFont = state->getFont().get();
1838 if (!gfxFont) {
1839 goto err1;
1840 }
1841 fontType = gfxFont->getType();
1842 if (fontType == fontType3) {
1843 goto err1;
1844 }
1845
1846 // sanity-check the font size - skip anything larger than 10 inches
1847 // (this avoids problems allocating memory for the font cache)
1848 if (state->getTransformedFontSize() > 10 * (state->getHDPI() + state->getVDPI())) {
1849 goto err1;
1850 }
1851
1852 // check the font file cache
1853reload:
1854 delete id;
1855 if (fontsrc && !fontsrc->isFile) {
1856 fontsrc->unref();
1857 fontsrc = nullptr;
1858 }
1859
1860 id = new SplashOutFontFileID(gfxFont->getID());
1861 if ((fontFile = fontEngine->getFontFile(id))) {
1862 delete id;
1863
1864 } else {
1865
1866 std::optional<GfxFontLoc> fontLoc = gfxFont->locateFont(xref: (xref) ? xref : doc->getXRef(), ps: nullptr);
1867 if (!fontLoc) {
1868 error(category: errSyntaxError, pos: -1, msg: "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1869 goto err2;
1870 }
1871
1872 // embedded font
1873 std::string fileName;
1874 std::optional<std::vector<unsigned char>> tmpBuf;
1875
1876 if (fontLoc->locType == gfxFontLocEmbedded) {
1877 // if there is an embedded font, read it to memory
1878 tmpBuf = gfxFont->readEmbFontFile(xref: (xref) ? xref : doc->getXRef());
1879 if (!tmpBuf) {
1880 goto err2;
1881 }
1882
1883 // external font
1884 } else { // gfxFontLocExternal
1885 fileName = fontLoc->path;
1886 fontType = fontLoc->fontType;
1887 doAdjustFontMatrix = true;
1888 }
1889
1890 fontsrc = new SplashFontSrc;
1891 if (!fileName.empty()) {
1892 fontsrc->setFile(fileName);
1893 } else {
1894 fontsrc->setBuf(std::move(tmpBuf.value()));
1895 }
1896
1897 // load the font file
1898 switch (fontType) {
1899 case fontType1:
1900 if (!(fontFile = fontEngine->loadType1Font(idA: id, src: fontsrc, enc: (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
1901 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1902 if (gfxFont->invalidateEmbeddedFont()) {
1903 goto reload;
1904 }
1905 goto err2;
1906 }
1907 break;
1908 case fontType1C:
1909 if (!(fontFile = fontEngine->loadType1CFont(idA: id, src: fontsrc, enc: (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
1910 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1911 if (gfxFont->invalidateEmbeddedFont()) {
1912 goto reload;
1913 }
1914 goto err2;
1915 }
1916 break;
1917 case fontType1COT:
1918 if (!(fontFile = fontEngine->loadOpenTypeT1CFont(idA: id, src: fontsrc, enc: (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
1919 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1920 if (gfxFont->invalidateEmbeddedFont()) {
1921 goto reload;
1922 }
1923 goto err2;
1924 }
1925 break;
1926 case fontTrueType:
1927 case fontTrueTypeOT: {
1928 std::unique_ptr<FoFiTrueType> ff;
1929 if (!fileName.empty()) {
1930 ff = FoFiTrueType::load(fileName: fileName.c_str());
1931 } else {
1932 ff = FoFiTrueType::make(fileA: fontsrc->buf.data(), lenA: fontsrc->buf.size());
1933 }
1934 int *codeToGID;
1935 const int n = ff ? 256 : 0;
1936 if (ff) {
1937 codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff: ff.get());
1938 // if we're substituting for a non-TrueType font, we need to mark
1939 // all notdef codes as "do not draw" (rather than drawing TrueType
1940 // notdef glyphs)
1941 if (gfxFont->getType() != fontTrueType && gfxFont->getType() != fontTrueTypeOT) {
1942 for (int i = 0; i < 256; ++i) {
1943 if (codeToGID[i] == 0) {
1944 codeToGID[i] = -1;
1945 }
1946 }
1947 }
1948 } else {
1949 codeToGID = nullptr;
1950 }
1951 if (!(fontFile = fontEngine->loadTrueTypeFont(idA: id, src: fontsrc, codeToGID, codeToGIDLen: n))) {
1952 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1953 if (gfxFont->invalidateEmbeddedFont()) {
1954 goto reload;
1955 }
1956 goto err2;
1957 }
1958 break;
1959 }
1960 case fontCIDType0:
1961 case fontCIDType0C:
1962 if (!(fontFile = fontEngine->loadCIDFont(idA: id, src: fontsrc))) {
1963 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1964 if (gfxFont->invalidateEmbeddedFont()) {
1965 goto reload;
1966 }
1967 goto err2;
1968 }
1969 break;
1970 case fontCIDType0COT: {
1971 int *codeToGID;
1972 int n;
1973 if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
1974 n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
1975 codeToGID = (int *)gmallocn(count: n, size: sizeof(int));
1976 memcpy(dest: codeToGID, src: ((GfxCIDFont *)gfxFont)->getCIDToGID(), n: n * sizeof(int));
1977 } else {
1978 codeToGID = nullptr;
1979 n = 0;
1980 }
1981 if (!(fontFile = fontEngine->loadOpenTypeCFFFont(idA: id, src: fontsrc, codeToGID, codeToGIDLen: n))) {
1982 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
1983 gfree(p: codeToGID);
1984 if (gfxFont->invalidateEmbeddedFont()) {
1985 goto reload;
1986 }
1987 goto err2;
1988 }
1989 break;
1990 }
1991 case fontCIDType2:
1992 case fontCIDType2OT: {
1993 int *codeToGID = nullptr;
1994 int n = 0;
1995 if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
1996 n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
1997 if (n) {
1998 codeToGID = (int *)gmallocn(count: n, size: sizeof(int));
1999 memcpy(dest: codeToGID, src: ((GfxCIDFont *)gfxFont)->getCIDToGID(), n: n * sizeof(int));
2000 }
2001 } else {
2002 std::unique_ptr<FoFiTrueType> ff;
2003 if (!fileName.empty()) {
2004 ff = FoFiTrueType::load(fileName: fileName.c_str());
2005 } else {
2006 ff = FoFiTrueType::make(fileA: fontsrc->buf.data(), lenA: fontsrc->buf.size());
2007 }
2008 if (!ff) {
2009 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
2010 goto err2;
2011 }
2012 codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff: ff.get(), codeToGIDLen: &n);
2013 }
2014 if (!(fontFile = fontEngine->loadTrueTypeFont(idA: id, src: fontsrc, codeToGID, codeToGIDLen: n, faceIndex))) {
2015 error(category: errSyntaxError, pos: -1, msg: "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
2016 if (gfxFont->invalidateEmbeddedFont()) {
2017 goto reload;
2018 }
2019 goto err2;
2020 }
2021 break;
2022 }
2023 default:
2024 // this shouldn't happen
2025 goto err2;
2026 }
2027 fontFile->doAdjustMatrix = doAdjustFontMatrix;
2028 }
2029
2030 // get the font matrix
2031 textMat = state->getTextMat();
2032 fontSize = state->getFontSize();
2033 m11 = textMat[0] * fontSize * state->getHorizScaling();
2034 m12 = textMat[1] * fontSize * state->getHorizScaling();
2035 m21 = textMat[2] * fontSize;
2036 m22 = textMat[3] * fontSize;
2037
2038 // create the scaled font
2039 mat[0] = m11;
2040 mat[1] = m12;
2041 mat[2] = m21;
2042 mat[3] = m22;
2043 font = fontEngine->getFont(fontFile, textMat: mat, ctm: splash->getMatrix());
2044
2045 // for substituted fonts: adjust the font matrix -- compare the
2046 // width of 'm' in the original font and the substituted font
2047 if (fontFile->doAdjustMatrix && !gfxFont->isCIDFont()) {
2048 double w1, w2, w3;
2049 CharCode code;
2050 const char *name;
2051 for (code = 0; code < 256; ++code) {
2052 if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && name[0] == 'm' && name[1] == '\0') {
2053 break;
2054 }
2055 }
2056 if (code < 256) {
2057 w1 = ((Gfx8BitFont *)gfxFont)->getWidth(c: code);
2058 w2 = font->getGlyphAdvance(c: code);
2059 w3 = ((Gfx8BitFont *)gfxFont)->getWidth(c: 0);
2060 if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) {
2061 // if real font is substantially narrower than substituted
2062 // font, reduce the font size accordingly
2063 if (w1 > 0.01 && w1 < 0.9 * w2) {
2064 w1 /= w2;
2065 m11 *= w1;
2066 m21 *= w1;
2067 recreateFont = true;
2068 }
2069 }
2070 }
2071 }
2072
2073 if (recreateFont) {
2074 mat[0] = m11;
2075 mat[1] = m12;
2076 mat[2] = m21;
2077 mat[3] = m22;
2078 font = fontEngine->getFont(fontFile, textMat: mat, ctm: splash->getMatrix());
2079 }
2080
2081 if (fontsrc && !fontsrc->isFile) {
2082 fontsrc->unref();
2083 }
2084 return;
2085
2086err2:
2087 delete id;
2088err1:
2089 if (fontsrc && !fontsrc->isFile) {
2090 fontsrc->unref();
2091 }
2092 return;
2093}
2094
2095void SplashOutputDev::stroke(GfxState *state)
2096{
2097 if (state->getStrokeColorSpace()->isNonMarking()) {
2098 return;
2099 }
2100 setOverprintMask(colorSpace: state->getStrokeColorSpace(), overprintFlag: state->getStrokeOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getStrokeColor());
2101 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: false);
2102 splash->stroke(path: &path);
2103}
2104
2105void SplashOutputDev::fill(GfxState *state)
2106{
2107 if (state->getFillColorSpace()->isNonMarking()) {
2108 return;
2109 }
2110 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2111 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
2112 splash->fill(path: &path, eo: false);
2113}
2114
2115void SplashOutputDev::eoFill(GfxState *state)
2116{
2117 if (state->getFillColorSpace()->isNonMarking()) {
2118 return;
2119 }
2120 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2121 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
2122 splash->fill(path: &path, eo: true);
2123}
2124
2125void SplashOutputDev::clip(GfxState *state)
2126{
2127 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
2128 splash->clipToPath(path: &path, eo: false);
2129}
2130
2131void SplashOutputDev::eoClip(GfxState *state)
2132{
2133 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
2134 splash->clipToPath(path: &path, eo: true);
2135}
2136
2137void SplashOutputDev::clipToStrokePath(GfxState *state)
2138{
2139 SplashPath *path2;
2140
2141 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: false);
2142 path2 = splash->makeStrokePath(path: &path, w: state->getLineWidth());
2143 splash->clipToPath(path: path2, eo: false);
2144 delete path2;
2145}
2146
2147SplashPath SplashOutputDev::convertPath(GfxState *state, const GfxPath *path, bool dropEmptySubpaths)
2148{
2149 SplashPath sPath;
2150 int n, i, j;
2151
2152 n = dropEmptySubpaths ? 1 : 0;
2153 for (i = 0; i < path->getNumSubpaths(); ++i) {
2154 const GfxSubpath *subpath = path->getSubpath(i);
2155 if (subpath->getNumPoints() > n) {
2156 sPath.reserve(n: subpath->getNumPoints() + 1);
2157 sPath.moveTo(x: (SplashCoord)subpath->getX(i: 0), y: (SplashCoord)subpath->getY(i: 0));
2158 j = 1;
2159 while (j < subpath->getNumPoints()) {
2160 if (subpath->getCurve(i: j)) {
2161 sPath.curveTo(x1: (SplashCoord)subpath->getX(i: j), y1: (SplashCoord)subpath->getY(i: j), x2: (SplashCoord)subpath->getX(i: j + 1), y2: (SplashCoord)subpath->getY(i: j + 1), x3: (SplashCoord)subpath->getX(i: j + 2), y3: (SplashCoord)subpath->getY(i: j + 2));
2162 j += 3;
2163 } else {
2164 sPath.lineTo(x: (SplashCoord)subpath->getX(i: j), y: (SplashCoord)subpath->getY(i: j));
2165 ++j;
2166 }
2167 }
2168 if (subpath->isClosed()) {
2169 sPath.close();
2170 }
2171 }
2172 }
2173 return sPath;
2174}
2175
2176void SplashOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, const Unicode *u, int uLen)
2177{
2178 SplashPath *path;
2179 int render;
2180 bool doFill, doStroke, doClip, strokeAdjust;
2181 double m[4];
2182 bool horiz;
2183
2184 if (skipHorizText || skipRotatedText) {
2185 state->getFontTransMat(m11: &m[0], m12: &m[1], m21: &m[2], m22: &m[3]);
2186 horiz = m[0] > 0 && fabs(x: m[1]) < 0.001 && fabs(x: m[2]) < 0.001 && m[3] < 0;
2187 if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) {
2188 return;
2189 }
2190 }
2191
2192 // check for invisible text -- this is used by Acrobat Capture
2193 render = state->getRender();
2194 if (render == 3) {
2195 return;
2196 }
2197
2198 if (needFontUpdate) {
2199 doUpdateFont(state);
2200 }
2201 if (!font) {
2202 return;
2203 }
2204
2205 x -= originX;
2206 y -= originY;
2207
2208 doFill = !(render & 1) && !state->getFillColorSpace()->isNonMarking();
2209 doStroke = ((render & 3) == 1 || (render & 3) == 2) && !state->getStrokeColorSpace()->isNonMarking();
2210 doClip = render & 4;
2211
2212 path = nullptr;
2213 SplashCoord lineWidth = splash->getLineWidth();
2214 if (doStroke && lineWidth == 0.0) {
2215 splash->setLineWidth(1 / state->getVDPI());
2216 }
2217 if (doStroke || doClip) {
2218 if ((path = font->getGlyphPath(c: code))) {
2219 path->offset(dx: (SplashCoord)x, dy: (SplashCoord)y);
2220 }
2221 }
2222
2223 // don't use stroke adjustment when stroking text -- the results
2224 // tend to be ugly (because characters with horizontal upper or
2225 // lower edges get misaligned relative to the other characters)
2226 strokeAdjust = false; // make gcc happy
2227 if (doStroke) {
2228 strokeAdjust = splash->getStrokeAdjust();
2229 splash->setStrokeAdjust(false);
2230 }
2231
2232 // fill and stroke
2233 if (doFill && doStroke) {
2234 if (path) {
2235 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2236 splash->fill(path, eo: false);
2237 setOverprintMask(colorSpace: state->getStrokeColorSpace(), overprintFlag: state->getStrokeOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getStrokeColor());
2238 splash->stroke(path);
2239 }
2240
2241 // fill
2242 } else if (doFill) {
2243 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2244 splash->fillChar(x: (SplashCoord)x, y: (SplashCoord)y, c: code, font);
2245
2246 // stroke
2247 } else if (doStroke) {
2248 if (path) {
2249 setOverprintMask(colorSpace: state->getStrokeColorSpace(), overprintFlag: state->getStrokeOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getStrokeColor());
2250 splash->stroke(path);
2251 }
2252 }
2253 splash->setLineWidth(lineWidth);
2254
2255 // clip
2256 if (doClip) {
2257 if (path) {
2258 if (textClipPath) {
2259 textClipPath->append(path);
2260 } else {
2261 textClipPath = path;
2262 path = nullptr;
2263 }
2264 }
2265 }
2266
2267 if (doStroke) {
2268 splash->setStrokeAdjust(strokeAdjust);
2269 }
2270
2271 if (path) {
2272 delete path;
2273 }
2274}
2275
2276bool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, const Unicode *u, int uLen)
2277{
2278 std::shared_ptr<const GfxFont> gfxFont;
2279 const Ref *fontID;
2280 const double *ctm, *bbox;
2281 T3FontCache *t3Font;
2282 T3GlyphStack *t3gs;
2283 bool validBBox;
2284 double m[4];
2285 bool horiz;
2286 double x1, y1, xMin, yMin, xMax, yMax, xt, yt;
2287 int i, j;
2288
2289 // check for invisible text -- this is used by Acrobat Capture
2290 if (state->getRender() == 3) {
2291 // this is a bit of cheating, we say yes, font is already on cache
2292 // so we actually skip the rendering of it
2293 return true;
2294 }
2295
2296 if (skipHorizText || skipRotatedText) {
2297 state->getFontTransMat(m11: &m[0], m12: &m[1], m21: &m[2], m22: &m[3]);
2298 horiz = m[0] > 0 && fabs(x: m[1]) < 0.001 && fabs(x: m[2]) < 0.001 && m[3] < 0;
2299 if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) {
2300 return true;
2301 }
2302 }
2303
2304 if (!(gfxFont = state->getFont())) {
2305 return false;
2306 }
2307 fontID = gfxFont->getID();
2308 ctm = state->getCTM();
2309 state->transform(x1: 0, y1: 0, x2: &xt, y2: &yt);
2310
2311 // is it the first (MRU) font in the cache?
2312 if (!(nT3Fonts > 0 && t3FontCache[0]->matches(idA: fontID, m11A: ctm[0], m12A: ctm[1], m21A: ctm[2], m22A: ctm[3]))) {
2313
2314 // is the font elsewhere in the cache?
2315 for (i = 1; i < nT3Fonts; ++i) {
2316 if (t3FontCache[i]->matches(idA: fontID, m11A: ctm[0], m12A: ctm[1], m21A: ctm[2], m22A: ctm[3])) {
2317 t3Font = t3FontCache[i];
2318 for (j = i; j > 0; --j) {
2319 t3FontCache[j] = t3FontCache[j - 1];
2320 }
2321 t3FontCache[0] = t3Font;
2322 break;
2323 }
2324 }
2325 if (i >= nT3Fonts) {
2326
2327 // create new entry in the font cache
2328 if (nT3Fonts == splashOutT3FontCacheSize) {
2329 t3gs = t3GlyphStack;
2330 while (t3gs != nullptr) {
2331 if (t3gs->cache == t3FontCache[nT3Fonts - 1]) {
2332 error(category: errSyntaxWarning, pos: -1, msg: "t3FontCache reaches limit but font still on stack in SplashOutputDev::beginType3Char");
2333 return true;
2334 }
2335 t3gs = t3gs->next;
2336 }
2337 delete t3FontCache[nT3Fonts - 1];
2338 --nT3Fonts;
2339 }
2340 for (j = nT3Fonts; j > 0; --j) {
2341 t3FontCache[j] = t3FontCache[j - 1];
2342 }
2343 ++nT3Fonts;
2344 bbox = gfxFont->getFontBBox();
2345 if (bbox[0] == 0 && bbox[1] == 0 && bbox[2] == 0 && bbox[3] == 0) {
2346 // unspecified bounding box -- just take a guess
2347 xMin = xt - 5;
2348 xMax = xMin + 30;
2349 yMax = yt + 15;
2350 yMin = yMax - 45;
2351 validBBox = false;
2352 } else {
2353 state->transform(x1: bbox[0], y1: bbox[1], x2: &x1, y2: &y1);
2354 xMin = xMax = x1;
2355 yMin = yMax = y1;
2356 state->transform(x1: bbox[0], y1: bbox[3], x2: &x1, y2: &y1);
2357 if (x1 < xMin) {
2358 xMin = x1;
2359 } else if (x1 > xMax) {
2360 xMax = x1;
2361 }
2362 if (y1 < yMin) {
2363 yMin = y1;
2364 } else if (y1 > yMax) {
2365 yMax = y1;
2366 }
2367 state->transform(x1: bbox[2], y1: bbox[1], x2: &x1, y2: &y1);
2368 if (x1 < xMin) {
2369 xMin = x1;
2370 } else if (x1 > xMax) {
2371 xMax = x1;
2372 }
2373 if (y1 < yMin) {
2374 yMin = y1;
2375 } else if (y1 > yMax) {
2376 yMax = y1;
2377 }
2378 state->transform(x1: bbox[2], y1: bbox[3], x2: &x1, y2: &y1);
2379 if (x1 < xMin) {
2380 xMin = x1;
2381 } else if (x1 > xMax) {
2382 xMax = x1;
2383 }
2384 if (y1 < yMin) {
2385 yMin = y1;
2386 } else if (y1 > yMax) {
2387 yMax = y1;
2388 }
2389 validBBox = true;
2390 }
2391 t3FontCache[0] = new T3FontCache(fontID, ctm[0], ctm[1], ctm[2], ctm[3], (int)floor(x: xMin - xt) - 2, (int)floor(x: yMin - yt) - 2, (int)ceil(x: xMax) - (int)floor(x: xMin) + 4, (int)ceil(x: yMax) - (int)floor(x: yMin) + 4, validBBox,
2392 colorMode != splashModeMono1);
2393 }
2394 }
2395 t3Font = t3FontCache[0];
2396
2397 // is the glyph in the cache?
2398 i = (code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc;
2399 for (j = 0; j < t3Font->cacheAssoc; ++j) {
2400 if (t3Font->cacheTags != nullptr) {
2401 if ((t3Font->cacheTags[i + j].mru & 0x8000) && t3Font->cacheTags[i + j].code == code) {
2402 drawType3Glyph(state, t3Font, tag: &t3Font->cacheTags[i + j], data: t3Font->cacheData + (i + j) * t3Font->glyphSize);
2403 return true;
2404 }
2405 }
2406 }
2407
2408 // push a new Type 3 glyph record
2409 t3gs = new T3GlyphStack();
2410 t3gs->next = t3GlyphStack;
2411 t3GlyphStack = t3gs;
2412 t3GlyphStack->code = code;
2413 t3GlyphStack->cache = t3Font;
2414 t3GlyphStack->cacheTag = nullptr;
2415 t3GlyphStack->cacheData = nullptr;
2416 t3GlyphStack->haveDx = false;
2417 t3GlyphStack->doNotCache = false;
2418
2419 return false;
2420}
2421
2422void SplashOutputDev::endType3Char(GfxState *state)
2423{
2424 T3GlyphStack *t3gs;
2425
2426 if (t3GlyphStack->cacheTag) {
2427 memcpy(dest: t3GlyphStack->cacheData, src: bitmap->getDataPtr(), n: t3GlyphStack->cache->glyphSize);
2428 delete bitmap;
2429 delete splash;
2430 bitmap = t3GlyphStack->origBitmap;
2431 splash = t3GlyphStack->origSplash;
2432 const double *ctm = state->getCTM();
2433 state->setCTM(a: ctm[0], b: ctm[1], c: ctm[2], d: ctm[3], e: t3GlyphStack->origCTM4, f: t3GlyphStack->origCTM5);
2434 updateCTM(state, m11: 0, m12: 0, m21: 0, m22: 0, m31: 0, m32: 0);
2435 drawType3Glyph(state, t3Font: t3GlyphStack->cache, tag: t3GlyphStack->cacheTag, data: t3GlyphStack->cacheData);
2436 }
2437 t3gs = t3GlyphStack;
2438 t3GlyphStack = t3gs->next;
2439 delete t3gs;
2440}
2441
2442void SplashOutputDev::type3D0(GfxState *state, double wx, double wy)
2443{
2444 if (likely(t3GlyphStack != nullptr)) {
2445 t3GlyphStack->haveDx = true;
2446 } else {
2447 error(category: errSyntaxWarning, pos: -1, msg: "t3GlyphStack was null in SplashOutputDev::type3D0");
2448 }
2449}
2450
2451void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury)
2452{
2453 T3FontCache *t3Font;
2454 SplashColor color;
2455 double xt, yt, xMin, xMax, yMin, yMax, x1, y1;
2456 int i, j;
2457
2458 // ignore multiple d0/d1 operators
2459 if (!t3GlyphStack || t3GlyphStack->haveDx) {
2460 return;
2461 }
2462 t3GlyphStack->haveDx = true;
2463 // don't cache if we got a gsave/grestore before the d1
2464 if (t3GlyphStack->doNotCache) {
2465 return;
2466 }
2467
2468 if (unlikely(t3GlyphStack == nullptr)) {
2469 error(category: errSyntaxWarning, pos: -1, msg: "t3GlyphStack was null in SplashOutputDev::type3D1");
2470 return;
2471 }
2472
2473 if (unlikely(t3GlyphStack->origBitmap != nullptr)) {
2474 error(category: errSyntaxWarning, pos: -1, msg: "t3GlyphStack origBitmap was not null in SplashOutputDev::type3D1");
2475 return;
2476 }
2477
2478 if (unlikely(t3GlyphStack->origSplash != nullptr)) {
2479 error(category: errSyntaxWarning, pos: -1, msg: "t3GlyphStack origSplash was not null in SplashOutputDev::type3D1");
2480 return;
2481 }
2482
2483 t3Font = t3GlyphStack->cache;
2484
2485 // check for a valid bbox
2486 state->transform(x1: 0, y1: 0, x2: &xt, y2: &yt);
2487 state->transform(x1: llx, y1: lly, x2: &x1, y2: &y1);
2488 xMin = xMax = x1;
2489 yMin = yMax = y1;
2490 state->transform(x1: llx, y1: ury, x2: &x1, y2: &y1);
2491 if (x1 < xMin) {
2492 xMin = x1;
2493 } else if (x1 > xMax) {
2494 xMax = x1;
2495 }
2496 if (y1 < yMin) {
2497 yMin = y1;
2498 } else if (y1 > yMax) {
2499 yMax = y1;
2500 }
2501 state->transform(x1: urx, y1: lly, x2: &x1, y2: &y1);
2502 if (x1 < xMin) {
2503 xMin = x1;
2504 } else if (x1 > xMax) {
2505 xMax = x1;
2506 }
2507 if (y1 < yMin) {
2508 yMin = y1;
2509 } else if (y1 > yMax) {
2510 yMax = y1;
2511 }
2512 state->transform(x1: urx, y1: ury, x2: &x1, y2: &y1);
2513 if (x1 < xMin) {
2514 xMin = x1;
2515 } else if (x1 > xMax) {
2516 xMax = x1;
2517 }
2518 if (y1 < yMin) {
2519 yMin = y1;
2520 } else if (y1 > yMax) {
2521 yMax = y1;
2522 }
2523 if (xMin - xt < t3Font->glyphX || yMin - yt < t3Font->glyphY || xMax - xt > t3Font->glyphX + t3Font->glyphW || yMax - yt > t3Font->glyphY + t3Font->glyphH) {
2524 if (t3Font->validBBox) {
2525 error(category: errSyntaxWarning, pos: -1, msg: "Bad bounding box in Type 3 glyph");
2526 }
2527 return;
2528 }
2529
2530 if (t3Font->cacheTags == nullptr) {
2531 return;
2532 }
2533
2534 // allocate a cache entry
2535 i = (t3GlyphStack->code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc;
2536 for (j = 0; j < t3Font->cacheAssoc; ++j) {
2537 if ((t3Font->cacheTags[i + j].mru & 0x7fff) == t3Font->cacheAssoc - 1) {
2538 t3Font->cacheTags[i + j].mru = 0x8000;
2539 t3Font->cacheTags[i + j].code = t3GlyphStack->code;
2540 t3GlyphStack->cacheTag = &t3Font->cacheTags[i + j];
2541 t3GlyphStack->cacheData = t3Font->cacheData + (i + j) * t3Font->glyphSize;
2542 } else {
2543 ++t3Font->cacheTags[i + j].mru;
2544 }
2545 }
2546
2547 // save state
2548 t3GlyphStack->origBitmap = bitmap;
2549 t3GlyphStack->origSplash = splash;
2550 const double *ctm = state->getCTM();
2551 t3GlyphStack->origCTM4 = ctm[4];
2552 t3GlyphStack->origCTM5 = ctm[5];
2553
2554 // create the temporary bitmap
2555 if (colorMode == splashModeMono1) {
2556 bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono1, false);
2557 splash = new Splash(bitmap, false, t3GlyphStack->origSplash->getScreen());
2558 color[0] = 0;
2559 splash->clear(color);
2560 color[0] = 0xff;
2561 } else {
2562 bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono8, false);
2563 splash = new Splash(bitmap, vectorAntialias, t3GlyphStack->origSplash->getScreen());
2564 color[0] = 0x00;
2565 splash->clear(color);
2566 color[0] = 0xff;
2567 }
2568 splash->setMinLineWidth(s_minLineWidth);
2569 splash->setThinLineMode(splashThinLineDefault);
2570 splash->setFillPattern(new SplashSolidColor(color));
2571 splash->setStrokePattern(new SplashSolidColor(color));
2572 //~ this should copy other state from t3GlyphStack->origSplash?
2573 state->setCTM(a: ctm[0], b: ctm[1], c: ctm[2], d: ctm[3], e: -t3Font->glyphX, f: -t3Font->glyphY);
2574 updateCTM(state, m11: 0, m12: 0, m21: 0, m22: 0, m31: 0, m32: 0);
2575}
2576
2577void SplashOutputDev::drawType3Glyph(GfxState *state, T3FontCache *t3Font, T3FontCacheTag * /*tag*/, unsigned char *data)
2578{
2579 SplashGlyphBitmap glyph;
2580
2581 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2582 glyph.x = -t3Font->glyphX;
2583 glyph.y = -t3Font->glyphY;
2584 glyph.w = t3Font->glyphW;
2585 glyph.h = t3Font->glyphH;
2586 glyph.aa = colorMode != splashModeMono1;
2587 glyph.data = data;
2588 glyph.freeData = false;
2589 splash->fillGlyph(x: 0, y: 0, glyph: &glyph);
2590}
2591
2592void SplashOutputDev::beginTextObject(GfxState *state) { }
2593
2594void SplashOutputDev::endTextObject(GfxState *state)
2595{
2596 if (textClipPath) {
2597 splash->clipToPath(path: textClipPath, eo: false);
2598 delete textClipPath;
2599 textClipPath = nullptr;
2600 }
2601}
2602
2603struct SplashOutImageMaskData
2604{
2605 ImageStream *imgStr;
2606 bool invert;
2607 int width, height, y;
2608};
2609
2610bool SplashOutputDev::imageMaskSrc(void *data, SplashColorPtr line)
2611{
2612 SplashOutImageMaskData *imgMaskData = (SplashOutImageMaskData *)data;
2613 unsigned char *p;
2614 SplashColorPtr q;
2615 int x;
2616
2617 if (imgMaskData->y == imgMaskData->height) {
2618 return false;
2619 }
2620 if (!(p = imgMaskData->imgStr->getLine())) {
2621 return false;
2622 }
2623 for (x = 0, q = line; x < imgMaskData->width; ++x) {
2624 *q++ = *p++ ^ imgMaskData->invert;
2625 }
2626 ++imgMaskData->y;
2627 return true;
2628}
2629
2630void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg)
2631{
2632 SplashCoord mat[6];
2633 SplashOutImageMaskData imgMaskData;
2634
2635 if (state->getFillColorSpace()->isNonMarking()) {
2636 return;
2637 }
2638 setOverprintMask(colorSpace: state->getFillColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: state->getFillColor());
2639
2640 const double *ctm = state->getCTM();
2641 for (int i = 0; i < 6; ++i) {
2642 if (!std::isfinite(x: ctm[i])) {
2643 return;
2644 }
2645 }
2646 mat[0] = ctm[0];
2647 mat[1] = ctm[1];
2648 mat[2] = -ctm[2];
2649 mat[3] = -ctm[3];
2650 mat[4] = ctm[2] + ctm[4];
2651 mat[5] = ctm[3] + ctm[5];
2652
2653 imgMaskData.imgStr = new ImageStream(str, width, 1, 1);
2654 imgMaskData.imgStr->reset();
2655 imgMaskData.invert = invert ? false : true;
2656 imgMaskData.width = width;
2657 imgMaskData.height = height;
2658 imgMaskData.y = 0;
2659
2660 splash->fillImageMask(src: &imageMaskSrc, srcData: &imgMaskData, w: width, h: height, mat, glyphMode: t3GlyphStack != nullptr);
2661 if (inlineImg) {
2662 while (imgMaskData.y < height) {
2663 if (!imgMaskData.imgStr->getLine()) {
2664 break;
2665 }
2666 ++imgMaskData.y;
2667 }
2668 }
2669
2670 delete imgMaskData.imgStr;
2671 str->close();
2672}
2673
2674void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg, double *baseMatrix)
2675{
2676 const double *ctm;
2677 SplashCoord mat[6];
2678 SplashOutImageMaskData imgMaskData;
2679 Splash *maskSplash;
2680 SplashColor maskColor;
2681 double bbox[4] = { 0, 0, 1, 1 }; // default;
2682
2683 if (state->getFillColorSpace()->isNonMarking()) {
2684 return;
2685 }
2686
2687 ctm = state->getCTM();
2688 for (int i = 0; i < 6; ++i) {
2689 if (!std::isfinite(x: ctm[i])) {
2690 return;
2691 }
2692 }
2693
2694 beginTransparencyGroup(state, bbox, blendingColorSpace: nullptr, isolated: false, knockout: false, forSoftMask: false);
2695 baseMatrix[4] -= transpGroupStack->tx;
2696 baseMatrix[5] -= transpGroupStack->ty;
2697
2698 ctm = state->getCTM();
2699 mat[0] = ctm[0];
2700 mat[1] = ctm[1];
2701 mat[2] = -ctm[2];
2702 mat[3] = -ctm[3];
2703 mat[4] = ctm[2] + ctm[4];
2704 mat[5] = ctm[3] + ctm[5];
2705 imgMaskData.imgStr = new ImageStream(str, width, 1, 1);
2706 imgMaskData.imgStr->reset();
2707 imgMaskData.invert = invert ? false : true;
2708 imgMaskData.width = width;
2709 imgMaskData.height = height;
2710 imgMaskData.y = 0;
2711
2712 transpGroupStack->softmask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false);
2713 maskSplash = new Splash(transpGroupStack->softmask, vectorAntialias);
2714 maskColor[0] = 0;
2715 maskSplash->clear(color: maskColor);
2716 maskColor[0] = 0xff;
2717 maskSplash->setFillPattern(new SplashSolidColor(maskColor));
2718 maskSplash->fillImageMask(src: &imageMaskSrc, srcData: &imgMaskData, w: width, h: height, mat, glyphMode: t3GlyphStack != nullptr);
2719 delete maskSplash;
2720 delete imgMaskData.imgStr;
2721 str->close();
2722}
2723
2724void SplashOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix)
2725{
2726 double bbox[4] = { 0, 0, 1, 1 }; // dummy
2727
2728 if (!transpGroupStack) {
2729 return;
2730 }
2731
2732 /* transfer mask to alpha channel! */
2733 // memcpy(maskBitmap->getAlphaPtr(), maskBitmap->getDataPtr(), bitmap->getRowSize() * bitmap->getHeight());
2734 // memset(maskBitmap->getDataPtr(), 0, bitmap->getRowSize() * bitmap->getHeight());
2735 if (transpGroupStack->softmask != nullptr) {
2736 unsigned char *dest = bitmap->getAlphaPtr();
2737 unsigned char *src = transpGroupStack->softmask->getDataPtr();
2738 for (int c = 0; c < transpGroupStack->softmask->getRowSize() * transpGroupStack->softmask->getHeight(); c++) {
2739 dest[c] = src[c];
2740 }
2741 delete transpGroupStack->softmask;
2742 transpGroupStack->softmask = nullptr;
2743 }
2744 endTransparencyGroup(state);
2745 baseMatrix[4] += transpGroupStack->tx;
2746 baseMatrix[5] += transpGroupStack->ty;
2747 paintTransparencyGroup(state, bbox);
2748}
2749
2750struct SplashOutImageData
2751{
2752 ImageStream *imgStr;
2753 GfxImageColorMap *colorMap;
2754 SplashColorPtr lookup;
2755 const int *maskColors;
2756 SplashColorMode colorMode;
2757 int width, height, y;
2758 ImageStream *maskStr;
2759 GfxImageColorMap *maskColorMap;
2760 SplashColor matteColor;
2761};
2762
2763#ifdef USE_CMS
2764bool SplashOutputDev::useIccImageSrc(void *data)
2765{
2766 SplashOutImageData *imgData = (SplashOutImageData *)data;
2767
2768 if (!imgData->lookup && imgData->colorMap->getColorSpace()->getMode() == csICCBased && imgData->colorMap->getBits() != 1) {
2769 GfxICCBasedColorSpace *colorSpace = (GfxICCBasedColorSpace *)imgData->colorMap->getColorSpace();
2770 switch (imgData->colorMode) {
2771 case splashModeMono1:
2772 case splashModeMono8:
2773 if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceGray) {
2774 return true;
2775 }
2776 break;
2777 case splashModeXBGR8:
2778 case splashModeRGB8:
2779 case splashModeBGR8:
2780 if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceRGB) {
2781 return true;
2782 }
2783 break;
2784 case splashModeCMYK8:
2785 if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceCMYK) {
2786 return true;
2787 }
2788 break;
2789 case splashModeDeviceN8:
2790 if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceN) {
2791 return true;
2792 }
2793 break;
2794 }
2795 }
2796
2797 return false;
2798}
2799#endif
2800
2801// Clip x to lie in [0, 255].
2802static inline unsigned char clip255(int x)
2803{
2804 return x < 0 ? 0 : x > 255 ? 255 : x;
2805}
2806
2807bool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine, unsigned char * /*alphaLine*/)
2808{
2809 SplashOutImageData *imgData = (SplashOutImageData *)data;
2810 unsigned char *p;
2811 SplashColorPtr q, col;
2812 GfxRGB rgb;
2813 GfxGray gray;
2814 GfxCMYK cmyk;
2815 GfxColor deviceN;
2816 int nComps, x;
2817
2818 if (imgData->y == imgData->height) {
2819 return false;
2820 }
2821 if (!(p = imgData->imgStr->getLine())) {
2822 int destComps = 1;
2823 if (imgData->colorMode == splashModeRGB8 || imgData->colorMode == splashModeBGR8) {
2824 destComps = 3;
2825 } else if (imgData->colorMode == splashModeXBGR8) {
2826 destComps = 4;
2827 } else if (imgData->colorMode == splashModeCMYK8) {
2828 destComps = 4;
2829 } else if (imgData->colorMode == splashModeDeviceN8) {
2830 destComps = SPOT_NCOMPS + 4;
2831 }
2832 memset(s: colorLine, c: 0, n: imgData->width * destComps);
2833 return false;
2834 }
2835
2836 nComps = imgData->colorMap->getNumPixelComps();
2837
2838 if (imgData->lookup) {
2839 switch (imgData->colorMode) {
2840 case splashModeMono1:
2841 case splashModeMono8:
2842 for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
2843 *q++ = imgData->lookup[*p];
2844 }
2845 break;
2846 case splashModeRGB8:
2847 case splashModeBGR8:
2848 for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
2849 col = &imgData->lookup[3 * *p];
2850 *q++ = col[0];
2851 *q++ = col[1];
2852 *q++ = col[2];
2853 }
2854 break;
2855 case splashModeXBGR8:
2856 for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
2857 col = &imgData->lookup[4 * *p];
2858 *q++ = col[0];
2859 *q++ = col[1];
2860 *q++ = col[2];
2861 *q++ = col[3];
2862 }
2863 break;
2864 case splashModeCMYK8:
2865 for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
2866 col = &imgData->lookup[4 * *p];
2867 *q++ = col[0];
2868 *q++ = col[1];
2869 *q++ = col[2];
2870 *q++ = col[3];
2871 }
2872 break;
2873 case splashModeDeviceN8:
2874 for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
2875 col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p];
2876 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
2877 *q++ = col[cp];
2878 }
2879 }
2880 break;
2881 }
2882 } else {
2883 switch (imgData->colorMode) {
2884 case splashModeMono1:
2885 case splashModeMono8:
2886 for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
2887 imgData->colorMap->getGray(x: p, gray: &gray);
2888 *q++ = colToByte(x: gray);
2889 }
2890 break;
2891 case splashModeRGB8:
2892 case splashModeBGR8:
2893 if (imgData->colorMap->useRGBLine()) {
2894 imgData->colorMap->getRGBLine(in: p, out: (unsigned char *)colorLine, length: imgData->width);
2895 } else {
2896 for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
2897 imgData->colorMap->getRGB(x: p, rgb: &rgb);
2898 *q++ = colToByte(x: rgb.r);
2899 *q++ = colToByte(x: rgb.g);
2900 *q++ = colToByte(x: rgb.b);
2901 }
2902 }
2903 break;
2904 case splashModeXBGR8:
2905 if (imgData->colorMap->useRGBLine()) {
2906 imgData->colorMap->getRGBXLine(in: p, out: (unsigned char *)colorLine, length: imgData->width);
2907 } else {
2908 for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
2909 imgData->colorMap->getRGB(x: p, rgb: &rgb);
2910 *q++ = colToByte(x: rgb.r);
2911 *q++ = colToByte(x: rgb.g);
2912 *q++ = colToByte(x: rgb.b);
2913 *q++ = 255;
2914 }
2915 }
2916 break;
2917 case splashModeCMYK8:
2918 if (imgData->colorMap->useCMYKLine()) {
2919 imgData->colorMap->getCMYKLine(in: p, out: (unsigned char *)colorLine, length: imgData->width);
2920 } else {
2921 for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
2922 imgData->colorMap->getCMYK(x: p, cmyk: &cmyk);
2923 *q++ = colToByte(x: cmyk.c);
2924 *q++ = colToByte(x: cmyk.m);
2925 *q++ = colToByte(x: cmyk.y);
2926 *q++ = colToByte(x: cmyk.k);
2927 }
2928 }
2929 break;
2930 case splashModeDeviceN8:
2931 if (imgData->colorMap->useDeviceNLine()) {
2932 imgData->colorMap->getDeviceNLine(in: p, out: (unsigned char *)colorLine, length: imgData->width);
2933 } else {
2934 for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
2935 imgData->colorMap->getDeviceN(x: p, deviceN: &deviceN);
2936 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
2937 *q++ = colToByte(x: deviceN.c[cp]);
2938 }
2939 }
2940 }
2941 break;
2942 }
2943 }
2944
2945 if (imgData->maskStr != nullptr && (p = imgData->maskStr->getLine()) != nullptr) {
2946 int destComps = splashColorModeNComps[imgData->colorMode];
2947 int convComps = (imgData->colorMode == splashModeXBGR8) ? 3 : destComps;
2948 imgData->maskColorMap->getGrayLine(in: p, out: p, length: imgData->width);
2949 for (x = 0, q = colorLine; x < imgData->width; ++x, p++, q += destComps) {
2950 for (int cp = 0; cp < convComps; cp++) {
2951 q[cp] = (*p) ? clip255(x: imgData->matteColor[cp] + (int)(q[cp] - imgData->matteColor[cp]) * 255 / *p) : imgData->matteColor[cp];
2952 }
2953 }
2954 }
2955 ++imgData->y;
2956 return true;
2957}
2958
2959#ifdef USE_CMS
2960bool SplashOutputDev::iccImageSrc(void *data, SplashColorPtr colorLine, unsigned char * /*alphaLine*/)
2961{
2962 SplashOutImageData *imgData = (SplashOutImageData *)data;
2963 unsigned char *p;
2964 int nComps;
2965
2966 if (imgData->y == imgData->height) {
2967 return false;
2968 }
2969 if (!(p = imgData->imgStr->getLine())) {
2970 int destComps = 1;
2971 if (imgData->colorMode == splashModeRGB8 || imgData->colorMode == splashModeBGR8) {
2972 destComps = 3;
2973 } else if (imgData->colorMode == splashModeXBGR8) {
2974 destComps = 4;
2975 } else if (imgData->colorMode == splashModeCMYK8) {
2976 destComps = 4;
2977 } else if (imgData->colorMode == splashModeDeviceN8) {
2978 destComps = SPOT_NCOMPS + 4;
2979 }
2980 memset(colorLine, 0, imgData->width * destComps);
2981 return false;
2982 }
2983
2984 if (imgData->colorMode == splashModeXBGR8) {
2985 SplashColorPtr q;
2986 int x;
2987 for (x = 0, q = colorLine; x < imgData->width; ++x) {
2988 *q++ = *p++;
2989 *q++ = *p++;
2990 *q++ = *p++;
2991 *q++ = 255;
2992 }
2993 } else {
2994 nComps = imgData->colorMap->getNumPixelComps();
2995 memcpy(colorLine, p, imgData->width * nComps);
2996 }
2997
2998 ++imgData->y;
2999 return true;
3000}
3001
3002void SplashOutputDev::iccTransform(void *data, SplashBitmap *bitmap)
3003{
3004 SplashOutImageData *imgData = (SplashOutImageData *)data;
3005 int nComps = imgData->colorMap->getNumPixelComps();
3006
3007 unsigned char *colorLine = (unsigned char *)gmalloc(nComps * bitmap->getWidth());
3008 unsigned char *rgbxLine = (imgData->colorMode == splashModeXBGR8) ? (unsigned char *)gmalloc(3 * bitmap->getWidth()) : nullptr;
3009 for (int i = 0; i < bitmap->getHeight(); i++) {
3010 unsigned char *p = bitmap->getDataPtr() + i * bitmap->getRowSize();
3011 switch (imgData->colorMode) {
3012 case splashModeMono1:
3013 case splashModeMono8:
3014 imgData->colorMap->getGrayLine(p, colorLine, bitmap->getWidth());
3015 memcpy(p, colorLine, nComps * bitmap->getWidth());
3016 break;
3017 case splashModeRGB8:
3018 case splashModeBGR8:
3019 imgData->colorMap->getRGBLine(p, colorLine, bitmap->getWidth());
3020 memcpy(p, colorLine, nComps * bitmap->getWidth());
3021 break;
3022 case splashModeCMYK8:
3023 imgData->colorMap->getCMYKLine(p, colorLine, bitmap->getWidth());
3024 memcpy(p, colorLine, nComps * bitmap->getWidth());
3025 break;
3026 case splashModeDeviceN8:
3027 imgData->colorMap->getDeviceNLine(p, colorLine, bitmap->getWidth());
3028 memcpy(p, colorLine, nComps * bitmap->getWidth());
3029 break;
3030 case splashModeXBGR8:
3031 unsigned char *q;
3032 unsigned char *b = p;
3033 int x;
3034 for (x = 0, q = rgbxLine; x < bitmap->getWidth(); ++x, b += 4) {
3035 *q++ = b[2];
3036 *q++ = b[1];
3037 *q++ = b[0];
3038 }
3039 imgData->colorMap->getRGBLine(rgbxLine, colorLine, bitmap->getWidth());
3040 b = p;
3041 for (x = 0, q = colorLine; x < bitmap->getWidth(); ++x, b += 4) {
3042 b[2] = *q++;
3043 b[1] = *q++;
3044 b[0] = *q++;
3045 }
3046 break;
3047 }
3048 }
3049 gfree(colorLine);
3050 if (rgbxLine != nullptr) {
3051 gfree(rgbxLine);
3052 }
3053}
3054#endif
3055
3056bool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine)
3057{
3058 SplashOutImageData *imgData = (SplashOutImageData *)data;
3059 unsigned char *p, *aq;
3060 SplashColorPtr q, col;
3061 GfxRGB rgb;
3062 GfxGray gray;
3063 GfxCMYK cmyk;
3064 GfxColor deviceN;
3065 unsigned char alpha;
3066 int nComps, x, i;
3067
3068 if (imgData->y == imgData->height) {
3069 return false;
3070 }
3071 if (!(p = imgData->imgStr->getLine())) {
3072 return false;
3073 }
3074
3075 nComps = imgData->colorMap->getNumPixelComps();
3076
3077 for (x = 0, q = colorLine, aq = alphaLine; x < imgData->width; ++x, p += nComps) {
3078 alpha = 0;
3079 for (i = 0; i < nComps; ++i) {
3080 if (p[i] < imgData->maskColors[2 * i] || p[i] > imgData->maskColors[2 * i + 1]) {
3081 alpha = 0xff;
3082 break;
3083 }
3084 }
3085 if (imgData->lookup) {
3086 switch (imgData->colorMode) {
3087 case splashModeMono1:
3088 case splashModeMono8:
3089 *q++ = imgData->lookup[*p];
3090 break;
3091 case splashModeRGB8:
3092 case splashModeBGR8:
3093 col = &imgData->lookup[3 * *p];
3094 *q++ = col[0];
3095 *q++ = col[1];
3096 *q++ = col[2];
3097 break;
3098 case splashModeXBGR8:
3099 col = &imgData->lookup[4 * *p];
3100 *q++ = col[0];
3101 *q++ = col[1];
3102 *q++ = col[2];
3103 *q++ = 255;
3104 break;
3105 case splashModeCMYK8:
3106 col = &imgData->lookup[4 * *p];
3107 *q++ = col[0];
3108 *q++ = col[1];
3109 *q++ = col[2];
3110 *q++ = col[3];
3111 break;
3112 case splashModeDeviceN8:
3113 col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p];
3114 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3115 *q++ = col[cp];
3116 }
3117 break;
3118 }
3119 *aq++ = alpha;
3120 } else {
3121 switch (imgData->colorMode) {
3122 case splashModeMono1:
3123 case splashModeMono8:
3124 imgData->colorMap->getGray(x: p, gray: &gray);
3125 *q++ = colToByte(x: gray);
3126 break;
3127 case splashModeXBGR8:
3128 case splashModeRGB8:
3129 case splashModeBGR8:
3130 imgData->colorMap->getRGB(x: p, rgb: &rgb);
3131 *q++ = colToByte(x: rgb.r);
3132 *q++ = colToByte(x: rgb.g);
3133 *q++ = colToByte(x: rgb.b);
3134 if (imgData->colorMode == splashModeXBGR8) {
3135 *q++ = 255;
3136 }
3137 break;
3138 case splashModeCMYK8:
3139 imgData->colorMap->getCMYK(x: p, cmyk: &cmyk);
3140 *q++ = colToByte(x: cmyk.c);
3141 *q++ = colToByte(x: cmyk.m);
3142 *q++ = colToByte(x: cmyk.y);
3143 *q++ = colToByte(x: cmyk.k);
3144 break;
3145 case splashModeDeviceN8:
3146 imgData->colorMap->getDeviceN(x: p, deviceN: &deviceN);
3147 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3148 *q++ = colToByte(x: deviceN.c[cp]);
3149 }
3150 break;
3151 }
3152 *aq++ = alpha;
3153 }
3154 }
3155
3156 ++imgData->y;
3157 return true;
3158}
3159
3160struct TilingSplashOutBitmap
3161{
3162 SplashBitmap *bitmap;
3163 SplashPattern *pattern;
3164 SplashColorMode colorMode;
3165 int paintType;
3166 int repeatX;
3167 int repeatY;
3168 int y;
3169};
3170
3171bool SplashOutputDev::tilingBitmapSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine)
3172{
3173 TilingSplashOutBitmap *imgData = (TilingSplashOutBitmap *)data;
3174
3175 if (imgData->y == imgData->bitmap->getHeight()) {
3176 imgData->repeatY--;
3177 if (imgData->repeatY == 0) {
3178 return false;
3179 }
3180 imgData->y = 0;
3181 }
3182
3183 if (imgData->paintType == 1) {
3184 const SplashColorMode cMode = imgData->bitmap->getMode();
3185 SplashColorPtr q = colorLine;
3186 // For splashModeBGR8 and splashModeXBGR8 we need to use getPixel
3187 // for the others we can use raw access
3188 if (cMode == splashModeBGR8 || cMode == splashModeXBGR8) {
3189 for (int m = 0; m < imgData->repeatX; m++) {
3190 for (int x = 0; x < imgData->bitmap->getWidth(); x++) {
3191 imgData->bitmap->getPixel(x, y: imgData->y, pixel: q);
3192 q += splashColorModeNComps[cMode];
3193 }
3194 }
3195 } else {
3196 const int n = imgData->bitmap->getRowSize();
3197 SplashColorPtr p;
3198 for (int m = 0; m < imgData->repeatX; m++) {
3199 p = imgData->bitmap->getDataPtr() + imgData->y * imgData->bitmap->getRowSize();
3200 for (int x = 0; x < n; ++x) {
3201 *q++ = *p++;
3202 }
3203 }
3204 }
3205 if (alphaLine != nullptr) {
3206 SplashColorPtr aq = alphaLine;
3207 SplashColorPtr p;
3208 const int n = imgData->bitmap->getWidth() - 1;
3209 for (int m = 0; m < imgData->repeatX; m++) {
3210 p = imgData->bitmap->getAlphaPtr() + imgData->y * imgData->bitmap->getWidth();
3211 for (int x = 0; x < n; ++x) {
3212 *aq++ = *p++;
3213 }
3214 // This is a hack, because of how Splash antialias works if we overwrite the
3215 // last alpha pixel of the tile most/all of the files look much better
3216 *aq++ = (n == 0) ? *p : *(p - 1);
3217 }
3218 }
3219 } else {
3220 SplashColor col, pat;
3221 SplashColorPtr dest = colorLine;
3222 for (int m = 0; m < imgData->repeatX; m++) {
3223 for (int x = 0; x < imgData->bitmap->getWidth(); x++) {
3224 imgData->bitmap->getPixel(x, y: imgData->y, pixel: col);
3225 imgData->pattern->getColor(x, y: imgData->y, c: pat);
3226 for (int i = 0; i < splashColorModeNComps[imgData->colorMode]; ++i) {
3227 if (imgData->colorMode == splashModeCMYK8 || imgData->colorMode == splashModeDeviceN8) {
3228 dest[i] = div255(x: pat[i] * (255 - col[0]));
3229 } else {
3230 dest[i] = 255 - div255(x: (255 - pat[i]) * (255 - col[0]));
3231 }
3232 }
3233 dest += splashColorModeNComps[imgData->colorMode];
3234 }
3235 }
3236 if (alphaLine != nullptr) {
3237 const int y = (imgData->y == imgData->bitmap->getHeight() - 1 && imgData->y > 50) ? imgData->y - 1 : imgData->y;
3238 SplashColorPtr aq = alphaLine;
3239 SplashColorPtr p;
3240 const int n = imgData->bitmap->getWidth();
3241 for (int m = 0; m < imgData->repeatX; m++) {
3242 p = imgData->bitmap->getAlphaPtr() + y * imgData->bitmap->getWidth();
3243 for (int x = 0; x < n; ++x) {
3244 *aq++ = *p++;
3245 }
3246 }
3247 }
3248 }
3249 ++imgData->y;
3250 return true;
3251}
3252
3253void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg)
3254{
3255 SplashCoord mat[6];
3256 SplashOutImageData imgData;
3257 SplashColorMode srcMode;
3258 SplashImageSource src;
3259 SplashICCTransform tf;
3260 GfxGray gray;
3261 GfxRGB rgb;
3262 GfxCMYK cmyk;
3263 bool grayIndexed = false;
3264 GfxColor deviceN;
3265 unsigned char pix;
3266 int n, i;
3267
3268 const double *ctm = state->getCTM();
3269 for (i = 0; i < 6; ++i) {
3270 if (!std::isfinite(x: ctm[i])) {
3271 return;
3272 }
3273 }
3274 mat[0] = ctm[0];
3275 mat[1] = ctm[1];
3276 mat[2] = -ctm[2];
3277 mat[3] = -ctm[3];
3278 mat[4] = ctm[2] + ctm[4];
3279 mat[5] = ctm[3] + ctm[5];
3280
3281 imgData.imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
3282 imgData.imgStr->reset();
3283 imgData.colorMap = colorMap;
3284 imgData.maskColors = maskColors;
3285 imgData.colorMode = colorMode;
3286 imgData.width = width;
3287 imgData.height = height;
3288 imgData.maskStr = nullptr;
3289 imgData.maskColorMap = nullptr;
3290 imgData.y = 0;
3291
3292 // special case for one-channel (monochrome/gray/separation) images:
3293 // build a lookup table here
3294 imgData.lookup = nullptr;
3295 if (colorMap->getNumPixelComps() == 1) {
3296 n = 1 << colorMap->getBits();
3297 switch (colorMode) {
3298 case splashModeMono1:
3299 case splashModeMono8:
3300 imgData.lookup = (SplashColorPtr)gmalloc_checkoverflow(size: n);
3301 if (likely(imgData.lookup != nullptr)) {
3302 for (i = 0; i < n; ++i) {
3303 pix = (unsigned char)i;
3304 colorMap->getGray(x: &pix, gray: &gray);
3305 imgData.lookup[i] = colToByte(x: gray);
3306 }
3307 }
3308 break;
3309 case splashModeRGB8:
3310 case splashModeBGR8:
3311 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 3);
3312 if (likely(imgData.lookup != nullptr)) {
3313 for (i = 0; i < n; ++i) {
3314 pix = (unsigned char)i;
3315 colorMap->getRGB(x: &pix, rgb: &rgb);
3316 imgData.lookup[3 * i] = colToByte(x: rgb.r);
3317 imgData.lookup[3 * i + 1] = colToByte(x: rgb.g);
3318 imgData.lookup[3 * i + 2] = colToByte(x: rgb.b);
3319 }
3320 }
3321 break;
3322 case splashModeXBGR8:
3323 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 4);
3324 if (likely(imgData.lookup != nullptr)) {
3325 for (i = 0; i < n; ++i) {
3326 pix = (unsigned char)i;
3327 colorMap->getRGB(x: &pix, rgb: &rgb);
3328 imgData.lookup[4 * i] = colToByte(x: rgb.r);
3329 imgData.lookup[4 * i + 1] = colToByte(x: rgb.g);
3330 imgData.lookup[4 * i + 2] = colToByte(x: rgb.b);
3331 imgData.lookup[4 * i + 3] = 255;
3332 }
3333 }
3334 break;
3335 case splashModeCMYK8:
3336 grayIndexed = colorMap->getColorSpace()->getMode() != csDeviceGray;
3337 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 4);
3338 if (likely(imgData.lookup != nullptr)) {
3339 for (i = 0; i < n; ++i) {
3340 pix = (unsigned char)i;
3341 colorMap->getCMYK(x: &pix, cmyk: &cmyk);
3342 if (cmyk.c != 0 || cmyk.m != 0 || cmyk.y != 0) {
3343 grayIndexed = false;
3344 }
3345 imgData.lookup[4 * i] = colToByte(x: cmyk.c);
3346 imgData.lookup[4 * i + 1] = colToByte(x: cmyk.m);
3347 imgData.lookup[4 * i + 2] = colToByte(x: cmyk.y);
3348 imgData.lookup[4 * i + 3] = colToByte(x: cmyk.k);
3349 }
3350 }
3351 break;
3352 case splashModeDeviceN8:
3353 colorMap->getColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
3354 grayIndexed = colorMap->getColorSpace()->getMode() != csDeviceGray;
3355 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, SPOT_NCOMPS + 4);
3356 if (likely(imgData.lookup != nullptr)) {
3357 for (i = 0; i < n; ++i) {
3358 pix = (unsigned char)i;
3359 colorMap->getCMYK(x: &pix, cmyk: &cmyk);
3360 if (cmyk.c != 0 || cmyk.m != 0 || cmyk.y != 0) {
3361 grayIndexed = false;
3362 }
3363 colorMap->getDeviceN(x: &pix, deviceN: &deviceN);
3364 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3365 imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(x: deviceN.c[cp]);
3366 }
3367 }
3368 }
3369 break;
3370 }
3371 }
3372
3373 setOverprintMask(colorSpace: colorMap->getColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: nullptr, grayIndexed);
3374
3375 if (colorMode == splashModeMono1) {
3376 srcMode = splashModeMono8;
3377 } else {
3378 srcMode = colorMode;
3379 }
3380#ifdef USE_CMS
3381 src = maskColors ? &alphaImageSrc : useIccImageSrc(&imgData) ? &iccImageSrc : &imageSrc;
3382 tf = maskColors == nullptr && useIccImageSrc(&imgData) ? &iccTransform : nullptr;
3383#else
3384 src = maskColors ? &alphaImageSrc : &imageSrc;
3385 tf = nullptr;
3386#endif
3387 splash->drawImage(src, tf, srcData: &imgData, srcMode, srcAlpha: maskColors ? true : false, w: width, h: height, mat, interpolate);
3388 if (inlineImg) {
3389 while (imgData.y < height) {
3390 imgData.imgStr->getLine();
3391 ++imgData.y;
3392 }
3393 }
3394
3395 gfree(p: imgData.lookup);
3396 delete imgData.imgStr;
3397 str->close();
3398}
3399
3400struct SplashOutMaskedImageData
3401{
3402 ImageStream *imgStr;
3403 GfxImageColorMap *colorMap;
3404 SplashBitmap *mask;
3405 SplashColorPtr lookup;
3406 SplashColorMode colorMode;
3407 int width, height, y;
3408};
3409
3410bool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine)
3411{
3412 SplashOutMaskedImageData *imgData = (SplashOutMaskedImageData *)data;
3413 unsigned char *p, *aq;
3414 SplashColorPtr q, col;
3415 GfxRGB rgb;
3416 GfxGray gray;
3417 GfxCMYK cmyk;
3418 GfxColor deviceN;
3419 unsigned char alpha;
3420 unsigned char *maskPtr;
3421 int maskBit;
3422 int nComps, x;
3423
3424 if (imgData->y == imgData->height) {
3425 return false;
3426 }
3427 if (!(p = imgData->imgStr->getLine())) {
3428 return false;
3429 }
3430
3431 nComps = imgData->colorMap->getNumPixelComps();
3432
3433 maskPtr = imgData->mask->getDataPtr() + imgData->y * imgData->mask->getRowSize();
3434 maskBit = 0x80;
3435 for (x = 0, q = colorLine, aq = alphaLine; x < imgData->width; ++x, p += nComps) {
3436 alpha = (*maskPtr & maskBit) ? 0xff : 0x00;
3437 if (!(maskBit >>= 1)) {
3438 ++maskPtr;
3439 maskBit = 0x80;
3440 }
3441 if (imgData->lookup) {
3442 switch (imgData->colorMode) {
3443 case splashModeMono1:
3444 case splashModeMono8:
3445 *q++ = imgData->lookup[*p];
3446 break;
3447 case splashModeRGB8:
3448 case splashModeBGR8:
3449 col = &imgData->lookup[3 * *p];
3450 *q++ = col[0];
3451 *q++ = col[1];
3452 *q++ = col[2];
3453 break;
3454 case splashModeXBGR8:
3455 col = &imgData->lookup[4 * *p];
3456 *q++ = col[0];
3457 *q++ = col[1];
3458 *q++ = col[2];
3459 *q++ = 255;
3460 break;
3461 case splashModeCMYK8:
3462 col = &imgData->lookup[4 * *p];
3463 *q++ = col[0];
3464 *q++ = col[1];
3465 *q++ = col[2];
3466 *q++ = col[3];
3467 break;
3468 case splashModeDeviceN8:
3469 col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p];
3470 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3471 *q++ = col[cp];
3472 }
3473 break;
3474 }
3475 *aq++ = alpha;
3476 } else {
3477 switch (imgData->colorMode) {
3478 case splashModeMono1:
3479 case splashModeMono8:
3480 imgData->colorMap->getGray(x: p, gray: &gray);
3481 *q++ = colToByte(x: gray);
3482 break;
3483 case splashModeXBGR8:
3484 case splashModeRGB8:
3485 case splashModeBGR8:
3486 imgData->colorMap->getRGB(x: p, rgb: &rgb);
3487 *q++ = colToByte(x: rgb.r);
3488 *q++ = colToByte(x: rgb.g);
3489 *q++ = colToByte(x: rgb.b);
3490 if (imgData->colorMode == splashModeXBGR8) {
3491 *q++ = 255;
3492 }
3493 break;
3494 case splashModeCMYK8:
3495 imgData->colorMap->getCMYK(x: p, cmyk: &cmyk);
3496 *q++ = colToByte(x: cmyk.c);
3497 *q++ = colToByte(x: cmyk.m);
3498 *q++ = colToByte(x: cmyk.y);
3499 *q++ = colToByte(x: cmyk.k);
3500 break;
3501 case splashModeDeviceN8:
3502 imgData->colorMap->getDeviceN(x: p, deviceN: &deviceN);
3503 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3504 *q++ = colToByte(x: deviceN.c[cp]);
3505 }
3506 break;
3507 }
3508 *aq++ = alpha;
3509 }
3510 }
3511
3512 ++imgData->y;
3513 return true;
3514}
3515
3516void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate)
3517{
3518 GfxImageColorMap *maskColorMap;
3519 SplashCoord mat[6];
3520 SplashOutMaskedImageData imgData;
3521 SplashOutImageMaskData imgMaskData;
3522 SplashColorMode srcMode;
3523 SplashBitmap *maskBitmap;
3524 Splash *maskSplash;
3525 SplashColor maskColor;
3526 GfxGray gray;
3527 GfxRGB rgb;
3528 GfxCMYK cmyk;
3529 GfxColor deviceN;
3530 unsigned char pix;
3531 int n, i;
3532
3533 colorMap->getColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
3534 setOverprintMask(colorSpace: colorMap->getColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: nullptr);
3535
3536 // If the mask is higher resolution than the image, use
3537 // drawSoftMaskedImage() instead.
3538 if (maskWidth > width || maskHeight > height) {
3539 Object maskDecode(new Array((xref) ? xref : doc->getXRef()));
3540 maskDecode.arrayAdd(elem: Object(maskInvert ? 0 : 1));
3541 maskDecode.arrayAdd(elem: Object(maskInvert ? 1 : 0));
3542 maskColorMap = new GfxImageColorMap(1, &maskDecode, new GfxDeviceGrayColorSpace());
3543 drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
3544 delete maskColorMap;
3545
3546 } else {
3547 //----- scale the mask image to the same size as the source image
3548
3549 mat[0] = (SplashCoord)width;
3550 mat[1] = 0;
3551 mat[2] = 0;
3552 mat[3] = (SplashCoord)height;
3553 mat[4] = 0;
3554 mat[5] = 0;
3555 imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
3556 imgMaskData.imgStr->reset();
3557 imgMaskData.invert = maskInvert ? false : true;
3558 imgMaskData.width = maskWidth;
3559 imgMaskData.height = maskHeight;
3560 imgMaskData.y = 0;
3561 maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, false);
3562 if (!maskBitmap->getDataPtr()) {
3563 delete maskBitmap;
3564 width = height = 1;
3565 maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, false);
3566 }
3567 maskSplash = new Splash(maskBitmap, false);
3568 maskColor[0] = 0;
3569 maskSplash->clear(color: maskColor);
3570 maskColor[0] = 0xff;
3571 maskSplash->setFillPattern(new SplashSolidColor(maskColor));
3572 maskSplash->fillImageMask(src: &imageMaskSrc, srcData: &imgMaskData, w: maskWidth, h: maskHeight, mat, glyphMode: false);
3573 delete imgMaskData.imgStr;
3574 maskStr->close();
3575 delete maskSplash;
3576
3577 //----- draw the source image
3578
3579 const double *ctm = state->getCTM();
3580 for (i = 0; i < 6; ++i) {
3581 if (!std::isfinite(x: ctm[i])) {
3582 delete maskBitmap;
3583 return;
3584 }
3585 }
3586 mat[0] = ctm[0];
3587 mat[1] = ctm[1];
3588 mat[2] = -ctm[2];
3589 mat[3] = -ctm[3];
3590 mat[4] = ctm[2] + ctm[4];
3591 mat[5] = ctm[3] + ctm[5];
3592
3593 imgData.imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
3594 imgData.imgStr->reset();
3595 imgData.colorMap = colorMap;
3596 imgData.mask = maskBitmap;
3597 imgData.colorMode = colorMode;
3598 imgData.width = width;
3599 imgData.height = height;
3600 imgData.y = 0;
3601
3602 // special case for one-channel (monochrome/gray/separation) images:
3603 // build a lookup table here
3604 imgData.lookup = nullptr;
3605 if (colorMap->getNumPixelComps() == 1) {
3606 n = 1 << colorMap->getBits();
3607 switch (colorMode) {
3608 case splashModeMono1:
3609 case splashModeMono8:
3610 imgData.lookup = (SplashColorPtr)gmalloc(size: n);
3611 for (i = 0; i < n; ++i) {
3612 pix = (unsigned char)i;
3613 colorMap->getGray(x: &pix, gray: &gray);
3614 imgData.lookup[i] = colToByte(x: gray);
3615 }
3616 break;
3617 case splashModeRGB8:
3618 case splashModeBGR8:
3619 imgData.lookup = (SplashColorPtr)gmallocn(count: n, size: 3);
3620 for (i = 0; i < n; ++i) {
3621 pix = (unsigned char)i;
3622 colorMap->getRGB(x: &pix, rgb: &rgb);
3623 imgData.lookup[3 * i] = colToByte(x: rgb.r);
3624 imgData.lookup[3 * i + 1] = colToByte(x: rgb.g);
3625 imgData.lookup[3 * i + 2] = colToByte(x: rgb.b);
3626 }
3627 break;
3628 case splashModeXBGR8:
3629 imgData.lookup = (SplashColorPtr)gmallocn(count: n, size: 4);
3630 for (i = 0; i < n; ++i) {
3631 pix = (unsigned char)i;
3632 colorMap->getRGB(x: &pix, rgb: &rgb);
3633 imgData.lookup[4 * i] = colToByte(x: rgb.r);
3634 imgData.lookup[4 * i + 1] = colToByte(x: rgb.g);
3635 imgData.lookup[4 * i + 2] = colToByte(x: rgb.b);
3636 imgData.lookup[4 * i + 3] = 255;
3637 }
3638 break;
3639 case splashModeCMYK8:
3640 imgData.lookup = (SplashColorPtr)gmallocn(count: n, size: 4);
3641 for (i = 0; i < n; ++i) {
3642 pix = (unsigned char)i;
3643 colorMap->getCMYK(x: &pix, cmyk: &cmyk);
3644 imgData.lookup[4 * i] = colToByte(x: cmyk.c);
3645 imgData.lookup[4 * i + 1] = colToByte(x: cmyk.m);
3646 imgData.lookup[4 * i + 2] = colToByte(x: cmyk.y);
3647 imgData.lookup[4 * i + 3] = colToByte(x: cmyk.k);
3648 }
3649 break;
3650 case splashModeDeviceN8:
3651 imgData.lookup = (SplashColorPtr)gmallocn(count: n, SPOT_NCOMPS + 4);
3652 for (i = 0; i < n; ++i) {
3653 pix = (unsigned char)i;
3654 colorMap->getDeviceN(x: &pix, deviceN: &deviceN);
3655 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3656 imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(x: deviceN.c[cp]);
3657 }
3658 }
3659 break;
3660 }
3661 }
3662
3663 if (colorMode == splashModeMono1) {
3664 srcMode = splashModeMono8;
3665 } else {
3666 srcMode = colorMode;
3667 }
3668 splash->drawImage(src: &maskedImageSrc, tf: nullptr, srcData: &imgData, srcMode, srcAlpha: true, w: width, h: height, mat, interpolate);
3669 delete maskBitmap;
3670 gfree(p: imgData.lookup);
3671 delete imgData.imgStr;
3672 str->close();
3673 }
3674}
3675
3676void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object * /* ref */, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap,
3677 bool maskInterpolate)
3678{
3679 SplashCoord mat[6];
3680 SplashOutImageData imgData;
3681 SplashOutImageData imgMaskData;
3682 SplashColorMode srcMode;
3683 SplashBitmap *maskBitmap;
3684 Splash *maskSplash;
3685 SplashColor maskColor;
3686 GfxGray gray;
3687 GfxRGB rgb;
3688 GfxCMYK cmyk;
3689 GfxColor deviceN;
3690 unsigned char pix;
3691
3692 colorMap->getColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
3693 setOverprintMask(colorSpace: colorMap->getColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: nullptr);
3694
3695 const double *ctm = state->getCTM();
3696 for (int i = 0; i < 6; ++i) {
3697 if (!std::isfinite(x: ctm[i])) {
3698 return;
3699 }
3700 }
3701 mat[0] = ctm[0];
3702 mat[1] = ctm[1];
3703 mat[2] = -ctm[2];
3704 mat[3] = -ctm[3];
3705 mat[4] = ctm[2] + ctm[4];
3706 mat[5] = ctm[3] + ctm[5];
3707
3708 //----- set up the soft mask
3709
3710 if (maskColorMap->getMatteColor() != nullptr) {
3711 int maskChars;
3712 if (checkedMultiply(x: maskWidth, y: maskHeight, z: &maskChars)) {
3713 return;
3714 }
3715 unsigned char *data = (unsigned char *)gmalloc(size: maskChars);
3716 maskStr->reset();
3717 const int readChars = maskStr->doGetChars(nChars: maskChars, buffer: data);
3718 if (unlikely(readChars < maskChars)) {
3719 memset(s: &data[readChars], c: 0, n: maskChars - readChars);
3720 }
3721 maskStr->close();
3722 maskStr = new AutoFreeMemStream((char *)data, 0, maskChars, maskStr->getDictObject()->copy());
3723 }
3724 imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits());
3725 imgMaskData.imgStr->reset();
3726 imgMaskData.colorMap = maskColorMap;
3727 imgMaskData.maskColors = nullptr;
3728 imgMaskData.colorMode = splashModeMono8;
3729 imgMaskData.width = maskWidth;
3730 imgMaskData.height = maskHeight;
3731 imgMaskData.y = 0;
3732 imgMaskData.maskStr = nullptr;
3733 imgMaskData.maskColorMap = nullptr;
3734 const unsigned imgMaskDataLookupSize = 1 << maskColorMap->getBits();
3735 imgMaskData.lookup = (SplashColorPtr)gmalloc(size: imgMaskDataLookupSize);
3736 for (unsigned i = 0; i < imgMaskDataLookupSize; ++i) {
3737 pix = (unsigned char)i;
3738 maskColorMap->getGray(x: &pix, gray: &gray);
3739 imgMaskData.lookup[i] = colToByte(x: gray);
3740 }
3741 maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false);
3742 maskSplash = new Splash(maskBitmap, vectorAntialias);
3743 maskColor[0] = 0;
3744 maskSplash->clear(color: maskColor);
3745 maskSplash->drawImage(src: &imageSrc, tf: nullptr, srcData: &imgMaskData, srcMode: splashModeMono8, srcAlpha: false, w: maskWidth, h: maskHeight, mat, interpolate: maskInterpolate);
3746 delete imgMaskData.imgStr;
3747 if (maskColorMap->getMatteColor() == nullptr) {
3748 maskStr->close();
3749 }
3750 gfree(p: imgMaskData.lookup);
3751 delete maskSplash;
3752 splash->setSoftMask(maskBitmap);
3753
3754 //----- draw the source image
3755
3756 imgData.imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits());
3757 imgData.imgStr->reset();
3758 imgData.colorMap = colorMap;
3759 imgData.maskColors = nullptr;
3760 imgData.colorMode = colorMode;
3761 imgData.width = width;
3762 imgData.height = height;
3763 imgData.maskStr = nullptr;
3764 imgData.maskColorMap = nullptr;
3765 if (maskColorMap->getMatteColor() != nullptr) {
3766 getMatteColor(colorMode, colorMap, matteColorIn: maskColorMap->getMatteColor(), matteColor: imgData.matteColor);
3767 imgData.maskColorMap = maskColorMap;
3768 imgData.maskStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits());
3769 imgData.maskStr->reset();
3770 }
3771 imgData.y = 0;
3772
3773 // special case for one-channel (monochrome/gray/separation) images:
3774 // build a lookup table here
3775 imgData.lookup = nullptr;
3776 if (colorMap->getNumPixelComps() == 1) {
3777 const unsigned n = 1 << colorMap->getBits();
3778 switch (colorMode) {
3779 case splashModeMono1:
3780 case splashModeMono8:
3781 imgData.lookup = (SplashColorPtr)gmalloc(size: n);
3782 for (unsigned i = 0; i < n; ++i) {
3783 pix = (unsigned char)i;
3784 colorMap->getGray(x: &pix, gray: &gray);
3785 imgData.lookup[i] = colToByte(x: gray);
3786 }
3787 break;
3788 case splashModeRGB8:
3789 case splashModeBGR8:
3790 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 3);
3791 if (likely(imgData.lookup != nullptr)) {
3792 for (unsigned i = 0; i < n; ++i) {
3793 pix = (unsigned char)i;
3794 colorMap->getRGB(x: &pix, rgb: &rgb);
3795 imgData.lookup[3 * i] = colToByte(x: rgb.r);
3796 imgData.lookup[3 * i + 1] = colToByte(x: rgb.g);
3797 imgData.lookup[3 * i + 2] = colToByte(x: rgb.b);
3798 }
3799 }
3800 break;
3801 case splashModeXBGR8:
3802 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 4);
3803 if (likely(imgData.lookup != nullptr)) {
3804 for (unsigned i = 0; i < n; ++i) {
3805 pix = (unsigned char)i;
3806 colorMap->getRGB(x: &pix, rgb: &rgb);
3807 imgData.lookup[4 * i] = colToByte(x: rgb.r);
3808 imgData.lookup[4 * i + 1] = colToByte(x: rgb.g);
3809 imgData.lookup[4 * i + 2] = colToByte(x: rgb.b);
3810 imgData.lookup[4 * i + 3] = 255;
3811 }
3812 }
3813 break;
3814 case splashModeCMYK8:
3815 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, size: 4);
3816 if (likely(imgData.lookup != nullptr)) {
3817 for (unsigned i = 0; i < n; ++i) {
3818 pix = (unsigned char)i;
3819 colorMap->getCMYK(x: &pix, cmyk: &cmyk);
3820 imgData.lookup[4 * i] = colToByte(x: cmyk.c);
3821 imgData.lookup[4 * i + 1] = colToByte(x: cmyk.m);
3822 imgData.lookup[4 * i + 2] = colToByte(x: cmyk.y);
3823 imgData.lookup[4 * i + 3] = colToByte(x: cmyk.k);
3824 }
3825 }
3826 break;
3827 case splashModeDeviceN8:
3828 imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(count: n, SPOT_NCOMPS + 4);
3829 if (likely(imgData.lookup != nullptr)) {
3830 for (unsigned i = 0; i < n; ++i) {
3831 pix = (unsigned char)i;
3832 colorMap->getDeviceN(x: &pix, deviceN: &deviceN);
3833 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
3834 imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(x: deviceN.c[cp]);
3835 }
3836 }
3837 }
3838 break;
3839 }
3840 }
3841
3842 if (colorMode == splashModeMono1) {
3843 srcMode = splashModeMono8;
3844 } else {
3845 srcMode = colorMode;
3846 }
3847 splash->drawImage(src: &imageSrc, tf: nullptr, srcData: &imgData, srcMode, srcAlpha: false, w: width, h: height, mat, interpolate);
3848 splash->setSoftMask(nullptr);
3849 gfree(p: imgData.lookup);
3850 delete imgData.maskStr;
3851 delete imgData.imgStr;
3852 if (maskColorMap->getMatteColor() != nullptr) {
3853 maskStr->close();
3854 delete maskStr;
3855 }
3856 str->close();
3857}
3858
3859bool SplashOutputDev::checkTransparencyGroup(GfxState *state, bool knockout)
3860{
3861 if (state->getFillOpacity() != 1 || state->getStrokeOpacity() != 1 || state->getAlphaIsShape() || state->getBlendMode() != gfxBlendNormal || splash->getSoftMask() != nullptr || knockout) {
3862 return true;
3863 }
3864 return transpGroupStack != nullptr && transpGroupStack->shape != nullptr;
3865}
3866
3867void SplashOutputDev::beginTransparencyGroup(GfxState *state, const double *bbox, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, bool forSoftMask)
3868{
3869 SplashTransparencyGroup *transpGroup;
3870 SplashColor color;
3871 double xMin, yMin, xMax, yMax, x, y;
3872 int tx, ty, w, h;
3873
3874 // transform the bbox
3875 state->transform(x1: bbox[0], y1: bbox[1], x2: &x, y2: &y);
3876 xMin = xMax = x;
3877 yMin = yMax = y;
3878 state->transform(x1: bbox[0], y1: bbox[3], x2: &x, y2: &y);
3879 if (x < xMin) {
3880 xMin = x;
3881 } else if (x > xMax) {
3882 xMax = x;
3883 }
3884 if (y < yMin) {
3885 yMin = y;
3886 } else if (y > yMax) {
3887 yMax = y;
3888 }
3889 state->transform(x1: bbox[2], y1: bbox[1], x2: &x, y2: &y);
3890 if (x < xMin) {
3891 xMin = x;
3892 } else if (x > xMax) {
3893 xMax = x;
3894 }
3895 if (y < yMin) {
3896 yMin = y;
3897 } else if (y > yMax) {
3898 yMax = y;
3899 }
3900 state->transform(x1: bbox[2], y1: bbox[3], x2: &x, y2: &y);
3901 if (x < xMin) {
3902 xMin = x;
3903 } else if (x > xMax) {
3904 xMax = x;
3905 }
3906 if (y < yMin) {
3907 yMin = y;
3908 } else if (y > yMax) {
3909 yMax = y;
3910 }
3911 tx = (int)floor(x: xMin);
3912 if (tx < 0) {
3913 tx = 0;
3914 } else if (tx >= bitmap->getWidth()) {
3915 tx = bitmap->getWidth() - 1;
3916 }
3917 ty = (int)floor(x: yMin);
3918 if (ty < 0) {
3919 ty = 0;
3920 } else if (ty >= bitmap->getHeight()) {
3921 ty = bitmap->getHeight() - 1;
3922 }
3923 w = (int)ceil(x: xMax) - tx + 1;
3924 if (tx + w > bitmap->getWidth()) {
3925 w = bitmap->getWidth() - tx;
3926 }
3927 if (w < 1) {
3928 w = 1;
3929 }
3930 h = (int)ceil(x: yMax) - ty + 1;
3931 if (ty + h > bitmap->getHeight()) {
3932 h = bitmap->getHeight() - ty;
3933 }
3934 if (h < 1) {
3935 h = 1;
3936 }
3937
3938 // push a new stack entry
3939 transpGroup = new SplashTransparencyGroup();
3940 transpGroup->softmask = nullptr;
3941 transpGroup->tx = tx;
3942 transpGroup->ty = ty;
3943 transpGroup->blendingColorSpace = blendingColorSpace;
3944 transpGroup->isolated = isolated;
3945 transpGroup->shape = (knockout && !isolated) ? SplashBitmap::copy(src: bitmap) : nullptr;
3946 transpGroup->knockout = (knockout && isolated);
3947 transpGroup->knockoutOpacity = 1.0;
3948 transpGroup->next = transpGroupStack;
3949 transpGroupStack = transpGroup;
3950
3951 // save state
3952 transpGroup->origBitmap = bitmap;
3953 transpGroup->origSplash = splash;
3954 transpGroup->fontAA = fontEngine->getAA();
3955
3956 //~ this handles the blendingColorSpace arg for soft masks, but
3957 //~ not yet for transparency groups
3958
3959 // switch to the blending color space
3960 if (forSoftMask && isolated && blendingColorSpace) {
3961 if (blendingColorSpace->getMode() == csDeviceGray || blendingColorSpace->getMode() == csCalGray || (blendingColorSpace->getMode() == csICCBased && blendingColorSpace->getNComps() == 1)) {
3962 colorMode = splashModeMono8;
3963 } else if (blendingColorSpace->getMode() == csDeviceRGB || blendingColorSpace->getMode() == csCalRGB || (blendingColorSpace->getMode() == csICCBased && blendingColorSpace->getNComps() == 3)) {
3964 //~ does this need to use BGR8?
3965 colorMode = splashModeRGB8;
3966 } else if (blendingColorSpace->getMode() == csDeviceCMYK || (blendingColorSpace->getMode() == csICCBased && blendingColorSpace->getNComps() == 4)) {
3967 colorMode = splashModeCMYK8;
3968 }
3969 }
3970
3971 // create the temporary bitmap
3972 bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, true, bitmapTopDown, bitmap->getSeparationList());
3973 if (!bitmap->getDataPtr()) {
3974 delete bitmap;
3975 w = h = 1;
3976 bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, true, bitmapTopDown);
3977 }
3978 splash = new Splash(bitmap, vectorAntialias, transpGroup->origSplash->getScreen());
3979 if (transpGroup->next != nullptr && transpGroup->next->knockout) {
3980 fontEngine->setAA(false);
3981 }
3982 splash->setThinLineMode(transpGroup->origSplash->getThinLineMode());
3983 splash->setMinLineWidth(s_minLineWidth);
3984 //~ Acrobat apparently copies at least the fill and stroke colors, and
3985 //~ maybe other state(?) -- but not the clipping path (and not sure
3986 //~ what else)
3987 //~ [this is likely the same situation as in type3D1()]
3988 splash->setFillPattern(transpGroup->origSplash->getFillPattern()->copy());
3989 splash->setStrokePattern(transpGroup->origSplash->getStrokePattern()->copy());
3990 if (isolated) {
3991 splashClearColor(dest: color);
3992 if (colorMode == splashModeXBGR8) {
3993 color[3] = 255;
3994 }
3995 splash->clear(color, alpha: 0);
3996 } else {
3997 SplashBitmap *shape = (knockout) ? transpGroup->shape : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->shape : transpGroup->origBitmap;
3998 int shapeTx = (knockout) ? tx : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->tx + tx : tx;
3999 int shapeTy = (knockout) ? ty : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->ty + ty : ty;
4000 splash->blitTransparent(src: transpGroup->origBitmap, xSrc: tx, ySrc: ty, xDest: 0, yDest: 0, w, h);
4001 splash->setInNonIsolatedGroup(alpha0BitmapA: shape, alpha0XA: shapeTx, alpha0YA: shapeTy);
4002 }
4003 transpGroup->tBitmap = bitmap;
4004 state->shiftCTMAndClip(tx: -tx, ty: -ty);
4005 updateCTM(state, m11: 0, m12: 0, m21: 0, m22: 0, m31: 0, m32: 0);
4006}
4007
4008void SplashOutputDev::endTransparencyGroup(GfxState *state)
4009{
4010 // restore state
4011 delete splash;
4012 bitmap = transpGroupStack->origBitmap;
4013 colorMode = bitmap->getMode();
4014 splash = transpGroupStack->origSplash;
4015 state->shiftCTMAndClip(tx: transpGroupStack->tx, ty: transpGroupStack->ty);
4016 updateCTM(state, m11: 0, m12: 0, m21: 0, m22: 0, m31: 0, m32: 0);
4017}
4018
4019void SplashOutputDev::paintTransparencyGroup(GfxState *state, const double *bbox)
4020{
4021 SplashBitmap *tBitmap;
4022 SplashTransparencyGroup *transpGroup;
4023 bool isolated;
4024 int tx, ty;
4025
4026 tx = transpGroupStack->tx;
4027 ty = transpGroupStack->ty;
4028 tBitmap = transpGroupStack->tBitmap;
4029 isolated = transpGroupStack->isolated;
4030
4031 // paint the transparency group onto the parent bitmap
4032 // - the clip path was set in the parent's state)
4033 if (tx < bitmap->getWidth() && ty < bitmap->getHeight()) {
4034 SplashCoord knockoutOpacity = (transpGroupStack->next != nullptr) ? transpGroupStack->next->knockoutOpacity : transpGroupStack->knockoutOpacity;
4035 splash->setOverprintMask(overprintMask: 0xffffffff, additive: false);
4036 splash->composite(src: tBitmap, xSrc: 0, ySrc: 0, xDest: tx, yDest: ty, w: tBitmap->getWidth(), h: tBitmap->getHeight(), noClip: false, nonIsolated: !isolated, knockout: transpGroupStack->next != nullptr && transpGroupStack->next->knockout, knockoutOpacity);
4037 fontEngine->setAA(transpGroupStack->fontAA);
4038 if (transpGroupStack->next != nullptr && transpGroupStack->next->shape != nullptr) {
4039 transpGroupStack->next->knockout = true;
4040 }
4041 }
4042
4043 // pop the stack
4044 transpGroup = transpGroupStack;
4045 transpGroupStack = transpGroup->next;
4046 if (transpGroupStack != nullptr && transpGroup->knockoutOpacity < transpGroupStack->knockoutOpacity) {
4047 transpGroupStack->knockoutOpacity = transpGroup->knockoutOpacity;
4048 }
4049 delete transpGroup->shape;
4050 delete transpGroup;
4051
4052 delete tBitmap;
4053}
4054
4055void SplashOutputDev::setSoftMask(GfxState *state, const double *bbox, bool alpha, Function *transferFunc, GfxColor *backdropColor)
4056{
4057 SplashBitmap *softMask, *tBitmap;
4058 Splash *tSplash;
4059 SplashTransparencyGroup *transpGroup;
4060 SplashColor color;
4061 SplashColorPtr p;
4062 GfxGray gray;
4063 GfxRGB rgb;
4064 GfxCMYK cmyk;
4065 GfxColor deviceN;
4066 double lum, lum2;
4067 int tx, ty, x, y;
4068
4069 tx = transpGroupStack->tx;
4070 ty = transpGroupStack->ty;
4071 tBitmap = transpGroupStack->tBitmap;
4072
4073 // composite with backdrop color
4074 if (!alpha && tBitmap->getMode() != splashModeMono1) {
4075 //~ need to correctly handle the case where no blending color
4076 //~ space is given
4077 if (transpGroupStack->blendingColorSpace) {
4078 tSplash = new Splash(tBitmap, vectorAntialias, transpGroupStack->origSplash->getScreen());
4079 switch (tBitmap->getMode()) {
4080 case splashModeMono1:
4081 // transparency is not supported in mono1 mode
4082 break;
4083 case splashModeMono8:
4084 transpGroupStack->blendingColorSpace->getGray(color: backdropColor, gray: &gray);
4085 color[0] = colToByte(x: gray);
4086 tSplash->compositeBackground(color);
4087 break;
4088 case splashModeXBGR8:
4089 color[3] = 255;
4090 // fallthrough
4091 case splashModeRGB8:
4092 case splashModeBGR8:
4093 transpGroupStack->blendingColorSpace->getRGB(color: backdropColor, rgb: &rgb);
4094 color[0] = colToByte(x: rgb.r);
4095 color[1] = colToByte(x: rgb.g);
4096 color[2] = colToByte(x: rgb.b);
4097 tSplash->compositeBackground(color);
4098 break;
4099 case splashModeCMYK8:
4100 transpGroupStack->blendingColorSpace->getCMYK(color: backdropColor, cmyk: &cmyk);
4101 color[0] = colToByte(x: cmyk.c);
4102 color[1] = colToByte(x: cmyk.m);
4103 color[2] = colToByte(x: cmyk.y);
4104 color[3] = colToByte(x: cmyk.k);
4105 tSplash->compositeBackground(color);
4106 break;
4107 case splashModeDeviceN8:
4108 transpGroupStack->blendingColorSpace->getDeviceN(color: backdropColor, deviceN: &deviceN);
4109 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) {
4110 color[cp] = colToByte(x: deviceN.c[cp]);
4111 }
4112 tSplash->compositeBackground(color);
4113 break;
4114 }
4115 delete tSplash;
4116 }
4117 }
4118
4119 softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false);
4120 unsigned char fill = 0;
4121 if (transpGroupStack->blendingColorSpace) {
4122 transpGroupStack->blendingColorSpace->getGray(color: backdropColor, gray: &gray);
4123 fill = colToByte(x: gray);
4124 }
4125 memset(s: softMask->getDataPtr(), c: fill, n: softMask->getRowSize() * softMask->getHeight());
4126 p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx;
4127 int xMax = tBitmap->getWidth();
4128 int yMax = tBitmap->getHeight();
4129 if (xMax > bitmap->getWidth() - tx) {
4130 xMax = bitmap->getWidth() - tx;
4131 }
4132 if (yMax > bitmap->getHeight() - ty) {
4133 yMax = bitmap->getHeight() - ty;
4134 }
4135 for (y = 0; y < yMax; ++y) {
4136 for (x = 0; x < xMax; ++x) {
4137 if (alpha) {
4138 if (transferFunc) {
4139 lum = tBitmap->getAlpha(x, y) / 255.0;
4140 transferFunc->transform(in: &lum, out: &lum2);
4141 p[x] = (int)(lum2 * 255.0 + 0.5);
4142 } else {
4143 p[x] = tBitmap->getAlpha(x, y);
4144 }
4145 } else {
4146 tBitmap->getPixel(x, y, pixel: color);
4147 // convert to luminosity
4148 switch (tBitmap->getMode()) {
4149 case splashModeMono1:
4150 case splashModeMono8:
4151 lum = color[0] / 255.0;
4152 break;
4153 case splashModeXBGR8:
4154 case splashModeRGB8:
4155 case splashModeBGR8:
4156 lum = (0.3 / 255.0) * color[0] + (0.59 / 255.0) * color[1] + (0.11 / 255.0) * color[2];
4157 break;
4158 case splashModeCMYK8:
4159 case splashModeDeviceN8:
4160 lum = (1 - color[3] / 255.0) - (0.3 / 255.0) * color[0] - (0.59 / 255.0) * color[1] - (0.11 / 255.0) * color[2];
4161 if (lum < 0) {
4162 lum = 0;
4163 }
4164 break;
4165 }
4166 if (transferFunc) {
4167 transferFunc->transform(in: &lum, out: &lum2);
4168 } else {
4169 lum2 = lum;
4170 }
4171 p[x] = (int)(lum2 * 255.0 + 0.5);
4172 }
4173 }
4174 p += softMask->getRowSize();
4175 }
4176 splash->setSoftMask(softMask);
4177
4178 // pop the stack
4179 transpGroup = transpGroupStack;
4180 transpGroupStack = transpGroup->next;
4181 delete transpGroup;
4182
4183 delete tBitmap;
4184}
4185
4186void SplashOutputDev::clearSoftMask(GfxState *state)
4187{
4188 splash->setSoftMask(nullptr);
4189}
4190
4191void SplashOutputDev::setPaperColor(SplashColorPtr paperColorA)
4192{
4193 splashColorCopy(dest: paperColor, src: paperColorA);
4194}
4195
4196int SplashOutputDev::getBitmapWidth()
4197{
4198 return bitmap->getWidth();
4199}
4200
4201int SplashOutputDev::getBitmapHeight()
4202{
4203 return bitmap->getHeight();
4204}
4205
4206SplashBitmap *SplashOutputDev::takeBitmap()
4207{
4208 SplashBitmap *ret;
4209
4210 ret = bitmap;
4211 bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown);
4212 return ret;
4213}
4214
4215#if 1 //~tmp: turn off anti-aliasing temporarily
4216bool SplashOutputDev::getVectorAntialias()
4217{
4218 return splash->getVectorAntialias();
4219}
4220
4221void SplashOutputDev::setVectorAntialias(bool vaa)
4222{
4223 vaa = vaa && colorMode != splashModeMono1;
4224 vectorAntialias = vaa;
4225 splash->setVectorAntialias(vaa);
4226}
4227#endif
4228
4229void SplashOutputDev::setFreeTypeHinting(bool enable, bool enableSlightHintingA)
4230{
4231 enableFreeTypeHinting = enable;
4232 enableSlightHinting = enableSlightHintingA;
4233}
4234
4235bool SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *catalog, GfxTilingPattern *tPat, const double *mat, int x0, int y0, int x1, int y1, double xStep, double yStep)
4236{
4237 PDFRectangle box;
4238 Splash *formerSplash = splash;
4239 SplashBitmap *formerBitmap = bitmap;
4240 double width, height;
4241 int surface_width, surface_height, result_width, result_height, i;
4242 int repeatX, repeatY;
4243 SplashCoord matc[6];
4244 Matrix m1;
4245 const double *ctm;
4246 double savedCTM[6];
4247 double kx, ky, sx, sy;
4248 bool retValue = false;
4249 const double *bbox = tPat->getBBox();
4250 const double *ptm = tPat->getMatrix();
4251 const int paintType = tPat->getPaintType();
4252 Dict *resDict = tPat->getResDict();
4253
4254 width = bbox[2] - bbox[0];
4255 height = bbox[3] - bbox[1];
4256
4257 if (xStep != width || yStep != height) {
4258 return false;
4259 }
4260
4261 // calculate offsets
4262 ctm = state->getCTM();
4263 for (i = 0; i < 6; ++i) {
4264 savedCTM[i] = ctm[i];
4265 }
4266 state->concatCTM(a: mat[0], b: mat[1], c: mat[2], d: mat[3], e: mat[4], f: mat[5]);
4267 state->concatCTM(a: 1, b: 0, c: 0, d: 1, e: bbox[0], f: bbox[1]);
4268 ctm = state->getCTM();
4269 for (i = 0; i < 6; ++i) {
4270 if (!std::isfinite(x: ctm[i])) {
4271 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4272 return false;
4273 }
4274 }
4275 matc[4] = x0 * xStep * ctm[0] + y0 * yStep * ctm[2] + ctm[4];
4276 matc[5] = x0 * xStep * ctm[1] + y0 * yStep * ctm[3] + ctm[5];
4277 if (splashAbs(x: ctm[1]) > splashAbs(x: ctm[0])) {
4278 kx = -ctm[1];
4279 ky = ctm[2] - (ctm[0] * ctm[3]) / ctm[1];
4280 } else {
4281 kx = ctm[0];
4282 ky = ctm[3] - (ctm[1] * ctm[2]) / ctm[0];
4283 }
4284 result_width = (int)ceil(x: fabs(x: kx * width * (x1 - x0)));
4285 result_height = (int)ceil(x: fabs(x: ky * height * (y1 - y0)));
4286 kx = state->getHDPI() / 72.0;
4287 ky = state->getVDPI() / 72.0;
4288 m1.m[0] = std::max(a: fabs(x: ptm[0]), b: fabs(x: ptm[2])) * kx;
4289 m1.m[1] = 0;
4290 m1.m[2] = 0;
4291 m1.m[3] = std::max(a: fabs(x: ptm[1]), b: fabs(x: ptm[3])) * ky;
4292 m1.m[4] = 0;
4293 m1.m[5] = 0;
4294 m1.transform(x: width, y: height, tx: &kx, ty: &ky);
4295 surface_width = (int)ceil(x: fabs(x: kx));
4296 surface_height = (int)ceil(x: fabs(x: ky));
4297
4298 sx = (double)result_width / (surface_width * (x1 - x0));
4299 sy = (double)result_height / (surface_height * (y1 - y0));
4300 m1.m[0] *= sx;
4301 m1.m[3] *= sy;
4302 m1.transform(x: width, y: height, tx: &kx, ty: &ky);
4303
4304 if (fabs(x: kx) < 1 && fabs(x: ky) < 1) {
4305 kx = std::min<double>(a: kx, b: ky);
4306 ky = 2 / kx;
4307 m1.m[0] *= ky;
4308 m1.m[3] *= ky;
4309 m1.transform(x: width, y: height, tx: &kx, ty: &ky);
4310 surface_width = (int)ceil(x: fabs(x: kx));
4311 surface_height = (int)ceil(x: fabs(x: ky));
4312 repeatX = x1 - x0;
4313 repeatY = y1 - y0;
4314 while ((unsigned long)repeatX * repeatY > 0x800000L) {
4315 // try to avoid bogus memory allocation size
4316 if (repeatX > 1) {
4317 repeatX /= 2;
4318 }
4319 if (repeatY > 1) {
4320 repeatY /= 2;
4321 }
4322 }
4323 } else {
4324 if ((unsigned long)surface_width * surface_height > 0x800000L) {
4325 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4326 return false;
4327 }
4328 while (fabs(x: kx) > 16384 || fabs(x: ky) > 16384) {
4329 // limit pattern bitmap size
4330 m1.m[0] /= 2;
4331 m1.m[3] /= 2;
4332 m1.transform(x: width, y: height, tx: &kx, ty: &ky);
4333 }
4334 surface_width = (int)ceil(x: fabs(x: kx));
4335 surface_height = (int)ceil(x: fabs(x: ky));
4336 // adjust repeat values to completely fill region
4337 if (unlikely(surface_width == 0 || surface_height == 0)) {
4338 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4339 return false;
4340 }
4341 repeatX = result_width / surface_width;
4342 repeatY = result_height / surface_height;
4343 if (surface_width * repeatX < result_width) {
4344 repeatX++;
4345 }
4346 if (surface_height * repeatY < result_height) {
4347 repeatY++;
4348 }
4349 if (x1 - x0 > repeatX) {
4350 repeatX = x1 - x0;
4351 }
4352 if (y1 - y0 > repeatY) {
4353 repeatY = y1 - y0;
4354 }
4355 }
4356 // restore CTM and calculate rotate and scale with rounded matrix
4357 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4358 state->concatCTM(a: mat[0], b: mat[1], c: mat[2], d: mat[3], e: mat[4], f: mat[5]);
4359 state->concatCTM(a: width * repeatX, b: 0, c: 0, d: height * repeatY, e: bbox[0], f: bbox[1]);
4360 ctm = state->getCTM();
4361 matc[0] = ctm[0];
4362 matc[1] = ctm[1];
4363 matc[2] = ctm[2];
4364 matc[3] = ctm[3];
4365
4366 if (surface_width == 0 || surface_height == 0 || repeatX * repeatY <= 4) {
4367 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4368 return false;
4369 }
4370 m1.transform(x: bbox[0], y: bbox[1], tx: &kx, ty: &ky);
4371 m1.m[4] = -kx;
4372 m1.m[5] = -ky;
4373
4374 box.x1 = bbox[0];
4375 box.y1 = bbox[1];
4376 box.x2 = bbox[2];
4377 box.y2 = bbox[3];
4378 std::unique_ptr<Gfx> gfx = std::make_unique<Gfx>(args&: doc, args: this, args&: resDict, args: &box, args: nullptr, args: nullptr, args: nullptr, args&: gfxA);
4379 // set pattern transformation matrix
4380 gfx->getState()->setCTM(a: m1.m[0], b: m1.m[1], c: m1.m[2], d: m1.m[3], e: m1.m[4], f: m1.m[5]);
4381 if (splashAbs(x: matc[1]) > splashAbs(x: matc[0])) {
4382 kx = -matc[1];
4383 ky = matc[2] - (matc[0] * matc[3]) / matc[1];
4384 } else {
4385 kx = matc[0];
4386 ky = matc[3] - (matc[1] * matc[2]) / matc[0];
4387 }
4388 result_width = surface_width * repeatX;
4389 result_height = surface_height * repeatY;
4390 kx = result_width / (fabs(x: kx) + 1);
4391 ky = result_height / (fabs(x: ky) + 1);
4392 state->concatCTM(a: kx, b: 0, c: 0, d: ky, e: 0, f: 0);
4393 ctm = state->getCTM();
4394 matc[0] = ctm[0];
4395 matc[1] = ctm[1];
4396 matc[2] = ctm[2];
4397 matc[3] = ctm[3];
4398
4399 const bool doFastBlit = matc[0] > 0 && matc[1] == 0 && matc[2] == 0 && matc[3] > 0;
4400 bitmap = new SplashBitmap(surface_width, surface_height, 1, (paintType == 1 || doFastBlit) ? colorMode : splashModeMono8, true);
4401 if (bitmap->getDataPtr() == nullptr) {
4402 SplashBitmap *tBitmap = bitmap;
4403 bitmap = formerBitmap;
4404 delete tBitmap;
4405 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4406 return false;
4407 }
4408 splash = new Splash(bitmap, true);
4409 updateCTM(state: gfx->getState(), m11: m1.m[0], m12: m1.m[1], m21: m1.m[2], m22: m1.m[3], m31: m1.m[4], m32: m1.m[5]);
4410
4411 if (paintType == 2) {
4412 SplashColor clearColor;
4413 clearColor[0] = (colorMode == splashModeCMYK8 || colorMode == splashModeDeviceN8) ? 0x00 : 0xFF;
4414 splash->clear(color: clearColor, alpha: 0);
4415 } else {
4416 splash->clear(color: paperColor, alpha: 0);
4417 }
4418 splash->setThinLineMode(formerSplash->getThinLineMode());
4419 splash->setMinLineWidth(s_minLineWidth);
4420 if (doFastBlit) {
4421 // drawImage would colorize the greyscale pattern in tilingBitmapSrc buffer accessor while tiling.
4422 // blitImage can't, it has no buffer accessor. We instead colorize the pattern prototype in advance.
4423 splash->setFillPattern(formerSplash->getFillPattern()->copy());
4424 splash->setStrokePattern(formerSplash->getStrokePattern()->copy());
4425 }
4426 gfx->display(obj: tPat->getContentStream());
4427 delete splash;
4428 splash = formerSplash;
4429
4430 TilingSplashOutBitmap imgData;
4431 imgData.bitmap = bitmap;
4432 imgData.paintType = paintType;
4433 imgData.pattern = splash->getFillPattern();
4434 imgData.colorMode = colorMode;
4435 imgData.y = 0;
4436 imgData.repeatX = repeatX;
4437 imgData.repeatY = repeatY;
4438 SplashBitmap *tBitmap = bitmap;
4439 bitmap = formerBitmap;
4440 if (doFastBlit) {
4441 // draw the tiles
4442 for (int y = 0; y < imgData.repeatY; ++y) {
4443 for (int x = 0; x < imgData.repeatX; ++x) {
4444 x0 = splashFloor(x: matc[4]) + x * tBitmap->getWidth();
4445 y0 = splashFloor(x: matc[5]) + y * tBitmap->getHeight();
4446 splash->blitImage(src: tBitmap, srcAlpha: true, xDest: x0, yDest: y0);
4447 }
4448 }
4449 retValue = true;
4450 } else {
4451 retValue = splash->drawImage(src: &tilingBitmapSrc, tf: nullptr, srcData: &imgData, srcMode: colorMode, srcAlpha: true, w: result_width, h: result_height, mat: matc, interpolate: false, tilingPattern: true) == splashOk;
4452 }
4453 delete tBitmap;
4454 if (!retValue) {
4455 state->setCTM(a: savedCTM[0], b: savedCTM[1], c: savedCTM[2], d: savedCTM[3], e: savedCTM[4], f: savedCTM[5]);
4456 }
4457 return retValue;
4458}
4459
4460bool SplashOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading)
4461{
4462 GfxColorSpaceMode shadingMode = shading->getColorSpace()->getMode();
4463 bool bDirectColorTranslation = false; // triggers an optimization.
4464 switch (colorMode) {
4465 case splashModeRGB8:
4466 bDirectColorTranslation = (shadingMode == csDeviceRGB);
4467 break;
4468 case splashModeCMYK8:
4469 case splashModeDeviceN8:
4470 bDirectColorTranslation = (shadingMode == csDeviceCMYK);
4471 break;
4472 default:
4473 break;
4474 }
4475 // restore vector antialias because we support it here
4476 SplashGouraudPattern splashShading(bDirectColorTranslation, state, shading);
4477 const bool vaa = getVectorAntialias();
4478 setVectorAntialias(true);
4479 const bool retVal = splash->gouraudTriangleShadedFill(shading: &splashShading);
4480 setVectorAntialias(vaa);
4481 return retVal;
4482}
4483
4484bool SplashOutputDev::univariateShadedFill(GfxState *state, SplashUnivariatePattern *pattern, double tMin, double tMax)
4485{
4486 double xMin, yMin, xMax, yMax;
4487 bool vaa = getVectorAntialias();
4488 // restore vector antialias because we support it here
4489 setVectorAntialias(true);
4490
4491 bool retVal = false;
4492 // get the clip region bbox
4493 if (pattern->getShading()->getHasBBox()) {
4494 pattern->getShading()->getBBox(xMinA: &xMin, yMinA: &yMin, xMaxA: &xMax, yMaxA: &yMax);
4495 } else {
4496 state->getClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
4497
4498 xMin = floor(x: xMin);
4499 yMin = floor(x: yMin);
4500 xMax = ceil(x: xMax);
4501 yMax = ceil(x: yMax);
4502
4503 {
4504 Matrix ctm, ictm;
4505 double x[4], y[4];
4506 int i;
4507
4508 state->getCTM(m: &ctm);
4509 ctm.invertTo(other: &ictm);
4510
4511 ictm.transform(x: xMin, y: yMin, tx: &x[0], ty: &y[0]);
4512 ictm.transform(x: xMax, y: yMin, tx: &x[1], ty: &y[1]);
4513 ictm.transform(x: xMin, y: yMax, tx: &x[2], ty: &y[2]);
4514 ictm.transform(x: xMax, y: yMax, tx: &x[3], ty: &y[3]);
4515
4516 xMin = xMax = x[0];
4517 yMin = yMax = y[0];
4518 for (i = 1; i < 4; i++) {
4519 xMin = std::min<double>(a: xMin, b: x[i]);
4520 yMin = std::min<double>(a: yMin, b: y[i]);
4521 xMax = std::max<double>(a: xMax, b: x[i]);
4522 yMax = std::max<double>(a: yMax, b: y[i]);
4523 }
4524 }
4525 }
4526
4527 // fill the region
4528 state->moveTo(x: xMin, y: yMin);
4529 state->lineTo(x: xMax, y: yMin);
4530 state->lineTo(x: xMax, y: yMax);
4531 state->lineTo(x: xMin, y: yMax);
4532 state->closePath();
4533 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
4534
4535 pattern->getShading()->getColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
4536 setOverprintMask(colorSpace: pattern->getShading()->getColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: nullptr);
4537 // If state->getStrokePattern() is set, then the current clipping region
4538 // is a stroke path.
4539 retVal = (splash->shadedFill(path: &path, hasBBox: pattern->getShading()->getHasBBox(), pattern, clipToStrokePath: (state->getStrokePattern() != nullptr)) == splashOk);
4540 state->clearPath();
4541 setVectorAntialias(vaa);
4542
4543 return retVal;
4544}
4545
4546bool SplashOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading)
4547{
4548 SplashFunctionPattern *pattern = new SplashFunctionPattern(colorMode, state, shading);
4549 double xMin, yMin, xMax, yMax;
4550 bool vaa = getVectorAntialias();
4551 // restore vector antialias because we support it here
4552 setVectorAntialias(true);
4553
4554 bool retVal = false;
4555 // get the clip region bbox
4556 if (pattern->getShading()->getHasBBox()) {
4557 pattern->getShading()->getBBox(xMinA: &xMin, yMinA: &yMin, xMaxA: &xMax, yMaxA: &yMax);
4558 } else {
4559 state->getClipBBox(xMin: &xMin, yMin: &yMin, xMax: &xMax, yMax: &yMax);
4560
4561 xMin = floor(x: xMin);
4562 yMin = floor(x: yMin);
4563 xMax = ceil(x: xMax);
4564 yMax = ceil(x: yMax);
4565
4566 {
4567 Matrix ctm, ictm;
4568 double x[4], y[4];
4569 int i;
4570
4571 state->getCTM(m: &ctm);
4572 ctm.invertTo(other: &ictm);
4573
4574 ictm.transform(x: xMin, y: yMin, tx: &x[0], ty: &y[0]);
4575 ictm.transform(x: xMax, y: yMin, tx: &x[1], ty: &y[1]);
4576 ictm.transform(x: xMin, y: yMax, tx: &x[2], ty: &y[2]);
4577 ictm.transform(x: xMax, y: yMax, tx: &x[3], ty: &y[3]);
4578
4579 xMin = xMax = x[0];
4580 yMin = yMax = y[0];
4581 for (i = 1; i < 4; i++) {
4582 xMin = std::min<double>(a: xMin, b: x[i]);
4583 yMin = std::min<double>(a: yMin, b: y[i]);
4584 xMax = std::max<double>(a: xMax, b: x[i]);
4585 yMax = std::max<double>(a: yMax, b: y[i]);
4586 }
4587 }
4588 }
4589
4590 // fill the region
4591 state->moveTo(x: xMin, y: yMin);
4592 state->lineTo(x: xMax, y: yMin);
4593 state->lineTo(x: xMax, y: yMax);
4594 state->lineTo(x: xMin, y: yMax);
4595 state->closePath();
4596 SplashPath path = convertPath(state, path: state->getPath(), dropEmptySubpaths: true);
4597
4598 pattern->getShading()->getColorSpace()->createMapping(separationList: bitmap->getSeparationList(), SPOT_NCOMPS);
4599 setOverprintMask(colorSpace: pattern->getShading()->getColorSpace(), overprintFlag: state->getFillOverprint(), overprintMode: state->getOverprintMode(), singleColor: nullptr);
4600 // If state->getStrokePattern() is set, then the current clipping region
4601 // is a stroke path.
4602 retVal = (splash->shadedFill(path: &path, hasBBox: pattern->getShading()->getHasBBox(), pattern, clipToStrokePath: (state->getStrokePattern() != nullptr)) == splashOk);
4603 state->clearPath();
4604 setVectorAntialias(vaa);
4605
4606 delete pattern;
4607
4608 return retVal;
4609}
4610
4611bool SplashOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax)
4612{
4613 SplashAxialPattern *pattern = new SplashAxialPattern(colorMode, state, shading);
4614 bool retVal = univariateShadedFill(state, pattern, tMin, tMax);
4615
4616 delete pattern;
4617
4618 return retVal;
4619}
4620
4621bool SplashOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double tMin, double tMax)
4622{
4623 SplashRadialPattern *pattern = new SplashRadialPattern(colorMode, state, shading);
4624 bool retVal = univariateShadedFill(state, pattern, tMin, tMax);
4625
4626 delete pattern;
4627
4628 return retVal;
4629}
4630

source code of poppler/poppler/SplashOutputDev.cc