//======================================================================== // // GfxState.cc // // Copyright 1996-2003 Glyph & Cog, LLC // //======================================================================== //======================================================================== // // Modified under the Poppler project - http://poppler.freedesktop.org // // All changes made under the Poppler project to this file are licensed // under GPL version 2 or later // // Copyright (C) 2005 Kristian Høgsberg // Copyright (C) 2006, 2007 Jeff Muizelaar // Copyright (C) 2006, 2010 Carlos Garcia Campos // Copyright (C) 2006-2020 Albert Astals Cid // Copyright (C) 2009, 2012 Koji Otani // Copyright (C) 2009, 2011-2016 Thomas Freitag // Copyright (C) 2009, 2019 Christian Persch // Copyright (C) 2010 Paweł Wiejacha // Copyright (C) 2010 Christian Feuersänger // Copyright (C) 2011 Andrea Canciani // Copyright (C) 2012 William Bader // Copyright (C) 2013 Lu Wang // Copyright (C) 2013 Hib Eris // Copyright (C) 2013 Fabio D'Urso // Copyright (C) 2015, 2020 Adrian Johnson // Copyright (C) 2016 Marek Kasik // Copyright (C) 2017, 2019 Oliver Sander // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich // Copyright (C) 2018 Volker Krause // Copyright (C) 2018, 2019 Adam Reichold // Copyright (C) 2019 LE GARREC Vincent // Copyright (C) 2020 Philipp Knechtges // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #include #include #include #include #include #include #include "goo/gfile.h" #include "goo/gmem.h" #include "Error.h" #include "Object.h" #include "Array.h" #include "Page.h" #include "Gfx.h" #include "GfxState.h" #include "GfxState_helpers.h" #include "GfxFont.h" #include "GlobalParams.h" #include "PopplerCache.h" #include "OutputDev.h" #include "splash/SplashTypes.h" //------------------------------------------------------------------------ // Max depth of nested color spaces. This is used to catch infinite // loops in the color space object structure. #define colorSpaceRecursionLimit 8 //------------------------------------------------------------------------ bool Matrix::invertTo(Matrix *other) const { const double det_denominator = determinant(); if (unlikely(det_denominator == 0)) { *other = { 1, 0, 0, 1, 0, 0 }; return false; } const double det = 1 / det_denominator; other->m[0] = m[3] * det; other->m[1] = -m[1] * det; other->m[2] = -m[2] * det; other->m[3] = m[0] * det; other->m[4] = (m[2] * m[5] - m[3] * m[4]) * det; other->m[5] = (m[1] * m[4] - m[0] * m[5]) * det; return true; } void Matrix::translate(double tx, double ty) { double x0 = tx * m[0] + ty * m[2] + m[4]; double y0 = tx * m[1] + ty * m[3] + m[5]; m[4] = x0; m[5] = y0; } void Matrix::scale(double sx, double sy) { m[0] *= sx; m[1] *= sx; m[2] *= sy; m[3] *= sy; } void Matrix::transform(double x, double y, double *tx, double *ty) const { double temp_x, temp_y; temp_x = m[0] * x + m[2] * y + m[4]; temp_y = m[1] * x + m[3] * y + m[5]; *tx = temp_x; *ty = temp_y; } // Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis double Matrix::norm() const { double f, g, h, i, j; i = m[0] * m[0] + m[1] * m[1]; j = m[2] * m[2] + m[3] * m[3]; f = 0.5 * (i + j); g = 0.5 * (i - j); h = m[0] * m[2] + m[1] * m[3]; return sqrt(f + hypot(g, h)); } //------------------------------------------------------------------------ struct GfxBlendModeInfo { const char *name; GfxBlendMode mode; }; static const GfxBlendModeInfo gfxBlendModeNames[] = { { "Normal", gfxBlendNormal }, { "Compatible", gfxBlendNormal }, { "Multiply", gfxBlendMultiply }, { "Screen", gfxBlendScreen }, { "Overlay", gfxBlendOverlay }, { "Darken", gfxBlendDarken }, { "Lighten", gfxBlendLighten }, { "ColorDodge", gfxBlendColorDodge }, { "ColorBurn", gfxBlendColorBurn }, { "HardLight", gfxBlendHardLight }, { "SoftLight", gfxBlendSoftLight }, { "Difference", gfxBlendDifference }, { "Exclusion", gfxBlendExclusion }, { "Hue", gfxBlendHue }, { "Saturation", gfxBlendSaturation }, { "Color", gfxBlendColor }, { "Luminosity", gfxBlendLuminosity } }; #define nGfxBlendModeNames ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo)))) //------------------------------------------------------------------------ // // NB: This must match the GfxColorSpaceMode enum defined in // GfxState.h static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern" }; #define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *))) #ifdef USE_CMS static const std::map::size_type CMSCACHE_LIMIT = 2048; # include # define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION static void lcmsprofiledeleter(void *profile) { cmsCloseProfile(profile); } GfxLCMSProfilePtr make_GfxLCMSProfilePtr(void *profile) { if (profile == nullptr) { return GfxLCMSProfilePtr(); } return GfxLCMSProfilePtr(profile, lcmsprofiledeleter); } void GfxColorTransform::doTransform(void *in, void *out, unsigned int size) { cmsDoTransform(transform, in, out, size); } // transformA should be a cmsHTRANSFORM GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA) { transform = transformA; cmsIntent = cmsIntentA; inputPixelType = inputPixelTypeA; transformPixelType = transformPixelTypeA; } GfxColorTransform::~GfxColorTransform() { cmsDeleteTransform(transform); } // convert color space signature to cmsColor type static unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs); static unsigned int getCMSNChannels(cmsColorSpaceSignature cs); #endif //------------------------------------------------------------------------ // GfxColorSpace //------------------------------------------------------------------------ GfxColorSpace::GfxColorSpace() { overprintMask = 0x0f; mapping = nullptr; } GfxColorSpace::~GfxColorSpace() { } GfxColorSpace *GfxColorSpace::parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion) { GfxColorSpace *cs; Object obj1; if (recursion > colorSpaceRecursionLimit) { error(errSyntaxError, -1, "Loop detected in color space objects"); return nullptr; } cs = nullptr; if (csObj->isName()) { if (csObj->isName("DeviceGray") || csObj->isName("G")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultGray"); if (objCS.isNull()) { cs = new GfxDeviceGrayColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceGrayColorSpace(); } } else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultRGB"); if (objCS.isNull()) { cs = new GfxDeviceRGBColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceRGBColorSpace(); } } else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultCMYK"); if (objCS.isNull()) { cs = new GfxDeviceCMYKColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceCMYKColorSpace(); } } else if (csObj->isName("Pattern")) { cs = new GfxPatternColorSpace(nullptr); } else { error(errSyntaxWarning, -1, "Bad color space '{0:s}'", csObj->getName()); } } else if (csObj->isArray() && csObj->arrayGetLength() > 0) { obj1 = csObj->arrayGet(0); if (obj1.isName("DeviceGray") || obj1.isName("G")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultGray"); if (objCS.isNull()) { cs = new GfxDeviceGrayColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceGrayColorSpace(); } } else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultRGB"); if (objCS.isNull()) { cs = new GfxDeviceRGBColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceRGBColorSpace(); } } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultCMYK"); if (objCS.isNull()) { cs = new GfxDeviceCMYKColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceCMYKColorSpace(); } } else if (obj1.isName("CalGray")) { cs = GfxCalGrayColorSpace::parse(csObj->getArray(), state); } else if (obj1.isName("CalRGB")) { cs = GfxCalRGBColorSpace::parse(csObj->getArray(), state); } else if (obj1.isName("Lab")) { cs = GfxLabColorSpace::parse(csObj->getArray(), state); } else if (obj1.isName("ICCBased")) { cs = GfxICCBasedColorSpace::parse(csObj->getArray(), out, state, recursion); } else if (obj1.isName("Indexed") || obj1.isName("I")) { cs = GfxIndexedColorSpace::parse(res, csObj->getArray(), out, state, recursion); } else if (obj1.isName("Separation")) { cs = GfxSeparationColorSpace::parse(res, csObj->getArray(), out, state, recursion); } else if (obj1.isName("DeviceN")) { cs = GfxDeviceNColorSpace::parse(res, csObj->getArray(), out, state, recursion); } else if (obj1.isName("Pattern")) { cs = GfxPatternColorSpace::parse(res, csObj->getArray(), out, state, recursion); } else { error(errSyntaxWarning, -1, "Bad color space"); } } else if (csObj->isDict()) { obj1 = csObj->dictLookup("ColorSpace"); if (obj1.isName("DeviceGray")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultGray"); if (objCS.isNull()) { cs = new GfxDeviceGrayColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceGrayColorSpace(); } } else if (obj1.isName("DeviceRGB")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultRGB"); if (objCS.isNull()) { cs = new GfxDeviceRGBColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceRGBColorSpace(); } } else if (obj1.isName("DeviceCMYK")) { if (res != nullptr) { Object objCS = res->lookupColorSpace("DefaultCMYK"); if (objCS.isNull()) { cs = new GfxDeviceCMYKColorSpace(); } else { cs = GfxColorSpace::parse(nullptr, &objCS, out, state); } } else { cs = new GfxDeviceCMYKColorSpace(); } } else { error(errSyntaxWarning, -1, "Bad color space dict'"); } } else { error(errSyntaxWarning, -1, "Bad color space - expected name or array or dict"); } return cs; } void GfxColorSpace::createMapping(std::vector *separationList, int maxSepComps) { return; } void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const { int i; for (i = 0; i < getNComps(); ++i) { decodeLow[i] = 0; decodeRange[i] = 1; } } int GfxColorSpace::getNumColorSpaceModes() { return nGfxColorSpaceModes; } const char *GfxColorSpace::getColorSpaceModeName(int idx) { return gfxColorSpaceModeNames[idx]; } #ifdef USE_CMS static void CMSError(cmsContext /*contextId*/, cmsUInt32Number /*ecode*/, const char *text) { error(errSyntaxWarning, -1, "{0:s}", text); } unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs) { switch (cs) { case cmsSigXYZData: return PT_XYZ; break; case cmsSigLabData: return PT_Lab; break; case cmsSigLuvData: return PT_YUV; break; case cmsSigYCbCrData: return PT_YCbCr; break; case cmsSigYxyData: return PT_Yxy; break; case cmsSigRgbData: return PT_RGB; break; case cmsSigGrayData: return PT_GRAY; break; case cmsSigHsvData: return PT_HSV; break; case cmsSigHlsData: return PT_HLS; break; case cmsSigCmykData: return PT_CMYK; break; case cmsSigCmyData: return PT_CMY; break; case cmsSig2colorData: case cmsSig3colorData: case cmsSig4colorData: case cmsSig5colorData: case cmsSig6colorData: case cmsSig7colorData: case cmsSig8colorData: case cmsSig9colorData: case cmsSig10colorData: case cmsSig11colorData: case cmsSig12colorData: case cmsSig13colorData: case cmsSig14colorData: case cmsSig15colorData: default: break; } return PT_RGB; } unsigned int getCMSNChannels(cmsColorSpaceSignature cs) { switch (cs) { case cmsSigXYZData: case cmsSigLuvData: case cmsSigLabData: case cmsSigYCbCrData: case cmsSigYxyData: case cmsSigRgbData: case cmsSigHsvData: case cmsSigHlsData: case cmsSigCmyData: case cmsSig3colorData: return 3; break; case cmsSigGrayData: return 1; break; case cmsSigCmykData: case cmsSig4colorData: return 4; break; case cmsSig2colorData: return 2; break; case cmsSig5colorData: return 5; break; case cmsSig6colorData: return 6; break; case cmsSig7colorData: return 7; break; case cmsSig8colorData: return 8; break; case cmsSig9colorData: return 9; break; case cmsSig10colorData: return 10; break; case cmsSig11colorData: return 11; break; case cmsSig12colorData: return 12; break; case cmsSig13colorData: return 13; break; case cmsSig14colorData: return 14; break; case cmsSig15colorData: return 15; default: break; } return 3; } #endif //------------------------------------------------------------------------ // GfxDeviceGrayColorSpace //------------------------------------------------------------------------ GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() { } GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() { } GfxColorSpace *GfxDeviceGrayColorSpace::copy() const { return new GfxDeviceGrayColorSpace(); } void GfxDeviceGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { *gray = clip01(color->c[0]); } void GfxDeviceGrayColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length) { memcpy(out, in, length); } void GfxDeviceGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { rgb->r = rgb->g = rgb->b = clip01(color->c[0]); } void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) { int i; for (i = 0; i < length; i++) out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0); } void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = in[i]; *out++ = in[i]; *out++ = in[i]; } } void GfxDeviceGrayColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = in[i]; *out++ = in[i]; *out++ = in[i]; *out++ = 255; } } void GfxDeviceGrayColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = 0; *out++ = 0; *out++ = 0; *out++ = in[i]; } } void GfxDeviceGrayColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { for (int j = 0; j < SPOT_NCOMPS + 4; j++) out[j] = 0; out[4] = in[i]; out += (SPOT_NCOMPS + 4); } } void GfxDeviceGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { cmyk->c = cmyk->m = cmyk->y = 0; cmyk->k = clip01(gfxColorComp1 - color->c[0]); } void GfxDeviceGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { clearGfxColor(deviceN); deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]); } void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; } //------------------------------------------------------------------------ // GfxCalGrayColorSpace //------------------------------------------------------------------------ GfxCalGrayColorSpace::GfxCalGrayColorSpace() { whiteX = whiteY = whiteZ = 1; blackX = blackY = blackZ = 0; gamma = 1; } GfxCalGrayColorSpace::~GfxCalGrayColorSpace() { } GfxColorSpace *GfxCalGrayColorSpace::copy() const { GfxCalGrayColorSpace *cs; cs = new GfxCalGrayColorSpace(); cs->whiteX = whiteX; cs->whiteY = whiteY; cs->whiteZ = whiteZ; cs->blackX = blackX; cs->blackY = blackY; cs->blackZ = blackZ; cs->gamma = gamma; cs->kr = kr; cs->kg = kg; cs->kb = kb; #ifdef USE_CMS cs->transform = transform; #endif return cs; } // This is the inverse of MatrixLMN in Example 4.10 from the PostScript // Language Reference, Third Edition. static const double xyzrgb[3][3] = { { 3.240449, -1.537136, -0.498531 }, { -0.969265, 1.876011, 0.041556 }, { 0.055643, -0.204026, 1.057229 } }; GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state) { GfxCalGrayColorSpace *cs; Object obj1, obj2; obj1 = arr->get(1); if (!obj1.isDict()) { error(errSyntaxWarning, -1, "Bad CalGray color space"); return nullptr; } cs = new GfxCalGrayColorSpace(); obj2 = obj1.dictLookup("WhitePoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1); cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1); cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1); } obj2 = obj1.dictLookup("BlackPoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0); cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0); cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0); } cs->gamma = obj1.dictLookup("Gamma").getNumWithDefaultValue(1); cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif return cs; } // convert CalGray to media XYZ color space // (not multiply by the white point) void GfxCalGrayColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const { const double A = colToDbl(color->c[0]); const double xyzColor = pow(A, gamma); *pX = xyzColor; *pY = xyzColor; *pZ = xyzColor; } void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { GfxRGB rgb; #ifdef USE_CMS if (transform && transform->getTransformPixelType() == PT_GRAY) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; double X, Y, Z; getXYZ(color, &X, &Y, &Z); in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; } #endif getRGB(color, &rgb); *gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5)); } void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double X, Y, Z; double r, g, b; getXYZ(color, &X, &Y, &Z); #ifdef USE_CMS if (transform && transform->getTransformPixelType() == PT_RGB) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); rgb->b = byteToCol(out[2]); return; } #endif X *= whiteX; Y *= whiteY; Z *= whiteZ; // convert XYZ to RGB, including gamut mapping and gamma correction r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; rgb->r = dblToCol(sqrt(clip01(r * kr))); rgb->g = dblToCol(sqrt(clip01(g * kg))); rgb->b = dblToCol(sqrt(clip01(b * kb))); } void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { GfxRGB rgb; GfxColorComp c, m, y, k; #ifdef USE_CMS if (transform && transform->getTransformPixelType() == PT_CMYK) { double in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; double X, Y, Z; getXYZ(color, &X, &Y, &Z); in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); cmyk->y = byteToCol(out[2]); cmyk->k = byteToCol(out[3]); return; } #endif getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); y = clip01(gfxColorComp1 - rgb.b); k = c; if (m < k) { k = m; } if (y < k) { k = y; } cmyk->c = c - k; cmyk->m = m - k; cmyk->y = y - k; cmyk->k = k; } void GfxCalGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxCMYK cmyk; clearGfxColor(deviceN); getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; } //------------------------------------------------------------------------ // GfxDeviceRGBColorSpace //------------------------------------------------------------------------ GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() { } GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() { } GfxColorSpace *GfxDeviceRGBColorSpace::copy() const { return new GfxDeviceRGBColorSpace(); } void GfxDeviceRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { *gray = clip01((GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + 0.11 * color->c[2] + 0.5)); } void GfxDeviceRGBColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length) { int i; for (i = 0; i < length; i++) { out[i] = (in[i * 3 + 0] * 19595 + in[i * 3 + 1] * 38469 + in[i * 3 + 2] * 7472) / 65536; } } void GfxDeviceRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { rgb->r = clip01(color->c[0]); rgb->g = clip01(color->c[1]); rgb->b = clip01(color->c[2]); } void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) { unsigned char *p; int i; for (i = 0, p = in; i < length; i++, p += 3) out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0); } void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = *in++; *out++ = *in++; *out++ = *in++; } } void GfxDeviceRGBColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = *in++; *out++ = *in++; *out++ = *in++; *out++ = 255; } } void GfxDeviceRGBColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) { GfxColorComp c, m, y, k; for (int i = 0; i < length; i++) { c = byteToCol(255 - *in++); m = byteToCol(255 - *in++); y = byteToCol(255 - *in++); k = c; if (m < k) { k = m; } if (y < k) { k = y; } *out++ = colToByte(c - k); *out++ = colToByte(m - k); *out++ = colToByte(y - k); *out++ = colToByte(k); } } void GfxDeviceRGBColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { GfxColorComp c, m, y, k; for (int i = 0; i < length; i++) { for (int j = 0; j < SPOT_NCOMPS + 4; j++) out[j] = 0; c = byteToCol(255 - *in++); m = byteToCol(255 - *in++); y = byteToCol(255 - *in++); k = c; if (m < k) { k = m; } if (y < k) { k = y; } out[0] = colToByte(c - k); out[1] = colToByte(m - k); out[2] = colToByte(y - k); out[3] = colToByte(k); out += (SPOT_NCOMPS + 4); } } void GfxDeviceRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { GfxColorComp c, m, y, k; c = clip01(gfxColorComp1 - color->c[0]); m = clip01(gfxColorComp1 - color->c[1]); y = clip01(gfxColorComp1 - color->c[2]); k = c; if (m < k) { k = m; } if (y < k) { k = y; } cmyk->c = c - k; cmyk->m = m - k; cmyk->y = y - k; cmyk->k = k; } void GfxDeviceRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxCMYK cmyk; clearGfxColor(deviceN); getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; color->c[1] = 0; color->c[2] = 0; } //------------------------------------------------------------------------ // GfxCalRGBColorSpace //------------------------------------------------------------------------ GfxCalRGBColorSpace::GfxCalRGBColorSpace() { whiteX = whiteY = whiteZ = 1; blackX = blackY = blackZ = 0; gammaR = gammaG = gammaB = 1; mat[0] = 1; mat[1] = 0; mat[2] = 0; mat[3] = 0; mat[4] = 1; mat[5] = 0; mat[6] = 0; mat[7] = 0; mat[8] = 1; } GfxCalRGBColorSpace::~GfxCalRGBColorSpace() { } GfxColorSpace *GfxCalRGBColorSpace::copy() const { GfxCalRGBColorSpace *cs; int i; cs = new GfxCalRGBColorSpace(); cs->whiteX = whiteX; cs->whiteY = whiteY; cs->whiteZ = whiteZ; cs->blackX = blackX; cs->blackY = blackY; cs->blackZ = blackZ; cs->gammaR = gammaR; cs->gammaG = gammaG; cs->gammaB = gammaB; cs->kr = kr; cs->kg = kg; cs->kb = kb; for (i = 0; i < 9; ++i) { cs->mat[i] = mat[i]; } #ifdef USE_CMS cs->transform = transform; #endif return cs; } GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state) { GfxCalRGBColorSpace *cs; Object obj1, obj2; int i; obj1 = arr->get(1); if (!obj1.isDict()) { error(errSyntaxWarning, -1, "Bad CalRGB color space"); return nullptr; } cs = new GfxCalRGBColorSpace(); obj2 = obj1.dictLookup("WhitePoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1); cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1); cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1); } obj2 = obj1.dictLookup("BlackPoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0); cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0); cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0); } obj2 = obj1.dictLookup("Gamma"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->gammaR = obj2.arrayGet(0).getNumWithDefaultValue(1); cs->gammaG = obj2.arrayGet(1).getNumWithDefaultValue(1); cs->gammaB = obj2.arrayGet(2).getNumWithDefaultValue(1); } obj2 = obj1.dictLookup("Matrix"); if (obj2.isArray() && obj2.arrayGetLength() == 9) { for (i = 0; i < 9; ++i) { Object obj3 = obj2.arrayGet(i); if (likely(obj3.isNum())) cs->mat[i] = obj3.getNum(); } } cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif return cs; } // convert CalRGB to XYZ color space void GfxCalRGBColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const { double A, B, C; A = pow(colToDbl(color->c[0]), gammaR); B = pow(colToDbl(color->c[1]), gammaG); C = pow(colToDbl(color->c[2]), gammaB); *pX = mat[0] * A + mat[3] * B + mat[6] * C; *pY = mat[1] * A + mat[4] * B + mat[7] * C; *pZ = mat[2] * A + mat[5] * B + mat[8] * C; } void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { GfxRGB rgb; #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; double X, Y, Z; getXYZ(color, &X, &Y, &Z); in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; } #endif getRGB(color, &rgb); *gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5)); } void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double X, Y, Z; double r, g, b; getXYZ(color, &X, &Y, &Z); #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; in[0] = clip01(X / whiteX); in[1] = clip01(Y / whiteY); in[2] = clip01(Z / whiteZ); transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); rgb->b = byteToCol(out[2]); return; } #endif // convert XYZ to RGB, including gamut mapping and gamma correction r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; rgb->r = dblToCol(sqrt(clip01(r))); rgb->g = dblToCol(sqrt(clip01(g))); rgb->b = dblToCol(sqrt(clip01(b))); } void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { GfxRGB rgb; GfxColorComp c, m, y, k; #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) { double in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; double X, Y, Z; getXYZ(color, &X, &Y, &Z); in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); cmyk->y = byteToCol(out[2]); cmyk->k = byteToCol(out[3]); return; } #endif getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); y = clip01(gfxColorComp1 - rgb.b); k = c; if (m < k) { k = m; } if (y < k) { k = y; } cmyk->c = c - k; cmyk->m = m - k; cmyk->y = y - k; cmyk->k = k; } void GfxCalRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxCMYK cmyk; clearGfxColor(deviceN); getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; color->c[1] = 0; color->c[2] = 0; } //------------------------------------------------------------------------ // GfxDeviceCMYKColorSpace //------------------------------------------------------------------------ GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() { } GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() { } GfxColorSpace *GfxDeviceCMYKColorSpace::copy() const { return new GfxDeviceCMYKColorSpace(); } void GfxDeviceCMYKColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { *gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] - 0.59 * color->c[1] - 0.11 * color->c[2] + 0.5)); } void GfxDeviceCMYKColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double c, m, y, k, c1, m1, y1, k1, r, g, b; c = colToDbl(color->c[0]); m = colToDbl(color->c[1]); y = colToDbl(color->c[2]); k = colToDbl(color->c[3]); c1 = 1 - c; m1 = 1 - m; y1 = 1 - y; k1 = 1 - k; cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b); rgb->r = clip01(dblToCol(r)); rgb->g = clip01(dblToCol(g)); rgb->b = clip01(dblToCol(b)); } static inline void GfxDeviceCMYKColorSpacegetRGBLineHelper(unsigned char *&in, double &r, double &g, double &b) { double c, m, y, k, c1, m1, y1, k1; c = byteToDbl(*in++); m = byteToDbl(*in++); y = byteToDbl(*in++); k = byteToDbl(*in++); c1 = 1 - c; m1 = 1 - m; y1 = 1 - y; k1 = 1 - k; cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b); } void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) { double r, g, b; for (int i = 0; i < length; i++) { GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b); *out++ = (dblToByte(clip01(r)) << 16) | (dblToByte(clip01(g)) << 8) | dblToByte(clip01(b)); } } void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) { double r, g, b; for (int i = 0; i < length; i++) { GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b); *out++ = dblToByte(clip01(r)); *out++ = dblToByte(clip01(g)); *out++ = dblToByte(clip01(b)); } } void GfxDeviceCMYKColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) { double r, g, b; for (int i = 0; i < length; i++) { GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b); *out++ = dblToByte(clip01(r)); *out++ = dblToByte(clip01(g)); *out++ = dblToByte(clip01(b)); *out++ = 255; } } void GfxDeviceCMYKColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { *out++ = *in++; *out++ = *in++; *out++ = *in++; *out++ = *in++; } } void GfxDeviceCMYKColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { for (int i = 0; i < length; i++) { for (int j = 0; j < SPOT_NCOMPS + 4; j++) out[j] = 0; out[0] = *in++; out[1] = *in++; out[2] = *in++; out[3] = *in++; out += (SPOT_NCOMPS + 4); } } void GfxDeviceCMYKColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { cmyk->c = clip01(color->c[0]); cmyk->m = clip01(color->c[1]); cmyk->y = clip01(color->c[2]); cmyk->k = clip01(color->c[3]); } void GfxDeviceCMYKColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { clearGfxColor(deviceN); deviceN->c[0] = clip01(color->c[0]); deviceN->c[1] = clip01(color->c[1]); deviceN->c[2] = clip01(color->c[2]); deviceN->c[3] = clip01(color->c[3]); } void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; color->c[1] = 0; color->c[2] = 0; color->c[3] = gfxColorComp1; } //------------------------------------------------------------------------ // GfxLabColorSpace //------------------------------------------------------------------------ GfxLabColorSpace::GfxLabColorSpace() { whiteX = whiteY = whiteZ = 1; blackX = blackY = blackZ = 0; aMin = bMin = -100; aMax = bMax = 100; } GfxLabColorSpace::~GfxLabColorSpace() { } GfxColorSpace *GfxLabColorSpace::copy() const { GfxLabColorSpace *cs; cs = new GfxLabColorSpace(); cs->whiteX = whiteX; cs->whiteY = whiteY; cs->whiteZ = whiteZ; cs->blackX = blackX; cs->blackY = blackY; cs->blackZ = blackZ; cs->aMin = aMin; cs->aMax = aMax; cs->bMin = bMin; cs->bMax = bMax; cs->kr = kr; cs->kg = kg; cs->kb = kb; #ifdef USE_CMS cs->transform = transform; #endif return cs; } GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state) { GfxLabColorSpace *cs; Object obj1, obj2; obj1 = arr->get(1); if (!obj1.isDict()) { error(errSyntaxWarning, -1, "Bad Lab color space"); return nullptr; } cs = new GfxLabColorSpace(); bool ok = true; obj2 = obj1.dictLookup("WhitePoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->whiteX = obj2.arrayGet(0).getNum(&ok); cs->whiteY = obj2.arrayGet(1).getNum(&ok); cs->whiteZ = obj2.arrayGet(2).getNum(&ok); } obj2 = obj1.dictLookup("BlackPoint"); if (obj2.isArray() && obj2.arrayGetLength() == 3) { cs->blackX = obj2.arrayGet(0).getNum(&ok); cs->blackY = obj2.arrayGet(1).getNum(&ok); cs->blackZ = obj2.arrayGet(2).getNum(&ok); } obj2 = obj1.dictLookup("Range"); if (obj2.isArray() && obj2.arrayGetLength() == 4) { cs->aMin = obj2.arrayGet(0).getNum(&ok); cs->aMax = obj2.arrayGet(1).getNum(&ok); cs->bMin = obj2.arrayGet(2).getNum(&ok); cs->bMax = obj2.arrayGet(3).getNum(&ok); } if (!ok) { error(errSyntaxWarning, -1, "Bad Lab color space"); #ifdef USE_CMS cs->transform = nullptr; #endif delete cs; return nullptr; } const auto krDenominator = (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); const auto kgDenominator = (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); const auto kbDenominator = (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); if (unlikely(krDenominator == 0 || kgDenominator == 0 || kbDenominator == 0)) { delete cs; return nullptr; } cs->kr = 1 / krDenominator; cs->kg = 1 / kgDenominator; cs->kb = 1 / kbDenominator; #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif return cs; } void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { GfxRGB rgb; #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; getXYZ(color, &in[0], &in[1], &in[2]); transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; } #endif getRGB(color, &rgb); *gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5)); } // convert L*a*b* to media XYZ color space // (not multiply by the white point) void GfxLabColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const { double X, Y, Z; double t1, t2; t1 = (colToDbl(color->c[0]) + 16) / 116; t2 = t1 + colToDbl(color->c[1]) / 500; if (t2 >= (6.0 / 29.0)) { X = t2 * t2 * t2; } else { X = (108.0 / 841.0) * (t2 - (4.0 / 29.0)); } if (t1 >= (6.0 / 29.0)) { Y = t1 * t1 * t1; } else { Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0)); } t2 = t1 - colToDbl(color->c[2]) / 200; if (t2 >= (6.0 / 29.0)) { Z = t2 * t2 * t2; } else { Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0)); } *pX = X; *pY = Y; *pZ = Z; } void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double X, Y, Z; getXYZ(color, &X, &Y, &Z); #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); rgb->b = byteToCol(out[2]); return; } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) { unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; double c, m, y, k, c1, m1, y1, k1, r, g, b; in[0] = clip01(X); in[1] = clip01(Y); in[2] = clip01(Z); transform->doTransform(in, out, 1); c = byteToDbl(out[0]); m = byteToDbl(out[1]); y = byteToDbl(out[2]); k = byteToDbl(out[3]); c1 = 1 - c; m1 = 1 - m; y1 = 1 - y; k1 = 1 - k; cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b); rgb->r = clip01(dblToCol(r)); rgb->g = clip01(dblToCol(g)); rgb->b = clip01(dblToCol(b)); return; } #endif X *= whiteX; Y *= whiteY; Z *= whiteZ; // convert XYZ to RGB, including gamut mapping and gamma correction const double r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; const double g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; const double b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; rgb->r = dblToCol(sqrt(clip01(r * kr))); rgb->g = dblToCol(sqrt(clip01(g * kg))); rgb->b = dblToCol(sqrt(clip01(b * kb))); } void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { GfxRGB rgb; GfxColorComp c, m, y, k; #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) { double in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; getXYZ(color, &in[0], &in[1], &in[2]); transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); cmyk->y = byteToCol(out[2]); cmyk->k = byteToCol(out[3]); return; } #endif getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); y = clip01(gfxColorComp1 - rgb.b); k = c; if (m < k) { k = m; } if (y < k) { k = y; } cmyk->c = c - k; cmyk->m = m - k; cmyk->y = y - k; cmyk->k = k; } void GfxLabColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxCMYK cmyk; clearGfxColor(deviceN); getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } void GfxLabColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; if (aMin > 0) { color->c[1] = dblToCol(aMin); } else if (aMax < 0) { color->c[1] = dblToCol(aMax); } else { color->c[1] = 0; } if (bMin > 0) { color->c[2] = dblToCol(bMin); } else if (bMax < 0) { color->c[2] = dblToCol(bMax); } else { color->c[2] = 0; } } void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const { decodeLow[0] = 0; decodeRange[0] = 100; decodeLow[1] = aMin; decodeRange[1] = aMax - aMin; decodeLow[2] = bMin; decodeRange[2] = bMax - bMin; } //------------------------------------------------------------------------ // GfxICCBasedColorSpace //------------------------------------------------------------------------ GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA, const Ref *iccProfileStreamA) { nComps = nCompsA; alt = altA; iccProfileStream = *iccProfileStreamA; rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0; rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1; #ifdef USE_CMS transform = nullptr; lineTransform = nullptr; psCSA = nullptr; #endif } GfxICCBasedColorSpace::~GfxICCBasedColorSpace() { delete alt; #ifdef USE_CMS if (psCSA) gfree(psCSA); #endif } GfxColorSpace *GfxICCBasedColorSpace::copy() const { GfxICCBasedColorSpace *cs; int i; cs = new GfxICCBasedColorSpace(nComps, alt->copy(), &iccProfileStream); for (i = 0; i < 4; ++i) { cs->rangeMin[i] = rangeMin[i]; cs->rangeMax[i] = rangeMax[i]; } #ifdef USE_CMS cs->profile = profile; cs->transform = transform; cs->lineTransform = lineTransform; #endif return cs; } GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) { GfxICCBasedColorSpace *cs; int nCompsA; GfxColorSpace *altA; Dict *dict; Object obj1, obj2; int i; if (arr->getLength() < 2) { error(errSyntaxError, -1, "Bad ICCBased color space"); return nullptr; } const Object &obj1Ref = arr->getNF(1); const Ref iccProfileStreamA = obj1Ref.isRef() ? obj1Ref.getRef() : Ref::INVALID(); #ifdef USE_CMS // check cache if (out && iccProfileStreamA != Ref::INVALID()) { if (auto *item = out->getIccColorSpaceCache()->lookup(iccProfileStreamA)) { cs = static_cast(item->copy()); int transformIntent = cs->getIntent(); int cmsIntent = INTENT_RELATIVE_COLORIMETRIC; if (state != nullptr) { cmsIntent = state->getCmsRenderingIntent(); } if (transformIntent == cmsIntent) { return cs; } delete cs; } } #endif obj1 = arr->get(1); if (!obj1.isStream()) { error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)"); return nullptr; } dict = obj1.streamGetDict(); obj2 = dict->lookup("N"); if (!obj2.isInt()) { error(errSyntaxWarning, -1, "Bad ICCBased color space (N)"); return nullptr; } nCompsA = obj2.getInt(); if (nCompsA > 4) { error(errSyntaxError, -1, "ICCBased color space with too many ({0:d} > 4) components", nCompsA); nCompsA = 4; } obj2 = dict->lookup("Alternate"); if (obj2.isNull() || !(altA = GfxColorSpace::parse(nullptr, &obj2, out, state, recursion + 1))) { switch (nCompsA) { case 1: altA = new GfxDeviceGrayColorSpace(); break; case 3: altA = new GfxDeviceRGBColorSpace(); break; case 4: altA = new GfxDeviceCMYKColorSpace(); break; default: error(errSyntaxWarning, -1, "Bad ICCBased color space - invalid N"); return nullptr; } } if (altA->getNComps() != nCompsA) { error(errSyntaxWarning, -1, "Bad ICCBased color space - N doesn't match alt color space"); delete altA; return nullptr; } cs = new GfxICCBasedColorSpace(nCompsA, altA, &iccProfileStreamA); obj2 = dict->lookup("Range"); if (obj2.isArray() && obj2.arrayGetLength() == 2 * nCompsA) { for (i = 0; i < nCompsA; ++i) { cs->rangeMin[i] = obj2.arrayGet(2 * i).getNumWithDefaultValue(0); cs->rangeMax[i] = obj2.arrayGet(2 * i + 1).getNumWithDefaultValue(1); } } #ifdef USE_CMS obj1 = arr->get(1); unsigned char *profBuf; Stream *iccStream = obj1.getStream(); int length = 0; profBuf = iccStream->toUnsignedChars(&length, 65536, 65536); auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf, length)); cs->profile = hp; gfree(profBuf); if (!hp) { error(errSyntaxWarning, -1, "read ICCBased color space profile error"); } else { auto dhp = (state != nullptr && state->getDisplayProfile() != nullptr) ? state->getDisplayProfile() : nullptr; if (!dhp) { dhp = GfxState::sRGBProfile; } unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(hp.get())); unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp.get())); unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp.get())); cmsHTRANSFORM transform; int cmsIntent = INTENT_RELATIVE_COLORIMETRIC; if (state != nullptr) { cmsIntent = state->getCmsRenderingIntent(); } if ((transform = cmsCreateTransform(hp.get(), COLORSPACE_SH(cst) | CHANNELS_SH(nCompsA) | BYTES_SH(1), dhp.get(), COLORSPACE_SH(dcst) | CHANNELS_SH(dNChannels) | BYTES_SH(1), cmsIntent, LCMS_FLAGS)) == nullptr) { error(errSyntaxWarning, -1, "Can't create transform"); cs->transform = nullptr; } else { cs->transform = std::make_shared(transform, cmsIntent, cst, dcst); } if (dcst == PT_RGB || dcst == PT_CMYK) { // create line transform only when the display is RGB type color space if ((transform = cmsCreateTransform(hp.get(), CHANNELS_SH(nCompsA) | BYTES_SH(1), dhp.get(), (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == nullptr) { error(errSyntaxWarning, -1, "Can't create transform"); cs->lineTransform = nullptr; } else { cs->lineTransform = std::make_shared(transform, cmsIntent, cst, dcst); } } } // put this colorSpace into cache if (out && iccProfileStreamA != Ref::INVALID()) { out->getIccColorSpaceCache()->put(iccProfileStreamA, static_cast(cs->copy())); } #endif return cs; } void GfxICCBasedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) { unsigned char in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; if (nComps == 3 && transform->getInputPixelType() == PT_Lab) { in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0)); in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0)); in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0)); } else { for (int i = 0; i < nComps; i++) { in[i] = colToByte(color->c[i]); } } if (nComps <= 4) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } std::map::iterator it = cmsCache.find(key); if (it != cmsCache.end()) { unsigned int value = it->second; *gray = byteToCol(value & 0xff); return; } } transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } unsigned int value = out[0]; cmsCache.insert(std::pair(key, value)); } } else { GfxRGB rgb; getRGB(color, &rgb); *gray = clip01((GfxColorComp)(0.3 * rgb.r + 0.59 * rgb.g + 0.11 * rgb.b + 0.5)); } #else alt->getGray(color, gray); #endif } void GfxICCBasedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) { unsigned char in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; if (nComps == 3 && transform->getInputPixelType() == PT_Lab) { in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0)); in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0)); in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0)); } else { for (int i = 0; i < nComps; i++) { in[i] = colToByte(color->c[i]); } } if (nComps <= 4) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } std::map::iterator it = cmsCache.find(key); if (it != cmsCache.end()) { unsigned int value = it->second; rgb->r = byteToCol(value >> 16); rgb->g = byteToCol((value >> 8) & 0xff); rgb->b = byteToCol(value & 0xff); return; } } transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); rgb->b = byteToCol(out[2]); if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2]; cmsCache.insert(std::pair(key, value)); } } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) { unsigned char in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; double c, m, y, k, c1, m1, y1, k1, r, g, b; if (nComps == 3 && transform->getInputPixelType() == PT_Lab) { in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0)); in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0)); in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0)); } else { for (int i = 0; i < nComps; i++) { in[i] = colToByte(color->c[i]); } } if (nComps <= 4) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } std::map::iterator it = cmsCache.find(key); if (it != cmsCache.end()) { unsigned int value = it->second; rgb->r = byteToCol(value >> 16); rgb->g = byteToCol((value >> 8) & 0xff); rgb->b = byteToCol(value & 0xff); return; } } transform->doTransform(in, out, 1); c = byteToDbl(out[0]); m = byteToDbl(out[1]); y = byteToDbl(out[2]); k = byteToDbl(out[3]); c1 = 1 - c; m1 = 1 - m; y1 = 1 - y; k1 = 1 - k; cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b); rgb->r = clip01(dblToCol(r)); rgb->g = clip01(dblToCol(g)); rgb->b = clip01(dblToCol(b)); if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b); cmsCache.insert(std::pair(key, value)); } } else { alt->getRGB(color, rgb); } #else alt->getRGB(color, rgb); #endif } void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) { #ifdef USE_CMS if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) { unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char)); lineTransform->doTransform(in, tmp, length); for (int i = 0; i < length; ++i) { unsigned char *current = tmp + (i * 3); out[i] = (current[0] << 16) | (current[1] << 8) | current[2]; } gfree(tmp); } else { alt->getRGBLine(in, out, length); } #else alt->getRGBLine(in, out, length); #endif } void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) { #ifdef USE_CMS if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) { unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char)); lineTransform->doTransform(in, tmp, length); unsigned char *current = tmp; for (int i = 0; i < length; ++i) { *out++ = *current++; *out++ = *current++; *out++ = *current++; } gfree(tmp); } else if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) { unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char)); lineTransform->doTransform(in, tmp, length); unsigned char *current = tmp; double c, m, y, k, c1, m1, y1, k1, r, g, b; for (int i = 0; i < length; ++i) { c = byteToDbl(*current++); m = byteToDbl(*current++); y = byteToDbl(*current++); k = byteToDbl(*current++); c1 = 1 - c; m1 = 1 - m; y1 = 1 - y; k1 = 1 - k; cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b); *out++ = dblToByte(r); *out++ = dblToByte(g); *out++ = dblToByte(b); } gfree(tmp); } else { alt->getRGBLine(in, out, length); } #else alt->getRGBLine(in, out, length); #endif } void GfxICCBasedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) { #ifdef USE_CMS if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) { unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char)); lineTransform->doTransform(in, tmp, length); unsigned char *current = tmp; for (int i = 0; i < length; ++i) { *out++ = *current++; *out++ = *current++; *out++ = *current++; *out++ = 255; } gfree(tmp); } else { alt->getRGBXLine(in, out, length); } #else alt->getRGBXLine(in, out, length); #endif } void GfxICCBasedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) { #ifdef USE_CMS if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) { transform->doTransform(in, out, length); } else if (lineTransform != nullptr && nComps != 4) { GfxColorComp c, m, y, k; unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char)); getRGBLine(in, tmp, length); unsigned char *p = tmp; for (int i = 0; i < length; i++) { c = byteToCol(255 - *p++); m = byteToCol(255 - *p++); y = byteToCol(255 - *p++); k = c; if (m < k) { k = m; } if (y < k) { k = y; } *out++ = colToByte(c - k); *out++ = colToByte(m - k); *out++ = colToByte(y - k); *out++ = colToByte(k); } gfree(tmp); } else { alt->getCMYKLine(in, out, length); } #else alt->getCMYKLine(in, out, length); #endif } void GfxICCBasedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { #ifdef USE_CMS if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) { unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char)); transform->doTransform(in, tmp, length); unsigned char *p = tmp; for (int i = 0; i < length; i++) { for (int j = 0; j < 4; j++) *out++ = *p++; for (int j = 4; j < SPOT_NCOMPS + 4; j++) *out++ = 0; } gfree(tmp); } else if (lineTransform != nullptr && nComps != 4) { GfxColorComp c, m, y, k; unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char)); getRGBLine(in, tmp, length); unsigned char *p = tmp; for (int i = 0; i < length; i++) { for (int j = 0; j < SPOT_NCOMPS + 4; j++) out[j] = 0; c = byteToCol(255 - *p++); m = byteToCol(255 - *p++); y = byteToCol(255 - *p++); k = c; if (m < k) { k = m; } if (y < k) { k = y; } out[0] = colToByte(c - k); out[1] = colToByte(m - k); out[2] = colToByte(y - k); out[3] = colToByte(k); out += (SPOT_NCOMPS + 4); } gfree(tmp); } else { alt->getDeviceNLine(in, out, length); } #else alt->getDeviceNLine(in, out, length); #endif } void GfxICCBasedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { #ifdef USE_CMS if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) { unsigned char in[gfxColorMaxComps]; unsigned char out[gfxColorMaxComps]; if (nComps == 3 && transform->getInputPixelType() == PT_Lab) { in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0)); in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0)); in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0)); } else { for (int i = 0; i < nComps; i++) { in[i] = colToByte(color->c[i]); } } if (nComps <= 4) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } std::map::iterator it = cmsCache.find(key); if (it != cmsCache.end()) { unsigned int value = it->second; cmyk->c = byteToCol(value >> 24); cmyk->m = byteToCol((value >> 16) & 0xff); cmyk->y = byteToCol((value >> 8) & 0xff); cmyk->k = byteToCol(value & 0xff); return; } } transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); cmyk->y = byteToCol(out[2]); cmyk->k = byteToCol(out[3]); if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) { unsigned int key = 0; for (int j = 0; j < nComps; j++) { key = (key << 8) + in[j]; } unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3]; cmsCache.insert(std::pair(key, value)); } } else if (nComps != 4 && transform != nullptr && transform->getTransformPixelType() == PT_RGB) { GfxRGB rgb; GfxColorComp c, m, y, k; getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); y = clip01(gfxColorComp1 - rgb.b); k = c; if (m < k) { k = m; } if (y < k) { k = y; } cmyk->c = c - k; cmyk->m = m - k; cmyk->y = y - k; cmyk->k = k; } else { alt->getCMYK(color, cmyk); } #else alt->getCMYK(color, cmyk); #endif } bool GfxICCBasedColorSpace::useGetRGBLine() const { #ifdef USE_CMS return lineTransform != nullptr || alt->useGetRGBLine(); #else return alt->useGetRGBLine(); #endif } bool GfxICCBasedColorSpace::useGetCMYKLine() const { #ifdef USE_CMS return lineTransform != nullptr || alt->useGetCMYKLine(); #else return alt->useGetCMYKLine(); #endif } bool GfxICCBasedColorSpace::useGetDeviceNLine() const { #ifdef USE_CMS return lineTransform != nullptr || alt->useGetDeviceNLine(); #else return alt->useGetDeviceNLine(); #endif } void GfxICCBasedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxCMYK cmyk; clearGfxColor(deviceN); getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) const { int i; for (i = 0; i < nComps; ++i) { if (rangeMin[i] > 0) { color->c[i] = dblToCol(rangeMin[i]); } else if (rangeMax[i] < 0) { color->c[i] = dblToCol(rangeMax[i]); } else { color->c[i] = 0; } } } void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const { alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel); #if 0 // this is nominally correct, but some PDF files don't set the // correct ranges in the ICCBased dict int i; for (i = 0; i < nComps; ++i) { decodeLow[i] = rangeMin[i]; decodeRange[i] = rangeMax[i] - rangeMin[i]; } #endif } #ifdef USE_CMS char *GfxICCBasedColorSpace::getPostScriptCSA() { # if LCMS_VERSION >= 2070 // The runtime version check of lcms2 is only available from release 2.7 upwards. // The generation of the CSA code only works reliably for version 2.10 and upwards. // Cf. the explanation in the corresponding lcms2 merge request [1], and the original mail thread [2]. // [1] https://github.com/mm2/Little-CMS/pull/214 // [2] https://sourceforge.net/p/lcms/mailman/message/33182987/ if (cmsGetEncodedCMMversion() < 2100) return nullptr; int size; if (psCSA) return psCSA; if (!profile) { error(errSyntaxWarning, -1, "profile is nullptr"); return nullptr; } void *rawprofile = profile.get(); size = cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, nullptr, 0); if (size == 0) { error(errSyntaxWarning, -1, "PostScript CSA is nullptr"); return nullptr; } psCSA = (char *)gmalloc(size + 1); cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, psCSA, size); psCSA[size] = 0; // TODO REMOVE-ME-IN-THE-FUTURE // until we can depend on https://github.com/mm2/Little-CMS/issues/223 being fixed // lcms returns ps code with , instead of . for some locales. The lcms author says // that there's no room for any , in the rest of the ps code, so replacing all the , with . // is an "acceptable" workaround for (int i = 0; i < size; ++i) { if (psCSA[i] == ',') psCSA[i] = '.'; } return psCSA; # else return nullptr; # endif } #endif //------------------------------------------------------------------------ // GfxIndexedColorSpace //------------------------------------------------------------------------ GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA, int indexHighA) { base = baseA; indexHigh = indexHighA; lookup = (unsigned char *)gmallocn((indexHigh + 1) * base->getNComps(), sizeof(unsigned char)); overprintMask = base->getOverprintMask(); } GfxIndexedColorSpace::~GfxIndexedColorSpace() { delete base; gfree(lookup); } GfxColorSpace *GfxIndexedColorSpace::copy() const { GfxIndexedColorSpace *cs; cs = new GfxIndexedColorSpace(base->copy(), indexHigh); memcpy(cs->lookup, lookup, (indexHigh + 1) * base->getNComps() * sizeof(unsigned char)); return cs; } GfxColorSpace *GfxIndexedColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) { GfxColorSpace *baseA; int indexHighA; Object obj1; const char *s; int i, j; if (arr->getLength() != 4) { error(errSyntaxWarning, -1, "Bad Indexed color space"); return nullptr; } obj1 = arr->get(1); if (!(baseA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) { error(errSyntaxWarning, -1, "Bad Indexed color space (base color space)"); return nullptr; } obj1 = arr->get(2); if (!obj1.isInt()) { error(errSyntaxWarning, -1, "Bad Indexed color space (hival)"); delete baseA; return nullptr; } indexHighA = obj1.getInt(); if (indexHighA < 0 || indexHighA > 255) { // the PDF spec requires indexHigh to be in [0,255] -- allowing // values larger than 255 creates a security hole: if nComps * // indexHigh is greater than 2^31, the loop below may overwrite // past the end of the array int previousValue = indexHighA; if (indexHighA < 0) indexHighA = 0; else indexHighA = 255; error(errSyntaxWarning, -1, "Bad Indexed color space (invalid indexHigh value, was {0:d} using {1:d} to try to recover)", previousValue, indexHighA); } GfxIndexedColorSpace *cs = new GfxIndexedColorSpace(baseA, indexHighA); obj1 = arr->get(3); const int n = baseA->getNComps(); if (obj1.isStream()) { obj1.streamReset(); for (i = 0; i <= indexHighA; ++i) { const int readChars = obj1.streamGetChars(n, &cs->lookup[i * n]); for (j = readChars; j < n; ++j) { error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table stream too short) padding with zeroes"); cs->lookup[i * n + j] = 0; } } obj1.streamClose(); } else if (obj1.isString()) { if (obj1.getString()->getLength() < (indexHighA + 1) * n) { error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table string too short)"); goto err3; } s = obj1.getString()->c_str(); for (i = 0; i <= indexHighA; ++i) { for (j = 0; j < n; ++j) { cs->lookup[i * n + j] = (unsigned char)*s++; } } } else { error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table)"); goto err3; } return cs; err3: delete cs; return nullptr; } GfxColor *GfxIndexedColorSpace::mapColorToBase(const GfxColor *color, GfxColor *baseColor) const { unsigned char *p; double low[gfxColorMaxComps], range[gfxColorMaxComps]; int n, i; n = base->getNComps(); base->getDefaultRanges(low, range, indexHigh); const int idx = (int)(colToDbl(color->c[0]) + 0.5) * n; if (likely((idx + n - 1 < (indexHigh + 1) * base->getNComps()) && idx >= 0)) { p = &lookup[idx]; for (i = 0; i < n; ++i) { baseColor->c[i] = dblToCol(low[i] + (p[i] / 255.0) * range[i]); } } else { for (i = 0; i < n; ++i) { baseColor->c[i] = 0; } } return baseColor; } void GfxIndexedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { GfxColor color2; base->getGray(mapColorToBase(color, &color2), gray); } void GfxIndexedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { GfxColor color2; base->getRGB(mapColorToBase(color, &color2), rgb); } void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) { unsigned char *line; int i, j, n; n = base->getNComps(); line = (unsigned char *)gmallocn(length, n); for (i = 0; i < length; i++) for (j = 0; j < n; j++) line[i * n + j] = lookup[in[i] * n + j]; base->getRGBLine(line, out, length); gfree(line); } void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) { unsigned char *line; int i, j, n; n = base->getNComps(); line = (unsigned char *)gmallocn(length, n); for (i = 0; i < length; i++) for (j = 0; j < n; j++) line[i * n + j] = lookup[in[i] * n + j]; base->getRGBLine(line, out, length); gfree(line); } void GfxIndexedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) { unsigned char *line; int i, j, n; n = base->getNComps(); line = (unsigned char *)gmallocn(length, n); for (i = 0; i < length; i++) for (j = 0; j < n; j++) line[i * n + j] = lookup[in[i] * n + j]; base->getRGBXLine(line, out, length); gfree(line); } void GfxIndexedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) { unsigned char *line; int i, j, n; n = base->getNComps(); line = (unsigned char *)gmallocn(length, n); for (i = 0; i < length; i++) for (j = 0; j < n; j++) line[i * n + j] = lookup[in[i] * n + j]; base->getCMYKLine(line, out, length); gfree(line); } void GfxIndexedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { unsigned char *line; int i, j, n; n = base->getNComps(); line = (unsigned char *)gmallocn(length, n); for (i = 0; i < length; i++) for (j = 0; j < n; j++) line[i * n + j] = lookup[in[i] * n + j]; base->getDeviceNLine(line, out, length); gfree(line); } void GfxIndexedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { GfxColor color2; base->getCMYK(mapColorToBase(color, &color2), cmyk); } void GfxIndexedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { GfxColor color2; base->getDeviceN(mapColorToBase(color, &color2), deviceN); } void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; } void GfxIndexedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const { decodeLow[0] = 0; decodeRange[0] = maxImgPixel; } //------------------------------------------------------------------------ // GfxSeparationColorSpace //------------------------------------------------------------------------ GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA) { name = nameA; alt = altA; func = funcA; nonMarking = !name->cmp("None"); if (!name->cmp("Cyan")) { overprintMask = 0x01; } else if (!name->cmp("Magenta")) { overprintMask = 0x02; } else if (!name->cmp("Yellow")) { overprintMask = 0x04; } else if (!name->cmp("Black")) { overprintMask = 0x08; } else if (!name->cmp("All")) { overprintMask = 0xffffffff; } } GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA, bool nonMarkingA, unsigned int overprintMaskA, int *mappingA) { name = nameA; alt = altA; func = funcA; nonMarking = nonMarkingA; overprintMask = overprintMaskA; mapping = mappingA; } GfxSeparationColorSpace::~GfxSeparationColorSpace() { delete name; delete alt; delete func; if (mapping != nullptr) gfree(mapping); } GfxColorSpace *GfxSeparationColorSpace::copy() const { int *mappingA = nullptr; if (mapping != nullptr) { mappingA = (int *)gmalloc(sizeof(int)); *mappingA = *mapping; } return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), nonMarking, overprintMask, mappingA); } //~ handle the 'All' and 'None' colorants GfxColorSpace *GfxSeparationColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) { GfxSeparationColorSpace *cs; GooString *nameA; GfxColorSpace *altA; Function *funcA; Object obj1; if (arr->getLength() != 4) { error(errSyntaxWarning, -1, "Bad Separation color space"); goto err1; } obj1 = arr->get(1); if (!obj1.isName()) { error(errSyntaxWarning, -1, "Bad Separation color space (name)"); goto err1; } nameA = new GooString(obj1.getName()); obj1 = arr->get(2); if (!(altA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) { error(errSyntaxWarning, -1, "Bad Separation color space (alternate color space)"); goto err3; } obj1 = arr->get(3); if (!(funcA = Function::parse(&obj1))) { goto err4; } if (funcA->getInputSize() != 1) { error(errSyntaxWarning, -1, "Bad SeparationColorSpace function"); goto err5; } cs = new GfxSeparationColorSpace(nameA, altA, funcA); return cs; err5: delete funcA; err4: delete altA; err3: delete nameA; err1: return nullptr; } void GfxSeparationColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { double x; double c[gfxColorMaxComps]; GfxColor color2; int i; if (alt->getMode() == csDeviceGray && name->cmp("Black") == 0) { *gray = clip01(gfxColorComp1 - color->c[0]); } else { x = colToDbl(color->c[0]); func->transform(&x, c); for (i = 0; i < alt->getNComps(); ++i) { color2.c[i] = dblToCol(c[i]); } alt->getGray(&color2, gray); } } void GfxSeparationColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double x; double c[gfxColorMaxComps]; GfxColor color2; int i; if (alt->getMode() == csDeviceGray && name->cmp("Black") == 0) { rgb->r = clip01(gfxColorComp1 - color->c[0]); rgb->g = clip01(gfxColorComp1 - color->c[0]); rgb->b = clip01(gfxColorComp1 - color->c[0]); } else { x = colToDbl(color->c[0]); func->transform(&x, c); const int altNComps = alt->getNComps(); for (i = 0; i < altNComps; ++i) { color2.c[i] = dblToCol(c[i]); } if (unlikely(altNComps > func->getOutputSize())) { for (i = func->getOutputSize(); i < altNComps; ++i) { color2.c[i] = 0; } } alt->getRGB(&color2, rgb); } } void GfxSeparationColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { double x; double c[gfxColorMaxComps]; GfxColor color2; int i; if (name->cmp("Black") == 0) { cmyk->c = 0; cmyk->m = 0; cmyk->y = 0; cmyk->k = color->c[0]; } else if (name->cmp("Cyan") == 0) { cmyk->c = color->c[0]; cmyk->m = 0; cmyk->y = 0; cmyk->k = 0; } else if (name->cmp("Magenta") == 0) { cmyk->c = 0; cmyk->m = color->c[0]; cmyk->y = 0; cmyk->k = 0; } else if (name->cmp("Yellow") == 0) { cmyk->c = 0; cmyk->m = 0; cmyk->y = color->c[0]; cmyk->k = 0; } else { x = colToDbl(color->c[0]); func->transform(&x, c); for (i = 0; i < alt->getNComps(); ++i) { color2.c[i] = dblToCol(c[i]); } alt->getCMYK(&color2, cmyk); } } void GfxSeparationColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { clearGfxColor(deviceN); if (mapping == nullptr || mapping[0] == -1) { GfxCMYK cmyk; getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } else { deviceN->c[mapping[0]] = color->c[0]; } } void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = gfxColorComp1; } void GfxSeparationColorSpace::createMapping(std::vector *separationList, int maxSepComps) { if (nonMarking) return; mapping = (int *)gmalloc(sizeof(int)); switch (overprintMask) { case 0x01: *mapping = 0; break; case 0x02: *mapping = 1; break; case 0x04: *mapping = 2; break; case 0x08: *mapping = 3; break; default: unsigned int newOverprintMask = 0x10; for (std::size_t i = 0; i < separationList->size(); i++) { GfxSeparationColorSpace *sepCS = (*separationList)[i]; if (!sepCS->getName()->cmp(name)) { if (sepCS->getFunc()->hasDifferentResultSet(func)) { error(errSyntaxWarning, -1, "Different functions found for '{0:t}', convert immediately", name); gfree(mapping); mapping = nullptr; return; } *mapping = i + 4; overprintMask = newOverprintMask; return; } newOverprintMask <<= 1; } if ((int)separationList->size() == maxSepComps) { error(errSyntaxWarning, -1, "Too many ({0:d}) spots, convert '{1:t}' immediately", maxSepComps, name); gfree(mapping); mapping = nullptr; return; } *mapping = separationList->size() + 4; separationList->push_back((GfxSeparationColorSpace *)copy()); overprintMask = newOverprintMask; break; } } //------------------------------------------------------------------------ // GfxDeviceNColorSpace //------------------------------------------------------------------------ GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, std::vector &&namesA, GfxColorSpace *altA, Function *funcA, std::vector *sepsCSA) : nComps(nCompsA), names(std::move(namesA)) { alt = altA; func = funcA; sepsCS = sepsCSA; nonMarking = true; overprintMask = 0; mapping = nullptr; for (int i = 0; i < nComps; ++i) { if (names[i] != "None") { nonMarking = false; } if (names[i] == "Cyan") { overprintMask |= 0x01; } else if (names[i] == "Magenta") { overprintMask |= 0x02; } else if (names[i] == "Yellow") { overprintMask |= 0x04; } else if (names[i] == "Black") { overprintMask |= 0x08; } else if (names[i] == "All") { overprintMask = 0xffffffff; } else { overprintMask = 0x0f; } } } GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, const std::vector &namesA, GfxColorSpace *altA, Function *funcA, std::vector *sepsCSA, int *mappingA, bool nonMarkingA, unsigned int overprintMaskA) : nComps(nCompsA), names(namesA) { alt = altA; func = funcA; sepsCS = sepsCSA; mapping = mappingA; nonMarking = nonMarkingA; overprintMask = overprintMaskA; } GfxDeviceNColorSpace::~GfxDeviceNColorSpace() { delete alt; delete func; for (auto entry : *sepsCS) { delete entry; } delete sepsCS; if (mapping != nullptr) gfree(mapping); } GfxColorSpace *GfxDeviceNColorSpace::copy() const { int *mappingA = nullptr; auto sepsCSA = new std::vector(); sepsCSA->reserve(sepsCS->size()); for (const GfxSeparationColorSpace *scs : *sepsCS) { if (likely(scs != nullptr)) { sepsCSA->push_back((GfxSeparationColorSpace *)scs->copy()); } } if (mapping != nullptr) { mappingA = (int *)gmalloc(sizeof(int) * nComps); for (int i = 0; i < nComps; i++) mappingA[i] = mapping[i]; } return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), sepsCSA, mappingA, nonMarking, overprintMask); } //~ handle the 'None' colorant GfxColorSpace *GfxDeviceNColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) { int nCompsA; std::vector namesA; GfxColorSpace *altA; Function *funcA; Object obj1; auto separationList = new std::vector(); if (arr->getLength() != 4 && arr->getLength() != 5) { error(errSyntaxWarning, -1, "Bad DeviceN color space"); goto err1; } obj1 = arr->get(1); if (!obj1.isArray()) { error(errSyntaxWarning, -1, "Bad DeviceN color space (names)"); goto err1; } nCompsA = obj1.arrayGetLength(); if (nCompsA > gfxColorMaxComps) { error(errSyntaxWarning, -1, "DeviceN color space with too many ({0:d} > {1:d}) components", nCompsA, gfxColorMaxComps); nCompsA = gfxColorMaxComps; } for (int i = 0; i < nCompsA; ++i) { Object obj2 = obj1.arrayGet(i); if (!obj2.isName()) { error(errSyntaxWarning, -1, "Bad DeviceN color space (names)"); nCompsA = i; goto err1; } namesA.emplace_back(obj2.getName()); } obj1 = arr->get(2); if (!(altA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) { error(errSyntaxWarning, -1, "Bad DeviceN color space (alternate color space)"); goto err1; } obj1 = arr->get(3); if (!(funcA = Function::parse(&obj1))) { goto err4; } if (arr->getLength() == 5) { obj1 = arr->get(4); if (!obj1.isDict()) { error(errSyntaxWarning, -1, "Bad DeviceN color space (attributes)"); goto err5; } Dict *attribs = obj1.getDict(); Object obj2 = attribs->lookup("Colorants"); if (obj2.isDict()) { Dict *colorants = obj2.getDict(); for (int i = 0; i < colorants->getLength(); i++) { Object obj3 = colorants->getVal(i); if (obj3.isArray()) { GfxSeparationColorSpace *cs = (GfxSeparationColorSpace *)GfxSeparationColorSpace::parse(res, obj3.getArray(), out, state, recursion); if (cs) { separationList->push_back(cs); } } else { error(errSyntaxWarning, -1, "Bad DeviceN color space (colorant value entry is not an Array)"); goto err5; } } } } return new GfxDeviceNColorSpace(nCompsA, std::move(namesA), altA, funcA, separationList); err5: delete funcA; err4: delete altA; err1: delete separationList; return nullptr; } void GfxDeviceNColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { double x[gfxColorMaxComps], c[gfxColorMaxComps]; GfxColor color2; int i; for (i = 0; i < nComps; ++i) { x[i] = colToDbl(color->c[i]); } func->transform(x, c); for (i = 0; i < alt->getNComps(); ++i) { color2.c[i] = dblToCol(c[i]); } alt->getGray(&color2, gray); } void GfxDeviceNColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { double x[gfxColorMaxComps], c[gfxColorMaxComps]; GfxColor color2; int i; for (i = 0; i < nComps; ++i) { x[i] = colToDbl(color->c[i]); } func->transform(x, c); for (i = 0; i < alt->getNComps(); ++i) { color2.c[i] = dblToCol(c[i]); } alt->getRGB(&color2, rgb); } void GfxDeviceNColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { double x[gfxColorMaxComps], c[gfxColorMaxComps]; GfxColor color2; int i; for (i = 0; i < nComps; ++i) { x[i] = colToDbl(color->c[i]); } func->transform(x, c); for (i = 0; i < alt->getNComps(); ++i) { color2.c[i] = dblToCol(c[i]); } alt->getCMYK(&color2, cmyk); } void GfxDeviceNColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { clearGfxColor(deviceN); if (mapping == nullptr) { GfxCMYK cmyk; getCMYK(color, &cmyk); deviceN->c[0] = cmyk.c; deviceN->c[1] = cmyk.m; deviceN->c[2] = cmyk.y; deviceN->c[3] = cmyk.k; } else { for (int j = 0; j < nComps; j++) if (mapping[j] != -1) deviceN->c[mapping[j]] = color->c[j]; } } void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) const { int i; for (i = 0; i < nComps; ++i) { color->c[i] = gfxColorComp1; } } void GfxDeviceNColorSpace::createMapping(std::vector *separationList, int maxSepComps) { if (nonMarking) // None return; mapping = (int *)gmalloc(sizeof(int) * nComps); unsigned int newOverprintMask = 0; for (int i = 0; i < nComps; i++) { if (names[i] == "None") { mapping[i] = -1; } else if (names[i] == "Cyan") { newOverprintMask |= 0x01; mapping[i] = 0; } else if (names[i] == "Magenta") { newOverprintMask |= 0x02; mapping[i] = 1; } else if (names[i] == "Yellow") { newOverprintMask |= 0x04; mapping[i] = 2; } else if (names[i] == "Black") { newOverprintMask |= 0x08; mapping[i] = 3; } else { unsigned int startOverprintMask = 0x10; bool found = false; const Function *sepFunc = nullptr; if (nComps == 1) sepFunc = func; else { for (const GfxSeparationColorSpace *sepCS : *sepsCS) { if (!sepCS->getName()->cmp(names[i])) { sepFunc = sepCS->getFunc(); break; } } } for (std::size_t j = 0; j < separationList->size(); j++) { GfxSeparationColorSpace *sepCS = (*separationList)[j]; if (!sepCS->getName()->cmp(names[i])) { if (sepFunc != nullptr && sepCS->getFunc()->hasDifferentResultSet(sepFunc)) { error(errSyntaxWarning, -1, "Different functions found for '{0:s}', convert immediately", names[i].c_str()); gfree(mapping); mapping = nullptr; overprintMask = 0xffffffff; return; } mapping[i] = j + 4; newOverprintMask |= startOverprintMask; found = true; break; } startOverprintMask <<= 1; } if (!found) { if ((int)separationList->size() == maxSepComps) { error(errSyntaxWarning, -1, "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, names[i].c_str()); gfree(mapping); mapping = nullptr; overprintMask = 0xffffffff; return; } mapping[i] = separationList->size() + 4; newOverprintMask |= startOverprintMask; if (nComps == 1) separationList->push_back(new GfxSeparationColorSpace(new GooString(names[i]), alt->copy(), func->copy())); else { for (const GfxSeparationColorSpace *sepCS : *sepsCS) { if (!sepCS->getName()->cmp(names[i])) { found = true; separationList->push_back((GfxSeparationColorSpace *)sepCS->copy()); break; } } if (!found) { error(errSyntaxWarning, -1, "DeviceN has no suitable colorant"); gfree(mapping); mapping = nullptr; overprintMask = 0xffffffff; return; } } } } } overprintMask = newOverprintMask; } //------------------------------------------------------------------------ // GfxPatternColorSpace //------------------------------------------------------------------------ GfxPatternColorSpace::GfxPatternColorSpace(GfxColorSpace *underA) { under = underA; } GfxPatternColorSpace::~GfxPatternColorSpace() { if (under) { delete under; } } GfxColorSpace *GfxPatternColorSpace::copy() const { return new GfxPatternColorSpace(under ? under->copy() : nullptr); } GfxColorSpace *GfxPatternColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) { GfxPatternColorSpace *cs; GfxColorSpace *underA; Object obj1; if (arr->getLength() != 1 && arr->getLength() != 2) { error(errSyntaxWarning, -1, "Bad Pattern color space"); return nullptr; } underA = nullptr; if (arr->getLength() == 2) { obj1 = arr->get(1); if (!(underA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) { error(errSyntaxWarning, -1, "Bad Pattern color space (underlying color space)"); return nullptr; } } cs = new GfxPatternColorSpace(underA); return cs; } void GfxPatternColorSpace::getGray(const GfxColor *color, GfxGray *gray) const { *gray = 0; } void GfxPatternColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const { rgb->r = rgb->g = rgb->b = 0; } void GfxPatternColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const { cmyk->c = cmyk->m = cmyk->y = 0; cmyk->k = 1; } void GfxPatternColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const { clearGfxColor(deviceN); deviceN->c[3] = 1; } void GfxPatternColorSpace::getDefaultColor(GfxColor *color) const { color->c[0] = 0; } //------------------------------------------------------------------------ // Pattern //------------------------------------------------------------------------ GfxPattern::GfxPattern(int typeA, int patternRefNumA) : type(typeA), patternRefNum(patternRefNumA) { } GfxPattern::~GfxPattern() { } GfxPattern *GfxPattern::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state, int patternRefNum) { GfxPattern *pattern; Object obj1; if (obj->isDict()) { obj1 = obj->dictLookup("PatternType"); } else if (obj->isStream()) { obj1 = obj->streamGetDict()->lookup("PatternType"); } else { return nullptr; } pattern = nullptr; if (obj1.isInt() && obj1.getInt() == 1) { pattern = GfxTilingPattern::parse(obj, patternRefNum); } else if (obj1.isInt() && obj1.getInt() == 2) { pattern = GfxShadingPattern::parse(res, obj, out, state, patternRefNum); } return pattern; } //------------------------------------------------------------------------ // GfxTilingPattern //------------------------------------------------------------------------ GfxTilingPattern *GfxTilingPattern::parse(Object *patObj, int patternRefNum) { Dict *dict; int paintTypeA, tilingTypeA; double bboxA[4], matrixA[6]; double xStepA, yStepA; Object resDictA; Object obj1; int i; if (!patObj->isStream()) { return nullptr; } dict = patObj->streamGetDict(); obj1 = dict->lookup("PaintType"); if (obj1.isInt()) { paintTypeA = obj1.getInt(); } else { paintTypeA = 1; error(errSyntaxWarning, -1, "Invalid or missing PaintType in pattern"); } obj1 = dict->lookup("TilingType"); if (obj1.isInt()) { tilingTypeA = obj1.getInt(); } else { tilingTypeA = 1; error(errSyntaxWarning, -1, "Invalid or missing TilingType in pattern"); } bboxA[0] = bboxA[1] = 0; bboxA[2] = bboxA[3] = 1; obj1 = dict->lookup("BBox"); if (obj1.isArray() && obj1.arrayGetLength() == 4) { for (i = 0; i < 4; ++i) { Object obj2 = obj1.arrayGet(i); if (obj2.isNum()) { bboxA[i] = obj2.getNum(); } } } else { error(errSyntaxWarning, -1, "Invalid or missing BBox in pattern"); } obj1 = dict->lookup("XStep"); if (obj1.isNum()) { xStepA = obj1.getNum(); } else { xStepA = 1; error(errSyntaxWarning, -1, "Invalid or missing XStep in pattern"); } obj1 = dict->lookup("YStep"); if (obj1.isNum()) { yStepA = obj1.getNum(); } else { yStepA = 1; error(errSyntaxWarning, -1, "Invalid or missing YStep in pattern"); } resDictA = dict->lookup("Resources"); if (!resDictA.isDict()) { error(errSyntaxWarning, -1, "Invalid or missing Resources in pattern"); } matrixA[0] = 1; matrixA[1] = 0; matrixA[2] = 0; matrixA[3] = 1; matrixA[4] = 0; matrixA[5] = 0; obj1 = dict->lookup("Matrix"); if (obj1.isArray() && obj1.arrayGetLength() == 6) { for (i = 0; i < 6; ++i) { Object obj2 = obj1.arrayGet(i); if (obj2.isNum()) { matrixA[i] = obj2.getNum(); } } } return new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA, &resDictA, matrixA, patObj, patternRefNum); } GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, const double *bboxA, double xStepA, double yStepA, const Object *resDictA, const double *matrixA, const Object *contentStreamA, int patternRefNumA) : GfxPattern(1, patternRefNumA) { int i; paintType = paintTypeA; tilingType = tilingTypeA; for (i = 0; i < 4; ++i) { bbox[i] = bboxA[i]; } xStep = xStepA; yStep = yStepA; resDict = resDictA->copy(); for (i = 0; i < 6; ++i) { matrix[i] = matrixA[i]; } contentStream = contentStreamA->copy(); } GfxTilingPattern::~GfxTilingPattern() { } GfxPattern *GfxTilingPattern::copy() const { return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep, &resDict, matrix, &contentStream, getPatternRefNum()); } //------------------------------------------------------------------------ // GfxShadingPattern //------------------------------------------------------------------------ GfxShadingPattern *GfxShadingPattern::parse(GfxResources *res, Object *patObj, OutputDev *out, GfxState *state, int patternRefNum) { Dict *dict; GfxShading *shadingA; double matrixA[6]; Object obj1; int i; if (!patObj->isDict()) { return nullptr; } dict = patObj->getDict(); obj1 = dict->lookup("Shading"); shadingA = GfxShading::parse(res, &obj1, out, state); if (!shadingA) { return nullptr; } matrixA[0] = 1; matrixA[1] = 0; matrixA[2] = 0; matrixA[3] = 1; matrixA[4] = 0; matrixA[5] = 0; obj1 = dict->lookup("Matrix"); if (obj1.isArray() && obj1.arrayGetLength() == 6) { for (i = 0; i < 6; ++i) { Object obj2 = obj1.arrayGet(i); if (obj2.isNum()) { matrixA[i] = obj2.getNum(); } } } return new GfxShadingPattern(shadingA, matrixA, patternRefNum); } GfxShadingPattern::GfxShadingPattern(GfxShading *shadingA, const double *matrixA, int patternRefNumA) : GfxPattern(2, patternRefNumA) { int i; shading = shadingA; for (i = 0; i < 6; ++i) { matrix[i] = matrixA[i]; } } GfxShadingPattern::~GfxShadingPattern() { delete shading; } GfxPattern *GfxShadingPattern::copy() const { return new GfxShadingPattern(shading->copy(), matrix, getPatternRefNum()); } //------------------------------------------------------------------------ // GfxShading //------------------------------------------------------------------------ GfxShading::GfxShading(int typeA) { type = typeA; colorSpace = nullptr; } GfxShading::GfxShading(const GfxShading *shading) { int i; type = shading->type; colorSpace = shading->colorSpace->copy(); for (i = 0; i < gfxColorMaxComps; ++i) { background.c[i] = shading->background.c[i]; } hasBackground = shading->hasBackground; bbox_xMin = shading->bbox_xMin; bbox_yMin = shading->bbox_yMin; bbox_xMax = shading->bbox_xMax; bbox_yMax = shading->bbox_yMax; hasBBox = shading->hasBBox; } GfxShading::~GfxShading() { if (colorSpace) { delete colorSpace; } } GfxShading *GfxShading::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state) { GfxShading *shading; Dict *dict; int typeA; Object obj1; if (obj->isDict()) { dict = obj->getDict(); } else if (obj->isStream()) { dict = obj->streamGetDict(); } else { return nullptr; } obj1 = dict->lookup("ShadingType"); if (!obj1.isInt()) { error(errSyntaxWarning, -1, "Invalid ShadingType in shading dictionary"); return nullptr; } typeA = obj1.getInt(); switch (typeA) { case 1: shading = GfxFunctionShading::parse(res, dict, out, state); break; case 2: shading = GfxAxialShading::parse(res, dict, out, state); break; case 3: shading = GfxRadialShading::parse(res, dict, out, state); break; case 4: if (obj->isStream()) { shading = GfxGouraudTriangleShading::parse(res, 4, dict, obj->getStream(), out, state); } else { error(errSyntaxWarning, -1, "Invalid Type 4 shading object"); goto err1; } break; case 5: if (obj->isStream()) { shading = GfxGouraudTriangleShading::parse(res, 5, dict, obj->getStream(), out, state); } else { error(errSyntaxWarning, -1, "Invalid Type 5 shading object"); goto err1; } break; case 6: if (obj->isStream()) { shading = GfxPatchMeshShading::parse(res, 6, dict, obj->getStream(), out, state); } else { error(errSyntaxWarning, -1, "Invalid Type 6 shading object"); goto err1; } break; case 7: if (obj->isStream()) { shading = GfxPatchMeshShading::parse(res, 7, dict, obj->getStream(), out, state); } else { error(errSyntaxWarning, -1, "Invalid Type 7 shading object"); goto err1; } break; default: error(errSyntaxWarning, -1, "Unimplemented shading type {0:d}", typeA); goto err1; } return shading; err1: return nullptr; } bool GfxShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) { Object obj1; int i; obj1 = dict->lookup("ColorSpace"); if (!(colorSpace = GfxColorSpace::parse(res, &obj1, out, state))) { error(errSyntaxWarning, -1, "Bad color space in shading dictionary"); return false; } for (i = 0; i < gfxColorMaxComps; ++i) { background.c[i] = 0; } hasBackground = false; obj1 = dict->lookup("Background"); if (obj1.isArray()) { if (obj1.arrayGetLength() == colorSpace->getNComps()) { hasBackground = true; for (i = 0; i < colorSpace->getNComps(); ++i) { Object obj2 = obj1.arrayGet(i); background.c[i] = dblToCol(obj2.getNum()); } } else { error(errSyntaxWarning, -1, "Bad Background in shading dictionary"); } } bbox_xMin = bbox_yMin = bbox_xMax = bbox_yMax = 0; hasBBox = false; obj1 = dict->lookup("BBox"); if (obj1.isArray()) { if (obj1.arrayGetLength() == 4) { hasBBox = true; bbox_xMin = obj1.arrayGet(0).getNum(&hasBBox); bbox_yMin = obj1.arrayGet(1).getNum(&hasBBox); bbox_xMax = obj1.arrayGet(2).getNum(&hasBBox); bbox_yMax = obj1.arrayGet(3).getNum(&hasBBox); if (!hasBBox) { error(errSyntaxWarning, -1, "Bad BBox in shading dictionary (Values not numbers)"); } } else { error(errSyntaxWarning, -1, "Bad BBox in shading dictionary"); } } return true; } //------------------------------------------------------------------------ // GfxFunctionShading //------------------------------------------------------------------------ GfxFunctionShading::GfxFunctionShading(double x0A, double y0A, double x1A, double y1A, const double *matrixA, std::vector> &&funcsA) : GfxShading(1), funcs(std::move(funcsA)) { x0 = x0A; y0 = y0A; x1 = x1A; y1 = y1A; for (int i = 0; i < 6; ++i) { matrix[i] = matrixA[i]; } } GfxFunctionShading::GfxFunctionShading(const GfxFunctionShading *shading) : GfxShading(shading) { x0 = shading->x0; y0 = shading->y0; x1 = shading->x1; y1 = shading->y1; for (int i = 0; i < 6; ++i) { matrix[i] = shading->matrix[i]; } for (const auto &f : shading->funcs) { funcs.emplace_back(f->copy()); } } GfxFunctionShading::~GfxFunctionShading() { } GfxFunctionShading *GfxFunctionShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) { GfxFunctionShading *shading; double x0A, y0A, x1A, y1A; double matrixA[6]; std::vector> funcsA; Object obj1; int i; x0A = y0A = 0; x1A = y1A = 1; obj1 = dict->lookup("Domain"); if (obj1.isArray() && obj1.arrayGetLength() == 4) { bool decodeOk = true; x0A = obj1.arrayGet(0).getNum(&decodeOk); x1A = obj1.arrayGet(1).getNum(&decodeOk); y0A = obj1.arrayGet(2).getNum(&decodeOk); y1A = obj1.arrayGet(3).getNum(&decodeOk); if (!decodeOk) { error(errSyntaxWarning, -1, "Invalid Domain array in function shading dictionary"); return nullptr; } } matrixA[0] = 1; matrixA[1] = 0; matrixA[2] = 0; matrixA[3] = 1; matrixA[4] = 0; matrixA[5] = 0; obj1 = dict->lookup("Matrix"); if (obj1.isArray() && obj1.arrayGetLength() == 6) { bool decodeOk = true; matrixA[0] = obj1.arrayGet(0).getNum(&decodeOk); matrixA[1] = obj1.arrayGet(1).getNum(&decodeOk); matrixA[2] = obj1.arrayGet(2).getNum(&decodeOk); matrixA[3] = obj1.arrayGet(3).getNum(&decodeOk); matrixA[4] = obj1.arrayGet(4).getNum(&decodeOk); matrixA[5] = obj1.arrayGet(5).getNum(&decodeOk); if (!decodeOk) { error(errSyntaxWarning, -1, "Invalid Matrix array in function shading dictionary"); return nullptr; } } obj1 = dict->lookup("Function"); if (obj1.isArray()) { const int nFuncsA = obj1.arrayGetLength(); if (nFuncsA > gfxColorMaxComps || nFuncsA <= 0) { error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary"); return nullptr; } for (i = 0; i < nFuncsA; ++i) { Object obj2 = obj1.arrayGet(i); Function *f = Function::parse(&obj2); if (!f) { return nullptr; } funcsA.emplace_back(f); } } else { Function *f = Function::parse(&obj1); if (!f) { return nullptr; } funcsA.emplace_back(f); } shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA, std::move(funcsA)); if (!shading->init(res, dict, out, state)) { delete shading; return nullptr; } return shading; } GfxShading *GfxFunctionShading::copy() const { return new GfxFunctionShading(this); } void GfxFunctionShading::getColor(double x, double y, GfxColor *color) const { double in[2], out[gfxColorMaxComps]; // NB: there can be one function with n outputs or n functions with // one output each (where n = number of color components) for (double &i : out) { i = 0; } in[0] = x; in[1] = y; for (int i = 0; i < getNFuncs(); ++i) { if (likely(funcs[i]->getInputSize() <= 2)) { funcs[i]->transform(in, &out[i]); } else { error(errSyntaxWarning, -1, "GfxFunctionShading::getColor: function with input size > 2"); } } for (int i = 0; i < gfxColorMaxComps; ++i) { color->c[i] = dblToCol(out[i]); } } //------------------------------------------------------------------------ // GfxUnivariateShading //------------------------------------------------------------------------ GfxUnivariateShading::GfxUnivariateShading(int typeA, double t0A, double t1A, std::vector> &&funcsA, bool extend0A, bool extend1A) : GfxShading(typeA), funcs(std::move(funcsA)) { t0 = t0A; t1 = t1A; extend0 = extend0A; extend1 = extend1A; cacheSize = 0; lastMatch = 0; cacheBounds = nullptr; cacheCoeff = nullptr; cacheValues = nullptr; } GfxUnivariateShading::GfxUnivariateShading(const GfxUnivariateShading *shading) : GfxShading(shading) { t0 = shading->t0; t1 = shading->t1; for (const auto &f : shading->funcs) { funcs.emplace_back(f->copy()); } extend0 = shading->extend0; extend1 = shading->extend1; cacheSize = 0; lastMatch = 0; cacheBounds = nullptr; cacheCoeff = nullptr; cacheValues = nullptr; } GfxUnivariateShading::~GfxUnivariateShading() { gfree(cacheBounds); } int GfxUnivariateShading::getColor(double t, GfxColor *color) { double out[gfxColorMaxComps]; int nComps; if (likely(getNFuncs() >= 1)) { // NB: there can be one function with n outputs or n functions with // one output each (where n = number of color components) nComps = getNFuncs() * funcs[0]->getOutputSize(); } if (unlikely(getNFuncs() < 1 || nComps > gfxColorMaxComps)) { clearGfxColor(color); return gfxColorMaxComps; } if (cacheSize > 0) { double x, ix, *l, *u, *upper; if (cacheBounds[lastMatch - 1] >= t) { upper = std::lower_bound(cacheBounds, cacheBounds + lastMatch - 1, t); lastMatch = upper - cacheBounds; lastMatch = std::min(std::max(1, lastMatch), cacheSize - 1); } else if (cacheBounds[lastMatch] < t) { upper = std::lower_bound(cacheBounds + lastMatch + 1, cacheBounds + cacheSize, t); lastMatch = upper - cacheBounds; lastMatch = std::min(std::max(1, lastMatch), cacheSize - 1); } x = (t - cacheBounds[lastMatch - 1]) * cacheCoeff[lastMatch]; ix = 1.0 - x; u = cacheValues + lastMatch * nComps; l = u - nComps; for (int i = 0; i < nComps; ++i) { out[i] = ix * l[i] + x * u[i]; } } else { for (int i = 0; i < nComps; ++i) { out[i] = 0; } for (int i = 0; i < getNFuncs(); ++i) { if (funcs[i]->getInputSize() != 1) { error(errSyntaxWarning, -1, "Invalid shading function (input != 1)"); break; } funcs[i]->transform(&t, &out[i]); } } for (int i = 0; i < nComps; ++i) { color->c[i] = dblToCol(out[i]); } return nComps; } void GfxUnivariateShading::setupCache(const Matrix *ctm, double xMin, double yMin, double xMax, double yMax) { double sMin, sMax, tMin, tMax, upperBound; int i, j, nComps, maxSize; gfree(cacheBounds); cacheBounds = nullptr; cacheSize = 0; if (unlikely(getNFuncs() < 1)) return; // NB: there can be one function with n outputs or n functions with // one output each (where n = number of color components) nComps = getNFuncs() * funcs[0]->getOutputSize(); getParameterRange(&sMin, &sMax, xMin, yMin, xMax, yMax); upperBound = ctm->norm() * getDistance(sMin, sMax); maxSize = ceil(upperBound); maxSize = std::max(maxSize, 2); { double x[4], y[4]; ctm->transform(xMin, yMin, &x[0], &y[0]); ctm->transform(xMax, yMin, &x[1], &y[1]); ctm->transform(xMin, yMax, &x[2], &y[2]); ctm->transform(xMax, yMax, &x[3], &y[3]); xMin = xMax = x[0]; yMin = yMax = y[0]; for (i = 1; i < 4; i++) { xMin = std::min(xMin, x[i]); yMin = std::min(yMin, y[i]); xMax = std::max(xMax, x[i]); yMax = std::max(yMax, y[i]); } } if (maxSize > (xMax - xMin) * (yMax - yMin)) { return; } if (t0 < t1) { tMin = t0 + sMin * (t1 - t0); tMax = t0 + sMax * (t1 - t0); } else { tMin = t0 + sMax * (t1 - t0); tMax = t0 + sMin * (t1 - t0); } cacheBounds = (double *)gmallocn_checkoverflow(maxSize, sizeof(double) * (nComps + 2)); if (unlikely(!cacheBounds)) return; cacheCoeff = cacheBounds + maxSize; cacheValues = cacheCoeff + maxSize; if (cacheSize != 0) { for (j = 0; j < cacheSize; ++j) { cacheCoeff[j] = 1 / (cacheBounds[j + 1] - cacheBounds[j]); } } else if (tMax != tMin) { double step = (tMax - tMin) / (maxSize - 1); double coeff = (maxSize - 1) / (tMax - tMin); cacheSize = maxSize; for (j = 0; j < cacheSize; ++j) { cacheBounds[j] = tMin + j * step; cacheCoeff[j] = coeff; for (i = 0; i < nComps; ++i) { cacheValues[j * nComps + i] = 0; } for (i = 0; i < getNFuncs(); ++i) { funcs[i]->transform(&cacheBounds[j], &cacheValues[j * nComps + i]); } } } lastMatch = 1; } //------------------------------------------------------------------------ // GfxAxialShading //------------------------------------------------------------------------ GfxAxialShading::GfxAxialShading(double x0A, double y0A, double x1A, double y1A, double t0A, double t1A, std::vector> &&funcsA, bool extend0A, bool extend1A) : GfxUnivariateShading(2, t0A, t1A, std::move(funcsA), extend0A, extend1A) { x0 = x0A; y0 = y0A; x1 = x1A; y1 = y1A; } GfxAxialShading::GfxAxialShading(const GfxAxialShading *shading) : GfxUnivariateShading(shading) { x0 = shading->x0; y0 = shading->y0; x1 = shading->x1; y1 = shading->y1; } GfxAxialShading::~GfxAxialShading() { } GfxAxialShading *GfxAxialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) { GfxAxialShading *shading; double x0A, y0A, x1A, y1A; double t0A, t1A; std::vector> funcsA; bool extend0A, extend1A; Object obj1; x0A = y0A = x1A = y1A = 0; obj1 = dict->lookup("Coords"); if (obj1.isArray() && obj1.arrayGetLength() == 4) { x0A = obj1.arrayGet(0).getNumWithDefaultValue(0); y0A = obj1.arrayGet(1).getNumWithDefaultValue(0); x1A = obj1.arrayGet(2).getNumWithDefaultValue(0); y1A = obj1.arrayGet(3).getNumWithDefaultValue(0); } else { error(errSyntaxWarning, -1, "Missing or invalid Coords in shading dictionary"); return nullptr; } t0A = 0; t1A = 1; obj1 = dict->lookup("Domain"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { t0A = obj1.arrayGet(0).getNumWithDefaultValue(0); t1A = obj1.arrayGet(1).getNumWithDefaultValue(1); } obj1 = dict->lookup("Function"); if (obj1.isArray()) { const int nFuncsA = obj1.arrayGetLength(); if (nFuncsA > gfxColorMaxComps || nFuncsA == 0) { error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary"); return nullptr; } for (int i = 0; i < nFuncsA; ++i) { Object obj2 = obj1.arrayGet(i); Function *f = Function::parse(&obj2); if (!f) { return nullptr; } funcsA.emplace_back(f); } } else { Function *f = Function::parse(&obj1); if (!f) { return nullptr; } funcsA.emplace_back(f); } extend0A = extend1A = false; obj1 = dict->lookup("Extend"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { Object obj2 = obj1.arrayGet(0); if (obj2.isBool()) { extend0A = obj2.getBool(); } else { error(errSyntaxWarning, -1, "Invalid axial shading extend (0)"); } obj2 = obj1.arrayGet(1); if (obj2.isBool()) { extend1A = obj2.getBool(); } else { error(errSyntaxWarning, -1, "Invalid axial shading extend (1)"); } } shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A, std::move(funcsA), extend0A, extend1A); if (!shading->init(res, dict, out, state)) { delete shading; shading = nullptr; } return shading; } GfxShading *GfxAxialShading::copy() const { return new GfxAxialShading(this); } double GfxAxialShading::getDistance(double sMin, double sMax) const { double xMin, yMin, xMax, yMax; xMin = x0 + sMin * (x1 - x0); yMin = y0 + sMin * (y1 - y0); xMax = x0 + sMax * (x1 - x0); yMax = y0 + sMax * (y1 - y0); return hypot(xMax - xMin, yMax - yMin); } void GfxAxialShading::getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax) { double pdx, pdy, invsqnorm, tdx, tdy, t, range[2]; // Linear gradients are orthogonal to the line passing through their // extremes. Because of convexity, the parameter range can be // computed as the convex hull (one the real line) of the parameter // values of the 4 corners of the box. // // The parameter value t for a point (x,y) can be computed as: // // t = (p2 - p1) . (x,y) / |p2 - p1|^2 // // t0 is the t value for the top left corner // tdx is the difference between left and right corners // tdy is the difference between top and bottom corners pdx = x1 - x0; pdy = y1 - y0; const double invsqnorm_denominator = (pdx * pdx + pdy * pdy); if (unlikely(invsqnorm_denominator == 0)) { *lower = 0; *upper = 0; return; } invsqnorm = 1.0 / invsqnorm_denominator; pdx *= invsqnorm; pdy *= invsqnorm; t = (xMin - x0) * pdx + (yMin - y0) * pdy; tdx = (xMax - xMin) * pdx; tdy = (yMax - yMin) * pdy; // Because of the linearity of the t value, tdx can simply be added // the t0 to move along the top edge. After this, *lower and *upper // represent the parameter range for the top edge, so extending it // to include the whole box simply requires adding tdy to the // correct extreme. range[0] = range[1] = t; if (tdx < 0) range[0] += tdx; else range[1] += tdx; if (tdy < 0) range[0] += tdy; else range[1] += tdy; *lower = std::max(0., std::min(1., range[0])); *upper = std::max(0., std::min(1., range[1])); } //------------------------------------------------------------------------ // GfxRadialShading //------------------------------------------------------------------------ #ifndef RADIAL_EPSILON # define RADIAL_EPSILON (1. / 1024 / 1024) #endif GfxRadialShading::GfxRadialShading(double x0A, double y0A, double r0A, double x1A, double y1A, double r1A, double t0A, double t1A, std::vector> &&funcsA, bool extend0A, bool extend1A) : GfxUnivariateShading(3, t0A, t1A, std::move(funcsA), extend0A, extend1A) { x0 = x0A; y0 = y0A; r0 = r0A; x1 = x1A; y1 = y1A; r1 = r1A; } GfxRadialShading::GfxRadialShading(const GfxRadialShading *shading) : GfxUnivariateShading(shading) { x0 = shading->x0; y0 = shading->y0; r0 = shading->r0; x1 = shading->x1; y1 = shading->y1; r1 = shading->r1; } GfxRadialShading::~GfxRadialShading() { } GfxRadialShading *GfxRadialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) { GfxRadialShading *shading; double x0A, y0A, r0A, x1A, y1A, r1A; double t0A, t1A; std::vector> funcsA; bool extend0A, extend1A; Object obj1; int i; x0A = y0A = r0A = x1A = y1A = r1A = 0; obj1 = dict->lookup("Coords"); if (obj1.isArray() && obj1.arrayGetLength() == 6) { x0A = obj1.arrayGet(0).getNumWithDefaultValue(0); y0A = obj1.arrayGet(1).getNumWithDefaultValue(0); r0A = obj1.arrayGet(2).getNumWithDefaultValue(0); x1A = obj1.arrayGet(3).getNumWithDefaultValue(0); y1A = obj1.arrayGet(4).getNumWithDefaultValue(0); r1A = obj1.arrayGet(5).getNumWithDefaultValue(0); } else { error(errSyntaxWarning, -1, "Missing or invalid Coords in shading dictionary"); return nullptr; } t0A = 0; t1A = 1; obj1 = dict->lookup("Domain"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { t0A = obj1.arrayGet(0).getNumWithDefaultValue(0); t1A = obj1.arrayGet(1).getNumWithDefaultValue(1); } obj1 = dict->lookup("Function"); if (obj1.isArray()) { const int nFuncsA = obj1.arrayGetLength(); if (nFuncsA > gfxColorMaxComps) { error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary"); return nullptr; } for (i = 0; i < nFuncsA; ++i) { Object obj2 = obj1.arrayGet(i); Function *f = Function::parse(&obj2); if (!f) { return nullptr; } funcsA.emplace_back(f); } } else { Function *f = Function::parse(&obj1); if (!f) { return nullptr; } funcsA.emplace_back(f); } extend0A = extend1A = false; obj1 = dict->lookup("Extend"); if (obj1.isArray() && obj1.arrayGetLength() == 2) { extend0A = obj1.arrayGet(0).getBoolWithDefaultValue(false); extend1A = obj1.arrayGet(1).getBoolWithDefaultValue(false); } shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A, std::move(funcsA), extend0A, extend1A); if (!shading->init(res, dict, out, state)) { delete shading; return nullptr; } return shading; } GfxShading *GfxRadialShading::copy() const { return new GfxRadialShading(this); } double GfxRadialShading::getDistance(double sMin, double sMax) const { double xMin, yMin, rMin, xMax, yMax, rMax; xMin = x0 + sMin * (x1 - x0); yMin = y0 + sMin * (y1 - y0); rMin = r0 + sMin * (r1 - r0); xMax = x0 + sMax * (x1 - x0); yMax = y0 + sMax * (y1 - y0); rMax = r0 + sMax * (r1 - r0); return hypot(xMax - xMin, yMax - yMin) + fabs(rMax - rMin); } // extend range, adapted from cairo, radialExtendRange static bool radialExtendRange(double range[2], double value, bool valid) { if (!valid) range[0] = range[1] = value; else if (value < range[0]) range[0] = value; else if (value > range[1]) range[1] = value; return true; } inline void radialEdge(double num, double den, double delta, double lower, double upper, double dr, double mindr, bool &valid, double *range) { if (fabs(den) >= RADIAL_EPSILON) { double t_edge, v; t_edge = (num) / (den); v = t_edge * (delta); if (t_edge * dr >= mindr && (lower) <= v && v <= (upper)) valid = radialExtendRange(range, t_edge, valid); } } inline void radialCorner1(double x, double y, double &b, double dx, double dy, double cr, double dr, double mindr, bool &valid, double *range) { b = (x)*dx + (y)*dy + cr * dr; if (fabs(b) >= RADIAL_EPSILON) { double t_corner; double x2 = (x) * (x); double y2 = (y) * (y); double cr2 = (cr) * (cr); double c = x2 + y2 - cr2; t_corner = 0.5 * c / b; if (t_corner * dr >= mindr) valid = radialExtendRange(range, t_corner, valid); } } inline 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) { b = (x)*dx + (y)*dy + cr * dr; c = (x) * (x) + (y) * (y)-cr * cr; d = b * b - a * c; if (d >= 0) { double t_corner; d = sqrt(d); t_corner = (b + d) * inva; if (t_corner * dr >= mindr) valid = radialExtendRange(range, t_corner, valid); t_corner = (b - d) * inva; if (t_corner * dr >= mindr) valid = radialExtendRange(range, t_corner, valid); } } void GfxRadialShading::getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax) { double cx, cy, cr, dx, dy, dr; double a, x_focus, y_focus; double mindr, minx, miny, maxx, maxy; double range[2]; bool valid; // A radial pattern is considered degenerate if it can be // represented as a solid or clear pattern. This corresponds to one // of the two cases: // // 1) The radii are both very small: // |dr| < FLT_EPSILON && min (r0, r1) < FLT_EPSILON // // 2) The two circles have about the same radius and are very // close to each other (approximately a cylinder gradient that // doesn't move with the parameter): // |dr| < FLT_EPSILON && max (|dx|, |dy|) < 2 * FLT_EPSILON if (xMin >= xMax || yMin >= yMax || (fabs(r0 - r1) < RADIAL_EPSILON && (std::min(r0, r1) < RADIAL_EPSILON || std::max(fabs(x0 - x1), fabs(y0 - y1)) < 2 * RADIAL_EPSILON))) { *lower = *upper = 0; return; } range[0] = range[1] = 0; valid = false; x_focus = y_focus = 0; // silence gcc cx = x0; cy = y0; cr = r0; dx = x1 - cx; dy = y1 - cy; dr = r1 - cr; // translate by -(cx, cy) to simplify computations xMin -= cx; yMin -= cy; xMax -= cx; yMax -= cy; // enlarge boundaries slightly to avoid rounding problems in the // parameter range computation xMin -= RADIAL_EPSILON; yMin -= RADIAL_EPSILON; xMax += RADIAL_EPSILON; yMax += RADIAL_EPSILON; // enlarge boundaries even more to avoid rounding problems when // testing if a point belongs to the box minx = xMin - RADIAL_EPSILON; miny = yMin - RADIAL_EPSILON; maxx = xMax + RADIAL_EPSILON; maxy = yMax + RADIAL_EPSILON; // we dont' allow negative radiuses, so we will be checking that // t*dr >= mindr to consider t valid mindr = -(cr + RADIAL_EPSILON); // After the previous transformations, the start circle is centered // in the origin and has radius cr. A 1-unit change in the t // parameter corresponds to dx,dy,dr changes in the x,y,r of the // circle (center coordinates, radius). // // To compute the minimum range needed to correctly draw the // pattern, we start with an empty range and extend it to include // the circles touching the bounding box or within it. // Focus, the point where the circle has radius == 0. // // r = cr + t * dr = 0 // t = -cr / dr // // If the radius is constant (dr == 0) there is no focus (the // gradient represents a cylinder instead of a cone). if (fabs(dr) >= RADIAL_EPSILON) { double t_focus; t_focus = -cr / dr; x_focus = t_focus * dx; y_focus = t_focus * dy; if (minx <= x_focus && x_focus <= maxx && miny <= y_focus && y_focus <= maxy) { valid = radialExtendRange(range, t_focus, valid); } } // Circles externally tangent to box edges. // // All circles have center in (dx, dy) * t // // If the circle is tangent to the line defined by the edge of the // box, then at least one of the following holds true: // // (dx*t) + (cr + dr*t) == x0 (left edge) // (dx*t) - (cr + dr*t) == x1 (right edge) // (dy*t) + (cr + dr*t) == y0 (top edge) // (dy*t) - (cr + dr*t) == y1 (bottom edge) // // The solution is only valid if the tangent point is actually on // the edge, i.e. if its y coordinate is in [y0,y1] for left/right // edges and if its x coordinate is in [x0,x1] for top/bottom edges. // // For the first equation: // // (dx + dr) * t = x0 - cr // t = (x0 - cr) / (dx + dr) // y = dy * t // // in the code this becomes: // // t_edge = (num) / (den) // v = (delta) * t_edge // // If the denominator in t is 0, the pattern is tangent to a line // parallel to the edge under examination. The corner-case where the // boundary line is the same as the edge is handled by the focus // point case and/or by the a==0 case. // circles tangent (externally) to left/right/top/bottom edge radialEdge(xMin - cr, dx + dr, dy, miny, maxy, dr, mindr, valid, range); radialEdge(xMax + cr, dx - dr, dy, miny, maxy, dr, mindr, valid, range); radialEdge(yMin - cr, dy + dr, dx, minx, maxx, dr, mindr, valid, range); radialEdge(yMax + cr, dy - dr, dx, minx, maxx, dr, mindr, valid, range); // Circles passing through a corner. // // A circle passing through the point (x,y) satisfies: // // (x-t*dx)^2 + (y-t*dy)^2 == (cr + t*dr)^2 // // If we set: // a = dx^2 + dy^2 - dr^2 // b = x*dx + y*dy + cr*dr // c = x^2 + y^2 - cr^2 // we have: // a*t^2 - 2*b*t + c == 0 a = dx * dx + dy * dy - dr * dr; if (fabs(a) < RADIAL_EPSILON * RADIAL_EPSILON) { double b; // Ensure that gradients with both a and dr small are // considered degenerate. // The floating point version of the degeneracy test implemented // in _radial_pattern_is_degenerate() is: // // 1) The circles are practically the same size: // |dr| < RADIAL_EPSILON // AND // 2a) The circles are both very small: // min (r0, r1) < RADIAL_EPSILON // OR // 2b) The circles are very close to each other: // max (|dx|, |dy|) < 2 * RADIAL_EPSILON // // Assuming that the gradient is not degenerate, we want to // show that |a| < RADIAL_EPSILON^2 implies |dr| >= RADIAL_EPSILON. // // If the gradient is not degenerate yet it has |dr| < // RADIAL_EPSILON, (2b) is false, thus: // // max (|dx|, |dy|) >= 2*RADIAL_EPSILON // which implies: // 4*RADIAL_EPSILON^2 <= max (|dx|, |dy|)^2 <= dx^2 + dy^2 // // From the definition of a, we get: // a = dx^2 + dy^2 - dr^2 < RADIAL_EPSILON^2 // dx^2 + dy^2 - RADIAL_EPSILON^2 < dr^2 // 3*RADIAL_EPSILON^2 < dr^2 // // which is inconsistent with the hypotheses, thus |dr| < // RADIAL_EPSILON is false or the gradient is degenerate. assert(fabs(dr) >= RADIAL_EPSILON); // If a == 0, all the circles are tangent to a line in the // focus point. If this line is within the box extents, we // should add the circle with infinite radius, but this would // make the range unbounded. We will be limiting the range to // [0,1] anyway, so we simply add the biggest legitimate // circle (it happens for 0 or for 1). if (dr < 0) { valid = radialExtendRange(range, 0, valid); } else { valid = radialExtendRange(range, 1, valid); } // Nondegenerate, nonlimit circles passing through the corners. // // a == 0 && a*t^2 - 2*b*t + c == 0 // // t = c / (2*b) // // The b == 0 case has just been handled, so we only have to // compute this if b != 0. // circles touching each corner radialCorner1(xMin, yMin, b, dx, dy, cr, dr, mindr, valid, range); radialCorner1(xMin, yMax, b, dx, dy, cr, dr, mindr, valid, range); radialCorner1(xMax, yMin, b, dx, dy, cr, dr, mindr, valid, range); radialCorner1(xMax, yMax, b, dx, dy, cr, dr, mindr, valid, range); } else { double inva, b, c, d; inva = 1 / a; // Nondegenerate, nonlimit circles passing through the corners. // // a != 0 && a*t^2 - 2*b*t + c == 0 // // t = (b +- sqrt (b*b - a*c)) / a // // If the argument of sqrt() is negative, then no circle // passes through the corner. // circles touching each corner radialCorner2(xMin, yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range); radialCorner2(xMin, yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range); radialCorner2(xMax, yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range); radialCorner2(xMax, yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range); } *lower = std::max(0., std::min(1., range[0])); *upper = std::max(0., std::min(1., range[1])); } //------------------------------------------------------------------------ // GfxShadingBitBuf //------------------------------------------------------------------------ class GfxShadingBitBuf { public: GfxShadingBitBuf(Stream *strA); ~GfxShadingBitBuf(); GfxShadingBitBuf(const GfxShadingBitBuf &) = delete; GfxShadingBitBuf &operator=(const GfxShadingBitBuf &) = delete; bool getBits(int n, unsigned int *val); void flushBits(); private: Stream *str; int bitBuf; int nBits; }; GfxShadingBitBuf::GfxShadingBitBuf(Stream *strA) { str = strA; str->reset(); bitBuf = 0; nBits = 0; } GfxShadingBitBuf::~GfxShadingBitBuf() { str->close(); } bool GfxShadingBitBuf::getBits(int n, unsigned int *val) { unsigned int x; if (nBits >= n) { x = (bitBuf >> (nBits - n)) & ((1 << n) - 1); nBits -= n; } else { x = 0; if (nBits > 0) { x = bitBuf & ((1 << nBits) - 1); n -= nBits; nBits = 0; } while (n > 0) { if ((bitBuf = str->getChar()) == EOF) { nBits = 0; return false; } if (n >= 8) { x = (x << 8) | bitBuf; n -= 8; } else { x = (x << n) | (bitBuf >> (8 - n)); nBits = 8 - n; n = 0; } } } *val = x; return true; } void GfxShadingBitBuf::flushBits() { bitBuf = 0; nBits = 0; } //------------------------------------------------------------------------ // GfxGouraudTriangleShading //------------------------------------------------------------------------ GfxGouraudTriangleShading::GfxGouraudTriangleShading(int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, std::vector> &&funcsA) : GfxShading(typeA), funcs(std::move(funcsA)) { vertices = verticesA; nVertices = nVerticesA; triangles = trianglesA; nTriangles = nTrianglesA; } GfxGouraudTriangleShading::GfxGouraudTriangleShading(const GfxGouraudTriangleShading *shading) : GfxShading(shading) { nVertices = shading->nVertices; vertices = (GfxGouraudVertex *)gmallocn(nVertices, sizeof(GfxGouraudVertex)); memcpy(vertices, shading->vertices, nVertices * sizeof(GfxGouraudVertex)); nTriangles = shading->nTriangles; triangles = (int(*)[3])gmallocn(nTriangles * 3, sizeof(int)); memcpy(triangles, shading->triangles, nTriangles * 3 * sizeof(int)); for (const auto &f : shading->funcs) { funcs.emplace_back(f->copy()); } } GfxGouraudTriangleShading::~GfxGouraudTriangleShading() { gfree(vertices); gfree(triangles); } GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *gfxState) { GfxGouraudTriangleShading *shading; std::vector> funcsA; int coordBits, compBits, flagBits, vertsPerRow, nRows; double xMin, xMax, yMin, yMax; double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps]; double xMul, yMul; double cMul[gfxColorMaxComps]; GfxGouraudVertex *verticesA; int(*trianglesA)[3]; int nComps, nVerticesA, nTrianglesA, vertSize, triSize; unsigned int x, y, flag; unsigned int c[gfxColorMaxComps]; GfxShadingBitBuf *bitBuf; Object obj1; int i, j, k, state; obj1 = dict->lookup("BitsPerCoordinate"); if (obj1.isInt()) { coordBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerCoordinate in shading dictionary"); return nullptr; } if (unlikely(coordBits <= 0)) { error(errSyntaxWarning, -1, "Invalid BitsPerCoordinate in shading dictionary"); return nullptr; } obj1 = dict->lookup("BitsPerComponent"); if (obj1.isInt()) { compBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerComponent in shading dictionary"); return nullptr; } if (unlikely(compBits <= 0 || compBits > 31)) { error(errSyntaxWarning, -1, "Invalid BitsPerComponent in shading dictionary"); return nullptr; } flagBits = vertsPerRow = 0; // make gcc happy if (typeA == 4) { obj1 = dict->lookup("BitsPerFlag"); if (obj1.isInt()) { flagBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerFlag in shading dictionary"); return nullptr; } } else { obj1 = dict->lookup("VerticesPerRow"); if (obj1.isInt()) { vertsPerRow = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid VerticesPerRow in shading dictionary"); return nullptr; } } obj1 = dict->lookup("Decode"); if (obj1.isArray() && obj1.arrayGetLength() >= 6) { bool decodeOk = true; xMin = obj1.arrayGet(0).getNum(&decodeOk); xMax = obj1.arrayGet(1).getNum(&decodeOk); xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1); yMin = obj1.arrayGet(2).getNum(&decodeOk); yMax = obj1.arrayGet(3).getNum(&decodeOk); yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1); for (i = 0; 5 + 2 * i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) { cMin[i] = obj1.arrayGet(4 + 2 * i).getNum(&decodeOk); cMax[i] = obj1.arrayGet(5 + 2 * i).getNum(&decodeOk); cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1); } nComps = i; if (!decodeOk) { error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary"); return nullptr; } } else { error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary"); return nullptr; } obj1 = dict->lookup("Function"); if (!obj1.isNull()) { if (obj1.isArray()) { const int nFuncsA = obj1.arrayGetLength(); if (nFuncsA > gfxColorMaxComps) { error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary"); return nullptr; } for (i = 0; i < nFuncsA; ++i) { Object obj2 = obj1.arrayGet(i); Function *f = Function::parse(&obj2); if (!f) { return nullptr; } funcsA.emplace_back(f); } } else { Function *f = Function::parse(&obj1); if (!f) { return nullptr; } funcsA.emplace_back(f); } } nVerticesA = nTrianglesA = 0; verticesA = nullptr; trianglesA = nullptr; vertSize = triSize = 0; state = 0; flag = 0; // make gcc happy bitBuf = new GfxShadingBitBuf(str); while (true) { if (typeA == 4) { if (!bitBuf->getBits(flagBits, &flag)) { break; } } if (!bitBuf->getBits(coordBits, &x) || !bitBuf->getBits(coordBits, &y)) { break; } for (i = 0; i < nComps; ++i) { if (!bitBuf->getBits(compBits, &c[i])) { break; } } if (i < nComps) { break; } if (nVerticesA == vertSize) { int oldVertSize = vertSize; vertSize = (vertSize == 0) ? 16 : 2 * vertSize; verticesA = (GfxGouraudVertex *)greallocn_checkoverflow(verticesA, vertSize, sizeof(GfxGouraudVertex)); if (unlikely(!verticesA)) { error(errSyntaxWarning, -1, "GfxGouraudTriangleShading::parse: vertices size overflow"); gfree(trianglesA); delete bitBuf; return nullptr; } memset(verticesA + oldVertSize, 0, (vertSize - oldVertSize) * sizeof(GfxGouraudVertex)); } verticesA[nVerticesA].x = xMin + xMul * (double)x; verticesA[nVerticesA].y = yMin + yMul * (double)y; for (i = 0; i < nComps; ++i) { verticesA[nVerticesA].color.c[i] = dblToCol(cMin[i] + cMul[i] * (double)c[i]); } ++nVerticesA; bitBuf->flushBits(); if (typeA == 4) { if (state == 0 || state == 1) { ++state; } else if (state == 2 || flag > 0) { if (nTrianglesA == triSize) { triSize = (triSize == 0) ? 16 : 2 * triSize; trianglesA = (int(*)[3])greallocn(trianglesA, triSize * 3, sizeof(int)); } if (state == 2) { trianglesA[nTrianglesA][0] = nVerticesA - 3; trianglesA[nTrianglesA][1] = nVerticesA - 2; trianglesA[nTrianglesA][2] = nVerticesA - 1; ++state; } else if (flag == 1) { trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][1]; trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2]; trianglesA[nTrianglesA][2] = nVerticesA - 1; } else { // flag == 2 trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][0]; trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2]; trianglesA[nTrianglesA][2] = nVerticesA - 1; } ++nTrianglesA; } else { // state == 3 && flag == 0 state = 1; } } } delete bitBuf; if (typeA == 5 && nVerticesA > 0 && vertsPerRow > 0) { nRows = nVerticesA / vertsPerRow; nTrianglesA = (nRows - 1) * 2 * (vertsPerRow - 1); trianglesA = (int(*)[3])gmallocn_checkoverflow(nTrianglesA * 3, sizeof(int)); if (unlikely(!trianglesA)) { gfree(verticesA); return nullptr; } k = 0; for (i = 0; i < nRows - 1; ++i) { for (j = 0; j < vertsPerRow - 1; ++j) { trianglesA[k][0] = i * vertsPerRow + j; trianglesA[k][1] = i * vertsPerRow + j + 1; trianglesA[k][2] = (i + 1) * vertsPerRow + j; ++k; trianglesA[k][0] = i * vertsPerRow + j + 1; trianglesA[k][1] = (i + 1) * vertsPerRow + j; trianglesA[k][2] = (i + 1) * vertsPerRow + j + 1; ++k; } } } shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA, trianglesA, nTrianglesA, std::move(funcsA)); if (!shading->init(res, dict, out, gfxState)) { delete shading; return nullptr; } return shading; } GfxShading *GfxGouraudTriangleShading::copy() const { return new GfxGouraudTriangleShading(this); } void GfxGouraudTriangleShading::getTriangle(int i, double *x0, double *y0, GfxColor *color0, double *x1, double *y1, GfxColor *color1, double *x2, double *y2, GfxColor *color2) { int v; assert(!isParameterized()); v = triangles[i][0]; *x0 = vertices[v].x; *y0 = vertices[v].y; *color0 = vertices[v].color; v = triangles[i][1]; *x1 = vertices[v].x; *y1 = vertices[v].y; *color1 = vertices[v].color; v = triangles[i][2]; *x2 = vertices[v].x; *y2 = vertices[v].y; *color2 = vertices[v].color; } void GfxGouraudTriangleShading::getParameterizedColor(double t, GfxColor *color) const { double out[gfxColorMaxComps]; for (unsigned int j = 0; j < funcs.size(); ++j) { funcs[j]->transform(&t, &out[j]); } for (int j = 0; j < gfxColorMaxComps; ++j) { color->c[j] = dblToCol(out[j]); } } void GfxGouraudTriangleShading::getTriangle(int i, double *x0, double *y0, double *color0, double *x1, double *y1, double *color1, double *x2, double *y2, double *color2) { int v; assert(isParameterized()); v = triangles[i][0]; if (likely(v >= 0 && v < nVertices)) { *x0 = vertices[v].x; *y0 = vertices[v].y; *color0 = colToDbl(vertices[v].color.c[0]); } v = triangles[i][1]; if (likely(v >= 0 && v < nVertices)) { *x1 = vertices[v].x; *y1 = vertices[v].y; *color1 = colToDbl(vertices[v].color.c[0]); } v = triangles[i][2]; if (likely(v >= 0 && v < nVertices)) { *x2 = vertices[v].x; *y2 = vertices[v].y; *color2 = colToDbl(vertices[v].color.c[0]); } } //------------------------------------------------------------------------ // GfxPatchMeshShading //------------------------------------------------------------------------ GfxPatchMeshShading::GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, std::vector> &&funcsA) : GfxShading(typeA), funcs(std::move(funcsA)) { patches = patchesA; nPatches = nPatchesA; } GfxPatchMeshShading::GfxPatchMeshShading(const GfxPatchMeshShading *shading) : GfxShading(shading) { nPatches = shading->nPatches; patches = (GfxPatch *)gmallocn(nPatches, sizeof(GfxPatch)); memcpy(patches, shading->patches, nPatches * sizeof(GfxPatch)); for (const auto &f : shading->funcs) { funcs.emplace_back(f->copy()); } } GfxPatchMeshShading::~GfxPatchMeshShading() { gfree(patches); } GfxPatchMeshShading *GfxPatchMeshShading::parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state) { GfxPatchMeshShading *shading; std::vector> funcsA; int coordBits, compBits, flagBits; double xMin, xMax, yMin, yMax; double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps]; double xMul, yMul; double cMul[gfxColorMaxComps]; GfxPatch *patchesA, *p; int nComps, nPatchesA, patchesSize, nPts, nColors; unsigned int flag; double x[16], y[16]; unsigned int xi, yi; double c[4][gfxColorMaxComps]; unsigned int ci; Object obj1; int i, j; obj1 = dict->lookup("BitsPerCoordinate"); if (obj1.isInt()) { coordBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerCoordinate in shading dictionary"); return nullptr; } if (unlikely(coordBits <= 0)) { error(errSyntaxWarning, -1, "Invalid BitsPerCoordinate in shading dictionary"); return nullptr; } obj1 = dict->lookup("BitsPerComponent"); if (obj1.isInt()) { compBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerComponent in shading dictionary"); return nullptr; } if (unlikely(compBits <= 0 || compBits > 31)) { error(errSyntaxWarning, -1, "Invalid BitsPerComponent in shading dictionary"); return nullptr; } obj1 = dict->lookup("BitsPerFlag"); if (obj1.isInt()) { flagBits = obj1.getInt(); } else { error(errSyntaxWarning, -1, "Missing or invalid BitsPerFlag in shading dictionary"); return nullptr; } obj1 = dict->lookup("Decode"); if (obj1.isArray() && obj1.arrayGetLength() >= 6) { bool decodeOk = true; xMin = obj1.arrayGet(0).getNum(&decodeOk); xMax = obj1.arrayGet(1).getNum(&decodeOk); xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1); yMin = obj1.arrayGet(2).getNum(&decodeOk); yMax = obj1.arrayGet(3).getNum(&decodeOk); yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1); for (i = 0; 5 + 2 * i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) { cMin[i] = obj1.arrayGet(4 + 2 * i).getNum(&decodeOk); cMax[i] = obj1.arrayGet(5 + 2 * i).getNum(&decodeOk); cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1); } nComps = i; if (!decodeOk) { error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary"); return nullptr; } } else { error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary"); return nullptr; } obj1 = dict->lookup("Function"); if (!obj1.isNull()) { if (obj1.isArray()) { const int nFuncsA = obj1.arrayGetLength(); if (nFuncsA > gfxColorMaxComps) { error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary"); return nullptr; } for (i = 0; i < nFuncsA; ++i) { Object obj2 = obj1.arrayGet(i); Function *f = Function::parse(&obj2); if (!f) { return nullptr; } funcsA.emplace_back(f); } } else { Function *f = Function::parse(&obj1); if (!f) { return nullptr; } funcsA.emplace_back(f); } } nPatchesA = 0; patchesA = nullptr; patchesSize = 0; auto bitBuf = std::make_unique(str); while (true) { if (!bitBuf->getBits(flagBits, &flag)) { break; } if (typeA == 6) { switch (flag) { case 0: nPts = 12; nColors = 4; break; case 1: case 2: case 3: default: nPts = 8; nColors = 2; break; } } else { switch (flag) { case 0: nPts = 16; nColors = 4; break; case 1: case 2: case 3: default: nPts = 12; nColors = 2; break; } } for (i = 0; i < nPts; ++i) { if (!bitBuf->getBits(coordBits, &xi) || !bitBuf->getBits(coordBits, &yi)) { break; } x[i] = xMin + xMul * (double)xi; y[i] = yMin + yMul * (double)yi; } if (i < nPts) { break; } for (i = 0; i < nColors; ++i) { for (j = 0; j < nComps; ++j) { if (!bitBuf->getBits(compBits, &ci)) { break; } c[i][j] = cMin[j] + cMul[j] * (double)ci; if (funcsA.empty()) { // ... and colorspace values can also be stored into doubles. // They will be casted later. c[i][j] = dblToCol(c[i][j]); } } if (j < nComps) { break; } } if (i < nColors) { break; } if (nPatchesA == patchesSize) { int oldPatchesSize = patchesSize; patchesSize = (patchesSize == 0) ? 16 : 2 * patchesSize; patchesA = (GfxPatch *)greallocn_checkoverflow(patchesA, patchesSize, sizeof(GfxPatch)); if (unlikely(!patchesA)) { return nullptr; } memset(patchesA + oldPatchesSize, 0, (patchesSize - oldPatchesSize) * sizeof(GfxPatch)); } p = &patchesA[nPatchesA]; if (typeA == 6) { switch (flag) { case 0: p->x[0][0] = x[0]; p->y[0][0] = y[0]; p->x[0][1] = x[1]; p->y[0][1] = y[1]; p->x[0][2] = x[2]; p->y[0][2] = y[2]; p->x[0][3] = x[3]; p->y[0][3] = y[3]; p->x[1][3] = x[4]; p->y[1][3] = y[4]; p->x[2][3] = x[5]; p->y[2][3] = y[5]; p->x[3][3] = x[6]; p->y[3][3] = y[6]; p->x[3][2] = x[7]; p->y[3][2] = y[7]; p->x[3][1] = x[8]; p->y[3][1] = y[8]; p->x[3][0] = x[9]; p->y[3][0] = y[9]; p->x[2][0] = x[10]; p->y[2][0] = y[10]; p->x[1][0] = x[11]; p->y[1][0] = y[11]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = c[0][j]; p->color[0][1].c[j] = c[1][j]; p->color[1][1].c[j] = c[2][j]; p->color[1][0].c[j] = c[3][j]; } break; case 1: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[0][3]; p->y[0][0] = patchesA[nPatchesA - 1].y[0][3]; p->x[0][1] = patchesA[nPatchesA - 1].x[1][3]; p->y[0][1] = patchesA[nPatchesA - 1].y[1][3]; p->x[0][2] = patchesA[nPatchesA - 1].x[2][3]; p->y[0][2] = patchesA[nPatchesA - 1].y[2][3]; p->x[0][3] = patchesA[nPatchesA - 1].x[3][3]; p->y[0][3] = patchesA[nPatchesA - 1].y[3][3]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[0][1].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; case 2: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[3][3]; p->y[0][0] = patchesA[nPatchesA - 1].y[3][3]; p->x[0][1] = patchesA[nPatchesA - 1].x[3][2]; p->y[0][1] = patchesA[nPatchesA - 1].y[3][2]; p->x[0][2] = patchesA[nPatchesA - 1].x[3][1]; p->y[0][2] = patchesA[nPatchesA - 1].y[3][1]; p->x[0][3] = patchesA[nPatchesA - 1].x[3][0]; p->y[0][3] = patchesA[nPatchesA - 1].y[3][0]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; case 3: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[3][0]; p->y[0][0] = patchesA[nPatchesA - 1].y[3][0]; p->x[0][1] = patchesA[nPatchesA - 1].x[2][0]; p->y[0][1] = patchesA[nPatchesA - 1].y[2][0]; p->x[0][2] = patchesA[nPatchesA - 1].x[1][0]; p->y[0][2] = patchesA[nPatchesA - 1].y[1][0]; p->x[0][3] = patchesA[nPatchesA - 1].x[0][0]; p->y[0][3] = patchesA[nPatchesA - 1].y[0][0]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[0][0].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; } } else { switch (flag) { case 0: p->x[0][0] = x[0]; p->y[0][0] = y[0]; p->x[0][1] = x[1]; p->y[0][1] = y[1]; p->x[0][2] = x[2]; p->y[0][2] = y[2]; p->x[0][3] = x[3]; p->y[0][3] = y[3]; p->x[1][3] = x[4]; p->y[1][3] = y[4]; p->x[2][3] = x[5]; p->y[2][3] = y[5]; p->x[3][3] = x[6]; p->y[3][3] = y[6]; p->x[3][2] = x[7]; p->y[3][2] = y[7]; p->x[3][1] = x[8]; p->y[3][1] = y[8]; p->x[3][0] = x[9]; p->y[3][0] = y[9]; p->x[2][0] = x[10]; p->y[2][0] = y[10]; p->x[1][0] = x[11]; p->y[1][0] = y[11]; p->x[1][1] = x[12]; p->y[1][1] = y[12]; p->x[1][2] = x[13]; p->y[1][2] = y[13]; p->x[2][2] = x[14]; p->y[2][2] = y[14]; p->x[2][1] = x[15]; p->y[2][1] = y[15]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = c[0][j]; p->color[0][1].c[j] = c[1][j]; p->color[1][1].c[j] = c[2][j]; p->color[1][0].c[j] = c[3][j]; } break; case 1: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[0][3]; p->y[0][0] = patchesA[nPatchesA - 1].y[0][3]; p->x[0][1] = patchesA[nPatchesA - 1].x[1][3]; p->y[0][1] = patchesA[nPatchesA - 1].y[1][3]; p->x[0][2] = patchesA[nPatchesA - 1].x[2][3]; p->y[0][2] = patchesA[nPatchesA - 1].y[2][3]; p->x[0][3] = patchesA[nPatchesA - 1].x[3][3]; p->y[0][3] = patchesA[nPatchesA - 1].y[3][3]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; p->x[1][1] = x[8]; p->y[1][1] = y[8]; p->x[1][2] = x[9]; p->y[1][2] = y[9]; p->x[2][2] = x[10]; p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[0][1].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; case 2: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[3][3]; p->y[0][0] = patchesA[nPatchesA - 1].y[3][3]; p->x[0][1] = patchesA[nPatchesA - 1].x[3][2]; p->y[0][1] = patchesA[nPatchesA - 1].y[3][2]; p->x[0][2] = patchesA[nPatchesA - 1].x[3][1]; p->y[0][2] = patchesA[nPatchesA - 1].y[3][1]; p->x[0][3] = patchesA[nPatchesA - 1].x[3][0]; p->y[0][3] = patchesA[nPatchesA - 1].y[3][0]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; p->x[1][1] = x[8]; p->y[1][1] = y[8]; p->x[1][2] = x[9]; p->y[1][2] = y[9]; p->x[2][2] = x[10]; p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][1].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; case 3: if (nPatchesA == 0) { gfree(patchesA); return nullptr; } p->x[0][0] = patchesA[nPatchesA - 1].x[3][0]; p->y[0][0] = patchesA[nPatchesA - 1].y[3][0]; p->x[0][1] = patchesA[nPatchesA - 1].x[2][0]; p->y[0][1] = patchesA[nPatchesA - 1].y[2][0]; p->x[0][2] = patchesA[nPatchesA - 1].x[1][0]; p->y[0][2] = patchesA[nPatchesA - 1].y[1][0]; p->x[0][3] = patchesA[nPatchesA - 1].x[0][0]; p->y[0][3] = patchesA[nPatchesA - 1].y[0][0]; p->x[1][3] = x[0]; p->y[1][3] = y[0]; p->x[2][3] = x[1]; p->y[2][3] = y[1]; p->x[3][3] = x[2]; p->y[3][3] = y[2]; p->x[3][2] = x[3]; p->y[3][2] = y[3]; p->x[3][1] = x[4]; p->y[3][1] = y[4]; p->x[3][0] = x[5]; p->y[3][0] = y[5]; p->x[2][0] = x[6]; p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; p->x[1][1] = x[8]; p->y[1][1] = y[8]; p->x[1][2] = x[9]; p->y[1][2] = y[9]; p->x[2][2] = x[10]; p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; for (j = 0; j < nComps; ++j) { p->color[0][0].c[j] = patchesA[nPatchesA - 1].color[1][0].c[j]; p->color[0][1].c[j] = patchesA[nPatchesA - 1].color[0][0].c[j]; p->color[1][1].c[j] = c[0][j]; p->color[1][0].c[j] = c[1][j]; } break; } } ++nPatchesA; bitBuf->flushBits(); } if (typeA == 6) { for (i = 0; i < nPatchesA; ++i) { p = &patchesA[i]; 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; 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; 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; 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; 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; 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; 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; 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; } } shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA, std::move(funcsA)); if (!shading->init(res, dict, out, state)) { delete shading; return nullptr; } return shading; } void GfxPatchMeshShading::getParameterizedColor(double t, GfxColor *color) const { double out[gfxColorMaxComps] = {}; for (unsigned int j = 0; j < funcs.size(); ++j) { funcs[j]->transform(&t, &out[j]); } for (int j = 0; j < gfxColorMaxComps; ++j) { color->c[j] = dblToCol(out[j]); } } GfxShading *GfxPatchMeshShading::copy() const { return new GfxPatchMeshShading(this); } //------------------------------------------------------------------------ // GfxImageColorMap //------------------------------------------------------------------------ GfxImageColorMap::GfxImageColorMap(int bitsA, Object *decode, GfxColorSpace *colorSpaceA) { GfxIndexedColorSpace *indexedCS; GfxSeparationColorSpace *sepCS; int maxPixel, indexHigh; unsigned char *indexedLookup; const Function *sepFunc; double x[gfxColorMaxComps]; double y[gfxColorMaxComps] = {}; int i, j, k; double mapped; bool useByteLookup; ok = true; useMatte = false; colorSpace = colorSpaceA; // initialize for (k = 0; k < gfxColorMaxComps; ++k) { lookup[k] = nullptr; lookup2[k] = nullptr; } byte_lookup = nullptr; // bits per component and color space if (unlikely(bitsA <= 0 || bitsA > 30)) goto err1; bits = bitsA; maxPixel = (1 << bits) - 1; // this is a hack to support 16 bits images, everywhere // we assume a component fits in 8 bits, with this hack // we treat 16 bit images as 8 bit ones until it's fixed correctly. // The hack has another part on ImageStream::getLine if (maxPixel > 255) maxPixel = 255; // get decode map if (decode->isNull()) { nComps = colorSpace->getNComps(); colorSpace->getDefaultRanges(decodeLow, decodeRange, maxPixel); } else if (decode->isArray()) { nComps = decode->arrayGetLength() / 2; if (nComps < colorSpace->getNComps()) { goto err1; } if (nComps > colorSpace->getNComps()) { error(errSyntaxWarning, -1, "Too many elements in Decode array"); nComps = colorSpace->getNComps(); } for (i = 0; i < nComps; ++i) { Object obj = decode->arrayGet(2 * i); if (!obj.isNum()) { goto err1; } decodeLow[i] = obj.getNum(); obj = decode->arrayGet(2 * i + 1); if (!obj.isNum()) { goto err1; } decodeRange[i] = obj.getNum() - decodeLow[i]; } } else { goto err1; } // Construct a lookup table -- this stores pre-computed decoded // values for each component, i.e., the result of applying the // decode mapping to each possible image pixel component value. for (k = 0; k < nComps; ++k) { lookup[k] = (GfxColorComp *)gmallocn(maxPixel + 1, sizeof(GfxColorComp)); for (i = 0; i <= maxPixel; ++i) { lookup[k][i] = dblToCol(decodeLow[k] + (i * decodeRange[k]) / maxPixel); } } // Optimization: for Indexed and Separation color spaces (which have // only one component), we pre-compute a second lookup table with // color values colorSpace2 = nullptr; nComps2 = 0; useByteLookup = false; switch (colorSpace->getMode()) { case csIndexed: // Note that indexHigh may not be the same as maxPixel -- // Distiller will remove unused palette entries, resulting in // indexHigh < maxPixel. indexedCS = (GfxIndexedColorSpace *)colorSpace; colorSpace2 = indexedCS->getBase(); indexHigh = indexedCS->getIndexHigh(); nComps2 = colorSpace2->getNComps(); indexedLookup = indexedCS->getLookup(); colorSpace2->getDefaultRanges(x, y, indexHigh); if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) { byte_lookup = (unsigned char *)gmallocn((maxPixel + 1), nComps2); useByteLookup = true; } for (k = 0; k < nComps2; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1, sizeof(GfxColorComp)); for (i = 0; i <= maxPixel; ++i) { j = (int)(decodeLow[0] + (i * decodeRange[0]) / maxPixel + 0.5); if (j < 0) { j = 0; } else if (j > indexHigh) { j = indexHigh; } mapped = x[k] + (indexedLookup[j * nComps2 + k] / 255.0) * y[k]; lookup2[k][i] = dblToCol(mapped); if (useByteLookup) byte_lookup[i * nComps2 + k] = (unsigned char)(mapped * 255); } } break; case csSeparation: sepCS = (GfxSeparationColorSpace *)colorSpace; colorSpace2 = sepCS->getAlt(); nComps2 = colorSpace2->getNComps(); sepFunc = sepCS->getFunc(); if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) { byte_lookup = (unsigned char *)gmallocn((maxPixel + 1), nComps2); useByteLookup = true; } for (k = 0; k < nComps2; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1, sizeof(GfxColorComp)); for (i = 0; i <= maxPixel; ++i) { x[0] = decodeLow[0] + (i * decodeRange[0]) / maxPixel; sepFunc->transform(x, y); lookup2[k][i] = dblToCol(y[k]); if (useByteLookup) byte_lookup[i * nComps2 + k] = (unsigned char)(y[k] * 255); } } break; default: if (colorSpace->useGetGrayLine() || colorSpace->useGetRGBLine() || colorSpace->useGetCMYKLine() || colorSpace->useGetDeviceNLine()) { byte_lookup = (unsigned char *)gmallocn((maxPixel + 1), nComps); useByteLookup = true; } for (k = 0; k < nComps; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1, sizeof(GfxColorComp)); for (i = 0; i <= maxPixel; ++i) { mapped = decodeLow[k] + (i * decodeRange[k]) / maxPixel; lookup2[k][i] = dblToCol(mapped); if (useByteLookup) { int byte; byte = (int)(mapped * 255.0 + 0.5); if (byte < 0) byte = 0; else if (byte > 255) byte = 255; byte_lookup[i * nComps + k] = byte; } } } } return; err1: ok = false; } GfxImageColorMap::GfxImageColorMap(const GfxImageColorMap *colorMap) { int n, i, k; colorSpace = colorMap->colorSpace->copy(); bits = colorMap->bits; nComps = colorMap->nComps; nComps2 = colorMap->nComps2; useMatte = colorMap->useMatte; matteColor = colorMap->matteColor; colorSpace2 = nullptr; for (k = 0; k < gfxColorMaxComps; ++k) { lookup[k] = nullptr; lookup2[k] = nullptr; } byte_lookup = nullptr; n = 1 << bits; for (k = 0; k < nComps; ++k) { lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp)); } if (colorSpace->getMode() == csIndexed) { colorSpace2 = ((GfxIndexedColorSpace *)colorSpace)->getBase(); for (k = 0; k < nComps2; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp)); } } else if (colorSpace->getMode() == csSeparation) { colorSpace2 = ((GfxSeparationColorSpace *)colorSpace)->getAlt(); for (k = 0; k < nComps2; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp)); } } else { for (k = 0; k < nComps; ++k) { lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp)); } } if (colorMap->byte_lookup) { int nc = colorSpace2 ? nComps2 : nComps; byte_lookup = (unsigned char *)gmallocn(n, nc); memcpy(byte_lookup, colorMap->byte_lookup, n * nc); } for (i = 0; i < nComps; ++i) { decodeLow[i] = colorMap->decodeLow[i]; decodeRange[i] = colorMap->decodeRange[i]; } ok = true; } GfxImageColorMap::~GfxImageColorMap() { int i; delete colorSpace; for (i = 0; i < gfxColorMaxComps; ++i) { gfree(lookup[i]); gfree(lookup2[i]); } gfree(byte_lookup); } void GfxImageColorMap::getGray(const unsigned char *x, GfxGray *gray) { GfxColor color; int i; if (colorSpace2) { for (i = 0; i < nComps2; ++i) { color.c[i] = lookup2[i][x[0]]; } colorSpace2->getGray(&color, gray); } else { for (i = 0; i < nComps; ++i) { color.c[i] = lookup2[i][x[i]]; } colorSpace->getGray(&color, gray); } } void GfxImageColorMap::getRGB(const unsigned char *x, GfxRGB *rgb) { GfxColor color; int i; if (colorSpace2) { for (i = 0; i < nComps2; ++i) { color.c[i] = lookup2[i][x[0]]; } colorSpace2->getRGB(&color, rgb); } else { for (i = 0; i < nComps; ++i) { color.c[i] = lookup2[i][x[i]]; } colorSpace->getRGB(&color, rgb); } } void GfxImageColorMap::getGrayLine(unsigned char *in, unsigned char *out, int length) { int i, j; unsigned char *inp, *tmp_line; if ((colorSpace2 && !colorSpace2->useGetGrayLine()) || (!colorSpace2 && !colorSpace->useGetGrayLine())) { GfxGray gray; inp = in; for (i = 0; i < length; i++) { getGray(inp, &gray); out[i] = colToByte(gray); inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (i = 0; i < length; i++) { for (j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getGrayLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (j = 0; j < length; j++) for (i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getGrayLine(in, out, length); break; } } void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned int *out, int length) { int i, j; unsigned char *inp, *tmp_line; if (!useRGBLine()) { GfxRGB rgb; inp = in; for (i = 0; i < length; i++) { getRGB(inp, &rgb); out[i] = ((int)colToByte(rgb.r) << 16) | ((int)colToByte(rgb.g) << 8) | ((int)colToByte(rgb.b) << 0); inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (i = 0; i < length; i++) { for (j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getRGBLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (j = 0; j < length; j++) for (i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getRGBLine(in, out, length); break; } } void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned char *out, int length) { int i, j; unsigned char *inp, *tmp_line; if (!useRGBLine()) { GfxRGB rgb; inp = in; for (i = 0; i < length; i++) { getRGB(inp, &rgb); *out++ = colToByte(rgb.r); *out++ = colToByte(rgb.g); *out++ = colToByte(rgb.b); inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (i = 0; i < length; i++) { for (j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getRGBLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (j = 0; j < length; j++) for (i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getRGBLine(in, out, length); break; } } void GfxImageColorMap::getRGBXLine(unsigned char *in, unsigned char *out, int length) { int i, j; unsigned char *inp, *tmp_line; if (!useRGBLine()) { GfxRGB rgb; inp = in; for (i = 0; i < length; i++) { getRGB(inp, &rgb); *out++ = colToByte(rgb.r); *out++ = colToByte(rgb.g); *out++ = colToByte(rgb.b); *out++ = 255; inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (i = 0; i < length; i++) { for (j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getRGBXLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (j = 0; j < length; j++) for (i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getRGBXLine(in, out, length); break; } } void GfxImageColorMap::getCMYKLine(unsigned char *in, unsigned char *out, int length) { int i, j; unsigned char *inp, *tmp_line; if (!useCMYKLine()) { GfxCMYK cmyk; inp = in; for (i = 0; i < length; i++) { getCMYK(inp, &cmyk); *out++ = colToByte(cmyk.c); *out++ = colToByte(cmyk.m); *out++ = colToByte(cmyk.y); *out++ = colToByte(cmyk.k); inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (i = 0; i < length; i++) { for (j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getCMYKLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (j = 0; j < length; j++) for (i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getCMYKLine(in, out, length); break; } } void GfxImageColorMap::getDeviceNLine(unsigned char *in, unsigned char *out, int length) { unsigned char *inp, *tmp_line; if (!useDeviceNLine()) { GfxColor deviceN; inp = in; for (int i = 0; i < length; i++) { getDeviceN(inp, &deviceN); for (int j = 0; j < SPOT_NCOMPS + 4; j++) *out++ = deviceN.c[j]; inp += nComps; } return; } switch (colorSpace->getMode()) { case csIndexed: case csSeparation: tmp_line = (unsigned char *)gmallocn(length, nComps2); for (int i = 0; i < length; i++) { for (int j = 0; j < nComps2; j++) { tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j]; } } colorSpace2->getDeviceNLine(tmp_line, out, length); gfree(tmp_line); break; default: inp = in; for (int j = 0; j < length; j++) for (int i = 0; i < nComps; i++) { *inp = byte_lookup[*inp * nComps + i]; inp++; } colorSpace->getDeviceNLine(in, out, length); break; } } void GfxImageColorMap::getCMYK(const unsigned char *x, GfxCMYK *cmyk) { GfxColor color; int i; if (colorSpace2) { for (i = 0; i < nComps2; ++i) { color.c[i] = lookup2[i][x[0]]; } colorSpace2->getCMYK(&color, cmyk); } else { for (i = 0; i < nComps; ++i) { color.c[i] = lookup[i][x[i]]; } colorSpace->getCMYK(&color, cmyk); } } void GfxImageColorMap::getDeviceN(const unsigned char *x, GfxColor *deviceN) { GfxColor color; int i; if (colorSpace2) { for (i = 0; i < nComps2; ++i) { color.c[i] = lookup2[i][x[0]]; } colorSpace2->getDeviceN(&color, deviceN); } else { for (i = 0; i < nComps; ++i) { color.c[i] = lookup[i][x[i]]; } colorSpace->getDeviceN(&color, deviceN); } } void GfxImageColorMap::getColor(const unsigned char *x, GfxColor *color) { int maxPixel, i; maxPixel = (1 << bits) - 1; for (i = 0; i < nComps; ++i) { color->c[i] = dblToCol(decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel); } } //------------------------------------------------------------------------ // GfxSubpath and GfxPath //------------------------------------------------------------------------ GfxSubpath::GfxSubpath(double x1, double y1) { size = 16; x = (double *)gmallocn(size, sizeof(double)); y = (double *)gmallocn(size, sizeof(double)); curve = (bool *)gmallocn(size, sizeof(bool)); n = 1; x[0] = x1; y[0] = y1; curve[0] = false; closed = false; } GfxSubpath::~GfxSubpath() { gfree(x); gfree(y); gfree(curve); } // Used for copy(). GfxSubpath::GfxSubpath(const GfxSubpath *subpath) { size = subpath->size; n = subpath->n; x = (double *)gmallocn(size, sizeof(double)); y = (double *)gmallocn(size, sizeof(double)); curve = (bool *)gmallocn(size, sizeof(bool)); memcpy(x, subpath->x, n * sizeof(double)); memcpy(y, subpath->y, n * sizeof(double)); memcpy(curve, subpath->curve, n * sizeof(bool)); closed = subpath->closed; } void GfxSubpath::lineTo(double x1, double y1) { if (n >= size) { size *= 2; x = (double *)greallocn(x, size, sizeof(double)); y = (double *)greallocn(y, size, sizeof(double)); curve = (bool *)greallocn(curve, size, sizeof(bool)); } x[n] = x1; y[n] = y1; curve[n] = false; ++n; } void GfxSubpath::curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { if (n + 3 > size) { size *= 2; x = (double *)greallocn(x, size, sizeof(double)); y = (double *)greallocn(y, size, sizeof(double)); curve = (bool *)greallocn(curve, size, sizeof(bool)); } x[n] = x1; y[n] = y1; x[n + 1] = x2; y[n + 1] = y2; x[n + 2] = x3; y[n + 2] = y3; curve[n] = curve[n + 1] = true; curve[n + 2] = false; n += 3; } void GfxSubpath::close() { if (x[n - 1] != x[0] || y[n - 1] != y[0]) { lineTo(x[0], y[0]); } closed = true; } void GfxSubpath::offset(double dx, double dy) { int i; for (i = 0; i < n; ++i) { x[i] += dx; y[i] += dy; } } GfxPath::GfxPath() { justMoved = false; size = 16; n = 0; firstX = firstY = 0; subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *)); } GfxPath::~GfxPath() { int i; for (i = 0; i < n; ++i) delete subpaths[i]; gfree(subpaths); } // Used for copy(). GfxPath::GfxPath(bool justMoved1, double firstX1, double firstY1, GfxSubpath **subpaths1, int n1, int size1) { int i; justMoved = justMoved1; firstX = firstX1; firstY = firstY1; size = size1; n = n1; subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *)); for (i = 0; i < n; ++i) subpaths[i] = subpaths1[i]->copy(); } void GfxPath::moveTo(double x, double y) { justMoved = true; firstX = x; firstY = y; } void GfxPath::lineTo(double x, double y) { if (justMoved || (n > 0 && subpaths[n - 1]->isClosed())) { if (n >= size) { size *= 2; subpaths = (GfxSubpath **)greallocn(subpaths, size, sizeof(GfxSubpath *)); } if (justMoved) { subpaths[n] = new GfxSubpath(firstX, firstY); } else { subpaths[n] = new GfxSubpath(subpaths[n - 1]->getLastX(), subpaths[n - 1]->getLastY()); } ++n; justMoved = false; } subpaths[n - 1]->lineTo(x, y); } void GfxPath::curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { if (justMoved || (n > 0 && subpaths[n - 1]->isClosed())) { if (n >= size) { size *= 2; subpaths = (GfxSubpath **)greallocn(subpaths, size, sizeof(GfxSubpath *)); } if (justMoved) { subpaths[n] = new GfxSubpath(firstX, firstY); } else { subpaths[n] = new GfxSubpath(subpaths[n - 1]->getLastX(), subpaths[n - 1]->getLastY()); } ++n; justMoved = false; } subpaths[n - 1]->curveTo(x1, y1, x2, y2, x3, y3); } void GfxPath::close() { // this is necessary to handle the pathological case of // moveto/closepath/clip, which defines an empty clipping region if (justMoved) { if (n >= size) { size *= 2; subpaths = (GfxSubpath **)greallocn(subpaths, size, sizeof(GfxSubpath *)); } subpaths[n] = new GfxSubpath(firstX, firstY); ++n; justMoved = false; } subpaths[n - 1]->close(); } void GfxPath::append(GfxPath *path) { int i; if (n + path->n > size) { size = n + path->n; subpaths = (GfxSubpath **)greallocn(subpaths, size, sizeof(GfxSubpath *)); } for (i = 0; i < path->n; ++i) { subpaths[n++] = path->subpaths[i]->copy(); } justMoved = false; } void GfxPath::offset(double dx, double dy) { int i; for (i = 0; i < n; ++i) { subpaths[i]->offset(dx, dy); } } //------------------------------------------------------------------------ // //------------------------------------------------------------------------ GfxState::ReusablePathIterator::ReusablePathIterator(GfxPath *pathA) : path(pathA), subPathOff(0), coordOff(0), numCoords(0), curSubPath(nullptr) { if (path->getNumSubpaths()) { curSubPath = path->getSubpath(subPathOff); numCoords = curSubPath->getNumPoints(); } } bool GfxState::ReusablePathIterator::isEnd() const { return coordOff >= numCoords; } void GfxState::ReusablePathIterator::next() { ++coordOff; if (coordOff == numCoords) { ++subPathOff; if (subPathOff < path->getNumSubpaths()) { coordOff = 0; curSubPath = path->getSubpath(subPathOff); numCoords = curSubPath->getNumPoints(); } } } void GfxState::ReusablePathIterator::setCoord(double x, double y) { curSubPath->setX(coordOff, x); curSubPath->setY(coordOff, y); } void GfxState::ReusablePathIterator::reset() { coordOff = 0; subPathOff = 0; curSubPath = path->getSubpath(0); numCoords = curSubPath->getNumPoints(); } GfxState::GfxState(double hDPIA, double vDPIA, const PDFRectangle *pageBox, int rotateA, bool upsideDown) { double kx, ky; hDPI = hDPIA; vDPI = vDPIA; rotate = rotateA; px1 = pageBox->x1; py1 = pageBox->y1; px2 = pageBox->x2; py2 = pageBox->y2; kx = hDPI / 72.0; ky = vDPI / 72.0; if (rotate == 90) { ctm[0] = 0; ctm[1] = upsideDown ? ky : -ky; ctm[2] = kx; ctm[3] = 0; ctm[4] = -kx * py1; ctm[5] = ky * (upsideDown ? -px1 : px2); pageWidth = kx * (py2 - py1); pageHeight = ky * (px2 - px1); } else if (rotate == 180) { ctm[0] = -kx; ctm[1] = 0; ctm[2] = 0; ctm[3] = upsideDown ? ky : -ky; ctm[4] = kx * px2; ctm[5] = ky * (upsideDown ? -py1 : py2); pageWidth = kx * (px2 - px1); pageHeight = ky * (py2 - py1); } else if (rotate == 270) { ctm[0] = 0; ctm[1] = upsideDown ? -ky : ky; ctm[2] = -kx; ctm[3] = 0; ctm[4] = kx * py2; ctm[5] = ky * (upsideDown ? px2 : -px1); pageWidth = kx * (py2 - py1); pageHeight = ky * (px2 - px1); } else { ctm[0] = kx; ctm[1] = 0; ctm[2] = 0; ctm[3] = upsideDown ? -ky : ky; ctm[4] = -kx * px1; ctm[5] = ky * (upsideDown ? py2 : -py1); pageWidth = kx * (px2 - px1); pageHeight = ky * (py2 - py1); } fillColorSpace = new GfxDeviceGrayColorSpace(); strokeColorSpace = new GfxDeviceGrayColorSpace(); fillColor.c[0] = 0; strokeColor.c[0] = 0; fillPattern = nullptr; strokePattern = nullptr; blendMode = gfxBlendNormal; fillOpacity = 1; strokeOpacity = 1; fillOverprint = false; strokeOverprint = false; overprintMode = 0; transfer[0] = transfer[1] = transfer[2] = transfer[3] = nullptr; lineWidth = 1; lineDash = nullptr; lineDashLength = 0; lineDashStart = 0; flatness = 1; lineJoin = 0; lineCap = 0; miterLimit = 10; strokeAdjust = false; alphaIsShape = false; textKnockout = false; font = nullptr; fontSize = 0; textMat[0] = 1; textMat[1] = 0; textMat[2] = 0; textMat[3] = 1; textMat[4] = 0; textMat[5] = 0; charSpace = 0; wordSpace = 0; horizScaling = 1; leading = 0; rise = 0; render = 0; path = new GfxPath(); curX = curY = 0; lineX = lineY = 0; clipXMin = 0; clipYMin = 0; clipXMax = pageWidth; clipYMax = pageHeight; renderingIntent[0] = 0; saved = nullptr; #ifdef USE_CMS XYZ2DisplayTransformRelCol = nullptr; XYZ2DisplayTransformAbsCol = nullptr; XYZ2DisplayTransformSat = nullptr; XYZ2DisplayTransformPerc = nullptr; localDisplayProfile = nullptr; if (!sRGBProfile) { // This is probably the one of the first invocations of lcms2, so we set the error handler cmsSetLogErrorHandler(CMSError); sRGBProfile = make_GfxLCMSProfilePtr(cmsCreate_sRGBProfile()); } if (!XYZProfile) { XYZProfile = make_GfxLCMSProfilePtr(cmsCreateXYZProfile()); } #endif } GfxState::~GfxState() { int i; if (fillColorSpace) { delete fillColorSpace; } if (strokeColorSpace) { delete strokeColorSpace; } if (fillPattern) { delete fillPattern; } if (strokePattern) { delete strokePattern; } for (i = 0; i < 4; ++i) { if (transfer[i]) { delete transfer[i]; } } gfree(lineDash); if (path) { // this gets set to NULL by restore() delete path; } if (font) { font->decRefCnt(); } } // Used for copy(); GfxState::GfxState(const GfxState *state, bool copyPath) { int i; hDPI = state->hDPI; vDPI = state->vDPI; memcpy(ctm, state->ctm, sizeof(ctm)); px1 = state->px1; py1 = state->py1; px2 = state->px2; py2 = state->py2; pageWidth = state->pageWidth; pageHeight = state->pageHeight; rotate = state->rotate; fillColorSpace = state->fillColorSpace; if (fillColorSpace) { fillColorSpace = state->fillColorSpace->copy(); } strokeColorSpace = state->strokeColorSpace; if (strokeColorSpace) { strokeColorSpace = state->strokeColorSpace->copy(); } fillColor = state->fillColor; strokeColor = state->strokeColor; fillPattern = state->fillPattern; if (fillPattern) { fillPattern = state->fillPattern->copy(); } strokePattern = state->strokePattern; if (strokePattern) { strokePattern = state->strokePattern->copy(); } blendMode = state->blendMode; fillOpacity = state->fillOpacity; strokeOpacity = state->strokeOpacity; fillOverprint = state->fillOverprint; strokeOverprint = state->strokeOverprint; overprintMode = state->overprintMode; for (i = 0; i < 4; ++i) { transfer[i] = state->transfer[i]; if (transfer[i]) { transfer[i] = state->transfer[i]->copy(); } } lineWidth = state->lineWidth; lineDashLength = state->lineDashLength; lineDash = nullptr; if (lineDashLength > 0) { lineDash = (double *)gmallocn(lineDashLength, sizeof(double)); memcpy(lineDash, state->lineDash, lineDashLength * sizeof(double)); } lineDashStart = state->lineDashStart; flatness = state->flatness; lineJoin = state->lineJoin; lineCap = state->lineCap; miterLimit = state->miterLimit; strokeAdjust = state->strokeAdjust; alphaIsShape = state->alphaIsShape; textKnockout = state->textKnockout; font = state->font; if (font) font->incRefCnt(); fontSize = state->fontSize; memcpy(textMat, state->textMat, sizeof(textMat)); charSpace = state->charSpace; wordSpace = state->wordSpace; horizScaling = state->horizScaling; leading = state->leading; rise = state->rise; render = state->render; path = state->path; if (copyPath) { path = state->path->copy(); } curX = state->curX; curY = state->curY; lineX = state->lineX; lineY = state->lineY; clipXMin = state->clipXMin; clipYMin = state->clipYMin; clipXMax = state->clipXMax; clipYMax = state->clipYMax; memcpy(renderingIntent, state->renderingIntent, sizeof(renderingIntent)); saved = nullptr; #ifdef USE_CMS localDisplayProfile = state->localDisplayProfile; XYZ2DisplayTransformRelCol = state->XYZ2DisplayTransformRelCol; XYZ2DisplayTransformAbsCol = state->XYZ2DisplayTransformAbsCol; XYZ2DisplayTransformSat = state->XYZ2DisplayTransformSat; XYZ2DisplayTransformPerc = state->XYZ2DisplayTransformPerc; #endif } #ifdef USE_CMS GfxLCMSProfilePtr GfxState::sRGBProfile = nullptr; GfxLCMSProfilePtr GfxState::XYZProfile = nullptr; void GfxState::setDisplayProfile(const GfxLCMSProfilePtr &localDisplayProfileA) { localDisplayProfile = localDisplayProfileA; if (localDisplayProfile) { cmsHTRANSFORM transform; unsigned int nChannels; unsigned int localDisplayPixelType; localDisplayPixelType = getCMSColorSpaceType(cmsGetColorSpace(localDisplayProfile.get())); nChannels = getCMSNChannels(cmsGetColorSpace(localDisplayProfile.get())); // create transform from XYZ 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) { error(errSyntaxWarning, -1, "Can't create Lab transform"); } else { XYZ2DisplayTransformRelCol = std::make_shared(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, localDisplayPixelType); } 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) { error(errSyntaxWarning, -1, "Can't create Lab transform"); } else { XYZ2DisplayTransformAbsCol = std::make_shared(transform, INTENT_ABSOLUTE_COLORIMETRIC, PT_XYZ, localDisplayPixelType); } if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_SATURATION, LCMS_FLAGS)) == nullptr) { error(errSyntaxWarning, -1, "Can't create Lab transform"); } else { XYZ2DisplayTransformSat = std::make_shared(transform, INTENT_SATURATION, PT_XYZ, localDisplayPixelType); } if ((transform = cmsCreateTransform(XYZProfile.get(), TYPE_XYZ_DBL, localDisplayProfile.get(), COLORSPACE_SH(localDisplayPixelType) | CHANNELS_SH(nChannels) | BYTES_SH(1), INTENT_PERCEPTUAL, LCMS_FLAGS)) == nullptr) { error(errSyntaxWarning, -1, "Can't create Lab transform"); } else { XYZ2DisplayTransformPerc = std::make_shared(transform, INTENT_PERCEPTUAL, PT_XYZ, localDisplayPixelType); } } } std::shared_ptr GfxState::getXYZ2DisplayTransform() { auto transform = XYZ2DisplayTransformRelCol; if (strcmp(renderingIntent, "AbsoluteColorimetric") == 0) { transform = XYZ2DisplayTransformAbsCol; } else if (strcmp(renderingIntent, "Saturation") == 0) { transform = XYZ2DisplayTransformSat; } else if (strcmp(renderingIntent, "Perceptual") == 0) { transform = XYZ2DisplayTransformPerc; } return transform; } int GfxState::getCmsRenderingIntent() { const char *intent = getRenderingIntent(); int cmsIntent = INTENT_RELATIVE_COLORIMETRIC; if (intent) { if (strcmp(intent, "AbsoluteColorimetric") == 0) { cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC; } else if (strcmp(intent, "Saturation") == 0) { cmsIntent = INTENT_SATURATION; } else if (strcmp(intent, "Perceptual") == 0) { cmsIntent = INTENT_PERCEPTUAL; } } return cmsIntent; } #endif void GfxState::setPath(GfxPath *pathA) { delete path; path = pathA; } void GfxState::getUserClipBBox(double *xMin, double *yMin, double *xMax, double *yMax) const { double ictm[6]; double xMin1, yMin1, xMax1, yMax1, tx, ty; // invert the CTM const double det_denominator = (ctm[0] * ctm[3] - ctm[1] * ctm[2]); if (unlikely(det_denominator == 0)) { *xMin = 0; *yMin = 0; *xMax = 0; *yMax = 0; return; } const double det = 1 / det_denominator; ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; ictm[3] = ctm[0] * det; ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; // transform all four corners of the clip bbox; find the min and max // x and y values xMin1 = xMax1 = clipXMin * ictm[0] + clipYMin * ictm[2] + ictm[4]; yMin1 = yMax1 = clipXMin * ictm[1] + clipYMin * ictm[3] + ictm[5]; tx = clipXMin * ictm[0] + clipYMax * ictm[2] + ictm[4]; ty = clipXMin * ictm[1] + clipYMax * ictm[3] + ictm[5]; if (tx < xMin1) { xMin1 = tx; } else if (tx > xMax1) { xMax1 = tx; } if (ty < yMin1) { yMin1 = ty; } else if (ty > yMax1) { yMax1 = ty; } tx = clipXMax * ictm[0] + clipYMin * ictm[2] + ictm[4]; ty = clipXMax * ictm[1] + clipYMin * ictm[3] + ictm[5]; if (tx < xMin1) { xMin1 = tx; } else if (tx > xMax1) { xMax1 = tx; } if (ty < yMin1) { yMin1 = ty; } else if (ty > yMax1) { yMax1 = ty; } tx = clipXMax * ictm[0] + clipYMax * ictm[2] + ictm[4]; ty = clipXMax * ictm[1] + clipYMax * ictm[3] + ictm[5]; if (tx < xMin1) { xMin1 = tx; } else if (tx > xMax1) { xMax1 = tx; } if (ty < yMin1) { yMin1 = ty; } else if (ty > yMax1) { yMax1 = ty; } *xMin = xMin1; *yMin = yMin1; *xMax = xMax1; *yMax = yMax1; } double GfxState::transformWidth(double w) const { double x, y; x = ctm[0] + ctm[2]; y = ctm[1] + ctm[3]; return w * sqrt(0.5 * (x * x + y * y)); } double GfxState::getTransformedFontSize() const { double x1, y1, x2, y2; x1 = textMat[2] * fontSize; y1 = textMat[3] * fontSize; x2 = ctm[0] * x1 + ctm[2] * y1; y2 = ctm[1] * x1 + ctm[3] * y1; return sqrt(x2 * x2 + y2 * y2); } void GfxState::getFontTransMat(double *m11, double *m12, double *m21, double *m22) const { *m11 = (textMat[0] * ctm[0] + textMat[1] * ctm[2]) * fontSize; *m12 = (textMat[0] * ctm[1] + textMat[1] * ctm[3]) * fontSize; *m21 = (textMat[2] * ctm[0] + textMat[3] * ctm[2]) * fontSize; *m22 = (textMat[2] * ctm[1] + textMat[3] * ctm[3]) * fontSize; } void GfxState::setCTM(double a, double b, double c, double d, double e, double f) { ctm[0] = a; ctm[1] = b; ctm[2] = c; ctm[3] = d; ctm[4] = e; ctm[5] = f; } void GfxState::concatCTM(double a, double b, double c, double d, double e, double f) { double a1 = ctm[0]; double b1 = ctm[1]; double c1 = ctm[2]; double d1 = ctm[3]; ctm[0] = a * a1 + b * c1; ctm[1] = a * b1 + b * d1; ctm[2] = c * a1 + d * c1; ctm[3] = c * b1 + d * d1; ctm[4] = e * a1 + f * c1 + ctm[4]; ctm[5] = e * b1 + f * d1 + ctm[5]; } void GfxState::shiftCTMAndClip(double tx, double ty) { ctm[4] += tx; ctm[5] += ty; clipXMin += tx; clipYMin += ty; clipXMax += tx; clipYMax += ty; } void GfxState::setFillColorSpace(GfxColorSpace *colorSpace) { if (fillColorSpace) { delete fillColorSpace; } fillColorSpace = colorSpace; } void GfxState::setStrokeColorSpace(GfxColorSpace *colorSpace) { if (strokeColorSpace) { delete strokeColorSpace; } strokeColorSpace = colorSpace; } void GfxState::setFillPattern(GfxPattern *pattern) { if (fillPattern) { delete fillPattern; } fillPattern = pattern; } void GfxState::setStrokePattern(GfxPattern *pattern) { if (strokePattern) { delete strokePattern; } strokePattern = pattern; } void GfxState::setFont(GfxFont *fontA, double fontSizeA) { if (font) font->decRefCnt(); font = fontA; fontSize = fontSizeA; } void GfxState::setTransfer(Function **funcs) { int i; for (i = 0; i < 4; ++i) { if (transfer[i]) { delete transfer[i]; } transfer[i] = funcs[i]; } } void GfxState::setLineDash(double *dash, int length, double start) { if (lineDash) gfree(lineDash); lineDash = dash; lineDashLength = length; lineDashStart = start; } void GfxState::clearPath() { delete path; path = new GfxPath(); } void GfxState::clip() { double xMin, yMin, xMax, yMax, x, y; GfxSubpath *subpath; int i, j; xMin = xMax = yMin = yMax = 0; // make gcc happy for (i = 0; i < path->getNumSubpaths(); ++i) { subpath = path->getSubpath(i); for (j = 0; j < subpath->getNumPoints(); ++j) { transform(subpath->getX(j), subpath->getY(j), &x, &y); if (i == 0 && j == 0) { xMin = xMax = x; yMin = yMax = y; } else { if (x < xMin) { xMin = x; } else if (x > xMax) { xMax = x; } if (y < yMin) { yMin = y; } else if (y > yMax) { yMax = y; } } } } if (xMin > clipXMin) { clipXMin = xMin; } if (yMin > clipYMin) { clipYMin = yMin; } if (xMax < clipXMax) { clipXMax = xMax; } if (yMax < clipYMax) { clipYMax = yMax; } } void GfxState::clipToStrokePath() { double xMin, yMin, xMax, yMax, x, y, t0, t1; GfxSubpath *subpath; int i, j; xMin = xMax = yMin = yMax = 0; // make gcc happy for (i = 0; i < path->getNumSubpaths(); ++i) { subpath = path->getSubpath(i); for (j = 0; j < subpath->getNumPoints(); ++j) { transform(subpath->getX(j), subpath->getY(j), &x, &y); if (i == 0 && j == 0) { xMin = xMax = x; yMin = yMax = y; } else { if (x < xMin) { xMin = x; } else if (x > xMax) { xMax = x; } if (y < yMin) { yMin = y; } else if (y > yMax) { yMax = y; } } } } // allow for the line width //~ miter joins can extend farther than this t0 = fabs(ctm[0]); t1 = fabs(ctm[2]); if (t0 > t1) { xMin -= 0.5 * lineWidth * t0; xMax += 0.5 * lineWidth * t0; } else { xMin -= 0.5 * lineWidth * t1; xMax += 0.5 * lineWidth * t1; } t0 = fabs(ctm[0]); t1 = fabs(ctm[3]); if (t0 > t1) { yMin -= 0.5 * lineWidth * t0; yMax += 0.5 * lineWidth * t0; } else { yMin -= 0.5 * lineWidth * t1; yMax += 0.5 * lineWidth * t1; } if (xMin > clipXMin) { clipXMin = xMin; } if (yMin > clipYMin) { clipYMin = yMin; } if (xMax < clipXMax) { clipXMax = xMax; } if (yMax < clipYMax) { clipYMax = yMax; } } void GfxState::clipToRect(double xMin, double yMin, double xMax, double yMax) { double x, y, xMin1, yMin1, xMax1, yMax1; transform(xMin, yMin, &x, &y); xMin1 = xMax1 = x; yMin1 = yMax1 = y; transform(xMax, yMin, &x, &y); if (x < xMin1) { xMin1 = x; } else if (x > xMax1) { xMax1 = x; } if (y < yMin1) { yMin1 = y; } else if (y > yMax1) { yMax1 = y; } transform(xMax, yMax, &x, &y); if (x < xMin1) { xMin1 = x; } else if (x > xMax1) { xMax1 = x; } if (y < yMin1) { yMin1 = y; } else if (y > yMax1) { yMax1 = y; } transform(xMin, yMax, &x, &y); if (x < xMin1) { xMin1 = x; } else if (x > xMax1) { xMax1 = x; } if (y < yMin1) { yMin1 = y; } else if (y > yMax1) { yMax1 = y; } if (xMin1 > clipXMin) { clipXMin = xMin1; } if (yMin1 > clipYMin) { clipYMin = yMin1; } if (xMax1 < clipXMax) { clipXMax = xMax1; } if (yMax1 < clipYMax) { clipYMax = yMax1; } } void GfxState::textShift(double tx, double ty) { double dx, dy; textTransformDelta(tx, ty, &dx, &dy); curX += dx; curY += dy; } void GfxState::shift(double dx, double dy) { curX += dx; curY += dy; } GfxState *GfxState::save() { GfxState *newState; newState = copy(); newState->saved = this; return newState; } GfxState *GfxState::restore() { GfxState *oldState; if (saved) { oldState = saved; // these attributes aren't saved/restored by the q/Q operators oldState->path = path; oldState->curX = curX; oldState->curY = curY; oldState->lineX = lineX; oldState->lineY = lineY; path = nullptr; saved = nullptr; delete this; } else { oldState = this; } return oldState; } bool GfxState::parseBlendMode(Object *obj, GfxBlendMode *mode) { int i, j; if (obj->isName()) { for (i = 0; i < nGfxBlendModeNames; ++i) { if (!strcmp(obj->getName(), gfxBlendModeNames[i].name)) { *mode = gfxBlendModeNames[i].mode; return true; } } return false; } else if (obj->isArray()) { for (i = 0; i < obj->arrayGetLength(); ++i) { Object obj2 = obj->arrayGet(i); if (!obj2.isName()) { return false; } for (j = 0; j < nGfxBlendModeNames; ++j) { if (!strcmp(obj2.getName(), gfxBlendModeNames[j].name)) { *mode = gfxBlendModeNames[j].mode; return true; } } } *mode = gfxBlendNormal; return true; } else { return false; } }