Precompiled pages

This commit is contained in:
Jakub Melka 2019-12-14 14:39:43 +01:00
parent 1b7fba2f78
commit 0277a9f059
5 changed files with 725 additions and 218 deletions

View File

@ -19,6 +19,7 @@
#include "pdfdrawspacecontroller.h"
#include "pdfdrawwidget.h"
#include "pdfrenderer.h"
#include "pdfpainter.h"
#include <QPainter>
@ -599,9 +600,27 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
// Clear the page space by white color
painter->fillRect(placedRect, Qt::white);
/*
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings);
QList<PDFRenderError> errors = renderer.render(painter, placedRect, item.pageIndex);
if (!errors.empty())
{
emit renderingError(item.pageIndex, errors);
}*/
PDFPrecompiledPage compiledPage;
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings);
renderer.compile(&compiledPage, item.pageIndex);
if (compiledPage.isValid())
{
const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex);
QMatrix matrix = renderer.createPagePointToDevicePointMatrix(page, placedRect);
compiledPage.draw(painter, page->getCropBox(), matrix, m_features);
}
const QList<PDFRenderError>& errors = compiledPage.getErrors();
if (!errors.empty())
{
emit renderingError(item.pageIndex, errors);

View File

@ -23,6 +23,210 @@
namespace pdf
{
PDFPainterBase::PDFPainterBase(PDFRenderer::Features features,
const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix pagePointToDevicePointMatrix,
const PDFMeshQualitySettings& meshQualitySettings) :
BaseClass(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings),
m_features(features)
{
}
void PDFPainterBase::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
{
const PDFPageContentProcessorState::StateFlags flags = state.getStateFlags();
// If current transformation matrix has changed, then update it
if (flags.testFlag(PDFPageContentProcessorState::StateCurrentTransformationMatrix))
{
setWorldMatrix(getCurrentWorldMatrix());
}
if (flags.testFlag(PDFPageContentProcessorState::StateStrokeColor) ||
flags.testFlag(PDFPageContentProcessorState::StateLineWidth) ||
flags.testFlag(PDFPageContentProcessorState::StateLineCapStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateLineJoinStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateMitterLimit) ||
flags.testFlag(PDFPageContentProcessorState::StateLineDashPattern) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaStroking))
{
m_currentPen.dirty();
}
if (flags.testFlag(PDFPageContentProcessorState::StateFillColor) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaFilling))
{
m_currentBrush.dirty();
}
// If current blend mode has changed, then update it
if (flags.testFlag(PDFPageContentProcessorState::StateBlendMode))
{
// Try to simulate transparency groups. Use only first composition mode,
// outside the transparency groups (so we are on pages main transparency
// groups).
const BlendMode blendMode = state.getBlendMode();
if (canSetBlendMode(blendMode))
{
if (!PDFBlendModeInfo::isSupportedByQt(blendMode))
{
reportRenderErrorOnce(RenderErrorType::NotSupported, PDFTranslationContext::tr("Blend mode '%1' not supported.").arg(PDFBlendModeInfo::getBlendModeName(blendMode)));
}
const QPainter::CompositionMode compositionMode = PDFBlendModeInfo::getCompositionModeFromBlendMode(blendMode);
setCompositionMode(compositionMode);
}
else if (blendMode != BlendMode::Normal && blendMode != BlendMode::Compatible)
{
reportRenderErrorOnce(RenderErrorType::NotSupported, PDFTranslationContext::tr("Blend mode '%1' is in transparency group, which is not supported.").arg(PDFBlendModeInfo::getBlendModeName(blendMode)));
}
}
BaseClass::performUpdateGraphicsState(state);
}
bool PDFPainterBase::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd)
{
if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent))
{
return false;
}
return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd);
}
QPen PDFPainterBase::getCurrentPenImpl() const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
QColor color = graphicState->getStrokeColor();
if (color.isValid())
{
color.setAlphaF(getEffectiveStrokingAlpha());
const PDFReal lineWidth = graphicState->getLineWidth();
Qt::PenCapStyle penCapStyle = graphicState->getLineCapStyle();
Qt::PenJoinStyle penJoinStyle = graphicState->getLineJoinStyle();
const PDFLineDashPattern& lineDashPattern = graphicState->getLineDashPattern();
const PDFReal mitterLimit = graphicState->getMitterLimit();
QPen pen(color);
pen.setWidthF(lineWidth);
pen.setCapStyle(penCapStyle);
pen.setJoinStyle(penJoinStyle);
pen.setMiterLimit(mitterLimit);
if (lineDashPattern.isSolid())
{
pen.setStyle(Qt::SolidLine);
}
else
{
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(QVector<PDFReal>::fromStdVector(lineDashPattern.getDashArray()));
pen.setDashOffset(lineDashPattern.getDashOffset());
}
return pen;
}
else
{
return QPen(Qt::NoPen);
}
}
QBrush PDFPainterBase::getCurrentBrushImpl() const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
QColor color = graphicState->getFillColor();
if (color.isValid())
{
color.setAlphaF(getEffectiveFillingAlpha());
return QBrush(color, Qt::SolidPattern);
}
else
{
return QBrush(Qt::NoBrush);
}
}
PDFReal PDFPainterBase::getEffectiveStrokingAlpha() const
{
PDFReal alpha = getGraphicState()->getAlphaStroking();
auto it = m_transparencyGroupDataStack.crbegin();
auto itEnd = m_transparencyGroupDataStack.crend();
for (; it != itEnd; ++it)
{
const PDFTransparencyGroupPainterData& transparencyGroup = *it;
alpha *= transparencyGroup.alphaStroke;
if (transparencyGroup.group.isolated)
{
break;
}
}
return alpha;
}
PDFReal PDFPainterBase::getEffectiveFillingAlpha() const
{
PDFReal alpha = getGraphicState()->getAlphaFilling();
auto it = m_transparencyGroupDataStack.crbegin();
auto itEnd = m_transparencyGroupDataStack.crend();
for (; it != itEnd; ++it)
{
const PDFTransparencyGroupPainterData& transparencyGroup = *it;
alpha *= transparencyGroup.alphaFill;
if (transparencyGroup.group.isolated)
{
break;
}
}
return alpha;
}
bool PDFPainterBase::canSetBlendMode(BlendMode mode) const
{
// We will assume, that we can set blend mode, when
// all other blend modes on transparency stack are normal,
// or compatible. It should work.
Q_UNUSED(mode);
return std::all_of(m_transparencyGroupDataStack.cbegin(), m_transparencyGroupDataStack.cend(), [](const PDFTransparencyGroupPainterData& group) { return group.blendMode == BlendMode::Normal || group.blendMode == BlendMode::Compatible; });
}
void PDFPainterBase::performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup)
{
if (order == ProcessOrder::BeforeOperation)
{
PDFTransparencyGroupPainterData data;
data.group = transparencyGroup;
data.alphaFill = getGraphicState()->getAlphaFilling();
data.alphaStroke = getGraphicState()->getAlphaStroking();
data.blendMode = getGraphicState()->getBlendMode();
m_transparencyGroupDataStack.emplace_back(qMove(data));
}
}
void PDFPainterBase::performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup)
{
Q_UNUSED(transparencyGroup);
if (order == ProcessOrder::AfterOperation)
{
m_transparencyGroupDataStack.pop_back();
}
}
PDFPainter::PDFPainter(QPainter* painter,
PDFRenderer::Features features,
QMatrix pagePointToDevicePointMatrix,
@ -31,9 +235,8 @@ PDFPainter::PDFPainter(QPainter* painter,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFMeshQualitySettings& meshQualitySettings) :
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings),
m_painter(painter),
m_features(features)
BaseClass(features, page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings),
m_painter(painter)
{
Q_ASSERT(painter);
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
@ -65,7 +268,7 @@ void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool
Q_ASSERT(stroke || fill);
// Set antialiasing
const bool antialiasing = (text && m_features.testFlag(PDFRenderer::TextAntialiasing)) || (!text && m_features.testFlag(PDFRenderer::Antialiasing));
const bool antialiasing = (text && hasFeature(PDFRenderer::TextAntialiasing)) || (!text && hasFeature(PDFRenderer::Antialiasing));
m_painter->setRenderHint(QPainter::Antialiasing, antialiasing);
if (stroke)
@ -108,7 +311,7 @@ void PDFPainter::performImagePainting(const QImage& image)
QImage adjustedImage = image;
if (m_features.testFlag(PDFRenderer::SmoothImages))
if (hasFeature(PDFRenderer::SmoothImages))
{
// Test, if we can use smooth images. We can use them under following conditions:
// 1) Transformed rectangle is not skewed or deformed (so vectors (0, 1) and (1, 0) are orthogonal)
@ -158,61 +361,6 @@ void PDFPainter::performMeshPainting(const PDFMesh& mesh)
m_painter->restore();
}
void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
{
const PDFPageContentProcessorState::StateFlags flags = state.getStateFlags();
// If current transformation matrix has changed, then update it
if (flags.testFlag(PDFPageContentProcessorState::StateCurrentTransformationMatrix))
{
m_painter->setWorldMatrix(getCurrentWorldMatrix(), false);
}
if (flags.testFlag(PDFPageContentProcessorState::StateStrokeColor) ||
flags.testFlag(PDFPageContentProcessorState::StateLineWidth) ||
flags.testFlag(PDFPageContentProcessorState::StateLineCapStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateLineJoinStyle) ||
flags.testFlag(PDFPageContentProcessorState::StateMitterLimit) ||
flags.testFlag(PDFPageContentProcessorState::StateLineDashPattern) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaStroking))
{
m_currentPen.dirty();
}
if (flags.testFlag(PDFPageContentProcessorState::StateFillColor) ||
flags.testFlag(PDFPageContentProcessorState::StateAlphaFilling))
{
m_currentBrush.dirty();
}
// If current blend mode has changed, then update it
if (flags.testFlag(PDFPageContentProcessorState::StateBlendMode))
{
// Try to simulate transparency groups. Use only first composition mode,
// outside the transparency groups (so we are on pages main transparency
// groups).
const BlendMode blendMode = state.getBlendMode();
if (canSetBlendMode(blendMode))
{
const QPainter::CompositionMode compositionMode = PDFBlendModeInfo::getCompositionModeFromBlendMode(blendMode);
if (!PDFBlendModeInfo::isSupportedByQt(blendMode))
{
reportRenderErrorOnce(RenderErrorType::NotSupported, PDFTranslationContext::tr("Blend mode '%1' not supported.").arg(PDFBlendModeInfo::getBlendModeName(blendMode)));
}
m_painter->setCompositionMode(compositionMode);
}
else if (blendMode != BlendMode::Normal && blendMode != BlendMode::Compatible)
{
reportRenderErrorOnce(RenderErrorType::NotSupported, PDFTranslationContext::tr("Blend mode '%1' is in transparency group, which is not supported.").arg(PDFBlendModeInfo::getBlendModeName(blendMode)));
}
}
PDFPageContentProcessor::performUpdateGraphicsState(state);
}
void PDFPainter::performSaveGraphicState(ProcessOrder order)
{
if (order == ProcessOrder::AfterOperation)
@ -229,141 +377,242 @@ void PDFPainter::performRestoreGraphicState(ProcessOrder order)
}
}
void PDFPainter::performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup)
void PDFPainter::setWorldMatrix(const QMatrix& matrix)
{
m_painter->setWorldMatrix(matrix, false);
}
void PDFPainter::setCompositionMode(QPainter::CompositionMode mode)
{
m_painter->setCompositionMode(mode);
}
void PDFPrecompiledPageGenerator::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
Q_ASSERT(stroke || fill);
Q_ASSERT(path.fillRule() == fillRule);
QPen pen = stroke ? getCurrentPen() : QPen(Qt::NoPen);
QBrush brush = fill ? getCurrentBrush() : QBrush(Qt::NoBrush);
m_precompiledPage->addPath(qMove(pen), qMove(brush), path, text);
}
void PDFPrecompiledPageGenerator::performClipping(const QPainterPath& path, Qt::FillRule fillRule)
{
Q_ASSERT(path.fillRule() == fillRule);
m_precompiledPage->addClip(path);
}
void PDFPrecompiledPageGenerator::performImagePainting(const QImage& image)
{
if (isContentSuppressed())
{
// Content is suppressed, do not paint anything
return;
}
m_precompiledPage->addImage(image);
}
void PDFPrecompiledPageGenerator::performMeshPainting(const PDFMesh& mesh)
{
m_precompiledPage->addMesh(mesh, getEffectiveFillingAlpha());
}
void PDFPrecompiledPageGenerator::performSaveGraphicState(PDFPageContentProcessor::ProcessOrder order)
{
if (order == ProcessOrder::AfterOperation)
{
m_precompiledPage->addSaveGraphicState();
}
}
void PDFPrecompiledPageGenerator::performRestoreGraphicState(PDFPageContentProcessor::ProcessOrder order)
{
if (order == ProcessOrder::BeforeOperation)
{
PDFTransparencyGroupPainterData data;
data.group = transparencyGroup;
data.alphaFill = getGraphicState()->getAlphaFilling();
data.alphaStroke = getGraphicState()->getAlphaStroking();
data.blendMode = getGraphicState()->getBlendMode();
m_transparencyGroupDataStack.emplace_back(qMove(data));
m_precompiledPage->addRestoreGraphicState();
}
}
void PDFPainter::performEndTransparencyGroup(PDFPageContentProcessor::ProcessOrder order, const PDFPageContentProcessor::PDFTransparencyGroup& transparencyGroup)
void PDFPrecompiledPageGenerator::setWorldMatrix(const QMatrix& matrix)
{
Q_UNUSED(transparencyGroup);
if (order == ProcessOrder::AfterOperation)
{
m_transparencyGroupDataStack.pop_back();
}
m_precompiledPage->addSetWorldMatrix(matrix);
}
bool PDFPainter::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd)
void PDFPrecompiledPageGenerator::setCompositionMode(QPainter::CompositionMode mode)
{
if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent))
{
return false;
}
return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd);
m_precompiledPage->addSetCompositionMode(mode);
}
QPen PDFPainter::getCurrentPenImpl() const
void PDFPrecompiledPage::draw(QPainter* painter, const QRectF& cropBox, const QMatrix& pagePointToDevicePointMatrix, PDFRenderer::Features features) const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
QColor color = graphicState->getStrokeColor();
if (color.isValid())
Q_ASSERT(painter);
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
painter->save();
if (features.testFlag(PDFRenderer::ClipToCropBox))
{
color.setAlphaF(getEffectiveStrokingAlpha());
const PDFReal lineWidth = graphicState->getLineWidth();
Qt::PenCapStyle penCapStyle = graphicState->getLineCapStyle();
Qt::PenJoinStyle penJoinStyle = graphicState->getLineJoinStyle();
const PDFLineDashPattern& lineDashPattern = graphicState->getLineDashPattern();
const PDFReal mitterLimit = graphicState->getMitterLimit();
QPen pen(color);
pen.setWidthF(lineWidth);
pen.setCapStyle(penCapStyle);
pen.setJoinStyle(penJoinStyle);
pen.setMiterLimit(mitterLimit);
if (lineDashPattern.isSolid())
if (cropBox.isValid())
{
pen.setStyle(Qt::SolidLine);
}
else
{
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(QVector<PDFReal>::fromStdVector(lineDashPattern.getDashArray()));
pen.setDashOffset(lineDashPattern.getDashOffset());
}
return pen;
}
else
{
return QPen(Qt::NoPen);
}
}
QBrush PDFPainter::getCurrentBrushImpl() const
{
const PDFPageContentProcessorState* graphicState = getGraphicState();
QColor color = graphicState->getFillColor();
if (color.isValid())
{
color.setAlphaF(getEffectiveFillingAlpha());
return QBrush(color, Qt::SolidPattern);
}
else
{
return QBrush(Qt::NoBrush);
}
}
PDFReal PDFPainter::getEffectiveStrokingAlpha() const
{
PDFReal alpha = getGraphicState()->getAlphaStroking();
auto it = m_transparencyGroupDataStack.crbegin();
auto itEnd = m_transparencyGroupDataStack.crend();
for (; it != itEnd; ++it)
{
const PDFTransparencyGroupPainterData& transparencyGroup = *it;
alpha *= transparencyGroup.alphaStroke;
if (transparencyGroup.group.isolated)
{
break;
QPainterPath path;
path.addPolygon(pagePointToDevicePointMatrix.map(cropBox));
painter->setClipPath(path, Qt::IntersectClip);
}
}
return alpha;
}
painter->setRenderHint(QPainter::SmoothPixmapTransform, features.testFlag(PDFRenderer::SmoothImages));
PDFReal PDFPainter::getEffectiveFillingAlpha() const
{
PDFReal alpha = getGraphicState()->getAlphaFilling();
auto it = m_transparencyGroupDataStack.crbegin();
auto itEnd = m_transparencyGroupDataStack.crend();
for (; it != itEnd; ++it)
// Process all instructions
for (const Instruction& instruction : m_instructions)
{
const PDFTransparencyGroupPainterData& transparencyGroup = *it;
alpha *= transparencyGroup.alphaFill;
if (transparencyGroup.group.isolated)
switch (instruction.type)
{
break;
case InstructionType::DrawPath:
{
const PathPaintData& data = m_paths[instruction.dataIndex];
// Set antialiasing
const bool antialiasing = (data.isText && features.testFlag(PDFRenderer::TextAntialiasing)) || (!data.isText && features.testFlag(PDFRenderer::Antialiasing));
painter->setRenderHint(QPainter::Antialiasing, antialiasing);
painter->setPen(data.pen);
painter->setBrush(data.brush);
painter->drawPath(data.path);
break;
}
case InstructionType::DrawImage:
{
const ImageData& data = m_images[instruction.dataIndex];
const QImage& image = data.image;
painter->save();
QMatrix imageTransform(1.0 / image.width(), 0, 0, 1.0 / image.height(), 0, 0);
QMatrix worldMatrix = imageTransform * painter->worldMatrix();
// Jakub Melka: Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
// to the opposite (so the image is then unchanged)
worldMatrix.translate(0, image.height());
worldMatrix.scale(1, -1);
painter->setWorldMatrix(worldMatrix);
painter->drawImage(0, 0, image);
painter->restore();
break;
}
case InstructionType::DrawMesh:
{
const MeshPaintData& data = m_meshes[instruction.dataIndex];
painter->save();
painter->setWorldMatrix(pagePointToDevicePointMatrix);
data.mesh.paint(painter, data.alpha);
painter->restore();
break;
}
case InstructionType::Clip:
{
painter->setClipPath(m_clips[instruction.dataIndex].clipPath, Qt::IntersectClip);
break;
}
case InstructionType::SaveGraphicState:
{
painter->save();
break;
}
case InstructionType::RestoreGraphicState:
{
painter->restore();
break;
}
case InstructionType::SetWorldMatrix:
{
painter->setWorldMatrix(m_matrices[instruction.dataIndex] * pagePointToDevicePointMatrix);
break;
}
case InstructionType::SetCompositionMode:
{
painter->setCompositionMode(m_compositionModes[instruction.dataIndex]);
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
}
return alpha;
painter->restore();
}
bool PDFPainter::canSetBlendMode(BlendMode mode) const
void PDFPrecompiledPage::addPath(QPen pen, QBrush brush, QPainterPath path, bool isText)
{
// We will assume, that we can set blend mode, when
// all other blend modes on transparency stack are normal,
// or compatible. It should work.
m_instructions.emplace_back(InstructionType::DrawPath, m_paths.size());
m_paths.emplace_back(qMove(pen), qMove(brush), qMove(path), isText);
}
Q_UNUSED(mode);
return std::all_of(m_transparencyGroupDataStack.cbegin(), m_transparencyGroupDataStack.cend(), [](const PDFTransparencyGroupPainterData& group) { return group.blendMode == BlendMode::Normal || group.blendMode == BlendMode::Compatible; });
void PDFPrecompiledPage::addClip(QPainterPath path)
{
m_instructions.emplace_back(InstructionType::Clip, m_clips.size());
m_clips.emplace_back(qMove(path));
}
void PDFPrecompiledPage::addImage(QImage image)
{
// Convert the image into format Format_ARGB32_Premultiplied for fast drawing.
// If this format is used, then no image conversion is performed while drawing.
if (image.format() != QImage::Format_ARGB32_Premultiplied)
{
image.convertTo(QImage::Format_ARGB32_Premultiplied);
}
m_instructions.emplace_back(InstructionType::DrawImage, m_images.size());
m_images.emplace_back(qMove(image));
}
void PDFPrecompiledPage::addMesh(PDFMesh mesh, PDFReal alpha)
{
m_instructions.emplace_back(InstructionType::DrawMesh, m_meshes.size());
m_meshes.emplace_back(qMove(mesh), alpha);
}
void PDFPrecompiledPage::addSetWorldMatrix(const QMatrix& matrix)
{
m_instructions.emplace_back(InstructionType::SetWorldMatrix, m_matrices.size());
m_matrices.push_back(matrix);
}
void PDFPrecompiledPage::addSetCompositionMode(QPainter::CompositionMode compositionMode)
{
m_instructions.emplace_back(InstructionType::SetCompositionMode, m_compositionModes.size());
m_compositionModes.push_back(compositionMode);
}
void PDFPrecompiledPage::optimize()
{
m_instructions.shrink_to_fit();
m_paths.shrink_to_fit();
m_clips.shrink_to_fit();
m_images.shrink_to_fit();
m_meshes.shrink_to_fit();
m_matrices.shrink_to_fit();
m_compositionModes.shrink_to_fit();
}
void PDFPrecompiledPage::finalize(qint64 compilingTimeNS, QList<PDFRenderError> errors)
{
m_compilingTimeNS = compilingTimeNS;
m_errors = qMove(errors);
}
} // namespace pdf

View File

@ -19,6 +19,7 @@
#define PDFPAINTER_H
#include "pdfutils.h"
#include "pdfpattern.h"
#include "pdfrenderer.h"
#include "pdfpagecontentprocessor.h"
@ -28,12 +29,76 @@
namespace pdf
{
/// Base painter, encapsulating common functionality for all PDF painters (for example,
/// direct painter, or painter, which generates list of graphic commands).
class PDFPainterBase : public PDFPageContentProcessor
{
using BaseClass = PDFPageContentProcessor;
public:
explicit PDFPainterBase(PDFRenderer::Features features,
const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity,
QMatrix pagePointToDevicePointMatrix,
const PDFMeshQualitySettings& meshQualitySettings);
protected:
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override;
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override;
virtual void setWorldMatrix(const QMatrix& matrix) = 0;
virtual void setCompositionMode(QPainter::CompositionMode mode) = 0;
/// Returns current pen
const QPen& getCurrentPen() { return m_currentPen.get(this, &PDFPainterBase::getCurrentPenImpl); }
/// Returns current brush
const QBrush& getCurrentBrush() { return m_currentBrush.get(this, &PDFPainterBase::getCurrentBrushImpl); }
/// Returns effective stroking alpha from transparency groups and current graphic state
PDFReal getEffectiveStrokingAlpha() const;
/// Returns effective filling alpha from transparency groups and current graphic state
PDFReal getEffectiveFillingAlpha() const;
/// Returns true, if blend mode can be set according the transparency group stack
bool canSetBlendMode(BlendMode mode) const;
/// Returns, if feature is turned on
bool hasFeature(PDFRenderer::Feature feature) const { return m_features.testFlag(feature); }
private:
/// Returns current pen (implementation)
QPen getCurrentPenImpl() const;
/// Returns current brush (implementation)
QBrush getCurrentBrushImpl() const;
struct PDFTransparencyGroupPainterData
{
PDFTransparencyGroup group;
PDFReal alphaStroke = 1.0;
PDFReal alphaFill = 1.0;
BlendMode blendMode = BlendMode::Normal;
};
PDFRenderer::Features m_features;
PDFCachedItem<QPen> m_currentPen;
PDFCachedItem<QBrush> m_currentBrush;
std::vector<PDFTransparencyGroupPainterData> m_transparencyGroupDataStack;
};
/// Processor, which processes PDF's page commands on the QPainter. It works with QPainter
/// and with transformation matrix, which translates page points to the device points.
/// Only basic transparency is supported, advanced transparency, such as transparency groups,
/// are not supported. Painter will try to emulate them so painting will not fail completely.
class PDFPainter : public PDFPageContentProcessor
class PDFPainter : public PDFPainterBase
{
using BaseClass = PDFPainterBase;
public:
/// Constructs new PDFPainter object, with default parameters.
/// \param painter Painter, on which page content is drawn
@ -59,48 +124,182 @@ protected:
virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule) override;
virtual void performImagePainting(const QImage& image) override;
virtual void performMeshPainting(const PDFMesh& mesh) override;
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override;
virtual void performSaveGraphicState(ProcessOrder order) override;
virtual void performRestoreGraphicState(ProcessOrder order) override;
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override;
virtual void setWorldMatrix(const QMatrix& matrix) override;
virtual void setCompositionMode(QPainter::CompositionMode mode) override;
private:
/// Returns current pen
const QPen& getCurrentPen() { return m_currentPen.get(this, &PDFPainter::getCurrentPenImpl); }
QPainter* m_painter;
};
/// Returns current brush
const QBrush& getCurrentBrush() { return m_currentBrush.get(this, &PDFPainter::getCurrentBrushImpl); }
/// Precompiled page contains precompiled graphic instructions of a PDF page to draw it quickly
/// on the target painter. It enables very fast drawing, because instructions are not decoded
/// and interpreted from the PDF stream, but they are just "played" on the painter.
class PDFPrecompiledPage
{
public:
/// Returns current pen (implementation)
QPen getCurrentPenImpl() const;
/// Returns current brush (implementation)
QBrush getCurrentBrushImpl() const;
/// Returns effective stroking alpha from transparency groups and current graphic state
PDFReal getEffectiveStrokingAlpha() const;
/// Returns effective filling alpha from transparency groups and current graphic state
PDFReal getEffectiveFillingAlpha() const;
/// Returns true, if blend mode can be set according the transparency group stack
bool canSetBlendMode(BlendMode mode) const;
struct PDFTransparencyGroupPainterData
enum class InstructionType
{
PDFTransparencyGroup group;
PDFReal alphaStroke = 1.0;
PDFReal alphaFill = 1.0;
BlendMode blendMode = BlendMode::Normal;
Invalid,
DrawPath,
DrawImage,
DrawMesh,
Clip,
SaveGraphicState,
RestoreGraphicState,
SetWorldMatrix,
SetCompositionMode
};
QPainter* m_painter;
PDFRenderer::Features m_features;
PDFCachedItem<QPen> m_currentPen;
PDFCachedItem<QBrush> m_currentBrush;
std::vector<PDFTransparencyGroupPainterData> m_transparencyGroupDataStack;
struct Instruction
{
inline Instruction() = default;
inline Instruction(InstructionType type, size_t dataIndex) :
type(type),
dataIndex(dataIndex)
{
}
InstructionType type = InstructionType::Invalid;
size_t dataIndex = 0;
};
/// Paints page onto the painter using matrix
/// \param painter Painter, onto which is page drawn
/// \param cropBox Page's crop box
/// \param pagePointToDevicePointMatrix Page point to device point transformation matrix
/// \param features Renderer features
void draw(QPainter* painter, const QRectF& cropBox, const QMatrix& pagePointToDevicePointMatrix, PDFRenderer::Features features) const;
void addPath(QPen pen, QBrush brush, QPainterPath path, bool isText);
void addClip(QPainterPath path);
void addImage(QImage image);
void addMesh(PDFMesh mesh, PDFReal alpha);
void addSaveGraphicState() { m_instructions.emplace_back(InstructionType::SaveGraphicState, 0); }
void addRestoreGraphicState() { m_instructions.emplace_back(InstructionType::RestoreGraphicState, 0); }
void addSetWorldMatrix(const QMatrix& matrix);
void addSetCompositionMode(QPainter::CompositionMode compositionMode);
/// Optimizes page memory allocation to contain less space
void optimize();
/// Finalizes precompiled page
/// \param compilingTimeNS Compiling time in nanoseconds
/// \param errors List of rendering errors
void finalize(qint64 compilingTimeNS, QList<PDFRenderError> errors);
/// Returns compiling time in nanoseconds
qint64 getCompilingTimeNS() const { return m_compilingTimeNS; }
/// Returns a list of rendering errors
const QList<PDFRenderError>& getErrors() const { return m_errors; }
/// Returns true, if page is valid (i.e. has nonzero instruction count)
bool isValid() const { return !m_instructions.empty(); }
private:
struct PathPaintData
{
inline PathPaintData() = default;
inline PathPaintData(QPen pen, QBrush brush, QPainterPath path, bool isText) :
pen(qMove(pen)),
brush(qMove(brush)),
path(qMove(path)),
isText(isText)
{
}
QPen pen;
QBrush brush;
QPainterPath path;
bool isText = false;
};
struct ClipData
{
inline ClipData() = default;
inline ClipData(QPainterPath path) :
clipPath(qMove(path))
{
}
QPainterPath clipPath;
};
struct ImageData
{
inline ImageData() = default;
inline ImageData(QImage image) :
image(qMove(image))
{
}
QImage image;
};
struct MeshPaintData
{
inline MeshPaintData() = default;
inline MeshPaintData(PDFMesh mesh, PDFReal alpha) :
mesh(qMove(mesh)),
alpha(alpha)
{
}
PDFMesh mesh;
PDFReal alpha = 1.0;
};
qint64 m_compilingTimeNS = 0;
std::vector<Instruction> m_instructions;
std::vector<PathPaintData> m_paths;
std::vector<ClipData> m_clips;
std::vector<ImageData> m_images;
std::vector<MeshPaintData> m_meshes;
std::vector<QMatrix> m_matrices;
std::vector<QPainter::CompositionMode> m_compositionModes;
QList<PDFRenderError> m_errors;
};
/// Processor, which processes PDF's page commands and writes them to the precompiled page.
/// Precompiled page then can be used to execute these commands on QPainter.
class PDFPrecompiledPageGenerator : public PDFPainterBase
{
using BaseClass = PDFPainterBase;
public:
explicit inline PDFPrecompiledPageGenerator(PDFPrecompiledPage* precompiledPage,
PDFRenderer::Features features,
const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFMeshQualitySettings& meshQualitySettings) :
BaseClass(features, page, document, fontCache, optionalContentActivity, QMatrix(), meshQualitySettings),
m_precompiledPage(precompiledPage)
{
}
protected:
virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) override;
virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule) override;
virtual void performImagePainting(const QImage& image) override;
virtual void performMeshPainting(const PDFMesh& mesh) override;
virtual void performSaveGraphicState(ProcessOrder order) override;
virtual void performRestoreGraphicState(ProcessOrder order) override;
virtual void setWorldMatrix(const QMatrix& matrix) override;
virtual void setCompositionMode(QPainter::CompositionMode mode) override;
private:
PDFPrecompiledPage* m_precompiledPage;
};
} // namespace pdf

