Shading: Axial shading (first part)

This commit is contained in:
Jakub Melka 2019-08-25 18:16:37 +02:00
parent d6d112667d
commit 7e2d1b266f
15 changed files with 544 additions and 23 deletions

View File

@ -42,6 +42,7 @@ SOURCES += \
sources/pdfparser.cpp \
sources/pdfdocument.cpp \
sources/pdfdocumentreader.cpp \
sources/pdfpattern.cpp \
sources/pdfsecurityhandler.cpp \
sources/pdfutils.cpp \
sources/pdfxreftable.cpp \
@ -71,6 +72,7 @@ HEADERS += \
sources/pdfconstants.h \
sources/pdfdocument.h \
sources/pdfdocumentreader.h \
sources/pdfpattern.h \
sources/pdfsecurityhandler.h \
sources/pdfxreftable.h \
sources/pdfflatmap.h \

View File

@ -20,6 +20,7 @@
#include "pdfdocument.h"
#include "pdfexception.h"
#include "pdfutils.h"
#include "pdfpattern.h"
namespace pdf
{
@ -238,6 +239,16 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData) const
return QImage();
}
QColor PDFAbstractColorSpace::getCheckedColor(const PDFColor& color) const
{
if (getColorComponentCount() != color.size())
{
throw PDFParserException(PDFTranslationContext::tr("Invalid number of color components. Expected number is %1, actual number is %2.").arg(getColorComponentCount(), color.size()));
}
return getColor(color);
}
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary,
const PDFDocument* document,
const PDFObject& colorSpace)
@ -252,6 +263,18 @@ PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByName(const P
return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, name, COLOR_SPACE_MAX_LEVEL_OF_RECURSION);
}
PDFColor PDFAbstractColorSpace::convertToColor(const std::vector<PDFReal>& components)
{
PDFColor result;
for (PDFReal component : components)
{
result.push_back(component);
}
return result;
}
PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary,
const PDFDocument* document,
const PDFObject& colorSpace,
@ -293,6 +316,12 @@ PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictio
{
stream = colorSpaceSettings.getStream();
}
if (name == COLOR_SPACE_NAME_PATTERN)
{
PDFPatternPtr pattern = PDFPattern::createPattern(colorSpaceDictionary, document, array->getItem(1));
return PDFColorSpacePointer(new PDFPatternColorSpace(qMove(pattern)));
}
}
if (dictionary)
@ -915,4 +944,19 @@ const unsigned char* PDFImageData::getRow(unsigned int rowIndex) const
return data + (rowIndex * m_stride);
}
QColor PDFPatternColorSpace::getDefaultColor() const
{
throw PDFParserException(PDFTranslationContext::tr("Pattern doesn't have default color."));
}
QColor PDFPatternColorSpace::getColor(const PDFColor& color) const
{
throw PDFParserException(PDFTranslationContext::tr("Pattern doesn't have defined uniform color."));
}
size_t PDFPatternColorSpace::getColorComponentCount() const
{
return 0;
}
} // namespace pdf

View File

