Tiling patterns (first part)

This commit is contained in:
Jakub Melka 2019-09-25 19:17:52 +02:00
parent d87995f8b8
commit 40f3f9f9b4
4 changed files with 195 additions and 13 deletions

View File

@ -601,14 +601,14 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
{
case PatternType::Tiling:
{
// TODO: Implement tiling pattern
throw PDFParserException(PDFTranslationContext::tr("Tiling pattern not implemented."));
const PDFTilingPattern* tilingPattern = pattern->getTilingPattern();
processTillingPatternPainting(tilingPattern, path);
break;
}
case PatternType::Shading:
{
const PDFShadingPattern* shadingPatern = pattern->getShadingPattern();
const PDFShadingPattern* shadingPattern = pattern->getShadingPattern();
// We must create a mesh and then draw pattern
PDFMeshQualitySettings settings;
@ -616,7 +616,7 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix();
settings.initDefaultResolution();
PDFMesh mesh = shadingPatern->createMesh(settings);
PDFMesh mesh = shadingPattern->createMesh(settings);
// Now, merge the current path to the mesh clipping path
QPainterPath boundingPath = mesh.getBoundingPath();
@ -660,8 +660,23 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
{
case PatternType::Tiling:
{
// TODO: Implement tiling pattern
throw PDFParserException(PDFTranslationContext::tr("Tiling pattern not implemented."));
const PDFTilingPattern* tilingPattern = pattern->getTilingPattern();
// We must stroke the path.
QPainterPathStroker stroker;
stroker.setCapStyle(m_graphicState.getLineCapStyle());
stroker.setWidth(m_graphicState.getLineWidth());
stroker.setMiterLimit(m_graphicState.getMitterLimit());
stroker.setJoinStyle(m_graphicState.getLineJoinStyle());
const PDFLineDashPattern& lineDashPattern = m_graphicState.getLineDashPattern();
if (!lineDashPattern.isSolid())
{
stroker.setDashPattern(QVector<PDFReal>::fromStdVector(lineDashPattern.getDashArray()));
stroker.setDashOffset(lineDashPattern.getDashOffset());
}
QPainterPath strokedPath = stroker.createStroke(path);
processTillingPatternPainting(tilingPattern, strokedPath);
break;
}
@ -730,6 +745,59 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool
}
}
void PDFPageContentProcessor::processTillingPatternPainting(const PDFTilingPattern* tilingPattern, const QPainterPath& path)
{
PDFPageContentProcessorStateGuard guard(this);
performClipping(path, path.fillRule());
// Initialize resources
const PDFObject& resources = tilingPattern->getResources();
if (!resources.isNull())
{
initDictionaries(resources);
}
Q_ASSERT(m_pagePointToDevicePointMatrix.isInvertible());
// Initialize rendering matrix
QMatrix patternMatrix = tilingPattern->getMatrix() * getPatternBaseMatrix();
QMatrix matrix = patternMatrix * m_pagePointToDevicePointMatrix.inverted();
QMatrix pathTransformationMatrix = m_graphicState.getCurrentTransformationMatrix() * matrix.inverted();
m_graphicState.setCurrentTransformationMatrix(matrix);
updateGraphicState();
// Tiling parameters
const QRectF tilingArea = pathTransformationMatrix.map(path).boundingRect();
const QRectF boundingBox = tilingPattern->getBoundingBox();
const PDFReal xStep = qAbs(tilingPattern->getXStep());
const PDFReal yStep = qAbs(tilingPattern->getYStep());
const QByteArray& content = tilingPattern->getContent();
QPainterPath boundingPath;
boundingPath.addRect(boundingBox);
// Draw the tiling
const PDFInteger columns = qMax<PDFInteger>(qCeil(tilingArea.width() / xStep), 1);
const PDFInteger rows = qMax<PDFInteger>(qCeil(tilingArea.height() / yStep), 1);
QMatrix baseTransformationMatrix = m_graphicState.getCurrentTransformationMatrix();
for (PDFInteger column = 0; column < columns; ++column)
{
for (PDFInteger row = 0; row < rows; ++row)
{
PDFPageContentProcessorGraphicStateSaveRestoreGuard guard(this);
QMatrix transformationMatrix = baseTransformationMatrix;
transformationMatrix.translate(tilingArea.left(), tilingArea.top());
transformationMatrix.translate(column * xStep, row * yStep);
m_graphicState.setCurrentTransformationMatrix(transformationMatrix);
updateGraphicState();
performClipping(boundingPath, boundingPath.fillRule());
processContent(content);
}
}
}
void PDFPageContentProcessor::processCommand(const QByteArray& command)
{
Operator op = Operator::Invalid;

View File

@ -35,6 +35,7 @@
namespace pdf
{
class PDFMesh;
class PDFTilingPattern;
class PDFOptionalContentActivity;
static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState";
@ -464,6 +465,11 @@ private:
/// \param fillRule Fill rule used in the fill mode
void processPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule);
/// Performs tiling pattern painting
/// \param tilingPattern Tiling pattern to be painted
/// \param path Clipping path
void processTillingPatternPainting(const PDFTilingPattern* tilingPattern, const QPainterPath& path);
enum class MarkedContentKind
{
OptionalContent,
@ -505,6 +511,23 @@ private:
const PDFDictionary* m_patternDictionary;
};
struct PDFPageContentProcessorGraphicStateSaveRestoreGuard
{
public:
inline explicit PDFPageContentProcessorGraphicStateSaveRestoreGuard(PDFPageContentProcessor* processor) :
m_processor(processor)
{
m_processor->operatorSaveGraphicState();
}
inline ~PDFPageContentProcessorGraphicStateSaveRestoreGuard()
{
m_processor->operatorRestoreGraphicState();
}
private:
PDFPageContentProcessor* m_processor;
};
/// Wrapper for PDF Name
struct PDFOperandName
{

View File

@ -46,11 +46,22 @@ ShadingType PDFAxialShading::getShadingType() const
PDFPatternPtr PDFPattern::createPattern(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFObject& object)
{
const PDFObject& dereferencedObject = document->getObject(object);
const PDFDictionary* patternDictionary = nullptr;
QByteArray streamData;
if (dereferencedObject.isDictionary())
{
PDFPatternPtr result;
patternDictionary = dereferencedObject.getDictionary();
}
else if (dereferencedObject.isStream())
{
const PDFStream* stream = dereferencedObject.getStream();
patternDictionary = stream->getDictionary();
streamData = document->getDecodedStream(stream);
}
const PDFDictionary* patternDictionary = dereferencedObject.getDictionary();
if (patternDictionary)
{
PDFDocumentDataLoaderDecorator loader(document);
const PatternType patternType = static_cast<PatternType>(loader.readIntegerFromDictionary(patternDictionary, "PatternType", static_cast<PDFInteger>(PatternType::Invalid)));
@ -58,9 +69,43 @@ PDFPatternPtr PDFPattern::createPattern(const PDFDictionary* colorSpaceDictionar
{
case PatternType::Tiling:
{
// TODO: Implement tiling pattern
throw PDFParserException(PDFTranslationContext::tr("Tiling pattern not implemented."));
break;
const PDFTilingPattern::PaintType paintType = static_cast<PDFTilingPattern::PaintType>(loader.readIntegerFromDictionary(patternDictionary, "PaintType", static_cast<PDFInteger>(PDFTilingPattern::PaintType::Invalid)));
const PDFTilingPattern::TilingType tilingType = static_cast<PDFTilingPattern::TilingType>(loader.readIntegerFromDictionary(patternDictionary, "TilingType", static_cast<PDFInteger>(PDFTilingPattern::TilingType::Invalid)));
const QRectF boundingBox = loader.readRectangle(patternDictionary->get("BBox"), QRectF());
const PDFReal xStep = loader.readNumberFromDictionary(patternDictionary, "XStep", 0.0);
const PDFReal yStep = loader.readNumberFromDictionary(patternDictionary, "YStep", 0.0);
PDFObject resources = document->getObject(patternDictionary->get("Resources"));
QMatrix matrix = loader.readMatrixFromDictionary(patternDictionary, "Matrix", QMatrix());
// Verify the data
if (paintType != PDFTilingPattern::PaintType::Colored && paintType != PDFTilingPattern::PaintType::Uncolored)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid tiling pattern - wrong paint type %1.").arg(static_cast<PDFInteger>(paintType)));
}
if (tilingType != PDFTilingPattern::TilingType::ConstantSpacing && tilingType != PDFTilingPattern::TilingType::NoDistortion && tilingType != PDFTilingPattern::TilingType::ConstantSpacingAndFasterTiling)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid tiling pattern - wrong tiling type %1.").arg(static_cast<PDFInteger>(tilingType)));
}
if (!boundingBox.isValid())
{
throw PDFParserException(PDFTranslationContext::tr("Invalid tiling pattern - bounding box is invalid.").arg(static_cast<PDFInteger>(paintType)));
}
if (isZero(xStep) || isZero(yStep))
{
throw PDFParserException(PDFTranslationContext::tr("Invalid tiling pattern - steps are invalid.").arg(static_cast<PDFInteger>(paintType)));
}
PDFTilingPattern* pattern = new PDFTilingPattern();
pattern->m_boundingBox = boundingBox;
pattern->m_matrix = matrix;
pattern->m_paintType = paintType;
pattern->m_tilingType = tilingType;
pattern->m_xStep = xStep;
pattern->m_yStep = yStep;
pattern->m_resources = resources;
pattern->m_content = qMove(streamData);
return PDFPatternPtr(pattern);
}
case PatternType::Shading:
@ -74,7 +119,7 @@ PDFPatternPtr PDFPattern::createPattern(const PDFDictionary* colorSpaceDictionar
throw PDFParserException(PDFTranslationContext::tr("Invalid pattern."));
}
return result;
return PDFPatternPtr();
}
throw PDFParserException(PDFTranslationContext::tr("Invalid pattern."));

View File

@ -29,6 +29,7 @@
namespace pdf
{
class PDFPattern;
class PDFTilingPattern;
class PDFShadingPattern;
using PDFPatternPtr = std::shared_ptr<PDFPattern>;
@ -193,6 +194,7 @@ public:
virtual PatternType getType() const = 0;
virtual const PDFShadingPattern* getShadingPattern() const = 0;
virtual const PDFTilingPattern* getTilingPattern() const = 0;
/// Returns bounding box in the shadings target coordinate system (not in
/// pattern coordinate system).
@ -233,7 +235,50 @@ public:
explicit PDFInvalidPattern() = default;
virtual PatternType getType() const { return PatternType::Invalid; }
virtual const PDFShadingPattern* getShadingPattern() const { return nullptr; }
virtual const PDFShadingPattern* getShadingPattern() const override { return nullptr; }
virtual const PDFTilingPattern* getTilingPattern() const override { return nullptr; }
};
class PDFTilingPattern : public PDFPattern
{
public:
explicit PDFTilingPattern() = default;
virtual PatternType getType() const override { return PatternType::Tiling; }
virtual const PDFShadingPattern* getShadingPattern() const override { return nullptr; }
virtual const PDFTilingPattern* getTilingPattern() const override { return this; }
enum class PaintType
{
Colored,
Uncolored,
Invalid
};
enum class TilingType
{
ConstantSpacing,
NoDistortion,
ConstantSpacingAndFasterTiling,
Invalid
};
PaintType getPaintingType() const { return m_paintType; }
TilingType getTilingType() const { return m_tilingType; }
PDFReal getXStep() const { return m_xStep; }
PDFReal getYStep() const { return m_yStep; }
const PDFObject& getResources() const { return m_resources; }
const QByteArray& getContent() const { return m_content; }
private:
friend class PDFPattern;
PaintType m_paintType = PaintType::Colored;
TilingType m_tilingType = TilingType::ConstantSpacing;
PDFReal m_xStep = 0.0;
PDFReal m_yStep = 0.0;
PDFObject m_resources;
QByteArray m_content;
};
/// Shading pattern - smooth color distribution along the pattern's space
@ -245,6 +290,7 @@ public:
virtual PatternType getType() const override;
virtual ShadingType getShadingType() const = 0;
virtual const PDFShadingPattern* getShadingPattern() const override { return this; }
virtual const PDFTilingPattern* getTilingPattern() const override { return nullptr; }
/// Creates a colored mesh using settings. Mesh is generated in device space
/// coordinate system. You must transform the mesh, if you want to