mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Issue #123: Alternative software rendering backend
This commit is contained in:
@@ -1482,6 +1482,17 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
|
||||
}
|
||||
}
|
||||
|
||||
void PDFAnnotationManager::drawPage(BLContext& context, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, const QTransform& pagePointToDevicePointMatrix, QList<PDFRenderError>& errors) const
|
||||
{
|
||||
// TODO: Implement it
|
||||
Q_UNUSED(context);
|
||||
Q_UNUSED(pageIndex);
|
||||
Q_UNUSED(compiledPage);
|
||||
Q_UNUSED(layoutGetter);
|
||||
Q_UNUSED(pagePointToDevicePointMatrix);
|
||||
Q_UNUSED(errors);
|
||||
}
|
||||
|
||||
void PDFAnnotationManager::drawAnnotation(const PageAnnotation& annotation,
|
||||
const QTransform& pagePointToDevicePointMatrix,
|
||||
const PDFPage* page,
|
||||
|
@@ -40,6 +40,8 @@ class QKeyEvent;
|
||||
class QMouseEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
class BLContext;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFWidget;
|
||||
@@ -1466,6 +1468,13 @@ public:
|
||||
const QTransform& pagePointToDevicePointMatrix,
|
||||
QList<PDFRenderError>& errors) const;
|
||||
|
||||
virtual void drawPage(BLContext& context,
|
||||
PDFInteger pageIndex,
|
||||
const PDFPrecompiledPage* compiledPage,
|
||||
PDFTextLayoutGetter& layoutGetter,
|
||||
const QTransform& pagePointToDevicePointMatrix,
|
||||
QList<PDFRenderError>& errors) const;
|
||||
|
||||
/// Set document
|
||||
/// \param document New document
|
||||
virtual void setDocument(const PDFModifiedDocument& document);
|
||||
|
@@ -187,8 +187,8 @@ private:
|
||||
|
||||
enum class RendererEngine
|
||||
{
|
||||
Software,
|
||||
OpenGL
|
||||
Blend2D,
|
||||
QPainter,
|
||||
};
|
||||
|
||||
enum class RenderingIntent
|
||||
|
@@ -18,11 +18,14 @@
|
||||
#include "pdfpainter.h"
|
||||
#include "pdfpattern.h"
|
||||
#include "pdfcms.h"
|
||||
#include "pdfpainterutils.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QCryptographicHash>
|
||||
#include <QtMath>
|
||||
|
||||
#include <Blend2d.h>
|
||||
|
||||
#include "pdfdbgheap.h"
|
||||
|
||||
namespace pdf
|
||||
@@ -620,6 +623,135 @@ void PDFPrecompiledPage::draw(QPainter* painter,
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void PDFPrecompiledPage::draw(BLContext& painter,
|
||||
const QRectF& cropBox,
|
||||
const QTransform& pagePointToDevicePointMatrix,
|
||||
PDFRenderer::Features features,
|
||||
PDFReal opacity) const
|
||||
{
|
||||
Q_ASSERT(painter);
|
||||
Q_ASSERT(pagePointToDevicePointMatrix.isInvertible());
|
||||
|
||||
painter.save();
|
||||
painter.setMatrix(BLMatrix2D());
|
||||
painter.setGlobalAlpha(opacity);
|
||||
|
||||
if (features.testFlag(PDFRenderer::ClipToCropBox) && cropBox.isValid())
|
||||
{
|
||||
QRectF mappedCropBox = pagePointToDevicePointMatrix.mapRect(cropBox);
|
||||
BLRect clipRect = PDFPainterHelper::getBLRect(mappedCropBox);
|
||||
painter.clipToRect(clipRect);
|
||||
}
|
||||
|
||||
// Process all instructions
|
||||
for (const Instruction& instruction : m_instructions)
|
||||
{
|
||||
switch (instruction.type)
|
||||
{
|
||||
case InstructionType::DrawPath:
|
||||
{
|
||||
const PathPaintData& data = m_paths[instruction.dataIndex];
|
||||
BLPath path = PDFPainterHelper::getBLPath(data.path);
|
||||
|
||||
PDFPainterHelper::setBLPen(painter, data.pen);
|
||||
PDFPainterHelper::setBLBrush(painter, data.brush);
|
||||
|
||||
if (data.brush.style() != Qt::NoBrush)
|
||||
{
|
||||
painter.fillPath(path);
|
||||
}
|
||||
|
||||
if (data.pen.style() != Qt::NoPen)
|
||||
{
|
||||
painter.strokePath(path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::DrawImage:
|
||||
{
|
||||
const ImageData& data = m_images[instruction.dataIndex];
|
||||
QImage image = data.image;
|
||||
|
||||
if (image.format() != QImage::Format_ARGB32_Premultiplied)
|
||||
{
|
||||
image.convertTo(QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
|
||||
painter.save();
|
||||
|
||||
BLImage blImage;
|
||||
blImage.createFromData(image.width(), image.height(), BL_FORMAT_PRGB32, image.bits(), image.bytesPerLine());
|
||||
|
||||
BLMatrix2D imageTransform(1.0 / image.width(), 0, 0, 1.0 / image.height(), 0, 0);
|
||||
BLMatrix2D worldTransform = imageTransform;
|
||||
worldTransform.postTransform(painter.userMatrix());
|
||||
|
||||
// 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)
|
||||
worldTransform.translate(0, image.height());
|
||||
worldTransform.scale(1, -1);
|
||||
|
||||
painter.setMatrix(worldTransform);
|
||||
painter.blitImage(BLPointI(0, 0), blImage);
|
||||
painter.restore();
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::DrawMesh:
|
||||
{
|
||||
const MeshPaintData& data = m_meshes[instruction.dataIndex];
|
||||
|
||||
painter.save();
|
||||
painter.setMatrix(PDFPainterHelper::getBLMatrix(pagePointToDevicePointMatrix));
|
||||
data.mesh.paint(painter, data.alpha);
|
||||
painter.restore();
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::Clip:
|
||||
{
|
||||
// Blend2D does not have path clipping
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::SaveGraphicState:
|
||||
{
|
||||
painter.save();
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::RestoreGraphicState:
|
||||
{
|
||||
painter.restore();
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::SetWorldMatrix:
|
||||
{
|
||||
QTransform transform(m_matrices[instruction.dataIndex] * pagePointToDevicePointMatrix);
|
||||
BLMatrix2D matrix = PDFPainterHelper::getBLMatrix(transform);
|
||||
painter.setMatrix(matrix);
|
||||
break;
|
||||
}
|
||||
|
||||
case InstructionType::SetCompositionMode:
|
||||
{
|
||||
painter.setCompOp(PDFPainterHelper::getBLCompOp(m_compositionModes[instruction.dataIndex]));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void PDFPrecompiledPage::redact(QPainterPath redactPath, const QTransform& matrix, QColor color)
|
||||
{
|
||||
if (redactPath.isEmpty())
|
||||
|
@@ -30,6 +30,8 @@
|
||||
#include <QBrush>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
class BLContext;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
@@ -196,6 +198,18 @@ public:
|
||||
PDFRenderer::Features features,
|
||||
PDFReal opacity) const;
|
||||
|
||||
/// Paints page onto the blend2d 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
|
||||
/// \param opacity Opacity of page graphics
|
||||
void draw(BLContext& painter,
|
||||
const QRectF& cropBox,
|
||||
const QTransform& pagePointToDevicePointMatrix,
|
||||
PDFRenderer::Features features,
|
||||
PDFReal opacity) const;
|
||||
|
||||
/// Redact path - remove all content intersecting given path,
|
||||
/// and fill redact path with given color.
|
||||
/// \param redactPath Redaction path in page coordinates
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2022 Jakub Melka
|
||||
// Copyright (C) 2021-2024 Jakub Melka
|
||||
//
|
||||
// This file is part of PDF4QT.
|
||||
//
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "pdfpainterutils.h"
|
||||
|
||||
#include <QPainterPath>
|
||||
#include <QFontMetrics>
|
||||
|
||||
#include "pdfdbgheap.h"
|
||||
@@ -63,4 +64,277 @@ QRect PDFPainterHelper::drawBubble(QPainter* painter, QPoint point, QColor color
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
BLMatrix2D PDFPainterHelper::getBLMatrix(QTransform transform)
|
||||
{
|
||||
BLMatrix2D matrix;
|
||||
matrix.reset(transform.m11(), transform.m12(), transform.m21(), transform.m22(), transform.dx(), transform.dy());
|
||||
return matrix;
|
||||
}
|
||||
|
||||
BLRect PDFPainterHelper::getBLRect(QRect rect)
|
||||
{
|
||||
return BLRect(rect.x(), rect.y(), rect.width(), rect.height());
|
||||
}
|
||||
|
||||
BLRect PDFPainterHelper::getBLRect(QRectF rect)
|
||||
{
|
||||
return BLRect(rect.x(), rect.y(), rect.width(), rect.height());
|
||||
}
|
||||
|
||||
BLPath PDFPainterHelper::getBLPath(const QPainterPath& path)
|
||||
{
|
||||
BLPath blPath;
|
||||
|
||||
int elementCount = path.elementCount();
|
||||
for (int i = 0; i < elementCount; ++i) {
|
||||
const QPainterPath::Element& element = path.elementAt(i);
|
||||
|
||||
switch (element.type)
|
||||
{
|
||||
case QPainterPath::MoveToElement:
|
||||
blPath.moveTo(element.x, element.y);
|
||||
break;
|
||||
|
||||
case QPainterPath::LineToElement:
|
||||
blPath.lineTo(element.x, element.y);
|
||||
break;
|
||||
|
||||
case QPainterPath::CurveToElement:
|
||||
if (i + 2 < elementCount)
|
||||
{
|
||||
const QPainterPath::Element& ctrlPoint1 = path.elementAt(i++);
|
||||
const QPainterPath::Element& ctrlPoint2 = path.elementAt(i++);
|
||||
const QPainterPath::Element& endPoint = path.elementAt(i);
|
||||
blPath.cubicTo(ctrlPoint1.x, ctrlPoint1.y, ctrlPoint2.x, ctrlPoint2.y, endPoint.x, endPoint.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case QPainterPath::CurveToDataElement:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return blPath;
|
||||
}
|
||||
|
||||
void PDFPainterHelper::setBLPen(BLContext& context, const QPen& pen)
|
||||
{
|
||||
const Qt::PenCapStyle capStyle = pen.capStyle();
|
||||
const Qt::PenJoinStyle joinStyle = pen.joinStyle();
|
||||
const QColor color = pen.color();
|
||||
const qreal width = pen.widthF();
|
||||
const qreal miterLimit = pen.miterLimit();
|
||||
const qreal dashOffset = pen.dashOffset();
|
||||
const QList<qreal> customDashPattern = pen.dashPattern();
|
||||
const Qt::PenStyle penStyle = pen.style();
|
||||
|
||||
context.setStrokeAlpha(pen.color().alphaF());
|
||||
context.setStrokeWidth(width);
|
||||
context.setStrokeMiterLimit(miterLimit);
|
||||
|
||||
switch (capStyle)
|
||||
{
|
||||
case Qt::FlatCap:
|
||||
context.setStrokeCaps(BL_STROKE_CAP_BUTT);
|
||||
break;
|
||||
case Qt::SquareCap:
|
||||
context.setStrokeCaps(BL_STROKE_CAP_SQUARE);
|
||||
break;
|
||||
case Qt::RoundCap:
|
||||
context.setStrokeCaps(BL_STROKE_CAP_ROUND);
|
||||
break;
|
||||
}
|
||||
|
||||
BLArray<double> dashArray;
|
||||
|
||||
for (double value : customDashPattern)
|
||||
{
|
||||
dashArray.append(value);
|
||||
}
|
||||
|
||||
context.setStrokeDashOffset(dashOffset);
|
||||
context.setStrokeDashArray(dashArray);
|
||||
|
||||
switch (joinStyle)
|
||||
{
|
||||
case Qt::MiterJoin:
|
||||
context.setStrokeJoin(BL_STROKE_JOIN_MITER_CLIP);
|
||||
break;
|
||||
case Qt::BevelJoin:
|
||||
context.setStrokeJoin(BL_STROKE_JOIN_BEVEL);
|
||||
break;
|
||||
case Qt::RoundJoin:
|
||||
context.setStrokeJoin(BL_STROKE_JOIN_ROUND);
|
||||
break;
|
||||
case Qt::SvgMiterJoin:
|
||||
context.setStrokeJoin(BL_STROKE_JOIN_MITER_CLIP);
|
||||
break;
|
||||
}
|
||||
|
||||
context.setStrokeStyle(BLRgba32(color.rgba()));
|
||||
|
||||
BLStrokeOptions strokeOptions = context.strokeOptions();
|
||||
|
||||
switch (penStyle)
|
||||
{
|
||||
case Qt::SolidLine:
|
||||
strokeOptions.dashArray.clear();
|
||||
strokeOptions.dashOffset = 0.0;
|
||||
break;
|
||||
|
||||
case Qt::DashLine:
|
||||
{
|
||||
constexpr double dashPattern[] = {4, 4};
|
||||
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::DotLine:
|
||||
{
|
||||
constexpr double dashPattern[] = {1, 3};
|
||||
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::DashDotLine:
|
||||
{
|
||||
constexpr double dashPattern[] = {4, 2, 1, 2};
|
||||
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::DashDotDotLine:
|
||||
{
|
||||
constexpr double dashPattern[] = {4, 2, 1, 2, 1, 2};
|
||||
strokeOptions.dashArray.assignData(dashPattern, std::size(dashPattern));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
context.setStrokeOptions(strokeOptions);
|
||||
}
|
||||
|
||||
void PDFPainterHelper::setBLBrush(BLContext& context, const QBrush& brush)
|
||||
{
|
||||
auto setGradientStops = [](BLGradient& blGradient, const auto& qGradient)
|
||||
{
|
||||
QVector<BLGradientStop> stops;
|
||||
for (const auto& stop : qGradient.stops())
|
||||
{
|
||||
stops.append(BLGradientStop(stop.first, BLRgba32(stop.second.red(), stop.second.green(), stop.second.blue(), stop.second.alpha())));
|
||||
}
|
||||
blGradient.assignStops(stops.constData(), stops.size());
|
||||
};
|
||||
|
||||
switch (brush.style())
|
||||
{
|
||||
default:
|
||||
case Qt::SolidPattern:
|
||||
{
|
||||
QColor color = brush.color();
|
||||
BLRgba32 blColor = BLRgba32(color.red(), color.green(), color.blue(), color.alpha());
|
||||
context.setFillStyle(blColor);
|
||||
break;
|
||||
}
|
||||
case Qt::LinearGradientPattern:
|
||||
{
|
||||
const QGradient* gradient = brush.gradient();
|
||||
if (gradient && gradient->type() == QGradient::LinearGradient)
|
||||
{
|
||||
const QLinearGradient* linearGradient = static_cast<const QLinearGradient*>(gradient);
|
||||
BLLinearGradientValues blLinearGradient;
|
||||
blLinearGradient.x0 = linearGradient->start().x();
|
||||
blLinearGradient.y0 = linearGradient->start().y();
|
||||
blLinearGradient.x1 = linearGradient->finalStop().x();
|
||||
blLinearGradient.y1 = linearGradient->finalStop().y();
|
||||
BLGradient blGradient(blLinearGradient);
|
||||
setGradientStops(blGradient, *gradient);
|
||||
context.setFillStyle(blGradient);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Qt::RadialGradientPattern:
|
||||
{
|
||||
const QGradient* gradient = brush.gradient();
|
||||
if (gradient && gradient->type() == QGradient::RadialGradient)
|
||||
{
|
||||
const QRadialGradient* radialGradient = static_cast<const QRadialGradient*>(gradient);
|
||||
BLRadialGradientValues blRadialGradientValues;
|
||||
blRadialGradientValues.x0 = radialGradient->center().x();
|
||||
blRadialGradientValues.y0 = radialGradient->center().y();
|
||||
blRadialGradientValues.x1 = radialGradient->focalPoint().x();
|
||||
blRadialGradientValues.y1 = radialGradient->focalPoint().y();
|
||||
blRadialGradientValues.r0 = radialGradient->radius();
|
||||
BLGradient blGradient(blRadialGradientValues);
|
||||
setGradientStops(blGradient, *gradient);
|
||||
context.setFillStyle(blGradient);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLCompOp PDFPainterHelper::getBLCompOp(QPainter::CompositionMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case QPainter::CompositionMode_SourceOver:
|
||||
return BL_COMP_OP_SRC_OVER;
|
||||
case QPainter::CompositionMode_DestinationOver:
|
||||
return BL_COMP_OP_DST_OVER;
|
||||
case QPainter::CompositionMode_Clear:
|
||||
return BL_COMP_OP_CLEAR;
|
||||
case QPainter::CompositionMode_Source:
|
||||
return BL_COMP_OP_SRC_COPY;
|
||||
case QPainter::CompositionMode_Destination:
|
||||
return BL_COMP_OP_DST_COPY;
|
||||
case QPainter::CompositionMode_SourceIn:
|
||||
return BL_COMP_OP_SRC_IN;
|
||||
case QPainter::CompositionMode_DestinationIn:
|
||||
return BL_COMP_OP_DST_IN;
|
||||
case QPainter::CompositionMode_SourceOut:
|
||||
return BL_COMP_OP_SRC_OUT;
|
||||
case QPainter::CompositionMode_DestinationOut:
|
||||
return BL_COMP_OP_DST_OUT;
|
||||
case QPainter::CompositionMode_SourceAtop:
|
||||
return BL_COMP_OP_SRC_ATOP;
|
||||
case QPainter::CompositionMode_DestinationAtop:
|
||||
return BL_COMP_OP_DST_ATOP;
|
||||
case QPainter::CompositionMode_Xor:
|
||||
return BL_COMP_OP_XOR;
|
||||
case QPainter::CompositionMode_Plus:
|
||||
return BL_COMP_OP_PLUS;
|
||||
case QPainter::CompositionMode_Multiply:
|
||||
return BL_COMP_OP_MULTIPLY;
|
||||
case QPainter::CompositionMode_Screen:
|
||||
return BL_COMP_OP_SCREEN;
|
||||
case QPainter::CompositionMode_Overlay:
|
||||
return BL_COMP_OP_OVERLAY;
|
||||
case QPainter::CompositionMode_Darken:
|
||||
return BL_COMP_OP_DARKEN;
|
||||
case QPainter::CompositionMode_Lighten:
|
||||
return BL_COMP_OP_LIGHTEN;
|
||||
case QPainter::CompositionMode_ColorDodge:
|
||||
return BL_COMP_OP_COLOR_DODGE;
|
||||
case QPainter::CompositionMode_ColorBurn:
|
||||
return BL_COMP_OP_COLOR_BURN;
|
||||
case QPainter::CompositionMode_HardLight:
|
||||
return BL_COMP_OP_HARD_LIGHT;
|
||||
case QPainter::CompositionMode_SoftLight:
|
||||
return BL_COMP_OP_SOFT_LIGHT;
|
||||
case QPainter::CompositionMode_Difference:
|
||||
return BL_COMP_OP_DIFFERENCE;
|
||||
case QPainter::CompositionMode_Exclusion:
|
||||
return BL_COMP_OP_EXCLUSION;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return BL_COMP_OP_SRC_OVER;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <Blend2d.h>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
@@ -55,6 +57,27 @@ public:
|
||||
/// \param text Text inside the bubble
|
||||
/// \param alignment Bubble alignment relative to the bubble position point
|
||||
static QRect drawBubble(QPainter* painter, QPoint point, QColor color, QString text, Qt::Alignment alignment);
|
||||
|
||||
/// Get BL matrix from transformation
|
||||
static BLMatrix2D getBLMatrix(QTransform transform);
|
||||
|
||||
/// Get BL rect from regular rect
|
||||
static BLRect getBLRect(QRect rect);
|
||||
|
||||
/// Get BL rect from regular rect
|
||||
static BLRect getBLRect(QRectF rect);
|
||||
|
||||
/// Get BL path from path
|
||||
static BLPath getBLPath(const QPainterPath& path);
|
||||
|
||||
/// Set pen to the context
|
||||
static void setBLPen(BLContext& context, const QPen& pen);
|
||||
|
||||
/// Set brush to the context
|
||||
static void setBLBrush(BLContext& context, const QBrush& brush);
|
||||
|
||||
/// Returns composition operator
|
||||
static BLCompOp getBLCompOp(QPainter::CompositionMode mode);
|
||||
};
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -22,10 +22,13 @@
|
||||
#include "pdfcolorspaces.h"
|
||||
#include "pdfexecutionpolicy.h"
|
||||
#include "pdfconstants.h"
|
||||
#include "pdfpainterutils.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QPainter>
|
||||
|
||||
#include <Blend2d.h>
|
||||
|
||||
#include "pdfdbgheap.h"
|
||||
|
||||
#include <execution>
|
||||
@@ -1359,6 +1362,45 @@ void PDFMesh::paint(QPainter* painter, PDFReal alpha) const
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void PDFMesh::paint(BLContext& context, PDFReal alpha) const
|
||||
{
|
||||
if (m_triangles.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.save();
|
||||
PDFPainterHelper::setBLPen(context, Qt::NoPen);
|
||||
|
||||
if (!m_backgroundPath.isEmpty() && m_backgroundColor.isValid())
|
||||
{
|
||||
QColor backgroundColor = m_backgroundColor;
|
||||
backgroundColor.setAlphaF(alpha);
|
||||
PDFPainterHelper::setBLBrush(context, QBrush(backgroundColor, Qt::SolidPattern));
|
||||
context.fillPath(PDFPainterHelper::getBLPath(m_backgroundPath));
|
||||
}
|
||||
|
||||
QColor color;
|
||||
|
||||
// Draw all triangles
|
||||
for (const Triangle& triangle : m_triangles)
|
||||
{
|
||||
if (color != triangle.color)
|
||||
{
|
||||
QColor newColor(triangle.color);
|
||||
newColor.setAlphaF(alpha);
|
||||
PDFPainterHelper::setBLBrush(context, QBrush(newColor, Qt::SolidPattern));
|
||||
color = newColor;
|
||||
}
|
||||
|
||||
context.fillTriangle(m_vertices[triangle.v1].x(), m_vertices[triangle.v1].y(),
|
||||
m_vertices[triangle.v2].x(), m_vertices[triangle.v2].y(),
|
||||
m_vertices[triangle.v3].x(), m_vertices[triangle.v3].y());
|
||||
}
|
||||
|
||||
context.restore();
|
||||
}
|
||||
|
||||
void PDFMesh::transform(const QTransform& matrix)
|
||||
{
|
||||
for (QPointF& vertex : m_vertices)
|
||||
|
@@ -29,6 +29,8 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
class BLContext;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFPattern;
|
||||
@@ -94,6 +96,11 @@ public:
|
||||
/// \param alpha Opacity factor
|
||||
void paint(QPainter* painter, PDFReal alpha) const;
|
||||
|
||||
/// Paints the mesh on the context
|
||||
/// \param context Painter, onto which is mesh drawn
|
||||
/// \param alpha Opacity factor
|
||||
void paint(BLContext& context, PDFReal alpha) const;
|
||||
|
||||
/// Transforms the mesh according to the matrix transform
|
||||
/// \param matrix Matrix transform to be performed
|
||||
void transform(const QTransform& matrix);
|
||||
|
@@ -26,13 +26,7 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QtMath>
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
#include <QOpenGLContext>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLFunctions>
|
||||
#endif
|
||||
#include <Blend2d.h>
|
||||
|
||||
#include "pdfdbgheap.h"
|
||||
|
||||
@@ -227,54 +221,19 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex)
|
||||
|
||||
PDFRasterizer::PDFRasterizer(QObject* parent) :
|
||||
BaseClass(parent),
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
m_features(),
|
||||
m_surfaceFormat(),
|
||||
m_surface(nullptr),
|
||||
m_context(nullptr),
|
||||
m_fbo(nullptr)
|
||||
#else
|
||||
m_features()
|
||||
#endif
|
||||
m_rendererEngine(RendererEngine::Blend2D)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PDFRasterizer::~PDFRasterizer()
|
||||
{
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
releaseOpenGL();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void PDFRasterizer::reset(bool useOpenGL, const QSurfaceFormat& surfaceFormat)
|
||||
void PDFRasterizer::reset(RendererEngine rendererEngine)
|
||||
{
|
||||
if (!PDFRendererInfo::isHardwareAccelerationSupported())
|
||||
{
|
||||
m_features.setFlag(FailedOpenGL, true);
|
||||
m_features.setFlag(ValidOpenGL, false);
|
||||
}
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
if (useOpenGL != m_features.testFlag(UseOpenGL) || surfaceFormat != m_surfaceFormat)
|
||||
{
|
||||
// In either case, we must reset OpenGL
|
||||
releaseOpenGL();
|
||||
|
||||
m_features.setFlag(UseOpenGL, useOpenGL);
|
||||
m_surfaceFormat = surfaceFormat;
|
||||
|
||||
// We create new OpenGL renderer, but only if it hasn't failed (we do not try
|
||||
// again to create new OpenGL renderer.
|
||||
if (m_features.testFlag(UseOpenGL) && !m_features.testFlag(FailedOpenGL))
|
||||
{
|
||||
initializeOpenGL();
|
||||
}
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(surfaceFormat);
|
||||
m_features.setFlag(UseOpenGL, useOpenGL);
|
||||
#endif
|
||||
m_rendererEngine = rendererEngine;
|
||||
}
|
||||
|
||||
QImage PDFRasterizer::render(PDFInteger pageIndex,
|
||||
@@ -285,70 +244,37 @@ QImage PDFRasterizer::render(PDFInteger pageIndex,
|
||||
const PDFAnnotationManager* annotationManager,
|
||||
PageRotation extraRotation)
|
||||
{
|
||||
QImage image;
|
||||
QImage image(size, QImage::Format_ARGB32_Premultiplied);
|
||||
|
||||
QTransform matrix = PDFRenderer::createPagePointToDevicePointMatrix(page, QRect(QPoint(0, 0), size), extraRotation);
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
if (m_features.testFlag(UseOpenGL) && m_features.testFlag(ValidOpenGL))
|
||||
if (m_rendererEngine == RendererEngine::Blend2D)
|
||||
{
|
||||
// We have valid OpenGL context, try to select it and possibly create framebuffer object
|
||||
// for target image (to paint it using paint device).
|
||||
Q_ASSERT(m_surface && m_context);
|
||||
if (m_context->makeCurrent(m_surface))
|
||||
BLContext blContext;
|
||||
BLImage blImage;
|
||||
|
||||
blContext.setHint(BL_CONTEXT_HINT_RENDERING_QUALITY, BL_RENDERING_QUALITY_MAX_VALUE);
|
||||
|
||||
blImage.createFromData(image.width(), image.height(), BL_FORMAT_PRGB32, image.bits(), image.bytesPerLine());
|
||||
if (blContext.begin(blImage) == BL_SUCCESS)
|
||||
{
|
||||
if (!m_fbo || m_fbo->width() != size.width() || m_fbo->height() != size.height())
|
||||
{
|
||||
// Delete old framebuffer object
|
||||
delete m_fbo;
|
||||
blContext.clearAll();
|
||||
|
||||
// Create a new framebuffer object
|
||||
QOpenGLFramebufferObjectFormat format;
|
||||
format.setSamples(m_surfaceFormat.samples());
|
||||
m_fbo = new QOpenGLFramebufferObject(size.width(), size.height(), format);
|
||||
compiledPage->draw(blContext, page->getCropBox(), matrix, features, 1.0);
|
||||
|
||||
if (annotationManager)
|
||||
{
|
||||
QList<PDFRenderError> errors;
|
||||
PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex);
|
||||
annotationManager->drawPage(blContext, pageIndex, compiledPage, textLayoutGetter, matrix, errors);
|
||||
}
|
||||
|
||||
Q_ASSERT(m_fbo);
|
||||
if (m_fbo->isValid() && m_fbo->bind())
|
||||
{
|
||||
// Now, we have bind the buffer. Due to bug in Qt's OpenGL drawing subsystem,
|
||||
// we must render it two times, otherwise painter paths will be sometimes
|
||||
// replaced by filled rectangles.
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
QOpenGLPaintDevice device(size);
|
||||
QPainter painter(&device);
|
||||
painter.fillRect(QRect(QPoint(0, 0), size), compiledPage->getPaperColor());
|
||||
compiledPage->draw(&painter, page->getCropBox(), matrix, features, 1.0);
|
||||
|
||||
if (annotationManager)
|
||||
{
|
||||
QList<PDFRenderError> errors;
|
||||
PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex);
|
||||
annotationManager->drawPage(&painter, pageIndex, compiledPage, textLayoutGetter, matrix, errors);
|
||||
}
|
||||
}
|
||||
|
||||
m_fbo->release();
|
||||
|
||||
image = m_fbo->toImage();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.setFlag(FailedOpenGL, true);
|
||||
m_features.setFlag(ValidOpenGL, false);
|
||||
}
|
||||
|
||||
m_context->doneCurrent();
|
||||
blContext.end();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (image.isNull())
|
||||
else
|
||||
{
|
||||
// If we can't use OpenGL, or user doesn't want to use OpenGL, then fallback
|
||||
// to standard software rasterizer.
|
||||
image = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||
// Use standard software rasterizer.
|
||||
image.fill(Qt::white);
|
||||
|
||||
QPainter painter(&image);
|
||||
@@ -362,13 +288,6 @@ QImage PDFRasterizer::render(PDFInteger pageIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// Jakub Melka: 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);
|
||||
}
|
||||
|
||||
// Calculate image DPI
|
||||
QSizeF rotatedSizeInMeters = page->getRotatedMediaBoxMM().size() / 1000.0;
|
||||
QSizeF rotatedSizeInPixels = image.size();
|
||||
@@ -380,87 +299,6 @@ QImage PDFRasterizer::render(PDFInteger pageIndex,
|
||||
return image;
|
||||
}
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
void PDFRasterizer::initializeOpenGL()
|
||||
{
|
||||
Q_ASSERT(!m_surface);
|
||||
Q_ASSERT(!m_context);
|
||||
Q_ASSERT(!m_fbo);
|
||||
|
||||
m_features.setFlag(ValidOpenGL, false);
|
||||
m_features.setFlag(FailedOpenGL, false);
|
||||
|
||||
// Create context
|
||||
m_context = new QOpenGLContext(this);
|
||||
m_context->setFormat(m_surfaceFormat);
|
||||
if (!m_context->create())
|
||||
{
|
||||
m_features.setFlag(FailedOpenGL, true);
|
||||
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
||||
// Create surface
|
||||
m_surface = new QOffscreenSurface(nullptr, this);
|
||||
m_surface->setFormat(m_surfaceFormat);
|
||||
m_surface->create();
|
||||
if (!m_surface->isValid())
|
||||
{
|
||||
m_features.setFlag(FailedOpenGL, true);
|
||||
|
||||
delete m_context;
|
||||
delete m_surface;
|
||||
|
||||
m_context = nullptr;
|
||||
m_surface = nullptr;
|
||||
}
|
||||
|
||||
// Check, if we can make it current
|
||||
if (m_context->makeCurrent(m_surface))
|
||||
{
|
||||
m_features.setFlag(ValidOpenGL, true);
|
||||
m_context->doneCurrent();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.setFlag(FailedOpenGL, true);
|
||||
releaseOpenGL();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
void PDFRasterizer::releaseOpenGL()
|
||||
{
|
||||
if (m_surface)
|
||||
{
|
||||
Q_ASSERT(m_context);
|
||||
|
||||
// Delete framebuffer
|
||||
if (m_fbo)
|
||||
{
|
||||
m_context->makeCurrent(m_surface);
|
||||
delete m_fbo;
|
||||
m_fbo = nullptr;
|
||||
m_context->doneCurrent();
|
||||
}
|
||||
|
||||
// Delete OpenGL context
|
||||
delete m_context;
|
||||
m_context = nullptr;
|
||||
|
||||
// Delete surface
|
||||
m_surface->destroy();
|
||||
delete m_surface;
|
||||
m_surface = nullptr;
|
||||
|
||||
// Set flag, that we do not have valid OpenGL
|
||||
m_features.setFlag(ValidOpenGL, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
PDFRasterizer* PDFRasterizerPool::acquire()
|
||||
{
|
||||
m_semaphore.acquire();
|
||||
@@ -978,8 +816,7 @@ PDFRasterizerPool::PDFRasterizerPool(const PDFDocument* document,
|
||||
PDFRenderer::Features features,
|
||||
const PDFMeshQualitySettings& meshQualitySettings,
|
||||
int rasterizerCount,
|
||||
bool useOpenGL,
|
||||
const QSurfaceFormat& surfaceFormat,
|
||||
RendererEngine rendererEngine,
|
||||
QObject* parent) :
|
||||
BaseClass(parent),
|
||||
m_document(document),
|
||||
@@ -994,96 +831,8 @@ PDFRasterizerPool::PDFRasterizerPool(const PDFDocument* document,
|
||||
for (int i = 0; i < rasterizerCount; ++i)
|
||||
{
|
||||
m_rasterizers.push_back(new PDFRasterizer(this));
|
||||
m_rasterizers.back()->reset(useOpenGL, surfaceFormat);
|
||||
m_rasterizers.back()->reset(rendererEngine);
|
||||
}
|
||||
}
|
||||
|
||||
PDFCachedItem<PDFRendererInfo::Info> PDFRendererInfo::s_info;
|
||||
|
||||
const PDFRendererInfo::Info& PDFRendererInfo::getHardwareAccelerationSupportedInfo()
|
||||
{
|
||||
auto getInfo = []()
|
||||
{
|
||||
Info info;
|
||||
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
QOffscreenSurface surface;
|
||||
surface.create();
|
||||
|
||||
if (!surface.isValid())
|
||||
{
|
||||
info.renderer = PDFTranslationContext::tr("GDI Generic");
|
||||
info.version = PDFTranslationContext::tr("1.1");
|
||||
info.vendor = PDFTranslationContext::tr("System");
|
||||
return info;
|
||||
}
|
||||
|
||||
QOpenGLContext context;
|
||||
|
||||
if (!context.create())
|
||||
{
|
||||
info.renderer = PDFTranslationContext::tr("GDI Generic");
|
||||
info.version = PDFTranslationContext::tr("1.1");
|
||||
info.vendor = PDFTranslationContext::tr("System");
|
||||
surface.destroy();
|
||||
return info;
|
||||
}
|
||||
|
||||
if (!context.makeCurrent(&surface))
|
||||
{
|
||||
info.renderer = PDFTranslationContext::tr("GDI Generic");
|
||||
info.version = PDFTranslationContext::tr("1.1");
|
||||
info.vendor = PDFTranslationContext::tr("System");
|
||||
surface.destroy();
|
||||
return info;
|
||||
}
|
||||
|
||||
const char* versionStr = reinterpret_cast<const char*>(context.functions()->glGetString(GL_VERSION));
|
||||
const char* vendorStr = reinterpret_cast<const char*>(context.functions()->glGetString(GL_VENDOR));
|
||||
const char* rendererStr = reinterpret_cast<const char*>(context.functions()->glGetString(GL_RENDERER));
|
||||
|
||||
QString versionString = QString::fromLocal8Bit(versionStr, std::strlen(versionStr));
|
||||
QString vendorString = QString::fromLocal8Bit(vendorStr, std::strlen(vendorStr));
|
||||
QString rendererString = QString::fromLocal8Bit(rendererStr, std::strlen(rendererStr));
|
||||
|
||||
context.doneCurrent();
|
||||
surface.destroy();
|
||||
|
||||
versionString = versionString.trimmed();
|
||||
|
||||
int spaceIndex = versionString.indexOf(QChar(QChar::Space));
|
||||
if (spaceIndex != -1)
|
||||
{
|
||||
versionString = versionString.left(spaceIndex);
|
||||
}
|
||||
|
||||
info.vendor = vendorString;
|
||||
info.renderer = rendererString;
|
||||
info.version = versionString;
|
||||
|
||||
QStringList versionStrSplitted = versionString.split('.', Qt::KeepEmptyParts);
|
||||
|
||||
if (versionStrSplitted.size() >= 2)
|
||||
{
|
||||
info.majorOpenGLVersion = versionStrSplitted[0].toInt();
|
||||
info.minorOpenGLVersion = versionStrSplitted[1].toInt();
|
||||
}
|
||||
#endif
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
return s_info.get(getInfo);
|
||||
}
|
||||
|
||||
bool PDFRendererInfo::isHardwareAccelerationSupported()
|
||||
{
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
const Info& info = getHardwareAccelerationSupportedInfo();
|
||||
return std::make_pair(info.majorOpenGLVersion, info.minorOpenGLVersion) >= std::make_pair(REQUIRED_OPENGL_MAJOR_VERSION, REQUIRED_OPENGL_MINOR_VERSION);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
@@ -28,13 +28,9 @@
|
||||
#include <QMutex>
|
||||
#include <QSemaphore>
|
||||
#include <QImageWriter>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QImage>
|
||||
|
||||
class QPainter;
|
||||
class QOpenGLContext;
|
||||
class QOffscreenSurface;
|
||||
class QOpenGLFramebufferObject;
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
@@ -46,30 +42,6 @@ class PDFPrecompiledPage;
|
||||
class PDFAnnotationManager;
|
||||
class PDFOptionalContentActivity;
|
||||
|
||||
class PDF4QTLIBCORESHARED_EXPORT PDFRendererInfo
|
||||
{
|
||||
public:
|
||||
PDFRendererInfo() = delete;
|
||||
|
||||
struct Info
|
||||
{
|
||||
QString vendor;
|
||||
QString renderer;
|
||||
QString version;
|
||||
int majorOpenGLVersion = 0;
|
||||
int minorOpenGLVersion = 0;
|
||||
};
|
||||
|
||||
static const Info& getHardwareAccelerationSupportedInfo();
|
||||
static bool isHardwareAccelerationSupported();
|
||||
|
||||
static constexpr int REQUIRED_OPENGL_MAJOR_VERSION = 3;
|
||||
static constexpr int REQUIRED_OPENGL_MINOR_VERSION = 2;
|
||||
|
||||
private:
|
||||
static PDFCachedItem<Info> s_info;
|
||||
};
|
||||
|
||||
/// Renders the PDF page on the painter, or onto an image.
|
||||
class PDF4QTLIBCORESHARED_EXPORT PDFRenderer
|
||||
{
|
||||
@@ -165,9 +137,7 @@ private:
|
||||
PDFMeshQualitySettings m_meshQualitySettings;
|
||||
};
|
||||
|
||||
/// Renders PDF pages to bitmap images (QImage). It can use OpenGL for painting,
|
||||
/// if it is enabled, if this is the case, offscreen rendering to framebuffer
|
||||
/// is used.
|
||||
/// Renders PDF pages to bitmap images (QImage).
|
||||
/// \note Construct this object only in main GUI thread
|
||||
class PDF4QTLIBCORESHARED_EXPORT PDFRasterizer : public QObject
|
||||
{
|
||||
@@ -180,22 +150,9 @@ public:
|
||||
explicit PDFRasterizer(QObject* parent);
|
||||
virtual ~PDFRasterizer() override;
|
||||
|
||||
/// Resets the renderer. This function must be called from main GUI thread,
|
||||
/// it cannot be called from deferred threads, because it can create hidden
|
||||
/// window (offscreen surface). If hardware renderer is required, and
|
||||
/// none is available, then software renderer is used.
|
||||
/// \param useOpenGL Use OpenGL for rendering (ignored if not available)
|
||||
/// \param surfaceFormat Surface format to render
|
||||
void reset(bool useOpenGL, const QSurfaceFormat& surfaceFormat);
|
||||
|
||||
enum Feature
|
||||
{
|
||||
UseOpenGL = 0x0001, ///< Use OpenGL for rendering
|
||||
ValidOpenGL = 0x0002, ///< OpenGL is initialized and valid
|
||||
FailedOpenGL = 0x0004, ///< OpenGL creation has failed
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(Features, Feature)
|
||||
/// Resets the renderer.
|
||||
/// \param rendererEngine Renderer engine type
|
||||
void reset(RendererEngine rendererEngine);
|
||||
|
||||
/// Renders page to the image of given size. If some error occurs, then
|
||||
/// empty image is returned. Warning: this function can modify this object,
|
||||
@@ -217,18 +174,7 @@ public:
|
||||
PageRotation extraRotation);
|
||||
|
||||
private:
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
void initializeOpenGL();
|
||||
void releaseOpenGL();
|
||||
#endif
|
||||
|
||||
Features m_features;
|
||||
#ifdef PDF4QT_ENABLE_OPENGL
|
||||
QSurfaceFormat m_surfaceFormat;
|
||||
QOffscreenSurface* m_surface;
|
||||
QOpenGLContext* m_context;
|
||||
QOpenGLFramebufferObject* m_fbo;
|
||||
#endif
|
||||
RendererEngine m_rendererEngine;
|
||||
};
|
||||
|
||||
/// Simple structure for storing rendered page images
|
||||
@@ -267,8 +213,7 @@ public:
|
||||
/// \param features Renderer features
|
||||
/// \param meshQualitySettings Mesh quality settings
|
||||
/// \param rasterizerCount Number of rasterizers
|
||||
/// \param useOpenGL Use OpenGL for rendering?
|
||||
/// \param surfaceFormat Surface format
|
||||
/// \param rendererEngine Renderer engine
|
||||
/// \param parent Parent object
|
||||
explicit PDFRasterizerPool(const PDFDocument* document,
|
||||
PDFFontCache* fontCache,
|
||||
@@ -277,8 +222,7 @@ public:
|
||||
PDFRenderer::Features features,
|
||||
const PDFMeshQualitySettings& meshQualitySettings,
|
||||
int rasterizerCount,
|
||||
bool useOpenGL,
|
||||
const QSurfaceFormat& surfaceFormat,
|
||||
RendererEngine rendererEngine,
|
||||
QObject* parent);
|
||||
|
||||
/// Acquire rasterizer. This function is thread safe.
|
||||
|
Reference in New Issue
Block a user