vcl/inc/font/LogicalFontInstance.hxx    |    9 +++
 vcl/inc/pdf/pdfwriter_impl.hxx          |    6 +-
 vcl/source/font/LogicalFontInstance.cxx |   88 ++++++++++++++++++++++++++++++++
 vcl/source/gdi/pdfwriter_impl.cxx       |   28 ++++++++--
 4 files changed, 126 insertions(+), 5 deletions(-)

New commits:
commit 164d717530aff8d2581d0a2ff249f83aabb27502
Author:     Khaled Hosny <kha...@aliftype.com>
AuthorDate: Mon Nov 21 15:37:40 2022 +0200
Commit:     خالد حسني <kha...@aliftype.com>
CommitDate: Wed Nov 23 21:35:47 2022 +0100

    tdf#108497: instantiate variable fonts in PDF
    
    Draw the outlines as PDF Type 3 glyphs until we can use HarfBuzz
    subsetter to instantiate the fonts before embedding.
    
    Change-Id: I811eaa8f7e5875db2eeb3755d69616242e263ca2
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/142028
    Tested-by: Jenkins
    Reviewed-by: خالد حسني <kha...@aliftype.com>

diff --git a/vcl/inc/font/LogicalFontInstance.hxx 
b/vcl/inc/font/LogicalFontInstance.hxx
index 0b40a9c7d4f7..6f4645c82c0c 100644
--- a/vcl/inc/font/LogicalFontInstance.hxx
+++ b/vcl/inc/font/LogicalFontInstance.hxx
@@ -103,6 +103,7 @@ public: // TODO: make data members private
 
     bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const;
     virtual bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) 
const = 0;
+    bool GetGlyphOutlineUntransformed(sal_GlyphId, basegfx::B2DPolyPolygon&) 
const;
 
     sal_GlyphId GetGlyphIndex(uint32_t, uint32_t = 0) const;
 
@@ -125,6 +126,8 @@ protected:
     virtual void ImplInitHbFont(hb_font_t*) {}
 
 private:
+    hb_font_t* GetHbFontUntransformed() const;
+
     struct MapEntry
     {
         OUString sFontName;
@@ -139,6 +142,7 @@ private:
     mutable ImplFontCache* mpFontCache;
     const vcl::font::FontSelectPattern m_aFontSelData;
     hb_font_t* m_pHbFont;
+    mutable hb_font_t* m_pHbFontUntransformed = nullptr;
     double m_nAveWidthFactor;
     rtl::Reference<vcl::font::PhysicalFontFace> m_pFontFace;
     std::optional<bool> m_xbIsGraphiteFont;
@@ -151,6 +155,11 @@ private:
 
     // The value is initialized and used in NeedOffsetCorrection().
     std::optional<FontFamilyEnum> m_xeFontFamilyEnum;
+
+#if HB_VERSION_ATLEAST(4, 0, 0)
+    mutable hb_draw_funcs_t* m_pHbDrawFuncs = nullptr;
+    basegfx::B2DPolygon m_aDrawPolygon;
+#endif
 };
 
 inline hb_font_t* LogicalFontInstance::GetHbFont()
diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx
index 1459cf16e0d2..b3222f4072fd 100644
--- a/vcl/inc/pdf/pdfwriter_impl.hxx
+++ b/vcl/inc/pdf/pdfwriter_impl.hxx
@@ -299,6 +299,7 @@ class GlyphEmit
     std::vector<ColorLayer>         m_aColorLayers;
     font::RawFontData               m_aColorBitmap;
     tools::Rectangle                m_aRect;
+    basegfx::B2DPolyPolygon         m_aOutline;
 
 public:
     GlyphEmit() : m_nSubsetGlyphID(0), m_nGlyphWidth(0)
@@ -325,6 +326,9 @@ public:
         return m_aColorBitmap;
     }
 
+    void setOutline(basegfx::B2DPolyPolygon aOutline) { m_aOutline = aOutline; 
}
+    const basegfx::B2DPolyPolygon& getOutline() const { return m_aOutline; }
+
     void addCode( sal_Ucs i_cCode )
     {
         m_CodeUnits.push_back(i_cCode);
@@ -852,7 +856,7 @@ i12626
     void appendLiteralStringEncrypt( std::string_view rInString, const 
sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer );
 
     /* creates fonts and subsets that will be emitted later */
-    void registerGlyph(const sal_GlyphId, const vcl::font::PhysicalFontFace*, 
const std::vector<sal_Ucs>&, sal_Int32, sal_uInt8&, sal_Int32&);
+    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&);
 
     /*  emits a text object according to the passed layout */
