Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "mozilla/ArrayUtils.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "gfxFontconfigUtils.h" |
michael@0 | 9 | #include "gfxFont.h" |
michael@0 | 10 | #include "nsGkAtoms.h" |
michael@0 | 11 | |
michael@0 | 12 | #include <locale.h> |
michael@0 | 13 | #include <fontconfig/fontconfig.h> |
michael@0 | 14 | |
michael@0 | 15 | #include "nsServiceManagerUtils.h" |
michael@0 | 16 | #include "nsILanguageAtomService.h" |
michael@0 | 17 | #include "nsTArray.h" |
michael@0 | 18 | #include "mozilla/Preferences.h" |
michael@0 | 19 | |
michael@0 | 20 | #include "nsIAtom.h" |
michael@0 | 21 | #include "nsCRT.h" |
michael@0 | 22 | #include "gfxFontConstants.h" |
michael@0 | 23 | #include "mozilla/gfx/2D.h" |
michael@0 | 24 | |
michael@0 | 25 | using namespace mozilla; |
michael@0 | 26 | |
michael@0 | 27 | /* static */ gfxFontconfigUtils* gfxFontconfigUtils::sUtils = nullptr; |
michael@0 | 28 | static nsILanguageAtomService* gLangService = nullptr; |
michael@0 | 29 | |
michael@0 | 30 | /* static */ void |
michael@0 | 31 | gfxFontconfigUtils::Shutdown() { |
michael@0 | 32 | if (sUtils) { |
michael@0 | 33 | delete sUtils; |
michael@0 | 34 | sUtils = nullptr; |
michael@0 | 35 | } |
michael@0 | 36 | NS_IF_RELEASE(gLangService); |
michael@0 | 37 | } |
michael@0 | 38 | |
michael@0 | 39 | /* static */ uint8_t |
michael@0 | 40 | gfxFontconfigUtils::FcSlantToThebesStyle(int aFcSlant) |
michael@0 | 41 | { |
michael@0 | 42 | switch (aFcSlant) { |
michael@0 | 43 | case FC_SLANT_ITALIC: |
michael@0 | 44 | return NS_FONT_STYLE_ITALIC; |
michael@0 | 45 | case FC_SLANT_OBLIQUE: |
michael@0 | 46 | return NS_FONT_STYLE_OBLIQUE; |
michael@0 | 47 | default: |
michael@0 | 48 | return NS_FONT_STYLE_NORMAL; |
michael@0 | 49 | } |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | /* static */ uint8_t |
michael@0 | 53 | gfxFontconfigUtils::GetThebesStyle(FcPattern *aPattern) |
michael@0 | 54 | { |
michael@0 | 55 | int slant; |
michael@0 | 56 | if (FcPatternGetInteger(aPattern, FC_SLANT, 0, &slant) != FcResultMatch) { |
michael@0 | 57 | return NS_FONT_STYLE_NORMAL; |
michael@0 | 58 | } |
michael@0 | 59 | |
michael@0 | 60 | return FcSlantToThebesStyle(slant); |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | /* static */ int |
michael@0 | 64 | gfxFontconfigUtils::GetFcSlant(const gfxFontStyle& aFontStyle) |
michael@0 | 65 | { |
michael@0 | 66 | if (aFontStyle.style == NS_FONT_STYLE_ITALIC) |
michael@0 | 67 | return FC_SLANT_ITALIC; |
michael@0 | 68 | if (aFontStyle.style == NS_FONT_STYLE_OBLIQUE) |
michael@0 | 69 | return FC_SLANT_OBLIQUE; |
michael@0 | 70 | |
michael@0 | 71 | return FC_SLANT_ROMAN; |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | // OS/2 weight classes were introduced in fontconfig-2.1.93 (2003). |
michael@0 | 75 | #ifndef FC_WEIGHT_THIN |
michael@0 | 76 | #define FC_WEIGHT_THIN 0 // 2.1.93 |
michael@0 | 77 | #define FC_WEIGHT_EXTRALIGHT 40 // 2.1.93 |
michael@0 | 78 | #define FC_WEIGHT_REGULAR 80 // 2.1.93 |
michael@0 | 79 | #define FC_WEIGHT_EXTRABOLD 205 // 2.1.93 |
michael@0 | 80 | #endif |
michael@0 | 81 | // book was introduced in fontconfig-2.2.90 (and so fontconfig-2.3.0 in 2005) |
michael@0 | 82 | #ifndef FC_WEIGHT_BOOK |
michael@0 | 83 | #define FC_WEIGHT_BOOK 75 |
michael@0 | 84 | #endif |
michael@0 | 85 | // extra black was introduced in fontconfig-2.4.91 (2007) |
michael@0 | 86 | #ifndef FC_WEIGHT_EXTRABLACK |
michael@0 | 87 | #define FC_WEIGHT_EXTRABLACK 215 |
michael@0 | 88 | #endif |
michael@0 | 89 | |
michael@0 | 90 | /* static */ uint16_t |
michael@0 | 91 | gfxFontconfigUtils::GetThebesWeight(FcPattern *aPattern) |
michael@0 | 92 | { |
michael@0 | 93 | int weight; |
michael@0 | 94 | if (FcPatternGetInteger(aPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) |
michael@0 | 95 | return NS_FONT_WEIGHT_NORMAL; |
michael@0 | 96 | |
michael@0 | 97 | if (weight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) |
michael@0 | 98 | return 100; |
michael@0 | 99 | if (weight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) |
michael@0 | 100 | return 200; |
michael@0 | 101 | if (weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) |
michael@0 | 102 | return 300; |
michael@0 | 103 | if (weight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) |
michael@0 | 104 | // This includes FC_WEIGHT_BOOK |
michael@0 | 105 | return 400; |
michael@0 | 106 | if (weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) |
michael@0 | 107 | return 500; |
michael@0 | 108 | if (weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) |
michael@0 | 109 | return 600; |
michael@0 | 110 | if (weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) |
michael@0 | 111 | return 700; |
michael@0 | 112 | if (weight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) |
michael@0 | 113 | return 800; |
michael@0 | 114 | if (weight <= FC_WEIGHT_BLACK) |
michael@0 | 115 | return 900; |
michael@0 | 116 | |
michael@0 | 117 | // including FC_WEIGHT_EXTRABLACK |
michael@0 | 118 | return 901; |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | /* static */ int |
michael@0 | 122 | gfxFontconfigUtils::FcWeightForBaseWeight(int8_t aBaseWeight) |
michael@0 | 123 | { |
michael@0 | 124 | NS_PRECONDITION(aBaseWeight >= 0 && aBaseWeight <= 10, |
michael@0 | 125 | "base weight out of range"); |
michael@0 | 126 | |
michael@0 | 127 | switch (aBaseWeight) { |
michael@0 | 128 | case 2: |
michael@0 | 129 | return FC_WEIGHT_EXTRALIGHT; |
michael@0 | 130 | case 3: |
michael@0 | 131 | return FC_WEIGHT_LIGHT; |
michael@0 | 132 | case 4: |
michael@0 | 133 | return FC_WEIGHT_REGULAR; |
michael@0 | 134 | case 5: |
michael@0 | 135 | return FC_WEIGHT_MEDIUM; |
michael@0 | 136 | case 6: |
michael@0 | 137 | return FC_WEIGHT_DEMIBOLD; |
michael@0 | 138 | case 7: |
michael@0 | 139 | return FC_WEIGHT_BOLD; |
michael@0 | 140 | case 8: |
michael@0 | 141 | return FC_WEIGHT_EXTRABOLD; |
michael@0 | 142 | case 9: |
michael@0 | 143 | return FC_WEIGHT_BLACK; |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | // extremes |
michael@0 | 147 | return aBaseWeight < 2 ? FC_WEIGHT_THIN : FC_WEIGHT_EXTRABLACK; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | /* static */ int16_t |
michael@0 | 151 | gfxFontconfigUtils::GetThebesStretch(FcPattern *aPattern) |
michael@0 | 152 | { |
michael@0 | 153 | int width; |
michael@0 | 154 | if (FcPatternGetInteger(aPattern, FC_WIDTH, 0, &width) != FcResultMatch) { |
michael@0 | 155 | return NS_FONT_STRETCH_NORMAL; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | if (width <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { |
michael@0 | 159 | return NS_FONT_STRETCH_ULTRA_CONDENSED; |
michael@0 | 160 | } |
michael@0 | 161 | if (width <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { |
michael@0 | 162 | return NS_FONT_STRETCH_EXTRA_CONDENSED; |
michael@0 | 163 | } |
michael@0 | 164 | if (width <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { |
michael@0 | 165 | return NS_FONT_STRETCH_CONDENSED; |
michael@0 | 166 | } |
michael@0 | 167 | if (width <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { |
michael@0 | 168 | return NS_FONT_STRETCH_SEMI_CONDENSED; |
michael@0 | 169 | } |
michael@0 | 170 | if (width <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { |
michael@0 | 171 | return NS_FONT_STRETCH_NORMAL; |
michael@0 | 172 | } |
michael@0 | 173 | if (width <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { |
michael@0 | 174 | return NS_FONT_STRETCH_SEMI_EXPANDED; |
michael@0 | 175 | } |
michael@0 | 176 | if (width <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { |
michael@0 | 177 | return NS_FONT_STRETCH_EXPANDED; |
michael@0 | 178 | } |
michael@0 | 179 | if (width <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { |
michael@0 | 180 | return NS_FONT_STRETCH_EXTRA_EXPANDED; |
michael@0 | 181 | } |
michael@0 | 182 | return NS_FONT_STRETCH_ULTRA_EXPANDED; |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | /* static */ int |
michael@0 | 186 | gfxFontconfigUtils::FcWidthForThebesStretch(int16_t aStretch) |
michael@0 | 187 | { |
michael@0 | 188 | switch (aStretch) { |
michael@0 | 189 | default: // this will catch "normal" (0) as well as out-of-range values |
michael@0 | 190 | return FC_WIDTH_NORMAL; |
michael@0 | 191 | case NS_FONT_STRETCH_ULTRA_CONDENSED: |
michael@0 | 192 | return FC_WIDTH_ULTRACONDENSED; |
michael@0 | 193 | case NS_FONT_STRETCH_EXTRA_CONDENSED: |
michael@0 | 194 | return FC_WIDTH_EXTRACONDENSED; |
michael@0 | 195 | case NS_FONT_STRETCH_CONDENSED: |
michael@0 | 196 | return FC_WIDTH_CONDENSED; |
michael@0 | 197 | case NS_FONT_STRETCH_SEMI_CONDENSED: |
michael@0 | 198 | return FC_WIDTH_SEMICONDENSED; |
michael@0 | 199 | case NS_FONT_STRETCH_SEMI_EXPANDED: |
michael@0 | 200 | return FC_WIDTH_SEMIEXPANDED; |
michael@0 | 201 | case NS_FONT_STRETCH_EXPANDED: |
michael@0 | 202 | return FC_WIDTH_EXPANDED; |
michael@0 | 203 | case NS_FONT_STRETCH_EXTRA_EXPANDED: |
michael@0 | 204 | return FC_WIDTH_EXTRAEXPANDED; |
michael@0 | 205 | case NS_FONT_STRETCH_ULTRA_EXPANDED: |
michael@0 | 206 | return FC_WIDTH_ULTRAEXPANDED; |
michael@0 | 207 | } |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | // This makes a guess at an FC_WEIGHT corresponding to a base weight and |
michael@0 | 211 | // offset (without any knowledge of which weights are available). |
michael@0 | 212 | |
michael@0 | 213 | /* static */ int |
michael@0 | 214 | GuessFcWeight(const gfxFontStyle& aFontStyle) |
michael@0 | 215 | { |
michael@0 | 216 | /* |
michael@0 | 217 | * weights come in two parts crammed into one |
michael@0 | 218 | * integer -- the "base" weight is weight / 100, |
michael@0 | 219 | * the rest of the value is the "offset" from that |
michael@0 | 220 | * weight -- the number of steps to move to adjust |
michael@0 | 221 | * the weight in the list of supported font weights, |
michael@0 | 222 | * this value can be negative or positive. |
michael@0 | 223 | */ |
michael@0 | 224 | int8_t weight = aFontStyle.ComputeWeight(); |
michael@0 | 225 | |
michael@0 | 226 | // ComputeWeight trimmed the range of weights for us |
michael@0 | 227 | NS_ASSERTION(weight >= 0 && weight <= 10, |
michael@0 | 228 | "base weight out of range"); |
michael@0 | 229 | |
michael@0 | 230 | return gfxFontconfigUtils::FcWeightForBaseWeight(weight); |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | static void |
michael@0 | 234 | AddString(FcPattern *aPattern, const char *object, const char *aString) |
michael@0 | 235 | { |
michael@0 | 236 | FcPatternAddString(aPattern, object, |
michael@0 | 237 | gfxFontconfigUtils::ToFcChar8(aString)); |
michael@0 | 238 | } |
michael@0 | 239 | |
michael@0 | 240 | static void |
michael@0 | 241 | AddWeakString(FcPattern *aPattern, const char *object, const char *aString) |
michael@0 | 242 | { |
michael@0 | 243 | FcValue value; |
michael@0 | 244 | value.type = FcTypeString; |
michael@0 | 245 | value.u.s = gfxFontconfigUtils::ToFcChar8(aString); |
michael@0 | 246 | |
michael@0 | 247 | FcPatternAddWeak(aPattern, object, value, FcTrue); |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | static void |
michael@0 | 251 | AddLangGroup(FcPattern *aPattern, nsIAtom *aLangGroup) |
michael@0 | 252 | { |
michael@0 | 253 | // Translate from mozilla's internal mapping into fontconfig's |
michael@0 | 254 | nsAutoCString lang; |
michael@0 | 255 | gfxFontconfigUtils::GetSampleLangForGroup(aLangGroup, &lang); |
michael@0 | 256 | |
michael@0 | 257 | if (!lang.IsEmpty()) { |
michael@0 | 258 | AddString(aPattern, FC_LANG, lang.get()); |
michael@0 | 259 | } |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | nsReturnRef<FcPattern> |
michael@0 | 263 | gfxFontconfigUtils::NewPattern(const nsTArray<nsString>& aFamilies, |
michael@0 | 264 | const gfxFontStyle& aFontStyle, |
michael@0 | 265 | const char *aLang) |
michael@0 | 266 | { |
michael@0 | 267 | static const char* sFontconfigGenerics[] = |
michael@0 | 268 | { "sans-serif", "serif", "monospace", "fantasy", "cursive" }; |
michael@0 | 269 | |
michael@0 | 270 | nsAutoRef<FcPattern> pattern(FcPatternCreate()); |
michael@0 | 271 | if (!pattern) |
michael@0 | 272 | return nsReturnRef<FcPattern>(); |
michael@0 | 273 | |
michael@0 | 274 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aFontStyle.size); |
michael@0 | 275 | FcPatternAddInteger(pattern, FC_SLANT, GetFcSlant(aFontStyle)); |
michael@0 | 276 | FcPatternAddInteger(pattern, FC_WEIGHT, GuessFcWeight(aFontStyle)); |
michael@0 | 277 | FcPatternAddInteger(pattern, FC_WIDTH, FcWidthForThebesStretch(aFontStyle.stretch)); |
michael@0 | 278 | |
michael@0 | 279 | if (aLang) { |
michael@0 | 280 | AddString(pattern, FC_LANG, aLang); |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | bool useWeakBinding = false; |
michael@0 | 284 | for (uint32_t i = 0; i < aFamilies.Length(); ++i) { |
michael@0 | 285 | NS_ConvertUTF16toUTF8 family(aFamilies[i]); |
michael@0 | 286 | if (!useWeakBinding) { |
michael@0 | 287 | AddString(pattern, FC_FAMILY, family.get()); |
michael@0 | 288 | |
michael@0 | 289 | // fontconfig generic families are typically implemented with weak |
michael@0 | 290 | // aliases (so that the preferred font depends on language). |
michael@0 | 291 | // However, this would give them lower priority than subsequent |
michael@0 | 292 | // non-generic families in the list. To ensure that subsequent |
michael@0 | 293 | // families do not have a higher priority, they are given weak |
michael@0 | 294 | // bindings. |
michael@0 | 295 | for (uint32_t g = 0; |
michael@0 | 296 | g < ArrayLength(sFontconfigGenerics); |
michael@0 | 297 | ++g) { |
michael@0 | 298 | if (0 == FcStrCmpIgnoreCase(ToFcChar8(sFontconfigGenerics[g]), |
michael@0 | 299 | ToFcChar8(family.get()))) { |
michael@0 | 300 | useWeakBinding = true; |
michael@0 | 301 | break; |
michael@0 | 302 | } |
michael@0 | 303 | } |
michael@0 | 304 | } else { |
michael@0 | 305 | AddWeakString(pattern, FC_FAMILY, family.get()); |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | return pattern.out(); |
michael@0 | 310 | } |
michael@0 | 311 | |
michael@0 | 312 | gfxFontconfigUtils::gfxFontconfigUtils() |
michael@0 | 313 | : mFontsByFamily(50) |
michael@0 | 314 | , mFontsByFullname(50) |
michael@0 | 315 | , mLangSupportTable(50) |
michael@0 | 316 | , mLastConfig(nullptr) |
michael@0 | 317 | { |
michael@0 | 318 | UpdateFontListInternal(); |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | nsresult |
michael@0 | 322 | gfxFontconfigUtils::GetFontList(nsIAtom *aLangGroup, |
michael@0 | 323 | const nsACString& aGenericFamily, |
michael@0 | 324 | nsTArray<nsString>& aListOfFonts) |
michael@0 | 325 | { |
michael@0 | 326 | aListOfFonts.Clear(); |
michael@0 | 327 | |
michael@0 | 328 | nsTArray<nsCString> fonts; |
michael@0 | 329 | nsresult rv = GetFontListInternal(fonts, aLangGroup); |
michael@0 | 330 | if (NS_FAILED(rv)) |
michael@0 | 331 | return rv; |
michael@0 | 332 | |
michael@0 | 333 | for (uint32_t i = 0; i < fonts.Length(); ++i) { |
michael@0 | 334 | aListOfFonts.AppendElement(NS_ConvertUTF8toUTF16(fonts[i])); |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | aListOfFonts.Sort(); |
michael@0 | 338 | |
michael@0 | 339 | int32_t serif = 0, sansSerif = 0, monospace = 0; |
michael@0 | 340 | |
michael@0 | 341 | // Fontconfig supports 3 generic fonts, "serif", "sans-serif", and |
michael@0 | 342 | // "monospace", slightly different from CSS's 5. |
michael@0 | 343 | if (aGenericFamily.IsEmpty()) |
michael@0 | 344 | serif = sansSerif = monospace = 1; |
michael@0 | 345 | else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) |
michael@0 | 346 | serif = 1; |
michael@0 | 347 | else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) |
michael@0 | 348 | sansSerif = 1; |
michael@0 | 349 | else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) |
michael@0 | 350 | monospace = 1; |
michael@0 | 351 | else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || |
michael@0 | 352 | aGenericFamily.LowerCaseEqualsLiteral("fantasy")) |
michael@0 | 353 | serif = sansSerif = 1; |
michael@0 | 354 | else |
michael@0 | 355 | NS_NOTREACHED("unexpected CSS generic font family"); |
michael@0 | 356 | |
michael@0 | 357 | // The first in the list becomes the default in |
michael@0 | 358 | // gFontsDialog.readFontSelection() if the preference-selected font is not |
michael@0 | 359 | // available, so put system configured defaults first. |
michael@0 | 360 | if (monospace) |
michael@0 | 361 | aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace")); |
michael@0 | 362 | if (sansSerif) |
michael@0 | 363 | aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif")); |
michael@0 | 364 | if (serif) |
michael@0 | 365 | aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif")); |
michael@0 | 366 | |
michael@0 | 367 | return NS_OK; |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | struct MozLangGroupData { |
michael@0 | 371 | nsIAtom* const& mozLangGroup; |
michael@0 | 372 | const char *defaultLang; |
michael@0 | 373 | }; |
michael@0 | 374 | |
michael@0 | 375 | const MozLangGroupData MozLangGroups[] = { |
michael@0 | 376 | { nsGkAtoms::x_western, "en" }, |
michael@0 | 377 | { nsGkAtoms::x_central_euro, "pl" }, |
michael@0 | 378 | { nsGkAtoms::x_cyrillic, "ru" }, |
michael@0 | 379 | { nsGkAtoms::x_baltic, "lv" }, |
michael@0 | 380 | { nsGkAtoms::x_devanagari, "hi" }, |
michael@0 | 381 | { nsGkAtoms::x_tamil, "ta" }, |
michael@0 | 382 | { nsGkAtoms::x_armn, "hy" }, |
michael@0 | 383 | { nsGkAtoms::x_beng, "bn" }, |
michael@0 | 384 | { nsGkAtoms::x_cans, "iu" }, |
michael@0 | 385 | { nsGkAtoms::x_ethi, "am" }, |
michael@0 | 386 | { nsGkAtoms::x_geor, "ka" }, |
michael@0 | 387 | { nsGkAtoms::x_gujr, "gu" }, |
michael@0 | 388 | { nsGkAtoms::x_guru, "pa" }, |
michael@0 | 389 | { nsGkAtoms::x_khmr, "km" }, |
michael@0 | 390 | { nsGkAtoms::x_knda, "kn" }, |
michael@0 | 391 | { nsGkAtoms::x_mlym, "ml" }, |
michael@0 | 392 | { nsGkAtoms::x_orya, "or" }, |
michael@0 | 393 | { nsGkAtoms::x_sinh, "si" }, |
michael@0 | 394 | { nsGkAtoms::x_telu, "te" }, |
michael@0 | 395 | { nsGkAtoms::x_tibt, "bo" }, |
michael@0 | 396 | { nsGkAtoms::Unicode, 0 }, |
michael@0 | 397 | }; |
michael@0 | 398 | |
michael@0 | 399 | static bool |
michael@0 | 400 | TryLangForGroup(const nsACString& aOSLang, nsIAtom *aLangGroup, |
michael@0 | 401 | nsACString *aFcLang) |
michael@0 | 402 | { |
michael@0 | 403 | // Truncate at '.' or '@' from aOSLang, and convert '_' to '-'. |
michael@0 | 404 | // aOSLang is in the form "language[_territory][.codeset][@modifier]". |
michael@0 | 405 | // fontconfig takes languages in the form "language-territory". |
michael@0 | 406 | // nsILanguageAtomService takes languages in the form language-subtag, |
michael@0 | 407 | // where subtag may be a territory. fontconfig and nsILanguageAtomService |
michael@0 | 408 | // handle case-conversion for us. |
michael@0 | 409 | const char *pos, *end; |
michael@0 | 410 | aOSLang.BeginReading(pos); |
michael@0 | 411 | aOSLang.EndReading(end); |
michael@0 | 412 | aFcLang->Truncate(); |
michael@0 | 413 | while (pos < end) { |
michael@0 | 414 | switch (*pos) { |
michael@0 | 415 | case '.': |
michael@0 | 416 | case '@': |
michael@0 | 417 | end = pos; |
michael@0 | 418 | break; |
michael@0 | 419 | case '_': |
michael@0 | 420 | aFcLang->Append('-'); |
michael@0 | 421 | break; |
michael@0 | 422 | default: |
michael@0 | 423 | aFcLang->Append(*pos); |
michael@0 | 424 | } |
michael@0 | 425 | ++pos; |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | nsIAtom *atom = |
michael@0 | 429 | gLangService->LookupLanguage(*aFcLang); |
michael@0 | 430 | |
michael@0 | 431 | return atom == aLangGroup; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | /* static */ void |
michael@0 | 435 | gfxFontconfigUtils::GetSampleLangForGroup(nsIAtom *aLangGroup, |
michael@0 | 436 | nsACString *aFcLang) |
michael@0 | 437 | { |
michael@0 | 438 | NS_PRECONDITION(aFcLang != nullptr, "aFcLang must not be NULL"); |
michael@0 | 439 | |
michael@0 | 440 | const MozLangGroupData *langGroup = nullptr; |
michael@0 | 441 | |
michael@0 | 442 | for (unsigned int i = 0; i < ArrayLength(MozLangGroups); ++i) { |
michael@0 | 443 | if (aLangGroup == MozLangGroups[i].mozLangGroup) { |
michael@0 | 444 | langGroup = &MozLangGroups[i]; |
michael@0 | 445 | break; |
michael@0 | 446 | } |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | if (!langGroup) { |
michael@0 | 450 | // Not a special mozilla language group. |
michael@0 | 451 | // Use aLangGroup as a language code. |
michael@0 | 452 | aLangGroup->ToUTF8String(*aFcLang); |
michael@0 | 453 | return; |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | // Check the environment for the users preferred language that corresponds |
michael@0 | 457 | // to this langGroup. |
michael@0 | 458 | if (!gLangService) { |
michael@0 | 459 | CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | if (gLangService) { |
michael@0 | 463 | const char *languages = getenv("LANGUAGE"); |
michael@0 | 464 | if (languages) { |
michael@0 | 465 | const char separator = ':'; |
michael@0 | 466 | |
michael@0 | 467 | for (const char *pos = languages; true; ++pos) { |
michael@0 | 468 | if (*pos == '\0' || *pos == separator) { |
michael@0 | 469 | if (languages < pos && |
michael@0 | 470 | TryLangForGroup(Substring(languages, pos), |
michael@0 | 471 | aLangGroup, aFcLang)) |
michael@0 | 472 | return; |
michael@0 | 473 | |
michael@0 | 474 | if (*pos == '\0') |
michael@0 | 475 | break; |
michael@0 | 476 | |
michael@0 | 477 | languages = pos + 1; |
michael@0 | 478 | } |
michael@0 | 479 | } |
michael@0 | 480 | } |
michael@0 | 481 | const char *ctype = setlocale(LC_CTYPE, nullptr); |
michael@0 | 482 | if (ctype && |
michael@0 | 483 | TryLangForGroup(nsDependentCString(ctype), aLangGroup, aFcLang)) |
michael@0 | 484 | return; |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | if (langGroup->defaultLang) { |
michael@0 | 488 | aFcLang->Assign(langGroup->defaultLang); |
michael@0 | 489 | } else { |
michael@0 | 490 | aFcLang->Truncate(); |
michael@0 | 491 | } |
michael@0 | 492 | } |
michael@0 | 493 | |
michael@0 | 494 | nsresult |
michael@0 | 495 | gfxFontconfigUtils::GetFontListInternal(nsTArray<nsCString>& aListOfFonts, |
michael@0 | 496 | nsIAtom *aLangGroup) |
michael@0 | 497 | { |
michael@0 | 498 | FcPattern *pat = nullptr; |
michael@0 | 499 | FcObjectSet *os = nullptr; |
michael@0 | 500 | FcFontSet *fs = nullptr; |
michael@0 | 501 | nsresult rv = NS_ERROR_FAILURE; |
michael@0 | 502 | |
michael@0 | 503 | aListOfFonts.Clear(); |
michael@0 | 504 | |
michael@0 | 505 | pat = FcPatternCreate(); |
michael@0 | 506 | if (!pat) |
michael@0 | 507 | goto end; |
michael@0 | 508 | |
michael@0 | 509 | os = FcObjectSetBuild(FC_FAMILY, nullptr); |
michael@0 | 510 | if (!os) |
michael@0 | 511 | goto end; |
michael@0 | 512 | |
michael@0 | 513 | // take the pattern and add the lang group to it |
michael@0 | 514 | if (aLangGroup) { |
michael@0 | 515 | AddLangGroup(pat, aLangGroup); |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | fs = FcFontList(nullptr, pat, os); |
michael@0 | 519 | if (!fs) |
michael@0 | 520 | goto end; |
michael@0 | 521 | |
michael@0 | 522 | for (int i = 0; i < fs->nfont; i++) { |
michael@0 | 523 | char *family; |
michael@0 | 524 | |
michael@0 | 525 | if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, |
michael@0 | 526 | (FcChar8 **) &family) != FcResultMatch) |
michael@0 | 527 | { |
michael@0 | 528 | continue; |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | // Remove duplicates... |
michael@0 | 532 | nsAutoCString strFamily(family); |
michael@0 | 533 | if (aListOfFonts.Contains(strFamily)) |
michael@0 | 534 | continue; |
michael@0 | 535 | |
michael@0 | 536 | aListOfFonts.AppendElement(strFamily); |
michael@0 | 537 | } |
michael@0 | 538 | |
michael@0 | 539 | rv = NS_OK; |
michael@0 | 540 | |
michael@0 | 541 | end: |
michael@0 | 542 | if (NS_FAILED(rv)) |
michael@0 | 543 | aListOfFonts.Clear(); |
michael@0 | 544 | |
michael@0 | 545 | if (pat) |
michael@0 | 546 | FcPatternDestroy(pat); |
michael@0 | 547 | if (os) |
michael@0 | 548 | FcObjectSetDestroy(os); |
michael@0 | 549 | if (fs) |
michael@0 | 550 | FcFontSetDestroy(fs); |
michael@0 | 551 | |
michael@0 | 552 | return rv; |
michael@0 | 553 | } |
michael@0 | 554 | |
michael@0 | 555 | nsresult |
michael@0 | 556 | gfxFontconfigUtils::UpdateFontList() |
michael@0 | 557 | { |
michael@0 | 558 | return UpdateFontListInternal(true); |
michael@0 | 559 | } |
michael@0 | 560 | |
michael@0 | 561 | nsresult |
michael@0 | 562 | gfxFontconfigUtils::UpdateFontListInternal(bool aForce) |
michael@0 | 563 | { |
michael@0 | 564 | if (!aForce) { |
michael@0 | 565 | // This checks periodically according to fontconfig's configured |
michael@0 | 566 | // <rescan> interval. |
michael@0 | 567 | FcInitBringUptoDate(); |
michael@0 | 568 | } else if (!FcConfigUptoDate(nullptr)) { // check now with aForce |
michael@0 | 569 | mLastConfig = nullptr; |
michael@0 | 570 | FcInitReinitialize(); |
michael@0 | 571 | } |
michael@0 | 572 | |
michael@0 | 573 | // FcInitReinitialize() (used by FcInitBringUptoDate) creates a new config |
michael@0 | 574 | // before destroying the old config, so the only way that we'd miss an |
michael@0 | 575 | // update is if fontconfig did more than one update and the memory for the |
michael@0 | 576 | // most recent config happened to be at the same location as the original |
michael@0 | 577 | // config. |
michael@0 | 578 | FcConfig *currentConfig = FcConfigGetCurrent(); |
michael@0 | 579 | if (currentConfig == mLastConfig) |
michael@0 | 580 | return NS_OK; |
michael@0 | 581 | |
michael@0 | 582 | // This FcFontSet is owned by fontconfig |
michael@0 | 583 | FcFontSet *fontSet = FcConfigGetFonts(currentConfig, FcSetSystem); |
michael@0 | 584 | |
michael@0 | 585 | mFontsByFamily.Clear(); |
michael@0 | 586 | mFontsByFullname.Clear(); |
michael@0 | 587 | mLangSupportTable.Clear(); |
michael@0 | 588 | mAliasForMultiFonts.Clear(); |
michael@0 | 589 | |
michael@0 | 590 | // Record the existing font families |
michael@0 | 591 | for (int f = 0; f < fontSet->nfont; ++f) { |
michael@0 | 592 | FcPattern *font = fontSet->fonts[f]; |
michael@0 | 593 | |
michael@0 | 594 | FcChar8 *family; |
michael@0 | 595 | for (int v = 0; |
michael@0 | 596 | FcPatternGetString(font, FC_FAMILY, v, &family) == FcResultMatch; |
michael@0 | 597 | ++v) { |
michael@0 | 598 | FontsByFcStrEntry *entry = mFontsByFamily.PutEntry(family); |
michael@0 | 599 | if (entry) { |
michael@0 | 600 | bool added = entry->AddFont(font); |
michael@0 | 601 | |
michael@0 | 602 | if (!entry->mKey) { |
michael@0 | 603 | // The reference to the font pattern keeps the pointer to |
michael@0 | 604 | // string for the key valid. If adding the font failed |
michael@0 | 605 | // then the entry must be removed. |
michael@0 | 606 | if (added) { |
michael@0 | 607 | entry->mKey = family; |
michael@0 | 608 | } else { |
michael@0 | 609 | mFontsByFamily.RawRemoveEntry(entry); |
michael@0 | 610 | } |
michael@0 | 611 | } |
michael@0 | 612 | } |
michael@0 | 613 | } |
michael@0 | 614 | } |
michael@0 | 615 | |
michael@0 | 616 | // XXX we don't support all alias names. |
michael@0 | 617 | // Because if we don't check whether the given font name is alias name, |
michael@0 | 618 | // fontconfig converts the non existing font to sans-serif. |
michael@0 | 619 | // This is not good if the web page specifies font-family |
michael@0 | 620 | // that has Windows font name in the first. |
michael@0 | 621 | NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); |
michael@0 | 622 | nsAdoptingCString list = Preferences::GetCString("font.alias-list"); |
michael@0 | 623 | |
michael@0 | 624 | if (!list.IsEmpty()) { |
michael@0 | 625 | const char kComma = ','; |
michael@0 | 626 | const char *p, *p_end; |
michael@0 | 627 | list.BeginReading(p); |
michael@0 | 628 | list.EndReading(p_end); |
michael@0 | 629 | while (p < p_end) { |
michael@0 | 630 | while (nsCRT::IsAsciiSpace(*p)) { |
michael@0 | 631 | if (++p == p_end) |
michael@0 | 632 | break; |
michael@0 | 633 | } |
michael@0 | 634 | if (p == p_end) |
michael@0 | 635 | break; |
michael@0 | 636 | const char *start = p; |
michael@0 | 637 | while (++p != p_end && *p != kComma) |
michael@0 | 638 | /* nothing */ ; |
michael@0 | 639 | nsAutoCString name(Substring(start, p)); |
michael@0 | 640 | name.CompressWhitespace(false, true); |
michael@0 | 641 | mAliasForMultiFonts.AppendElement(name); |
michael@0 | 642 | p++; |
michael@0 | 643 | } |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | mLastConfig = currentConfig; |
michael@0 | 647 | return NS_OK; |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | nsresult |
michael@0 | 651 | gfxFontconfigUtils::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) |
michael@0 | 652 | { |
michael@0 | 653 | aFamilyName.Truncate(); |
michael@0 | 654 | |
michael@0 | 655 | // The fontconfig has generic family names in the font list. |
michael@0 | 656 | if (aFontName.EqualsLiteral("serif") || |
michael@0 | 657 | aFontName.EqualsLiteral("sans-serif") || |
michael@0 | 658 | aFontName.EqualsLiteral("monospace")) { |
michael@0 | 659 | aFamilyName.Assign(aFontName); |
michael@0 | 660 | return NS_OK; |
michael@0 | 661 | } |
michael@0 | 662 | |
michael@0 | 663 | nsresult rv = UpdateFontListInternal(); |
michael@0 | 664 | if (NS_FAILED(rv)) |
michael@0 | 665 | return rv; |
michael@0 | 666 | |
michael@0 | 667 | NS_ConvertUTF16toUTF8 fontname(aFontName); |
michael@0 | 668 | |
michael@0 | 669 | // return empty string if no such family exists |
michael@0 | 670 | if (!IsExistingFamily(fontname)) |
michael@0 | 671 | return NS_OK; |
michael@0 | 672 | |
michael@0 | 673 | FcPattern *pat = nullptr; |
michael@0 | 674 | FcObjectSet *os = nullptr; |
michael@0 | 675 | FcFontSet *givenFS = nullptr; |
michael@0 | 676 | nsTArray<nsCString> candidates; |
michael@0 | 677 | FcFontSet *candidateFS = nullptr; |
michael@0 | 678 | rv = NS_ERROR_FAILURE; |
michael@0 | 679 | |
michael@0 | 680 | pat = FcPatternCreate(); |
michael@0 | 681 | if (!pat) |
michael@0 | 682 | goto end; |
michael@0 | 683 | |
michael@0 | 684 | FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)fontname.get()); |
michael@0 | 685 | |
michael@0 | 686 | os = FcObjectSetBuild(FC_FAMILY, FC_FILE, FC_INDEX, nullptr); |
michael@0 | 687 | if (!os) |
michael@0 | 688 | goto end; |
michael@0 | 689 | |
michael@0 | 690 | givenFS = FcFontList(nullptr, pat, os); |
michael@0 | 691 | if (!givenFS) |
michael@0 | 692 | goto end; |
michael@0 | 693 | |
michael@0 | 694 | // The first value associated with a FC_FAMILY property is the family |
michael@0 | 695 | // returned by GetFontList(), so use this value if appropriate. |
michael@0 | 696 | |
michael@0 | 697 | // See if there is a font face with first family equal to the given family. |
michael@0 | 698 | for (int i = 0; i < givenFS->nfont; ++i) { |
michael@0 | 699 | char *firstFamily; |
michael@0 | 700 | if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, |
michael@0 | 701 | (FcChar8 **) &firstFamily) != FcResultMatch) |
michael@0 | 702 | continue; |
michael@0 | 703 | |
michael@0 | 704 | nsDependentCString first(firstFamily); |
michael@0 | 705 | if (!candidates.Contains(first)) { |
michael@0 | 706 | candidates.AppendElement(first); |
michael@0 | 707 | |
michael@0 | 708 | if (fontname.Equals(first)) { |
michael@0 | 709 | aFamilyName.Assign(aFontName); |
michael@0 | 710 | rv = NS_OK; |
michael@0 | 711 | goto end; |
michael@0 | 712 | } |
michael@0 | 713 | } |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | // See if any of the first family names represent the same set of font |
michael@0 | 717 | // faces as the given family. |
michael@0 | 718 | for (uint32_t j = 0; j < candidates.Length(); ++j) { |
michael@0 | 719 | FcPatternDel(pat, FC_FAMILY); |
michael@0 | 720 | FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get()); |
michael@0 | 721 | |
michael@0 | 722 | candidateFS = FcFontList(nullptr, pat, os); |
michael@0 | 723 | if (!candidateFS) |
michael@0 | 724 | goto end; |
michael@0 | 725 | |
michael@0 | 726 | if (candidateFS->nfont != givenFS->nfont) |
michael@0 | 727 | continue; |
michael@0 | 728 | |
michael@0 | 729 | bool equal = true; |
michael@0 | 730 | for (int i = 0; i < givenFS->nfont; ++i) { |
michael@0 | 731 | if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { |
michael@0 | 732 | equal = false; |
michael@0 | 733 | break; |
michael@0 | 734 | } |
michael@0 | 735 | } |
michael@0 | 736 | if (equal) { |
michael@0 | 737 | AppendUTF8toUTF16(candidates[j], aFamilyName); |
michael@0 | 738 | rv = NS_OK; |
michael@0 | 739 | goto end; |
michael@0 | 740 | } |
michael@0 | 741 | } |
michael@0 | 742 | |
michael@0 | 743 | // No match found; return empty string. |
michael@0 | 744 | rv = NS_OK; |
michael@0 | 745 | |
michael@0 | 746 | end: |
michael@0 | 747 | if (pat) |
michael@0 | 748 | FcPatternDestroy(pat); |
michael@0 | 749 | if (os) |
michael@0 | 750 | FcObjectSetDestroy(os); |
michael@0 | 751 | if (givenFS) |
michael@0 | 752 | FcFontSetDestroy(givenFS); |
michael@0 | 753 | if (candidateFS) |
michael@0 | 754 | FcFontSetDestroy(candidateFS); |
michael@0 | 755 | |
michael@0 | 756 | return rv; |
michael@0 | 757 | } |
michael@0 | 758 | |
michael@0 | 759 | nsresult |
michael@0 | 760 | gfxFontconfigUtils::ResolveFontName(const nsAString& aFontName, |
michael@0 | 761 | gfxPlatform::FontResolverCallback aCallback, |
michael@0 | 762 | void *aClosure, |
michael@0 | 763 | bool& aAborted) |
michael@0 | 764 | { |
michael@0 | 765 | aAborted = false; |
michael@0 | 766 | |
michael@0 | 767 | nsresult rv = UpdateFontListInternal(); |
michael@0 | 768 | if (NS_FAILED(rv)) |
michael@0 | 769 | return rv; |
michael@0 | 770 | |
michael@0 | 771 | NS_ConvertUTF16toUTF8 fontname(aFontName); |
michael@0 | 772 | // Sometimes, the font has two or more names (e.g., "Sazanami Gothic" has |
michael@0 | 773 | // Japanese localized name). We should not resolve to a single name |
michael@0 | 774 | // because different names sometimes have different behavior. e.g., with |
michael@0 | 775 | // the default settings of "Sazanami" on Fedora Core 5, the non-localized |
michael@0 | 776 | // name uses anti-alias, but the localized name uses it. So, we should |
michael@0 | 777 | // check just whether the font is existing, without resolving to regular |
michael@0 | 778 | // name. |
michael@0 | 779 | // |
michael@0 | 780 | // The family names in mAliasForMultiFonts are names understood by |
michael@0 | 781 | // fontconfig. The actual font to which they resolve depends on the |
michael@0 | 782 | // entire match pattern. That info is not available here, but there |
michael@0 | 783 | // will be a font so leave the resolving to the gfxFontGroup. |
michael@0 | 784 | if (IsExistingFamily(fontname) || |
michael@0 | 785 | mAliasForMultiFonts.Contains(fontname, gfxIgnoreCaseCStringComparator())) |
michael@0 | 786 | aAborted = !(*aCallback)(aFontName, aClosure); |
michael@0 | 787 | |
michael@0 | 788 | return NS_OK; |
michael@0 | 789 | } |
michael@0 | 790 | |
michael@0 | 791 | bool |
michael@0 | 792 | gfxFontconfigUtils::IsExistingFamily(const nsCString& aFamilyName) |
michael@0 | 793 | { |
michael@0 | 794 | return mFontsByFamily.GetEntry(ToFcChar8(aFamilyName)) != nullptr; |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | const nsTArray< nsCountedRef<FcPattern> >& |
michael@0 | 798 | gfxFontconfigUtils::GetFontsForFamily(const FcChar8 *aFamilyName) |
michael@0 | 799 | { |
michael@0 | 800 | FontsByFcStrEntry *entry = mFontsByFamily.GetEntry(aFamilyName); |
michael@0 | 801 | |
michael@0 | 802 | if (!entry) |
michael@0 | 803 | return mEmptyPatternArray; |
michael@0 | 804 | |
michael@0 | 805 | return entry->GetFonts(); |
michael@0 | 806 | } |
michael@0 | 807 | |
michael@0 | 808 | // Fontconfig only provides a fullname property for fonts in formats with SFNT |
michael@0 | 809 | // wrappers. For other font formats (including PCF and PS Type 1), a fullname |
michael@0 | 810 | // must be generated from the family and style properties. Only the first |
michael@0 | 811 | // family and style is checked, but that should be OK, as I don't expect |
michael@0 | 812 | // non-SFNT fonts to have multiple families or styles. |
michael@0 | 813 | bool |
michael@0 | 814 | gfxFontconfigUtils::GetFullnameFromFamilyAndStyle(FcPattern *aFont, |
michael@0 | 815 | nsACString *aFullname) |
michael@0 | 816 | { |
michael@0 | 817 | FcChar8 *family; |
michael@0 | 818 | if (FcPatternGetString(aFont, FC_FAMILY, 0, &family) != FcResultMatch) |
michael@0 | 819 | return false; |
michael@0 | 820 | |
michael@0 | 821 | aFullname->Truncate(); |
michael@0 | 822 | aFullname->Append(ToCString(family)); |
michael@0 | 823 | |
michael@0 | 824 | FcChar8 *style; |
michael@0 | 825 | if (FcPatternGetString(aFont, FC_STYLE, 0, &style) == FcResultMatch && |
michael@0 | 826 | strcmp(ToCString(style), "Regular") != 0) { |
michael@0 | 827 | aFullname->Append(' '); |
michael@0 | 828 | aFullname->Append(ToCString(style)); |
michael@0 | 829 | } |
michael@0 | 830 | |
michael@0 | 831 | return true; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | bool |
michael@0 | 835 | gfxFontconfigUtils::FontsByFullnameEntry::KeyEquals(KeyTypePointer aKey) const |
michael@0 | 836 | { |
michael@0 | 837 | const FcChar8 *key = mKey; |
michael@0 | 838 | // If mKey is nullptr, key comes from the style and family of the first |
michael@0 | 839 | // font. |
michael@0 | 840 | nsAutoCString fullname; |
michael@0 | 841 | if (!key) { |
michael@0 | 842 | NS_ASSERTION(mFonts.Length(), "No font in FontsByFullnameEntry!"); |
michael@0 | 843 | GetFullnameFromFamilyAndStyle(mFonts[0], &fullname); |
michael@0 | 844 | |
michael@0 | 845 | key = ToFcChar8(fullname); |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | return FcStrCmpIgnoreCase(aKey, key) == 0; |
michael@0 | 849 | } |
michael@0 | 850 | |
michael@0 | 851 | void |
michael@0 | 852 | gfxFontconfigUtils::AddFullnameEntries() |
michael@0 | 853 | { |
michael@0 | 854 | // This FcFontSet is owned by fontconfig |
michael@0 | 855 | FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); |
michael@0 | 856 | |
michael@0 | 857 | // Record the existing font families |
michael@0 | 858 | for (int f = 0; f < fontSet->nfont; ++f) { |
michael@0 | 859 | FcPattern *font = fontSet->fonts[f]; |
michael@0 | 860 | |
michael@0 | 861 | int v = 0; |
michael@0 | 862 | FcChar8 *fullname; |
michael@0 | 863 | while (FcPatternGetString(font, |
michael@0 | 864 | FC_FULLNAME, v, &fullname) == FcResultMatch) { |
michael@0 | 865 | FontsByFullnameEntry *entry = mFontsByFullname.PutEntry(fullname); |
michael@0 | 866 | if (entry) { |
michael@0 | 867 | // entry always has space for one font, so the first AddFont |
michael@0 | 868 | // will always succeed, and so the entry will always have a |
michael@0 | 869 | // font from which to obtain the key. |
michael@0 | 870 | bool added = entry->AddFont(font); |
michael@0 | 871 | // The key may be nullptr either if this is the first font, or |
michael@0 | 872 | // if the first font does not have a fullname property, and so |
michael@0 | 873 | // the key is obtained from the font. Set the key in both |
michael@0 | 874 | // cases. The check that AddFont succeeded is required for |
michael@0 | 875 | // the second case. |
michael@0 | 876 | if (!entry->mKey && added) { |
michael@0 | 877 | entry->mKey = fullname; |
michael@0 | 878 | } |
michael@0 | 879 | } |
michael@0 | 880 | |
michael@0 | 881 | ++v; |
michael@0 | 882 | } |
michael@0 | 883 | |
michael@0 | 884 | // Fontconfig does not provide a fullname property for all fonts. |
michael@0 | 885 | if (v == 0) { |
michael@0 | 886 | nsAutoCString name; |
michael@0 | 887 | if (!GetFullnameFromFamilyAndStyle(font, &name)) |
michael@0 | 888 | continue; |
michael@0 | 889 | |
michael@0 | 890 | FontsByFullnameEntry *entry = |
michael@0 | 891 | mFontsByFullname.PutEntry(ToFcChar8(name)); |
michael@0 | 892 | if (entry) { |
michael@0 | 893 | entry->AddFont(font); |
michael@0 | 894 | // Either entry->mKey has been set for a previous font or it |
michael@0 | 895 | // remains nullptr to indicate that the key is obtained from |
michael@0 | 896 | // the first font. |
michael@0 | 897 | } |
michael@0 | 898 | } |
michael@0 | 899 | } |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | const nsTArray< nsCountedRef<FcPattern> >& |
michael@0 | 903 | gfxFontconfigUtils::GetFontsForFullname(const FcChar8 *aFullname) |
michael@0 | 904 | { |
michael@0 | 905 | if (mFontsByFullname.Count() == 0) { |
michael@0 | 906 | AddFullnameEntries(); |
michael@0 | 907 | } |
michael@0 | 908 | |
michael@0 | 909 | FontsByFullnameEntry *entry = mFontsByFullname.GetEntry(aFullname); |
michael@0 | 910 | |
michael@0 | 911 | if (!entry) |
michael@0 | 912 | return mEmptyPatternArray; |
michael@0 | 913 | |
michael@0 | 914 | return entry->GetFonts(); |
michael@0 | 915 | } |
michael@0 | 916 | |
michael@0 | 917 | static FcLangResult |
michael@0 | 918 | CompareLangString(const FcChar8 *aLangA, const FcChar8 *aLangB) { |
michael@0 | 919 | FcLangResult result = FcLangDifferentLang; |
michael@0 | 920 | for (uint32_t i = 0; ; ++i) { |
michael@0 | 921 | FcChar8 a = FcToLower(aLangA[i]); |
michael@0 | 922 | FcChar8 b = FcToLower(aLangB[i]); |
michael@0 | 923 | |
michael@0 | 924 | if (a != b) { |
michael@0 | 925 | if ((a == '\0' && b == '-') || (a == '-' && b == '\0')) |
michael@0 | 926 | return FcLangDifferentCountry; |
michael@0 | 927 | |
michael@0 | 928 | return result; |
michael@0 | 929 | } |
michael@0 | 930 | if (a == '\0') |
michael@0 | 931 | return FcLangEqual; |
michael@0 | 932 | |
michael@0 | 933 | if (a == '-') { |
michael@0 | 934 | result = FcLangDifferentCountry; |
michael@0 | 935 | } |
michael@0 | 936 | } |
michael@0 | 937 | } |
michael@0 | 938 | |
michael@0 | 939 | /* static */ |
michael@0 | 940 | FcLangResult |
michael@0 | 941 | gfxFontconfigUtils::GetLangSupport(FcPattern *aFont, const FcChar8 *aLang) |
michael@0 | 942 | { |
michael@0 | 943 | // When fontconfig builds a pattern for a system font, it will set a |
michael@0 | 944 | // single LangSet property value for the font. That value may be removed |
michael@0 | 945 | // and additional string values may be added through FcConfigSubsitute |
michael@0 | 946 | // with FcMatchScan. Values that are neither LangSet nor string are |
michael@0 | 947 | // considered errors in fontconfig sort and match functions. |
michael@0 | 948 | // |
michael@0 | 949 | // If no string nor LangSet value is found, then either the font is a |
michael@0 | 950 | // system font and the LangSet has been removed through FcConfigSubsitute, |
michael@0 | 951 | // or the font is a web font and its language support is unknown. |
michael@0 | 952 | // Returning FcLangDifferentLang for these fonts ensures that this font |
michael@0 | 953 | // will not be assumed to satisfy the language, and so language will be |
michael@0 | 954 | // prioritized in sorting fallback fonts. |
michael@0 | 955 | FcValue value; |
michael@0 | 956 | FcLangResult best = FcLangDifferentLang; |
michael@0 | 957 | for (int v = 0; |
michael@0 | 958 | FcPatternGet(aFont, FC_LANG, v, &value) == FcResultMatch; |
michael@0 | 959 | ++v) { |
michael@0 | 960 | |
michael@0 | 961 | FcLangResult support; |
michael@0 | 962 | switch (value.type) { |
michael@0 | 963 | case FcTypeLangSet: |
michael@0 | 964 | support = FcLangSetHasLang(value.u.l, aLang); |
michael@0 | 965 | break; |
michael@0 | 966 | case FcTypeString: |
michael@0 | 967 | support = CompareLangString(value.u.s, aLang); |
michael@0 | 968 | break; |
michael@0 | 969 | default: |
michael@0 | 970 | // error. continue to see if there is a useful value. |
michael@0 | 971 | continue; |
michael@0 | 972 | } |
michael@0 | 973 | |
michael@0 | 974 | if (support < best) { // lower is better |
michael@0 | 975 | if (support == FcLangEqual) |
michael@0 | 976 | return support; |
michael@0 | 977 | best = support; |
michael@0 | 978 | } |
michael@0 | 979 | } |
michael@0 | 980 | |
michael@0 | 981 | return best; |
michael@0 | 982 | } |
michael@0 | 983 | |
michael@0 | 984 | gfxFontconfigUtils::LangSupportEntry * |
michael@0 | 985 | gfxFontconfigUtils::GetLangSupportEntry(const FcChar8 *aLang, bool aWithFonts) |
michael@0 | 986 | { |
michael@0 | 987 | // Currently any unrecognized languages from documents will be converted |
michael@0 | 988 | // to x-unicode by nsILanguageAtomService, so there is a limit on the |
michael@0 | 989 | // langugages that will be added here. Reconsider when/if document |
michael@0 | 990 | // languages are passed to this routine. |
michael@0 | 991 | |
michael@0 | 992 | LangSupportEntry *entry = mLangSupportTable.PutEntry(aLang); |
michael@0 | 993 | if (!entry) |
michael@0 | 994 | return nullptr; |
michael@0 | 995 | |
michael@0 | 996 | FcLangResult best = FcLangDifferentLang; |
michael@0 | 997 | |
michael@0 | 998 | if (!entry->IsKeyInitialized()) { |
michael@0 | 999 | entry->InitKey(aLang); |
michael@0 | 1000 | } else { |
michael@0 | 1001 | // mSupport is already initialized. |
michael@0 | 1002 | if (!aWithFonts) |
michael@0 | 1003 | return entry; |
michael@0 | 1004 | |
michael@0 | 1005 | best = entry->mSupport; |
michael@0 | 1006 | // If there is support for this language, an empty font list indicates |
michael@0 | 1007 | // that the list hasn't been initialized yet. |
michael@0 | 1008 | if (best == FcLangDifferentLang || entry->mFonts.Length() > 0) |
michael@0 | 1009 | return entry; |
michael@0 | 1010 | } |
michael@0 | 1011 | |
michael@0 | 1012 | // This FcFontSet is owned by fontconfig |
michael@0 | 1013 | FcFontSet *fontSet = FcConfigGetFonts(nullptr, FcSetSystem); |
michael@0 | 1014 | |
michael@0 | 1015 | nsAutoTArray<FcPattern*,100> fonts; |
michael@0 | 1016 | |
michael@0 | 1017 | for (int f = 0; f < fontSet->nfont; ++f) { |
michael@0 | 1018 | FcPattern *font = fontSet->fonts[f]; |
michael@0 | 1019 | |
michael@0 | 1020 | FcLangResult support = GetLangSupport(font, aLang); |
michael@0 | 1021 | |
michael@0 | 1022 | if (support < best) { // lower is better |
michael@0 | 1023 | best = support; |
michael@0 | 1024 | if (aWithFonts) { |
michael@0 | 1025 | fonts.Clear(); |
michael@0 | 1026 | } else if (best == FcLangEqual) { |
michael@0 | 1027 | break; |
michael@0 | 1028 | } |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | // The font list in the LangSupportEntry is expected to be used only |
michael@0 | 1032 | // when no default fonts support the language. There would be a large |
michael@0 | 1033 | // number of fonts in entries for languages using Latin script but |
michael@0 | 1034 | // these do not need to be created because default fonts already |
michael@0 | 1035 | // support these languages. |
michael@0 | 1036 | if (aWithFonts && support != FcLangDifferentLang && support == best) { |
michael@0 | 1037 | fonts.AppendElement(font); |
michael@0 | 1038 | } |
michael@0 | 1039 | } |
michael@0 | 1040 | |
michael@0 | 1041 | entry->mSupport = best; |
michael@0 | 1042 | if (aWithFonts) { |
michael@0 | 1043 | if (fonts.Length() != 0) { |
michael@0 | 1044 | entry->mFonts.AppendElements(fonts.Elements(), fonts.Length()); |
michael@0 | 1045 | } else if (best != FcLangDifferentLang) { |
michael@0 | 1046 | // Previously there was a font that supported this language at the |
michael@0 | 1047 | // level of entry->mSupport, but it has now disappeared. At least |
michael@0 | 1048 | // entry->mSupport needs to be recalculated, but this is an |
michael@0 | 1049 | // indication that the set of installed fonts has changed, so |
michael@0 | 1050 | // update all caches. |
michael@0 | 1051 | mLastConfig = nullptr; // invalidates caches |
michael@0 | 1052 | UpdateFontListInternal(true); |
michael@0 | 1053 | return GetLangSupportEntry(aLang, aWithFonts); |
michael@0 | 1054 | } |
michael@0 | 1055 | } |
michael@0 | 1056 | |
michael@0 | 1057 | return entry; |
michael@0 | 1058 | } |
michael@0 | 1059 | |
michael@0 | 1060 | FcLangResult |
michael@0 | 1061 | gfxFontconfigUtils::GetBestLangSupport(const FcChar8 *aLang) |
michael@0 | 1062 | { |
michael@0 | 1063 | UpdateFontListInternal(); |
michael@0 | 1064 | |
michael@0 | 1065 | LangSupportEntry *entry = GetLangSupportEntry(aLang, false); |
michael@0 | 1066 | if (!entry) |
michael@0 | 1067 | return FcLangEqual; |
michael@0 | 1068 | |
michael@0 | 1069 | return entry->mSupport; |
michael@0 | 1070 | } |
michael@0 | 1071 | |
michael@0 | 1072 | const nsTArray< nsCountedRef<FcPattern> >& |
michael@0 | 1073 | gfxFontconfigUtils::GetFontsForLang(const FcChar8 *aLang) |
michael@0 | 1074 | { |
michael@0 | 1075 | LangSupportEntry *entry = GetLangSupportEntry(aLang, true); |
michael@0 | 1076 | if (!entry) |
michael@0 | 1077 | return mEmptyPatternArray; |
michael@0 | 1078 | |
michael@0 | 1079 | return entry->mFonts; |
michael@0 | 1080 | } |