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