diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index f8ce5bf..3ebfe48 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -142,6 +142,7 @@ HEADERS += \ sources/pdfobjecteditorwidget.h \ sources/pdfobjecteditorwidget_impl.h \ sources/pdfobjectutils.h \ + sources/pdfoperationcontrol.h \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ diff --git a/Pdf4QtLib/sources/pdfcolorspaces.cpp b/Pdf4QtLib/sources/pdfcolorspaces.cpp index d2ed9fb..8059842 100644 --- a/Pdf4QtLib/sources/pdfcolorspaces.cpp +++ b/Pdf4QtLib/sources/pdfcolorspaces.cpp @@ -218,7 +218,8 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, const PDFImageData& softMask, const PDFCMS* cms, RenderingIntent intent, - PDFRenderErrorReporter* reporter) const + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { if (imageData.isValid()) { @@ -249,6 +250,12 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, auto transformPixelLine = [&](unsigned int i) { + // Is operation being cancelled? + if (PDFOperationControl::isOperationCancelled(operationControl)) + { + return; + } + try { PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); @@ -260,21 +267,27 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, std::vector inputColors(imageWidth * componentCount, 0.0f); auto itInputColor = inputColors.begin(); - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - for (unsigned int k = 0; k < componentCount; ++k) - { - PDFReal value = reader.read(); - // Interpolate value, if it is not empty - if (!decode.empty()) + if (!decode.empty()) + { + // Interpolate value + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + for (unsigned int k = 0; k < componentCount; ++k) { + PDFReal value = reader.read(); *itInputColor++ = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); } - else - { - *itInputColor++ = value * coefficient; - } + } + } + else + { + // Just read all values and multiply them with coefficient + const unsigned int pixelCount = imageData.getWidth() * componentCount; + for (unsigned int j = 0; j < pixelCount; ++j) + { + PDFReal value = reader.read(); + *itInputColor++ = value * coefficient; } } @@ -334,6 +347,12 @@ QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, auto transformPixelLine = [&](unsigned int i) { + // Is operation being cancelled? + if (PDFOperationControl::isOperationCancelled(operationControl)) + { + return; + } + try { PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); @@ -1872,7 +1891,8 @@ QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData, const PDFImageData& softMask, const PDFCMS* cms, RenderingIntent intent, - PDFRenderErrorReporter* reporter) const + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { if (imageData.isValid()) { @@ -1898,6 +1918,12 @@ QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData, for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) { + // Is operation being cancelled? + if (PDFOperationControl::isOperationCancelled(operationControl)) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Operation cancelled!")); + } + reader.seek(i * imageData.getStride()); unsigned char* outputLine = image.scanLine(i); @@ -1945,6 +1971,12 @@ QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData, for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) { + // Is operation being cancelled? + if (PDFOperationControl::isOperationCancelled(operationControl)) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Operation cancelled!")); + } + reader.seek(i * imageData.getStride()); unsigned char* outputLine = image.scanLine(i); unsigned char* alphaLine = alphaMask.scanLine(i); diff --git a/Pdf4QtLib/sources/pdfcolorspaces.h b/Pdf4QtLib/sources/pdfcolorspaces.h index 0399897..69b444f 100644 --- a/Pdf4QtLib/sources/pdfcolorspaces.h +++ b/Pdf4QtLib/sources/pdfcolorspaces.h @@ -21,6 +21,7 @@ #include "pdfflatarray.h" #include "pdffunction.h" #include "pdfutils.h" +#include "pdfoperationcontrol.h" #include #include @@ -365,11 +366,13 @@ public: /// \param cms Color management system /// \param intent Rendering intent /// \param reporter Error reporter + /// \param operationControl Operation control virtual QImage getImage(const PDFImageData& imageData, const PDFImageData& softMask, const PDFCMS* cms, RenderingIntent intent, - PDFRenderErrorReporter* reporter) const; + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const; /// Fills RGB buffer using colors from \p colors. Colors are transformed /// by this color space (or color management system is used). Buffer @@ -755,7 +758,8 @@ public: const PDFImageData& softMask, const PDFCMS* cms, RenderingIntent intent, - PDFRenderErrorReporter* reporter) const override; + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; /// Creates indexed color space from provided values. /// \param colorSpaceDictionary Color space dictionary diff --git a/Pdf4QtLib/sources/pdfcompiler.cpp b/Pdf4QtLib/sources/pdfcompiler.cpp index 7b67f82..418baba 100644 --- a/Pdf4QtLib/sources/pdfcompiler.cpp +++ b/Pdf4QtLib/sources/pdfcompiler.cpp @@ -64,11 +64,12 @@ void PDFAsynchronousPageCompilerWorkerThread::run() auto proxy = m_compiler->getProxy(); proxy->getFontCache()->setCacheShrinkEnabled(this, false); - auto compilePage = [proxy](PDFAsynchronousPageCompiler::CompileTask& task) -> PDFPrecompiledPage + auto compilePage = [this, proxy](PDFAsynchronousPageCompiler::CompileTask& task) -> PDFPrecompiledPage { PDFPrecompiledPage compiledPage; PDFCMSPointer cms = proxy->getCMSManager()->getCurrentCMS(); PDFRenderer renderer(proxy->getDocument(), proxy->getFontCache(), cms.data(), proxy->getOptionalContentActivity(), proxy->getFeatures(), proxy->getMeshQualitySettings()); + renderer.setOperationControl(m_compiler); renderer.compile(&task.precompiledPage, task.pageIndex); task.finished = true; return compiledPage; @@ -122,6 +123,11 @@ PDFAsynchronousPageCompiler::~PDFAsynchronousPageCompiler() stop(true); } +bool PDFAsynchronousPageCompiler::isOperationCancelled() const +{ + return m_state == State::Stopping; +} + void PDFAsynchronousPageCompiler::start() { switch (m_state) diff --git a/Pdf4QtLib/sources/pdfcompiler.h b/Pdf4QtLib/sources/pdfcompiler.h index f305d89..8f00359 100644 --- a/Pdf4QtLib/sources/pdfcompiler.h +++ b/Pdf4QtLib/sources/pdfcompiler.h @@ -54,7 +54,7 @@ private: /// Asynchronous page compiler compiles pages asynchronously, and stores them in the /// cache. Cache size can be set. This object is designed to cooperate with /// draw widget proxy. -class PDFAsynchronousPageCompiler : public QObject +class PDFAsynchronousPageCompiler : public QObject, public PDFOperationControl { Q_OBJECT @@ -106,6 +106,9 @@ public: /// \param compile Compile the page, if it is not found in the cache const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile); + /// Is operation being cancelled? + virtual bool isOperationCancelled() const override; + signals: void pageImageChanged(bool all, const std::vector& pages); void renderingError(pdf::PDFInteger pageIndex, const QList& errors); diff --git a/Pdf4QtLib/sources/pdfimage.cpp b/Pdf4QtLib/sources/pdfimage.cpp index 8edf5b6..5cc976d 100644 --- a/Pdf4QtLib/sources/pdfimage.cpp +++ b/Pdf4QtLib/sources/pdfimage.cpp @@ -745,12 +745,14 @@ PDFImage PDFImage::createImage(const PDFDocument* document, return image; } -QImage PDFImage::getImage(const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +QImage PDFImage::getImage(const PDFCMS* cms, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { const bool isImageMask = m_imageData.getMaskingType() == PDFImageData::MaskingType::ImageMask; if (m_colorSpace && !isImageMask) { - return m_colorSpace->getImage(m_imageData, m_softMask, cms, m_renderingIntent, reporter); + return m_colorSpace->getImage(m_imageData, m_softMask, cms, m_renderingIntent, reporter, operationControl); } else if (isImageMask) { diff --git a/Pdf4QtLib/sources/pdfimage.h b/Pdf4QtLib/sources/pdfimage.h index bab0753..f92dbbc 100644 --- a/Pdf4QtLib/sources/pdfimage.h +++ b/Pdf4QtLib/sources/pdfimage.h @@ -20,6 +20,7 @@ #include "pdfobject.h" #include "pdfcolorspaces.h" +#include "pdfoperationcontrol.h" #include @@ -73,7 +74,9 @@ public: PDFRenderErrorReporter* errorReporter); /// Returns image transformed from image data and color space - QImage getImage(const PDFCMS* cms, PDFRenderErrorReporter* reporter) const; + QImage getImage(const PDFCMS* cms, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const; /// Returns rendering intent of the image RenderingIntent getRenderingIntent() const { return m_renderingIntent; } diff --git a/Pdf4QtLib/sources/pdfoperationcontrol.h b/Pdf4QtLib/sources/pdfoperationcontrol.h new file mode 100644 index 0000000..0b9855c --- /dev/null +++ b/Pdf4QtLib/sources/pdfoperationcontrol.h @@ -0,0 +1,51 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFOPERATIONCONTROL_H +#define PDFOPERATIONCONTROL_H + +#include "pdfglobal.h" + +namespace pdf +{ + +/// Operation controller. This interface can be used for +/// long operation interruption - so operation is cancelled +/// immediately. +class PDFOperationControl +{ +public: + constexpr PDFOperationControl() = default; + virtual ~PDFOperationControl() = default; + + /// Returns true, if operation is cancelled and + /// processed data should be abandoned. It is safe + /// to call this function in another thread, implementators + /// of this interface must ensure thread safety. + /// If this function returns true, it must return true until + /// all operations are stopped and correctly handled. + virtual bool isOperationCancelled() const = 0; + + static inline bool isOperationCancelled(const PDFOperationControl* operationControl) + { + return operationControl && operationControl->isOperationCancelled(); + } +}; + +} + +#endif // PDFOPERATIONCONTROL_H diff --git a/Pdf4QtLib/sources/pdfpagecontentprocessor.cpp b/Pdf4QtLib/sources/pdfpagecontentprocessor.cpp index 197d8f9..facf4de 100644 --- a/Pdf4QtLib/sources/pdfpagecontentprocessor.cpp +++ b/Pdf4QtLib/sources/pdfpagecontentprocessor.cpp @@ -232,6 +232,7 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, m_fontCache(fontCache), m_CMS(CMS), m_optionalContentActivity(optionalContentActivity), + m_operationControl(nullptr), m_colorSpaceDictionary(nullptr), m_fontDictionary(nullptr), m_xobjectDictionary(nullptr), @@ -311,6 +312,12 @@ QList PDFPageContentProcessor::processContents() for (size_t i = 0; i < count; ++i) { + if (isProcessingCancelled()) + { + // Break, if processing is being cancelled + break; + } + const PDFObject& streamObject = m_document->getObject(array->getItem(i)); if (streamObject.isStream()) { @@ -523,7 +530,7 @@ void PDFPageContentProcessor::processContent(const QByteArray& content) { PDFLexicalAnalyzer parser(content.constBegin(), content.constEnd()); - while (!parser.isAtEnd()) + while (!parser.isAtEnd() && !isProcessingCancelled()) { bool tokenFetched = false; PDFInteger oldParserPosition = parser.pos(); @@ -844,7 +851,7 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool if (!performPathPaintingUsingShading(path, false, true, shadingPattern)) { - PDFMesh mesh = shadingPattern->createMesh(settings, m_CMS, m_graphicState.getRenderingIntent(), this); + PDFMesh mesh = shadingPattern->createMesh(settings, m_CMS, m_graphicState.getRenderingIntent(), this, m_operationControl); // Now, merge the current path to the mesh clipping path QPainterPath boundingPath = mesh.getBoundingPath(); @@ -961,7 +968,7 @@ void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool if (!performPathPaintingUsingShading(strokedPath, true, false, shadingPattern)) { - PDFMesh mesh = shadingPattern->createMesh(settings, m_CMS, m_graphicState.getRenderingIntent(), this); + PDFMesh mesh = shadingPattern->createMesh(settings, m_CMS, m_graphicState.getRenderingIntent(), this, m_operationControl); QPainterPath boundingPath = mesh.getBoundingPath(); if (boundingPath.isEmpty()) @@ -1095,6 +1102,16 @@ void PDFPageContentProcessor::processTillingPatternPainting(const PDFTilingPatte performClipping(boundingPath, boundingPath.fillRule()); processContent(content); + + if (isProcessingCancelled()) + { + break; + } + } + + if (isProcessingCancelled()) + { + break; } } } @@ -1831,6 +1848,16 @@ void PDFPageContentProcessor::finishMarkedContent() } } +void PDFPageContentProcessor::setOperationControl(const PDFOperationControl* newOperationControl) +{ + m_operationControl = newOperationControl; +} + +bool PDFPageContentProcessor::isProcessingCancelled() const +{ + return m_operationControl && m_operationControl->isOperationCancelled(); +} + void PDFPageContentProcessor::reportRenderErrorOnce(RenderErrorType type, QString message) { if (!m_onceReportedErrors.count(message)) @@ -2952,24 +2979,27 @@ void PDFPageContentProcessor::paintXObjectImage(const PDFStream* stream) if (!performOriginalImagePainting(pdfImage)) { - QImage image = pdfImage.getImage(m_CMS, this); + QImage image = pdfImage.getImage(m_CMS, this, m_operationControl); - if (image.format() == QImage::Format_Alpha8) + if (!isProcessingCancelled()) { - QSize size = image.size(); - QImage unmaskedImage(size, QImage::Format_ARGB32_Premultiplied); - unmaskedImage.fill(m_graphicState.getFillColor()); - unmaskedImage.setAlphaChannel(image); - image = qMove(unmaskedImage); - } + if (image.format() == QImage::Format_Alpha8) + { + QSize size = image.size(); + QImage unmaskedImage(size, QImage::Format_ARGB32_Premultiplied); + unmaskedImage.fill(m_graphicState.getFillColor()); + unmaskedImage.setAlphaChannel(image); + image = qMove(unmaskedImage); + } - if (!image.isNull()) - { - performImagePainting(image); - } - else - { - throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't decode the image.")); + if (!image.isNull()) + { + performImagePainting(image); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't decode the image.")); + } } } } diff --git a/Pdf4QtLib/sources/pdfpagecontentprocessor.h b/Pdf4QtLib/sources/pdfpagecontentprocessor.h index d3dabac..b341664 100644 --- a/Pdf4QtLib/sources/pdfpagecontentprocessor.h +++ b/Pdf4QtLib/sources/pdfpagecontentprocessor.h @@ -26,6 +26,7 @@ #include "pdfmeshqualitysettings.h" #include "pdfblendfunction.h" #include "pdftextlayout.h" +#include "pdfoperationcontrol.h" #include #include @@ -237,6 +238,15 @@ public: /// or true, if it is suppressed. virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd); + /// Sets operation control object which can decide, if operation should + /// be cancelled. If this is the case, page content processor stops + /// processing page contents. + /// \param newOperationControl Operation control object + void setOperationControl(const PDFOperationControl* newOperationControl); + + /// Returns true, if page content processing is being cancelled + bool isProcessingCancelled() const; + protected: struct PDFTransparencyGroup @@ -1009,6 +1019,7 @@ private: const PDFFontCache* m_fontCache; const PDFCMS* m_CMS; const PDFOptionalContentActivity* m_optionalContentActivity; + const PDFOperationControl* m_operationControl; const PDFDictionary* m_colorSpaceDictionary; const PDFDictionary* m_fontDictionary; const PDFDictionary* m_xobjectDictionary; diff --git a/Pdf4QtLib/sources/pdfpattern.cpp b/Pdf4QtLib/sources/pdfpattern.cpp index 68cbd92..2e490f9 100644 --- a/Pdf4QtLib/sources/pdfpattern.cpp +++ b/Pdf4QtLib/sources/pdfpattern.cpp @@ -608,10 +608,16 @@ ShadingType PDFFunctionShading::getShadingType() const return ShadingType::Function; } -PDFMesh PDFFunctionShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFFunctionShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; + Q_UNUSED(operationControl); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); QMatrix domainToDeviceSpaceMatrix = m_domainToTargetTransform * patternSpaceToDeviceSpaceMatrix; QLineF topLine(m_domain.topLeft(), m_domain.topRight()); @@ -916,10 +922,16 @@ PDFShadingSampler* PDFFunctionShading::createSampler(QMatrix userSpaceToDeviceSp return new PDFFunctionShadingSampler(this, userSpaceToDeviceSpaceMatrix); } -PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; + Q_UNUSED(operationControl); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); QPointF p1 = patternSpaceToDeviceSpaceMatrix.map(m_startPoint); QPointF p2 = patternSpaceToDeviceSpaceMatrix.map(m_endPoint); @@ -1423,10 +1435,16 @@ ShadingType PDFRadialShading::getShadingType() const return ShadingType::Radial; } -PDFMesh PDFRadialShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFRadialShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; + Q_UNUSED(operationControl); + QMatrix patternSpaceToDeviceSpaceMatrix = getPatternSpaceToDeviceSpaceMatrix(settings); QPointF p1 = patternSpaceToDeviceSpaceMatrix.map(m_startPoint); QPointF p2 = patternSpaceToDeviceSpaceMatrix.map(m_endPoint); @@ -2239,10 +2257,16 @@ bool PDFFreeFormGouradTriangleShading::processTriangles(InitializeFunction initi return true; } -PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFFreeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; + Q_UNUSED(operationControl); + auto addTriangle = [this, &settings, &mesh, cms, intent, reporter](const VertexData* va, const VertexData* vb, const VertexData* vc) { const uint32_t via = va->index; @@ -2401,10 +2425,16 @@ bool PDFLatticeFormGouradTriangleShading::processTriangles(InitializeFunction in return true; } -PDFMesh PDFLatticeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFLatticeFormGouradTriangleShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; + Q_UNUSED(operationControl); + auto addTriangle = [this, &settings, &mesh, cms, intent, reporter](const VertexData* va, const VertexData* vb, const VertexData* vc) { const uint32_t via = va->index; @@ -3071,7 +3101,11 @@ PDFTensorPatches PDFTensorProductPatchShading::createPatches(QMatrix userSpaceTo return patches; } -PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; @@ -3082,7 +3116,7 @@ PDFMesh PDFTensorProductPatchShading::createMesh(const PDFMeshQualitySettings& s throw PDFException(PDFTranslationContext::tr("Invalid data in tensor product patch shading.")); } - fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings.userSpaceToDeviceSpaceMatrix), settings, patches, cms, intent, reporter); + fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings.userSpaceToDeviceSpaceMatrix), settings, patches, cms, intent, reporter, operationControl); return mesh; } @@ -3228,7 +3262,8 @@ void PDFTensorProductPatchShadingBase::fillMesh(PDFMesh& mesh, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, - bool fastAlgorithm) const + bool fastAlgorithm, + const PDFOperationControl* operationControl) const { // We implement algorithm similar to Ruppert's algorithm (see https://en.wikipedia.org/wiki/Ruppert%27s_algorithm), but // we do not need a mesh for FEM calculation, so we do not care about quality of the triangles (we can have triangles with @@ -3301,6 +3336,13 @@ void PDFTensorProductPatchShadingBase::fillMesh(PDFMesh& mesh, while (!unfinishedTriangles.empty()) { + // Mesh generation is cancelled + if (PDFOperationControl::isOperationCancelled(operationControl)) + { + mesh = PDFMesh(); + return; + } + Triangle triangle = unfinishedTriangles.back(); unfinishedTriangles.pop_back(); @@ -3417,12 +3459,13 @@ void PDFTensorProductPatchShadingBase::fillMesh(PDFMesh& mesh, const PDFTensorPatches& patches, const PDFCMS* cms, RenderingIntent intent, - PDFRenderErrorReporter* reporter) const + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { const bool fastAlgorithm = patches.size() > 16; for (const auto& patch : patches) { - fillMesh(mesh, settings, patch, cms, intent, reporter, fastAlgorithm); + fillMesh(mesh, settings, patch, cms, intent, reporter, fastAlgorithm, operationControl); } // Create bounding path @@ -3655,7 +3698,11 @@ PDFTensorPatches PDFCoonsPatchShading::createPatches(QMatrix userSpaceToDeviceSp return patches; } -PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const { PDFMesh mesh; PDFTensorPatches patches = createPatches(settings.userSpaceToDeviceSpaceMatrix, true); @@ -3665,7 +3712,7 @@ PDFMesh PDFCoonsPatchShading::createMesh(const PDFMeshQualitySettings& settings, throw PDFException(PDFTranslationContext::tr("Invalid data in coons patch shading.")); } - fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings), settings, patches, cms, intent, reporter); + fillMesh(mesh, getPatternSpaceToDeviceSpaceMatrix(settings), settings, patches, cms, intent, reporter, operationControl); return mesh; } diff --git a/Pdf4QtLib/sources/pdfpattern.h b/Pdf4QtLib/sources/pdfpattern.h index 1240ed4..2b0e5d1 100644 --- a/Pdf4QtLib/sources/pdfpattern.h +++ b/Pdf4QtLib/sources/pdfpattern.h @@ -311,7 +311,11 @@ public: /// \param cms Color management system /// \param intent Rendering intent /// \param reporter Error reporter - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; + /// \param operationControl Operation control + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const = 0; /// Returns patterns graphic state. This state must be applied before /// the shading pattern is painted to the target device. @@ -386,7 +390,11 @@ public: explicit PDFFunctionShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; const QRectF& getDomain() const { return m_domain; } @@ -407,7 +415,11 @@ public: explicit PDFAxialShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; private: @@ -420,7 +432,11 @@ public: explicit PDFRadialShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; PDFReal getR0() const { return m_r0; } @@ -480,7 +496,11 @@ public: explicit PDFFreeFormGouradTriangleShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; private: @@ -509,7 +529,11 @@ public: explicit PDFLatticeFormGouradTriangleShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFShadingSampler* createSampler(QMatrix userSpaceToDeviceSpaceMatrix) const override; private: @@ -675,8 +699,8 @@ public: protected: struct Triangle; - void fillMesh(PDFMesh& mesh, const PDFMeshQualitySettings& settings, const PDFTensorPatch& patch, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool fastAlgorithm) const; - void fillMesh(PDFMesh& mesh, const QMatrix& patternSpaceToDeviceSpaceMatrix, const PDFMeshQualitySettings& settings, const PDFTensorPatches& patches, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const; + void fillMesh(PDFMesh& mesh, const PDFMeshQualitySettings& settings, const PDFTensorPatch& patch, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool fastAlgorithm, const PDFOperationControl* operationControl) const; + void fillMesh(PDFMesh& mesh, const QMatrix& patternSpaceToDeviceSpaceMatrix, const PDFMeshQualitySettings& settings, const PDFTensorPatches& patches, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, const PDFOperationControl* operationControl) const; static void addTriangle(std::vector& triangles, const PDFTensorPatch& patch, std::array uvCoordinates); private: @@ -689,7 +713,11 @@ public: explicit PDFCoonsPatchShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFTensorPatches createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const override; private: @@ -702,7 +730,11 @@ public: explicit PDFTensorProductPatchShading() = default; virtual ShadingType getShadingType() const override; - virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter, + const PDFOperationControl* operationControl) const override; virtual PDFTensorPatches createPatches(QMatrix userSpaceToDeviceSpaceMatrix, bool transformColor) const override; private: diff --git a/Pdf4QtLib/sources/pdfrenderer.cpp b/Pdf4QtLib/sources/pdfrenderer.cpp index 0401b49..0b0777e 100644 --- a/Pdf4QtLib/sources/pdfrenderer.cpp +++ b/Pdf4QtLib/sources/pdfrenderer.cpp @@ -43,6 +43,7 @@ PDFRenderer::PDFRenderer(const PDFDocument* document, m_fontCache(fontCache), m_cms(cms), m_optionalContentActivity(optionalContentActivity), + m_operationControl(nullptr), m_features(features), m_meshQualitySettings(meshQualitySettings) { @@ -107,6 +108,16 @@ QMatrix PDFRenderer::createMediaBoxToDevicePointMatrix(const QRectF& mediaBox, return matrix; } +const PDFOperationControl* PDFRenderer::getOperationControl() const +{ + return m_operationControl; +} + +void PDFRenderer::setOperationControl(const PDFOperationControl* newOperationControl) +{ + m_operationControl = newOperationControl; +} + QList PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const { const PDFCatalog* catalog = m_document->getCatalog(); @@ -122,6 +133,7 @@ QList PDFRenderer::render(QPainter* painter, const QRectF& recta QMatrix matrix = createPagePointToDevicePointMatrix(page, rectangle); PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_cms, m_optionalContentActivity, m_meshQualitySettings); + processor.setOperationControl(m_operationControl); return processor.processContents(); } @@ -138,6 +150,7 @@ QList PDFRenderer::render(QPainter* painter, const QMatrix& matr Q_ASSERT(page); PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_cms, m_optionalContentActivity, m_meshQualitySettings); + processor.setOperationControl(m_operationControl); return processor.processContents(); } @@ -158,6 +171,7 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex) timer.start(); PDFPrecompiledPageGenerator generator(precompiledPage, m_features, page, m_document, m_fontCache, m_cms, m_optionalContentActivity, m_meshQualitySettings); + generator.setOperationControl(m_operationControl); QList errors = generator.processContents(); if (m_features.testFlag(InvertColors)) diff --git a/Pdf4QtLib/sources/pdfrenderer.h b/Pdf4QtLib/sources/pdfrenderer.h index 80fa600..d53c03a 100644 --- a/Pdf4QtLib/sources/pdfrenderer.h +++ b/Pdf4QtLib/sources/pdfrenderer.h @@ -20,6 +20,7 @@ #include "pdfpage.h" #include "pdfexception.h" +#include "pdfoperationcontrol.h" #include "pdfmeshqualitysettings.h" #include @@ -113,11 +114,15 @@ public: /// Returns default renderer features static constexpr Features getDefaultFeatures() { return Features(Antialiasing | TextAntialiasing | ClipToCropBox | DisplayAnnotations); } + const PDFOperationControl* getOperationControl() const; + void setOperationControl(const PDFOperationControl* newOperationControl); + private: const PDFDocument* m_document; const PDFFontCache* m_fontCache; const PDFCMS* m_cms; const PDFOptionalContentActivity* m_optionalContentActivity; + const PDFOperationControl* m_operationControl; Features m_features; PDFMeshQualitySettings m_meshQualitySettings; }; diff --git a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectviewerwidget.cpp b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectviewerwidget.cpp index 562444c..528e3b8 100644 --- a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectviewerwidget.cpp +++ b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectviewerwidget.cpp @@ -228,7 +228,7 @@ void ObjectViewerWidget::updateUi() pdf::PDFRenderErrorReporterDummy dummyErrorReporter; pdf::PDFImage pdfImage = pdf::PDFImage::createImage(m_document, stream, qMove(colorSpace), false, pdf::RenderingIntent::Perceptual, &dummyErrorReporter); - QImage image = pdfImage.getImage(m_cms, &dummyErrorReporter); + QImage image = pdfImage.getImage(m_cms, &dummyErrorReporter, nullptr); ui->stackedWidget->setCurrentWidget(ui->imageBrowserPage); ui->imageBrowser->setPixmap(QPixmap::fromImage(image));