diff options
Diffstat (limited to 'gfx/thebes/gfxFcPlatformFontList.cpp')
-rw-r--r-- | gfx/thebes/gfxFcPlatformFontList.cpp | 2860 |
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 |