Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/thebes/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 104 kB image not shown  

Quelle  gfxFcPlatformFontList.cpp

  Sprache: C
 

/* -*- 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 "nsPresContext.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/glean/GfxMetrics.h"
#include "mozilla/TimeStamp.h"
#include "nsGkAtoms.h"
#include "nsIConsoleService.h"
#include "nsIGfxInfo.h"
#include "mozilla/Components.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
#ifndef FC_NAMED_INSTANCE
#  define FC_NAMED_INSTANCE "namedinstance" /* 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) {
  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),
      mFontPattern(CreatePatternForFace(aFace->GetFace())),
      mFTFace(aFace.forget().take()),
      mFTFaceInitialized(true),
      mIgnoreFcCharmap(true) {
  mWeightRange = aWeight;
  mStyleRange = aStyle;
  mStretchRange = aStretch;
  mIsDataUserFont = true;
}

gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsACString& aFaceName,
                                               FcPattern* aFontPattern,
                                               WeightRange aWeight,
                                               StretchRange aStretch,
                                               SlantStyleRange aStyle)
    : gfxFT2FontEntryBase(aFaceName),
      mFontPattern(aFontPattern),
      mFTFaceInitialized(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) {
      auto ftFace = GetFTFace();
      MOZ_ASSERT(ftFace, "How did mMMVar get set without a face?");
      (*sDoneVar)(ftFace->GetFace()->glyph->library, mMMVar);
    } else {
      free(mMMVar);
    }
  }
  if (mFTFaceInitialized) {
    auto face = mFTFace.exchange(nullptr);
    NS_IF_RELEASE(face);
  }
}

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, mShmemFamily);
      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;
  {
    AutoReadLock lock(mLock);
    unscaledFont = mUnscaledFontCache.Lookup(file, index);
  }

  if (!unscaledFont) {
    AutoWriteLock lock(mLock);
    // Here, we use the original mFTFace, not a potential clone with variation
    // settings applied.
    auto ftFace = GetFTFace();
    unscaledFont = ftFace->GetData() ? new UnscaledFontFontconfig(ftFace)
                                     : new UnscaledFontFontconfig(
                                           std::move(file), index, ftFace);
    mUnscaledFontCache.Add(unscaledFont);
  }

  gfxFont* newFont = new gfxFontconfigFont(
      unscaledFont, std::move(face), renderPattern, size, this, aFontStyle,
      loadFlags, (synthFlags & CAIRO_FT_SYNTHESIZE_BOLD) != 0);

  return newFont;
}

SharedFTFace* gfxFontconfigFontEntry::GetFTFace() {
  if (!mFTFaceInitialized) {
    RefPtr<SharedFTFace> face = CreateFaceForPattern(mFontPattern);
    if (face) {
      if (mFTFace.compareExchange(nullptr, face.get())) {
        Unused << face.forget();  // The reference is now owned by mFTFace.
        mFTFaceInitialized = true;
      } else {
        // We lost a race to set mFTFace! Just discard our new face.
      }
    }
  }
  return mFTFace;
}

FTUserFontData* gfxFontconfigFontEntry::GetUserFontData() {
  auto face = GetFTFace();
  if (face && face->GetData()) {
    return static_cast<FTUserFontData*>(face->GetData());
  }
  return nullptr;
}

bool gfxFontconfigFontEntry::HasVariations() {
  // If the answer is already cached, just return it.
  switch (mHasVariations) {
    case HasVariationsState::No:
      return false;
    case HasVariationsState::Yes:
      return true;
    case HasVariationsState::Uninitialized:
      break;
  }

  // Figure out whether we have variations, and record in mHasVariations.
  // (It doesn't matter if we race with another thread to set this; the result
  // will be the same.)

  if (!gfxPlatform::HasVariationFontSupport()) {
    mHasVariations = HasVariationsState::No;
    return false;
  }

  // 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 = HasVariationsState::Yes;
      return true;
    }
  } else {
    if (auto ftFace = GetFTFace()) {
      if (ftFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
        mHasVariations = HasVariationsState::Yes;
        return true;
      }
    }
  }

  mHasVariations = HasVariationsState::No;
  return false;
}

FT_MM_Var* gfxFontconfigFontEntry::GetMMVar() {
  {
    AutoReadLock lock(mLock);
    if (mMMVarInitialized) {
      return mMMVar;
    }
  }

  AutoWriteLock lock(mLock);

  mMMVarInitialized = true;
  InitializeVarFuncs();
  if (!sGetVar) {
    return nullptr;
  }
  auto ftFace = GetFTFace();
  if (!ftFace) {
    return nullptr;
  }
  if (FT_Err_Ok != (*sGetVar)(ftFace->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_22_04);
  CheckFamilyList(kLangFonts_Ubuntu_22_04);
  CheckFamilyList(kBaseFonts_Ubuntu_20_04);
  CheckFamilyList(kLangFonts_Ubuntu_20_04);
  CheckFamilyList(kBaseFonts_Fedora_39);
  CheckFamilyList(kBaseFonts_Fedora_38);
  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 (NS_WARN_IF(!aFontSet)) {
    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);
  }
}

// Check whether a pattern refers to a non-variable font, or a specific named
// instance of a variable font. Only such patterns are eligible for src:local()
// name lookups, as local() is defined to reference a specific face.
static bool IsNonVariableOrNamedInstance(FcPattern* aPattern) {
  FcBool value;
  if (FcPatternGetBool(aPattern, FC_VARIABLE, 0, &value) == FcResultMatch &&
      value) {
    // It's a variable font resource; check if this is a named instance.
    if (FcPatternGetBool(aPattern, FC_NAMED_INSTANCE, 0, &value) ==
            FcResultMatch &&
        value) {
      // Yes, named instance: ok to use.
      return true;
    }
    // It's a variable font; we don't want it.
    return false;
  }
  // Non-variable: no problem.
  return true;
}

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);

  if (IsNonVariableOrNamedInstance(aFont)) {
    // 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);
      });
    }
  }
}

using Device = nsIGfxInfo::FontVisibilityDeviceDetermination;
static Device sFontVisibilityDevice = Device::Unassigned;

void AssignFontVisibilityDevice() {
  if (sFontVisibilityDevice == Device::Unassigned) {
    nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
    NS_ENSURE_SUCCESS_VOID(
        gfxInfo->GetFontVisibilityDetermination(&sFontVisibilityDevice));
  }
}

// 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) {
    auto timerId = glean::fontlist::bundledfonts_activate.Start();
    ActivateBundledFonts();
    glean::fontlist::bundledfonts_activate.StopAndAccumulate(
        std::move(timerId));
  }
#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;

  // Returns true if the font was added with FontVisibility::Base.
  // This enables us to count how many known Base fonts are present.
  auto addPattern = [this, fcCharsetParseBug, &families, &faces](
                        FcPattern* aPattern, FcChar8*& aLastFamilyName,
                        nsCString& aFamilyName, bool aAppFont) -> bool {
    // get canonical name
    uint32_t cIndex = FindCanonicalNameIndex(aPattern, FC_FAMILYLANG);
    FcChar8* canonical = nullptr;
    FcPatternGetString(aPattern, FC_FAMILY, cIndex, &canonical);
    if (!canonical) {
      return false;
    }

    nsAutoCString keyName;
    keyName = ToCharPtr(canonical);
    ToLowerCase(keyName);

    aLastFamilyName = canonical;
    aFamilyName = ToCharPtr(canonical);

    const FontVisibility visibility =
        aAppFont ? FontVisibility::Base : GetVisibilityForFamily(keyName);

    // Same canonical family name as the last one? Definitely no need to add a
    // new family record.
    auto* faceList =
        faces
            .LookupOrInsertWith(
                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).
    // These get the same visibility level as we looked up for the first name.
    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,
              [&] {
                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);

    if (IsNonVariableOrNamedInstance(aPattern)) {
      // map the psname, fullname ==> font family for local font lookups
      nsAutoCString psname, fullname;
      GetFaceNames(aPattern, aFamilyName, psname, fullname);
      MOZ_PUSH_IGNORE_THREAD_SAFETY
      if (!psname.IsEmpty()) {
        ToLowerCase(psname);
        MaybeAddToLocalNameTable(
            psname, fontlist::LocalFaceRec::InitData(keyName, descriptor));
      }
      if (!fullname.IsEmpty()) {
        ToLowerCase(fullname);
        if (fullname != psname) {
          // We only consider overriding an existing entry if this is the only
          // way to name this family. This prevents dubious aliases from
          // clobbering the local name table.
          if (singleName || !mLocalNameTable.Contains(fullname)) {
            MaybeAddToLocalNameTable(fullname, fontlist::LocalFaceRec::InitData(
                                                   keyName, descriptor));
          }
        }
      }
      MOZ_POP_THREAD_SAFETY
    }

    return visibility == FontVisibility::Base;
  };

  // Returns the number of families with FontVisibility::Base that were found.
  auto addFontSetFamilies = [&addPattern](FcFontSet* aFontSet,
                                          SandboxPolicy* aPolicy,
                                          bool aAppFonts) -> size_t {
    size_t count = 0;
    if (NS_WARN_IF(!aFontSet)) {
      return count;
    }
    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

      // Clone the pattern, because we can't operate on the one belonging to
      // the FcFontSet directly.
      FcPattern* clone = FcPatternDuplicate(pattern);

      // Pick up any configuration options applicable to the font (e.g. custom
      // fontfeatures settings).
      if (!FcConfigSubstitute(nullptr, clone, FcMatchFont)) {
        // Out of memory?! We're probably doomed, but just skip this font.
        FcPatternDestroy(clone);
        continue;
      }
      // But ignore hinting settings from FcConfigSubstitute, as we don't want
      // to bake them into the pattern in the font list.
      FcPatternDel(clone, FC_HINT_STYLE);
      FcPatternDel(clone, FC_HINTING);

      // 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;
      MOZ_PUSH_IGNORE_THREAD_SAFETY
      if (FcPatternGetString(clone, FC_FONTFORMAT, 0, &fontFormat) ==
              FcResultMatch &&
          (!FcStrCmp(fontFormat, (const FcChar8*)"TrueType") ||
           !FcStrCmp(fontFormat, (const FcChar8*)"CFF"))) {
        FcPatternDel(clone, FC_CHARSET);
        if (addPattern(clone, lastFamilyName, familyName, aAppFonts)) {
          ++count;
        }
      } else {
        if (addPattern(clone, lastFamilyName, familyName, aAppFonts)) {
          ++count;
        }
      }
      MOZ_POP_THREAD_SAFETY

      FcPatternDestroy(clone);
    }
    return count;
  };

#ifdef MOZ_BUNDLED_FONTS
  // Add bundled fonts before system fonts, to set correct visibility status
  // for any families that appear in both.
  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);
  auto numBaseFamilies = addFontSetFamilies(systemFonts, policy.get(),
                                            /* aAppFonts = */ false);
  AssignFontVisibilityDevice();
  if (numBaseFamilies < 3 && sFontVisibilityDevice != Device::Linux_Unknown) {
    // If we found fewer than 3 known FontVisibility::Base families in the
    // system (ignoring app-bundled fonts), we must be dealing with a very
    // non-standard configuration; disable the distro-specific font
    // fingerprinting protection by marking all fonts as Unknown.
    for (auto& f : families) {
      f.mVisibility = FontVisibility::Unknown;
    }
    // Issue a warning that we're disabling this protection.
    nsCOMPtr<nsIConsoleService> console(
        do_GetService("@mozilla.org/consoleservice;1"));
    if (console) {
      console->LogStringMessage(
          u"Font-fingerprinting protection disabled; not enough standard "
          u"distro fonts installed.");
    }
  }

  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());
  }
}

