sw/qa/extras/layout/data/tdf170620.docx |binary sw/qa/extras/layout/data/tdf170630.docx |binary sw/qa/extras/layout/layout6.cxx | 95 ++++++++++++++++++++++++++++++++ sw/source/core/layout/flowfrm.cxx | 19 +++++- sw/source/core/layout/tabfrm.cxx | 17 ++++- 5 files changed, 125 insertions(+), 6 deletions(-)
New commits: commit 2262d188e47727b0283903788800d52b8bef4eaa Author: Mike Kaganski <[email protected]> AuthorDate: Fri Feb 6 11:08:35 2026 +0500 Commit: Xisco Fauli <[email protected]> CommitDate: Tue Feb 10 14:41:30 2026 +0100 tdf#170630: consider not-yet-moved flys in cell height as "too high" As the bugdoc demonstrated, it is not enough to split too high inner floating table: after the split, the follow anchor and follow fly may not cause the outer table to require an own split. This change introduces a shortcut in CalcHeightWithFlys_Impl (used to calculate cell heights considering anchored objects): if a lower is a follow anchor with a follow fly that hasn't yet moved, do not continue calculating accurate cell height, just return a huge height of this lower. This means that cells with such not-yet-moved anchors will never fit, and will always cause their table to be split. After that, the heights will be calculated accurately. Change-Id: Id81e69c08cfa58ffddfa771fa768f78735433d91 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198811 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198952 diff --git a/sw/qa/extras/layout/data/tdf170630.docx b/sw/qa/extras/layout/data/tdf170630.docx new file mode 100644 index 000000000000..99a8a8eb961d Binary files /dev/null and b/sw/qa/extras/layout/data/tdf170630.docx differ diff --git a/sw/qa/extras/layout/layout6.cxx b/sw/qa/extras/layout/layout6.cxx index 9e7886f7768f..8a2a23af6634 100644 --- a/sw/qa/extras/layout/layout6.cxx +++ b/sw/qa/extras/layout/layout6.cxx @@ -2116,6 +2116,34 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170620_float_table_after_keep_with_ assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[2]/anchored/fly", 2); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170630) +{ + // Given a document with a keep-with-next paragraph, followed by floating table containing + // two keep-with-next paragraphs and another floating table which is split across pages: + createSwDoc("tdf170630.docx"); + + // The keep-with-next paragraph and the floating table must start on page 1: + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT(pXmlDoc); + + // Exactly two pages (without the fix, there was only one): + assertXPath(pXmlDoc, "//page", 2); + + // "Keep-with-next paragraph" is on the first page: + assertXPath(pXmlDoc, "//page[1]/body/txt[2]//SwLineLayout", "portion", + u"Keep-with-next paragraph"); + + // Exactly two objects anchored at each page (without the fix, the first page had three: one + // outer, and two inner, where the follow frame never moved forward): + assertXPath(pXmlDoc, "//page[1]/sorted_objs/fly", 2); + assertXPath(pXmlDoc, "//page[2]/sorted_objs/fly", 2); + + // Page 1's paragraph 3 has two anchored flys (without the fix, it was one: the outer table + // never split): + assertXPath(pXmlDoc, "//page[1]/body/txt[3]/anchored/fly", 2); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index 69b37c37a47d..ea41e8e5f62b 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -4868,10 +4868,21 @@ static tools::Long CalcHeightWithFlys_Impl(const SwFrame* pTmp, const SwFrame* p { // #i26945# - if <pTmp> is follow, the // anchor character frame has to be <pTmp>. - if ( bIsFollow && - pAnchoredObj->FindAnchorCharFrame() != pTmp ) + if (bIsFollow) { - continue; + if (pAnchoredObj->FindAnchorCharFrame() != pTmp) + continue; + + if (SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame()) + { + if (pFly->IsSplitButNotYetMovedFollow()) + { + // A follow split fly, that is yet to move forward: upper's height is not + // correct yet - just return a huge value to signal "too large". This will + // eventually force split / move to the next page. + return SAL_MAX_INT32 / 2; // ~19 km + } + } } // #i26945# - consider also drawing objects { commit fd1b14b59d190fa663da947df24b8c4b7bc29dd4 Author: Mike Kaganski <[email protected]> AuthorDate: Thu Feb 5 18:58:53 2026 +0500 Commit: Xisco Fauli <[email protected]> CommitDate: Tue Feb 10 14:41:21 2026 +0100 tdf#170620: CheckMoveFwd: only ignore empty master without split fly SwFlowFrame::CheckMoveFwd decides if a keep-with-next paragraph should move to next page. When bKeep is true, and the keep-with-next frame is followed by an empty master, it ignores that empty master, and may decide to move. To check if the frame returned by GetIndNext was empty master, a call to IsEmptyMaster was used. But that function doesn't consider if the empty master (having a follow, but not having any text) is an anchor of a split fly. The bugdoc demonstrated, that in such case, keep-with- next frame would ignore the following split fly on the current page, and will move forward; that would naturally move the split fly forward, too. This change improves the check to also use HasNonLastSplitFlyDrawObj. Change-Id: I08771d7a3e48f88f4272ae8f1718615bc89a5bf4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198757 Reviewed-by: Mike Kaganski <[email protected]> Tested-by: Jenkins Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198951 diff --git a/sw/qa/extras/layout/data/tdf170620.docx b/sw/qa/extras/layout/data/tdf170620.docx new file mode 100644 index 000000000000..2565a36b4391 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf170620.docx differ diff --git a/sw/qa/extras/layout/layout6.cxx b/sw/qa/extras/layout/layout6.cxx index 88fd08d5113e..9e7886f7768f 100644 --- a/sw/qa/extras/layout/layout6.cxx +++ b/sw/qa/extras/layout/layout6.cxx @@ -2049,6 +2049,73 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170477) calcLayout(); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170620_float_table_after_keep_with_next_para) +{ + // Given a document with a keep-with-next paragraph, followed by floating table containing + // another floating table which is split across pages: + createSwDoc("tdf170620.docx"); + + // The keep-with-next paragraph and the floating table must start on page 1: + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT(pXmlDoc); + + // Exactly two pages: + assertXPath(pXmlDoc, "//page", 2); + + // "Keep-with-next paragraph" is on the first page: + assertXPath(pXmlDoc, "//page[1]/body/txt[2]//SwLineLayout", "portion", + u"Keep-with-next paragraph"); + + // Exactly two objects anchored at each page: + assertXPath(pXmlDoc, "//page[1]/sorted_objs/fly", 2); + assertXPath(pXmlDoc, "//page[2]/sorted_objs/fly", 2); + + // Get master/follow paragraph isd: + assertXPath(pXmlDoc, "//page[1]/body/txt", 3); + assertXPath(pXmlDoc, "//page[2]/body/txt", 1); + assertXPath(pXmlDoc, "//page[1]/body/txt[3]", "follow", + getXPath(pXmlDoc, "//page[2]/body/txt", "id")); + assertXPath(pXmlDoc, "//page[2]/body/txt", "precede", + getXPath(pXmlDoc, "//page[1]/body/txt[3]", "id")); + + // Page 1's paragraph 3 has two anchored flys: + assertXPath(pXmlDoc, "//page[1]/body/txt[3]/anchored/fly", 2); + + // Get the ids of the two outer flys. + // Page 1: + OString f1 = getXPath(pXmlDoc, "//page[1]/sorted_objs/fly[1]", "ptr").toUtf8(); + OString f2 = getXPath(pXmlDoc, "//page[1]/sorted_objs/fly[2]", "ptr").toUtf8(); + CPPUNIT_ASSERT(f1 != f2); + OString filter1 = "@ptr='" + f1 + "' or @ptr='" + f2 + "'"; + OUString id = getXPath(pXmlDoc, "//page[1]/body/txt[3]/anchored/fly[" + filter1 + "]", "id"); + OString aP1OuterFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // Page 2: + f1 = getXPath(pXmlDoc, "//page[2]/sorted_objs/fly[1]", "ptr").toUtf8(); + f2 = getXPath(pXmlDoc, "//page[2]/sorted_objs/fly[2]", "ptr").toUtf8(); + CPPUNIT_ASSERT(f1 != f2); + OString filter2 = "@ptr='" + f1 + "' or @ptr='" + f2 + "'"; + id = getXPath(pXmlDoc, "//page[1]/body/txt[3]/anchored/fly[" + filter2 + "]", "id"); + OString aP2OuterFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // One row in top-level floating table on page 1, four rows on page 2: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row", 1); + assertXPath(pXmlDoc, aP2OuterFlyTab + "/row", 4); + + // One cell in top-level floating table on page 1, four cells on page 2: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell", 1); + assertXPath(pXmlDoc, aP2OuterFlyTab + "/row/cell", 4); + + // First page's top-level floating table's cell has two paragraphs: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt", 2); + // Check text in the first paragraph: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[1]//SwLineLayout", "portion", + u"Something"); + // The second paragraph has two attached inner floating tables: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[2]/anchored/fly", 2); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/layout/flowfrm.cxx b/sw/source/core/layout/flowfrm.cxx index 441da753f810..33f6dc793c04 100644 --- a/sw/source/core/layout/flowfrm.cxx +++ b/sw/source/core/layout/flowfrm.cxx @@ -1995,10 +1995,23 @@ bool SwFlowFrame::CheckMoveFwd( bool& rbMakePage, bool bKeep, bool bIgnoreMyOwnK { if (m_rThis.IsHiddenNow()) return false; - const SwFrame* pNxt = m_rThis.GetIndNext(); + auto isIgnoredIndNext = [](const SwFrame* pNxt) + { + if (!pNxt) + return true; + // We ignore non-null next, when it's empty master, than doesn't host a split fly: + if (pNxt->IsTextFrame()) + { + auto pTextFrame = static_cast<const SwTextFrame*>(pNxt); + if (pTextFrame->IsEmptyMaster() && !pTextFrame->HasNonLastSplitFlyDrawObj()) + return true; + } + return false; + }; - if ( bKeep && //!bMovedBwd && - ( !pNxt || ( pNxt->IsTextFrame() && static_cast<const SwTextFrame*>(pNxt)->IsEmptyMaster() ) ) && + if ( const SwFrame* pNxt; + bKeep && //!bMovedBwd && + isIgnoredIndNext(m_rThis.GetIndNext()) && ( nullptr != (pNxt = m_rThis.FindNext()) ) && IsKeepFwdMoveAllowed(bIgnoreMyOwnKeepValue) ) { if( pNxt->IsSctFrame() )
