sw/CppunitTest_sw_core_layout_flycnt.mk |   82 +++++++++++++++++++++++++++++++
 sw/Module_sw.mk                         |    1 
 sw/inc/formatflysplit.hxx               |    4 +
 sw/qa/core/layout/data/floattable.docx  |binary
 sw/qa/core/layout/flycnt.cxx            |   83 ++++++++++++++++++++++++++++++++
 sw/source/core/attr/formatflysplit.cxx  |   28 ++++++++++
 sw/source/core/inc/flyfrms.hxx          |    7 +-
 sw/source/core/inc/pagefrm.hxx          |    3 -
 sw/source/core/inc/tabfrm.hxx           |    4 +
 sw/source/core/inc/txtfrm.hxx           |    8 ++-
 sw/source/core/layout/flycnt.cxx        |   46 +++++++++++++++--
 sw/source/core/text/frmform.cxx         |   20 +++++--
 sw/source/core/text/frmpaint.cxx        |    3 -
 sw/source/core/text/itratr.cxx          |   17 +++++-
 sw/source/core/text/itrform2.cxx        |   12 ----
 sw/source/core/text/porrst.cxx          |    4 +
 sw/source/core/text/xmldump.cxx         |   30 +++++++++++
 17 files changed, 319 insertions(+), 33 deletions(-)

New commits:
commit ad4ea1df8260bf673f8717bb9066ec122abbb06c
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Feb 13 12:55:32 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Mar 1 08:48:19 2023 +0100

    sw floattable: handle table-in-fly in SwFrame::GetNextFlyLeaf()
    
    Trying to lay out a split fly that contains a table resulted in a stack
    overflow.
    
    The reason for this was that SwObjectFormatter::FormatObjsAtFrame_() has
    a loop that will format all objects of the current fly frame, but we
    managed to anchor the follow fly into itself.
    
    Fix the problem by improving SwFrame::GetNextFlyLeaf(), somewhat based
    on how SwFrame::GetNextSctLeaf() has special cases for tables. This way
    once we split the fly frame, we'll try to move the follow anchor frame
    to the next page's body frame, and not to a child of the follow fly
    itself.
    
    Also add a first floattable testcase, we can already assert that the
    table is split correctly. Do this in a separate suite for now, since the
    pool's default SwFormatFlySplit is only created once, so
    SwFormatFlySplit::SetForce(false) doesn't have the wanted effect. The
    anchor's text is still on both pages, should be on page 2 only.
    
    (cherry picked from commit 995198bfff4ae8abaf2129fe99d9f8ef899a4f25)
    
    Change-Id: Ie2ce75dbace5d9716008351aedb6a8989036badb

