XFA: layout algorithm refactoring

This commit is contained in:
Jakub Melka
2021-11-24 20:43:55 +01:00
parent 7c1b90948d
commit 187cfc6d4b

View File

@ -9840,6 +9840,8 @@ private:
/// of this node). /// of this node).
QRectF nominalExtent; QRectF nominalExtent;
std::vector<LayoutItem> items; std::vector<LayoutItem> items;
int colSpan = 1;
}; };
struct LayoutParameters struct LayoutParameters
@ -9872,6 +9874,11 @@ private:
/// Layout of the subitems /// Layout of the subitems
std::vector<Layout> layout; std::vector<Layout> layout;
/// Rows for table cells
std::vector<std::vector<Layout>> tableRows;
QString colWidths;
}; };
class LayoutParametersStackGuard class LayoutParametersStackGuard
@ -9920,11 +9927,27 @@ private:
/// Initializes single layout item /// Initializes single layout item
Layout initializeSingleLayout(QRectF nominalExtent); Layout initializeSingleLayout(QRectF nominalExtent);
/// Returns margins computed from the caption
QMarginsF getCaptionMargins(const LayoutParameters& layoutParameters) const;
/// Performs layout (so, using current layout parameters /// Performs layout (so, using current layout parameters
/// and given layouts, it layouts them to the current layout node) /// and given layouts, it layouts them to the current layout node)
/// \param layoutParameters Parameters, which will be lay out /// \param layoutParameters Parameters, which will be lay out
/// \param isTop Are we layouting top item?
void layout(LayoutParameters layoutParameters); void layout(LayoutParameters layoutParameters);
/// Perform flow layout. Does nothing, if layout has positional
/// layout type. Final layout is stored also in the layout parameters.
/// \param layoutParameters Layout parameters
void layoutFlow(LayoutParameters& layoutParameters);
/// Performs positiona layout from source layout to target layout.
/// Target layout must have positional layout type.
/// \param sourceLayoutParameters Source layout
/// \param targetLayoutParameters Target layout
void layoutPositional(LayoutParameters& sourceLayoutParameters,
LayoutParameters& targetLayoutParameters);
LayoutParameters& getLayoutParameters() { return m_layoutParameters.top(); } LayoutParameters& getLayoutParameters() { return m_layoutParameters.top(); }
const LayoutParameters& getLayoutParameters() const { return m_layoutParameters.top(); } const LayoutParameters& getLayoutParameters() const { return m_layoutParameters.top(); }
@ -9944,6 +9967,7 @@ void PDFXFALayoutEngine::visit(const xfa::XFA_area* node)
parameters.xOffset = node->getX().getValuePt(&parameters.paragraphSettings); parameters.xOffset = node->getX().getValuePt(&parameters.paragraphSettings);
parameters.yOffset = node->getY().getValuePt(&parameters.paragraphSettings); parameters.yOffset = node->getY().getValuePt(&parameters.paragraphSettings);
parameters.nodeArea = node; parameters.nodeArea = node;
parameters.columnSpan = node->getColSpan();
xfa::XFA_AbstractNode::acceptOrdered(this, xfa::XFA_AbstractNode::acceptOrdered(this,
node->getArea(), node->getArea(),
@ -9967,26 +9991,8 @@ PDFXFALayoutEngine::Layout PDFXFALayoutEngine::initializeSingleLayout(QRectF nom
return layout; return layout;
} }
void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters) QMarginsF PDFXFALayoutEngine::getCaptionMargins(const LayoutParameters& layoutParameters) const
{ {
LayoutParameters& currentLayoutParameters = getLayoutParameters();
if (layoutParameters.nodeArea)
{
PDFReal x = layoutParameters.xOffset;
PDFReal y = layoutParameters.yOffset;
// Just translate the layout by area offset
for (Layout& layout : layoutParameters.layout)
{
layout.translate(x, y);
}
currentLayoutParameters.layout = std::move(layoutParameters.layout);
return;
}
std::map<size_t, std::vector<Layout>> layoutsPerPage;
// Do we have visible caption? // Do we have visible caption?
QMarginsF captionMargins(0.0, 0.0, 0.0, 0.0); QMarginsF captionMargins(0.0, 0.0, 0.0, 0.0);
if (layoutParameters.nodeCaption && if (layoutParameters.nodeCaption &&
@ -10014,10 +10020,61 @@ void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters)
} }
} }
return captionMargins;
}
void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters)
{
// Current layout parameters are layout parameters, to which we are performing
// layout - new layout nodes will emerge in current layout. Layout parameters
// as parameter of this function comes from node, which layout is finalizing.
// We have several issues:
//
// 1) We must finish layout of the finalized node, if it is
// not a positional layout (positional layout is performed
// differently)
//
// 2) We must check, if we are performing layouting of the "area" node,
// which just translates the nodes by given offset.
//
// 3) Finally, we move layout from child parameters to parent layout parameters,
// and in case of positional layout, we move them to the right position.
// Case 1)
LayoutParameters& currentLayoutParameters = getLayoutParameters();
layoutFlow(layoutParameters);
if (layoutParameters.nodeArea)
{
// Case 2)
const PDFReal x = layoutParameters.xOffset;
const PDFReal y = layoutParameters.yOffset;
// Just translate the layout by area offset
for (Layout& layout : layoutParameters.layout)
{
layout.translate(x, y);
}
currentLayoutParameters.layout = std::move(layoutParameters.layout);
}
else
{
// Case 3)
layoutPositional(layoutParameters, currentLayoutParameters);
}
}
void PDFXFALayoutEngine::layoutFlow(LayoutParameters& layoutParameters)
{
std::map<size_t, std::vector<Layout>> layoutsPerPage;
for (Layout& layout : layoutParameters.layout) for (Layout& layout : layoutParameters.layout)
{ {
layoutsPerPage[layout.pageIndex].emplace_back(std::move(layout)); layoutsPerPage[layout.pageIndex].emplace_back(std::move(layout));
} }
layoutParameters.layout.clear();
QMarginsF captionMargins = getCaptionMargins(layoutParameters);
for (auto& item : layoutsPerPage) for (auto& item : layoutsPerPage)
{ {
@ -10026,96 +10083,59 @@ void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters)
for (Layout& layout : layouts) for (Layout& layout : layouts)
{ {
layout.updatePresence(currentLayoutParameters.presence); layout.updatePresence(layoutParameters.presence);
} }
switch (currentLayoutParameters.layoutType) switch (layoutParameters.layoutType)
{ {
case xfa::XFA_BaseNode::LAYOUT::Position: case xfa::XFA_BaseNode::LAYOUT::Position:
{ // Do nothing - positional layout is done elsewhere
Layout finalLayout;
finalLayout.pageIndex = pageIndex;
PDFReal x = layoutParameters.xOffset + currentLayoutParameters.margins.left() + captionMargins.left();
PDFReal y = layoutParameters.yOffset + currentLayoutParameters.margins.top() + captionMargins.top();
for (Layout& layout : layouts)
{
// Jakub Melka: we must add offset from anchor type
switch (layoutParameters.anchorType)
{
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopLeft:
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomCenter:
x -= layout.nominalExtent.width() * 0.5;
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomLeft:
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomRight:
x -= layout.nominalExtent.width();
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleCenter:
x -= layout.nominalExtent.width() * 0.5;
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleLeft:
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleRight:
x -= layout.nominalExtent.width();
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopCenter:
x -= layout.nominalExtent.width() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopRight:
x -= layout.nominalExtent.width();
break;
default:
Q_ASSERT(false);
break;
}
layout.translate(x, y);
finalLayout.items.insert(finalLayout.items.end(), layout.items.begin(), layout.items.end());
}
QSizeF nominalExtentSize = currentLayoutParameters.sizeInfo.effSize;
finalLayout.nominalExtent = QRectF(QPointF(0, 0), nominalExtentSize);
if (!finalLayout.items.empty())
{
currentLayoutParameters.layout.emplace_back(std::move(finalLayout));
}
break; break;
}
case xfa::XFA_BaseNode::LAYOUT::Lr_tb: case xfa::XFA_BaseNode::LAYOUT::Lr_tb:
break; break;
case xfa::XFA_BaseNode::LAYOUT::Rl_row: case xfa::XFA_BaseNode::LAYOUT::Rl_row:
{
std::reverse(layouts.begin(), layouts.end());
layoutParameters.tableRows.emplace_back(std::move(layouts));
break; break;
}
case xfa::XFA_BaseNode::LAYOUT::Rl_tb: case xfa::XFA_BaseNode::LAYOUT::Rl_tb:
break; break;
case xfa::XFA_BaseNode::LAYOUT::Row: case xfa::XFA_BaseNode::LAYOUT::Row:
{
layoutParameters.tableRows.emplace_back(std::move(layouts));
break; break;
}
case xfa::XFA_BaseNode::LAYOUT::Table: case xfa::XFA_BaseNode::LAYOUT::Table:
{
std::vector<PDFReal> rowHeights(layoutParameters.tableRows.size(), 0.0);
size_t maxColumns = 0;
for (size_t rowIndex = 0; rowIndex < layoutParameters.tableRows.size(); ++rowIndex)
{
size_t columns = 0;
const std::vector<Layout>& row = layoutParameters.tableRows[rowIndex];
for (size_t columnIndex = 0; columnIndex < row.size(); ++columnIndex)
{
const PDFReal cellHeight = row[columnIndex].nominalExtent.height();
if (rowHeights[rowIndex] < cellHeight)
{
rowHeights[rowIndex] = cellHeight;
}
if (row[columnIndex].colSpan == 1)
{
++columns;
}
}
maxColumns = qMax(maxColumns, columns);
}
break; break;
}
case xfa::XFA_BaseNode::LAYOUT::Tb: case xfa::XFA_BaseNode::LAYOUT::Tb:
{ {
@ -10136,18 +10156,19 @@ void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters)
} }
// Translate by margin // Translate by margin
finalLayout.translate(currentLayoutParameters.margins.left(), currentLayoutParameters.margins.top()); finalLayout.translate(layoutParameters.margins.left(), layoutParameters.margins.top());
QSizeF nominalContentSize(maxW, y); QSizeF nominalContentSize(maxW, y);
QSizeF nominalExtentSizeWithoutCaption = nominalContentSize.grownBy(currentLayoutParameters.margins); QSizeF nominalExtentSizeWithoutCaption = nominalContentSize.grownBy(layoutParameters.margins);
QSizeF nominalExtentSize = nominalExtentSizeWithoutCaption.grownBy(captionMargins); QSizeF nominalExtentSize = nominalExtentSizeWithoutCaption.grownBy(captionMargins);
nominalExtentSize = currentLayoutParameters.sizeInfo.adjustNominalExtentSize(nominalExtentSize); nominalExtentSize = layoutParameters.sizeInfo.adjustNominalExtentSize(nominalExtentSize);
QRectF nominalExtentRegion(QPointF(0, 0), nominalExtentSize); QRectF nominalExtentRegion(QPointF(0, 0), nominalExtentSize);
finalLayout.nominalExtent = nominalExtentRegion; finalLayout.nominalExtent = nominalExtentRegion;
finalLayout.colSpan = layoutParameters.columnSpan;
if (!finalLayout.items.empty()) if (!finalLayout.items.empty())
{ {
currentLayoutParameters.layout.emplace_back(std::move(finalLayout)); layoutParameters.layout.emplace_back(std::move(finalLayout));
} }
break; break;
} }
@ -10159,6 +10180,104 @@ void PDFXFALayoutEngine::layout(LayoutParameters layoutParameters)
} }
} }
void PDFXFALayoutEngine::layoutPositional(LayoutParameters& sourceLayoutParameters,
LayoutParameters& targetLayoutParameters)
{
if (targetLayoutParameters.layoutType != xfa::XFA_BaseNode::LAYOUT::Position)
{
// Not a positional layout
return;
}
std::map<size_t, std::vector<Layout>> layoutsPerPage;
for (Layout& layout : sourceLayoutParameters.layout)
{
layoutsPerPage[layout.pageIndex].emplace_back(std::move(layout));
}
QMarginsF captionMargins = getCaptionMargins(targetLayoutParameters);
for (auto& item : layoutsPerPage)
{
const size_t pageIndex = item.first;
std::vector<Layout> layouts = std::move(item.second);
for (Layout& layout : layouts)
{
layout.updatePresence(targetLayoutParameters.presence);
}
Layout finalLayout;
finalLayout.pageIndex = pageIndex;
PDFReal x = sourceLayoutParameters.xOffset + targetLayoutParameters.margins.left() + captionMargins.left();
PDFReal y = sourceLayoutParameters.yOffset + targetLayoutParameters.margins.top() + captionMargins.top();
for (Layout& layout : layouts)
{
// Jakub Melka: we must add offset from anchor type
switch (sourceLayoutParameters.anchorType)
{
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopLeft:
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomCenter:
x -= layout.nominalExtent.width() * 0.5;
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomLeft:
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::BottomRight:
x -= layout.nominalExtent.width();
y -= layout.nominalExtent.height();
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleCenter:
x -= layout.nominalExtent.width() * 0.5;
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleLeft:
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::MiddleRight:
x -= layout.nominalExtent.width();
y -= layout.nominalExtent.height() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopCenter:
x -= layout.nominalExtent.width() * 0.5;
break;
case pdf::xfa::XFA_BaseNode::ANCHORTYPE::TopRight:
x -= layout.nominalExtent.width();
break;
default:
Q_ASSERT(false);
break;
}
layout.translate(x, y);
finalLayout.items.insert(finalLayout.items.end(), layout.items.begin(), layout.items.end());
}
QSizeF nominalExtentSize = targetLayoutParameters.sizeInfo.effSize;
finalLayout.nominalExtent = QRectF(QPointF(0, 0), nominalExtentSize);
finalLayout.colSpan = targetLayoutParameters.columnSpan;
if (!finalLayout.items.empty())
{
targetLayoutParameters.layout.emplace_back(std::move(finalLayout));
}
}
}
void PDFXFALayoutEngine::visit(const xfa::XFA_draw* node) void PDFXFALayoutEngine::visit(const xfa::XFA_draw* node)
{ {
switch (node->getPresence()) switch (node->getPresence())
@ -10193,6 +10312,7 @@ void PDFXFALayoutEngine::visit(const xfa::XFA_draw* node)
parameters.presence = node->getPresence(); parameters.presence = node->getPresence();
parameters.sizeInfo = sizeInfo; parameters.sizeInfo = sizeInfo;
parameters.nodeCaption = node->getCaption(); parameters.nodeCaption = node->getCaption();
parameters.columnSpan = node->getColSpan();
handlePara(node->getPara()); handlePara(node->getPara());
handleFont(node->getFont()); handleFont(node->getFont());
@ -10440,6 +10560,8 @@ void PDFXFALayoutEngine::visit(const xfa::XFA_subform* node)
parameters.anchorType = node->getAnchorType(); parameters.anchorType = node->getAnchorType();
parameters.presence = node->getPresence(); parameters.presence = node->getPresence();
parameters.sizeInfo = sizeInfo; parameters.sizeInfo = sizeInfo;
parameters.layoutType = node->getLayout();
parameters.colWidths = node->getColumnWidths();
// Handle break before // Handle break before
handleBreak(node->getBreak(), true); handleBreak(node->getBreak(), true);
@ -11005,6 +11127,7 @@ void PDFXFAEngineImpl::draw(const QMatrix& pagePointToDevicePointMatrix,
painter->setWorldMatrix(pagePointToDevicePointMatrix, true); painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
painter->translate(0, page->getMediaBox().height()); painter->translate(0, page->getMediaBox().height());
painter->scale(1.0, -1.0); painter->scale(1.0, -1.0);
painter->fillRect(page->getMediaBox(), Qt::white);
const LayoutItems& items = it->second; const LayoutItems& items = it->second;
for (const LayoutItem& item : items) for (const LayoutItem& item : items)