sw/qa/extras/rtfexport/data/tdf167661.rtf | 12 +++ sw/qa/extras/rtfexport/rtfexport8.cxx | 33 +++++++++ sw/source/filter/ww8/wrtw8num.cxx | 105 ++++++++++++++---------------- sw/source/filter/ww8/wrtw8sty.cxx | 43 ++++++++---- sw/source/filter/ww8/wrtww8.cxx | 2 sw/source/filter/ww8/wrtww8.hxx | 6 + 6 files changed, 131 insertions(+), 70 deletions(-)
New commits: commit ff0a5f1a5deaf402cff40cea8ffa4e2f3fb2734b Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Thu Jul 24 15:35:13 2025 +0500 Commit: Michael Stahl <michael.st...@collabora.com> CommitDate: Fri Jul 25 11:39:55 2025 +0200 tdf#167661: collect bullet fonts before exporting font table The problem was, that InitFontTable, used to collect all fonts used in the document, didn't handle bullets. When font table was written, some fonts needed for bullets could be still missing from it; only when writing list levels, these fonts were added to maFonts. It might seem that that is a bug in SwDoc::ForEachCharacterFontItem, called from InitFontTable. It might be; and maybe even needs to be addressed at some point (though I don't know which side effects it could have). But in the case of export to RTF, this wouldn't fix it completely, because there is some special processing of bullets in MSWordExportBase::SubstituteBullet, which might change the exported font name; therefore, it could happen to still be not covered by an iteration over actual document's fonts. 1. Extract the code filling used numbering rules in GetNumberingId, into a separate function (EnsureUsedNumberingTable). 2. Extract the code getting bullet string + font in NumberingLevel, into a separate function (GetNumberingLevelBulletStringAndFont). 3. In wwFontHelper::InitFontTable, use these functions to iterate the used numbering rules, and handle fonts of these. To do that, let the function take an Export object. Change-Id: Ib7ecdeb9908cd3bd53346ccb035d623fa603db0d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188289 Reviewed-by: Michael Stahl <michael.st...@collabora.com> Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> diff --git a/sw/qa/extras/rtfexport/data/tdf167661.rtf b/sw/qa/extras/rtfexport/data/tdf167661.rtf new file mode 100644 index 000000000000..368c0c56a338 --- /dev/null +++ b/sw/qa/extras/rtfexport/data/tdf167661.rtf @@ -0,0 +1,12 @@ +{ tf1 +{onttbl +{0bidi romancharset0prq2 Liberation Sans;} +{1bidi nilcharset2prq2 Wingdings;}} +{\*\listtable +{\list\listtemplateid-1 +{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u183 ?;}{\levelnumbers;}1 i-360\li720\lin720 } +{\listname ;}\listid1}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} +\pard i-360\li720 i0\ls1 in0\lin720\itap00 bullet list item +\par +} \ No newline at end of file diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx b/sw/qa/extras/rtfexport/rtfexport8.cxx index 6d6f261f5b36..bb035043c6ce 100644 --- a/sw/qa/extras/rtfexport/rtfexport8.cxx +++ b/sw/qa/extras/rtfexport/rtfexport8.cxx @@ -862,6 +862,39 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167569) } } +CPPUNIT_TEST_FIXTURE(Test, testTdf167661) +{ + // Given a document with a bulleted list using Wingdings + createSwDoc("tdf167661.rtf"); + + { + // Check font of the bullet + auto xNumberingRules = getProperty<uno::Reference<container::XIndexAccess>>( + getParagraph(1), u"NumberingRules"_ustr); + + comphelper::SequenceAsHashMap level1( + xNumberingRules->getByIndex(0).get<uno::Sequence<beans::PropertyValue>>()); + + CPPUNIT_ASSERT_EQUAL(u"Wingdings"_ustr, level1[u"BulletFontName"_ustr].get<OUString>()); + } + + saveAndReload(mpFilter); + + { + // Check that the font of the bullet is not lost + auto xNumberingRules = getProperty<uno::Reference<container::XIndexAccess>>( + getParagraph(1), u"NumberingRules"_ustr); + + comphelper::SequenceAsHashMap level1( + xNumberingRules->getByIndex(0).get<uno::Sequence<beans::PropertyValue>>()); + + // Without a fix, this failed with + // - Expected: Wingdings + // - Actual : 0 + CPPUNIT_ASSERT_EQUAL(u"Wingdings"_ustr, level1[u"BulletFontName"_ustr].get<OUString>()); + } +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/wrtw8num.cxx b/sw/source/filter/ww8/wrtw8num.cxx index 9067327cf4f1..6103c9126b3b 100644 --- a/sw/source/filter/ww8/wrtw8num.cxx +++ b/sw/source/filter/ww8/wrtw8num.cxx @@ -117,7 +117,7 @@ void MSWordExportBase::AddListLevelOverride(sal_uInt16 nListId, m_ListLevelOverrides[nListId][nLevelNum] = nStartAt; } -sal_uInt16 MSWordExportBase::GetNumberingId( const SwNumRule& rNumRule ) +void MSWordExportBase::EnsureUsedNumberingTable() { if ( !m_pUsedNumTable ) { @@ -146,6 +146,11 @@ sal_uInt16 MSWordExportBase::GetNumberingId( const SwNumRule& rNumRule ) m_pUsedNumTable->push_back( pR ); } } +} + +sal_uInt16 MSWordExportBase::GetNumberingId( const SwNumRule& rNumRule ) +{ + EnsureUsedNumberingTable(); SwNumRule* p = const_cast<SwNumRule*>(&rNumRule); sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(m_pUsedNumTable->GetPos(p)); @@ -413,6 +418,35 @@ void MSWordExportBase::AbstractNumberingDefinitions() } } +std::pair<OUString, std::unique_ptr<wwFont>> +MSWordExportBase::GetNumberingLevelBulletStringAndFont(const SwNumFormat& rLevelFormat) +{ + assert(rLevelFormat.GetNumberingType() == SVX_NUM_CHAR_SPECIAL + || rLevelFormat.GetNumberingType() == SVX_NUM_BITMAP); + + sal_UCS4 cBullet = rLevelFormat.GetBulletChar(); + OUString sNumStr(&cBullet, 1); + + std::optional<vcl::Font> pBulletFont = rLevelFormat.GetBulletFont(); + if (!pBulletFont) + { + pBulletFont = numfunc::GetDefBulletFont(); + } + + rtl_TextEncoding eChrSet = pBulletFont->GetCharSet(); + OUString sFontName = pBulletFont->GetFamilyName(); + FontFamily eFamily = pBulletFont->GetFamilyType(); + + if (IsOpenSymbol(sFontName)) + SubstituteBullet(sNumStr, eChrSet, sFontName); + + if (sFontName.isEmpty()) + sFontName = pBulletFont->GetFamilyName(); + + return { sNumStr, std::make_unique<wwFont>(sFontName, pBulletFont->GetPitch(), + eFamily, eChrSet) }; +} + void MSWordExportBase::NumberingLevel( SwNumRule const& rRule, sal_uInt8 const nLvl) { @@ -462,17 +496,25 @@ void MSWordExportBase::NumberingLevel( // Build the NumString for this Level OUString sNumStr; - OUString sFontName; - bool bWriteBullet = false; - std::optional<vcl::Font> pBulletFont; - rtl_TextEncoding eChrSet=0; - FontFamily eFamily=FAMILY_DECORATIVE; + + // Attributes of the numbering + std::unique_ptr<wwFont> pPseudoFont; + const SfxItemSet* pOutSet = nullptr; + + // cbGrpprlChpx + SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet( m_rDoc.GetAttrPool() ); + if (SVX_NUM_CHAR_SPECIAL == rFormat.GetNumberingType() || SVX_NUM_BITMAP == rFormat.GetNumberingType()) { // Use bullet - sal_UCS4 cBullet = rFormat.GetBulletChar(); - sNumStr = OUString(&cBullet, 1); + std::tie(sNumStr, pPseudoFont) = GetNumberingLevelBulletStringAndFont(rFormat); + + pOutSet = &aSet; + if (rFormat.GetCharFormat()) + aSet.Put( rFormat.GetCharFormat()->GetAttrSet() ); + aSet.ClearItem( RES_CHRATR_CJK_FONT ); + aSet.ClearItem( RES_CHRATR_FONT ); } else { @@ -551,51 +593,8 @@ void MSWordExportBase::NumberingLevel( assert(false && "deprecated format still exists and is unhandled. Inform Vasily or Justin"); } - if (SVX_NUM_CHAR_SPECIAL == rFormat.GetNumberingType() || - SVX_NUM_BITMAP == rFormat.GetNumberingType()) - { - bWriteBullet = true; - - pBulletFont = rFormat.GetBulletFont(); - if (!pBulletFont) - { - pBulletFont = numfunc::GetDefBulletFont(); - } - - eChrSet = pBulletFont->GetCharSet(); - sFontName = pBulletFont->GetFamilyName(); - eFamily = pBulletFont->GetFamilyType(); - - if (IsOpenSymbol(sFontName)) - SubstituteBullet(sNumStr, eChrSet, sFontName); - } - - // Attributes of the numbering - std::unique_ptr<wwFont> pPseudoFont; - const SfxItemSet* pOutSet = nullptr; - - // cbGrpprlChpx - SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet( m_rDoc.GetAttrPool() ); - if (rFormat.GetCharFormat() || bWriteBullet) - { - if (bWriteBullet) - { - pOutSet = &aSet; - - if (rFormat.GetCharFormat()) - aSet.Put( rFormat.GetCharFormat()->GetAttrSet() ); - aSet.ClearItem( RES_CHRATR_CJK_FONT ); - aSet.ClearItem( RES_CHRATR_FONT ); - - if (sFontName.isEmpty()) - sFontName = pBulletFont->GetFamilyName(); - - pPseudoFont.reset(new wwFont( sFontName, pBulletFont->GetPitch(), - eFamily, eChrSet)); - } - else - pOutSet = &rFormat.GetCharFormat()->GetAttrSet(); - } + if (!pOutSet && rFormat.GetCharFormat()) + pOutSet = &rFormat.GetCharFormat()->GetAttrSet(); sal_Int16 nIndentAt = 0; sal_Int16 nFirstLineIndex = 0; diff --git a/sw/source/filter/ww8/wrtw8sty.cxx b/sw/source/filter/ww8/wrtw8sty.cxx index e88e64659a78..d8d277768724 100644 --- a/sw/source/filter/ww8/wrtw8sty.cxx +++ b/sw/source/filter/ww8/wrtw8sty.cxx @@ -919,7 +919,7 @@ sal_uInt16 wwFontHelper::GetId(const wwFont &rFont) return nRet; } -void wwFontHelper::InitFontTable(const SwDoc& rDoc) +void wwFontHelper::InitFontTable(MSWordExportBase& rExport) { GetId(wwFont(u"Times New Roman", PITCH_VARIABLE, FAMILY_ROMAN, RTL_TEXTENCODING_MS_1252)); @@ -930,18 +930,10 @@ void wwFontHelper::InitFontTable(const SwDoc& rDoc) GetId(wwFont(u"Arial", PITCH_VARIABLE, FAMILY_SWISS, RTL_TEXTENCODING_MS_1252)); - const SvxFontItem* pFont = GetDfltAttr(RES_CHRATR_FONT); + GetId(*GetDfltAttr(RES_CHRATR_FONT)); - GetId(wwFont(pFont->GetFamilyName(), pFont->GetPitch(), - pFont->GetFamily(), pFont->GetCharSet())); - - const SfxItemPool& rPool = rDoc.GetAttrPool(); - pFont = rPool.GetUserDefaultItem(RES_CHRATR_FONT); - if (nullptr != pFont) - { - GetId(wwFont(pFont->GetFamilyName(), pFont->GetPitch(), - pFont->GetFamily(), pFont->GetCharSet())); - } + if (const SvxFontItem* pFont = rExport.m_rDoc.GetAttrPool().GetUserDefaultItem(RES_CHRATR_FONT)) + GetId(*pFont); if (!m_bLoadAllFonts) return; @@ -949,14 +941,35 @@ void wwFontHelper::InitFontTable(const SwDoc& rDoc) const TypedWhichId<SvxFontItem> aTypes[] { RES_CHRATR_FONT, RES_CHRATR_CJK_FONT, RES_CHRATR_CTL_FONT }; for (const TypedWhichId<SvxFontItem> & pId : aTypes) { - const_cast<SwDoc&>(rDoc).ForEachCharacterFontItem(pId, /*bIgnoreAutoStyles*/false, + rExport.m_rDoc.ForEachCharacterFontItem(pId, /*bIgnoreAutoStyles*/false, [this] (const SvxFontItem& rFontItem) -> bool { - GetId(wwFont(rFontItem.GetFamilyName(), rFontItem.GetPitch(), - rFontItem.GetFamily(), rFontItem.GetCharSet())); + GetId(rFontItem); return true; }); } + + // Bullets in lists may need own fonts; and may even want to substitute fonts (see + // MSWordExportBase::SubstituteBullet). We need to collect these here, too. + rExport.EnsureUsedNumberingTable(); + for (const SwNumRule* pRule : *rExport.m_pUsedNumTable) + { + assert(pRule); + int n = pRule->IsContinusNum() ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel; + for (int nLvl = 0; nLvl < n; ++nLvl) + { + const SwNumFormat& rFormat = pRule->Get(nLvl); + + if (rFormat.GetNumberingType() == SVX_NUM_CHAR_SPECIAL + || rFormat.GetNumberingType() == SVX_NUM_BITMAP) + { + const auto [s, pFont] = rExport.GetNumberingLevelBulletStringAndFont(rFormat); + (void)s; + assert(pFont); + GetId(*pFont); + } + } + } } sal_uInt16 wwFontHelper::GetId(const SvxFontItem& rFont) diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx index 45fc74e5419c..77a1a92517de 100644 --- a/sw/source/filter/ww8/wrtww8.cxx +++ b/sw/source/filter/ww8/wrtww8.cxx @@ -3463,7 +3463,7 @@ ErrCode MSWordExportBase::ExportDocument( bool bWriteAll ) // fix the SwPositions in m_aFrames after SetRedlineFlags UpdateFramePositions(m_aFrames); - m_aFontHelper.InitFontTable(m_rDoc); + m_aFontHelper.InitFontTable(*this); GatherChapterFields(); CollectOutlineBookmarks(m_rDoc); diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx index fa34838426f2..4cba24900374 100644 --- a/sw/source/filter/ww8/wrtww8.hxx +++ b/sw/source/filter/ww8/wrtww8.hxx @@ -330,7 +330,7 @@ private: public: wwFontHelper() : m_bLoadAllFonts(false) {} /// rDoc used only to get the initial standard font(s) in use. - void InitFontTable(const SwDoc& rDoc); + void InitFontTable(MSWordExportBase& rExport); sal_uInt16 GetId(const SvxFontItem& rFont); sal_uInt16 GetId(const wwFont& rFont); void WriteFontTable( SvStream *pTableStream, WW8Fib& pFib ); @@ -614,6 +614,7 @@ public: /// Return the numeric id of the numbering rule sal_uInt16 GetNumberingId( const SwNumRule& rNumRule ); + void EnsureUsedNumberingTable(); /// Return the numeric id of the style. sal_uInt16 GetId( const SwTextFormatColl& rColl ) const; @@ -781,6 +782,9 @@ public: /// Write one numbering level void NumberingLevel(SwNumRule const& rRule, sal_uInt8 nLvl); + std::pair<OUString, std::unique_ptr<wwFont>> + GetNumberingLevelBulletStringAndFont(const SwNumFormat& rLevelFormat); + // Convert the bullet according to the font. void SubstituteBullet( OUString& rNumStr, rtl_TextEncoding& rChrSet, OUString& rFontName ) const;