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