tdf#158341 sw floattable: fix layout loop when fly is below the body frame

Regression from commit 25b8fdd3b939a221ba00ca37fbf89adaf893aab7 (sw
floattable: maintain the invariant that fly height is at least MINFLY,
2023-09-28), the document started to layout-loop on load.

What happens is we have a fly frame where the bottom of the body frame
is above both the top and bottom of the fly. We used to make sure these
flys don't "flip" (with a negative height) and ensure that their height
is still MINFLY. But that causes a new problem, because the layout will
try to make sure they fit, but they can't have enough space.

Fix the problem by improving the correction of the fly height, so in
case even the top is below the deadline, then we set the height to 0 and
explicitly mark the frame as clipped. That keeps the unwanted warnings
about violated invariants fixed and fixes the layout loop.

The test just ensures that all pages but the last one has a single
multi-page floating table, chained over several pages.

Change-Id: Ibac0a465839a59abe5ae49809c0d76c955aa39b9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160061
Reviewed-by: Miklos Vajna <[email protected]>
Tested-by: Jenkins
diff --git a/sw/CppunitTest_sw_core_layout.mk b/sw/CppunitTest_sw_core_layout.mk
index a499f53..b77cf51 100644
--- a/sw/CppunitTest_sw_core_layout.mk
+++ b/sw/CppunitTest_sw_core_layout.mk
@@ -14,6 +14,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,sw_core_layout))
$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_layout))

$(eval $(call gb_CppunitTest_add_exception_objects,sw_core_layout, \
    sw/qa/core/layout/fly \
    sw/qa/core/layout/flycnt \
    sw/qa/core/layout/frmtool \
    sw/qa/core/layout/ftnfrm \
diff --git a/sw/qa/core/layout/data/floattable-negative-height.docx b/sw/qa/core/layout/data/floattable-negative-height.docx
new file mode 100644
index 0000000..e5b6cd5
--- /dev/null
+++ b/sw/qa/core/layout/data/floattable-negative-height.docx
Binary files differ
diff --git a/sw/qa/core/layout/fly.cxx b/sw/qa/core/layout/fly.cxx
new file mode 100644
index 0000000..c1d5411
--- /dev/null
+++ b/sw/qa/core/layout/fly.cxx
@@ -0,0 +1,91 @@
/* -*- 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 <IDocumentLayoutAccess.hxx>
#include <anchoredobject.hxx>
#include <flyfrms.hxx>
#include <pagefrm.hxx>
#include <rootfrm.hxx>
#include <sortedobjs.hxx>
#include <docsh.hxx>
#include <wrtsh.hxx>

namespace
{
/// Covers sw/source/core/layout/fly.cxx fixes, i.e. mostly SwFlyFrame.
class Test : public SwModelTestBase
{
public:
    Test()
        : SwModelTestBase("/sw/qa/core/layout/data/")
    {
    }
};

CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNegativeHeight)
{
    // Given a document with complex enough content that a split fly frame temporarily moves below
    // the bottom of the body frame on a page:
    // When laying out the document, SwEditShell::CalcLayout() never returned:
    createSwDoc("floattable-negative-height.docx");

    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
    pWrtShell->Reformat();

    // Make sure that all the pages have the expected content:
    SwDoc* pDoc = getSwDoc();
    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    for (SwFrame* pFrame = pLayout->Lower(); pFrame; pFrame = pFrame->GetNext())
    {
        auto pPage = pFrame->DynCastPageFrame();
        if (!pPage->GetPrev())
        {
            // First page: start of the split fly chain:
            CPPUNIT_ASSERT(pPage->GetSortedObjs());
            SwSortedObjs& rPageObjs = *pPage->GetSortedObjs();
            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
            auto pFly = rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
            CPPUNIT_ASSERT(pFly);
            CPPUNIT_ASSERT(!pFly->GetPrecede());
            CPPUNIT_ASSERT(pFly->GetFollow());
        }
        else if (pPage->GetPrev() && pPage->GetNext() && pPage->GetNext()->GetNext())
        {
            // Middle pages: have a prevous and a next fly:
            CPPUNIT_ASSERT(pPage->GetSortedObjs());
            SwSortedObjs& rPageObjs = *pPage->GetSortedObjs();
            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
            auto pFly = rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
            CPPUNIT_ASSERT(pFly);
            CPPUNIT_ASSERT(pFly->GetPrecede());
            CPPUNIT_ASSERT(pFly->GetFollow());
        }
        else if (pPage->GetPrev() && pPage->GetNext() && !pPage->GetNext()->GetNext())
        {
            // Page last but one: end of the fly chain:
            CPPUNIT_ASSERT(pPage->GetSortedObjs());
            SwSortedObjs& rPageObjs = *pPage->GetSortedObjs();
            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPageObjs.size());
            auto pFly = rPageObjs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
            CPPUNIT_ASSERT(pFly);
            CPPUNIT_ASSERT(pFly->GetPrecede());
            CPPUNIT_ASSERT(!pFly->GetFollow());
        }
        else if (pPage->GetPrev() && !pPage->GetNext())
        {
            // Last page: no flys.
            CPPUNIT_ASSERT(!pPage->GetSortedObjs());
        }
    }
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx
index 8dad5a5..d23c30a 100644
--- a/sw/source/core/layout/fly.cxx
+++ b/sw/source/core/layout/fly.cxx
@@ -1441,9 +1441,19 @@ void SwFlyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderA
                SwTwips nDeadline = GetFlyAnchorBottom(this, *pAnchor);
                SwTwips nTop = aRectFnSet.GetTop(getFrameArea());
                SwTwips nBottom = aRectFnSet.GetTop(getFrameArea()) + nRemaining;
                if (nBottom > nDeadline && nDeadline > nTop)
                if (nBottom > nDeadline)
                {
                    nRemaining = nDeadline - nTop;
                    if (nDeadline > nTop)
                    {
                        nRemaining = nDeadline - nTop;
                    }
                    else
                    {
                        // Even the top is below the deadline, set size to empty and mark it as
                        // clipped so we re-format later.
                        nRemaining = 0;
                        m_bHeightClipped = true;
                    }
                }
            }