sw/inc/anchoredobject.hxx | 5 + sw/qa/extras/layout/data/tdf148897.odt |binary sw/qa/extras/layout/layout2.cxx | 82 +++++++++++++++++++++++++++ sw/source/core/inc/txtfly.hxx | 4 + sw/source/core/inc/txtfrm.hxx | 3 sw/source/core/layout/flylay.cxx | 2 sw/source/core/layout/frmtool.cxx | 5 + sw/source/core/layout/layact.cxx | 6 + sw/source/core/text/frmform.cxx | 100 +++++++++++++++++++++++++++++++-- sw/source/core/text/txtfly.cxx | 29 +++++---- sw/source/core/view/viewsh.cxx | 3 11 files changed, 219 insertions(+), 20 deletions(-)
New commits: commit b0e2b60fa45f236f6a993cc1295a7afddabb5098 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed May 24 20:16:49 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed May 24 22:29:15 2023 +0200 tdf#148897 tdf#143239 sw: move flys off the page in SwTextFrame::Format() The bugfix regresses testTdf143239, so we add some scary additional hacks: * testTdf143239: turns out that this was already moving back and forth many times but then ran into some loop control - now with the fix for tdf#148897 this still happens, but the result is now bad, where previously it was good (by accident?). Move the flys off the page also in SwTextFrame::Format() - there is already code there to detect that footnotes have been moved off by Format_() - this is similar, the formatting should be redone immediately if there is a fly that created SwFlyPortions in the current frame, but the fly was actually moved to the next page by the very same Format_() splitting the frame. * testKeepWithNextPlusFlyFollowTextFlow: here the problem was that the para with the fly didn't move back to page 1; need to clear the SwLayouter when toggling FieldNames. * testKeepwithnextFullheight: this test document will loop creating infinite pages if the newly add move code is run during SwObjectFormatterTextFrame - the latter must move the flys if it is active, not SwTextFrame::Format(). This is all a bit experimental but i failed to have a better idea... (regression from previous commit) Change-Id: Icfcbd270137116198128d549b34e7f49a47b6911 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152246 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/source/core/inc/txtfly.hxx b/sw/source/core/inc/txtfly.hxx index 37d78e3c822f..95d70198f858 100644 --- a/sw/source/core/inc/txtfly.hxx +++ b/sw/source/core/inc/txtfly.hxx @@ -152,7 +152,9 @@ class SwTextFly SwAnchoredObjList* InitAnchoredObjList(); +public: SwAnchoredObjList* GetAnchoredObjList() const; +private: /** Look for the first object which overlaps with the rectangle. @@ -302,6 +304,8 @@ public: void SetIgnoreContour( bool bNew ); void SetIgnoreObjsInHeaderFooter( const bool bNew ); + + SwRect GetFrameArea() const; }; inline SwAnchoredObjList* SwTextFly::GetAnchoredObjList() const diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index 2376153b40d3..c4c51503ec98 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -268,7 +268,8 @@ class SW_DLLPUBLIC SwTextFrame final : public SwContentFrame // In order to safe stack space, we split this method: // Format_ calls Format_ with parameters - void Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pPara ); + void FormatImpl( vcl::RenderContext* pRenderContext, SwParaPortion *pPara, + ::std::vector<SwAnchoredObject *> & rIntersectingObjs); void Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf, const bool bAdjust = false ); void FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf ); diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 912047487a64..5fdf320d4eee 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -50,6 +50,8 @@ #include <redline.hxx> #include <comphelper/lok.hxx> #include <flyfrms.hxx> +#include <frmtool.hxx> +#include <layouter.hxx> // Tolerance in formatting and text output #define SLOPPY_TWIPS 5 @@ -1775,7 +1777,8 @@ void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf } } -void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pPara ) +void SwTextFrame::FormatImpl(vcl::RenderContext* pRenderContext, SwParaPortion *pPara, + std::vector<SwAnchoredObject *> & rIntersectingObjs) { const bool bIsEmpty = GetText().isEmpty(); @@ -1810,6 +1813,18 @@ void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pP if( aLine.IsOnceMore() ) FormatOnceMore( aLine, aInf ); + if (aInf.GetTextFly().IsOn()) + { + SwRect const aRect(aInf.GetTextFly().GetFrameArea()); + for (SwAnchoredObject *const pObj : *aInf.GetTextFly().GetAnchoredObjList()) + { + if (!aInf.GetTextFly().AnchoredObjToRect(pObj, aRect).IsEmpty()) + { + rIntersectingObjs.push_back(pObj); + } + } + } + if ( IsVertical() ) SwapWidthAndHeight(); @@ -1991,7 +2006,13 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr } do { - Format_( pRenderContext, aAccess.GetPara() ); + ::std::vector<SwAnchoredObject *> intersectingObjs; + ::std::vector<SwFrame const*> nexts; + for (SwFrame const* pNext = GetNext(); pNext; pNext = pNext->GetNext()) + { + nexts.push_back(pNext); + } + FormatImpl(pRenderContext, aAccess.GetPara(), intersectingObjs); if( pFootnoteBoss && nFootnoteHeight ) { const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont(); @@ -1999,12 +2020,79 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr // If we lost some footnotes, we may have more space // for our main text, so we have to format again ... if( nNewHeight < nFootnoteHeight ) + { nFootnoteHeight = nNewHeight; - else - break; + continue; + } } - else - break; + if (!intersectingObjs.empty()) + { + // assumption is that FormatImpl() only moves frames + // in the next-chain to next page + SwPageFrame *const pPage(FindPageFrame()); + SwTextFrame * pLastMovedAnchor(nullptr); + auto lastIter(nexts.end()); + for (SwAnchoredObject *const pObj : intersectingObjs) + { + SwFrame *const pAnchor(pObj->AnchorFrame()); + SwPageFrame *const pAnchorPage(pAnchor->FindPageFrame()); + if (pAnchorPage != pPage) + { + auto const iter(::std::find(nexts.begin(), nexts.end(), pAnchor)); + if (iter != nexts.end()) + { + assert(pAnchor->IsTextFrame()); + // (can't check SwOszControl::IsInProgress()?) + // called in loop in FormatAnchorFrameAndItsPrevs() + if (static_cast<SwTextFrame const*>(pAnchor)->IsJoinLocked() + // called in loop in SwFrame::PrepareMake() + || pAnchor->IsDeleteForbidden()) + { + // when called via FormatAnchorFrameAndItsPrevs(): + // don't do anything, caller will handle it + pLastMovedAnchor = nullptr; + break; + } + assert(pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum()); // how could it move backward? + + if (!pLastMovedAnchor || iter < lastIter) + { + pLastMovedAnchor = static_cast<SwTextFrame *>(pAnchor); + lastIter = iter; + } + } + } + } + SwPageFrame const*const pPrevPage(static_cast<SwPageFrame const*>(pPage->GetPrev())); + if (pLastMovedAnchor) + { + for (SwAnchoredObject *const pObj : intersectingObjs) + { + if (pObj->AnchorFrame() == pLastMovedAnchor) + { + SwPageFrame *const pAnchorPage(pLastMovedAnchor->FindPageFrame()); + SAL_INFO("sw.layout", "SwTextFrame::Format: move anchored " << pObj << " from " << pPage->GetPhyPageNum() << " to " << pAnchorPage->GetPhyPageNum()); + pObj->RegisterAtPage(*pAnchorPage); + // tdf#143239 if the position remains valid, it may not be + // positioned again so would remain on the wrong page! + pObj->InvalidateObjPos(); + ::Notify_Background(pObj->GetDrawObj(), pPage, + pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false); + pObj->SetForceNotifyNewBackground(true); + } + } + if (GetFollow() // this frame was split + && (!pPrevPage // prev page is still valid + || (!pPrevPage->IsInvalid() + && (!pPrevPage->GetSortedObjs() || !pPrevPage->IsInvalidFly())))) + { // this seems a bit risky... + SwLayouter::InsertMovedFwdFrame(GetTextNodeFirst()->GetDoc(), + *pLastMovedAnchor, FindPageFrame()->GetPhyPageNum() + 1); + } + continue; // try again without the fly + } + } + break; } while ( pFootnoteBoss ); if( bOrphan ) { diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index 2c7f5aeadb4f..cb5105763e73 100644 --- a/sw/source/core/text/txtfly.cxx +++ b/sw/source/core/text/txtfly.cxx @@ -823,6 +823,22 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, return false; } +SwRect SwTextFly::GetFrameArea() const +{ + // i#28701 - consider complete frame area for new text wrapping + SwRect aRect; + if (m_pCurrFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING)) + { + aRect = m_pCurrFrame->getFramePrintArea(); + aRect += m_pCurrFrame->getFrameArea().Pos(); + } + else + { + aRect = m_pCurrFrame->getFrameArea(); + } + return aRect; +} + // #i68520# SwAnchoredObjList* SwTextFly::InitAnchoredObjList() { @@ -853,18 +869,7 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() // #i68520# mpAnchoredObjList.reset(new SwAnchoredObjList ); - // #i28701# - consider complete frame area for new - // text wrapping - SwRect aRect; - if ( pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ) - { - aRect = m_pCurrFrame->getFramePrintArea(); - aRect += m_pCurrFrame->getFrameArea().Pos(); - } - else - { - aRect = m_pCurrFrame->getFrameArea(); - } + SwRect const aRect(GetFrameArea()); // Make ourselves a little smaller than we are, // so that 1-Twip-overlappings are ignored (#49532) SwRectFnSet aRectFnSet(m_pCurrFrame); diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx index e501545e7396..b514990bfed3 100644 --- a/sw/source/core/view/viewsh.cxx +++ b/sw/source/core/view/viewsh.cxx @@ -66,6 +66,7 @@ #include <anchoredobject.hxx> #include <DocumentSettingManager.hxx> #include <DocumentRedlineManager.hxx> +#include <DocumentLayoutManager.hxx> #include <unotxdoc.hxx> #include <view.hxx> @@ -2425,6 +2426,8 @@ void SwViewShell::ImplApplyViewOptions( const SwViewOption &rOpt ) } } } + // the layout changes but SetModified() wasn't called so do it here: + mxDoc->GetDocumentLayoutManager().ClearSwLayouterEntries(); } if( !bOnlineSpellChgd ) commit 35f7f1ad9c2aba1644a46dfb4da0a86f72c6a224 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed May 24 19:50:16 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed May 24 22:29:09 2023 +0200 tdf#148897 sw: layout: force invalidate background when moving fly off page The fly 15 moves forward in SwLayAction::FormatContent(), then back again later, without being positioned on the next page, and it happens to go to the same position that it originally had. Then SwFlyNotify sees that the position is the same, and doesn't do any invalidation of the background, so the fly overlaps a text frame. When the fly is moved by the object positioning code (before the below commit), it first gets a new position on the next page, and then moves back, so all invalidations happen. Force the invalidation of the background in this case. (regression from commit eb85de8e6b61fb3fcb6c03ae0145f7fe5478bccf "sw: layout: if fly's anchor moves forward, move fly off SwPageFrame") Change-Id: Ifdc9460defe7b1f37cbb05310906146919e8fb6a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152245 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/inc/anchoredobject.hxx b/sw/inc/anchoredobject.hxx index ea8104882b4d..19cd2e75802b 100644 --- a/sw/inc/anchoredobject.hxx +++ b/sw/inc/anchoredobject.hxx @@ -109,6 +109,8 @@ class SW_DLLPUBLIC SwAnchoredObject // page frame starts. bool mbTmpConsiderWrapInfluence; + bool mbForceNotifyNewBackground = false; + mutable SwRect maObjRectWithSpaces; mutable bool mbObjRectWithSpacesValid; mutable SwRect maLastObjRect; @@ -423,6 +425,9 @@ class SW_DLLPUBLIC SwAnchoredObject bool IsTmpConsiderWrapInfluence() const { return mbTmpConsiderWrapInfluence;} void ClearTmpConsiderWrapInfluence(); + bool IsForceNotifyNewBackground() { return mbForceNotifyNewBackground; } + void SetForceNotifyNewBackground(bool const b) { mbForceNotifyNewBackground = b; } + /** method to determine, if the anchored object is overlapping with a previous column diff --git a/sw/qa/extras/layout/data/tdf148897.odt b/sw/qa/extras/layout/data/tdf148897.odt new file mode 100644 index 000000000000..d5d1555c9976 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf148897.odt differ diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx index 3f8045804e24..b4b004951bf0 100644 --- a/sw/qa/extras/layout/layout2.cxx +++ b/sw/qa/extras/layout/layout2.cxx @@ -12,6 +12,7 @@ #include <com/sun/star/text/XTextFrame.hpp> #include <com/sun/star/text/XTextTable.hpp> #include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> #include <comphelper/scopeguard.hxx> #include <comphelper/sequence.hxx> @@ -144,6 +145,87 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf100680_as_char_wrap) // If the third line missing that assert will fire, as was before the fix. } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf148897) +{ + createSwDoc("tdf148897.odt"); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/body/txt[3]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[3]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page[4]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[4]/body/txt[1]/anchored/fly", 1); + // fly portion exists, no overlapping text + assertXPath(pXmlDoc, "/root/page[4]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwFixPortion", + "height", "5797"); + assertXPath(pXmlDoc, "/root/page[5]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page", 5); + + auto xModel = mxComponent.queryThrow<frame::XModel>(); + uno::Reference<drawing::XShape> xShape(getShapeByName(u"Image3")); + uno::Reference<view::XSelectionSupplier> xCtrl(xModel->getCurrentController(), uno::UNO_QUERY); + xCtrl->select(uno::Any(xShape)); + + dispatchCommand(mxComponent, ".uno:Delete", {}); + + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[1]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page[2]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/body/txt[2]/anchored/fly", 1); + // fly portion exists, no overlapping text + assertXPath(pXmlDoc, "/root/page[3]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwFixPortion", + "height", "5797"); + assertXPath(pXmlDoc, "/root/page[4]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[4]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page", 4); + + dispatchCommand(mxComponent, ".uno:Undo", {}); + + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/body/txt[3]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[3]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page[4]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[4]/body/txt[1]/anchored/fly", 1); + // fly portion exists, no overlapping text + assertXPath(pXmlDoc, "/root/page[4]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwFixPortion", + "height", "5797"); + assertXPath(pXmlDoc, "/root/page[5]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page", 5); + + dispatchCommand(mxComponent, ".uno:Redo", {}); + + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[1]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page[2]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/anchored/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "/root/page[3]/body/txt[2]/anchored/fly", 1); + // fly portion exists, no overlapping text + assertXPath(pXmlDoc, "/root/page[3]/body/txt[1]/SwParaPortion/SwLineLayout[1]/SwFixPortion", + "height", "5797"); + assertXPath(pXmlDoc, "/root/page[4]/sorted_objs/fly", 0); + assertXPath(pXmlDoc, "/root/page[4]/body/txt/anchored/fly", 0); + assertXPath(pXmlDoc, "/root/page", 4); +} + CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testRedlineCharAttributes) { createSwDoc("redline_charatr.fodt"); diff --git a/sw/source/core/layout/flylay.cxx b/sw/source/core/layout/flylay.cxx index b1bfe32aab10..40b8547811fd 100644 --- a/sw/source/core/layout/flylay.cxx +++ b/sw/source/core/layout/flylay.cxx @@ -220,7 +220,7 @@ void SwFlyFreeFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) else // #i26791# - use new method <MakeObjPos()> MakeObjPos(); - if( aOldPos == aRectFnSet.GetPos(getFrameArea()) ) + if (!IsForceNotifyNewBackground() && aOldPos == aRectFnSet.GetPos(getFrameArea())) { if( !isFrameAreaPositionValid() && GetAnchorFrame()->IsInSct() && !GetAnchorFrame()->FindSctFrame()->isFrameAreaDefinitionValid() ) diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index 193478da450e..bc0231e67307 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -691,6 +691,11 @@ void SwFlyNotify::ImplDestroy() } pFly->ResetNotifyBack(); } + if (pFly->IsForceNotifyNewBackground()) + { + pFly->NotifyBackground(pFly->FindPageFrame(), pFly->GetObjRectWithSpaces(), PrepareHint::FlyFrameArrive); + pFly->SetForceNotifyNewBackground(false); + } //Have the size or the position changed, //so should the view know this. diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx index f0ac091d16ea..1996f8e319e6 100644 --- a/sw/source/core/layout/layact.cxx +++ b/sw/source/core/layout/layact.cxx @@ -1682,6 +1682,12 @@ bool SwLayAction::FormatContent(SwPageFrame *const pPage) pObj->InvalidateObjPos(); ::Notify_Background(pObj->GetDrawObj(), pPage, pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false); + // tdf#148897 in case the fly moves back to this page before + // being positioned again, the SwFlyNotify / ::Notify() could + // conclude that it didn't move at all and not call + // NotifyBackground(); note: pObj->SetObjTop(FAR_AWAY) results + // in wrong positions, use different approach! + pObj->SetForceNotifyNewBackground(true); } if (!moved.empty()) {