DocDiff application: document view update

This commit is contained in:
Jakub Melka 2021-10-13 17:51:16 +02:00
parent 8975a18d93
commit 41889070fb
10 changed files with 407 additions and 21 deletions

View File

@ -41,3 +41,4 @@ Pdf4QtViewerPlugins.depends = Pdf4QtLib
Pdf4QtViewerProfi.depends = Pdf4QtViewer
Pdf4QtViewerLite.depends = Pdf4QtViewer
Pdf4QtDocPageOrganizer.depends = Pdf4QtLib
Pdf4QtDocDiff.depends = Pdf4QtLib

View File

@ -25,6 +25,7 @@
#include "pdfwidgetutils.h"
#include "pdfdocumentreader.h"
#include "pdfdrawspacecontroller.h"
#include "pdfdocumentmanipulator.h"
#include <QToolBar>
#include <QDesktopWidget>
@ -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())
{
case ComparedDocumentMapper::Mode::Left:
document = &m_leftDocument;
}
break;
if (ui->actionView_Right->isChecked())
{
case ComparedDocumentMapper::Mode::Right:
document = &m_rightDocument;
}
break;
if (ui->actionView_Differences->isChecked() || ui->actionView_Overlay->isChecked())
{
case ComparedDocumentMapper::Mode::Combined:
case ComparedDocumentMapper::Mode::Overlay:
document = &m_combinedDocument;
break;
}
setViewDocument(document);
setViewDocument(document, true);
}
void MainWindow::updateCustomPageLayout()
{
m_documentMapper.update(getDocumentViewMode(),
ui->actionShow_Pages_with_Differences->isChecked(),
m_filteredDiffResult,
&m_leftDocument,
&m_rightDocument,
m_pdfWidget->getDrawWidgetProxy()->getDocument());
m_pdfWidget->getDrawWidgetProxy()->setCustomPageLayout(m_documentMapper.getLayout());
m_pdfWidget->getDrawWidgetProxy()->setPageLayout(pdf::PageLayout::Custom);
}
std::optional<pdf::PDFDocument> 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<pdf::PDFInteger> leftPageIndices = diff.getChangedLeftPageIndices();
std::vector<pdf::PDFInteger> 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

View File

@ -25,6 +25,7 @@
#include "pdfdrawwidget.h"
#include "pdfcms.h"
#include "pdfoptionalcontent.h"
#include "pdfdrawspacecontroller.h"
#include <QMainWindow>
#include <QSignalMapper>
@ -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<pdf::PDFDocument> 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

View File

@ -44,7 +44,8 @@ enum class PageLayout
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
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.

View File

@ -1200,6 +1200,32 @@ bool PDFDiffResult::isReplaceDifference(size_t index) const
return getTypeFlags(index) & FLAGS_TYPE_REPLACE;
}
std::vector<PDFInteger> PDFDiffResult::getChangedLeftPageIndices() const
{
std::set<PDFInteger> changedPageIndices;
for (size_t i = 0; i < m_differences.size(); ++i)
{
changedPageIndices.insert(getLeftPage(i));
}
changedPageIndices.erase(-1);
return std::vector<PDFInteger>(changedPageIndices.cbegin(), changedPageIndices.cend());
}
std::vector<PDFInteger> PDFDiffResult::getChangedRightPageIndices() const
{
std::set<PDFInteger> changedPageIndices;
for (size_t i = 0; i < m_differences.size(); ++i)
{
changedPageIndices.insert(getRightPage(i));
}
changedPageIndices.erase(-1);
return std::vector<PDFInteger>(changedPageIndices.cbegin(), changedPageIndices.cend());
}
PDFDiffResult PDFDiffResult::filter(bool filterPageMoveDifferences,
bool filterTextDifferences,
bool filterVectorGraphicsDifferences,

View File

@ -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<PDFInteger> getChangedLeftPageIndices() const;
/// Returns sorted changed page indices from right document
std::vector<PDFInteger> getChangedRightPageIndices() const;
/// Filters results using given critera
/// \param filterPageMoveDifferences Filter page move differences?
/// \param filterTextDifferences Filter text diffferences?

View File

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

View File

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

View File

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

View File

@ -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<PDFRenderError>& errors);
void renderingError(pdf::PDFInteger pageIndex, const QList<pdf::PDFRenderError>& errors);
void repaintNeeded();
void pageImageChanged(bool all, const std::vector<PDFInteger>& pages);
void textLayoutChanged();