summaryrefslogtreecommitdiffstats
path: root/vcl/unx/generic/fontmanager/fontconfig.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/unx/generic/fontmanager/fontconfig.cxx')
-rw-r--r--vcl/unx/generic/fontmanager/fontconfig.cxx1316
1 files changed, 1316 insertions, 0 deletions
diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx
new file mode 100644
index 000000000..c44cdd2ee
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontconfig.cxx
@@ -0,0 +1,1316 @@
+/* -*- 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 .
+ */
+
+#if defined __GNUC__ && !defined __clang_ && __GNUC__ == 10
+// gcc 10.2.0 gets unhappy about one of the OString inside PrintFont at line 656 (while at least a
+// recent GCC 12 trunk is happy);
+// I have to turn it off here because the warning actually occurs inside rtl::OString
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+
+#include <sal/config.h>
+
+#include <memory>
+#include <string_view>
+
+#include <o3tl/lru_map.hxx>
+#include <unx/fontmanager.hxx>
+#include <unx/helper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclenum.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/unicode.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <unicode/uchar.h>
+#include <unicode/uscript.h>
+#include <officecfg/Office/Common.hxx>
+#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
+#include <config_fonts.h>
+
+using namespace psp;
+
+#include <fontconfig/fontconfig.h>
+
+#include <cstdio>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+#include <osl/process.h>
+
+#include <o3tl/hash_combine.hxx>
+#include <utility>
+#include <algorithm>
+
+using namespace osl;
+
+namespace
+{
+
+struct FontOptionsKey
+{
+ OUString m_sFamilyName;
+ int m_nFontSize;
+ FontItalic m_eItalic;
+ FontWeight m_eWeight;
+ FontWidth m_eWidth;
+ FontPitch m_ePitch;
+
+ bool operator==(const FontOptionsKey& rOther) const
+ {
+ return m_sFamilyName == rOther.m_sFamilyName &&
+ m_nFontSize == rOther.m_nFontSize &&
+ m_eItalic == rOther.m_eItalic &&
+ m_eWeight == rOther.m_eWeight &&
+ m_eWidth == rOther.m_eWidth &&
+ m_ePitch == rOther.m_ePitch;
+ }
+};
+
+}
+
+namespace std
+{
+
+template <> struct hash<FontOptionsKey>
+{
+ std::size_t operator()(const FontOptionsKey& k) const noexcept
+ {
+ std::size_t seed = k.m_sFamilyName.hashCode();
+ o3tl::hash_combine(seed, k.m_nFontSize);
+ o3tl::hash_combine(seed, k.m_eItalic);
+ o3tl::hash_combine(seed, k.m_eWeight);
+ o3tl::hash_combine(seed, k.m_eWidth);
+ o3tl::hash_combine(seed, k.m_ePitch);
+ return seed;
+ }
+};
+
+} // end std namespace
+
+namespace
+{
+
+struct FcPatternDeleter
+{
+ void operator()(FcPattern* pPattern) const
+ {
+ FcPatternDestroy(pPattern);
+ }
+};
+
+typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr;
+
+class CachedFontConfigFontOptions
+{
+private:
+ o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache;
+
+public:
+ CachedFontConfigFontOptions()
+ : lru_options_cache(10) // arbitrary cache size of 10
+ {
+ }
+
+ std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey)
+ {
+ auto it = lru_options_cache.find(rKey);
+ if (it != lru_options_cache.end())
+ return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get()));
+ return nullptr;
+ }
+
+ void cache(const FontOptionsKey& rKey, const FcPattern* pPattern)
+ {
+ lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern))));
+ }
+
+};
+
+typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
+
+class FontCfgWrapper
+{
+ FcFontSet* m_pFontSet;
+
+ void addFontSet( FcSetName );
+
+ FontCfgWrapper();
+ ~FontCfgWrapper();
+
+public:
+ static FontCfgWrapper& get();
+ static void release();
+
+ FcFontSet* getFontSet();
+
+ void clear();
+
+public:
+ FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
+ const char *elementtype, const char *elementlangtype);
+//to-do, make private and add some cleaner accessor methods
+ std::unordered_map< OString, OString > m_aFontNameToLocalized;
+ std::unordered_map< OString, OString > m_aLocalizedToCanonical;
+ CachedFontConfigFontOptions m_aCachedFontOptions;
+private:
+ void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
+
+ std::unique_ptr<LanguageTag> m_pLanguageTag;
+};
+
+}
+
+FontCfgWrapper::FontCfgWrapper()
+ : m_pFontSet( nullptr )
+{
+ FcInit();
+}
+
+void FontCfgWrapper::addFontSet( FcSetName eSetName )
+{
+ // Add only acceptable fonts to our config, for future fontconfig use.
+ FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
+ if( !pOrig )
+ return;
+
+ // filter the font sets to remove obsolete faces
+ for( int i = 0; i < pOrig->nfont; ++i )
+ {
+ FcPattern* pPattern = pOrig->fonts[i];
+ // #i115131# ignore non-scalable fonts
+ // Scalable fonts are usually outline fonts, but some bitmaps fonts
+ // (like Noto Color Emoji) are also scalable.
+ FcBool bScalable = FcFalse;
+ FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
+ if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
+ continue;
+
+ // Ignore Type 1 fonts, too.
+ FcChar8* pFormat = nullptr;
+ FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
+ if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
+ continue;
+
+ FcPatternReference( pPattern );
+ FcFontSetAdd( m_pFontSet, pPattern );
+ }
+
+ // TODO?: FcFontSetDestroy( pOrig );
+}
+
+namespace
+{
+ int compareFontNames(const FcPattern *a, const FcPattern *b)
+ {
+ FcChar8 *pNameA=nullptr, *pNameB=nullptr;
+
+ bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
+ bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
+
+ return int(bHaveA) - int(bHaveB);
+ }
+
+ //Sort fonts so that fonts with the same family name are side-by-side, with
+ //those with higher version numbers first
+ class SortFont
+ {
+ public:
+ bool operator()(const FcPattern *a, const FcPattern *b)
+ {
+ int comp = compareFontNames(a, b);
+ if (comp != 0)
+ return comp < 0;
+
+ int nVersionA=0, nVersionB=0;
+
+ bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
+ bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return nVersionA > nVersionB;
+
+ return bHaveA > bHaveB;
+ }
+ };
+
+ //See fdo#30729 for where an old opensymbol installed system-wide can
+ //clobber the new opensymbol installed locally
+
+ //See if this font is a duplicate with equal attributes which has already been
+ //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
+ //on being sorted with SortFont
+ bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
+ {
+ const FcPattern *a = pFSet->fonts[i];
+
+ FcPattern* pTestPatternA = FcPatternDuplicate(a);
+ FcPatternDel(pTestPatternA, FC_FILE);
+ FcPatternDel(pTestPatternA, FC_CHARSET);
+ FcPatternDel(pTestPatternA, FC_CAPABILITY);
+ FcPatternDel(pTestPatternA, FC_FONTVERSION);
+ FcPatternDel(pTestPatternA, FC_LANG);
+
+ bool bIsDup(false);
+
+ // fdo#66715: loop for case of several font files for same font
+ for (int j = i - 1; 0 <= j && !bIsDup; --j)
+ {
+ const FcPattern *b = pFSet->fonts[j];
+
+ if (compareFontNames(a, b) != 0)
+ break;
+
+ FcPattern* pTestPatternB = FcPatternDuplicate(b);
+ FcPatternDel(pTestPatternB, FC_FILE);
+ FcPatternDel(pTestPatternB, FC_CHARSET);
+ FcPatternDel(pTestPatternB, FC_CAPABILITY);
+ FcPatternDel(pTestPatternB, FC_FONTVERSION);
+ FcPatternDel(pTestPatternB, FC_LANG);
+
+ bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
+
+ FcPatternDestroy(pTestPatternB);
+ }
+
+ FcPatternDestroy(pTestPatternA);
+
+ return bIsDup;
+ }
+}
+
+FcFontSet* FontCfgWrapper::getFontSet()
+{
+ if( !m_pFontSet )
+ {
+ m_pFontSet = FcFontSetCreate();
+ bool bRestrictFontSetToApplicationFonts = false;
+#if HAVE_MORE_FONTS
+ bRestrictFontSetToApplicationFonts = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+#endif
+ if (!bRestrictFontSetToApplicationFonts)
+ addFontSet( FcSetSystem );
+ addFontSet( FcSetApplication );
+
+ std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
+ }
+
+ return m_pFontSet;
+}
+
+FontCfgWrapper::~FontCfgWrapper()
+{
+ clear();
+ //To-Do: get gtk vclplug smoketest to pass
+ //FcFini();
+}
+
+static FontCfgWrapper* pOneInstance = nullptr;
+
+FontCfgWrapper& FontCfgWrapper::get()
+{
+ if( ! pOneInstance )
+ pOneInstance = new FontCfgWrapper();
+ return *pOneInstance;
+}
+
+void FontCfgWrapper::release()
+{
+ if( pOneInstance )
+ {
+ delete pOneInstance;
+ pOneInstance = nullptr;
+ }
+}
+
+namespace
+{
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
+
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
+ {
+ FcChar8* candidate = elements.begin()->second;
+ /* FIXME-BCP47: once fontconfig supports language tags this
+ * language-territory stuff needs to be changed! */
+ SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
+ OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
+ OString sFullMatch = sLangMatch +
+ "-" +
+ OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
+
+ bool alreadyclosematch = false;
+ bool found_fallback_englishname = false;
+ for (auto const& element : elements)
+ {
+ const char *pLang = reinterpret_cast<const char*>(element.first);
+ if( sFullMatch == pLang)
+ {
+ // both language and country match
+ candidate = element.second;
+ break;
+ }
+ else if( alreadyclosematch )
+ {
+ // current candidate matches lang of lang-TERRITORY
+ // override candidate only if there is a full match
+ continue;
+ }
+ else if( sLangMatch == pLang)
+ {
+ // just the language matches
+ candidate = element.second;
+ alreadyclosematch = true;
+ }
+ else if( found_fallback_englishname )
+ {
+ // already found an english fallback, don't override candidate
+ // unless there is a better language match
+ continue;
+ }
+ else if( rtl_str_compare( pLang, "en") == 0)
+ {
+ // select a fallback candidate of the first english element
+ // name
+ candidate = element.second;
+ found_fallback_englishname = true;
+ }
+ }
+ return candidate;
+ }
+}
+
+//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
+void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
+ const std::vector< lang_and_element > &lang_and_elements)
+{
+ for (auto const& element : lang_and_elements)
+ {
+ const char *candidate = reinterpret_cast<const char*>(element.second);
+ if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
+ }
+ if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
+}
+
+FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
+ const char *elementtype, const char *elementlangtype)
+{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */
+ FcChar8 *origelement;
+ FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
+ *element = origelement;
+
+ if( eElementRes == FcResultMatch)
+ {
+ FcChar8* elementlang = nullptr;
+ if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
+ {
+ std::vector< lang_and_element > lang_and_elements;
+ lang_and_elements.emplace_back(elementlang, *element);
+ int k = 1;
+ while (true)
+ {
+ if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
+ break;
+ if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
+ break;
+ lang_and_elements.emplace_back(elementlang, *element);
+ ++k;
+ }
+
+ if (!m_pLanguageTag)
+ m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag()));
+
+ *element = bestname(lang_and_elements, *m_pLanguageTag);
+
+ //if this element is a fontname, map the other names to this best-name
+ if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
+ cacheLocalizedFontNames(origelement, *element, lang_and_elements);
+ }
+ }
+
+ return eElementRes;
+}
+
+void FontCfgWrapper::clear()
+{
+ m_aFontNameToLocalized.clear();
+ m_aLocalizedToCanonical.clear();
+ if( m_pFontSet )
+ {
+ FcFontSetDestroy( m_pFontSet );
+ m_pFontSet = nullptr;
+ }
+ m_pLanguageTag.reset();
+}
+
+/*
+ * PrintFontManager::initFontconfig
+ */
+void PrintFontManager::initFontconfig()
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+ rWrapper.clear();
+}
+
+namespace
+{
+ FontWeight convertWeight(int weight)
+ {
+ // set weight
+ if( weight <= FC_WEIGHT_THIN )
+ return WEIGHT_THIN;
+ else if( weight <= FC_WEIGHT_ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if( weight <= FC_WEIGHT_LIGHT )
+ return WEIGHT_LIGHT;
+ else if( weight <= FC_WEIGHT_BOOK )
+ return WEIGHT_SEMILIGHT;
+ else if( weight <= FC_WEIGHT_NORMAL )
+ return WEIGHT_NORMAL;
+ else if( weight <= FC_WEIGHT_MEDIUM )
+ return WEIGHT_MEDIUM;
+ else if( weight <= FC_WEIGHT_SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if( weight <= FC_WEIGHT_BOLD )
+ return WEIGHT_BOLD;
+ else if( weight <= FC_WEIGHT_ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ return WEIGHT_BLACK;
+ }
+
+ FontItalic convertSlant(int slant)
+ {
+ // set italic
+ if( slant == FC_SLANT_ITALIC )
+ return ITALIC_NORMAL;
+ else if( slant == FC_SLANT_OBLIQUE )
+ return ITALIC_OBLIQUE;
+ return ITALIC_NONE;
+ }
+
+ FontPitch convertSpacing(int spacing)
+ {
+ // set pitch
+ if( spacing == FC_MONO || spacing == FC_CHARCELL )
+ return PITCH_FIXED;
+ return PITCH_VARIABLE;
+ }
+
+ // translation: fontconfig enum -> vcl enum
+ FontWidth convertWidth(int width)
+ {
+ if (width == FC_WIDTH_ULTRACONDENSED)
+ return WIDTH_ULTRA_CONDENSED;
+ else if (width == FC_WIDTH_EXTRACONDENSED)
+ return WIDTH_EXTRA_CONDENSED;
+ else if (width == FC_WIDTH_CONDENSED)
+ return WIDTH_CONDENSED;
+ else if (width == FC_WIDTH_SEMICONDENSED)
+ return WIDTH_SEMI_CONDENSED;
+ else if (width == FC_WIDTH_SEMIEXPANDED)
+ return WIDTH_SEMI_EXPANDED;
+ else if (width == FC_WIDTH_EXPANDED)
+ return WIDTH_EXPANDED;
+ else if (width == FC_WIDTH_EXTRAEXPANDED)
+ return WIDTH_EXTRA_EXPANDED;
+ else if (width == FC_WIDTH_ULTRAEXPANDED)
+ return WIDTH_ULTRA_EXPANDED;
+ return WIDTH_NORMAL;
+ }
+}
+
+//FontConfig doesn't come with a way to remove an element from a FontSet as far
+//as I can see
+static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i)
+{
+ FcPatternDestroy(pFSet->fonts[i]);
+
+ int nTail = pFSet->nfont - (i + 1);
+ --pFSet->nfont;
+ if (!nTail)
+ return;
+ memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*));
+}
+
+namespace
+{
+ // for variable fonts, FC_INDEX has been changed such that the lower half is now the
+ // index of the font within the collection, and the upper half has been repurposed
+ // as the index within the variations
+ unsigned int GetCollectionIndex(unsigned int nEntryId)
+ {
+ return nEntryId & 0xFFFF;
+ }
+
+ unsigned int GetVariationIndex(unsigned int nEntryId)
+ {
+ return nEntryId >> 16;
+ }
+}
+
+void PrintFontManager::countFontconfigFonts()
+{
+ int nFonts = 0;
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcFontSet* pFSet = rWrapper.getFontSet();
+ const bool bMinimalFontset = utl::ConfigManager::IsFuzzing();
+ if( pFSet )
+ {
+ SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
+ for( int i = 0; i < pFSet->nfont; i++ )
+ {
+ FcChar8* file = nullptr;
+ FcChar8* family = nullptr;
+ FcChar8* style = nullptr;
+ FcChar8* format = nullptr;
+ int slant = 0;
+ int weight = 0;
+ int width = 0;
+ int spacing = 0;
+ int nEntryId = -1;
+ FcBool scalable = false;
+
+ FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
+ FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
+ if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
+ continue;
+ FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG );
+ FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant);
+ FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
+ FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
+ FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
+ FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
+ FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId);
+ FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
+
+ if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch )
+ continue;
+
+ SAL_INFO(
+ "vcl.fonts.detail",
+ "found font \"" << family << "\" in file " << file << ", weight = "
+ << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
+ << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
+ << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
+ << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
+ << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
+ << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
+ << (eFormatRes == FcResultMatch
+ ? reinterpret_cast<const char*>(format) : "<unknown>"));
+
+// OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
+
+ // only scalable fonts are usable to psprint anyway
+ if( eScalableRes == FcResultMatch && ! scalable )
+ continue;
+
+ if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
+ {
+ SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
+ continue;
+ }
+
+ // see if this font is already cached
+ // update attributes
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+
+ int nDirID = getDirectoryAtom( aDir );
+ SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached");
+ // not known, analyze font file to get attributes
+ // not described by fontconfig (e.g. alias names, PSName)
+ if (eFormatRes != FcResultMatch)
+ format = nullptr;
+ std::vector<PrintFont> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) );
+ if(aFonts.empty())
+ {
+ SAL_INFO(
+ "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint");
+ //remove font, reuse index
+ //we want to remove unusable fonts here, in case there is a usable font
+ //which duplicates the properties of the unusable one
+
+ //not removing the unusable font will risk the usable font being rejected
+ //as a duplicate by isPreviouslyDuplicateOrObsoleted
+ lcl_FcFontSetRemove(pFSet, i--);
+ continue;
+ }
+
+ std::optional<PrintFont> xUpdate;
+
+ if (aFonts.size() == 1) // one font
+ xUpdate = aFonts.front();
+ else // more than one font
+ {
+ // a collection entry, get the correct index
+ if( eIndexRes == FcResultMatch && nEntryId != -1 )
+ {
+ int nCollectionEntry = GetCollectionIndex(nEntryId);
+ for (const auto & font : aFonts)
+ {
+ if( font.m_nCollectionEntry == nCollectionEntry )
+ {
+ xUpdate = font;
+ break;
+ }
+ }
+ }
+
+ if (xUpdate)
+ {
+ // update collection entry
+ // additional entries will be created in the cache
+ // if this is a new index (that is if the loop above
+ // ran to the end of the list)
+ xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId);
+ }
+ else
+ {
+ SAL_INFO(
+ "vcl.fonts",
+ "multiple fonts for file, but no index in fontconfig pattern ! (index res ="
+ << eIndexRes << " collection entry = " << nEntryId
+ << "; file will not be used");
+ // we have found more than one font in this file
+ // but fontconfig will not tell us which index is meant
+ // -> something is in disorder, do not use this font
+ }
+ }
+
+ if (xUpdate)
+ {
+ // set family name
+ if( eWeightRes == FcResultMatch )
+ xUpdate->m_eWeight = convertWeight(weight);
+ if( eWidthRes == FcResultMatch )
+ xUpdate->m_eWidth = convertWidth(width);
+ if( eSpacRes == FcResultMatch )
+ xUpdate->m_ePitch = convertSpacing(spacing);
+ if( eSlantRes == FcResultMatch )
+ xUpdate->m_eItalic = convertSlant(slant);
+ if( eStyleRes == FcResultMatch )
+ xUpdate->m_aStyleName = OStringToOUString( std::string_view( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 );
+ if( eIndexRes == FcResultMatch )
+ xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId);
+
+ // sort into known fonts
+ fontID aFont = m_nNextFontID++;
+ m_aFonts.emplace( aFont, *xUpdate );
+ m_aFontFileToFontID[ aBase ].insert( aFont );
+ nFonts++;
+ SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont);
+ }
+ }
+ }
+
+ // how does one get rid of the config ?
+ SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
+}
+
+void PrintFontManager::deinitFontconfig()
+{
+ FontCfgWrapper::release();
+}
+
+void PrintFontManager::addFontconfigDir( const OString& rDirName )
+{
+ const char* pDirName = rDirName.getStr();
+ bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
+
+ SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
+
+ if( !bDirOk )
+ return;
+
+ // load dir-specific fc-config file too if available
+ const OString aConfFileName = rDirName + "/fc_local.conf";
+ FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
+ if( pCfgFile )
+ {
+ fclose( pCfgFile);
+ bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
+ reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
+
+ SAL_INFO_IF(!bCfgOk,
+ "vcl.fonts", "FcConfigParseAndLoad( \""
+ << aConfFileName << "\") => " << bCfgOk);
+ } else {
+ SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
+ }
+}
+
+static void addtopattern(FcPattern *pPattern,
+ FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
+{
+ if( eItalic != ITALIC_DONTKNOW )
+ {
+ int nSlant = FC_SLANT_ROMAN;
+ switch( eItalic )
+ {
+ case ITALIC_NORMAL:
+ nSlant = FC_SLANT_ITALIC;
+ break;
+ case ITALIC_OBLIQUE:
+ nSlant = FC_SLANT_OBLIQUE;
+ break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
+ }
+ if( eWeight != WEIGHT_DONTKNOW )
+ {
+ int nWeight = FC_WEIGHT_NORMAL;
+ switch( eWeight )
+ {
+ case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break;
+ case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break;
+ case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break;
+ case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break;
+ case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break;
+ case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break;
+ case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break;
+ case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break;
+ case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break;
+ case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
+ }
+ if( eWidth != WIDTH_DONTKNOW )
+ {
+ int nWidth = FC_WIDTH_NORMAL;
+ switch( eWidth )
+ {
+ case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
+ case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
+ case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break;
+ case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break;
+ case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break;
+ case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break;
+ case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break;
+ case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break;
+ case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
+ }
+ if( ePitch == PITCH_DONTKNOW )
+ return;
+
+ int nSpacing = FC_PROPORTIONAL;
+ switch( ePitch )
+ {
+ case PITCH_FIXED: nSpacing = FC_MONO;break;
+ case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
+ if (nSpacing == FC_MONO)
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
+}
+
+namespace
+{
+ //Someday fontconfig will hopefully use bcp47, see fdo#19869
+ //In the meantime try something that will fit to workaround fdo#35118
+ OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
+ {
+#if defined(FC_VERSION) && (FC_VERSION >= 20492)
+ std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
+ OString sLangAttrib;
+
+ sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+
+ if (!sRegion.isEmpty())
+ {
+ sLangAttrib = sLang + "-" + sRegion;
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+ }
+
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
+ {
+ return sLang;
+ }
+
+ return OString();
+#else
+ OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (sLangAttrib.equalsIgnoreAsciiCase("pa-in"))
+ sLangAttrib = "pa";
+ return sLangAttrib;
+#endif
+ }
+
+ bool isEmoji(sal_uInt32 nCurrentChar)
+ {
+#if U_ICU_VERSION_MAJOR_NUM >= 57
+ return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
+#else
+ return false;
+#endif
+ }
+
+ //returns true if the given code-point couldn't possibly be in rLangTag.
+ bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
+ {
+ //a non-default script is set, lets believe it
+ if (rLangTag.hasScript())
+ return false;
+
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ bool bIsImpossible = false;
+ OUString sLang = rLangTag.getLanguage();
+ switch (eScript)
+ {
+ //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
+ case USCRIPT_ORIYA:
+ bIsImpossible =
+ sLang != "or" &&
+ sLang != "kxv";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
+ case USCRIPT_TELUGU:
+ bIsImpossible =
+ sLang != "te" &&
+ sLang != "gon" &&
+ sLang != "kfc";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
+ case USCRIPT_BENGALI:
+ bIsImpossible =
+ sLang != "bn" &&
+ sLang != "as" &&
+ sLang != "bpy" &&
+ sLang != "ctg" &&
+ sLang != "sa";
+ break;
+ default:
+ break;
+ }
+ SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
+ << sLang << " because the detected script for '0x"
+ << OUString::number(currentChar, 16)
+ << "' is " << uscript_getName(eScript)
+ << " and that language doesn't make sense. Autodetecting instead.");
+ return bIsImpossible;
+ }
+
+ OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
+ {
+ if (isEmoji(currentChar))
+ return "und-zsye";
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
+ if (const char* pScriptCode = uscript_getShortName(eScript))
+ aBuf.append('-').append(pScriptCode);
+ return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
+{
+ try
+ {
+ using namespace org::freedesktop::PackageKit;
+ css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
+ xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished");
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
+ // Disable this method from now on. It's simply not available on some systems
+ // and leads to an error dialog being shown each time this is called tdf#104883
+ std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
+ officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
+ batch->commit();
+ }
+
+ m_aCurrentRequests.clear();
+}
+
+void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes)
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ // build pattern argument for fontconfig query
+ FcPattern* pPattern = FcPatternCreate();
+
+ // Prefer scalable fonts
+ FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
+
+ const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
+ const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
+ FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
+
+ LanguageTag aLangTag(rPattern.meLanguage);
+ OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+
+ bool bMissingJustBullet = false;
+
+ // Add required Unicode characters, if any
+ if ( !rMissingCodes.isEmpty() )
+ {
+ FcCharSet *codePoints = FcCharSetCreate();
+ bMissingJustBullet = rMissingCodes.getLength() == 1 && rMissingCodes[0] == 0xb7;
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle unicode surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ FcCharSetAddChar( codePoints, nCode );
+ //if the codepoint is impossible for this lang tag, then clear it
+ //and autodetect something useful
+ if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
+ aLangAttrib.clear();
+ //#i105784#/rhbz#527719 improve selection of fallback font
+ if (aLangAttrib.isEmpty())
+ {
+ aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
+ aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ }
+ }
+ FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
+ FcCharSetDestroy(codePoints);
+ }
+
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
+ rPattern.GetWidthType(), rPattern.GetPitch());
+
+ // query fontconfig for a substitute
+ FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+
+ // process the result of the fontconfig query
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
+ FcPatternDestroy( pPattern );
+
+ FcFontSet* pSet = nullptr;
+ if( pResult )
+ {
+ pSet = FcFontSetCreate();
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetAdd( pSet, pResult );
+ }
+
+ if( pSet )
+ {
+ if( pSet->nfont > 0 )
+ {
+ bool bRet = false;
+
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ {
+ FastPrintFontInfo aInfo;
+ bRet = getFontFastInfo( aFont, aInfo );
+ rPattern.maSearchName = aInfo.m_aFamilyName;
+ }
+ }
+
+ SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
+
+ if (!bRet)
+ {
+ FcChar8* family = nullptr;
+ FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
+
+ // get the family name
+ if( eFamilyRes == FcResultMatch )
+ {
+ OString sFamily(reinterpret_cast<char*>(family));
+ std::unordered_map< OString, OString >::const_iterator aI =
+ rWrapper.m_aFontNameToLocalized.find(sFamily);
+ if (aI != rWrapper.m_aFontNameToLocalized.end())
+ sFamily = aI->second;
+ rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ {
+ int val = 0;
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
+ rPattern.SetWeight( convertWeight(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
+ rPattern.SetItalic( convertSlant(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
+ rPattern.SetPitch ( convertSpacing(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
+ rPattern.SetWidthType ( convertWidth(val) );
+ FcBool bEmbolden;
+ if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
+ rPattern.mbEmbolden = bEmbolden;
+ FcMatrix *pMatrix = nullptr;
+ if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
+ {
+ rPattern.maItalicMatrix.xx = pMatrix->xx;
+ rPattern.maItalicMatrix.xy = pMatrix->xy;
+ rPattern.maItalicMatrix.yx = pMatrix->yx;
+ rPattern.maItalicMatrix.yy = pMatrix->yy;
+ }
+ }
+
+ // update rMissingCodes by removing resolved code points
+ if( !rMissingCodes.isEmpty() )
+ {
+ std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
+ int nRemainingLen = 0;
+ FcCharSet* codePoints;
+ if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
+ {
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
+ pRemainingCodes[ nRemainingLen++ ] = nCode;
+ }
+ }
+ OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
+ if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
+ {
+ if (sStillMissing == rMissingCodes) //replaced nothing
+ {
+ //It'd be better if we could ask packagekit using the
+ //missing codepoints or some such rather than using
+ //"language" as a proxy to how fontconfig considers
+ //scripts to default to a given language.
+ for (sal_Int32 i = 0; i < nRemainingLen; ++i)
+ {
+ LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
+ OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
+ if (!m_aPreviousLangSupportRequests.insert(sTag).second)
+ continue;
+ sTag = mapToFontConfigLangTag(aOurTag);
+ if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
+ {
+ OString sReq = OString::Concat(":lang=") + sTag;
+ m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
+ m_aPreviousLangSupportRequests.insert(sTag);
+ }
+ }
+ }
+ if (!m_aCurrentRequests.empty())
+ m_aFontInstallerTimer.Start();
+ }
+ rMissingCodes = sStillMissing;
+ }
+ }
+
+ FcFontSetDestroy( pSet );
+ }
+
+ SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
+ << rPattern.maTargetName << "' with '" << rPattern.maSearchName
+ << "'");
+
+ static bool bAbortOnFontSubstitute = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+ if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName)
+ {
+ if (bMissingJustBullet)
+ {
+ assert(rPattern.maTargetName == "Amiri Quran" || rPattern.maTargetName == "David CLM" ||
+ rPattern.maTargetName == "EmojiOne Color" || rPattern.maTargetName == "Frank Ruehl CLM" ||
+ rPattern.maTargetName == "KacstBook" || rPattern.maTargetName == "KacstOffice");
+ // These fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT
+ // so will always glyph fallback on measuring mnBulletOffset in
+ // ImplFontMetricData::ImplInitTextLineSize
+ return;
+ }
+ if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O")
+ return;
+ SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName <<
+ "' try: " << rPattern.maSearchName << " instead");
+ std::abort();
+ }
+}
+
+FontConfigFontOptions::~FontConfigFontOptions()
+{
+ FcPatternDestroy(mpPattern);
+}
+
+FcPattern *FontConfigFontOptions::GetPattern() const
+{
+ return mpPattern;
+}
+
+void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden)
+{
+ FcPatternDel(mpPattern, FC_FILE);
+ FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
+ FcPatternDel(mpPattern, FC_INDEX);
+ sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
+ FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
+ FcPatternDel(mpPattern, FC_EMBOLDEN);
+ FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
+}
+
+std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize)
+{
+ FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(),
+ rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() };
+
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey);
+ if (pOptions)
+ return pOptions;
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8);
+
+ std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
+ if (aI != rWrapper.m_aLocalizedToCanonical.end())
+ sFamily = aI->second;
+ if( !sFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
+
+ addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch);
+ FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FontConfigFontOptions::cairo_font_options_substitute(pPattern);
+ FcDefaultSubstitute(pPattern);
+
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult))
+ {
+ rWrapper.m_aCachedFontOptions.cache(aKey, pResult);
+ pOptions.reset(new FontConfigFontOptions(pResult));
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+
+ return pOptions;
+}
+
+
+void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale )
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ // populate pattern with font characteristics
+ const LanguageTag aLangTag(rLocale);
+ const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
+ if( !aFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
+
+ addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet *pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
+ if( pResult )
+ {
+ FcFontSet* pSet = FcFontSetCreate();
+ FcFontSetAdd( pSet, pResult );
+ if( pSet->nfont > 0 )
+ {
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID aFont = findFontFileID(nDirID, aBase,
+ GetCollectionIndex(nEntryId),
+ GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ getFontFastInfo( aFont, rInfo );
+ }
+ }
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetDestroy( pSet );
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */