Related: tdf#171708 sw: keep floattable clear of a header/footer object

To see the problem, open that bug's document,
https://bugs.documentfoundation.org/attachment.cgi?id=206628 - its
body starts with a floating table spanning three pages (w:tblpPr,
w:tblOverlap val="never"), and its footer holds a picture with
<wp:wrapTopAndBottom/> and a negative positionV offset, so the picture
reaches up into the body. Writer put every page's part of the table at
the top of the body and painted its last rows over the picture, and it
left the table lower on the page than Word leaves it.

What Word does, measured from its print output over hand-authored probes
with exact row heights:

 - how much of the table goes on a page comes from the full body height,
   with nothing in the way taken into account;
 - the part is then shifted up as a whole until its bottom clears the
   object below it, which may take it into the top margin area;
 - the page edge is the limit - where clearing the object would take the
   part off the page, it stays put and keeps the overlap.

The low placement and the overlap are therefore one behaviour: the shift
is what avoids the object. CalcOverlap already avoids overlaps for split
flys, but a header/footer object is anchored outside the body and so was
dismissed as a fly from another upper, and the one conflict it knows how
to resolve is by moving down - which here would push the fly off the
page.

So consider such an object too, and resolve a conflict with one that
starts below the fly by shifting the fly up until its bottom clears it,
as far as the page frame allows. Where the page cannot hold the shifted
fly, keep the overlap, and in no case give up a row to fit.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I00eed5401b5981b0f3eb9b40e7be920bf71b15b3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8826
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit b356ac63ed2824e20906b26f337d7852994c1a6b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209266
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Mike Kaganski
2026-08-18 17:46:49 +02:00
committed by Xisco Fauli
parent 139f436078
commit 0b151e25c8
4 changed files with 83 additions and 2 deletions
+53
View File
@@ -2447,6 +2447,59 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf171708)
u"Content after the table.");
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testFloatTableOverFooterObject)
{
// A floating table split over three pages, and a footer picture reaching up into the body of
// every page.
createSwDoc("floattable-footer-object.docx");
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
CPPUNIT_ASSERT(pXmlDoc);
CPPUNIT_ASSERT_EQUAL(4, countXPathNodes(pXmlDoc, "//page"));
// All three parts of the fly are dumped below the master anchor, on page 1.
const sal_Int32 nFlyBottom
= getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/infos/bounds", "bottom").toInt32();
const sal_Int32 nFlyTop
= getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/infos/bounds", "top").toInt32();
const sal_Int32 nPictureTop
= getXPath(pXmlDoc, "//page[1]/footer/txt/anchored/fly/infos/bounds", "top").toInt32();
const sal_Int32 nBodyTop = getXPath(pXmlDoc, "//page[1]/body/infos/bounds", "top").toInt32();
// Without the fix, the fly stayed at the top of the body and ran over the picture: it was
// 1725..7235 against a picture at 6059.
CPPUNIT_ASSERT_LESS(nPictureTop, nFlyBottom);
// It clears the picture by reaching into the top margin area, as Word does, rather than by
// giving up a row.
CPPUNIT_ASSERT_LESS(nBodyTop, nFlyTop);
CPPUNIT_ASSERT_EQUAL(5, countXPathNodes(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/tab/row"));
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testFloatTableOverFooterObjectNoFit)
{
// Same as testFloatTableOverFooterObject, but the picture reaches so far up that clearing it
// would take the fly off the page.
createSwDoc("floattable-footer-object-nofit.docx");
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
CPPUNIT_ASSERT(pXmlDoc);
CPPUNIT_ASSERT_EQUAL(4, countXPathNodes(pXmlDoc, "//page"));
const sal_Int32 nFlyTop
= getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/infos/bounds", "top").toInt32();
const sal_Int32 nFlyBottom
= getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/infos/bounds", "bottom").toInt32();
const sal_Int32 nPictureTop
= getXPath(pXmlDoc, "//page[1]/footer/txt/anchored/fly/infos/bounds", "top").toInt32();
const sal_Int32 nBodyTop = getXPath(pXmlDoc, "//page[1]/body/infos/bounds", "top").toInt32();
// There is nowhere to shift to, so the fly keeps its place and the overlap, rather than
// dropping a row or climbing off the page. Word does the same.
CPPUNIT_ASSERT_GREATEREQUAL(nBodyTop, nFlyTop);
CPPUNIT_ASSERT_GREATER(nPictureTop, nFlyBottom);
CPPUNIT_ASSERT_EQUAL(5, countXPathNodes(pXmlDoc, "//page[1]/body/txt/anchored/fly[1]/tab/row"));
}
} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
@@ -1276,6 +1276,10 @@ void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr
continue;
}
// Set for an object which the header or footer pushes into the body: it is not part of the
// body's flow, so it cannot be resolved by moving down past it.
bool bObjectFromHeaderOrFooter = false;
if (bSplitFly)
{
SwFlyFrame* pAnchoredObjFly = pAnchoredObj->DynCastFlyFrame();
@@ -1303,8 +1307,13 @@ void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr
if (pAnchoredObjFlyAnchor && pAnchoredObjFlyAnchor->GetUpper() != pAnchorUpper)
{
// A fly overlapping with a fly from another upper is fine.
continue;
// A fly overlapping with a fly from another upper is fine, except for a header or
// footer object reaching into the body we are in.
if (pAnchorUpper && pAnchorUpper->IsInDocBody()
&& pAnchoredObjFlyAnchor->FindFooterOrHeader())
bObjectFromHeaderOrFooter = true;
else
continue;
}
bool bAnchoredObjFlyAnchorInTable
@@ -1331,6 +1340,25 @@ void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr
continue;
}
if (bObjectFromHeaderOrFooter
&& pAnchoredObj->GetObjRect().Top() > GetAnchoredObj().GetObjRect().Top())
{
// The other object starts below us, so shifting down would push us off the page:
// shift up instead, until our bottom clears it. The page is the limit - a split fly
// may reach into the top margin area, but not past the page edge; where even that is
// not enough, keep the overlap.
const SwTwips nUp
= GetAnchoredObj().GetObjRect().Bottom() - pAnchoredObj->GetObjRect().Top() + 1;
const SwPageFrame* pPageFrame = pAnchorFrameForVertPos->FindPageFrame();
if (pPageFrame
&& GetAnchoredObj().GetObjRect().Top() - nUp >= pPageFrame->getFrameArea().Top())
{
rRelPos.setY(rRelPos.getY() - nUp);
GetAnchoredObj().SetObjTop(nTopOfAnch + rRelPos.Y());
}
continue;
}
// Already formatted, overlaps: resolve the conflict by shifting ourselves down.
SwTwips nYDiff = pAnchoredObj->GetObjRect().Bottom() - GetAnchoredObj().GetObjRect().Top();
rRelPos.setY(rRelPos.getY() + nYDiff + 1);