mirror of https://github.com/JakubMelka/PDF4QT.git
Advanced color spaces
This commit is contained in:
parent
648879eadf
commit
8e766376bc
|
@ -16,6 +16,9 @@
|
|||
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "pdfcolorspaces.h"
|
||||
#include "pdfobject.h"
|
||||
#include "pdfdocument.h"
|
||||
#include "pdfparser.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
@ -40,13 +43,7 @@ QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color) const
|
|||
{
|
||||
Q_ASSERT(color.size() == getColorComponentCount());
|
||||
|
||||
PDFColorComponent r = clip01(color[0]);
|
||||
PDFColorComponent g = clip01(color[1]);
|
||||
PDFColorComponent b = clip01(color[2]);
|
||||
|
||||
QColor result(QColor::Rgb);
|
||||
result.setRgbF(r, g, b, 1.0);
|
||||
return result;
|
||||
return fromRGB01({ color[0], color[1], color[2] });
|
||||
}
|
||||
|
||||
size_t PDFDeviceRGBColorSpace::getColorComponentCount() const
|
||||
|
@ -73,4 +70,411 @@ size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const
|
|||
return 4;
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFObject& colorSpace)
|
||||
{
|
||||
return createColorSpaceImpl(colorSpaceDictionary, document, colorSpace, COLOR_SPACE_MAX_LEVEL_OF_RECURSION);
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFObject& colorSpace,
|
||||
int recursion)
|
||||
{
|
||||
if (--recursion <= 0)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex."));
|
||||
}
|
||||
|
||||
if (colorSpace.isName())
|
||||
{
|
||||
QByteArray name = colorSpace.getString();
|
||||
|
||||
if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY)
|
||||
{
|
||||
if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY))
|
||||
{
|
||||
return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PDFColorSpacePointer(new PDFDeviceGrayColorSpace());
|
||||
}
|
||||
}
|
||||
else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB)
|
||||
{
|
||||
if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB))
|
||||
{
|
||||
return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PDFColorSpacePointer(new PDFDeviceRGBColorSpace());
|
||||
}
|
||||
}
|
||||
else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK)
|
||||
{
|
||||
if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK))
|
||||
{
|
||||
return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (colorSpace.isArray())
|
||||
{
|
||||
// First value of the array should be identification name, second value dictionary with parameters
|
||||
const PDFArray* array = colorSpace.getArray();
|
||||
size_t count = array->getCount();
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
// Name of the color space
|
||||
const PDFObject& colorSpaceIdentifier = document->getObject(array->getItem(0));
|
||||
if (colorSpaceIdentifier.isName())
|
||||
{
|
||||
QByteArray name = colorSpaceIdentifier.getString();
|
||||
|
||||
const PDFDictionary* dictionary = nullptr;
|
||||
const PDFStream* stream = nullptr;
|
||||
if (count > 1)
|
||||
{
|
||||
const PDFObject& colorSpaceSettings = document->getObject(array->getItem(1));
|
||||
if (colorSpaceSettings.isDictionary())
|
||||
{
|
||||
dictionary = colorSpaceSettings.getDictionary();
|
||||
}
|
||||
if (colorSpaceSettings.isStream())
|
||||
{
|
||||
stream = colorSpaceSettings.getStream();
|
||||
}
|
||||
}
|
||||
|
||||
if (dictionary)
|
||||
{
|
||||
if (name == COLOR_SPACE_NAME_CAL_GRAY)
|
||||
{
|
||||
return PDFCalGrayColorSpace::createCalGrayColorSpace(document, dictionary);
|
||||
}
|
||||
else if (name == COLOR_SPACE_NAME_CAL_RGB)
|
||||
{
|
||||
return PDFCalRGBColorSpace::createCalRGBColorSpace(document, dictionary);
|
||||
}
|
||||
else if (name == COLOR_SPACE_NAME_LAB)
|
||||
{
|
||||
return PDFLabColorSpace::createLabColorSpace(document, dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream && name == COLOR_SPACE_NAME_ICCBASED)
|
||||
{
|
||||
return PDFICCBasedColorSpace::createICCBasedColorSpace(colorSpaceDictionary, document, stream, recursion);
|
||||
}
|
||||
|
||||
// Try to just load by standard way - we can have "standard" color space stored in array
|
||||
return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw PDFParserException(PDFTranslationContext::tr("Invalid color space."));
|
||||
return PDFColorSpacePointer();
|
||||
}
|
||||
|
||||
/// Conversion matrix from XYZ space to RGB space. Values are taken from this article:
|
||||
/// https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_.28.22gamma.22.29
|
||||
static constexpr const PDFColorComponentMatrix<3, 3> matrixXYZtoRGB(
|
||||
3.2406f, -1.5372f, -0.4986f,
|
||||
-0.9689f, 1.8758f, 0.0415f,
|
||||
0.0557f, -0.2040f, 1.0570f
|
||||
);
|
||||
|
||||
PDFColor3 PDFAbstractColorSpace::convertXYZtoRGB(const PDFColor3& xyzColor)
|
||||
{
|
||||
return matrixXYZtoRGB * xyzColor;
|
||||
}
|
||||
|
||||
PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) :
|
||||
m_whitePoint(whitePoint),
|
||||
m_blackPoint(blackPoint),
|
||||
m_gamma(gamma),
|
||||
m_correctionCoefficients()
|
||||
{
|
||||
PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint);
|
||||
|
||||
m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0];
|
||||
m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1];
|
||||
m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2];
|
||||
}
|
||||
|
||||
QColor PDFCalGrayColorSpace::getColor(const PDFColor& color) const
|
||||
{
|
||||
Q_ASSERT(color.size() == getColorComponentCount());
|
||||
|
||||
const PDFColorComponent A = clip01(color[0]);
|
||||
const PDFColorComponent xyzColor = std::powf(A, m_gamma);
|
||||
const PDFColor3 xyzColorMultipliedByWhitePoint = colorMultiplyByFactor(m_whitePoint, xyzColor);
|
||||
const PDFColor3 rgb = convertXYZtoRGB(xyzColorMultipliedByWhitePoint);
|
||||
const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients);
|
||||
return fromRGB01(calibratedRGB);
|
||||
}
|
||||
|
||||
size_t PDFCalGrayColorSpace::getColorComponentCount() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFCalGrayColorSpace::createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary)
|
||||
{
|
||||
// Standard D65 white point
|
||||
PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f };
|
||||
PDFColor3 blackPoint = { 0, 0, 0 };
|
||||
PDFColorComponent gamma = 1.0f;
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(document);
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end());
|
||||
gamma = loader.readNumberFromDictionary(dictionary, CAL_GAMMA, gamma);
|
||||
|
||||
return PDFColorSpacePointer(new PDFCalGrayColorSpace(whitePoint, blackPoint, gamma));
|
||||
}
|
||||
|
||||
PDFCalRGBColorSpace::PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix) :
|
||||
m_whitePoint(whitePoint),
|
||||
m_blackPoint(blackPoint),
|
||||
m_gamma(gamma),
|
||||
m_matrix(matrix),
|
||||
m_correctionCoefficients()
|
||||
{
|
||||
PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint);
|
||||
|
||||
m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0];
|
||||
m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1];
|
||||
m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2];
|
||||
}
|
||||
|
||||
QColor PDFCalRGBColorSpace::getColor(const PDFColor& color) const
|
||||
{
|
||||
Q_ASSERT(color.size() == getColorComponentCount());
|
||||
|
||||
const PDFColor3 ABC = clip01(PDFColor3{ color[0], color[1], color[2] });
|
||||
const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma);
|
||||
const PDFColor3 XYZ = m_matrix * ABCwithGamma;
|
||||
const PDFColor3 rgb = convertXYZtoRGB(XYZ);
|
||||
const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients);
|
||||
return fromRGB01(calibratedRGB);
|
||||
}
|
||||
|
||||
size_t PDFCalRGBColorSpace::getColorComponentCount() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFCalRGBColorSpace::createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary)
|
||||
{
|
||||
// Standard D65 white point
|
||||
PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f };
|
||||
PDFColor3 blackPoint = { 0, 0, 0 };
|
||||
PDFColor3 gamma = { 1.0f, 1.0f, 1.0f };
|
||||
PDFColorComponentMatrix_3x3 matrix( 1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1 );
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(document);
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_GAMMA, gamma.begin(), gamma.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_MATRIX, matrix.begin(), matrix.end());
|
||||
|
||||
return PDFColorSpacePointer(new PDFCalRGBColorSpace(whitePoint, blackPoint, gamma, matrix));
|
||||
}
|
||||
|
||||
PDFLabColorSpace::PDFLabColorSpace(PDFColor3 whitePoint,
|
||||
PDFColor3 blackPoint,
|
||||
PDFColorComponent aMin,
|
||||
PDFColorComponent aMax,
|
||||
PDFColorComponent bMin,
|
||||
PDFColorComponent bMax) :
|
||||
m_whitePoint(whitePoint),
|
||||
m_blackPoint(blackPoint),
|
||||
m_aMin(aMin),
|
||||
m_aMax(aMax),
|
||||
m_bMin(bMin),
|
||||
m_bMax(bMax),
|
||||
m_correctionCoefficients()
|
||||
{
|
||||
PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint);
|
||||
|
||||
m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0];
|
||||
m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1];
|
||||
m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2];
|
||||
}
|
||||
|
||||
QColor PDFLabColorSpace::getColor(const PDFColor& color) const
|
||||
{
|
||||
Q_ASSERT(color.size() == getColorComponentCount());
|
||||
|
||||
const PDFColorComponent LStar = qBound(0.0f, color[0], 100.0f);
|
||||
const PDFColorComponent aStar = qBound(m_aMin, color[1], m_aMax);
|
||||
const PDFColorComponent bStar = qBound(m_bMin, color[2], m_bMax);
|
||||
|
||||
const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f;
|
||||
const PDFColorComponent param2 = aStar / 500.0f;
|
||||
const PDFColorComponent param3 = bStar / 200.0f;
|
||||
|
||||
const PDFColorComponent L = param1 + param2;
|
||||
const PDFColorComponent M = param1;
|
||||
const PDFColorComponent N = param1 - param3;
|
||||
|
||||
auto g = [](PDFColorComponent x) -> PDFColorComponent
|
||||
{
|
||||
if (x >= 6.0f / 29.0f)
|
||||
{
|
||||
return x * x * x;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (108.0f / 841.0f) * (x - 4.0f / 29.0f);
|
||||
}
|
||||
};
|
||||
|
||||
const PDFColorComponent gL = g(L);
|
||||
const PDFColorComponent gM = g(M);
|
||||
const PDFColorComponent gN = g(N);
|
||||
const PDFColor3 gLMN = { gL, gM, gN };
|
||||
|
||||
const PDFColor3 XYZ = colorMultiplyByFactors(m_whitePoint, gLMN);
|
||||
const PDFColor3 rgb = convertXYZtoRGB(XYZ);
|
||||
const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients);
|
||||
return fromRGB01(calibratedRGB);
|
||||
}
|
||||
|
||||
size_t PDFLabColorSpace::getColorComponentCount() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFLabColorSpace::createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary)
|
||||
{
|
||||
// Standard D65 white point
|
||||
PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f };
|
||||
PDFColor3 blackPoint = { 0, 0, 0 };
|
||||
|
||||
static_assert(std::numeric_limits<PDFColorComponent>::has_infinity, "Fix this code!");
|
||||
const PDFColorComponent infPos = std::numeric_limits<PDFColorComponent>::infinity();
|
||||
const PDFColorComponent infNeg = -std::numeric_limits<PDFColorComponent>::infinity();
|
||||
std::array<PDFColorComponent, 4> minMax = { infNeg, infPos, infNeg, infPos };
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(document);
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end());
|
||||
loader.readNumberArrayFromDictionary(dictionary, CAL_RANGE, minMax.begin(), minMax.end());
|
||||
|
||||
return PDFColorSpacePointer(new PDFLabColorSpace(whitePoint, blackPoint, minMax[0], minMax[1], minMax[2], minMax[3]));
|
||||
}
|
||||
|
||||
PDFICCBasedColorSpace::PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range) :
|
||||
m_alternateColorSpace(qMove(alternateColorSpace)),
|
||||
m_range(range)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QColor PDFICCBasedColorSpace::getColor(const PDFColor& color) const
|
||||
{
|
||||
Q_ASSERT(color.size() == getColorComponentCount());
|
||||
|
||||
size_t colorComponentCount = getColorComponentCount();
|
||||
|
||||
// Clip color values by range
|
||||
PDFColor clippedColor = color;
|
||||
for (size_t i = 0; i < colorComponentCount; ++i)
|
||||
{
|
||||
const size_t imin = 2 * i + 0;
|
||||
const size_t imax = 2 * i + 1;
|
||||
clippedColor[i] = qBound(m_range[imin], clippedColor[i], m_range[imax]);
|
||||
}
|
||||
|
||||
return m_alternateColorSpace->getColor(clippedColor);
|
||||
}
|
||||
|
||||
size_t PDFICCBasedColorSpace::getColorComponentCount() const
|
||||
{
|
||||
return m_alternateColorSpace->getColorComponentCount();
|
||||
}
|
||||
|
||||
PDFColorSpacePointer PDFICCBasedColorSpace::createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFStream* stream,
|
||||
int recursion)
|
||||
{
|
||||
// First, try to load alternate color space, if it is present
|
||||
const PDFDictionary* dictionary = stream->getDictionary();
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(document);
|
||||
PDFColorSpacePointer alternateColorSpace;
|
||||
if (dictionary->hasKey(ICCBASED_ALTERNATE))
|
||||
{
|
||||
alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(dictionary->get(ICCBASED_ALTERNATE)), recursion);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine color space from parameter N, which determines number of components
|
||||
const PDFInteger N = loader.readIntegerFromDictionary(dictionary, ICCBASED_N, 0);
|
||||
|
||||
switch (N)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared<PDFString>(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_GRAY)))), recursion);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared<PDFString>(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_RGB)))), recursion);
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(std::make_shared<PDFString>(std::move(QByteArray(COLOR_SPACE_NAME_DEVICE_CMYK)))), recursion);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile. Number of components is %1.").arg(N));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alternateColorSpace)
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile."));
|
||||
}
|
||||
|
||||
Ranges ranges = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f };
|
||||
static_assert(ranges.size() == 8, "Fix initialization above!");
|
||||
|
||||
const size_t components = alternateColorSpace->getColorComponentCount();
|
||||
const size_t rangeSize = 2 * components;
|
||||
|
||||
if (rangeSize > ranges.size())
|
||||
{
|
||||
throw PDFParserException(PDFTranslationContext::tr("Too much color components for ICC based profile."));
|
||||
}
|
||||
|
||||
auto itStart = ranges.begin();
|
||||
auto itEnd = std::next(itStart, rangeSize);
|
||||
loader.readNumberArrayFromDictionary(dictionary, ICCBASED_RANGE, itStart, itEnd);
|
||||
|
||||
return PDFColorSpacePointer(new PDFICCBasedColorSpace(qMove(alternateColorSpace), ranges));
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
|
|
@ -21,14 +21,87 @@
|
|||
#include "pdfflatarray.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFObject;
|
||||
class PDFStream;
|
||||
class PDFDocument;
|
||||
class PDFDictionary;
|
||||
class PDFAbstractColorSpace;
|
||||
|
||||
using PDFColorComponent = float;
|
||||
using PDFColor = PDFFlatArray<PDFColorComponent, 4>;
|
||||
using PDFColorSpacePointer = QSharedPointer<PDFAbstractColorSpace>;
|
||||
|
||||
/// Represents PDF's color space
|
||||
static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12;
|
||||
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEVICE_GRAY = "DeviceGray";
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEVICE_RGB = "DeviceRGB";
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEVICE_CMYK = "DeviceCMYK";
|
||||
|
||||
static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY = "G";
|
||||
static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB = "RGB";
|
||||
static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK = "CMYK";
|
||||
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEFAULT_GRAY = "DefaultGray";
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEFAULT_RGB = "DefaultRGB";
|
||||
static constexpr const char* COLOR_SPACE_NAME_DEFAULT_CMYK = "DefaultCMYK";
|
||||
|
||||
static constexpr const char* COLOR_SPACE_NAME_CAL_GRAY = "CalGray";
|
||||
static constexpr const char* COLOR_SPACE_NAME_CAL_RGB = "CalRGB";
|
||||
static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab";
|
||||
static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased";
|
||||
|
||||
static constexpr const char* CAL_WHITE_POINT = "WhitePoint";
|
||||
static constexpr const char* CAL_BLACK_POINT = "BlackPoint";
|
||||
static constexpr const char* CAL_GAMMA = "Gamma";
|
||||
static constexpr const char* CAL_MATRIX = "Matrix";
|
||||
static constexpr const char* CAL_RANGE = "Range";
|
||||
|
||||
static constexpr const char* ICCBASED_ALTERNATE = "Alternate";
|
||||
static constexpr const char* ICCBASED_N = "N";
|
||||
static constexpr const char* ICCBASED_RANGE = "Range";
|
||||
|
||||
using PDFColor3 = std::array<PDFColorComponent, 3>;
|
||||
|
||||
/// Matrix for color component multiplication (for example, conversion between some color spaces)
|
||||
template<size_t Rows, size_t Cols>
|
||||
class PDFColorComponentMatrix
|
||||
{
|
||||
public:
|
||||
explicit constexpr inline PDFColorComponentMatrix() : m_values() { }
|
||||
|
||||
template<typename... Components>
|
||||
explicit constexpr inline PDFColorComponentMatrix(Components... components) : m_values({ static_cast<PDFColorComponent>(components)... }) { }
|
||||
|
||||
std::array<PDFColorComponent, Rows> operator*(const std::array<PDFColorComponent, Cols>& color) const
|
||||
{
|
||||
std::array<PDFColorComponent, Rows> result = { };
|
||||
|
||||
for (size_t row = 0; row < Rows; ++row)
|
||||
{
|
||||
for (size_t column = 0; column < Cols; ++column)
|
||||
{
|
||||
result[row] += m_values[row * Cols + column] * color[column];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline typename std::array<PDFColorComponent, Rows * Cols>::iterator begin() { return m_values.begin(); }
|
||||
inline typename std::array<PDFColorComponent, Rows * Cols>::iterator end() { return m_values.end(); }
|
||||
|
||||
private:
|
||||
std::array<PDFColorComponent, Rows * Cols> m_values;
|
||||
};
|
||||
|
||||
using PDFColorComponentMatrix_3x3 = PDFColorComponentMatrix<3, 3>;
|
||||
|
||||
/// Represents PDF's color space (abstract class). Contains functions for parsing
|
||||
/// color spaces.
|
||||
class PDFAbstractColorSpace
|
||||
{
|
||||
public:
|
||||
|
@ -38,9 +111,99 @@ public:
|
|||
virtual QColor getColor(const PDFColor& color) const = 0;
|
||||
virtual size_t getColorComponentCount() const = 0;
|
||||
|
||||
/// Parses the desired color space. If desired color space is not found, then exception is thrown.
|
||||
/// If everything is OK, then shared pointer to the new color space is returned.
|
||||
/// \param colorSpaceDictionary Dictionary containing color spaces of the page
|
||||
/// \param document Document (for loading objects)
|
||||
/// \param colorSpace Identification of color space (either name or array), must be a direct object
|
||||
static PDFColorSpacePointer createColorSpace(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFObject& colorSpace);
|
||||
|
||||
protected:
|
||||
/// Clips the color component to range [0, 1]
|
||||
static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound<PDFColorComponent>(0.0, component, 1.0); }
|
||||
|
||||
/// Clips the color to range [0 1] in all components
|
||||
static constexpr PDFColor3 clip01(const PDFColor3& color)
|
||||
{
|
||||
PDFColor3 result = color;
|
||||
|
||||
for (PDFColorComponent& component : result)
|
||||
{
|
||||
component = clip01(component);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parses the desired color space. If desired color space is not found, then exception is thrown.
|
||||
/// If everything is OK, then shared pointer to the new color space is returned.
|
||||
/// \param colorSpaceDictionary Dictionary containing color spaces of the page
|
||||
/// \param document Document (for loading objects)
|
||||
/// \param colorSpace Identification of color space (either name or array), must be a direct object
|
||||
/// \param recursion Recursion guard
|
||||
static PDFColorSpacePointer createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary,
|
||||
const PDFDocument* document,
|
||||
const PDFObject& colorSpace,
|
||||
int recursion);
|
||||
|
||||
/// Converts XYZ value to the standard RGB value (linear). No gamma correction is applied.
|
||||
/// Default transformation matrix is applied.
|
||||
/// \param xyzColor Color in XYZ space
|
||||
static PDFColor3 convertXYZtoRGB(const PDFColor3& xyzColor);
|
||||
|
||||
/// Multiplies color by factor
|
||||
/// \param color Color to be multiplied
|
||||
/// \param factor Multiplication factor
|
||||
static constexpr PDFColor3 colorMultiplyByFactor(const PDFColor3& color, PDFColorComponent factor)
|
||||
{
|
||||
PDFColor3 result = color;
|
||||
for (PDFColorComponent& component : result)
|
||||
{
|
||||
component *= factor;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Multiplies color by factors (stored in components of the color)
|
||||
/// \param color Color to be multiplied
|
||||
/// \param factor Multiplication factors
|
||||
static constexpr PDFColor3 colorMultiplyByFactors(const PDFColor3& color, const PDFColor3& factors)
|
||||
{
|
||||
PDFColor3 result = { };
|
||||
for (size_t i = 0; i < color.size(); ++i)
|
||||
{
|
||||
result[i] = color[i] * factors[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Powers color by factors (stored in components of the color)
|
||||
/// \param color Color to be multiplied
|
||||
/// \param factor Power factors
|
||||
static constexpr PDFColor3 colorPowerByFactors(const PDFColor3& color, const PDFColor3& factors)
|
||||
{
|
||||
PDFColor3 result = { };
|
||||
for (size_t i = 0; i < color.size(); ++i)
|
||||
{
|
||||
result[i] = std::powf(color[i], factors[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Converts RGB values of range [0.0, 1.0] to standard QColor
|
||||
/// \param color Color to be converted
|
||||
static inline QColor fromRGB01(const PDFColor3& color)
|
||||
{
|
||||
PDFColorComponent r = clip01(color[0]);
|
||||
PDFColorComponent g = clip01(color[1]);
|
||||
PDFColorComponent b = clip01(color[2]);
|
||||
|
||||
QColor result(QColor::Rgb);
|
||||
result.setRgbF(r, g, b, 1.0);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class PDFDeviceGrayColorSpace : public PDFAbstractColorSpace
|
||||
|
@ -73,6 +236,113 @@ public:
|
|||
virtual size_t getColorComponentCount() const override;
|
||||
};
|
||||
|
||||
class PDFCalGrayColorSpace : public PDFAbstractColorSpace
|
||||
{
|
||||
public:
|
||||
explicit inline PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma);
|
||||
virtual ~PDFCalGrayColorSpace() = default;
|
||||
|
||||
virtual QColor getColor(const PDFColor& color) const override;
|
||||
virtual size_t getColorComponentCount() const override;
|
||||
|
||||
/// Creates CalGray color space from provided values.
|
||||
/// \param document Document
|
||||
/// \param dictionary Dictionary
|
||||
static PDFColorSpacePointer createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary);
|
||||
|
||||
private:
|
||||
PDFColor3 m_whitePoint;
|
||||
PDFColor3 m_blackPoint;
|
||||
PDFColorComponent m_gamma;
|
||||
|
||||
/// What are these coefficients? We want to map white point from XYZ space to white point
|
||||
/// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white
|
||||
/// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0)
|
||||
/// after multiplication by these coefficients.
|
||||
PDFColor3 m_correctionCoefficients;
|
||||
};
|
||||
|
||||
class PDFCalRGBColorSpace : public PDFAbstractColorSpace
|
||||
{
|
||||
public:
|
||||
explicit inline PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix);
|
||||
virtual ~PDFCalRGBColorSpace() = default;
|
||||
|
||||
virtual QColor getColor(const PDFColor& color) const override;
|
||||
virtual size_t getColorComponentCount() const override;
|
||||
|
||||
/// Creates CalRGB color space from provided values.
|
||||
/// \param document Document
|
||||
/// \param dictionary Dictionary
|
||||
static PDFColorSpacePointer createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary);
|
||||
|
||||
private:
|
||||
PDFColor3 m_whitePoint;
|
||||
PDFColor3 m_blackPoint;
|
||||
PDFColor3 m_gamma;
|
||||
PDFColorComponentMatrix_3x3 m_matrix;
|
||||
|
||||
/// What are these coefficients? We want to map white point from XYZ space to white point
|
||||
/// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white
|
||||
/// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0)
|
||||
/// after multiplication by these coefficients.
|
||||
PDFColor3 m_correctionCoefficients;
|
||||
};
|
||||
|
||||
class PDFLabColorSpace : public PDFAbstractColorSpace
|
||||
{
|
||||
public:
|
||||
explicit inline PDFLabColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent aMin, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax);
|
||||
virtual ~PDFLabColorSpace() = default;
|
||||
|
||||
virtual QColor getColor(const PDFColor& color) const override;
|
||||
virtual size_t getColorComponentCount() const override;
|
||||
|
||||
/// Creates Lab color space from provided values.
|
||||
/// \param document Document
|
||||
/// \param dictionary Dictionary
|
||||
static PDFColorSpacePointer createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary);
|
||||
|
||||
private:
|
||||
PDFColor3 m_whitePoint;
|
||||
PDFColor3 m_blackPoint;
|
||||
PDFColorComponent m_aMin;
|
||||
PDFColorComponent m_aMax;
|
||||
PDFColorComponent m_bMin;
|
||||
PDFColorComponent m_bMax;
|
||||
|
||||
/// What are these coefficients? We want to map white point from XYZ space to white point
|
||||
/// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white
|
||||
/// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0)
|
||||
/// after multiplication by these coefficients.
|
||||
PDFColor3 m_correctionCoefficients;
|
||||
};
|
||||
|
||||
class PDFICCBasedColorSpace : public PDFAbstractColorSpace
|
||||
{
|
||||
private:
|
||||
static constexpr const size_t MAX_COLOR_COMPONENTS = 4;
|
||||
using Ranges = std::array<PDFColorComponent, MAX_COLOR_COMPONENTS * 2>;
|
||||
|
||||
public:
|
||||
explicit inline PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range);
|
||||
virtual ~PDFICCBasedColorSpace() = default;
|
||||
|
||||
virtual QColor getColor(const PDFColor& color) const override;
|
||||
virtual size_t getColorComponentCount() const override;
|
||||
|
||||
/// Creates ICC based color space from provided values.
|
||||
/// \param colorSpaceDictionary Color space dictionary
|
||||
/// \param document Document
|
||||
/// \param stream Stream with ICC profile
|
||||
/// \param recursion Recursion guard
|
||||
static PDFColorSpacePointer createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFStream* stream, int recursion);
|
||||
|
||||
private:
|
||||
PDFColorSpacePointer m_alternateColorSpace;
|
||||
Ranges m_range;
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
||||
#endif // PDFCOLORSPACES_H
|
||||
|
|
|
@ -273,6 +273,21 @@ PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object,
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFInteger defaultValue) const
|
||||
{
|
||||
const PDFObject& dereferencedObject = m_document->getObject(object);
|
||||
|
||||
if (dereferencedObject.isReal())
|
||||
{
|
||||
return dereferencedObject.getReal();
|
||||
} else if (dereferencedObject.isInt())
|
||||
{
|
||||
return dereferencedObject.getInteger();
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
QString PDFDocumentDataLoaderDecorator::readTextString(const PDFObject& object, const QString& defaultValue) const
|
||||
{
|
||||
const PDFObject& dereferencedObject = m_document->getObject(object);
|
||||
|
@ -322,4 +337,24 @@ QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, co
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
return readNumber(dictionary->get(key), defaultValue);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
PDFInteger PDFDocumentDataLoaderDecorator::readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
return readInteger(dictionary->get(key), defaultValue);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
|
|
@ -92,6 +92,12 @@ public:
|
|||
/// \param defaultValue Default value
|
||||
PDFInteger readInteger(const PDFObject& object, PDFInteger defaultValue) const;
|
||||
|
||||
/// Reads a real number from the object, if it is possible. If integer appears as object,
|
||||
/// then it is converted to real number.
|
||||
/// \param object Object, can be an indirect reference to object (it is dereferenced)
|
||||
/// \param defaultValue Default value
|
||||
PDFReal readNumber(const PDFObject& object, PDFInteger defaultValue) const;
|
||||
|
||||
/// Reads a text string from the object, if it is possible.
|
||||
/// \param object Object, can be an indirect reference to object (it is dereferenced)
|
||||
/// \param defaultValue Default value
|
||||
|
@ -127,6 +133,59 @@ public:
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
/// Tries to read array of real values. Reads as much values as possible.
|
||||
/// If array size differs, then nothing happens.
|
||||
/// \param object Array of integers
|
||||
/// \param first First iterator
|
||||
/// \param second Second iterator
|
||||
template<typename T>
|
||||
void readNumberArray(const PDFObject& object, T first, T last)
|
||||
{
|
||||
const PDFObject& dereferencedObject = m_document->getObject(object);
|
||||
if (dereferencedObject.isArray())
|
||||
{
|
||||
const PDFArray* array = dereferencedObject.getArray();
|
||||
|
||||
size_t distance = std::distance(first, last);
|
||||
if (array->getCount() == distance)
|
||||
{
|
||||
T it = first;
|
||||
for (size_t i = 0; i < distance; ++i)
|
||||
{
|
||||
*it = readNumber(array->getItem(i), *it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read array of real values from dictionary. Reads as much values as possible.
|
||||
/// If array size differs, or entry dictionary doesn't exist, then nothing happens.
|
||||
/// \param dictionary Dictionary with desired values
|
||||
/// \param key Entry key
|
||||
/// \param first First iterator
|
||||
/// \param second Second iterator
|
||||
template<typename T>
|
||||
void readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, T first, T last)
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
readNumberArray(dictionary->get(key), first, last);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned.
|
||||
/// \param dictionary Dictionary containing desired data
|
||||
/// \param key Entry key
|
||||
/// \param defaultValue Default value
|
||||
PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const;
|
||||
|
||||
/// Reads integer from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned.
|
||||
/// \param dictionary Dictionary containing desired data
|
||||
/// \param key Entry key
|
||||
/// \param defaultValue Default value
|
||||
PDFInteger readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const;
|
||||
|
||||
private:
|
||||
const PDFDocument* m_document;
|
||||
};
|
||||
|
|
|
@ -104,6 +104,20 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
T& operator[] (size_t index)
|
||||
{
|
||||
Q_ASSERT(index < size());
|
||||
|
||||
if (index < FlatSize)
|
||||
{
|
||||
return m_flatBlock[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_variableBlock[index - FlatSize];
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_flatBlockEndIterator = m_flatBlock.begin();
|
||||
|
|
|
@ -69,16 +69,16 @@ static constexpr const std::pair<const char*, PDFPageContentProcessor::Operator>
|
|||
{ "re", PDFPageContentProcessor::Operator::Rectangle },
|
||||
|
||||
// Path painting: S, s, f, F, f*, B, B*, b, b*, n
|
||||
{ "S", PDFPageContentProcessor::Operator::StrokePath },
|
||||
{ "s", PDFPageContentProcessor::Operator::CloseAndStrokePath },
|
||||
{ "f", PDFPageContentProcessor::Operator::FillPathWinding },
|
||||
{ "F", PDFPageContentProcessor::Operator::FillPathWinding2 },
|
||||
{ "f*", PDFPageContentProcessor::Operator::FillPathEvenOdd },
|
||||
{ "B", PDFPageContentProcessor::Operator::StrokeAndFillWinding },
|
||||
{ "B*", PDFPageContentProcessor::Operator::StrokeAndFillEvenOdd },
|
||||
{ "b", PDFPageContentProcessor::Operator::CloseAndStrokeAndFillWinding },
|
||||
{ "b*", PDFPageContentProcessor::Operator::CloseAndStrokeAndFillEvenOdd },
|
||||
{ "n", PDFPageContentProcessor::Operator::ClearPath },
|
||||
{ "S", PDFPageContentProcessor::Operator::PathStroke },
|
||||
{ "s", PDFPageContentProcessor::Operator::PathCloseStroke },
|
||||
{ "f", PDFPageContentProcessor::Operator::PathFillWinding },
|
||||
{ "F", PDFPageContentProcessor::Operator::PathFillWinding2 },
|
||||
{ "f*", PDFPageContentProcessor::Operator::PathFillEvenOdd },
|
||||
{ "B", PDFPageContentProcessor::Operator::PathFillStrokeWinding },
|
||||
{ "B*", PDFPageContentProcessor::Operator::PathFillStrokeEvenOdd },
|
||||
{ "b", PDFPageContentProcessor::Operator::PathCloseFillStrokeWinding },
|
||||
{ "b*", PDFPageContentProcessor::Operator::PathCloseFillStrokeEvenOdd },
|
||||
{ "n", PDFPageContentProcessor::Operator::PathClear },
|
||||
|
||||
// Clipping paths: W, W*
|
||||
{ "W", PDFPageContentProcessor::Operator::ClipWinding },
|
||||
|
@ -221,6 +221,14 @@ QList<PDFRenderError> PDFPageContentProcessor::processContents()
|
|||
return m_errorList;
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule)
|
||||
{
|
||||
Q_UNUSED(path);
|
||||
Q_UNUSED(stroke);
|
||||
Q_UNUSED(fill);
|
||||
Q_UNUSED(fillRule);
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::processContentStream(const PDFStream* stream)
|
||||
{
|
||||
QByteArray content = m_document->getDecodedStream(stream);
|
||||
|
@ -325,6 +333,61 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
|
|||
break;
|
||||
}
|
||||
|
||||
case Operator::PathStroke:
|
||||
{
|
||||
operatorPathStroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathCloseStroke:
|
||||
{
|
||||
operatorPathCloseStroke();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathFillWinding:
|
||||
case Operator::PathFillWinding2:
|
||||
{
|
||||
operatorPathFillWinding();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathFillEvenOdd:
|
||||
{
|
||||
operatorPathFillEvenOdd();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathFillStrokeWinding:
|
||||
{
|
||||
operatorPathFillStrokeWinding();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathFillStrokeEvenOdd:
|
||||
{
|
||||
operatorPathFillStrokeEvenOdd();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathCloseFillStrokeWinding:
|
||||
{
|
||||
operatorPathCloseFillStrokeWinding();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathCloseFillStrokeEvenOdd:
|
||||
{
|
||||
operatorPathCloseFillStrokeEvenOdd();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::PathClear:
|
||||
{
|
||||
operatorPathClear();
|
||||
break;
|
||||
}
|
||||
|
||||
case Operator::Invalid:
|
||||
{
|
||||
m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown operator '%1'.").arg(QString::fromLatin1(command))));
|
||||
|
@ -358,7 +421,7 @@ QPointF PDFPageContentProcessor::getCurrentPoint() const
|
|||
template<>
|
||||
PDFReal PDFPageContentProcessor::readOperand<PDFReal>(size_t index) const
|
||||
{
|
||||
if (m_operands.size() < index)
|
||||
if (index < m_operands.size())
|
||||
{
|
||||
const PDFLexicalAnalyzer::Token& token = m_operands[index];
|
||||
|
||||
|
@ -421,6 +484,94 @@ void PDFPageContentProcessor::operatorRectangle(PDFReal x, PDFReal y, PDFReal wi
|
|||
m_currentPath.addRect(QRectF(x, y, width, height));
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathStroke()
|
||||
{
|
||||
// Do not close the path
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
performPathPainting(m_currentPath, true, false, Qt::WindingFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathCloseStroke()
|
||||
{
|
||||
// Close the path
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.closeSubpath();
|
||||
performPathPainting(m_currentPath, true, false, Qt::WindingFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathFillWinding()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.setFillRule(Qt::WindingFill);
|
||||
performPathPainting(m_currentPath, false, true, Qt::WindingFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathFillEvenOdd()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.setFillRule(Qt::OddEvenFill);
|
||||
performPathPainting(m_currentPath, false, true, Qt::OddEvenFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathFillStrokeWinding()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.setFillRule(Qt::WindingFill);
|
||||
performPathPainting(m_currentPath, true, true, Qt::WindingFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathFillStrokeEvenOdd()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.setFillRule(Qt::OddEvenFill);
|
||||
performPathPainting(m_currentPath, true, true, Qt::OddEvenFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathCloseFillStrokeWinding()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.closeSubpath();
|
||||
m_currentPath.setFillRule(Qt::WindingFill);
|
||||
performPathPainting(m_currentPath, true, true, Qt::WindingFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathCloseFillStrokeEvenOdd()
|
||||
{
|
||||
if (!m_currentPath.isEmpty())
|
||||
{
|
||||
m_currentPath.closeSubpath();
|
||||
m_currentPath.setFillRule(Qt::OddEvenFill);
|
||||
performPathPainting(m_currentPath, true, true, Qt::OddEvenFill);
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PDFPageContentProcessor::operatorPathClear()
|
||||
{
|
||||
m_currentPath = QPainterPath();
|
||||
}
|
||||
|
||||
PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() :
|
||||
m_currentTransformationMatrix(),
|
||||
m_fillColorSpace(),
|
||||
|
|
|
@ -81,16 +81,16 @@ public:
|
|||
Rectangle, ///< re, adds rectangle
|
||||
|
||||
// Path painting: S, s, f, F, f*, B, B*, b, b*, n
|
||||
StrokePath, ///< S, stroke the path
|
||||
CloseAndStrokePath, ///< s, close the path and then stroke (equivalent of operators h S)
|
||||
FillPathWinding, ///< f, close the path, and then fill the path using "Non zero winding number rule"
|
||||
FillPathWinding2, ///< F, same as previous, see PDF Reference 1.7, Table 4.10
|
||||
FillPathEvenOdd, ///< f*, fill the path using "Even-odd rule"
|
||||
StrokeAndFillWinding, ///< B, stroke and fill path, using "Non zero winding number rule"
|
||||
StrokeAndFillEvenOdd, ///< B*, stroke and fill path, using "Even-odd rule"
|
||||
CloseAndStrokeAndFillWinding, ///< b, close, stroke and fill path, using "Non zero winding number rule", equivalent of operators h B
|
||||
CloseAndStrokeAndFillEvenOdd, ///< b*, close, stroke and fill path, using "Even-odd rule", equivalent of operators h B*
|
||||
ClearPath, ///< n, clear parh (close current) path, "no-operation", used with clipping
|
||||
PathStroke, ///< S, Stroke
|
||||
PathCloseStroke, ///< s, Close, Stroke (equivalent of operators h S)
|
||||
PathFillWinding, ///< f, Fill, Winding
|
||||
PathFillWinding2, ///< F, same as previous, see PDF Reference 1.7, Table 4.10
|
||||
PathFillEvenOdd, ///< f*, Fill, Even-Odd
|
||||
PathFillStrokeWinding, ///< B, Fill, Stroke, Winding
|
||||
PathFillStrokeEvenOdd, ///< B*, Fill, Stroke, Even-Odd
|
||||
PathCloseFillStrokeWinding, ///< b, Close, Fill, Stroke, Winding (equivalent of operators h B)
|
||||
PathCloseFillStrokeEvenOdd, ///< b*, Close, Fill, Stroke, Even-Odd (equivalent of operators h B*)
|
||||
PathClear, ///< n, clear path (close current) path, "no-operation", used with clipping
|
||||
|
||||
// Clipping paths: W, W*
|
||||
ClipWinding, ///< W, modify current clipping path by intersecting it with current path using "Non zero winding number rule"
|
||||
|
@ -166,6 +166,15 @@ public:
|
|||
/// Process the contents of the page
|
||||
QList<PDFRenderError> processContents();
|
||||
|
||||
protected:
|
||||
/// This function has to be implemented in the client drawing implementation, it should
|
||||
/// draw the path according to the parameters.
|
||||
/// \param path Path, which should be drawn (can be emtpy - in that case nothing happens)
|
||||
/// \param stroke Stroke the path
|
||||
/// \param fill Fill the path using given rule
|
||||
/// \param fillRule Fill rule used in the fill mode
|
||||
virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule);
|
||||
|
||||
private:
|
||||
/// Process the content stream
|
||||
void processContentStream(const PDFStream* stream);
|
||||
|
@ -211,6 +220,17 @@ private:
|
|||
void operatorEndSubpath();
|
||||
void operatorRectangle(PDFReal x, PDFReal y, PDFReal width, PDFReal height);
|
||||
|
||||
// Path painting operators
|
||||
void operatorPathStroke();
|
||||
void operatorPathCloseStroke();
|
||||
void operatorPathFillWinding();
|
||||
void operatorPathFillEvenOdd();
|
||||
void operatorPathFillStrokeWinding();
|
||||
void operatorPathFillStrokeEvenOdd();
|
||||
void operatorPathCloseFillStrokeWinding();
|
||||
void operatorPathCloseFillStrokeEvenOdd();
|
||||
void operatorPathClear();
|
||||
|
||||
/// Represents graphic state of the PDF (holding current graphic state parameters).
|
||||
/// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State"
|
||||
class PDFPageContentProcessorState
|
||||
|
|
Loading…
Reference in New Issue