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())