sw/CppunitTest_sw_core_text.mk | 1 sw/qa/core/text/data/floattable-negative-vert-offset-empty.docx |binary sw/qa/core/text/itratr.cxx | 58 ++++++++++ sw/source/core/inc/txtfrm.hxx | 4 sw/source/core/text/frmform.cxx | 9 + sw/source/core/text/itratr.cxx | 41 +++++++ sw/source/core/text/porrst.cxx | 7 + sw/source/core/text/widorp.cxx | 25 ---- 8 files changed, 124 insertions(+), 21 deletions(-)
New commits: commit 16b5b21d36da87be9b50235acbbb8008ed23b8bb Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Jul 3 08:30:21 2023 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Mon Jul 3 10:12:49 2023 +0200 sw floattable: fix negative vert offset with on page boundary without anch text The bugdoc has 3 floating tables on page 1, and the anchor of the last floating table goes to page 2. All anchors are empty (no text), apart from serving as an anchor for the floating tables. The last floating table goes to page 2 in Writer, but not in Word. The problem is quite similar to what commit 2d0a4ef1d83b8de6cb133970c2c35ae706fb058e (sw floattable: fix negative vertical offset handling on page boundary, 2023-06-20) fixed already, but here the anchor of the floating table is empty, and there it had some text, which is a different codepath. Fix the problem by first requesting a frame split in SwTextFrame::FormatAdjust() even for empty paragraphs with flys (so the fly part can go to page 1 and the empty paragraph can go to page 2) and second disabling the SwTextFrame::FormatEmpty() optimization that would assume no split is needed, while a split is required for a correct layout here. With this, the DOCX version of the original bnc#816603 document finally renders without overlaps (4 pages, 11 floating tables). Change-Id: Ie64ce92bf19b3dee8059fa14294d7599b41cc53f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153866 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/CppunitTest_sw_core_text.mk b/sw/CppunitTest_sw_core_text.mk index 79ac4a0320bf..a04824e08e07 100644 --- a/sw/CppunitTest_sw_core_text.mk +++ b/sw/CppunitTest_sw_core_text.mk @@ -15,6 +15,7 @@ $(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_text)) $(eval $(call gb_CppunitTest_add_exception_objects,sw_core_text, \ sw/qa/core/text/frmform \ + sw/qa/core/text/itratr \ sw/qa/core/text/text \ )) diff --git a/sw/qa/core/text/data/floattable-negative-vert-offset-empty.docx b/sw/qa/core/text/data/floattable-negative-vert-offset-empty.docx new file mode 100644 index 000000000000..132fde553825 Binary files /dev/null and b/sw/qa/core/text/data/floattable-negative-vert-offset-empty.docx differ diff --git a/sw/qa/core/text/itratr.cxx b/sw/qa/core/text/itratr.cxx new file mode 100644 index 000000000000..6913d0037dbd --- /dev/null +++ b/sw/qa/core/text/itratr.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <memory> + +#include <IDocumentLayoutAccess.hxx> +#include <doc.hxx> +#include <frame.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <sortedobjs.hxx> + +namespace +{ +/// Covers sw/source/core/text/itratr.cxx fixes. +class Test : public SwModelTestBase +{ +public: + Test() + : SwModelTestBase("/sw/qa/core/text/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testFloattableNegativeVertOffsetEmptyAnchor) +{ + // Given a document with 3 floating tables, all of them on page 1, but the anchor of the last + // floating table is on page 2, but that anchor has no own text: + createSwDoc("floattable-negative-vert-offset-empty.docx"); + + // When laying out that document: + calcLayout(); + + // Then make sure the that 3rd floating table is not shifted to page 2: + SwDoc* pDoc = getSwDoc(); + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage1); + CPPUNIT_ASSERT(pPage1->GetSortedObjs()); + const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 3 + // - Actual : 2 + // i.e. the last floating table was shifted to page 2, which lead to overlapping text in the + // original document. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rPage1Objs.size()); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index 090f0bc0c340..ca1b34239055 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -803,6 +803,10 @@ public: /// fly is anchored to the text frame. bool IsEmptyMasterWithSplitFly() const; + /// This text frame is not split, doesn't fit the upper, has a single split fly anchored to it + /// with a negative vertical offset. Such frames may need explicit splitting. + bool IsEmptyWithSplitFly() const; + static SwView* GetView(); virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 9398f28ad0ae..c2a280211a8f 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -1084,6 +1084,15 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, !rFrameBreak.IsInside( rLine ) ) : rFrameBreak.IsBreakNow( rLine ) ) ) ) ? 1 : 0; + + SwTextFormatInfo& rInf = rLine.GetInfo(); + if (nNew == 0 && !nStrLen && !rInf.GetTextFly().IsOn() && IsEmptyWithSplitFly()) + { + // Empty paragraph, so IsBreakNow() is not called, but we should split the fly portion and + // the paragraph marker. + nNew = 1; + } + // i#84870 // no split of text frame, which only contains an as-character anchored object bool bOnlyContainsAsCharAnchoredObj = diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index 176e06c4490e..97425515b0a0 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -1546,6 +1546,47 @@ bool SwTextFrame::IsEmptyMasterWithSplitFly() const return true; } +bool SwTextFrame::IsEmptyWithSplitFly() const +{ + if (IsFollow()) + { + return false; + } + + if (GetTextNodeFirst()->GetSwAttrSet().HasItem(RES_PAGEDESC)) + { + return false; + } + + if (getFrameArea().Bottom() <= GetUpper()->getFramePrintArea().Bottom()) + { + return false; + } + + // This is a master that doesn't fit the current parent. + if (!m_pDrawObjs || m_pDrawObjs->size() != 1) + { + return false; + } + + SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame(); + if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed()) + { + return false; + } + + // It has a split fly anchored to it. + if (pFlyFrame->GetFrameFormat().GetVertOrient().GetPos() >= 0) + { + return false; + } + + // Negative vertical offset means that visually it already may have a first line. + // Consider that, we may need to split the frame, so the fly frame is on one page and the empty + // paragraph's frame is on a next page. + return true; +} + SwTwips SwTextNode::GetWidthOfLeadingTabs() const { SwTwips nRet = 0; diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx index 5c04b60b925e..255318119f87 100644 --- a/sw/source/core/text/porrst.cxx +++ b/sw/source/core/text/porrst.cxx @@ -439,6 +439,13 @@ bool SwTextFrame::FormatEmpty() aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) && !bHasNonLastSplitFlyDrawObj ) return false; + if (IsEmptyWithSplitFly()) + { + // We don't want this optimization in case the paragraph is not really empty, because it has + // a fly frame and it also needs space for the empty paragraph in a next line. + return false; + } + // only need to check one node because of early return on GetMerged() for (SwContentIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) diff --git a/sw/source/core/text/widorp.cxx b/sw/source/core/text/widorp.cxx index 72903502749e..8a66e40f8234 100644 --- a/sw/source/core/text/widorp.cxx +++ b/sw/source/core/text/widorp.cxx @@ -241,28 +241,11 @@ bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine ) bool bFirstLine = 1 == rLine.GetLineNr() && !rLine.GetPrev(); m_bBreak = true; - if (bFirstLine) + if (bFirstLine && m_pFrame->IsEmptyWithSplitFly()) { - bool bFits = m_pFrame->getFrameArea().Bottom() <= m_pFrame->GetUpper()->getFramePrintArea().Bottom(); - if (!m_pFrame->IsFollow() && !bFits) - { - // This is a master that doesn't fit the current parent. - if (m_pFrame->GetDrawObjs() && m_pFrame->GetDrawObjs()->size() == 1) - { - SwFlyFrame* pFlyFrame = (*m_pFrame->GetDrawObjs())[0]->DynCastFlyFrame(); - if (pFlyFrame && pFlyFrame->IsFlySplitAllowed()) - { - // It has a split fly anchored to it. - if (pFlyFrame->GetFrameFormat().GetVertOrient().GetPos() < 0) - { - // Negative vertical offset means that visually it already has a - // previous line. Consider that, otherwise the split fly would move to - // the next page as well, which is not wanted. - bFirstLine = false; - } - } - } - } + // Not really the first line, visually we may have a previous line (including the fly + // frame) already. + bFirstLine = false; } if( ( bFirstLine && m_pFrame->GetIndPrev() )