/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include LogicalFontInstance::LogicalFontInstance(const vcl::font::PhysicalFontFace& rFontFace, const vcl::font::FontSelectPattern& rFontSelData) : mxFontMetric(new FontMetricData(rFontSelData)) , mpConversion(nullptr) , mnLineHeight(0) , mnOwnOrientation(0) , mnOrientation(0) , mbInit(false) , mpFontCache(nullptr) , m_aFontSelData(rFontSelData) , m_pHbFont(nullptr) , m_nAveWidthFactor(1.0f) , m_pFontFace(&const_cast(rFontFace)) { } LogicalFontInstance::~LogicalFontInstance() { maUnicodeFallbackList.clear(); mpFontCache = nullptr; mxFontMetric = nullptr; if (m_pHbFont) hb_font_destroy(m_pHbFont); if (m_pHbFontUntransformed) hb_font_destroy(m_pHbFontUntransformed); if (m_pHbDrawFuncs) hb_draw_funcs_destroy(m_pHbDrawFuncs); } hb_font_t* LogicalFontInstance::InitHbFont() { auto pFace = GetFontFace(); hb_face_t* pHbFace = pFace->GetHbFace(); assert(pHbFace); auto nUPEM = pFace->UnitsPerEm(); hb_font_t* pHbFont = hb_font_create(pHbFace); hb_font_set_scale(pHbFont, nUPEM, nUPEM); hb_ot_font_set_funcs(pHbFont); auto aVariations = pFace->GetVariations(*this); if (!aVariations.empty()) hb_font_set_variations(pHbFont, aVariations.data(), aVariations.size()); // If we are applying artificial italic, instruct HarfBuzz to do the same // so that mark positioning is also transformed. if (NeedsArtificialItalic()) hb_font_set_synthetic_slant(pHbFont, ARTIFICIAL_ITALIC_SKEW); ImplInitHbFont(pHbFont); return pHbFont; } hb_font_t* LogicalFontInstance::GetHbFontUntransformed() const { auto* pHbFont = const_cast(this)->GetHbFont(); 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; } return pHbFont; } double LogicalFontInstance::GetKashidaWidth() const { sal_GlyphId nGlyph = GetGlyphIndex(0x0640); if (nGlyph) return GetGlyphWidth(nGlyph); return 0; } void LogicalFontInstance::GetScale(double* nXScale, double* nYScale) const { double nUPEM = GetFontFace()->UnitsPerEm(); if (nYScale) *nYScale = m_aFontSelData.mnHeight / nUPEM; if (nXScale) { // On Windows, mnWidth is relative to average char width not font height, // and we need to keep it that way for GDI to correctly scale the glyphs. // Here we compensate for this so that HarfBuzz gives us the correct glyph // positions. double nWidth(m_aFontSelData.mnWidth ? m_aFontSelData.mnWidth * GetAverageWidthFactor() : m_aFontSelData.mnHeight); *nXScale = nWidth / nUPEM; } } void LogicalFontInstance::AddFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName, bool bEmbolden, const ItalicMatrix& rMatrix) { MapEntry& rEntry = maUnicodeFallbackList[std::pair(cChar, eWeight)]; rEntry.sFontName = rFontName; rEntry.bEmbolden = bEmbolden; rEntry.aItalicMatrix = rMatrix; } bool LogicalFontInstance::GetFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight, OUString* pFontName, bool* pEmbolden, ItalicMatrix* pMatrix) const { UnicodeFallbackList::const_iterator it = maUnicodeFallbackList.find(std::pair(cChar, eWeight)); if (it == maUnicodeFallbackList.end()) return false; const MapEntry& rEntry = (*it).second; *pFontName = rEntry.sFontName; *pEmbolden = rEntry.bEmbolden; *pMatrix = rEntry.aItalicMatrix; return true; } void LogicalFontInstance::IgnoreFallbackForUnicode(sal_UCS4 cChar, FontWeight eWeight, std::u16string_view rFontName) { UnicodeFallbackList::iterator it = maUnicodeFallbackList.find(std::pair(cChar, eWeight)); if (it == maUnicodeFallbackList.end()) return; const MapEntry& rEntry = (*it).second; if (rEntry.sFontName == rFontName) maUnicodeFallbackList.erase(it); } bool LogicalFontInstance::GetGlyphBoundRect(sal_GlyphId nID, basegfx::B2DRectangle& rRect, bool bVertical) const { // TODO: find out if it's possible for the same glyph in the same font to be used both // normally and vertically; if yes, then these two variants must be cached separately if (mpFontCache && mpFontCache->GetCachedGlyphBoundRect(this, nID, rRect)) return true; auto* pHbFont = const_cast(this)->GetHbFont(); hb_glyph_extents_t aExtents; if (!hb_font_get_glyph_extents(pHbFont, nID, &aExtents)) return false; double nXScale = 0, nYScale = 0; GetScale(&nXScale, &nYScale); double fMinX = aExtents.x_bearing * nXScale; double fMinY = -aExtents.y_bearing * nYScale; double fMaxX = (aExtents.x_bearing + aExtents.width) * nXScale; double fMaxY = -(aExtents.y_bearing + aExtents.height) * nYScale; rRect = basegfx::B2DRectangle(fMinX, fMinY, fMaxX, fMaxY); auto orientation = mnOrientation; if (bVertical) orientation += 900_deg10; if (orientation) { // Apply font rotation. rRect.transform(basegfx::utils::createRotateB2DHomMatrix(-toRadians(orientation))); } if (mpFontCache) mpFontCache->CacheGlyphBoundRect(this, nID, rRect); return true; } sal_GlyphId LogicalFontInstance::GetGlyphIndex(uint32_t nUnicode, uint32_t nVariationSelector) const { auto* pHbFont = const_cast(this)->GetHbFont(); sal_GlyphId nGlyph = 0; if (hb_font_get_glyph(pHbFont, nUnicode, nVariationSelector, &nGlyph)) return nGlyph; return 0; } double LogicalFontInstance::GetGlyphWidth(sal_GlyphId nGlyph, bool bVertical, bool bScale) const { auto* pHbFont = const_cast(this)->GetHbFont(); int nWidth; if (bVertical) nWidth = hb_font_get_glyph_v_advance(pHbFont, nGlyph); else nWidth = hb_font_get_glyph_h_advance(pHbFont, nGlyph); if (!bScale) return nWidth; double nScale = 0; GetScale(&nScale, nullptr); return double(nWidth * nScale); } bool LogicalFontInstance::IsGraphiteFont() { if (!m_xbIsGraphiteFont.has_value()) { m_xbIsGraphiteFont = hb_graphite2_face_get_gr_face(hb_font_get_face(GetHbFont())) != nullptr; } return *m_xbIsGraphiteFont; } bool LogicalFontInstance::NeedOffsetCorrection(sal_Int32 nYOffset) { if (!m_xeFontFamilyEnum) { m_xeFontFamilyEnum = FontFamilyEnum::Unclassified; // DFKai-SB (ukai.ttf) is a built-in font under traditional Chinese // Windows. It has wrong extent values in glyf table. The problem results // in wrong positioning of glyphs in vertical writing. // Check https://github.com/harfbuzz/harfbuzz/issues/3521 for reference. if (GetFontFace()->GetName(vcl::font::NAME_ID_FONT_FAMILY) == "DFKai-SB") m_xeFontFamilyEnum = FontFamilyEnum::DFKaiSB; } bool bRet = true; switch (*m_xeFontFamilyEnum) { case FontFamilyEnum::DFKaiSB: // -839: optimization for one third of ukai.ttf if (nYOffset == -839) bRet = false; break; default: bRet = false; } return bRet; } bool LogicalFontInstance::NeedsArtificialBold() const { return m_aFontSelData.GetWeight() > WEIGHT_MEDIUM && m_pFontFace->GetWeight() <= WEIGHT_MEDIUM; } 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(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(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(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(pDrawData); auto pPoly = static_cast(pUserData); pPolyPoly->append(*pPoly); pPoly->clear(); } } basegfx::B2DPolyPolygon LogicalFontInstance::GetGlyphOutlineUntransformed(sal_GlyphId nGlyph) const { if (!m_pHbDrawFuncs) { m_pHbDrawFuncs = hb_draw_funcs_create(); auto pUserData = const_cast(&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); } basegfx::B2DPolyPolygon aPolyPoly; #if HB_VERSION_ATLEAST(7, 0, 0) hb_font_draw_glyph(GetHbFontUntransformed(), nGlyph, m_pHbDrawFuncs, &aPolyPoly); #else hb_font_get_glyph_shape(GetHbFontUntransformed(), nGlyph, m_pHbDrawFuncs, &aPolyPoly); #endif return aPolyPoly; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */