sw/qa/core/layout/data/floattable-table-join.docx |binary
 sw/qa/core/layout/tabfrm.cxx                      |   60 ++++++++++++++++++++++
 sw/source/core/layout/tabfrm.cxx                  |   14 ++++-
 3 files changed, 73 insertions(+), 1 deletion(-)

New commits:
commit 660b60281d0626a73a4bc1b626a813a2ab1b9ee0
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Nov 22 08:31:17 2023 +0100
Commit:     Christian Lohmaier <lohmaier+libreoff...@googlemail.com>
CommitDate: Tue Mar 19 18:04:28 2024 +0100

    tdf#158344: sw floattable: fix crash by trying harder to split tables
    
    Regression from commit 60e2fdf1d7e8346e5a3835369c47e582c737ce20 (sw
    floattable: maintain the invariant that fly height is at least MINFLY,
    2023-09-28), the bugdoc crashed on load in SwTabFrame::MakeAll(),
    because the tab frame's HasFollowFlowLine() was true, but
    GetFollow()->GetFirstNonHeadlineRow() was nullptr and the invarint is
    that these are always in sync.
    
    Digging deeper, what happens is that the master table has a split row at
    the end, so the follow table has a "follow flow line". We remove that
    when we try to split the master table (split either moves rows to the
    follow or creates a new follow), so the follow table only has a
    "headline row" remaining. Then Split() is called with bTryToSplit set to
    true, this fails (because only a single line would fit the master, but
    orphan/widow control rejects that) and then we join the follow table
    (because it only has headline rows), so a split with bTryToSplit set to
    false (don't split the row itself) never happens. This at the end leads
    to a strange table frame with only headline rows and gets deleted, which
    is odd to happen during the initial layout.
    
    Fix the problem by remembering if we just removed the follow flow line,
    and in case we tried to split the rows itself and table split failed,
    then don't join the follow table, so a next split can be invoked with
    bTryToSplit set to false, which leads to the correct layout. This means
    not only the crash is fixed, but also no layout loop happens and result
    matches Word.
    
    Limit this to tables in split flys, at least for this bugdoc the inline
    table case would not have this problem as widow/orphan control is
    disabled inside inline tables.
    
    Change-Id: I172e38be11baf6f73df722a4c6c035a6a283d727
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159802
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins
    (cherry picked from commit 223d2fac61e061478721a7a4a89b1362f5037d8f)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164689
    Tested-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>
    (cherry picked from commit 34fceee624ae1e478af145af37b76444780f8457)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164737
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    Reviewed-by: Michael Weghorn <m.wegh...@posteo.de>
    Tested-by: Christian Lohmaier <lohmaier+libreoff...@googlemail.com>
    Reviewed-by: Christian Lohmaier <lohmaier+libreoff...@googlemail.com>

diff --git a/sw/qa/core/layout/data/floattable-table-join.docx 
b/sw/qa/core/layout/data/floattable-table-join.docx
new file mode 100644
index 000000000000..807b4cfa8c11
Binary files /dev/null and b/sw/qa/core/layout/data/floattable-table-join.docx 
differ
diff --git a/sw/qa/core/layout/tabfrm.cxx b/sw/qa/core/layout/tabfrm.cxx
index 1659cf0df6fd..84f7ba48c572 100644
--- a/sw/qa/core/layout/tabfrm.cxx
+++ b/sw/qa/core/layout/tabfrm.cxx
@@ -13,6 +13,10 @@
 #include <rootfrm.hxx>
 #include <pagefrm.hxx>
 #include <tabfrm.hxx>
+#include <sortedobjs.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrm.hxx>
+#include <flyfrms.hxx>
 
 namespace
 {
@@ -109,6 +113,62 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNestedRowSpan)
     // Then make sure the resulting page count matches Word:
     CPPUNIT_ASSERT_EQUAL(6, getPages());
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSplitFlyTableJoin)
+{
+    // Given a document with a multi-page floating table:
+    // When loading this document:
+    createSwDoc("floattable-table-join.docx");
+
+    // Then make sure this document doesn't crash the layout and has a 
floating table split on 4
+    // pages:
+    SwDoc* pDoc = getSwDoc();
+    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
+    auto pPage1 = pLayout->Lower()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage1);
+    CPPUNIT_ASSERT(pPage1->GetSortedObjs());
+    {
+        SwSortedObjs& rPageObjs = *pPage1->GetSortedObjs();
+        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
+        auto pFly = 
rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+        CPPUNIT_ASSERT(pFly);
+        // Start of the chain.
+        CPPUNIT_ASSERT(!pFly->GetPrecede());
+        CPPUNIT_ASSERT(pFly->HasFollow());
+    }
+    auto pPage2 = pPage1->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage2);
+    CPPUNIT_ASSERT(pPage2->GetSortedObjs());
+    {
+        SwSortedObjs& rPageObjs = *pPage2->GetSortedObjs();
+        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
+        auto pFly = 
rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+        CPPUNIT_ASSERT(pFly);
+        CPPUNIT_ASSERT(pFly->GetPrecede());
+        CPPUNIT_ASSERT(pFly->HasFollow());
+    }
+    auto pPage3 = pPage2->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage3);
+    CPPUNIT_ASSERT(pPage3->GetSortedObjs());
+    {
+        SwSortedObjs& rPageObjs = *pPage3->GetSortedObjs();
+        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
+        auto pFly = 
rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+        CPPUNIT_ASSERT(pFly);
+        CPPUNIT_ASSERT(pFly->GetPrecede());
+        CPPUNIT_ASSERT(pFly->HasFollow());
+    }
+    auto pPage4 = pPage3->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage4);
+    CPPUNIT_ASSERT(pPage4->GetSortedObjs());
+    SwSortedObjs& rPageObjs = *pPage4->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
+    auto pFly = rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+    CPPUNIT_ASSERT(pFly);
+    // End of the chain.
+    CPPUNIT_ASSERT(pFly->GetPrecede());
+    CPPUNIT_ASSERT(!pFly->HasFollow());
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx
index f730d85f7db5..c85f86672013 100644
--- a/sw/source/core/layout/tabfrm.cxx
+++ b/sw/source/core/layout/tabfrm.cxx
@@ -2739,6 +2739,7 @@ void SwTabFrame::MakeAll(vcl::RenderContext* 
pRenderContext)
                         aRectFnSet.GetTopMargin(*this) +
                          lcl_GetHeightOfRows( GetLower(), nMinNumOfLines ) );
 
