configure.ac | 2 +- vcl/inc/font/PhysicalFontFace.hxx | 3 ++- vcl/inc/pdf/pdfwriter_impl.hxx | 31 +++++++++++++++++++++++++++++-- vcl/source/font/PhysicalFontFace.cxx | 17 +++++++++++++++-- vcl/source/pdf/pdfwriter_impl.cxx | 35 ++++++++++++++++++----------------- 5 files changed, 65 insertions(+), 23 deletions(-)
New commits: commit 7ffb13159276c0a74ce4fffc49bb0da4a1950f1d Author: Khaled Hosny <[email protected]> AuthorDate: Wed Feb 18 16:40:20 2026 +0200 Commit: Khaled Hosny <[email protected]> CommitDate: Fri Feb 20 22:31:41 2026 +0100 Use HarfBuzz for instancing variable fonts Instance variable fonts into static instances for embedding in PDF as regular fonts, instead of drawing the glyph outlines in PDF Type 3 fonts. This should be more efficient and preserve most font data like hinting. Variable fonts with CFF2 table (which are rare) continue to be drawn as Type 3 fonts as we would also need to to convert CFF2 to CFF and current versions of HarfBuzz has no support for that yet. The new APIs we use require HarfBuzz 8.3.1 Change-Id: Ic1e0b0ad445cb5918ad8f0b04d4bf9d364d870d8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199626 Tested-by: Jenkins Reviewed-by: Khaled Hosny <[email protected]> diff --git a/configure.ac b/configure.ac index 9d02bbce9900..a962aba3ec24 100644 --- a/configure.ac +++ b/configure.ac @@ -11679,7 +11679,7 @@ AC_SUBST(SYSTEM_LIBORCUS) dnl =================================================================== dnl HarfBuzz dnl =================================================================== -harfbuzz_required_version=7.3.0 +harfbuzz_required_version=8.3.1 GRAPHITE_CFLAGS_internal="-I${WORKDIR}/UnpackedTarball/graphite/include -DGRAPHITE2_STATIC" HARFBUZZ_CFLAGS_internal="-I${WORKDIR}/UnpackedTarball/harfbuzz/src" diff --git a/vcl/inc/font/PhysicalFontFace.hxx b/vcl/inc/font/PhysicalFontFace.hxx index 5efdc4e2be41..2b7e159dd56f 100644 --- a/vcl/inc/font/PhysicalFontFace.hxx +++ b/vcl/inc/font/PhysicalFontFace.hxx @@ -161,7 +161,8 @@ public: // implementation note: encoding 0 with glyph id 0 should be added implicitly // as "undefined character" SAL_DLLPRIVATE bool CreateFontSubset(std::vector<sal_uInt8>&, const sal_GlyphId*, - const sal_uInt8*, const int, FontSubsetInfo&) const; + const sal_uInt8*, const int, FontSubsetInfo&, + const std::vector<hb_variation_t>& rVariations = {}) const; bool IsColorFont() const { return HasColorLayers() || HasColorBitmaps(); } diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index 62f86e0a92f5..c762a6fb0959 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -348,6 +348,33 @@ struct FontSubset std::map<sal_GlyphId, Glyph> m_aMapping; }; +struct FontSubsetKey +{ + const vcl::font::PhysicalFontFace* m_pFace; + const std::vector<hb_variation_t>& m_rVariations; + size_t m_nHash; + + FontSubsetKey(const vcl::font::PhysicalFontFace* pFace, const LogicalFontInstance* pFont) + : m_pFace(pFace) + , m_rVariations(pFace->GetVariations(*pFont)) + , m_nHash(0) + { + o3tl::hash_combine(m_nHash, m_rVariations.size()); + for (const auto& rVar : m_rVariations) + { + o3tl::hash_combine(m_nHash, rVar.tag); + o3tl::hash_combine(m_nHash, rVar.value); + } + } + + bool operator<(const FontSubsetKey& rOther) const + { + if (m_pFace != rOther.m_pFace) + return m_pFace < rOther.m_pFace; + return m_nHash < rOther.m_nHash; + } +}; + struct EmbedFont { sal_Int32 m_nNormalFontID; @@ -786,7 +813,7 @@ private: std::vector< TilingEmit > m_aTilings; std::vector< TransparencyEmit > m_aTransparentObjects; /* contains all font subsets in use */ - std::map<const vcl::font::PhysicalFontFace*, FontSubset> m_aSubsets; + std::map<FontSubsetKey, FontSubset> m_aSubsets; std::map<const vcl::font::PhysicalFontFace*, EmbedFont> m_aSystemFonts; std::map<const vcl::font::PhysicalFontFace*, FontSubset> m_aType3Fonts; sal_Int32 m_nNextFID; @@ -866,7 +893,7 @@ private: private: /* creates fonts and subsets that will be emitted later */ void registerGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const LogicalFontInstance* pFont, const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&); - void registerSimpleGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&); + void registerSimpleGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, const LogicalFontInstance*, const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&); /* emits a text object according to the passed layout */ /* TODO: remove rText as soon as SalLayout will change so that rText is not necessary anymore */ diff --git a/vcl/source/font/PhysicalFontFace.cxx b/vcl/source/font/PhysicalFontFace.cxx index 3f965ac5852f..8569a35ecb85 100644 --- a/vcl/source/font/PhysicalFontFace.cxx +++ b/vcl/source/font/PhysicalFontFace.cxx @@ -307,7 +307,8 @@ constexpr auto DESCENT_HHEA = static_cast<hb_ot_metrics_tag_t>(HB_TAG('H', 'd', bool PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer, const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding, - const int nGlyphCount, FontSubsetInfo& rInfo) const + const int nGlyphCount, FontSubsetInfo& rInfo, + const std::vector<hb_variation_t>& rVariations) const { // Create subset input hb_subset_input_t* pInput = hb_subset_input_create_or_fail(); @@ -343,8 +344,20 @@ bool PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer, for (auto nKeep : aKeepTables) hb_set_del(pDropTableSet, nKeep); + hb_face_t* pHbFace = GetHbFace(); + bool bIsVariableFont = hb_ot_var_has_data(pHbFace); + if (bIsVariableFont) + { + // Instance variable font. We first pin all axes to their default values, so we don’t have to + // enumerate all axes in the font. Then we pin the axes we want to instance to their specified + // values. + hb_subset_input_pin_all_axes_to_default(pInput, pHbFace); + for (const auto& rVariation : rVariations) + hb_subset_input_pin_axis_location(pInput, pHbFace, rVariation.tag, rVariation.value); + } + // Perform the subsettting - hb_face_t* pSubsetFace = hb_subset_or_fail(GetHbFace(), pInput); + hb_face_t* pSubsetFace = hb_subset_or_fail(pHbFace, pInput); comphelper::ScopeGuard aSubsetFaceGuard([&]() { hb_face_destroy(pSubsetFace); }); if (!pSubsetFace) return false; diff --git a/vcl/source/pdf/pdfwriter_impl.cxx b/vcl/source/pdf/pdfwriter_impl.cxx index 4442f15fbba4..f893ae47f910 100644 --- a/vcl/source/pdf/pdfwriter_impl.cxx +++ b/vcl/source/pdf/pdfwriter_impl.cxx @@ -2083,8 +2083,9 @@ bool PDFWriterImpl::emitFonts() std::vector<sal_uInt8> aBuffer; FontSubsetInfo aSubsetInfo; - const auto* pFace = subset.first; - if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo)) + const auto* pFace = subset.first.m_pFace; + if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo, + subset.first.m_rVariations)) { // create font stream if (g_bDebugDisableCompression) @@ -2185,7 +2186,7 @@ bool PDFWriterImpl::emitFonts() if ( !writeBuffer( aLine ) ) return false; // write font descriptor - sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream ); + sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first.m_pFace, aSubsetInfo, s_subset.m_nFontID, nFontStream ); if( nToUnicodeStream ) nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs ); @@ -5456,12 +5457,14 @@ sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont ) void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId, const vcl::font::PhysicalFontFace* pFace, + const LogicalFontInstance* pFont, const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth, sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject) { - FontSubset& rSubset = m_aSubsets[ pFace ]; + FontSubset& rSubset = m_aSubsets[ { pFace, pFont } ]; + // search for font specific glyphID auto it = rSubset.m_aMapping.find( nFontGlyphId ); if( it != rSubset.m_aMapping.end() ) @@ -5504,21 +5507,19 @@ void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth, sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject) { - auto bVariations = !pFace->GetVariations(*pFont).empty(); // tdf#155161 - // PDF doesn’t support CFF2 table and we currently don’t convert them to - // Type 1 (like we do with CFF table), so treat it like fonts with - // variations and embed as Type 3 fonts. - if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty()) - bVariations = true; + // PDF doesn't support CFF2 table and we currently don't convert them to + // Type 1 (like we do with CFF table), so embed as Type 3 fonts. + // Non-CFF2 variable fonts are instanced via hb-subset and embedded normally. + bool bCFF2 = !pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty(); - if (pFace->IsColorFont() || bVariations) + if (pFace->IsColorFont() || bCFF2) { // Font has colors, check if this glyph has color layers or bitmap. tools::Rectangle aRect; auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId); auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect); - if (!aLayers.empty() || !aBitmap.empty() || bVariations) + if (!aLayers.empty() || !aBitmap.empty() || bCFF2) { auto& rSubset = m_aType3Fonts[pFace]; auto it = rSubset.m_aMapping.find(nFontGlyphId); @@ -5557,8 +5558,8 @@ void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, { sal_uInt8 nLayerGlyph; sal_Int32 nLayerFontID; - registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth, - nLayerGlyph, nLayerFontID); + registerSimpleGlyph(aLayer.nGlyphIndex, pFace, pFont, rCodeUnits, + nGlyphWidth, nLayerGlyph, nLayerFontID); rNewGlyphEmit.addColorLayer( { nLayerFontID, nLayerGlyph, aLayer.nColorIndex }); @@ -5566,7 +5567,7 @@ void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, } else if (!aBitmap.empty()) rNewGlyphEmit.setColorBitmap(aBitmap, aRect); - else if (bVariations) + else if (bCFF2) rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId)); // add new glyph to font mapping @@ -5579,7 +5580,7 @@ void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, } // If we reach here then the glyph has no color layers. - registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph, + registerSimpleGlyph(nFontGlyphId, pFace, pFont, rCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject); } @@ -6013,7 +6014,7 @@ void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool // instead. if (!aCodeUnits.empty() && !bUseActualText) { - for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets) + for (const auto& rSubset : m_aSubsets[{ pFace, pGlyphFont }].m_aSubsets) { const auto it = rSubset.m_aMapping.find(nGlyphId); if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
