summaryrefslogtreecommitdiffstats
path: root/vcl/source/font/PhysicalFontFace.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/font/PhysicalFontFace.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/font/PhysicalFontFace.cxx')
-rw-r--r--vcl/source/font/PhysicalFontFace.cxx510
1 files changed, 510 insertions, 0 deletions
diff --git a/vcl/source/font/PhysicalFontFace.cxx b/vcl/source/font/PhysicalFontFace.cxx
new file mode 100644
index 0000000000..aa9a9327f7
--- /dev/null
+++ b/vcl/source/font/PhysicalFontFace.cxx
@@ -0,0 +1,510 @@
+/* -*- 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 <sal/config.h>
+
+#include <sal/types.h>
+#include <tools/fontenum.hxx>
+#include <unotools/fontdefs.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.h>
+
+#include <fontattributes.hxx>
+#include <impfontcharmap.hxx>
+#include <sft.hxx>
+#include <salgdi.hxx>
+
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <string_view>
+
+#include <hb-ot.h>
+
+namespace vcl::font
+{
+PhysicalFontFace::PhysicalFontFace(const FontAttributes& rDFA)
+ : FontAttributes(rDFA)
+ , mpHbFace(nullptr)
+ , mpHbUnscaledFont(nullptr)
+{
+}
+
+PhysicalFontFace::~PhysicalFontFace()
+{
+ if (mpHbFace)
+ hb_face_destroy(mpHbFace);
+ if (mpHbUnscaledFont)
+ hb_font_destroy(mpHbUnscaledFont);
+}
+
+sal_Int32 PhysicalFontFace::CompareIgnoreSize(const PhysicalFontFace& rOther) const
+{
+ // compare their width, weight, italic, style name and family name
+ if (GetWidthType() < rOther.GetWidthType())
+ return -1;
+ else if (GetWidthType() > rOther.GetWidthType())
+ return 1;
+
+ if (GetWeight() < rOther.GetWeight())
+ return -1;
+ else if (GetWeight() > rOther.GetWeight())
+ return 1;
+
+ if (GetItalic() < rOther.GetItalic())
+ return -1;
+ else if (GetItalic() > rOther.GetItalic())
+ return 1;
+
+ sal_Int32 nRet = GetFamilyName().compareTo(rOther.GetFamilyName());
+
+ if (nRet == 0)
+ {
+ nRet = GetStyleName().compareTo(rOther.GetStyleName());
+ }
+
+ return nRet;
+}
+
+static int FamilyNameMatchValue(FontSelectPattern const& rFSP, std::u16string_view sFontFamily)
+{
+ const OUString& rFontName = rFSP.maTargetName;
+
+ if (rFontName.equalsIgnoreAsciiCase(sFontFamily))
+ return 240000;
+
+ return 0;
+}
+
+static int StyleNameMatchValue(FontMatchStatus const& rStatus, std::u16string_view rStyle)
+{
+ if (rStatus.mpTargetStyleName
+ && o3tl::equalsIgnoreAsciiCase(rStyle, *rStatus.mpTargetStyleName))
+ return 120000;
+
+ return 0;
+}
+
+static int PitchMatchValue(FontSelectPattern const& rFSP, FontPitch ePitch)
+{
+ if ((rFSP.GetPitch() != PITCH_DONTKNOW) && (rFSP.GetPitch() == ePitch))
+ return 20000;
+
+ return 0;
+}
+
+static int PreferNormalFontWidthMatchValue(FontWidth eWidthType)
+{
+ // TODO: change when the upper layers can tell their width preference
+ if (eWidthType == WIDTH_NORMAL)
+ return 400;
+ else if ((eWidthType == WIDTH_SEMI_EXPANDED) || (eWidthType == WIDTH_SEMI_CONDENSED))
+ return 300;
+
+ return 0;
+}
+
+static int WeightMatchValue(FontSelectPattern const& rFSP, FontWeight eWeight)
+{
+ int nMatch = 0;
+
+ if (rFSP.GetWeight() != WEIGHT_DONTKNOW)
+ {
+ // if not bold or requiring emboldening prefer light fonts to bold fonts
+ FontWeight ePatternWeight = rFSP.mbEmbolden ? WEIGHT_NORMAL : rFSP.GetWeight();
+
+ int nReqWeight = static_cast<int>(ePatternWeight);
+ if (ePatternWeight > WEIGHT_MEDIUM)
+ nReqWeight += 100;
+
+ int nGivenWeight = static_cast<int>(eWeight);
+ if (eWeight > WEIGHT_MEDIUM)
+ nGivenWeight += 100;
+
+ int nWeightDiff = nReqWeight - nGivenWeight;
+
+ if (nWeightDiff == 0)
+ nMatch += 1000;
+ else if (nWeightDiff == +1 || nWeightDiff == -1)
+ nMatch += 700;
+ else if (nWeightDiff < +50 && nWeightDiff > -50)
+ nMatch += 200;
+ }
+ else
+ {
+ // prefer NORMAL font weight
+ // TODO: change when the upper layers can tell their weight preference
+ if (eWeight == WEIGHT_NORMAL)
+ nMatch += 450;
+ else if (eWeight == WEIGHT_MEDIUM)
+ nMatch += 350;
+ else if ((eWeight == WEIGHT_SEMILIGHT) || (eWeight == WEIGHT_SEMIBOLD))
+ nMatch += 200;
+ else if (eWeight == WEIGHT_LIGHT)
+ nMatch += 150;
+ }
+
+ return nMatch;
+}
+
+static int ItalicMatchValue(FontSelectPattern const& rFSP, FontItalic eItalic)
+{
+ // if requiring custom matrix to fake italic, prefer upright font
+ FontItalic ePatternItalic
+ = rFSP.maItalicMatrix != ItalicMatrix() ? ITALIC_NONE : rFSP.GetItalic();
+
+ if (ePatternItalic == ITALIC_NONE)
+ {
+ if (eItalic == ITALIC_NONE)
+ return 900;
+ }
+ else
+ {
+ if (ePatternItalic == eItalic)
+ return 900;
+ else if (eItalic != ITALIC_NONE)
+ return 600;
+ }
+
+ return 0;
+}
+
+bool PhysicalFontFace::IsBetterMatch(const FontSelectPattern& rFSP, FontMatchStatus& rStatus) const
+{
+ int nMatch = FamilyNameMatchValue(rFSP, GetFamilyName());
+ nMatch += StyleNameMatchValue(rStatus, GetStyleName());
+ nMatch += PitchMatchValue(rFSP, GetPitch());
+ nMatch += PreferNormalFontWidthMatchValue(GetWidthType());
+ nMatch += WeightMatchValue(rFSP, GetWeight());
+ nMatch += ItalicMatchValue(rFSP, GetItalic());
+
+ if (rFSP.mnOrientation != 0_deg10)
+ nMatch += 80;
+ else if (rFSP.mnWidth != 0)
+ nMatch += 25;
+ else
+ nMatch += 5;
+
+ if (rStatus.mnFaceMatch > nMatch)
+ {
+ return false;
+ }
+ else if (rStatus.mnFaceMatch < nMatch)
+ {
+ rStatus.mnFaceMatch = nMatch;
+ return true;
+ }
+
+ return true;
+}
+
+RawFontData PhysicalFontFace::GetRawFontData(uint32_t nTag) const
+{
+ auto pHbFace = GetHbFace();
+ // If nTag is 0, reference the whole font.
+ if (!nTag)
+ return RawFontData(hb_face_reference_blob(pHbFace));
+ return RawFontData(hb_face_reference_table(pHbFace, nTag));
+}
+
+static hb_blob_t* getTable(hb_face_t*, hb_tag_t nTag, void* pUserData)
+{
+ return static_cast<const PhysicalFontFace*>(pUserData)->GetHbTable(nTag);
+}
+
+hb_face_t* PhysicalFontFace::GetHbFace() const
+{
+ if (mpHbFace == nullptr)
+ mpHbFace
+ = hb_face_create_for_tables(getTable, const_cast<PhysicalFontFace*>(this), nullptr);
+ return mpHbFace;
+}
+
+hb_font_t* PhysicalFontFace::GetHbUnscaledFont() const
+{
+ if (mpHbUnscaledFont == nullptr)
+ mpHbUnscaledFont = hb_font_create(GetHbFace());
+ return mpHbUnscaledFont;
+}
+
+FontCharMapRef PhysicalFontFace::GetFontCharMap() const
+{
+ if (mxCharMap.is())
+ return mxCharMap;
+
+ // Check if this font is using symbol cmap subtable, most likely redundant
+ // since HarfBuzz handles mapping symbol fonts for us.
+ RawFontData aData(GetRawFontData(HB_TAG('c', 'm', 'a', 'p')));
+ bool bSymbol = HasMicrosoftSymbolCmap(aData.data(), aData.size());
+
+ hb_face_t* pHbFace = GetHbFace();
+ hb_set_t* pUnicodes = hb_set_create();
+ hb_face_collect_unicodes(pHbFace, pUnicodes);
+
+ if (hb_set_get_population(pUnicodes))
+ {
+ // Convert HarfBuzz set to code ranges.
+ std::vector<sal_UCS4> aRangeCodes;
+ hb_codepoint_t nFirst, nLast = HB_SET_VALUE_INVALID;
+ while (hb_set_next_range(pUnicodes, &nFirst, &nLast))
+ {
+ aRangeCodes.push_back(nFirst);
+ aRangeCodes.push_back(nLast + 1);
+ }
+
+ mxCharMap = new FontCharMap(bSymbol, std::move(aRangeCodes));
+ }
+
+ hb_set_destroy(pUnicodes);
+
+ if (!mxCharMap.is())
+ mxCharMap = FontCharMap::GetDefaultMap(IsMicrosoftSymbolEncoded());
+
+ return mxCharMap;
+}
+
+bool PhysicalFontFace::GetFontCapabilities(vcl::FontCapabilities& rFontCapabilities) const
+{
+ if (!mxFontCapabilities)
+ {
+ mxFontCapabilities.emplace();
+ RawFontData aData(GetRawFontData(HB_TAG('O', 'S', '/', '2')));
+ getTTCoverage(mxFontCapabilities->oUnicodeRange, mxFontCapabilities->oCodePageRange,
+ aData.data(), aData.size());
+ }
+
+ rFontCapabilities = *mxFontCapabilities;
+ return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
+}
+
+namespace
+{
+class RawFace
+{
+public:
+ RawFace(hb_face_t* pFace)
+ : mpFace(hb_face_reference(pFace))
+ {
+ }
+
+ RawFace(const RawFace& rOther)
+ : mpFace(hb_face_reference(rOther.mpFace))
+ {
+ }
+
+ ~RawFace() { hb_face_destroy(mpFace); }
+
+ RawFontData GetTable(uint32_t nTag) const
+ {
+ return RawFontData(hb_face_reference_table(mpFace, nTag));
+ }
+
+private:
+ hb_face_t* mpFace;
+};
+
+class TrueTypeFace final : public AbstractTrueTypeFont
+{
+ const RawFace m_aFace;
+ mutable std::array<RawFontData, NUM_TAGS> m_aTableList;
+
+ const RawFontData& table(sal_uInt32 nIdx) const
+ {
+ assert(nIdx < NUM_TAGS);
+ static const uint32_t aTags[NUM_TAGS] = {
+ T_maxp, T_glyf, T_head, T_loca, T_name, T_hhea, T_hmtx, T_cmap,
+ T_vhea, T_vmtx, T_OS2, T_post, T_cvt, T_prep, T_fpgm, T_CFF,
+ };
+ if (m_aTableList[nIdx].empty())
+ m_aTableList[nIdx] = std::move(m_aFace.GetTable(aTags[nIdx]));
+ return m_aTableList[nIdx];
+ }
+
+public:
+ TrueTypeFace(RawFace aFace, const FontCharMapRef rCharMap)
+ : AbstractTrueTypeFont(nullptr, rCharMap)
+ , m_aFace(std::move(aFace))
+ {
+ }
+
+ bool hasTable(sal_uInt32 nIdx) const override { return !table(nIdx).empty(); }
+ const sal_uInt8* table(sal_uInt32 nIdx, sal_uInt32& nSize) const override
+ {
+ auto& rTable = table(nIdx);
+ nSize = rTable.size();
+ return rTable.data();
+ }
+};
+}
+
+bool PhysicalFontFace::CreateFontSubset(std::vector<sal_uInt8>& rOutBuffer,
+ const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
+ const int nGlyphCount, FontSubsetInfo& rInfo) const
+{
+ // Prepare data for font subsetter.
+ TrueTypeFace aSftFont(RawFace(GetHbFace()), GetFontCharMap());
+ if (aSftFont.initialize() != SFErrCodes::Ok)
+ return false;
+
+ // write subset into destination file
+ return CreateTTFfontSubset(aSftFont, rOutBuffer, pGlyphIds, pEncoding, nGlyphCount, rInfo);
+}
+
+bool PhysicalFontFace::HasColorLayers() const
+{
+ const auto pHbFace = GetHbFace();
+ return hb_ot_color_has_layers(pHbFace) && hb_ot_color_has_palettes(pHbFace);
+}
+
+const std::vector<ColorPalette>& PhysicalFontFace::GetColorPalettes() const
+{
+ if (!mxColorPalettes)
+ {
+ mxColorPalettes.emplace();
+ const auto pHbFace = GetHbFace();
+ auto nPalettes = hb_ot_color_palette_get_count(pHbFace);
+ mxColorPalettes->reserve(nPalettes);
+ for (auto nPalette = 0u; nPalette < nPalettes; nPalette++)
+ {
+ auto nColors = hb_ot_color_palette_get_colors(pHbFace, nPalette, 0, nullptr, nullptr);
+ ColorPalette aPalette(nColors);
+ for (auto nColor = 0u; nColor < nColors; nColor++)
+ {
+ uint32_t nCount = 1;
+ hb_color_t aColor;
+ hb_ot_color_palette_get_colors(pHbFace, nPalette, nColor, &nCount, &aColor);
+ auto a = hb_color_get_alpha(aColor);
+ auto r = hb_color_get_red(aColor);
+ auto g = hb_color_get_green(aColor);
+ auto b = hb_color_get_blue(aColor);
+ aPalette[nColor] = Color(ColorAlphaTag::ColorAlpha, a, r, g, b);
+ }
+ mxColorPalettes->push_back(aPalette);
+ }
+ }
+
+ return *mxColorPalettes;
+}
+
+std::vector<ColorLayer> PhysicalFontFace::GetGlyphColorLayers(sal_GlyphId nGlyphIndex) const
+{
+ if (!HasColorLayers())
+ return {};
+
+ const auto pHbFace = GetHbFace();
+
+ auto nLayers = hb_ot_color_glyph_get_layers(pHbFace, nGlyphIndex, 0, nullptr, nullptr);
+ std::vector<ColorLayer> aLayers(nLayers);
+ for (auto nLayer = 0u; nLayer < nLayers; nLayer++)
+ {
+ hb_ot_color_layer_t aLayer;
+ uint32_t nCount = 1;
+ hb_ot_color_glyph_get_layers(pHbFace, nGlyphIndex, nLayer, &nCount, &aLayer);
+ aLayers[nLayer] = { aLayer.glyph, aLayer.color_index };
+ }
+
+ return aLayers;
+}
+
+bool PhysicalFontFace::HasColorBitmaps() const { return hb_ot_color_has_png(GetHbFace()); }
+
+RawFontData PhysicalFontFace::GetGlyphColorBitmap(sal_GlyphId nGlyphIndex,
+ tools::Rectangle& rRect) const
+{
+ if (!HasColorBitmaps())
+ return {};
+
+ hb_font_t* pHbFont = GetHbUnscaledFont();
+ auto aData = RawFontData(hb_ot_color_glyph_reference_png(pHbFont, nGlyphIndex));
+ if (!aData.empty())
+ {
+ hb_glyph_extents_t aExtents;
+ if (hb_font_get_glyph_extents(pHbFont, nGlyphIndex, &aExtents))
+ {
+ auto aPoint = Point(aExtents.x_bearing, aExtents.y_bearing + aExtents.height);
+ auto aSize = Size(aExtents.width, -aExtents.height);
+ rRect = tools::Rectangle(aPoint, aSize);
+ }
+ }
+
+ return aData;
+}
+
+OString PhysicalFontFace::GetGlyphName(sal_GlyphId nGlyphIndex, bool bValidate) const
+{
+ char aBuffer[256];
+ hb_font_glyph_to_string(GetHbUnscaledFont(), nGlyphIndex, aBuffer, 256);
+ if (bValidate)
+ {
+ // https://learn.microsoft.com/en-us/typography/opentype/spec/post#version-20
+ // Valid characters are limited to A–Z, a–z, 0–9, “.” (FULL STOP), and “_” (LOW LINE).
+ const char* p = aBuffer;
+ while ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z')
+ || *p == '.' || *p == '_')
+ ++p;
+ if (*p != '\0')
+ return "g" + OString::number(nGlyphIndex);
+ }
+
+ return OString(aBuffer);
+}
+
+OUString PhysicalFontFace::GetName(NameID aNameID, const LanguageTag& rLanguageTag) const
+{
+ auto pHbFace = GetHbFace();
+
+ auto aHbLang = HB_LANGUAGE_INVALID;
+ if (rLanguageTag.getLanguageType() != LANGUAGE_NONE)
+ {
+ auto aLanguage(rLanguageTag.getBcp47().toUtf8());
+ aHbLang = hb_language_from_string(aLanguage.getStr(), aLanguage.getLength());
+ }
+
+ auto nName = hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, nullptr, nullptr);
+ if (!nName && aHbLang == HB_LANGUAGE_INVALID)
+ {
+ // Fallback to English if localized name is missing.
+ aHbLang = hb_language_from_string("en", 2);
+ nName = hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, nullptr, nullptr);
+ }
+
+ OUString sName;
+ if (nName)
+ {
+ std::vector<uint16_t> aBuf(++nName); // make space for terminating NUL.
+ hb_ot_name_get_utf16(pHbFace, aNameID, aHbLang, &nName, aBuf.data());
+ sName = OUString(reinterpret_cast<sal_Unicode*>(aBuf.data()), nName);
+ }
+
+ return sName;
+}
+
+const std::vector<hb_variation_t>& PhysicalFontFace::GetVariations(const LogicalFontInstance&) const
+{
+ if (!mxVariations)
+ {
+ SAL_WARN("vcl.fonts", "Getting font variations is not supported.");
+ mxVariations.emplace();
+ }
+ return *mxVariations;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */