Issue #123: Alternative software rendering backend

This commit is contained in:
Jakub Melka 2024-02-04 18:05:38 +01:00
parent 87cedf01dc
commit d314683d38
48 changed files with 872 additions and 761 deletions

View File

@ -33,7 +33,6 @@ if(WIN32 AND MSVC)
option(PDF4QT_INSTALL_PREPARE_WIX_INSTALLER "Prepare Wix installer for Windows" ON)
endif()
option(PDF4QT_ENABLE_OPENGL "Enable OpenGL" ON)
option(PDF4QT_BUILD_ONLY_CORE_LIBRARY "Build only core library" OFF)
set(PDF4QT_QT_ROOT "" CACHE PATH "Qt root directory")
@ -49,18 +48,8 @@ include(GNUInstallDirs)
if(PDF4QT_BUILD_ONLY_CORE_LIBRARY)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Svg Xml)
if(PDF4QT_ENABLE_OPENGL)
add_compile_definitions(PDF4QT_ENABLE_OPENGL)
find_package(Qt6 REQUIRED COMPONENTS OpenGL)
endif()
else()
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Svg Xml PrintSupport TextToSpeech Test)
if(PDF4QT_ENABLE_OPENGL)
add_compile_definitions(PDF4QT_ENABLE_OPENGL)
find_package(Qt6 REQUIRED COMPONENTS OpenGL OpenGLWidgets)
endif()
endif()
qt_standard_project_setup()
@ -72,6 +61,7 @@ find_package(Freetype REQUIRED)
find_package(OpenJPEG CONFIG REQUIRED)
find_package(JPEG REQUIRED)
find_package(PNG REQUIRED)
find_package(blend2d CONFIG REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
@ -178,7 +168,7 @@ if(PDF4QT_INSTALL_DEPENDENCIES AND NOT PDF4QT_BUILD_ONLY_CORE_LIBRARY)
install(DIRECTORY ${PDF4QT_QT_ROOT}/bin/
RUNTIME DESTINATION ${PDF4QT_INSTALL_LIB_DIR}/
FILES_MATCHING
REGEX "(Qt6Core|Qt6Gui|Qt6PrintSupport|Qt6Svg|Qt6TextToSpeech|Qt6Widgets|Qt6Xml|Qt6OpenGL|Qt6OpenGLWidgets|Qt6Multimedia|Qt6Network)\\..*"
REGEX "(Qt6Core|Qt6Gui|Qt6PrintSupport|Qt6Svg|Qt6TextToSpeech|Qt6Widgets|Qt6Xml|Qt6Multimedia|Qt6Network)\\..*"
PATTERN "Debug" EXCLUDE
)

View File

@ -35,10 +35,6 @@ add_executable(Pdf4QtDocDiff
icon.rc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(Pdf4QtDocDiff PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(Pdf4QtDocDiff PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(Pdf4QtDocDiff PROPERTIES

View File

@ -24,7 +24,6 @@
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QApplication application(argc, argv);
QCoreApplication::setOrganizationName("MelkaJ");

View File

@ -24,7 +24,6 @@
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QApplication application(argc, argv);
QCoreApplication::setOrganizationName("MelkaJ");

View File

@ -35,9 +35,7 @@ PageItemDelegate::PageItemDelegate(PageItemModel* model, QObject* parent) :
m_rasterizer(nullptr)
{
m_rasterizer = new pdf::PDFRasterizer(this);
QSurfaceFormat format;
format.setSamples(16);
m_rasterizer->reset(false, format);
m_rasterizer->reset(pdf::RendererEngine::Blend2D);
}
PageItemDelegate::~PageItemDelegate()

View File

@ -163,10 +163,6 @@ target_link_libraries(Pdf4QtLibCore PRIVATE Freetype::Freetype)
target_link_libraries(Pdf4QtLibCore PRIVATE openjp2)
target_link_libraries(Pdf4QtLibCore PRIVATE JPEG::JPEG)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(Pdf4QtLibCore PRIVATE Qt6::OpenGL)
endif()
if(LINUX_GCC)
target_link_libraries(Pdf4QtLibCore PUBLIC TBB::tbb)
endif()
@ -178,6 +174,8 @@ endif()
target_include_directories(Pdf4QtLibCore INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/sources)
target_include_directories(Pdf4QtLibCore PUBLIC ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR})
target_link_libraries(Pdf4QtLibCore PRIVATE blend2d::blend2d)
set_target_properties(Pdf4QtLibCore PROPERTIES
VERSION ${PDF4QT_VERSION}
SOVERSION ${PDF4QT_VERSION}

View File

@ -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,

View File

@ -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);

View File

@ -187,8 +187,8 @@ private:
enum class RendererEngine
{
Software,
OpenGL
Blend2D,
QPainter,
};
enum class RenderingIntent

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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.

View File

@ -74,10 +74,6 @@ GENERATE_EXPORT_HEADER(Pdf4QtLibWidgets
PDF4QTLIBWIDGETSSHARED_EXPORT
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/pdf4qtlibwidgets_export.h")
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(Pdf4QtLibWidgets PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(Pdf4QtLibWidgets PRIVATE Pdf4QtLibCore Qt6::Core Qt6::Gui Qt6::Xml Qt6::Svg Qt6::Widgets)
if(LINUX_GCC)
@ -88,6 +84,8 @@ if(MINGW)
target_link_libraries(Pdf4QtLibWidgets PRIVATE Secur32 Mscms Gdi32 User32 crypt32)
endif()
target_link_libraries(Pdf4QtLibWidgets PRIVATE blend2d::blend2d)
target_include_directories(Pdf4QtLibWidgets INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/sources)
target_include_directories(Pdf4QtLibWidgets PUBLIC ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR})

View File

@ -24,6 +24,7 @@
#include "pdfannotation.h"
#include "pdfdrawwidget.h"
#include "pdfwidgetannotation.h"
#include "pdfpainterutils.h"
#include <QTimer>
#include <QPainter>
@ -31,6 +32,8 @@
#include <QScreen>
#include <QGuiApplication>
#include <Blend2d.h>
#include "pdfdbgheap.h"
namespace pdf
@ -469,7 +472,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
m_rasterizer(new PDFRasterizer(this)),
m_progress(nullptr),
m_cacheClearTimer(new QTimer(this)),
m_useOpenGL(false)
m_rendererEngine(RendererEngine::Blend2D)
{
m_controller = new PDFDrawSpaceController(this);
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
@ -765,6 +768,18 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
}
}
void PDFDrawWidgetProxy::draw(BLContext& context, QRect rect)
{
drawPages(context, rect, m_features);
for (IDocumentDrawInterface* drawInterface : m_drawInterfaces)
{
context.save();
drawInterface->drawPostRendering(context, rect);
context.restore();
}
}
QColor PDFDrawWidgetProxy::getPaperColor()
{
QColor paperColor = getCMSManager()->getCurrentCMS()->getPaperColor();
@ -920,6 +935,153 @@ void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::F
}
}
void PDFDrawWidgetProxy::drawPages(BLContext& context, QRect rect, PDFRenderer::Features features)
{
PDFPainterHelper::setBLBrush(context, QBrush(Qt::lightGray));
context.fillRect(rect);
BLMatrix2D baseMatrix = context.userMatrix();
// Use current paper color (it can be a bit different from white)
QColor paperColor = getPaperColor();
// Iterate trough pages and display them on the painter device
for (const LayoutItem& item : m_layout.items)
{
// The offsets m_horizontalOffset and m_verticalOffset are offsets to the
// topleft point of the block. But block maybe doesn't start at (0, 0),
// so we must also use translation from the block beginning.
QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top());
if (placedRect.intersects(rect))
{
GroupInfo groupInfo = getGroupInfo(item.groupIndex);
// Clear the page space by paper color
if (groupInfo.drawPaper)
{
PDFPainterHelper::setBLBrush(context, paperColor);
context.fillRect(placedRect);
}
const PDFPrecompiledPage* compiledPage = m_compiler->getCompiledPage(item.pageIndex, true);
if (compiledPage && compiledPage->isValid())
{
QElapsedTimer timer;
timer.start();
const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex);
QTransform matrix = QTransform(createPagePointToDevicePointMatrix(page, placedRect)) * baseMatrix;
compiledPage->draw(context, page->getCropBox(), matrix, features, groupInfo.transparency);
PDFTextLayoutGetter layoutGetter = m_textLayoutCompiler->getTextLayoutLazy(item.pageIndex);
// Draw text blocks/text lines, if it is enabled
if (features.testFlag(PDFRenderer::DebugTextBlocks))
{
m_textLayoutCompiler->makeTextLayout();
const PDFTextLayout& layout = layoutGetter;
const PDFTextBlocks& textBlocks = layout.getTextBlocks();
context.save();
painter->setFont(m_widget->font());
painter->setPen(Qt::red);
painter->setBrush(QColor(255, 0, 0, 128));
QFontMetricsF fontMetrics(painter->font(), painter->device());
int blockIndex = 1;
for (const PDFTextBlock& block : textBlocks)
{
QString blockNumber = QString::number(blockIndex++);
painter->drawPath(matrix.map(block.getBoundingBox()));
painter->drawText(matrix.map(block.getTopLeft()) - QPointF(fontMetrics.horizontalAdvance(blockNumber), 0), blockNumber, Qt::TextSingleLine, 0);
}
context.restore();
}
if (features.testFlag(PDFRenderer::DebugTextLines))
{
m_textLayoutCompiler->makeTextLayout();
const PDFTextLayout& layout = layoutGetter;
const PDFTextBlocks& textBlocks = layout.getTextBlocks();
context.save();
painter->setFont(m_widget->font());
painter->setPen(Qt::green);
painter->setBrush(QColor(0, 255, 0, 128));
QFontMetricsF fontMetrics(painter->font(), painter->device());
int lineIndex = 1;
for (const PDFTextBlock& block : textBlocks)
{
for (const PDFTextLine& line : block.getLines())
{
QString lineNumber = QString::number(lineIndex++);
painter->drawPath(matrix.map(line.getBoundingBox()));
painter->drawText(matrix.map(line.getTopLeft()) - QPointF(fontMetrics.horizontalAdvance(lineNumber), 0), lineNumber, Qt::TextSingleLine, 0);
}
}
context.restore();
}
QList<PDFRenderError> drawInterfaceErrors;
if (!features.testFlag(PDFRenderer::DenyExtraGraphics))
{
for (IDocumentDrawInterface* drawInterface : m_drawInterfaces)
{
context.save();
drawInterface->drawPage(context, item.pageIndex, compiledPage, layoutGetter, matrix, drawInterfaceErrors);
context.restore();
}
}
const qint64 drawTimeNS = timer.nsecsElapsed();
// Draw rendering times
if (features.testFlag(PDFRenderer::DisplayTimes))
{
QFont font = m_widget->font();
font.setPointSize(12);
auto formatDrawTime = [](qint64 nanoseconds)
{
PDFReal miliseconds = nanoseconds / 1000000.0;
return QString::number(miliseconds, 'f', 3);
};
QFontMetrics fontMetrics(font);
const int lineSpacing = fontMetrics.lineSpacing();
context.save();
painter->setPen(Qt::red);
painter->setFont(font);
context.translate(placedRect.topLeft());
context.translate(placedRect.width() / 20.0, placedRect.height() / 20.0); // Offset
painter->setBackground(QBrush(Qt::white));
painter->setBackgroundMode(Qt::OpaqueMode);
painter->drawText(0, 0, PDFTranslationContext::tr("Compile time: %1 [ms]").arg(formatDrawTime(compiledPage->getCompilingTimeNS())));
painter->translate(0, lineSpacing);
painter->drawText(0, 0, PDFTranslationContext::tr("Draw time: %1 [ms]").arg(formatDrawTime(drawTimeNS)));
context.restore();
}
const QList<PDFRenderError>& pageErrors = compiledPage->getErrors();
if (!pageErrors.empty() || !drawInterfaceErrors.empty())
{
QList<PDFRenderError> errors = pageErrors;
if (!drawInterfaceErrors.isEmpty())
{
errors.append(drawInterfaceErrors);
}
Q_EMIT renderingError(item.pageIndex, qMove(errors));
}
}
}
}
}
QImage PDFDrawWidgetProxy::drawThumbnailImage(PDFInteger pageIndex, int pixelSize) const
{
QImage image;
@ -1403,11 +1565,10 @@ bool PDFDrawWidgetProxy::isBlockMode() const
return false;
}
void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat)
void PDFDrawWidgetProxy::updateRenderer(RendererEngine rendererEngine)
{
m_useOpenGL = useOpenGL;
m_surfaceFormat = surfaceFormat;
m_rasterizer->reset(useOpenGL && ENABLE_OPENGL_FOR_THUMBNAILS, surfaceFormat);
m_rendererEngine = rendererEngine;
m_rasterizer->reset(m_rendererEngine);
}
void PDFDrawWidgetProxy::prefetchPages(PDFInteger pageIndex)

View File

@ -34,6 +34,8 @@ class QPainter;
class QScrollBar;
class QTimer;
class BLContext;
namespace pdf
{
class PDFProgress;
@ -212,6 +214,15 @@ public:
/// \param rect Rectangle in which the content is painted
void draw(QPainter* painter, QRect rect);
/// Draws the actually visible pages on the context using the rectangle.
/// Rectangle is space in the widget, which is used for painting the PDF.
/// This function is using drawPages function to draw all pages. After that,
/// custom drawing is performed.
/// \sa drawPages
/// \param context Context to paint the PDF pages
/// \param rect Rectangle in which the content is painted
void draw(BLContext& context, QRect rect);
/// Draws the actually visible pages on the painter using the rectangle.
/// Rectangle is space in the widget, which is used for painting the PDF.
/// \param painter Painter to paint the PDF pages
@ -219,6 +230,13 @@ public:
/// \param features Rendering features
void drawPages(QPainter* painter, QRect rect, PDFRenderer::Features features);
/// Draws the actually visible pages on the painter using the rectangle.
/// Rectangle is space in the widget, which is used for painting the PDF.
/// \param painter Painter to paint the PDF pages
/// \param rect Rectangle in which the content is painted
/// \param features Rendering features
void drawPages(BLContext& context, QRect rect, PDFRenderer::Features features);
/// Draws thumbnail image of the given size (so larger of the page size
/// width or height equals to pixel size and the latter size is rescaled
/// using the aspect ratio)
@ -317,9 +335,8 @@ public:
/// Updates renderer (in current implementation, renderer for page thumbnails)
/// using given parameters.
/// \param useOpenGL Use OpenGL for rendering?
/// \param surfaceFormat Surface format for OpenGL rendering
void updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat);
/// \param rendererEngine Renderer engine
void updateRenderer(RendererEngine rendererEngine);
/// Prefetches (prerenders) pages after page with pageIndex, i.e., prepares
/// for non-flickering scroll operation.
@ -338,8 +355,7 @@ public:
void setProgress(PDFProgress* progress) { m_progress = progress; }
PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; }
PDFWidget* getWidget() const { return m_widget; }
bool isUsingOpenGL() const { return m_useOpenGL; }
const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; }
RendererEngine getRendererEngine() const { return m_rendererEngine; }
PageRotation getPageRotation() const { return m_controller->getPageRotation(); }
void setFeatures(PDFRenderer::Features features);
@ -461,8 +477,6 @@ private:
constexpr inline T bound(T value) { return qBound(min, value, max); }
};
static constexpr bool ENABLE_OPENGL_FOR_THUMBNAILS = false;
/// Flag, disables the update
bool m_updateDisabled;
@ -536,11 +550,8 @@ private:
/// Additional drawing interfaces
std::set<IDocumentDrawInterface*> m_drawInterfaces;
/// Use OpenGL for rendering?
bool m_useOpenGL;
/// Surface format for OpenGL
QSurfaceFormat m_surfaceFormat;
/// Renderer engine
RendererEngine m_rendererEngine;
/// Page group info for rendering. Group of pages
/// can be rendered with transparency or without paper

View File

@ -30,12 +30,14 @@
#include <QPixmapCache>
#include <QColorSpace>
#include <Blend2d.h>
#include "pdfdbgheap.h"
namespace pdf
{
PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) :
PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, QWidget* parent) :
QWidget(parent),
m_cmsManager(cmsManager),
m_toolManager(nullptr),
@ -44,9 +46,10 @@ PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int
m_drawWidget(nullptr),
m_horizontalScrollBar(nullptr),
m_verticalScrollBar(nullptr),
m_proxy(nullptr)
m_proxy(nullptr),
m_rendererEngine(engine)
{
m_drawWidget = createDrawWidget(getEffectiveRenderer(engine), samplesCount);
m_drawWidget = new PDFDrawWidget(this, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
@ -62,10 +65,10 @@ PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int
m_proxy = new PDFDrawWidgetProxy(this);
m_proxy->init(this);
m_proxy->updateRenderer(m_rendererEngine);
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged);
updateRendererImpl();
}
PDFWidget::~PDFWidget()
@ -90,39 +93,10 @@ void PDFWidget::setDocument(const PDFModifiedDocument& document)
m_drawWidget->getWidget()->update();
}
void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount)
void PDFWidget::updateRenderer(RendererEngine engine)
{
engine = getEffectiveRenderer(engine);
PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast<PDFOpenGLDrawWidget*>(m_drawWidget->getWidget());
PDFDrawWidget* softwareDrawWidget = qobject_cast<PDFDrawWidget*>(m_drawWidget->getWidget());
// Do we need to change renderer?
if ((openglDrawWidget && engine != RendererEngine::OpenGL) || (softwareDrawWidget && engine != RendererEngine::Software))
{
QGridLayout* layout = qobject_cast<QGridLayout*>(this->layout());
layout->removeWidget(m_drawWidget->getWidget());
delete m_drawWidget->getWidget();
m_drawWidget = createDrawWidget(engine, samplesCount);
layout->addWidget(m_drawWidget->getWidget(), 0, 0);
setFocusProxy(m_drawWidget->getWidget());
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update));
}
#ifdef PDF4QT_ENABLE_OPENGL
else if (openglDrawWidget)
{
// Just check the samples count
QSurfaceFormat format = openglDrawWidget->format();
if (format.samples() != samplesCount)
{
format.setSamples(samplesCount);
openglDrawWidget->setFormat(format);
}
}
#endif
updateRendererImpl();
m_rendererEngine = engine;
m_proxy->updateRenderer(m_rendererEngine);
}
void PDFWidget::updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit)
@ -142,16 +116,6 @@ int PDFWidget::getPageRenderingErrorCount() const
return count;
}
void PDFWidget::updateRendererImpl()
{
#ifdef PDF4QT_ENABLE_OPENGL
PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast<PDFOpenGLDrawWidget*>(m_drawWidget->getWidget());
m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat());
#else
m_proxy->updateRenderer(false, QSurfaceFormat::defaultFormat());
#endif
}
void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors)
{
// Empty list of error should not be reported!
@ -182,29 +146,6 @@ void PDFWidget::onPageImageChanged(bool all, const std::vector<PDFInteger>& page
}
}
IDrawWidget* PDFWidget::createDrawWidget(RendererEngine rendererEngine, int samplesCount)
{
switch (rendererEngine)
{
case RendererEngine::Software:
return new PDFDrawWidget(this, this);
case RendererEngine::OpenGL:
#ifdef PDF4QT_ENABLE_OPENGL
return new PDFOpenGLDrawWidget(this, samplesCount, this);
#else
Q_UNUSED(samplesCount);
return new PDFDrawWidget(this, this);
#endif
default:
Q_ASSERT(false);
break;
}
return nullptr;
}
void PDFWidget::removeInputInterface(IDrawWidgetInputInterface* inputInterface)
{
auto it = std::find(m_inputInterfaces.begin(), m_inputInterfaces.end(), inputInterface);
@ -223,16 +164,6 @@ void PDFWidget::addInputInterface(IDrawWidgetInputInterface* inputInterface)
}
}
RendererEngine PDFWidget::getEffectiveRenderer(RendererEngine rendererEngine)
{
if (rendererEngine == RendererEngine::OpenGL && !pdf::PDFRendererInfo::isHardwareAccelerationSupported())
{
return RendererEngine::Software;
}
return rendererEngine;
}
PDFWidgetFormManager* PDFWidget::getFormManager() const
{
return m_formManager;
@ -259,43 +190,38 @@ void PDFWidget::setAnnotationManager(PDFWidgetAnnotationManager* annotationManag
addInputInterface(m_annotationManager);
}
template<typename BaseWidget>
PDFDrawWidgetBase<BaseWidget>::PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent) :
BaseWidget(parent),
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
BaseClass(parent),
m_widget(widget),
m_mouseOperation(MouseOperation::None)
{
this->setFocusPolicy(Qt::StrongFocus);
this->setMouseTracking(true);
QObject::connect(&m_autoScrollTimer, &QTimer::timeout, this, &PDFDrawWidgetBase::onAutoScrollTimeout);
QObject::connect(&m_autoScrollTimer, &QTimer::timeout, this, &PDFDrawWidget::onAutoScrollTimeout);
}
template<typename BaseWidget>
std::vector<PDFInteger> PDFDrawWidgetBase<BaseWidget>::getCurrentPages() const
std::vector<PDFInteger> PDFDrawWidget::getCurrentPages() const
{
return this->m_widget->getDrawWidgetProxy()->getPagesIntersectingRect(this->rect());
}
template<typename BaseWidget>
QSize PDFDrawWidgetBase<BaseWidget>::minimumSizeHint() const
QSize PDFDrawWidget::minimumSizeHint() const
{
return QSize(200, 200);
}
template<typename BaseWidget>
bool PDFDrawWidgetBase<BaseWidget>::event(QEvent* event)
bool PDFDrawWidget::event(QEvent* event)
{
if (event->type() == QEvent::ShortcutOverride)
{
return processEvent<QKeyEvent, &IDrawWidgetInputInterface::shortcutOverrideEvent>(static_cast<QKeyEvent*>(event));
}
return BaseWidget::event(event);
return BaseClass::event(event);
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::performMouseOperation(QPoint currentMousePosition)
void PDFDrawWidget::performMouseOperation(QPoint currentMousePosition)
{
switch (m_mouseOperation)
{
@ -323,9 +249,8 @@ void PDFDrawWidgetBase<BaseWidget>::performMouseOperation(QPoint currentMousePos
}
}
template<typename BaseWidget>
template<typename Event, void (IDrawWidgetInputInterface::* Function)(QWidget*, Event*)>
bool PDFDrawWidgetBase<BaseWidget>::processEvent(Event* event)
bool PDFDrawWidget::processEvent(Event* event)
{
QString tooltip;
for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces())
@ -351,8 +276,7 @@ bool PDFDrawWidgetBase<BaseWidget>::processEvent(Event* event)
return false;
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
void PDFDrawWidget::keyPressEvent(QKeyEvent* event)
{
event->ignore();
@ -388,8 +312,7 @@ void PDFDrawWidgetBase<BaseWidget>::keyPressEvent(QKeyEvent* event)
updateCursor();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::keyReleaseEvent(QKeyEvent* event)
void PDFDrawWidget::keyReleaseEvent(QKeyEvent* event)
{
event->ignore();
@ -401,8 +324,7 @@ void PDFDrawWidgetBase<BaseWidget>::keyReleaseEvent(QKeyEvent* event)
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mousePressEvent(QMouseEvent* event)
void PDFDrawWidget::mousePressEvent(QMouseEvent* event)
{
event->ignore();
@ -442,8 +364,7 @@ void PDFDrawWidgetBase<BaseWidget>::mousePressEvent(QMouseEvent* event)
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseDoubleClickEvent(QMouseEvent* event)
void PDFDrawWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
event->ignore();
@ -453,8 +374,7 @@ void PDFDrawWidgetBase<BaseWidget>::mouseDoubleClickEvent(QMouseEvent* event)
}
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
void PDFDrawWidget::mouseReleaseEvent(QMouseEvent* event)
{
event->ignore();
@ -491,8 +411,7 @@ void PDFDrawWidgetBase<BaseWidget>::mouseReleaseEvent(QMouseEvent* event)
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::mouseMoveEvent(QMouseEvent* event)
void PDFDrawWidget::mouseMoveEvent(QMouseEvent* event)
{
event->ignore();
@ -506,8 +425,7 @@ void PDFDrawWidgetBase<BaseWidget>::mouseMoveEvent(QMouseEvent* event)
event->accept();
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::updateCursor()
void PDFDrawWidget::updateCursor()
{
std::optional<QCursor> cursor;
@ -554,8 +472,7 @@ void PDFDrawWidgetBase<BaseWidget>::updateCursor()
}
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::onAutoScrollTimeout()
void PDFDrawWidget::onAutoScrollTimeout()
{
if (m_mouseOperation != MouseOperation::AutoScroll)
{
@ -579,8 +496,7 @@ void PDFDrawWidgetBase<BaseWidget>::onAutoScrollTimeout()
proxy->scrollByPixels(QPoint(scrollX, scrollY));
}
template<typename BaseWidget>
void PDFDrawWidgetBase<BaseWidget>::wheelEvent(QWheelEvent* event)
void PDFDrawWidget::wheelEvent(QWheelEvent* event)
{
event->ignore();
@ -659,62 +575,56 @@ void PDFDrawWidgetBase<BaseWidget>::wheelEvent(QWheelEvent* event)
event->accept();
}
#ifdef PDF4QT_ENABLE_OPENGL
PDFOpenGLDrawWidget::PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent) :
BaseClass(widget, parent)
{
QSurfaceFormat format = this->format();
format.setProfile(QSurfaceFormat::CoreProfile);
format.setSamples(samplesCount);
format.setColorSpace(QColorSpace(QColorSpace::SRgb));
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
setFormat(format);
}
PDFOpenGLDrawWidget::~PDFOpenGLDrawWidget()
{
}
void PDFOpenGLDrawWidget::resizeGL(int w, int h)
{
QOpenGLWidget::resizeGL(w, h);
getPDFWidget()->getDrawWidgetProxy()->update();
}
void PDFOpenGLDrawWidget::initializeGL()
{
QOpenGLWidget::initializeGL();
}
void PDFOpenGLDrawWidget::paintGL()
{
if (this->isValid())
{
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
}
}
#endif
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
BaseClass(widget, parent)
{
}
PDFDrawWidget::~PDFDrawWidget()
{
}
void PDFDrawWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
switch (getPDFWidget()->getDrawWidgetProxy()->getRendererEngine())
{
case RendererEngine::Blend2D:
{
BLContext blContext;
BLImage blImage;
QRect rect = this->rect();
if (m_blend2DframeBuffer.size() != rect.size())
{
m_blend2DframeBuffer = QImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
}
BLContextCreateInfo info{};
info.reset();
info.flags = BL_CONTEXT_CREATE_FLAG_FALLBACK_TO_SYNC;
info.threadCount = QThread::idealThreadCount();
blContext.setHint(BL_CONTEXT_HINT_RENDERING_QUALITY, BL_RENDERING_QUALITY_MAX_VALUE);
blImage.createFromData(m_blend2DframeBuffer.width(), m_blend2DframeBuffer.height(), BL_FORMAT_PRGB32, m_blend2DframeBuffer.bits(), m_blend2DframeBuffer.bytesPerLine());
if (blContext.begin(blImage, info) == BL_SUCCESS)
{
blContext.clearAll();
getPDFWidget()->getDrawWidgetProxy()->draw(blContext, rect);
blContext.end();
QPainter painter(this);
painter.drawImage(QPoint(0, 0), m_blend2DframeBuffer);
}
break;
}
case RendererEngine::QPainter:
{
QPainter painter(this);
getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect());
m_blend2DframeBuffer = QImage();
break;
}
default:
Q_ASSERT(false);
break;
}
}
void PDFDrawWidget::resizeEvent(QResizeEvent* event)
@ -724,9 +634,4 @@ void PDFDrawWidget::resizeEvent(QResizeEvent* event)
getPDFWidget()->getDrawWidgetProxy()->update();
}
#ifdef PDF4QT_ENABLE_OPENGL
template class PDFDrawWidgetBase<QOpenGLWidget>;
#endif
template class PDFDrawWidgetBase<QWidget>;
} // namespace pdf

View File

@ -27,10 +27,6 @@
#include <QTimer>
#include <QElapsedTimer>
#ifdef PDF4QT_ENABLE_OPENGL
#include <QOpenGLWidget>
#endif
namespace pdf
{
class PDFDocument;
@ -65,8 +61,7 @@ public:
/// Constructs new PDFWidget.
/// \param cmsManager Color management system manager
/// \param engine Rendering engine type
/// \param samplesCount Samples count for rendering engine MSAA antialiasing
explicit PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent);
explicit PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, QWidget* parent);
virtual ~PDFWidget() override;
virtual bool focusNextPrevChild(bool next) override;
@ -81,8 +76,7 @@ public:
/// Update rendering engine according the settings
/// \param engine Engine type
/// \param samplesCount Samples count for rendering engine MSAA antialiasing
void updateRenderer(RendererEngine engine, int samplesCount);
void updateRenderer(RendererEngine engine);
/// Updates cache limits
/// \param compiledPageCacheLimit Compiled page cache limit [bytes]
@ -115,14 +109,9 @@ signals:
void pageRenderingErrorsChanged(pdf::PDFInteger pageIndex, int errorsCount);
private:
RendererEngine getEffectiveRenderer(RendererEngine rendererEngine);
void updateRendererImpl();
void onRenderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
void onPageImageChanged(bool all, const std::vector<PDFInteger>& pages);
IDrawWidget* createDrawWidget(RendererEngine rendererEngine, int samplesCount);
const PDFCMSManager* m_cmsManager;
PDFToolManager* m_toolManager;
PDFWidgetAnnotationManager* m_annotationManager;
@ -133,14 +122,19 @@ private:
PDFDrawWidgetProxy* m_proxy;
PageRenderingErrors m_pageRenderingErrors;
std::vector<IDrawWidgetInputInterface*> m_inputInterfaces;
RendererEngine m_rendererEngine;
};
template<typename BaseWidget>
class PDFDrawWidgetBase : public BaseWidget, public IDrawWidget
class PDFDrawWidget : public QWidget, public IDrawWidget
{
Q_OBJECT
private:
using BaseClass = QWidget;
public:
explicit PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent);
virtual ~PDFDrawWidgetBase() override = default;
explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent);
virtual ~PDFDrawWidget() override = default;
/// Returns page indices, which are currently displayed in the widget
virtual std::vector<PDFInteger> getCurrentPages() const override;
@ -158,6 +152,8 @@ protected:
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void mouseMoveEvent(QMouseEvent* event) override;
virtual void wheelEvent(QWheelEvent* event) override;
virtual void paintEvent(QPaintEvent* event) override;
virtual void resizeEvent(QResizeEvent* event) override;
PDFWidget* getPDFWidget() const { return m_widget; }
@ -186,50 +182,9 @@ private:
QTimer m_autoScrollTimer;
QPointF m_autoScrollOffset;
QElapsedTimer m_autoScrollLastElapsedTimer;
QImage m_blend2DframeBuffer;
};
class PDFDrawWidget : public PDFDrawWidgetBase<QWidget>
{
Q_OBJECT
private:
using BaseClass = PDFDrawWidgetBase<QWidget>;
public:
explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent);
virtual ~PDFDrawWidget() override;
protected:
virtual void paintEvent(QPaintEvent* event) override;
virtual void resizeEvent(QResizeEvent* event) override;
};
#ifdef PDF4QT_ENABLE_OPENGL
class PDFOpenGLDrawWidget : public PDFDrawWidgetBase<QOpenGLWidget>
{
Q_OBJECT
private:
using BaseClass = PDFDrawWidgetBase<QOpenGLWidget>;
public:
explicit PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent);
virtual ~PDFOpenGLDrawWidget() override;
protected:
virtual void resizeGL(int w, int h) override;
virtual void initializeGL() override;
virtual void paintGL() override;
};
#else
using PDFOpenGLDrawWidget = PDFDrawWidget;
#endif
#ifdef PDF4QT_ENABLE_OPENGL
extern template class PDFDrawWidgetBase<QOpenGLWidget>;
#endif
extern template class PDFDrawWidgetBase<QWidget>;
} // namespace pdf
#endif // PDFDRAWWIDGET_H

