sw/qa/core/layout/data/floattable-3pages.docx                      |binary
 sw/qa/core/layout/flycnt.cxx                                       |   67 +++
 sw/qa/filter/ww8/ww8.cxx                                           |   39 ++
 sw/source/core/layout/anchoredobject.cxx                           |   20 -
 sw/source/core/layout/flycnt.cxx                                   |   17 
 sw/source/core/layout/tabfrm.cxx                                   |    8 
 sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx |   23 -
 sw/source/core/text/frmform.cxx                                    |    8 
 sw/source/core/text/itratr.cxx                                     |   15 
 sw/source/core/text/txtfrm.cxx                                     |    7 
 sw/source/filter/ww8/docxattributeoutput.cxx                       |  171 
+++++-----
 writerfilter/source/dmapper/DomainMapperTableHandler.cxx           |   28 +
 12 files changed, 305 insertions(+), 98 deletions(-)

New commits:
commit 58e70891131a99d4eb38b362bb2d4246c7a67cb6
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Feb 22 08:11:00 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Mar 3 08:45:03 2023 +0100

    sw floatable: teach the DOCX filter about SwFormatFlySplit
    
    - stop creating a grab-bag for floating tables in the DOCX import if
      split flys are allowed, which gives the exporter an opportunity to
      actually read the doc model
    
    - extract that code that writes a <w:tblpPr> from a ww8::Frame to a new
      CollectFloatingTableAttributes()
    
    - in case a fly frame has a table and the fly has SwFormatFlySplit=true,
      then call CollectFloatingTableAttributes() even without grab-bags
    
    - in the unlikely case that we would have both a split fly and a
      grab-bag, ignore the grab-bag
    
    With this, we get a working DOCX export for multi-page floating tables.
    The import is still disabled by default.
    
    (cherry picked from commit e7be3b821cd42fdc9d8e51772b8202030d76497e)
    
    Change-Id: I601833c49f49f94e1ff3cdc994e3027ee0542b94

diff --git a/sw/qa/filter/ww8/ww8.cxx b/sw/qa/filter/ww8/ww8.cxx
index 611d63259ae8..8eac94ae36d0 100644
--- a/sw/qa/filter/ww8/ww8.cxx
+++ b/sw/qa/filter/ww8/ww8.cxx
@@ -15,6 +15,10 @@
 #include <docsh.hxx>
 #include <formatcontentcontrol.hxx>
 #include <wrtsh.hxx>
+#include <itabenum.hxx>
+#include <frmmgr.hxx>
+#include <frameformats.hxx>
+#include <formatflysplit.hxx>
 
 namespace
 {
@@ -189,6 +193,41 @@ CPPUNIT_TEST_FIXTURE(Test, testDocxSymbolFontExport)
     assertXPath(pXmlDoc, "//w:p/w:r/w:sym[1]", "font", "Wingdings");
     assertXPath(pXmlDoc, "//w:p/w:r/w:sym[1]", "char", "f0e0");
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testDocxFloatingTableExport)
+{
+    // Given a document with a floating table:
+    createSwDoc();
+    SwDoc* pDoc = getSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    // Insert a table:
+    SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0);
+    pWrtShell->InsertTable(aTableOptions, 1, 1);
+    pWrtShell->MoveTable(GotoPrevTable, fnTableStart);
+    // Select it:
+    pWrtShell->SelAll();
+    // Wrap in a fly:
+    SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr);
+    pWrtShell->StartAllAction();
+    aMgr.InsertFlyFrame(RndStdIds::FLY_AT_PARA, aMgr.GetPos(), aMgr.GetSize());
+    // Mark it as a floating table:
+    SwFrameFormats& rFlys = *pDoc->GetSpzFrameFormats();
+    SwFrameFormat* pFly = rFlys[0];
+    SwAttrSet aSet(pFly->GetAttrSet());
+    aSet.Put(SwFormatFlySplit(true));
+    pDoc->SetAttr(aSet, *pFly);
+    pWrtShell->EndAllAction();
+
+    // When saving to docx:
+    save("Office Open XML Text");
+
+    // Then make sure we write a floating table, not a textframe containing a 
table:
+    xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml");
+    // Without the accompanying fix in place, this test would have failed with:
+    // - XPath '//w:tbl/w:tblPr/w:tblpPr' number of nodes is incorrect
+    // i.e. no floating table was exported.
+    assertXPath(pXmlDoc, "//w:tbl/w:tblPr/w:tblpPr", 1);
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 11a35b8bd55e..f1485b5814f4 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -137,6 +137,7 @@
 #include <txtatr.hxx>
 #include <frameformats.hxx>
 #include <textcontentcontrol.hxx>