diff --git a/sw/CppunitTest_sw_core_layout_flycnt.mk 
b/sw/CppunitTest_sw_core_layout_flycnt.mk
new file mode 100644
index 000000000000..c9668afbc9b8
--- /dev/null
+++ b/sw/CppunitTest_sw_core_layout_flycnt.mk
@@ -0,0 +1,82 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,sw_core_layout_flycnt))
+
+$(eval $(call 
gb_CppunitTest_use_common_precompiled_header,sw_core_layout_flycnt))
+
+# TODO merge this with sw_core_layout once SwFormatFlySplit::SetForce() is 
gone.
+$(eval $(call gb_CppunitTest_add_exception_objects,sw_core_layout_flycnt, \
+    sw/qa/core/layout/flycnt \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sw_core_layout_flycnt, \
+    editeng \
+    comphelper \
+    cppu \
+    cppuhelper \
+    sal \
+    sfx \
+    subsequenttest \
+    sw \
+       swqahelper \
+    test \
+    unotest \
+    utl \
+    vcl \
+    svt \
+    tl \
+    svl \
+    svxcore \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sw_core_layout_flycnt,\
+    boost_headers \
+    libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sw_core_layout_flycnt,\
+    -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/source/core/inc \
+    -I$(SRCDIR)/sw/source/uibase/inc \
+    -I$(SRCDIR)/sw/qa/inc \
+    $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_api,sw_core_layout_flycnt,\
+       udkapi \
+       offapi \
+       oovbaapi \
+))
+
+$(eval $(call gb_CppunitTest_use_ure,sw_core_layout_flycnt))
+$(eval $(call gb_CppunitTest_use_vcl,sw_core_layout_flycnt))
+
+$(eval $(call gb_CppunitTest_use_rdb,sw_core_layout_flycnt,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,sw_core_layout_flycnt,\
+    officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,sw_core_layout_flycnt))
+
+$(eval $(call gb_CppunitTest_use_uiconfigs,sw_core_layout_flycnt, \
+    modules/swriter \
+    svt \
+    svx \
+))
+
+# assert if font/glyph fallback occurs
+$(eval $(call 
gb_CppunitTest_set_non_application_font_use,sw_core_layout_flycnt,abort))
+
+$(eval $(call gb_CppunitTest_use_more_fonts,sw_core_layout_flycnt))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk
index 44d5bc4e8d22..f875935dac9e 100644
--- a/sw/Module_sw.mk
+++ b/sw/Module_sw.mk
@@ -138,6 +138,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\
     CppunitTest_sw_uibase_wrtsh \
     CppunitTest_sw_core_accessibilitycheck \
     CppunitTest_sw_core_layout \
+    CppunitTest_sw_core_layout_flycnt \
     CppunitTest_sw_core_fields \
     CppunitTest_sw_core_tox \
     CppunitTest_sw_core_frmedt \
diff --git a/sw/inc/formatflysplit.hxx b/sw/inc/formatflysplit.hxx
index 5f7dda675ed2..1db9587752b6 100644
--- a/sw/inc/formatflysplit.hxx
+++ b/sw/inc/formatflysplit.hxx
@@ -34,6 +34,10 @@ public:
     SwFormatFlySplit* Clone(SfxItemPool* pPool = nullptr) const override;
 
     void dumpAsXml(xmlTextWriterPtr pWriter) const override;
+
+    // Force-enable for test purposes.
+    static void SetForce(bool bForce);
+    static bool GetForce();
 };
 
 inline const SwFormatFlySplit& SwAttrSet::GetFlySplit(bool bInP) const