@ -30,6 +30,7 @@ namespace pdf
class PDFArray;
class PDFObject;
class PDFStream;
class PDFPattern;
class PDFDocument;
class PDFDictionary;
class PDFAbstractColorSpace;
@ -60,6 +61,7 @@ static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab";
static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased";
static constexpr const char* COLOR_SPACE_NAME_INDEXED = "Indexed";
static constexpr const char* COLOR_SPACE_NAME_SEPARATION = "Separation";
static constexpr const char* COLOR_SPACE_NAME_PATTERN = "Pattern";
static constexpr const char* CAL_WHITE_POINT = "WhitePoint";
static constexpr const char* CAL_BLACK_POINT = "BlackPoint";
@ -209,6 +211,10 @@ public:
virtual size_t getColorComponentCount() const = 0;
virtual QImage getImage(const PDFImageData& imageData) const;
/// Checks, if number of color components is OK, and if yes, converts them to the QColor value.
/// If they are not OK, exception is thrown.
QColor getCheckedColor(const PDFColor& color) const;
/// 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
@ -227,6 +233,9 @@ public:
const PDFDocument* document,
const QByteArray& name);
/// Converts a vector of real numbers to the PDFColor
static PDFColor convertToColor(const std::vector<PDFReal>& components);
protected:
/// Clips the color component to range [0, 1]
static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound<PDFColorComponent>(0.0, component, 1.0); }
@ -511,8 +520,23 @@ private:
PDFFunctionPtr m_tintTransform;
};
class PDFPatternColorSpace : public PDFAbstractColorSpace
{
public:
explicit PDFPatternColorSpace(std::shared_ptr<PDFPattern>&& pattern) : m_pattern(qMove(pattern)) { }
virtual ~PDFPatternColorSpace() override = default;
virtual QColor getDefaultColor() const override;
virtual QColor getColor(const PDFColor& color) const override;
virtual size_t getColorComponentCount() const override;
const PDFPattern* getPattern() const { return m_pattern.get(); }
private:
std::shared_ptr<PDFPattern> m_pattern;
};
// TODO: Implement DeviceN color space
// TODO: Implement Pattern color space
} // namespace pdf

View File

@ -296,6 +296,22 @@ QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, co
return defaultValue;
}
QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue)
{
if (dictionary->hasKey(key))
{
std::vector<PDFReal> matrixNumbers = readNumberArrayFromDictionary(dictionary, key);
if (matrixNumbers.size() != 6)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number of matrix elements. Expected 6, actual %1.").arg(matrixNumbers.size()));
}
return QMatrix(matrixNumbers[0], matrixNumbers[1], matrixNumbers[2], matrixNumbers[3], matrixNumbers[4], matrixNumbers[5]);
}
return defaultValue;
}
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
{
if (dictionary->hasKey(key))

View File

@ -25,6 +25,7 @@
#include "pdfsecurityhandler.h"
#include <QtCore>
#include <QMatrix>
#include <QDateTime>
namespace pdf
@ -195,6 +196,10 @@ public:
}
}
/// Tries to read matrix from the dictionary. If matrix entry is not present, default value is returned.
/// If it is present and invalid, exception is thrown.
QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue);
/// Tries to read array of real values from dictionary. If entry dictionary doesn't exist,
/// or error occurs, empty array is returned.
std::vector<PDFReal> readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key);

View File

