2021-09-27 11:29:00 +02:00
|
|
|
// Copyright (C) 2021 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 <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#ifndef PDFDIFF_H
|
|
|
|
#define PDFDIFF_H
|
|
|
|
|
|
|
|
#include "pdfdocument.h"
|
|
|
|
#include "pdfprogress.h"
|
|
|
|
#include "pdfutils.h"
|
|
|
|
#include "pdfalgorithmlcs.h"
|
|
|
|
#include "pdfdocumenttextflow.h"
|
|
|
|
|
|
|
|
#include <QObject>
|
|
|
|
#include <QFuture>
|
|
|
|
#include <QFutureWatcher>
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
namespace pdf
|
|
|
|
{
|
|
|
|
|
|
|
|
struct PDFDiffPageContext;
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
class PDF4QTLIBSHARED_EXPORT PDFDiffResult
|
2021-09-27 11:29:00 +02:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit PDFDiffResult();
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
enum class Type : uint32_t
|
2021-09-27 11:29:00 +02:00
|
|
|
{
|
2021-09-29 16:59:13 +02:00
|
|
|
Invalid = 0x0000,
|
|
|
|
PageMoved = 0x0001,
|
|
|
|
PageAdded = 0x0002,
|
|
|
|
PageRemoved = 0x0004,
|
|
|
|
RemovedTextCharContent = 0x0008,
|
|
|
|
RemovedVectorGraphicContent = 0x0010,
|
|
|
|
RemovedImageContent = 0x0020,
|
|
|
|
RemovedShadingContent = 0x0040,
|
|
|
|
AddedTextCharContent = 0x0080,
|
|
|
|
AddedVectorGraphicContent = 0x0100,
|
|
|
|
AddedImageContent = 0x0200,
|
|
|
|
AddedShadingContent = 0x0400,
|
|
|
|
TextReplaced = 0x0800,
|
|
|
|
TextAdded = 0x1000,
|
|
|
|
TextRemoved = 0x2000,
|
2021-09-27 11:29:00 +02:00
|
|
|
};
|
|
|
|
|
2021-09-27 19:40:46 +02:00
|
|
|
using RectInfos = std::vector<std::pair<PDFInteger, QRectF>>;
|
2021-09-27 11:29:00 +02:00
|
|
|
|
|
|
|
void setResult(PDFOperationResult result) { m_result = std::move(result); }
|
|
|
|
const PDFOperationResult& getResult() const { return m_result; }
|
|
|
|
|
2021-09-27 19:40:46 +02:00
|
|
|
/// Returns true, if some difference was found
|
|
|
|
bool isChanged() const { return getDifferencesCount() > 0; }
|
|
|
|
|
|
|
|
/// Returns true, if no difference was found
|
|
|
|
bool isSame() const { return !isChanged(); }
|
|
|
|
|
|
|
|
/// Returns number of detected changes
|
|
|
|
size_t getDifferencesCount() const { return m_differences.size(); }
|
|
|
|
|
|
|
|
/// Returns message describing difference in a page content
|
|
|
|
/// \param index Index
|
|
|
|
QString getMessage(size_t index) const;
|
2021-10-02 15:57:07 +02:00
|
|
|
|
|
|
|
/// Returns index of left page (or -1, if difference occured
|
|
|
|
/// only on a right page)
|
|
|
|
/// \param index Index
|
|
|
|
PDFInteger getLeftPage(size_t index) const;
|
|
|
|
|
|
|
|
/// Returns index of right page (or -1, if difference occured
|
|
|
|
/// only on a left page)
|
|
|
|
/// \param index Index
|
|
|
|
PDFInteger getRightPage(size_t index) const;
|
2021-09-27 19:40:46 +02:00
|
|
|
|
2021-10-02 19:30:18 +02:00
|
|
|
bool isPageMoveDifference(size_t index) const;
|
|
|
|
bool isAddDifference(size_t index) const;
|
|
|
|
bool isRemoveDifference(size_t index) const;
|
|
|
|
bool isReplaceDifference(size_t index) const;
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
bool hasPageMoveDifferences() const { return m_typeFlags & FLAGS_PAGE_MOVE; }
|
|
|
|
bool hasTextDifferences() const { return m_typeFlags & FLAGS_TEXT; }
|
|
|
|
bool hasVectorGraphicsDifferences() const { return m_typeFlags & FLAGS_VECTOR_GRAPHICS; }
|
|
|
|
bool hasImageDifferences() const { return m_typeFlags & FLAGS_IMAGE; }
|
|
|
|
bool hasShadingDifferences() const { return m_typeFlags & FLAGS_SHADING; }
|
|
|
|
|
|
|
|
/// Filters results using given critera
|
|
|
|
/// \param filterPageMoveDifferences Filter page move differences?
|
|
|
|
/// \param filterTextDifferences Filter text diffferences?
|
|
|
|
/// \param filterVectorGraphicsDifferences Filter vector graphics differences?
|
|
|
|
/// \param filterImageDifferences Filter image differences?
|
|
|
|
/// \param filterShadingDifferences Filter shading differences?
|
|
|
|
PDFDiffResult filter(bool filterPageMoveDifferences,
|
|
|
|
bool filterTextDifferences,
|
|
|
|
bool filterVectorGraphicsDifferences,
|
|
|
|
bool filterImageDifferences,
|
|
|
|
bool filterShadingDifferences);
|
|
|
|
|
2021-09-27 19:40:46 +02:00
|
|
|
private:
|
|
|
|
friend class PDFDiff;
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
static constexpr uint32_t FLAGS_PAGE_MOVE = uint32_t(Type::PageMoved) | uint32_t(Type::PageAdded) | uint32_t(Type::PageRemoved);
|
|
|
|
static constexpr uint32_t FLAGS_TEXT = uint32_t(Type::RemovedTextCharContent) | uint32_t(Type::AddedTextCharContent) | uint32_t(Type::TextReplaced) | uint32_t(Type::TextAdded) | uint32_t(Type::TextRemoved);
|
|
|
|
static constexpr uint32_t FLAGS_VECTOR_GRAPHICS = uint32_t(Type::RemovedVectorGraphicContent) | uint32_t(Type::AddedVectorGraphicContent);
|
|
|
|
static constexpr uint32_t FLAGS_IMAGE = uint32_t(Type::RemovedImageContent) | uint32_t(Type::AddedImageContent);
|
|
|
|
static constexpr uint32_t FLAGS_SHADING = uint32_t(Type::RemovedShadingContent) | uint32_t(Type::AddedShadingContent);
|
|
|
|
|
2021-10-02 19:30:18 +02:00
|
|
|
static constexpr uint32_t FLAGS_TYPE_PAGE_MOVE = uint32_t(Type::PageMoved);
|
|
|
|
static constexpr uint32_t FLAGS_TYPE_ADD = uint32_t(Type::PageAdded) | uint32_t(Type::AddedTextCharContent) | uint32_t(Type::AddedVectorGraphicContent) | uint32_t(Type::AddedImageContent) | uint32_t(Type::AddedShadingContent) | uint32_t(Type::TextAdded);
|
|
|
|
static constexpr uint32_t FLAGS_TYPE_REMOVE = uint32_t(Type::PageRemoved) | uint32_t(Type::RemovedTextCharContent) | uint32_t(Type::RemovedVectorGraphicContent) | uint32_t(Type::RemovedImageContent) | uint32_t(Type::RemovedShadingContent) | uint32_t(Type::TextRemoved);
|
|
|
|
static constexpr uint32_t FLAGS_TYPE_REPLACE = uint32_t(Type::TextReplaced);
|
|
|
|
|
2021-09-27 11:29:00 +02:00
|
|
|
void addPageMoved(PDFInteger pageIndex1, PDFInteger pageIndex2);
|
|
|
|
void addPageAdded(PDFInteger pageIndex);
|
|
|
|
void addPageRemoved(PDFInteger pageIndex);
|
|
|
|
|
|
|
|
void addRemovedTextCharContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addRemovedVectorGraphicContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addRemovedImageContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addRemovedShadingContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addAddedTextCharContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addAddedVectorGraphicContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addAddedImageContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addAddedShadingContent(PDFInteger pageIndex, QRectF rect);
|
|
|
|
|
2021-09-27 19:40:46 +02:00
|
|
|
void addTextAdded(PDFInteger pageIndex, QString text, const RectInfos& rectInfos);
|
|
|
|
void addTextRemoved(PDFInteger pageIndex, QString text, const RectInfos& rectInfos);
|
|
|
|
|
|
|
|
void addTextReplaced(PDFInteger pageIndex1,
|
|
|
|
PDFInteger pageIndex2,
|
|
|
|
QString textRemoved,
|
|
|
|
QString textAdded,
|
|
|
|
const RectInfos& rectInfos1,
|
|
|
|
const RectInfos& rectInfos2);
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
void finalize();
|
|
|
|
|
2021-10-02 19:30:18 +02:00
|
|
|
uint32_t getTypeFlags(size_t index) const;
|
|
|
|
|
2021-09-27 19:40:46 +02:00
|
|
|
/// Single content difference descriptor. It describes type
|
|
|
|
/// of difference (such as graphics, image, text change) on a page
|
|
|
|
/// or on a list of multiple pages.
|
|
|
|
struct Difference
|
|
|
|
{
|
|
|
|
Type type = Type::Invalid;
|
|
|
|
PDFInteger pageIndex1 = -1;
|
|
|
|
PDFInteger pageIndex2 = -1;
|
|
|
|
size_t leftRectIndex = 0;
|
|
|
|
size_t leftRectCount = 0;
|
|
|
|
size_t rightRectIndex = 0;
|
|
|
|
size_t rightRectCount = 0;
|
|
|
|
int textAddedIndex = -1;
|
|
|
|
int textRemovedIndex = -1;
|
|
|
|
};
|
|
|
|
|
|
|
|
using Differences = std::vector<Difference>;
|
2021-09-27 11:29:00 +02:00
|
|
|
|
|
|
|
void addLeftItem(Type type, PDFInteger pageIndex, QRectF rect);
|
|
|
|
void addRightItem(Type type, PDFInteger pageIndex, QRectF rect);
|
|
|
|
|
|
|
|
void addRectLeft(Difference& difference, QRectF rect);
|
|
|
|
void addRectRight(Difference& difference, QRectF rect);
|
|
|
|
|
|
|
|
Differences m_differences;
|
2021-09-27 19:40:46 +02:00
|
|
|
RectInfos m_rects; ///< Rectangles with page indices
|
2021-09-27 11:29:00 +02:00
|
|
|
PDFOperationResult m_result;
|
2021-09-27 19:40:46 +02:00
|
|
|
QStringList m_strings;
|
2021-09-29 16:59:13 +02:00
|
|
|
uint32_t m_typeFlags = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Class for result navigation, can go to next, or previous result.
|
|
|
|
class PDF4QTLIBSHARED_EXPORT PDFDiffResultNavigator : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit PDFDiffResultNavigator(QObject* parent);
|
|
|
|
virtual ~PDFDiffResultNavigator() override;
|
|
|
|
|
|
|
|
void setResult(const PDFDiffResult* diffResult);
|
|
|
|
|
|
|
|
/// Returns true, if valid result is selected
|
|
|
|
bool isSelected() const;
|
|
|
|
|
|
|
|
/// Returns true if action go to next result can be performed,
|
|
|
|
/// otherwise false is returned.
|
|
|
|
bool canGoNext() const;
|
|
|
|
|
|
|
|
/// Returns true if action go to previous result can be performed,
|
|
|
|
/// otherwise false is returned.
|
|
|
|
bool canGoPrevious() const;
|
|
|
|
|
|
|
|
/// Goes to next result. If action cannot be performed,
|
|
|
|
/// nothing happens and signal is not emitted.
|
|
|
|
void goNext();
|
|
|
|
|
|
|
|
/// Goes to previous result. If action cannot be performed,
|
|
|
|
/// nothing happens and signal is not emitted.
|
|
|
|
void goPrevious();
|
|
|
|
|
|
|
|
/// Updates selection, if difference result was changed
|
|
|
|
void update();
|
|
|
|
|
2021-10-03 17:16:12 +02:00
|
|
|
/// Selects current index
|
|
|
|
/// \param currentIndex
|
|
|
|
void select(size_t currentIndex);
|
|
|
|
|
2021-09-29 16:59:13 +02:00
|
|
|
signals:
|
|
|
|
void selectionChanged(size_t currentIndex);
|
|
|
|
|
|
|
|
private:
|
|
|
|
size_t getLimit() const { return m_diffResult ? m_diffResult->getDifferencesCount() : 0; }
|
|
|
|
|
|
|
|
const PDFDiffResult* m_diffResult;
|
|
|
|
size_t m_currentIndex;
|
2021-09-27 11:29:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Diff engine for comparing two pdf documents.
|
|
|
|
class PDF4QTLIBSHARED_EXPORT PDFDiff : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
private:
|
|
|
|
using BaseClass = QObject;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit PDFDiff(QObject* parent);
|
|
|
|
virtual ~PDFDiff() override;
|
|
|
|
|
|
|
|
enum Option
|
|
|
|
{
|
|
|
|
None = 0x0000,
|
|
|
|
Asynchronous = 0x0001, ///< Compare document asynchronously
|
|
|
|
PC_Text = 0x0002, ///< Use text to compare pages (determine, which pages correspond to each other)
|
|
|
|
PC_VectorGraphics = 0x0004, ///< Use vector graphics to compare pages (determine, which pages correspond to each other)
|
|
|
|
PC_Images = 0x0008, ///< Use images to compare pages (determine, which pages correspond to each other)
|
|
|
|
PC_Mesh = 0x0010, ///< Use mesh to compare pages (determine, which pages correspond to each other)
|
|
|
|
CompareTextsAsVector = 0x0020, ///< Compare texts as vector graphics
|
|
|
|
CompareWords = 0x0040, ///< Compare words, not just characters
|
|
|
|
};
|
|
|
|
Q_DECLARE_FLAGS(Options, Option)
|
|
|
|
|
|
|
|
/// Source document (left)
|
|
|
|
/// \param leftDocument Document
|
|
|
|
void setLeftDocument(const PDFDocument* leftDocument);
|
|
|
|
|
|
|
|
/// Source document (right)(
|
|
|
|
/// \param rightDocument Document
|
|
|
|
void setRightDocument(const PDFDocument* rightDocument);
|
|
|
|
|
|
|
|
/// Source pages to be compared (left document)
|
|
|
|
/// \param pagesForLeftDocument Page indices
|
|
|
|
void setPagesForLeftDocument(PDFClosedIntervalSet pagesForLeftDocument);
|
|
|
|
|
|
|
|
/// Source pages to be compared (right document)
|
|
|
|
/// \param pagesForRightDocument Page indices
|
|
|
|
void setPagesForRightDocument(PDFClosedIntervalSet pagesForRightDocument);
|
|
|
|
|
|
|
|
/// Sets progress object
|
|
|
|
/// \param progress Progress object
|
|
|
|
void setProgress(PDFProgress* progress) { m_progress = progress; }
|
|
|
|
|
|
|
|
/// Enables or disables comparator engine option
|
|
|
|
/// \param option Option
|
|
|
|
/// \param enable Enable or disable option?
|
|
|
|
void setOption(Option option, bool enable) { m_options.setFlag(option, enable); }
|
|
|
|
|
|
|
|
/// Starts comparator engine. If asynchronous engine option
|
|
|
|
/// is enabled, then separate thread is started, in which two
|
|
|
|
/// document is compared, and then signal \p comparationFinished,
|
|
|
|
/// otherwise this function is blocking until comparation process
|
|
|
|
/// is finished.
|
|
|
|
void start();
|
|
|
|
|
|
|
|
/// Stops comparator engine. Result data are cleared.
|
|
|
|
void stop();
|
|
|
|
|
|
|
|
/// Returns result of a comparation process
|
|
|
|
const PDFDiffResult& getResult() const { return m_result; }
|
|
|
|
|
|
|
|
PDFDocumentTextFlowFactory::Algorithm getTextAnalysisAlgorithm() const;
|
|
|
|
void setTextAnalysisAlgorithm(PDFDocumentTextFlowFactory::Algorithm textAnalysisAlgorithm);
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void comparationFinished();
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
enum Steps
|
|
|
|
{
|
|
|
|
StepExtractContentLeftDocument,
|
|
|
|
StepExtractContentRightDocument,
|
|
|
|
StepMatchPages,
|
|
|
|
StepExtractTextLeftDocument,
|
|
|
|
StepExtractTextRightDocument,
|
|
|
|
StepCompare,
|
|
|
|
StepLast
|
|
|
|
};
|
|
|
|
|
|
|
|
PDFDiffResult perform();
|
|
|
|
void stepProgress();
|
|
|
|
void performSteps(const std::vector<PDFInteger>& leftPages,
|
2021-09-29 16:59:13 +02:00
|
|
|
const std::vector<PDFInteger>& rightPages,
|
|
|
|
PDFDiffResult& result);
|
2021-09-27 11:29:00 +02:00
|
|
|
void performPageMatching(const std::vector<PDFDiffPageContext>& leftPreparedPages,
|
|
|
|
const std::vector<PDFDiffPageContext>& rightPreparedPages,
|
|
|
|
PDFAlgorithmLongestCommonSubsequenceBase::Sequence& pageSequence,
|
|
|
|
std::map<size_t, size_t>& pageMatches);
|
|
|
|
void performCompare(const std::vector<PDFDiffPageContext>& leftPreparedPages,
|
|
|
|
const std::vector<PDFDiffPageContext>& rightPreparedPages,
|
|
|
|
PDFAlgorithmLongestCommonSubsequenceBase::Sequence& pageSequence,
|
2021-09-29 16:59:13 +02:00
|
|
|
const std::map<size_t, size_t>& pageMatches,
|
|
|
|
PDFDiffResult& result);
|
2021-09-27 11:29:00 +02:00
|
|
|
void finalizeGraphicsPieces(PDFDiffPageContext& context);
|
|
|
|
|
|
|
|
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;
|
|
|
|
PDFDocumentTextFlowFactory::Algorithm m_textAnalysisAlgorithm;
|
|
|
|
|
|
|
|
QFuture<PDFDiffResult> m_future;
|
|
|
|
std::optional<QFutureWatcher<PDFDiffResult>> m_futureWatcher;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace pdf
|
|
|
|
|
|
|
|
#endif // PDFDIFF_H
|