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