mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Precompiled pages
This commit is contained in:
@ -19,6 +19,7 @@
|
|||||||
#include "pdfdrawspacecontroller.h"
|
#include "pdfdrawspacecontroller.h"
|
||||||
#include "pdfdrawwidget.h"
|
#include "pdfdrawwidget.h"
|
||||||
#include "pdfrenderer.h"
|
#include "pdfrenderer.h"
|
||||||
|
#include "pdfpainter.h"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
@ -599,9 +600,27 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
|
|||||||
// Clear the page space by white color
|
// Clear the page space by white color
|
||||||
painter->fillRect(placedRect, Qt::white);
|
painter->fillRect(placedRect, Qt::white);
|
||||||
|
|
||||||
|
/*
|
||||||
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings);
|
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity(), m_features, m_meshQualitySettings);
|
||||||
QList<PDFRenderError> errors = renderer.render(painter, placedRect, item.pageIndex);
|
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())
|
if (!errors.empty())
|
||||||
{
|
{
|
||||||
emit renderingError(item.pageIndex, errors);
|
emit renderingError(item.pageIndex, errors);
|
||||||
|
@ -23,6 +23,210 @@
|
|||||||
namespace pdf
|
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,
|
PDFPainter::PDFPainter(QPainter* painter,
|
||||||
PDFRenderer::Features features,
|
PDFRenderer::Features features,
|
||||||
QMatrix pagePointToDevicePointMatrix,
|
QMatrix pagePointToDevicePointMatrix,
|
||||||
@ -31,9 +235,8 @@ PDFPainter::PDFPainter(QPainter* painter,
|
|||||||
const PDFFontCache* fontCache,
|
const PDFFontCache* fontCache,
|
||||||
const PDFOptionalContentActivity* optionalContentActivity,
|
const PDFOptionalContentActivity* optionalContentActivity,
|
||||||
const PDFMeshQualitySettings& meshQualitySettings) :
|
const PDFMeshQualitySettings& meshQualitySettings) :
|
||||||
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings),
|
BaseClass(features, page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings),
|
||||||
m_painter(painter),
|
m_painter(painter)
|
||||||
m_features(features)
|
|
||||||
{
|
{
|
||||||
Q_ASSERT(painter);
|
Q_ASSERT(painter);
|
||||||
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
||||||
@ -65,7 +268,7 @@ void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool
|
|||||||
Q_ASSERT(stroke || fill);
|
Q_ASSERT(stroke || fill);
|
||||||
|
|
||||||
// Set antialiasing
|
// 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);
|
m_painter->setRenderHint(QPainter::Antialiasing, antialiasing);
|
||||||
|
|
||||||
if (stroke)
|
if (stroke)
|
||||||
@ -108,7 +311,7 @@ void PDFPainter::performImagePainting(const QImage& image)
|
|||||||
|
|
||||||
QImage adjustedImage = 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:
|
// 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)
|
// 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();
|
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)
|
void PDFPainter::performSaveGraphicState(ProcessOrder order)
|
||||||
{
|
{
|
||||||
if (order == ProcessOrder::AfterOperation)
|
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)
|
if (order == ProcessOrder::BeforeOperation)
|
||||||
{
|
{
|
||||||
PDFTransparencyGroupPainterData data;
|
m_precompiledPage->addRestoreGraphicState();
|
||||||
data.group = transparencyGroup;
|
|
||||||
data.alphaFill = getGraphicState()->getAlphaFilling();
|
|
||||||
data.alphaStroke = getGraphicState()->getAlphaStroking();
|
|
||||||
data.blendMode = getGraphicState()->getBlendMode();
|
|
||||||
m_transparencyGroupDataStack.emplace_back(qMove(data));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PDFPainter::performEndTransparencyGroup(PDFPageContentProcessor::ProcessOrder order, const PDFPageContentProcessor::PDFTransparencyGroup& transparencyGroup)
|
void PDFPrecompiledPageGenerator::setWorldMatrix(const QMatrix& matrix)
|
||||||
{
|
{
|
||||||
Q_UNUSED(transparencyGroup);
|
m_precompiledPage->addSetWorldMatrix(matrix);
|
||||||
|
|
||||||
if (order == ProcessOrder::AfterOperation)
|
|
||||||
{
|
|
||||||
m_transparencyGroupDataStack.pop_back();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PDFPainter::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd)
|
void PDFPrecompiledPageGenerator::setCompositionMode(QPainter::CompositionMode mode)
|
||||||
{
|
{
|
||||||
if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent))
|
m_precompiledPage->addSetCompositionMode(mode);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPen PDFPainter::getCurrentPenImpl() const
|
void PDFPrecompiledPage::draw(QPainter* painter, const QRectF& cropBox, const QMatrix& pagePointToDevicePointMatrix, PDFRenderer::Features features) const
|
||||||
{
|
{
|
||||||
const PDFPageContentProcessorState* graphicState = getGraphicState();
|
Q_ASSERT(painter);
|
||||||
QColor color = graphicState->getStrokeColor();
|
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
||||||
if (color.isValid())
|
|
||||||
|
painter->save();
|
||||||
|
|
||||||
|
if (features.testFlag(PDFRenderer::ClipToCropBox))
|
||||||
{
|
{
|
||||||
color.setAlphaF(getEffectiveStrokingAlpha());
|
if (cropBox.isValid())
|
||||||
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);
|
QPainterPath path;
|
||||||
}
|
path.addPolygon(pagePointToDevicePointMatrix.map(cropBox));
|
||||||
else
|
painter->setClipPath(path, Qt::IntersectClip);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return alpha;
|
painter->setRenderHint(QPainter::SmoothPixmapTransform, features.testFlag(PDFRenderer::SmoothImages));
|
||||||
}
|
|
||||||
|
|
||||||
PDFReal PDFPainter::getEffectiveFillingAlpha() const
|
// Process all instructions
|
||||||
{
|
for (const Instruction& instruction : m_instructions)
|
||||||
PDFReal alpha = getGraphicState()->getAlphaFilling();
|
|
||||||
|
|
||||||
auto it = m_transparencyGroupDataStack.crbegin();
|
|
||||||
auto itEnd = m_transparencyGroupDataStack.crend();
|
|
||||||
for (; it != itEnd; ++it)
|
|
||||||
{
|
{
|
||||||
const PDFTransparencyGroupPainterData& transparencyGroup = *it;
|
switch (instruction.type)
|
||||||
alpha *= transparencyGroup.alphaFill;
|
|
||||||
|
|
||||||
if (transparencyGroup.group.isolated)
|
|
||||||
{
|
{
|
||||||
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
|
m_instructions.emplace_back(InstructionType::DrawPath, m_paths.size());
|
||||||
// all other blend modes on transparency stack are normal,
|
m_paths.emplace_back(qMove(pen), qMove(brush), qMove(path), isText);
|
||||||
// or compatible. It should work.
|
}
|
||||||
|
|
||||||
Q_UNUSED(mode);
|
void PDFPrecompiledPage::addClip(QPainterPath path)
|
||||||
return std::all_of(m_transparencyGroupDataStack.cbegin(), m_transparencyGroupDataStack.cend(), [](const PDFTransparencyGroupPainterData& group) { return group.blendMode == BlendMode::Normal || group.blendMode == BlendMode::Compatible; });
|
{
|
||||||
|
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
|
} // namespace pdf
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#define PDFPAINTER_H
|
#define PDFPAINTER_H
|
||||||
|
|
||||||
#include "pdfutils.h"
|
#include "pdfutils.h"
|
||||||
|
#include "pdfpattern.h"
|
||||||
#include "pdfrenderer.h"
|
#include "pdfrenderer.h"
|
||||||
#include "pdfpagecontentprocessor.h"
|
#include "pdfpagecontentprocessor.h"
|
||||||
|
|
||||||
@ -28,12 +29,76 @@
|
|||||||
namespace pdf
|
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
|
/// 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.
|
/// and with transformation matrix, which translates page points to the device points.
|
||||||
/// Only basic transparency is supported, advanced transparency, such as transparency groups,
|
/// 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.
|
/// 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:
|
public:
|
||||||
/// Constructs new PDFPainter object, with default parameters.
|
/// Constructs new PDFPainter object, with default parameters.
|
||||||
/// \param painter Painter, on which page content is drawn
|
/// \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 performClipping(const QPainterPath& path, Qt::FillRule fillRule) override;
|
||||||
virtual void performImagePainting(const QImage& image) override;
|
virtual void performImagePainting(const QImage& image) override;
|
||||||
virtual void performMeshPainting(const PDFMesh& mesh) override;
|
virtual void performMeshPainting(const PDFMesh& mesh) override;
|
||||||
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override;
|
|
||||||
virtual void performSaveGraphicState(ProcessOrder order) override;
|
virtual void performSaveGraphicState(ProcessOrder order) override;
|
||||||
virtual void performRestoreGraphicState(ProcessOrder order) override;
|
virtual void performRestoreGraphicState(ProcessOrder order) override;
|
||||||
virtual void performBeginTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
|
virtual void setWorldMatrix(const QMatrix& matrix) override;
|
||||||
virtual void performEndTransparencyGroup(ProcessOrder order, const PDFTransparencyGroup& transparencyGroup);
|
virtual void setCompositionMode(QPainter::CompositionMode mode) override;
|
||||||
virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Returns current pen
|
QPainter* m_painter;
|
||||||
const QPen& getCurrentPen() { return m_currentPen.get(this, &PDFPainter::getCurrentPenImpl); }
|
};
|
||||||
|
|
||||||
/// Returns current brush
|
/// Precompiled page contains precompiled graphic instructions of a PDF page to draw it quickly
|
||||||
const QBrush& getCurrentBrush() { return m_currentBrush.get(this, &PDFPainter::getCurrentBrushImpl); }
|
/// 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)
|
enum class InstructionType
|
||||||
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
|
|
||||||
{
|
{
|
||||||
PDFTransparencyGroup group;
|
Invalid,
|
||||||
PDFReal alphaStroke = 1.0;
|
DrawPath,
|
||||||
PDFReal alphaFill = 1.0;
|
DrawImage,
|
||||||
BlendMode blendMode = BlendMode::Normal;
|
DrawMesh,
|
||||||
|
Clip,
|
||||||
|
SaveGraphicState,
|
||||||
|
RestoreGraphicState,
|
||||||
|
SetWorldMatrix,
|
||||||
|
SetCompositionMode
|
||||||
};
|
};
|
||||||
|
|
||||||
QPainter* m_painter;
|
struct Instruction
|
||||||
PDFRenderer::Features m_features;
|
{
|
||||||
PDFCachedItem<QPen> m_currentPen;
|
inline Instruction() = default;
|
||||||
PDFCachedItem<QBrush> m_currentBrush;
|
inline Instruction(InstructionType type, size_t dataIndex) :
|
||||||
std::vector<PDFTransparencyGroupPainterData> m_transparencyGroupDataStack;
|
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
|
} // namespace pdf
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include "pdfpainter.h"
|
#include "pdfpainter.h"
|
||||||
#include "pdfdocument.h"
|
#include "pdfdocument.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -36,21 +38,8 @@ PDFRenderer::PDFRenderer(const PDFDocument* document,
|
|||||||
Q_ASSERT(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();
|
QRectF mediaBox = page->getRotatedMediaBox();
|
||||||
|
|
||||||
QMatrix matrix;
|
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);
|
PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_optionalContentActivity, m_meshQualitySettings);
|
||||||
return processor.processContents();
|
return processor.processContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matrix, size_t pageIndex) const
|
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matrix, size_t pageIndex) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(painter);
|
|
||||||
|
|
||||||
const PDFCatalog* catalog = m_document->getCatalog();
|
const PDFCatalog* catalog = m_document->getCatalog();
|
||||||
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
|
if (pageIndex >= catalog->getPageCount() || !catalog->getPage(pageIndex))
|
||||||
{
|
{
|
||||||
@ -116,4 +120,27 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matr
|
|||||||
return processor.processContents();
|
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
|
} // namespace pdf
|
||||||
|
@ -27,6 +27,7 @@ class QPainter;
|
|||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
class PDFFontCache;
|
class PDFFontCache;
|
||||||
|
class PDFPrecompiledPage;
|
||||||
class PDFOptionalContentActivity;
|
class PDFOptionalContentActivity;
|
||||||
|
|
||||||
/// Renders the PDF page on the painter, or onto an image.
|
/// 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.
|
/// 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;
|
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
|
/// Returns default renderer features
|
||||||
static constexpr Features getDefaultFeatures() { return Antialiasing | TextAntialiasing | ClipToCropBox; }
|
static constexpr Features getDefaultFeatures() { return Antialiasing | TextAntialiasing | ClipToCropBox; }
|
||||||
|
|
||||||
@ -71,7 +85,6 @@ private:
|
|||||||
PDFMeshQualitySettings m_meshQualitySettings;
|
PDFMeshQualitySettings m_meshQualitySettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFRenderer::Features)
|
||||||
|
Reference in New Issue
Block a user