sw/qa/core/layout/data/floattable-nested-rowspan.docx |binary sw/qa/core/layout/tabfrm.cxx | 11 +++++ sw/source/core/layout/tabfrm.cxx | 37 ++++++++++++++++++ 3 files changed, 48 insertions(+)
New commits: commit 3be73099197aa8892ad34f267e3908734464dddf Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Oct 20 08:57:08 2023 +0200 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Tue Oct 24 16:41:40 2023 +0200 tdf#157590 sw floattable: avoid hang in the nested + row span case Regression from commit 905962db870e9d1cf1dcf3bd1be44c347cddafe1 (sw floattable: handle AllowOverlap==false in the layout, 2023-08-10), the document load resulted in a hang due to a layout loop. What happens is that SwTabFrame::MakeAll() first does a Split(), but the problematic row has cells with rowspans, and once this is combined with multi-page nested floating tables, we move all the content to the next page (we only leave a stub table frame on the old page), so the next time SwTabFrame::MakeAll() is called, we do a Join(), and this leads to a loop. The traditional Writer way here would be to add a loop control, but we can do a little bit better: nobody really asked for row span handling with nested floating tables, so just don't split rows with row span in this case, move the entire row forward instead. This is enough to avoid the layout loop, and a next iteration can still use SwFlowFrame::MoveBwd() / SwFlowFrame::MoveFwd() to split the complex row. The bug is fairly hard to hit, any naive simplification to the original bugdoc leads to a working layout. Carefully keeping the size of the document, it's possible to at least simplify the content of the table cells (while keeping their size unchanged), so we avoid half of the tables and half of the shapes for a faster test case. (cherry picked from commit 8e03dfd6a4bff4eabf779ace9b758b49cf80f8ba) Change-Id: Ib14154200c45ecb7f59e85f9f4f1fe0124c4256e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158379 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/sw/qa/core/layout/data/floattable-nested-rowspan.docx b/sw/qa/core/layout/data/floattable-nested-rowspan.docx new file mode 100644 index 000000000000..48cbdbfe59c3 Binary files /dev/null and b/sw/qa/core/layout/data/floattable-nested-rowspan.docx differ diff --git a/sw/qa/core/layout/tabfrm.cxx b/sw/qa/core/layout/tabfrm.cxx index 64a30e8368db..44ee7c2a6966 100644 --- a/sw/qa/core/layout/tabfrm.cxx +++ b/sw/qa/core/layout/tabfrm.cxx @@ -82,6 +82,17 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInInlineTable) CPPUNIT_ASSERT(!pTab->GetFollow()); } } + +CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNestedRowSpan) +{ + // Given a document with nested floating tables and a row with rowspan cells at page boundary: + // When loading that document: + // Without the accompanying fix in place, this test would have resulted in a layout loop. + createSwDoc("floattable-nested-rowspan.docx"); + + // Then make sure the resulting page count matches Word: + CPPUNIT_ASSERT_EQUAL(6, getPages()); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index 344039b1bffe..b97536d015c6 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -2608,6 +2608,43 @@ void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) // See if this is a split fly that can also grow. auto pUpperFly = static_cast<SwFlyFrame*>(GetUpper()); bFlySplit = pUpperFly->IsFlySplitAllowed(); + + if (bFlySplit) + { + // See if this is a nested split fly where the inner table also has + // rowspans. + SwTextFrame* pAnchorCharFrame = pUpperFly->FindAnchorCharFrame(); + if (pAnchorCharFrame && pAnchorCharFrame->IsInFly()) + { + // Find the row we'll split. + SwTwips nRemaining + = aRectFnSet.YDiff(nDeadLine, aRectFnSet.GetTop(getFrameArea())); + nRemaining -= aRectFnSet.GetTopMargin(*this); + const SwFrame* pRow = Lower(); + for (; pRow->GetNext(); pRow = pRow->GetNext()) + { + if (nRemaining < aRectFnSet.GetHeight(pRow->getFrameArea())) + { + break; + } + + nRemaining -= aRectFnSet.GetHeight(pRow->getFrameArea()); + } + // See if any cells have rowspans. + for (const SwFrame* pLower = pRow->GetLower(); pLower; + pLower = pLower->GetNext()) + { + auto pCellFrame = static_cast<const SwCellFrame*>(pLower); + if (pCellFrame->GetTabBox()->getRowSpan() != 1) + { + // The cell has a rowspan, don't split the row itself in this + // case (but just move it forward, i.e. split between the rows). + bTryToSplit = false; + break; + } + } + } + } } if( IsInSct() || GetUpper()->IsInTab() || bFlySplit ) nDeadLine = aRectFnSet.YInc( nDeadLine,