// 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 .
#include "utils.h"
#include "pdfutils.h"
#include "pdfwidgetutils.h"
#include "pdfpainterutils.h"
#include "pdfdocumentbuilder.h"
#include
namespace pdfdiff
{
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();
m_leftPageIndices.clear();
m_rightPageIndices.clear();
m_allLeft = false;
m_allRight = false;
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);
m_allLeft = true;
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, -1, 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, -1, rect);
yPos += pageSize.height() + 5;
}
}
break;
}
case ComparedDocumentMapper::Mode::Right:
{
Q_ASSERT(rightDocument == currentDocument);
m_allRight = true;
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, -1, 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, -1, rect);
yPos += pageSize.height() + 5;
}
}
break;
}
case ComparedDocumentMapper::Mode::Combined:
case ComparedDocumentMapper::Mode::Overlay:
{
double yPos = 0.0;
const pdf::PDFCatalog* catalog = currentDocument->getCatalog();
pdf::PDFInteger offset = leftDocument->getCatalog()->getPageCount();
for (const pdf::PDFDiffResult::PageSequenceItem& item : pageSequence)
{
double yAdvance = 0.0;
if (item.leftPage != -1)
{
QSizeF pageSize = catalog->getPage(item.leftPage)->getRotatedMediaBoxMM().size();
QRectF rect;
pdf::PDFInteger groupIndex = -1;
if (mode == ComparedDocumentMapper::Mode::Combined)
{
rect = QRectF(-pageSize.width() - 5, yPos, pageSize.width(), pageSize.height());
}
else
{
if (item.rightPage != -1)
{
groupIndex = 1;
}
rect = QRectF(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height());
}
m_layout.emplace_back(0, item.leftPage, groupIndex, rect);
yAdvance = pageSize.height() + 5;
m_leftPageIndices[item.leftPage] = item.leftPage;
}
if (item.rightPage != -1)
{
pdf::PDFInteger rightPageIndex = item.rightPage + offset;
QSizeF pageSize = catalog->getPage(rightPageIndex)->getRotatedMediaBoxMM().size();
QRectF rect;
pdf::PDFInteger groupIndex = -1;
if (mode == ComparedDocumentMapper::Mode::Combined)
{
rect = QRectF(5, yPos, pageSize.width(), pageSize.height());
}
else
{
if (item.leftPage != -1)
{
groupIndex = 2;
}
rect = QRectF(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height());
}
m_layout.emplace_back(0, rightPageIndex, groupIndex, rect);
yAdvance = qMax(yAdvance, pageSize.height() + 5);
m_rightPageIndices[rightPageIndex] = item.rightPage;
}
yPos += yAdvance;
}
break;
}
default:
Q_ASSERT(false);
break;
}
}
pdf::PDFInteger ComparedDocumentMapper::getLeftPageIndex(pdf::PDFInteger pageIndex) const
{
if (m_allLeft)
{
return pageIndex;
}
auto it = m_leftPageIndices.find(pageIndex);
if (it != m_leftPageIndices.cend())
{
return it->second;
}
return -1;
}
pdf::PDFInteger ComparedDocumentMapper::getRightPageIndex(pdf::PDFInteger pageIndex) const
{
if (m_allRight)
{
return pageIndex;
}
auto it = m_rightPageIndices.find(pageIndex);
if (it != m_rightPageIndices.cend())
{
return it->second;
}
return -1;
}
pdf::PDFInteger ComparedDocumentMapper::getPageIndexFromLeftPageIndex(pdf::PDFInteger leftPageIndex) const
{
if (m_allLeft)
{
return leftPageIndex;
}
for (const auto& indexItem : m_leftPageIndices)
{
if (indexItem.second == leftPageIndex)
{
return indexItem.first;
}
}
return -1;
}
pdf::PDFInteger ComparedDocumentMapper::getPageIndexFromRightPageIndex(pdf::PDFInteger rightPageIndex) const
{
if (m_allRight)
{
return rightPageIndex;
}
for (const auto& indexItem : m_rightPageIndices)
{
if (indexItem.second == rightPageIndex)
{
return indexItem.first;
}
}
return -1;
}
DifferencesDrawInterface::DifferencesDrawInterface(const Settings* settings,
const ComparedDocumentMapper* mapper,
const pdf::PDFDiffResult* diffResult) :
m_settings(settings),
m_mapper(mapper),
m_diffResult(diffResult)
{
}
void DifferencesDrawInterface::drawPage(QPainter* painter,
pdf::PDFInteger pageIndex,
const pdf::PDFPrecompiledPage* compiledPage,
pdf::PDFTextLayoutGetter& layoutGetter,
const QTransform& pagePointToDevicePointMatrix,
const pdf::PDFColorConvertor& convertor,
QList& errors) const
{
Q_UNUSED(compiledPage);
Q_UNUSED(layoutGetter);
Q_UNUSED(errors);
if (!m_settings->displayDifferences)
{
return;
}
const size_t differencesCount = m_diffResult->getDifferencesCount();
const pdf::PDFInteger leftPageIndex = m_mapper->getLeftPageIndex(pageIndex);
const pdf::PDFInteger rightPageIndex = m_mapper->getRightPageIndex(pageIndex);
std::optional pageMoveIndex;
if (leftPageIndex != -1)
{
for (size_t i = 0; i < differencesCount; ++i)
{
auto leftRectangles = m_diffResult->getLeftRectangles(i);
for (auto it = leftRectangles.first; it != leftRectangles.second; ++it)
{
const auto& item = *it;
if (item.first == leftPageIndex)
{
QColor color = convertor.convert(getColorForIndex(i), false, true);
drawRectangle(painter, pagePointToDevicePointMatrix, item.second, color);
drawMarker(painter, pagePointToDevicePointMatrix, item.second, color, true);
}
}
if (m_diffResult->isPageMoveAddRemoveDifference(i) && m_diffResult->getLeftPage(i) == leftPageIndex)
{
pageMoveIndex = i;
}
}
}
if (rightPageIndex != -1)
{
for (size_t i = 0; i < differencesCount; ++i)
{
auto rightRectangles = m_diffResult->getRightRectangles(i);
for (auto it = rightRectangles.first; it != rightRectangles.second; ++it)
{
const auto& item = *it;
if (item.first == rightPageIndex)
{
QColor color = convertor.convert(getColorForIndex(i), false, true);
drawRectangle(painter, pagePointToDevicePointMatrix, item.second, color);
drawMarker(painter, pagePointToDevicePointMatrix, item.second, color, false);
}
}
if (m_diffResult->isPageMoveAddRemoveDifference(i) && m_diffResult->getRightPage(i) == rightPageIndex)
{
pageMoveIndex = i;
}
}
}
if (pageMoveIndex)
{
QString text;
switch (m_diffResult->getType(*pageMoveIndex))
{
case pdf::PDFDiffResult::Type::PageAdded:
text = " + ";
break;
case pdf::PDFDiffResult::Type::PageRemoved:
text = " - ";
break;
case pdf::PDFDiffResult::Type::PageMoved:
text = QString("%1🠖%2").arg(m_diffResult->getLeftPage(*pageMoveIndex) + 1).arg(m_diffResult->getRightPage(*pageMoveIndex) + 1);
break;
default:
Q_ASSERT(false);
break;
}
QColor color = convertor.convert(getColorForIndex(*pageMoveIndex), false, true);
QPointF targetPoint = pagePointToDevicePointMatrix.map(QPointF(5, 5));
pdf::PDFPainterHelper::drawBubble(painter, targetPoint.toPoint(), color, text, Qt::AlignRight | Qt::AlignTop);
}
}
QColor DifferencesDrawInterface::getColorForIndex(size_t index) const
{
QColor color;
const size_t resultIndex = index;
if (m_diffResult->isReplaceDifference(resultIndex))
{
color = m_settings->colorReplaced;
}
else if (m_diffResult->isRemoveDifference(resultIndex))
{
color = m_settings->colorRemoved;
}
else if (m_diffResult->isAddDifference(resultIndex))
{
color = m_settings->colorAdded;
}
else if (m_diffResult->isPageMoveDifference(resultIndex))
{
color = m_settings->colorPageMove;
}
return color;
}
void DifferencesDrawInterface::drawPostRendering(QPainter* painter, QRect rect) const
{
Q_UNUSED(painter);
Q_UNUSED(rect);
}
void DifferencesDrawInterface::drawAnnotations(const pdf::PDFDocument* document,
pdf::PDFDocumentBuilder* builder)
{
pdf::PDFInteger pageCount = document->getCatalog()->getPageCount();
QString title = pdf::PDFSysUtils::getUserName();
QString subject = tr("Difference");
for (pdf::PDFInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex)
{
const size_t differencesCount = m_diffResult->getDifferencesCount();
const pdf::PDFInteger leftPageIndex = m_mapper->getLeftPageIndex(pageIndex);
const pdf::PDFInteger rightPageIndex = m_mapper->getRightPageIndex(pageIndex);
const pdf::PDFPage* page = document->getCatalog()->getPage(pageIndex);
pdf::PDFObjectReference reference = page->getPageReference();
if (leftPageIndex != -1)
{
for (size_t i = 0; i < differencesCount; ++i)
{
auto leftRectangles = m_diffResult->getLeftRectangles(i);
for (auto it = leftRectangles.first; it != leftRectangles.second; ++it)
{
const auto& item = *it;
if (item.first == leftPageIndex)
{
QColor color = getColorForIndex(i);
const QRectF& rect = item.second;
QPolygonF polygon;
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft();
pdf::PDFObjectReference annotation = builder->createAnnotationPolygon(reference, polygon, 1.0, color, color, title, subject, m_diffResult->getMessage(i));
builder->setAnnotationOpacity(annotation, 0.3);
builder->updateAnnotationAppearanceStreams(annotation);
}
}
}
}
if (rightPageIndex != -1)
{
for (size_t i = 0; i < differencesCount; ++i)
{
auto rightRectangles = m_diffResult->getRightRectangles(i);
for (auto it = rightRectangles.first; it != rightRectangles.second; ++it)
{
const auto& item = *it;
if (item.first == rightPageIndex)
{
QColor color = getColorForIndex(i);
const QRectF& rect = item.second;
QPolygonF polygon;
polygon << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft();
pdf::PDFObjectReference annotation = builder->createAnnotationPolygon(reference, polygon, 1.0, color, color, title, subject, m_diffResult->getMessage(i));
builder->setAnnotationOpacity(annotation, 0.3);
builder->updateAnnotationAppearanceStreams(annotation);
}
}
}
}
}
}
void DifferencesDrawInterface::drawRectangle(QPainter* painter,
const QTransform& pagePointToDevicePointMatrix,
const QRectF& rect,
QColor color) const
{
color.setAlphaF(0.5);
QRectF resultRect = pagePointToDevicePointMatrix.mapRect(rect);
painter->fillRect(resultRect, color);
}
void DifferencesDrawInterface::drawMarker(QPainter* painter,
const QTransform& pagePointToDevicePointMatrix,
const QRectF& rect,
QColor color,
bool isLeft) const
{
if (!m_settings->displayMarkers)
{
return;
}
pdf::PDFPainterStateGuard guard(painter);
QRectF deviceRect = pagePointToDevicePointMatrix.mapRect(rect);
QPointF snapPoint;
QPointF markPoint;
if (isLeft)
{
snapPoint.ry() = deviceRect.center().y();
snapPoint.rx() = deviceRect.left();
markPoint = snapPoint;
markPoint.rx() = 0;
}
else
{
snapPoint.ry() = deviceRect.center().y();
snapPoint.rx() = deviceRect.right();
markPoint = snapPoint;
markPoint.rx() = painter->device()->width();
}
const qreal lineWidthF = pdf::PDFWidgetUtils::scaleDPI_y(painter->device(), 2);
QPen pen(Qt::DotLine);
pen.setColor(color);
pen.setWidthF(lineWidthF);
painter->setBrush(Qt::NoBrush);
painter->setPen(pen);
painter->drawLine(snapPoint, markPoint);
const qreal markSizeX = pdf::PDFWidgetUtils::scaleDPI_x(painter->device(), 10);
const qreal markSizeY = pdf::PDFWidgetUtils::scaleDPI_y(painter->device(), 10);
QPointF ptc = markPoint;
QPointF ptTop = ptc;
QPointF ptBottom = ptc;
ptTop.ry() -= markSizeY;
ptBottom.ry() += markSizeY;
ptc.rx() += isLeft ? markSizeX : -markSizeX;
std::array points = { ptTop, ptc, ptBottom };
painter->setPen(Qt::NoPen);
painter->setBrush(QBrush(color));
painter->drawConvexPolygon(points.data(), int(points.size()));
}
} // namespace pdfdiff