diff --git a/Pdf4QtLib/sources/pdfdiff.cpp b/Pdf4QtLib/sources/pdfdiff.cpp index 7284204..98e9246 100644 --- a/Pdf4QtLib/sources/pdfdiff.cpp +++ b/Pdf4QtLib/sources/pdfdiff.cpp @@ -16,7 +16,13 @@ // along with PDF4QT. If not, see . #include "pdfdiff.h" +#include "pdfrenderer.h" #include "pdfdocumenttextflow.h" +#include "pdfexecutionpolicy.h" +#include "pdffont.h" +#include "pdfcms.h" +#include "pdfcompiler.h" +#include "pdfconstants.h" #include @@ -29,6 +35,7 @@ PDFDiff::PDFDiff(QObject* parent) : m_leftDocument(nullptr), m_rightDocument(nullptr), m_options(Asynchronous), + m_epsilon(0.0001), m_cancelled(false) { @@ -141,30 +148,11 @@ PDFDiffResult PDFDiff::perform() { ProgressStartupInfo info; info.showDialog = false; - info.text = tr(""); + info.text = tr("Comparing documents."); m_progress->start(StepLast, std::move(info)); } - // StepExtractContentLeftDocument - stepProgress(); - - // StepExtractContentRightDocument - stepProgress(); - - // StepExtractTextLeftDocument - pdf::PDFDocumentTextFlowFactory factoryLeftDocumentTextFlow; - factoryLeftDocumentTextFlow.setCalculateBoundingBoxes(true); - PDFDocumentTextFlow leftTextFlow = factoryLeftDocumentTextFlow.create(m_leftDocument, leftPages, PDFDocumentTextFlowFactory::Algorithm::Auto); - stepProgress(); - - // StepExtractTextRightDocument - pdf::PDFDocumentTextFlowFactory factoryRightDocumentTextFlow; - factoryRightDocumentTextFlow.setCalculateBoundingBoxes(true); - PDFDocumentTextFlow rightTextFlow = factoryRightDocumentTextFlow.create(m_rightDocument, rightPages, PDFDocumentTextFlowFactory::Algorithm::Auto); - stepProgress(); - - // StepCompare - stepProgress(); + performSteps(leftPages, rightPages); if (m_progress) { @@ -182,6 +170,102 @@ void PDFDiff::stepProgress() } } +struct PDFDiffPageContext +{ + PDFInteger pageIndex = 0; + PDFPrecompiledPage::GraphicPieceInfos graphicPieces; +}; + +void PDFDiff::performSteps(const std::vector& leftPages, const std::vector& rightPages) +{ + std::vector leftPreparedPages; + std::vector rightPreparedPages; + + auto createDiffPageContext = [](auto pageIndex) + { + PDFDiffPageContext context; + context.pageIndex = pageIndex; + return context; + }; + std::transform(leftPages.cbegin(), leftPages.cend(), std::back_inserter(leftPreparedPages), createDiffPageContext); + std::transform(rightPages.cbegin(), rightPages.cend(), std::back_inserter(rightPreparedPages), createDiffPageContext); + + // StepExtractContentLeftDocument + if (!m_cancelled) + { + PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); + PDFOptionalContentActivity optionalContentActivity(m_leftDocument, pdf::OCUsage::View, nullptr); + fontCache.setDocument(pdf::PDFModifiedDocument(const_cast(m_leftDocument), &optionalContentActivity)); + + PDFCMSManager cmsManager(nullptr); + cmsManager.setDocument(m_leftDocument); + PDFCMSPointer cms = cmsManager.getCurrentCMS(); + + auto fillPageContext = [&, this](PDFDiffPageContext& context) + { + PDFPrecompiledPage compiledPage; + constexpr PDFRenderer::Features features = PDFRenderer::IgnoreOptionalContent; + PDFRenderer renderer(m_leftDocument, &fontCache, cms.data(), &optionalContentActivity, features, pdf::PDFMeshQualitySettings()); + renderer.compile(&compiledPage, context.pageIndex); + + PDFReal epsilon = calculateEpsilonForPage(m_leftDocument->getCatalog()->getPage(context.pageIndex)); + context.graphicPieces = compiledPage.calculateGraphicPieceInfos(epsilon); + }; + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, leftPreparedPages.begin(), leftPreparedPages.end(), fillPageContext); + stepProgress(); + } + + // StepExtractContentRightDocument + if (!m_cancelled) + { + PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); + PDFOptionalContentActivity optionalContentActivity(m_rightDocument, pdf::OCUsage::View, nullptr); + fontCache.setDocument(pdf::PDFModifiedDocument(const_cast(m_rightDocument), &optionalContentActivity)); + + PDFCMSManager cmsManager(nullptr); + cmsManager.setDocument(m_rightDocument); + PDFCMSPointer cms = cmsManager.getCurrentCMS(); + + auto fillPageContext = [&, this](PDFDiffPageContext& context) + { + PDFPrecompiledPage compiledPage; + constexpr PDFRenderer::Features features = PDFRenderer::IgnoreOptionalContent; + PDFRenderer renderer(m_rightDocument, &fontCache, cms.data(), &optionalContentActivity, features, pdf::PDFMeshQualitySettings()); + renderer.compile(&compiledPage, context.pageIndex); + + PDFReal epsilon = calculateEpsilonForPage(m_leftDocument->getCatalog()->getPage(context.pageIndex)); + context.graphicPieces = compiledPage.calculateGraphicPieceInfos(epsilon); + }; + + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, rightPreparedPages.begin(), rightPreparedPages.end(), fillPageContext); + stepProgress(); + } + + // StepExtractTextLeftDocument + if (!m_cancelled) + { + pdf::PDFDocumentTextFlowFactory factoryLeftDocumentTextFlow; + factoryLeftDocumentTextFlow.setCalculateBoundingBoxes(true); + PDFDocumentTextFlow leftTextFlow = factoryLeftDocumentTextFlow.create(m_leftDocument, leftPages, PDFDocumentTextFlowFactory::Algorithm::Auto); + stepProgress(); + } + + // StepExtractTextRightDocument + if (!m_cancelled) + { + pdf::PDFDocumentTextFlowFactory factoryRightDocumentTextFlow; + factoryRightDocumentTextFlow.setCalculateBoundingBoxes(true); + PDFDocumentTextFlow rightTextFlow = factoryRightDocumentTextFlow.create(m_rightDocument, rightPages, PDFDocumentTextFlowFactory::Algorithm::Auto); + stepProgress(); + } + + // StepCompare + if (!m_cancelled) + { + stepProgress(); + } +} + void PDFDiff::onComparationPerformed() { m_cancelled = false; @@ -189,6 +273,19 @@ void PDFDiff::onComparationPerformed() emit comparationFinished(); } +PDFReal PDFDiff::calculateEpsilonForPage(const PDFPage* page) const +{ + Q_ASSERT(page); + + QRectF mediaBox = page->getMediaBox(); + + PDFReal width = mediaBox.width(); + PDFReal height = mediaBox.height(); + PDFReal factor = qMax(width, height); + + return factor * m_epsilon; +} + PDFDiffResult::PDFDiffResult() : m_result(true) { diff --git a/Pdf4QtLib/sources/pdfdiff.h b/Pdf4QtLib/sources/pdfdiff.h index a5c177d..ce3a046 100644 --- a/Pdf4QtLib/sources/pdfdiff.h +++ b/Pdf4QtLib/sources/pdfdiff.h @@ -117,15 +117,24 @@ private: PDFDiffResult perform(); void stepProgress(); + void performSteps(const std::vector& leftPages, + const std::vector& rightPages); void onComparationPerformed(); + /// Calculates real epsilon for a page. Epsilon is used in page + /// comparation process, where points closer that epsilon + /// are recognized as equal. + /// \param page Page + PDFReal calculateEpsilonForPage(const PDFPage* page) const; + PDFProgress* m_progress; const PDFDocument* m_leftDocument; const PDFDocument* m_rightDocument; PDFClosedIntervalSet m_pagesForLeftDocument; PDFClosedIntervalSet m_pagesForRightDocument; Options m_options; + PDFReal m_epsilon; std::atomic_bool m_cancelled; PDFDiffResult m_result; diff --git a/Pdf4QtLib/sources/pdfdrawwidget.h b/Pdf4QtLib/sources/pdfdrawwidget.h index 3c8e6f2..998e14a 100644 --- a/Pdf4QtLib/sources/pdfdrawwidget.h +++ b/Pdf4QtLib/sources/pdfdrawwidget.h @@ -103,7 +103,7 @@ public: void addInputInterface(IDrawWidgetInputInterface* inputInterface); signals: - void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); + void pageRenderingErrorsChanged(pdf::PDFInteger pageIndex, int errorsCount); private: void updateRendererImpl(); diff --git a/Pdf4QtLib/sources/pdfpainter.cpp b/Pdf4QtLib/sources/pdfpainter.cpp index 7988881..d1d8d7c 100644 --- a/Pdf4QtLib/sources/pdfpainter.cpp +++ b/Pdf4QtLib/sources/pdfpainter.cpp @@ -20,6 +20,7 @@ #include "pdfcms.h" #include +#include namespace pdf { @@ -831,4 +832,146 @@ void PDFPrecompiledPage::finalize(qint64 compilingTimeNS, QList } } +PDFPrecompiledPage::GraphicPieceInfos PDFPrecompiledPage::calculateGraphicPieceInfos(PDFReal epsilon) const +{ + GraphicPieceInfos infos; + + struct State + { + QMatrix matrix; + }; + std::stack stateStack; + stateStack.emplace(); + + // Check, if epsilon is not too small + if (qFuzzyIsNull(epsilon)) + { + epsilon = 0.000001; + } + PDFReal factor = 1.0 / epsilon; + + // Process all instructions + for (const Instruction& instruction : m_instructions) + { + switch (instruction.type) + { + case InstructionType::DrawPath: + { + const PathPaintData& data = m_paths[instruction.dataIndex]; + + GraphicPieceInfo info; + QByteArray serializedPath; + + // Serialize data + if (true) + { + QDataStream stream(&serializedPath, QIODevice::WriteOnly); + + stream << data.isText; + stream << data.pen; + stream << data.brush; + + // Translate map to page coordinates + QPainterPath pagePath = stateStack.top().matrix.map(data.path); + + info.type = data.isText ? GraphicPieceInfo::Type::Text : GraphicPieceInfo::Type::VectorGraphics; + info.boundingRect = pagePath.controlPointRect(); + + const int elementCount = pagePath.elementCount(); + for (int i = 0; i < elementCount; ++i) + { + QPainterPath::Element element = pagePath.elementAt(i); + + PDFReal roundedX = qRound(element.x * factor); + PDFReal roundedY = qRound(element.y * factor); + + stream << roundedX; + stream << roundedY; + stream << element.type; + } + } + + QByteArray hash = QCryptographicHash::hash(serializedPath, QCryptographicHash::Sha512); + Q_ASSERT(QCryptographicHash::hashLength(QCryptographicHash::Sha512) == 64); + + size_t size = qMin(hash.length(), info.hash.size()); + std::copy(hash.data(), hash.data() + size, info.hash.data()); + + infos.emplace_back(std::move(info)); + break; + } + + case InstructionType::DrawImage: + { + /*const ImageData& data = m_images[instruction.dataIndex]; + const QImage& image = data.image; + + painter->save(); + + QMatrix imageTransform(1.0 / image.width(), 0, 0, 1.0 / image.height(), 0, 0); + QMatrix worldMatrix = imageTransform * painter->worldMatrix(); + + // Jakub Melka: Because Qt uses opposite axis direction than PDF, then we must transform the y-axis + // to the opposite (so the image is then unchanged) + worldMatrix.translate(0, image.height()); + worldMatrix.scale(1, -1); + + painter->setWorldMatrix(worldMatrix); + painter->drawImage(0, 0, image); + painter->restore();*/ + break; + } + + case InstructionType::DrawMesh: + { + /*const MeshPaintData& data = m_meshes[instruction.dataIndex]; + + painter->save(); + painter->setWorldMatrix(pagePointToDevicePointMatrix); + data.mesh.paint(painter, data.alpha); + painter->restore();*/ + break; + } + + case InstructionType::Clip: + { + // Do nothing, we are just collecting information + break; + } + + case InstructionType::SaveGraphicState: + { + stateStack.push(stateStack.top()); + break; + } + + case InstructionType::RestoreGraphicState: + { + stateStack.pop(); + break; + } + + case InstructionType::SetWorldMatrix: + { + stateStack.top().matrix = m_matrices[instruction.dataIndex]; + break; + } + + case InstructionType::SetCompositionMode: + { + // Do nothing, we are just collecting information + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + } + + return infos; +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpainter.h b/Pdf4QtLib/sources/pdfpainter.h index 7a4e741..ecf872a 100644 --- a/Pdf4QtLib/sources/pdfpainter.h +++ b/Pdf4QtLib/sources/pdfpainter.h @@ -234,6 +234,30 @@ public: PDFSnapInfo* getSnapInfo() { return &m_snapInfo; } const PDFSnapInfo* getSnapInfo() const { return &m_snapInfo; } + struct GraphicPieceInfo + { + enum class Type + { + Unknown, + Text, + VectorGraphics, + Image + }; + + Type type = Type::Unknown; + QRectF boundingRect; + std::array hash = { }; + }; + + using GraphicPieceInfos = std::vector; + + /// Creates information about piece of graphic in this page, + /// for example, for comparation reasons. Parameter \p epsilon + /// is for numerical precision - values under epsilon are considered + /// as equal. + /// \param epsilon Epsilon + GraphicPieceInfos calculateGraphicPieceInfos(PDFReal epsilon) const; + private: struct PathPaintData {