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() )

Reply via email to