sw/qa/core/layout/data/floattable-move-master.docx                 |binary
 sw/qa/core/layout/flycnt.cxx                                       |   42 
++++++++++
 sw/source/core/inc/rootfrm.hxx                                     |    1 
 sw/source/core/inc/txtfrm.hxx                                      |    2 
 sw/source/core/layout/calcmove.cxx                                 |   13 +++
 sw/source/core/layout/flycnt.cxx                                   |   27 
++++++
 sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx |    6 +
 sw/source/core/text/itratr.cxx                                     |   10 +-
 8 files changed, 95 insertions(+), 6 deletions(-)

New commits:
commit cfe9c68a7a19dd77d1fcbde3a7dd75730634becc
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Sep 21 08:31:01 2023 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Thu Sep 21 09:46:28 2023 +0200

    tdf#157119 sw floattable: fix moving master of split fly to next page
    
    The problem with the bugdoc is that in case you split the text frame
    hosting the start of the anchor text, then the fly will be split in a
    broken way on pages 1 -> 4 -> 2, while we want a split on just pages 2
    -> 3.
    
    There were several problems here:
    
    1) We created an unnecessary follow fly in SwFrame::GetNextFlyLeaf():
       when page 1 wanted to split, we discarded the flys on page 2 and 3,
       because the original anchor was in the body text and the flys on page
       2/3 was in a fly, so that's rejected. This resulted in a follow fly on
       page 4, which is not correct. Fix this by using an existing follow if
       possible, this is similar to what SwFrame::GetNextSctLeaf() does.
    
    2) SwFlyAtContentFrame::DelEmpty() broke the invariant that in case the
       fly is split to N pieces then the anchor chain's first N frame is
       matching that. Fix this by joining the unwanted anchor with its
       follow right before unlinking the to-be-deleted fly from the fly chain.
    
    3) SwToContentAnchoredObjectPosition::CalcOverlap() tried to shift down
       flys due to overlapping with the to-be-deleted frames, fix this by
       ignoring flys which are already in the to-delete list.
    
    4) SwContentFrame::ShouldBwdMoved() tried to move the master fly back
       from page 2 to page 1, which makes no sense, since there is not
       enough space there to lay out the fly master correctly there.
    
       Normally we only check if the paragraph fits the remaining space on
       the previous page, and this is wanted: if there is a normal to-para
       anchored image that would not fit, we simply shift up the image. But
       this is not wanted to floating tables, since here the anchor's only
       purpose is to host the start of the fly chain at a correct position.
    
       Fix this by checking not only for the text frame height vs the
       available space, but also the height of our (only) anchored object.
    
    Note that 3) is not a huge problem, it just causes some extra cycles for
    the layout (erase the to-delete flys, then position flys once more), do
    it because things are complicated enough already.
    
    Change-Id: I6f2c9d479125309d16b95df0236715c9353e8ba0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/157133
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/qa/core/layout/data/floattable-move-master.docx 
b/sw/qa/core/layout/data/floattable-move-master.docx
new file mode 100644
index 000000000000..03087eb358a8
Binary files /dev/null and b/sw/qa/core/layout/data/floattable-move-master.docx 
differ
diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx
index 73a107c4bc5e..f5f5fb094a37 100644
--- a/sw/qa/core/layout/flycnt.cxx
+++ b/sw/qa/core/layout/flycnt.cxx
@@ -1103,6 +1103,48 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNestedOverlap)
     SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs();
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage2Objs.size());
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testSplitFlyMoveMaster)
+{
+    // Given a document with a multi-page floating table on pages 1 -> 2 -> 3:
+    createSwDoc("floattable-move-master.docx");
+
+    // When adding an empty para before the table, so the table gets shifted 
to pages 2 -> 3:
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    pWrtShell->Down(/*bSelect=*/false, /*nCount=*/4);
+    pWrtShell->SplitNode();
+
+    // Then make sure page 1 has no flys, page 2 and 3 has the split fly and 
no flys on page 4:
+    SwDoc* pDoc = getSwDoc();
+    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
+    auto pPage1 = pLayout->Lower()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage1);
+    // Without the accompanying fix in place, this test would have failed, the 
start of the fly was
+    // still on page 1 instead of page 2.
+    CPPUNIT_ASSERT(!pPage1->GetSortedObjs());
+    auto pPage2 = pPage1->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage2);
+    CPPUNIT_ASSERT(pPage2->GetSortedObjs());
+    SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs.size());
+    auto pPage2Fly = 
rPage2Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+    CPPUNIT_ASSERT(pPage2Fly);
+    CPPUNIT_ASSERT(!pPage2Fly->GetPrecede());
+    CPPUNIT_ASSERT(pPage2Fly->HasFollow());
+    auto pPage3 = pPage2->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage3);
+    CPPUNIT_ASSERT(pPage3->GetSortedObjs());
+    SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs.size());
+    auto pPage3Fly = 
rPage3Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
+    CPPUNIT_ASSERT(pPage3Fly);
+    CPPUNIT_ASSERT(pPage3Fly->GetPrecede());
+    CPPUNIT_ASSERT(!pPage3Fly->GetFollow());
+    auto pPage4 = pPage3->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(pPage4);
+    CPPUNIT_ASSERT(!pPage4->GetSortedObjs());
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/rootfrm.hxx b/sw/source/core/inc/rootfrm.hxx
index 80e104c9c63b..29f813360d82 100644
--- a/sw/source/core/inc/rootfrm.hxx
+++ b/sw/source/core/inc/rootfrm.hxx
@@ -395,6 +395,7 @@ public:
 #ifdef DBG_UTIL
     bool IsInDelList( SwSectionFrame* pSct ) const;
 #endif