diff --git a/sw/qa/core/layout/data/floattable.docx 
b/sw/qa/core/layout/data/floattable.docx
new file mode 100644
index 000000000000..ee1b029882dd
Binary files /dev/null and b/sw/qa/core/layout/data/floattable.docx differ
diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx
new file mode 100644
index 000000000000..a006ecb54f00
--- /dev/null
+++ b/sw/qa/core/layout/flycnt.cxx
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <swmodeltestbase.hxx>
+
+#include <comphelper/scopeguard.hxx>
+
+#include <IDocumentLayoutAccess.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrms.hxx>
+#include <formatflysplit.hxx>
+#include <pagefrm.hxx>
+#include <rootfrm.hxx>
+#include <sortedobjs.hxx>
+#include <tabfrm.hxx>
+
+namespace
+{
+/// Covers sw/source/core/layout/flycnt.cxx fixes, i.e. mostly 
SwFlyAtContentFrame.
+class Test : public SwModelTestBase
+{
+public:
+    Test()
+        : SwModelTestBase("/sw/qa/core/layout/data/")
+    {
+    }
+};
+
+CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable)
+{
+    // Given a document with a multi-page floating table:
+    SwFormatFlySplit::SetForce(true);
+    comphelper::ScopeGuard g([] { SwFormatFlySplit::SetForce(false); });
+    createSwDoc("floattable.docx");
+
+    // When laying out that document:
+    calcLayout();
+
+    // Then make sure that the first row goes to page 1 and the second row 
goes to page 2, while the
+    // table is floating:
+    SwDoc* pDoc = getSwDoc();
+    // Without the accompanying fix in place, this test would have failed with 
a stack overflow
+    // because the follow frame of the anchor was moved into the follow frame 
of the fly, so the fly
+    // was anchored in itself.
+    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
+    // Page 1 has a master fly, which contains a master table:
+    auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower());
+    CPPUNIT_ASSERT(pPage1);
+    const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs.size());
+    auto pPage1Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage1Objs[0]);
+    CPPUNIT_ASSERT(pPage1Fly);
+    CPPUNIT_ASSERT(!pPage1Fly->GetPrecede());
+    CPPUNIT_ASSERT(pPage1Fly->GetFollow());
+    auto pPage1Table = dynamic_cast<SwTabFrame*>(pPage1Fly->GetLower());
+    CPPUNIT_ASSERT(pPage1Table);
+    CPPUNIT_ASSERT(!pPage1Table->GetPrecede());
+    CPPUNIT_ASSERT(pPage1Table->GetFollow());
+    // Page 2 has a follow fly, which contains a follow table:
+    auto pPage2 = dynamic_cast<SwPageFrame*>(pPage1->GetNext());
+    CPPUNIT_ASSERT(pPage2);
+    const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs.size());
+    auto pPage2Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage2Objs[0]);
+    CPPUNIT_ASSERT(pPage2Fly);
+    CPPUNIT_ASSERT(pPage2Fly->GetPrecede());
+    CPPUNIT_ASSERT(!pPage2Fly->GetFollow());
+    auto pPage2Table = dynamic_cast<SwTabFrame*>(pPage2Fly->GetLower());
+    CPPUNIT_ASSERT(pPage2Table);
+    CPPUNIT_ASSERT(pPage2Table->GetPrecede());
+    CPPUNIT_ASSERT(!pPage2Table->GetFollow());
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/attr/formatflysplit.cxx 
b/sw/source/core/attr/formatflysplit.cxx
index 904fd9a8bb5c..7bc8a75594d7 100644
--- a/sw/source/core/attr/formatflysplit.cxx
+++ b/sw/source/core/attr/formatflysplit.cxx
@@ -21,6 +21,8 @@
 
 #include <libxml/xmlwriter.h>
 
+static std::optional<bool> g_oForce;
+
 SwFormatFlySplit::SwFormatFlySplit(bool bSplit)
     : SfxBoolItem(RES_FLY_SPLIT, bSplit)
 {
@@ -34,9 +36,9 @@ SwFormatFlySplit::SwFormatFlySplit(bool bSplit)
     //
     // - Both the master fly and the follow flys need an anchor. At the same 
time, we want all text
     // of the anchor frame to be wrapped around the last follow fly frame, for 
Word compatibility.
-    // These are solved by splitting the anchor frame as many times as needed, 
always at text
+    // These are solved by splitting the anchor frame as many times as needed, 
always at
     // TextFrameIndex 0.
-    if (getenv("SW_FORCE_FLY_SPLIT"))
+    if (SwFormatFlySplit::GetForce())
     {
         SetValue(true);
     }
@@ -57,4 +59,16 @@ void SwFormatFlySplit::dumpAsXml(xmlTextWriterPtr pWriter) 
const
     (void)xmlTextWriterEndElement(pWriter);
 }
 
