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