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: خالد حسني <khaled@aliftype.com>
diff --git a/vcl/inc/font/LogicalFontInstance.hxx b/vcl/inc/font/LogicalFontInstance.hxx
index 0b40a9c..6f4645c 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 1459cf1..b3222f4 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 34f05bb..6923ce6 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 57ea2c7..c3a4b09 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())