+                bool bHadFollowFlowLineBeforeSplit = false;
                 // Some more checks if we want to call the split algorithm or 
not:
                 // The repeating lines / keeping lines still fit into the 
upper or
                 // if we do not have an (in)direct Prev, we split anyway.
@@ -2754,6 +2755,7 @@ void SwTabFrame::MakeAll(vcl::RenderContext* 
pRenderContext)
                         if (!nThrowAwayValidLayoutLimit)
                             continue;
                         const bool 
bInitialLoopEndCondition(isFrameAreaDefinitionValid());
+                        bHadFollowFlowLineBeforeSplit = true;
                         RemoveFollowFlowLine();
                         const bool 
bFinalLoopEndCondition(isFrameAreaDefinitionValid());
 
@@ -2790,7 +2792,15 @@ void SwTabFrame::MakeAll(vcl::RenderContext* 
pRenderContext)
                     // If splitting the table was successful or not,
                     // we do not want to have 'empty' follow tables.
                     if ( GetFollow() && !GetFollow()->GetFirstNonHeadlineRow() 
)
-                        Join();
+                    {
+                        // For split flys, if we just removed the follow flow 
line before split,
+                        // then avoid the join in the error + rowsplit case, 
so split can be called
+                        // again, this time without a rowsplit.
+                        if (!bFlySplit || !bHadFollowFlowLineBeforeSplit || 
!bSplitError || !bTryToSplit)
+                        {
+                            Join();
+                        }
+                    }
 
                     // We want to restore the situation before the failed
                     // split operation as good as possible. Therefore we
@@ -2808,6 +2818,8 @@ void SwTabFrame::MakeAll(vcl::RenderContext* 
pRenderContext)
                         continue;
                     }
 
+                    // If split failed, then next time try without
+                    // allowing to split the table rows.
                     bTryToSplit = !bSplitError;
 
                     //To avoid oscillations the Follow must become valid now

Reply via email to