View File

@ -19,6 +19,8 @@
#include "pdfpainter.h"
#include "pdfdocument.h"
#include <QElapsedTimer>
namespace pdf
{
@ -36,21 +38,8 @@ PDFRenderer::PDFRenderer(const PDFDocument* document,
Q_ASSERT(document);
}
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const
QMatrix PDFRenderer::createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const
{
Q_UNUSED(painter);
Q_UNUSED(rectangle);
const PDFCatalog* catalog = m_document->getCatalog();
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
{
// Invalid page index
return { PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 doesn't exist.").arg(pageIndex + 1)) };
}
const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page);
QRectF mediaBox = page->getRotatedMediaBox();
QMatrix matrix;
@ -94,14 +83,29 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& recta
}
}
return matrix;
}
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const
{
const PDFCatalog* catalog = m_document->getCatalog();
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
{
// Invalid page index
return { PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 doesn't exist.").arg(pageIndex + 1)) };
}
const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page);
QMatrix matrix = createPagePointToDevicePointMatrix(page, rectangle);
PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_optionalContentActivity, m_meshQualitySettings);
return processor.processContents();
}
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matrix, size_t pageIndex) const
{
Q_UNUSED(painter);
const PDFCatalog* catalog = m_document->getCatalog();
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
{
@ -116,4 +120,27 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matr
return processor.processContents();
}
void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex) const
{
const PDFCatalog* catalog = m_document->getCatalog();
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
{
// Invalid page index
precompiledPage->finalize(0, { PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 doesn't exist.").arg(pageIndex + 1)) });
return;
}
const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page);
QElapsedTimer timer;
timer.start();
PDFPrecompiledPageGenerator generator(precompiledPage, m_features, page, m_document, m_fontCache, m_optionalContentActivity, m_meshQualitySettings);
QList<PDFRenderError> errors = generator.processContents();
precompiledPage->optimize();
precompiledPage->finalize(timer.nsecsElapsed(), qMove(errors));
timer.invalidate();
}
} // namespace pdf