@ -116,6 +116,11 @@ QRectF PDFPage::getRectMM(const QRectF& rect) const
convertPDFPointToMM(rect.height()));
}
QRectF PDFPage::getRotatedMediaBox() const
{
return getRotatedBox(getMediaBox(), getPageRotation());
}
QRectF PDFPage::getRotatedBox(const QRectF& rect, PageRotation rotation)
{
switch (rotation)

View File

@ -94,6 +94,8 @@ public:
inline QRectF getTrimBoxMM() const { return getRectMM(m_trimBox); }
inline QRectF getArtBoxMM() const { return getRectMM(m_artBox); }
QRectF getRotatedMediaBox() const;
static QRectF getRotatedBox(const QRectF& rect, PageRotation rotation);
private:

View File

@ -19,6 +19,7 @@
#include "pdfdocument.h"
#include "pdfexception.h"
#include "pdfimage.h"
#include "pdfpattern.h"
namespace pdf
{
@ -173,12 +174,14 @@ void PDFPageContentProcessor::initDictionaries(const PDFObject& resourcesObject)
m_xobjectDictionary = getDictionary("XObject");
m_extendedGraphicStateDictionary = getDictionary(PDF_RESOURCE_EXTGSTATE);
m_propertiesDictionary = getDictionary("Properties");
m_shadingDictionary = getDictionary("Shading");
}
PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity) :
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix patternBaseMatrix) :
m_page(page),
m_document(document),
m_fontCache(fontCache),
@ -188,12 +191,18 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page,
m_xobjectDictionary(nullptr),
m_extendedGraphicStateDictionary(nullptr),
m_propertiesDictionary(nullptr),
m_shadingDictionary(nullptr),
m_textBeginEndState(0),
m_compatibilityBeginEndState(0)
m_compatibilityBeginEndState(0),
m_patternBaseMatrix(patternBaseMatrix)
{
Q_ASSERT(page);
Q_ASSERT(document);
QPainterPath pageRectPath;
pageRectPath.addRect(m_page->getRotatedMediaBox());
m_pageBoundingRectDeviceSpace = patternBaseMatrix.map(pageRectPath).boundingRect();
initDictionaries(m_page->getResources());
}
@ -539,6 +548,8 @@ void PDFPageContentProcessor::processForm(const QMatrix& matrix, const QRectF& b
m_graphicState.setCurrentTransformationMatrix(formMatrix);
updateGraphicState();
PDFTemporaryValueChange patternMatrixGuard(&m_patternBaseMatrix, formMatrix);
// If the clipping box is valid, then use clipping. Clipping box is in the form coordinate system
if (boundingBox.isValid())
{
@ -971,6 +982,13 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
break;
}
case Operator::ShadingPaintShape:
{
// sh, paint shape
invokeOperator(&PDFPageContentProcessor::operatorShadingPaintShape);
break;
}
case Operator::MarkedContentPoint:
{
// MP, marked content point
@ -2024,6 +2042,34 @@ void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDF
operatorTextNextLineShowText(qMove(text));
}
void PDFPageContentProcessor::operatorShadingPaintShape(PDFPageContentProcessor::PDFOperandName name)
{
QMatrix matrix = getGraphicState()->getCurrentTransformationMatrix();
PDFPageContentProcessorStateGuard guard(this);
PDFTemporaryValueChange guard2(&m_patternBaseMatrix, matrix);
if (!m_shadingDictionary)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Shading '%1' not found.").arg(QString::fromLatin1(name.name)));
}
PDFPatternPtr pattern = PDFPattern::createShadingPattern(m_colorSpaceDictionary, m_document, m_shadingDictionary->get(name.name), QMatrix(), PDFObject(), true);
// We will do a trick: we will set current fill color space, and then paint
// bounding rectangle in the color pattern.
m_graphicState.setFillColorSpace(PDFColorSpacePointer(new PDFPatternColorSpace(qMove(pattern))));
updateGraphicState();
Q_ASSERT(matrix.isInvertible());
QMatrix inverted = matrix.inverted();
QPainterPath deviceBoundingRectPath;
deviceBoundingRectPath.addRect(m_pageBoundingRectDeviceSpace);
QPainterPath boundingRectPath = inverted.map(deviceBoundingRectPath);
performPathPainting(boundingRectPath, false, true, false, boundingRectPath.fillRule());
}
void PDFPageContentProcessor::paintXObjectImage(const PDFStream* stream)
{
PDFColorSpacePointer colorSpace;
@ -2112,18 +2158,7 @@ void PDFPageContentProcessor::operatorPaintXObject(PDFPageContentProcessor::PDFO
QRectF boundingBox = loader.readRectangle(streamDictionary->get("BBox"), QRectF());
// Read the transformation matrix, if it is present
QMatrix transformationMatrix;
if (streamDictionary->hasKey("Matrix"))
{
std::vector<PDFReal> matrixNumbers = loader.readNumberArrayFromDictionary(streamDictionary, "Matrix");
if (matrixNumbers.size() != 6)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number of matrix elements. Expected 6, actual %1.").arg(matrixNumbers.size()));
}
transformationMatrix = QMatrix(matrixNumbers[0], matrixNumbers[1], matrixNumbers[2], matrixNumbers[3], matrixNumbers[4], matrixNumbers[5]);
}
QMatrix transformationMatrix = loader.readMatrixFromDictionary(streamDictionary, "Matrix", QMatrix());
// Read the dictionary content
QByteArray content = m_document->getDecodedStream(stream);
@ -2721,7 +2756,8 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::PDFPageContentProces
m_fontDictionary(processor->m_fontDictionary),
m_xobjectDictionary(processor->m_xobjectDictionary),
m_extendedGraphicStateDictionary(processor->m_extendedGraphicStateDictionary),
m_propertiesDictionary(processor->m_propertiesDictionary)
m_propertiesDictionary(processor->m_propertiesDictionary),
m_shadingDictionary(processor->m_shadingDictionary)
{
m_processor->operatorSaveGraphicState();
}
@ -2734,6 +2770,7 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::~PDFPageContentProce
m_processor->m_xobjectDictionary = m_xobjectDictionary;
m_processor->m_extendedGraphicStateDictionary = m_extendedGraphicStateDictionary;
m_processor->m_propertiesDictionary = m_propertiesDictionary;
m_processor->m_shadingDictionary = m_shadingDictionary;
m_processor->operatorRestoreGraphicState();
}

