diff --git a/Pdf4Qt.pro b/Pdf4Qt.pro index 0e6f4da..56a21cf 100644 --- a/Pdf4Qt.pro +++ b/Pdf4Qt.pro @@ -41,3 +41,4 @@ Pdf4QtViewerPlugins.depends = Pdf4QtLib Pdf4QtViewerProfi.depends = Pdf4QtViewer Pdf4QtViewerLite.depends = Pdf4QtViewer Pdf4QtDocPageOrganizer.depends = Pdf4QtLib +Pdf4QtDocDiff.depends = Pdf4QtLib diff --git a/Pdf4QtDocDiff/mainwindow.cpp b/Pdf4QtDocDiff/mainwindow.cpp index 7824caf..3cd06d9 100644 --- a/Pdf4QtDocDiff/mainwindow.cpp +++ b/Pdf4QtDocDiff/mainwindow.cpp @@ -25,6 +25,7 @@ #include "pdfwidgetutils.h" #include "pdfdocumentreader.h" #include "pdfdrawspacecontroller.h" +#include "pdfdocumentmanipulator.h" #include #include @@ -206,6 +207,25 @@ void MainWindow::onComparationFinished() } } + // Create merged document + pdf::PDFDocumentManipulator manipulator; + manipulator.setOutlineMode(pdf::PDFDocumentManipulator::OutlineMode::NoOutline); + manipulator.addDocument(1, &m_leftDocument); + manipulator.addDocument(2, &m_rightDocument); + + pdf::PDFDocumentManipulator::AssembledPages assembledPages1 = pdf::PDFDocumentManipulator::createAllDocumentPages(1, &m_leftDocument); + pdf::PDFDocumentManipulator::AssembledPages assembledPages2 = pdf::PDFDocumentManipulator::createAllDocumentPages(2, &m_rightDocument); + assembledPages1.insert(assembledPages1.end(), std::make_move_iterator(assembledPages2.begin()), std::make_move_iterator(assembledPages2.end())); + + if (manipulator.assemble(assembledPages1)) + { + m_combinedDocument = manipulator.takeAssembledDocument(); + } + else + { + m_combinedDocument = pdf::PDFDocument(); + } + updateAll(true); } @@ -466,8 +486,16 @@ void MainWindow::performOperation(Operation operation) case Operation::FilterImages: case Operation::FilterShading: case Operation::FilterPageMovement: + { updateFilteredResult(); + + if (ui->actionShow_Pages_with_Differences->isChecked()) + { + updateCustomPageLayout(); + } + break; + } case Operation::ViewDifferences: case Operation::ViewLeft: @@ -477,6 +505,9 @@ void MainWindow::performOperation(Operation operation) break; case Operation::ShowPageswithDifferences: + updateCustomPageLayout(); + break; + case Operation::SaveDifferencesToXML: case Operation::CreateCompareReport: Q_ASSERT(false); @@ -492,7 +523,7 @@ void MainWindow::performOperation(Operation operation) updateActions(); } -void MainWindow::setViewDocument(pdf::PDFDocument* document) +void MainWindow::setViewDocument(pdf::PDFDocument* document, bool updateCustomPageLayout) { if (document != m_pdfWidget->getDrawWidgetProxy()->getDocument()) { @@ -514,11 +545,36 @@ void MainWindow::setViewDocument(pdf::PDFDocument* document) m_pdfWidget->setDocument(pdf::PDFModifiedDocument()); } } + + if (updateCustomPageLayout) + { + this->updateCustomPageLayout(); + } +} + +ComparedDocumentMapper::Mode MainWindow::getDocumentViewMode() const +{ + if (ui->actionView_Left->isChecked()) + { + return ComparedDocumentMapper::Mode::Left; + } + + if (ui->actionView_Right->isChecked()) + { + return ComparedDocumentMapper::Mode::Right; + } + + if (ui->actionView_Overlay->isChecked()) + { + return ComparedDocumentMapper::Mode::Overlay; + } + + return ComparedDocumentMapper::Mode::Combined; } void MainWindow::clear(bool clearLeftDocument, bool clearRightDocument) { - setViewDocument(nullptr); + setViewDocument(nullptr, true); if (clearLeftDocument) { @@ -575,22 +631,37 @@ void MainWindow::updateViewDocument() { pdf::PDFDocument* document = nullptr; - if (ui->actionView_Left->isChecked()) + switch (getDocumentViewMode()) { - document = &m_leftDocument; + case ComparedDocumentMapper::Mode::Left: + document = &m_leftDocument; + break; + + case ComparedDocumentMapper::Mode::Right: + document = &m_rightDocument; + break; + + case ComparedDocumentMapper::Mode::Combined: + case ComparedDocumentMapper::Mode::Overlay: + document = &m_combinedDocument; + break; } - if (ui->actionView_Right->isChecked()) - { - document = &m_rightDocument; - } + setViewDocument(document, true); +} - if (ui->actionView_Differences->isChecked() || ui->actionView_Overlay->isChecked()) - { - document = &m_combinedDocument; - } +void MainWindow::updateCustomPageLayout() +{ + m_documentMapper.update(getDocumentViewMode(), + ui->actionShow_Pages_with_Differences->isChecked(), + m_filteredDiffResult, + &m_leftDocument, + &m_rightDocument, + m_pdfWidget->getDrawWidgetProxy()->getDocument()); - setViewDocument(document); + + m_pdfWidget->getDrawWidgetProxy()->setCustomPageLayout(m_documentMapper.getLayout()); + m_pdfWidget->getDrawWidgetProxy()->setPageLayout(pdf::PageLayout::Custom); } std::optional MainWindow::openDocument() @@ -652,4 +723,131 @@ void MainWindow::onProgressFinished() m_progressTaskbarIndicator->hide(); } +void ComparedDocumentMapper::update(ComparedDocumentMapper::Mode mode, + bool filterDifferences, + const pdf::PDFDiffResult& diff, + const pdf::PDFDocument* leftDocument, + const pdf::PDFDocument* rightDocument, + const pdf::PDFDocument* currentDocument) +{ + m_layout.clear(); + + if (!leftDocument || !rightDocument || !currentDocument) + { + return; + } + + // Jakub Melka + pdf::PDFDiffResult::PageSequence pageSequence = diff.getPageSequence(); + const bool isEmpty = pageSequence.empty(); + + if (filterDifferences) + { + pdf::PDFDiffResult::PageSequence filteredPageSequence; + + std::vector leftPageIndices = diff.getChangedLeftPageIndices(); + std::vector rightPageIndices = diff.getChangedRightPageIndices(); + + for (const pdf::PDFDiffResult::PageSequenceItem& item : pageSequence) + { + const bool isLeftModified = std::binary_search(leftPageIndices.cbegin(), leftPageIndices.cend(), item.leftPage); + const bool isRightModified = std::binary_search(rightPageIndices.cbegin(), rightPageIndices.cend(), item.rightPage); + + if (isLeftModified || isRightModified) + { + filteredPageSequence.push_back(item); + } + } + + pageSequence = std::move(filteredPageSequence); + } + + switch (mode) + { + case ComparedDocumentMapper::Mode::Left: + { + Q_ASSERT(leftDocument == currentDocument); + + double yPos = 0.0; + const pdf::PDFCatalog* catalog = leftDocument->getCatalog(); + + if (isEmpty) + { + // Just copy all pages + const size_t pageCount = catalog->getPageCount(); + for (size_t i = 0; i < pageCount; ++i) + { + QSizeF pageSize = catalog->getPage(i)->getRotatedMediaBoxMM().size(); + QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); + m_layout.emplace_back(0, i, rect); + yPos += pageSize.height() + 5; + } + } + else + { + for (const pdf::PDFDiffResult::PageSequenceItem& item : pageSequence) + { + if (item.leftPage == -1) + { + continue; + } + + QSizeF pageSize = catalog->getPage(item.leftPage)->getRotatedMediaBoxMM().size(); + QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); + m_layout.emplace_back(0, item.leftPage, rect); + yPos += pageSize.height() + 5; + } + } + + break; + } + + case ComparedDocumentMapper::Mode::Right: + { + Q_ASSERT(rightDocument == currentDocument); + + double yPos = 0.0; + const pdf::PDFCatalog* catalog = rightDocument->getCatalog(); + + if (isEmpty) + { + // Just copy all pages + const size_t pageCount = catalog->getPageCount(); + for (size_t i = 0; i < pageCount; ++i) + { + QSizeF pageSize = catalog->getPage(i)->getRotatedMediaBoxMM().size(); + QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); + m_layout.emplace_back(0, i, rect); + yPos += pageSize.height() + 5; + } + } + else + { + for (const pdf::PDFDiffResult::PageSequenceItem& item : pageSequence) + { + if (item.rightPage == -1) + { + continue; + } + + QSizeF pageSize = catalog->getPage(item.rightPage)->getRotatedMediaBoxMM().size(); + QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); + m_layout.emplace_back(0, item.rightPage, rect); + yPos += pageSize.height() + 5; + } + } + + break; + } + + case ComparedDocumentMapper::Mode::Combined: + case ComparedDocumentMapper::Mode::Overlay: + break; + + default: + Q_ASSERT(false); + break; + } +} + } // namespace pdfdocdiff diff --git a/Pdf4QtDocDiff/mainwindow.h b/Pdf4QtDocDiff/mainwindow.h index 60274a4..5a270f3 100644 --- a/Pdf4QtDocDiff/mainwindow.h +++ b/Pdf4QtDocDiff/mainwindow.h @@ -25,6 +25,7 @@ #include "pdfdrawwidget.h" #include "pdfcms.h" #include "pdfoptionalcontent.h" +#include "pdfdrawspacecontroller.h" #include #include @@ -42,6 +43,33 @@ namespace pdfdocdiff class SettingsDockWidget; class DifferencesDockWidget; +class ComparedDocumentMapper +{ +public: + + enum class Mode + { + Left, + Right, + Combined, + Overlay + }; + + void update(Mode mode, + bool filterDifferences, + const pdf::PDFDiffResult& diff, + const pdf::PDFDocument* leftDocument, + const pdf::PDFDocument* rightDocument, + const pdf::PDFDocument* currentDocument); + + const pdf::PDFDrawSpaceController::LayoutItems& getLayout() const { return m_layout; } + void setPageSequence(const pdf::PDFDiffResult::PageSequence& sequence) { m_pageSequence = sequence; } + +private: + pdf::PDFDrawSpaceController::LayoutItems m_layout; + pdf::PDFDiffResult::PageSequence m_pageSequence; +}; + class MainWindow : public QMainWindow { Q_OBJECT @@ -98,7 +126,9 @@ private: bool canPerformOperation(Operation operation) const; void performOperation(Operation operation); - void setViewDocument(pdf::PDFDocument* document); + void setViewDocument(pdf::PDFDocument* document, bool updateCustomPageLayout); + + ComparedDocumentMapper::Mode getDocumentViewMode() const; /// Clears all data, and possibly documents also. /// View document is set to nullptr. @@ -109,6 +139,7 @@ private: void updateAll(bool resetFilters); void updateFilteredResult(); void updateViewDocument(); + void updateCustomPageLayout(); std::optional openDocument(); @@ -136,6 +167,7 @@ private: pdf::PDFDiffResult m_diffResult; pdf::PDFDiffResult m_filteredDiffResult; ///< Difference result with filters applied pdf::PDFDiffResultNavigator m_diffNavigator; ///< Difference navigator + ComparedDocumentMapper m_documentMapper; }; } // namespace pdfdocdiff diff --git a/Pdf4QtLib/sources/pdfcatalog.h b/Pdf4QtLib/sources/pdfcatalog.h index d23def5..a16b2bc 100644 --- a/Pdf4QtLib/sources/pdfcatalog.h +++ b/Pdf4QtLib/sources/pdfcatalog.h @@ -39,12 +39,13 @@ class PDFDocument; /// to be used in viewer application. enum class PageLayout { - SinglePage, ///< Display one page at time (single page on screen) - OneColumn, ///< Displays pages in one column (continuous mode) - TwoColumnLeft, ///< Display pages in two continuous columns, odd numbered pages are on the left - TwoColumnRight, ///< Display pages in two continuous columns, even numbered pages are on the left - TwoPagesLeft, ///< Display two pages on the screen, odd numbered pages are on the left - TwoPagesRight ///< Display two pages on the screen, even numbered pages are on the left + SinglePage, ///< Display one page at time (single page on screen) + OneColumn, ///< Displays pages in one column (continuous mode) + TwoColumnLeft, ///< Display pages in two continuous columns, odd numbered pages are on the left + TwoColumnRight, ///< Display pages in two continuous columns, even numbered pages are on the left + TwoPagesLeft, ///< Display two pages on the screen, odd numbered pages are on the left + TwoPagesRight, ///< Display two pages on the screen, even numbered pages are on the left + Custom ///< Custom layout, multiple columns can be used, -1 as page index means page is omitted }; /// Specifies, how the document should be displayed in the viewer application. diff --git a/Pdf4QtLib/sources/pdfdiff.cpp b/Pdf4QtLib/sources/pdfdiff.cpp index 942b895..7efa9c5 100644 --- a/Pdf4QtLib/sources/pdfdiff.cpp +++ b/Pdf4QtLib/sources/pdfdiff.cpp @@ -1200,6 +1200,32 @@ bool PDFDiffResult::isReplaceDifference(size_t index) const return getTypeFlags(index) & FLAGS_TYPE_REPLACE; } +std::vector PDFDiffResult::getChangedLeftPageIndices() const +{ + std::set changedPageIndices; + + for (size_t i = 0; i < m_differences.size(); ++i) + { + changedPageIndices.insert(getLeftPage(i)); + } + changedPageIndices.erase(-1); + + return std::vector(changedPageIndices.cbegin(), changedPageIndices.cend()); +} + +std::vector PDFDiffResult::getChangedRightPageIndices() const +{ + std::set changedPageIndices; + + for (size_t i = 0; i < m_differences.size(); ++i) + { + changedPageIndices.insert(getRightPage(i)); + } + changedPageIndices.erase(-1); + + return std::vector(changedPageIndices.cbegin(), changedPageIndices.cend()); +} + PDFDiffResult PDFDiffResult::filter(bool filterPageMoveDifferences, bool filterTextDifferences, bool filterVectorGraphicsDifferences, diff --git a/Pdf4QtLib/sources/pdfdiff.h b/Pdf4QtLib/sources/pdfdiff.h index ef91dc2..abff9b2 100644 --- a/Pdf4QtLib/sources/pdfdiff.h +++ b/Pdf4QtLib/sources/pdfdiff.h @@ -106,6 +106,12 @@ public: bool hasImageDifferences() const { return m_typeFlags & FLAGS_IMAGE; } bool hasShadingDifferences() const { return m_typeFlags & FLAGS_SHADING; } + /// Returns sorted changed page indices from left document + std::vector getChangedLeftPageIndices() const; + + /// Returns sorted changed page indices from right document + std::vector getChangedRightPageIndices() const; + /// Filters results using given critera /// \param filterPageMoveDifferences Filter page move differences? /// \param filterTextDifferences Filter text diffferences? diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp index 8f89df6..812c022 100644 --- a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp +++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp @@ -99,6 +99,31 @@ PDFOperationResult PDFDocumentManipulator::assemble(const AssembledPages& pages) return true; } +PDFDocumentManipulator::AssembledPages PDFDocumentManipulator::createAllDocumentPages(int documentIndex, const PDFDocument* document) +{ + AssembledPages assembledPages; + size_t pageCount = document->getCatalog()->getPageCount(); + + for (size_t i = 0; i < pageCount; ++i) + { + pdf::PDFDocumentManipulator::AssembledPage assembledPage; + + assembledPage.documentIndex = documentIndex; + assembledPage.imageIndex = -1; + assembledPage.pageIndex = i; + + const pdf::PDFPage* page = document->getCatalog()->getPage(i); + const pdf::PageRotation originalPageRotation = page->getPageRotation(); + + assembledPage.pageRotation = originalPageRotation; + assembledPage.pageSize = page->getMediaBox().size(); + + assembledPages.emplace_back(assembledPage); + } + + return assembledPages; +} + PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::processPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages) { ProcessedPages processedPages; diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.h b/Pdf4QtLib/sources/pdfdocumentmanipulator.h index a8deb63..e0c8ef7 100644 --- a/Pdf4QtLib/sources/pdfdocumentmanipulator.h +++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.h @@ -91,6 +91,8 @@ public: /// \returns Assembled document PDFDocument&& takeAssembledDocument() { return std::move(m_assembledDocument); } + static AssembledPages createAllDocumentPages(int documentIndex, const PDFDocument* document); + static constexpr AssembledPage createDocumentPage(int documentIndex, int pageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ documentIndex, -1, pageIndex, pageSize, pageRotation}; } static constexpr AssembledPage createImagePage(int imageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, imageIndex, -1, pageSize, pageRotation}; } static constexpr AssembledPage createBlankPage(QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, -1, -1, pageSize, pageRotation}; } diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index 51d26a7..411609a 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -154,6 +154,20 @@ void PDFDrawSpaceController::setPageRotation(PageRotation pageRotation) } } +void PDFDrawSpaceController::setCustomLayout(LayoutItems customLayoutItems) +{ + if (m_customLayoutItems != customLayoutItems) + { + m_customLayoutItems = std::move(customLayoutItems); + + if (m_pageLayoutMode == PageLayout::Custom) + { + // Recalculate only, if custom layout is active + recalculate(); + } + } +} + void PDFDrawSpaceController::recalculate() { if (!m_document) @@ -361,6 +375,54 @@ void PDFDrawSpaceController::recalculate() break; } + case PageLayout::Custom: + { + m_layoutItems = m_customLayoutItems; + + // We do not support page rotation for custom layout + Q_ASSERT(m_pageRotation == PageRotation::None); + + // Assure, that layout items are sorted by block + auto comparator = [](const LayoutItem& l, const LayoutItem& r) + { + return l.blockIndex < r.blockIndex; + }; + std::stable_sort(m_layoutItems.begin(), m_layoutItems.end(), comparator); + + // Now, compute blocks + if (!m_layoutItems.empty()) + { + m_blockItems.reserve(m_layoutItems.back().blockIndex + 1); + + QRectF currentBoundingRect; + PDFInteger blockIndex = -1; + + for (const LayoutItem& layoutItem : m_layoutItems) + { + if (blockIndex != layoutItem.blockIndex) + { + blockIndex = layoutItem.blockIndex; + + if (currentBoundingRect.isValid()) + { + m_blockItems.push_back(LayoutBlock(currentBoundingRect)); + currentBoundingRect = QRectF(); + } + } + + currentBoundingRect = currentBoundingRect.united(layoutItem.pageRectMM); + } + + if (currentBoundingRect.isValid()) + { + m_blockItems.push_back(LayoutBlock(currentBoundingRect)); + currentBoundingRect = QRectF(); + } + } + + break; + } + default: { Q_ASSERT(false); @@ -1156,6 +1218,15 @@ void PDFDrawWidgetProxy::setPageLayout(PageLayout pageLayout) } } +void PDFDrawWidgetProxy::setCustomPageLayout(PDFDrawSpaceController::LayoutItems layoutItems) +{ + if (m_controller->getCustomLayout() != layoutItems) + { + m_controller->setCustomLayout(std::move(layoutItems)); + emit pageLayoutChanged(); + } +} + QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const { Q_ASSERT(rect.isValid()); @@ -1185,6 +1256,9 @@ bool PDFDrawWidgetProxy::isBlockMode() const case PageLayout::TwoPagesLeft: case PageLayout::TwoPagesRight: return true; + + case PageLayout::Custom: + return m_controller->getBlockCount() > 1; } Q_ASSERT(false); @@ -1217,6 +1291,10 @@ void PDFDrawWidgetProxy::prefetchPages(PDFInteger pageIndex) prefetchCount = 2; break; + case PageLayout::Custom: + prefetchCount = 0; + break; + default: Q_ASSERT(false); break; diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.h b/Pdf4QtLib/sources/pdfdrawspacecontroller.h index 14eed72..b1dc8da 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.h +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.h @@ -81,6 +81,8 @@ public: constexpr inline explicit LayoutItem(PDFInteger blockIndex, PDFInteger pageIndex, const QRectF& pageRectMM) : blockIndex(blockIndex), pageIndex(pageIndex), pageRectMM(pageRectMM) { } + bool operator ==(const LayoutItem&) const = default; + bool isValid() const { return pageIndex != -1; } PDFInteger blockIndex; @@ -123,6 +125,15 @@ public: /// Sets page rotation void setPageRotation(PageRotation pageRotation); + /// Set custom layout. Custom layout provides a way how to define + /// custom page layout, including blocks. Block indices must be properly defined, + /// that means block index must start by zero and must be continuous. If this + /// criteria are not fulfilled, behaviour is undefined. + void setCustomLayout(LayoutItems customLayoutItems); + + /// Returns custom layout + const LayoutItems& getCustomLayout() const { return m_customLayoutItems; } + signals: void drawSpaceChanged(); void repaintNeeded(); @@ -155,6 +166,7 @@ private: PDFReal m_verticalSpacingMM; PDFReal m_horizontalSpacingMM; PageRotation m_pageRotation; + LayoutItems m_customLayoutItems; /// Font cache PDFFontCache m_fontCache; @@ -282,6 +294,11 @@ public: /// \param pageLayout Page layout void setPageLayout(PageLayout pageLayout); + /// Sets custom page layout. If this function is used, page layout mode + /// must be set to 'Custom'. + /// \param layoutItems Layout items + void setCustomPageLayout(PDFDrawSpaceController::LayoutItems layoutItems); + /// Returns the page layout PageLayout getPageLayout() const { return m_controller->getPageLayout(); } @@ -360,7 +377,7 @@ public: signals: void drawSpaceChanged(); void pageLayoutChanged(); - void renderingError(PDFInteger pageIndex, const QList& errors); + void renderingError(pdf::PDFInteger pageIndex, const QList& errors); void repaintNeeded(); void pageImageChanged(bool all, const std::vector& pages); void textLayoutChanged();