sw floattable: handle Word 2010 legacy mode in SwFlyFrame

The tdf#61594 bugdoc has 3 non-header rows on the first page, while
Writer splits the 3rd row, so the follow row goes to the 2nd page.

It turns out that the 3rd row fits the first page in Word because >=
2013 matches Writer, but <= 2010 used to behave differently, see
<https://answers.microsoft.com/en-us/msoffice/forum/all/floating-tables-in-word-2013-will-not-extend/576962cc-392d-43e5-b603-654406e1969f>.
(The spec doesn't seem to document this, sadly.)

Fix the problem by using one of the existing compat flags that we only
set in the Word <= 2010 case (triggered by <w:compatSetting
w:name="compatibilityMode" w:val="14"> or lower values), that scenario
wants to allow the fly frame to extend till the bottom of the page, not
only till the bottom of the body frame print area.

The bugdoc now doesn't split that 3rd row, but the layout of the 4th row
is still bad with this.

Change-Id: I547abb19df250e5fbe4d8e123593a645cdbdc6b0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148751
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
diff --git a/sw/qa/core/layout/data/floattable-compat14.docx b/sw/qa/core/layout/data/floattable-compat14.docx
new file mode 100644
index 0000000..5afb0af
--- /dev/null
+++ b/sw/qa/core/layout/data/floattable-compat14.docx
Binary files differ
diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx
index d76a344..079030d 100644
--- a/sw/qa/core/layout/flycnt.cxx
+++ b/sw/qa/core/layout/flycnt.cxx
@@ -28,6 +28,7 @@
#include <itabenum.hxx>
#include <frmmgr.hxx>
#include <frameformats.hxx>
#include <cellfrm.hxx>

namespace
{
@@ -475,6 +476,41 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWidow)
    // And then similarly this was 1, not 2.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uLong>(2), pText2->GetThisLines());
}

CPPUNIT_TEST_FIXTURE(Test, testSplitFlyCompat14)
{
    // Given a Word 2010 document with 2 pages, one table row each:
    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-compat14.docx");

    // When laying out that document:
    calcLayout();

    // Then make sure that the first row is entirely on page 1:
    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);
    SwFrame* pTab1 = pPage1Fly->GetLower();
    SwFrame* pRow1 = pTab1->GetLower();
    auto pCell1 = dynamic_cast<SwCellFrame*>(pRow1->GetLower());
    // Without the accompanying fix in place, this test would have failed, the first row was split,
    // but not in Word.
    CPPUNIT_ASSERT(!pCell1->GetFollowCell());
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/cellfrm.hxx b/sw/source/core/inc/cellfrm.hxx
index 2e34b8a..4fdbc70 100644
--- a/sw/source/core/inc/cellfrm.hxx
+++ b/sw/source/core/inc/cellfrm.hxx
@@ -27,7 +27,7 @@ struct SwCursorMoveState;
class SwBorderAttrs;

/// SwCellFrame is one table cell in the document layout.
class SwCellFrame final : public SwLayoutFrame
class SW_DLLPUBLIC SwCellFrame final : public SwLayoutFrame
{
    const SwTableBox* m_pTabBox;

diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx
index b1955e8..7f9256a 100644
--- a/sw/source/core/layout/fly.cxx
+++ b/sw/source/core/layout/fly.cxx
@@ -79,6 +79,38 @@

using namespace ::com::sun::star;

namespace
{
/// Gets the bottom position which is a deadline for a split fly.
SwTwips GetFlyAnchorBottom(SwFlyFrame* pFly, const SwFrame& rAnchor)
{
    SwRectFnSet aRectFnSet(pFly);

    const IDocumentSettingAccess& rIDSA = pFly->GetFrameFormat().getIDocumentSettingAccess();
    bool bLegacy = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN);
    if (bLegacy)
    {
        // Word <= 2010 style: the fly can overlap with the bottom margin / footer area.
        const SwFrame* pAnchorUpper = rAnchor.FindPageFrame();
        if (!pAnchorUpper)
        {
            return 0;
        }

        return aRectFnSet.GetBottom(pAnchorUpper->getFrameArea());
    }

    // Word >= 2013 style: the fly has to stay inside the body frame.
    const SwFrame* pAnchorUpper = rAnchor.GetUpper();
    if (!pAnchorUpper)
    {
        return 0;
    }

    return aRectFnSet.GetPrtBottom(*pAnchorUpper);
}
}

static SwTwips lcl_CalcAutoWidth( const SwLayoutFrame& rFrame );

SwFlyFrame::SwFlyFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch, bool bFollow ) :
@@ -1325,12 +1357,11 @@ void SwFlyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderA
                // then use that as the anchor for sizing purposes.
                pAnchor = pAnchorChar;
            }
            const SwFrame* pAnchorUpper = pAnchor ? pAnchor->GetUpper() : nullptr;
            if (pAnchorUpper && IsFlySplitAllowed())
            if (pAnchor && IsFlySplitAllowed())
            {
                // If the fly is allowed to be split, then limit its size to the upper of the
                // anchor.
                SwTwips nDeadline = aRectFnSet.GetPrtBottom(*pAnchorUpper);
                SwTwips nDeadline = GetFlyAnchorBottom(this, *pAnchor);
                SwTwips nTop = aRectFnSet.GetTop(getFrameArea());
                SwTwips nBottom = aRectFnSet.GetTop(getFrameArea()) + nRemaining;
                if (nBottom > nDeadline)
@@ -2064,10 +2095,9 @@ SwTwips SwFlyFrame::Grow_( SwTwips nDist, bool bTst )
                {
                    pAnchor = pAnchorChar;
                }
                const SwFrame* pAnchorUpper = pAnchor ? pAnchor->GetUpper() : nullptr;
                if (pAnchorUpper)
                if (pAnchor)
                {
                    SwTwips nDeadline = aRectFnSet.GetPrtBottom(*pAnchorUpper);
                    SwTwips nDeadline = GetFlyAnchorBottom(this, *pAnchor);
                    SwTwips nTop = aRectFnSet.GetTop(getFrameArea());
                    SwTwips nBottom = nTop + aRectFnSet.GetHeight(getFrameArea());
                    SwTwips nMaxGrow = nDeadline - nBottom;
@@ -2105,10 +2135,9 @@ SwTwips SwFlyFrame::Grow_( SwTwips nDist, bool bTst )
            {
                pAnchor = pAnchorChar;
            }
            const SwFrame* pAnchorUpper = pAnchor ? pAnchor->GetUpper() : nullptr;
            if (pAnchorUpper && IsFlySplitAllowed())
            if (pAnchor && IsFlySplitAllowed())
            {
                SwTwips nDeadline = aRectFnSet.GetPrtBottom(*pAnchorUpper);
                SwTwips nDeadline = GetFlyAnchorBottom(this, *pAnchor);
                SwTwips nTop = aRectFnSet.GetTop(getFrameArea());
                SwTwips nBottom = nTop + aRectFnSet.GetHeight(getFrameArea());
                // Calculate max grow and compare to the requested growth, adding to nDist may