sw/inc/fesh.hxx | 2 sw/inc/strings.hrc | 2 sw/inc/swundo.hxx | 1 sw/qa/extras/uiwriter/data/tdf169651.odt |binary sw/qa/extras/uiwriter/uiwriter11.cxx | 25 +++++++++++ sw/source/core/draw/dcontact.cxx | 1 sw/source/core/frmedt/fefly1.cxx | 67 +++++++++++++++++++++++++++++-- sw/source/core/undo/undobj.cxx | 3 + 8 files changed, 96 insertions(+), 5 deletions(-)
New commits: commit ef597d2e1e0fa24c2bdcdde30faa416cef7bf9c1 Author: Jim Raykowski <[email protected]> AuthorDate: Sat Dec 13 16:10:52 2025 -0900 Commit: Jim Raykowski <[email protected]> CommitDate: Wed Feb 4 09:04:28 2026 +0100 tdf#169651 Avoid crash in SwLayAction::FormatContent Here is effort to avoid a crash that occurs when a fly frame is unfloated that has a drawing object or fly frame anchored to its frame. To avoid the crash, this approach uses FN_TOOL_ANCHOR_PARAGRAPH to change the anchor of frame anchored drawing and fly frame objects to be paragraph anchored in the fly frame content. MoveObjToVisibleLayer is added in SwDrawContact::SwClientNotify sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR case handling to make undo restored fly anchored objects visible. SwFEShell::SelectFlyFrame is made SW_DLLPUBLIC to make the clang linker not complain about 'linker cannot find symbol(s) for architecture x86_64' when used in the included unit test. Change-Id: Ie6791cb45e7dce31132d3b6a8dac79d63bc27eb2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195650 Tested-by: Jenkins Reviewed-by: Jim Raykowski <[email protected]> diff --git a/sw/inc/fesh.hxx b/sw/inc/fesh.hxx index 085adf8dee52..3871507b9120 100644 --- a/sw/inc/fesh.hxx +++ b/sw/inc/fesh.hxx @@ -268,7 +268,7 @@ public: SW_DLLPUBLIC bool Copy( SwFEShell&, const Point& rSttPt, const Point& rInsPt, bool bIsMove = false, bool bSelectInsert = true ); - void SelectFlyFrame( SwFlyFrame& rFrame ); + SW_DLLPUBLIC void SelectFlyFrame(SwFlyFrame& rFrame); SW_DLLPUBLIC void UnfloatFlyFrame(); diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index 0b70e22c6077..99e23aab3110 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -1577,6 +1577,8 @@ #define STR_UNDO_SORT_CHAPTERS NC_("STR_UNDO_SORT_CHAPTERS", "Sort chapters") +#define STR_UNDO_UNFLOAT_FRAME_CONTENT NC_("STR_UNDO_UNFLOAT_FRAME_CONTENT", "Unfloat frame content") + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/inc/swundo.hxx b/sw/inc/swundo.hxx index 76af157fd129..56bbd771fb25 100644 --- a/sw/inc/swundo.hxx +++ b/sw/inc/swundo.hxx @@ -186,6 +186,7 @@ enum class SwUndoId REINSTATE_REDLINE = 154, COPY_HEADER_FOOTER = 155, SORT_CHAPTERS = 156, + UNFLOAT_FRAME_CONTENT = 157 }; OUString GetUndoComment(SwUndoId eId); diff --git a/sw/qa/extras/uiwriter/data/tdf169651.odt b/sw/qa/extras/uiwriter/data/tdf169651.odt new file mode 100644 index 000000000000..dccae694025f Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf169651.odt differ diff --git a/sw/qa/extras/uiwriter/uiwriter11.cxx b/sw/qa/extras/uiwriter/uiwriter11.cxx index 9da790d04fde..92548813bd25 100644 --- a/sw/qa/extras/uiwriter/uiwriter11.cxx +++ b/sw/qa/extras/uiwriter/uiwriter11.cxx @@ -30,6 +30,10 @@ #include <ndtxt.hxx> #include <IDocumentLayoutAccess.hxx> #include <svx/svxids.hrc> +#include <sortedobjs.hxx> +#include <rootfrm.hxx> +#include <anchoredobject.hxx> +#include <flyfrm.hxx> namespace { @@ -621,6 +625,27 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf165206DirSwitchPreservesAlignment) getProperty<short>(getRun(getParagraph(1), 1), u"ParaAdjust"_ustr)); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest11, testTdf169651) +{ + // Given a document with a fly frame that has anchored FLY_AT_FLY, a shape object and a + // fly frame that itself has a shape object anchored FLY_AT_FLY: + createSwDoc("tdf169651.odt"); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + CPPUNIT_ASSERT(pWrtShell); + + const SwSortedObjs* pAnchoredObjs + = pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower()->GetDrawObjs(); + CPPUNIT_ASSERT(pAnchoredObjs); + SwAnchoredObject* pAnchoredObj = (*pAnchoredObjs)[0]; + SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame(); + CPPUNIT_ASSERT(pFlyFrame); + + pWrtShell->SelectFlyFrame(*pFlyFrame); + + // Without the patch this test would crash during the following + pWrtShell->UnfloatFlyFrame(); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/draw/dcontact.cxx b/sw/source/core/draw/dcontact.cxx index 2421f2a3622c..129e6e1b12b2 100644 --- a/sw/source/core/draw/dcontact.cxx +++ b/sw/source/core/draw/dcontact.cxx @@ -1573,6 +1573,7 @@ void SwDrawContact::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) break; case sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR: GetAnchoredObj(GetMaster())->MakeObjPos(); + MoveObjToVisibleLayer(GetMaster()); break; default: ; diff --git a/sw/source/core/frmedt/fefly1.cxx b/sw/source/core/frmedt/fefly1.cxx index 15af7cfd2376..241a5272b2b2 100644 --- a/sw/source/core/frmedt/fefly1.cxx +++ b/sw/source/core/frmedt/fefly1.cxx @@ -75,6 +75,9 @@ #include <frameformats.hxx> #include <textboxhelper.hxx> +#include <sortedobjs.hxx> +#include <cmdid.h> +#include <sfx2/dispatch.hxx> using namespace ::com::sun::star; @@ -276,10 +279,6 @@ void SwFEShell::SelectFlyFrame( SwFlyFrame& rFrame ) void SwFEShell::UnfloatFlyFrame() { - GetIDocumentUndoRedo().StartUndo(SwUndoId::DELLAYFMT, nullptr); - comphelper::ScopeGuard g([this] - { GetIDocumentUndoRedo().EndUndo(SwUndoId::DELLAYFMT, nullptr); }); - SwFlyFrame* pFly = GetSelectedFlyFrame(); if (!pFly) { @@ -300,6 +299,55 @@ void SwFEShell::UnfloatFlyFrame() return; } + GetIDocumentUndoRedo().StartUndo(SwUndoId::UNFLOAT_FRAME_CONTENT, nullptr); + comphelper::ScopeGuard g( + [this] + { + GetIDocumentUndoRedo().EndUndo(SwUndoId::UNFLOAT_FRAME_CONTENT, nullptr); + EndAllAction(); + }); + + StartAllAction(); + + // tdf#169651 Crash in: SwLayAction::FormatContent + // To avoid crash, dispatch FN_TOOL_ANCHOR_PARAGRAPH to change anchored objects that have an + // anchor id of FLY_AT_FLY to have an anchored id of FLY_AT_PARA before removing the containing + // fly frame. + if (pFly->GetDrawObjs()) + { + for (size_t i = 0; pFly->GetDrawObjs() && i < pFly->GetDrawObjs()->size(); i++) + { + SwAnchoredObject* pAnchoredObj = (*pFly->GetDrawObjs())[i]; + if (!pAnchoredObj) + continue; + + const SwFrameFormat* pFrameFormat = pAnchoredObj->GetFrameFormat(); + if (!pFrameFormat) + continue; + + if (pFrameFormat->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_FLY) + continue; + + // if (SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame()) + // SelectFlyFrame(*pFlyFrame); + // else + SelectObj(Point(), 0, pAnchoredObj->DrawObj()); + + GetSfxViewShell()->GetDispatcher()->Execute(FN_TOOL_ANCHOR_PARAGRAPH, + SfxCallMode::SYNCHRON); + + // Check if anchor id changed. + if (pFrameFormat->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_PARA) + continue; + + // The fly frame should now have one less draw object anchored to it. Anchored + // objects are stored sorted. The next anchored object in the sorted objects vector + // should now be at the current index. Adjust the index variable value by -1 so the + // next pass of the for loop will set it to the same index as this pass. + i--; + } + } + // Create an empty paragraph after the table, so the frame's SwNodes section is non-empty after // MoveNodeRange(). Undo would ensure it's non-empty and then node offsets won't match. IDocumentContentOperations& rIDCO = GetDoc()->getIDocumentContentOperations(); @@ -322,6 +370,17 @@ void SwFEShell::UnfloatFlyFrame() return; } + // The anchor node could be a start node, indicating anchored to frame, move past it to the + // first content node. pFlyFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_FLY + if (pAnchor->IsStartNode()) + { + SwNodeIndex aIdx(*pAnchor); + pAnchor = SwNodes::GoNext(&aIdx); + assert(pAnchor); + if (!pAnchor) + return; + } + // Move the content outside of the text frame. SwNodeIndex aInsertPos(*pAnchor); rIDCO.MoveNodeRange(aRange, aInsertPos.GetNode(), SwMoveFlags::CREATEUNDOOBJ); diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index c994448a10c6..1c5f0571b7b7 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -703,6 +703,9 @@ OUString GetUndoComment(SwUndoId eId) case SwUndoId::SORT_CHAPTERS: pId = STR_UNDO_SORT_CHAPTERS; break; + case SwUndoId::UNFLOAT_FRAME_CONTENT: + pId = STR_UNDO_UNFLOAT_FRAME_CONTENT; + break; } assert(pId);
