/* -*- 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 .
*/
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;
if (bHaveA && bHaveB) return strcmp(reinterpret_cast<constchar*>(pNameA), reinterpret_cast<constchar*>(pNameB));
returnint(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: booloperator()(const FcPattern *a, const FcPattern *b)
{ int comp = compareFontNames(a, b); if (comp != 0) return comp < 0;
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];
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 );
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 (autoconst& element : elements)
{ constchar *pLang = reinterpret_cast<constchar*>(element.first); if( sFullMatch == pLang)
{ // both language and country match
candidate = element.second; break;
} elseif( alreadyclosematch )
{ // current candidate matches lang of lang-TERRITORY // override candidate only if there is a full match continue;
} elseif( sLangMatch == pLang)
{ // just the language matches
candidate = element.second;
alreadyclosematch = true;
} elseif( found_fallback_englishname )
{ // already found an english fallback, don't override candidate // unless there is a better language match continue;
} elseif( 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 (autoconst& element : lang_and_elements)
{ constchar *candidate = reinterpret_cast<constchar*>(element.second); if (rtl_str_compare(candidate, reinterpret_cast<constchar*>(bestfontname)) != 0)
m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<constchar*>(bestfontname));
} if (rtl_str_compare(reinterpret_cast<constchar*>(origfontname), reinterpret_cast<constchar*>(bestfontname)) != 0)
m_aLocalizedToCanonical[OString(reinterpret_cast<constchar*>(bestfontname))] = OString(reinterpret_cast<constchar*>(origfontname));
}
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());
//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);
}
}
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 unsignedint GetCollectionIndex(unsignedint nEntryId)
{ return nEntryId & 0xFFFF;
}
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;
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");
}
// 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 );
}
staticvoid 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"));
}
//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()) returnfalse;
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;
}
IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
{ try
{ usingnamespace 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();
}
// 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;
}
// 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);
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;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.