diff --git a/vcl/source/font/LogicalFontInstance.cxx 
b/vcl/source/font/LogicalFontInstance.cxx
index 34f05bbe6e33..6923ce6cd87a 100644
--- a/vcl/source/font/LogicalFontInstance.cxx
+++ b/vcl/source/font/LogicalFontInstance.cxx
@@ -50,6 +50,14 @@ LogicalFontInstance::~LogicalFontInstance()
 
     if (m_pHbFont)
         hb_font_destroy(m_pHbFont);
+
+    if (m_pHbFontUntransformed)
+        hb_font_destroy(m_pHbFontUntransformed);
+
+#if HB_VERSION_ATLEAST(4, 0, 0)
+    if (m_pHbDrawFuncs)
+        hb_draw_funcs_destroy(m_pHbDrawFuncs);
+#endif
 }
 
 hb_font_t* LogicalFontInstance::InitHbFont()
@@ -79,6 +87,26 @@ hb_font_t* LogicalFontInstance::InitHbFont()
     return pHbFont;
 }
 
+hb_font_t* LogicalFontInstance::GetHbFontUntransformed() const
+{
+    auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
+
+#if HB_VERSION_ATLEAST(3, 3, 0)
+    if (NeedsArtificialItalic()) // || NeedsArtificialBold()
+    {
+        if (!m_pHbFontUntransformed)
+        {
+            m_pHbFontUntransformed = hb_font_create_sub_font(pHbFont);
+            // Unset slant set on parent font.
+            // Does not actually work: 
https://github.com/harfbuzz/harfbuzz/issues/3890
+            hb_font_set_synthetic_slant(m_pHbFontUntransformed, 0);
+        }
+        return m_pHbFontUntransformed;
+    }
+#endif
+    return pHbFont;
+}
+
 int LogicalFontInstance::GetKashidaWidth() const
 {
     sal_GlyphId nGlyph = GetGlyphIndex(0x0640);
@@ -231,4 +259,64 @@ bool LogicalFontInstance::NeedsArtificialItalic() const
     return m_aFontSelData.GetItalic() != ITALIC_NONE && 
m_pFontFace->GetItalic() == ITALIC_NONE;
 }
 
+namespace
+{
+void move_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, 
float to_x, float to_y,
+                  void* pUserData)
+{
+    auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+    pPoly->append(basegfx::B2DPoint(to_x, -to_y));
+}
+
+void line_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, 
float to_x, float to_y,
+                  void* pUserData)
+{
+    auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+    pPoly->append(basegfx::B2DPoint(to_x, -to_y));
+}
+
+void cubic_to_func(hb_draw_funcs_t*, void* /*pDrawData*/, hb_draw_state_t*, 
float control1_x,
+                   float control1_y, float control2_x, float control2_y, float 
to_x, float to_y,
+                   void* pUserData)
+{
+    auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+    pPoly->appendBezierSegment(basegfx::B2DPoint(control1_x, -control1_y),
+                               basegfx::B2DPoint(control2_x, -control2_y),
+                               basegfx::B2DPoint(to_x, -to_y));
+}
+
+void close_path_func(hb_draw_funcs_t*, void* pDrawData, hb_draw_state_t*, 
void* pUserData)
+{
+    auto pPolyPoly = static_cast<basegfx::B2DPolyPolygon*>(pDrawData);
+    auto pPoly = static_cast<basegfx::B2DPolygon*>(pUserData);
+    pPolyPoly->append(*pPoly);
+    pPoly->clear();
+}
+}
+
+bool LogicalFontInstance::GetGlyphOutlineUntransformed(sal_GlyphId nGlyph,
+                                                       
basegfx::B2DPolyPolygon& rPolyPoly) const
+{
+#if HB_VERSION_ATLEAST(4, 0, 0)
+    if (!m_pHbDrawFuncs)
+    {
+        m_pHbDrawFuncs = hb_draw_funcs_create();
+        auto pUserData = const_cast<basegfx::B2DPolygon*>(&m_aDrawPolygon);
+        hb_draw_funcs_set_move_to_func(m_pHbDrawFuncs, move_to_func, 
pUserData, nullptr);
+        hb_draw_funcs_set_line_to_func(m_pHbDrawFuncs, line_to_func, 
pUserData, nullptr);
+        hb_draw_funcs_set_cubic_to_func(m_pHbDrawFuncs, cubic_to_func, 
pUserData, nullptr);
+        // B2DPolyPolygon does not support quadratic curves, HarfBuzz will
+        // convert them to cubic curves for us if we don’t set a callback
+        // function.
+        //hb_draw_funcs_set_quadratic_to_func(m_pHbDrawFuncs, 
quadratic_to_func, pUserData, nullptr);
+        hb_draw_funcs_set_close_path_func(m_pHbDrawFuncs, close_path_func, 
pUserData, nullptr);
+    }
+
+    hb_font_get_glyph_shape(GetHbFontUntransformed(), nGlyph, m_pHbDrawFuncs, 
&rPolyPoly);
+    return true;
+#else
+    return false;
+#endif
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx 
b/vcl/source/gdi/pdfwriter_impl.cxx
index 57ea2c785b81..c3a4b092e06e 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -2645,6 +2645,18 @@ bool PDFWriterImpl::emitType3Font(const 
vcl::font::PhysicalFontFace* pFace,
                 aContents.append(" Do Q\n");
             }
 
