From 1886cb2ee3d5a637922064b83c45883dc0a08cd8 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 6 Oct 2026 08:26:22 +0200 Subject: [PATCH] cool#16219 engine sd, RTF export: fix lost bullets of paragraphs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a few bulleted lines to a text frame in an Impress document, copy them, paste in Writer: the paragraphs are imported with no bullet marks. ImpEditEngine::WriteRTF() emits a bare \level for the paragraph's outline level, which editeng's own RTF importer reads back as a bulleted item, but sw's RTF import ignores. The PowerPoint RTF clipboard markup for the same thing is the RTF 1.0 old-style numbering pair: a {\pntext \tab} fallback and a {\*\pn\pnlvlblt{\pntxtb }} destination that contains the bullet character. Fix the problem by emitting that pair alongside \level, from the paragraph loop in WriteRTF(), when the paragraph's EE_PARA_NUMBULLET at that outline level is of type SVX_NUM_CHAR_SPECIAL. Signed-off-by: Miklos Vajna Change-Id: I18a022ac28d50a8b7557ee2282cd3fd8e7c37299 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/14439 Tested-by: Jenkins CPCI Reviewed-by: Caolán McNamara (cherry picked from commit b7002f07f475ce4eb5cf7309dfe8e59ba21ecd7a) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211942 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- editeng/qa/editeng/editeng.cxx | 31 +++++++++++++++++++++++++++++ editeng/source/editeng/impedit4.cxx | 27 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/editeng/qa/editeng/editeng.cxx b/editeng/qa/editeng/editeng.cxx index d2348d986c16..19fadcb9bbca 100644 --- a/editeng/qa/editeng/editeng.cxx +++ b/editeng/qa/editeng/editeng.cxx @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include #include @@ -213,6 +215,35 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf119192RTFFontAttributesInParagraphScope) bool bContains = (aCnt.find("{mytest}") != std::string::npos); CPPUNIT_ASSERT(bContains); } + +CPPUNIT_TEST_FIXTURE(Test, testRTFBullet) +{ + // Given a document with a paragraph that is a bulleted item: + EditEngine aEditEngine(mpItemPool.get()); + OUString aText = u"mytest"_ustr; + aEditEngine.SetText(aText); + SfxItemSet aItems(aEditEngine.GetParaAttribs(0)); + aItems.Put(SfxInt16Item(EE_PARA_OUTLLEVEL, 0)); + SvxNumRule aRule(SvxNumRuleFlags::BULLET_REL_SIZE, 10, false); + SvxNumberFormat aFmt(SVX_NUM_CHAR_SPECIAL); + aFmt.SetBulletChar(0x2022); // bullet + aRule.SetLevel(0, aFmt); + aItems.Put(SvxNumBulletItem(std::move(aRule), EE_PARA_NUMBULLET)); + aEditEngine.SetParaAttribs(0, aItems); + + // When copying to the clipboard as RTF: + uno::Reference xData + = aEditEngine.CreateTransferable(ESelection(0, 0, 0, aText.getLength())); + + // Then make sure the RTF carries the bullet so a non-editeng reader sees it: + auto pData = dynamic_cast(xData.get()); + SvMemoryStream& rStream = pData->GetRTFStream(); + std::string aCnt{ static_cast(rStream.GetData()), + static_cast(rStream.GetSize()) }; + // Without the accompanying fix in place, this failed, the export wrote + // only editeng's own \level0 and \pnlvlblt. + CPPUNIT_ASSERT(aCnt.find("\\pnlvlblt") != std::string::npos); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx index f1450560c27a..bba1ae42b34c 100644 --- a/editeng/source/editeng/impedit4.cxx +++ b/editeng/source/editeng/impedit4.cxx @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "eertfpar.hxx" @@ -644,6 +645,32 @@ ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel, bool bCl if ( bAttr ) rOutput.WriteChar( ' ' ); // Separator + const SfxInt16Item& rOutlLevelItem = GetParaAttrib(nNode, EE_PARA_OUTLLEVEL); + sal_Int16 nOutlLevel = rOutlLevelItem.GetValue(); + if (nOutlLevel >= 0) + { + // The paragraph has an outline level, check if that is a bullet. + const SvxNumBulletItem& rNumBullet = GetParaAttrib(nNode, EE_PARA_NUMBULLET); + const SvxNumberFormat* pFmt = rNumBullet.GetNumRule().Get(nOutlLevel); + if (pFmt && pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL) + { + sal_Unicode cBullet = static_cast(pFmt->GetBulletChar()); + OUString aBullet(&cBullet, 1); + rOutput.WriteChar('{').WriteOString(OOO_STRING_SVTOOLS_RTF_PNTEXT).WriteChar(' '); + RTFOutFuncs::Out_String(rOutput, aBullet, eDestEnc); + rOutput.WriteOString("\\tab}"); + rOutput.WriteChar('{') + .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE) + .WriteOString(OOO_STRING_SVTOOLS_RTF_PN) + .WriteOString(OOO_STRING_SVTOOLS_RTF_PNLVLBLT) + .WriteChar('{') + .WriteOString(OOO_STRING_SVTOOLS_RTF_PNTXTB) + .WriteChar(' '); + RTFOutFuncs::Out_String(rOutput, aBullet, eDestEnc); + rOutput.WriteOString("}}"); + } + } + ItemList aAttribItems; ParaPortion* pParaPortion = FindParaPortion( pNode ); DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" );