View File

@ -45,7 +45,8 @@ public:
explicit PDFPageContentProcessor(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity);
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix patternBaseMatrix);
virtual ~PDFPageContentProcessor();
enum class Operator
@ -473,6 +474,7 @@ private:
const PDFDictionary* m_xobjectDictionary;
const PDFDictionary* m_extendedGraphicStateDictionary;
const PDFDictionary* m_propertiesDictionary;
const PDFDictionary* m_shadingDictionary;
};
/// Wrapper for PDF Name
@ -647,6 +649,9 @@ private:
void operatorTextNextLineShowText(PDFOperandString text); ///< ', move to the next line and show text ("string '" is equivalent to "T* string Tj")
void operatorTextSetSpacingAndShowText(PDFReal t_w, PDFReal t_c, PDFOperandString text); ///< ", move to the next line, set spacing and show text (equivalent to sequence "w1 Tw w2 Tc string '")
// Shading pattern: sh
void operatorShadingPaintShape(PDFOperandName name); ///< sh, paint shape
// XObject: Do
void operatorPaintXObject(PDFOperandName name); ///< Do, paint the X Object (image, form, ...)
@ -691,6 +696,7 @@ private:
const PDFDictionary* m_xobjectDictionary;
const PDFDictionary* m_extendedGraphicStateDictionary;
const PDFDictionary* m_propertiesDictionary;
const PDFDictionary* m_shadingDictionary;
// Default color spaces
PDFColorSpacePointer m_deviceGrayColorSpace;
@ -727,6 +733,14 @@ private:
/// Actual clipping path obtained from text. Clipping path
/// is in device space coordinates.
QPainterPath m_textClippingPath;
/// Base matrix to be used when drawing patterns. Concatenate this matrix
/// with pattern matrix to get transformation from pattern space to device space.
QMatrix m_patternBaseMatrix;
/// Bounding rectangle of pages media box in device space coordinates. If drawing rotation
/// is zero, then it corresponds to the scaled media box of the page.
QRectF m_pageBoundingRectDeviceSpace;
};
} // namespace pdf

View File