+#include <formatflysplit.hxx>
 
 #include <o3tl/string_view.hxx>
 #include <o3tl/unit_conversion.hxx>
@@ -409,7 +410,14 @@ static void 
checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu
         std::map<OUString, css::uno::Any> aTableGrabBag = 
pTableGrabBag->GetGrabBag();
         // no grabbag?
         if (aTableGrabBag.find("TablePosition") == aTableGrabBag.end())
+        {
+            if (pFrameFormat->GetFlySplit().GetValue())
+            {
+                ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
+                rDocxAttributeOutput.WriteFloatingTable(&aFrame);
+            }
             continue;
+        }
 
         // write table to docx
         ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
@@ -4554,6 +4562,84 @@ sal_Int32 lcl_getWordCompatibilityMode(const DocxExport& 
rDocExport)
     return nWordCompatibilityMode;
 }
 
+void CollectFloatingTableAttributes(DocxExport& rExport, const ww8::Frame& 
rFrame,
+                                    ww8::WW8TableNodeInfoInner::Pointer_t 
pTableTextNodeInfoInner,
+                                    rtl::Reference<FastAttributeList>& 
pAttributes)
+{
+    // we export the values of the surrounding Frame
+    OString sOrientation;
+    sal_Int32 nValue;
+
+    // If tblpXSpec or tblpYSpec are present, we do not write tblpX or tblpY!
+    OString sTblpXSpec
+        = 
convertToOOXMLHoriOrient(rFrame.GetFrameFormat().GetHoriOrient().GetHoriOrient(),
+                                   
rFrame.GetFrameFormat().GetHoriOrient().IsPosToggle());
+    OString sTblpYSpec
+        = 
convertToOOXMLVertOrient(rFrame.GetFrameFormat().GetVertOrient().GetVertOrient());
+
+    sOrientation
+        = 
convertToOOXMLVertOrientRel(rFrame.GetFrameFormat().GetVertOrient().GetRelationOrient());
+    pAttributes->add(FSNS(XML_w, XML_vertAnchor), sOrientation);
+
+    if (!sTblpYSpec.isEmpty())
+        pAttributes->add(FSNS(XML_w, XML_tblpYSpec), sTblpYSpec);
+
+    sOrientation
+        = 
convertToOOXMLHoriOrientRel(rFrame.GetFrameFormat().GetHoriOrient().GetRelationOrient());
+    pAttributes->add(FSNS(XML_w, XML_horzAnchor), sOrientation);
+
+    if (!sTblpXSpec.isEmpty())
+        pAttributes->add(FSNS(XML_w, XML_tblpXSpec), sTblpXSpec);
+
+    nValue = rFrame.GetFrameFormat().GetULSpace().GetLower();
+    if (nValue != 0)
+        pAttributes->add(FSNS(XML_w, XML_bottomFromText), 
OString::number(nValue));
+
+    nValue = rFrame.GetFrameFormat().GetLRSpace().GetLeft();
+    if (nValue != 0)
+        pAttributes->add(FSNS(XML_w, XML_leftFromText), 
OString::number(nValue));
+
+    nValue = rFrame.GetFrameFormat().GetLRSpace().GetRight();
+    if (nValue != 0)
+        pAttributes->add(FSNS(XML_w, XML_rightFromText), 
OString::number(nValue));
+
+    nValue = rFrame.GetFrameFormat().GetULSpace().GetUpper();
+    if (nValue != 0)
+        pAttributes->add(FSNS(XML_w, XML_topFromText), 
OString::number(nValue));
+
+    if (sTblpXSpec.isEmpty()) // do not write tblpX if tblpXSpec is present
+    {
+        nValue = rFrame.GetFrameFormat().GetHoriOrient().GetPos();
+        // we need to revert the additional shift introduced by
+        // lcl_DecrementHoriOrientPosition() in writerfilter
+        // 1st: left distance of the table
+        const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox();
+        const SwFrameFormat* pFrameFormat = pTabBox->GetFrameFormat();
+        const SvxBoxItem& rBox = pFrameFormat->GetBox();
+        sal_Int32 nMode = lcl_getWordCompatibilityMode(rExport);
+        if (nMode < 15)
+        {
+            sal_uInt16 nLeftDistance = rBox.GetDistance(SvxBoxItemLine::LEFT);
+            nValue += nLeftDistance;
+        }
+
+        // 2nd: if a left border is given, revert the shift by half the width
+        // from lcl_DecrementHoriOrientPosition() in writerfilter
+        if (const editeng::SvxBorderLine* pLeftBorder = rBox.GetLeft())
+        {
+            tools::Long nWidth = pLeftBorder->GetWidth();
+            nValue += (nWidth / 2);
+        }
+
+        pAttributes->add(FSNS(XML_w, XML_tblpX), OString::number(nValue));
+    }
+
+    if (sTblpYSpec.isEmpty()) // do not write tblpY if tblpYSpec is present
+    {
+        nValue = rFrame.GetFrameFormat().GetVertOrient().GetPos();
+        pAttributes->add(FSNS(XML_w, XML_tblpY), OString::number(nValue));
+    }
+}
 }
 
 void DocxAttributeOutput::TableDefinition( 
ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
@@ -4660,6 +4746,16 @@ void DocxAttributeOutput::TableDefinition( 
ww8::WW8TableNodeInfoInner::Pointer_t
     std::map<SvxBoxItemLine, css::table::BorderLine2>& rTableStyleConf = 
m_aTableStyleConfs.back();
     rTableStyleConf.clear();
 
+    bool bFloatingTableWritten = false;
+    if (pFloatingTableFrame && 
pFloatingTableFrame->GetFrameFormat().GetFlySplit().GetValue())
+    {
+        rtl::Reference<FastAttributeList> pAttributes = 
FastSerializerHelper::createAttrList();
+        CollectFloatingTableAttributes(m_rExport, *pFloatingTableFrame, 
pTableTextNodeInfoInner,
+                                       pAttributes);
+        m_pSerializer->singleElementNS(XML_w, XML_tblpPr, pAttributes);
+        bFloatingTableWritten = true;
+    }
+
     // Extract properties from grab bag
     for( const auto & rGrabBagElement : aGrabBag )
     {
@@ -4719,74 +4815,8 @@ void DocxAttributeOutput::TableDefinition( 
ww8::WW8TableNodeInfoInner::Pointer_t
             const ww8::Frame* pFrame = m_rExport.GetFloatingTableFrame();
             if( pFrame )
             {
-                // we export the values of the surrounding Frame
-                OString sOrientation;
-                sal_Int32 nValue;
-
-                // If tblpXSpec or tblpYSpec are present, we do not write 
tblpX or tblpY!
-                OString sTblpXSpec = convertToOOXMLHoriOrient( 
pFrame->GetFrameFormat().GetHoriOrient().GetHoriOrient(), 
pFrame->GetFrameFormat().GetHoriOrient().IsPosToggle() );
-                OString sTblpYSpec = convertToOOXMLVertOrient( 
pFrame->GetFrameFormat().GetVertOrient().GetVertOrient() );
-
-                sOrientation = convertToOOXMLVertOrientRel( 
pFrame->GetFrameFormat().GetVertOrient().GetRelationOrient() );
-                attrListTablePos->add(FSNS(XML_w, XML_vertAnchor), 
sOrientation);
-
-                if( !sTblpYSpec.isEmpty() )
-                    attrListTablePos->add(FSNS(XML_w, XML_tblpYSpec), 
sTblpYSpec);
-
-                sOrientation = convertToOOXMLHoriOrientRel( 
pFrame->GetFrameFormat().GetHoriOrient().GetRelationOrient() );
-                attrListTablePos->add(FSNS(XML_w, XML_horzAnchor), 
sOrientation);
-
-                if( !sTblpXSpec.isEmpty() )
-                    attrListTablePos->add(FSNS(XML_w, XML_tblpXSpec), 
sTblpXSpec);
-
-                nValue = pFrame->GetFrameFormat().GetULSpace().GetLower();
-                if( nValue != 0 )
-                    attrListTablePos->add( FSNS( XML_w, XML_bottomFromText ), 
OString::number( nValue ) );
-
-                nValue = pFrame->GetFrameFormat().GetLRSpace().GetLeft();
-                if( nValue != 0 )
-                    attrListTablePos->add( FSNS( XML_w, XML_leftFromText ), 
OString::number( nValue ) );
-
-                nValue = pFrame->GetFrameFormat().GetLRSpace().GetRight();
-                if( nValue != 0 )
-                    attrListTablePos->add( FSNS( XML_w, XML_rightFromText ), 
OString::number( nValue ) );
-
-                nValue = pFrame->GetFrameFormat().GetULSpace().GetUpper();
-                if( nValue != 0 )
-                    attrListTablePos->add( FSNS( XML_w, XML_topFromText ), 
OString::number( nValue ) );
-
-                if( sTblpXSpec.isEmpty() ) // do not write tblpX if tblpXSpec 
is present
-                {
-                    nValue = pFrame->GetFrameFormat().GetHoriOrient().GetPos();
-                    // we need to revert the additional shift introduced by
-                    // lcl_DecrementHoriOrientPosition() in writerfilter
-                    // 1st: left distance of the table
-                    const SwTableBox * pTabBox = 
pTableTextNodeInfoInner->getTableBox();
-                    const SwFrameFormat * pFrameFormat = 
pTabBox->GetFrameFormat();
-                    const SvxBoxItem& rBox = pFrameFormat->GetBox( );
-                    sal_Int32 nMode = lcl_getWordCompatibilityMode(m_rExport);
-                    if (nMode < 15)
-                    {
-                        sal_uInt16 nLeftDistance = 
rBox.GetDistance(SvxBoxItemLine::LEFT);
-                        nValue += nLeftDistance;
-                    }
-
-                    // 2nd: if a left border is given, revert the shift by 
half the width
-                    // from lcl_DecrementHoriOrientPosition() in writerfilter
-                    if (const editeng::SvxBorderLine* pLeftBorder = 
rBox.GetLeft())
-                    {
-                        tools::Long nWidth = pLeftBorder->GetWidth();
-                        nValue += (nWidth / 2);
-                    }
-
-                    attrListTablePos->add( FSNS( XML_w, XML_tblpX ), 
OString::number( nValue ) );
-                }
-
-                if( sTblpYSpec.isEmpty() ) // do not write tblpY if tblpYSpec 
is present
-                {
-                    nValue = pFrame->GetFrameFormat().GetVertOrient().GetPos();
-                    attrListTablePos->add( FSNS( XML_w, XML_tblpY ), 
OString::number( nValue ) );
-                }
+                CollectFloatingTableAttributes(m_rExport, *pFrame, 
pTableTextNodeInfoInner,
+                                               attrListTablePos);
             }
             else // ( pFrame = 0 )
             {
@@ -4846,7 +4876,10 @@ void DocxAttributeOutput::TableDefinition( 
ww8::WW8TableNodeInfoInner::Pointer_t
                 }
             }
 
-            m_pSerializer->singleElementNS( XML_w, XML_tblpPr, 
attrListTablePos);
+            if (!bFloatingTableWritten)
+            {
+                m_pSerializer->singleElementNS(XML_w, XML_tblpPr, 
attrListTablePos);
+            }
             attrListTablePos = nullptr;
         }
         else
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx 
b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index df4e3fb5424e..9608509b03e6 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -59,6 +59,22 @@
 #include <utility>
 #endif
 
+namespace
+{
+bool IsFlySplitAllowed()
+{
+    bool bRet
+        = 
officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::get();
+
+    if (!bRet)
+    {
+        bRet = getenv("SW_FORCE_FLY_SPLIT") != nullptr;
+    }
+
+    return bRet;
+}
+}
+
 namespace writerfilter::dmapper {
 
 using namespace ::com::sun::star;
@@ -376,7 +392,10 @@ TableStyleSheetEntry * 
DomainMapperTableHandler::endTableGetTableStyle(TableInfo
                 comphelper::makePropertyValue("vertAnchor", 
pTablePositions->getVertAnchor())
             };
 
-            aGrabBag["TablePosition"] <<= aGrabBagTS;
+            if (!IsFlySplitAllowed())
+            {
+                aGrabBag["TablePosition"] <<= aGrabBagTS;
+            }
         }
         else if (bConvertToFloatingInFootnote)
         {
@@ -1566,12 +1585,7 @@ void DomainMapperTableHandler::endTable(unsigned int 
nestedTableLevel, bool bTab
                     comphelper::makePropertyValue("IsFollowingTextFlow", 
true));
             }
 
-            bool bSplitAllowed = 
officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::get();
-            if (!bSplitAllowed)
-            {
-                bSplitAllowed = getenv("SW_FORCE_FLY_SPLIT") != nullptr;
-            }
-            if (bSplitAllowed)
+            if (IsFlySplitAllowed())
             {
                 
aFrameProperties.push_back(comphelper::makePropertyValue("IsSplitAllowed", 
true));
             }
commit 22826c8a9e765a5894828ae1764b72b30284352c
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Feb 21 08:05:33 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Mar 3 08:43:20 2023 +0100

    sw floattable: fix locking in SwTextFrame::Prepare()
    
    Once a floating table is split across 2 pages, we currently emit this
    warning:
    
    warn:legacy.osl:23427:23427:sw/source/core/text/txtfrm.cxx:2808: 
SwTextFrame::Prepare: three of a perfect pair
    
    Which suggests that we want to invalidate the size of a frame that is
    already locked.
    
    The way normal frame split doesn't hit this warning is that the master
    has some content, then once SwContentFrame::Paste() on the follow calls
    SwTextFrame::Prepare() on the master, the !HasPara() condition is not
    hit, so it's not a problem that the frame is locked.
    
    Fix this for the split fly case by assuming that in case our offset and
    our follow's offset is both 0, then this frame will have some fly
    content, so don't return early (similar to what happens when the frame
    has some text content).
    
    We may want to revisit this later so that at least an SwParaPortion is
    generated for non-last anchors for split flys, but this is good enough
    for now, since the point is to not invalidate the size of a locked
    frame.
    
    (cherry picked from commit b697ee5dc3c38806fc6f096364590e9e60256aeb)
    
    Change-Id: I7c78472653bb6818f0d5880d21f468286be547e4

diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx
index aa371cd3df63..080bf1d89987 100644
--- a/sw/source/core/text/txtfrm.cxx
+++ b/sw/source/core/text/txtfrm.cxx
@@ -2802,7 +2802,12 @@ bool SwTextFrame::Prepare( const PrepareHint ePrep, 
const void* pVoid,
         }
     }
 
-    if( !HasPara() && PrepareHint::MustFit != ePrep )
+    // Split fly anchors are technically empty (have no SwParaPortion), but 
otherwise behave like
+    // other split text frames, which are non-empty.
+    bool bSplitFlyAnchor = GetOffset() == TextFrameIndex(0) && HasFollow()
+                           && GetFollow()->GetOffset() == TextFrameIndex(0);
+
+    if( !HasPara() && !bSplitFlyAnchor && PrepareHint::MustFit != ePrep )
     {
         SetInvalidVert( true ); // Test
         OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect 
pair" );
commit c65c11e0721199729dd10d65cff5bbf8277d0be8
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Feb 20 08:15:41 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Mar 3 08:40:37 2023 +0100

    sw floattable: fix handling of multiple splits, i.e. table over 3 or more 
pages
    
    - fix SwAnchoredObject::FindAnchorCharFrame() and
      SwToContentAnchoredObjectPosition::CalcPosition() to not assume that the
      precede of a follow fly is the master fly, which is not true when the 
table
      is split to 3 pages. In this case we have a master, then a normal follow
      and finally a last follow
    
    - fix SwFrame::GetNextFlyLeaf() to split the right anchor, which may be
      a follow text frame now, and register the new fly in master text
      frame, not at the split position
    
    - improve SwTextFrame::HasNonLastSplitFlyDrawObj() to look at flys in
      the master, but only consider the ones which will be positioned
      relative to the current anchor. Previously this was easier, as a
      "non-last split fly" happened to be always a master text frame, but
      now we han have split flys which are neither a master nor a last follow
    
    - finally improve SwTextFrame::MakePos(), because the anchor on the 2nd
      page is calculated after the 2nd fly is positioned, so it was only
      positioned inside the 2nd page, but not at the correct position
    
    (cherry picked from commit 7c9acfe5baef275f07c185c6fedf8b6d62d88637)
    
    Change-Id: Ibecaf5fcf0b8c56c5109461a16dcca058231b660

diff --git a/sw/qa/core/layout/data/floattable-3pages.docx 
b/sw/qa/core/layout/data/floattable-3pages.docx
new file mode 100644
index 000000000000..d6cbdafc1945
Binary files /dev/null and b/sw/qa/core/layout/data/floattable-3pages.docx 
differ
diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx
index a41fb17ef54d..4b083914b74d 100644
--- a/sw/qa/core/layout/flycnt.cxx
+++ b/sw/qa/core/layout/flycnt.cxx
@@ -93,7 +93,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable)
     CPPUNIT_ASSERT(!pPage1Anchor->HasPara());
 }
 
-CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertoffset)
+CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertOffset)
 {
     // Given a document with a floattable, split on 2 pages and a positive 
vertical offset:
     std::shared_ptr<comphelper::ConfigurationChanges> pChanges(
@@ -145,6 +145,71 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertoffset)
     // i.e. the fly frame on the 2nd page was also shifted down in Writer, but 
not in Word.
     CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage2FlyTop - 
nPage2AnchorTop);
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSplitFly3Pages)
+{
+    // Given a document with a floattable, split on 3 pages:
+    std::shared_ptr<comphelper::ConfigurationChanges> pChanges(
+        comphelper::ConfigurationChanges::create());
+    
officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::set(true,
+                                                                               
         pChanges);
+    pChanges->commit();
+    comphelper::ScopeGuard g([pChanges] {
+        
officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::set(
+            false, pChanges);
+        pChanges->commit();
+    });
+    createSwDoc("floattable-3pages.docx");
+
+    // When laying out that document:
+    calcLayout();
+
+    // Then make sure that row 1, 2 & 3 go to page 1, 2 & 3, while the table 
is floating:
+    SwDoc* pDoc = getSwDoc();
+    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
+    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);
+    auto pPage1Anchor = 
dynamic_cast<SwTextFrame*>(pPage1->FindLastBodyContent());
+    CPPUNIT_ASSERT(pPage1Anchor);
+    SwTwips nPage1AnchorTop = pPage1Anchor->getFrameArea().Top();
+    SwTwips nPage1FlyTop = pPage1Fly->getFrameArea().Top();
+    // The vert offset should be there on the first page:
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(1135), nPage1FlyTop - 
nPage1AnchorTop);
+    // Second page:
+    auto pPage2 = dynamic_cast<SwPageFrame*>(pPage1->GetNext());
+    CPPUNIT_ASSERT(pPage2);
+    const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 1
+    // - Actual  : 2
+    // i.e. both the 2nd and 3rd fly was anchored on page 2.
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs.size());
+    auto pPage2Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage2Objs[0]);
+    CPPUNIT_ASSERT(pPage2Fly);
+    auto pPage2Anchor = 
dynamic_cast<SwTextFrame*>(pPage2->FindLastBodyContent());
+    CPPUNIT_ASSERT(pPage2Anchor);
+    SwTwips nPage2AnchorTop = pPage2Anchor->getFrameArea().Top();
+    SwTwips nPage2FlyTop = pPage2Fly->getFrameArea().Top();
+    // No vert offset on the second page:
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage2FlyTop - 
nPage2AnchorTop);
+    // 3rd page:
+    auto pPage3 = dynamic_cast<SwPageFrame*>(pPage1->GetNext());
+    CPPUNIT_ASSERT(pPage3);
+    const SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs.size());
+    auto pPage3Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage3Objs[0]);
+    CPPUNIT_ASSERT(pPage3Fly);
+    auto pPage3Anchor = 
dynamic_cast<SwTextFrame*>(pPage3->FindLastBodyContent());
+    CPPUNIT_ASSERT(pPage3Anchor);
+    SwTwips nPage3AnchorTop = pPage3Anchor->getFrameArea().Top();
+    SwTwips nPage3FlyTop = pPage3Fly->getFrameArea().Top();
+    // No vert offset on the 3rd page:
+    CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage3FlyTop - 
nPage3AnchorTop);
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/layout/anchoredobject.cxx 
b/sw/source/core/layout/anchoredobject.cxx
index 4272437f9e25..5f8a307867bf 100644
--- a/sw/source/core/layout/anchoredobject.cxx
+++ b/sw/source/core/layout/anchoredobject.cxx
@@ -724,14 +724,24 @@ SwTextFrame* SwAnchoredObject::FindAnchorCharFrame()
             if (pFlyFrame->IsFlySplitAllowed())
             {
                 auto pFlyAtContentFrame = 
static_cast<SwFlyAtContentFrame*>(pFlyFrame);
-                if (pFlyAtContentFrame->GetPrecede())
+                SwFlyAtContentFrame* pFly = pFlyAtContentFrame;
+                SwTextFrame* pAnchor = 
static_cast<SwTextFrame*>(AnchorFrame());
+                // If we have to jump back N frames to find the master fly, 
then we have to step N
+                // frames from the master anchor to reach the correct follow 
anchor.
+                while (pFly->GetPrecede())
                 {
-                    SwTextFrame* 
pFrame(static_cast<SwTextFrame*>(AnchorFrame()));
-                    const SwTextFrame* pFollow = pFrame->GetFollow();
-                    if (pFollow)
+                    pFly = pFly->GetPrecede();
+                    if (!pAnchor)
                     {
-                        pAnchorCharFrame = const_cast<SwTextFrame*>(pFollow);
+                        SAL_WARN("sw.layout", 
"SwAnchoredObject::FindAnchorCharFrame: fly chain "
+                                              "length is longer then anchor 
chain length");
+                        break;
                     }
+                    pAnchor = pAnchor->GetFollow();
+                }
+                if (pAnchor)
+                {
+                    pAnchorCharFrame = pAnchor;
                 }
             }
         }
diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx
index b18cb6a21cb5..e05370e46750 100644
--- a/sw/source/core/layout/flycnt.cxx
+++ b/sw/source/core/layout/flycnt.cxx
@@ -1600,19 +1600,22 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType 
eMakePage )
     if( pLayLeaf )
     {
         SwFlyAtContentFrame* pNew = nullptr;
-        SwFrame* pFlyAnchor = const_cast<SwFrame*>(pFly->GetAnchorFrame());
-        if (pFlyAnchor && pFlyAnchor->IsTextFrame())
+        // Find the anchor frame to split.
+        SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
+        if (pFlyAnchor)
         {
             // Split the anchor at char 0: all the content goes to the follow 
of the anchor.
-            auto pFlyAnchorTextFrame = static_cast<SwTextFrame*>(pFlyAnchor);
-            pFlyAnchorTextFrame->SplitFrame(TextFrameIndex(0));
+            pFlyAnchor->SplitFrame(TextFrameIndex(0));
             auto pNext = static_cast<SwTextFrame*>(pFlyAnchor->GetNext());
             pNext->MoveSubTree(pLayLeaf);
 
-            // Now create the follow of the fly and anchor it in the just 
created follow of the
-            // anchor.
+            // Now create the follow of the fly and anchor it in the master of 
the anchor.
             pNew = new SwFlyAtContentFrame(*pFly);
-            pFlyAnchorTextFrame->AppendFly(pNew);
+            while (pFlyAnchor->IsFollow())
+            {
+                pFlyAnchor = pFlyAnchor->FindMaster();
+            }
+            pFlyAnchor->AppendFly(pNew);
         }
         pLayLeaf = pNew;
     }