+void SwFormatFlySplit::SetForce(bool bForce) { g_oForce = bForce; }
+
+bool SwFormatFlySplit::GetForce()
+{
+    if (g_oForce.has_value())
+    {
+        return *g_oForce;
+    }
+
+    return getenv("SW_FORCE_FLY_SPLIT") != nullptr;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/flyfrms.hxx b/sw/source/core/inc/flyfrms.hxx
index 608e04ff8f11..8d3bd95589bc 100644
--- a/sw/source/core/inc/flyfrms.hxx
+++ b/sw/source/core/inc/flyfrms.hxx
@@ -21,6 +21,8 @@
 
 #include <sal/config.h>
 
+#include <swdllapi.h>
+
 #include "flyfrm.hxx"
 #include "flowfrm.hxx"
 
@@ -30,7 +32,7 @@ double getLocalFrameRotation_from_SwNoTextFrame(const 
SwNoTextFrame& rNoTextFram
 
 // Base class for those Flys that can "move freely" or better that are not
 // bound in Content.
-class SwFlyFreeFrame : public SwFlyFrame
+class SW_DLLPUBLIC SwFlyFreeFrame : public SwFlyFrame
 {
 private:
     // #i34753# - flag for at-page anchored Writer fly frames
@@ -157,7 +159,7 @@ public:
 };
 
 // Flys that are bound to Content but not in Content
-class SwFlyAtContentFrame final: public SwFlyFreeFrame, public SwFlowFrame
+class SW_DLLPUBLIC SwFlyAtContentFrame final: public SwFlyFreeFrame, public 
SwFlowFrame
 {
     virtual void MakeAll(vcl::RenderContext* pRenderContext) override;
 
@@ -198,6 +200,7 @@ public:
     SwFlyAtContentFrame* GetFollow();
     const SwFlyAtContentFrame* GetPrecede() const;
     SwFlyAtContentFrame* GetPrecede();
+    void dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const override;
 };
 
 // Flys that are bound to a character in Content
diff --git a/sw/source/core/inc/pagefrm.hxx b/sw/source/core/inc/pagefrm.hxx
index 73d81a6895b5..88004bb7ccb3 100644
--- a/sw/source/core/inc/pagefrm.hxx
+++ b/sw/source/core/inc/pagefrm.hxx
@@ -20,6 +20,7 @@
 #define INCLUDED_SW_SOURCE_CORE_INC_PAGEFRM_HXX
 
 #include <viewsh.hxx>
+#include <swdllapi.h>
 #include "ftnboss.hxx"
 #include "hffrm.hxx"
 
@@ -54,7 +55,7 @@ namespace o3tl {
 
 /// A page of the document layout. Upper frame is expected to be an SwRootFrame
 /// instance. At least an SwBodyFrame lower is expected.
-class SAL_DLLPUBLIC_RTTI SwPageFrame final: public SwFootnoteBossFrame
+class SW_DLLPUBLIC SwPageFrame final: public SwFootnoteBossFrame
 {
     friend class SwFrame;
 
diff --git a/sw/source/core/inc/tabfrm.hxx b/sw/source/core/inc/tabfrm.hxx
index 8bf2b863548e..ca4af59b223c 100644
--- a/sw/source/core/inc/tabfrm.hxx
+++ b/sw/source/core/inc/tabfrm.hxx
@@ -19,6 +19,8 @@
 #ifndef INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX
 #define INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX
 
+#include <swdllapi.h>
+
 #include "layfrm.hxx"
 #include "flowfrm.hxx"
 
@@ -43,7 +45,7 @@ namespace o3tl {
 }
 
 /// SwTabFrame is one table in the document layout, containing rows (which 
contain cells).
-class SAL_DLLPUBLIC_RTTI SwTabFrame final: public SwLayoutFrame, public 
SwFlowFrame
+class SW_DLLPUBLIC SwTabFrame final: public SwLayoutFrame, public SwFlowFrame
 {
     friend void CalcContent( SwLayoutFrame *pLay, bool bNoColl );
 
diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx
index 2d96e0fcea64..b18cb6a21cb5 100644
--- a/sw/source/core/layout/flycnt.cxx
+++ b/sw/source/core/layout/flycnt.cxx
@@ -1552,14 +1552,49 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType 
eMakePage )
     auto pFly = dynamic_cast<SwFlyAtContentFrame*>(FindFlyFrame());
     assert(pFly && "GetNextFlyLeaf: missing fly frame");
 
-    SwLayoutFrame *pLayLeaf = GetNextLayoutLeaf();
-    if (!pLayLeaf)
+    SwLayoutFrame *pLayLeaf = nullptr;
+    // Look up the first candidate.
+    if (IsTabFrame())
     {
-        if (eMakePage == MAKEPAGE_INSERT)
+        // If we're in a table, try to find the next frame of the table's last 
content.
+        SwFrame* pContent = 
static_cast<SwTabFrame*>(this)->FindLastContentOrTable();
+        pLayLeaf = pContent ? pContent->GetUpper() : nullptr;
+    }
+    else
+    {
+        pLayLeaf = GetNextLayoutLeaf();
+    }
+
+    SwLayoutFrame* pOldLayLeaf = nullptr;
+    while (true)
+    {
+        if (pLayLeaf)
         {
-            InsertPage(FindPageFrame(), false);
-            pLayLeaf = GetNextLayoutLeaf();
+            // If we have a candidate, make sure that it's a child of our 
follow.
+            if (pFly->IsFlySplitAllowed())
+            {
+                if (pFly->GetFollow() != pLayLeaf->FindFlyFrame())
+                {
+                    // It's not in our follow, reject.
+                    pOldLayLeaf = pLayLeaf;
+                    pLayLeaf = pLayLeaf->GetNextLayoutLeaf();
+                    continue;
+                }
+            }
+        }
+        else
+        {
+            // No candidate: insert a page and try again.
+            if (eMakePage == MAKEPAGE_INSERT)
+            {
+                InsertPage(FindPageFrame(), false);
+                // If we already had a cancidate, continue trying with that 
instead of starting from
+                // scratch.
+                pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf();
+                continue;
+            }
         }
+        break;
     }
 
     if( pLayLeaf )
diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx
index 9e87d12c81d5..ff22279f5c5b 100644
--- a/sw/source/core/text/xmldump.cxx
+++ b/sw/source/core/text/xmldump.cxx
@@ -26,6 +26,7 @@
 #include <libxml/xmlwriter.h>
 #include <SwPortionHandler.hxx>
 #include <view.hxx>
+#include <flyfrms.hxx>
 #include <svx/svdobj.hxx>
 
 #include "porlay.hxx"
@@ -388,6 +389,11 @@ void SwFrame::dumpAsXml( xmlTextWriterPtr writer ) const
         else
         {
             dumpChildrenAsXml( writer );
+            if (IsFlyFrame())
+            {
+                auto pFlyFrame = static_cast<const SwFlyFrame*>(this);
+                pFlyFrame->SwAnchoredObject::dumpAsXml(writer);
+            }
         }
         (void)xmlTextWriterEndElement( writer );
     }
@@ -488,6 +494,12 @@ void SwAnchoredObject::dumpAsXml( xmlTextWriterPtr writer 
) const
 
     (void)xmlTextWriterStartElement( writer, BAD_CAST( getElementName() ) );
     (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", 
this );
+    (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("anchor-frame"), 
BAD_CAST(OString::number(mpAnchorFrame->GetFrameId()).getStr()));
+    SwTextFrame* pAnchorCharFrame = 
const_cast<SwAnchoredObject*>(this)->FindAnchorCharFrame();
+    if (pAnchorCharFrame)
+    {
+        (void)xmlTextWriterWriteAttribute(writer, 
BAD_CAST("anchor-char-frame"), 
BAD_CAST(OString::number(pAnchorCharFrame->GetFrameId()).getStr()));
+    }
 
     (void)xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) );
     // don't call GetObjBoundRect(), it modifies the layout