@ -29,7 +29,7 @@ PDFPainter::PDFPainter(QPainter* painter,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity) :
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity),
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix),
m_painter(painter),
m_features(features),
m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix)
@ -47,6 +47,7 @@ PDFPainter::~PDFPainter()
void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
// TODO: Implement Pattern features (shading/tiling)
if (isContentSuppressed())
{
// Content is suppressed, do not paint anything

View File

@ -0,0 +1,189 @@
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfpattern.h"
#include "pdfdocument.h"
#include "pdfexception.h"
namespace pdf
{
PatternType PDFShadingPattern::getType() const
{
return PatternType::Shading;
}
ShadingType PDFAxialShading::getShadingType() const
{
return ShadingType::Axial;
}
PDFPatternPtr PDFPattern::createPattern(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFObject& object)
{
const PDFObject& dereferencedObject = document->getObject(object);
if (dereferencedObject.isDictionary())
{
PDFPatternPtr result;
const PDFDictionary* patternDictionary = dereferencedObject.getDictionary();
PDFDocumentDataLoaderDecorator loader(document);
if (loader.readNameFromDictionary(patternDictionary, "Type") != "Pattern")
{
throw PDFParserException(PDFTranslationContext::tr("Invalid pattern."));
}
const PatternType patternType = static_cast<PatternType>(loader.readIntegerFromDictionary(patternDictionary, "PatternType", static_cast<PDFInteger>(PatternType::Invalid)));
switch (patternType)
{
case PatternType::Tiling:
{
// TODO: Implement tiling pattern
throw PDFParserException(PDFTranslationContext::tr("Tiling pattern not implemented."));
break;
}
case PatternType::Shading:
{
PDFObject patternGraphicState = document->getObject(patternDictionary->get("ExtGState"));
QMatrix matrix = loader.readMatrixFromDictionary(patternDictionary, "Matrix", QMatrix());
return createShadingPattern(colorSpaceDictionary, document, patternDictionary->get("Shading"), matrix, patternGraphicState, false);
}
default:
throw PDFParserException(PDFTranslationContext::tr("Invalid pattern."));
}
return result;
}
throw PDFParserException(PDFTranslationContext::tr("Invalid pattern."));
return PDFPatternPtr();
}
PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDictionary,
const PDFDocument* document,
const PDFObject& shadingObject,
const QMatrix& matrix,
const PDFObject& patternGraphicState,
bool ignoreBackgroundColor)
{
const PDFObject& dereferencedShadingObject = document->getObject(shadingObject);
if (!dereferencedShadingObject.isDictionary())
{
throw PDFParserException(PDFTranslationContext::tr("Invalid shading."));
}
PDFDocumentDataLoaderDecorator loader(document);
const PDFDictionary* shadingDictionary = dereferencedShadingObject.getDictionary();
// Parse common data for all shadings
PDFColorSpacePointer colorSpace = PDFAbstractColorSpace::createColorSpace(colorSpaceDictionary, document, shadingDictionary->get("ColorSpace"));
QColor backgroundColor;
if (!ignoreBackgroundColor)
{
std::vector<PDFReal> backgroundColorValues = loader.readNumberArrayFromDictionary(shadingDictionary, "Background");
if (!backgroundColorValues.empty())
{
backgroundColor = colorSpace->getCheckedColor(PDFAbstractColorSpace::convertToColor(backgroundColorValues));
}
}
QRectF boundingBox = loader.readRectangle(shadingDictionary->get("BBox"), QRectF());
bool antialias = loader.readBooleanFromDictionary(shadingDictionary, "AntiAlias", false);
const PDFObject& extendObject = document->getObject(shadingDictionary->get("Extend"));
bool extendStart = false;
bool extendEnd = false;
if (extendObject.isArray())
{
const PDFArray* array = extendObject.getArray();
if (array->getCount() != 2)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid shading pattern extends. Expected 2, but %1 provided.").arg(array->getCount()));
}
extendStart = loader.readBoolean(array->getItem(0), false);
extendEnd = loader.readBoolean(array->getItem(1), false);
}
std::vector<PDFFunctionPtr> functions;
const PDFObject& functionsObject = document->getObject(shadingDictionary->get("Function"));
if (functionsObject.isArray())
{
const PDFArray* functionsArray = functionsObject.getArray();
functions.reserve(functionsArray->getCount());
for (size_t i = 0, functionCount = functionsArray->getCount(); i < functionCount; ++i)
{
functions.push_back(PDFFunction::createFunction(document, functionsArray->getItem(i)));
}
}
else if (!functionsObject.isNull())
{
functions.push_back(PDFFunction::createFunction(document, functionsObject));
}
const ShadingType shadingType = static_cast<ShadingType>(loader.readIntegerFromDictionary(shadingDictionary, "ShadingType", static_cast<PDFInteger>(ShadingType::Invalid)));
switch (shadingType)
{
case ShadingType::Axial:
{
PDFAxialShading* axialShading = new PDFAxialShading();
PDFPatternPtr result(axialShading);
std::vector<PDFReal> coordinates = loader.readNumberArrayFromDictionary(shadingDictionary, "Coords");
if (coordinates.size() != 4)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid axial shading pattern coordinates. Expected 4, but %1 provided.").arg(coordinates.size()));
}
std::vector<PDFReal> domain = loader.readNumberArrayFromDictionary(shadingDictionary, "Domain");
if (domain.empty())
{
domain = { 0.0, 1.0 };
}
if (domain.size() != 2)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid axial shading pattern domain. Expected 2, but %1 provided.").arg(domain.size()));
}
// Load items for axial shading
axialShading->m_antiAlias = antialias;
axialShading->m_backgroundColor = backgroundColor;
axialShading->m_colorSpace = colorSpace;
axialShading->m_boundingBox = boundingBox;
axialShading->m_domainStart = domain[0];
axialShading->m_domainEnd = domain[1];
axialShading->m_startPoint = QPointF(coordinates[0], coordinates[1]);
axialShading->m_endPoint = QPointF(coordinates[2], coordinates[3]);
axialShading->m_extendStart = extendStart;
axialShading->m_extendEnd = extendEnd;
axialShading->m_functions = qMove(functions);
axialShading->m_matrix = matrix;
axialShading->m_patternGraphicState = patternGraphicState;
return result;
}
default:
{
throw PDFParserException(PDFTranslationContext::tr("Invalid shading pattern type (%1).").arg(static_cast<PDFInteger>(shadingType)));
}
}
throw PDFParserException(PDFTranslationContext::tr("Invalid shading."));
return PDFPatternPtr();
}
} // namespace pdf

