summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxFontconfigUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/thebes/gfxFontconfigUtils.cpp')
-rw-r--r--gfx/thebes/gfxFontconfigUtils.cpp1100
1 files changed, 1100 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFontconfigUtils.cpp b/gfx/thebes/gfxFontconfigUtils.cpp
new file mode 100644
index 000000000..5bf606c13
--- /dev/null
+++ b/gfx/thebes/gfxFontconfigUtils.cpp
@@ -0,0 +1,1100 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/ArrayUtils.h"
+
+#include "gfxFontconfigUtils.h"
+#include "gfxFont.h"
+#include "nsGkAtoms.h"
+
+#include <locale.h>
+#include <fontconfig/fontconfig.h>
+
+#include "nsServiceManagerUtils.h"
+#include "nsILanguageAtomService.h"
+#include "nsTArray.h"
+#include "mozilla/Preferences.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "nsIAtom.h"
+#include "nsCRT.h"
+#include "gfxFontConstants.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+
+/* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr;
+static nsILanguageAtomService* gLangService = nullptr;
+
+/* static */ void
+gfxFontconfigUtils::Shutdown() {
+ if (sUtils) {
+ delete sUtils;
+ sUtils = nullptr;
+ }
+ NS_IF_RELEASE(gLangService);
+}
+
+/* static */ uint8_t
+gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant)
+{
+ switch (aFcSlant) {
+ case FC_SLANT_ITALIC:
+ return NS_FONT_STYLE_ITALIC;
+ case FC_SLANT_OBLIQUE:
+ return NS_FONT_STYLE_OBLIQUE;
+ default:
+ return NS_FONT_STYLE_NORMAL;
+ }
+}
+
+/* static */ uint8_t
+gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern)
+{
+ int slant;
+ if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
+ return NS_FONT_STYLE_NORMAL;
+ }
+
+ return FcSlantToThebesStyle(slant);
+}
+
+/* static */ int
+gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle)
+{
+ if (aFontStyle.style == NS_FONT_STYLE_ITALIC)
+ return FC_SLANT_ITALIC;
+ if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE)
+ return FC_SLANT_OBLIQUE;
+
+ return FC_SLANT_ROMAN;
+}
+
+// OS/2 weight classes were introduced in fontconfig-2.1.93 (2003).
+#ifndef FC_WEIGHT_THIN
+#define FC_WEIGHT_THIN 0 // 2.1.93
+#define FC_WEIGHT_EXTRALIGHT 40 // 2.1.93
+#define FC_WEIGHT_REGULAR 80 // 2.1.93
+#define FC_WEIGHT_EXTRABOLD 205 // 2.1.93
+#endif
+// book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005)
+#ifndef FC_WEIGHT_BOOK
+#define FC_WEIGHT_BOOK 75
+#endif
+// extra black was introduced in fontconfig-2.4.91 (2007)
+#ifndef FC_WEIGHT_EXTRABLACK
+#define FC_WEIGHT_EXTRABLACK 215
+#endif
+
+/* static */ uint16_t
+gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern)
+{
+ int weight;
+ if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch)
+ return NS_FONT_WEIGHT_NORMAL;
+
+ if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2)
+ return 100;
+ if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2)
+ return 200;
+ if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2)
+ return 300;
+ if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2)
+ // This includes FC_WEIGHT_BOOK
+ return 400;
+ if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2)
+ return 500;
+ if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2)
+ return 600;
+ if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2)
+ return 700;
+ if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2)
+ return 800;
+ if (weight <= FC_WEIGHT_BLACK)
+ return 900;
+
+ // including FC_WEIGHT_EXTRABLACK
+ return 901;
+}
+
+/* static */ int
+gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight)
+{
+ NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10,
+ "base weight out of range");
+
+ switch (aBaseWeight) {
+ case 2:
+ return FC_WEIGHT_EXTRALIGHT;
+ case 3:
+ return FC_WEIGHT_LIGHT;
+ case 4:
+ return FC_WEIGHT_REGULAR;
+ case 5:
+ return FC_WEIGHT_MEDIUM;
+ case 6:
+ return FC_WEIGHT_DEMIBOLD;
+ case 7:
+ return FC_WEIGHT_BOLD;
+ case 8:
+ return FC_WEIGHT_EXTRABOLD;
+ case 9:
+ return FC_WEIGHT_BLACK;
+ }
+
+ // extremes
+ return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK;
+}
+
+/* static */ int16_t
+gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern)
+{
+ int width;
+ if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+
+ if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) {
+ return NS_FONT_STRETCH_ULTRA_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) {
+ return NS_FONT_STRETCH_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) {
+ return NS_FONT_STRETCH_SEMI_CONDENSED;
+ }
+ if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) {
+ return NS_FONT_STRETCH_NORMAL;
+ }
+ if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) {
+ return NS_FONT_STRETCH_SEMI_EXPANDED;
+ }
+ if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXPANDED;
+ }
+ if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) {
+ return NS_FONT_STRETCH_EXTRA_EXPANDED;
+ }
+ return NS_FONT_STRETCH_ULTRA_EXPANDED;
+}
+
+/* static */ int
+gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch)
+{
+ switch (aStretch) {
+ default: // this will catch "normal" (0) as well as out-of-range values
+ return FC_WIDTH_NORMAL;
+ case NS_FONT_STRETCH_ULTRA_CONDENSED:
+ return FC_WIDTH_ULTRACONDENSED;
+ case NS_FONT_STRETCH_EXTRA_CONDENSED:
+ return FC_WIDTH_EXTRACONDENSED;
+ case NS_FONT_STRETCH_CONDENSED:
+ return FC_WIDTH_CONDENSED;
+ case NS_FONT_STRETCH_SEMI_CONDENSED:
+ return FC_WIDTH_SEMICONDENSED;
+ case NS_FONT_STRETCH_SEMI_EXPANDED:
+ return FC_WIDTH_SEMIEXPANDED;
+ case NS_FONT_STRETCH_EXPANDED:
+ return FC_WIDTH_EXPANDED;
+ case NS_FONT_STRETCH_EXTRA_EXPANDED:
+ return FC_WIDTH_EXTRAEXPANDED;
+ case NS_FONT_STRETCH_ULTRA_EXPANDED:
+ return FC_WIDTH_ULTRAEXPANDED;
+ }
+}
+
+// This makes a guess at an FC_WEIGHT corresponding to a base weight and
+// offset (without any knowledge of which weights are available).
+
+/* static */ int
+GuessFcWeight(const gfxFontStyle& aFontStyle)
+{
+ /*
+ * weights come in two parts crammed into one
+ * integer -- the "base" weight is weight / 100,
+ * the rest of the value is the "offset" from that
+ * weight -- the number of steps to move to adjust
+ * the weight in the list of supported font weights,
+ * this value can be negative or positive.
+ */
+ int8_t weight = aFontStyle.ComputeWeight();
+
+ // ComputeWeight trimmed the range of weights for us
+ NS_ASSERTION(weight >= 0 && weight <= 10,
+ "base weight out of range");
+
+ return gfxFontconfigUtils::FcWeightForBaseWeight(weight);
+}
+
+static void
+AddString(FcPattern *aPattern, const char *object, const char *aString)
+{
+ FcPatternAddString(aPattern, object,
+ gfxFontconfigUtils::ToFcChar8(aString));
+}
+
+static void
+AddWeakString(FcPattern *aPattern, const char *object, const char *aString)
+{
+ FcValue value;
+ value.type = FcTypeString;
+ value.u.s = gfxFontconfigUtils::ToFcChar8(aString);
+
+ FcPatternAddWeak(aPattern, object, value, FcTrue);
+}
+
+static void
+AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup)
+{
+ // Translate from mozilla's internal mapping into fontconfig's
+ nsAutoCString lang;
+ gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang);
+
+ if (!lang.IsEmpty()) {
+ AddString(aPattern, FC_LANG, lang.get());
+ }
+}
+
+nsReturnRef<FcPattern>
+gfxFontconfigUtils::NewPattern(const nsTArray<nsString>& aFamilies,
+ const gfxFontStyle& aFontStyle,
+ const char *aLang)
+{
+ static const char* sFontconfigGenerics[] =
+ { "sans-serif", "serif", "monospace", "fantasy", "cursive" };
+
+ nsAutoRef<FcPattern> pattern(FcPatternCreate());
+ if (!pattern)
+ return nsReturnRef<FcPattern>();
+
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size);
+ FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle));
+ FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle));
+ FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch));
+
+ if (aLang) {
+ AddString(pattern, FC_LANG, aLang);
+ }
+
+ bool useWeakBinding = false;
+ for (uint32_t i = 0; i < aFamilies.Length(); ++i) {
+ NS_ConvertUTF16toUTF8 family(aFamilies[i]);
+ if (!useWeakBinding) {
+ AddString(pattern, FC_FAMILY, family.get());
+
+ // fontconfig generic families are typically implemented with weak
+ // aliases (so that the preferred font depends on language).
+ // However, this would give them lower priority than subsequent
+ // non-generic families in the list. To ensure that subsequent
+ // families do not have a higher priority, they are given weak
+ // bindings.
+ for (uint32_t g = 0;
+ g < ArrayLength(sFontconfigGenerics);
+ ++g) {
+ if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]),
+ ToFcChar8(family.get()))) {
+ useWeakBinding = true;
+ break;
+ }
+ }
+ } else {
+ AddWeakString(pattern, FC_FAMILY, family.get());
+ }
+ }
+
+ return pattern.out();
+}
+
+gfxFontconfigUtils::gfxFontconfigUtils()
+ : mFontsByFamily(32)
+ , mFontsByFullname(32)
+ , mLangSupportTable(32)
+ , mLastConfig(nullptr)
+#ifdef MOZ_BUNDLED_FONTS
+ , mBundledFontsInitialized(false)
+#endif
+{
+ UpdateFontListInternal();
+}
+
+nsresult
+gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup,
+ const nsACString& aGenericFamily,
+ nsTArray<nsString>& aListOfFonts)
+{
+ aListOfFonts.Clear();
+
+ nsTArray<nsCString> fonts;
+ nsresult rv = GetFontListInternal(fonts, aLangGroup);
+ if (NS_FAILED(rv))
+ return rv;
+
+ for (uint32_t i = 0; i < fonts.Length(); ++i) {
+ aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i]));
+ }
+
+ aListOfFonts.Sort();
+
+ int32_t serif = 0, sansSerif = 0, monospace = 0;
+
+ // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and
+ // "monospace", slightly different from CSS's 5.
+ if (aGenericFamily.IsEmpty())
+ serif = sansSerif = monospace = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("serif"))
+ serif = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif"))
+ sansSerif = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("monospace"))
+ monospace = 1;
+ else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") ||
+ aGenericFamily.LowerCaseEqualsLiteral("fantasy"))
+ serif = sansSerif = 1;
+ else
+ NS_NOTREACHED("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, NS_LITERAL_STRING("monospace"));
+ if (sansSerif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif"));
+ if (serif)
+ aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif"));
+
+ return NS_OK;
+}
+
+struct MozLangGroupData {
+ nsIAtom* 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_telu, "te" },
+ { nsGkAtoms::x_tibt, "bo" },
+ { nsGkAtoms::Unicode, 0 },
+};
+
+static bool
+TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup,
+ nsACString *aFcLang)
+{
+ // 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".
+ // nsILanguageAtomService takes languages in the form language-subtag,
+ // where subtag may be a territory. fontconfig and nsILanguageAtomService
+ // 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;
+ }
+
+ nsIAtom *atom =
+ gLangService->LookupLanguage(*aFcLang);
+
+ return atom == aLangGroup;
+}
+
+/* static */ void
+gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup,
+ nsACString *aFcLang)
+{
+ NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL");
+
+ const MozLangGroupData *langGroup = nullptr;
+
+ for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) {
+ if (aLangGroup == MozLangGroups[i].mozLangGroup) {
+ langGroup = &MozLangGroups[i];
+ break;
+ }
+ }
+
+ if (!langGroup) {
+ // Not a special mozilla language group.
+ // Use aLangGroup as a language code.
+ aLangGroup->ToUTF8String(*aFcLang);
+ return;
+ }
+
+ // Check the environment for the users preferred language that corresponds
+ // to this langGroup.
+ if (!gLangService) {
+ CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService);
+ }
+
+ if (gLangService) {
+ 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),
+ aLangGroup, aFcLang))
+ return;
+
+ if (*pos == '\0')
+ break;
+
+ languages = pos + 1;
+ }
+ }
+ }
+ const char *ctype = setlocale(LC_CTYPE, nullptr);
+ if (ctype &&
+ TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang))
+ return;
+ }
+
+ if (langGroup->defaultLang) {
+ aFcLang->Assign(langGroup->defaultLang);
+ } else {
+ aFcLang->Truncate();
+ }
+}
+
+nsresult
+gfxFontconfigUtils::GetFontListInternal(nsTArray<nsCString>& aListOfFonts,
+ nsIAtom *aLangGroup)
+{
+ FcPattern *pat = nullptr;
+ FcObjectSet *os = nullptr;
+ FcFontSet *fs = nullptr;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ aListOfFonts.Clear();
+
+ pat = FcPatternCreate();
+ if (!pat)
+ goto end;
+
+ os = FcObjectSetBuild(FC_FAMILY, nullptr);
+ if (!os)
+ goto end;
+
+ // take the pattern and add the lang group to it
+ if (aLangGroup) {
+ AddLangGroup(pat, aLangGroup);
+ }
+
+ fs = FcFontList(nullptr, pat, os);
+ if (!fs)
+ goto end;
+
+ for (int i = 0; i < fs->nfont; i++) {
+ char *family;
+
+ if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0,
+ (FcChar8 **) &family) != FcResultMatch)
+ {
+ continue;
+ }
+
+ // Remove duplicates...
+ nsAutoCString strFamily(family);
+ if (aListOfFonts.Contains(strFamily))
+ continue;
+
+ aListOfFonts.AppendElement(strFamily);
+ }
+
+ rv = NS_OK;
+
+ end:
+ if (NS_FAILED(rv))
+ aListOfFonts.Clear();
+
+ if (pat)
+ FcPatternDestroy(pat);
+ if (os)
+ FcObjectSetDestroy(os);
+ if (fs)
+ FcFontSetDestroy(fs);
+
+ return rv;
+}
+
+nsresult
+gfxFontconfigUtils::UpdateFontList()
+{
+ return UpdateFontListInternal(true);
+}
+
+nsresult
+gfxFontconfigUtils::UpdateFontListInternal(bool aForce)
+{
+ if (!aForce) {
+ // This checks periodically according to fontconfig's configured
+ // <rescan> interval.
+ FcInitBringUptoDate();
+ } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce
+ mLastConfig = nullptr;
+ FcInitReinitialize();
+ }
+
+ // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config
+ // before destroying the old config, so the only way that we'd miss an
+ // update is if fontconfig did more than one update and the memory for the
+ // most recent config happened to be at the same location as the original
+ // config.
+ FcConfig *currentConfig = FcConfigGetCurrent();
+ if (currentConfig == mLastConfig)
+ return NS_OK;
+
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+#endif
+
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(currentConfig, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(currentConfig, FcSetApplication)
+#endif
+ };
+
+ mFontsByFamily.Clear();
+ mFontsByFullname.Clear();
+ mLangSupportTable.Clear();
+
+ // Record the existing font families
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) { // the application set might not exist
+ continue;
+ }
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ FcChar8 *family;
+ for (int v = 0;
+ FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch;
+ ++v) {
+ FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family);
+ if (entry) {
+ bool added = entry->AddFont(font);
+
+ if (!entry->mKey) {
+ // The reference to the font pattern keeps the pointer
+ // to string for the key valid. If adding the font
+ // failed then the entry must be removed.
+ if (added) {
+ entry->mKey = family;
+ } else {
+ mFontsByFamily.RemoveEntry(entry);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mLastConfig = currentConfig;
+ return NS_OK;
+}
+
+nsresult
+gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ aFamilyName.Truncate();
+
+ // The fontconfig has generic family names in the font list.
+ if (aFontName.EqualsLiteral("serif") ||
+ aFontName.EqualsLiteral("sans-serif") ||
+ aFontName.EqualsLiteral("monospace")) {
+ aFamilyName.Assign(aFontName);
+ return NS_OK;
+ }
+
+ nsresult rv = UpdateFontListInternal();
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ConvertUTF16toUTF8 fontname(aFontName);
+
+ // return empty string if no such family exists
+ if (!IsExistingFamily(fontname))
+ return NS_OK;
+
+ FcPattern *pat = nullptr;
+ FcObjectSet *os = nullptr;
+ FcFontSet *givenFS = nullptr;
+ nsTArray<nsCString> candidates;
+ FcFontSet *candidateFS = nullptr;
+ rv = NS_ERROR_FAILURE;
+
+ pat = FcPatternCreate();
+ if (!pat)
+ goto end;
+
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get());
+
+ os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr);
+ if (!os)
+ goto end;
+
+ givenFS = FcFontList(nullptr, pat, os);
+ if (!givenFS)
+ goto end;
+
+ // The first value associated with a FC_FAMILY property is the family
+ // returned by GetFontList(), so use this value if appropriate.
+
+ // See if there is a font face with first family equal to the given family.
+ 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 (fontname.Equals(first)) {
+ aFamilyName.Assign(aFontName);
+ rv = NS_OK;
+ goto end;
+ }
+ }
+ }
+
+ // See if any of the first family names represent the same set of font
+ // faces as the given family.
+ for (uint32_t j = 0; j < candidates.Length(); ++j) {
+ FcPatternDel(pat, FC_FAMILY);
+ FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get());
+
+ candidateFS = FcFontList(nullptr, pat, os);
+ if (!candidateFS)
+ goto end;
+
+ 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) {
+ AppendUTF8toUTF16(candidates[j], aFamilyName);
+ rv = NS_OK;
+ goto end;
+ }
+ }
+
+ // No match found; return empty string.
+ rv = NS_OK;
+
+ end:
+ if (pat)
+ FcPatternDestroy(pat);
+ if (os)
+ FcObjectSetDestroy(os);
+ if (givenFS)
+ FcFontSetDestroy(givenFS);
+ if (candidateFS)
+ FcFontSetDestroy(candidateFS);
+
+ return rv;
+}
+
+bool
+gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName)
+{
+ return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr;
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName)
+{
+ FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName);
+
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->GetFonts();
+}
+
+// Fontconfig only provides a fullname property for fonts in formats with SFNT
+// wrappers. For other font formats (including PCF and PS Type 1), a fullname
+// must be generated from the family and style properties. Only the first
+// family and style is checked, but that should be OK, as I don't expect
+// non-SFNT fonts to have multiple families or styles.
+bool
+gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont,
+ nsACString *aFullname)
+{
+ FcChar8 *family;
+ if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch)
+ return false;
+
+ aFullname->Truncate();
+ aFullname->Append(ToCString(family));
+
+ FcChar8 *style;
+ if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch &&
+ strcmp(ToCString(style), "Regular") != 0) {
+ aFullname->Append(' ');
+ aFullname->Append(ToCString(style));
+ }
+
+ return true;
+}
+
+bool
+gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const
+{
+ const FcChar8 *key = mKey;
+ // If mKey is nullptr, key comes from the style and family of the first
+ // font.
+ nsAutoCString fullname;
+ if (!key) {
+ NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!");
+ GetFullnameFromFamilyAndStyle(mFonts[0], &fullname);
+
+ key = ToFcChar8(fullname);
+ }
+
+ return FcStrCmpIgnoreCase(aKey, key) == 0;
+}
+
+void
+gfxFontconfigUtils::AddFullnameEntries()
+{
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(nullptr, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(nullptr, FcSetApplication)
+#endif
+ };
+
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) {
+ continue;
+ }
+ // Record the existing font families
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ int v = 0;
+ FcChar8 *fullname;
+ while (FcPatternGetString(font,
+ FC_FULLNAME, v, &fullname) == FcResultMatch) {
+ FontsByFullnameEntry *entry =
+ mFontsByFullname.PutEntry(fullname);
+ if (entry) {
+ // entry always has space for one font, so the first
+ // AddFont will always succeed, and so the entry will
+ // always have a font from which to obtain the key.
+ bool added = entry->AddFont(font);
+ // The key may be nullptr either if this is the first
+ // font, or if the first font does not have a fullname
+ // property, and so the key is obtained from the font.
+ // Set the key in both cases. The check that AddFont
+ // succeeded is required for the second case.
+ if (!entry->mKey && added) {
+ entry->mKey = fullname;
+ }
+ }
+
+ ++v;
+ }
+
+ // Fontconfig does not provide a fullname property for all fonts.
+ if (v == 0) {
+ nsAutoCString name;
+ if (!GetFullnameFromFamilyAndStyle(font, &name))
+ continue;
+
+ FontsByFullnameEntry *entry =
+ mFontsByFullname.PutEntry(ToFcChar8(name));
+ if (entry) {
+ entry->AddFont(font);
+ // Either entry->mKey has been set for a previous font or it
+ // remains nullptr to indicate that the key is obtained from
+ // the first font.
+ }
+ }
+ }
+ }
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname)
+{
+ if (mFontsByFullname.Count() == 0) {
+ AddFullnameEntries();
+ }
+
+ FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname);
+
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->GetFonts();
+}
+
+static FcLangResult
+CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) {
+ FcLangResult result = FcLangDifferentLang;
+ for (uint32_t i = 0; ; ++i) {
+ FcChar8 a = FcToLower(aLangA[i]);
+ FcChar8 b = FcToLower(aLangB[i]);
+
+ if (a != b) {
+ if ((a == '\0' && b == '-') || (a == '-' && b == '\0'))
+ return FcLangDifferentCountry;
+
+ return result;
+ }
+ if (a == '\0')
+ return FcLangEqual;
+
+ if (a == '-') {
+ result = FcLangDifferentCountry;
+ }
+ }
+}
+
+/* static */
+FcLangResult
+gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang)
+{
+ // When fontconfig builds a pattern for a system font, it will set a
+ // single LangSet property value for the font. That value may be removed
+ // and additional string values may be added through FcConfigSubsitute
+ // with FcMatchScan. Values that are neither LangSet nor string are
+ // considered errors in fontconfig sort and match functions.
+ //
+ // If no string nor LangSet value is found, then either the font is a
+ // system font and the LangSet has been removed through FcConfigSubsitute,
+ // or the font is a web font and its language support is unknown.
+ // Returning FcLangDifferentLang for these fonts ensures that this font
+ // will not be assumed to satisfy the language, and so language will be
+ // prioritized in sorting fallback fonts.
+ FcValue value;
+ FcLangResult best = FcLangDifferentLang;
+ for (int v = 0;
+ FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch;
+ ++v) {
+
+ FcLangResult support;
+ switch (value.type) {
+ case FcTypeLangSet:
+ support = FcLangSetHasLang(value.u.l, aLang);
+ break;
+ case FcTypeString:
+ support = CompareLangString(value.u.s, aLang);
+ break;
+ default:
+ // error. continue to see if there is a useful value.
+ continue;
+ }
+
+ if (support < best) { // lower is better
+ if (support == FcLangEqual)
+ return support;
+ best = support;
+ }
+ }
+
+ return best;
+}
+
+gfxFontconfigUtils::LangSupportEntry *
+gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts)
+{
+ // Currently any unrecognized languages from documents will be converted
+ // to x-unicode by nsILanguageAtomService, so there is a limit on the
+ // langugages that will be added here. Reconsider when/if document
+ // languages are passed to this routine.
+
+ LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang);
+ if (!entry)
+ return nullptr;
+
+ FcLangResult best = FcLangDifferentLang;
+
+ if (!entry->IsKeyInitialized()) {
+ entry->InitKey(aLang);
+ } else {
+ // mSupport is already initialized.
+ if (!aWithFonts)
+ return entry;
+
+ best = entry->mSupport;
+ // If there is support for this language, an empty font list indicates
+ // that the list hasn't been initialized yet.
+ if (best == FcLangDifferentLang || entry->mFonts.Length() > 0)
+ return entry;
+ }
+
+ // These FcFontSets are owned by fontconfig
+ FcFontSet *fontSets[] = {
+ FcConfigGetFonts(nullptr, FcSetSystem)
+#ifdef MOZ_BUNDLED_FONTS
+ , FcConfigGetFonts(nullptr, FcSetApplication)
+#endif
+ };
+
+ AutoTArray<FcPattern*,100> fonts;
+
+ for (unsigned fs = 0; fs < ArrayLength(fontSets); ++fs) {
+ FcFontSet *fontSet = fontSets[fs];
+ if (!fontSet) {
+ continue;
+ }
+ for (int f = 0; f < fontSet->nfont; ++f) {
+ FcPattern *font = fontSet->fonts[f];
+
+ FcLangResult support = GetLangSupport(font, aLang);
+
+ if (support < best) { // lower is better
+ best = support;
+ if (aWithFonts) {
+ fonts.Clear();
+ } else if (best == FcLangEqual) {
+ break;
+ }
+ }
+
+ // The font list in the LangSupportEntry is expected to be used
+ // only when no default fonts support the language. There would
+ // be a large number of fonts in entries for languages using Latin
+ // script but these do not need to be created because default
+ // fonts already support these languages.
+ if (aWithFonts && support != FcLangDifferentLang &&
+ support == best) {
+ fonts.AppendElement(font);
+ }
+ }
+ }
+
+ entry->mSupport = best;
+ if (aWithFonts) {
+ if (fonts.Length() != 0) {
+ entry->mFonts.AppendElements(fonts.Elements(), fonts.Length());
+ } else if (best != FcLangDifferentLang) {
+ // Previously there was a font that supported this language at the
+ // level of entry->mSupport, but it has now disappeared. At least
+ // entry->mSupport needs to be recalculated, but this is an
+ // indication that the set of installed fonts has changed, so
+ // update all caches.
+ mLastConfig = nullptr; // invalidates caches
+ UpdateFontListInternal(true);
+ return GetLangSupportEntry(aLang, aWithFonts);
+ }
+ }
+
+ return entry;
+}
+
+FcLangResult
+gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang)
+{
+ UpdateFontListInternal();
+
+ LangSupportEntry *entry = GetLangSupportEntry(aLang, false);
+ if (!entry)
+ return FcLangEqual;
+
+ return entry->mSupport;
+}
+
+const nsTArray< nsCountedRef<FcPattern> >&
+gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang)
+{
+ LangSupportEntry *entry = GetLangSupportEntry(aLang, true);
+ if (!entry)
+ return mEmptyPatternArray;
+
+ return entry->mFonts;
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+void
+gfxFontconfigUtils::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(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+ if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) {
+ return;
+ }
+ }
+ if (!mBundledFontsPath.IsEmpty()) {
+ FcConfigAppFontAddDir(nullptr, (const FcChar8*)mBundledFontsPath.get());
+ }
+}
+
+#endif
+
+gfxFontconfigFontBase::gfxFontconfigFontBase(cairo_scaled_font_t *aScaledFont,
+ FcPattern *aPattern,
+ gfxFontEntry *aFontEntry,
+ const gfxFontStyle *aFontStyle)
+ : gfxFT2FontBase(aScaledFont, aFontEntry, aFontStyle)
+ , mPattern(aPattern)
+{
+}
+