@@ -527,6 +539,24 @@ void SwTextFrame::dumpAsXmlAttributes( xmlTextWriterPtr 
writer ) const
         (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" 
), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() );
 }
 
+void SwFlyAtContentFrame::dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const
+{
+    SwFlyFreeFrame::dumpAsXmlAttributes(pWriter);
+
+    if (m_pFollow != nullptr)
+    {
+        (void)xmlTextWriterWriteAttribute(
+            pWriter, BAD_CAST("follow"),
+            
BAD_CAST(OString::number(m_pFollow->GetFrame().GetFrameId()).getStr()));
+    }
+    if (m_pPrecede != nullptr)
+    {
+        (void)xmlTextWriterWriteAttribute(
+            pWriter, BAD_CAST("precede"),
+            
BAD_CAST(OString::number(m_pPrecede->GetFrame().GetFrameId()).getStr()));
+    }
+}
+
 void SwSectionFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const
 {
     SwFrame::dumpAsXmlAttributes( writer );
commit 1ce09a7ecff51574d8fa90f7d638d5624dfee8f0
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Feb 9 08:26:21 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Mar 1 08:41:42 2023 +0100

    sw: call FormatEmpty() in SwTextFrame::Format() for split fly masters
    
    The problem was that the text in the anchor frame of a split fly frame
    was duplicated on the old page and the new page as well. The reason for
    this seems to be that the master has no content and the follow has all
    the content (this is wanted), but then there is no code to explicitly
    clear the master.
    
    In other cases the master always gets some new content where portion
    building for that new content starts by throwing away the old portions.
    
    Once SwTextFrame::Format() and SwTextFrame::FormatEmpty() explicitly
    checks for these master anchors, the unwanted text in the master anchor
    disappears.
    
    An extra tweak is needed in SwTextFrame::PaintEmpty() to even hide the
    paragraph marker: this frame is empty but has a follow frame, so we
    should not show a paragraph marker there.
    
    Finally introduce a SwTextFrame::HasNonLastSplitFlyDrawObj() to be able
    to check for this "empty master anchor for split fly" case at a single
    place.
    
    With this <https://bugs.documentfoundation.org/attachment.cgi?id=185144>
    from <https://bugs.documentfoundation.org/show_bug.cgi?id=61594> gets
    laid out reasonably: the position is not perfect but we detect that only
    1 para of the text frame fits page 1, we create a 2nd page and we
    correctly move exactly the text frame's 2nd para to page 2.
    
    (cherry picked from commit 00b9b33334791079c2dc26b1ed4c123450cabf7d)
    
    Change-Id: I871bba2de5b829e667d5cfb1cbe0ba4cc2274edd

diff --git a/sw/source/core/attr/formatflysplit.cxx 
b/sw/source/core/attr/formatflysplit.cxx
index bcbfcc2d5e96..904fd9a8bb5c 100644
--- a/sw/source/core/attr/formatflysplit.cxx
+++ b/sw/source/core/attr/formatflysplit.cxx
@@ -24,6 +24,18 @@
 SwFormatFlySplit::SwFormatFlySplit(bool bSplit)
     : SfxBoolItem(RES_FLY_SPLIT, bSplit)
 {
+    // Once this pool item is true, a floating table (text frame + table 
inside it) is meant to
+    // split across multiple pages.
+    //
+    // The layout representation is the following:
+    //
+    // - We assume that the anchor type is at-para for such fly frames, and 
SwFlyAtContentFrame
+    // derives from SwFlowFrame to be able to split in general.
+    //
+    // - Both the master fly and the follow flys need an anchor. At the same 
time, we want all text
+    // of the anchor frame to be wrapped around the last follow fly frame, for 
Word compatibility.
+    // These are solved by splitting the anchor frame as many times as needed, 
always at text
+    // TextFrameIndex 0.
     if (getenv("SW_FORCE_FLY_SPLIT"))
     {
         SetValue(true);
diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx
index cb22ebc439f8..942867882626 100644
--- a/sw/source/core/inc/txtfrm.hxx
+++ b/sw/source/core/inc/txtfrm.hxx
@@ -332,6 +332,9 @@ class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame
 
     virtual void SwClientNotify(SwModify const& rModify, SfxHint const& rHint) 
override;
 
+    /// Like GetDrawObjs(), but limit to fly frames which are allowed to split.
+    std::vector<SwFlyAtContentFrame*> GetSplitFlyDrawObjs() const;
+
 public:
 
     virtual const SvxFormatBreakItem& GetBreakItem() const override;
@@ -784,8 +787,9 @@ public:
     OUString GetCurWord(SwPosition const&) const;
     sal_uInt16 GetScalingOfSelectedText(TextFrameIndex nStt, TextFrameIndex 
nEnd);
 
-    /// Like GetDrawObjs(), but limit to fly frames which are allowed to split.
-    std::vector<SwFlyAtContentFrame*> GetSplitFlyDrawObjs();
+    /// This text frame may have a split fly frames anchored to it. Is any of 
them a frame that has
+    /// a follow, i.e. not the last in a master -> follow 1 -> ... -> last 
follow chain?
+    bool HasNonLastSplitFlyDrawObj() const;
 
     virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override;
 };
diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx
index 31becc976597..1975840aa1d3 100644
--- a/sw/source/core/text/frmform.cxx
+++ b/sw/source/core/text/frmform.cxx
@@ -581,14 +581,11 @@ void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine,
             if (GetFollow()->IsDeleteForbidden())
                 return;
 
-            for (const auto& pFlyFrame : GetSplitFlyDrawObjs())
+            if (HasNonLastSplitFlyDrawObj())
             {
                 // If a fly frame is anchored to us that has a follow, then 
don't join the anchor.
                 // First those fly frames have to be joined.
-                if (pFlyFrame->GetFollow())
-                {
-                    return;
-                }
+                return;
             }
 
             JoinFrame();
@@ -1847,7 +1844,18 @@ void SwTextFrame::Format( vcl::RenderContext* 
pRenderContext, const SwBorderAttr
         return;
     }
 
-    const TextFrameIndex nStrLen(GetText().getLength());
+    TextFrameIndex nStrLen(GetText().getLength());
+
+    SwTextFrame* pFollow = GetFollow();
+    if (pFollow && pFollow->GetOffset() == mnOffset)
+    {
+        if (HasNonLastSplitFlyDrawObj())
+        {
+            // Non-last part of split fly anchor: consider this empty.
+            nStrLen = TextFrameIndex(0);
+        }
+    }
+
     if ( nStrLen || !FormatEmpty() )
     {
 
diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx
index e09ad12373cb..461c8094c0ac 100644
--- a/sw/source/core/text/frmpaint.cxx
+++ b/sw/source/core/text/frmpaint.cxx
@@ -601,7 +601,8 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool 
bCheck ) const
                 }
 
                 // Don't show the paragraph mark for collapsed paragraphs, 
