1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/thebes/gfxFontconfigUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1080 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/ArrayUtils.h" 1.10 + 1.11 +#include "gfxFontconfigUtils.h" 1.12 +#include "gfxFont.h" 1.13 +#include "nsGkAtoms.h" 1.14 + 1.15 +#include <locale.h> 1.16 +#include <fontconfig/fontconfig.h> 1.17 + 1.18 +#include "nsServiceManagerUtils.h" 1.19 +#include "nsILanguageAtomService.h" 1.20 +#include "nsTArray.h" 1.21 +#include "mozilla/Preferences.h" 1.22 + 1.23 +#include "nsIAtom.h" 1.24 +#include "nsCRT.h" 1.25 +#include "gfxFontConstants.h" 1.26 +#include "mozilla/gfx/2D.h" 1.27 + 1.28 +using namespace mozilla; 1.29 + 1.30 +/* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr; 1.31 +static nsILanguageAtomService* gLangService = nullptr; 1.32 + 1.33 +/* static */ void 1.34 +gfxFontconfigUtils::Shutdown() { 1.35 + if (sUtils) { 1.36 + delete sUtils; 1.37 + sUtils = nullptr; 1.38 + } 1.39 + NS_IF_RELEASE(gLangService); 1.40 +} 1.41 + 1.42 +/* static */ uint8_t 1.43 +gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant) 1.44 +{ 1.45 + switch (aFcSlant) { 1.46 + case FC_SLANT_ITALIC: 1.47 + return NS_FONT_STYLE_ITALIC; 1.48 + case FC_SLANT_OBLIQUE: 1.49 + return NS_FONT_STYLE_OBLIQUE; 1.50 + default: 1.51 + return NS_FONT_STYLE_NORMAL; 1.52 + } 1.53 +} 1.54 + 1.55 +/* static */ uint8_t 1.56 +gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern) 1.57 +{ 1.58 + int slant; 1.59 + if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) { 1.60 + return NS_FONT_STYLE_NORMAL; 1.61 + } 1.62 + 1.63 + return FcSlantToThebesStyle(slant); 1.64 +} 1.65 + 1.66 +/* static */ int 1.67 +gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle) 1.68 +{ 1.69 + if (aFontStyle.style == NS_FONT_STYLE_ITALIC) 1.70 + return FC_SLANT_ITALIC; 1.71 + if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE) 1.72 + return FC_SLANT_OBLIQUE; 1.73 + 1.74 + return FC_SLANT_ROMAN; 1.75 +} 1.76 + 1.77 +// OS/2 weight classes were introduced in fontconfig-2.1.93 (2003). 1.78 +#ifndef FC_WEIGHT_THIN 1.79 +#define FC_WEIGHT_THIN 0 // 2.1.93 1.80 +#define FC_WEIGHT_EXTRALIGHT 40 // 2.1.93 1.81 +#define FC_WEIGHT_REGULAR 80 // 2.1.93 1.82 +#define FC_WEIGHT_EXTRABOLD 205 // 2.1.93 1.83 +#endif 1.84 +// book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005) 1.85 +#ifndef FC_WEIGHT_BOOK 1.86 +#define FC_WEIGHT_BOOK 75 1.87 +#endif 1.88 +// extra black was introduced in fontconfig-2.4.91 (2007) 1.89 +#ifndef FC_WEIGHT_EXTRABLACK 1.90 +#define FC_WEIGHT_EXTRABLACK 215 1.91 +#endif 1.92 + 1.93 +/* static */ uint16_t 1.94 +gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern) 1.95 +{ 1.96 + int weight; 1.97 + if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) 1.98 + return NS_FONT_WEIGHT_NORMAL; 1.99 + 1.100 + if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) 1.101 + return 100; 1.102 + if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) 1.103 + return 200; 1.104 + if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) 1.105 + return 300; 1.106 + if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) 1.107 + // This includes FC_WEIGHT_BOOK 1.108 + return 400; 1.109 + if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) 1.110 + return 500; 1.111 + if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) 1.112 + return 600; 1.113 + if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) 1.114 + return 700; 1.115 + if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) 1.116 + return 800; 1.117 + if (weight <= FC_WEIGHT_BLACK) 1.118 + return 900; 1.119 + 1.120 + // including FC_WEIGHT_EXTRABLACK 1.121 + return 901; 1.122 +} 1.123 + 1.124 +/* static */ int 1.125 +gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight) 1.126 +{ 1.127 + NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10, 1.128 + "base weight out of range"); 1.129 + 1.130 + switch (aBaseWeight) { 1.131 + case 2: 1.132 + return FC_WEIGHT_EXTRALIGHT; 1.133 + case 3: 1.134 + return FC_WEIGHT_LIGHT; 1.135 + case 4: 1.136 + return FC_WEIGHT_REGULAR; 1.137 + case 5: 1.138 + return FC_WEIGHT_MEDIUM; 1.139 + case 6: 1.140 + return FC_WEIGHT_DEMIBOLD; 1.141 + case 7: 1.142 + return FC_WEIGHT_BOLD; 1.143 + case 8: 1.144 + return FC_WEIGHT_EXTRABOLD; 1.145 + case 9: 1.146 + return FC_WEIGHT_BLACK; 1.147 + } 1.148 + 1.149 + // extremes 1.150 + return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK; 1.151 +} 1.152 + 1.153 +/* static */ int16_t 1.154 +gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern) 1.155 +{ 1.156 + int width; 1.157 + if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) { 1.158 + return NS_FONT_STRETCH_NORMAL; 1.159 + } 1.160 + 1.161 + if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { 1.162 + return NS_FONT_STRETCH_ULTRA_CONDENSED; 1.163 + } 1.164 + if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { 1.165 + return NS_FONT_STRETCH_EXTRA_CONDENSED; 1.166 + } 1.167 + if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { 1.168 + return NS_FONT_STRETCH_CONDENSED; 1.169 + } 1.170 + if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { 1.171 + return NS_FONT_STRETCH_SEMI_CONDENSED; 1.172 + } 1.173 + if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { 1.174 + return NS_FONT_STRETCH_NORMAL; 1.175 + } 1.176 + if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { 1.177 + return NS_FONT_STRETCH_SEMI_EXPANDED; 1.178 + } 1.179 + if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { 1.180 + return NS_FONT_STRETCH_EXPANDED; 1.181 + } 1.182 + if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { 1.183 + return NS_FONT_STRETCH_EXTRA_EXPANDED; 1.184 + } 1.185 + return NS_FONT_STRETCH_ULTRA_EXPANDED; 1.186 +} 1.187 + 1.188 +/* static */ int 1.189 +gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch) 1.190 +{ 1.191 + switch (aStretch) { 1.192 + default: // this will catch "normal" (0) as well as out-of-range values 1.193 + return FC_WIDTH_NORMAL; 1.194 + case NS_FONT_STRETCH_ULTRA_CONDENSED: 1.195 + return FC_WIDTH_ULTRACONDENSED; 1.196 + case NS_FONT_STRETCH_EXTRA_CONDENSED: 1.197 + return FC_WIDTH_EXTRACONDENSED; 1.198 + case NS_FONT_STRETCH_CONDENSED: 1.199 + return FC_WIDTH_CONDENSED; 1.200 + case NS_FONT_STRETCH_SEMI_CONDENSED: 1.201 + return FC_WIDTH_SEMICONDENSED; 1.202 + case NS_FONT_STRETCH_SEMI_EXPANDED: 1.203 + return FC_WIDTH_SEMIEXPANDED; 1.204 + case NS_FONT_STRETCH_EXPANDED: 1.205 + return FC_WIDTH_EXPANDED; 1.206 + case NS_FONT_STRETCH_EXTRA_EXPANDED: 1.207 + return FC_WIDTH_EXTRAEXPANDED; 1.208 + case NS_FONT_STRETCH_ULTRA_EXPANDED: 1.209 + return FC_WIDTH_ULTRAEXPANDED; 1.210 + } 1.211 +} 1.212 + 1.213 +// This makes a guess at an FC_WEIGHT corresponding to a base weight and 1.214 +// offset (without any knowledge of which weights are available). 1.215 + 1.216 +/* static */ int 1.217 +GuessFcWeight(const gfxFontStyle& aFontStyle) 1.218 +{ 1.219 + /* 1.220 + * weights come in two parts crammed into one 1.221 + * integer -- the "base" weight is weight / 100, 1.222 + * the rest of the value is the "offset" from that 1.223 + * weight -- the number of steps to move to adjust 1.224 + * the weight in the list of supported font weights, 1.225 + * this value can be negative or positive. 1.226 + */ 1.227 + int8_t weight = aFontStyle.ComputeWeight(); 1.228 + 1.229 + // ComputeWeight trimmed the range of weights for us 1.230 + NS_ASSERTION(weight >= 0 && weight <= 10, 1.231 + "base weight out of range"); 1.232 + 1.233 + return gfxFontconfigUtils::FcWeightForBaseWeight(weight); 1.234 +} 1.235 + 1.236 +static void 1.237 +AddString(FcPattern *aPattern, const char *object, const char *aString) 1.238 +{ 1.239 + FcPatternAddString(aPattern, object, 1.240 + gfxFontconfigUtils::ToFcChar8(aString)); 1.241 +} 1.242 + 1.243 +static void 1.244 +AddWeakString(FcPattern *aPattern, const char *object, const char *aString) 1.245 +{ 1.246 + FcValue value; 1.247 + value.type = FcTypeString; 1.248 + value.u.s = gfxFontconfigUtils::ToFcChar8(aString); 1.249 + 1.250 + FcPatternAddWeak(aPattern, object, value, FcTrue); 1.251 +} 1.252 + 1.253 +static void 1.254 +AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup) 1.255 +{ 1.256 + // Translate from mozilla's internal mapping into fontconfig's 1.257 + nsAutoCString lang; 1.258 + gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang); 1.259 + 1.260 + if (!lang.IsEmpty()) { 1.261 + AddString(aPattern, FC_LANG, lang.get()); 1.262 + } 1.263 +} 1.264 + 1.265 +nsReturnRef<FcPattern> 1.266 +gfxFontconfigUtils::NewPattern(const nsTArray<nsString>& aFamilies, 1.267 + const gfxFontStyle& aFontStyle, 1.268 + const char *aLang) 1.269 +{ 1.270 + static const char* sFontconfigGenerics[] = 1.271 + { "sans-serif", "serif", "monospace", "fantasy", "cursive" }; 1.272 + 1.273 + nsAutoRef<FcPattern> pattern(FcPatternCreate()); 1.274 + if (!pattern) 1.275 + return nsReturnRef<FcPattern>(); 1.276 + 1.277 + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size); 1.278 + FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle)); 1.279 + FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle)); 1.280 + FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch)); 1.281 + 1.282 + if (aLang) { 1.283 + AddString(pattern, FC_LANG, aLang); 1.284 + } 1.285 + 1.286 + bool useWeakBinding = false; 1.287 + for (uint32_t i = 0; i < aFamilies.Length(); ++i) { 1.288 + NS_ConvertUTF16toUTF8 family(aFamilies[i]); 1.289 + if (!useWeakBinding) { 1.290 + AddString(pattern, FC_FAMILY, family.get()); 1.291 + 1.292 + // fontconfig generic families are typically implemented with weak 1.293 + // aliases (so that the preferred font depends on language). 1.294 + // However, this would give them lower priority than subsequent 1.295 + // non-generic families in the list. To ensure that subsequent 1.296 + // families do not have a higher priority, they are given weak 1.297 + // bindings. 1.298 + for (uint32_t g = 0; 1.299 + g < ArrayLength(sFontconfigGenerics); 1.300 + ++g) { 1.301 + if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]), 1.302 + ToFcChar8(family.get()))) { 1.303 + useWeakBinding = true; 1.304 + break; 1.305 + } 1.306 + } 1.307 + } else { 1.308 + AddWeakString(pattern, FC_FAMILY, family.get()); 1.309 + } 1.310 + } 1.311 + 1.312 + return pattern.out(); 1.313 +} 1.314 + 1.315 +gfxFontconfigUtils::gfxFontconfigUtils() 1.316 + : mFontsByFamily(50) 1.317 + , mFontsByFullname(50) 1.318 + , mLangSupportTable(50) 1.319 + , mLastConfig(nullptr) 1.320 +{ 1.321 + UpdateFontListInternal(); 1.322 +} 1.323 + 1.324 +nsresult 1.325 +gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup, 1.326 + const nsACString& aGenericFamily, 1.327 + nsTArray<nsString>& aListOfFonts) 1.328 +{ 1.329 + aListOfFonts.Clear(); 1.330 + 1.331 + nsTArray<nsCString> fonts; 1.332 + nsresult rv = GetFontListInternal(fonts, aLangGroup); 1.333 + if (NS_FAILED(rv)) 1.334 + return rv; 1.335 + 1.336 + for (uint32_t i = 0; i < fonts.Length(); ++i) { 1.337 + aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i])); 1.338 + } 1.339 + 1.340 + aListOfFonts.Sort(); 1.341 + 1.342 + int32_t serif = 0, sansSerif = 0, monospace = 0; 1.343 + 1.344 + // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and 1.345 + // "monospace", slightly different from CSS's 5. 1.346 + if (aGenericFamily.IsEmpty()) 1.347 + serif = sansSerif = monospace = 1; 1.348 + else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) 1.349 + serif = 1; 1.350 + else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) 1.351 + sansSerif = 1; 1.352 + else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) 1.353 + monospace = 1; 1.354 + else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || 1.355 + aGenericFamily.LowerCaseEqualsLiteral("fantasy")) 1.356 + serif = sansSerif = 1; 1.357 + else 1.358 + NS_NOTREACHED("unexpected CSS generic font family"); 1.359 + 1.360 + // The first in the list becomes the default in 1.361 + // gFontsDialog.readFontSelection() if the preference-selected font is not 1.362 + // available, so put system configured defaults first. 1.363 + if (monospace) 1.364 + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace")); 1.365 + if (sansSerif) 1.366 + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif")); 1.367 + if (serif) 1.368 + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif")); 1.369 + 1.370 + return NS_OK; 1.371 +} 1.372 + 1.373 +struct MozLangGroupData { 1.374 + nsIAtom* const& mozLangGroup; 1.375 + const char *defaultLang; 1.376 +}; 1.377 + 1.378 +const MozLangGroupData MozLangGroups[] = { 1.379 + { nsGkAtoms::x_western, "en" }, 1.380 + { nsGkAtoms::x_central_euro, "pl" }, 1.381 + { nsGkAtoms::x_cyrillic, "ru" }, 1.382 + { nsGkAtoms::x_baltic, "lv" }, 1.383 + { nsGkAtoms::x_devanagari, "hi" }, 1.384 + { nsGkAtoms::x_tamil, "ta" }, 1.385 + { nsGkAtoms::x_armn, "hy" }, 1.386 + { nsGkAtoms::x_beng, "bn" }, 1.387 + { nsGkAtoms::x_cans, "iu" }, 1.388 + { nsGkAtoms::x_ethi, "am" }, 1.389 + { nsGkAtoms::x_geor, "ka" }, 1.390 + { nsGkAtoms::x_gujr, "gu" }, 1.391 + { nsGkAtoms::x_guru, "pa" }, 1.392 + { nsGkAtoms::x_khmr, "km" }, 1.393 + { nsGkAtoms::x_knda, "kn" }, 1.394 + { nsGkAtoms::x_mlym, "ml" }, 1.395 + { nsGkAtoms::x_orya, "or" }, 1.396 + { nsGkAtoms::x_sinh, "si" }, 1.397 + { nsGkAtoms::x_telu, "te" }, 1.398 + { nsGkAtoms::x_tibt, "bo" }, 1.399 + { nsGkAtoms::Unicode, 0 }, 1.400 +}; 1.401 + 1.402 +static bool 1.403 +TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup, 1.404 + nsACString *aFcLang) 1.405 +{ 1.406 + // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'. 1.407 + // aOSLang is in the form "language[_territory][.codeset][@modifier]". 1.408 + // fontconfig takes languages in the form "language-territory". 1.409 + // nsILanguageAtomService takes languages in the form language-subtag, 1.410 + // where subtag may be a territory. fontconfig and nsILanguageAtomService 1.411 + // handle case-conversion for us. 1.412 + const char *pos, *end; 1.413 + aOSLang.BeginReading(pos); 1.414 + aOSLang.EndReading(end); 1.415 + aFcLang->Truncate(); 1.416 + while (pos < end) { 1.417 + switch (*pos) { 1.418 + case '.': 1.419 + case '@': 1.420 + end = pos; 1.421 + break; 1.422 + case '_': 1.423 + aFcLang->Append('-'); 1.424 + break; 1.425 + default: 1.426 + aFcLang->Append(*pos); 1.427 + } 1.428 + ++pos; 1.429 + } 1.430 + 1.431 + nsIAtom *atom = 1.432 + gLangService->LookupLanguage(*aFcLang); 1.433 + 1.434 + return atom == aLangGroup; 1.435 +} 1.436 + 1.437 +/* static */ void 1.438 +gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup, 1.439 + nsACString *aFcLang) 1.440 +{ 1.441 + NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL"); 1.442 + 1.443 + const MozLangGroupData *langGroup = nullptr; 1.444 + 1.445 + for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) { 1.446 + if (aLangGroup == MozLangGroups[i].mozLangGroup) { 1.447 + langGroup = &MozLangGroups[i]; 1.448 + break; 1.449 + } 1.450 + } 1.451 + 1.452 + if (!langGroup) { 1.453 + // Not a special mozilla language group. 1.454 + // Use aLangGroup as a language code. 1.455 + aLangGroup->ToUTF8String(*aFcLang); 1.456 + return; 1.457 + } 1.458 + 1.459 + // Check the environment for the users preferred language that corresponds 1.460 + // to this langGroup. 1.461 + if (!gLangService) { 1.462 + CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); 1.463 + } 1.464 + 1.465 + if (gLangService) { 1.466 + const char *languages = getenv("LANGUAGE"); 1.467 + if (languages) { 1.468 + const char separator = ':'; 1.469 + 1.470 + for (const char *pos = languages; true; ++pos) { 1.471 + if (*pos == '\0' || *pos == separator) { 1.472 + if (languages < pos && 1.473 + TryLangForGroup(Substring(languages, pos), 1.474 + aLangGroup, aFcLang)) 1.475 + return; 1.476 + 1.477 + if (*pos == '\0') 1.478 + break; 1.479 + 1.480 + languages = pos + 1; 1.481 + } 1.482 + } 1.483 + } 1.484 + const char *ctype = setlocale(LC_CTYPE, nullptr); 1.485 + if (ctype && 1.486 + TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang)) 1.487 + return; 1.488 + } 1.489 + 1.490 + if (langGroup->defaultLang) { 1.491 + aFcLang->Assign(langGroup->defaultLang); 1.492 + } else { 1.493 + aFcLang->Truncate(); 1.494 + } 1.495 +} 1.496 + 1.497 +nsresult 1.498 +gfxFontconfigUtils::GetFontListInternal(nsTArray<nsCString>& aListOfFonts, 1.499 + nsIAtom *aLangGroup) 1.500 +{ 1.501 + FcPattern *pat = nullptr; 1.502 + FcObjectSet *os = nullptr; 1.503 + FcFontSet *fs = nullptr; 1.504 + nsresult rv = NS_ERROR_FAILURE; 1.505 + 1.506 + aListOfFonts.Clear(); 1.507 + 1.508 + pat = FcPatternCreate(); 1.509 + if (!pat) 1.510 + goto end; 1.511 + 1.512 + os = FcObjectSetBuild(FC_FAMILY, nullptr); 1.513 + if (!os) 1.514 + goto end; 1.515 + 1.516 + // take the pattern and add the lang group to it 1.517 + if (aLangGroup) { 1.518 + AddLangGroup(pat, aLangGroup); 1.519 + } 1.520 + 1.521 + fs = FcFontList(nullptr, pat, os); 1.522 + if (!fs) 1.523 + goto end; 1.524 + 1.525 + for (int i = 0; i < fs->nfont; i++) { 1.526 + char *family; 1.527 + 1.528 + if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, 1.529 + (FcChar8 **) &family) != FcResultMatch) 1.530 + { 1.531 + continue; 1.532 + } 1.533 + 1.534 + // Remove duplicates... 1.535 + nsAutoCString strFamily(family); 1.536 + if (aListOfFonts.Contains(strFamily)) 1.537 + continue; 1.538 + 1.539 + aListOfFonts.AppendElement(strFamily); 1.540 + } 1.541 + 1.542 + rv = NS_OK; 1.543 + 1.544 + end: 1.545 + if (NS_FAILED(rv)) 1.546 + aListOfFonts.Clear(); 1.547 + 1.548 + if (pat) 1.549 + FcPatternDestroy(pat); 1.550 + if (os) 1.551 + FcObjectSetDestroy(os); 1.552 + if (fs) 1.553 + FcFontSetDestroy(fs); 1.554 + 1.555 + return rv; 1.556 +} 1.557 + 1.558 +nsresult 1.559 +gfxFontconfigUtils::UpdateFontList() 1.560 +{ 1.561 + return UpdateFontListInternal(true); 1.562 +} 1.563 + 1.564 +nsresult 1.565 +gfxFontconfigUtils::UpdateFontListInternal(bool aForce) 1.566 +{ 1.567 + if (!aForce) { 1.568 + // This checks periodically according to fontconfig's configured 1.569 + // <rescan> interval. 1.570 + FcInitBringUptoDate(); 1.571 + } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce 1.572 + mLastConfig = nullptr; 1.573 + FcInitReinitialize(); 1.574 + } 1.575 + 1.576 + // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config 1.577 + // before destroying the old config, so the only way that we'd miss an 1.578 + // update is if fontconfig did more than one update and the memory for the 1.579 + // most recent config happened to be at the same location as the original 1.580 + // config. 1.581 + FcConfig *currentConfig = FcConfigGetCurrent(); 1.582 + if (currentConfig == mLastConfig) 1.583 + return NS_OK; 1.584 + 1.585 + // This FcFontSet is owned by fontconfig 1.586 + FcFontSet *fontSet = FcConfigGetFonts(currentConfig, FcSetSystem); 1.587 + 1.588 + mFontsByFamily.Clear(); 1.589 + mFontsByFullname.Clear(); 1.590 + mLangSupportTable.Clear(); 1.591 + mAliasForMultiFonts.Clear(); 1.592 + 1.593 + // Record the existing font families 1.594 + for (int f = 0; f < fontSet->nfont; ++f) { 1.595 + FcPattern *font = fontSet->fonts[f]; 1.596 + 1.597 + FcChar8 *family; 1.598 + for (int v = 0; 1.599 + FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch; 1.600 + ++v) { 1.601 + FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family); 1.602 + if (entry) { 1.603 + bool added = entry->AddFont(font); 1.604 + 1.605 + if (!entry->mKey) { 1.606 + // The reference to the font pattern keeps the pointer to 1.607 + // string for the key valid. If adding the font failed 1.608 + // then the entry must be removed. 1.609 + if (added) { 1.610 + entry->mKey = family; 1.611 + } else { 1.612 + mFontsByFamily.RawRemoveEntry(entry); 1.613 + } 1.614 + } 1.615 + } 1.616 + } 1.617 + } 1.618 + 1.619 + // XXX we don't support all alias names. 1.620 + // Because if we don't check whether the given font name is alias name, 1.621 + // fontconfig converts the non existing font to sans-serif. 1.622 + // This is not good if the web page specifies font-family 1.623 + // that has Windows font name in the first. 1.624 + NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); 1.625 + nsAdoptingCString list = Preferences::GetCString("font.alias-list"); 1.626 + 1.627 + if (!list.IsEmpty()) { 1.628 + const char kComma = ','; 1.629 + const char *p, *p_end; 1.630 + list.BeginReading(p); 1.631 + list.EndReading(p_end); 1.632 + while (p < p_end) { 1.633 + while (nsCRT::IsAsciiSpace(*p)) { 1.634 + if (++p == p_end) 1.635 + break; 1.636 + } 1.637 + if (p == p_end) 1.638 + break; 1.639 + const char *start = p; 1.640 + while (++p != p_end && *p != kComma) 1.641 + /* nothing */ ; 1.642 + nsAutoCString name(Substring(start, p)); 1.643 + name.CompressWhitespace(false, true); 1.644 + mAliasForMultiFonts.AppendElement(name); 1.645 + p++; 1.646 + } 1.647 + } 1.648 + 1.649 + mLastConfig = currentConfig; 1.650 + return NS_OK; 1.651 +} 1.652 + 1.653 +nsresult 1.654 +gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) 1.655 +{ 1.656 + aFamilyName.Truncate(); 1.657 + 1.658 + // The fontconfig has generic family names in the font list. 1.659 + if (aFontName.EqualsLiteral("serif") || 1.660 + aFontName.EqualsLiteral("sans-serif") || 1.661 + aFontName.EqualsLiteral("monospace")) { 1.662 + aFamilyName.Assign(aFontName); 1.663 + return NS_OK; 1.664 + } 1.665 + 1.666 + nsresult rv = UpdateFontListInternal(); 1.667 + if (NS_FAILED(rv)) 1.668 + return rv; 1.669 + 1.670 + NS_ConvertUTF16toUTF8 fontname(aFontName); 1.671 + 1.672 + // return empty string if no such family exists 1.673 + if (!IsExistingFamily(fontname)) 1.674 + return NS_OK; 1.675 + 1.676 + FcPattern *pat = nullptr; 1.677 + FcObjectSet *os = nullptr; 1.678 + FcFontSet *givenFS = nullptr; 1.679 + nsTArray<nsCString> candidates; 1.680 + FcFontSet *candidateFS = nullptr; 1.681 + rv = NS_ERROR_FAILURE; 1.682 + 1.683 + pat = FcPatternCreate(); 1.684 + if (!pat) 1.685 + goto end; 1.686 + 1.687 + FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get()); 1.688 + 1.689 + os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr); 1.690 + if (!os) 1.691 + goto end; 1.692 + 1.693 + givenFS = FcFontList(nullptr, pat, os); 1.694 + if (!givenFS) 1.695 + goto end; 1.696 + 1.697 + // The first value associated with a FC_FAMILY property is the family 1.698 + // returned by GetFontList(), so use this value if appropriate. 1.699 + 1.700 + // See if there is a font face with first family equal to the given family. 1.701 + for (int i = 0; i < givenFS->nfont; ++i) { 1.702 + char *firstFamily; 1.703 + if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, 1.704 + (FcChar8 **) &firstFamily) != FcResultMatch) 1.705 + continue; 1.706 + 1.707 + nsDependentCString first(firstFamily); 1.708 + if (!candidates.Contains(first)) { 1.709 + candidates.AppendElement(first); 1.710 + 1.711 + if (fontname.Equals(first)) { 1.712 + aFamilyName.Assign(aFontName); 1.713 + rv = NS_OK; 1.714 + goto end; 1.715 + } 1.716 + } 1.717 + } 1.718 + 1.719 + // See if any of the first family names represent the same set of font 1.720 + // faces as the given family. 1.721 + for (uint32_t j = 0; j < candidates.Length(); ++j) { 1.722 + FcPatternDel(pat, FC_FAMILY); 1.723 + FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get()); 1.724 + 1.725 + candidateFS = FcFontList(nullptr, pat, os); 1.726 + if (!candidateFS) 1.727 + goto end; 1.728 + 1.729 + if (candidateFS->nfont != givenFS->nfont) 1.730 + continue; 1.731 + 1.732 + bool equal = true; 1.733 + for (int i = 0; i < givenFS->nfont; ++i) { 1.734 + if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { 1.735 + equal = false; 1.736 + break; 1.737 + } 1.738 + } 1.739 + if (equal) { 1.740 + AppendUTF8toUTF16(candidates[j], aFamilyName); 1.741 + rv = NS_OK; 1.742 + goto end; 1.743 + } 1.744 + } 1.745 + 1.746 + // No match found; return empty string. 1.747 + rv = NS_OK; 1.748 + 1.749 + end: 1.750 + if (pat) 1.751 + FcPatternDestroy(pat); 1.752 + if (os) 1.753 + FcObjectSetDestroy(os); 1.754 + if (givenFS) 1.755 + FcFontSetDestroy(givenFS); 1.756 + if (candidateFS) 1.757 + FcFontSetDestroy(candidateFS); 1.758 + 1.759 + return rv; 1.760 +} 1.761 + 1.762 +nsresult 1.763 +gfxFontconfigUtils::ResolveFontName(const nsAString& aFontName, 1.764 + gfxPlatform::FontResolverCallback aCallback, 1.765 + void *aClosure, 1.766 + bool& aAborted) 1.767 +{ 1.768 + aAborted = false; 1.769 + 1.770 + nsresult rv = UpdateFontListInternal(); 1.771 + if (NS_FAILED(rv)) 1.772 + return rv; 1.773 + 1.774 + NS_ConvertUTF16toUTF8 fontname(aFontName); 1.775 + // Sometimes, the font has two or more names (e.g., "Sazanami Gothic" has 1.776 + // Japanese localized name). We should not resolve to a single name 1.777 + // because different names sometimes have different behavior. e.g., with 1.778 + // the default settings of "Sazanami" on Fedora Core 5, the non-localized 1.779 + // name uses anti-alias, but the localized name uses it. So, we should 1.780 + // check just whether the font is existing, without resolving to regular 1.781 + // name. 1.782 + // 1.783 + // The family names in mAliasForMultiFonts are names understood by 1.784 + // fontconfig. The actual font to which they resolve depends on the 1.785 + // entire match pattern. That info is not available here, but there 1.786 + // will be a font so leave the resolving to the gfxFontGroup. 1.787 + if (IsExistingFamily(fontname) || 1.788 + mAliasForMultiFonts.Contains(fontname, gfxIgnoreCaseCStringComparator())) 1.789 + aAborted = !(*aCallback)(aFontName, aClosure); 1.790 + 1.791 + return NS_OK; 1.792 +} 1.793 + 1.794 +bool 1.795 +gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName) 1.796 +{ 1.797 + return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr; 1.798 +} 1.799 + 1.800 +const nsTArray< nsCountedRef<FcPattern> >& 1.801 +gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName) 1.802 +{ 1.803 + FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName); 1.804 + 1.805 + if (!entry) 1.806 + return mEmptyPatternArray; 1.807 + 1.808 + return entry->GetFonts(); 1.809 +} 1.810 + 1.811 +// Fontconfig only provides a fullname property for fonts in formats with SFNT 1.812 +// wrappers. For other font formats (including PCF and PS Type 1), a fullname 1.813 +// must be generated from the family and style properties. Only the first 1.814 +// family and style is checked, but that should be OK, as I don't expect 1.815 +// non-SFNT fonts to have multiple families or styles. 1.816 +bool 1.817 +gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont, 1.818 + nsACString *aFullname) 1.819 +{ 1.820 + FcChar8 *family; 1.821 + if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch) 1.822 + return false; 1.823 + 1.824 + aFullname->Truncate(); 1.825 + aFullname->Append(ToCString(family)); 1.826 + 1.827 + FcChar8 *style; 1.828 + if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch && 1.829 + strcmp(ToCString(style), "Regular") != 0) { 1.830 + aFullname->Append(' '); 1.831 + aFullname->Append(ToCString(style)); 1.832 + } 1.833 + 1.834 + return true; 1.835 +} 1.836 + 1.837 +bool 1.838 +gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const 1.839 +{ 1.840 + const FcChar8 *key = mKey; 1.841 + // If mKey is nullptr, key comes from the style and family of the first 1.842 + // font. 1.843 + nsAutoCString fullname; 1.844 + if (!key) { 1.845 + NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!"); 1.846 + GetFullnameFromFamilyAndStyle(mFonts[0], &fullname); 1.847 + 1.848 + key = ToFcChar8(fullname); 1.849 + } 1.850 + 1.851 + return FcStrCmpIgnoreCase(aKey, key) == 0; 1.852 +} 1.853 + 1.854 +void 1.855 +gfxFontconfigUtils::AddFullnameEntries() 1.856 +{ 1.857 + // This FcFontSet is owned by fontconfig 1.858 + FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); 1.859 + 1.860 + // Record the existing font families 1.861 + for (int f = 0; f < fontSet->nfont; ++f) { 1.862 + FcPattern *font = fontSet->fonts[f]; 1.863 + 1.864 + int v = 0; 1.865 + FcChar8 *fullname; 1.866 + while (FcPatternGetString(font, 1.867 + FC_FULLNAME, v, &fullname) == FcResultMatch) { 1.868 + FontsByFullnameEntry *entry = mFontsByFullname.PutEntry(fullname); 1.869 + if (entry) { 1.870 + // entry always has space for one font, so the first AddFont 1.871 + // will always succeed, and so the entry will always have a 1.872 + // font from which to obtain the key. 1.873 + bool added = entry->AddFont(font); 1.874 + // The key may be nullptr either if this is the first font, or 1.875 + // if the first font does not have a fullname property, and so 1.876 + // the key is obtained from the font. Set the key in both 1.877 + // cases. The check that AddFont succeeded is required for 1.878 + // the second case. 1.879 + if (!entry->mKey && added) { 1.880 + entry->mKey = fullname; 1.881 + } 1.882 + } 1.883 + 1.884 + ++v; 1.885 + } 1.886 + 1.887 + // Fontconfig does not provide a fullname property for all fonts. 1.888 + if (v == 0) { 1.889 + nsAutoCString name; 1.890 + if (!GetFullnameFromFamilyAndStyle(font, &name)) 1.891 + continue; 1.892 + 1.893 + FontsByFullnameEntry *entry = 1.894 + mFontsByFullname.PutEntry(ToFcChar8(name)); 1.895 + if (entry) { 1.896 + entry->AddFont(font); 1.897 + // Either entry->mKey has been set for a previous font or it 1.898 + // remains nullptr to indicate that the key is obtained from 1.899 + // the first font. 1.900 + } 1.901 + } 1.902 + } 1.903 +} 1.904 + 1.905 +const nsTArray< nsCountedRef<FcPattern> >& 1.906 +gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname) 1.907 +{ 1.908 + if (mFontsByFullname.Count() == 0) { 1.909 + AddFullnameEntries(); 1.910 + } 1.911 + 1.912 + FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname); 1.913 + 1.914 + if (!entry) 1.915 + return mEmptyPatternArray; 1.916 + 1.917 + return entry->GetFonts(); 1.918 +} 1.919 + 1.920 +static FcLangResult 1.921 +CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) { 1.922 + FcLangResult result = FcLangDifferentLang; 1.923 + for (uint32_t i = 0; ; ++i) { 1.924 + FcChar8 a = FcToLower(aLangA[i]); 1.925 + FcChar8 b = FcToLower(aLangB[i]); 1.926 + 1.927 + if (a != b) { 1.928 + if ((a == '\0' && b == '-') || (a == '-' && b == '\0')) 1.929 + return FcLangDifferentCountry; 1.930 + 1.931 + return result; 1.932 + } 1.933 + if (a == '\0') 1.934 + return FcLangEqual; 1.935 + 1.936 + if (a == '-') { 1.937 + result = FcLangDifferentCountry; 1.938 + } 1.939 + } 1.940 +} 1.941 + 1.942 +/* static */ 1.943 +FcLangResult 1.944 +gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang) 1.945 +{ 1.946 + // When fontconfig builds a pattern for a system font, it will set a 1.947 + // single LangSet property value for the font. That value may be removed 1.948 + // and additional string values may be added through FcConfigSubsitute 1.949 + // with FcMatchScan. Values that are neither LangSet nor string are 1.950 + // considered errors in fontconfig sort and match functions. 1.951 + // 1.952 + // If no string nor LangSet value is found, then either the font is a 1.953 + // system font and the LangSet has been removed through FcConfigSubsitute, 1.954 + // or the font is a web font and its language support is unknown. 1.955 + // Returning FcLangDifferentLang for these fonts ensures that this font 1.956 + // will not be assumed to satisfy the language, and so language will be 1.957 + // prioritized in sorting fallback fonts. 1.958 + FcValue value; 1.959 + FcLangResult best = FcLangDifferentLang; 1.960 + for (int v = 0; 1.961 + FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch; 1.962 + ++v) { 1.963 + 1.964 + FcLangResult support; 1.965 + switch (value.type) { 1.966 + case FcTypeLangSet: 1.967 + support = FcLangSetHasLang(value.u.l, aLang); 1.968 + break; 1.969 + case FcTypeString: 1.970 + support = CompareLangString(value.u.s, aLang); 1.971 + break; 1.972 + default: 1.973 + // error. continue to see if there is a useful value. 1.974 + continue; 1.975 + } 1.976 + 1.977 + if (support < best) { // lower is better 1.978 + if (support == FcLangEqual) 1.979 + return support; 1.980 + best = support; 1.981 + } 1.982 + } 1.983 + 1.984 + return best; 1.985 +} 1.986 + 1.987 +gfxFontconfigUtils::LangSupportEntry * 1.988 +gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts) 1.989 +{ 1.990 + // Currently any unrecognized languages from documents will be converted 1.991 + // to x-unicode by nsILanguageAtomService, so there is a limit on the 1.992 + // langugages that will be added here. Reconsider when/if document 1.993 + // languages are passed to this routine. 1.994 + 1.995 + LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang); 1.996 + if (!entry) 1.997 + return nullptr; 1.998 + 1.999 + FcLangResult best = FcLangDifferentLang; 1.1000 + 1.1001 + if (!entry->IsKeyInitialized()) { 1.1002 + entry->InitKey(aLang); 1.1003 + } else { 1.1004 + // mSupport is already initialized. 1.1005 + if (!aWithFonts) 1.1006 + return entry; 1.1007 + 1.1008 + best = entry->mSupport; 1.1009 + // If there is support for this language, an empty font list indicates 1.1010 + // that the list hasn't been initialized yet. 1.1011 + if (best == FcLangDifferentLang || entry->mFonts.Length() > 0) 1.1012 + return entry; 1.1013 + } 1.1014 + 1.1015 + // This FcFontSet is owned by fontconfig 1.1016 + FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); 1.1017 + 1.1018 + nsAutoTArray<FcPattern*,100> fonts; 1.1019 + 1.1020 + for (int f = 0; f < fontSet->nfont; ++f) { 1.1021 + FcPattern *font = fontSet->fonts[f]; 1.1022 + 1.1023 + FcLangResult support = GetLangSupport(font, aLang); 1.1024 + 1.1025 + if (support < best) { // lower is better 1.1026 + best = support; 1.1027 + if (aWithFonts) { 1.1028 + fonts.Clear(); 1.1029 + } else if (best == FcLangEqual) { 1.1030 + break; 1.1031 + } 1.1032 + } 1.1033 + 1.1034 + // The font list in the LangSupportEntry is expected to be used only 1.1035 + // when no default fonts support the language. There would be a large 1.1036 + // number of fonts in entries for languages using Latin script but 1.1037 + // these do not need to be created because default fonts already 1.1038 + // support these languages. 1.1039 + if (aWithFonts && support != FcLangDifferentLang && support == best) { 1.1040 + fonts.AppendElement(font); 1.1041 + } 1.1042 + } 1.1043 + 1.1044 + entry->mSupport = best; 1.1045 + if (aWithFonts) { 1.1046 + if (fonts.Length() != 0) { 1.1047 + entry->mFonts.AppendElements(fonts.Elements(), fonts.Length()); 1.1048 + } else if (best != FcLangDifferentLang) { 1.1049 + // Previously there was a font that supported this language at the 1.1050 + // level of entry->mSupport, but it has now disappeared. At least 1.1051 + // entry->mSupport needs to be recalculated, but this is an 1.1052 + // indication that the set of installed fonts has changed, so 1.1053 + // update all caches. 1.1054 + mLastConfig = nullptr; // invalidates caches 1.1055 + UpdateFontListInternal(true); 1.1056 + return GetLangSupportEntry(aLang, aWithFonts); 1.1057 + } 1.1058 + } 1.1059 + 1.1060 + return entry; 1.1061 +} 1.1062 + 1.1063 +FcLangResult 1.1064 +gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang) 1.1065 +{ 1.1066 + UpdateFontListInternal(); 1.1067 + 1.1068 + LangSupportEntry *entry = GetLangSupportEntry(aLang, false); 1.1069 + if (!entry) 1.1070 + return FcLangEqual; 1.1071 + 1.1072 + return entry->mSupport; 1.1073 +} 1.1074 + 1.1075 +const nsTArray< nsCountedRef<FcPattern> >& 1.1076 +gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang) 1.1077 +{ 1.1078 + LangSupportEntry *entry = GetLangSupportEntry(aLang, true); 1.1079 + if (!entry) 1.1080 + return mEmptyPatternArray; 1.1081 + 1.1082 + return entry->mFonts; 1.1083 +}