michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "gfxFontconfigUtils.h" michael@0: #include "gfxFont.h" michael@0: #include "nsGkAtoms.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsILanguageAtomService.h" michael@0: #include "nsTArray.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #include "nsIAtom.h" michael@0: #include "nsCRT.h" michael@0: #include "gfxFontConstants.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr; michael@0: static nsILanguageAtomService* gLangService = nullptr; michael@0: michael@0: /* static */ void michael@0: gfxFontconfigUtils::Shutdown() { michael@0: if (sUtils) { michael@0: delete sUtils; michael@0: sUtils = nullptr; michael@0: } michael@0: NS_IF_RELEASE(gLangService); michael@0: } michael@0: michael@0: /* static */ uint8_t michael@0: gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant) michael@0: { michael@0: switch (aFcSlant) { michael@0: case FC_SLANT_ITALIC: michael@0: return NS_FONT_STYLE_ITALIC; michael@0: case FC_SLANT_OBLIQUE: michael@0: return NS_FONT_STYLE_OBLIQUE; michael@0: default: michael@0: return NS_FONT_STYLE_NORMAL; michael@0: } michael@0: } michael@0: michael@0: /* static */ uint8_t michael@0: gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern) michael@0: { michael@0: int slant; michael@0: if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) { michael@0: return NS_FONT_STYLE_NORMAL; michael@0: } michael@0: michael@0: return FcSlantToThebesStyle(slant); michael@0: } michael@0: michael@0: /* static */ int michael@0: gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle) michael@0: { michael@0: if (aFontStyle.style == NS_FONT_STYLE_ITALIC) michael@0: return FC_SLANT_ITALIC; michael@0: if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE) michael@0: return FC_SLANT_OBLIQUE; michael@0: michael@0: return FC_SLANT_ROMAN; michael@0: } michael@0: michael@0: // OS/2 weight classes were introduced in fontconfig-2.1.93 (2003). michael@0: #ifndef FC_WEIGHT_THIN michael@0: #define FC_WEIGHT_THIN 0 // 2.1.93 michael@0: #define FC_WEIGHT_EXTRALIGHT 40 // 2.1.93 michael@0: #define FC_WEIGHT_REGULAR 80 // 2.1.93 michael@0: #define FC_WEIGHT_EXTRABOLD 205 // 2.1.93 michael@0: #endif michael@0: // book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005) michael@0: #ifndef FC_WEIGHT_BOOK michael@0: #define FC_WEIGHT_BOOK 75 michael@0: #endif michael@0: // extra black was introduced in fontconfig-2.4.91 (2007) michael@0: #ifndef FC_WEIGHT_EXTRABLACK michael@0: #define FC_WEIGHT_EXTRABLACK 215 michael@0: #endif michael@0: michael@0: /* static */ uint16_t michael@0: gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern) michael@0: { michael@0: int weight; michael@0: if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) michael@0: return NS_FONT_WEIGHT_NORMAL; michael@0: michael@0: if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) michael@0: return 100; michael@0: if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) michael@0: return 200; michael@0: if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) michael@0: return 300; michael@0: if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) michael@0: // This includes FC_WEIGHT_BOOK michael@0: return 400; michael@0: if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) michael@0: return 500; michael@0: if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) michael@0: return 600; michael@0: if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) michael@0: return 700; michael@0: if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) michael@0: return 800; michael@0: if (weight <= FC_WEIGHT_BLACK) michael@0: return 900; michael@0: michael@0: // including FC_WEIGHT_EXTRABLACK michael@0: return 901; michael@0: } michael@0: michael@0: /* static */ int michael@0: gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight) michael@0: { michael@0: NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10, michael@0: "base weight out of range"); michael@0: michael@0: switch (aBaseWeight) { michael@0: case 2: michael@0: return FC_WEIGHT_EXTRALIGHT; michael@0: case 3: michael@0: return FC_WEIGHT_LIGHT; michael@0: case 4: michael@0: return FC_WEIGHT_REGULAR; michael@0: case 5: michael@0: return FC_WEIGHT_MEDIUM; michael@0: case 6: michael@0: return FC_WEIGHT_DEMIBOLD; michael@0: case 7: michael@0: return FC_WEIGHT_BOLD; michael@0: case 8: michael@0: return FC_WEIGHT_EXTRABOLD; michael@0: case 9: michael@0: return FC_WEIGHT_BLACK; michael@0: } michael@0: michael@0: // extremes michael@0: return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK; michael@0: } michael@0: michael@0: /* static */ int16_t michael@0: gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern) michael@0: { michael@0: int width; michael@0: if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) { michael@0: return NS_FONT_STRETCH_NORMAL; michael@0: } michael@0: michael@0: if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { michael@0: return NS_FONT_STRETCH_ULTRA_CONDENSED; michael@0: } michael@0: if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { michael@0: return NS_FONT_STRETCH_EXTRA_CONDENSED; michael@0: } michael@0: if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { michael@0: return NS_FONT_STRETCH_CONDENSED; michael@0: } michael@0: if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { michael@0: return NS_FONT_STRETCH_SEMI_CONDENSED; michael@0: } michael@0: if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { michael@0: return NS_FONT_STRETCH_NORMAL; michael@0: } michael@0: if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { michael@0: return NS_FONT_STRETCH_SEMI_EXPANDED; michael@0: } michael@0: if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { michael@0: return NS_FONT_STRETCH_EXPANDED; michael@0: } michael@0: if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { michael@0: return NS_FONT_STRETCH_EXTRA_EXPANDED; michael@0: } michael@0: return NS_FONT_STRETCH_ULTRA_EXPANDED; michael@0: } michael@0: michael@0: /* static */ int michael@0: gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch) michael@0: { michael@0: switch (aStretch) { michael@0: default: // this will catch "normal" (0) as well as out-of-range values michael@0: return FC_WIDTH_NORMAL; michael@0: case NS_FONT_STRETCH_ULTRA_CONDENSED: michael@0: return FC_WIDTH_ULTRACONDENSED; michael@0: case NS_FONT_STRETCH_EXTRA_CONDENSED: michael@0: return FC_WIDTH_EXTRACONDENSED; michael@0: case NS_FONT_STRETCH_CONDENSED: michael@0: return FC_WIDTH_CONDENSED; michael@0: case NS_FONT_STRETCH_SEMI_CONDENSED: michael@0: return FC_WIDTH_SEMICONDENSED; michael@0: case NS_FONT_STRETCH_SEMI_EXPANDED: michael@0: return FC_WIDTH_SEMIEXPANDED; michael@0: case NS_FONT_STRETCH_EXPANDED: michael@0: return FC_WIDTH_EXPANDED; michael@0: case NS_FONT_STRETCH_EXTRA_EXPANDED: michael@0: return FC_WIDTH_EXTRAEXPANDED; michael@0: case NS_FONT_STRETCH_ULTRA_EXPANDED: michael@0: return FC_WIDTH_ULTRAEXPANDED; michael@0: } michael@0: } michael@0: michael@0: // This makes a guess at an FC_WEIGHT corresponding to a base weight and michael@0: // offset (without any knowledge of which weights are available). michael@0: michael@0: /* static */ int michael@0: GuessFcWeight(const gfxFontStyle& aFontStyle) michael@0: { michael@0: /* michael@0: * weights come in two parts crammed into one michael@0: * integer -- the "base" weight is weight / 100, michael@0: * the rest of the value is the "offset" from that michael@0: * weight -- the number of steps to move to adjust michael@0: * the weight in the list of supported font weights, michael@0: * this value can be negative or positive. michael@0: */ michael@0: int8_t weight = aFontStyle.ComputeWeight(); michael@0: michael@0: // ComputeWeight trimmed the range of weights for us michael@0: NS_ASSERTION(weight >= 0 && weight <= 10, michael@0: "base weight out of range"); michael@0: michael@0: return gfxFontconfigUtils::FcWeightForBaseWeight(weight); michael@0: } michael@0: michael@0: static void michael@0: AddString(FcPattern *aPattern, const char *object, const char *aString) michael@0: { michael@0: FcPatternAddString(aPattern, object, michael@0: gfxFontconfigUtils::ToFcChar8(aString)); michael@0: } michael@0: michael@0: static void michael@0: AddWeakString(FcPattern *aPattern, const char *object, const char *aString) michael@0: { michael@0: FcValue value; michael@0: value.type = FcTypeString; michael@0: value.u.s = gfxFontconfigUtils::ToFcChar8(aString); michael@0: michael@0: FcPatternAddWeak(aPattern, object, value, FcTrue); michael@0: } michael@0: michael@0: static void michael@0: AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup) michael@0: { michael@0: // Translate from mozilla's internal mapping into fontconfig's michael@0: nsAutoCString lang; michael@0: gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang); michael@0: michael@0: if (!lang.IsEmpty()) { michael@0: AddString(aPattern, FC_LANG, lang.get()); michael@0: } michael@0: } michael@0: michael@0: nsReturnRef michael@0: gfxFontconfigUtils::NewPattern(const nsTArray& aFamilies, michael@0: const gfxFontStyle& aFontStyle, michael@0: const char *aLang) michael@0: { michael@0: static const char* sFontconfigGenerics[] = michael@0: { "sans-serif", "serif", "monospace", "fantasy", "cursive" }; michael@0: michael@0: nsAutoRef pattern(FcPatternCreate()); michael@0: if (!pattern) michael@0: return nsReturnRef(); michael@0: michael@0: FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size); michael@0: FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle)); michael@0: FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle)); michael@0: FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch)); michael@0: michael@0: if (aLang) { michael@0: AddString(pattern, FC_LANG, aLang); michael@0: } michael@0: michael@0: bool useWeakBinding = false; michael@0: for (uint32_t i = 0; i < aFamilies.Length(); ++i) { michael@0: NS_ConvertUTF16toUTF8 family(aFamilies[i]); michael@0: if (!useWeakBinding) { michael@0: AddString(pattern, FC_FAMILY, family.get()); michael@0: michael@0: // fontconfig generic families are typically implemented with weak michael@0: // aliases (so that the preferred font depends on language). michael@0: // However, this would give them lower priority than subsequent michael@0: // non-generic families in the list. To ensure that subsequent michael@0: // families do not have a higher priority, they are given weak michael@0: // bindings. michael@0: for (uint32_t g = 0; michael@0: g < ArrayLength(sFontconfigGenerics); michael@0: ++g) { michael@0: if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]), michael@0: ToFcChar8(family.get()))) { michael@0: useWeakBinding = true; michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: AddWeakString(pattern, FC_FAMILY, family.get()); michael@0: } michael@0: } michael@0: michael@0: return pattern.out(); michael@0: } michael@0: michael@0: gfxFontconfigUtils::gfxFontconfigUtils() michael@0: : mFontsByFamily(50) michael@0: , mFontsByFullname(50) michael@0: , mLangSupportTable(50) michael@0: , mLastConfig(nullptr) michael@0: { michael@0: UpdateFontListInternal(); michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup, michael@0: const nsACString& aGenericFamily, michael@0: nsTArray& aListOfFonts) michael@0: { michael@0: aListOfFonts.Clear(); michael@0: michael@0: nsTArray fonts; michael@0: nsresult rv = GetFontListInternal(fonts, aLangGroup); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: for (uint32_t i = 0; i < fonts.Length(); ++i) { michael@0: aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i])); michael@0: } michael@0: michael@0: aListOfFonts.Sort(); michael@0: michael@0: int32_t serif = 0, sansSerif = 0, monospace = 0; michael@0: michael@0: // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and michael@0: // "monospace", slightly different from CSS's 5. michael@0: if (aGenericFamily.IsEmpty()) michael@0: serif = sansSerif = monospace = 1; michael@0: else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) michael@0: serif = 1; michael@0: else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) michael@0: sansSerif = 1; michael@0: else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) michael@0: monospace = 1; michael@0: else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || michael@0: aGenericFamily.LowerCaseEqualsLiteral("fantasy")) michael@0: serif = sansSerif = 1; michael@0: else michael@0: NS_NOTREACHED("unexpected CSS generic font family"); michael@0: michael@0: // The first in the list becomes the default in michael@0: // gFontsDialog.readFontSelection() if the preference-selected font is not michael@0: // available, so put system configured defaults first. michael@0: if (monospace) michael@0: aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace")); michael@0: if (sansSerif) michael@0: aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif")); michael@0: if (serif) michael@0: aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif")); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct MozLangGroupData { michael@0: nsIAtom* const& mozLangGroup; michael@0: const char *defaultLang; michael@0: }; michael@0: michael@0: const MozLangGroupData MozLangGroups[] = { michael@0: { nsGkAtoms::x_western, "en" }, michael@0: { nsGkAtoms::x_central_euro, "pl" }, michael@0: { nsGkAtoms::x_cyrillic, "ru" }, michael@0: { nsGkAtoms::x_baltic, "lv" }, michael@0: { nsGkAtoms::x_devanagari, "hi" }, michael@0: { nsGkAtoms::x_tamil, "ta" }, michael@0: { nsGkAtoms::x_armn, "hy" }, michael@0: { nsGkAtoms::x_beng, "bn" }, michael@0: { nsGkAtoms::x_cans, "iu" }, michael@0: { nsGkAtoms::x_ethi, "am" }, michael@0: { nsGkAtoms::x_geor, "ka" }, michael@0: { nsGkAtoms::x_gujr, "gu" }, michael@0: { nsGkAtoms::x_guru, "pa" }, michael@0: { nsGkAtoms::x_khmr, "km" }, michael@0: { nsGkAtoms::x_knda, "kn" }, michael@0: { nsGkAtoms::x_mlym, "ml" }, michael@0: { nsGkAtoms::x_orya, "or" }, michael@0: { nsGkAtoms::x_sinh, "si" }, michael@0: { nsGkAtoms::x_telu, "te" }, michael@0: { nsGkAtoms::x_tibt, "bo" }, michael@0: { nsGkAtoms::Unicode, 0 }, michael@0: }; michael@0: michael@0: static bool michael@0: TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup, michael@0: nsACString *aFcLang) michael@0: { michael@0: // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'. michael@0: // aOSLang is in the form "language[_territory][.codeset][@modifier]". michael@0: // fontconfig takes languages in the form "language-territory". michael@0: // nsILanguageAtomService takes languages in the form language-subtag, michael@0: // where subtag may be a territory. fontconfig and nsILanguageAtomService michael@0: // handle case-conversion for us. michael@0: const char *pos, *end; michael@0: aOSLang.BeginReading(pos); michael@0: aOSLang.EndReading(end); michael@0: aFcLang->Truncate(); michael@0: while (pos < end) { michael@0: switch (*pos) { michael@0: case '.': michael@0: case '@': michael@0: end = pos; michael@0: break; michael@0: case '_': michael@0: aFcLang->Append('-'); michael@0: break; michael@0: default: michael@0: aFcLang->Append(*pos); michael@0: } michael@0: ++pos; michael@0: } michael@0: michael@0: nsIAtom *atom = michael@0: gLangService->LookupLanguage(*aFcLang); michael@0: michael@0: return atom == aLangGroup; michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup, michael@0: nsACString *aFcLang) michael@0: { michael@0: NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL"); michael@0: michael@0: const MozLangGroupData *langGroup = nullptr; michael@0: michael@0: for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) { michael@0: if (aLangGroup == MozLangGroups[i].mozLangGroup) { michael@0: langGroup = &MozLangGroups[i]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!langGroup) { michael@0: // Not a special mozilla language group. michael@0: // Use aLangGroup as a language code. michael@0: aLangGroup->ToUTF8String(*aFcLang); michael@0: return; michael@0: } michael@0: michael@0: // Check the environment for the users preferred language that corresponds michael@0: // to this langGroup. michael@0: if (!gLangService) { michael@0: CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); michael@0: } michael@0: michael@0: if (gLangService) { michael@0: const char *languages = getenv("LANGUAGE"); michael@0: if (languages) { michael@0: const char separator = ':'; michael@0: michael@0: for (const char *pos = languages; true; ++pos) { michael@0: if (*pos == '\0' || *pos == separator) { michael@0: if (languages < pos && michael@0: TryLangForGroup(Substring(languages, pos), michael@0: aLangGroup, aFcLang)) michael@0: return; michael@0: michael@0: if (*pos == '\0') michael@0: break; michael@0: michael@0: languages = pos + 1; michael@0: } michael@0: } michael@0: } michael@0: const char *ctype = setlocale(LC_CTYPE, nullptr); michael@0: if (ctype && michael@0: TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang)) michael@0: return; michael@0: } michael@0: michael@0: if (langGroup->defaultLang) { michael@0: aFcLang->Assign(langGroup->defaultLang); michael@0: } else { michael@0: aFcLang->Truncate(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::GetFontListInternal(nsTArray& aListOfFonts, michael@0: nsIAtom *aLangGroup) michael@0: { michael@0: FcPattern *pat = nullptr; michael@0: FcObjectSet *os = nullptr; michael@0: FcFontSet *fs = nullptr; michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: aListOfFonts.Clear(); michael@0: michael@0: pat = FcPatternCreate(); michael@0: if (!pat) michael@0: goto end; michael@0: michael@0: os = FcObjectSetBuild(FC_FAMILY, nullptr); michael@0: if (!os) michael@0: goto end; michael@0: michael@0: // take the pattern and add the lang group to it michael@0: if (aLangGroup) { michael@0: AddLangGroup(pat, aLangGroup); michael@0: } michael@0: michael@0: fs = FcFontList(nullptr, pat, os); michael@0: if (!fs) michael@0: goto end; michael@0: michael@0: for (int i = 0; i < fs->nfont; i++) { michael@0: char *family; michael@0: michael@0: if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, michael@0: (FcChar8 **) &family) != FcResultMatch) michael@0: { michael@0: continue; michael@0: } michael@0: michael@0: // Remove duplicates... michael@0: nsAutoCString strFamily(family); michael@0: if (aListOfFonts.Contains(strFamily)) michael@0: continue; michael@0: michael@0: aListOfFonts.AppendElement(strFamily); michael@0: } michael@0: michael@0: rv = NS_OK; michael@0: michael@0: end: michael@0: if (NS_FAILED(rv)) michael@0: aListOfFonts.Clear(); michael@0: michael@0: if (pat) michael@0: FcPatternDestroy(pat); michael@0: if (os) michael@0: FcObjectSetDestroy(os); michael@0: if (fs) michael@0: FcFontSetDestroy(fs); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::UpdateFontList() michael@0: { michael@0: return UpdateFontListInternal(true); michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::UpdateFontListInternal(bool aForce) michael@0: { michael@0: if (!aForce) { michael@0: // This checks periodically according to fontconfig's configured michael@0: // interval. michael@0: FcInitBringUptoDate(); michael@0: } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce michael@0: mLastConfig = nullptr; michael@0: FcInitReinitialize(); michael@0: } michael@0: michael@0: // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config michael@0: // before destroying the old config, so the only way that we'd miss an michael@0: // update is if fontconfig did more than one update and the memory for the michael@0: // most recent config happened to be at the same location as the original michael@0: // config. michael@0: FcConfig *currentConfig = FcConfigGetCurrent(); michael@0: if (currentConfig == mLastConfig) michael@0: return NS_OK; michael@0: michael@0: // This FcFontSet is owned by fontconfig michael@0: FcFontSet *fontSet = FcConfigGetFonts(currentConfig, FcSetSystem); michael@0: michael@0: mFontsByFamily.Clear(); michael@0: mFontsByFullname.Clear(); michael@0: mLangSupportTable.Clear(); michael@0: mAliasForMultiFonts.Clear(); michael@0: michael@0: // Record the existing font families michael@0: for (int f = 0; f < fontSet->nfont; ++f) { michael@0: FcPattern *font = fontSet->fonts[f]; michael@0: michael@0: FcChar8 *family; michael@0: for (int v = 0; michael@0: FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch; michael@0: ++v) { michael@0: FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family); michael@0: if (entry) { michael@0: bool added = entry->AddFont(font); michael@0: michael@0: if (!entry->mKey) { michael@0: // The reference to the font pattern keeps the pointer to michael@0: // string for the key valid. If adding the font failed michael@0: // then the entry must be removed. michael@0: if (added) { michael@0: entry->mKey = family; michael@0: } else { michael@0: mFontsByFamily.RawRemoveEntry(entry); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // XXX we don't support all alias names. michael@0: // Because if we don't check whether the given font name is alias name, michael@0: // fontconfig converts the non existing font to sans-serif. michael@0: // This is not good if the web page specifies font-family michael@0: // that has Windows font name in the first. michael@0: NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); michael@0: nsAdoptingCString list = Preferences::GetCString("font.alias-list"); michael@0: michael@0: if (!list.IsEmpty()) { michael@0: const char kComma = ','; michael@0: const char *p, *p_end; michael@0: list.BeginReading(p); michael@0: list.EndReading(p_end); michael@0: while (p < p_end) { michael@0: while (nsCRT::IsAsciiSpace(*p)) { michael@0: if (++p == p_end) michael@0: break; michael@0: } michael@0: if (p == p_end) michael@0: break; michael@0: const char *start = p; michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: nsAutoCString name(Substring(start, p)); michael@0: name.CompressWhitespace(false, true); michael@0: mAliasForMultiFonts.AppendElement(name); michael@0: p++; michael@0: } michael@0: } michael@0: michael@0: mLastConfig = currentConfig; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) michael@0: { michael@0: aFamilyName.Truncate(); michael@0: michael@0: // The fontconfig has generic family names in the font list. michael@0: if (aFontName.EqualsLiteral("serif") || michael@0: aFontName.EqualsLiteral("sans-serif") || michael@0: aFontName.EqualsLiteral("monospace")) { michael@0: aFamilyName.Assign(aFontName); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = UpdateFontListInternal(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_ConvertUTF16toUTF8 fontname(aFontName); michael@0: michael@0: // return empty string if no such family exists michael@0: if (!IsExistingFamily(fontname)) michael@0: return NS_OK; michael@0: michael@0: FcPattern *pat = nullptr; michael@0: FcObjectSet *os = nullptr; michael@0: FcFontSet *givenFS = nullptr; michael@0: nsTArray candidates; michael@0: FcFontSet *candidateFS = nullptr; michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: pat = FcPatternCreate(); michael@0: if (!pat) michael@0: goto end; michael@0: michael@0: FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get()); michael@0: michael@0: os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr); michael@0: if (!os) michael@0: goto end; michael@0: michael@0: givenFS = FcFontList(nullptr, pat, os); michael@0: if (!givenFS) michael@0: goto end; michael@0: michael@0: // The first value associated with a FC_FAMILY property is the family michael@0: // returned by GetFontList(), so use this value if appropriate. michael@0: michael@0: // See if there is a font face with first family equal to the given family. michael@0: for (int i = 0; i < givenFS->nfont; ++i) { michael@0: char *firstFamily; michael@0: if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, michael@0: (FcChar8 **) &firstFamily) != FcResultMatch) michael@0: continue; michael@0: michael@0: nsDependentCString first(firstFamily); michael@0: if (!candidates.Contains(first)) { michael@0: candidates.AppendElement(first); michael@0: michael@0: if (fontname.Equals(first)) { michael@0: aFamilyName.Assign(aFontName); michael@0: rv = NS_OK; michael@0: goto end; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // See if any of the first family names represent the same set of font michael@0: // faces as the given family. michael@0: for (uint32_t j = 0; j < candidates.Length(); ++j) { michael@0: FcPatternDel(pat, FC_FAMILY); michael@0: FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get()); michael@0: michael@0: candidateFS = FcFontList(nullptr, pat, os); michael@0: if (!candidateFS) michael@0: goto end; michael@0: michael@0: if (candidateFS->nfont != givenFS->nfont) michael@0: continue; michael@0: michael@0: bool equal = true; michael@0: for (int i = 0; i < givenFS->nfont; ++i) { michael@0: if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { michael@0: equal = false; michael@0: break; michael@0: } michael@0: } michael@0: if (equal) { michael@0: AppendUTF8toUTF16(candidates[j], aFamilyName); michael@0: rv = NS_OK; michael@0: goto end; michael@0: } michael@0: } michael@0: michael@0: // No match found; return empty string. michael@0: rv = NS_OK; michael@0: michael@0: end: michael@0: if (pat) michael@0: FcPatternDestroy(pat); michael@0: if (os) michael@0: FcObjectSetDestroy(os); michael@0: if (givenFS) michael@0: FcFontSetDestroy(givenFS); michael@0: if (candidateFS) michael@0: FcFontSetDestroy(candidateFS); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontconfigUtils::ResolveFontName(const nsAString& aFontName, michael@0: gfxPlatform::FontResolverCallback aCallback, michael@0: void *aClosure, michael@0: bool& aAborted) michael@0: { michael@0: aAborted = false; michael@0: michael@0: nsresult rv = UpdateFontListInternal(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_ConvertUTF16toUTF8 fontname(aFontName); michael@0: // Sometimes, the font has two or more names (e.g., "Sazanami Gothic" has michael@0: // Japanese localized name). We should not resolve to a single name michael@0: // because different names sometimes have different behavior. e.g., with michael@0: // the default settings of "Sazanami" on Fedora Core 5, the non-localized michael@0: // name uses anti-alias, but the localized name uses it. So, we should michael@0: // check just whether the font is existing, without resolving to regular michael@0: // name. michael@0: // michael@0: // The family names in mAliasForMultiFonts are names understood by michael@0: // fontconfig. The actual font to which they resolve depends on the michael@0: // entire match pattern. That info is not available here, but there michael@0: // will be a font so leave the resolving to the gfxFontGroup. michael@0: if (IsExistingFamily(fontname) || michael@0: mAliasForMultiFonts.Contains(fontname, gfxIgnoreCaseCStringComparator())) michael@0: aAborted = !(*aCallback)(aFontName, aClosure); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName) michael@0: { michael@0: return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr; michael@0: } michael@0: michael@0: const nsTArray< nsCountedRef >& michael@0: gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName) michael@0: { michael@0: FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName); michael@0: michael@0: if (!entry) michael@0: return mEmptyPatternArray; michael@0: michael@0: return entry->GetFonts(); michael@0: } michael@0: michael@0: // Fontconfig only provides a fullname property for fonts in formats with SFNT michael@0: // wrappers. For other font formats (including PCF and PS Type 1), a fullname michael@0: // must be generated from the family and style properties. Only the first michael@0: // family and style is checked, but that should be OK, as I don't expect michael@0: // non-SFNT fonts to have multiple families or styles. michael@0: bool michael@0: gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont, michael@0: nsACString *aFullname) michael@0: { michael@0: FcChar8 *family; michael@0: if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch) michael@0: return false; michael@0: michael@0: aFullname->Truncate(); michael@0: aFullname->Append(ToCString(family)); michael@0: michael@0: FcChar8 *style; michael@0: if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch && michael@0: strcmp(ToCString(style), "Regular") != 0) { michael@0: aFullname->Append(' '); michael@0: aFullname->Append(ToCString(style)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const michael@0: { michael@0: const FcChar8 *key = mKey; michael@0: // If mKey is nullptr, key comes from the style and family of the first michael@0: // font. michael@0: nsAutoCString fullname; michael@0: if (!key) { michael@0: NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!"); michael@0: GetFullnameFromFamilyAndStyle(mFonts[0], &fullname); michael@0: michael@0: key = ToFcChar8(fullname); michael@0: } michael@0: michael@0: return FcStrCmpIgnoreCase(aKey, key) == 0; michael@0: } michael@0: michael@0: void michael@0: gfxFontconfigUtils::AddFullnameEntries() michael@0: { michael@0: // This FcFontSet is owned by fontconfig michael@0: FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); michael@0: michael@0: // Record the existing font families michael@0: for (int f = 0; f < fontSet->nfont; ++f) { michael@0: FcPattern *font = fontSet->fonts[f]; michael@0: michael@0: int v = 0; michael@0: FcChar8 *fullname; michael@0: while (FcPatternGetString(font, michael@0: FC_FULLNAME, v, &fullname) == FcResultMatch) { michael@0: FontsByFullnameEntry *entry = mFontsByFullname.PutEntry(fullname); michael@0: if (entry) { michael@0: // entry always has space for one font, so the first AddFont michael@0: // will always succeed, and so the entry will always have a michael@0: // font from which to obtain the key. michael@0: bool added = entry->AddFont(font); michael@0: // The key may be nullptr either if this is the first font, or michael@0: // if the first font does not have a fullname property, and so michael@0: // the key is obtained from the font. Set the key in both michael@0: // cases. The check that AddFont succeeded is required for michael@0: // the second case. michael@0: if (!entry->mKey && added) { michael@0: entry->mKey = fullname; michael@0: } michael@0: } michael@0: michael@0: ++v; michael@0: } michael@0: michael@0: // Fontconfig does not provide a fullname property for all fonts. michael@0: if (v == 0) { michael@0: nsAutoCString name; michael@0: if (!GetFullnameFromFamilyAndStyle(font, &name)) michael@0: continue; michael@0: michael@0: FontsByFullnameEntry *entry = michael@0: mFontsByFullname.PutEntry(ToFcChar8(name)); michael@0: if (entry) { michael@0: entry->AddFont(font); michael@0: // Either entry->mKey has been set for a previous font or it michael@0: // remains nullptr to indicate that the key is obtained from michael@0: // the first font. michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: const nsTArray< nsCountedRef >& michael@0: gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname) michael@0: { michael@0: if (mFontsByFullname.Count() == 0) { michael@0: AddFullnameEntries(); michael@0: } michael@0: michael@0: FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname); michael@0: michael@0: if (!entry) michael@0: return mEmptyPatternArray; michael@0: michael@0: return entry->GetFonts(); michael@0: } michael@0: michael@0: static FcLangResult michael@0: CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) { michael@0: FcLangResult result = FcLangDifferentLang; michael@0: for (uint32_t i = 0; ; ++i) { michael@0: FcChar8 a = FcToLower(aLangA[i]); michael@0: FcChar8 b = FcToLower(aLangB[i]); michael@0: michael@0: if (a != b) { michael@0: if ((a == '\0' && b == '-') || (a == '-' && b == '\0')) michael@0: return FcLangDifferentCountry; michael@0: michael@0: return result; michael@0: } michael@0: if (a == '\0') michael@0: return FcLangEqual; michael@0: michael@0: if (a == '-') { michael@0: result = FcLangDifferentCountry; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: FcLangResult michael@0: gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang) michael@0: { michael@0: // When fontconfig builds a pattern for a system font, it will set a michael@0: // single LangSet property value for the font. That value may be removed michael@0: // and additional string values may be added through FcConfigSubsitute michael@0: // with FcMatchScan. Values that are neither LangSet nor string are michael@0: // considered errors in fontconfig sort and match functions. michael@0: // michael@0: // If no string nor LangSet value is found, then either the font is a michael@0: // system font and the LangSet has been removed through FcConfigSubsitute, michael@0: // or the font is a web font and its language support is unknown. michael@0: // Returning FcLangDifferentLang for these fonts ensures that this font michael@0: // will not be assumed to satisfy the language, and so language will be michael@0: // prioritized in sorting fallback fonts. michael@0: FcValue value; michael@0: FcLangResult best = FcLangDifferentLang; michael@0: for (int v = 0; michael@0: FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch; michael@0: ++v) { michael@0: michael@0: FcLangResult support; michael@0: switch (value.type) { michael@0: case FcTypeLangSet: michael@0: support = FcLangSetHasLang(value.u.l, aLang); michael@0: break; michael@0: case FcTypeString: michael@0: support = CompareLangString(value.u.s, aLang); michael@0: break; michael@0: default: michael@0: // error. continue to see if there is a useful value. michael@0: continue; michael@0: } michael@0: michael@0: if (support < best) { // lower is better michael@0: if (support == FcLangEqual) michael@0: return support; michael@0: best = support; michael@0: } michael@0: } michael@0: michael@0: return best; michael@0: } michael@0: michael@0: gfxFontconfigUtils::LangSupportEntry * michael@0: gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts) michael@0: { michael@0: // Currently any unrecognized languages from documents will be converted michael@0: // to x-unicode by nsILanguageAtomService, so there is a limit on the michael@0: // langugages that will be added here. Reconsider when/if document michael@0: // languages are passed to this routine. michael@0: michael@0: LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang); michael@0: if (!entry) michael@0: return nullptr; michael@0: michael@0: FcLangResult best = FcLangDifferentLang; michael@0: michael@0: if (!entry->IsKeyInitialized()) { michael@0: entry->InitKey(aLang); michael@0: } else { michael@0: // mSupport is already initialized. michael@0: if (!aWithFonts) michael@0: return entry; michael@0: michael@0: best = entry->mSupport; michael@0: // If there is support for this language, an empty font list indicates michael@0: // that the list hasn't been initialized yet. michael@0: if (best == FcLangDifferentLang || entry->mFonts.Length() > 0) michael@0: return entry; michael@0: } michael@0: michael@0: // This FcFontSet is owned by fontconfig michael@0: FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); michael@0: michael@0: nsAutoTArray fonts; michael@0: michael@0: for (int f = 0; f < fontSet->nfont; ++f) { michael@0: FcPattern *font = fontSet->fonts[f]; michael@0: michael@0: FcLangResult support = GetLangSupport(font, aLang); michael@0: michael@0: if (support < best) { // lower is better michael@0: best = support; michael@0: if (aWithFonts) { michael@0: fonts.Clear(); michael@0: } else if (best == FcLangEqual) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // The font list in the LangSupportEntry is expected to be used only michael@0: // when no default fonts support the language. There would be a large michael@0: // number of fonts in entries for languages using Latin script but michael@0: // these do not need to be created because default fonts already michael@0: // support these languages. michael@0: if (aWithFonts && support != FcLangDifferentLang && support == best) { michael@0: fonts.AppendElement(font); michael@0: } michael@0: } michael@0: michael@0: entry->mSupport = best; michael@0: if (aWithFonts) { michael@0: if (fonts.Length() != 0) { michael@0: entry->mFonts.AppendElements(fonts.Elements(), fonts.Length()); michael@0: } else if (best != FcLangDifferentLang) { michael@0: // Previously there was a font that supported this language at the michael@0: // level of entry->mSupport, but it has now disappeared. At least michael@0: // entry->mSupport needs to be recalculated, but this is an michael@0: // indication that the set of installed fonts has changed, so michael@0: // update all caches. michael@0: mLastConfig = nullptr; // invalidates caches michael@0: UpdateFontListInternal(true); michael@0: return GetLangSupportEntry(aLang, aWithFonts); michael@0: } michael@0: } michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: FcLangResult michael@0: gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang) michael@0: { michael@0: UpdateFontListInternal(); michael@0: michael@0: LangSupportEntry *entry = GetLangSupportEntry(aLang, false); michael@0: if (!entry) michael@0: return FcLangEqual; michael@0: michael@0: return entry->mSupport; michael@0: } michael@0: michael@0: const nsTArray< nsCountedRef >& michael@0: gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang) michael@0: { michael@0: LangSupportEntry *entry = GetLangSupportEntry(aLang, true); michael@0: if (!entry) michael@0: return mEmptyPatternArray; michael@0: michael@0: return entry->mFonts; michael@0: }