+    bool IsInFlyDelList( SwFlyFrame* pFly ) const;
 
     void SetCallbackActionEnabled( bool b ) { mbCallbackActionEnabled = b; }
     bool IsCallbackActionEnabled() const    { return mbCallbackActionEnabled; }
diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx
index 2cfa91e88dca..d73c2efa499f 100644
--- a/sw/source/core/inc/txtfrm.hxx
+++ b/sw/source/core/inc/txtfrm.hxx
@@ -796,7 +796,7 @@ public:
 
     /// 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;
+    SwFlyAtContentFrame* HasNonLastSplitFlyDrawObj() const;
 
     /// This text frame has a follow and the text frame don't contain text. 
Additionally one split
     /// fly is anchored to the text frame.
diff --git a/sw/source/core/layout/calcmove.cxx 
b/sw/source/core/layout/calcmove.cxx
index 5fb30d5c7afa..4d4b2de28994 100644
--- a/sw/source/core/layout/calcmove.cxx
+++ b/sw/source/core/layout/calcmove.cxx
@@ -162,6 +162,19 @@ bool SwContentFrame::ShouldBwdMoved( SwLayoutFrame 
*pNewUpper, bool & )
                      !pNewUpper->GetUpper()->GetNext() ) ) ) )
                 nSpace += pNewUpper->Grow( LONG_MAX, true );
 