View File

@ -0,0 +1,158 @@
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#ifndef PDFPATTERN_H
#define PDFPATTERN_H
#include "pdfobject.h"
#include "pdffunction.h"
#include "pdfcolorspaces.h"
#include <QMatrix>
#include <memory>
namespace pdf
{
class PDFPattern;
using PDFPatternPtr = std::shared_ptr<PDFPattern>;
enum class PatternType
{
Invalid = 0,
Tiling = 1,
Shading = 2,
};
enum class ShadingType
{
Invalid = 0,
Function = 1,
Axial = 2,
Radial = 3,
FreeFormGouradTriangle = 4,
LatticeFormGouradTriangle = 5,
CoonsPatchMesh = 6,
TensorProductPatchMesh = 7
};
/// Represents tiling/shading pattern
class PDFPattern
{
public:
explicit PDFPattern() = default;
virtual ~PDFPattern() = default;
virtual PatternType getType() const = 0;
/// Returns bounding box in the shadings target coordinate system (not in
/// pattern coordinate system).
const QRectF& getBoundingBox() const { return m_boundingBox; }
/// Returns transformation matrix from pattern space to the default
/// target space.
const QMatrix& getMatrix() const { return m_matrix; }
/// Create pattern from the object. If error occurs, exception is thrown
/// \param colorSpaceDictionary Color space dictionary
/// \param document Document, owning the pdf object
/// \param object Object defining the pattern
static PDFPatternPtr createPattern(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFObject& object);
/// Create shading pattern from the object. If error occurs, exception is thrown
/// \param colorSpaceDictionary Color space dictionary
/// \param document Document, owning the pdf object
/// \param object Object defining the shading
/// \param matrix Matrix converting reference coordinate system to the device coordinate system
/// \param patternGraphicState Pattern graphic state
/// \param ignoreBackgroundColor If set, then ignores background color, even if it is present
static PDFPatternPtr createShadingPattern(const PDFDictionary* colorSpaceDictionary,
const PDFDocument* document,
const PDFObject& shadingObject,
const QMatrix& matrix,
const PDFObject& patternGraphicState,
bool ignoreBackgroundColor);
private:
QRectF m_boundingBox;
QMatrix m_matrix;
};
/// Shading pattern - smooth color distribution along the pattern's space
class PDFShadingPattern : public PDFPattern
{
public:
explicit PDFShadingPattern() = default;
virtual PatternType getType() const override;
virtual ShadingType getShadingType() const = 0;
/// Returns patterns graphic state. This state must be applied before
/// the shading pattern is painted to the target device.
const PDFObject& getPatternGraphicState() const { return m_patternGraphicState; }
/// Returns color space of the pattern.
const PDFAbstractColorSpace* getColorSpace() const;
/// Returns patterns background color (if pattern has background color).
/// If pattern has not background color, then invalid color is returned.
const QColor& getBackgroundColor() const { return m_backgroundColor; }
/// Returns true, if shading pattern should be anti-aliased
bool isAntialiasing() const { return m_antiAlias; }
private:
friend class PDFPattern;
PDFObject m_patternGraphicState;
PDFColorSpacePointer m_colorSpace;
QColor m_backgroundColor;
bool m_antiAlias = false;
};
class PDFSingleDimensionShading : public PDFShadingPattern
{
public:
explicit PDFSingleDimensionShading() = default;
private:
friend class PDFPattern;
std::vector<PDFFunctionPtr> m_functions;
QPointF m_startPoint;
QPointF m_endPoint;
PDFReal m_domainStart = 0.0;
PDFReal m_domainEnd = 1.0;
bool m_extendStart = false;
bool m_extendEnd = false;
};
class PDFAxialShading : public PDFSingleDimensionShading
{
public:
explicit PDFAxialShading() = default;
virtual ShadingType getShadingType() const override;
private:
friend class PDFPattern;
};
} // namespace pdf
#endif // PDFPATTERN_H