FontVisibility gfxFcPlatformFontList::GetVisibilityForFamily(
    const nsACString& aName) const {
  AssignFontVisibilityDevice();

  switch (sFontVisibilityDevice) {
    case Device::Linux_Ubuntu_any:
    case Device::Linux_Ubuntu_22:
      if (FamilyInList(aName, kBaseFonts_Ubuntu_22_04)) {
        return FontVisibility::Base;
      }
      if (FamilyInList(aName, kLangFonts_Ubuntu_22_04)) {
        return FontVisibility::LangPack;
      }
      if (sFontVisibilityDevice == Device::Linux_Ubuntu_22) {
        return FontVisibility::User;
      }
      // For Ubuntu_any, we fall through to also check the 20_04 lists.
      [[fallthrough]];

    case Device::Linux_Ubuntu_20:
      if (FamilyInList(aName, kBaseFonts_Ubuntu_20_04)) {
        return FontVisibility::Base;
      }
      if (FamilyInList(aName, kLangFonts_Ubuntu_20_04)) {
        return FontVisibility::LangPack;
      }
      return FontVisibility::User;

    case Device::Linux_Fedora_any:
    case Device::Linux_Fedora_39:
      if (FamilyInList(aName, kBaseFonts_Fedora_39)) {
        return FontVisibility::Base;
      }
      if (sFontVisibilityDevice == Device::Linux_Fedora_39) {
        return FontVisibility::User;
      }
      // For Fedora_any, fall through to also check Fedora 38 list.
      [[fallthrough]];

    case Device::Linux_Fedora_38:
      if (FamilyInList(aName, kBaseFonts_Fedora_38)) {
        return FontVisibility::Base;
      }
      return FontVisibility::User;

    default:
      // We don't know how to categorize fonts on this system
      return FontVisibility::Unknown;
  }
}

