diff options
Diffstat (limited to 'gfx/thebes/gfxFontconfigUtils.cpp')
-rw-r--r-- | gfx/thebes/gfxFontconfigUtils.cpp | 1100 |
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) +{ +} + |