View File

@ -48,12 +48,10 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& recta
const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page);
QRectF mediaBox = page->getMediaBox();
const PageRotation rotation = page->getPageRotation();
mediaBox = page->getRotatedBox(mediaBox, rotation);
QRectF mediaBox = page->getRotatedMediaBox();
QMatrix matrix;
switch (rotation)
switch (page->getPageRotation())
{
case PageRotation::None:
{

View File

@ -101,6 +101,33 @@ private:
Value m_bitsInBuffer;
};
/// Simple class guard, for properly saving/restoring new/old value. In the constructor,
/// new value is stored in the pointer (old one is being saved), and in the destructor,
/// old value is restored. This object assumes, that value is not a null pointer.
template<typename Value>
class PDFTemporaryValueChange
{
public:
/// Constructor
/// \param value Value pointer (must not be a null pointer)
/// \param newValue New value to be set to the pointer
explicit inline PDFTemporaryValueChange(Value* valuePointer, Value newValue) :
m_oldValue(qMove(*valuePointer)),
m_value(valuePointer)
{
*valuePointer = qMove(newValue);
}
inline ~PDFTemporaryValueChange()
{
*m_value = qMove(m_oldValue);
}
private:
Value m_oldValue;
Value* m_value;
};
/// Performs linear mapping of value x in interval [x_min, x_max] to the interval [y_min, y_max].
/// \param x Value to be linearly remapped from interval [x_min, x_max] to the interval [y_min, y_max].
/// \param x_min Start of the input interval

View File

@ -17,7 +17,6 @@
QT += testlib
QT -= gui
CONFIG += qt console warn_on depend_includepath testcase
CONFIG -= app_bundle