+            const auto& rOutline = rGlyph.getOutline();
+            if (rOutline.count())
+            {
+                // XXX I have no idea why this transformation matrix is needed.
+                aContents.append("q 10 0 0 10 0 ");
+                appendDouble(m_aPages.back().getHeight() * -10, aContents, 3);
+                aContents.append(" cm\n");
+                m_aPages.back().appendPolyPolygon(rOutline, aContents);
+                aContents.append("f\n");
+                aContents.append("Q\n");
+            }
+
             aLine.setLength(0);
             aLine.append(nStream);
             aLine.append(" 0 obj\n<</Length ");
@@ -4194,7 +4206,7 @@ void PDFWriterImpl::createDefaultCheckBoxAppearance( 
PDFWidget& rBox, const PDFW
 
     sal_uInt8 nMappedGlyph;
     sal_Int32 nMappedFontObject;
-    registerGlyph(nGlyphId, pFace, { cMark }, nGlyphWidth, nMappedGlyph, 
nMappedFontObject);
+    registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, 
nMappedGlyph, nMappedFontObject);
 
     appendNonStrokingColor( replaceColor( rWidget.TextColor, 
rSettings.GetRadioCheckTextColor() ), aDA );
     aDA.append( ' ' );
@@ -6137,16 +6149,18 @@ void PDFWriterImpl::registerSimpleGlyph(const 
sal_GlyphId nFontGlyphId,
 
 void PDFWriterImpl::registerGlyph(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)
 {
-    if (pFace->IsColorFont())
+    auto bVariations = !pFace->GetVariations(*pFont).empty();
+    if (pFace->IsColorFont() || bVariations)
     {
         // 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())
+        if (!aLayers.empty() || !aBitmap.empty() || bVariations)
         {
             auto& rSubset = m_aType3Fonts[pFace];
             auto it = rSubset.m_aMapping.find(nFontGlyphId);
@@ -6194,6 +6208,12 @@ void PDFWriterImpl::registerGlyph(const sal_GlyphId 
nFontGlyphId,
                 }
                 else if (!aBitmap.empty())
                     rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
+                else if (bVariations)
+                {
+                    basegfx::B2DPolyPolygon aOutline;
+                    if (pFont->GetGlyphOutlineUntransformed(nFontGlyphId, 
aOutline))
+                        rNewGlyphEmit.setOutline(aOutline);
+                }
 
                 // add new glyph to font mapping
                 Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
@@ -6646,7 +6666,7 @@ void PDFWriterImpl::drawLayout( SalLayout& rLayout, const 
OUString& rText, bool
 
         sal_uInt8 nMappedGlyph;
         sal_Int32 nMappedFontObject;
-        registerGlyph(nGlyphId, pFace, aCodeUnits, nGlyphWidth, nMappedGlyph, 
nMappedFontObject);
+        registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, 
nMappedGlyph, nMappedFontObject);
 
         int nCharPos = -1;
         if (bUseActualText || pGlyph->IsInCluster())

Reply via email to