Signature plugin: Form layout

This commit is contained in:
Jakub Melka 2022-03-18 19:49:14 +01:00
parent 2082732cb5
commit a5cd109a6e
3 changed files with 159 additions and 1 deletions

View File

@ -20,6 +20,7 @@
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include "pdfwidgetutils.h"
#include "pdfutils.h"
#include <QPainter>
#include <QKeyEvent>
@ -464,7 +465,7 @@ void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event)
{
m_manipulator.deselect(info.hoveredElementIds);
}
else
else if (!m_manipulator.isAllSelected(info.hoveredElementIds))
{
m_manipulator.selectNew(info.hoveredElementIds);
}
@ -1335,6 +1336,11 @@ bool PDFPageContentElementManipulator::isSelected(PDFInteger id) const
return std::find(m_selection.cbegin(), m_selection.cend(), id) != m_selection.cend();
}
bool PDFPageContentElementManipulator::isAllSelected(const std::set<PDFInteger>& elementIds) const
{
return std::all_of(elementIds.cbegin(), elementIds.cend(), [this](PDFInteger id) { return isSelected(id); });
}
void PDFPageContentElementManipulator::reset()
{
cancelManipulation();
@ -1720,7 +1726,150 @@ void PDFPageContentElementManipulator::performOperation(Operation operation)
}
case Operation::LayoutForm:
{
std::vector<std::pair<PDFPageContentElement*, PDFPageContentElement*>> formLayout;
// Divide elements to left/right side
std::vector<PDFPageContentElement*> elementsLeft;
std::vector<PDFPageContentElement*> elementsRight;
for (PDFPageContentElement* element : manipulatedElements)
{
QRectF boundingBox = element->getBoundingBox();
const qreal distanceLeft = boundingBox.left() - representativeRect.left();
const qreal distanceRight = representativeRect.right() - boundingBox.right();
if (distanceLeft < distanceRight)
{
elementsLeft.push_back(element);
}
else
{
elementsRight.push_back(element);
}
}
// Create pairs of left/right elements
while (!elementsLeft.empty())
{
if (!elementsLeft.empty() && !elementsRight.empty())
{
PDFPageContentElement* elementRight = nullptr;
PDFPageContentElement* elementLeft = elementsLeft.back();
QRectF leftBoundingBox = elementLeft->getBoundingBox();
elementsLeft.pop_back();
qreal overlap = 0.0;
// Find matching element on the right
for (PDFPageContentElement* elementRightCurrent : elementsRight)
{
QRectF rightBoundingBox = elementRightCurrent->getBoundingBox();
if (isRectangleVerticallyOverlapped(leftBoundingBox, rightBoundingBox))
{
QRectF unitedRect = leftBoundingBox.united(rightBoundingBox);
qreal currentOverlap = leftBoundingBox.height() + rightBoundingBox.height() - unitedRect.height();
if (currentOverlap > overlap)
{
overlap = currentOverlap;
elementRight = elementRightCurrent;
}
}
}
if (elementRight)
{
auto it = std::find(elementsRight.begin(), elementsRight.end(), elementRight);
elementsRight.erase(it);
}
formLayout.emplace_back(elementLeft, elementRight);
continue;
}
}
for (PDFPageContentElement* rightElement : elementsRight)
{
formLayout.emplace_back(nullptr, rightElement);
}
// Sort elements vertically
auto comparator = [](const auto& left, const auto& right)
{
const PDFPageContentElement* l1 = left.first ? left.first : left.second;
const PDFPageContentElement* l2 = left.second ? left.second : left.first;
const PDFPageContentElement* r1 = right.first ? right.first : right.second;
const PDFPageContentElement* r2 = right.second ? right.second : right.first;
QRectF lbb1 = l1->getBoundingBox();
QRectF lbb2 = l2->getBoundingBox();
QRectF rbb1 = r1->getBoundingBox();
QRectF rbb2 = r2->getBoundingBox();
const qreal ly = (lbb1.center().y() + lbb2.center().y()) * 0.5;
const qreal ry = (rbb1.center().y() + rbb2.center().y()) * 0.5;
return ly > ry;
};
std::stable_sort(formLayout.begin(), formLayout.end(), comparator);
// Calculate width
qreal leftWidth = 0.0;
qreal rightWidth = 0.0;
for (const auto& row : formLayout)
{
PDFPageContentElement* elementLeft = row.first;
PDFPageContentElement* elementRight = row.second;
if (elementLeft)
{
qreal width = elementLeft->getBoundingBox().width();
leftWidth = qMax(leftWidth, width);
}
if (elementRight)
{
qreal width = elementRight->getBoundingBox().width();
rightWidth = qMax(rightWidth, width);
}
}
// Now, perform layout
qreal yTop = representativeRect.bottom();
qreal xLeft = representativeRect.left();
qreal xRight = representativeRect.right() - rightWidth;
for (const auto& row : formLayout)
{
PDFPageContentElement* elementLeft = row.first;
PDFPageContentElement* elementRight = row.second;
qreal yHeight = 0.0;
if (elementLeft)
{
QRectF boundingBoxLeft = elementLeft->getBoundingBox();
QPointF offsetLeft(xLeft - boundingBoxLeft.x(), yTop - boundingBoxLeft.bottom());
elementLeft->performManipulation(PDFPageContentElement::Translate, offsetLeft);
yHeight = qMax(yHeight, boundingBoxLeft.height());
}
if (elementRight)
{
QRectF boundingBoxRight = elementRight->getBoundingBox();
QPointF offsetRight(xRight - boundingBoxRight.x(), yTop - boundingBoxRight.bottom());
elementRight->performManipulation(PDFPageContentElement::Translate, offsetRight);
yHeight = qMax(yHeight, boundingBoxRight.height());
}
yTop -= yHeight;
}
break;
}
case Operation::LayoutGrid:
break;

View File

@ -335,6 +335,10 @@ public:
/// \param id Element id
bool isSelected(PDFInteger id) const;
/// Returns true, if all elements are selected
/// \param ids Element ids
bool isAllSelected(const std::set<PDFInteger>& elementIds) const;
/// Returns true, if selection is empty
bool isSelectionEmpty() const { return m_selection.empty(); }

View File

@ -465,6 +465,11 @@ constexpr bool isRectangleHorizontallyOverlapped(const QRectF& r1, const QRectF&
return isIntervalOverlap(r1.left(), r1.right(), r2.left(), r2.right());
}
constexpr bool isRectangleVerticallyOverlapped(const QRectF& r1, const QRectF& r2)
{
return isIntervalOverlap(r1.top(), r1.bottom(), r2.top(), r2.bottom());
}
inline QColor invertColor(QColor color)
{
qreal r = 0.0;