+            auto pTextFrame = DynCastTextFrame();
+            if (pTextFrame)
+            {
+                // This is a text frame. Check if it's an anchor for a 
non-last element in a split
+                // fly chain. If so, we can only move back in case not only 
the text frame itself,
+                // but also its fly fits nSpace.
+                SwFlyAtContentFrame* pFly = 
pTextFrame->HasNonLastSplitFlyDrawObj();
+                if (pFly && pFly->getFrameArea().Height() > nSpace)
+                {
+                    return false;
+                }
+            }
+
             if ( nMoveAnyway < 3 )
             {
                 if ( nSpace )
diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx
index 2cba4e295728..907444efc321 100644
--- a/sw/source/core/layout/flycnt.cxx
+++ b/sw/source/core/layout/flycnt.cxx
@@ -1554,6 +1554,12 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType 
eMakePage )
     assert(pFly && "GetNextFlyLeaf: missing fly frame");
     assert(pFly->IsFlySplitAllowed() && "GetNextFlyLeaf: fly split not 
allowed");
 
+    if (pFly->HasFollow())
+    {
+        // If we already have a follow, then no need to create a new one, just 
use it.
+        return pFly->GetFollow();
+    }
+
     SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
 
     if (!pFlyAnchor)
@@ -1701,6 +1707,16 @@ void SwRootFrame::DeleteEmptyFlys_()
     }
 }
 
+bool SwRootFrame::IsInFlyDelList( SwFlyFrame* pFly ) const
+{
+    if (!mpFlyDestroy)
+    {
+        return false;
+    }
+
+    return mpFlyDestroy->find(pFly) != mpFlyDestroy->end();
+}
+
 const SwFlyAtContentFrame* SwFlyAtContentFrame::GetPrecede() const
 {
     return static_cast<const SwFlyAtContentFrame*>(SwFlowFrame::GetPrecede());
@@ -1721,6 +1737,17 @@ void SwFlyAtContentFrame::DelEmpty()
             // The anchor has a precede: invalidate it so that JoinFrame() is 
called on it.
             pAnchorPrecede->GetFrame().InvalidateSize();
         }
+        else if (SwTextFrame* pAnchorFollow = pAnchor->GetFollow())
+        {
+            // No precede, but has a follow. Then move the master just before 
the follow and join.
+            // This way the anchor chain and the fly chain will match even 
after clearing this
+            // frame's follow pointer.
+            if (pAnchorFollow != pAnchor->GetNext())
+            {
+                pAnchor->MoveSubTree(pAnchorFollow->GetUpper(), pAnchorFollow);
+                pAnchor->JoinFrame();
+            }
+        }
     }
 
     SwFlyAtContentFrame* pMaster = IsFollow() ? GetPrecede() : nullptr;
diff --git a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx 
b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
index b83d979d31b7..69951ba08a2c 100644
--- a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
+++ b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx
@@ -1255,6 +1255,12 @@ void 
SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr
                 continue;
             }
 
+            if 
(pAnchoredObjFly->getRootFrame()->IsInFlyDelList(pAnchoredObjFly))
+            {
+                // A fly overlapping with a to-be-deleted fly is fine.
+                continue;
+            }
+
             SwFrame* pAnchoredObjFlyAnchor = 
pAnchoredObjFly->GetAnchorFrameContainingAnchPos();
             if (pAnchoredObjFlyAnchor && pAnchoredObjFlyAnchor->IsInFly())
             {
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index 97425515b0a0..358d6d5068f3 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -1480,17 +1480,17 @@ std::vector<SwFlyAtContentFrame*> 
SwTextFrame::GetSplitFlyDrawObjs() const
     return aObjs;
 }
 
-bool SwTextFrame::HasNonLastSplitFlyDrawObj() const
+SwFlyAtContentFrame* SwTextFrame::HasNonLastSplitFlyDrawObj() const
 {
     const SwTextFrame* pFollow = GetFollow();
     if (!pFollow)
     {
-        return false;
+        return nullptr;
     }
 
     if (mnOffset != pFollow->GetOffset())
     {
-        return false;
+        return nullptr;
     }
 
     // At this point we know what we're part of a chain that is an anchor for 
split fly frames, but
@@ -1513,11 +1513,11 @@ bool SwTextFrame::HasNonLastSplitFlyDrawObj() const
         }
         if (pFly->GetFollow())
         {
-            return true;
+            return pFly;
         }
     }
 
-    return false;
+    return nullptr;
 }
 
 bool SwTextFrame::IsEmptyMasterWithSplitFly() const

Reply via email to