View File

@ -27,6 +27,7 @@ class QPainter;
namespace pdf
{
class PDFFontCache;
class PDFPrecompiledPage;
class PDFOptionalContentActivity;
/// Renders the PDF page on the painter, or onto an image.
@ -60,6 +61,19 @@ public:
/// Rendering errors are reported and returned in the error list. If no error occured, empty list is returned.
QList<PDFRenderError> render(QPainter* painter, const QMatrix& matrix, size_t pageIndex) const;
/// Compiles page (i.e. prepares compiled page). \p page should be empty page, onto which
/// are graphics commands written. No exception is thrown. Rendering errors are reported and written
/// to the compiled page.
/// \param precompiledPage Precompiled page pointer
/// \param pageIndex Index of page to be compiled
void compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex) const;
/// Creates page point to device point matrix for the given rectangle. It creates transformation
/// from page's media box to the target rectangle.
/// \param page Page, for which we want to create matrix
/// \param rectangle Page rectangle, to which is page media box transformed
QMatrix createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const;
/// Returns default renderer features
static constexpr Features getDefaultFeatures() { return Antialiasing | TextAntialiasing | ClipToCropBox; }
@ -71,7 +85,6 @@ private:
PDFMeshQualitySettings m_meshQualitySettings;
};
} // namespace pdf
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)