summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxFcPlatformFontList.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /gfx/thebes/gfxFcPlatformFontList.cpp
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/thebes/gfxFcPlatformFontList.cpp')
-rw-r--r--gfx/thebes/gfxFcPlatformFontList.cpp2860
1 files changed, 2860 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp
new file mode 100644
index 0000000000..04703e1284
--- /dev/null
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -0,0 +1,2860 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#include "mozilla/Logging.h"
+
+#include "gfxFcPlatformFontList.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxFT2Utils.h"
+#include "gfxPlatform.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsGkAtoms.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsUnicodeProperties.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsXULAppAPI.h"
+#include "SharedFontList-impl.h"
+#include "StandardFonts-linux.inc"
+#include "mozilla/intl/Locale.h"
+
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include <cairo-ft.h>
+#include <fontconfig/fcfreetype.h>
+#include <fontconfig/fontconfig.h>
+#include <harfbuzz/hb.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#ifdef MOZ_WIDGET_GTK
+# include <gdk/gdk.h>
+# include <gtk/gtk.h>
+# include "gfxPlatformGtk.h"
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+#endif
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+# include "mozilla/SandboxBrokerPolicyFactory.h"
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#include FT_MULTIPLE_MASTERS_H
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+using namespace mozilla::intl;
+
+#ifndef FC_POSTSCRIPT_NAME
+# define FC_POSTSCRIPT_NAME "postscriptname" /* String */
+#endif
+#ifndef FC_VARIABLE
+# define FC_VARIABLE "variable" /* Bool */
+#endif
+
+#define PRINTING_FC_PROPERTY "gfx.printing"
+
+#define LOG_FONTLIST(args) \
+ MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), LogLevel::Debug)
+#define LOG_CMAPDATA_ENABLED() \
+ MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), LogLevel::Debug)
+
+static const FcChar8* ToFcChar8Ptr(const char* aStr) {
+ return reinterpret_cast<const FcChar8*>(aStr);
+}
+
+static const char* ToCharPtr(const FcChar8* aStr) {
+ return reinterpret_cast<const char*>(aStr);
+}
+
+// canonical name ==> first en name or first name if no en name
+// This is the required logic for fullname lookups as per CSS3 Fonts spec.
+static uint32_t FindCanonicalNameIndex(FcPattern* aFont,
+ const char* aLangField) {
+ uint32_t n = 0, en = 0;
+ FcChar8* lang;
+ while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) {
+ // look for 'en' or variants, en-US, en-JP etc.
+ uint32_t len = strlen(ToCharPtr(lang));
+ bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0);
+ if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) {
+ en = n;
+ break;
+ }
+ n++;
+ }
+ return en;
+}
+
+static void GetFaceNames(FcPattern* aFont, const nsACString& aFamilyName,
+ nsACString& aPostscriptName, nsACString& aFullname) {
+ // get the Postscript name
+ FcChar8* psname;
+ if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) ==
+ FcResultMatch) {
+ aPostscriptName = ToCharPtr(psname);
+ }
+
+ // get the canonical fullname (i.e. en name or first name)
+ uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG);
+ FcChar8* fullname;
+ if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) {
+ aFullname = ToCharPtr(fullname);
+ }
+
+ // if have fullname, done
+ if (!aFullname.IsEmpty()) {
+ return;
+ }
+
+ // otherwise, set the fullname to family + style name [en] and use that
+ aFullname = aFamilyName;
+
+ // figure out the en style name
+ en = FindCanonicalNameIndex(aFont, FC_STYLELANG);
+ nsAutoCString style;
+ FcChar8* stylename = nullptr;
+ FcPatternGetString(aFont, FC_STYLE, en, &stylename);
+ if (stylename) {
+ style = ToCharPtr(stylename);
+ }
+
+ if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) {
+ aFullname.Append(' ');
+ aFullname.Append(style);
+ }
+}
+
+static FontWeight MapFcWeight(int aFcWeight) {
+ if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) {
+ return FontWeight::FromInt(100);
+ }
+ if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) {
+ return FontWeight::FromInt(200);
+ }
+ if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) {
+ return FontWeight::FromInt(300);
+ }
+ if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) {
+ // This includes FC_WEIGHT_BOOK
+ return FontWeight::FromInt(400);
+ }
+ if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) {
+ return FontWeight::FromInt(500);
+ }
+ if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) {
+ return FontWeight::FromInt(600);
+ }
+ if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) {
+ return FontWeight::FromInt(700);
+ }
+ if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) {
+ return FontWeight::FromInt(800);
+ }
+ if (aFcWeight <= FC_WEIGHT_BLACK) {
+ return FontWeight::FromInt(900);
+ }
+
+ // including FC_WEIGHT_EXTRABLACK
+ return FontWeight::FromInt(901);
+}
+
+// TODO(emilio, jfkthame): I think this can now be more fine-grained.
+static FontStretch MapFcWidth(int aFcWidth) {
+ if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return FontStretch::ULTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return FontStretch::EXTRA_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return FontStretch::CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return FontStretch::SEMI_CONDENSED;
+ }
+ if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return FontStretch::NORMAL;
+ }
+ if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return FontStretch::SEMI_EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return FontStretch::EXPANDED;
+ }
+ if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return FontStretch::EXTRA_EXPANDED;
+ }
+ return FontStretch::ULTRA_EXPANDED;
+}
+
+static void GetFontProperties(FcPattern* aFontPattern, WeightRange* aWeight,
+ StretchRange* aStretch,
+ SlantStyleRange* aSlantStyle,
+ uint16_t* aSize = nullptr) {
+ // weight
+ int weight;
+ if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) !=
+ FcResultMatch) {
+ weight = FC_WEIGHT_REGULAR;
+ }
+ *aWeight = WeightRange(MapFcWeight(weight));
+
+ // width
+ int width;
+ if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ width = FC_WIDTH_NORMAL;
+ }
+ *aStretch = StretchRange(MapFcWidth(width));
+
+ // italic
+ int slant;
+ if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ slant = FC_SLANT_ROMAN;
+ }
+ if (slant == FC_SLANT_OBLIQUE) {
+ *aSlantStyle = SlantStyleRange(FontSlantStyle::OBLIQUE);
+ } else if (slant > 0) {
+ *aSlantStyle = SlantStyleRange(FontSlantStyle::ITALIC);
+ }
+
+ if (aSize) {
+ // pixel size, or zero if scalable
+ FcBool scalable;
+ if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ *aSize = 0;
+ } else {
+ double size;
+ if (FcPatternGetDouble(aFontPattern, FC_PIXEL_SIZE, 0, &size) ==
+ FcResultMatch) {
+ *aSize = uint16_t(NS_round(size));
+ } else {
+ *aSize = 0;
+ }
+ }
+ }
+}
+
+void gfxFontconfigFontEntry::GetUserFontFeatures(FcPattern* aPattern) {
+ int fontFeaturesNum = 0;
+ char* s;
+ hb_feature_t tmpFeature;
+ while (FcResultMatch == FcPatternGetString(aPattern, "fontfeatures",
+ fontFeaturesNum, (FcChar8**)&s)) {
+ bool ret = hb_feature_from_string(s, -1, &tmpFeature);
+ if (ret) {
+ mFeatureSettings.AppendElement(
+ (gfxFontFeature){tmpFeature.tag, tmpFeature.value});
+ }
+ fontFeaturesNum++;
+ }
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern,
+ bool aIgnoreFcCharmap)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFontPattern(aFontPattern),
+ mFTFaceInitialized(false),
+ mIgnoreFcCharmap(aIgnoreFcCharmap),
+ mHasVariationsInitialized(false) {
+ GetFontProperties(aFontPattern, &mWeightRange, &mStretchRange, &mStyleRange);
+ GetUserFontFeatures(mFontPattern);
+}
+
+gfxFontEntry* gfxFontconfigFontEntry::Clone() const {
+ MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
+ return new gfxFontconfigFontEntry(Name(), mFontPattern, mIgnoreFcCharmap);
+}
+
+static already_AddRefed<FcPattern> CreatePatternForFace(FT_Face aFace) {
+ // Use fontconfig to fill out the pattern from the FTFace.
+ // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at
+ // least). The dummy file passed here is removed below.
+ //
+ // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr)
+ // is passed as the "blanks" argument, which provides that unexpectedly
+ // blank glyphs are elided. Here, however, we pass nullptr for
+ // "blanks", effectively assuming that, if the font has a blank glyph,
+ // then the author intends any associated character to be rendered
+ // blank.
+ RefPtr<FcPattern> pattern =
+ dont_AddRef(FcFreeTypeQueryFace(aFace, ToFcChar8Ptr(""), 0, nullptr));
+ // given that we have a FT_Face, not really sure this is possible...
+ if (!pattern) {
+ pattern = dont_AddRef(FcPatternCreate());
+ }
+ FcPatternDel(pattern, FC_FILE);
+ FcPatternDel(pattern, FC_INDEX);
+
+ // Make a new pattern and store the face in it so that cairo uses
+ // that when creating a cairo font face.
+ FcPatternAddFTFace(pattern, FC_FT_FACE, aFace);
+
+ return pattern.forget();
+}
+
+static already_AddRefed<SharedFTFace> CreateFaceForPattern(
+ FcPattern* aPattern) {
+ FcChar8* filename;
+ if (FcPatternGetString(aPattern, FC_FILE, 0, &filename) != FcResultMatch) {
+ return nullptr;
+ }
+ int index;
+ if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index) != FcResultMatch) {
+ index = 0; // default to 0 if not found in pattern
+ }
+ return Factory::NewSharedFTFace(nullptr, ToCharPtr(filename), index);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ SlantStyleRange aStyle,
+ RefPtr<SharedFTFace>&& aFace)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFTFace(std::move(aFace)),
+ mFTFaceInitialized(true),
+ mIgnoreFcCharmap(true),
+ mHasVariationsInitialized(false) {
+ mWeightRange = aWeight;
+ mStyleRange = aStyle;
+ mStretchRange = aStretch;
+ mIsDataUserFont = true;
+
+ mFontPattern = CreatePatternForFace(mFTFace->GetFace());
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
+ FcPattern* aFontPattern,
+ WeightRange aWeight,
+ StretchRange aStretch,
+ SlantStyleRange aStyle)
+ : gfxFT2FontEntryBase(aFaceName),
+ mFontPattern(aFontPattern),
+ mFTFaceInitialized(false),
+ mHasVariationsInitialized(false) {
+ mWeightRange = aWeight;
+ mStyleRange = aStyle;
+ mStretchRange = aStretch;
+ mIsLocalUserFont = true;
+
+ // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded
+ // via src:local()...
+ // If the local font happens to come from the application fontset,
+ // we want to set it to true so that color/svg fonts will work even
+ // if the default glyphs are blank; but if the local font is a non-
+ // sfnt face (e.g. legacy type 1) then we need to set it to false
+ // because our cmap-reading code will fail and we depend on FT+Fc to
+ // determine the coverage.
+ // We set the flag here, but may flip it the first time TestCharacterMap
+ // is called, at which point we'll look to see whether a 'cmap' is
+ // actually present in the font.
+ mIgnoreFcCharmap = true;
+
+ GetUserFontFeatures(mFontPattern);
+}
+
+typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**);
+typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*);
+static GetVarFunc sGetVar;
+static DoneVarFunc sDoneVar;
+static bool sInitializedVarFuncs = false;
+
+static void InitializeVarFuncs() {
+ if (sInitializedVarFuncs) {
+ return;
+ }
+ sInitializedVarFuncs = true;
+#if MOZ_TREE_FREETYPE
+ sGetVar = &FT_Get_MM_Var;
+ sDoneVar = &FT_Done_MM_Var;
+#else
+ sGetVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var");
+ sDoneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var");
+#endif
+}
+
+gfxFontconfigFontEntry::~gfxFontconfigFontEntry() {
+ if (mMMVar) {
+ // Prior to freetype 2.9, there was no specific function to free the
+ // FT_MM_Var record, and the docs just said to use free().
+ // InitializeVarFuncs must have been called in order for mMMVar to be
+ // non-null here, so we don't need to do it again.
+ if (sDoneVar) {
+ MOZ_ASSERT(mFTFace, "How did mMMVar get set without a face?");
+ (*sDoneVar)(mFTFace->GetFace()->glyph->library, mMMVar);
+ } else {
+ free(mMMVar);
+ }
+ }
+}
+
+nsresult gfxFontconfigFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+
+ uint32_t uvsOffset = 0;
+ if (aFontInfoData &&
+ (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ uint32_t cmapLen;
+ const uint8_t* cmapData = reinterpret_cast<const uint8_t*>(
+ hb_blob_get_data(cmapTable, &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ mUVSOffset.exchange(uvsOffset);
+
+ bool setCharMap = true;
+ if (NS_SUCCEEDED(rv)) {
+ gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
+ fontlist::FontList* sharedFontList = pfl->SharedFontList();
+ if (!IsUserFont() && mShmemFace) {
+ mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
+ if (TrySetShmemCharacterMap()) {
+ setCharMap = false;
+ }
+ } else {
+ charmap = pfl->FindCharMap(charmap);
+ }
+ mHasCmapTable = true;
+ } else {
+ // if error occurred, initialize to null cmap
+ charmap = new gfxCharacterMap();
+ mHasCmapTable = false;
+ }
+ if (setCharMap) {
+ if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
+ charmap.get()->AddRef();
+ }
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n",
+ mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+static bool HasChar(FcPattern* aFont, FcChar32 aCh) {
+ FcCharSet* charset = nullptr;
+ FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset);
+ return charset && FcCharSetHasChar(charset, aCh);
+}
+
+bool gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh) {
+ // For user fonts, or for fonts bundled with the app (which might include
+ // color/svg glyphs where the default glyphs may be blank, and thus confuse
+ // fontconfig/freetype's char map checking), we instead check the cmap
+ // directly for character coverage.
+ if (mIgnoreFcCharmap) {
+ // If it does not actually have a cmap, switch our strategy to use
+ // fontconfig's charmap after all (except for data fonts, which must
+ // always have a cmap to have passed OTS validation).
+ if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c', 'm', 'a', 'p'))) {
+ mIgnoreFcCharmap = false;
+ // ...and continue with HasChar() below.
+ } else {
+ return gfxFontEntry::TestCharacterMap(aCh);
+ }
+ }
+ // otherwise (for system fonts), use the charmap in the pattern
+ return HasChar(mFontPattern, aCh);
+}
+
+bool gfxFontconfigFontEntry::HasFontTable(uint32_t aTableTag) {
+ if (FTUserFontData* ufd = GetUserFontData()) {
+ if (ufd->FontData()) {
+ return !!gfxFontUtils::FindTableDirEntry(ufd->FontData(), aTableTag);
+ }
+ }
+ return gfxFT2FontEntryBase::FaceHasTable(GetFTFace(), aTableTag);
+}
+
+hb_blob_t* gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag) {
+ // for data fonts, read directly from the font data
+ if (FTUserFontData* ufd = GetUserFontData()) {
+ if (ufd->FontData()) {
+ return gfxFontUtils::GetTableFromFontData(ufd->FontData(), aTableTag);
+ }
+ }
+
+ return gfxFontEntry::GetFontTable(aTableTag);
+}
+
+double gfxFontconfigFontEntry::GetAspect(uint8_t aSizeAdjustBasis) {
+ using FontSizeAdjust = gfxFont::FontSizeAdjust;
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::ExHeight ||
+ FontSizeAdjust::Tag(aSizeAdjustBasis) == FontSizeAdjust::Tag::CapHeight) {
+ // try to compute aspect from OS/2 metrics if available
+ AutoTable os2Table(this, TRUETYPE_TAG('O', 'S', '/', '2'));
+ if (os2Table) {
+ uint16_t upem = UnitsPerEm();
+ if (upem != kInvalidUPEM) {
+ uint32_t len;
+ const auto* os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ if (uint16_t(os2->version) >= 2) {
+ // XXX(jfkthame) Other implementations don't have the check for
+ // values <= 0.1em; should we drop that here? Just require it to be
+ // a positive number?
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::ExHeight) {
+ if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
+ int16_t(os2->sxHeight) > 0.1 * upem) {
+ return double(int16_t(os2->sxHeight)) / upem;
+ }
+ }
+ if (FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::CapHeight) {
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0.1 * upem) {
+ return double(int16_t(os2->sCapHeight)) / upem;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // create a font to calculate the requested aspect
+ gfxFontStyle s;
+ s.size = 256.0; // pick large size to reduce hinting artifacts
+ RefPtr<gfxFont> font = FindOrMakeFont(&s);
+ if (font) {
+ const gfxFont::Metrics& metrics =
+ font->GetMetrics(nsFontMetrics::eHorizontal);
+ if (metrics.emHeight == 0) {
+ return 0;
+ }
+ switch (FontSizeAdjust::Tag(aSizeAdjustBasis)) {
+ case FontSizeAdjust::Tag::ExHeight:
+ return metrics.xHeight / metrics.emHeight;
+ case FontSizeAdjust::Tag::CapHeight:
+ return metrics.capHeight / metrics.emHeight;
+ case FontSizeAdjust::Tag::ChWidth:
+ return metrics.zeroWidth > 0 ? metrics.zeroWidth / metrics.emHeight
+ : 0.5;
+ case FontSizeAdjust::Tag::IcWidth:
+ case FontSizeAdjust::Tag::IcHeight: {
+ bool vertical = FontSizeAdjust::Tag(aSizeAdjustBasis) ==
+ FontSizeAdjust::Tag::IcHeight;
+ gfxFloat advance =
+ font->GetCharAdvance(gfxFont::kWaterIdeograph, vertical);
+ return advance > 0 ? advance / metrics.emHeight : 1.0;
+ }
+ default:
+ break;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("failed to compute size-adjust aspect");
+ return 0.5;
+}
+
+static void PrepareFontOptions(FcPattern* aPattern, int* aOutLoadFlags,
+ unsigned int* aOutSynthFlags) {
+ int loadFlags = FT_LOAD_DEFAULT;
+ unsigned int synthFlags = 0;
+
+ // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed
+
+ FcBool printing;
+ if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) !=
+ FcResultMatch) {
+ printing = FcFalse;
+ }
+
+ // Font options are set explicitly here to improve cairo's caching
+ // behavior and to record the relevant parts of the pattern so that
+ // the pattern can be released.
+ //
+ // Most font_options have already been set as defaults on the FcPattern
+ // with cairo_ft_font_options_substitute(), then user and system
+ // fontconfig configurations were applied. The resulting font_options
+ // have been recorded on the face during
+ // cairo_ft_font_face_create_for_pattern().
+ //
+ // None of the settings here cause this scaled_font to behave any
+ // differently from how it would behave if it were created from the same
+ // face with default font_options.
+ //
+ // We set options explicitly so that the same scaled_font will be found in
+ // the cairo_scaled_font_map when cairo loads glyphs from a context with
+ // the same font_face, font_matrix, ctm, and surface font_options.
+ //
+ // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the
+ // font_options on the cairo_ft_font_face, and doesn't consider default
+ // option values to not match any explicit values.
+ //
+ // Even after cairo_set_scaled_font is used to set font_options for the
+ // cairo context, when cairo looks for a scaled_font for the context, it
+ // will look for a font with some option values from the target surface if
+ // any values are left default on the context font_options. If this
+ // scaled_font is created with default font_options, cairo will not find
+ // it.
+ //
+ // The one option not recorded in the pattern is hint_metrics, which will
+ // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON.
+ // We should be considering the font_options of the surface on which this
+ // font will be used, but currently we don't have different gfxFonts for
+ // different surface font_options, so we'll create a font suitable for the
+ // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON.
+
+ // The remaining options have been recorded on the pattern and the face.
+ // _cairo_ft_options_merge has some logic to decide which options from the
+ // scaled_font or from the cairo_ft_font_face take priority in the way the
+ // font behaves.
+ //
+ // In the majority of cases, _cairo_ft_options_merge uses the options from
+ // the cairo_ft_font_face, so sometimes it is not so important which
+ // values are set here so long as they are not defaults, but we'll set
+ // them to the exact values that we expect from the font, to be consistent
+ // and to protect against changes in cairo.
+ //
+ // In some cases, _cairo_ft_options_merge uses some options from the
+ // scaled_font's font_options rather than options on the
+ // cairo_ft_font_face (from fontconfig).
+ // https://bugs.freedesktop.org/show_bug.cgi?id=11838
+ //
+ // Surface font options were set on the pattern in
+ // cairo_ft_font_options_substitute. If fontconfig has changed the
+ // hint_style then that is what the user (or distribution) wants, so we
+ // use the setting from the FcPattern.
+ //
+ // Fallback values here mirror treatment of defaults in cairo-ft-font.c.
+ FcBool hinting = FcFalse;
+ if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) {
+ hinting = FcTrue;
+ }
+
+ int fc_hintstyle = FC_HINT_NONE;
+ if (!printing && hinting &&
+ FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &fc_hintstyle) !=
+ FcResultMatch) {
+ fc_hintstyle = FC_HINT_FULL;
+ }
+ switch (fc_hintstyle) {
+ case FC_HINT_NONE:
+ loadFlags = FT_LOAD_NO_HINTING;
+ break;
+ case FC_HINT_SLIGHT:
+ loadFlags = FT_LOAD_TARGET_LIGHT;
+ break;
+ }
+
+ FcBool fc_antialias;
+ if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &fc_antialias) !=
+ FcResultMatch) {
+ fc_antialias = FcTrue;
+ }
+ if (!fc_antialias) {
+ if (fc_hintstyle != FC_HINT_NONE) {
+ loadFlags = FT_LOAD_TARGET_MONO;
+ }
+ loadFlags |= FT_LOAD_MONOCHROME;
+ } else if (fc_hintstyle == FC_HINT_FULL) {
+ int fc_rgba;
+ if (FcPatternGetInteger(aPattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch) {
+ fc_rgba = FC_RGBA_UNKNOWN;
+ }
+ switch (fc_rgba) {
+ case FC_RGBA_RGB:
+ case FC_RGBA_BGR:
+ loadFlags = FT_LOAD_TARGET_LCD;
+ break;
+ case FC_RGBA_VRGB:
+ case FC_RGBA_VBGR:
+ loadFlags = FT_LOAD_TARGET_LCD_V;
+ break;
+ }
+ }
+
+ if (!FcPatternAllowsBitmaps(aPattern, fc_antialias != FcFalse,
+ fc_hintstyle != FC_HINT_NONE)) {
+ loadFlags |= FT_LOAD_NO_BITMAP;
+ }
+
+ FcBool autohint;
+ if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch &&
+ autohint) {
+ loadFlags |= FT_LOAD_FORCE_AUTOHINT;
+ }
+
+ FcBool embolden;
+ if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch &&
+ embolden) {
+ synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD;
+ }
+
+ *aOutLoadFlags = loadFlags;
+ *aOutSynthFlags = synthFlags;
+}
+
+#ifdef MOZ_X11
+static bool GetXftInt(Display* aDisplay, const char* aName, int* aResult) {
+ if (!aDisplay) {
+ return false;
+ }
+ char* value = XGetDefault(aDisplay, "Xft", aName);
+ if (!value) {
+ return false;
+ }
+ if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) {
+ return true;
+ }
+ char* end;
+ *aResult = strtol(value, &end, 0);
+ if (end != value) {
+ return true;
+ }
+ return false;
+}
+#endif
+
+static void PreparePattern(FcPattern* aPattern, bool aIsPrinterFont) {
+ FcConfigSubstitute(nullptr, aPattern, FcMatchPattern);
+
+ // This gets cairo_font_options_t for the Screen. We should have
+ // different font options for printing (no hinting) but we are not told
+ // what we are measuring for.
+ //
+ // If cairo adds support for lcd_filter, gdk will not provide the default
+ // setting for that option. We could get the default setting by creating
+ // an xlib surface once, recording its font_options, and then merging the
+ // gdk options.
+ //
+ // Using an xlib surface would also be an option to get Screen font
+ // options for non-GTK X11 toolkits, but less efficient than using GDK to
+ // pick up dynamic changes.
+ if (aIsPrinterFont) {
+ cairo_font_options_t* options = cairo_font_options_create();
+ cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_GRAY);
+ cairo_ft_font_options_substitute(options, aPattern);
+ cairo_font_options_destroy(options);
+ FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue);
+#ifdef MOZ_WIDGET_GTK
+ } else {
+ gfxFcPlatformFontList::PlatformFontList()->SubstituteSystemFontOptions(
+ aPattern);
+#endif // MOZ_WIDGET_GTK
+ }
+
+ FcDefaultSubstitute(aPattern);
+}
+
+void gfxFontconfigFontEntry::UnscaledFontCache::MoveToFront(size_t aIndex) {
+ if (aIndex > 0) {
+ ThreadSafeWeakPtr<UnscaledFontFontconfig> front =
+ std::move(mUnscaledFonts[aIndex]);
+ for (size_t i = aIndex; i > 0; i--) {
+ mUnscaledFonts[i] = std::move(mUnscaledFonts[i - 1]);
+ }
+ mUnscaledFonts[0] = std::move(front);
+ }
+}
+
+already_AddRefed<UnscaledFontFontconfig>
+gfxFontconfigFontEntry::UnscaledFontCache::Lookup(const std::string& aFile,
+ uint32_t aIndex) {
+ for (size_t i = 0; i < kNumEntries; i++) {
+ RefPtr<UnscaledFontFontconfig> entry(mUnscaledFonts[i]);
+ if (entry && entry->GetFile() == aFile && entry->GetIndex() == aIndex) {
+ MoveToFront(i);
+ return entry.forget();
+ }
+ }
+ return nullptr;
+}
+
+static inline gfxFloat SizeForStyle(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle) {
+ return StyleFontSizeAdjust::Tag(aStyle.sizeAdjustBasis) !=
+ StyleFontSizeAdjust::Tag::None
+ ? aStyle.GetAdjustedSize(aEntry->GetAspect(aStyle.sizeAdjustBasis))
+ : aStyle.size * aEntry->mSizeAdjust;
+}
+
+static double ChooseFontSize(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle) {
+ double requestedSize = SizeForStyle(aEntry, aStyle);
+ double bestDist = -1.0;
+ double bestSize = requestedSize;
+ double size;
+ int v = 0;
+ while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) ==
+ FcResultMatch) {
+ ++v;
+ double dist = fabs(size - requestedSize);
+ if (bestDist < 0.0 || dist < bestDist) {
+ bestDist = dist;
+ bestSize = size;
+ }
+ }
+ // If the font has bitmaps but wants to be scaled, then let it scale.
+ if (bestSize >= 0.0) {
+ FcBool scalable;
+ if (FcPatternGetBool(aEntry->GetPattern(), FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ return requestedSize;
+ }
+ }
+ return bestSize;
+}
+
+gfxFont* gfxFontconfigFontEntry::CreateFontInstance(
+ const gfxFontStyle* aFontStyle) {
+ RefPtr<FcPattern> pattern = dont_AddRef(FcPatternCreate());
+ if (!pattern) {
+ NS_WARNING("Failed to create Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ double size = ChooseFontSize(this, *aFontStyle);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
+
+ RefPtr<SharedFTFace> face = GetFTFace();
+ if (!face) {
+ NS_WARNING("Failed to get FreeType face for pattern");
+ return nullptr;
+ }
+ if (HasVariations()) {
+ // For variation fonts, we create a new FT_Face here so that
+ // variation coordinates from the style can be applied without
+ // affecting other font instances created from the same entry
+ // (font resource).
+ // For user fonts: create a new FT_Face from the font data, and then make
+ // a pattern from that.
+ // For system fonts: create a new FT_Face and store it in a copy of the
+ // original mFontPattern.
+ RefPtr<SharedFTFace> varFace = face->GetData()
+ ? face->GetData()->CloneFace()
+ : CreateFaceForPattern(mFontPattern);
+ if (varFace) {
+ AutoTArray<gfxFontVariation, 8> settings;
+ GetVariationsForStyle(settings, *aFontStyle);
+ gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, varFace->GetFace());
+ face = std::move(varFace);
+ }
+ }
+
+ PreparePattern(pattern, aFontStyle->printerFont);
+ RefPtr<FcPattern> renderPattern =
+ dont_AddRef(FcFontRenderPrepare(nullptr, pattern, mFontPattern));
+ if (!renderPattern) {
+ NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
+ return nullptr;
+ }
+
+ if (aFontStyle->NeedsSyntheticBold(this)) {
+ FcPatternAddBool(renderPattern, FC_EMBOLDEN, FcTrue);
+ }
+
+ // will synthetic oblique be applied using a transform?
+ if (IsUpright() && !aFontStyle->style.IsNormal() &&
+ aFontStyle->allowSyntheticStyle) {
+ // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
+ FcPatternDel(renderPattern, FC_EMBEDDED_BITMAP);
+ FcPatternAddBool(renderPattern, FC_EMBEDDED_BITMAP, FcFalse);
+ }
+
+ int loadFlags;
+ unsigned int synthFlags;
+ PrepareFontOptions(renderPattern, &loadFlags, &synthFlags);
+
+ std::string file;
+ int index = 0;
+ if (!face->GetData()) {
+ const FcChar8* fcFile;
+ if (FcPatternGetString(renderPattern, FC_FILE, 0,
+ const_cast<FcChar8**>(&fcFile)) != FcResultMatch ||
+ FcPatternGetInteger(renderPattern, FC_INDEX, 0, &index) !=
+ FcResultMatch) {
+ NS_WARNING("No file in Fontconfig pattern for font instance");
+ return nullptr;
+ }
+ file = ToCharPtr(fcFile);
+ }
+
+ RefPtr<UnscaledFontFontconfig> unscaledFont =
+ mUnscaledFontCache.Lookup(file, index);
+ if (!unscaledFont) {
+ unscaledFont = mFTFace->GetData() ? new UnscaledFontFontconfig(mFTFace)
+ : new UnscaledFontFontconfig(
+ std::move(file), index, mFTFace);
+ mUnscaledFontCache.Add(unscaledFont);
+ }
+
+ gfxFont* newFont = new gfxFontconfigFont(
+ unscaledFont, std::move(face), renderPattern, size, this, aFontStyle,
+ loadFlags, (synthFlags & CAIRO_FT_SYNTHESIZE_BOLD) != 0);
+
+ return newFont;
+}
+
+const RefPtr<SharedFTFace>& gfxFontconfigFontEntry::GetFTFace() {
+ if (!mFTFaceInitialized) {
+ mFTFaceInitialized = true;
+ mFTFace = CreateFaceForPattern(mFontPattern);
+ }
+ return mFTFace;
+}
+
+FTUserFontData* gfxFontconfigFontEntry::GetUserFontData() {
+ if (mFTFace && mFTFace->GetData()) {
+ return static_cast<FTUserFontData*>(mFTFace->GetData());
+ }
+ return nullptr;
+}
+
+bool gfxFontconfigFontEntry::HasVariations() {
+ if (mHasVariationsInitialized) {
+ return mHasVariations;
+ }
+ mHasVariationsInitialized = true;
+ mHasVariations = false;
+
+ if (!gfxPlatform::HasVariationFontSupport()) {
+ return mHasVariations;
+ }
+
+ // For installed fonts, query the fontconfig pattern rather than paying
+ // the cost of loading a FT_Face that we otherwise might never need.
+ if (!IsUserFont() || IsLocalUserFont()) {
+ FcBool variable;
+ if ((FcPatternGetBool(mFontPattern, FC_VARIABLE, 0, &variable) ==
+ FcResultMatch) &&
+ variable) {
+ mHasVariations = true;
+ }
+ } else {
+ if (GetFTFace()) {
+ mHasVariations =
+ mFTFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS;
+ }
+ }
+
+ return mHasVariations;
+}
+
+FT_MM_Var* gfxFontconfigFontEntry::GetMMVar() {
+ if (mMMVarInitialized) {
+ return mMMVar;
+ }
+ mMMVarInitialized = true;
+ InitializeVarFuncs();
+ if (!sGetVar) {
+ return nullptr;
+ }
+ if (!GetFTFace()) {
+ return nullptr;
+ }
+ if (FT_Err_Ok != (*sGetVar)(mFTFace->GetFace(), &mMMVar)) {
+ mMMVar = nullptr;
+ }
+ return mMMVar;
+}
+
+void gfxFontconfigFontEntry::GetVariationAxes(
+ nsTArray<gfxFontVariationAxis>& aAxes) {
+ if (!HasVariations()) {
+ return;
+ }
+ gfxFT2Utils::GetVariationAxes(GetMMVar(), aAxes);
+}
+
+void gfxFontconfigFontEntry::GetVariationInstances(
+ nsTArray<gfxFontVariationInstance>& aInstances) {
+ if (!HasVariations()) {
+ return;
+ }
+ gfxFT2Utils::GetVariationInstances(this, GetMMVar(), aInstances);
+}
+
+nsresult gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
+ nsTArray<uint8_t>& aBuffer) {
+ NS_ASSERTION(!mIsDataUserFont,
+ "data fonts should be reading tables directly from memory");
+ return gfxFT2FontEntryBase::CopyFaceTable(GetFTFace(), aTableTag, aBuffer);
+}
+
+void gfxFontconfigFontFamily::FindStyleVariationsLocked(
+ FontInfoData* aFontInfoData) {
+ if (mHasStyles) {
+ return;
+ }
+
+ // add font entries for each of the faces
+ uint32_t numFonts = mFontPatterns.Length();
+ NS_ASSERTION(numFonts, "font family containing no faces!!");
+ uint32_t numRegularFaces = 0;
+ for (uint32_t i = 0; i < numFonts; i++) {
+ FcPattern* face = mFontPatterns[i];
+
+ // figure out the psname/fullname and choose which to use as the facename
+ nsAutoCString psname, fullname;
+ GetFaceNames(face, mName, psname, fullname);
+ const nsAutoCString& faceName = !psname.IsEmpty() ? psname : fullname;
+
+ gfxFontconfigFontEntry* fontEntry =
+ new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts);
+
+ if (gfxPlatform::HasVariationFontSupport()) {
+ fontEntry->SetupVariationRanges();
+ }
+
+ AddFontEntryLocked(fontEntry);
+
+ if (fontEntry->IsNormalStyle()) {
+ numRegularFaces++;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ nsAutoCString weightString;
+ fontEntry->Weight().ToString(weightString);
+ nsAutoCString stretchString;
+ fontEntry->Stretch().ToString(stretchString);
+ nsAutoCString styleString;
+ fontEntry->SlantStyle().ToString(styleString);
+ LOG_FONTLIST(
+ ("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %s stretch: %s"
+ " psname: %s fullname: %s",
+ fontEntry->Name().get(), Name().get(), styleString.get(),
+ weightString.get(), stretchString.get(), psname.get(),
+ fullname.get()));
+ }
+ }
+
+ // somewhat arbitrary, but define a family with two or more regular
+ // faces as a family for which intra-family fallback should be used
+ if (numRegularFaces > 1) {
+ mCheckForFallbackFaces = true;
+ }
+ mFaceNamesInitialized = true;
+ mFontPatterns.Clear();
+ SetHasStyles(true);
+
+ CheckForSimpleFamily();
+}
+
+void gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern,
+ bool aSingleName) {
+ NS_ASSERTION(
+ !mHasStyles,
+ "font patterns must not be added to already enumerated families");
+
+ FcBool outline;
+ if (FcPatternGetBool(aFontPattern, FC_OUTLINE, 0, &outline) !=
+ FcResultMatch ||
+ !outline) {
+ mHasNonScalableFaces = true;
+
+ FcBool scalable;
+ if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) ==
+ FcResultMatch &&
+ scalable) {
+ mForceScalable = true;
+ }
+ }
+
+ if (aSingleName) {
+ mFontPatterns.InsertElementAt(mUniqueNameFaceCount++, aFontPattern);
+ } else {
+ mFontPatterns.AppendElement(aFontPattern);
+ }
+}
+
+static const double kRejectDistance = 10000.0;
+
+// Calculate a distance score representing the size disparity between the
+// requested style's size and the font entry's size.
+static double SizeDistance(gfxFontconfigFontEntry* aEntry,
+ const gfxFontStyle& aStyle, bool aForceScalable) {
+ double requestedSize = SizeForStyle(aEntry, aStyle);
+ double bestDist = -1.0;
+ double size;
+ int v = 0;
+ while (FcPatternGetDouble(aEntry->GetPattern(), FC_PIXEL_SIZE, v, &size) ==
+ FcResultMatch) {
+ ++v;
+ double dist = fabs(size - requestedSize);
+ if (bestDist < 0.0 || dist < bestDist) {
+ bestDist = dist;
+ }
+ }
+ if (bestDist < 0.0) {
+ // No size means scalable
+ return -1.0;
+ } else if (aForceScalable || 5.0 * bestDist < requestedSize) {
+ // fontconfig prefers a matching family or lang to pixelsize of bitmap
+ // fonts. CSS suggests a tolerance of 20% on pixelsize.
+ return bestDist;
+ } else {
+ // Reject any non-scalable fonts that are not within tolerance.
+ return kRejectDistance;
+ }
+}
+
+void gfxFontconfigFontFamily::FindAllFontsForStyle(
+ const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance) {
+ gfxFontFamily::FindAllFontsForStyle(aFontStyle, aFontEntryList,
+ aIgnoreSizeTolerance);
+
+ if (!mHasNonScalableFaces) {
+ return;
+ }
+
+ // Iterate over the the available fonts while compacting any groups
+ // of unscalable fonts with matching styles into a single entry
+ // corresponding to the closest available size. If the closest
+ // available size is rejected for being outside tolerance, then the
+ // entire group will be skipped.
+ size_t skipped = 0;
+ gfxFontconfigFontEntry* bestEntry = nullptr;
+ double bestDist = -1.0;
+ for (size_t i = 0; i < aFontEntryList.Length(); i++) {
+ gfxFontconfigFontEntry* entry =
+ static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]);
+ double dist =
+ SizeDistance(entry, aFontStyle, mForceScalable || aIgnoreSizeTolerance);
+ // If the entry is scalable or has a style that does not match
+ // the group of unscalable fonts, then start a new group.
+ if (dist < 0.0 || !bestEntry || bestEntry->Stretch() != entry->Stretch() ||
+ bestEntry->Weight() != entry->Weight() ||
+ bestEntry->SlantStyle() != entry->SlantStyle()) {
+ // If the best entry in this group is still outside the tolerance,
+ // then skip the entire group.
+ if (bestDist >= kRejectDistance) {
+ skipped++;
+ }
+ // Remove any compacted entries from the previous group.
+ if (skipped) {
+ i -= skipped;
+ aFontEntryList.RemoveElementsAt(i, skipped);
+ skipped = 0;
+ }
+ // Mark the start of the new group.
+ bestEntry = entry;
+ bestDist = dist;
+ } else {
+ // If this entry more closely matches the requested size than the
+ // current best in the group, then take this entry instead.
+ if (dist < bestDist) {
+ aFontEntryList[i - 1 - skipped] = entry;
+ bestEntry = entry;
+ bestDist = dist;
+ }
+ skipped++;
+ }
+ }
+ // If the best entry in this group is still outside the tolerance,
+ // then skip the entire group.
+ if (bestDist >= kRejectDistance) {
+ skipped++;
+ }
+ // Remove any compacted entries from the current group.
+ if (skipped) {
+ aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped);
+ }
+}
+
+static bool PatternHasLang(const FcPattern* aPattern, const FcChar8* aLang) {
+ FcLangSet* langset;
+
+ if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) {
+ return false;
+ }
+
+ if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) {
+ return true;
+ }
+ return false;
+}
+
+bool gfxFontconfigFontFamily::SupportsLangGroup(nsAtom* aLangGroup) const {
+ if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) {
+ return true;
+ }
+
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang);
+ if (fcLang.IsEmpty()) {
+ return true;
+ }
+
+ // Before FindStyleVariations has been called, mFontPatterns will contain
+ // the font patterns. Afterward, it'll be empty, but mAvailableFonts
+ // will contain the font entries, each of which holds a reference to its
+ // pattern. We only check the first pattern in each list, because support
+ // for langs is considered to be consistent across all faces in a family.
+ AutoReadLock lock(mLock);
+ FcPattern* fontPattern;
+ if (mFontPatterns.Length()) {
+ fontPattern = mFontPatterns[0];
+ } else if (mAvailableFonts.Length()) {
+ fontPattern = static_cast<gfxFontconfigFontEntry*>(mAvailableFonts[0].get())
+ ->GetPattern();
+ } else {
+ return true;
+ }
+
+ // is lang included in the underlying pattern?
+ return PatternHasLang(fontPattern, ToFcChar8Ptr(fcLang.get()));
+}
+
+/* virtual */
+gfxFontconfigFontFamily::~gfxFontconfigFontFamily() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+template <typename Func>
+void gfxFontconfigFontFamily::AddFacesToFontList(Func aAddPatternFunc) {
+ AutoReadLock lock(mLock);
+ if (HasStyles()) {
+ for (auto& fe : mAvailableFonts) {
+ if (!fe) {
+ continue;
+ }
+ auto fce = static_cast<gfxFontconfigFontEntry*>(fe.get());
+ aAddPatternFunc(fce->GetPattern(), mContainsAppFonts);
+ }
+ } else {
+ for (auto& pat : mFontPatterns) {
+ aAddPatternFunc(pat, mContainsAppFonts);
+ }
+ }
+}
+
+gfxFontconfigFont::gfxFontconfigFont(
+ const RefPtr<UnscaledFontFontconfig>& aUnscaledFont,
+ RefPtr<SharedFTFace>&& aFTFace, FcPattern* aPattern, gfxFloat aAdjustedSize,
+ gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, int aLoadFlags,
+ bool aEmbolden)
+ : gfxFT2FontBase(aUnscaledFont, std::move(aFTFace), aFontEntry, aFontStyle,
+ aLoadFlags, aEmbolden),
+ mPattern(aPattern) {
+ mAdjustedSize = aAdjustedSize;
+ InitMetrics();
+}
+
+gfxFontconfigFont::~gfxFontconfigFont() = default;
+
+already_AddRefed<ScaledFont> gfxFontconfigFont::GetScaledFont(
+ const TextRunDrawParams& aRunParams) {
+ if (ScaledFont* scaledFont = mAzureScaledFont) {
+ return do_AddRef(scaledFont);
+ }
+
+ RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForFontconfigFont(
+ GetUnscaledFont(), GetAdjustedSize(), mFTFace, GetPattern());
+ if (!newScaledFont) {
+ return nullptr;
+ }
+
+ InitializeScaledFont(newScaledFont);
+
+ if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
+ Unused << newScaledFont.forget();
+ }
+ ScaledFont* scaledFont = mAzureScaledFont;
+ return do_AddRef(scaledFont);
+}
+
+bool gfxFontconfigFont::ShouldHintMetrics() const {
+ return !GetStyle()->printerFont;
+}
+
+gfxFcPlatformFontList::gfxFcPlatformFontList()
+ : mLocalNames(64),
+ mGenericMappings(32),
+ mFcSubstituteCache(64),
+ mLastConfig(nullptr),
+ mAlwaysUseFontconfigGenerics(true) {
+ CheckFamilyList(kBaseFonts_Ubuntu_20_04);
+ CheckFamilyList(kLangFonts_Ubuntu_20_04);
+ CheckFamilyList(kBaseFonts_Fedora_32);
+ mLastConfig = FcConfigGetCurrent();
+ if (XRE_IsParentProcess()) {
+ // if the rescan interval is set, start the timer
+ int rescanInterval = FcConfigGetRescanInterval(nullptr);
+ if (rescanInterval) {
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mCheckFontUpdatesTimer), CheckFontUpdates, this,
+ (rescanInterval + 1) * 1000, nsITimer::TYPE_REPEATING_SLACK,
+ "gfxFcPlatformFontList::gfxFcPlatformFontList");
+ if (!mCheckFontUpdatesTimer) {
+ NS_WARNING("Failure to create font updates timer");
+ }
+ }
+ }
+
+#ifdef MOZ_BUNDLED_FONTS
+ mBundledFontsInitialized = false;
+#endif
+}
+
+gfxFcPlatformFontList::~gfxFcPlatformFontList() {
+ AutoLock lock(mLock);
+
+ if (mCheckFontUpdatesTimer) {
+ mCheckFontUpdatesTimer->Cancel();
+ mCheckFontUpdatesTimer = nullptr;
+ }
+#ifdef MOZ_WIDGET_GTK
+ ClearSystemFontOptions();
+#endif
+}
+
+void gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet,
+ const SandboxPolicy* aPolicy,
+ bool aAppFonts) {
+ // This iterates over the fonts in a font set and adds in gfxFontFamily
+ // objects for each family. Individual gfxFontEntry objects for each face
+ // are not created here; the patterns are just stored in the family. When
+ // a family is actually used, it will be populated with gfxFontEntry
+ // records and the patterns moved to those.
+
+ if (!aFontSet) {
+ NS_WARNING("AddFontSetFamilies called with a null font set.");
+ return;
+ }
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* pattern = aFontSet->fonts[f];
+
+ // Skip any fonts that aren't readable for us (e.g. due to restrictive
+ // file ownership/permissions).
+ FcChar8* path;
+ if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) {
+ continue;
+ }
+ if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) {
+ continue;
+ }
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ // Skip any fonts that will be blocked by the content-process sandbox
+ // policy.
+ if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) &
+ SandboxBroker::Perms::MAY_READ)) {
+ continue;
+ }
+#endif
+
+ AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily,
+ aAppFonts);
+ }
+}
+
+void gfxFcPlatformFontList::AddPatternToFontList(
+ FcPattern* aFont, FcChar8*& aLastFamilyName, nsACString& aFamilyName,
+ RefPtr<gfxFontconfigFontFamily>& aFontFamily, bool aAppFonts) {
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(aFont, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(aFont, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ return;
+ }
+
+ // same as the last one? no need to add a new family, skip
+ if (FcStrCmp(canonical, aLastFamilyName) != 0) {
+ aLastFamilyName = canonical;
+
+ // add new family if one doesn't already exist
+ aFamilyName.Truncate();
+ aFamilyName = ToCharPtr(canonical);
+ nsAutoCString keyName(aFamilyName);
+ ToLowerCase(keyName);
+
+ aFontFamily = static_cast<gfxFontconfigFontFamily*>(
+ mFontFamilies
+ .LookupOrInsertWith(keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFonts
+ ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ return MakeRefPtr<gfxFontconfigFontFamily>(
+ aFamilyName, visibility);
+ })
+ .get());
+ // Record if the family contains fonts from the app font set
+ // (in which case we won't rely on fontconfig's charmap, due to
+ // bug 1276594).
+ if (aAppFonts) {
+ aFontFamily->SetFamilyContainsAppFonts(true);
+ }
+ }
+
+ // Add pointers to other localized family names. Most fonts
+ // only have a single name, so the first call to GetString
+ // will usually not match
+ FcChar8* otherName;
+ int n = (cIndex == 0 ? 1 : 0);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+ while (FcPatternGetString(aFont, FC_FAMILY, n, &otherName) == FcResultMatch) {
+ otherFamilyNames.AppendElement(nsCString(ToCharPtr(otherName)));
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+ if (!otherFamilyNames.IsEmpty()) {
+ AddOtherFamilyNames(aFontFamily, otherFamilyNames);
+ }
+
+ const bool singleName = n == 1;
+
+ MOZ_ASSERT(aFontFamily, "font must belong to a font family");
+ aFontFamily->AddFontPattern(aFont, singleName);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoCString psname, fullname;
+ GetFaceNames(aFont, aFamilyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNames.InsertOrUpdate(psname, RefPtr{aFont});
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ mLocalNames.WithEntryHandle(fullname, [&](auto&& entry) {
+ if (entry && !singleName) {
+ return;
+ }
+ entry.InsertOrUpdate(RefPtr{aFont});
+ });
+ }
+}
+
+nsresult gfxFcPlatformFontList::InitFontListForPlatform() {
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ ActivateBundledFonts();
+ }
+#endif
+
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ ClearSystemFontOptions();
+
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+ mOtherFamilyNamesInitialized = true;
+
+ mLastConfig = FcConfigGetCurrent();
+
+ if (XRE_IsContentProcess()) {
+ // Content process: use the font list passed from the chrome process,
+ // because we can't rely on fontconfig in the presence of sandboxing;
+ // it may report fonts that we can't actually access.
+
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+
+ // Get font list that was passed during XPCOM startup
+ // or in an UpdateFontList message.
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+
+#ifdef MOZ_WIDGET_GTK
+ UpdateSystemFontOptionsFromIpc(fontList.options());
+#endif
+
+ // For fontconfig versions between 2.10.94 and 2.11.1 inclusive,
+ // we need to escape any leading space in the charset element,
+ // otherwise FcNameParse will fail. :(
+ //
+ // The bug was introduced on 2013-05-24 by
+ // https://cgit.freedesktop.org/fontconfig/commit/?id=cd9b1033a68816a7acfbba1718ba0aa5888f6ec7
+ // "Bug 64906 - FcNameParse() should ignore leading whitespace in
+ // parameters"
+ // because ignoring a leading space in the encoded value of charset
+ // causes erroneous decoding of the whole element.
+ // This first shipped in version 2.10.94, and was eventually fixed as
+ // a side-effect of switching to the "human-readable" representation of
+ // charsets on 2014-07-03 in
+ // https://cgit.freedesktop.org/fontconfig/commit/?id=e708e97c351d3bc9f7030ef22ac2f007d5114730
+ // "Change charset parse/unparse format to be human readable"
+ // (with a followup fix next day) which means a leading space is no
+ // longer significant. This fix landed after 2.11.1 had been shipped,
+ // so the first version tag without the bug is 2.11.91.
+ int fcVersion = FcGetVersion();
+ bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101;
+
+ for (FontPatternListEntry& fpe : fontList.entries()) {
+ nsCString& patternStr = fpe.pattern();
+ if (fcCharsetParseBug) {
+ int32_t index = patternStr.Find(":charset= ");
+ if (index != kNotFound) {
+ // insert backslash after the =, before the space
+ patternStr.Insert('\\', index + 9);
+ }
+ }
+ FcPattern* pattern = FcNameParse((const FcChar8*)patternStr.get());
+ AddPatternToFontList(pattern, lastFamilyName, familyName, fontFamily,
+ fpe.appFontFamily());
+ FcPatternDestroy(pattern);
+ }
+
+ LOG_FONTLIST(
+ ("got font list from chrome process: "
+ "%u faces in %u families",
+ (unsigned)fontList.entries().Length(), mFontFamilies.Count()));
+
+ fontList.entries().Clear();
+ return NS_OK;
+ }
+
+ UpdateSystemFontOptions();
+
+ UniquePtr<SandboxPolicy> policy;
+
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ // If read sandboxing is enabled, create a temporary SandboxPolicy to
+ // check font paths; use a fake PID to avoid picking up any PID-specific
+ // rules by accident.
+ SandboxBrokerPolicyFactory policyFactory;
+ if (GetEffectiveContentSandboxLevel() > 2 &&
+ !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ policy = policyFactory.GetContentPolicy(-1, false);
+ }
+#endif
+
+#ifdef MOZ_BUNDLED_FONTS
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1745715:
+ // It's important to do this *before* processing the standard system fonts,
+ // so that if a family is present in both font sets, we'll treat it as app-
+ // bundled and therefore always visible.
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ AddFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true);
+ }
+#endif
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ AddFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false);
+
+ return NS_OK;
+}
+
+void gfxFcPlatformFontList::ReadSystemFontList(dom::SystemFontList* retValue) {
+ AutoLock lock(mLock);
+
+ // Fontconfig versions below 2.9 drop the FC_FILE element in FcNameUnparse
+ // (see https://bugs.freedesktop.org/show_bug.cgi?id=26718), so when using
+ // an older version, we manually append it to the unparsed pattern.
+#ifdef MOZ_WIDGET_GTK
+ SystemFontOptionsToIpc(retValue->options());
+#endif
+
+ if (FcGetVersion() < 20900) {
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak());
+ family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) {
+ char* s = (char*)FcNameUnparse(aPat);
+ nsDependentCString patternStr(s);
+ char* file = nullptr;
+ if (FcResultMatch ==
+ FcPatternGetString(aPat, FC_FILE, 0, (FcChar8**)&file)) {
+ patternStr.Append(":file=");
+ patternStr.Append(file);
+ }
+ retValue->entries().AppendElement(
+ FontPatternListEntry(patternStr, aAppFonts));
+ free(s);
+ });
+ }
+ } else {
+ for (const auto& entry : mFontFamilies) {
+ auto* family = static_cast<gfxFontconfigFontFamily*>(entry.GetWeak());
+ family->AddFacesToFontList([&](FcPattern* aPat, bool aAppFonts) {
+ char* s = (char*)FcNameUnparse(aPat);
+ nsDependentCString patternStr(s);
+ retValue->entries().AppendElement(
+ FontPatternListEntry(patternStr, aAppFonts));
+ free(s);
+ });
+ }
+ }
+}
+
+// Per family array of faces.
+class FacesData {
+ using FaceInitArray = AutoTArray<fontlist::Face::InitData, 8>;
+
+ FaceInitArray mFaces;
+
+ // Number of faces that have a single name. Faces that have multiple names are
+ // sorted last.
+ uint32_t mUniqueNameFaceCount = 0;
+
+ public:
+ void Add(fontlist::Face::InitData&& aData, bool aSingleName) {
+ if (aSingleName) {
+ mFaces.InsertElementAt(mUniqueNameFaceCount++, std::move(aData));
+ } else {
+ mFaces.AppendElement(std::move(aData));
+ }
+ }
+
+ const FaceInitArray& Get() const { return mFaces; }
+};
+
+void gfxFcPlatformFontList::InitSharedFontListForPlatform() {
+ mLocalNames.Clear();
+ mFcSubstituteCache.Clear();
+
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+ mOtherFamilyNamesInitialized = true;
+
+ mLastConfig = FcConfigGetCurrent();
+
+ if (!XRE_IsParentProcess()) {
+#ifdef MOZ_WIDGET_GTK
+ auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
+ UpdateSystemFontOptionsFromIpc(fontList.options());
+#endif
+ // Content processes will access the shared-memory data created by the
+ // parent, so they do not need to query fontconfig for the available
+ // fonts themselves.
+ return;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ UpdateSystemFontOptions();
+#endif
+
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ TimeStamp start = TimeStamp::Now();
+ ActivateBundledFonts();
+ TimeStamp end = TimeStamp::Now();
+ Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
+ (end - start).ToMilliseconds());
+ }
+#endif
+
+ UniquePtr<SandboxPolicy> policy;
+
+#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX)
+ // If read sandboxing is enabled, create a temporary SandboxPolicy to
+ // check font paths; use a fake PID to avoid picking up any PID-specific
+ // rules by accident.
+ SandboxBrokerPolicyFactory policyFactory;
+ if (GetEffectiveContentSandboxLevel() > 2 &&
+ !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+ policy = policyFactory.GetContentPolicy(-1, false);
+ }
+#endif
+
+ nsTArray<fontlist::Family::InitData> families;
+
+ nsClassHashtable<nsCStringHashKey, FacesData> faces;
+
+ // Do we need to work around the fontconfig FcNameParse/FcNameUnparse bug
+ // (present in versions between 2.10.94 and 2.11.1 inclusive)? See comment
+ // in InitFontListForPlatform for details.
+ int fcVersion = FcGetVersion();
+ bool fcCharsetParseBug = fcVersion >= 21094 && fcVersion <= 21101;
+
+ auto addPattern = [this, fcCharsetParseBug, &families, &faces](
+ FcPattern* aPattern, FcChar8*& aLastFamilyName,
+ nsCString& aFamilyName, bool aAppFont) -> void {
+ // get canonical name
+ uint32_t cIndex = FindCanonicalNameIndex(aPattern, FC_FAMILYLANG);
+ FcChar8* canonical = nullptr;
+ FcPatternGetString(aPattern, FC_FAMILY, cIndex, &canonical);
+ if (!canonical) {
+ return;
+ }
+
+ nsAutoCString keyName;
+ keyName = ToCharPtr(canonical);
+ ToLowerCase(keyName);
+
+ aLastFamilyName = canonical;
+ aFamilyName = ToCharPtr(canonical);
+
+ // Same canonical family name as the last one? Definitely no need to add a
+ // new family record.
+ auto* faceList =
+ faces
+ .LookupOrInsertWith(
+ keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFont ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ families.AppendElement(fontlist::Family::InitData(
+ keyName, aFamilyName, fontlist::Family::kNoIndex,
+ visibility,
+ /*bundled*/ aAppFont, /*badUnderline*/ false));
+ return MakeUnique<FacesData>();
+ })
+ .get();
+
+ char* s = (char*)FcNameUnparse(aPattern);
+ nsAutoCString descriptor(s);
+ free(s);
+
+ if (fcCharsetParseBug) {
+ // Escape any leading space in charset to work around FcNameParse bug.
+ int32_t index = descriptor.Find(":charset= ");
+ if (index != kNotFound) {
+ // insert backslash after the =, before the space
+ descriptor.Insert('\\', index + 9);
+ }
+ }
+
+ WeightRange weight(FontWeight::NORMAL);
+ StretchRange stretch(FontStretch::NORMAL);
+ SlantStyleRange style(FontSlantStyle::NORMAL);
+ uint16_t size;
+ GetFontProperties(aPattern, &weight, &stretch, &style, &size);
+
+ auto initData = fontlist::Face::InitData{descriptor, 0, size, false,
+ weight, stretch, style};
+
+ // Add entries for any other localized family names. (Most fonts only have
+ // a single family name, so the first call to GetString will usually fail).
+ FcChar8* otherName;
+ int n = (cIndex == 0 ? 1 : 0);
+ while (FcPatternGetString(aPattern, FC_FAMILY, n, &otherName) ==
+ FcResultMatch) {
+ nsAutoCString otherFamilyName(ToCharPtr(otherName));
+ keyName = otherFamilyName;
+ ToLowerCase(keyName);
+
+ faces
+ .LookupOrInsertWith(
+ keyName,
+ [&] {
+ FontVisibility visibility =
+ aAppFont ? FontVisibility::Base
+ : GetVisibilityForFamily(keyName);
+ families.AppendElement(fontlist::Family::InitData(
+ keyName, otherFamilyName, fontlist::Family::kNoIndex,
+ visibility,
+ /*bundled*/ aAppFont, /*badUnderline*/ false));
+
+ return MakeUnique<FacesData>();
+ })
+ .get()
+ ->Add(fontlist::Face::InitData(initData), /* singleName = */ false);
+
+ n++;
+ if (n == int(cIndex)) {
+ n++; // skip over canonical name
+ }
+ }
+
+ const bool singleName = n == 1;
+ faceList->Add(std::move(initData), singleName);
+
+ // map the psname, fullname ==> font family for local font lookups
+ nsAutoCString psname, fullname;
+ GetFaceNames(aPattern, aFamilyName, psname, fullname);
+ if (!psname.IsEmpty()) {
+ ToLowerCase(psname);
+ mLocalNameTable.InsertOrUpdate(
+ psname, fontlist::LocalFaceRec::InitData(keyName, descriptor));
+ }
+ if (!fullname.IsEmpty()) {
+ ToLowerCase(fullname);
+ if (fullname != psname) {
+ mLocalNameTable.WithEntryHandle(fullname, [&](auto&& entry) {
+ if (entry && !singleName) {
+ // We only override an existing entry if this is the only way to
+ // name this family. This prevents dubious aliases from clobbering
+ // the local name table.
+ return;
+ }
+ entry.InsertOrUpdate(
+ fontlist::LocalFaceRec::InitData(keyName, descriptor));
+ });
+ }
+ }
+ };
+
+ auto addFontSetFamilies = [&addPattern](FcFontSet* aFontSet,
+ SandboxPolicy* aPolicy,
+ bool aAppFonts) -> void {
+ FcChar8* lastFamilyName = (FcChar8*)"";
+ RefPtr<gfxFontconfigFontFamily> fontFamily;
+ nsAutoCString familyName;
+ for (int f = 0; f < aFontSet->nfont; f++) {
+ FcPattern* pattern = aFontSet->fonts[f];
+
+ // Skip any fonts that aren't readable for us (e.g. due to restrictive
+ // file ownership/permissions).
+ FcChar8* path;
+ if (FcPatternGetString(pattern, FC_FILE, 0, &path) != FcResultMatch) {
+ continue;
+ }
+ if (access(reinterpret_cast<const char*>(path), F_OK | R_OK) != 0) {
+ continue;
+ }
+
+#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_LINUX)
+ // Skip any fonts that will be blocked by the content-process sandbox
+ // policy.
+ if (aPolicy && !(aPolicy->Lookup(reinterpret_cast<const char*>(path)) &
+ SandboxBroker::Perms::MAY_READ)) {
+ continue;
+ }
+#endif
+
+ // If this is a TrueType or OpenType font, discard the FC_CHARSET object
+ // (which may be very large), because we'll read the 'cmap' directly.
+ // This substantially reduces the pressure on shared memory (bug 1664151)
+ // due to the large font descriptors (serialized patterns).
+ FcChar8* fontFormat;
+ if (FcPatternGetString(pattern, FC_FONTFORMAT, 0, &fontFormat) ==
+ FcResultMatch &&
+ (!FcStrCmp(fontFormat, (const FcChar8*)"TrueType") ||
+ !FcStrCmp(fontFormat, (const FcChar8*)"CFF"))) {
+ FcPattern* clone = FcPatternDuplicate(pattern);
+ FcPatternDel(clone, FC_CHARSET);
+ addPattern(clone, lastFamilyName, familyName, aAppFonts);
+ FcPatternDestroy(clone);
+ } else {
+ addPattern(pattern, lastFamilyName, familyName, aAppFonts);
+ }
+ }
+ };
+
+ // iterate over available fonts
+ FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
+ addFontSetFamilies(systemFonts, policy.get(), /* aAppFonts = */ false);
+
+#ifdef MOZ_BUNDLED_FONTS
+ if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
+ FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
+ addFontSetFamilies(appFonts, policy.get(), /* aAppFonts = */ true);
+ }
+#endif
+
+ mozilla::fontlist::FontList* list = SharedFontList();
+ list->SetFamilyNames(families);
+
+ for (uint32_t i = 0; i < families.Length(); i++) {
+ list->Families()[i].AddFaces(list, faces.Get(families[i].mKey)->Get());
+ }
+}
+
+gfxFcPlatformFontList::DistroID gfxFcPlatformFontList::GetDistroID() const {
+ // Helper called to initialize sResult the first time this is used.
+ auto getDistroID = []() {
+ DistroID result = DistroID::Unknown;
+ FILE* fp = fopen("/etc/os-release", "r");
+ if (fp) {
+ char buf[512];
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (strncmp(buf, "ID=", 3) == 0) {
+ if (strncmp(buf + 3, "ubuntu", 6) == 0) {
+ result = DistroID::Ubuntu;
+ } else if (strncmp(buf + 3, "fedora", 6) == 0) {
+ result = DistroID::Fedora;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+ }
+ return result;
+ };
+ static DistroID sResult = getDistroID();
+ return sResult;
+}
+
+FontVisibility gfxFcPlatformFontList::GetVisibilityForFamily(
+ const nsACString& aName) const {
+ switch (GetDistroID()) {
+ case DistroID::Ubuntu:
+ if (FamilyInList(aName, kBaseFonts_Ubuntu_20_04)) {
+ return FontVisibility::Base;
+ }
+ if (FamilyInList(aName, kLangFonts_Ubuntu_20_04)) {
+ return FontVisibility::LangPack;
+ }
+ return FontVisibility::User;
+ case DistroID::Fedora:
+ if (FamilyInList(aName, kBaseFonts_Fedora_32)) {
+ return FontVisibility::Base;
+ }
+ return FontVisibility::User;
+ default:
+ // We don't know how to categorize fonts on this system
+ return FontVisibility::Unknown;
+ }
+}
+
+gfxFontEntry* gfxFcPlatformFontList::CreateFontEntry(
+ fontlist::Face* aFace, const fontlist::Family* aFamily) {
+ nsAutoCString desc(aFace->mDescriptor.AsString(SharedFontList()));
+ FcPattern* pattern = FcNameParse((const FcChar8*)desc.get());
+ auto* fe = new gfxFontconfigFontEntry(desc, pattern, true);
+ FcPatternDestroy(pattern);
+ fe->InitializeFrom(aFace, aFamily);
+ return fe;
+}
+
+// For displaying the fontlist in UI, use explicit call to FcFontList. Using
+// FcFontList results in the list containing the localized names as dictated
+// by system defaults.
+static void GetSystemFontList(nsTArray<nsString>& aListOfFonts,
+ nsAtom* aLangGroup) {
+ aListOfFonts.Clear();
+
+ RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate());
+ if (!pat) {
+ return;
+ }
+
+ UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return;
+ }
+
+ // add the lang to the pattern
+ nsAutoCString fcLang;
+ gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
+ pfl->GetSampleLangForGroup(aLangGroup, fcLang,
+ /*aForFontEnumerationThread*/ true);
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get()));
+ }
+
+ UniquePtr<FcFontSet> fs(FcFontList(nullptr, pat, os.get()));
+ if (!fs) {
+ return;
+ }
+
+ for (int i = 0; i < fs->nfont; i++) {
+ char* family;
+
+ if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, (FcChar8**)&family) !=
+ FcResultMatch) {
+ continue;
+ }
+
+ // Remove duplicates...
+ nsAutoString strFamily;
+ AppendUTF8toUTF16(MakeStringSpan(family), strFamily);
+ if (aListOfFonts.Contains(strFamily)) {
+ continue;
+ }
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ aListOfFonts.Sort();
+}
+
+void gfxFcPlatformFontList::GetFontList(nsAtom* aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts) {
+ // Get the list of font family names using fontconfig
+ GetSystemFontList(aListOfFonts, aLangGroup);
+
+ // Under Linux, the generics "serif", "sans-serif" and "monospace"
+ // are included in the pref fontlist. These map to whatever fontconfig
+ // decides they should be for a given language, rather than one of the
+ // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*)
+ bool serif = false, sansSerif = false, monospace = false;
+ if (aGenericFamily.IsEmpty())
+ serif = sansSerif = monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
+ serif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
+ sansSerif = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
+ monospace = true;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
+ aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
+ serif = sansSerif = true;
+ else
+ MOZ_ASSERT_UNREACHABLE("unexpected CSS generic font family");
+
+ // The first in the list becomes the default in
+ // FontBuilder.readFontSelection() if the preference-selected font is not
+ // available, so put system configured defaults first.
+ if (monospace) aListOfFonts.InsertElementAt(0, u"monospace"_ns);
+ if (sansSerif) aListOfFonts.InsertElementAt(0, u"sans-serif"_ns);
+ if (serif) aListOfFonts.InsertElementAt(0, u"serif"_ns);
+}
+
+FontFamily gfxFcPlatformFontList::GetDefaultFontForPlatform(
+ nsPresContext* aPresContext, const gfxFontStyle* aStyle,
+ nsAtom* aLanguage) {
+ // Get the default font by using a fake name to retrieve the first
+ // scalable font that fontconfig suggests for the given language.
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, "-moz-default"_ns,
+ aLanguage ? aLanguage : nsGkAtoms::x_western);
+ NS_ASSERTION(prefFonts, "null list of generic fonts");
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ return (*prefFonts)[0];
+ }
+ return FontFamily();
+}
+
+gfxFontEntry* gfxFcPlatformFontList::LookupLocalFont(
+ nsPresContext* aPresContext, const nsACString& aFontName,
+ WeightRange aWeightForEntry, StretchRange aStretchForEntry,
+ SlantStyleRange aStyleForEntry) {
+ AutoLock lock(mLock);
+
+ nsAutoCString keyName(aFontName);
+ ToLowerCase(keyName);
+
+ if (SharedFontList()) {
+ return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+ }
+
+ // if name is not in the global list, done
+ const auto fontPattern = mLocalNames.Lookup(keyName);
+ if (!fontPattern) {
+ return nullptr;
+ }
+
+ return new gfxFontconfigFontEntry(aFontName, *fontPattern, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry);
+}
+
+gfxFontEntry* gfxFcPlatformFontList::MakePlatformFont(
+ const nsACString& aFontName, WeightRange aWeightForEntry,
+ StretchRange aStretchForEntry, SlantStyleRange aStyleForEntry,
+ const uint8_t* aFontData, uint32_t aLength) {
+ RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength);
+ RefPtr<SharedFTFace> face = ufd->CloneFace();
+ if (!face) {
+ return nullptr;
+ }
+ return new gfxFontconfigFontEntry(aFontName, aWeightForEntry,
+ aStretchForEntry, aStyleForEntry,
+ std::move(face));
+}
+
+static bool UseCustomFontconfigLookupsForLocale(const Locale& aLocale) {
+ return aLocale.Script().EqualTo("Hans") || aLocale.Script().EqualTo("Hant") ||
+ aLocale.Script().EqualTo("Jpan") || aLocale.Script().EqualTo("Kore") ||
+ aLocale.Script().EqualTo("Arab");
+}
+
+bool gfxFcPlatformFontList::FindAndAddFamiliesLocked(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
+ const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
+ FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
+ gfxFloat aDevToCssSize) {
+ nsAutoCString familyName(aFamily);
+ ToLowerCase(familyName);
+
+ if (!(aFlags & FindFamiliesFlags::eQuotedFamilyName)) {
+ // deprecated generic names are explicitly converted to standard generics
+ bool isDeprecatedGeneric = false;
+ if (familyName.EqualsLiteral("sans") ||
+ familyName.EqualsLiteral("sans serif")) {
+ familyName.AssignLiteral("sans-serif");
+ isDeprecatedGeneric = true;
+ } else if (familyName.EqualsLiteral("mono")) {
+ familyName.AssignLiteral("monospace");
+ isDeprecatedGeneric = true;
+ }
+
+ // fontconfig generics? use fontconfig to determine the family for lang
+ if (isDeprecatedGeneric ||
+ mozilla::StyleSingleFontFamily::Parse(familyName).IsGeneric()) {
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, familyName, aLanguage);
+ if (prefFonts && !prefFonts->IsEmpty()) {
+ aOutput->AppendElements(*prefFonts);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ // fontconfig allows conditional substitutions in such a way that it's
+ // difficult to distinguish an explicit substitution from other suggested
+ // choices. To sniff out explicit substitutions, compare the substitutions
+ // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the
+ // substitutions
+ //
+ // Example:
+ //
+ // serif ==> DejaVu Serif, ...
+ // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu
+ // Serif
+ //
+ // In this case fontconfig is including Tex Gyre Heros and
+ // Nimbus Sans L as alternatives for Helvetica.
+
+ // Because the FcConfigSubstitute call is quite expensive, we cache the
+ // actual font families found via this process.
+ nsAutoCString cacheKey;
+
+ // For languages that use CJK or Arabic script, we include the language as
+ // part of the cache key because fontconfig may have lang-specific rules that
+ // specify different substitutions. (In theory, this could apply to *any*
+ // language, but it's highly unlikely to matter for non-CJK/Arabic scripts,
+ // and it gets really expensive to do separate lookups for 300+ distinct lang
+ // tags e.g. on wikipedia.org, when they all end up mapping to the same font
+ // list.)
+ // We remember the most recently checked aLanguage atom so that when the same
+ // language is used in many successive calls, we can avoid repeating the
+ // locale code processing every time.
+ if (aLanguage != mPrevLanguage) {
+ GetSampleLangForGroup(aLanguage, mSampleLang);
+ ToLowerCase(mSampleLang);
+ Locale locale;
+ mUseCustomLookups = LocaleParser::TryParse(mSampleLang, locale).isOk() &&
+ locale.AddLikelySubtags().isOk() &&
+ UseCustomFontconfigLookupsForLocale(locale);
+ mPrevLanguage = aLanguage;
+ }
+ if (mUseCustomLookups) {
+ cacheKey = mSampleLang;
+ cacheKey.Append(':');
+ }
+
+ cacheKey.Append(familyName);
+ if (const auto& cached = mFcSubstituteCache.Lookup(cacheKey)) {
+ if (cached->IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(*cached);
+ return true;
+ }
+
+ // It wasn't in the cache, so we need to ask fontconfig...
+ const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
+ const FcChar8* terminator = nullptr;
+ RefPtr<FcPattern> sentinelSubst = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+ if (!mSampleLang.IsEmpty()) {
+ FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get()));
+ }
+ FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+
+ // If the sentinel name is still present, we'll use that as the terminator
+ // for the family names we collect; this means that if fontconfig prepends
+ // additional family names (e.g. an emoji font, or lang-specific preferred
+ // font) to all patterns, it won't simply mask all actual requested names.
+ // If the sentinel has been deleted/replaced altogether, then we'll take
+ // the first substitute name as the new terminator.
+ FcChar8* substName;
+ for (int i = 0; FcPatternGetString(sentinelSubst, FC_FAMILY, i, &substName) ==
+ FcResultMatch;
+ i++) {
+ if (FcStrCmp(substName, kSentinelName) == 0) {
+ terminator = kSentinelName;
+ break;
+ }
+ if (!terminator) {
+ terminator = substName;
+ }
+ }
+
+ // substitutions for font, -moz-sentinel pattern
+ RefPtr<FcPattern> fontWithSentinel = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(fontWithSentinel, FC_FAMILY,
+ ToFcChar8Ptr(familyName.get()));
+ FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
+ if (!mSampleLang.IsEmpty()) {
+ FcPatternAddString(sentinelSubst, FC_LANG, ToFcChar8Ptr(mSampleLang.get()));
+ }
+ FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
+
+ // Add all font family matches until reaching the terminator.
+ AutoTArray<FamilyAndGeneric, 10> cachedFamilies;
+ for (int i = 0; FcPatternGetString(fontWithSentinel, FC_FAMILY, i,
+ &substName) == FcResultMatch;
+ i++) {
+ if (terminator && FcStrCmp(substName, terminator) == 0) {
+ break;
+ }
+ gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, aGeneric, nsDependentCString(ToCharPtr(substName)),
+ &cachedFamilies, aFlags, aStyle, aLanguage);
+ }
+
+ const auto& insertedCachedFamilies =
+ mFcSubstituteCache.InsertOrUpdate(cacheKey, std::move(cachedFamilies));
+
+ if (insertedCachedFamilies.IsEmpty()) {
+ return false;
+ }
+ aOutput->AppendElements(insertedCachedFamilies);
+ return true;
+}
+
+bool gfxFcPlatformFontList::GetStandardFamilyName(const nsCString& aFontName,
+ nsACString& aFamilyName) {
+ aFamilyName.Truncate();
+
+ // The fontconfig list of fonts includes generic family names in the
+ // font list. For these, just use the generic name.
+ if (aFontName.EqualsLiteral("serif") ||
+ aFontName.EqualsLiteral("sans-serif") ||
+ aFontName.EqualsLiteral("monospace")) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+
+ RefPtr<FcPattern> pat = dont_AddRef(FcPatternCreate());
+ if (!pat) {
+ return true;
+ }
+
+ UniquePtr<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr));
+ if (!os) {
+ return true;
+ }
+
+ // add the family name to the pattern
+ FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(aFontName.get()));
+
+ UniquePtr<FcFontSet> givenFS(FcFontList(nullptr, pat, os.get()));
+ if (!givenFS) {
+ return true;
+ }
+
+ // See if there is a font face with first family equal to the given family
+ // (needs to be in sync with names coming from GetFontList())
+ nsTArray<nsCString> candidates;
+ for (int i = 0; i < givenFS->nfont; i++) {
+ char* firstFamily;
+
+ if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0,
+ (FcChar8**)&firstFamily) != FcResultMatch) {
+ continue;
+ }
+
+ nsDependentCString first(firstFamily);
+ if (!candidates.Contains(first)) {
+ candidates.AppendElement(first);
+
+ if (aFontName.Equals(first)) {
+ aFamilyName.Assign(aFontName);
+ return true;
+ }
+ }
+ }
+
+ // Because fontconfig conflates different family name types, need to
+ // double check that the candidate name is not simply a different
+ // name type. For example, if a font with nameID=16 "Minion Pro" and
+ // nameID=21 "Minion Pro Caption" exists, calling FcFontList with
+ // family="Minion Pro" will return a set of patterns some of which
+ // will have a first family of "Minion Pro Caption". Ignore these
+ // patterns and use the first candidate that maps to a font set with
+ // the same number of faces and an identical set of patterns.
+ for (uint32_t j = 0; j < candidates.Length(); ++j) {
+ FcPatternDel(pat, FC_FAMILY);
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8*)candidates[j].get());
+
+ UniquePtr<FcFontSet> candidateFS(FcFontList(nullptr, pat, os.get()));
+ if (!candidateFS) {
+ return true;
+ }
+
+ if (candidateFS->nfont != givenFS->nfont) {
+ continue;
+ }
+
+ bool equal = true;
+ for (int i = 0; i < givenFS->nfont; ++i) {
+ if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) {
+ equal = false;
+ break;
+ }
+ }
+ if (equal) {
+ aFamilyName = candidates[j];
+ return true;
+ }
+ }
+
+ // didn't find localized name, leave family name blank
+ return true;
+}
+
+void gfxFcPlatformFontList::AddGenericFonts(
+ nsPresContext* aPresContext, StyleGenericFontFamily aGenericType,
+ nsAtom* aLanguage, nsTArray<FamilyAndGeneric>& aFamilyList) {
+ const char* generic = GetGenericName(aGenericType);
+ NS_ASSERTION(generic, "weird generic font type");
+ if (!generic) {
+ return;
+ }
+
+ // By default, most font prefs on Linux map to "use fontconfig"
+ // keywords. So only need to explicitly lookup font pref if
+ // non-default settings exist, or if we are the system-ui font, which we deal
+ // with in the base class.
+ const bool isSystemUi = aGenericType == StyleGenericFontFamily::SystemUi;
+ bool usePrefFontList = isSystemUi;
+
+ nsAutoCString genericToLookup(generic);
+ if ((!mAlwaysUseFontconfigGenerics && aLanguage) ||
+ aLanguage == nsGkAtoms::x_math) {
+ nsAtom* langGroup = GetLangGroup(aLanguage);
+ nsAutoCString fontlistValue;
+ mFontPrefs->LookupName(PrefName(generic, langGroup), fontlistValue);
+ if (fontlistValue.IsEmpty()) {
+ // The font name list may have two or more family names as comma
+ // separated list. In such case, not matching with generic font
+ // name is fine because if the list prefers specific font, we
+ // should try to use the pref with complicated path.
+ mFontPrefs->LookupNameList(PrefName(generic, langGroup), fontlistValue);
+ }
+ if (!fontlistValue.IsEmpty()) {
+ if (!fontlistValue.EqualsLiteral("serif") &&
+ !fontlistValue.EqualsLiteral("sans-serif") &&
+ !fontlistValue.EqualsLiteral("monospace")) {
+ usePrefFontList = true;
+ } else {
+ // serif, sans-serif or monospace was specified
+ genericToLookup = fontlistValue;
+ }
+ }
+ }
+
+ // when pref fonts exist, use standard pref font lookup
+ if (usePrefFontList) {
+ gfxPlatformFontList::AddGenericFonts(aPresContext, aGenericType, aLanguage,
+ aFamilyList);
+ if (!isSystemUi) {
+ return;
+ }
+ }
+
+ AutoLock lock(mLock);
+ PrefFontList* prefFonts =
+ FindGenericFamilies(aPresContext, genericToLookup, aLanguage);
+ NS_ASSERTION(prefFonts, "null generic font list");
+ aFamilyList.SetCapacity(aFamilyList.Length() + prefFonts->Length());
+ for (auto& f : *prefFonts) {
+ aFamilyList.AppendElement(FamilyAndGeneric(f, aGenericType));
+ }
+}
+
+void gfxFcPlatformFontList::ClearLangGroupPrefFontsLocked() {
+ ClearGenericMappingsLocked();
+ gfxPlatformFontList::ClearLangGroupPrefFontsLocked();
+ mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics();
+}
+
+gfxPlatformFontList::PrefFontList* gfxFcPlatformFontList::FindGenericFamilies(
+ nsPresContext* aPresContext, const nsCString& aGeneric, nsAtom* aLanguage) {
+ // set up name
+ nsAutoCString fcLang;
+ GetSampleLangForGroup(aLanguage, fcLang);
+ ToLowerCase(fcLang);
+
+ nsAutoCString cacheKey(aGeneric);
+ if (fcLang.Length() > 0) {
+ cacheKey.Append('-');
+ // If the script is CJK or Arabic, we cache by lang so that different fonts
+ // various locales can be supported; but otherwise, we cache by script
+ // subtag, to avoid a proliferation of entries for Western & similar
+ // languages.
+ // In theory, this means we could fail to respect custom fontconfig rules
+ // for individual (non-CJK/Arab) languages that share the same script, but
+ // such setups are probably vanishingly rare.
+ Locale locale;
+ if (LocaleParser::TryParse(fcLang, locale).isOk() &&
+ locale.AddLikelySubtags().isOk()) {
+ if (UseCustomFontconfigLookupsForLocale(locale)) {
+ cacheKey.Append(fcLang);
+ } else {
+ cacheKey.Append(locale.Script().Span());
+ }
+ } else {
+ cacheKey.Append(fcLang);
+ }
+ }
+
+ // try to get the family from the cache
+ return mGenericMappings.WithEntryHandle(
+ cacheKey, [&](auto&& entry) -> PrefFontList* {
+ if (!entry) {
+ // if not found, ask fontconfig to pick the appropriate font
+ RefPtr<FcPattern> genericPattern = dont_AddRef(FcPatternCreate());
+ FcPatternAddString(genericPattern, FC_FAMILY,
+ ToFcChar8Ptr(aGeneric.get()));
+
+ // -- prefer scalable fonts
+ FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue);
+
+ // -- add the lang to the pattern
+ if (!fcLang.IsEmpty()) {
+ FcPatternAddString(genericPattern, FC_LANG,
+ ToFcChar8Ptr(fcLang.get()));
+ }
+
+ // -- perform substitutions
+ FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern);
+ FcDefaultSubstitute(genericPattern);
+
+ // -- sort to get the closest matches
+ FcResult result;
+ UniquePtr<FcFontSet> faces(
+ FcFontSort(nullptr, genericPattern, FcFalse, nullptr, &result));
+
+ if (!faces) {
+ return nullptr;
+ }
+
+ // -- select the fonts to be used for the generic
+ auto prefFonts = MakeUnique<PrefFontList>(); // can be empty but in
+ // practice won't happen
+ uint32_t limit = StaticPrefs::
+ gfx_font_rendering_fontconfig_max_generic_substitutions();
+ bool foundFontWithLang = false;
+ for (int i = 0; i < faces->nfont; i++) {
+ FcPattern* font = faces->fonts[i];
+ FcChar8* mappedGeneric = nullptr;
+
+ FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric);
+ if (mappedGeneric) {
+ mLock.AssertCurrentThreadIn();
+ nsAutoCString mappedGenericName(ToCharPtr(mappedGeneric));
+ AutoTArray<FamilyAndGeneric, 1> genericFamilies;
+ if (gfxPlatformFontList::FindAndAddFamiliesLocked(
+ aPresContext, StyleGenericFontFamily::None,
+ mappedGenericName, &genericFamilies,
+ FindFamiliesFlags(0))) {
+ MOZ_ASSERT(genericFamilies.Length() == 1,
+ "expected a single family");
+ if (!prefFonts->Contains(genericFamilies[0].mFamily)) {
+ prefFonts->AppendElement(genericFamilies[0].mFamily);
+ bool foundLang =
+ !fcLang.IsEmpty() &&
+ PatternHasLang(font, ToFcChar8Ptr(fcLang.get()));
+ foundFontWithLang = foundFontWithLang || foundLang;
+ // check to see if the list is full
+ if (prefFonts->Length() >= limit) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // if no font in the list matches the lang, trim all but the first one
+ if (!prefFonts->IsEmpty() && !foundFontWithLang) {
+ prefFonts->TruncateLength(1);
+ }
+
+ entry.Insert(std::move(prefFonts));
+ }
+ return entry->get();
+ });
+}
+
+bool gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics() {
+ for (auto iter = mFontPrefs->NameIter(); !iter.Done(); iter.Next()) {
+ // Check whether all font.name prefs map to generic keywords
+ // and that the pref name and keyword match.
+ // Ex: font.name.serif.ar ==> "serif" (ok)
+ // Ex: font.name.serif.ar ==> "monospace" (return false)
+ // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false)
+ // Ex: font.name.serif.ar ==> "" and
+ // font.name-list.serif.ar ==> "serif" (ok)
+ // Ex: font.name.serif.ar ==> "" and
+ // font.name-list.serif.ar ==> "Something, serif"
+ // (return false)
+ const nsACString* prefValue = &iter.Data();
+ nsAutoCString listValue;
+ if (iter.Data().IsEmpty()) {
+ // The font name list may have two or more family names as comma
+ // separated list. In such case, not matching with generic font
+ // name is fine because if the list prefers specific font, this
+ // should return false.
+ mFontPrefs->LookupNameList(iter.Key(), listValue);
+ prefValue = &listValue;
+ }
+
+ nsCCharSeparatedTokenizer tokenizer(iter.Key(), '.');
+ const nsDependentCSubstring& generic = tokenizer.nextToken();
+ const nsDependentCSubstring& langGroup = tokenizer.nextToken();
+
+ if (!langGroup.EqualsLiteral("x-math") && !generic.Equals(*prefValue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */
+void gfxFcPlatformFontList::CheckFontUpdates(nsITimer* aTimer, void* aThis) {
+ // A content process is not supposed to check this directly;
+ // it will be notified by the parent when the font list changes.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // check for font updates
+ FcInitBringUptoDate();
+
+ // update fontlist if current config changed
+ gfxFcPlatformFontList* pfl = static_cast<gfxFcPlatformFontList*>(aThis);
+ FcConfig* current = FcConfigGetCurrent();
+ if (current != pfl->GetLastConfig()) {
+ pfl->UpdateFontList();
+
+ gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes);
+ mozilla::dom::ContentParent::NotifyUpdatedFonts(true);
+ }
+}
+
+gfxFontFamily* gfxFcPlatformFontList::CreateFontFamily(
+ const nsACString& aName, FontVisibility aVisibility) const {
+ return new gfxFontconfigFontFamily(aName, aVisibility);
+}
+
+// mapping of moz lang groups ==> default lang
+struct MozLangGroupData {
+ nsAtom* const& mozLangGroup;
+ const char* defaultLang;
+};
+
+const MozLangGroupData MozLangGroups[] = {
+ {nsGkAtoms::x_western, "en"}, {nsGkAtoms::x_cyrillic, "ru"},
+ {nsGkAtoms::x_devanagari, "hi"}, {nsGkAtoms::x_tamil, "ta"},
+ {nsGkAtoms::x_armn, "hy"}, {nsGkAtoms::x_beng, "bn"},
+ {nsGkAtoms::x_cans, "iu"}, {nsGkAtoms::x_ethi, "am"},
+ {nsGkAtoms::x_geor, "ka"}, {nsGkAtoms::x_gujr, "gu"},
+ {nsGkAtoms::x_guru, "pa"}, {nsGkAtoms::x_khmr, "km"},
+ {nsGkAtoms::x_knda, "kn"}, {nsGkAtoms::x_mlym, "ml"},
+ {nsGkAtoms::x_orya, "or"}, {nsGkAtoms::x_sinh, "si"},
+ {nsGkAtoms::x_tamil, "ta"}, {nsGkAtoms::x_telu, "te"},
+ {nsGkAtoms::x_tibt, "bo"}, {nsGkAtoms::Unicode, 0}};
+
+bool gfxFcPlatformFontList::TryLangForGroup(const nsACString& aOSLang,
+ nsAtom* aLangGroup,
+ nsACString& aFcLang,
+ bool aForFontEnumerationThread) {
+ // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'.
+ // aOSLang is in the form "language[_territory][.codeset][@modifier]".
+ // fontconfig takes languages in the form "language-territory".
+ // nsLanguageAtomService takes languages in the form language-subtag,
+ // where subtag may be a territory. fontconfig and nsLanguageAtomService
+ // handle case-conversion for us.
+ const char *pos, *end;
+ aOSLang.BeginReading(pos);
+ aOSLang.EndReading(end);
+ aFcLang.Truncate();
+ while (pos < end) {
+ switch (*pos) {
+ case '.':
+ case '@':
+ end = pos;
+ break;
+ case '_':
+ aFcLang.Append('-');
+ break;
+ default:
+ aFcLang.Append(*pos);
+ }
+ ++pos;
+ }
+
+ if (!aForFontEnumerationThread) {
+ nsAtom* atom = mLangService->LookupLanguage(aFcLang);
+ return atom == aLangGroup;
+ }
+
+ // If we were called by the font enumeration thread, we can't use
+ // mLangService->LookupLanguage because it is not thread-safe.
+ // Use GetUncachedLanguageGroup to avoid unsafe access to the lang-group
+ // mapping cache hashtable.
+ nsAutoCString lowered(aFcLang);
+ ToLowerCase(lowered);
+ RefPtr<nsAtom> lang = NS_Atomize(lowered);
+ RefPtr<nsAtom> group = mLangService->GetUncachedLanguageGroup(lang);
+ return group.get() == aLangGroup;
+}
+
+void gfxFcPlatformFontList::GetSampleLangForGroup(
+ nsAtom* aLanguage, nsACString& aLangStr, bool aForFontEnumerationThread) {
+ aLangStr.Truncate();
+ if (!aLanguage) {
+ return;
+ }
+
+ // set up lang string
+ const MozLangGroupData* mozLangGroup = nullptr;
+
+ // -- look it up in the list of moz lang groups
+ for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) {
+ if (aLanguage == MozLangGroups[i].mozLangGroup) {
+ mozLangGroup = &MozLangGroups[i];
+ break;
+ }
+ }
+
+ // -- not a mozilla lang group? Just return the BCP47 string
+ // representation of the lang group
+ if (!mozLangGroup) {
+ // Not a special mozilla language group.
+ // Use aLanguage as a language code.
+ aLanguage->ToUTF8String(aLangStr);
+ return;
+ }
+
+ // -- check the environment for the user's preferred language that
+ // corresponds to this mozilla lang group.
+ const char* languages = getenv("LANGUAGE");
+ if (languages) {
+ const char separator = ':';
+
+ for (const char* pos = languages; true; ++pos) {
+ if (*pos == '\0' || *pos == separator) {
+ if (languages < pos &&
+ TryLangForGroup(Substring(languages, pos), aLanguage, aLangStr,
+ aForFontEnumerationThread)) {
+ return;
+ }
+
+ if (*pos == '\0') {
+ break;
+ }
+
+ languages = pos + 1;
+ }
+ }
+ }
+ const char* ctype = setlocale(LC_CTYPE, nullptr);
+ if (ctype && TryLangForGroup(nsDependentCString(ctype), aLanguage, aLangStr,
+ aForFontEnumerationThread)) {
+ return;
+ }
+
+ if (mozLangGroup->defaultLang) {
+ aLangStr.Assign(mozLangGroup->defaultLang);
+ } else {
+ aLangStr.Truncate();
+ }
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+void gfxFcPlatformFontList::ActivateBundledFonts() {
+ if (!mBundledFontsInitialized) {
+ mBundledFontsInitialized = true;
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(u"fonts"_ns))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+ if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
+ return;
+ }
+ }
+ if (!mBundledFontsPath.IsEmpty()) {
+ FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get()));
+ }
+}
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+/***************************************************************************
+ *
+ * These functions must be last in the file because it uses the system cairo
+ * library. Above this point the cairo library used is the tree cairo.
+ */
+
+// Tree cairo symbols have different names. Disable their activation through
+// preprocessor macros.
+# undef cairo_ft_font_options_substitute
+
+# undef cairo_font_options_create
+# undef cairo_font_options_destroy
+# undef cairo_font_options_copy
+# undef cairo_font_options_equal
+
+# undef cairo_font_options_get_antialias
+# undef cairo_font_options_set_antialias
+# undef cairo_font_options_get_hint_style
+# undef cairo_font_options_set_hint_style
+# undef cairo_font_options_get_lcd_filter
+# undef cairo_font_options_set_lcd_filter
+# undef cairo_font_options_get_subpixel_order
+# undef cairo_font_options_set_subpixel_order
+
+// The system cairo functions are not declared because the include paths cause
+// the gdk headers to pick up the tree cairo.h.
+extern "C" {
+NS_VISIBILITY_DEFAULT void cairo_ft_font_options_substitute(
+ const cairo_font_options_t* options, FcPattern* pattern);
+
+NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_copy(
+ const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT cairo_font_options_t* cairo_font_options_create();
+NS_VISIBILITY_DEFAULT void cairo_font_options_destroy(cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT cairo_bool_t cairo_font_options_equal(
+ const cairo_font_options_t*, const cairo_font_options_t*);
+
+NS_VISIBILITY_DEFAULT cairo_antialias_t
+cairo_font_options_get_antialias(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_antialias(
+ cairo_font_options_t*, cairo_antialias_t);
+NS_VISIBILITY_DEFAULT cairo_hint_style_t
+cairo_font_options_get_hint_style(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_hint_style(
+ cairo_font_options_t*, cairo_hint_style_t);
+NS_VISIBILITY_DEFAULT cairo_subpixel_order_t
+cairo_font_options_get_subpixel_order(const cairo_font_options_t*);
+NS_VISIBILITY_DEFAULT void cairo_font_options_set_subpixel_order(
+ cairo_font_options_t*, cairo_subpixel_order_t);
+}
+
+void gfxFcPlatformFontList::ClearSystemFontOptions() {
+ if (mSystemFontOptions) {
+ cairo_font_options_destroy(mSystemFontOptions);
+ mSystemFontOptions = nullptr;
+ }
+}
+
+bool gfxFcPlatformFontList::UpdateSystemFontOptions() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ if (gfxPlatform::IsHeadless()) {
+ return false;
+ }
+
+# ifdef MOZ_X11
+ {
+ // This one shouldn't change during the X session.
+ int lcdfilter;
+ GdkDisplay* dpy = gdk_display_get_default();
+ if (mozilla::widget::GdkIsX11Display(dpy) &&
+ GetXftInt(GDK_DISPLAY_XDISPLAY(dpy), "lcdfilter", &lcdfilter)) {
+ mFreetypeLcdSetting = lcdfilter;
+ }
+ }
+# endif // MOZ_X11
+
+ const cairo_font_options_t* options =
+ gdk_screen_get_font_options(gdk_screen_get_default());
+ if (!options) {
+ bool changed = !!mSystemFontOptions;
+ ClearSystemFontOptions();
+ return changed;
+ }
+
+ cairo_font_options_t* newOptions = cairo_font_options_copy(options);
+
+ if (mSystemFontOptions &&
+ cairo_font_options_equal(mSystemFontOptions, options)) {
+ cairo_font_options_destroy(newOptions);
+ return false;
+ }
+
+ ClearSystemFontOptions();
+ mSystemFontOptions = newOptions;
+ return true;
+}
+
+void gfxFcPlatformFontList::SystemFontOptionsToIpc(
+ dom::SystemFontOptions& aOptions) {
+ aOptions.antialias() =
+ mSystemFontOptions ? cairo_font_options_get_antialias(mSystemFontOptions)
+ : CAIRO_ANTIALIAS_DEFAULT;
+ aOptions.subpixelOrder() =
+ mSystemFontOptions
+ ? cairo_font_options_get_subpixel_order(mSystemFontOptions)
+ : CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ aOptions.hintStyle() =
+ mSystemFontOptions ? cairo_font_options_get_hint_style(mSystemFontOptions)
+ : CAIRO_HINT_STYLE_DEFAULT;
+ aOptions.lcdFilter() = mFreetypeLcdSetting;
+}
+
+void gfxFcPlatformFontList::UpdateSystemFontOptionsFromIpc(
+ const dom::SystemFontOptions& aOptions) {
+ ClearSystemFontOptions();
+ mSystemFontOptions = cairo_font_options_create();
+ cairo_font_options_set_antialias(mSystemFontOptions,
+ cairo_antialias_t(aOptions.antialias()));
+ cairo_font_options_set_hint_style(mSystemFontOptions,
+ cairo_hint_style_t(aOptions.hintStyle()));
+ cairo_font_options_set_subpixel_order(
+ mSystemFontOptions, cairo_subpixel_order_t(aOptions.subpixelOrder()));
+ mFreetypeLcdSetting = aOptions.lcdFilter();
+}
+
+void gfxFcPlatformFontList::SubstituteSystemFontOptions(FcPattern* aPattern) {
+ if (mSystemFontOptions) {
+ cairo_ft_font_options_substitute(mSystemFontOptions, aPattern);
+ }
+
+ if (mFreetypeLcdSetting != -1) {
+ FcValue value;
+ if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) == FcResultNoMatch) {
+ FcPatternAddInteger(aPattern, FC_LCD_FILTER, mFreetypeLcdSetting);
+ }
+ }
+}
+
+#endif // MOZ_WIDGET_GTK