/* -*- 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 <iostream>
#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 <comphelper/diagnose_ex.hxx>
#include <unicode/uchar.h>
#include <unicode/uscript.h>
#include <officecfg/Office/Common.hxx>
#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
#include <config_fonts.h>
#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 psp;
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;
bool m_bRestrictFontSetToApplicationFonts;
FontCfgWrapper();
~FontCfgWrapper();
public:
static FontCfgWrapper& get();
static void release();
void addFontSet( FcSetName );
FcFontSet* getFontSet();
void replaceFontSet(FcFontSet* pFilteredFontSet);
void clear();
bool isRestrictingFontSetForTesting() const
{
return m_bRestrictFontSetToApplicationFonts;
}
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)
, m_bRestrictFontSetToApplicationFonts(false)
{
FcInit();
}
#ifndef FC_FONT_WRAPPER
#define FC_FONT_WRAPPER "fontwrapper"
#endif
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;
// Ignore any other non-SFNT wrapper format, including WOFF and WOFF2, too.
FcChar8* pWrapper = nullptr;
FcResult eWrapperRes = FcPatternGetString(pPattern, FC_FONT_WRAPPER, 0, &pWrapper);
if ((eWrapperRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pWrapper), "SFNT") != 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
//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_INDEX);
FcPatternDel(pTestPatternA, FC_LANG);
FcPatternDel(pTestPatternA, FC_POSTSCRIPT_NAME);
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_INDEX);
FcPatternDel(pTestPatternB, FC_LANG);
FcPatternDel(pTestPatternB, FC_POSTSCRIPT_NAME);
bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
FcPatternDestroy(pTestPatternB);
}
FcPatternDestroy(pTestPatternA);
return bIsDup;
}
}
FcFontSet* FontCfgWrapper::getFontSet()
{
if( !m_pFontSet )
{
m_pFontSet = FcFontSetCreate();
#if HAVE_MORE_FONTS
m_bRestrictFontSetToApplicationFonts = [] {
return getenv("SAL_NON_APPLICATION_FONT_USE") != nullptr;
}();
#endif
// Add the application fonts before the system fonts.
// tdf#157939 We will remove duplicate fonts, where the duplicate is
// the one with a smaller version number. If the same version font is
// available system-wide or bundled with our application, then we
// prefer via stable-sort the first one we see. Load application fonts
// first to prefer the one we bundle in the application in that case.
addFontSet( FcSetApplication );
if (!m_bRestrictFontSetToApplicationFonts)
addFontSet( FcSetSystem );
std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
}
return m_pFontSet;
}
void FontCfgWrapper::replaceFontSet(FcFontSet* pFilteredFontSet)
{
if (m_pFontSet)
FcFontSetDestroy(m_pFontSet);
m_pFontSet = pFilteredFontSet;
}
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()));
// FontConfig orders Typographic Family/Subfamily before old
// R/B/I/BI-compatible ones, but we want the later, so reverse the
// names to match them first.
std::reverse(lang_and_elements.begin(), lang_and_elements.end());
*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;
}
}
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 = comphelper::IsFuzzing();
if( pFSet )
{
SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
FcFontSet* pFilteredSet = FcFontSetCreate();
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 symbol = 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 eSymbolRes = FcPatternGetBool(pFSet->fonts[i], FC_SYMBOL, 0, &symbol);
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 || eStyleRes != 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>")
<< " symbol = " << (eSymbolRes == FcResultMatch ? symbol : -1));
// OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
// We support only scalable fonts
if( eScalableRes == FcResultMatch && ! scalable )
continue;
if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
{
SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
continue;
}
OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
splitPath( aOrgPath, aDir, aBase );
int nDirID = getDirectoryAtom( aDir );
PrintFont aFont;
aFont.m_nDirectory = nDirID;
aFont.m_aFontFile = aBase;
if (eIndexRes == FcResultMatch)
{
aFont.m_nCollectionEntry = GetCollectionIndex(nEntryId);
aFont.m_nVariationEntry = GetVariationIndex(nEntryId);
}
auto& rFA = aFont.m_aFontAttributes;
rFA.SetWeight(WEIGHT_NORMAL);
rFA.SetWidthType(WIDTH_NORMAL);
rFA.SetPitch(PITCH_VARIABLE);
rFA.SetQuality(512);
rFA.SetFamilyName(OStringToOUString(std::string_view(reinterpret_cast<char*>(family)), RTL_TEXTENCODING_UTF8));
if (eStyleRes == FcResultMatch)
rFA.SetStyleName(OStringToOUString(std::string_view(reinterpret_cast<char*>(style)), RTL_TEXTENCODING_UTF8));
if (eWeightRes == FcResultMatch)
rFA.SetWeight(convertWeight(weight));
if (eWidthRes == FcResultMatch)
rFA.SetWidthType(convertWidth(width));
if (eSpacRes == FcResultMatch)
rFA.SetPitch(convertSpacing(spacing));
if (eSlantRes == FcResultMatch)
rFA.SetItalic(convertSlant(slant));
if (eSymbolRes == FcResultMatch)
rFA.SetMicrosoftSymbolEncoded(bool(symbol));
// sort into known fonts
fontID nFontID = m_nNextFontID++;
m_aFonts.emplace(nFontID, aFont);
m_aFontFileToFontID[aBase].insert(nFontID);
nFonts++;
FcPattern* pPattern = pFSet->fonts[i];
FcPatternReference(pPattern);
FcFontSetAdd(pFilteredSet, pPattern);
SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << nFontID);
}
// tdf#157939 if we drop fonts, drop them from the FcConfig set too so they are not
// candidates for suggestions by fontconfig
if (pFSet->nfont != pFilteredSet->nfont)
rWrapper.replaceFontSet(pFilteredSet);
else
FcFontSetDestroy(pFilteredSet);
}
// 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);
}
}
void PrintFontManager::addFontconfigFile( const OString& rFileName )
{
const char* pFileName = rFileName.getStr();
bool bFileOk = (FcConfigAppFontAddFile(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pFileName) ) == FcTrue);
SAL_INFO("vcl.fonts", "FcConfigAppFontAddFile(\"" << pFileName << "\") => " << std::boolalpha << bFileOk);
if( !bFileOk )
return;
// FIXME: we want to add only the newly added font not re-add the whole
// application font set.
FontCfgWrapper& rWrapper = FontCfgWrapper::get();
rWrapper.addFontSet( FcSetApplication );
}
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:
//https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/50
//In the meantime try something that will fit to workaround, see:
//https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/30
OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
{
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();
}
bool isEmoji(sal_uInt32 nCurrentChar)
{
return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
}
//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, let's 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 u"und-zsye"_ustr;
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(OStringChar('-') + 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), u"hide-finished"_ustr);
}
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);
// Try to map tools FontFamily to fontconfig FC_FAMILY. Note that FcPatternAddString() appends
// to a list, so it won't overwrite the previous FcPatternAddString(FC_FAMILY), this way we can
// express that we wanted a certain font, and otherwise a given family style.
FontFamily eFamilyType = rPattern.GetFamilyType();
switch (eFamilyType)
{
case FAMILY_ROMAN:
FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<const FcChar8*>("serif"));
break;
case FAMILY_SWISS:
FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<const FcChar8*>("sans"));
break;
default:
break;
}
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()));
// bodge: testTdf153440 wants a fallback to an emoji font it adds as a temp
// testing font which has the required glyphs, but that emoji font is not
// seen as a "color" font, while it is possible that OpenDyslexic can be
// bundled, which *is* a "color" font. The default rules (See in Fedora 38
// at least) then prefer a color font *without* the glyphs over a non-color
// font *with* the glyphs, which seems like a bug to me.
// Maybe this is an attempt to prefer color emoji fonts over non-color emoji
// containing fonts like Symbola which has gone awry?
// For testing purposes (isRestrictingFontSetForTesting is true) force a
// preference for non-color fonts.
if (rWrapper.isRestrictingFontSetForTesting())
FcPatternAddBool(pPattern, FC_COLOR, FcFalse);
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 nFontID = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
auto const* pFont = getFont(nFontID);
if (pFont)
{
rPattern.maSearchName = pFont->m_aFontAttributes.GetFamilyName();
bRet = true;
}
}
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 const bool bAbortOnFontSubstitute = [] {
const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
return pEnv && strcmp(pEnv, "abort") == 0;
}();
if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName)
{
if (bMissingJustBullet)
{
// Some fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT
// so will always glyph fallback on measuring mnBulletOffset in
// FontMetricData::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::cerr << "terminating test due to missing font: " << rPattern.maTargetName << std::endl;
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;
}
bool PrintFontManager::matchFont(FontAttributes& rDFA, const css::lang::Locale& rLocale)
{
bool bFound = false;
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(rDFA.GetFamilyName(), RTL_TEXTENCODING_UTF8);
if( !aFamily.isEmpty() )
FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
addtopattern(pPattern, rDFA.GetItalic(), rDFA.GetWeight(), rDFA.GetWidthType(), rDFA.GetPitch());
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 nFontID = findFontFileID(nDirID, aBase,
GetCollectionIndex(nEntryId),
GetVariationIndex(nEntryId));
auto const* pFont = getFont(nFontID);
if (pFont)
{
rDFA = pFont->m_aFontAttributes;
bFound = true;
}
}
}
// info: destroying the pSet destroys pResult implicitly
// since pResult was "added" to pSet
FcFontSetDestroy( pSet );
}
// cleanup
FcPatternDestroy( pPattern );
return bFound;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'reset' is required to be utilized.
↑ V547 Expression 'eStyleRes == FcResultMatch' is always true.
↑ V560 A part of conditional expression is always true: eScalableRes == FcResultMatch.
↑ V768 The expression is of enum type. It is odd that it is used as an expression of a Boolean-type.