mirror of https://github.com/JakubMelka/PDF4QT.git
Shading: Axial shading (first part)
This commit is contained in:
parent
d6d112667d
commit
7e2d1b266f
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
|
||||
QT += testlib
|
||||
QT -= gui
|
||||
|
||||
CONFIG += qt console warn_on depend_includepath testcase
|
||||
CONFIG -= app_bundle
|
||||
|
|
Loading…
Reference in New Issue