sw/CppunitTest_sw_core_layout_flycnt.mk | 82 +++++++++++++++++++++++++++++++ sw/Module_sw.mk | 1 sw/inc/formatflysplit.hxx | 4 + sw/qa/core/layout/data/floattable.docx |binary sw/qa/core/layout/flycnt.cxx | 83 ++++++++++++++++++++++++++++++++ sw/source/core/attr/formatflysplit.cxx | 28 ++++++++++ sw/source/core/inc/flyfrms.hxx | 7 +- sw/source/core/inc/pagefrm.hxx | 3 - sw/source/core/inc/tabfrm.hxx | 4 + sw/source/core/inc/txtfrm.hxx | 8 ++- sw/source/core/layout/flycnt.cxx | 46 +++++++++++++++-- sw/source/core/text/frmform.cxx | 20 +++++-- sw/source/core/text/frmpaint.cxx | 3 - sw/source/core/text/itratr.cxx | 17 +++++- sw/source/core/text/itrform2.cxx | 12 ---- sw/source/core/text/porrst.cxx | 4 + sw/source/core/text/xmldump.cxx | 30 +++++++++++ 17 files changed, 319 insertions(+), 33 deletions(-)
New commits: commit ad4ea1df8260bf673f8717bb9066ec122abbb06c Author: Miklos Vajna <[email protected]> AuthorDate: Mon Feb 13 12:55:32 2023 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Wed Mar 1 08:48:19 2023 +0100 sw floattable: handle table-in-fly in SwFrame::GetNextFlyLeaf() Trying to lay out a split fly that contains a table resulted in a stack overflow. The reason for this was that SwObjectFormatter::FormatObjsAtFrame_() has a loop that will format all objects of the current fly frame, but we managed to anchor the follow fly into itself. Fix the problem by improving SwFrame::GetNextFlyLeaf(), somewhat based on how SwFrame::GetNextSctLeaf() has special cases for tables. This way once we split the fly frame, we'll try to move the follow anchor frame to the next page's body frame, and not to a child of the follow fly itself. Also add a first floattable testcase, we can already assert that the table is split correctly. Do this in a separate suite for now, since the pool's default SwFormatFlySplit is only created once, so SwFormatFlySplit::SetForce(false) doesn't have the wanted effect. The anchor's text is still on both pages, should be on page 2 only. (cherry picked from commit 995198bfff4ae8abaf2129fe99d9f8ef899a4f25) Change-Id: Ie2ce75dbace5d9716008351aedb6a8989036badb diff --git a/sw/CppunitTest_sw_core_layout_flycnt.mk b/sw/CppunitTest_sw_core_layout_flycnt.mk new file mode 100644 index 000000000000..c9668afbc9b8 --- /dev/null +++ b/sw/CppunitTest_sw_core_layout_flycnt.mk @@ -0,0 +1,82 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,sw_core_layout_flycnt)) + +$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_layout_flycnt)) + +# TODO merge this with sw_core_layout once SwFormatFlySplit::SetForce() is gone. +$(eval $(call gb_CppunitTest_add_exception_objects,sw_core_layout_flycnt, \ + sw/qa/core/layout/flycnt \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,sw_core_layout_flycnt, \ + editeng \ + comphelper \ + cppu \ + cppuhelper \ + sal \ + sfx \ + subsequenttest \ + sw \ + swqahelper \ + test \ + unotest \ + utl \ + vcl \ + svt \ + tl \ + svl \ + svxcore \ +)) + +$(eval $(call gb_CppunitTest_use_externals,sw_core_layout_flycnt,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_set_include,sw_core_layout_flycnt,\ + -I$(SRCDIR)/sw/inc \ + -I$(SRCDIR)/sw/source/core/inc \ + -I$(SRCDIR)/sw/source/uibase/inc \ + -I$(SRCDIR)/sw/qa/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_api,sw_core_layout_flycnt,\ + udkapi \ + offapi \ + oovbaapi \ +)) + +$(eval $(call gb_CppunitTest_use_ure,sw_core_layout_flycnt)) +$(eval $(call gb_CppunitTest_use_vcl,sw_core_layout_flycnt)) + +$(eval $(call gb_CppunitTest_use_rdb,sw_core_layout_flycnt,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,sw_core_layout_flycnt,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,sw_core_layout_flycnt)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,sw_core_layout_flycnt, \ + modules/swriter \ + svt \ + svx \ +)) + +# assert if font/glyph fallback occurs +$(eval $(call gb_CppunitTest_set_non_application_font_use,sw_core_layout_flycnt,abort)) + +$(eval $(call gb_CppunitTest_use_more_fonts,sw_core_layout_flycnt)) + +# vim: set noet sw=4 ts=4: diff --git a/sw/Module_sw.mk b/sw/Module_sw.mk index 44d5bc4e8d22..f875935dac9e 100644 --- a/sw/Module_sw.mk +++ b/sw/Module_sw.mk @@ -138,6 +138,7 @@ $(eval $(call gb_Module_add_slowcheck_targets,sw,\ CppunitTest_sw_uibase_wrtsh \ CppunitTest_sw_core_accessibilitycheck \ CppunitTest_sw_core_layout \ + CppunitTest_sw_core_layout_flycnt \ CppunitTest_sw_core_fields \ CppunitTest_sw_core_tox \ CppunitTest_sw_core_frmedt \ diff --git a/sw/inc/formatflysplit.hxx b/sw/inc/formatflysplit.hxx index 5f7dda675ed2..1db9587752b6 100644 --- a/sw/inc/formatflysplit.hxx +++ b/sw/inc/formatflysplit.hxx @@ -34,6 +34,10 @@ public: SwFormatFlySplit* Clone(SfxItemPool* pPool = nullptr) const override; void dumpAsXml(xmlTextWriterPtr pWriter) const override; + + // Force-enable for test purposes. + static void SetForce(bool bForce); + static bool GetForce(); }; inline const SwFormatFlySplit& SwAttrSet::GetFlySplit(bool bInP) const diff --git a/sw/qa/core/layout/data/floattable.docx b/sw/qa/core/layout/data/floattable.docx new file mode 100644 index 000000000000..ee1b029882dd Binary files /dev/null and b/sw/qa/core/layout/data/floattable.docx differ diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx new file mode 100644 index 000000000000..a006ecb54f00 --- /dev/null +++ b/sw/qa/core/layout/flycnt.cxx @@ -0,0 +1,83 @@ +/* -*- 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 <comphelper/scopeguard.hxx> + +#include <IDocumentLayoutAccess.hxx> +#include <anchoredobject.hxx> +#include <flyfrms.hxx> +#include <formatflysplit.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <sortedobjs.hxx> +#include <tabfrm.hxx> + +namespace +{ +/// Covers sw/source/core/layout/flycnt.cxx fixes, i.e. mostly SwFlyAtContentFrame. +class Test : public SwModelTestBase +{ +public: + Test() + : SwModelTestBase("/sw/qa/core/layout/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable) +{ + // Given a document with a multi-page floating table: + SwFormatFlySplit::SetForce(true); + comphelper::ScopeGuard g([] { SwFormatFlySplit::SetForce(false); }); + createSwDoc("floattable.docx"); + + // When laying out that document: + calcLayout(); + + // Then make sure that the first row goes to page 1 and the second row goes to page 2, while the + // table is floating: + SwDoc* pDoc = getSwDoc(); + // Without the accompanying fix in place, this test would have failed with a stack overflow + // because the follow frame of the anchor was moved into the follow frame of the fly, so the fly + // was anchored in itself. + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + // Page 1 has a master fly, which contains a master table: + 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); + CPPUNIT_ASSERT(!pPage1Fly->GetPrecede()); + CPPUNIT_ASSERT(pPage1Fly->GetFollow()); + auto pPage1Table = dynamic_cast<SwTabFrame*>(pPage1Fly->GetLower()); + CPPUNIT_ASSERT(pPage1Table); + CPPUNIT_ASSERT(!pPage1Table->GetPrecede()); + CPPUNIT_ASSERT(pPage1Table->GetFollow()); + // Page 2 has a follow fly, which contains a follow table: + auto pPage2 = dynamic_cast<SwPageFrame*>(pPage1->GetNext()); + CPPUNIT_ASSERT(pPage2); + const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs.size()); + auto pPage2Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage2Objs[0]); + CPPUNIT_ASSERT(pPage2Fly); + CPPUNIT_ASSERT(pPage2Fly->GetPrecede()); + CPPUNIT_ASSERT(!pPage2Fly->GetFollow()); + auto pPage2Table = dynamic_cast<SwTabFrame*>(pPage2Fly->GetLower()); + CPPUNIT_ASSERT(pPage2Table); + CPPUNIT_ASSERT(pPage2Table->GetPrecede()); + CPPUNIT_ASSERT(!pPage2Table->GetFollow()); +} +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/attr/formatflysplit.cxx b/sw/source/core/attr/formatflysplit.cxx index 904fd9a8bb5c..7bc8a75594d7 100644 --- a/sw/source/core/attr/formatflysplit.cxx +++ b/sw/source/core/attr/formatflysplit.cxx @@ -21,6 +21,8 @@ #include <libxml/xmlwriter.h> +static std::optional<bool> g_oForce; + SwFormatFlySplit::SwFormatFlySplit(bool bSplit) : SfxBoolItem(RES_FLY_SPLIT, bSplit) { @@ -34,9 +36,9 @@ SwFormatFlySplit::SwFormatFlySplit(bool bSplit) // // - Both the master fly and the follow flys need an anchor. At the same time, we want all text // of the anchor frame to be wrapped around the last follow fly frame, for Word compatibility. - // These are solved by splitting the anchor frame as many times as needed, always at text + // These are solved by splitting the anchor frame as many times as needed, always at // TextFrameIndex 0. - if (getenv("SW_FORCE_FLY_SPLIT")) + if (SwFormatFlySplit::GetForce()) { SetValue(true); } @@ -57,4 +59,16 @@ void SwFormatFlySplit::dumpAsXml(xmlTextWriterPtr pWriter) const (void)xmlTextWriterEndElement(pWriter); } +void SwFormatFlySplit::SetForce(bool bForce) { g_oForce = bForce; } + +bool SwFormatFlySplit::GetForce() +{ + if (g_oForce.has_value()) + { + return *g_oForce; + } + + return getenv("SW_FORCE_FLY_SPLIT") != nullptr; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/flyfrms.hxx b/sw/source/core/inc/flyfrms.hxx index 608e04ff8f11..8d3bd95589bc 100644 --- a/sw/source/core/inc/flyfrms.hxx +++ b/sw/source/core/inc/flyfrms.hxx @@ -21,6 +21,8 @@ #include <sal/config.h> +#include <swdllapi.h> + #include "flyfrm.hxx" #include "flowfrm.hxx" @@ -30,7 +32,7 @@ double getLocalFrameRotation_from_SwNoTextFrame(const SwNoTextFrame& rNoTextFram // Base class for those Flys that can "move freely" or better that are not // bound in Content. -class SwFlyFreeFrame : public SwFlyFrame +class SW_DLLPUBLIC SwFlyFreeFrame : public SwFlyFrame { private: // #i34753# - flag for at-page anchored Writer fly frames @@ -157,7 +159,7 @@ public: }; // Flys that are bound to Content but not in Content -class SwFlyAtContentFrame final: public SwFlyFreeFrame, public SwFlowFrame +class SW_DLLPUBLIC SwFlyAtContentFrame final: public SwFlyFreeFrame, public SwFlowFrame { virtual void MakeAll(vcl::RenderContext* pRenderContext) override; @@ -198,6 +200,7 @@ public: SwFlyAtContentFrame* GetFollow(); const SwFlyAtContentFrame* GetPrecede() const; SwFlyAtContentFrame* GetPrecede(); + void dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const override; }; // Flys that are bound to a character in Content diff --git a/sw/source/core/inc/pagefrm.hxx b/sw/source/core/inc/pagefrm.hxx index 73d81a6895b5..88004bb7ccb3 100644 --- a/sw/source/core/inc/pagefrm.hxx +++ b/sw/source/core/inc/pagefrm.hxx @@ -20,6 +20,7 @@ #define INCLUDED_SW_SOURCE_CORE_INC_PAGEFRM_HXX #include <viewsh.hxx> +#include <swdllapi.h> #include "ftnboss.hxx" #include "hffrm.hxx" @@ -54,7 +55,7 @@ namespace o3tl { /// A page of the document layout. Upper frame is expected to be an SwRootFrame /// instance. At least an SwBodyFrame lower is expected. -class SAL_DLLPUBLIC_RTTI SwPageFrame final: public SwFootnoteBossFrame +class SW_DLLPUBLIC SwPageFrame final: public SwFootnoteBossFrame { friend class SwFrame; diff --git a/sw/source/core/inc/tabfrm.hxx b/sw/source/core/inc/tabfrm.hxx index 8bf2b863548e..ca4af59b223c 100644 --- a/sw/source/core/inc/tabfrm.hxx +++ b/sw/source/core/inc/tabfrm.hxx @@ -19,6 +19,8 @@ #ifndef INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX #define INCLUDED_SW_SOURCE_CORE_INC_TABFRM_HXX +#include <swdllapi.h> + #include "layfrm.hxx" #include "flowfrm.hxx" @@ -43,7 +45,7 @@ namespace o3tl { } /// SwTabFrame is one table in the document layout, containing rows (which contain cells). -class SAL_DLLPUBLIC_RTTI SwTabFrame final: public SwLayoutFrame, public SwFlowFrame +class SW_DLLPUBLIC SwTabFrame final: public SwLayoutFrame, public SwFlowFrame { friend void CalcContent( SwLayoutFrame *pLay, bool bNoColl ); diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx index 2d96e0fcea64..b18cb6a21cb5 100644 --- a/sw/source/core/layout/flycnt.cxx +++ b/sw/source/core/layout/flycnt.cxx @@ -1552,14 +1552,49 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType eMakePage ) auto pFly = dynamic_cast<SwFlyAtContentFrame*>(FindFlyFrame()); assert(pFly && "GetNextFlyLeaf: missing fly frame"); - SwLayoutFrame *pLayLeaf = GetNextLayoutLeaf(); - if (!pLayLeaf) + SwLayoutFrame *pLayLeaf = nullptr; + // Look up the first candidate. + if (IsTabFrame()) { - if (eMakePage == MAKEPAGE_INSERT) + // If we're in a table, try to find the next frame of the table's last content. + SwFrame* pContent = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + pLayLeaf = pContent ? pContent->GetUpper() : nullptr; + } + else + { + pLayLeaf = GetNextLayoutLeaf(); + } + + SwLayoutFrame* pOldLayLeaf = nullptr; + while (true) + { + if (pLayLeaf) { - InsertPage(FindPageFrame(), false); - pLayLeaf = GetNextLayoutLeaf(); + // If we have a candidate, make sure that it's a child of our follow. + if (pFly->IsFlySplitAllowed()) + { + if (pFly->GetFollow() != pLayLeaf->FindFlyFrame()) + { + // It's not in our follow, reject. + pOldLayLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + continue; + } + } + } + else + { + // No candidate: insert a page and try again. + if (eMakePage == MAKEPAGE_INSERT) + { + InsertPage(FindPageFrame(), false); + // If we already had a cancidate, continue trying with that instead of starting from + // scratch. + pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf(); + continue; + } } + break; } if( pLayLeaf ) diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx index 9e87d12c81d5..ff22279f5c5b 100644 --- a/sw/source/core/text/xmldump.cxx +++ b/sw/source/core/text/xmldump.cxx @@ -26,6 +26,7 @@ #include <libxml/xmlwriter.h> #include <SwPortionHandler.hxx> #include <view.hxx> +#include <flyfrms.hxx> #include <svx/svdobj.hxx> #include "porlay.hxx" @@ -388,6 +389,11 @@ void SwFrame::dumpAsXml( xmlTextWriterPtr writer ) const else { dumpChildrenAsXml( writer ); + if (IsFlyFrame()) + { + auto pFlyFrame = static_cast<const SwFlyFrame*>(this); + pFlyFrame->SwAnchoredObject::dumpAsXml(writer); + } } (void)xmlTextWriterEndElement( writer ); } @@ -488,6 +494,12 @@ void SwAnchoredObject::dumpAsXml( xmlTextWriterPtr writer ) const (void)xmlTextWriterStartElement( writer, BAD_CAST( getElementName() ) ); (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this ); + (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("anchor-frame"), BAD_CAST(OString::number(mpAnchorFrame->GetFrameId()).getStr())); + SwTextFrame* pAnchorCharFrame = const_cast<SwAnchoredObject*>(this)->FindAnchorCharFrame(); + if (pAnchorCharFrame) + { + (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("anchor-char-frame"), BAD_CAST(OString::number(pAnchorCharFrame->GetFrameId()).getStr())); + } (void)xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) ); // don't call GetObjBoundRect(), it modifies the layout @@ -527,6 +539,24 @@ void SwTextFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() ); } +void SwFlyAtContentFrame::dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const +{ + SwFlyFreeFrame::dumpAsXmlAttributes(pWriter); + + if (m_pFollow != nullptr) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("follow"), + BAD_CAST(OString::number(m_pFollow->GetFrame().GetFrameId()).getStr())); + } + if (m_pPrecede != nullptr) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("precede"), + BAD_CAST(OString::number(m_pPrecede->GetFrame().GetFrameId()).getStr())); + } +} + void SwSectionFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const { SwFrame::dumpAsXmlAttributes( writer ); commit 1ce09a7ecff51574d8fa90f7d638d5624dfee8f0 Author: Miklos Vajna <[email protected]> AuthorDate: Thu Feb 9 08:26:21 2023 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Wed Mar 1 08:41:42 2023 +0100 sw: call FormatEmpty() in SwTextFrame::Format() for split fly masters The problem was that the text in the anchor frame of a split fly frame was duplicated on the old page and the new page as well. The reason for this seems to be that the master has no content and the follow has all the content (this is wanted), but then there is no code to explicitly clear the master. In other cases the master always gets some new content where portion building for that new content starts by throwing away the old portions. Once SwTextFrame::Format() and SwTextFrame::FormatEmpty() explicitly checks for these master anchors, the unwanted text in the master anchor disappears. An extra tweak is needed in SwTextFrame::PaintEmpty() to even hide the paragraph marker: this frame is empty but has a follow frame, so we should not show a paragraph marker there. Finally introduce a SwTextFrame::HasNonLastSplitFlyDrawObj() to be able to check for this "empty master anchor for split fly" case at a single place. With this <https://bugs.documentfoundation.org/attachment.cgi?id=185144> from <https://bugs.documentfoundation.org/show_bug.cgi?id=61594> gets laid out reasonably: the position is not perfect but we detect that only 1 para of the text frame fits page 1, we create a 2nd page and we correctly move exactly the text frame's 2nd para to page 2. (cherry picked from commit 00b9b33334791079c2dc26b1ed4c123450cabf7d) Change-Id: I871bba2de5b829e667d5cfb1cbe0ba4cc2274edd diff --git a/sw/source/core/attr/formatflysplit.cxx b/sw/source/core/attr/formatflysplit.cxx index bcbfcc2d5e96..904fd9a8bb5c 100644 --- a/sw/source/core/attr/formatflysplit.cxx +++ b/sw/source/core/attr/formatflysplit.cxx @@ -24,6 +24,18 @@ SwFormatFlySplit::SwFormatFlySplit(bool bSplit) : SfxBoolItem(RES_FLY_SPLIT, bSplit) { + // Once this pool item is true, a floating table (text frame + table inside it) is meant to + // split across multiple pages. + // + // The layout representation is the following: + // + // - We assume that the anchor type is at-para for such fly frames, and SwFlyAtContentFrame + // derives from SwFlowFrame to be able to split in general. + // + // - Both the master fly and the follow flys need an anchor. At the same time, we want all text + // of the anchor frame to be wrapped around the last follow fly frame, for Word compatibility. + // These are solved by splitting the anchor frame as many times as needed, always at text + // TextFrameIndex 0. if (getenv("SW_FORCE_FLY_SPLIT")) { SetValue(true); diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index cb22ebc439f8..942867882626 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -332,6 +332,9 @@ class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame virtual void SwClientNotify(SwModify const& rModify, SfxHint const& rHint) override; + /// Like GetDrawObjs(), but limit to fly frames which are allowed to split. + std::vector<SwFlyAtContentFrame*> GetSplitFlyDrawObjs() const; + public: virtual const SvxFormatBreakItem& GetBreakItem() const override; @@ -784,8 +787,9 @@ public: OUString GetCurWord(SwPosition const&) const; sal_uInt16 GetScalingOfSelectedText(TextFrameIndex nStt, TextFrameIndex nEnd); - /// Like GetDrawObjs(), but limit to fly frames which are allowed to split. - std::vector<SwFlyAtContentFrame*> GetSplitFlyDrawObjs(); + /// 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; virtual void dumpAsXmlAttributes(xmlTextWriterPtr writer) const override; }; diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 31becc976597..1975840aa1d3 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -581,14 +581,11 @@ void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, if (GetFollow()->IsDeleteForbidden()) return; - for (const auto& pFlyFrame : GetSplitFlyDrawObjs()) + if (HasNonLastSplitFlyDrawObj()) { // If a fly frame is anchored to us that has a follow, then don't join the anchor. // First those fly frames have to be joined. - if (pFlyFrame->GetFollow()) - { - return; - } + return; } JoinFrame(); @@ -1847,7 +1844,18 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr return; } - const TextFrameIndex nStrLen(GetText().getLength()); + TextFrameIndex nStrLen(GetText().getLength()); + + SwTextFrame* pFollow = GetFollow(); + if (pFollow && pFollow->GetOffset() == mnOffset) + { + if (HasNonLastSplitFlyDrawObj()) + { + // Non-last part of split fly anchor: consider this empty. + nStrLen = TextFrameIndex(0); + } + } + if ( nStrLen || !FormatEmpty() ) { diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx index e09ad12373cb..461c8094c0ac 100644 --- a/sw/source/core/text/frmpaint.cxx +++ b/sw/source/core/text/frmpaint.cxx @@ -601,7 +601,8 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const } // Don't show the paragraph mark for collapsed paragraphs, when they are hidden - if ( EmptyHeight( ) > 1 ) + // No paragraph marker in the non-last part of a split fly anchor, either. + if ( EmptyHeight( ) > 1 && !HasNonLastSplitFlyDrawObj() ) { SwDrawTextInfo aDrawInf( pSh, *pSh->GetOut(), CH_PAR, 0, 1 ); aDrawInf.SetPos( aPos ); diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index 41431bf4b715..eb643f2a1264 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -1452,10 +1452,10 @@ sal_uInt16 SwTextFrame::GetScalingOfSelectedText( return o3tl::narrowing<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 ); } -std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() +std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const { std::vector<SwFlyAtContentFrame*> aObjs; - SwSortedObjs* pSortedObjs = GetDrawObjs(); + const SwSortedObjs* pSortedObjs = GetDrawObjs(); if (!pSortedObjs) { return aObjs; @@ -1480,6 +1480,19 @@ std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() return aObjs; } +bool SwTextFrame::HasNonLastSplitFlyDrawObj() const +{ + for (const auto& pFly : GetSplitFlyDrawObjs()) + { + if (pFly->GetFollow()) + { + return true; + } + } + + return false; +} + SwTwips SwTextNode::GetWidthOfLeadingTabs() const { SwTwips nRet = 0; diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 03ce7666c911..6c1f0c06a5a5 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1936,16 +1936,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) { // Don't oversize the line in case of split flys, so we don't try to move the anchor // of a precede fly forward, next to its follow. - bool bHasNonLastFlySplitAnchored = false; - for (const auto& pFlyFrame : m_pFrame->GetSplitFlyDrawObjs()) - { - if (pFlyFrame->GetFollow()) - { - bHasNonLastFlySplitAnchored = true; - break; - } - } - if (bHasNonLastFlySplitAnchored) + if (m_pFrame->HasNonLastSplitFlyDrawObj()) { m_pCurr->SetRealHeight(GetFrameRstHeight()); } diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx index 3a8dd51e014d..eb128eaa662e 100644 --- a/sw/source/core/text/porrst.cxx +++ b/sw/source/core/text/porrst.cxx @@ -410,7 +410,9 @@ bool SwTextFrame::FormatEmpty() bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( ); // sw_redlinehide: just disable FormatEmpty optimisation for now - if (HasFollow() || GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() || + // Split fly frames: non-last parts of the anchor want this optimization to clear the old + // content. + if ((HasFollow() && mnOffset != GetFollow()->GetOffset()) || GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() || nullptr != GetTextNodeForParaProps()->GetNumRule() || GetTextNodeFirst()->HasHiddenCharAttribute(true) || IsInFootnote() || ( HasPara() && GetPara()->IsPrepMustFit() ) ) commit 8a06b6300f131079e589fd2695a541d36fca44e3 Author: Miklos Vajna <[email protected]> AuthorDate: Thu Feb 9 08:22:16 2023 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Wed Mar 1 08:40:13 2023 +0100 sw floattable: fix cid#1520804 GetFrameRstHeight() already assumes the m_pFrame is non-nullptr, so no need to check for this. The logic in the block will be necessary elsewhere as well, I'll extract that to a function in a follow-up change, so let's just do a minimal fix here. (cherry picked from commit 25a16e7543965565a4227506003adc916deea500) Change-Id: I6cb2a44e629c273f473278d61607705a2b9a7a4d diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index e65468d62d4c..03ce7666c911 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1933,7 +1933,6 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) m_pCurr->Height( GetFrameRstHeight() + 1, false ); m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 ); - if (m_pFrame) { // Don't oversize the line in case of split flys, so we don't try to move the anchor // of a precede fly forward, next to its follow. commit 0bb0922452c12d49f95483f9c10aad000afe7438 Author: Miklos Vajna <[email protected]> AuthorDate: Tue Feb 7 16:19:52 2023 +0100 Commit: Miklos Vajna <[email protected]> CommitDate: Wed Mar 1 08:39:04 2023 +0100 sw floattable: fix cid#1520800 Only SwFrame::GetLeaf() calls this, but it only does so when we're in an at-para anchored fly frame, so we can require that this succeeds. (cherry picked from commit d6b9529c4f63d1dd5c57db4f4912471cce2507d9) Change-Id: I6c99bc2ea1ab4f338a536272ccce13fd22b30246 diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx index 474d21bd7f97..2d96e0fcea64 100644 --- a/sw/source/core/layout/flycnt.cxx +++ b/sw/source/core/layout/flycnt.cxx @@ -1597,6 +1597,7 @@ SwFlyAtContentFrame* SwFlyAtContentFrame::GetPrecede() SwLayoutFrame* SwFrame::GetPrevFlyLeaf() { auto pFly = dynamic_cast<SwFlyAtContentFrame*>(FindFlyFrame()); + assert(pFly && "GetPrevFlyLeaf: missing fly frame"); if (!pFly->IsFlySplitAllowed()) { return nullptr;