View File

@ -539,4 +539,9 @@ void PDFWidgetAnnotationManager::drawPage(QPainter* painter,
BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
}
void PDFWidgetAnnotationManager::drawPage(BLContext& context, PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, const QTransform& pagePointToDevicePointMatrix, QList<PDFRenderError>& errors) const
{
BaseClass::drawPage(context, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors);
}
} // namespace pdf

View File

@ -57,6 +57,13 @@ public:
const QTransform& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const override;
virtual void drawPage(BLContext& context,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QTransform& pagePointToDevicePointMatrix,
QList<PDFRenderError>& errors) const override;
/// Returns tooltip generated from annotation
virtual QString getTooltip() const override { return m_tooltip; }

View File

@ -86,10 +86,6 @@ GENERATE_EXPORT_HEADER(Pdf4QtViewer
PDF4QTVIEWERLIBSHARED_EXPORT
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/pdf4qtviewer_export.h")
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(Pdf4QtViewer PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(Pdf4QtViewer PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport Qt6::TextToSpeech Qt6::Xml Qt6::Svg)
target_include_directories(Pdf4QtViewer INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(Pdf4QtViewer PUBLIC ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR})

View File

@ -2298,24 +2298,6 @@ void PDFProgramController::resetSettings()
}
}
void PDFProgramController::checkHardwareOpenGLAvailability()
{
if (m_settings->getRendererEngine() == pdf::RendererEngine::OpenGL &&
!pdf::PDFRendererInfo::isHardwareAccelerationSupported())
{
#ifdef PDF4QT_ENABLE_OPENGL
pdf::PDFRendererInfo::Info info = pdf::PDFRendererInfo::getHardwareAccelerationSupportedInfo();
QMessageBox::warning(m_mainWindow, tr("Warning"),
tr("Hardware acceleration is not supported on this device. "
"OpenGL version at least 3.2 is required. Software rendering is used instead. "
"Available OpenGL is %1 using %2. You can turn off hardware acceleration "
"in 'Tools' menu using 'Options' item to stop displaying this message.").arg(info.version, info.renderer));
#else
QMessageBox::warning(m_mainWindow, tr("Warning"), tr("Hardware acceleration is not enabled in this build."));
#endif
}
}
void PDFProgramController::onActionOptionsTriggered()
{
PDFViewerSettingsDialog::OtherSettings otherSettings;

View File

@ -297,8 +297,6 @@ public:
void writeSettings();
void resetSettings();
void checkHardwareOpenGLAvailability();
void performPrint();
void performSave();
void performSaveAs();

View File

@ -299,7 +299,7 @@ void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button)
m_cms = m_proxy->getCMSManager()->getCurrentCMS();
m_rasterizerPool = new pdf::PDFRasterizerPool(m_document, m_proxy->getFontCache(), m_proxy->getCMSManager(),
m_optionalContentActivity, m_proxy->getFeatures(), m_proxy->getMeshQualitySettings(),
pdf::PDFRasterizerPool::getDefaultRasterizerCount(), m_proxy->isUsingOpenGL(), m_proxy->getSurfaceFormat(), this);
pdf::PDFRasterizerPool::getDefaultRasterizerCount(), m_proxy->getRendererEngine(), this);
connect(m_rasterizerPool, &pdf::PDFRasterizerPool::renderError, this, &PDFRenderToImagesDialog::onRenderError);
auto process = [this]()

