1//========================================================================
2//
3// GfxState.cc
4//
5// Copyright 1996-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 Kristian Høgsberg <krh@redhat.com>
17// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
18// Copyright (C) 2006, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
19// Copyright (C) 2006-2022 Albert Astals Cid <aacid@kde.org>
20// Copyright (C) 2009, 2012 Koji Otani <sho@bbr.jp>
21// Copyright (C) 2009, 2011-2016, 2020, 2023 Thomas Freitag <Thomas.Freitag@alfa.de>
22// Copyright (C) 2009, 2019 Christian Persch <chpe@gnome.org>
23// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
24// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
25// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
26// Copyright (C) 2012, 2020 William Bader <williambader@hotmail.com>
27// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
28// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
29// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
30// Copyright (C) 2015, 2020 Adrian Johnson <ajohnson@redneon.com>
31// Copyright (C) 2016 Marek Kasik <mkasik@redhat.com>
32// Copyright (C) 2017, 2019, 2022 Oliver Sander <oliver.sander@tu-dresden.de>
33// 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
34// Copyright (C) 2018 Volker Krause <vkrause@kde.org>
35// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
36// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
37// Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com>
38// Copyright (C) 2020 Lluís Batlle i Rossell <viric@viric.name>
39//
40// To see a description of the changes please see the Changelog file that
41// came with your tarball or type make ChangeLog if you are building from git
42//
43//========================================================================
44
45#include <config.h>
46
47#include <algorithm>
48#include <memory>
49#include <cstddef>
50#include <cmath>
51#include <cstring>
52#include "goo/gfile.h"
53#include "goo/gmem.h"
54#include "Error.h"
55#include "Object.h"
56#include "Array.h"
57#include "Page.h"
58#include "Gfx.h"
59#include "GfxState.h"
60#include "GfxState_helpers.h"
61#include "GfxFont.h"
62#include "GlobalParams.h"
63#include "PopplerCache.h"
64#include "OutputDev.h"
65#include "splash/SplashTypes.h"
66
67//------------------------------------------------------------------------
68
69// Max depth of nested color spaces. This is used to catch infinite
70// loops in the color space object structure.
71#define colorSpaceRecursionLimit 8
72
73//------------------------------------------------------------------------
74
75bool Matrix::invertTo(Matrix *other) const
76{
77 const double det_denominator = determinant();
78 if (unlikely(det_denominator == 0)) {
79 *other = { 1, 0, 0, 1, 0, 0 };
80 return false;
81 }
82
83 const double det = 1 / det_denominator;
84 other->m[0] = m[3] * det;
85 other->m[1] = -m[1] * det;
86 other->m[2] = -m[2] * det;
87 other->m[3] = m[0] * det;
88 other->m[4] = (m[2] * m[5] - m[3] * m[4]) * det;
89 other->m[5] = (m[1] * m[4] - m[0] * m[5]) * det;
90
91 return true;
92}
93
94void Matrix::translate(double tx, double ty)
95{
96 double x0 = tx * m[0] + ty * m[2] + m[4];
97 double y0 = tx * m[1] + ty * m[3] + m[5];
98 m[4] = x0;
99 m[5] = y0;
100}
101
102void Matrix::scale(double sx, double sy)
103{
104 m[0] *= sx;
105 m[1] *= sx;
106 m[2] *= sy;
107 m[3] *= sy;
108}
109
110void Matrix::transform(double x, double y, double *tx, double *ty) const
111{
112 double temp_x, temp_y;
113
114 temp_x = m[0] * x + m[2] * y + m[4];
115 temp_y = m[1] * x + m[3] * y + m[5];
116
117 *tx = temp_x;
118 *ty = temp_y;
119}
120
121// Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis
122double Matrix::norm() const
123{
124 double f, g, h, i, j;
125
126 i = m[0] * m[0] + m[1] * m[1];
127 j = m[2] * m[2] + m[3] * m[3];
128
129 f = 0.5 * (i + j);
130 g = 0.5 * (i - j);
131 h = m[0] * m[2] + m[1] * m[3];
132
133 return sqrt(x: f + hypot(x: g, y: h));
134}
135
136//------------------------------------------------------------------------
137
138struct GfxBlendModeInfo
139{
140 const char *name;
141 GfxBlendMode mode;
142};
143
144static const GfxBlendModeInfo gfxBlendModeNames[] = { { .name: "Normal", .mode: gfxBlendNormal }, { .name: "Compatible", .mode: gfxBlendNormal },
145 { .name: "Multiply", .mode: gfxBlendMultiply }, { .name: "Screen", .mode: gfxBlendScreen },
146 { .name: "Overlay", .mode: gfxBlendOverlay }, { .name: "Darken", .mode: gfxBlendDarken },
147 { .name: "Lighten", .mode: gfxBlendLighten }, { .name: "ColorDodge", .mode: gfxBlendColorDodge },
148 { .name: "ColorBurn", .mode: gfxBlendColorBurn }, { .name: "HardLight", .mode: gfxBlendHardLight },
149 { .name: "SoftLight", .mode: gfxBlendSoftLight }, { .name: "Difference", .mode: gfxBlendDifference },
150 { .name: "Exclusion", .mode: gfxBlendExclusion }, { .name: "Hue", .mode: gfxBlendHue },
151 { .name: "Saturation", .mode: gfxBlendSaturation }, { .name: "Color", .mode: gfxBlendColor },
152 { .name: "Luminosity", .mode: gfxBlendLuminosity } };
153
154#define nGfxBlendModeNames ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo))))
155
156//------------------------------------------------------------------------
157//
158// NB: This must match the GfxColorSpaceMode enum defined in
159// GfxState.h
160static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern" };
161
162#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))
163
164#ifdef USE_CMS
165
166static const std::map<unsigned int, unsigned int>::size_type CMSCACHE_LIMIT = 2048;
167
168# include <lcms2.h>
169# define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION
170
171static void lcmsprofiledeleter(void *profile)
172{
173 cmsCloseProfile(profile);
174}
175
176GfxLCMSProfilePtr make_GfxLCMSProfilePtr(void *profile)
177{
178 if (profile == nullptr) {
179 return GfxLCMSProfilePtr();
180 }
181 return GfxLCMSProfilePtr(profile, lcmsprofiledeleter);
182}
183
184void GfxColorTransform::doTransform(void *in, void *out, unsigned int size)
185{
186 cmsDoTransform(transform, in, out, size);
187}
188
189// transformA should be a cmsHTRANSFORM
190GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA)
191{
192 transform = transformA;
193 cmsIntent = cmsIntentA;
194 inputPixelType = inputPixelTypeA;
195 transformPixelType = transformPixelTypeA;
196}
197
198GfxColorTransform::~GfxColorTransform()
199{
200 cmsDeleteTransform(transform);
201}
202
203// convert color space signature to cmsColor type
204static unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs);
205static unsigned int getCMSNChannels(cmsColorSpaceSignature cs);
206
207#endif
208
209//------------------------------------------------------------------------
210// GfxColorSpace
211//------------------------------------------------------------------------
212
213GfxColorSpace::GfxColorSpace()
214{
215 overprintMask = 0x0f;
216 mapping = nullptr;
217}
218
219GfxColorSpace::~GfxColorSpace() { }
220
221GfxColorSpace *GfxColorSpace::parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion)
222{
223 GfxColorSpace *cs;
224 Object obj1;
225
226 if (recursion > colorSpaceRecursionLimit) {
227 error(category: errSyntaxError, pos: -1, msg: "Loop detected in color space objects");
228 return nullptr;
229 }
230
231 cs = nullptr;
232 if (csObj->isName()) {
233 if (csObj->isName(nameA: "DeviceGray") || csObj->isName(nameA: "G")) {
234 if (res != nullptr) {
235 Object objCS = res->lookupColorSpace(name: "DefaultGray");
236 if (objCS.isNull()) {
237 cs = state->copyDefaultGrayColorSpace();
238 } else {
239 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
240 }
241 } else {
242 cs = state->copyDefaultGrayColorSpace();
243 }
244 } else if (csObj->isName(nameA: "DeviceRGB") || csObj->isName(nameA: "RGB")) {
245 if (res != nullptr) {
246 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
247 if (objCS.isNull()) {
248 cs = state->copyDefaultRGBColorSpace();
249 } else {
250 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
251 }
252 } else {
253 cs = state->copyDefaultRGBColorSpace();
254 }
255 } else if (csObj->isName(nameA: "DeviceCMYK") || csObj->isName(nameA: "CMYK")) {
256 if (res != nullptr) {
257 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
258 if (objCS.isNull()) {
259 cs = state->copyDefaultCMYKColorSpace();
260 } else {
261 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
262 }
263 } else {
264 cs = state->copyDefaultCMYKColorSpace();
265 }
266 } else if (csObj->isName(nameA: "Pattern")) {
267 cs = new GfxPatternColorSpace(nullptr);
268 } else {
269 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space '{0:s}'", csObj->getName());
270 }
271 } else if (csObj->isArray() && csObj->arrayGetLength() > 0) {
272 obj1 = csObj->arrayGet(i: 0);
273 if (obj1.isName(nameA: "DeviceGray") || obj1.isName(nameA: "G")) {
274 if (res != nullptr) {
275 Object objCS = res->lookupColorSpace(name: "DefaultGray");
276 if (objCS.isNull()) {
277 cs = state->copyDefaultGrayColorSpace();
278 } else {
279 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
280 }
281 } else {
282 cs = state->copyDefaultGrayColorSpace();
283 }
284 } else if (obj1.isName(nameA: "DeviceRGB") || obj1.isName(nameA: "RGB")) {
285 if (res != nullptr) {
286 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
287 if (objCS.isNull()) {
288 cs = state->copyDefaultRGBColorSpace();
289 } else {
290 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
291 }
292 } else {
293 cs = state->copyDefaultRGBColorSpace();
294 }
295 } else if (obj1.isName(nameA: "DeviceCMYK") || obj1.isName(nameA: "CMYK")) {
296 if (res != nullptr) {
297 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
298 if (objCS.isNull()) {
299 cs = state->copyDefaultCMYKColorSpace();
300 } else {
301 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
302 }
303 } else {
304 cs = state->copyDefaultCMYKColorSpace();
305 }
306 } else if (obj1.isName(nameA: "CalGray")) {
307 cs = GfxCalGrayColorSpace::parse(arr: csObj->getArray(), state);
308 } else if (obj1.isName(nameA: "CalRGB")) {
309 cs = GfxCalRGBColorSpace::parse(arr: csObj->getArray(), state);
310 } else if (obj1.isName(nameA: "Lab")) {
311 cs = GfxLabColorSpace::parse(arr: csObj->getArray(), state);
312 } else if (obj1.isName(nameA: "ICCBased")) {
313 cs = GfxICCBasedColorSpace::parse(arr: csObj->getArray(), out, state, recursion);
314 } else if (obj1.isName(nameA: "Indexed") || obj1.isName(nameA: "I")) {
315 cs = GfxIndexedColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
316 } else if (obj1.isName(nameA: "Separation")) {
317 cs = GfxSeparationColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
318 } else if (obj1.isName(nameA: "DeviceN")) {
319 cs = GfxDeviceNColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
320 } else if (obj1.isName(nameA: "Pattern")) {
321 cs = GfxPatternColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
322 } else {
323 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space");
324 }
325 } else if (csObj->isDict()) {
326 obj1 = csObj->dictLookup(key: "ColorSpace");
327 if (obj1.isName(nameA: "DeviceGray")) {
328 if (res != nullptr) {
329 Object objCS = res->lookupColorSpace(name: "DefaultGray");
330 if (objCS.isNull()) {
331 cs = state->copyDefaultGrayColorSpace();
332 } else {
333 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
334 }
335 } else {
336 cs = state->copyDefaultGrayColorSpace();
337 }
338 } else if (obj1.isName(nameA: "DeviceRGB")) {
339 if (res != nullptr) {
340 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
341 if (objCS.isNull()) {
342 cs = state->copyDefaultRGBColorSpace();
343 } else {
344 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
345 }
346 } else {
347 cs = state->copyDefaultRGBColorSpace();
348 }
349 } else if (obj1.isName(nameA: "DeviceCMYK")) {
350 if (res != nullptr) {
351 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
352 if (objCS.isNull()) {
353 cs = state->copyDefaultCMYKColorSpace();
354 } else {
355 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
356 }
357 } else {
358 cs = state->copyDefaultCMYKColorSpace();
359 }
360 } else {
361 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space dict'");
362 }
363 } else {
364 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space - expected name or array or dict");
365 }
366 return cs;
367}
368
369void GfxColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
370{
371 return;
372}
373
374void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
375{
376 int i;
377
378 for (i = 0; i < getNComps(); ++i) {
379 decodeLow[i] = 0;
380 decodeRange[i] = 1;
381 }
382}
383
384int GfxColorSpace::getNumColorSpaceModes()
385{
386 return nGfxColorSpaceModes;
387}
388
389const char *GfxColorSpace::getColorSpaceModeName(int idx)
390{
391 return gfxColorSpaceModeNames[idx];
392}
393
394#ifdef USE_CMS
395
396static void CMSError(cmsContext /*contextId*/, cmsUInt32Number /*ecode*/, const char *text)
397{
398 error(errSyntaxWarning, -1, "{0:s}", text);
399}
400
401unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs)
402{
403 switch (cs) {
404 case cmsSigXYZData:
405 return PT_XYZ;
406 break;
407 case cmsSigLabData:
408 return PT_Lab;
409 break;
410 case cmsSigLuvData:
411 return PT_YUV;
412 break;
413 case cmsSigYCbCrData:
414 return PT_YCbCr;
415 break;
416 case cmsSigYxyData:
417 return PT_Yxy;
418 break;
419 case cmsSigRgbData:
420 return PT_RGB;
421 break;
422 case cmsSigGrayData:
423 return PT_GRAY;
424 break;
425 case cmsSigHsvData:
426 return PT_HSV;
427 break;
428 case cmsSigHlsData:
429 return PT_HLS;
430 break;
431 case cmsSigCmykData:
432 return PT_CMYK;
433 break;
434 case cmsSigCmyData:
435 return PT_CMY;
436 break;
437 case cmsSig2colorData:
438 case cmsSig3colorData:
439 case cmsSig4colorData:
440 case cmsSig5colorData:
441 case cmsSig6colorData:
442 case cmsSig7colorData:
443 case cmsSig8colorData:
444 case cmsSig9colorData:
445 case cmsSig10colorData:
446 case cmsSig11colorData:
447 case cmsSig12colorData:
448 case cmsSig13colorData:
449 case cmsSig14colorData:
450 case cmsSig15colorData:
451 default:
452 break;
453 }
454 return PT_RGB;
455}
456
457unsigned int getCMSNChannels(cmsColorSpaceSignature cs)
458{
459 switch (cs) {
460 case cmsSigXYZData:
461 case cmsSigLuvData:
462 case cmsSigLabData:
463 case cmsSigYCbCrData:
464 case cmsSigYxyData:
465 case cmsSigRgbData:
466 case cmsSigHsvData:
467 case cmsSigHlsData:
468 case cmsSigCmyData:
469 case cmsSig3colorData:
470 return 3;
471 break;
472 case cmsSigGrayData:
473 return 1;
474 break;
475 case cmsSigCmykData:
476 case cmsSig4colorData:
477 return 4;
478 break;
479 case cmsSig2colorData:
480 return 2;
481 break;
482 case cmsSig5colorData:
483 return 5;
484 break;
485 case cmsSig6colorData:
486 return 6;
487 break;
488 case cmsSig7colorData:
489 return 7;
490 break;
491 case cmsSig8colorData:
492 return 8;
493 break;
494 case cmsSig9colorData:
495 return 9;
496 break;
497 case cmsSig10colorData:
498 return 10;
499 break;
500 case cmsSig11colorData:
501 return 11;
502 break;
503 case cmsSig12colorData:
504 return 12;
505 break;
506 case cmsSig13colorData:
507 return 13;
508 break;
509 case cmsSig14colorData:
510 return 14;
511 break;
512 case cmsSig15colorData:
513 return 15;
514 default:
515 break;
516 }
517 return 3;
518}
519#endif
520
521//------------------------------------------------------------------------
522// GfxDeviceGrayColorSpace
523//------------------------------------------------------------------------
524
525GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() { }
526
527GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() { }
528
529GfxColorSpace *GfxDeviceGrayColorSpace::copy() const
530{
531 return new GfxDeviceGrayColorSpace();
532}
533
534void GfxDeviceGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
535{
536 *gray = clip01(x: color->c[0]);
537}
538
539void GfxDeviceGrayColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
540{
541 memcpy(dest: out, src: in, n: length);
542}
543
544void GfxDeviceGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
545{
546 rgb->r = rgb->g = rgb->b = clip01(x: color->c[0]);
547}
548
549void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
550{
551 int i;
552
553 for (i = 0; i < length; i++) {
554 out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0);
555 }
556}
557
558void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
559{
560 for (int i = 0; i < length; i++) {
561 *out++ = in[i];
562 *out++ = in[i];
563 *out++ = in[i];
564 }
565}
566
567void GfxDeviceGrayColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
568{
569 for (int i = 0; i < length; i++) {
570 *out++ = in[i];
571 *out++ = in[i];
572 *out++ = in[i];
573 *out++ = 255;
574 }
575}
576
577void GfxDeviceGrayColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
578{
579 for (int i = 0; i < length; i++) {
580 *out++ = 0;
581 *out++ = 0;
582 *out++ = 0;
583 *out++ = in[i];
584 }
585}
586
587void GfxDeviceGrayColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
588{
589 for (int i = 0; i < length; i++) {
590 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
591 out[j] = 0;
592 }
593 out[4] = in[i];
594 out += (SPOT_NCOMPS + 4);
595 }
596}
597
598void GfxDeviceGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
599{
600 cmyk->c = cmyk->m = cmyk->y = 0;
601 cmyk->k = clip01(gfxColorComp1 - color->c[0]);
602}
603
604void GfxDeviceGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
605{
606 clearGfxColor(gfxColor: deviceN);
607 deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]);
608}
609
610void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) const
611{
612 color->c[0] = 0;
613}
614
615//------------------------------------------------------------------------
616// GfxCalGrayColorSpace
617//------------------------------------------------------------------------
618
619GfxCalGrayColorSpace::GfxCalGrayColorSpace()
620{
621 whiteX = whiteY = whiteZ = 1;
622 blackX = blackY = blackZ = 0;
623 gamma = 1;
624}
625
626GfxCalGrayColorSpace::~GfxCalGrayColorSpace() { }
627
628GfxColorSpace *GfxCalGrayColorSpace::copy() const
629{
630 GfxCalGrayColorSpace *cs;
631
632 cs = new GfxCalGrayColorSpace();
633 cs->whiteX = whiteX;
634 cs->whiteY = whiteY;
635 cs->whiteZ = whiteZ;
636 cs->blackX = blackX;
637 cs->blackY = blackY;
638 cs->blackZ = blackZ;
639 cs->gamma = gamma;
640#ifdef USE_CMS
641 cs->transform = transform;
642#endif
643 return cs;
644}
645
646// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
647// Language Reference, Third Edition.
648static const double xyzrgb[3][3] = { { 3.240449, -1.537136, -0.498531 }, { -0.969265, 1.876011, 0.041556 }, { 0.055643, -0.204026, 1.057229 } };
649
650// From the same reference as above, the inverse of the DecodeLMN function.
651// This is essentially the gamma function of the sRGB profile.
652static double srgb_gamma_function(double x)
653{
654 // 0.04045 is what lcms2 uses, but the PS Reference Example 4.10 specifies 0.03928???
655 // if (x <= 0.04045 / 12.92321) {
656 if (x <= 0.03928 / 12.92321) {
657 return x * 12.92321;
658 }
659 return 1.055 * pow(x: x, y: 1.0 / 2.4) - 0.055;
660}
661
662// D65 is the white point of the sRGB profile as it is specified above in the xyzrgb array
663static const double white_d65_X = 0.9505;
664static const double white_d65_Y = 1.0;
665static const double white_d65_Z = 1.0890;
666
667#ifdef USE_CMS
668// D50 is the default white point as used in ICC profiles and in the lcms2 library
669static const double white_d50_X = 0.96422;
670static const double white_d50_Y = 1.0;
671static const double white_d50_Z = 0.82521;
672
673static void inline bradford_transform_to_d50(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ)
674{
675 if (source_whiteX == white_d50_X && source_whiteY == white_d50_Y && source_whiteZ == white_d50_Z) {
676 // early exit if noop
677 return;
678 }
679 // at first apply Bradford matrix
680 double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z;
681 double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z;
682 double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z;
683
684 // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point
685 rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ;
686 gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ;
687 beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ;
688
689 // now revert the two steps above, but substituting the source white point by the device white point (D50)
690 // Since the white point is known a priori this has been combined into a single operation.
691 X = 0.98332566 * rho_in - 0.15005819 * gamma_in + 0.13095252 * beta_in;
692 Y = 0.43069901 * rho_in + 0.52894900 * gamma_in + 0.04035199 * beta_in;
693 Z = 0.00849698 * rho_in + 0.04086079 * gamma_in + 0.79284618 * beta_in;
694}
695#endif
696
697static void inline bradford_transform_to_d65(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ)
698{
699 if (source_whiteX == white_d65_X && source_whiteY == white_d65_Y && source_whiteZ == white_d65_Z) {
700 // early exit if noop
701 return;
702 }
703 // at first apply Bradford matrix
704 double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z;
705 double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z;
706 double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z;
707
708 // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point
709 rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ;
710 gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ;
711 beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ;
712
713 // now revert the two steps above, but substituting the source white point by the device white point (D65)
714 // Since the white point is known a priori this has been combined into a single operation.
715 X = 0.92918329 * rho_in - 0.15299782 * gamma_in + 0.17428453 * beta_in;
716 Y = 0.40698452 * rho_in + 0.53931108 * gamma_in + 0.05370440 * beta_in;
717 Z = -0.00802913 * rho_in + 0.04166125 * gamma_in + 1.05519788 * beta_in;
718}
719
720GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state)
721{
722 GfxCalGrayColorSpace *cs;
723 Object obj1, obj2;
724
725 obj1 = arr->get(i: 1);
726 if (!obj1.isDict()) {
727 error(category: errSyntaxWarning, pos: -1, msg: "Bad CalGray color space");
728 return nullptr;
729 }
730 cs = new GfxCalGrayColorSpace();
731 obj2 = obj1.dictLookup(key: "WhitePoint");
732 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
733 cs->whiteX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
734 cs->whiteY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
735 cs->whiteZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
736 }
737 obj2 = obj1.dictLookup(key: "BlackPoint");
738 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
739 cs->blackX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
740 cs->blackY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
741 cs->blackZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
742 }
743
744 cs->gamma = obj1.dictLookup(key: "Gamma").getNumWithDefaultValue(defaultValue: 1);
745
746#ifdef USE_CMS
747 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
748#endif
749 return cs;
750}
751
752// convert CalGray to media XYZ color space
753// (not multiply by the white point)
754void GfxCalGrayColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
755{
756 const double A = colToDbl(x: color->c[0]);
757 const double xyzColor = pow(x: A, y: gamma);
758 *pX = xyzColor;
759 *pY = xyzColor;
760 *pZ = xyzColor;
761}
762
763void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
764{
765 GfxRGB rgb;
766
767#ifdef USE_CMS
768 if (transform && transform->getTransformPixelType() == PT_GRAY) {
769 unsigned char out[gfxColorMaxComps];
770 double in[gfxColorMaxComps];
771 double X, Y, Z;
772
773 getXYZ(color, &X, &Y, &Z);
774 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
775 in[0] = X;
776 in[1] = Y;
777 in[2] = Z;
778 transform->doTransform(in, out, 1);
779 *gray = byteToCol(out[0]);
780 return;
781 }
782#endif
783 getRGB(color, rgb: &rgb);
784 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
785}
786
787void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
788{
789 double X, Y, Z;
790 double r, g, b;
791
792 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
793#ifdef USE_CMS
794 if (transform && transform->getTransformPixelType() == PT_RGB) {
795 unsigned char out[gfxColorMaxComps];
796 double in[gfxColorMaxComps];
797
798 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
799 in[0] = X;
800 in[1] = Y;
801 in[2] = Z;
802 transform->doTransform(in, out, 1);
803 rgb->r = byteToCol(out[0]);
804 rgb->g = byteToCol(out[1]);
805 rgb->b = byteToCol(out[2]);
806 return;
807 }
808#endif
809 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
810 // convert XYZ to RGB, including gamut mapping and gamma correction
811 r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
812 g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
813 b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
814 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
815 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
816 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
817}
818
819void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
820{
821 GfxRGB rgb;
822 GfxColorComp c, m, y, k;
823
824#ifdef USE_CMS
825 if (transform && transform->getTransformPixelType() == PT_CMYK) {
826 double in[gfxColorMaxComps];
827 unsigned char out[gfxColorMaxComps];
828 double X, Y, Z;
829
830 getXYZ(color, &X, &Y, &Z);
831 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
832 in[0] = X;
833 in[1] = Y;
834 in[2] = Z;
835 transform->doTransform(in, out, 1);
836 cmyk->c = byteToCol(out[0]);
837 cmyk->m = byteToCol(out[1]);
838 cmyk->y = byteToCol(out[2]);
839 cmyk->k = byteToCol(out[3]);
840 return;
841 }
842#endif
843 getRGB(color, rgb: &rgb);
844 c = clip01(gfxColorComp1 - rgb.r);
845 m = clip01(gfxColorComp1 - rgb.g);
846 y = clip01(gfxColorComp1 - rgb.b);
847 k = c;
848 if (m < k) {
849 k = m;
850 }
851 if (y < k) {
852 k = y;
853 }
854 cmyk->c = c - k;
855 cmyk->m = m - k;
856 cmyk->y = y - k;
857 cmyk->k = k;
858}
859
860void GfxCalGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
861{
862 GfxCMYK cmyk;
863 clearGfxColor(gfxColor: deviceN);
864 getCMYK(color, cmyk: &cmyk);
865 deviceN->c[0] = cmyk.c;
866 deviceN->c[1] = cmyk.m;
867 deviceN->c[2] = cmyk.y;
868 deviceN->c[3] = cmyk.k;
869}
870
871void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) const
872{
873 color->c[0] = 0;
874}
875
876//------------------------------------------------------------------------
877// GfxDeviceRGBColorSpace
878//------------------------------------------------------------------------
879
880GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() { }
881
882GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() { }
883
884GfxColorSpace *GfxDeviceRGBColorSpace::copy() const
885{
886 return new GfxDeviceRGBColorSpace();
887}
888
889void GfxDeviceRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
890{
891 *gray = clip01(x: (GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + 0.11 * color->c[2] + 0.5));
892}
893
894void GfxDeviceRGBColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
895{
896 int i;
897
898 for (i = 0; i < length; i++) {
899 out[i] = (in[i * 3 + 0] * 19595 + in[i * 3 + 1] * 38469 + in[i * 3 + 2] * 7472) / 65536;
900 }
901}
902
903void GfxDeviceRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
904{
905 rgb->r = clip01(x: color->c[0]);
906 rgb->g = clip01(x: color->c[1]);
907 rgb->b = clip01(x: color->c[2]);
908}
909
910void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
911{
912 unsigned char *p;
913 int i;
914
915 for (i = 0, p = in; i < length; i++, p += 3) {
916 out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0);
917 }
918}
919
920void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
921{
922 for (int i = 0; i < length; i++) {
923 *out++ = *in++;
924 *out++ = *in++;
925 *out++ = *in++;
926 }
927}
928
929void GfxDeviceRGBColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
930{
931 for (int i = 0; i < length; i++) {
932 *out++ = *in++;
933 *out++ = *in++;
934 *out++ = *in++;
935 *out++ = 255;
936 }
937}
938
939void GfxDeviceRGBColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
940{
941 GfxColorComp c, m, y, k;
942
943 for (int i = 0; i < length; i++) {
944 c = byteToCol(x: 255 - *in++);
945 m = byteToCol(x: 255 - *in++);
946 y = byteToCol(x: 255 - *in++);
947 k = c;
948 if (m < k) {
949 k = m;
950 }
951 if (y < k) {
952 k = y;
953 }
954 *out++ = colToByte(x: c - k);
955 *out++ = colToByte(x: m - k);
956 *out++ = colToByte(x: y - k);
957 *out++ = colToByte(x: k);
958 }
959}
960
961void GfxDeviceRGBColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
962{
963 GfxColorComp c, m, y, k;
964
965 for (int i = 0; i < length; i++) {
966 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
967 out[j] = 0;
968 }
969 c = byteToCol(x: 255 - *in++);
970 m = byteToCol(x: 255 - *in++);
971 y = byteToCol(x: 255 - *in++);
972 k = c;
973 if (m < k) {
974 k = m;
975 }
976 if (y < k) {
977 k = y;
978 }
979 out[0] = colToByte(x: c - k);
980 out[1] = colToByte(x: m - k);
981 out[2] = colToByte(x: y - k);
982 out[3] = colToByte(x: k);
983 out += (SPOT_NCOMPS + 4);
984 }
985}
986
987void GfxDeviceRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
988{
989 GfxColorComp c, m, y, k;
990
991 c = clip01(gfxColorComp1 - color->c[0]);
992 m = clip01(gfxColorComp1 - color->c[1]);
993 y = clip01(gfxColorComp1 - color->c[2]);
994 k = c;
995 if (m < k) {
996 k = m;
997 }
998 if (y < k) {
999 k = y;
1000 }
1001 cmyk->c = c - k;
1002 cmyk->m = m - k;
1003 cmyk->y = y - k;
1004 cmyk->k = k;
1005}
1006
1007void GfxDeviceRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1008{
1009 GfxCMYK cmyk;
1010 clearGfxColor(gfxColor: deviceN);
1011 getCMYK(color, cmyk: &cmyk);
1012 deviceN->c[0] = cmyk.c;
1013 deviceN->c[1] = cmyk.m;
1014 deviceN->c[2] = cmyk.y;
1015 deviceN->c[3] = cmyk.k;
1016}
1017
1018void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const
1019{
1020 color->c[0] = 0;
1021 color->c[1] = 0;
1022 color->c[2] = 0;
1023}
1024
1025//------------------------------------------------------------------------
1026// GfxCalRGBColorSpace
1027//------------------------------------------------------------------------
1028
1029GfxCalRGBColorSpace::GfxCalRGBColorSpace()
1030{
1031 whiteX = whiteY = whiteZ = 1;
1032 blackX = blackY = blackZ = 0;
1033 gammaR = gammaG = gammaB = 1;
1034 mat[0] = 1;
1035 mat[1] = 0;
1036 mat[2] = 0;
1037 mat[3] = 0;
1038 mat[4] = 1;
1039 mat[5] = 0;
1040 mat[6] = 0;
1041 mat[7] = 0;
1042 mat[8] = 1;
1043}
1044
1045GfxCalRGBColorSpace::~GfxCalRGBColorSpace() { }
1046
1047GfxColorSpace *GfxCalRGBColorSpace::copy() const
1048{
1049 GfxCalRGBColorSpace *cs;
1050 int i;
1051
1052 cs = new GfxCalRGBColorSpace();
1053 cs->whiteX = whiteX;
1054 cs->whiteY = whiteY;
1055 cs->whiteZ = whiteZ;
1056 cs->blackX = blackX;
1057 cs->blackY = blackY;
1058 cs->blackZ = blackZ;
1059 cs->gammaR = gammaR;
1060 cs->gammaG = gammaG;
1061 cs->gammaB = gammaB;
1062 for (i = 0; i < 9; ++i) {
1063 cs->mat[i] = mat[i];
1064 }
1065#ifdef USE_CMS
1066 cs->transform = transform;
1067#endif
1068 return cs;
1069}
1070
1071GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state)
1072{
1073 GfxCalRGBColorSpace *cs;
1074 Object obj1, obj2;
1075 int i;
1076
1077 obj1 = arr->get(i: 1);
1078 if (!obj1.isDict()) {
1079 error(category: errSyntaxWarning, pos: -1, msg: "Bad CalRGB color space");
1080 return nullptr;
1081 }
1082 cs = new GfxCalRGBColorSpace();
1083 obj2 = obj1.dictLookup(key: "WhitePoint");
1084 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1085 cs->whiteX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
1086 cs->whiteY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
1087 cs->whiteZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
1088 }
1089 obj2 = obj1.dictLookup(key: "BlackPoint");
1090 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1091 cs->blackX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
1092 cs->blackY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
1093 cs->blackZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
1094 }
1095 obj2 = obj1.dictLookup(key: "Gamma");
1096 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1097 cs->gammaR = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
1098 cs->gammaG = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
1099 cs->gammaB = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
1100 }
1101 obj2 = obj1.dictLookup(key: "Matrix");
1102 if (obj2.isArray() && obj2.arrayGetLength() == 9) {
1103 for (i = 0; i < 9; ++i) {
1104 Object obj3 = obj2.arrayGet(i);
1105 if (likely(obj3.isNum())) {
1106 cs->mat[i] = obj3.getNum();
1107 }
1108 }
1109 }
1110
1111#ifdef USE_CMS
1112 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
1113#endif
1114 return cs;
1115}
1116
1117// convert CalRGB to XYZ color space
1118void GfxCalRGBColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
1119{
1120 double A, B, C;
1121
1122 A = pow(x: colToDbl(x: color->c[0]), y: gammaR);
1123 B = pow(x: colToDbl(x: color->c[1]), y: gammaG);
1124 C = pow(x: colToDbl(x: color->c[2]), y: gammaB);
1125 *pX = mat[0] * A + mat[3] * B + mat[6] * C;
1126 *pY = mat[1] * A + mat[4] * B + mat[7] * C;
1127 *pZ = mat[2] * A + mat[5] * B + mat[8] * C;
1128}
1129
1130void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1131{
1132 GfxRGB rgb;
1133
1134#ifdef USE_CMS
1135 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1136 unsigned char out[gfxColorMaxComps];
1137 double in[gfxColorMaxComps];
1138 double X, Y, Z;
1139
1140 getXYZ(color, &X, &Y, &Z);
1141 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1142 in[0] = X;
1143 in[1] = Y;
1144 in[2] = Z;
1145 transform->doTransform(in, out, 1);
1146 *gray = byteToCol(out[0]);
1147 return;
1148 }
1149#endif
1150 getRGB(color, rgb: &rgb);
1151 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
1152}
1153
1154void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1155{
1156 double X, Y, Z;
1157 double r, g, b;
1158
1159 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
1160#ifdef USE_CMS
1161 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1162 unsigned char out[gfxColorMaxComps];
1163 double in[gfxColorMaxComps];
1164
1165 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1166 in[0] = X;
1167 in[1] = Y;
1168 in[2] = Z;
1169 transform->doTransform(in, out, 1);
1170 rgb->r = byteToCol(out[0]);
1171 rgb->g = byteToCol(out[1]);
1172 rgb->b = byteToCol(out[2]);
1173
1174 return;
1175 }
1176#endif
1177 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
1178 // convert XYZ to RGB, including gamut mapping and gamma correction
1179 r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
1180 g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
1181 b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
1182 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
1183 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
1184 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
1185}
1186
1187void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1188{
1189 GfxRGB rgb;
1190 GfxColorComp c, m, y, k;
1191
1192#ifdef USE_CMS
1193 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1194 double in[gfxColorMaxComps];
1195 unsigned char out[gfxColorMaxComps];
1196 double X, Y, Z;
1197
1198 getXYZ(color, &X, &Y, &Z);
1199 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1200 in[0] = X;
1201 in[1] = Y;
1202 in[2] = Z;
1203 transform->doTransform(in, out, 1);
1204 cmyk->c = byteToCol(out[0]);
1205 cmyk->m = byteToCol(out[1]);
1206 cmyk->y = byteToCol(out[2]);
1207 cmyk->k = byteToCol(out[3]);
1208 return;
1209 }
1210#endif
1211 getRGB(color, rgb: &rgb);
1212 c = clip01(gfxColorComp1 - rgb.r);
1213 m = clip01(gfxColorComp1 - rgb.g);
1214 y = clip01(gfxColorComp1 - rgb.b);
1215 k = c;
1216 if (m < k) {
1217 k = m;
1218 }
1219 if (y < k) {
1220 k = y;
1221 }
1222 cmyk->c = c - k;
1223 cmyk->m = m - k;
1224 cmyk->y = y - k;
1225 cmyk->k = k;
1226}
1227
1228void GfxCalRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1229{
1230 GfxCMYK cmyk;
1231 clearGfxColor(gfxColor: deviceN);
1232 getCMYK(color, cmyk: &cmyk);
1233 deviceN->c[0] = cmyk.c;
1234 deviceN->c[1] = cmyk.m;
1235 deviceN->c[2] = cmyk.y;
1236 deviceN->c[3] = cmyk.k;
1237}
1238
1239void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) const
1240{
1241 color->c[0] = 0;
1242 color->c[1] = 0;
1243 color->c[2] = 0;
1244}
1245
1246//------------------------------------------------------------------------
1247// GfxDeviceCMYKColorSpace
1248//------------------------------------------------------------------------
1249
1250GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() { }
1251
1252GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() { }
1253
1254GfxColorSpace *GfxDeviceCMYKColorSpace::copy() const
1255{
1256 return new GfxDeviceCMYKColorSpace();
1257}
1258
1259void GfxDeviceCMYKColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1260{
1261 *gray = clip01(x: (GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] - 0.59 * color->c[1] - 0.11 * color->c[2] + 0.5));
1262}
1263
1264void GfxDeviceCMYKColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1265{
1266 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1267
1268 c = colToDbl(x: color->c[0]);
1269 m = colToDbl(x: color->c[1]);
1270 y = colToDbl(x: color->c[2]);
1271 k = colToDbl(x: color->c[3]);
1272 c1 = 1 - c;
1273 m1 = 1 - m;
1274 y1 = 1 - y;
1275 k1 = 1 - k;
1276 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1277 rgb->r = clip01(x: dblToCol(x: r));
1278 rgb->g = clip01(x: dblToCol(x: g));
1279 rgb->b = clip01(x: dblToCol(x: b));
1280}
1281
1282static inline void GfxDeviceCMYKColorSpacegetRGBLineHelper(unsigned char *&in, double &r, double &g, double &b)
1283{
1284 double c, m, y, k, c1, m1, y1, k1;
1285
1286 c = byteToDbl(x: *in++);
1287 m = byteToDbl(x: *in++);
1288 y = byteToDbl(x: *in++);
1289 k = byteToDbl(x: *in++);
1290 c1 = 1 - c;
1291 m1 = 1 - m;
1292 y1 = 1 - y;
1293 k1 = 1 - k;
1294 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1295}
1296
1297void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
1298{
1299 double r, g, b;
1300 for (int i = 0; i < length; i++) {
1301 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1302 *out++ = (dblToByte(x: clip01(x: r)) << 16) | (dblToByte(x: clip01(x: g)) << 8) | dblToByte(x: clip01(x: b));
1303 }
1304}
1305
1306void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
1307{
1308 double r, g, b;
1309
1310 for (int i = 0; i < length; i++) {
1311 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1312 *out++ = dblToByte(x: clip01(x: r));
1313 *out++ = dblToByte(x: clip01(x: g));
1314 *out++ = dblToByte(x: clip01(x: b));
1315 }
1316}
1317
1318void GfxDeviceCMYKColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
1319{
1320 double r, g, b;
1321
1322 for (int i = 0; i < length; i++) {
1323 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1324 *out++ = dblToByte(x: clip01(x: r));
1325 *out++ = dblToByte(x: clip01(x: g));
1326 *out++ = dblToByte(x: clip01(x: b));
1327 *out++ = 255;
1328 }
1329}
1330
1331void GfxDeviceCMYKColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
1332{
1333 for (int i = 0; i < length; i++) {
1334 *out++ = *in++;
1335 *out++ = *in++;
1336 *out++ = *in++;
1337 *out++ = *in++;
1338 }
1339}
1340
1341void GfxDeviceCMYKColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
1342{
1343 for (int i = 0; i < length; i++) {
1344 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
1345 out[j] = 0;
1346 }
1347 out[0] = *in++;
1348 out[1] = *in++;
1349 out[2] = *in++;
1350 out[3] = *in++;
1351 out += (SPOT_NCOMPS + 4);
1352 }
1353}
1354
1355void GfxDeviceCMYKColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1356{
1357 cmyk->c = clip01(x: color->c[0]);
1358 cmyk->m = clip01(x: color->c[1]);
1359 cmyk->y = clip01(x: color->c[2]);
1360 cmyk->k = clip01(x: color->c[3]);
1361}
1362
1363void GfxDeviceCMYKColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1364{
1365 clearGfxColor(gfxColor: deviceN);
1366 deviceN->c[0] = clip01(x: color->c[0]);
1367 deviceN->c[1] = clip01(x: color->c[1]);
1368 deviceN->c[2] = clip01(x: color->c[2]);
1369 deviceN->c[3] = clip01(x: color->c[3]);
1370}
1371
1372void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) const
1373{
1374 color->c[0] = 0;
1375 color->c[1] = 0;
1376 color->c[2] = 0;
1377 color->c[3] = gfxColorComp1;
1378}
1379
1380//------------------------------------------------------------------------
1381// GfxLabColorSpace
1382//------------------------------------------------------------------------
1383
1384GfxLabColorSpace::GfxLabColorSpace()
1385{
1386 whiteX = whiteY = whiteZ = 1;
1387 blackX = blackY = blackZ = 0;
1388 aMin = bMin = -100;
1389 aMax = bMax = 100;
1390}
1391
1392GfxLabColorSpace::~GfxLabColorSpace() { }
1393
1394GfxColorSpace *GfxLabColorSpace::copy() const
1395{
1396 GfxLabColorSpace *cs;
1397
1398 cs = new GfxLabColorSpace();
1399 cs->whiteX = whiteX;
1400 cs->whiteY = whiteY;
1401 cs->whiteZ = whiteZ;
1402 cs->blackX = blackX;
1403 cs->blackY = blackY;
1404 cs->blackZ = blackZ;
1405 cs->aMin = aMin;
1406 cs->aMax = aMax;
1407 cs->bMin = bMin;
1408 cs->bMax = bMax;
1409#ifdef USE_CMS
1410 cs->transform = transform;
1411#endif
1412 return cs;
1413}
1414
1415GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state)
1416{
1417 GfxLabColorSpace *cs;
1418 Object obj1, obj2;
1419
1420 obj1 = arr->get(i: 1);
1421 if (!obj1.isDict()) {
1422 error(category: errSyntaxWarning, pos: -1, msg: "Bad Lab color space");
1423 return nullptr;
1424 }
1425 cs = new GfxLabColorSpace();
1426 bool ok = true;
1427 obj2 = obj1.dictLookup(key: "WhitePoint");
1428 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1429 cs->whiteX = obj2.arrayGet(i: 0).getNum(ok: &ok);
1430 cs->whiteY = obj2.arrayGet(i: 1).getNum(ok: &ok);
1431 cs->whiteZ = obj2.arrayGet(i: 2).getNum(ok: &ok);
1432 }
1433 obj2 = obj1.dictLookup(key: "BlackPoint");
1434 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1435 cs->blackX = obj2.arrayGet(i: 0).getNum(ok: &ok);
1436 cs->blackY = obj2.arrayGet(i: 1).getNum(ok: &ok);
1437 cs->blackZ = obj2.arrayGet(i: 2).getNum(ok: &ok);
1438 }
1439 obj2 = obj1.dictLookup(key: "Range");
1440 if (obj2.isArray() && obj2.arrayGetLength() == 4) {
1441 cs->aMin = obj2.arrayGet(i: 0).getNum(ok: &ok);
1442 cs->aMax = obj2.arrayGet(i: 1).getNum(ok: &ok);
1443 cs->bMin = obj2.arrayGet(i: 2).getNum(ok: &ok);
1444 cs->bMax = obj2.arrayGet(i: 3).getNum(ok: &ok);
1445 }
1446
1447 if (!ok) {
1448 error(category: errSyntaxWarning, pos: -1, msg: "Bad Lab color space");
1449#ifdef USE_CMS
1450 cs->transform = nullptr;
1451#endif
1452 delete cs;
1453 return nullptr;
1454 }
1455
1456#ifdef USE_CMS
1457 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
1458#endif
1459 return cs;
1460}
1461
1462void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1463{
1464 GfxRGB rgb;
1465
1466#ifdef USE_CMS
1467 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1468 unsigned char out[gfxColorMaxComps];
1469 double in[gfxColorMaxComps];
1470
1471 getXYZ(color, &in[0], &in[1], &in[2]);
1472 bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ);
1473 transform->doTransform(in, out, 1);
1474 *gray = byteToCol(out[0]);
1475 return;
1476 }
1477#endif
1478 getRGB(color, rgb: &rgb);
1479 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
1480}
1481
1482// convert L*a*b* to media XYZ color space
1483// (not multiply by the white point)
1484void GfxLabColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
1485{
1486 double X, Y, Z;
1487 double t1, t2;
1488
1489 t1 = (colToDbl(x: color->c[0]) + 16) / 116;
1490 t2 = t1 + colToDbl(x: color->c[1]) / 500;
1491 if (t2 >= (6.0 / 29.0)) {
1492 X = t2 * t2 * t2;
1493 } else {
1494 X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
1495 }
1496 if (t1 >= (6.0 / 29.0)) {
1497 Y = t1 * t1 * t1;
1498 } else {
1499 Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
1500 }
1501 t2 = t1 - colToDbl(x: color->c[2]) / 200;
1502 if (t2 >= (6.0 / 29.0)) {
1503 Z = t2 * t2 * t2;
1504 } else {
1505 Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
1506 }
1507 *pX = X;
1508 *pY = Y;
1509 *pZ = Z;
1510}
1511
1512void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1513{
1514 double X, Y, Z;
1515
1516 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
1517 X *= whiteX;
1518 Y *= whiteY;
1519 Z *= whiteZ;
1520#ifdef USE_CMS
1521 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1522 unsigned char out[gfxColorMaxComps];
1523 double in[gfxColorMaxComps];
1524
1525 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1526 in[0] = X;
1527 in[1] = Y;
1528 in[2] = Z;
1529 transform->doTransform(in, out, 1);
1530 rgb->r = byteToCol(out[0]);
1531 rgb->g = byteToCol(out[1]);
1532 rgb->b = byteToCol(out[2]);
1533 return;
1534 } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1535 unsigned char out[gfxColorMaxComps];
1536 double in[gfxColorMaxComps];
1537 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1538
1539 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1540 in[0] = X;
1541 in[1] = Y;
1542 in[2] = Z;
1543 transform->doTransform(in, out, 1);
1544 c = byteToDbl(out[0]);
1545 m = byteToDbl(out[1]);
1546 y = byteToDbl(out[2]);
1547 k = byteToDbl(out[3]);
1548 c1 = 1 - c;
1549 m1 = 1 - m;
1550 y1 = 1 - y;
1551 k1 = 1 - k;
1552 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1553 rgb->r = clip01(dblToCol(r));
1554 rgb->g = clip01(dblToCol(g));
1555 rgb->b = clip01(dblToCol(b));
1556 return;
1557 }
1558#endif
1559 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
1560 // convert XYZ to RGB, including gamut mapping and gamma correction
1561 const double r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
1562 const double g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
1563 const double b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
1564 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
1565 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
1566 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
1567}
1568
1569void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1570{
1571 GfxRGB rgb;
1572 GfxColorComp c, m, y, k;
1573
1574#ifdef USE_CMS
1575 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1576 double in[gfxColorMaxComps];
1577 unsigned char out[gfxColorMaxComps];
1578
1579 getXYZ(color, &in[0], &in[1], &in[2]);
1580 bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ);
1581 transform->doTransform(in, out, 1);
1582 cmyk->c = byteToCol(out[0]);
1583 cmyk->m = byteToCol(out[1]);
1584 cmyk->y = byteToCol(out[2]);
1585 cmyk->k = byteToCol(out[3]);
1586 return;
1587 }
1588#endif
1589 getRGB(color, rgb: &rgb);
1590 c = clip01(gfxColorComp1 - rgb.r);
1591 m = clip01(gfxColorComp1 - rgb.g);
1592 y = clip01(gfxColorComp1 - rgb.b);
1593 k = c;
1594 if (m < k) {
1595 k = m;
1596 }
1597 if (y < k) {
1598 k = y;
1599 }
1600 cmyk->c = c - k;
1601 cmyk->m = m - k;
1602 cmyk->y = y - k;
1603 cmyk->k = k;
1604}
1605
1606void GfxLabColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1607{
1608 GfxCMYK cmyk;
1609 clearGfxColor(gfxColor: deviceN);
1610 getCMYK(color, cmyk: &cmyk);
1611 deviceN->c[0] = cmyk.c;
1612 deviceN->c[1] = cmyk.m;
1613 deviceN->c[2] = cmyk.y;
1614 deviceN->c[3] = cmyk.k;
1615}
1616
1617void GfxLabColorSpace::getDefaultColor(GfxColor *color) const
1618{
1619 color->c[0] = 0;
1620 if (aMin > 0) {
1621 color->c[1] = dblToCol(x: aMin);
1622 } else if (aMax < 0) {
1623 color->c[1] = dblToCol(x: aMax);
1624 } else {
1625 color->c[1] = 0;
1626 }
1627 if (bMin > 0) {
1628 color->c[2] = dblToCol(x: bMin);
1629 } else if (bMax < 0) {
1630 color->c[2] = dblToCol(x: bMax);
1631 } else {
1632 color->c[2] = 0;
1633 }
1634}
1635
1636void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
1637{
1638 decodeLow[0] = 0;
1639 decodeRange[0] = 100;
1640 decodeLow[1] = aMin;
1641 decodeRange[1] = aMax - aMin;
1642 decodeLow[2] = bMin;
1643 decodeRange[2] = bMax - bMin;
1644}
1645
1646//------------------------------------------------------------------------
1647// GfxICCBasedColorSpace
1648//------------------------------------------------------------------------
1649
1650GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA, const Ref *iccProfileStreamA)
1651{
1652 nComps = nCompsA;
1653 alt = altA;
1654 iccProfileStream = *iccProfileStreamA;
1655 rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
1656 rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
1657#ifdef USE_CMS
1658 transform = nullptr;
1659 lineTransform = nullptr;
1660 psCSA = nullptr;
1661#endif
1662}
1663
1664GfxICCBasedColorSpace::~GfxICCBasedColorSpace()
1665{
1666 delete alt;
1667#ifdef USE_CMS
1668 if (psCSA) {
1669 gfree(psCSA);
1670 }
1671#endif
1672}
1673
1674GfxColorSpace *GfxICCBasedColorSpace::copy() const
1675{
1676 GfxICCBasedColorSpace *cs;
1677 int i;
1678
1679 cs = new GfxICCBasedColorSpace(nComps, alt->copy(), &iccProfileStream);
1680 for (i = 0; i < 4; ++i) {
1681 cs->rangeMin[i] = rangeMin[i];
1682 cs->rangeMax[i] = rangeMax[i];
1683 }
1684#ifdef USE_CMS
1685 cs->profile = profile;
1686 cs->transform = transform;
1687 cs->lineTransform = lineTransform;
1688#endif
1689 return cs;
1690}
1691
1692GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion)
1693{
1694 GfxICCBasedColorSpace *cs;
1695 int nCompsA;
1696 GfxColorSpace *altA;
1697 Dict *dict;
1698 Object obj1, obj2;
1699 int i;
1700
1701 if (arr->getLength() < 2) {
1702 error(category: errSyntaxError, pos: -1, msg: "Bad ICCBased color space");
1703 return nullptr;
1704 }
1705 const Object &obj1Ref = arr->getNF(i: 1);
1706 const Ref iccProfileStreamA = obj1Ref.isRef() ? obj1Ref.getRef() : Ref::INVALID();
1707#ifdef USE_CMS
1708 // check cache
1709 if (out && iccProfileStreamA != Ref::INVALID()) {
1710 if (auto *item = out->getIccColorSpaceCache()->lookup(iccProfileStreamA)) {
1711 cs = static_cast<GfxICCBasedColorSpace *>(item->copy());
1712 int transformIntent = cs->getIntent();
1713 int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
1714 if (state != nullptr) {
1715 cmsIntent = state->getCmsRenderingIntent();
1716 }
1717 if (transformIntent == cmsIntent) {
1718 return cs;
1719 }
1720 delete cs;
1721 }
1722 }
1723#endif
1724 obj1 = arr->get(i: 1);
1725 if (!obj1.isStream()) {
1726 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space (stream)");
1727 return nullptr;
1728 }
1729 dict = obj1.streamGetDict();
1730 obj2 = dict->lookup(key: "N");
1731 if (!obj2.isInt()) {
1732 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space (N)");
1733 return nullptr;
1734 }
1735 nCompsA = obj2.getInt();
1736 if (nCompsA > 4) {
1737 error(category: errSyntaxError, pos: -1, msg: "ICCBased color space with too many ({0:d} > 4) components", nCompsA);
1738 nCompsA = 4;
1739 }
1740 obj2 = dict->lookup(key: "Alternate");
1741 if (obj2.isNull() || !(altA = GfxColorSpace::parse(res: nullptr, csObj: &obj2, out, state, recursion: recursion + 1))) {
1742 switch (nCompsA) {
1743 case 1:
1744 altA = new GfxDeviceGrayColorSpace();
1745 break;
1746 case 3:
1747 altA = new GfxDeviceRGBColorSpace();
1748 break;
1749 case 4:
1750 altA = new GfxDeviceCMYKColorSpace();
1751 break;
1752 default:
1753 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space - invalid N");
1754 return nullptr;
1755 }
1756 }
1757 if (altA->getNComps() != nCompsA) {
1758 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space - N doesn't match alt color space");
1759 delete altA;
1760 return nullptr;
1761 }
1762 cs = new GfxICCBasedColorSpace(nCompsA, altA, &iccProfileStreamA);
1763 obj2 = dict->lookup(key: "Range");
1764 if (obj2.isArray() && obj2.arrayGetLength() == 2 * nCompsA) {
1765 for (i = 0; i < nCompsA; ++i) {
1766 cs->rangeMin[i] = obj2.arrayGet(i: 2 * i).getNumWithDefaultValue(defaultValue: 0);
1767 cs->rangeMax[i] = obj2.arrayGet(i: 2 * i + 1).getNumWithDefaultValue(defaultValue: 1);
1768 }
1769 }
1770
1771#ifdef USE_CMS
1772 obj1 = arr->get(1);
1773 if (!obj1.isStream()) {
1774 error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)");
1775 delete cs;
1776 return nullptr;
1777 }
1778 Stream *iccStream = obj1.getStream();
1779
1780 const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
1781 auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size()));
1782 cs->profile = hp;
1783 if (!hp) {
1784 error(errSyntaxWarning, -1, "read ICCBased color space profile error");
1785 } else {
1786 cs->buildTransforms(state);
1787 }
1788 // put this colorSpace into cache
1789 if (out && iccProfileStreamA != Ref::INVALID()) {
1790 out->getIccColorSpaceCache()->put(iccProfileStreamA, static_cast<GfxICCBasedColorSpace *>(cs->copy()));
1791 }
1792#endif
1793 return cs;
1794}
1795
1796#ifdef USE_CMS
1797void GfxICCBasedColorSpace::buildTransforms(GfxState *state)
1798{
1799 auto dhp = (state != nullptr && state->getDisplayProfile() != nullptr) ? state->getDisplayProfile() : nullptr;
1800 if (!dhp) {
1801 dhp = GfxState::sRGBProfile;
1802 }
1803 unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(profile.get()));
1804 unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp.get()));
1805 unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp.get()));
1806 cmsHTRANSFORM transformA;
1807
1808 int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
1809 if (state != nullptr) {
1810 cmsIntent = state->getCmsRenderingIntent();
1811 }
1812 if ((transformA = cmsCreateTransform(profile.get(), COLORSPACE_SH(cst) | CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), COLORSPACE_SH(dcst) | CHANNELS_SH(dNChannels) | BYTES_SH(1), cmsIntent, LCMS_FLAGS)) == nullptr) {
1813 error(errSyntaxWarning, -1, "Can't create transform");
1814 transform = nullptr;
1815 } else {
1816 transform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
1817 }
1818 if (dcst == PT_RGB || dcst == PT_CMYK) {
1819 // create line transform only when the display is RGB type color space
1820 if ((transformA = cmsCreateTransform(profile.get(), CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == nullptr) {
1821 error(errSyntaxWarning, -1, "Can't create transform");
1822 lineTransform = nullptr;
1823 } else {
1824 lineTransform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
1825 }
1826 }
1827}
1828#endif
1829
1830void GfxICCBasedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1831{
1832#ifdef USE_CMS
1833 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1834 unsigned char in[gfxColorMaxComps];
1835 unsigned char out[gfxColorMaxComps];
1836
1837 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1838 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1839 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1840 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1841 } else {
1842 for (int i = 0; i < nComps; i++) {
1843 in[i] = colToByte(color->c[i]);
1844 }
1845 }
1846 if (nComps <= 4) {
1847 unsigned int key = 0;
1848 for (int j = 0; j < nComps; j++) {
1849 key = (key << 8) + in[j];
1850 }
1851 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1852 if (it != cmsCache.end()) {
1853 unsigned int value = it->second;
1854 *gray = byteToCol(value & 0xff);
1855 return;
1856 }
1857 }
1858 transform->doTransform(in, out, 1);
1859 *gray = byteToCol(out[0]);
1860 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1861 unsigned int key = 0;
1862 for (int j = 0; j < nComps; j++) {
1863 key = (key << 8) + in[j];
1864 }
1865 unsigned int value = out[0];
1866 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1867 }
1868 } else {
1869 GfxRGB rgb;
1870 getRGB(color, &rgb);
1871 *gray = clip01((GfxColorComp)(0.3 * rgb.r + 0.59 * rgb.g + 0.11 * rgb.b + 0.5));
1872 }
1873#else
1874 alt->getGray(color, gray);
1875#endif
1876}
1877
1878void GfxICCBasedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1879{
1880#ifdef USE_CMS
1881 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1882 unsigned char in[gfxColorMaxComps];
1883 unsigned char out[gfxColorMaxComps];
1884
1885 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1886 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1887 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1888 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1889 } else {
1890 for (int i = 0; i < nComps; i++) {
1891 in[i] = colToByte(color->c[i]);
1892 }
1893 }
1894 if (nComps <= 4) {
1895 unsigned int key = 0;
1896 for (int j = 0; j < nComps; j++) {
1897 key = (key << 8) + in[j];
1898 }
1899 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1900 if (it != cmsCache.end()) {
1901 unsigned int value = it->second;
1902 rgb->r = byteToCol(value >> 16);
1903 rgb->g = byteToCol((value >> 8) & 0xff);
1904 rgb->b = byteToCol(value & 0xff);
1905 return;
1906 }
1907 }
1908 transform->doTransform(in, out, 1);
1909 rgb->r = byteToCol(out[0]);
1910 rgb->g = byteToCol(out[1]);
1911 rgb->b = byteToCol(out[2]);
1912 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1913 unsigned int key = 0;
1914 for (int j = 0; j < nComps; j++) {
1915 key = (key << 8) + in[j];
1916 }
1917 unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2];
1918 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1919 }
1920 } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1921 unsigned char in[gfxColorMaxComps];
1922 unsigned char out[gfxColorMaxComps];
1923 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1924
1925 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1926 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1927 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1928 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1929 } else {
1930 for (int i = 0; i < nComps; i++) {
1931 in[i] = colToByte(color->c[i]);
1932 }
1933 }
1934 if (nComps <= 4) {
1935 unsigned int key = 0;
1936 for (int j = 0; j < nComps; j++) {
1937 key = (key << 8) + in[j];
1938 }
1939 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1940 if (it != cmsCache.end()) {
1941 unsigned int value = it->second;
1942 rgb->r = byteToCol(value >> 16);
1943 rgb->g = byteToCol((value >> 8) & 0xff);
1944 rgb->b = byteToCol(value & 0xff);
1945 return;
1946 }
1947 }
1948 transform->doTransform(in, out, 1);
1949 c = byteToDbl(out[0]);
1950 m = byteToDbl(out[1]);
1951 y = byteToDbl(out[2]);
1952 k = byteToDbl(out[3]);
1953 c1 = 1 - c;
1954 m1 = 1 - m;
1955 y1 = 1 - y;
1956 k1 = 1 - k;
1957 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1958 rgb->r = clip01(dblToCol(r));
1959 rgb->g = clip01(dblToCol(g));
1960 rgb->b = clip01(dblToCol(b));
1961 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1962 unsigned int key = 0;
1963 for (int j = 0; j < nComps; j++) {
1964 key = (key << 8) + in[j];
1965 }
1966 unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b);
1967 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1968 }
1969 } else {
1970 alt->getRGB(color, rgb);
1971 }
1972#else
1973 alt->getRGB(color, rgb);
1974#endif
1975}
1976
1977void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
1978{
1979#ifdef USE_CMS
1980 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
1981 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
1982 lineTransform->doTransform(in, tmp, length);
1983 for (int i = 0; i < length; ++i) {
1984 unsigned char *current = tmp + (i * 3);
1985 out[i] = (current[0] << 16) | (current[1] << 8) | current[2];
1986 }
1987 gfree(tmp);
1988 } else {
1989 alt->getRGBLine(in, out, length);
1990 }
1991#else
1992 alt->getRGBLine(in, out, length);
1993#endif
1994}
1995
1996void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
1997{
1998#ifdef USE_CMS
1999 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
2000 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2001 lineTransform->doTransform(in, tmp, length);
2002 unsigned char *current = tmp;
2003 for (int i = 0; i < length; ++i) {
2004 *out++ = *current++;
2005 *out++ = *current++;
2006 *out++ = *current++;
2007 }
2008 gfree(tmp);
2009 } else if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2010 unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
2011 lineTransform->doTransform(in, tmp, length);
2012 unsigned char *current = tmp;
2013 double c, m, y, k, c1, m1, y1, k1, r, g, b;
2014 for (int i = 0; i < length; ++i) {
2015 c = byteToDbl(*current++);
2016 m = byteToDbl(*current++);
2017 y = byteToDbl(*current++);
2018 k = byteToDbl(*current++);
2019 c1 = 1 - c;
2020 m1 = 1 - m;
2021 y1 = 1 - y;
2022 k1 = 1 - k;
2023 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
2024 *out++ = dblToByte(r);
2025 *out++ = dblToByte(g);
2026 *out++ = dblToByte(b);
2027 }
2028 gfree(tmp);
2029 } else {
2030 alt->getRGBLine(in, out, length);
2031 }
2032#else
2033 alt->getRGBLine(in, out, length);
2034#endif
2035}
2036
2037void GfxICCBasedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
2038{
2039#ifdef USE_CMS
2040 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
2041 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2042 lineTransform->doTransform(in, tmp, length);
2043 unsigned char *current = tmp;
2044 for (int i = 0; i < length; ++i) {
2045 *out++ = *current++;
2046 *out++ = *current++;
2047 *out++ = *current++;
2048 *out++ = 255;
2049 }
2050 gfree(tmp);
2051 } else {
2052 alt->getRGBXLine(in, out, length);
2053 }
2054#else
2055 alt->getRGBXLine(in, out, length);
2056#endif
2057}
2058
2059void GfxICCBasedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
2060{
2061#ifdef USE_CMS
2062 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2063 transform->doTransform(in, out, length);
2064 } else if (lineTransform != nullptr && nComps != 4) {
2065 GfxColorComp c, m, y, k;
2066 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2067 getRGBLine(in, tmp, length);
2068 unsigned char *p = tmp;
2069 for (int i = 0; i < length; i++) {
2070 c = byteToCol(255 - *p++);
2071 m = byteToCol(255 - *p++);
2072 y = byteToCol(255 - *p++);
2073 k = c;
2074 if (m < k) {
2075 k = m;
2076 }
2077 if (y < k) {
2078 k = y;
2079 }
2080 *out++ = colToByte(c - k);
2081 *out++ = colToByte(m - k);
2082 *out++ = colToByte(y - k);
2083 *out++ = colToByte(k);
2084 }
2085 gfree(tmp);
2086 } else {
2087 alt->getCMYKLine(in, out, length);
2088 }
2089#else
2090 alt->getCMYKLine(in, out, length);
2091#endif
2092}
2093
2094void GfxICCBasedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
2095{
2096#ifdef USE_CMS
2097 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2098 unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
2099 transform->doTransform(in, tmp, length);
2100 unsigned char *p = tmp;
2101 for (int i = 0; i < length; i++) {
2102 for (int j = 0; j < 4; j++) {
2103 *out++ = *p++;
2104 }
2105 for (int j = 4; j < SPOT_NCOMPS + 4; j++) {
2106 *out++ = 0;
2107 }
2108 }
2109 gfree(tmp);
2110 } else if (lineTransform != nullptr && nComps != 4) {
2111 GfxColorComp c, m, y, k;
2112 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2113 getRGBLine(in, tmp, length);
2114 unsigned char *p = tmp;
2115 for (int i = 0; i < length; i++) {
2116 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
2117 out[j] = 0;
2118 }
2119 c = byteToCol(255 - *p++);
2120 m = byteToCol(255 - *p++);
2121 y = byteToCol(255 - *p++);
2122 k = c;
2123 if (m < k) {
2124 k = m;
2125 }
2126 if (y < k) {
2127 k = y;
2128 }
2129 out[0] = colToByte(c - k);
2130 out[1] = colToByte(m - k);
2131 out[2] = colToByte(y - k);
2132 out[3] = colToByte(k);
2133 out += (SPOT_NCOMPS + 4);
2134 }
2135 gfree(tmp);
2136 } else {
2137 alt->getDeviceNLine(in, out, length);
2138 }
2139#else
2140 alt->getDeviceNLine(in, out, length);
2141#endif
2142}
2143
2144void GfxICCBasedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2145{
2146#ifdef USE_CMS
2147 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
2148 unsigned char in[gfxColorMaxComps];
2149 unsigned char out[gfxColorMaxComps];
2150
2151 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
2152 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
2153 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
2154 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
2155 } else {
2156 for (int i = 0; i < nComps; i++) {
2157 in[i] = colToByte(color->c[i]);
2158 }
2159 }
2160 if (nComps <= 4) {
2161 unsigned int key = 0;
2162 for (int j = 0; j < nComps; j++) {
2163 key = (key << 8) + in[j];
2164 }
2165 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
2166 if (it != cmsCache.end()) {
2167 unsigned int value = it->second;
2168 cmyk->c = byteToCol(value >> 24);
2169 cmyk->m = byteToCol((value >> 16) & 0xff);
2170 cmyk->y = byteToCol((value >> 8) & 0xff);
2171 cmyk->k = byteToCol(value & 0xff);
2172 return;
2173 }
2174 }
2175 transform->doTransform(in, out, 1);
2176 cmyk->c = byteToCol(out[0]);
2177 cmyk->m = byteToCol(out[1]);
2178 cmyk->y = byteToCol(out[2]);
2179 cmyk->k = byteToCol(out[3]);
2180 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
2181 unsigned int key = 0;
2182 for (int j = 0; j < nComps; j++) {
2183 key = (key << 8) + in[j];
2184 }
2185 unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3];
2186 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
2187 }
2188 } else if (nComps != 4 && transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
2189 GfxRGB rgb;
2190 GfxColorComp c, m, y, k;
2191
2192 getRGB(color, &rgb);
2193 c = clip01(gfxColorComp1 - rgb.r);
2194 m = clip01(gfxColorComp1 - rgb.g);
2195 y = clip01(gfxColorComp1 - rgb.b);
2196 k = c;
2197 if (m < k) {
2198 k = m;
2199 }
2200 if (y < k) {
2201 k = y;
2202 }
2203 cmyk->c = c - k;
2204 cmyk->m = m - k;
2205 cmyk->y = y - k;
2206 cmyk->k = k;
2207 } else {
2208 alt->getCMYK(color, cmyk);
2209 }
2210#else
2211 alt->getCMYK(color, cmyk);
2212#endif
2213}
2214
2215bool GfxICCBasedColorSpace::useGetRGBLine() const
2216{
2217#ifdef USE_CMS
2218 return lineTransform != nullptr || alt->useGetRGBLine();
2219#else
2220 return alt->useGetRGBLine();
2221#endif
2222}
2223
2224bool GfxICCBasedColorSpace::useGetCMYKLine() const
2225{
2226#ifdef USE_CMS
2227 return lineTransform != nullptr || alt->useGetCMYKLine();
2228#else
2229 return alt->useGetCMYKLine();
2230#endif
2231}
2232
2233bool GfxICCBasedColorSpace::useGetDeviceNLine() const
2234{
2235#ifdef USE_CMS
2236 return lineTransform != nullptr || alt->useGetDeviceNLine();
2237#else
2238 return alt->useGetDeviceNLine();
2239#endif
2240}
2241
2242void GfxICCBasedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2243{
2244 GfxCMYK cmyk;
2245 clearGfxColor(gfxColor: deviceN);
2246 getCMYK(color, cmyk: &cmyk);
2247 deviceN->c[0] = cmyk.c;
2248 deviceN->c[1] = cmyk.m;
2249 deviceN->c[2] = cmyk.y;
2250 deviceN->c[3] = cmyk.k;
2251}
2252
2253void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) const
2254{
2255 int i;
2256
2257 for (i = 0; i < nComps; ++i) {
2258 if (rangeMin[i] > 0) {
2259 color->c[i] = dblToCol(x: rangeMin[i]);
2260 } else if (rangeMax[i] < 0) {
2261 color->c[i] = dblToCol(x: rangeMax[i]);
2262 } else {
2263 color->c[i] = 0;
2264 }
2265 }
2266}
2267
2268void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
2269{
2270 alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel);
2271
2272#if 0
2273 // this is nominally correct, but some PDF files don't set the
2274 // correct ranges in the ICCBased dict
2275 int i;
2276
2277 for (i = 0; i < nComps; ++i) {
2278 decodeLow[i] = rangeMin[i];
2279 decodeRange[i] = rangeMax[i] - rangeMin[i];
2280 }
2281#endif
2282}
2283
2284#ifdef USE_CMS
2285char *GfxICCBasedColorSpace::getPostScriptCSA()
2286{
2287# if LCMS_VERSION >= 2070
2288 // The runtime version check of lcms2 is only available from release 2.7 upwards.
2289 // The generation of the CSA code only works reliably for version 2.10 and upwards.
2290 // Cf. the explanation in the corresponding lcms2 merge request [1], and the original mail thread [2].
2291 // [1] https://github.com/mm2/Little-CMS/pull/214
2292 // [2] https://sourceforge.net/p/lcms/mailman/message/33182987/
2293 if (cmsGetEncodedCMMversion() < 2100) {
2294 return nullptr;
2295 }
2296
2297 int size;
2298
2299 if (psCSA) {
2300 return psCSA;
2301 }
2302
2303 if (!profile) {
2304 error(errSyntaxWarning, -1, "profile is nullptr");
2305 return nullptr;
2306 }
2307
2308 void *rawprofile = profile.get();
2309 size = cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, nullptr, 0);
2310 if (size == 0) {
2311 error(errSyntaxWarning, -1, "PostScript CSA is nullptr");
2312 return nullptr;
2313 }
2314
2315 psCSA = (char *)gmalloc(size + 1);
2316 cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, psCSA, size);
2317 psCSA[size] = 0;
2318
2319 // TODO REMOVE-ME-IN-THE-FUTURE
2320 // until we can depend on https://github.com/mm2/Little-CMS/issues/223 being fixed
2321 // lcms returns ps code with , instead of . for some locales. The lcms author says
2322 // that there's no room for any , in the rest of the ps code, so replacing all the , with .
2323 // is an "acceptable" workaround
2324 for (int i = 0; i < size; ++i) {
2325 if (psCSA[i] == ',') {
2326 psCSA[i] = '.';
2327 }
2328 }
2329
2330 return psCSA;
2331# else
2332 return nullptr;
2333# endif
2334}
2335#endif
2336
2337//------------------------------------------------------------------------
2338// GfxIndexedColorSpace
2339//------------------------------------------------------------------------
2340
2341GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA, int indexHighA)
2342{
2343 base = baseA;
2344 indexHigh = indexHighA;
2345 lookup = (unsigned char *)gmallocn(count: (indexHigh + 1) * base->getNComps(), size: sizeof(unsigned char));
2346 overprintMask = base->getOverprintMask();
2347}
2348
2349GfxIndexedColorSpace::~GfxIndexedColorSpace()
2350{
2351 delete base;
2352 gfree(p: lookup);
2353}
2354
2355GfxColorSpace *GfxIndexedColorSpace::copy() const
2356{
2357 GfxIndexedColorSpace *cs;
2358
2359 cs = new GfxIndexedColorSpace(base->copy(), indexHigh);
2360 memcpy(dest: cs->lookup, src: lookup, n: (indexHigh + 1) * base->getNComps() * sizeof(unsigned char));
2361 return cs;
2362}
2363
2364GfxColorSpace *GfxIndexedColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2365{
2366 GfxColorSpace *baseA;
2367 int indexHighA;
2368 Object obj1;
2369 const char *s;
2370 int i, j;
2371
2372 if (arr->getLength() != 4) {
2373 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space");
2374 return nullptr;
2375 }
2376 obj1 = arr->get(i: 1);
2377 if (!(baseA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2378 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (base color space)");
2379 return nullptr;
2380 }
2381 obj1 = arr->get(i: 2);
2382 if (!obj1.isInt()) {
2383 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (hival)");
2384 delete baseA;
2385 return nullptr;
2386 }
2387 indexHighA = obj1.getInt();
2388 if (indexHighA < 0 || indexHighA > 255) {
2389 // the PDF spec requires indexHigh to be in [0,255] -- allowing
2390 // values larger than 255 creates a security hole: if nComps *
2391 // indexHigh is greater than 2^31, the loop below may overwrite
2392 // past the end of the array
2393 int previousValue = indexHighA;
2394 if (indexHighA < 0) {
2395 indexHighA = 0;
2396 } else {
2397 indexHighA = 255;
2398 }
2399 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (invalid indexHigh value, was {0:d} using {1:d} to try to recover)", previousValue, indexHighA);
2400 }
2401 GfxIndexedColorSpace *cs = new GfxIndexedColorSpace(baseA, indexHighA);
2402 obj1 = arr->get(i: 3);
2403 const int n = baseA->getNComps();
2404 if (obj1.isStream()) {
2405 obj1.streamReset();
2406 for (i = 0; i <= indexHighA; ++i) {
2407 const int readChars = obj1.streamGetChars(nChars: n, buffer: &cs->lookup[i * n]);
2408 for (j = readChars; j < n; ++j) {
2409 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table stream too short) padding with zeroes");
2410 cs->lookup[i * n + j] = 0;
2411 }
2412 }
2413 obj1.streamClose();
2414 } else if (obj1.isString()) {
2415 if (obj1.getString()->getLength() < (indexHighA + 1) * n) {
2416 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table string too short)");
2417 goto err3;
2418 }
2419 s = obj1.getString()->c_str();
2420 for (i = 0; i <= indexHighA; ++i) {
2421 for (j = 0; j < n; ++j) {
2422 cs->lookup[i * n + j] = (unsigned char)*s++;
2423 }
2424 }
2425 } else {
2426 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table)");
2427 goto err3;
2428 }
2429 return cs;
2430
2431err3:
2432 delete cs;
2433 return nullptr;
2434}
2435
2436GfxColor *GfxIndexedColorSpace::mapColorToBase(const GfxColor *color, GfxColor *baseColor) const
2437{
2438 unsigned char *p;
2439 double low[gfxColorMaxComps], range[gfxColorMaxComps];
2440 int n, i;
2441
2442 n = base->getNComps();
2443 base->getDefaultRanges(decodeLow: low, decodeRange: range, maxImgPixel: indexHigh);
2444 const int idx = (int)(colToDbl(x: color->c[0]) + 0.5) * n;
2445 if (likely((idx + n - 1 < (indexHigh + 1) * base->getNComps()) && idx >= 0)) {
2446 p = &lookup[idx];
2447 for (i = 0; i < n; ++i) {
2448 baseColor->c[i] = dblToCol(x: low[i] + (p[i] / 255.0) * range[i]);
2449 }
2450 } else {
2451 for (i = 0; i < n; ++i) {
2452 baseColor->c[i] = 0;
2453 }
2454 }
2455 return baseColor;
2456}
2457
2458void GfxIndexedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2459{
2460 GfxColor color2;
2461
2462 base->getGray(color: mapColorToBase(color, baseColor: &color2), gray);
2463}
2464
2465void GfxIndexedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
2466{
2467 GfxColor color2;
2468
2469 base->getRGB(color: mapColorToBase(color, baseColor: &color2), rgb);
2470}
2471
2472void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
2473{
2474 unsigned char *line;
2475 int i, j, n;
2476
2477 n = base->getNComps();
2478 line = (unsigned char *)gmallocn(count: length, size: n);
2479 for (i = 0; i < length; i++) {
2480 for (j = 0; j < n; j++) {
2481 line[i * n + j] = lookup[in[i] * n + j];
2482 }
2483 }
2484
2485 base->getRGBLine(line, out, length);
2486
2487 gfree(p: line);
2488}
2489
2490void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
2491{
2492 unsigned char *line;
2493 int i, j, n;
2494
2495 n = base->getNComps();
2496 line = (unsigned char *)gmallocn(count: length, size: n);
2497 for (i = 0; i < length; i++) {
2498 for (j = 0; j < n; j++) {
2499 line[i * n + j] = lookup[in[i] * n + j];
2500 }
2501 }
2502
2503 base->getRGBLine(line, out, length);
2504
2505 gfree(p: line);
2506}
2507
2508void GfxIndexedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
2509{
2510 unsigned char *line;
2511 int i, j, n;
2512
2513 n = base->getNComps();
2514 line = (unsigned char *)gmallocn(count: length, size: n);
2515 for (i = 0; i < length; i++) {
2516 for (j = 0; j < n; j++) {
2517 line[i * n + j] = lookup[in[i] * n + j];
2518 }
2519 }
2520
2521 base->getRGBXLine(line, out, length);
2522
2523 gfree(p: line);
2524}
2525
2526void GfxIndexedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
2527{
2528 unsigned char *line;
2529 int i, j, n;
2530
2531 n = base->getNComps();
2532 line = (unsigned char *)gmallocn(count: length, size: n);
2533 for (i = 0; i < length; i++) {
2534 for (j = 0; j < n; j++) {
2535 line[i * n + j] = lookup[in[i] * n + j];
2536 }
2537 }
2538
2539 base->getCMYKLine(line, out, length);
2540
2541 gfree(p: line);
2542}
2543
2544void GfxIndexedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
2545{
2546 unsigned char *line;
2547 int i, j, n;
2548
2549 n = base->getNComps();
2550 line = (unsigned char *)gmallocn(count: length, size: n);
2551 for (i = 0; i < length; i++) {
2552 for (j = 0; j < n; j++) {
2553 line[i * n + j] = lookup[in[i] * n + j];
2554 }
2555 }
2556
2557 base->getDeviceNLine(line, out, length);
2558
2559 gfree(p: line);
2560}
2561
2562void GfxIndexedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2563{
2564 GfxColor color2;
2565
2566 base->getCMYK(color: mapColorToBase(color, baseColor: &color2), cmyk);
2567}
2568
2569void GfxIndexedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2570{
2571 GfxColor color2;
2572
2573 base->getDeviceN(color: mapColorToBase(color, baseColor: &color2), deviceN);
2574}
2575
2576void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) const
2577{
2578 color->c[0] = 0;
2579}
2580
2581void GfxIndexedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
2582{
2583 decodeLow[0] = 0;
2584 decodeRange[0] = maxImgPixel;
2585}
2586
2587//------------------------------------------------------------------------
2588// GfxSeparationColorSpace
2589//------------------------------------------------------------------------
2590
2591GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA)
2592{
2593 name = nameA;
2594 alt = altA;
2595 func = funcA;
2596 nonMarking = !name->cmp(sA: "None");
2597 if (!name->cmp(sA: "Cyan")) {
2598 overprintMask = 0x01;
2599 } else if (!name->cmp(sA: "Magenta")) {
2600 overprintMask = 0x02;
2601 } else if (!name->cmp(sA: "Yellow")) {
2602 overprintMask = 0x04;
2603 } else if (!name->cmp(sA: "Black")) {
2604 overprintMask = 0x08;
2605 } else if (!name->cmp(sA: "All")) {
2606 overprintMask = 0xffffffff;
2607 }
2608}
2609
2610GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA, bool nonMarkingA, unsigned int overprintMaskA, int *mappingA)
2611{
2612 name = nameA;
2613 alt = altA;
2614 func = funcA;
2615 nonMarking = nonMarkingA;
2616 overprintMask = overprintMaskA;
2617 mapping = mappingA;
2618}
2619
2620GfxSeparationColorSpace::~GfxSeparationColorSpace()
2621{
2622 delete name;
2623 delete alt;
2624 delete func;
2625 if (mapping != nullptr) {
2626 gfree(p: mapping);
2627 }
2628}
2629
2630GfxColorSpace *GfxSeparationColorSpace::copy() const
2631{
2632 int *mappingA = nullptr;
2633 if (mapping != nullptr) {
2634 mappingA = (int *)gmalloc(size: sizeof(int));
2635 *mappingA = *mapping;
2636 }
2637 return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), nonMarking, overprintMask, mappingA);
2638}
2639
2640//~ handle the 'All' and 'None' colorants
2641GfxColorSpace *GfxSeparationColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2642{
2643 GooString *nameA;
2644 GfxColorSpace *altA;
2645 Function *funcA;
2646 Object obj1;
2647
2648 if (arr->getLength() != 4) {
2649 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space");
2650 goto err1;
2651 }
2652 obj1 = arr->get(i: 1);
2653 if (!obj1.isName()) {
2654 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space (name)");
2655 goto err1;
2656 }
2657 nameA = new GooString(obj1.getName());
2658 obj1 = arr->get(i: 2);
2659 if (!(altA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2660 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space (alternate color space)");
2661 goto err3;
2662 }
2663 obj1 = arr->get(i: 3);
2664 if (!(funcA = Function::parse(funcObj: &obj1))) {
2665 goto err4;
2666 }
2667 if (funcA->getInputSize() != 1) {
2668 error(category: errSyntaxWarning, pos: -1, msg: "Bad SeparationColorSpace function");
2669 goto err5;
2670 }
2671 if (altA->getNComps() <= funcA->getOutputSize()) {
2672 return new GfxSeparationColorSpace(nameA, altA, funcA);
2673 }
2674
2675err5:
2676 delete funcA;
2677err4:
2678 delete altA;
2679err3:
2680 delete nameA;
2681err1:
2682 return nullptr;
2683}
2684
2685void GfxSeparationColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2686{
2687 double x;
2688 double c[gfxColorMaxComps];
2689 GfxColor color2;
2690 int i;
2691
2692 if (alt->getMode() == csDeviceGray && name->cmp(sA: "Black") == 0) {
2693 *gray = clip01(gfxColorComp1 - color->c[0]);
2694 } else {
2695 x = colToDbl(x: color->c[0]);
2696 func->transform(in: &x, out: c);
2697 for (i = 0; i < alt->getNComps(); ++i) {
2698 color2.c[i] = dblToCol(x: c[i]);
2699 }
2700 alt->getGray(color: &color2, gray);
2701 }
2702}
2703
2704void GfxSeparationColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
2705{
2706 double x;
2707 double c[gfxColorMaxComps];
2708 GfxColor color2;
2709 int i;
2710
2711 if (alt->getMode() == csDeviceGray && name->cmp(sA: "Black") == 0) {
2712 rgb->r = clip01(gfxColorComp1 - color->c[0]);
2713 rgb->g = clip01(gfxColorComp1 - color->c[0]);
2714 rgb->b = clip01(gfxColorComp1 - color->c[0]);
2715 } else {
2716 x = colToDbl(x: color->c[0]);
2717 func->transform(in: &x, out: c);
2718 const int altNComps = alt->getNComps();
2719 for (i = 0; i < altNComps; ++i) {
2720 color2.c[i] = dblToCol(x: c[i]);
2721 }
2722 alt->getRGB(color: &color2, rgb);
2723 }
2724}
2725
2726void GfxSeparationColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2727{
2728 double x;
2729 double c[gfxColorMaxComps];
2730 GfxColor color2;
2731 int i;
2732
2733 if (name->cmp(sA: "Black") == 0) {
2734 cmyk->c = 0;
2735 cmyk->m = 0;
2736 cmyk->y = 0;
2737 cmyk->k = color->c[0];
2738 } else if (name->cmp(sA: "Cyan") == 0) {
2739 cmyk->c = color->c[0];
2740 cmyk->m = 0;
2741 cmyk->y = 0;
2742 cmyk->k = 0;
2743 } else if (name->cmp(sA: "Magenta") == 0) {
2744 cmyk->c = 0;
2745 cmyk->m = color->c[0];
2746 cmyk->y = 0;
2747 cmyk->k = 0;
2748 } else if (name->cmp(sA: "Yellow") == 0) {
2749 cmyk->c = 0;
2750 cmyk->m = 0;
2751 cmyk->y = color->c[0];
2752 cmyk->k = 0;
2753 } else {
2754 x = colToDbl(x: color->c[0]);
2755 func->transform(in: &x, out: c);
2756 for (i = 0; i < alt->getNComps(); ++i) {
2757 color2.c[i] = dblToCol(x: c[i]);
2758 }
2759 alt->getCMYK(color: &color2, cmyk);
2760 }
2761}
2762
2763void GfxSeparationColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2764{
2765 clearGfxColor(gfxColor: deviceN);
2766 if (mapping == nullptr || mapping[0] == -1) {
2767 GfxCMYK cmyk;
2768
2769 getCMYK(color, cmyk: &cmyk);
2770 deviceN->c[0] = cmyk.c;
2771 deviceN->c[1] = cmyk.m;
2772 deviceN->c[2] = cmyk.y;
2773 deviceN->c[3] = cmyk.k;
2774 } else {
2775 deviceN->c[mapping[0]] = color->c[0];
2776 }
2777}
2778
2779void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) const
2780{
2781 color->c[0] = gfxColorComp1;
2782}
2783
2784void GfxSeparationColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
2785{
2786 if (nonMarking) {
2787 return;
2788 }
2789 mapping = (int *)gmalloc(size: sizeof(int));
2790 switch (overprintMask) {
2791 case 0x01:
2792 *mapping = 0;
2793 break;
2794 case 0x02:
2795 *mapping = 1;
2796 break;
2797 case 0x04:
2798 *mapping = 2;
2799 break;
2800 case 0x08:
2801 *mapping = 3;
2802 break;
2803 default:
2804 unsigned int newOverprintMask = 0x10;
2805 for (std::size_t i = 0; i < separationList->size(); i++) {
2806 GfxSeparationColorSpace *sepCS = (*separationList)[i];
2807 if (!sepCS->getName()->cmp(str: name)) {
2808 if (sepCS->getFunc()->hasDifferentResultSet(func)) {
2809 error(category: errSyntaxWarning, pos: -1, msg: "Different functions found for '{0:t}', convert immediately", name);
2810 gfree(p: mapping);
2811 mapping = nullptr;
2812 return;
2813 }
2814 *mapping = i + 4;
2815 overprintMask = newOverprintMask;
2816 return;
2817 }
2818 newOverprintMask <<= 1;
2819 }
2820 if ((int)separationList->size() == maxSepComps) {
2821 error(category: errSyntaxWarning, pos: -1, msg: "Too many ({0:d}) spots, convert '{1:t}' immediately", maxSepComps, name);
2822 gfree(p: mapping);
2823 mapping = nullptr;
2824 return;
2825 }
2826 *mapping = separationList->size() + 4;
2827 separationList->push_back(x: (GfxSeparationColorSpace *)copy());
2828 overprintMask = newOverprintMask;
2829 break;
2830 }
2831}
2832
2833//------------------------------------------------------------------------
2834// GfxDeviceNColorSpace
2835//------------------------------------------------------------------------
2836
2837GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, std::vector<std::string> &&namesA, GfxColorSpace *altA, Function *funcA, std::vector<GfxSeparationColorSpace *> *sepsCSA) : nComps(nCompsA), names(std::move(namesA))
2838{
2839 alt = altA;
2840 func = funcA;
2841 sepsCS = sepsCSA;
2842 nonMarking = true;
2843 overprintMask = 0;
2844 mapping = nullptr;
2845 for (int i = 0; i < nComps; ++i) {
2846 if (names[i] != "None") {
2847 nonMarking = false;
2848 }
2849 if (names[i] == "Cyan") {
2850 overprintMask |= 0x01;
2851 } else if (names[i] == "Magenta") {
2852 overprintMask |= 0x02;
2853 } else if (names[i] == "Yellow") {
2854 overprintMask |= 0x04;
2855 } else if (names[i] == "Black") {
2856 overprintMask |= 0x08;
2857 } else if (names[i] == "All") {
2858 overprintMask = 0xffffffff;
2859 } else if (names[i] != "None") {
2860 overprintMask = 0x0f;
2861 }
2862 }
2863}
2864
2865GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, const std::vector<std::string> &namesA, GfxColorSpace *altA, Function *funcA, std::vector<GfxSeparationColorSpace *> *sepsCSA, int *mappingA, bool nonMarkingA,
2866 unsigned int overprintMaskA)
2867 : nComps(nCompsA), names(namesA)
2868{
2869 alt = altA;
2870 func = funcA;
2871 sepsCS = sepsCSA;
2872 mapping = mappingA;
2873 nonMarking = nonMarkingA;
2874 overprintMask = overprintMaskA;
2875}
2876
2877GfxDeviceNColorSpace::~GfxDeviceNColorSpace()
2878{
2879 delete alt;
2880 delete func;
2881 for (auto entry : *sepsCS) {
2882 delete entry;
2883 }
2884 delete sepsCS;
2885 if (mapping != nullptr) {
2886 gfree(p: mapping);
2887 }
2888}
2889
2890GfxColorSpace *GfxDeviceNColorSpace::copy() const
2891{
2892 int *mappingA = nullptr;
2893
2894 auto sepsCSA = new std::vector<GfxSeparationColorSpace *>();
2895 sepsCSA->reserve(n: sepsCS->size());
2896 for (const GfxSeparationColorSpace *scs : *sepsCS) {
2897 if (likely(scs != nullptr)) {
2898 sepsCSA->push_back(x: (GfxSeparationColorSpace *)scs->copy());
2899 }
2900 }
2901 if (mapping != nullptr) {
2902 mappingA = (int *)gmalloc(size: sizeof(int) * nComps);
2903 for (int i = 0; i < nComps; i++) {
2904 mappingA[i] = mapping[i];
2905 }
2906 }
2907 return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), sepsCSA, mappingA, nonMarking, overprintMask);
2908}
2909
2910//~ handle the 'None' colorant
2911GfxColorSpace *GfxDeviceNColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2912{
2913 int nCompsA;
2914 std::vector<std::string> namesA;
2915 GfxColorSpace *altA;
2916 Function *funcA;
2917 Object obj1;
2918 auto separationList = new std::vector<GfxSeparationColorSpace *>();
2919
2920 if (arr->getLength() != 4 && arr->getLength() != 5) {
2921 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space");
2922 goto err1;
2923 }
2924 obj1 = arr->get(i: 1);
2925 if (!obj1.isArray()) {
2926 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (names)");
2927 goto err1;
2928 }
2929 nCompsA = obj1.arrayGetLength();
2930 if (nCompsA > gfxColorMaxComps) {
2931 error(category: errSyntaxWarning, pos: -1, msg: "DeviceN color space with too many ({0:d} > {1:d}) components", nCompsA, gfxColorMaxComps);
2932 nCompsA = gfxColorMaxComps;
2933 }
2934 for (int i = 0; i < nCompsA; ++i) {
2935 Object obj2 = obj1.arrayGet(i);
2936 if (!obj2.isName()) {
2937 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (names)");
2938 nCompsA = i;
2939 goto err1;
2940 }
2941 namesA.emplace_back(args: obj2.getName());
2942 }
2943 obj1 = arr->get(i: 2);
2944 if (!(altA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2945 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (alternate color space)");
2946 goto err1;
2947 }
2948 obj1 = arr->get(i: 3);
2949 if (!(funcA = Function::parse(funcObj: &obj1))) {
2950 goto err4;
2951 }
2952 if (arr->getLength() == 5) {
2953 obj1 = arr->get(i: 4);
2954 if (!obj1.isDict()) {
2955 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (attributes)");
2956 goto err5;
2957 }
2958 Dict *attribs = obj1.getDict();
2959 Object obj2 = attribs->lookup(key: "Colorants");
2960 if (obj2.isDict()) {
2961 Dict *colorants = obj2.getDict();
2962 for (int i = 0; i < colorants->getLength(); i++) {
2963 Object obj3 = colorants->getVal(i);
2964 if (obj3.isArray()) {
2965 GfxSeparationColorSpace *cs = (GfxSeparationColorSpace *)GfxSeparationColorSpace::parse(res, arr: obj3.getArray(), out, state, recursion);
2966 if (cs) {
2967 separationList->push_back(x: cs);
2968 }
2969 } else {
2970 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (colorant value entry is not an Array)");
2971 goto err5;
2972 }
2973 }
2974 }
2975 }
2976
2977 if (likely(nCompsA >= funcA->getInputSize() && altA->getNComps() <= funcA->getOutputSize())) {
2978 return new GfxDeviceNColorSpace(nCompsA, std::move(namesA), altA, funcA, separationList);
2979 }
2980
2981err5:
2982 delete funcA;
2983err4:
2984 delete altA;
2985err1:
2986 delete separationList;
2987 return nullptr;
2988}
2989
2990void GfxDeviceNColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2991{
2992 double x[gfxColorMaxComps], c[gfxColorMaxComps];
2993 GfxColor color2;
2994 int i;
2995
2996 for (i = 0; i < nComps; ++i) {
2997 x[i] = colToDbl(x: color->c[i]);
2998 }
2999 func->transform(in: x, out: c);
3000 for (i = 0; i < alt->getNComps(); ++i) {
3001 color2.c[i] = dblToCol(x: c[i]);
3002 }
3003 alt->getGray(color: &color2, gray);
3004}
3005
3006void GfxDeviceNColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
3007{
3008 double x[gfxColorMaxComps], c[gfxColorMaxComps];
3009 GfxColor color2;
3010 int i;
3011
3012 for (i = 0; i < nComps; ++i) {
3013 x[i] = colToDbl(x: color->c[i]);
3014 }
3015 func->transform(in: x, out: c);
3016 for (i = 0; i < alt->getNComps(); ++i) {
3017 color2.c[i] = dblToCol(x: c[i]);
3018 }
3019 alt->getRGB(color: &color2, rgb);
3020}
3021
3022void GfxDeviceNColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
3023{
3024 double x[gfxColorMaxComps], c[gfxColorMaxComps];
3025 GfxColor color2;
3026 int i;
3027
3028 for (i = 0; i < nComps; ++i) {
3029 x[i] = colToDbl(x: color->c[i]);
3030 }
3031 func->transform(in: x, out: c);
3032 for (i = 0; i < alt->getNComps(); ++i) {
3033 color2.c[i] = dblToCol(x: c[i]);
3034 }
3035 alt->getCMYK(color: &color2, cmyk);
3036}
3037
3038void GfxDeviceNColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
3039{
3040 clearGfxColor(gfxColor: deviceN);
3041 if (mapping == nullptr) {
3042 GfxCMYK cmyk;
3043
3044 getCMYK(color, cmyk: &cmyk);
3045 deviceN->c[0] = cmyk.c;
3046 deviceN->c[1] = cmyk.m;
3047 deviceN->c[2] = cmyk.y;
3048 deviceN->c[3] = cmyk.k;
3049 } else {
3050 for (int j = 0; j < nComps; j++) {
3051 if (mapping[j] != -1) {
3052 deviceN->c[mapping[j]] = color->c[j];
3053 }
3054 }
3055 }
3056}
3057
3058void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) const
3059{
3060 int i;
3061
3062 for (i = 0; i < nComps; ++i) {
3063 color->c[i] = gfxColorComp1;
3064 }
3065}
3066
3067void GfxDeviceNColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
3068{
3069 if (nonMarking) { // None
3070 return;
3071 }
3072 mapping = (int *)gmalloc(size: sizeof(int) * nComps);
3073 unsigned int newOverprintMask = 0;
3074 for (int i = 0; i < nComps; i++) {
3075 if (names[i] == "None") {
3076 mapping[i] = -1;
3077 } else if (names[i] == "Cyan") {
3078 newOverprintMask |= 0x01;
3079 mapping[i] = 0;
3080 } else if (names[i] == "Magenta") {
3081 newOverprintMask |= 0x02;
3082 mapping[i] = 1;
3083 } else if (names[i] == "Yellow") {
3084 newOverprintMask |= 0x04;
3085 mapping[i] = 2;
3086 } else if (names[i] == "Black") {
3087 newOverprintMask |= 0x08;
3088 mapping[i] = 3;
3089 } else {
3090 unsigned int startOverprintMask = 0x10;
3091 bool found = false;
3092 const Function *sepFunc = nullptr;
3093 if (nComps == 1) {
3094 sepFunc = func;
3095 } else {
3096 for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
3097 if (!sepCS->getName()->cmp(str: names[i])) {
3098 sepFunc = sepCS->getFunc();
3099 break;
3100 }
3101 }
3102 }
3103 for (std::size_t j = 0; j < separationList->size(); j++) {
3104 GfxSeparationColorSpace *sepCS = (*separationList)[j];
3105 if (!sepCS->getName()->cmp(str: names[i])) {
3106 if (sepFunc != nullptr && sepCS->getFunc()->hasDifferentResultSet(func: sepFunc)) {
3107 error(category: errSyntaxWarning, pos: -1, msg: "Different functions found for '{0:s}', convert immediately", names[i].c_str());
3108 gfree(p: mapping);
3109 mapping = nullptr;
3110 overprintMask = 0xffffffff;
3111 return;
3112 }
3113 mapping[i] = j + 4;
3114 newOverprintMask |= startOverprintMask;
3115 found = true;
3116 break;
3117 }
3118 startOverprintMask <<= 1;
3119 }
3120 if (!found) {
3121 if ((int)separationList->size() == maxSepComps) {
3122 error(category: errSyntaxWarning, pos: -1, msg: "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, names[i].c_str());
3123 gfree(p: mapping);
3124 mapping = nullptr;
3125 overprintMask = 0xffffffff;
3126 return;
3127 }
3128 mapping[i] = separationList->size() + 4;
3129 newOverprintMask |= startOverprintMask;
3130 if (nComps == 1) {
3131 separationList->push_back(x: new GfxSeparationColorSpace(new GooString(names[i]), alt->copy(), func->copy()));
3132 } else {
3133 for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
3134 if (!sepCS->getName()->cmp(str: names[i])) {
3135 found = true;
3136 separationList->push_back(x: (GfxSeparationColorSpace *)sepCS->copy());
3137 break;
3138 }
3139 }
3140 if (!found) {
3141 error(category: errSyntaxWarning, pos: -1, msg: "DeviceN has no suitable colorant");
3142 gfree(p: mapping);
3143 mapping = nullptr;
3144 overprintMask = 0xffffffff;
3145 return;
3146 }
3147 }
3148 }
3149 }
3150 }
3151 overprintMask = newOverprintMask;
3152}
3153
3154//------------------------------------------------------------------------
3155// GfxPatternColorSpace
3156//------------------------------------------------------------------------
3157
3158GfxPatternColorSpace::GfxPatternColorSpace(GfxColorSpace *underA)
3159{
3160 under = underA;
3161}
3162
3163GfxPatternColorSpace::~GfxPatternColorSpace()
3164{
3165 if (under) {
3166 delete under;
3167 }
3168}
3169
3170GfxColorSpace *GfxPatternColorSpace::copy() const
3171{
3172 return new GfxPatternColorSpace(under ? under->copy() : nullptr);
3173}
3174
3175GfxColorSpace *GfxPatternColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
3176{
3177 GfxPatternColorSpace *cs;
3178 GfxColorSpace *underA;
3179 Object obj1;
3180
3181 if (arr->getLength() != 1 && arr->getLength() != 2) {
3182 error(category: errSyntaxWarning, pos: -1, msg: "Bad Pattern color space");
3183 return nullptr;
3184 }
3185 underA = nullptr;
3186 if (arr->getLength() == 2) {
3187 obj1 = arr->get(i: 1);
3188 if (!(underA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
3189 error(category: errSyntaxWarning, pos: -1, msg: "Bad Pattern color space (underlying color space)");
3190 return nullptr;
3191 }
3192 }
3193 cs = new GfxPatternColorSpace(underA);
3194 return cs;
3195}
3196
3197void GfxPatternColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
3198{
3199 *gray = 0;
3200}
3201
3202void GfxPatternColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
3203{
3204 rgb->r = rgb->g = rgb->b = 0;
3205}
3206
3207void GfxPatternColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
3208{
3209 cmyk->c = cmyk->m = cmyk->y = 0;
3210 cmyk->k = 1;
3211}
3212
3213void GfxPatternColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
3214{
3215 clearGfxColor(gfxColor: deviceN);
3216 deviceN->c[3] = 1;
3217}
3218
3219void GfxPatternColorSpace::getDefaultColor(GfxColor *color) const
3220{
3221 color->c[0] = 0;
3222}
3223
3224//------------------------------------------------------------------------
3225// Pattern
3226//------------------------------------------------------------------------
3227
3228GfxPattern::GfxPattern(int typeA, int patternRefNumA) : type(typeA), patternRefNum(patternRefNumA) { }
3229
3230GfxPattern::~GfxPattern() { }
3231
3232GfxPattern *GfxPattern::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state, int patternRefNum)
3233{
3234 GfxPattern *pattern;
3235 Object obj1;
3236
3237 if (obj->isDict()) {
3238 obj1 = obj->dictLookup(key: "PatternType");
3239 } else if (obj->isStream()) {
3240 obj1 = obj->streamGetDict()->lookup(key: "PatternType");
3241 } else {
3242 return nullptr;
3243 }
3244 pattern = nullptr;
3245 if (obj1.isInt() && obj1.getInt() == 1) {
3246 pattern = GfxTilingPattern::parse(patObj: obj, patternRefNum);
3247 } else if (obj1.isInt() && obj1.getInt() == 2) {
3248 pattern = GfxShadingPattern::parse(res, patObj: obj, out, state, patternRefNum);
3249 }
3250 return pattern;
3251}
3252
3253//------------------------------------------------------------------------
3254// GfxTilingPattern
3255//------------------------------------------------------------------------
3256
3257GfxTilingPattern *GfxTilingPattern::parse(Object *patObj, int patternRefNum)
3258{
3259 Dict *dict;
3260 int paintTypeA, tilingTypeA;
3261 double bboxA[4], matrixA[6];
3262 double xStepA, yStepA;
3263 Object resDictA;
3264 Object obj1;
3265 int i;
3266
3267 if (!patObj->isStream()) {
3268 return nullptr;
3269 }
3270 dict = patObj->streamGetDict();
3271
3272 obj1 = dict->lookup(key: "PaintType");
3273 if (obj1.isInt()) {
3274 paintTypeA = obj1.getInt();
3275 } else {
3276 paintTypeA = 1;
3277 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing PaintType in pattern");
3278 }
3279 obj1 = dict->lookup(key: "TilingType");
3280 if (obj1.isInt()) {
3281 tilingTypeA = obj1.getInt();
3282 } else {
3283 tilingTypeA = 1;
3284 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing TilingType in pattern");
3285 }
3286 bboxA[0] = bboxA[1] = 0;
3287 bboxA[2] = bboxA[3] = 1;
3288 obj1 = dict->lookup(key: "BBox");
3289 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
3290 for (i = 0; i < 4; ++i) {
3291 Object obj2 = obj1.arrayGet(i);
3292 if (obj2.isNum()) {
3293 bboxA[i] = obj2.getNum();
3294 }
3295 }
3296 } else {
3297 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing BBox in pattern");
3298 }
3299 obj1 = dict->lookup(key: "XStep");
3300 if (obj1.isNum()) {
3301 xStepA = obj1.getNum();
3302 } else {
3303 xStepA = 1;
3304 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing XStep in pattern");
3305 }
3306 obj1 = dict->lookup(key: "YStep");
3307 if (obj1.isNum()) {
3308 yStepA = obj1.getNum();
3309 } else {
3310 yStepA = 1;
3311 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing YStep in pattern");
3312 }
3313 resDictA = dict->lookup(key: "Resources");
3314 if (!resDictA.isDict()) {
3315 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing Resources in pattern");
3316 }
3317 matrixA[0] = 1;
3318 matrixA[1] = 0;
3319 matrixA[2] = 0;
3320 matrixA[3] = 1;
3321 matrixA[4] = 0;
3322 matrixA[5] = 0;
3323 obj1 = dict->lookup(key: "Matrix");
3324 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3325 for (i = 0; i < 6; ++i) {
3326 Object obj2 = obj1.arrayGet(i);
3327 if (obj2.isNum()) {
3328 matrixA[i] = obj2.getNum();
3329 }
3330 }
3331 }
3332
3333 return new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA, &resDictA, matrixA, patObj, patternRefNum);
3334}
3335
3336GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, const double *bboxA, double xStepA, double yStepA, const Object *resDictA, const double *matrixA, const Object *contentStreamA, int patternRefNumA)
3337 : GfxPattern(1, patternRefNumA)
3338{
3339 int i;
3340
3341 paintType = paintTypeA;
3342 tilingType = tilingTypeA;
3343 for (i = 0; i < 4; ++i) {
3344 bbox[i] = bboxA[i];
3345 }
3346 xStep = xStepA;
3347 yStep = yStepA;
3348 resDict = resDictA->copy();
3349 for (i = 0; i < 6; ++i) {
3350 matrix[i] = matrixA[i];
3351 }
3352 contentStream = contentStreamA->copy();
3353}
3354
3355GfxTilingPattern::~GfxTilingPattern() { }
3356
3357GfxPattern *GfxTilingPattern::copy() const
3358{
3359 return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep, &resDict, matrix, &contentStream, getPatternRefNum());
3360}
3361
3362//------------------------------------------------------------------------
3363// GfxShadingPattern
3364//------------------------------------------------------------------------
3365
3366GfxShadingPattern *GfxShadingPattern::parse(GfxResources *res, Object *patObj, OutputDev *out, GfxState *state, int patternRefNum)
3367{
3368 Dict *dict;
3369 GfxShading *shadingA;
3370 double matrixA[6];
3371 Object obj1;
3372 int i;
3373
3374 if (!patObj->isDict()) {
3375 return nullptr;
3376 }
3377 dict = patObj->getDict();
3378
3379 obj1 = dict->lookup(key: "Shading");
3380 shadingA = GfxShading::parse(res, obj: &obj1, out, state);
3381 if (!shadingA) {
3382 return nullptr;
3383 }
3384
3385 matrixA[0] = 1;
3386 matrixA[1] = 0;
3387 matrixA[2] = 0;
3388 matrixA[3] = 1;
3389 matrixA[4] = 0;
3390 matrixA[5] = 0;
3391 obj1 = dict->lookup(key: "Matrix");
3392 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3393 for (i = 0; i < 6; ++i) {
3394 Object obj2 = obj1.arrayGet(i);
3395 if (obj2.isNum()) {
3396 matrixA[i] = obj2.getNum();
3397 }
3398 }
3399 }
3400
3401 return new GfxShadingPattern(shadingA, matrixA, patternRefNum);
3402}
3403
3404GfxShadingPattern::GfxShadingPattern(GfxShading *shadingA, const double *matrixA, int patternRefNumA) : GfxPattern(2, patternRefNumA)
3405{
3406 int i;
3407
3408 shading = shadingA;
3409 for (i = 0; i < 6; ++i) {
3410 matrix[i] = matrixA[i];
3411 }
3412}
3413
3414GfxShadingPattern::~GfxShadingPattern()
3415{
3416 delete shading;
3417}
3418
3419GfxPattern *GfxShadingPattern::copy() const
3420{
3421 return new GfxShadingPattern(shading->copy(), matrix, getPatternRefNum());
3422}
3423
3424//------------------------------------------------------------------------
3425// GfxShading
3426//------------------------------------------------------------------------
3427
3428GfxShading::GfxShading(int typeA)
3429{
3430 type = typeA;
3431 colorSpace = nullptr;
3432}
3433
3434GfxShading::GfxShading(const GfxShading *shading)
3435{
3436 int i;
3437
3438 type = shading->type;
3439 colorSpace = shading->colorSpace->copy();
3440 for (i = 0; i < gfxColorMaxComps; ++i) {
3441 background.c[i] = shading->background.c[i];
3442 }
3443 hasBackground = shading->hasBackground;
3444 bbox_xMin = shading->bbox_xMin;
3445 bbox_yMin = shading->bbox_yMin;
3446 bbox_xMax = shading->bbox_xMax;
3447 bbox_yMax = shading->bbox_yMax;
3448 hasBBox = shading->hasBBox;
3449}
3450
3451GfxShading::~GfxShading()
3452{
3453 if (colorSpace) {
3454 delete colorSpace;
3455 }
3456}
3457
3458GfxShading *GfxShading::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state)
3459{
3460 GfxShading *shading;
3461 Dict *dict;
3462 int typeA;
3463 Object obj1;
3464
3465 if (obj->isDict()) {
3466 dict = obj->getDict();
3467 } else if (obj->isStream()) {
3468 dict = obj->streamGetDict();
3469 } else {
3470 return nullptr;
3471 }
3472
3473 obj1 = dict->lookup(key: "ShadingType");
3474 if (!obj1.isInt()) {
3475 error(category: errSyntaxWarning, pos: -1, msg: "Invalid ShadingType in shading dictionary");
3476 return nullptr;
3477 }
3478 typeA = obj1.getInt();
3479
3480 switch (typeA) {
3481 case 1:
3482 shading = GfxFunctionShading::parse(res, dict, out, state);
3483 break;
3484 case 2:
3485 shading = GfxAxialShading::parse(res, dict, out, state);
3486 break;
3487 case 3:
3488 shading = GfxRadialShading::parse(res, dict, out, state);
3489 break;
3490 case 4:
3491 if (obj->isStream()) {
3492 shading = GfxGouraudTriangleShading::parse(res, typeA: 4, dict, str: obj->getStream(), out, state);
3493 } else {
3494 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 4 shading object");
3495 goto err1;
3496 }
3497 break;
3498 case 5:
3499 if (obj->isStream()) {
3500 shading = GfxGouraudTriangleShading::parse(res, typeA: 5, dict, str: obj->getStream(), out, state);
3501 } else {
3502 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 5 shading object");
3503 goto err1;
3504 }
3505 break;
3506 case 6:
3507 if (obj->isStream()) {
3508 shading = GfxPatchMeshShading::parse(res, typeA: 6, dict, str: obj->getStream(), out, state);
3509 } else {
3510 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 6 shading object");
3511 goto err1;
3512 }
3513 break;
3514 case 7:
3515 if (obj->isStream()) {
3516 shading = GfxPatchMeshShading::parse(res, typeA: 7, dict, str: obj->getStream(), out, state);
3517 } else {
3518 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 7 shading object");
3519 goto err1;
3520 }
3521 break;
3522 default:
3523 error(category: errSyntaxWarning, pos: -1, msg: "Unimplemented shading type {0:d}", typeA);
3524 goto err1;
3525 }
3526
3527 return shading;
3528
3529err1:
3530 return nullptr;
3531}
3532
3533bool GfxShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3534{
3535 Object obj1;
3536 int i;
3537
3538 obj1 = dict->lookup(key: "ColorSpace");
3539 if (!(colorSpace = GfxColorSpace::parse(res, csObj: &obj1, out, state))) {
3540 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space in shading dictionary");
3541 return false;
3542 }
3543
3544 for (i = 0; i < gfxColorMaxComps; ++i) {
3545 background.c[i] = 0;
3546 }
3547 hasBackground = false;
3548 obj1 = dict->lookup(key: "Background");
3549 if (obj1.isArray()) {
3550 if (obj1.arrayGetLength() == colorSpace->getNComps()) {
3551 hasBackground = true;
3552 for (i = 0; i < colorSpace->getNComps(); ++i) {
3553 Object obj2 = obj1.arrayGet(i);
3554 background.c[i] = dblToCol(x: obj2.getNum(ok: &hasBackground));
3555 }
3556 if (!hasBackground) {
3557 error(category: errSyntaxWarning, pos: -1, msg: "Bad Background in shading dictionary");
3558 }
3559 } else {
3560 error(category: errSyntaxWarning, pos: -1, msg: "Bad Background in shading dictionary");
3561 }
3562 }
3563
3564 bbox_xMin = bbox_yMin = bbox_xMax = bbox_yMax = 0;
3565 hasBBox = false;
3566 obj1 = dict->lookup(key: "BBox");
3567 if (obj1.isArray()) {
3568 if (obj1.arrayGetLength() == 4) {
3569 hasBBox = true;
3570 bbox_xMin = obj1.arrayGet(i: 0).getNum(ok: &hasBBox);
3571 bbox_yMin = obj1.arrayGet(i: 1).getNum(ok: &hasBBox);
3572 bbox_xMax = obj1.arrayGet(i: 2).getNum(ok: &hasBBox);
3573 bbox_yMax = obj1.arrayGet(i: 3).getNum(ok: &hasBBox);
3574 if (!hasBBox) {
3575 error(category: errSyntaxWarning, pos: -1, msg: "Bad BBox in shading dictionary (Values not numbers)");
3576 }
3577 } else {
3578 error(category: errSyntaxWarning, pos: -1, msg: "Bad BBox in shading dictionary");
3579 }
3580 }
3581
3582 return true;
3583}
3584
3585//------------------------------------------------------------------------
3586// GfxFunctionShading
3587//------------------------------------------------------------------------
3588
3589GfxFunctionShading::GfxFunctionShading(double x0A, double y0A, double x1A, double y1A, const double *matrixA, std::vector<std::unique_ptr<Function>> &&funcsA) : GfxShading(1), funcs(std::move(funcsA))
3590{
3591 x0 = x0A;
3592 y0 = y0A;
3593 x1 = x1A;
3594 y1 = y1A;
3595 for (int i = 0; i < 6; ++i) {
3596 matrix[i] = matrixA[i];
3597 }
3598}
3599
3600GfxFunctionShading::GfxFunctionShading(const GfxFunctionShading *shading) : GfxShading(shading)
3601{
3602 x0 = shading->x0;
3603 y0 = shading->y0;
3604 x1 = shading->x1;
3605 y1 = shading->y1;
3606 for (int i = 0; i < 6; ++i) {
3607 matrix[i] = shading->matrix[i];
3608 }
3609 for (const auto &f : shading->funcs) {
3610 funcs.emplace_back(args: f->copy());
3611 }
3612}
3613
3614GfxFunctionShading::~GfxFunctionShading() { }
3615
3616GfxFunctionShading *GfxFunctionShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3617{
3618 GfxFunctionShading *shading;
3619 double x0A, y0A, x1A, y1A;
3620 double matrixA[6];
3621 std::vector<std::unique_ptr<Function>> funcsA;
3622 Object obj1;
3623 int i;
3624
3625 x0A = y0A = 0;
3626 x1A = y1A = 1;
3627 obj1 = dict->lookup(key: "Domain");
3628 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
3629 bool decodeOk = true;
3630 x0A = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
3631 x1A = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
3632 y0A = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
3633 y1A = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
3634
3635 if (!decodeOk) {
3636 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Domain array in function shading dictionary");
3637 return nullptr;
3638 }
3639 }
3640
3641 matrixA[0] = 1;
3642 matrixA[1] = 0;
3643 matrixA[2] = 0;
3644 matrixA[3] = 1;
3645 matrixA[4] = 0;
3646 matrixA[5] = 0;
3647 obj1 = dict->lookup(key: "Matrix");
3648 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3649 bool decodeOk = true;
3650 matrixA[0] = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
3651 matrixA[1] = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
3652 matrixA[2] = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
3653 matrixA[3] = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
3654 matrixA[4] = obj1.arrayGet(i: 4).getNum(ok: &decodeOk);
3655 matrixA[5] = obj1.arrayGet(i: 5).getNum(ok: &decodeOk);
3656
3657 if (!decodeOk) {
3658 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Matrix array in function shading dictionary");
3659 return nullptr;
3660 }
3661 }
3662
3663 obj1 = dict->lookup(key: "Function");
3664 if (obj1.isArray()) {
3665 const int nFuncsA = obj1.arrayGetLength();
3666 if (nFuncsA > gfxColorMaxComps || nFuncsA <= 0) {
3667 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
3668 return nullptr;
3669 }
3670 for (i = 0; i < nFuncsA; ++i) {
3671 Object obj2 = obj1.arrayGet(i);
3672 Function *f = Function::parse(funcObj: &obj2);
3673 if (!f) {
3674 return nullptr;
3675 }
3676 funcsA.emplace_back(args&: f);
3677 }
3678 } else {
3679 Function *f = Function::parse(funcObj: &obj1);
3680 if (!f) {
3681 return nullptr;
3682 }
3683 funcsA.emplace_back(args&: f);
3684 }
3685
3686 shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA, std::move(funcsA));
3687 if (!shading->init(res, dict, out, state)) {
3688 delete shading;
3689 return nullptr;
3690 }
3691 return shading;
3692}
3693
3694bool GfxFunctionShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3695{
3696 const bool parentInit = GfxShading::init(res, dict, out, state);
3697 if (!parentInit) {
3698 return false;
3699 }
3700
3701 // funcs needs to be one of the two:
3702 // * One function 2-in -> nComps-out
3703 // * nComps functions 2-in -> 1-out
3704 const int nComps = colorSpace->getNComps();
3705 const int nFuncs = funcs.size();
3706 if (nFuncs == 1) {
3707 if (funcs[0]->getInputSize() != 2) {
3708 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with input size != 2");
3709 return false;
3710 }
3711 if (funcs[0]->getOutputSize() != nComps) {
3712 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with wrong output size");
3713 return false;
3714 }
3715 } else if (nFuncs == nComps) {
3716 for (const std::unique_ptr<Function> &f : funcs) {
3717 if (f->getInputSize() != 2) {
3718 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with input size != 2");
3719 return false;
3720 }
3721 if (f->getOutputSize() != 1) {
3722 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with wrong output size");
3723 return false;
3724 }
3725 }
3726 } else {
3727 return false;
3728 }
3729
3730 return true;
3731}
3732
3733GfxShading *GfxFunctionShading::copy() const
3734{
3735 return new GfxFunctionShading(this);
3736}
3737
3738void GfxFunctionShading::getColor(double x, double y, GfxColor *color) const
3739{
3740 double in[2], out[gfxColorMaxComps];
3741
3742 // NB: there can be one function with n outputs or n functions with
3743 // one output each (where n = number of color components)
3744 for (double &i : out) {
3745 i = 0;
3746 }
3747 in[0] = x;
3748 in[1] = y;
3749 for (int i = 0; i < getNFuncs(); ++i) {
3750 funcs[i]->transform(in, out: &out[i]);
3751 }
3752 for (int i = 0; i < gfxColorMaxComps; ++i) {
3753 color->c[i] = dblToCol(x: out[i]);
3754 }
3755}
3756
3757//------------------------------------------------------------------------
3758// GfxUnivariateShading
3759//------------------------------------------------------------------------
3760
3761GfxUnivariateShading::GfxUnivariateShading(int typeA, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A) : GfxShading(typeA), funcs(std::move(funcsA))
3762{
3763 t0 = t0A;
3764 t1 = t1A;
3765 extend0 = extend0A;
3766 extend1 = extend1A;
3767
3768 cacheSize = 0;
3769 lastMatch = 0;
3770 cacheBounds = nullptr;
3771 cacheCoeff = nullptr;
3772 cacheValues = nullptr;
3773}
3774
3775GfxUnivariateShading::GfxUnivariateShading(const GfxUnivariateShading *shading) : GfxShading(shading)
3776{
3777 t0 = shading->t0;
3778 t1 = shading->t1;
3779 for (const auto &f : shading->funcs) {
3780 funcs.emplace_back(args: f->copy());
3781 }
3782 extend0 = shading->extend0;
3783 extend1 = shading->extend1;
3784
3785 cacheSize = 0;
3786 lastMatch = 0;
3787 cacheBounds = nullptr;
3788 cacheCoeff = nullptr;
3789 cacheValues = nullptr;
3790}
3791
3792GfxUnivariateShading::~GfxUnivariateShading()
3793{
3794 gfree(p: cacheBounds);
3795}
3796
3797int GfxUnivariateShading::getColor(double t, GfxColor *color)
3798{
3799 double out[gfxColorMaxComps];
3800
3801 // NB: there can be one function with n outputs or n functions with
3802 // one output each (where n = number of color components)
3803 const int nComps = getNFuncs() * funcs[0]->getOutputSize();
3804
3805 if (cacheSize > 0) {
3806 double x, ix, *l, *u, *upper;
3807
3808 if (cacheBounds[lastMatch - 1] >= t) {
3809 upper = std::lower_bound(first: cacheBounds, last: cacheBounds + lastMatch - 1, val: t);
3810 lastMatch = static_cast<int>(upper - cacheBounds);
3811 lastMatch = std::min<int>(a: std::max<int>(a: 1, b: lastMatch), b: cacheSize - 1);
3812 } else if (cacheBounds[lastMatch] < t) {
3813 upper = std::lower_bound(first: cacheBounds + lastMatch + 1, last: cacheBounds + cacheSize, val: t);
3814 lastMatch = static_cast<int>(upper - cacheBounds);
3815 lastMatch = std::min<int>(a: std::max<int>(a: 1, b: lastMatch), b: cacheSize - 1);
3816 }
3817
3818 x = (t - cacheBounds[lastMatch - 1]) * cacheCoeff[lastMatch];
3819 ix = 1.0 - x;
3820 u = cacheValues + lastMatch * nComps;
3821 l = u - nComps;
3822
3823 for (int i = 0; i < nComps; ++i) {
3824 out[i] = ix * l[i] + x * u[i];
3825 }
3826 } else {
3827 for (int i = 0; i < nComps; ++i) {
3828 out[i] = 0;
3829 }
3830 for (int i = 0; i < getNFuncs(); ++i) {
3831 funcs[i]->transform(in: &t, out: &out[i]);
3832 }
3833 }
3834
3835 for (int i = 0; i < nComps; ++i) {
3836 color->c[i] = dblToCol(x: out[i]);
3837 }
3838 return nComps;
3839}
3840
3841void GfxUnivariateShading::setupCache(const Matrix *ctm, double xMin, double yMin, double xMax, double yMax)
3842{
3843 double sMin, sMax, tMin, tMax, upperBound;
3844 int i, j, nComps, maxSize;
3845
3846 gfree(p: cacheBounds);
3847 cacheBounds = nullptr;
3848 cacheSize = 0;
3849
3850 if (unlikely(getNFuncs() < 1)) {
3851 return;
3852 }
3853
3854 // NB: there can be one function with n outputs or n functions with
3855 // one output each (where n = number of color components)
3856 nComps = getNFuncs() * funcs[0]->getOutputSize();
3857
3858 getParameterRange(lower: &sMin, upper: &sMax, xMin, yMin, xMax, yMax);
3859 upperBound = ctm->norm() * getDistance(sMin, sMax);
3860 maxSize = static_cast<int>(ceil(x: upperBound));
3861 maxSize = std::max<int>(a: maxSize, b: 2);
3862
3863 {
3864 double x[4], y[4];
3865
3866 ctm->transform(x: xMin, y: yMin, tx: &x[0], ty: &y[0]);
3867 ctm->transform(x: xMax, y: yMin, tx: &x[1], ty: &y[1]);
3868 ctm->transform(x: xMin, y: yMax, tx: &x[2], ty: &y[2]);
3869 ctm->transform(x: xMax, y: yMax, tx: &x[3], ty: &y[3]);
3870
3871 xMin = xMax = x[0];
3872 yMin = yMax = y[0];
3873 for (i = 1; i < 4; i++) {
3874 xMin = std::min<double>(a: xMin, b: x[i]);
3875 yMin = std::min<double>(a: yMin, b: y[i]);
3876 xMax = std::max<double>(a: xMax, b: x[i]);
3877 yMax = std::max<double>(a: yMax, b: y[i]);
3878 }
3879 }
3880
3881 if (maxSize > (xMax - xMin) * (yMax - yMin)) {
3882 return;
3883 }
3884
3885 if (t0 < t1) {
3886 tMin = t0 + sMin * (t1 - t0);
3887 tMax = t0 + sMax * (t1 - t0);
3888 } else {
3889 tMin = t0 + sMax * (t1 - t0);
3890 tMax = t0 + sMin * (t1 - t0);
3891 }
3892
3893 cacheBounds = (double *)gmallocn_checkoverflow(count: maxSize, size: sizeof(double) * (nComps + 2));
3894 if (unlikely(!cacheBounds)) {
3895 return;
3896 }
3897 cacheCoeff = cacheBounds + maxSize;
3898 cacheValues = cacheCoeff + maxSize;
3899
3900 if (cacheSize != 0) {
3901 for (j = 0; j < cacheSize; ++j) {
3902 cacheCoeff[j] = 1 / (cacheBounds[j + 1] - cacheBounds[j]);
3903 }
3904 } else if (tMax != tMin) {
3905 double step = (tMax - tMin) / (maxSize - 1);
3906 double coeff = (maxSize - 1) / (tMax - tMin);
3907
3908 cacheSize = maxSize;
3909
3910 for (j = 0; j < cacheSize; ++j) {
3911 cacheBounds[j] = tMin + j * step;
3912 cacheCoeff[j] = coeff;
3913
3914 for (i = 0; i < nComps; ++i) {
3915 cacheValues[j * nComps + i] = 0;
3916 }
3917 for (i = 0; i < getNFuncs(); ++i) {
3918 funcs[i]->transform(in: &cacheBounds[j], out: &cacheValues[j * nComps + i]);
3919 }
3920 }
3921 }
3922
3923 lastMatch = 1;
3924}
3925
3926bool GfxUnivariateShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3927{
3928 const bool parentInit = GfxShading::init(res, dict, out, state);
3929 if (!parentInit) {
3930 return false;
3931 }
3932
3933 // funcs needs to be one of the two:
3934 // * One function 1-in -> nComps-out
3935 // * nComps functions 1-in -> 1-out
3936 const int nComps = colorSpace->getNComps();
3937 const int nFuncs = funcs.size();
3938 if (nFuncs == 1) {
3939 if (funcs[0]->getInputSize() != 1) {
3940 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with input size != 2");
3941 return false;
3942 }
3943 if (funcs[0]->getOutputSize() != nComps) {
3944 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with wrong output size");
3945 return false;
3946 }
3947 } else if (nFuncs == nComps) {
3948 for (const std::unique_ptr<Function> &f : funcs) {
3949 if (f->getInputSize() != 1) {
3950 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with input size != 2");
3951 return false;
3952 }
3953 if (f->getOutputSize() != 1) {
3954 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with wrong output size");
3955 return false;
3956 }
3957 }
3958 } else {
3959 return false;
3960 }
3961
3962 return true;
3963}
3964
3965//------------------------------------------------------------------------
3966// GfxAxialShading
3967//------------------------------------------------------------------------
3968
3969GfxAxialShading::GfxAxialShading(double x0A, double y0A, double x1A, double y1A, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A)
3970 : GfxUnivariateShading(2, t0A, t1A, std::move(funcsA), extend0A, extend1A)
3971{
3972 x0 = x0A;
3973 y0 = y0A;
3974 x1 = x1A;
3975 y1 = y1A;
3976}
3977
3978GfxAxialShading::GfxAxialShading(const GfxAxialShading *shading) : GfxUnivariateShading(shading)
3979{
3980 x0 = shading->x0;
3981 y0 = shading->y0;
3982 x1 = shading->x1;
3983 y1 = shading->y1;
3984}
3985
3986GfxAxialShading::~GfxAxialShading() { }
3987
3988GfxAxialShading *GfxAxialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3989{
3990 GfxAxialShading *shading;
3991 double x0A, y0A, x1A, y1A;
3992 double t0A, t1A;
3993 std::vector<std::unique_ptr<Function>> funcsA;
3994 bool extend0A, extend1A;
3995 Object obj1;
3996
3997 x0A = y0A = x1A = y1A = 0;
3998 obj1 = dict->lookup(key: "Coords");
3999 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
4000 x0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4001 y0A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
4002 x1A = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
4003 y1A = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 0);
4004 } else {
4005 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Coords in shading dictionary");
4006 return nullptr;
4007 }
4008
4009 t0A = 0;
4010 t1A = 1;
4011 obj1 = dict->lookup(key: "Domain");
4012 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4013 t0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4014 t1A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
4015 }
4016
4017 obj1 = dict->lookup(key: "Function");
4018 if (obj1.isArray()) {
4019 const int nFuncsA = obj1.arrayGetLength();
4020 if (nFuncsA > gfxColorMaxComps || nFuncsA == 0) {
4021 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
4022 return nullptr;
4023 }
4024 for (int i = 0; i < nFuncsA; ++i) {
4025 Object obj2 = obj1.arrayGet(i);
4026 Function *f = Function::parse(funcObj: &obj2);
4027 if (!f) {
4028 return nullptr;
4029 }
4030 funcsA.emplace_back(args&: f);
4031 }
4032 } else {
4033 Function *f = Function::parse(funcObj: &obj1);
4034 if (!f) {
4035 return nullptr;
4036 }
4037 funcsA.emplace_back(args&: f);
4038 }
4039
4040 extend0A = extend1A = false;
4041 obj1 = dict->lookup(key: "Extend");
4042 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4043 Object obj2 = obj1.arrayGet(i: 0);
4044 if (obj2.isBool()) {
4045 extend0A = obj2.getBool();
4046 } else {
4047 error(category: errSyntaxWarning, pos: -1, msg: "Invalid axial shading extend (0)");
4048 }
4049 obj2 = obj1.arrayGet(i: 1);
4050 if (obj2.isBool()) {
4051 extend1A = obj2.getBool();
4052 } else {
4053 error(category: errSyntaxWarning, pos: -1, msg: "Invalid axial shading extend (1)");
4054 }
4055 }
4056
4057 shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A, std::move(funcsA), extend0A, extend1A);
4058 if (!shading->init(res, dict, out, state)) {
4059 delete shading;
4060 shading = nullptr;
4061 }
4062 return shading;
4063}
4064
4065GfxShading *GfxAxialShading::copy() const
4066{
4067 return new GfxAxialShading(this);
4068}
4069
4070double GfxAxialShading::getDistance(double sMin, double sMax) const
4071{
4072 double xMin, yMin, xMax, yMax;
4073
4074 xMin = x0 + sMin * (x1 - x0);
4075 yMin = y0 + sMin * (y1 - y0);
4076 xMax = x0 + sMax * (x1 - x0);
4077 yMax = y0 + sMax * (y1 - y0);
4078
4079 return hypot(x: xMax - xMin, y: yMax - yMin);
4080}
4081
4082void GfxAxialShading::getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax)
4083{
4084 double pdx, pdy, invsqnorm, tdx, tdy, t, range[2];
4085
4086 // Linear gradients are orthogonal to the line passing through their
4087 // extremes. Because of convexity, the parameter range can be
4088 // computed as the convex hull (one the real line) of the parameter
4089 // values of the 4 corners of the box.
4090 //
4091 // The parameter value t for a point (x,y) can be computed as:
4092 //
4093 // t = (p2 - p1) . (x,y) / |p2 - p1|^2
4094 //
4095 // t0 is the t value for the top left corner
4096 // tdx is the difference between left and right corners
4097 // tdy is the difference between top and bottom corners
4098
4099 pdx = x1 - x0;
4100 pdy = y1 - y0;
4101 const double invsqnorm_denominator = (pdx * pdx + pdy * pdy);
4102 if (unlikely(invsqnorm_denominator == 0)) {
4103 *lower = 0;
4104 *upper = 0;
4105 return;
4106 }
4107 invsqnorm = 1.0 / invsqnorm_denominator;
4108 pdx *= invsqnorm;
4109 pdy *= invsqnorm;
4110
4111 t = (xMin - x0) * pdx + (yMin - y0) * pdy;
4112 tdx = (xMax - xMin) * pdx;
4113 tdy = (yMax - yMin) * pdy;
4114
4115 // Because of the linearity of the t value, tdx can simply be added
4116 // the t0 to move along the top edge. After this, *lower and *upper
4117 // represent the parameter range for the top edge, so extending it
4118 // to include the whole box simply requires adding tdy to the
4119 // correct extreme.
4120
4121 range[0] = range[1] = t;
4122 if (tdx < 0) {
4123 range[0] += tdx;
4124 } else {
4125 range[1] += tdx;
4126 }
4127
4128 if (tdy < 0) {
4129 range[0] += tdy;
4130 } else {
4131 range[1] += tdy;
4132 }
4133
4134 *lower = std::max<double>(a: 0., b: std::min<double>(a: 1., b: range[0]));
4135 *upper = std::max<double>(a: 0., b: std::min<double>(a: 1., b: range[1]));
4136}
4137
4138//------------------------------------------------------------------------
4139// GfxRadialShading
4140//------------------------------------------------------------------------
4141
4142#ifndef RADIAL_EPSILON
4143# define RADIAL_EPSILON (1. / 1024 / 1024)
4144#endif
4145
4146GfxRadialShading::GfxRadialShading(double x0A, double y0A, double r0A, double x1A, double y1A, double r1A, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A)
4147 : GfxUnivariateShading(3, t0A, t1A, std::move(funcsA), extend0A, extend1A)
4148{
4149 x0 = x0A;
4150 y0 = y0A;
4151 r0 = r0A;
4152 x1 = x1A;
4153 y1 = y1A;
4154 r1 = r1A;
4155}
4156
4157GfxRadialShading::GfxRadialShading(const GfxRadialShading *shading) : GfxUnivariateShading(shading)
4158{
4159 x0 = shading->x0;
4160 y0 = shading->y0;
4161 r0 = shading->r0;
4162 x1 = shading->x1;
4163 y1 = shading->y1;
4164 r1 = shading->r1;
4165}
4166
4167GfxRadialShading::~GfxRadialShading() { }
4168
4169GfxRadialShading *GfxRadialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
4170{
4171 GfxRadialShading *shading;
4172 double x0A, y0A, r0A, x1A, y1A, r1A;
4173 double t0A, t1A;
4174 std::vector<std::unique_ptr<Function>> funcsA;
4175 bool extend0A, extend1A;
4176 Object obj1;
4177 int i;
4178
4179 x0A = y0A = r0A = x1A = y1A = r1A = 0;
4180 obj1 = dict->lookup(key: "Coords");
4181 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
4182 x0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4183 y0A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
4184 r0A = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
4185 x1A = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 0);
4186 y1A = obj1.arrayGet(i: 4).getNumWithDefaultValue(defaultValue: 0);
4187 r1A = obj1.arrayGet(i: 5).getNumWithDefaultValue(defaultValue: 0);
4188 } else {
4189 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Coords in shading dictionary");
4190 return nullptr;
4191 }
4192
4193 t0A = 0;
4194 t1A = 1;
4195 obj1 = dict->lookup(key: "Domain");
4196 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4197 t0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4198 t1A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
4199 }
4200
4201 obj1 = dict->lookup(key: "Function");
4202 if (obj1.isArray()) {
4203 const int nFuncsA = obj1.arrayGetLength();
4204 if (nFuncsA > gfxColorMaxComps) {
4205 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
4206 return nullptr;
4207 }
4208 for (i = 0; i < nFuncsA; ++i) {
4209 Object obj2 = obj1.arrayGet(i);
4210 Function *f = Function::parse(funcObj: &obj2);
4211 if (!f) {
4212 return nullptr;
4213 }
4214 funcsA.emplace_back(args&: f);
4215 }
4216 } else {
4217 Function *f = Function::parse(funcObj: &obj1);
4218 if (!f) {
4219 return nullptr;
4220 }
4221 funcsA.emplace_back(args&: f);
4222 }
4223
4224 extend0A = extend1A = false;
4225 obj1 = dict->lookup(key: "Extend");
4226 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4227 extend0A = obj1.arrayGet(i: 0).getBoolWithDefaultValue(defaultValue: false);
4228 extend1A = obj1.arrayGet(i: 1).getBoolWithDefaultValue(defaultValue: false);
4229 }
4230
4231 shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A, std::move(funcsA), extend0A, extend1A);
4232 if (!shading->init(res, dict, out, state)) {
4233 delete shading;
4234 return nullptr;
4235 }
4236 return shading;
4237}
4238
4239GfxShading *GfxRadialShading::copy() const
4240{
4241 return new GfxRadialShading(this);
4242}
4243
4244double GfxRadialShading::getDistance(double sMin, double sMax) const
4245{
4246 double xMin, yMin, rMin, xMax, yMax, rMax;
4247
4248 xMin = x0 + sMin * (x1 - x0);
4249 yMin = y0 + sMin * (y1 - y0);
4250 rMin = r0 + sMin * (r1 - r0);
4251
4252 xMax = x0 + sMax * (x1 - x0);
4253 yMax = y0 + sMax * (y1 - y0);
4254 rMax = r0 + sMax * (r1 - r0);
4255
4256 return hypot(x: xMax - xMin, y: yMax - yMin) + fabs(x: rMax - rMin);
4257}
4258
4259// extend range, adapted from cairo, radialExtendRange
4260static bool radialExtendRange(double range[2], double value, bool valid)
4261{
4262 if (!valid) {
4263 range[0] = range[1] = value;
4264 } else if (value < range[0]) {
4265 range[0] = value;
4266 } else if (value > range[1]) {
4267 range[1] = value;
4268 }
4269
4270 return true;
4271}
4272
4273inline void radialEdge(double num, double den, double delta, double lower, double upper, double dr, double mindr, bool &valid, double *range)
4274{
4275 if (fabs(x: den) >= RADIAL_EPSILON) {
4276 double t_edge, v;
4277 t_edge = (num) / (den);
4278 v = t_edge * (delta);
4279 if (t_edge * dr >= mindr && (lower) <= v && v <= (upper)) {
4280 valid = radialExtendRange(range, value: t_edge, valid);
4281 }
4282 }
4283}
4284
4285inline void radialCorner1(double x, double y, double &b, double dx, double dy, double cr, double dr, double mindr, bool &valid, double *range)
4286{
4287 b = (x)*dx + (y)*dy + cr * dr;
4288 if (fabs(x: b) >= RADIAL_EPSILON) {
4289 double t_corner;
4290 double x2 = (x) * (x);
4291 double y2 = (y) * (y);
4292 double cr2 = (cr) * (cr);
4293 double c = x2 + y2 - cr2;
4294
4295 t_corner = 0.5 * c / b;
4296 if (t_corner * dr >= mindr) {
4297 valid = radialExtendRange(range, value: t_corner, valid);
4298 }
4299 }
4300}
4301
4302inline void radialCorner2(double x, double y, double a, double &b, double &c, double &d, double dx, double dy, double cr, double inva, double dr, double mindr, bool &valid, double *range)
4303{
4304 b = (x)*dx + (y)*dy + cr * dr;
4305 c = (x) * (x) + (y) * (y)-cr * cr;
4306 d = b * b - a * c;
4307 if (d >= 0) {
4308 double t_corner;
4309
4310 d = sqrt(x: d);
4311 t_corner = (b + d) * inva;
4312 if (t_corner * dr >= mindr) {
4313 valid = radialExtendRange(range, value: t_corner, valid);
4314 }
4315 t_corner = (b - d) * inva;
4316 if (t_corner * dr >= mindr) {
4317 valid = radialExtendRange(range, value: t_corner, valid);
4318 }
4319 }
4320}
4321void GfxRadialShading::getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax)
4322{
4323 double cx, cy, cr, dx, dy, dr;
4324 double a, x_focus, y_focus;
4325 double mindr, minx, miny, maxx, maxy;
4326 double range[2];
4327 bool valid;
4328
4329 // A radial pattern is considered degenerate if it can be
4330 // represented as a solid or clear pattern. This corresponds to one
4331 // of the two cases:
4332 //
4333 // 1) The radii are both very small:
4334 // |dr| < FLT_EPSILON && min (r0, r1) < FLT_EPSILON
4335 //
4336 // 2) The two circles have about the same radius and are very
4337 // close to each other (approximately a cylinder gradient that
4338 // doesn't move with the parameter):
4339 // |dr| < FLT_EPSILON && max (|dx|, |dy|) < 2 * FLT_EPSILON
4340
4341 if (xMin >= xMax || yMin >= yMax || (fabs(x: r0 - r1) < RADIAL_EPSILON && (std::min<double>(a: r0, b: r1) < RADIAL_EPSILON || std::max<double>(a: fabs(x: x0 - x1), b: fabs(x: y0 - y1)) < 2 * RADIAL_EPSILON))) {
4342 *lower = *upper = 0;
4343 return;
4344 }
4345
4346 range[0] = range[1] = 0;
4347 valid = false;
4348
4349 x_focus = y_focus = 0; // silence gcc
4350
4351 cx = x0;
4352 cy = y0;
4353 cr = r0;
4354 dx = x1 - cx;
4355 dy = y1 - cy;
4356 dr = r1 - cr;
4357
4358 // translate by -(cx, cy) to simplify computations
4359 xMin -= cx;
4360 yMin -= cy;
4361 xMax -= cx;
4362 yMax -= cy;
4363
4364 // enlarge boundaries slightly to avoid rounding problems in the
4365 // parameter range computation
4366 xMin -= RADIAL_EPSILON;
4367 yMin -= RADIAL_EPSILON;
4368 xMax += RADIAL_EPSILON;
4369 yMax += RADIAL_EPSILON;
4370
4371 // enlarge boundaries even more to avoid rounding problems when
4372 // testing if a point belongs to the box
4373 minx = xMin - RADIAL_EPSILON;
4374 miny = yMin - RADIAL_EPSILON;
4375 maxx = xMax + RADIAL_EPSILON;
4376 maxy = yMax + RADIAL_EPSILON;
4377
4378 // we dont' allow negative radiuses, so we will be checking that
4379 // t*dr >= mindr to consider t valid
4380 mindr = -(cr + RADIAL_EPSILON);
4381
4382 // After the previous transformations, the start circle is centered
4383 // in the origin and has radius cr. A 1-unit change in the t
4384 // parameter corresponds to dx,dy,dr changes in the x,y,r of the
4385 // circle (center coordinates, radius).
4386 //
4387 // To compute the minimum range needed to correctly draw the
4388 // pattern, we start with an empty range and extend it to include
4389 // the circles touching the bounding box or within it.
4390
4391 // Focus, the point where the circle has radius == 0.
4392 //
4393 // r = cr + t * dr = 0
4394 // t = -cr / dr
4395 //
4396 // If the radius is constant (dr == 0) there is no focus (the
4397 // gradient represents a cylinder instead of a cone).
4398 if (fabs(x: dr) >= RADIAL_EPSILON) {
4399 double t_focus;
4400
4401 t_focus = -cr / dr;
4402 x_focus = t_focus * dx;
4403 y_focus = t_focus * dy;
4404 if (minx <= x_focus && x_focus <= maxx && miny <= y_focus && y_focus <= maxy) {
4405 valid = radialExtendRange(range, value: t_focus, valid);
4406 }
4407 }
4408
4409 // Circles externally tangent to box edges.
4410 //
4411 // All circles have center in (dx, dy) * t
4412 //
4413 // If the circle is tangent to the line defined by the edge of the
4414 // box, then at least one of the following holds true:
4415 //
4416 // (dx*t) + (cr + dr*t) == x0 (left edge)
4417 // (dx*t) - (cr + dr*t) == x1 (right edge)
4418 // (dy*t) + (cr + dr*t) == y0 (top edge)
4419 // (dy*t) - (cr + dr*t) == y1 (bottom edge)
4420 //
4421 // The solution is only valid if the tangent point is actually on
4422 // the edge, i.e. if its y coordinate is in [y0,y1] for left/right
4423 // edges and if its x coordinate is in [x0,x1] for top/bottom edges.
4424 //
4425 // For the first equation:
4426 //
4427 // (dx + dr) * t = x0 - cr
4428 // t = (x0 - cr) / (dx + dr)
4429 // y = dy * t
4430 //
4431 // in the code this becomes:
4432 //
4433 // t_edge = (num) / (den)
4434 // v = (delta) * t_edge
4435 //
4436 // If the denominator in t is 0, the pattern is tangent to a line
4437 // parallel to the edge under examination. The corner-case where the
4438 // boundary line is the same as the edge is handled by the focus
4439 // point case and/or by the a==0 case.
4440
4441 // circles tangent (externally) to left/right/top/bottom edge
4442 radialEdge(num: xMin - cr, den: dx + dr, delta: dy, lower: miny, upper: maxy, dr, mindr, valid, range);
4443 radialEdge(num: xMax + cr, den: dx - dr, delta: dy, lower: miny, upper: maxy, dr, mindr, valid, range);
4444 radialEdge(num: yMin - cr, den: dy + dr, delta: dx, lower: minx, upper: maxx, dr, mindr, valid, range);
4445 radialEdge(num: yMax + cr, den: dy - dr, delta: dx, lower: minx, upper: maxx, dr, mindr, valid, range);
4446
4447 // Circles passing through a corner.
4448 //
4449 // A circle passing through the point (x,y) satisfies:
4450 //
4451 // (x-t*dx)^2 + (y-t*dy)^2 == (cr + t*dr)^2
4452 //
4453 // If we set:
4454 // a = dx^2 + dy^2 - dr^2
4455 // b = x*dx + y*dy + cr*dr
4456 // c = x^2 + y^2 - cr^2
4457 // we have:
4458 // a*t^2 - 2*b*t + c == 0
4459
4460 a = dx * dx + dy * dy - dr * dr;
4461 if (fabs(x: a) < RADIAL_EPSILON * RADIAL_EPSILON) {
4462 double b;
4463
4464 // Ensure that gradients with both a and dr small are
4465 // considered degenerate.
4466 // The floating point version of the degeneracy test implemented
4467 // in _radial_pattern_is_degenerate() is:
4468 //
4469 // 1) The circles are practically the same size:
4470 // |dr| < RADIAL_EPSILON
4471 // AND
4472 // 2a) The circles are both very small:
4473 // min (r0, r1) < RADIAL_EPSILON
4474 // OR
4475 // 2b) The circles are very close to each other:
4476 // max (|dx|, |dy|) < 2 * RADIAL_EPSILON
4477 //
4478 // Assuming that the gradient is not degenerate, we want to
4479 // show that |a| < RADIAL_EPSILON^2 implies |dr| >= RADIAL_EPSILON.
4480 //
4481 // If the gradient is not degenerate yet it has |dr| <
4482 // RADIAL_EPSILON, (2b) is false, thus:
4483 //
4484 // max (|dx|, |dy|) >= 2*RADIAL_EPSILON
4485 // which implies:
4486 // 4*RADIAL_EPSILON^2 <= max (|dx|, |dy|)^2 <= dx^2 + dy^2
4487 //
4488 // From the definition of a, we get:
4489 // a = dx^2 + dy^2 - dr^2 < RADIAL_EPSILON^2
4490 // dx^2 + dy^2 - RADIAL_EPSILON^2 < dr^2
4491 // 3*RADIAL_EPSILON^2 < dr^2
4492 //
4493 // which is inconsistent with the hypotheses, thus |dr| <
4494 // RADIAL_EPSILON is false or the gradient is degenerate.
4495
4496 assert(fabs(dr) >= RADIAL_EPSILON);
4497
4498 // If a == 0, all the circles are tangent to a line in the
4499 // focus point. If this line is within the box extents, we
4500 // should add the circle with infinite radius, but this would
4501 // make the range unbounded. We will be limiting the range to
4502 // [0,1] anyway, so we simply add the biggest legitimate
4503 // circle (it happens for 0 or for 1).
4504 if (dr < 0) {
4505 valid = radialExtendRange(range, value: 0, valid);
4506 } else {
4507 valid = radialExtendRange(range, value: 1, valid);
4508 }
4509
4510 // Nondegenerate, nonlimit circles passing through the corners.
4511 //
4512 // a == 0 && a*t^2 - 2*b*t + c == 0
4513 //
4514 // t = c / (2*b)
4515 //
4516 // The b == 0 case has just been handled, so we only have to
4517 // compute this if b != 0.
4518
4519 // circles touching each corner
4520 radialCorner1(x: xMin, y: yMin, b, dx, dy, cr, dr, mindr, valid, range);
4521 radialCorner1(x: xMin, y: yMax, b, dx, dy, cr, dr, mindr, valid, range);
4522 radialCorner1(x: xMax, y: yMin, b, dx, dy, cr, dr, mindr, valid, range);
4523 radialCorner1(x: xMax, y: yMax, b, dx, dy, cr, dr, mindr, valid, range);
4524 } else {
4525 double inva, b, c, d;
4526
4527 inva = 1 / a;
4528
4529 // Nondegenerate, nonlimit circles passing through the corners.
4530 //
4531 // a != 0 && a*t^2 - 2*b*t + c == 0
4532 //
4533 // t = (b +- sqrt (b*b - a*c)) / a
4534 //
4535 // If the argument of sqrt() is negative, then no circle
4536 // passes through the corner.
4537
4538 // circles touching each corner
4539 radialCorner2(x: xMin, y: yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
4540 radialCorner2(x: xMin, y: yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
4541 radialCorner2(x: xMax, y: yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
4542 radialCorner2(x: xMax, y: yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
4543 }
4544
4545 *lower = std::max<double>(a: 0., b: std::min<double>(a: 1., b: range[0]));
4546 *upper = std::max<double>(a: 0., b: std::min<double>(a: 1., b: range[1]));
4547}
4548
4549//------------------------------------------------------------------------
4550// GfxShadingBitBuf
4551//------------------------------------------------------------------------
4552
4553class GfxShadingBitBuf
4554{
4555public:
4556 explicit GfxShadingBitBuf(Stream *strA);
4557 ~GfxShadingBitBuf();
4558 GfxShadingBitBuf(const GfxShadingBitBuf &) = delete;
4559 GfxShadingBitBuf &operator=(const GfxShadingBitBuf &) = delete;
4560 bool getBits(int n, unsigned int *val);
4561 void flushBits();
4562
4563private:
4564 Stream *str;
4565 int bitBuf;
4566 int nBits;
4567};
4568
4569GfxShadingBitBuf::GfxShadingBitBuf(Stream *strA)
4570{
4571 str = strA;
4572 str->reset();
4573 bitBuf = 0;
4574 nBits = 0;
4575}
4576
4577GfxShadingBitBuf::~GfxShadingBitBuf()
4578{
4579 str->close();
4580}
4581
4582bool GfxShadingBitBuf::getBits(int n, unsigned int *val)
4583{
4584 unsigned int x;
4585
4586 if (nBits >= n) {
4587 x = (bitBuf >> (nBits - n)) & ((1 << n) - 1);
4588 nBits -= n;
4589 } else {
4590 x = 0;
4591 if (nBits > 0) {
4592 x = bitBuf & ((1 << nBits) - 1);
4593 n -= nBits;
4594 nBits = 0;
4595 }
4596 while (n > 0) {
4597 if ((bitBuf = str->getChar()) == EOF) {
4598 nBits = 0;
4599 return false;
4600 }
4601 if (n >= 8) {
4602 x = (x << 8) | bitBuf;
4603 n -= 8;
4604 } else {
4605 x = (x << n) | (bitBuf >> (8 - n));
4606 nBits = 8 - n;
4607 n = 0;
4608 }
4609 }
4610 }
4611 *val = x;
4612 return true;
4613}
4614
4615void GfxShadingBitBuf::flushBits()
4616{
4617 bitBuf = 0;
4618 nBits = 0;
4619}
4620
4621//------------------------------------------------------------------------
4622// GfxGouraudTriangleShading
4623//------------------------------------------------------------------------
4624
4625GfxGouraudTriangleShading::GfxGouraudTriangleShading(int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, std::vector<std::unique_ptr<Function>> &&funcsA)
4626 : GfxShading(typeA), funcs(std::move(funcsA))
4627{
4628 vertices = verticesA;
4629 nVertices = nVerticesA;
4630 triangles = trianglesA;
4631 nTriangles = nTrianglesA;
4632}
4633
4634GfxGouraudTriangleShading::GfxGouraudTriangleShading(const GfxGouraudTriangleShading *shading) : GfxShading(shading)
4635{
4636 nVertices = shading->nVertices;
4637 vertices = (GfxGouraudVertex *)gmallocn(count: nVertices, size: sizeof(GfxGouraudVertex));
4638 memcpy(dest: vertices, src: shading->vertices, n: nVertices * sizeof(GfxGouraudVertex));
4639 nTriangles = shading->nTriangles;
4640 triangles = (int(*)[3])gmallocn(count: nTriangles * 3, size: sizeof(int));
4641 memcpy(dest: triangles, src: shading->triangles, n: nTriangles * 3 * sizeof(int));
4642 for (const auto &f : shading->funcs) {
4643 funcs.emplace_back(args: f->copy());
4644 }
4645}
4646
4647GfxGouraudTriangleShading::~GfxGouraudTriangleShading()
4648{
4649 gfree(p: vertices);
4650 gfree(p: triangles);
4651}
4652
4653GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *gfxState)
4654{
4655 GfxGouraudTriangleShading *shading;
4656 std::vector<std::unique_ptr<Function>> funcsA;
4657 int coordBits, compBits, flagBits, vertsPerRow, nRows;
4658 double xMin, xMax, yMin, yMax;
4659 double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
4660 double xMul, yMul;
4661 double cMul[gfxColorMaxComps];
4662 GfxGouraudVertex *verticesA;
4663 int(*trianglesA)[3];
4664 int nComps, nVerticesA, nTrianglesA, vertSize, triSize;
4665 unsigned int x, y, flag;
4666 unsigned int c[gfxColorMaxComps];
4667 GfxShadingBitBuf *bitBuf;
4668 Object obj1;
4669 int i, j, k, state;
4670
4671 obj1 = dict->lookup(key: "BitsPerCoordinate");
4672 if (obj1.isInt()) {
4673 coordBits = obj1.getInt();
4674 } else {
4675 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerCoordinate in shading dictionary");
4676 return nullptr;
4677 }
4678 if (unlikely(coordBits <= 0)) {
4679 error(category: errSyntaxWarning, pos: -1, msg: "Invalid BitsPerCoordinate in shading dictionary");
4680 return nullptr;
4681 }
4682 obj1 = dict->lookup(key: "BitsPerComponent");
4683 if (obj1.isInt()) {
4684 compBits = obj1.getInt();
4685 } else {
4686 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerComponent in shading dictionary");
4687 return nullptr;
4688 }
4689 if (unlikely(compBits <= 0 || compBits > 31)) {
4690 error(category: errSyntaxWarning, pos: -1, msg: "Invalid BitsPerComponent in shading dictionary");
4691 return nullptr;
4692 }
4693 flagBits = vertsPerRow = 0; // make gcc happy
4694 if (typeA == 4) {
4695 obj1 = dict->lookup(key: "BitsPerFlag");
4696 if (obj1.isInt()) {
4697 flagBits = obj1.getInt();
4698 } else {
4699 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerFlag in shading dictionary");
4700 return nullptr;
4701 }
4702 } else {
4703 obj1 = dict->lookup(key: "VerticesPerRow");
4704 if (obj1.isInt()) {
4705 vertsPerRow = obj1.getInt();
4706 } else {
4707 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid VerticesPerRow in shading dictionary");
4708 return nullptr;
4709 }
4710 }
4711 obj1 = dict->lookup(key: "Decode");
4712 if (obj1.isArray() && obj1.arrayGetLength() >= 6) {
4713 bool decodeOk = true;
4714 xMin = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
4715 xMax = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
4716 xMul = (xMax - xMin) / (pow(x: 2.0, y: coordBits) - 1);
4717 yMin = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
4718 yMax = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
4719 yMul = (yMax - yMin) / (pow(x: 2.0, y: coordBits) - 1);
4720 for (i = 0; 5 + 2 * i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
4721 cMin[i] = obj1.arrayGet(i: 4 + 2 * i).getNum(ok: &decodeOk);
4722 cMax[i] = obj1.arrayGet(i: 5 + 2 * i).getNum(ok: &decodeOk);
4723 cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1);
4724 }
4725 nComps = i;
4726
4727 if (!decodeOk) {
4728 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Decode array in shading dictionary");
4729 return nullptr;
4730 }
4731 } else {
4732 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Decode array in shading dictionary");
4733 return nullptr;
4734 }
4735
4736 obj1 = dict->lookup(key: "Function");
4737 if (!obj1.isNull()) {
4738 if (obj1.isArray()) {
4739 const int nFuncsA = obj1.arrayGetLength();
4740 if (nFuncsA > gfxColorMaxComps) {
4741 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
4742 return nullptr;
4743 }
4744 for (i = 0; i < nFuncsA; ++i) {
4745 Object obj2 = obj1.arrayGet(i);
4746 Function *f = Function::parse(funcObj: &obj2);
4747 if (!f) {
4748 return nullptr;
4749 }
4750 funcsA.emplace_back(args&: f);
4751 }
4752 } else {
4753 Function *f = Function::parse(funcObj: &obj1);
4754 if (!f) {
4755 return nullptr;
4756 }
4757 funcsA.emplace_back(args&: f);
4758 }
4759 }
4760
4761 nVerticesA = nTrianglesA = 0;
4762 verticesA = nullptr;
4763 trianglesA = nullptr;
4764 vertSize = triSize = 0;
4765 state = 0;
4766 flag = 0; // make gcc happy
4767 bitBuf = new GfxShadingBitBuf(str);
4768 while (true) {
4769 if (typeA == 4) {
4770 if (!bitBuf->getBits(n: flagBits, val: &flag)) {
4771 break;
4772 }
4773 }
4774 if (!bitBuf->getBits(n: coordBits, val: &x) || !bitBuf->getBits(n: coordBits, val: &y)) {
4775 break;
4776 }
4777 for (i = 0; i < nComps; ++i) {
4778 if (!bitBuf->getBits(n: compBits, val: &c[i])) {
4779 break;
4780 }
4781 }
4782 if (i < nComps) {
4783 break;
4784 }
4785 if (nVerticesA == vertSize) {
4786 int oldVertSize = vertSize;
4787 vertSize = (vertSize == 0) ? 16 : 2 * vertSize;
4788 verticesA = (GfxGouraudVertex *)greallocn_checkoverflow(p: verticesA, count: vertSize, size: sizeof(GfxGouraudVertex));
4789 if (unlikely(!verticesA)) {
4790 error(category: errSyntaxWarning, pos: -1, msg: "GfxGouraudTriangleShading::parse: vertices size overflow");
4791 gfree(p: trianglesA);
4792 delete bitBuf;
4793 return nullptr;
4794 }
4795 memset(s: verticesA + oldVertSize, c: 0, n: (vertSize - oldVertSize) * sizeof(GfxGouraudVertex));
4796 }
4797 verticesA[nVerticesA].x = xMin + xMul * (double)x;
4798 verticesA[nVerticesA].y = yMin + yMul * (double)y;
4799 for (i = 0; i < nComps; ++i) {
4800 verticesA[nVerticesA].color.c[i] = dblToCol(x: cMin[i] + cMul[i] * (double)c[i]);
4801 }
4802 ++nVerticesA;
4803 bitBuf->flushBits();
4804 if (typeA == 4) {
4805 if (state == 0 || state == 1) {
4806 ++state;
4807 } else if (state == 2 || flag > 0) {
4808 if (nTrianglesA == triSize) {
4809 triSize = (triSize == 0) ? 16 : 2 * triSize;
4810 trianglesA = (int(*)[3])greallocn(p: trianglesA, count: triSize * 3, size: sizeof(int));
4811 }
4812 if (state == 2) {
4813 trianglesA[nTrianglesA][0] = nVerticesA - 3;
4814 trianglesA[nTrianglesA][1] = nVerticesA - 2;
4815 trianglesA[nTrianglesA][2] = nVerticesA - 1;
4816 ++state;
4817 } else if (flag == 1) {
4818 trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][1];
4819 trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
4820 trianglesA[nTrianglesA][2] = nVerticesA - 1;
4821 } else { // flag == 2
4822 trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][0];
4823 trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
4824 trianglesA[nTrianglesA][2] = nVerticesA - 1;
4825 }
4826 ++nTrianglesA;
4827 } else { // state == 3 && flag == 0
4828 state = 1;
4829 }
4830 }
4831 }
4832 delete bitBuf;
4833 if (typeA == 5 && nVerticesA > 0 && vertsPerRow > 0) {
4834 nRows = nVerticesA / vertsPerRow;
4835 nTrianglesA = (nRows - 1) * 2 * (vertsPerRow - 1);
4836 trianglesA = (int(*)[3])gmallocn_checkoverflow(count: nTrianglesA * 3, size: sizeof(int));
4837 if (unlikely(!trianglesA)) {
4838 gfree(p: verticesA);
4839 return nullptr;
4840 }
4841 k = 0;
4842 for (i = 0; i < nRows - 1; ++i) {
4843 for (j = 0; j < vertsPerRow - 1; ++j) {
4844 trianglesA[k][0] = i * vertsPerRow + j;
4845 trianglesA[k][1] = i * vertsPerRow + j + 1;
4846 trianglesA[k][2] = (i + 1) * vertsPerRow + j;
4847 ++k;
4848 trianglesA[k][0] = i * vertsPerRow + j + 1;
4849 trianglesA[k][1] = (i + 1) * vertsPerRow + j;
4850 trianglesA[k][2] = (i + 1) * vertsPerRow + j + 1;
4851 ++k;
4852 }
4853 }
4854 }
4855
4856 shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA, trianglesA, nTrianglesA, std::move(funcsA));
4857 if (!shading->init(res, dict, out, state: gfxState)) {
4858 delete shading;
4859 return nullptr;
4860 }
4861 return shading;
4862}
4863
4864bool GfxGouraudTriangleShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
4865{
4866 const bool parentInit = GfxShading::init(res, dict, out, state);
4867 if (!parentInit) {
4868 return false;
4869 }
4870
4871 // funcs needs to be one of the three:
4872 // * One function 1-in -> nComps-out
4873 // * nComps functions 1-in -> 1-out
4874 // * empty
4875 const int nComps = colorSpace->getNComps();
4876 const int nFuncs = funcs.size();
4877 if (nFuncs == 1) {
4878 if (funcs[0]->getInputSize() != 1) {
4879 error(category: errSyntaxWarning, pos: -1, msg: "GfxGouraudTriangleShading: function with input size != 2");
4880 return false;
4881 }
4882 if (funcs[0]->getOutputSize() != nComps) {
4883 error(category: errSyntaxWarning, pos: -1, msg: "GfxGouraudTriangleShading: function with wrong output size");
4884 return false;
4885 }
4886 } else if (nFuncs == nComps) {
4887 for (const std::unique_ptr<Function> &f : funcs) {
4888 if (f->getInputSize() != 1) {
4889 error(category: errSyntaxWarning, pos: -1, msg: "GfxGouraudTriangleShading: function with input size != 2");
4890 return false;
4891 }
4892 if (f->getOutputSize() != 1) {
4893 error(category: errSyntaxWarning, pos: -1, msg: "GfxGouraudTriangleShading: function with wrong output size");
4894 return false;
4895 }
4896 }
4897 } else if (nFuncs != 0) {
4898 return false;
4899 }
4900
4901 return true;
4902}
4903
4904GfxShading *GfxGouraudTriangleShading::copy() const
4905{
4906 return new GfxGouraudTriangleShading(this);
4907}
4908
4909void GfxGouraudTriangleShading::getTriangle(int i, double *x0, double *y0, GfxColor *color0, double *x1, double *y1, GfxColor *color1, double *x2, double *y2, GfxColor *color2)
4910{
4911 int v;
4912
4913 assert(!isParameterized());
4914
4915 v = triangles[i][0];
4916 *x0 = vertices[v].x;
4917 *y0 = vertices[v].y;
4918 *color0 = vertices[v].color;
4919 v = triangles[i][1];
4920 *x1 = vertices[v].x;
4921 *y1 = vertices[v].y;
4922 *color1 = vertices[v].color;
4923 v = triangles[i][2];
4924 *x2 = vertices[v].x;
4925 *y2 = vertices[v].y;
4926 *color2 = vertices[v].color;
4927}
4928
4929void GfxGouraudTriangleShading::getParameterizedColor(double t, GfxColor *color) const
4930{
4931 double out[gfxColorMaxComps];
4932
4933 for (unsigned int j = 0; j < funcs.size(); ++j) {
4934 funcs[j]->transform(in: &t, out: &out[j]);
4935 }
4936 for (int j = 0; j < gfxColorMaxComps; ++j) {
4937 color->c[j] = dblToCol(x: out[j]);
4938 }
4939}
4940
4941void GfxGouraudTriangleShading::getTriangle(int i, double *x0, double *y0, double *color0, double *x1, double *y1, double *color1, double *x2, double *y2, double *color2)
4942{
4943 int v;
4944
4945 assert(isParameterized());
4946
4947 v = triangles[i][0];
4948 if (likely(v >= 0 && v < nVertices)) {
4949 *x0 = vertices[v].x;
4950 *y0 = vertices[v].y;
4951 *color0 = colToDbl(x: vertices[v].color.c[0]);
4952 }
4953 v = triangles[i][1];
4954 if (likely(v >= 0 && v < nVertices)) {
4955 *x1 = vertices[v].x;
4956 *y1 = vertices[v].y;
4957 *color1 = colToDbl(x: vertices[v].color.c[0]);
4958 }
4959 v = triangles[i][2];
4960 if (likely(v >= 0 && v < nVertices)) {
4961 *x2 = vertices[v].x;
4962 *y2 = vertices[v].y;
4963 *color2 = colToDbl(x: vertices[v].color.c[0]);
4964 }
4965}
4966
4967//------------------------------------------------------------------------
4968// GfxPatchMeshShading
4969//------------------------------------------------------------------------
4970
4971GfxPatchMeshShading::GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, std::vector<std::unique_ptr<Function>> &&funcsA) : GfxShading(typeA), funcs(std::move(funcsA))
4972{
4973 patches = patchesA;
4974 nPatches = nPatchesA;
4975}
4976
4977GfxPatchMeshShading::GfxPatchMeshShading(const GfxPatchMeshShading *shading) : GfxShading(shading)
4978{
4979 nPatches = shading->nPatches;
4980 patches = (GfxPatch *)gmallocn(count: nPatches, size: sizeof(GfxPatch));
4981 memcpy(dest: patches, src: shading->patches, n: nPatches * sizeof(GfxPatch));
4982 for (const auto &f : shading->funcs) {
4983 funcs.emplace_back(args: f->copy());
4984 }
4985}
4986
4987GfxPatchMeshShading::~GfxPatchMeshShading()
4988{
4989 gfree(p: patches);
4990}
4991
4992GfxPatchMeshShading *GfxPatchMeshShading::parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state)
4993{
4994 GfxPatchMeshShading *shading;
4995 std::vector<std::unique_ptr<Function>> funcsA;
4996 int coordBits, compBits, flagBits;
4997 double xMin, xMax, yMin, yMax;
4998 double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
4999 double xMul, yMul;
5000 double cMul[gfxColorMaxComps];
5001 GfxPatch *patchesA, *p;
5002 int nComps, nPatchesA, patchesSize, nPts, nColors;
5003 unsigned int flag;
5004 double x[16], y[16];
5005 unsigned int xi, yi;
5006 double c[4][gfxColorMaxComps];
5007 unsigned int ci;
5008 Object obj1;
5009 int i, j;
5010
5011 obj1 = dict->lookup(key: "BitsPerCoordinate");
5012 if (obj1.isInt()) {
5013 coordBits = obj1.getInt();
5014 } else {
5015 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerCoordinate in shading dictionary");
5016 return nullptr;
5017 }
5018 if (unlikely(coordBits <= 0)) {
5019 error(category: errSyntaxWarning, pos: -1, msg: "Invalid BitsPerCoordinate in shading dictionary");
5020 return nullptr;
5021 }
5022 obj1 = dict->lookup(key: "BitsPerComponent");
5023 if (obj1.isInt()) {
5024 compBits = obj1.getInt();
5025 } else {
5026 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerComponent in shading dictionary");
5027 return nullptr;
5028 }
5029 if (unlikely(compBits <= 0 || compBits > 31)) {
5030 error(category: errSyntaxWarning, pos: -1, msg: "Invalid BitsPerComponent in shading dictionary");
5031 return nullptr;
5032 }
5033 obj1 = dict->lookup(key: "BitsPerFlag");
5034 if (obj1.isInt()) {
5035 flagBits = obj1.getInt();
5036 } else {
5037 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid BitsPerFlag in shading dictionary");
5038 return nullptr;
5039 }
5040 obj1 = dict->lookup(key: "Decode");
5041 if (obj1.isArray() && obj1.arrayGetLength() >= 6) {
5042 bool decodeOk = true;
5043 xMin = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
5044 xMax = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
5045 xMul = (xMax - xMin) / (pow(x: 2.0, y: coordBits) - 1);
5046 yMin = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
5047 yMax = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
5048 yMul = (yMax - yMin) / (pow(x: 2.0, y: coordBits) - 1);
5049 for (i = 0; 5 + 2 * i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
5050 cMin[i] = obj1.arrayGet(i: 4 + 2 * i).getNum(ok: &decodeOk);
5051 cMax[i] = obj1.arrayGet(i: 5 + 2 * i).getNum(ok: &decodeOk);
5052 cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1);
5053 }
5054 nComps = i;
5055
5056 if (!decodeOk) {
5057 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Decode array in shading dictionary");
5058 return nullptr;
5059 }
5060 } else {
5061 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Decode array in shading dictionary");
5062 return nullptr;
5063 }
5064
5065 obj1 = dict->lookup(key: "Function");
5066 if (!obj1.isNull()) {
5067 if (obj1.isArray()) {
5068 const int nFuncsA = obj1.arrayGetLength();
5069 if (nFuncsA > gfxColorMaxComps) {
5070 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
5071 return nullptr;
5072 }
5073 for (i = 0; i < nFuncsA; ++i) {
5074 Object obj2 = obj1.arrayGet(i);
5075 Function *f = Function::parse(funcObj: &obj2);
5076 if (!f) {
5077 return nullptr;
5078 }
5079 funcsA.emplace_back(args&: f);
5080 }
5081 } else {
5082 Function *f = Function::parse(funcObj: &obj1);
5083 if (!f) {
5084 return nullptr;
5085 }
5086 funcsA.emplace_back(args&: f);
5087 }
5088 }
5089
5090 nPatchesA = 0;
5091 patchesA = nullptr;
5092 patchesSize = 0;
5093 auto bitBuf = std::make_unique<GfxShadingBitBuf>(args&: str);
5094 while (true) {
5095 if (!bitBuf->getBits(n: flagBits, val: &flag)) {
5096 break;
5097 }
5098 if (typeA == 6) {
5099 switch (flag) {
5100 case 0:
5101 nPts = 12;
5102 nColors = 4;
5103 break;
5104 case 1:
5105 case 2:
5106 case 3:
5107 default:
5108 nPts = 8;
5109 nColors = 2;
5110 break;
5111 }
5112 } else {
5113 switch (flag) {
5114 case 0:
5115 nPts = 16;
5116 nColors = 4;
5117 break;
5118 case 1:
5119 case 2:
5120 case 3:
5121 default:
5122 nPts = 12;
5123 nColors = 2;
5124 break;
5125 }
5126 }
5127 for (i = 0; i < nPts; ++i) {
5128 if (!bitBuf->getBits(n: coordBits, val: &xi) || !bitBuf->getBits(n: coordBits, val: &yi)) {
5129 break;
5130 }
5131 x[i] = xMin + xMul * (double)xi;
5132 y[i] = yMin + yMul * (double)yi;
5133 }
5134 if (i < nPts) {
5135 break;
5136 }
5137 for (i = 0; i < nColors; ++i) {
5138 for (j = 0; j < nComps; ++j) {
5139 if (!bitBuf->getBits(n: compBits, val: &ci)) {
5140 break;
5141 }
5142 c[i][j] = cMin[j] + cMul[j] * (double)ci;
5143 if (funcsA.empty()) {
5144 // ... and colorspace values can also be stored into doubles.
5145 // They will be casted later.
5146 c[i][j] = dblToCol(x: c[i][j]);
5147 }
5148 }
5149 if (j < nComps) {
5150 break;
5151 }
5152 }
5153 if (i < nColors) {
5154 break;
5155 }
5156 if (nPatchesA == patchesSize) {
5157 int oldPatchesSize = patchesSize;
5158 patchesSize = (patchesSize == 0) ? 16 : 2 * patchesSize;
5159 patchesA = (GfxPatch *)greallocn_checkoverflow(p: patchesA, count: patchesSize, size: sizeof(GfxPatch));
5160 if (unlikely(!patchesA)) {
5161 return nullptr;
5162 }
5163 memset(s: patchesA + oldPatchesSize, c: 0, n: (patchesSize - oldPatchesSize) * sizeof(GfxPatch));
5164 }
5165 p = &patchesA[nPatchesA];
5166 if (typeA == 6) {
5167 switch (flag) {
5168 case 0:
5169 p->x[0][0] = x[0];
5170 p->y[0][0] = y[0];
5171 p->x[0][1] = x[1];
5172 p->y[0][1] = y[1];
5173 p->x[0][2] = x[2];
5174 p->y[0][2] = y[2];
5175 p->x[0][3] = x[3];
5176 p->y[0][3] = y[3];
5177 p->x[1][3] = x[4];
5178 p->y[1][3] = y[4];
5179 p->x[2][3] = x[5];
5180 p->y[2][3] = y[5];
5181 p->x[3][3] = x[6];
5182 p->y[3][3] = y[6];
5183 p->x[3][2] = x[7];
5184 p->y[3][2] = y[7];
5185 p->x[3][1] = x[8];
5186 p->y[3][1] = y[8];
5187 p->x[3][0] = x[9];
5188 p->y[3][0] = y[9];
5189 p->x[2][0] = x[10];
5190 p->y[2][0] = y[10];
5191 p->x[1][0] = x[11];
5192 p->y[1][0] = y[11];
5193 for (j = 0; j < nComps; ++j) {
5194 p->color[0][0].c[j] = c[0][j];
5195 p->color[0][1].c[j] = c[1][j];
5196 p->color[1][1].c[j] = c[2][j];
5197 p->color[1][0].c[j] = c[3][j];
5198 }
5199 break;
5200 case 1:
5201 if (nPatchesA == 0) {
5202 gfree(p: patchesA);
5203 return nullptr;
5204 }
5205 p->x[0][0] = patchesA[nPatchesA - 1].x[0][3];
5206 p->y[0][0] = patchesA[nPatchesA - 1].y[0][3];
5207 p->x[0][1] = patchesA[nPatchesA - 1].x[1][3];
5208 p->y[0][1] = patchesA[nPatchesA - 1].y[1][3];
5209 p->x[0][2] = patchesA[nPatchesA - 1].x[2][3];
5210 p->y[0][2] = patchesA[nPatchesA - 1].y[2][3];
5211 p->x[0][3] = patchesA[nPatchesA - 1].x[3][3];
5212 p->y[0][3] = patchesA[nPatchesA - 1].y[3][3];
5213 p->x[1][3] = x[0];
5214 p->y[1][3] = y[0];
5215 p->x[2][3] = x[1];
5216 p->y[2][3] = y[1];
5217 p->x[3][3] = x[2];
5218 p->y[3][3] = y[2];
5219 p->x[3][2] = x[3];
5220 p->y[3][2] = y[3];
5221 p->x[3][1] = x[4];
5222 p->y[3][1] = y[4];
5223 p->x[3][0] = x[5];
5224 p->y[3][0] = y[5];
5225 p->x[2][0] = x[6];
5226 p->y[2][0] = y[6];
5227 p->x[1][0] = x[7];
5228 p->y[1][0] = y[7];
5229 for (j = 0; j < nComps; ++j) {
5230 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[0][1].c[j];
5231 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j];
5232 p->color[1][1].c[j] = c[0][j];
5233 p->color[1][0].c[j] = c[1][j];
5234 }
5235 break;
5236 case 2:
5237 if (nPatchesA == 0) {
5238 gfree(p: patchesA);
5239 return nullptr;
5240 }
5241 p->x[0][0] = patchesA[nPatchesA - 1].x[3][3];
5242 p->y[0][0] = patchesA[nPatchesA - 1].y[3][3];
5243 p->x[0][1] = patchesA[nPatchesA - 1].x[3][2];
5244 p->y[0][1] = patchesA[nPatchesA - 1].y[3][2];
5245 p->x[0][2] = patchesA[nPatchesA - 1].x[3][1];
5246 p->y[0][2] = patchesA[nPatchesA - 1].y[3][1];
5247 p->x[0][3] = patchesA[nPatchesA - 1].x[3][0];
5248 p->y[0][3] = patchesA[nPatchesA - 1].y[3][0];
5249 p->x[1][3] = x[0];
5250 p->y[1][3] = y[0];
5251 p->x[2][3] = x[1];
5252 p->y[2][3] = y[1];
5253 p->x[3][3] = x[2];
5254 p->y[3][3] = y[2];
5255 p->x[3][2] = x[3];
5256 p->y[3][2] = y[3];
5257 p->x[3][1] = x[4];
5258 p->y[3][1] = y[4];
5259 p->x[3][0] = x[5];
5260 p->y[3][0] = y[5];
5261 p->x[2][0] = x[6];
5262 p->y[2][0] = y[6];
5263 p->x[1][0] = x[7];
5264 p->y[1][0] = y[7];
5265 for (j = 0; j < nComps; ++j) {
5266 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j];
5267 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j];
5268 p->color[1][1].c[j] = c[0][j];
5269 p->color[1][0].c[j] = c[1][j];
5270 }
5271 break;
5272 case 3:
5273 if (nPatchesA == 0) {
5274 gfree(p: patchesA);
5275 return nullptr;
5276 }
5277 p->x[0][0] = patchesA[nPatchesA - 1].x[3][0];
5278 p->y[0][0] = patchesA[nPatchesA - 1].y[3][0];
5279 p->x[0][1] = patchesA[nPatchesA - 1].x[2][0];
5280 p->y[0][1] = patchesA[nPatchesA - 1].y[2][0];
5281 p->x[0][2] = patchesA[nPatchesA - 1].x[1][0];
5282 p->y[0][2] = patchesA[nPatchesA - 1].y[1][0];
5283 p->x[0][3] = patchesA[nPatchesA - 1].x[0][0];
5284 p->y[0][3] = patchesA[nPatchesA - 1].y[0][0];
5285 p->x[1][3] = x[0];
5286 p->y[1][3] = y[0];
5287 p->x[2][3] = x[1];
5288 p->y[2][3] = y[1];
5289 p->x[3][3] = x[2];
5290 p->y[3][3] = y[2];
5291 p->x[3][2] = x[3];
5292 p->y[3][2] = y[3];
5293 p->x[3][1] = x[4];
5294 p->y[3][1] = y[4];
5295 p->x[3][0] = x[5];
5296 p->y[3][0] = y[5];
5297 p->x[2][0] = x[6];
5298 p->y[2][0] = y[6];
5299 p->x[1][0] = x[7];
5300 p->y[1][0] = y[7];
5301 for (j = 0; j < nComps; ++j) {
5302 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j];
5303 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[0][0].c[j];
5304 p->color[1][1].c[j] = c[0][j];
5305 p->color[1][0].c[j] = c[1][j];
5306 }
5307 break;
5308 }
5309 } else {
5310 switch (flag) {
5311 case 0:
5312 p->x[0][0] = x[0];
5313 p->y[0][0] = y[0];
5314 p->x[0][1] = x[1];
5315 p->y[0][1] = y[1];
5316 p->x[0][2] = x[2];
5317 p->y[0][2] = y[2];
5318 p->x[0][3] = x[3];
5319 p->y[0][3] = y[3];
5320 p->x[1][3] = x[4];
5321 p->y[1][3] = y[4];
5322 p->x[2][3] = x[5];
5323 p->y[2][3] = y[5];
5324 p->x[3][3] = x[6];
5325 p->y[3][3] = y[6];
5326 p->x[3][2] = x[7];
5327 p->y[3][2] = y[7];
5328 p->x[3][1] = x[8];
5329 p->y[3][1] = y[8];
5330 p->x[3][0] = x[9];
5331 p->y[3][0] = y[9];
5332 p->x[2][0] = x[10];
5333 p->y[2][0] = y[10];
5334 p->x[1][0] = x[11];
5335 p->y[1][0] = y[11];
5336 p->x[1][1] = x[12];
5337 p->y[1][1] = y[12];
5338 p->x[1][2] = x[13];
5339 p->y[1][2] = y[13];
5340 p->x[2][2] = x[14];
5341 p->y[2][2] = y[14];
5342 p->x[2][1] = x[15];
5343 p->y[2][1] = y[15];
5344 for (j = 0; j < nComps; ++j) {
5345 p->color[0][0].c[j] = c[0][j];
5346 p->color[0][1].c[j] = c[1][j];
5347 p->color[1][1].c[j] = c[2][j];
5348 p->color[1][0].c[j] = c[3][j];
5349 }
5350 break;
5351 case 1:
5352 if (nPatchesA == 0) {
5353 gfree(p: patchesA);
5354 return nullptr;
5355 }
5356 p->x[0][0] = patchesA[nPatchesA - 1].x[0][3];
5357 p->y[0][0] = patchesA[nPatchesA - 1].y[0][3];
5358 p->x[0][1] = patchesA[nPatchesA - 1].x[1][3];
5359 p->y[0][1] = patchesA[nPatchesA - 1].y[1][3];
5360 p->x[0][2] = patchesA[nPatchesA - 1].x[2][3];
5361 p->y[0][2] = patchesA[nPatchesA - 1].y[2][3];
5362 p->x[0][3] = patchesA[nPatchesA - 1].x[3][3];
5363 p->y[0][3] = patchesA[nPatchesA - 1].y[3][3];
5364 p->x[1][3] = x[0];
5365 p->y[1][3] = y[0];
5366 p->x[2][3] = x[1];
5367 p->y[2][3] = y[1];
5368 p->x[3][3] = x[2];
5369 p->y[3][3] = y[2];
5370 p->x[3][2] = x[3];
5371 p->y[3][2] = y[3];
5372 p->x[3][1] = x[4];
5373 p->y[3][1] = y[4];
5374 p->x[3][0] = x[5];
5375 p->y[3][0] = y[5];
5376 p->x[2][0] = x[6];
5377 p->y[2][0] = y[6];
5378 p->x[1][0] = x[7];
5379 p->y[1][0] = y[7];
5380 p->x[1][1] = x[8];
5381 p->y[1][1] = y[8];
5382 p->x[1][2] = x[9];
5383 p->y[1][2] = y[9];
5384 p->x[2][2] = x[10];
5385 p->y[2][2] = y[10];
5386 p->x[2][1] = x[11];
5387 p->y[2][1] = y[11];
5388 for (j = 0; j < nComps; ++j) {
5389 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[0][1].c[j];
5390 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j];
5391 p->color[1][1].c[j] = c[0][j];
5392 p->color[1][0].c[j] = c[1][j];
5393 }
5394 break;
5395 case 2:
5396 if (nPatchesA == 0) {
5397 gfree(p: patchesA);
5398 return nullptr;
5399 }
5400 p->x[0][0] = patchesA[nPatchesA - 1].x[3][3];
5401 p->y[0][0] = patchesA[nPatchesA - 1].y[3][3];
5402 p->x[0][1] = patchesA[nPatchesA - 1].x[3][2];
5403 p->y[0][1] = patchesA[nPatchesA - 1].y[3][2];
5404 p->x[0][2] = patchesA[nPatchesA - 1].x[3][1];
5405 p->y[0][2] = patchesA[nPatchesA - 1].y[3][1];
5406 p->x[0][3] = patchesA[nPatchesA - 1].x[3][0];
5407 p->y[0][3] = patchesA[nPatchesA - 1].y[3][0];
5408 p->x[1][3] = x[0];
5409 p->y[1][3] = y[0];
5410 p->x[2][3] = x[1];
5411 p->y[2][3] = y[1];
5412 p->x[3][3] = x[2];
5413 p->y[3][3] = y[2];
5414 p->x[3][2] = x[3];
5415 p->y[3][2] = y[3];
5416 p->x[3][1] = x[4];
5417 p->y[3][1] = y[4];
5418 p->x[3][0] = x[5];
5419 p->y[3][0] = y[5];
5420 p->x[2][0] = x[6];
5421 p->y[2][0] = y[6];
5422 p->x[1][0] = x[7];
5423 p->y[1][0] = y[7];
5424 p->x[1][1] = x[8];
5425 p->y[1][1] = y[8];
5426 p->x[1][2] = x[9];
5427 p->y[1][2] = y[9];
5428 p->x[2][2] = x[10];
5429 p->y[2][2] = y[10];
5430 p->x[2][1] = x[11];
5431 p->y[2][1] = y[11];
5432 for (j = 0; j < nComps; ++j) {
5433 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j];
5434 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j];
5435 p->color[1][1].c[j] = c[0][j];
5436 p->color[1][0].c[j] = c[1][j];
5437 }
5438 break;
5439 case 3:
5440 if (nPatchesA == 0) {
5441 gfree(p: patchesA);
5442 return nullptr;
5443 }
5444 p->x[0][0] = patchesA[nPatchesA - 1].x[3][0];
5445 p->y[0][0] = patchesA[nPatchesA - 1].y[3][0];
5446 p->x[0][1] = patchesA[nPatchesA - 1].x[2][0];
5447 p->y[0][1] = patchesA[nPatchesA - 1].y[2][0];
5448 p->x[0][2] = patchesA[nPatchesA - 1].x[1][0];
5449 p->y[0][2] = patchesA[nPatchesA - 1].y[1][0];
5450 p->x[0][3] = patchesA[nPatchesA - 1].x[0][0];
5451 p->y[0][3] = patchesA[nPatchesA - 1].y[0][0];
5452 p->x[1][3] = x[0];
5453 p->y[1][3] = y[0];
5454 p->x[2][3] = x[1];
5455 p->y[2][3] = y[1];
5456 p->x[3][3] = x[2];
5457 p->y[3][3] = y[2];
5458 p->x[3][2] = x[3];
5459 p->y[3][2] = y[3];
5460 p->x[3][1] = x[4];
5461 p->y[3][1] = y[4];
5462 p->x[3][0] = x[5];
5463 p->y[3][0] = y[5];
5464 p->x[2][0] = x[6];
5465 p->y[2][0] = y[6];
5466 p->x[1][0] = x[7];
5467 p->y[1][0] = y[7];
5468 p->x[1][1] = x[8];
5469 p->y[1][1] = y[8];
5470 p->x[1][2] = x[9];
5471 p->y[1][2] = y[9];
5472 p->x[2][2] = x[10];
5473 p->y[2][2] = y[10];
5474 p->x[2][1] = x[11];
5475 p->y[2][1] = y[11];
5476 for (j = 0; j < nComps; ++j) {
5477 p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j];
5478 p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[0][0].c[j];
5479 p->color[1][1].c[j] = c[0][j];
5480 p->color[1][0].c[j] = c[1][j];
5481 }
5482 break;
5483 }
5484 }
5485 ++nPatchesA;
5486 bitBuf->flushBits();
5487 }
5488
5489 if (typeA == 6) {
5490 for (i = 0; i < nPatchesA; ++i) {
5491 p = &patchesA[i];
5492 p->x[1][1] = (-4 * p->x[0][0] + 6 * (p->x[0][1] + p->x[1][0]) - 2 * (p->x[0][3] + p->x[3][0]) + 3 * (p->x[3][1] + p->x[1][3]) - p->x[3][3]) / 9;
5493 p->y[1][1] = (-4 * p->y[0][0] + 6 * (p->y[0][1] + p->y[1][0]) - 2 * (p->y[0][3] + p->y[3][0]) + 3 * (p->y[3][1] + p->y[1][3]) - p->y[3][3]) / 9;
5494 p->x[1][2] = (-4 * p->x[0][3] + 6 * (p->x[0][2] + p->x[1][3]) - 2 * (p->x[0][0] + p->x[3][3]) + 3 * (p->x[3][2] + p->x[1][0]) - p->x[3][0]) / 9;
5495 p->y[1][2] = (-4 * p->y[0][3] + 6 * (p->y[0][2] + p->y[1][3]) - 2 * (p->y[0][0] + p->y[3][3]) + 3 * (p->y[3][2] + p->y[1][0]) - p->y[3][0]) / 9;
5496 p->x[2][1] = (-4 * p->x[3][0] + 6 * (p->x[3][1] + p->x[2][0]) - 2 * (p->x[3][3] + p->x[0][0]) + 3 * (p->x[0][1] + p->x[2][3]) - p->x[0][3]) / 9;
5497 p->y[2][1] = (-4 * p->y[3][0] + 6 * (p->y[3][1] + p->y[2][0]) - 2 * (p->y[3][3] + p->y[0][0]) + 3 * (p->y[0][1] + p->y[2][3]) - p->y[0][3]) / 9;
5498 p->x[2][2] = (-4 * p->x[3][3] + 6 * (p->x[3][2] + p->x[2][3]) - 2 * (p->x[3][0] + p->x[0][3]) + 3 * (p->x[0][2] + p->x[2][0]) - p->x[0][0]) / 9;
5499 p->y[2][2] = (-4 * p->y[3][3] + 6 * (p->y[3][2] + p->y[2][3]) - 2 * (p->y[3][0] + p->y[0][3]) + 3 * (p->y[0][2] + p->y[2][0]) - p->y[0][0]) / 9;
5500 }
5501 }
5502
5503 shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA, std::move(funcsA));
5504 if (!shading->init(res, dict, out, state)) {
5505 delete shading;
5506 return nullptr;
5507 }
5508 return shading;
5509}
5510
5511bool GfxPatchMeshShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
5512{
5513 const bool parentInit = GfxShading::init(res, dict, out, state);
5514 if (!parentInit) {
5515 return false;
5516 }
5517
5518 // funcs needs to be one of the three:
5519 // * One function 1-in -> nComps-out
5520 // * nComps functions 1-in -> 1-out
5521 // * empty
5522 const int nComps = colorSpace->getNComps();
5523 const int nFuncs = funcs.size();
5524 if (nFuncs == 1) {
5525 if (funcs[0]->getInputSize() != 1) {
5526 error(category: errSyntaxWarning, pos: -1, msg: "GfxPatchMeshShading: function with input size != 2");
5527 return false;
5528 }
5529 if (funcs[0]->getOutputSize() != nComps) {
5530 error(category: errSyntaxWarning, pos: -1, msg: "GfxPatchMeshShading: function with wrong output size");
5531 return false;
5532 }
5533 } else if (nFuncs == nComps) {
5534 for (const std::unique_ptr<Function> &f : funcs) {
5535 if (f->getInputSize() != 1) {
5536 error(category: errSyntaxWarning, pos: -1, msg: "GfxPatchMeshShading: function with input size != 2");
5537 return false;
5538 }
5539 if (f->getOutputSize() != 1) {
5540 error(category: errSyntaxWarning, pos: -1, msg: "GfxPatchMeshShading: function with wrong output size");
5541 return false;
5542 }
5543 }
5544 } else if (nFuncs != 0) {
5545 return false;
5546 }
5547
5548 return true;
5549}
5550
5551void GfxPatchMeshShading::getParameterizedColor(double t, GfxColor *color) const
5552{
5553 double out[gfxColorMaxComps] = {};
5554
5555 for (unsigned int j = 0; j < funcs.size(); ++j) {
5556 funcs[j]->transform(in: &t, out: &out[j]);
5557 }
5558 for (int j = 0; j < gfxColorMaxComps; ++j) {
5559 color->c[j] = dblToCol(x: out[j]);
5560 }
5561}
5562
5563GfxShading *GfxPatchMeshShading::copy() const
5564{
5565 return new GfxPatchMeshShading(this);
5566}
5567
5568//------------------------------------------------------------------------
5569// GfxImageColorMap
5570//------------------------------------------------------------------------
5571
5572GfxImageColorMap::GfxImageColorMap(int bitsA, Object *decode, GfxColorSpace *colorSpaceA)
5573{
5574 GfxIndexedColorSpace *indexedCS;
5575 GfxSeparationColorSpace *sepCS;
5576 int maxPixel, indexHigh;
5577 unsigned char *indexedLookup;
5578 const Function *sepFunc;
5579 double x[gfxColorMaxComps];
5580 double y[gfxColorMaxComps] = {};
5581 int i, j, k;
5582 double mapped;
5583 bool useByteLookup;
5584
5585 ok = true;
5586 useMatte = false;
5587
5588 colorSpace = colorSpaceA;
5589
5590 // initialize
5591 for (k = 0; k < gfxColorMaxComps; ++k) {
5592 lookup[k] = nullptr;
5593 lookup2[k] = nullptr;
5594 }
5595 byte_lookup = nullptr;
5596
5597 // bits per component and color space
5598 if (unlikely(bitsA <= 0 || bitsA > 30)) {
5599 goto err1;
5600 }
5601
5602 bits = bitsA;
5603 maxPixel = (1 << bits) - 1;
5604
5605 // this is a hack to support 16 bits images, everywhere
5606 // we assume a component fits in 8 bits, with this hack
5607 // we treat 16 bit images as 8 bit ones until it's fixed correctly.
5608 // The hack has another part on ImageStream::getLine
5609 if (maxPixel > 255) {
5610 maxPixel = 255;
5611 }
5612
5613 // get decode map
5614 if (decode->isNull()) {
5615 nComps = colorSpace->getNComps();
5616 colorSpace->getDefaultRanges(decodeLow, decodeRange, maxImgPixel: maxPixel);
5617 } else if (decode->isArray()) {
5618 nComps = decode->arrayGetLength() / 2;
5619 if (nComps < colorSpace->getNComps()) {
5620 goto err1;
5621 }
5622 if (nComps > colorSpace->getNComps()) {
5623 error(category: errSyntaxWarning, pos: -1, msg: "Too many elements in Decode array");
5624 nComps = colorSpace->getNComps();
5625 }
5626 for (i = 0; i < nComps; ++i) {
5627 Object obj = decode->arrayGet(i: 2 * i);
5628 if (!obj.isNum()) {
5629 goto err1;
5630 }
5631 decodeLow[i] = obj.getNum();
5632 obj = decode->arrayGet(i: 2 * i + 1);
5633 if (!obj.isNum()) {
5634 goto err1;
5635 }
5636 decodeRange[i] = obj.getNum() - decodeLow[i];
5637 }
5638 } else {
5639 goto err1;
5640 }
5641
5642 // Construct a lookup table -- this stores pre-computed decoded
5643 // values for each component, i.e., the result of applying the
5644 // decode mapping to each possible image pixel component value.
5645 for (k = 0; k < nComps; ++k) {
5646 lookup[k] = (GfxColorComp *)gmallocn(count: maxPixel + 1, size: sizeof(GfxColorComp));
5647 for (i = 0; i <= maxPixel; ++i) {
5648 lookup[k][i] = dblToCol(x: decodeLow[k] + (i * decodeRange[k]) / maxPixel);
5649 }
5650 }
5651
5652 // Optimization: for Indexed and Separation color spaces (which have
5653 // only one component), we pre-compute a second lookup table with
5654 // color values
5655 colorSpace2 = nullptr;
5656 nComps2 = 0;
5657 useByteLookup = false;
5658 switch (colorSpace->getMode()) {
5659 case csIndexed:
5660 // Note that indexHigh may not be the same as maxPixel --
5661 // Distiller will remove unused palette entries, resulting in
5662 // indexHigh < maxPixel.
5663 indexedCS = (GfxIndexedColorSpace *)colorSpace;
5664 colorSpace2 = indexedCS->getBase();
5665 indexHigh = indexedCS->getIndexHigh();
5666 nComps2 = colorSpace2->getNComps();
5667 indexedLookup = indexedCS->getLookup();
5668 colorSpace2->getDefaultRanges(decodeLow: x, decodeRange: y, maxImgPixel: indexHigh);
5669 if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
5670 byte_lookup = (unsigned char *)gmallocn(count: (maxPixel + 1), size: nComps2);
5671 useByteLookup = true;
5672 }
5673 for (k = 0; k < nComps2; ++k) {
5674 lookup2[k] = (GfxColorComp *)gmallocn(count: maxPixel + 1, size: sizeof(GfxColorComp));
5675 for (i = 0; i <= maxPixel; ++i) {
5676 j = (int)(decodeLow[0] + (i * decodeRange[0]) / maxPixel + 0.5);
5677 if (j < 0) {
5678 j = 0;
5679 } else if (j > indexHigh) {
5680 j = indexHigh;
5681 }
5682
5683 mapped = x[k] + (indexedLookup[j * nComps2 + k] / 255.0) * y[k];
5684 lookup2[k][i] = dblToCol(x: mapped);
5685 if (useByteLookup) {
5686 byte_lookup[i * nComps2 + k] = (unsigned char)(mapped * 255);
5687 }
5688 }
5689 }
5690 break;
5691 case csSeparation:
5692 sepCS = (GfxSeparationColorSpace *)colorSpace;
5693 colorSpace2 = sepCS->getAlt();
5694 nComps2 = colorSpace2->getNComps();
5695 sepFunc = sepCS->getFunc();
5696 if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
5697 byte_lookup = (unsigned char *)gmallocn(count: (maxPixel + 1), size: nComps2);
5698 useByteLookup = true;
5699 }
5700 for (k = 0; k < nComps2; ++k) {
5701 lookup2[k] = (GfxColorComp *)gmallocn(count: maxPixel + 1, size: sizeof(GfxColorComp));
5702 for (i = 0; i <= maxPixel; ++i) {
5703 x[0] = decodeLow[0] + (i * decodeRange[0]) / maxPixel;
5704 sepFunc->transform(in: x, out: y);
5705 lookup2[k][i] = dblToCol(x: y[k]);
5706 if (useByteLookup) {
5707 byte_lookup[i * nComps2 + k] = (unsigned char)(y[k] * 255);
5708 }
5709 }
5710 }
5711 break;
5712 default:
5713 if ((!decode->isNull() || maxPixel != 255) && (colorSpace->useGetGrayLine() || (colorSpace->useGetRGBLine() && !decode->isNull()) || colorSpace->useGetCMYKLine() || colorSpace->useGetDeviceNLine())) {
5714 byte_lookup = (unsigned char *)gmallocn(count: (maxPixel + 1), size: nComps);
5715 useByteLookup = true;
5716 }
5717 for (k = 0; k < nComps; ++k) {
5718 lookup2[k] = (GfxColorComp *)gmallocn(count: maxPixel + 1, size: sizeof(GfxColorComp));
5719 for (i = 0; i <= maxPixel; ++i) {
5720 mapped = decodeLow[k] + (i * decodeRange[k]) / maxPixel;
5721 lookup2[k][i] = dblToCol(x: mapped);
5722 if (useByteLookup) {
5723 int byte;
5724
5725 byte = (int)(mapped * 255.0 + 0.5);
5726 if (byte < 0) {
5727 byte = 0;
5728 } else if (byte > 255) {
5729 byte = 255;
5730 }
5731 byte_lookup[i * nComps + k] = byte;
5732 }
5733 }
5734 }
5735 }
5736
5737 return;
5738
5739err1:
5740 ok = false;
5741}
5742
5743GfxImageColorMap::GfxImageColorMap(const GfxImageColorMap *colorMap)
5744{
5745 int n, i, k;
5746
5747 colorSpace = colorMap->colorSpace->copy();
5748 bits = colorMap->bits;
5749 nComps = colorMap->nComps;
5750 nComps2 = colorMap->nComps2;
5751 useMatte = colorMap->useMatte;
5752 matteColor = colorMap->matteColor;
5753 colorSpace2 = nullptr;
5754 for (k = 0; k < gfxColorMaxComps; ++k) {
5755 lookup[k] = nullptr;
5756 lookup2[k] = nullptr;
5757 }
5758 byte_lookup = nullptr;
5759 n = 1 << bits;
5760 for (k = 0; k < nComps; ++k) {
5761 lookup[k] = (GfxColorComp *)gmallocn(count: n, size: sizeof(GfxColorComp));
5762 memcpy(dest: lookup[k], src: colorMap->lookup[k], n: n * sizeof(GfxColorComp));
5763 }
5764 if (colorSpace->getMode() == csIndexed) {
5765 colorSpace2 = ((GfxIndexedColorSpace *)colorSpace)->getBase();
5766 for (k = 0; k < nComps2; ++k) {
5767 lookup2[k] = (GfxColorComp *)gmallocn(count: n, size: sizeof(GfxColorComp));
5768 memcpy(dest: lookup2[k], src: colorMap->lookup2[k], n: n * sizeof(GfxColorComp));
5769 }
5770 } else if (colorSpace->getMode() == csSeparation) {
5771 colorSpace2 = ((GfxSeparationColorSpace *)colorSpace)->getAlt();
5772 for (k = 0; k < nComps2; ++k) {
5773 lookup2[k] = (GfxColorComp *)gmallocn(count: n, size: sizeof(GfxColorComp));
5774 memcpy(dest: lookup2[k], src: colorMap->lookup2[k], n: n * sizeof(GfxColorComp));
5775 }
5776 } else {
5777 for (k = 0; k < nComps; ++k) {
5778 lookup2[k] = (GfxColorComp *)gmallocn(count: n, size: sizeof(GfxColorComp));
5779 memcpy(dest: lookup2[k], src: colorMap->lookup2[k], n: n * sizeof(GfxColorComp));
5780 }
5781 }
5782 if (colorMap->byte_lookup) {
5783 int nc = colorSpace2 ? nComps2 : nComps;
5784
5785 byte_lookup = (unsigned char *)gmallocn(count: n, size: nc);
5786 memcpy(dest: byte_lookup, src: colorMap->byte_lookup, n: n * nc);
5787 }
5788 for (i = 0; i < nComps; ++i) {
5789 decodeLow[i] = colorMap->decodeLow[i];
5790 decodeRange[i] = colorMap->decodeRange[i];
5791 }
5792 ok = true;
5793}
5794
5795GfxImageColorMap::~GfxImageColorMap()
5796{
5797 int i;
5798
5799 delete colorSpace;
5800 for (i = 0; i < gfxColorMaxComps; ++i) {
5801 gfree(p: lookup[i]);
5802 gfree(p: lookup2[i]);
5803 }
5804 gfree(p: byte_lookup);
5805}
5806
5807void GfxImageColorMap::getGray(const unsigned char *x, GfxGray *gray)
5808{
5809 GfxColor color;
5810 int i;
5811
5812 if (colorSpace2) {
5813 for (i = 0; i < nComps2; ++i) {
5814 color.c[i] = lookup2[i][x[0]];
5815 }
5816 colorSpace2->getGray(color: &color, gray);
5817 } else {
5818 for (i = 0; i < nComps; ++i) {
5819 color.c[i] = lookup2[i][x[i]];
5820 }
5821 colorSpace->getGray(color: &color, gray);
5822 }
5823}
5824
5825void GfxImageColorMap::getRGB(const unsigned char *x, GfxRGB *rgb)
5826{
5827 GfxColor color;
5828 int i;
5829
5830 if (colorSpace2) {
5831 for (i = 0; i < nComps2; ++i) {
5832 color.c[i] = lookup2[i][x[0]];
5833 }
5834 colorSpace2->getRGB(color: &color, rgb);
5835 } else {
5836 for (i = 0; i < nComps; ++i) {
5837 color.c[i] = lookup2[i][x[i]];
5838 }
5839 colorSpace->getRGB(color: &color, rgb);
5840 }
5841}
5842
5843void GfxImageColorMap::getGrayLine(unsigned char *in, unsigned char *out, int length)
5844{
5845 int i, j;
5846 unsigned char *inp, *tmp_line;
5847
5848 if ((colorSpace2 && !colorSpace2->useGetGrayLine()) || (!colorSpace2 && !colorSpace->useGetGrayLine())) {
5849 GfxGray gray;
5850
5851 inp = in;
5852 for (i = 0; i < length; i++) {
5853 getGray(x: inp, gray: &gray);
5854 out[i] = colToByte(x: gray);
5855 inp += nComps;
5856 }
5857 return;
5858 }
5859
5860 switch (colorSpace->getMode()) {
5861 case csIndexed:
5862 case csSeparation:
5863 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
5864 for (i = 0; i < length; i++) {
5865 for (j = 0; j < nComps2; j++) {
5866 unsigned char c = in[i];
5867 if (byte_lookup) {
5868 c = byte_lookup[c * nComps2 + j];
5869 }
5870 tmp_line[i * nComps2 + j] = c;
5871 }
5872 }
5873 colorSpace2->getGrayLine(tmp_line, out, length);
5874 gfree(p: tmp_line);
5875 break;
5876
5877 default:
5878 if (byte_lookup) {
5879 inp = in;
5880 for (j = 0; j < length; j++) {
5881 for (i = 0; i < nComps; i++) {
5882 *inp = byte_lookup[*inp * nComps + i];
5883 inp++;
5884 }
5885 }
5886 }
5887 colorSpace->getGrayLine(in, out, length);
5888 break;
5889 }
5890}
5891
5892void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned int *out, int length)
5893{
5894 int i, j;
5895 unsigned char *inp, *tmp_line;
5896
5897 if (!useRGBLine()) {
5898 GfxRGB rgb;
5899
5900 inp = in;
5901 for (i = 0; i < length; i++) {
5902 getRGB(x: inp, rgb: &rgb);
5903 out[i] = ((int)colToByte(x: rgb.r) << 16) | ((int)colToByte(x: rgb.g) << 8) | ((int)colToByte(x: rgb.b) << 0);
5904 inp += nComps;
5905 }
5906 return;
5907 }
5908
5909 switch (colorSpace->getMode()) {
5910 case csIndexed:
5911 case csSeparation:
5912 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
5913 for (i = 0; i < length; i++) {
5914 for (j = 0; j < nComps2; j++) {
5915 unsigned char c = in[i];
5916 if (byte_lookup) {
5917 c = byte_lookup[c * nComps2 + j];
5918 }
5919 tmp_line[i * nComps2 + j] = c;
5920 }
5921 }
5922 colorSpace2->getRGBLine(tmp_line, out, length);
5923 gfree(p: tmp_line);
5924 break;
5925
5926 default:
5927 if (byte_lookup) {
5928 inp = in;
5929 for (j = 0; j < length; j++) {
5930 for (i = 0; i < nComps; i++) {
5931 *inp = byte_lookup[*inp * nComps + i];
5932 inp++;
5933 }
5934 }
5935 }
5936 colorSpace->getRGBLine(in, out, length);
5937 break;
5938 }
5939}
5940
5941void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned char *out, int length)
5942{
5943 int i, j;
5944 unsigned char *inp, *tmp_line;
5945
5946 if (!useRGBLine()) {
5947 GfxRGB rgb;
5948
5949 inp = in;
5950 for (i = 0; i < length; i++) {
5951 getRGB(x: inp, rgb: &rgb);
5952 *out++ = colToByte(x: rgb.r);
5953 *out++ = colToByte(x: rgb.g);
5954 *out++ = colToByte(x: rgb.b);
5955 inp += nComps;
5956 }
5957 return;
5958 }
5959
5960 switch (colorSpace->getMode()) {
5961 case csIndexed:
5962 case csSeparation:
5963 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
5964 for (i = 0; i < length; i++) {
5965 for (j = 0; j < nComps2; j++) {
5966 unsigned char c = in[i];
5967 if (byte_lookup) {
5968 c = byte_lookup[c * nComps2 + j];
5969 }
5970 tmp_line[i * nComps2 + j] = c;
5971 }
5972 }
5973 colorSpace2->getRGBLine(tmp_line, out, length);
5974 gfree(p: tmp_line);
5975 break;
5976
5977 default:
5978 if (byte_lookup) {
5979 inp = in;
5980 for (j = 0; j < length; j++) {
5981 for (i = 0; i < nComps; i++) {
5982 *inp = byte_lookup[*inp * nComps + i];
5983 inp++;
5984 }
5985 }
5986 }
5987 colorSpace->getRGBLine(in, out, length);
5988 break;
5989 }
5990}
5991
5992void GfxImageColorMap::getRGBXLine(unsigned char *in, unsigned char *out, int length)
5993{
5994 int i, j;
5995 unsigned char *inp, *tmp_line;
5996
5997 if (!useRGBLine()) {
5998 GfxRGB rgb;
5999
6000 inp = in;
6001 for (i = 0; i < length; i++) {
6002 getRGB(x: inp, rgb: &rgb);
6003 *out++ = colToByte(x: rgb.r);
6004 *out++ = colToByte(x: rgb.g);
6005 *out++ = colToByte(x: rgb.b);
6006 *out++ = 255;
6007 inp += nComps;
6008 }
6009 return;
6010 }
6011
6012 switch (colorSpace->getMode()) {
6013 case csIndexed:
6014 case csSeparation:
6015 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
6016 for (i = 0; i < length; i++) {
6017 for (j = 0; j < nComps2; j++) {
6018 unsigned char c = in[i];
6019 if (byte_lookup) {
6020 c = byte_lookup[c * nComps2 + j];
6021 }
6022 tmp_line[i * nComps2 + j] = c;
6023 }
6024 }
6025 colorSpace2->getRGBXLine(tmp_line, out, length);
6026 gfree(p: tmp_line);
6027 break;
6028
6029 default:
6030 if (byte_lookup) {
6031 inp = in;
6032 for (j = 0; j < length; j++) {
6033 for (i = 0; i < nComps; i++) {
6034 *inp = byte_lookup[*inp * nComps + i];
6035 inp++;
6036 }
6037 }
6038 }
6039 colorSpace->getRGBXLine(in, out, length);
6040 break;
6041 }
6042}
6043
6044void GfxImageColorMap::getCMYKLine(unsigned char *in, unsigned char *out, int length)
6045{
6046 int i, j;
6047 unsigned char *inp, *tmp_line;
6048
6049 if (!useCMYKLine()) {
6050 GfxCMYK cmyk;
6051
6052 inp = in;
6053 for (i = 0; i < length; i++) {
6054 getCMYK(x: inp, cmyk: &cmyk);
6055 *out++ = colToByte(x: cmyk.c);
6056 *out++ = colToByte(x: cmyk.m);
6057 *out++ = colToByte(x: cmyk.y);
6058 *out++ = colToByte(x: cmyk.k);
6059 inp += nComps;
6060 }
6061 return;
6062 }
6063
6064 switch (colorSpace->getMode()) {
6065 case csIndexed:
6066 case csSeparation:
6067 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
6068 for (i = 0; i < length; i++) {
6069 for (j = 0; j < nComps2; j++) {
6070 unsigned char c = in[i];
6071 if (byte_lookup) {
6072 c = byte_lookup[c * nComps2 + j];
6073 }
6074 tmp_line[i * nComps2 + j] = c;
6075 }
6076 }
6077 colorSpace2->getCMYKLine(tmp_line, out, length);
6078 gfree(p: tmp_line);
6079 break;
6080
6081 default:
6082 if (byte_lookup) {
6083 inp = in;
6084 for (j = 0; j < length; j++) {
6085 for (i = 0; i < nComps; i++) {
6086 *inp = byte_lookup[*inp * nComps + i];
6087 inp++;
6088 }
6089 }
6090 }
6091 colorSpace->getCMYKLine(in, out, length);
6092 break;
6093 }
6094}
6095
6096void GfxImageColorMap::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
6097{
6098 unsigned char *inp, *tmp_line;
6099
6100 if (!useDeviceNLine()) {
6101 GfxColor deviceN;
6102
6103 inp = in;
6104 for (int i = 0; i < length; i++) {
6105 getDeviceN(x: inp, deviceN: &deviceN);
6106 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
6107 *out++ = deviceN.c[j];
6108 }
6109 inp += nComps;
6110 }
6111 return;
6112 }
6113
6114 switch (colorSpace->getMode()) {
6115 case csIndexed:
6116 case csSeparation:
6117 tmp_line = (unsigned char *)gmallocn(count: length, size: nComps2);
6118 for (int i = 0; i < length; i++) {
6119 for (int j = 0; j < nComps2; j++) {
6120 unsigned char c = in[i];
6121 if (byte_lookup) {
6122 c = byte_lookup[c * nComps2 + j];
6123 }
6124 tmp_line[i * nComps2 + j] = c;
6125 }
6126 }
6127 colorSpace2->getDeviceNLine(tmp_line, out, length);
6128 gfree(p: tmp_line);
6129 break;
6130
6131 default:
6132 if (byte_lookup) {
6133 inp = in;
6134 for (int j = 0; j < length; j++) {
6135 for (int i = 0; i < nComps; i++) {
6136 *inp = byte_lookup[*inp * nComps + i];
6137 inp++;
6138 }
6139 }
6140 }
6141 colorSpace->getDeviceNLine(in, out, length);
6142 break;
6143 }
6144}
6145
6146void GfxImageColorMap::getCMYK(const unsigned char *x, GfxCMYK *cmyk)
6147{
6148 GfxColor color;
6149 int i;
6150
6151 if (colorSpace2) {
6152 for (i = 0; i < nComps2; ++i) {
6153 color.c[i] = lookup2[i][x[0]];
6154 }
6155 colorSpace2->getCMYK(color: &color, cmyk);
6156 } else {
6157 for (i = 0; i < nComps; ++i) {
6158 color.c[i] = lookup[i][x[i]];
6159 }
6160 colorSpace->getCMYK(color: &color, cmyk);
6161 }
6162}
6163
6164void GfxImageColorMap::getDeviceN(const unsigned char *x, GfxColor *deviceN)
6165{
6166 GfxColor color;
6167 int i;
6168
6169 if (colorSpace2 && (colorSpace->getMapping() == nullptr || colorSpace->getMapping()[0] == -1)) {
6170 for (i = 0; i < nComps2; ++i) {
6171 color.c[i] = lookup2[i][x[0]];
6172 }
6173 colorSpace2->getDeviceN(color: &color, deviceN);
6174 } else {
6175 for (i = 0; i < nComps; ++i) {
6176 color.c[i] = lookup[i][x[i]];
6177 }
6178 colorSpace->getDeviceN(color: &color, deviceN);
6179 }
6180}
6181
6182void GfxImageColorMap::getColor(const unsigned char *x, GfxColor *color)
6183{
6184 int maxPixel, i;
6185
6186 maxPixel = (1 << bits) - 1;
6187 for (i = 0; i < nComps; ++i) {
6188 color->c[i] = dblToCol(x: decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel);
6189 }
6190}
6191
6192//------------------------------------------------------------------------
6193// GfxSubpath and GfxPath
6194//------------------------------------------------------------------------
6195
6196GfxSubpath::GfxSubpath(double x1, double y1)
6197{
6198 size = 16;
6199 x = (double *)gmallocn(count: size, size: sizeof(double));
6200 y = (double *)gmallocn(count: size, size: sizeof(double));
6201 curve = (bool *)gmallocn(count: size, size: sizeof(bool));
6202 n = 1;
6203 x[0] = x1;
6204 y[0] = y1;
6205 curve[0] = false;
6206 closed = false;
6207}
6208
6209GfxSubpath::~GfxSubpath()
6210{
6211 gfree(p: x);
6212 gfree(p: y);
6213 gfree(p: curve);
6214}
6215
6216// Used for copy().
6217GfxSubpath::GfxSubpath(const GfxSubpath *subpath)
6218{
6219 size = subpath->size;
6220 n = subpath->n;
6221 x = (double *)gmallocn(count: size, size: sizeof(double));
6222 y = (double *)gmallocn(count: size, size: sizeof(double));
6223 curve = (bool *)gmallocn(count: size, size: sizeof(bool));
6224 memcpy(dest: x, src: subpath->x, n: n * sizeof(double));
6225 memcpy(dest: y, src: subpath->y, n: n * sizeof(double));
6226 memcpy(dest: curve, src: subpath->curve, n: n * sizeof(bool));
6227 closed = subpath->closed;
6228}
6229
6230void GfxSubpath::lineTo(double x1, double y1)
6231{
6232 if (n >= size) {
6233 size *= 2;
6234 x = (double *)greallocn(p: x, count: size, size: sizeof(double));
6235 y = (double *)greallocn(p: y, count: size, size: sizeof(double));
6236 curve = (bool *)greallocn(p: curve, count: size, size: sizeof(bool));
6237 }
6238 x[n] = x1;
6239 y[n] = y1;
6240 curve[n] = false;
6241 ++n;
6242}
6243
6244void GfxSubpath::curveTo(double x1, double y1, double x2, double y2, double x3, double y3)
6245{
6246 if (n + 3 > size) {
6247 size *= 2;
6248 x = (double *)greallocn(p: x, count: size, size: sizeof(double));
6249 y = (double *)greallocn(p: y, count: size, size: sizeof(double));
6250 curve = (bool *)greallocn(p: curve, count: size, size: sizeof(bool));
6251 }
6252 x[n] = x1;
6253 y[n] = y1;
6254 x[n + 1] = x2;
6255 y[n + 1] = y2;
6256 x[n + 2] = x3;
6257 y[n + 2] = y3;
6258 curve[n] = curve[n + 1] = true;
6259 curve[n + 2] = false;
6260 n += 3;
6261}
6262
6263void GfxSubpath::close()
6264{
6265 if (x[n - 1] != x[0] || y[n - 1] != y[0]) {
6266 lineTo(x1: x[0], y1: y[0]);
6267 }
6268 closed = true;
6269}
6270
6271void GfxSubpath::offset(double dx, double dy)
6272{
6273 int i;
6274
6275 for (i = 0; i < n; ++i) {
6276 x[i] += dx;
6277 y[i] += dy;
6278 }
6279}
6280
6281GfxPath::GfxPath()
6282{
6283 justMoved = false;
6284 size = 16;
6285 n = 0;
6286 firstX = firstY = 0;
6287 subpaths = (GfxSubpath **)gmallocn(count: size, size: sizeof(GfxSubpath *));
6288}
6289
6290GfxPath::~GfxPath()
6291{
6292 int i;
6293
6294 for (i = 0; i < n; ++i) {
6295 delete subpaths[i];
6296 }
6297 gfree(p: subpaths);
6298}
6299
6300// Used for copy().
6301GfxPath::GfxPath(bool justMoved1, double firstX1, double firstY1, GfxSubpath **subpaths1, int n1, int size1)
6302{
6303 int i;
6304
6305 justMoved = justMoved1;
6306 firstX = firstX1;
6307 firstY = firstY1;
6308 size = size1;
6309 n = n1;
6310 subpaths = (GfxSubpath **)gmallocn(count: size, size: sizeof(GfxSubpath *));
6311 for (i = 0; i < n; ++i) {
6312 subpaths[i] = subpaths1[i]->copy();
6313 }
6314}
6315
6316void GfxPath::moveTo(double x, double y)
6317{
6318 justMoved = true;
6319 firstX = x;
6320 firstY = y;
6321}
6322
6323void GfxPath::lineTo(double x, double y)
6324{
6325 if (justMoved || (n > 0 && subpaths[n - 1]->isClosed())) {
6326 if (n >= size) {
6327 size *= 2;
6328 subpaths = (GfxSubpath **)greallocn(p: subpaths, count: size, size: sizeof(GfxSubpath *));
6329 }
6330 if (justMoved) {
6331 subpaths[n] = new GfxSubpath(firstX, firstY);
6332 } else {
6333 subpaths[n] = new GfxSubpath(subpaths[n - 1]->getLastX(), subpaths[n - 1]->getLastY());
6334 }
6335 ++n;
6336 justMoved = false;
6337 }
6338 subpaths[n - 1]->lineTo(x1: x, y1: y);
6339}
6340
6341void GfxPath::curveTo(double x1, double y1, double x2, double y2, double x3, double y3)
6342{
6343 if (justMoved || (n > 0 && subpaths[n - 1]->isClosed())) {
6344 if (n >= size) {
6345 size *= 2;
6346 subpaths = (GfxSubpath **)greallocn(p: subpaths, count: size, size: sizeof(GfxSubpath *));
6347 }
6348 if (justMoved) {
6349 subpaths[n] = new GfxSubpath(firstX, firstY);
6350 } else {
6351 subpaths[n] = new GfxSubpath(subpaths[n - 1]->getLastX(), subpaths[n - 1]->getLastY());
6352 }
6353 ++n;
6354 justMoved = false;
6355 }
6356 subpaths[n - 1]->curveTo(x1, y1, x2, y2, x3, y3);
6357}
6358
6359void GfxPath::close()
6360{
6361 // this is necessary to handle the pathological case of
6362 // moveto/closepath/clip, which defines an empty clipping region
6363 if (justMoved) {
6364 if (n >= size) {
6365 size *= 2;
6366 subpaths = (GfxSubpath **)greallocn(p: subpaths, count: size, size: sizeof(GfxSubpath *));
6367 }
6368 subpaths[n] = new GfxSubpath(firstX, firstY);
6369 ++n;
6370 justMoved = false;
6371 }
6372 subpaths[n - 1]->close();
6373}
6374
6375void GfxPath::append(GfxPath *path)
6376{
6377 int i;
6378
6379 if (n + path->n > size) {
6380 size = n + path->n;
6381 subpaths = (GfxSubpath **)greallocn(p: subpaths, count: size, size: sizeof(GfxSubpath *));
6382 }
6383 for (i = 0; i < path->n; ++i) {
6384 subpaths[n++] = path->subpaths[i]->copy();
6385 }
6386 justMoved = false;
6387}
6388
6389void GfxPath::offset(double dx, double dy)
6390{
6391 int i;
6392
6393 for (i = 0; i < n; ++i) {
6394 subpaths[i]->offset(dx, dy);
6395 }
6396}
6397
6398//------------------------------------------------------------------------
6399//
6400//------------------------------------------------------------------------
6401GfxState::ReusablePathIterator::ReusablePathIterator(GfxPath *pathA) : path(pathA), subPathOff(0), coordOff(0), numCoords(0), curSubPath(nullptr)
6402{
6403 if (path->getNumSubpaths()) {
6404 curSubPath = path->getSubpath(i: subPathOff);
6405 numCoords = curSubPath->getNumPoints();
6406 }
6407}
6408
6409bool GfxState::ReusablePathIterator::isEnd() const
6410{
6411 return coordOff >= numCoords;
6412}
6413
6414void GfxState::ReusablePathIterator::next()
6415{
6416 ++coordOff;
6417 if (coordOff == numCoords) {
6418 ++subPathOff;
6419 if (subPathOff < path->getNumSubpaths()) {
6420 coordOff = 0;
6421 curSubPath = path->getSubpath(i: subPathOff);
6422 numCoords = curSubPath->getNumPoints();
6423 }
6424 }
6425}
6426
6427void GfxState::ReusablePathIterator::setCoord(double x, double y)
6428{
6429 curSubPath->setX(i: coordOff, a: x);
6430 curSubPath->setY(i: coordOff, a: y);
6431}
6432
6433void GfxState::ReusablePathIterator::reset()
6434{
6435 coordOff = 0;
6436 subPathOff = 0;
6437 curSubPath = path->getSubpath(i: 0);
6438 numCoords = curSubPath->getNumPoints();
6439}
6440
6441GfxState::GfxState(double hDPIA, double vDPIA, const PDFRectangle *pageBox, int rotateA, bool upsideDown)
6442{
6443 double kx, ky;
6444
6445 hDPI = hDPIA;
6446 vDPI = vDPIA;
6447 rotate = rotateA;
6448 px1 = pageBox->x1;
6449 py1 = pageBox->y1;
6450 px2 = pageBox->x2;
6451 py2 = pageBox->y2;
6452 kx = hDPI / 72.0;
6453 ky = vDPI / 72.0;
6454 if (rotate == 90) {
6455 ctm[0] = 0;
6456 ctm[1] = upsideDown ? ky : -ky;
6457 ctm[2] = kx;
6458 ctm[3] = 0;
6459 ctm[4] = -kx * py1;
6460 ctm[5] = ky * (upsideDown ? -px1 : px2);
6461 pageWidth = kx * (py2 - py1);
6462 pageHeight = ky * (px2 - px1);
6463 } else if (rotate == 180) {
6464 ctm[0] = -kx;
6465 ctm[1] = 0;
6466 ctm[2] = 0;
6467 ctm[3] = upsideDown ? ky : -ky;
6468 ctm[4] = kx * px2;
6469 ctm[5] = ky * (upsideDown ? -py1 : py2);
6470 pageWidth = kx * (px2 - px1);
6471 pageHeight = ky * (py2 - py1);
6472 } else if (rotate == 270) {
6473 ctm[0] = 0;
6474 ctm[1] = upsideDown ? -ky : ky;
6475 ctm[2] = -kx;
6476 ctm[3] = 0;
6477 ctm[4] = kx * py2;
6478 ctm[5] = ky * (upsideDown ? px2 : -px1);
6479 pageWidth = kx * (py2 - py1);
6480 pageHeight = ky * (px2 - px1);
6481 } else {
6482 ctm[0] = kx;
6483 ctm[1] = 0;
6484 ctm[2] = 0;
6485 ctm[3] = upsideDown ? -ky : ky;
6486 ctm[4] = -kx * px1;
6487 ctm[5] = ky * (upsideDown ? py2 : -py1);
6488 pageWidth = kx * (px2 - px1);
6489 pageHeight = ky * (py2 - py1);
6490 }
6491
6492 fillColorSpace = new GfxDeviceGrayColorSpace();
6493 strokeColorSpace = new GfxDeviceGrayColorSpace();
6494 fillColor.c[0] = 0;
6495 strokeColor.c[0] = 0;
6496 fillPattern = nullptr;
6497 strokePattern = nullptr;
6498 blendMode = gfxBlendNormal;
6499 fillOpacity = 1;
6500 strokeOpacity = 1;
6501 fillOverprint = false;
6502 strokeOverprint = false;
6503 overprintMode = 0;
6504 transfer[0] = transfer[1] = transfer[2] = transfer[3] = nullptr;
6505
6506 lineWidth = 1;
6507 lineDashStart = 0;
6508 flatness = 1;
6509 lineJoin = 0;
6510 lineCap = 0;
6511 miterLimit = 10;
6512 strokeAdjust = false;
6513 alphaIsShape = false;
6514 textKnockout = false;
6515
6516 font = nullptr;
6517 fontSize = 0;
6518 textMat[0] = 1;
6519 textMat[1] = 0;
6520 textMat[2] = 0;
6521 textMat[3] = 1;
6522 textMat[4] = 0;
6523 textMat[5] = 0;
6524 charSpace = 0;
6525 wordSpace = 0;
6526 horizScaling = 1;
6527 leading = 0;
6528 rise = 0;
6529 render = 0;
6530
6531 path = new GfxPath();
6532 curX = curY = 0;
6533 lineX = lineY = 0;
6534
6535 clipXMin = 0;
6536 clipYMin = 0;
6537 clipXMax = pageWidth;
6538 clipYMax = pageHeight;
6539
6540 renderingIntent[0] = 0;
6541
6542 saved = nullptr;
6543
6544 defaultGrayColorSpace = nullptr;
6545 defaultRGBColorSpace = nullptr;
6546 defaultCMYKColorSpace = nullptr;
6547#ifdef USE_CMS
6548 XYZ2DisplayTransformRelCol = nullptr;
6549 XYZ2DisplayTransformAbsCol = nullptr;
6550 XYZ2DisplayTransformSat = nullptr;
6551 XYZ2DisplayTransformPerc = nullptr;
6552 localDisplayProfile = nullptr;
6553
6554 if (!sRGBProfile) {
6555 // This is probably the one of the first invocations of lcms2, so we set the error handler
6556 cmsSetLogErrorHandler(CMSError);
6557
6558 sRGBProfile = make_GfxLCMSProfilePtr(cmsCreate_sRGBProfile());
6559 }
6560
6561 if (!XYZProfile) {
6562 XYZProfile = make_GfxLCMSProfilePtr(cmsCreateXYZProfile());
6563 }
6564#endif
6565}
6566
6567GfxState::~GfxState()
6568{
6569 int i;
6570
6571 if (fillColorSpace) {
6572 delete fillColorSpace;
6573 }
6574 if (strokeColorSpace) {
6575 delete strokeColorSpace;
6576 }
6577 if (fillPattern) {
6578 delete fillPattern;
6579 }
6580 if (strokePattern) {
6581 delete strokePattern;
6582 }
6583 for (i = 0; i < 4; ++i) {
6584 if (transfer[i]) {
6585 delete transfer[i];
6586 }
6587 }
6588 if (path) {
6589 // this gets set to NULL by restore()
6590 delete path;
6591 }
6592
6593 delete defaultGrayColorSpace;
6594 delete defaultRGBColorSpace;
6595 delete defaultCMYKColorSpace;
6596}
6597
6598// Used for copy();
6599GfxState::GfxState(const GfxState *state, bool copyPath)
6600{
6601 int i;
6602
6603 hDPI = state->hDPI;
6604 vDPI = state->vDPI;
6605 memcpy(dest: ctm, src: state->ctm, n: sizeof(ctm));
6606 px1 = state->px1;
6607 py1 = state->py1;
6608 px2 = state->px2;
6609 py2 = state->py2;
6610 pageWidth = state->pageWidth;
6611 pageHeight = state->pageHeight;
6612 rotate = state->rotate;
6613
6614 fillColorSpace = state->fillColorSpace;
6615 if (fillColorSpace) {
6616 fillColorSpace = state->fillColorSpace->copy();
6617 }
6618 strokeColorSpace = state->strokeColorSpace;
6619 if (strokeColorSpace) {
6620 strokeColorSpace = state->strokeColorSpace->copy();
6621 }
6622 fillColor = state->fillColor;
6623 strokeColor = state->strokeColor;
6624
6625 fillPattern = state->fillPattern;
6626 if (fillPattern) {
6627 fillPattern = state->fillPattern->copy();
6628 }
6629 strokePattern = state->strokePattern;
6630 if (strokePattern) {
6631 strokePattern = state->strokePattern->copy();
6632 }
6633 blendMode = state->blendMode;
6634 fillOpacity = state->fillOpacity;
6635 strokeOpacity = state->strokeOpacity;
6636 fillOverprint = state->fillOverprint;
6637 strokeOverprint = state->strokeOverprint;
6638 overprintMode = state->overprintMode;
6639 for (i = 0; i < 4; ++i) {
6640 transfer[i] = state->transfer[i];
6641 if (transfer[i]) {
6642 transfer[i] = state->transfer[i]->copy();
6643 }
6644 }
6645 lineWidth = state->lineWidth;
6646 lineDash = state->lineDash;
6647 lineDashStart = state->lineDashStart;
6648 flatness = state->flatness;
6649 lineJoin = state->lineJoin;
6650 lineCap = state->lineCap;
6651 miterLimit = state->miterLimit;
6652 strokeAdjust = state->strokeAdjust;
6653 alphaIsShape = state->alphaIsShape;
6654 textKnockout = state->textKnockout;
6655
6656 font = state->font;
6657 fontSize = state->fontSize;
6658 memcpy(dest: textMat, src: state->textMat, n: sizeof(textMat));
6659 charSpace = state->charSpace;
6660 wordSpace = state->wordSpace;
6661 horizScaling = state->horizScaling;
6662 leading = state->leading;
6663 rise = state->rise;
6664 render = state->render;
6665
6666 path = state->path;
6667 if (copyPath) {
6668 path = state->path->copy();
6669 }
6670 curX = state->curX;
6671 curY = state->curY;
6672 lineX = state->lineX;
6673 lineY = state->lineY;
6674
6675 clipXMin = state->clipXMin;
6676 clipYMin = state->clipYMin;
6677 clipXMax = state->clipXMax;
6678 clipYMax = state->clipYMax;
6679 memcpy(dest: renderingIntent, src: state->renderingIntent, n: sizeof(renderingIntent));
6680
6681 saved = nullptr;
6682#ifdef USE_CMS
6683 localDisplayProfile = state->localDisplayProfile;
6684 XYZ2DisplayTransformRelCol = state->XYZ2DisplayTransformRelCol;
6685 XYZ2DisplayTransformAbsCol = state->XYZ2DisplayTransformAbsCol;
6686 XYZ2DisplayTransformSat = state->XYZ2DisplayTransformSat;
6687 XYZ2DisplayTransformPerc = state->XYZ2DisplayTransformPerc;
6688#endif
6689
6690 if (state->defaultGrayColorSpace) {
6691 defaultGrayColorSpace = state->defaultGrayColorSpace->copy();
6692 } else {
6693 defaultGrayColorSpace = nullptr;
6694 }
6695 if (state->defaultRGBColorSpace) {
6696 defaultRGBColorSpace = state->defaultRGBColorSpace->copy();
6697 } else {
6698 defaultRGBColorSpace = nullptr;
6699 }
6700 if (state->defaultCMYKColorSpace) {
6701 defaultCMYKColorSpace = state->defaultCMYKColorSpace->copy();
6702 } else {
6703 defaultCMYKColorSpace = nullptr;
6704 }
6705}
6706
6707#ifdef USE_CMS
6708
6709GfxLCMSProfilePtr GfxState::sRGBProfile = nullptr;
6710GfxLCMSProfilePtr GfxState::XYZProfile = nullptr;
6711
6712void GfxState::setDisplayProfile(const GfxLCMSProfilePtr &localDisplayProfileA)
6713{
6714 localDisplayProfile = localDisplayProfileA;
6715 if (localDisplayProfile) {
6716 cmsHTRANSFORM transform;
6717 unsigned int nChannels;
6718 unsigned int localDisplayPixelType;
6719
6720 localDisplayPixelType = getCMSColorSpaceType(cmsGetColorSpace(localDisplayProfile.get()));
6721 nChannels = getCMSNChannels(cmsGetColorSpace(localDisplayProfile.get()));
6722 // create transform from XYZ
6723 if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_RELATIVE_COLORIMETRIC, LCMS_FLAGS)) == nullptr) {
6724 error(errSyntaxWarning, -1, "Can't create Lab transform");
6725 } else {
6726 XYZ2DisplayTransformRelCol = std::make_shared<GfxColorTransform>(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
6727 }
6728
6729 if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_ABSOLUTE_COLORIMETRIC, LCMS_FLAGS)) == nullptr) {
6730 error(errSyntaxWarning, -1, "Can't create Lab transform");
6731 } else {
6732 XYZ2DisplayTransformAbsCol = std::make_shared<GfxColorTransform>(transform, INTENT_ABSOLUTE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
6733 }
6734
6735 if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_SATURATION, LCMS_FLAGS)) == nullptr) {
6736 error(errSyntaxWarning, -1, "Can't create Lab transform");
6737 } else {
6738 XYZ2DisplayTransformSat = std::make_shared<GfxColorTransform>(transform, INTENT_SATURATION, PT_XYZ, localDisplayPixelType);
6739 }
6740
6741 if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_PERCEPTUAL, LCMS_FLAGS)) == nullptr) {
6742 error(errSyntaxWarning, -1, "Can't create Lab transform");
6743 } else {
6744 XYZ2DisplayTransformPerc = std::make_shared<GfxColorTransform>(transform, INTENT_PERCEPTUAL, PT_XYZ, localDisplayPixelType);
6745 }
6746 }
6747}
6748
6749std::shared_ptr<GfxColorTransform> GfxState::getXYZ2DisplayTransform()
6750{
6751 auto transform = XYZ2DisplayTransformRelCol;
6752 if (strcmp(renderingIntent, "AbsoluteColorimetric") == 0) {
6753 transform = XYZ2DisplayTransformAbsCol;
6754 } else if (strcmp(renderingIntent, "Saturation") == 0) {
6755 transform = XYZ2DisplayTransformSat;
6756 } else if (strcmp(renderingIntent, "Perceptual") == 0) {
6757 transform = XYZ2DisplayTransformPerc;
6758 }
6759 return transform;
6760}
6761
6762int GfxState::getCmsRenderingIntent()
6763{
6764 const char *intent = getRenderingIntent();
6765 int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
6766 if (intent) {
6767 if (strcmp(intent, "AbsoluteColorimetric") == 0) {
6768 cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
6769 } else if (strcmp(intent, "Saturation") == 0) {
6770 cmsIntent = INTENT_SATURATION;
6771 } else if (strcmp(intent, "Perceptual") == 0) {
6772 cmsIntent = INTENT_PERCEPTUAL;
6773 }
6774 }
6775 return cmsIntent;
6776}
6777
6778#endif
6779
6780void GfxState::setPath(GfxPath *pathA)
6781{
6782 delete path;
6783 path = pathA;
6784}
6785
6786void GfxState::getUserClipBBox(double *xMin, double *yMin, double *xMax, double *yMax) const
6787{
6788 double ictm[6];
6789 double xMin1, yMin1, xMax1, yMax1, tx, ty;
6790
6791 // invert the CTM
6792 const double det_denominator = (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
6793 if (unlikely(det_denominator == 0)) {
6794 *xMin = 0;
6795 *yMin = 0;
6796 *xMax = 0;
6797 *yMax = 0;
6798 return;
6799 }
6800 const double det = 1 / det_denominator;
6801 ictm[0] = ctm[3] * det;
6802 ictm[1] = -ctm[1] * det;
6803 ictm[2] = -ctm[2] * det;
6804 ictm[3] = ctm[0] * det;
6805 ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
6806 ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
6807
6808 // transform all four corners of the clip bbox; find the min and max
6809 // x and y values
6810 xMin1 = xMax1 = clipXMin * ictm[0] + clipYMin * ictm[2] + ictm[4];
6811 yMin1 = yMax1 = clipXMin * ictm[1] + clipYMin * ictm[3] + ictm[5];
6812 tx = clipXMin * ictm[0] + clipYMax * ictm[2] + ictm[4];
6813 ty = clipXMin * ictm[1] + clipYMax * ictm[3] + ictm[5];
6814 if (tx < xMin1) {
6815 xMin1 = tx;
6816 } else if (tx > xMax1) {
6817 xMax1 = tx;
6818 }
6819 if (ty < yMin1) {
6820 yMin1 = ty;
6821 } else if (ty > yMax1) {
6822 yMax1 = ty;
6823 }
6824 tx = clipXMax * ictm[0] + clipYMin * ictm[2] + ictm[4];
6825 ty = clipXMax * ictm[1] + clipYMin * ictm[3] + ictm[5];
6826 if (tx < xMin1) {
6827 xMin1 = tx;
6828 } else if (tx > xMax1) {
6829 xMax1 = tx;
6830 }
6831 if (ty < yMin1) {
6832 yMin1 = ty;
6833 } else if (ty > yMax1) {
6834 yMax1 = ty;
6835 }
6836 tx = clipXMax * ictm[0] + clipYMax * ictm[2] + ictm[4];
6837 ty = clipXMax * ictm[1] + clipYMax * ictm[3] + ictm[5];
6838 if (tx < xMin1) {
6839 xMin1 = tx;
6840 } else if (tx > xMax1) {
6841 xMax1 = tx;
6842 }
6843 if (ty < yMin1) {
6844 yMin1 = ty;
6845 } else if (ty > yMax1) {
6846 yMax1 = ty;
6847 }
6848
6849 *xMin = xMin1;
6850 *yMin = yMin1;
6851 *xMax = xMax1;
6852 *yMax = yMax1;
6853}
6854
6855double GfxState::transformWidth(double w) const
6856{
6857 double x, y;
6858
6859 x = ctm[0] + ctm[2];
6860 y = ctm[1] + ctm[3];
6861 return w * sqrt(x: 0.5 * (x * x + y * y));
6862}
6863
6864double GfxState::getTransformedFontSize() const
6865{
6866 double x1, y1, x2, y2;
6867
6868 x1 = textMat[2] * fontSize;
6869 y1 = textMat[3] * fontSize;
6870 x2 = ctm[0] * x1 + ctm[2] * y1;
6871 y2 = ctm[1] * x1 + ctm[3] * y1;
6872 return sqrt(x: x2 * x2 + y2 * y2);
6873}
6874
6875void GfxState::getFontTransMat(double *m11, double *m12, double *m21, double *m22) const
6876{
6877 *m11 = (textMat[0] * ctm[0] + textMat[1] * ctm[2]) * fontSize;
6878 *m12 = (textMat[0] * ctm[1] + textMat[1] * ctm[3]) * fontSize;
6879 *m21 = (textMat[2] * ctm[0] + textMat[3] * ctm[2]) * fontSize;
6880 *m22 = (textMat[2] * ctm[1] + textMat[3] * ctm[3]) * fontSize;
6881}
6882
6883void GfxState::setCTM(double a, double b, double c, double d, double e, double f)
6884{
6885 ctm[0] = a;
6886 ctm[1] = b;
6887 ctm[2] = c;
6888 ctm[3] = d;
6889 ctm[4] = e;
6890 ctm[5] = f;
6891}
6892
6893void GfxState::concatCTM(double a, double b, double c, double d, double e, double f)
6894{
6895 double a1 = ctm[0];
6896 double b1 = ctm[1];
6897 double c1 = ctm[2];
6898 double d1 = ctm[3];
6899
6900 ctm[0] = a * a1 + b * c1;
6901 ctm[1] = a * b1 + b * d1;
6902 ctm[2] = c * a1 + d * c1;
6903 ctm[3] = c * b1 + d * d1;
6904 ctm[4] = e * a1 + f * c1 + ctm[4];
6905 ctm[5] = e * b1 + f * d1 + ctm[5];
6906}
6907
6908void GfxState::shiftCTMAndClip(double tx, double ty)
6909{
6910 ctm[4] += tx;
6911 ctm[5] += ty;
6912 clipXMin += tx;
6913 clipYMin += ty;
6914 clipXMax += tx;
6915 clipYMax += ty;
6916}
6917
6918void GfxState::setFillColorSpace(GfxColorSpace *colorSpace)
6919{
6920 if (fillColorSpace) {
6921 delete fillColorSpace;
6922 }
6923 fillColorSpace = colorSpace;
6924}
6925
6926void GfxState::setStrokeColorSpace(GfxColorSpace *colorSpace)
6927{
6928 if (strokeColorSpace) {
6929 delete strokeColorSpace;
6930 }
6931 strokeColorSpace = colorSpace;
6932}
6933
6934void GfxState::setFillPattern(GfxPattern *pattern)
6935{
6936 if (fillPattern) {
6937 delete fillPattern;
6938 }
6939 fillPattern = pattern;
6940}
6941
6942void GfxState::setStrokePattern(GfxPattern *pattern)
6943{
6944 if (strokePattern) {
6945 delete strokePattern;
6946 }
6947 strokePattern = pattern;
6948}
6949
6950void GfxState::setFont(std::shared_ptr<GfxFont> fontA, double fontSizeA)
6951{
6952 font = std::move(fontA);
6953 fontSize = fontSizeA;
6954}
6955
6956void GfxState::setTransfer(Function **funcs)
6957{
6958 int i;
6959
6960 for (i = 0; i < 4; ++i) {
6961 if (transfer[i]) {
6962 delete transfer[i];
6963 }
6964 transfer[i] = funcs[i];
6965 }
6966}
6967
6968void GfxState::setLineDash(std::vector<double> &&dash, double start)
6969{
6970 lineDash = dash;
6971 lineDashStart = start;
6972}
6973
6974void GfxState::clearPath()
6975{
6976 delete path;
6977 path = new GfxPath();
6978}
6979
6980void GfxState::clip()
6981{
6982 double xMin, yMin, xMax, yMax, x, y;
6983 GfxSubpath *subpath;
6984 int i, j;
6985
6986 xMin = xMax = yMin = yMax = 0; // make gcc happy
6987 for (i = 0; i < path->getNumSubpaths(); ++i) {
6988 subpath = path->getSubpath(i);
6989 for (j = 0; j < subpath->getNumPoints(); ++j) {
6990 transform(x1: subpath->getX(i: j), y1: subpath->getY(i: j), x2: &x, y2: &y);
6991 if (i == 0 && j == 0) {
6992 xMin = xMax = x;
6993 yMin = yMax = y;
6994 } else {
6995 if (x < xMin) {
6996 xMin = x;
6997 } else if (x > xMax) {
6998 xMax = x;
6999 }
7000 if (y < yMin) {
7001 yMin = y;
7002 } else if (y > yMax) {
7003 yMax = y;
7004 }
7005 }
7006 }
7007 }
7008 if (xMin > clipXMin) {
7009 clipXMin = xMin;
7010 }
7011 if (yMin > clipYMin) {
7012 clipYMin = yMin;
7013 }
7014 if (xMax < clipXMax) {
7015 clipXMax = xMax;
7016 }
7017 if (yMax < clipYMax) {
7018 clipYMax = yMax;
7019 }
7020}
7021
7022void GfxState::clipToStrokePath()
7023{
7024 double xMin, yMin, xMax, yMax, x, y, t0, t1;
7025 GfxSubpath *subpath;
7026 int i, j;
7027
7028 xMin = xMax = yMin = yMax = 0; // make gcc happy
7029 for (i = 0; i < path->getNumSubpaths(); ++i) {
7030 subpath = path->getSubpath(i);
7031 for (j = 0; j < subpath->getNumPoints(); ++j) {
7032 transform(x1: subpath->getX(i: j), y1: subpath->getY(i: j), x2: &x, y2: &y);
7033 if (i == 0 && j == 0) {
7034 xMin = xMax = x;
7035 yMin = yMax = y;
7036 } else {
7037 if (x < xMin) {
7038 xMin = x;
7039 } else if (x > xMax) {
7040 xMax = x;
7041 }
7042 if (y < yMin) {
7043 yMin = y;
7044 } else if (y > yMax) {
7045 yMax = y;
7046 }
7047 }
7048 }
7049 }
7050
7051 // allow for the line width
7052 //~ miter joins can extend farther than this
7053 t0 = fabs(x: ctm[0]);
7054 t1 = fabs(x: ctm[2]);
7055 if (t0 > t1) {
7056 xMin -= 0.5 * lineWidth * t0;
7057 xMax += 0.5 * lineWidth * t0;
7058 } else {
7059 xMin -= 0.5 * lineWidth * t1;
7060 xMax += 0.5 * lineWidth * t1;
7061 }
7062 t0 = fabs(x: ctm[0]);
7063 t1 = fabs(x: ctm[3]);
7064 if (t0 > t1) {
7065 yMin -= 0.5 * lineWidth * t0;
7066 yMax += 0.5 * lineWidth * t0;
7067 } else {
7068 yMin -= 0.5 * lineWidth * t1;
7069 yMax += 0.5 * lineWidth * t1;
7070 }
7071
7072 if (xMin > clipXMin) {
7073 clipXMin = xMin;
7074 }
7075 if (yMin > clipYMin) {
7076 clipYMin = yMin;
7077 }
7078 if (xMax < clipXMax) {
7079 clipXMax = xMax;
7080 }
7081 if (yMax < clipYMax) {
7082 clipYMax = yMax;
7083 }
7084}
7085
7086void GfxState::clipToRect(double xMin, double yMin, double xMax, double yMax)
7087{
7088 double x, y, xMin1, yMin1, xMax1, yMax1;
7089
7090 transform(x1: xMin, y1: yMin, x2: &x, y2: &y);
7091 xMin1 = xMax1 = x;
7092 yMin1 = yMax1 = y;
7093 transform(x1: xMax, y1: yMin, x2: &x, y2: &y);
7094 if (x < xMin1) {
7095 xMin1 = x;
7096 } else if (x > xMax1) {
7097 xMax1 = x;
7098 }
7099 if (y < yMin1) {
7100 yMin1 = y;
7101 } else if (y > yMax1) {
7102 yMax1 = y;
7103 }
7104 transform(x1: xMax, y1: yMax, x2: &x, y2: &y);
7105 if (x < xMin1) {
7106 xMin1 = x;
7107 } else if (x > xMax1) {
7108 xMax1 = x;
7109 }
7110 if (y < yMin1) {
7111 yMin1 = y;
7112 } else if (y > yMax1) {
7113 yMax1 = y;
7114 }
7115 transform(x1: xMin, y1: yMax, x2: &x, y2: &y);
7116 if (x < xMin1) {
7117 xMin1 = x;
7118 } else if (x > xMax1) {
7119 xMax1 = x;
7120 }
7121 if (y < yMin1) {
7122 yMin1 = y;
7123 } else if (y > yMax1) {
7124 yMax1 = y;
7125 }
7126
7127 if (xMin1 > clipXMin) {
7128 clipXMin = xMin1;
7129 }
7130 if (yMin1 > clipYMin) {
7131 clipYMin = yMin1;
7132 }
7133 if (xMax1 < clipXMax) {
7134 clipXMax = xMax1;
7135 }
7136 if (yMax1 < clipYMax) {
7137 clipYMax = yMax1;
7138 }
7139}
7140
7141void GfxState::textShift(double tx, double ty)
7142{
7143 double dx, dy;
7144
7145 textTransformDelta(x1: tx, y1: ty, x2: &dx, y2: &dy);
7146 curX += dx;
7147 curY += dy;
7148}
7149
7150void GfxState::shift(double dx, double dy)
7151{
7152 curX += dx;
7153 curY += dy;
7154}
7155
7156GfxState *GfxState::save()
7157{
7158 GfxState *newState;
7159
7160 newState = copy();
7161 newState->saved = this;
7162 return newState;
7163}
7164
7165GfxState *GfxState::restore()
7166{
7167 GfxState *oldState;
7168
7169 if (saved) {
7170 oldState = saved;
7171
7172 // these attributes aren't saved/restored by the q/Q operators
7173 oldState->path = path;
7174 oldState->curX = curX;
7175 oldState->curY = curY;
7176 oldState->lineX = lineX;
7177 oldState->lineY = lineY;
7178
7179 path = nullptr;
7180 saved = nullptr;
7181 delete this;
7182
7183 } else {
7184 oldState = this;
7185 }
7186
7187 return oldState;
7188}
7189
7190bool GfxState::parseBlendMode(Object *obj, GfxBlendMode *mode)
7191{
7192 int i, j;
7193
7194 if (obj->isName()) {
7195 for (i = 0; i < nGfxBlendModeNames; ++i) {
7196 if (!strcmp(s1: obj->getName(), s2: gfxBlendModeNames[i].name)) {
7197 *mode = gfxBlendModeNames[i].mode;
7198 return true;
7199 }
7200 }
7201 return false;
7202 } else if (obj->isArray()) {
7203 for (i = 0; i < obj->arrayGetLength(); ++i) {
7204 Object obj2 = obj->arrayGet(i);
7205 if (!obj2.isName()) {
7206 return false;
7207 }
7208 for (j = 0; j < nGfxBlendModeNames; ++j) {
7209 if (!strcmp(s1: obj2.getName(), s2: gfxBlendModeNames[j].name)) {
7210 *mode = gfxBlendModeNames[j].mode;
7211 return true;
7212 }
7213 }
7214 }
7215 *mode = gfxBlendNormal;
7216 return true;
7217 } else {
7218 return false;
7219 }
7220}
7221

source code of poppler/poppler/GfxState.cc