when they are hidden
-                if ( EmptyHeight( ) > 1 )
+                // No paragraph marker in the non-last part of a split fly 
anchor, either.
+                if ( EmptyHeight( ) > 1 && !HasNonLastSplitFlyDrawObj() )
                 {
                     SwDrawTextInfo aDrawInf( pSh, *pSh->GetOut(), CH_PAR, 0, 1 
);
                     aDrawInf.SetPos( aPos );
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index 41431bf4b715..eb643f2a1264 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -1452,10 +1452,10 @@ sal_uInt16 SwTextFrame::GetScalingOfSelectedText(
     return o3tl::narrowing<sal_uInt16>( nWidth ? ((100 * 
aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 );
 }
 
-std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs()
+std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const
 {
     std::vector<SwFlyAtContentFrame*> aObjs;
-    SwSortedObjs* pSortedObjs = GetDrawObjs();
+    const SwSortedObjs* pSortedObjs = GetDrawObjs();
     if (!pSortedObjs)
     {
         return aObjs;
@@ -1480,6 +1480,19 @@ std::vector<SwFlyAtContentFrame*> 
SwTextFrame::GetSplitFlyDrawObjs()
     return aObjs;
 }
 
+bool SwTextFrame::HasNonLastSplitFlyDrawObj() const
+{
+    for (const auto& pFly : GetSplitFlyDrawObjs())
+    {
+        if (pFly->GetFollow())
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 SwTwips SwTextNode::GetWidthOfLeadingTabs() const
 {
     SwTwips nRet = 0;
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index 03ce7666c911..6c1f0c06a5a5 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1936,16 +1936,7 @@ TextFrameIndex 
SwTextFormatter::FormatLine(TextFrameIndex const nStartPos)
             {
                 // Don't oversize the line in case of split flys, so we don't 
try to move the anchor
                 // of a precede fly forward, next to its follow.
-                bool bHasNonLastFlySplitAnchored = false;
-                for (const auto& pFlyFrame : m_pFrame->GetSplitFlyDrawObjs())
-                {
-                    if (pFlyFrame->GetFollow())
-                    {
-                        bHasNonLastFlySplitAnchored = true;
-                        break;
-                    }
-                }
-                if (bHasNonLastFlySplitAnchored)
+                if (m_pFrame->HasNonLastSplitFlyDrawObj())
                 {
                     m_pCurr->SetRealHeight(GetFrameRstHeight());
                 }
diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx
index 3a8dd51e014d..eb128eaa662e 100644
--- a/sw/source/core/text/porrst.cxx
+++ b/sw/source/core/text/porrst.cxx
@@ -410,7 +410,9 @@ bool SwTextFrame::FormatEmpty()
     bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( );
 
     // sw_redlinehide: just disable FormatEmpty optimisation for now
-    if (HasFollow() || GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() ||
+    // Split fly frames: non-last parts of the anchor want this optimization 
to clear the old
+    // content.
+    if ((HasFollow() && mnOffset != GetFollow()->GetOffset()) || 
GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() ||
         nullptr != GetTextNodeForParaProps()->GetNumRule() ||
         GetTextNodeFirst()->HasHiddenCharAttribute(true) ||
          IsInFootnote() || ( HasPara() && GetPara()->IsPrepMustFit() ) )
commit 8a06b6300f131079e589fd2695a541d36fca44e3
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Feb 9 08:22:16 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Mar 1 08:40:13 2023 +0100

    sw floattable: fix cid#1520804
    
    GetFrameRstHeight() already assumes the m_pFrame is non-nullptr, so no
    need to check for this.
    
    The logic in the block will be necessary elsewhere as well, I'll extract
    that to a function in a follow-up change, so let's just do a minimal fix
    here.
    
    (cherry picked from commit 25a16e7543965565a4227506003adc916deea500)
    
    Change-Id: I6cb2a44e629c273f473278d61607705a2b9a7a4d

diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index e65468d62d4c..03ce7666c911 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1933,7 +1933,6 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex 
const nStartPos)
             m_pCurr->Height( GetFrameRstHeight() + 1, false );
             m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 );
 
-            if (m_pFrame)
             {
                 // Don't oversize the line in case of split flys, so we don't 
try to move the anchor
                 // of a precede fly forward, next to its follow.
commit 0bb0922452c12d49f95483f9c10aad000afe7438
Author:     Miklos Vajna <[email protected]>
AuthorDate: Tue Feb 7 16:19:52 2023 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Wed Mar 1 08:39:04 2023 +0100

    sw floattable: fix cid#1520800
    
    Only SwFrame::GetLeaf() calls this, but it only does so when we're in
    an at-para anchored fly frame, so we can require that this succeeds.
    
    (cherry picked from commit d6b9529c4f63d1dd5c57db4f4912471cce2507d9)
    
    Change-Id: I6c99bc2ea1ab4f338a536272ccce13fd22b30246

diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx
index 474d21bd7f97..2d96e0fcea64 100644
--- a/sw/source/core/layout/flycnt.cxx
+++ b/sw/source/core/layout/flycnt.cxx
@@ -1597,6 +1597,7 @@ SwFlyAtContentFrame* SwFlyAtContentFrame::GetPrecede()
 SwLayoutFrame* SwFrame::GetPrevFlyLeaf()
 {
     auto pFly = dynamic_cast<SwFlyAtContentFrame*>(FindFlyFrame());
+    assert(pFly && "GetPrevFlyLeaf: missing fly frame");
     if (!pFly->IsFlySplitAllowed())
     {
         return nullptr;

Reply via email to