View File

@ -543,7 +543,6 @@ void PDFViewerMainWindow::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
m_progressTaskbarIndicator->setWindow(windowHandle());
QTimer::singleShot(0, this, [this] { m_programController->checkHardwareOpenGLAvailability(); });
}
void PDFViewerMainWindow::dragEnterEvent(QDragEnterEvent* event)

View File

@ -432,7 +432,6 @@ void PDFViewerMainWindowLite::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
m_progressTaskbarIndicator->setWindow(windowHandle());
QTimer::singleShot(0, this, [this] { m_programController->checkHardwareOpenGLAvailability(); });
}
void PDFViewerMainWindowLite::dragEnterEvent(QDragEnterEvent* event)

View File

@ -43,7 +43,7 @@ void PDFViewerSettings::readSettings(QSettings& settings, const pdf::PDFCMSSetti
settings.beginGroup("ViewerSettings");
m_settings.m_directory = settings.value("defaultDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString();
m_settings.m_features = static_cast<pdf::PDFRenderer::Features>(settings.value("rendererFeaturesv2", static_cast<int>(pdf::PDFRenderer::getDefaultFeatures())).toInt());
m_settings.m_rendererEngine = static_cast<pdf::RendererEngine>(settings.value("renderingEngine", static_cast<int>(pdf::RendererEngine::OpenGL)).toInt());
m_settings.m_rendererEngine = static_cast<pdf::RendererEngine>(settings.value("renderingEngine", static_cast<int>(pdf::RendererEngine::Blend2D)).toInt());
m_settings.m_multisampleAntialiasing = settings.value("msaa", defaultSettings.m_multisampleAntialiasing).toBool();
m_settings.m_rendererSamples = settings.value("rendererSamples", defaultSettings.m_rendererSamples).toInt();
m_settings.m_prefetchPages = settings.value("prefetchPages", defaultSettings.m_prefetchPages).toBool();
@ -271,7 +271,7 @@ void PDFViewerSettings::setColorTolerance(pdf::PDFReal colorTolerance)
PDFViewerSettings::Settings::Settings() :
m_features(pdf::PDFRenderer::getDefaultFeatures()),
m_rendererEngine(pdf::RendererEngine::OpenGL),
m_rendererEngine(pdf::RendererEngine::Blend2D),
m_multisampleAntialiasing(true),
m_rendererSamples(16),
m_prefetchPages(true),

View File

@ -52,8 +52,6 @@ public:
pdf::PDFRenderer::Features m_features;
QString m_directory;
pdf::RendererEngine m_rendererEngine;
bool m_multisampleAntialiasing;
int m_rendererSamples;
bool m_prefetchPages;
pdf::PDFReal m_preferredMeshResolutionRatio;
pdf::PDFReal m_minimalMeshResolutionRatio;

View File

@ -102,8 +102,8 @@ PDFViewerSettingsDialog::PDFViewerSettingsDialog(const PDFViewerSettings::Settin
font.setPointSize(font.pointSize() * 1.5);
ui->optionsPagesWidget->setFont(font);
ui->renderingEngineComboBox->addItem(tr("Software"), static_cast<int>(pdf::RendererEngine::Software));
ui->renderingEngineComboBox->addItem(tr("Hardware accelerated (OpenGL)"), static_cast<int>(pdf::RendererEngine::OpenGL));
ui->renderingEngineComboBox->addItem(tr("Software | QPainter"), static_cast<int>(pdf::RendererEngine::QPainter));
ui->renderingEngineComboBox->addItem(tr("Software | Blend2D | Multithreaded"), static_cast<int>(pdf::RendererEngine::Blend2D));
for (int i : { 1, 2, 4, 8, 16 })
{
@ -279,29 +279,6 @@ void PDFViewerSettingsDialog::loadData()
ui->renderingEngineComboBox->setCurrentIndex(ui->renderingEngineComboBox->findData(static_cast<int>(m_settings.m_rendererEngine)));
// Engine
if (m_settings.m_rendererEngine == pdf::RendererEngine::OpenGL)
{
ui->multisampleAntialiasingCheckBox->setEnabled(true);
ui->multisampleAntialiasingCheckBox->setChecked(m_settings.m_multisampleAntialiasing);
if (m_settings.m_multisampleAntialiasing)
{
ui->multisampleAntialiasingSamplesCountComboBox->setEnabled(true);
ui->multisampleAntialiasingSamplesCountComboBox->setCurrentIndex(ui->multisampleAntialiasingSamplesCountComboBox->findData(m_settings.m_rendererSamples));
}
else
{
ui->multisampleAntialiasingSamplesCountComboBox->setEnabled(false);
ui->multisampleAntialiasingSamplesCountComboBox->setCurrentIndex(-1);
}
}
else
{
ui->multisampleAntialiasingCheckBox->setEnabled(false);
ui->multisampleAntialiasingCheckBox->setChecked(false);
ui->multisampleAntialiasingSamplesCountComboBox->setEnabled(false);
ui->multisampleAntialiasingSamplesCountComboBox->setCurrentIndex(-1);
}
ui->prefetchPagesCheckBox->setChecked(m_settings.m_prefetchPages);
ui->multithreadingComboBox->setCurrentIndex(ui->multithreadingComboBox->findData(static_cast<int>(m_settings.m_multithreadingStrategy)));

View File

@ -35,7 +35,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>6</number>
<number>0</number>
</property>
<widget class="QWidget" name="enginePage">
<layout class="QVBoxLayout" name="enginePageLayout">
@ -59,47 +59,16 @@
<layout class="QVBoxLayout" name="engineSettingsGroupBoxLayout">
<item>
<layout class="QGridLayout" name="engineSettingsGridLayout">
<item row="1" column="1">
<widget class="QCheckBox" name="multisampleAntialiasingCheckBox">
<item row="2" column="0">
<widget class="QLabel" name="multithreadingLabel">
<property name="text">
<string>Enable</string>
<string>Multithreading strategy</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="renderingEngineComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="multisampleAntialiasingLabel">
<property name="text">
<string>Multisample antialiasing (MSAA)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="multisampleAntialiasingSamplesCountComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="prefetchPagesLabel">
<property name="text">
<string>Prefetch pages</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="multisampleAntiailasingSamplesLabel">
<property name="text">
<string>MSAA Samples count</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="prefetchPagesCheckBox">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="renderingEngineLabel">
<property name="text">
@ -107,22 +76,29 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="multithreadingLabel">
<item row="1" column="0">
<widget class="QLabel" name="prefetchPagesLabel">
<property name="text">
<string>Multithreading strategy</string>
<string>Prefetch pages</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="multithreadingComboBox"/>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="prefetchPagesCheckBox">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="engineInfoLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Choose a rendering method based on your needs. Although &lt;span style=&quot; font-weight:600;&quot;&gt;Software Rendering&lt;/span&gt; is slower than the hardware-accelerated &lt;span style=&quot; font-weight:600;&quot;&gt;OpenGL Rendering&lt;/span&gt;, it can be utilized when OpenGL is not available on your platform. The default (and recommended) method is OpenGL Rendering. &lt;/p&gt;&lt;p&gt;OpenGL rendering employs &lt;span style=&quot; font-weight:600;&quot;&gt;Multisample Antialiasing (MSAA)&lt;/span&gt;, ensuring high-quality image rendering. You have the option to enable or disable this feature. However, disabling it might lead to inferior image quality. The Samples Count sets the number of samples per pixel used to determine pixel color. It can be set to 1, 2, 4, 8, or 16. Most modern GPUs support at least a value of 8. If your GPU doesn't support the desired sample count, lower this value. &lt;/p&gt;&lt;p&gt;The &lt;span style=&quot; font-weight:600;&quot;&gt;Prefetch Pages&lt;/span&gt; feature pre-renders pages adjacent to the currently viewed pages, minimizing flickering during scrolling. Prefetched pages are stored in the page cache. &lt;/p&gt;&lt;p&gt;The &lt;span style=&quot; font-weight:600;&quot;&gt;Multithreading Strategy&lt;/span&gt; determines how the program will utilize CPU cores. With the &lt;span style=&quot; font-weight:600;&quot;&gt;Single Thread&lt;/span&gt; strategy, only one CPU core is used for rendering a page. This results in longer processing times, but each page is independently compiled/drawn in its own thread. Alternatively, there are two multithreading strategies: Load Balanced and Maximum Threads. Load Balanced only parallelizes pages without processing individual page content. In contrast, the Maximum Threads strategy spawns as many threads as possible for operations to achieve optimal performance. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a rendering method tailored to your application's requirements. Software Rendering, utilizing QPainter, is a versatile choice that guarantees compatibility across all platforms. It's particularly useful in scenarios where direct access to hardware acceleration isn't crucial. QPainter, part of the Qt framework, excels in rendering 2D graphics with support for various painting styles, image processing, and intricate graphical transformations, making it an excellent tool for applications that require detailed and sophisticated 2D graphics without relying on hardware acceleration.&lt;/p&gt;&lt;p&gt;On the other hand, for applications that demand high-performance rendering, leveraging the Blend2D library offers a compelling alternative. Blend2D is a high-performance 2D vector graphics engine that utilizes multi-threading to accelerate the rendering process. It does not rely on QPainter or hardware acceleration but instead offers a software-based rendering solution optimized for speed and quality. Blend2D's advanced anti-aliasing techniques ensure crisp and clear image quality, making it suitable for applications where rendering performance and image quality are paramount.&lt;/p&gt;&lt;p&gt;The Prefetch Pages feature is a strategy that can be applied regardless of the rendering method chosen. By pre-rendering pages adjacent to the currently viewed content, this approach minimizes flickering and enhances the smoothness of transitions during scrolling, improving the overall user experience.&lt;/p&gt;&lt;p&gt;When it comes to optimizing the rendering process, the choice of multithreading strategy plays a crucial role. A Single Thread strategy, where rendering tasks are executed sequentially on a single CPU core, might be preferable in environments where simplicity and predictability are key. For more demanding applications, employing a Multi-threading strategy can significantly improve rendering times. Strategies like Load Balanced distribute the workload evenly across CPU cores without delving into content-specific processing, offering a good performance boost. The Maximum Threads strategy takes full advantage of available CPU resources by allocating as many threads as possible to the rendering tasks, achieving optimal performance and minimizing rendering times.&lt;/p&gt;&lt;p&gt;This delineation between using QPainter for software rendering and Blend2D for high-performance, multi-threaded rendering allows developers to choose the most appropriate rendering pathway based on their specific performance requirements and the graphical complexity of their application.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>

View File

@ -24,7 +24,6 @@
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QApplication application(argc, argv);
QCoreApplication::setOrganizationName("MelkaJ");

View File

@ -23,10 +23,6 @@ add_library(AudioBookPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(AudioBookPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(AudioBookPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
if(MINGW)

View File

@ -23,10 +23,6 @@ add_library(DimensionsPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(DimensionsPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(DimensionsPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(DimensionsPlugin PROPERTIES

View File

@ -29,10 +29,6 @@ add_library(ObjectInspectorPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(ObjectInspectorPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(ObjectInspectorPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(ObjectInspectorPlugin PROPERTIES

View File

@ -25,10 +25,6 @@ add_library(OutputPreviewPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(OutputPreviewPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(OutputPreviewPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(OutputPreviewPlugin PROPERTIES

View File

@ -22,10 +22,6 @@ add_library(RedactPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(RedactPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(RedactPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(RedactPlugin PROPERTIES

View File

@ -22,10 +22,6 @@ add_library(SignaturePlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(SignaturePlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(SignaturePlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
target_link_libraries(SignaturePlugin PRIVATE OpenSSL::SSL OpenSSL::Crypto)

View File

@ -22,10 +22,6 @@ add_library(SoftProofingPlugin SHARED
icons.qrc
)
if(PDF4QT_ENABLE_OPENGL)
target_link_libraries(SoftProofingPlugin PRIVATE Qt6::OpenGLWidgets)
endif()
target_link_libraries(SoftProofingPlugin PRIVATE Pdf4QtLibCore Pdf4QtLibWidgets Qt6::Core Qt6::Gui Qt6::Widgets)
set_target_properties(SoftProofingPlugin PROPERTIES

View File

@ -30,7 +30,6 @@ int main(int argc, char *argv[])
#endif
QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true);
QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QApplication application(argc, argv);
QCoreApplication::setOrganizationName("MelkaJ");

View File

@ -24,7 +24,6 @@
int main(int argc, char *argv[])
{
QGuiApplication a(argc, argv);
QGuiApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true);
QCoreApplication::setOrganizationName("MelkaJ");
QCoreApplication::setApplicationName("PdfTool");
QCoreApplication::setApplicationVersion(pdf::PDF_LIBRARY_VERSION);

View File

@ -847,12 +847,12 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser
}
}
QString textValue = parser->value("render-hw-accel");
QString textValue = parser->value("render-software");
bool ok = false;
bool value = textValue.toInt(&ok);
if (ok)
{
options.renderUseHardwareRendering = value;
options.renderUseSoftwareRendering = value;
}
else
{

View File

@ -135,7 +135,7 @@ struct PDFToolOptions
// For option 'RenderFlags'
pdf::PDFRenderer::Features renderFeatures = pdf::PDFRenderer::getDefaultFeatures();
bool renderUseHardwareRendering = true;
bool renderUseSoftwareRendering = true;
bool renderShowPageStatistics = false;
int renderMSAAsamples = 4;
int renderRasterizerCount = pdf::PDFRasterizerPool::getDefaultRasterizerCount();

View File

@ -180,21 +180,11 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options)
fontCache.setDocument(md);
fontCache.setCacheShrinkEnabled(nullptr, false);
QSurfaceFormat surfaceFormat;
if (options.renderUseHardwareRendering)
{
surfaceFormat = QSurfaceFormat::defaultFormat();
surfaceFormat.setProfile(QSurfaceFormat::CoreProfile);
surfaceFormat.setSamples(options.renderMSAAsamples);
surfaceFormat.setColorSpace(QColorSpace(QColorSpace::SRgb));
surfaceFormat.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
}
m_pageInfo.resize(document.getCatalog()->getPageCount());
pdf::PDFRasterizerPool rasterizerPool(&document, &fontCache, &cmsManager,
&optionalContentActivity, options.renderFeatures, meshQualitySettings,
pdf::PDFRasterizerPool::getCorrectedRasterizerCount(options.renderRasterizerCount),
options.renderUseHardwareRendering, surfaceFormat, nullptr);
options.renderUseSoftwareRendering ? pdf::RendererEngine::QPainter : pdf::RendererEngine::Blend2D, nullptr);
auto onRenderError = [this](pdf::PDFInteger pageIndex, pdf::PDFRenderError error)
{

View File

@ -1,5 +1,5 @@
{
"name": "pdf4qt",
"version-string": "1.3.6",
"dependencies": [ "tbb", "openssl", "lcms", "zlib", "openjpeg", "freetype", "ijg-libjpeg", "libpng" ]
"version-string": "1.3.7",
"dependencies": [ "tbb", "openssl", "lcms", "zlib", "openjpeg", "freetype", "ijg-libjpeg", "libpng", "blend2d" ]
}