cool#16219 engine sd, RTF export: fix lost bullets of paragraphs

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<n> 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 <bullet>\tab} fallback and a {\*\pn\pnlvlblt{\pntxtb <bullet>}}
destination that contains the bullet character.

Fix the problem by emitting that pair alongside \level<n>, 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 <vmiklos@collabora.com>
Change-Id: I18a022ac28d50a8b7557ee2282cd3fd8e7c37299
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/14439
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit b7002f07f475ce4eb5cf7309dfe8e59ba21ecd7a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211942
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Miklos Vajna
2026-10-07 21:11:19 +02:00
committed by Xisco Fauli
parent 09d3cc2c14
commit 1886cb2ee3
2 changed files with 58 additions and 0 deletions
+31
View File
@@ -11,8 +11,10 @@
#include <editeng/eeitem.hxx> #include <editeng/eeitem.hxx>
#include <editeng/editeng.hxx> #include <editeng/editeng.hxx>
#include <editeng/numitem.hxx>
#include <editeng/wghtitem.hxx> #include <editeng/wghtitem.hxx>
#include <sfx2/app.hxx> #include <sfx2/app.hxx>
#include <svl/intitem.hxx>
#include <svtools/parrtf.hxx> #include <svtools/parrtf.hxx>
#include <svtools/rtftoken.h> #include <svtools/rtftoken.h>
@@ -213,6 +215,35 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf119192RTFFontAttributesInParagraphScope)
bool bContains = (aCnt.find("{mytest}") != std::string::npos); bool bContains = (aCnt.find("{mytest}") != std::string::npos);
CPPUNIT_ASSERT(bContains); 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<datatransfer::XTransferable> 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<EditDataObject*>(xData.get());
SvMemoryStream& rStream = pData->GetRTFStream();
std::string aCnt{ static_cast<const char*>(rStream.GetData()),
static_cast<size_t>(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(); CPPUNIT_PLUGIN_IMPLEMENT();
+27
View File
@@ -22,6 +22,7 @@
#include <editeng/adjustitem.hxx> #include <editeng/adjustitem.hxx>
#include <editeng/cmapitem.hxx> #include <editeng/cmapitem.hxx>
#include <editeng/lspcitem.hxx> #include <editeng/lspcitem.hxx>
#include <editeng/numitem.hxx>
#include <editeng/tstpitem.hxx> #include <editeng/tstpitem.hxx>
#include "eertfpar.hxx" #include "eertfpar.hxx"
@@ -644,6 +645,32 @@ ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel, bool bCl
if ( bAttr ) if ( bAttr )
rOutput.WriteChar( ' ' ); // Separator 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<sal_Unicode>(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; ItemList aAttribItems;
ParaPortion* pParaPortion = FindParaPortion( pNode ); ParaPortion* pParaPortion = FindParaPortion( pNode );
DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" ); DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" );