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  |   18 ++++++
 sw/source/core/inc/flyfrms.hxx          |    7 +-
 sw/source/core/inc/pagefrm.hxx          |    3 -
 sw/source/core/inc/tabfrm.hxx           |    4 +
 sw/source/core/layout/flycnt.cxx        |   45 +++++++++++++++--
 sw/source/core/text/xmldump.cxx         |   30 +++++++++++
 11 files changed, 266 insertions(+), 11 deletions(-)

New commits:
commit 995198bfff4ae8abaf2129fe99d9f8ef899a4f25
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Feb 13 12:55:32 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Feb 13 15:04:12 2023 +0000

    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.
    
    Change-Id: Ie2ce75dbace5d9716008351aedb6a8989036badb
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146854
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

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 2b99d0bff22a..45325bf3ea9a 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 ba7a4d14ac4c..3fc64f1be7a2 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"
@@ -394,6 +395,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 );
     }
@@ -494,6 +500,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
@@ -537,6 +549,24 @@ void SwTextFrame::dumpAsXmlAttributes( xmlTextWriterPtr 
writer ) const
     (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("offset"), 
BAD_CAST(OString::number(static_cast<sal_Int32>(mnOffset)).getStr()));
 }
 
+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 );

Reply via email to