nsTArray<std::pair<const char**, uint32_t>>
gfxFcPlatformFontList::GetFilteredPlatformFontLists() {
  AssignFontVisibilityDevice();

  nsTArray<std::pair<const char**, uint32_t>> fontLists;

  switch (sFontVisibilityDevice) {
    case Device::Linux_Ubuntu_any:
    case Device::Linux_Ubuntu_22:
      fontLists.AppendElement(std::make_pair(
          kBaseFonts_Ubuntu_22_04, std::size(kBaseFonts_Ubuntu_22_04)));
      fontLists.AppendElement(std::make_pair(
          kLangFonts_Ubuntu_22_04, std::size(kLangFonts_Ubuntu_22_04)));
      // For Ubuntu_any, we fall through to also check the 20_04 lists.
      [[fallthrough]];

    case Device::Linux_Ubuntu_20:
      fontLists.AppendElement(std::make_pair(
          kBaseFonts_Ubuntu_20_04, std::size(kBaseFonts_Ubuntu_20_04)));
      fontLists.AppendElement(std::make_pair(
          kLangFonts_Ubuntu_20_04, std::size(kLangFonts_Ubuntu_20_04)));
      break;

    case Device::Linux_Fedora_any:
    case Device::Linux_Fedora_39:
      fontLists.AppendElement(std::make_pair(kBaseFonts_Fedora_39,
                                             std::size(kBaseFonts_Fedora_39)));
      // For Fedora_any, fall through to also check Fedora 38 list.
      [[fallthrough]];

    case Device::Linux_Fedora_38:
      fontLists.AppendElement(std::make_pair(kBaseFonts_Fedora_38,
                                             std::size(kBaseFonts_Fedora_38)));
      break;

    default:
      // We don't know how to categorize fonts on this system
      break;
  }

  return fontLists;
}

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);
  auto vis =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
  cacheKey.Append(':');
  cacheKey.AppendInt(int(vis));
  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::GlobalReflowFlags flags =
        gfxPlatform::GlobalReflowFlags::NeedsReframe |
        gfxPlatform::GlobalReflowFlags::FontsChanged |
        gfxPlatform::GlobalReflowFlags::BroadcastToChildren;
    gfxPlatform::ForceGlobalReflow(flags);
    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 < std::size(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

Messung V0.5 in Prozent
C=85 H=95 G=90

¤ Dauer der Verarbeitung: 0.38 Sekunden  (vorverarbeitet am  2026-04-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.