diff --git a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx 
b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
index 1a58d3a650dd..8756da6a325c 100644
--- a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
+++ b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
@@ -236,15 +236,26 @@ void SwToContentAnchoredObjectPosition::CalcPosition()
             if (pFlyFrame->IsFlySplitAllowed())
             {
                 auto pFlyAtContentFrame = 
static_cast<SwFlyAtContentFrame*>(pFlyFrame);
-                if (pFlyAtContentFrame->GetPrecede())
+                // Decrement pFly to point to the master; increment pAnchor to 
point to the correct
+                // follow anchor.
+                SwFlyAtContentFrame* pFly = pFlyAtContentFrame;
+                SwTextFrame* pAnchor = 
const_cast<SwTextFrame*>(&rAnchorTextFrame);
+                while (pFly->GetPrecede())
                 {
-                    const SwTextFrame* pFollow = rAnchorTextFrame.GetFollow();
-                    if (pFollow)
+                    pFly = pFly->GetPrecede();
+                    if (!pAnchor)
                     {
-                        pOrientFrame = pFollow;
-                        // Anchored object has a precede, so it's a follow.
-                        bFollowSplitFly = true;
+                        SAL_WARN("sw.core", 
"SwToContentAnchoredObjectPosition::CalcPosition: fly "
+                                            "chain length is longer then 
anchor chain length");
+                        break;
                     }
+                    pAnchor = pAnchor->GetFollow();
+                }
+                if (pAnchor && pAnchor->GetPrecede())
+                {
+                    pOrientFrame = pAnchor;
+                    // Anchored object has a precede, so it's a follow.
+                    bFollowSplitFly = true;
                 }
             }
         }
diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx
index 13ca7f71494c..d52bdccdef18 100644
--- a/sw/source/core/text/frmform.cxx
+++ b/sw/source/core/text/frmform.cxx
@@ -341,6 +341,14 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const 
nTextOfst)
 void SwTextFrame::MakePos()
 {
     SwFrame::MakePos();
+
+    for (const auto& pFly : GetSplitFlyDrawObjs())
+    {
+        // Possibly this fly was positioned relative to us, invalidate its 
position now that our
+        // position is changed.
+        pFly->InvalidatePos();
+    }
+
     // Inform LOK clients about change in position of redlines (if any)
     if(!comphelper::LibreOfficeKit::isActive())
         return;
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index 995481ceb97e..feddd1a2347f 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -1496,8 +1496,21 @@ bool SwTextFrame::HasNonLastSplitFlyDrawObj() const
     // At this point we know what we're part of a chain that is an anchor for 
split fly frames, but
     // we're not the last one. See if we have a matching fly.
 
-    for (const auto& pFly : GetSplitFlyDrawObjs())
+    // Look up the master of the anchor.
+    const SwTextFrame* pAnchor = this;
+    while (pAnchor->IsFollow())
     {
+        pAnchor = pAnchor->FindMaster();
+    }
+    for (const auto& pFly : pAnchor->GetSplitFlyDrawObjs())
+    {
+        // Nominally all flys are anchored in the master; see if this fly is 
effectively anchored in
+        // us.
+        SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
+        if (pFlyAnchor != pAnchor)
+        {
+            continue;
+        }
         if (pFly->GetFollow())
         {
             return true;
commit 217e0862da8d5055761aa227221a3002320adb95
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Feb 17 10:14:31 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Mar 3 08:37:54 2023 +0100

    sw floattable: it's fine to recalc the table in SwTabFrame::MakeAll()
    
    Avoids this warning:
    
    warn:legacy.osl:22791:22791:sw/source/core/layout/tabfrm.cxx:2751: debug 
assertion: <SwTabFrame::MakeAll()> - format of table lowers suppressed by fix 
i44910
    
    I assume that the intention is that flys first format their content and
    then grow as necessary, so there is no risk to call lcl_RecalcTable() in
    the floattable case. (Parent will grow, then split if necessary.)
    
    (cherry picked from commit e11e1d48abedf17db40c069d9f37b4edcbcc09c4)
    
    Change-Id: Ib15d2d56066b695010c76f2052114082678c2e9b

diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx
index b95f8ca45b25..177bfa947fb2 100644
--- a/sw/source/core/layout/tabfrm.cxx
+++ b/sw/source/core/layout/tabfrm.cxx
@@ -2736,7 +2736,13 @@ void SwTabFrame::MakeAll(vcl::RenderContext* 
pRenderContext)
             // allowed to split.
             SwTwips nDistToUpperPrtBottom =
                 aRectFnSet.BottomDist( getFrameArea(), 
aRectFnSet.GetPrtBottom(*GetUpper()));
-            if ( nDistToUpperPrtBottom >= 0 || bTryToSplit )
+            bool bFlySplit = false;
+            if (GetUpper()->IsFlyFrame())
+            {
+                auto pUpperFly = static_cast<SwFlyFrame*>(GetUpper());
+                bFlySplit = pUpperFly->IsFlySplitAllowed();
+            }
+            if ( nDistToUpperPrtBottom >= 0 || bTryToSplit || bFlySplit )
             {
                 lcl_RecalcTable( *this, nullptr, aNotify );
                 m_bLowersFormatted = true;

Reply via email to