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 #include "mozilla/Base64.h"
8 #include "mozilla/MemoryReporting.h"
9
10 #include "mozilla/dom/ContentChild.h"
11 #include "gfxAndroidPlatform.h"
12 #include "mozilla/Omnijar.h"
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/UniquePtrExtensions.h"
15 #include "nsIInputStream.h"
16 #define gfxToolkitPlatform gfxAndroidPlatform
17
18 #include "nsXULAppAPI.h"
19 #include <dirent.h>
20 #include <android/log.h>
21 #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args)
22
23 #include "ft2build.h"
24 #include FT_FREETYPE_H
25 #include FT_TRUETYPE_TAGS_H
26 #include FT_TRUETYPE_TABLES_H
27 #include "cairo-ft.h"
28
29 #include "gfxFT2FontList.h"
30 #include "gfxFT2Fonts.h"
31 #include "gfxUserFontSet.h"
32 #include "gfxFontUtils.h"
33
34 #include "nsServiceManagerUtils.h"
35 #include "nsIObserverService.h"
36 #include "nsTArray.h"
37 #include "nsUnicharUtils.h"
38 #include "nsCRT.h"
39
40 #include "nsDirectoryServiceUtils.h"
41 #include "nsDirectoryServiceDefs.h"
42 #include "nsAppDirectoryServiceDefs.h"
43 #include "nsISimpleEnumerator.h"
44 #include "nsIMemory.h"
45 #include "gfxFontConstants.h"
46
47 #include "mozilla/Preferences.h"
48 #include "mozilla/scache/StartupCache.h"
49 #include <fcntl.h>
50 #include <sys/mman.h>
51 #include <sys/stat.h>
52
53 using namespace mozilla;
54
55 static LazyLogModule sFontInfoLog("fontInfoLog");
56
57 #undef LOG
58 #define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args)
59 #define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug)
60
61 static cairo_user_data_key_t sFTUserFontDataKey;
62
63 static __inline void
BuildKeyNameFromFontName(nsAString & aName)64 BuildKeyNameFromFontName(nsAString &aName)
65 {
66 ToLowerCase(aName);
67 }
68
69 // Helper to access the FT_Face for a given FT2FontEntry,
70 // creating a temporary face if the entry does not have one yet.
71 // This allows us to read font names, tables, etc if necessary
72 // without permanently instantiating a freetype face and consuming
73 // memory long-term.
74 // This may fail (resulting in a null FT_Face), e.g. if it fails to
75 // allocate memory to uncompress a font from omnijar.
76 class AutoFTFace {
77 public:
AutoFTFace(FT2FontEntry * aFontEntry)78 AutoFTFace(FT2FontEntry* aFontEntry)
79 : mFace(nullptr), mFontDataBuf(nullptr), mOwnsFace(false)
80 {
81 if (aFontEntry->mFTFace) {
82 mFace = aFontEntry->mFTFace;
83 return;
84 }
85
86 NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(),
87 "can't use AutoFTFace for fonts without a filename");
88 FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary();
89
90 // A relative path (no initial "/") means this is a resource in
91 // omnijar, not an installed font on the device.
92 // The NS_ASSERTIONs here should never fail, as the resource must have
93 // been read successfully during font-list initialization or we'd never
94 // have created the font entry. The only legitimate runtime failure
95 // here would be memory allocation, in which case mFace remains null.
96 if (aFontEntry->mFilename[0] != '/') {
97 RefPtr<nsZipArchive> reader =
98 Omnijar::GetReader(Omnijar::Type::GRE);
99 nsZipItem *item = reader->GetItem(aFontEntry->mFilename.get());
100 NS_ASSERTION(item, "failed to find zip entry");
101
102 uint32_t bufSize = item->RealSize();
103 mFontDataBuf = static_cast<uint8_t*>(malloc(bufSize));
104 if (mFontDataBuf) {
105 nsZipCursor cursor(item, reader, mFontDataBuf, bufSize);
106 cursor.Copy(&bufSize);
107 NS_ASSERTION(bufSize == item->RealSize(),
108 "error reading bundled font");
109
110 if (FT_Err_Ok != FT_New_Memory_Face(ft, mFontDataBuf, bufSize,
111 aFontEntry->mFTFontIndex,
112 &mFace)) {
113 NS_WARNING("failed to create freetype face");
114 }
115 }
116 } else {
117 if (FT_Err_Ok != FT_New_Face(ft, aFontEntry->mFilename.get(),
118 aFontEntry->mFTFontIndex, &mFace)) {
119 NS_WARNING("failed to create freetype face");
120 }
121 }
122 if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) {
123 NS_WARNING("failed to select Unicode charmap");
124 }
125 mOwnsFace = true;
126 }
127
~AutoFTFace()128 ~AutoFTFace() {
129 if (mFace && mOwnsFace) {
130 FT_Done_Face(mFace);
131 if (mFontDataBuf) {
132 free(mFontDataBuf);
133 }
134 }
135 }
136
operator FT_Face()137 operator FT_Face() { return mFace; }
138
139 // If we 'forget' the FT_Face (used when ownership is handed over to Cairo),
140 // we do -not- free the mFontDataBuf (if used); that also becomes the
141 // responsibility of the new owner of the face.
forget()142 FT_Face forget() {
143 NS_ASSERTION(mOwnsFace, "can't forget() when we didn't own the face");
144 mOwnsFace = false;
145 return mFace;
146 }
147
FontData() const148 const uint8_t* FontData() const { return mFontDataBuf; }
149
150 private:
151 FT_Face mFace;
152 uint8_t* mFontDataBuf; // Uncompressed data (for fonts stored in a JAR),
153 // or null for fonts instantiated from a file.
154 // If non-null, this must survive as long as the
155 // FT_Face.
156 bool mOwnsFace;
157 };
158
159 /*
160 * FT2FontEntry
161 * gfxFontEntry subclass corresponding to a specific face that can be
162 * rendered by freetype. This is associated with a face index in a
163 * file (normally a .ttf/.otf file holding a single face, but in principle
164 * there could be .ttc files with multiple faces).
165 * The FT2FontEntry can create the necessary FT_Face on demand, and can
166 * then create a Cairo font_face and scaled_font for drawing.
167 */
168
169 cairo_scaled_font_t *
CreateScaledFont(const gfxFontStyle * aStyle)170 FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle)
171 {
172 cairo_font_face_t *cairoFace = CairoFontFace();
173 if (!cairoFace) {
174 return nullptr;
175 }
176
177 cairo_scaled_font_t *scaledFont = nullptr;
178
179 cairo_matrix_t sizeMatrix;
180 cairo_matrix_t identityMatrix;
181
182 // XXX deal with adjusted size
183 cairo_matrix_init_scale(&sizeMatrix, aStyle->size, aStyle->size);
184 cairo_matrix_init_identity(&identityMatrix);
185
186 // synthetic oblique by skewing via the font matrix
187 bool needsOblique = IsUpright() &&
188 aStyle->style != NS_FONT_STYLE_NORMAL &&
189 aStyle->allowSyntheticStyle;
190
191 if (needsOblique) {
192 cairo_matrix_t style;
193 cairo_matrix_init(&style,
194 1, //xx
195 0, //yx
196 -1 * OBLIQUE_SKEW_FACTOR, //xy
197 1, //yy
198 0, //x0
199 0); //y0
200 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
201 }
202
203 cairo_font_options_t *fontOptions = cairo_font_options_create();
204
205 if (gfxPlatform::GetPlatform()->RequiresLinearZoom()) {
206 cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF);
207 }
208
209 scaledFont = cairo_scaled_font_create(cairoFace,
210 &sizeMatrix,
211 &identityMatrix, fontOptions);
212 cairo_font_options_destroy(fontOptions);
213
214 NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS,
215 "Failed to make scaled font");
216
217 return scaledFont;
218 }
219
~FT2FontEntry()220 FT2FontEntry::~FT2FontEntry()
221 {
222 // Do nothing for mFTFace here since FTFontDestroyFunc is called by cairo.
223 mFTFace = nullptr;
224
225 #ifndef ANDROID
226 if (mFontFace) {
227 cairo_font_face_destroy(mFontFace);
228 mFontFace = nullptr;
229 }
230 #endif
231 }
232
233 gfxFont*
CreateFontInstance(const gfxFontStyle * aFontStyle,bool aNeedsBold)234 FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
235 {
236 cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle);
237 if (!scaledFont) {
238 return nullptr;
239 }
240 gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold);
241 cairo_scaled_font_destroy(scaledFont);
242 return font;
243 }
244
245 /* static */
246 FT2FontEntry*
CreateFontEntry(const nsAString & aFontName,uint16_t aWeight,int16_t aStretch,uint8_t aStyle,const uint8_t * aFontData,uint32_t aLength)247 FT2FontEntry::CreateFontEntry(const nsAString& aFontName,
248 uint16_t aWeight,
249 int16_t aStretch,
250 uint8_t aStyle,
251 const uint8_t* aFontData,
252 uint32_t aLength)
253 {
254 // Ownership of aFontData is passed in here; the fontEntry must
255 // retain it as long as the FT_Face needs it, and ensure it is
256 // eventually deleted.
257 FT_Face face;
258 FT_Error error =
259 FT_New_Memory_Face(gfxToolkitPlatform::GetPlatform()->GetFTLibrary(),
260 aFontData, aLength, 0, &face);
261 if (error != FT_Err_Ok) {
262 free((void*)aFontData);
263 return nullptr;
264 }
265 if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
266 FT_Done_Face(face);
267 free((void*)aFontData);
268 return nullptr;
269 }
270 // Create our FT2FontEntry, which inherits the name of the userfont entry
271 // as it's not guaranteed that the face has valid names (bug 737315)
272 FT2FontEntry* fe =
273 FT2FontEntry::CreateFontEntry(face, nullptr, 0, aFontName,
274 aFontData);
275 if (fe) {
276 fe->mStyle = aStyle;
277 fe->mWeight = aWeight;
278 fe->mStretch = aStretch;
279 fe->mIsDataUserFont = true;
280 }
281 return fe;
282 }
283
284 class FTUserFontData {
285 public:
FTUserFontData(FT_Face aFace,const uint8_t * aData)286 FTUserFontData(FT_Face aFace, const uint8_t* aData)
287 : mFace(aFace), mFontData(aData)
288 {
289 }
290
~FTUserFontData()291 ~FTUserFontData()
292 {
293 FT_Done_Face(mFace);
294 if (mFontData) {
295 free((void*)mFontData);
296 }
297 }
298
FontData() const299 const uint8_t *FontData() const { return mFontData; }
300
301 private:
302 FT_Face mFace;
303 const uint8_t *mFontData;
304 };
305
306 static void
FTFontDestroyFunc(void * data)307 FTFontDestroyFunc(void *data)
308 {
309 FTUserFontData *userFontData = static_cast<FTUserFontData*>(data);
310 delete userFontData;
311 }
312
313 /* static */
314 FT2FontEntry*
CreateFontEntry(const FontListEntry & aFLE)315 FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE)
316 {
317 FT2FontEntry *fe = new FT2FontEntry(aFLE.faceName());
318 fe->mFilename = aFLE.filepath();
319 fe->mFTFontIndex = aFLE.index();
320 fe->mWeight = aFLE.weight();
321 fe->mStretch = aFLE.stretch();
322 fe->mStyle = (aFLE.italic() ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL);
323 return fe;
324 }
325
326 // Helpers to extract font entry properties from an FT_Face
327 static bool
FTFaceIsItalic(FT_Face aFace)328 FTFaceIsItalic(FT_Face aFace)
329 {
330 return !!(aFace->style_flags & FT_STYLE_FLAG_ITALIC);
331 }
332
333 static uint16_t
FTFaceGetWeight(FT_Face aFace)334 FTFaceGetWeight(FT_Face aFace)
335 {
336 TT_OS2 *os2 = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(aFace, ft_sfnt_os2));
337 uint16_t os2weight = 0;
338 if (os2 && os2->version != 0xffff) {
339 // Technically, only 100 to 900 are valid, but some fonts
340 // have this set wrong -- e.g. "Microsoft Logo Bold Italic" has
341 // it set to 6 instead of 600. We try to be nice and handle that
342 // as well.
343 if (os2->usWeightClass >= 100 && os2->usWeightClass <= 900) {
344 os2weight = os2->usWeightClass;
345 } else if (os2->usWeightClass >= 1 && os2->usWeightClass <= 9) {
346 os2weight = os2->usWeightClass * 100;
347 }
348 }
349
350 uint16_t result;
351 if (os2weight != 0) {
352 result = os2weight;
353 } else if (aFace->style_flags & FT_STYLE_FLAG_BOLD) {
354 result = 700;
355 } else {
356 result = 400;
357 }
358
359 NS_ASSERTION(result >= 100 && result <= 900, "Invalid weight in font!");
360
361 return result;
362 }
363
364 // Used to create the font entry for installed faces on the device,
365 // when iterating over the fonts directories.
366 // We use the FT_Face to retrieve the details needed for the font entry,
367 // but unless we have been passed font data (i.e. for a user font),
368 // we do *not* save a reference to it, nor create a cairo face,
369 // as we don't want to keep a freetype face for every installed font
370 // permanently in memory.
371 /* static */
372 FT2FontEntry*
CreateFontEntry(FT_Face aFace,const char * aFilename,uint8_t aIndex,const nsAString & aName,const uint8_t * aFontData)373 FT2FontEntry::CreateFontEntry(FT_Face aFace,
374 const char* aFilename, uint8_t aIndex,
375 const nsAString& aName,
376 const uint8_t* aFontData)
377 {
378 FT2FontEntry *fe = new FT2FontEntry(aName);
379 fe->mStyle = (FTFaceIsItalic(aFace) ?
380 NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL);
381 fe->mWeight = FTFaceGetWeight(aFace);
382 fe->mFilename = aFilename;
383 fe->mFTFontIndex = aIndex;
384
385 if (aFontData) {
386 fe->mFTFace = aFace;
387 int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
388 FT_LOAD_DEFAULT :
389 (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
390 fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags);
391 FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData);
392 cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey,
393 userFontData, FTFontDestroyFunc);
394 }
395
396 return fe;
397 }
398
399 // construct font entry name for an installed font from names in the FT_Face,
400 // and then create our FT2FontEntry
401 static FT2FontEntry*
CreateNamedFontEntry(FT_Face aFace,const char * aFilename,uint8_t aIndex)402 CreateNamedFontEntry(FT_Face aFace, const char* aFilename, uint8_t aIndex)
403 {
404 if (!aFace->family_name) {
405 return nullptr;
406 }
407 nsAutoString fontName;
408 AppendUTF8toUTF16(aFace->family_name, fontName);
409 if (aFace->style_name && strcmp("Regular", aFace->style_name)) {
410 fontName.Append(' ');
411 AppendUTF8toUTF16(aFace->style_name, fontName);
412 }
413 return FT2FontEntry::CreateFontEntry(aFace, aFilename, aIndex, fontName);
414 }
415
416 FT2FontEntry*
GetFontEntry()417 gfxFT2Font::GetFontEntry()
418 {
419 return static_cast<FT2FontEntry*> (mFontEntry.get());
420 }
421
422 cairo_font_face_t *
CairoFontFace()423 FT2FontEntry::CairoFontFace()
424 {
425 if (!mFontFace) {
426 AutoFTFace face(this);
427 if (!face) {
428 return nullptr;
429 }
430 mFTFace = face.forget();
431 int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
432 FT_LOAD_DEFAULT :
433 (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
434 mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags);
435 FTUserFontData *userFontData = new FTUserFontData(face, face.FontData());
436 cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
437 userFontData, FTFontDestroyFunc);
438 }
439 return mFontFace;
440 }
441
442 // Copied/modified from similar code in gfxMacPlatformFontList.mm:
443 // Complex scripts will not render correctly unless Graphite or OT
444 // layout tables are present.
445 // For OpenType, we also check that the GSUB table supports the relevant
446 // script tag, to avoid using things like Arial Unicode MS for Lao (it has
447 // the characters, but lacks OpenType support).
448
449 // TODO: consider whether we should move this to gfxFontEntry and do similar
450 // cmap-masking on all platforms to avoid using fonts that won't shape
451 // properly.
452
453 nsresult
ReadCMAP(FontInfoData * aFontInfoData)454 FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData)
455 {
456 if (mCharacterMap) {
457 return NS_OK;
458 }
459
460 RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
461
462 AutoTArray<uint8_t, 16384> buffer;
463 nsresult rv = CopyFontTable(TTAG_cmap, buffer);
464
465 if (NS_SUCCEEDED(rv)) {
466 bool unicodeFont;
467 bool symbolFont;
468 rv = gfxFontUtils::ReadCMAP(buffer.Elements(), buffer.Length(),
469 *charmap, mUVSOffset,
470 unicodeFont, symbolFont);
471 }
472
473 if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
474 // We assume a Graphite font knows what it's doing,
475 // and provides whatever shaping is needed for the
476 // characters it supports, so only check/clear the
477 // complex-script ranges for non-Graphite fonts
478
479 // for layout support, check for the presence of opentype layout tables
480 bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B'));
481
482 for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
483 sr->rangeStart; sr++) {
484 // check to see if the cmap includes complex script codepoints
485 if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
486 // We check for GSUB here, as GPOS alone would not be ok.
487 if (hasGSUB && SupportsScriptInGSUB(sr->tags)) {
488 continue;
489 }
490 charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
491 }
492 }
493 }
494
495 #ifdef MOZ_WIDGET_ANDROID
496 // Hack for the SamsungDevanagari font, bug 1012365:
497 // pretend the font supports U+0972.
498 if (!charmap->test(0x0972) &&
499 charmap->test(0x0905) && charmap->test(0x0945)) {
500 charmap->set(0x0972);
501 }
502 #endif
503
504 mHasCmapTable = NS_SUCCEEDED(rv);
505 if (mHasCmapTable) {
506 gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
507 mCharacterMap = pfl->FindCharMap(charmap);
508 } else {
509 // if error occurred, initialize to null cmap
510 mCharacterMap = new gfxCharacterMap();
511 }
512 return rv;
513 }
514
515 nsresult
CopyFontTable(uint32_t aTableTag,nsTArray<uint8_t> & aBuffer)516 FT2FontEntry::CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>& aBuffer)
517 {
518 AutoFTFace face(this);
519 if (!face) {
520 return NS_ERROR_FAILURE;
521 }
522
523 FT_Error status;
524 FT_ULong len = 0;
525 status = FT_Load_Sfnt_Table(face, aTableTag, 0, nullptr, &len);
526 if (status != FT_Err_Ok || len == 0) {
527 return NS_ERROR_FAILURE;
528 }
529
530 if (!aBuffer.SetLength(len, fallible)) {
531 return NS_ERROR_OUT_OF_MEMORY;
532 }
533 uint8_t *buf = aBuffer.Elements();
534 status = FT_Load_Sfnt_Table(face, aTableTag, 0, buf, &len);
535 NS_ENSURE_TRUE(status == FT_Err_Ok, NS_ERROR_FAILURE);
536
537 return NS_OK;
538 }
539
540 hb_blob_t*
GetFontTable(uint32_t aTableTag)541 FT2FontEntry::GetFontTable(uint32_t aTableTag)
542 {
543 if (mFontFace) {
544 // if there's a cairo font face, we may be able to return a blob
545 // that just wraps a range of the attached user font data
546 FTUserFontData *userFontData = static_cast<FTUserFontData*>(
547 cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey));
548 if (userFontData && userFontData->FontData()) {
549 return gfxFontUtils::GetTableFromFontData(userFontData->FontData(),
550 aTableTag);
551 }
552 }
553
554 // otherwise, use the default method (which in turn will call our
555 // implementation of CopyFontTable)
556 return gfxFontEntry::GetFontTable(aTableTag);
557 }
558
559 void
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,FontListSizes * aSizes) const560 FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
561 FontListSizes* aSizes) const
562 {
563 gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
564 aSizes->mFontListSize +=
565 mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
566 }
567
568 void
AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,FontListSizes * aSizes) const569 FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
570 FontListSizes* aSizes) const
571 {
572 aSizes->mFontListSize += aMallocSizeOf(this);
573 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
574 }
575
576 /*
577 * FT2FontFamily
578 * A standard gfxFontFamily; just adds a method used to support sending
579 * the font list from chrome to content via IPC.
580 */
581
582 void
AddFacesToFontList(InfallibleTArray<FontListEntry> * aFontList,Visibility aVisibility)583 FT2FontFamily::AddFacesToFontList(InfallibleTArray<FontListEntry>* aFontList,
584 Visibility aVisibility)
585 {
586 for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) {
587 const FT2FontEntry *fe =
588 static_cast<const FT2FontEntry*>(mAvailableFonts[i].get());
589 if (!fe) {
590 continue;
591 }
592
593 aFontList->AppendElement(FontListEntry(Name(), fe->Name(),
594 fe->mFilename,
595 fe->Weight(), fe->Stretch(),
596 fe->mStyle,
597 fe->mFTFontIndex,
598 aVisibility == kHidden));
599 }
600 }
601
602 /*
603 * Startup cache support for the font list:
604 * We store the list of families and faces, with their style attributes and the
605 * corresponding font files, in the startup cache.
606 * This allows us to recreate the gfxFT2FontList collection of families and
607 * faces without instantiating Freetype faces for each font file (in order to
608 * find their attributes), leading to significantly quicker startup.
609 */
610
611 #define CACHE_KEY "font.cached-list"
612
613 class FontNameCache {
614 public:
615 // Creates the object but does NOT load the cached data from the startup
616 // cache; call Init() after creation to do that.
FontNameCache()617 FontNameCache()
618 : mMap(&mOps, sizeof(FNCMapEntry), 0)
619 , mWriteNeeded(false)
620 {
621 // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to
622 // it to |mMap|'s constructor. A more normal approach here would be to
623 // have a static |sOps| member. Unfortunately, this mysteriously but
624 // consistently makes Fennec start-up slower, so we take this
625 // unorthodox approach instead. It's safe because PLDHashTable's
626 // constructor doesn't dereference the pointer; it just makes a copy of
627 // it.
628 mOps = (PLDHashTableOps) {
629 StringHash,
630 HashMatchEntry,
631 MoveEntry,
632 PLDHashTable::ClearEntryStub,
633 nullptr
634 };
635
636 MOZ_ASSERT(XRE_IsParentProcess(),
637 "FontNameCache should only be used in chrome process");
638 mCache = mozilla::scache::StartupCache::GetSingleton();
639 }
640
~FontNameCache()641 ~FontNameCache()
642 {
643 if (!mWriteNeeded || !mCache) {
644 return;
645 }
646
647 nsAutoCString buf;
648 for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
649 auto entry = static_cast<FNCMapEntry*>(iter.Get());
650 if (!entry->mFileExists) {
651 // skip writing entries for files that are no longer present
652 continue;
653 }
654 buf.Append(entry->mFilename);
655 buf.Append(';');
656 buf.Append(entry->mFaces);
657 buf.Append(';');
658 buf.AppendInt(entry->mTimestamp);
659 buf.Append(';');
660 buf.AppendInt(entry->mFilesize);
661 buf.Append(';');
662 }
663 mCache->PutBuffer(CACHE_KEY, buf.get(), buf.Length() + 1);
664 }
665
666 // This may be called more than once (if we re-load the font list).
Init()667 void Init()
668 {
669 if (!mCache) {
670 return;
671 }
672
673 uint32_t size;
674 UniquePtr<char[]> buf;
675 if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &buf, &size))) {
676 return;
677 }
678
679 LOG(("got: %s from the cache", nsDependentCString(buf.get(), size).get()));
680
681 mMap.Clear();
682 mWriteNeeded = false;
683
684 const char* beginning = buf.get();
685 const char* end = strchr(beginning, ';');
686 while (end) {
687 nsCString filename(beginning, end - beginning);
688 beginning = end + 1;
689 if (!(end = strchr(beginning, ';'))) {
690 break;
691 }
692 nsCString faceList(beginning, end - beginning);
693 beginning = end + 1;
694 if (!(end = strchr(beginning, ';'))) {
695 break;
696 }
697 uint32_t timestamp = strtoul(beginning, nullptr, 10);
698 beginning = end + 1;
699 if (!(end = strchr(beginning, ';'))) {
700 break;
701 }
702 uint32_t filesize = strtoul(beginning, nullptr, 10);
703
704 auto mapEntry =
705 static_cast<FNCMapEntry*>(mMap.Add(filename.get(), fallible));
706 if (mapEntry) {
707 mapEntry->mFilename.Assign(filename);
708 mapEntry->mTimestamp = timestamp;
709 mapEntry->mFilesize = filesize;
710 mapEntry->mFaces.Assign(faceList);
711 // entries from the startupcache are marked "non-existing"
712 // until we have confirmed that the file still exists
713 mapEntry->mFileExists = false;
714 }
715
716 beginning = end + 1;
717 end = strchr(beginning, ';');
718 }
719 }
720
721 void
GetInfoForFile(const nsCString & aFileName,nsCString & aFaceList,uint32_t * aTimestamp,uint32_t * aFilesize)722 GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList,
723 uint32_t *aTimestamp, uint32_t *aFilesize)
724 {
725 auto entry = static_cast<FNCMapEntry*>(mMap.Search(aFileName.get()));
726 if (entry) {
727 *aTimestamp = entry->mTimestamp;
728 *aFilesize = entry->mFilesize;
729 aFaceList.Assign(entry->mFaces);
730 // this entry does correspond to an existing file
731 // (although it might not be up-to-date, in which case
732 // it will get overwritten via CacheFileInfo)
733 entry->mFileExists = true;
734 }
735 }
736
737 void
CacheFileInfo(const nsCString & aFileName,const nsCString & aFaceList,uint32_t aTimestamp,uint32_t aFilesize)738 CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList,
739 uint32_t aTimestamp, uint32_t aFilesize)
740 {
741 auto entry =
742 static_cast<FNCMapEntry*>(mMap.Add(aFileName.get(), fallible));
743 if (entry) {
744 entry->mFilename.Assign(aFileName);
745 entry->mTimestamp = aTimestamp;
746 entry->mFilesize = aFilesize;
747 entry->mFaces.Assign(aFaceList);
748 entry->mFileExists = true;
749 }
750 mWriteNeeded = true;
751 }
752
753 private:
754 mozilla::scache::StartupCache* mCache;
755 PLDHashTable mMap;
756 bool mWriteNeeded;
757
758 PLDHashTableOps mOps;
759
760 typedef struct : public PLDHashEntryHdr {
761 public:
762 nsCString mFilename;
763 uint32_t mTimestamp;
764 uint32_t mFilesize;
765 nsCString mFaces;
766 bool mFileExists;
767 } FNCMapEntry;
768
StringHash(const void * key)769 static PLDHashNumber StringHash(const void *key)
770 {
771 return HashString(reinterpret_cast<const char*>(key));
772 }
773
HashMatchEntry(const PLDHashEntryHdr * aHdr,const void * key)774 static bool HashMatchEntry(const PLDHashEntryHdr *aHdr, const void *key)
775 {
776 const FNCMapEntry* entry =
777 static_cast<const FNCMapEntry*>(aHdr);
778 return entry->mFilename.Equals(reinterpret_cast<const char*>(key));
779 }
780
MoveEntry(PLDHashTable * table,const PLDHashEntryHdr * aFrom,PLDHashEntryHdr * aTo)781 static void MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *aFrom,
782 PLDHashEntryHdr *aTo)
783 {
784 FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo);
785 const FNCMapEntry* from = static_cast<const FNCMapEntry*>(aFrom);
786 to->mFilename.Assign(from->mFilename);
787 to->mTimestamp = from->mTimestamp;
788 to->mFilesize = from->mFilesize;
789 to->mFaces.Assign(from->mFaces);
790 to->mFileExists = from->mFileExists;
791 }
792 };
793
794 /***************************************************************
795 *
796 * gfxFT2FontList
797 *
798 */
799
800 // For Mobile, we use gfxFT2Fonts, and we build the font list by directly
801 // scanning the system's Fonts directory for OpenType and TrueType files.
802
803 #define JAR_LAST_MODIFED_TIME "jar-last-modified-time"
804
805 class WillShutdownObserver : public nsIObserver
806 {
807 public:
808 NS_DECL_ISUPPORTS
809 NS_DECL_NSIOBSERVER
810
WillShutdownObserver(gfxFT2FontList * aFontList)811 explicit WillShutdownObserver(gfxFT2FontList* aFontList)
812 : mFontList(aFontList)
813 { }
814
815 protected:
~WillShutdownObserver()816 virtual ~WillShutdownObserver()
817 { }
818
819 gfxFT2FontList *mFontList;
820 };
821
NS_IMPL_ISUPPORTS(WillShutdownObserver,nsIObserver)822 NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver)
823
824 NS_IMETHODIMP
825 WillShutdownObserver::Observe(nsISupports *aSubject,
826 const char *aTopic,
827 const char16_t *aData)
828 {
829 if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
830 mFontList->WillShutdown();
831 } else {
832 NS_NOTREACHED("unexpected notification topic");
833 }
834 return NS_OK;
835 }
836
gfxFT2FontList()837 gfxFT2FontList::gfxFT2FontList()
838 : mJarModifiedTime(0)
839 {
840 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
841 if (obs) {
842 mObserver = new WillShutdownObserver(this);
843 obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
844 }
845 }
846
~gfxFT2FontList()847 gfxFT2FontList::~gfxFT2FontList()
848 {
849 if (mObserver) {
850 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
851 if (obs) {
852 obs->RemoveObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
853 }
854 mObserver = nullptr;
855 }
856 }
857
858 void
AppendFacesFromCachedFaceList(const nsCString & aFileName,const nsCString & aFaceList,StandardFile aStdFile,FT2FontFamily::Visibility aVisibility)859 gfxFT2FontList::AppendFacesFromCachedFaceList(
860 const nsCString& aFileName,
861 const nsCString& aFaceList,
862 StandardFile aStdFile,
863 FT2FontFamily::Visibility aVisibility)
864 {
865 const char *beginning = aFaceList.get();
866 const char *end = strchr(beginning, ',');
867 while (end) {
868 NS_ConvertUTF8toUTF16 familyName(beginning, end - beginning);
869 ToLowerCase(familyName);
870 beginning = end + 1;
871 if (!(end = strchr(beginning, ','))) {
872 break;
873 }
874 NS_ConvertUTF8toUTF16 faceName(beginning, end - beginning);
875 beginning = end + 1;
876 if (!(end = strchr(beginning, ','))) {
877 break;
878 }
879 uint32_t index = strtoul(beginning, nullptr, 10);
880 beginning = end + 1;
881 if (!(end = strchr(beginning, ','))) {
882 break;
883 }
884 bool italic = (*beginning != '0');
885 beginning = end + 1;
886 if (!(end = strchr(beginning, ','))) {
887 break;
888 }
889 uint32_t weight = strtoul(beginning, nullptr, 10);
890 beginning = end + 1;
891 if (!(end = strchr(beginning, ','))) {
892 break;
893 }
894 int32_t stretch = strtol(beginning, nullptr, 10);
895
896 FontListEntry fle(familyName, faceName, aFileName,
897 weight, stretch, italic, index,
898 aVisibility == FT2FontFamily::kHidden);
899 AppendFaceFromFontListEntry(fle, aStdFile);
900
901 beginning = end + 1;
902 end = strchr(beginning, ',');
903 }
904 }
905
906 static void
AppendToFaceList(nsCString & aFaceList,nsAString & aFamilyName,FT2FontEntry * aFontEntry)907 AppendToFaceList(nsCString& aFaceList,
908 nsAString& aFamilyName, FT2FontEntry* aFontEntry)
909 {
910 aFaceList.Append(NS_ConvertUTF16toUTF8(aFamilyName));
911 aFaceList.Append(',');
912 aFaceList.Append(NS_ConvertUTF16toUTF8(aFontEntry->Name()));
913 aFaceList.Append(',');
914 aFaceList.AppendInt(aFontEntry->mFTFontIndex);
915 aFaceList.Append(',');
916 aFaceList.Append(aFontEntry->IsItalic() ? '1' : '0');
917 aFaceList.Append(',');
918 aFaceList.AppendInt(aFontEntry->Weight());
919 aFaceList.Append(',');
920 aFaceList.AppendInt(aFontEntry->Stretch());
921 aFaceList.Append(',');
922 }
923
924 void
CheckForBrokenFont(gfxFontFamily * aFamily)925 FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily)
926 {
927 // note if the family is in the "bad underline" blacklist
928 if (aFamily->IsBadUnderlineFamily()) {
929 mIsBadUnderlineFont = true;
930 }
931
932 // bug 721719 - set the IgnoreGSUB flag on entries for Roboto
933 // because of unwanted on-by-default "ae" ligature.
934 // (See also AppendFaceFromFontListEntry.)
935 if (aFamily->Name().EqualsLiteral("roboto")) {
936 mIgnoreGSUB = true;
937 }
938
939 // bug 706888 - set the IgnoreGSUB flag on the broken version of
940 // Droid Sans Arabic from certain phones, as identified by the
941 // font checksum in the 'head' table
942 else if (aFamily->Name().EqualsLiteral("droid sans arabic")) {
943 AutoFTFace face(this);
944 if (face) {
945 const TT_Header *head = static_cast<const TT_Header*>
946 (FT_Get_Sfnt_Table(face, ft_sfnt_head));
947 if (head && head->CheckSum_Adjust == 0xe445242) {
948 mIgnoreGSUB = true;
949 }
950 }
951 }
952 }
953
954 void
AppendFacesFromFontFile(const nsCString & aFileName,FontNameCache * aCache,StandardFile aStdFile,FT2FontFamily::Visibility aVisibility)955 gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
956 FontNameCache *aCache,
957 StandardFile aStdFile,
958 FT2FontFamily::Visibility aVisibility)
959 {
960 nsCString cachedFaceList;
961 uint32_t filesize = 0, timestamp = 0;
962 if (aCache) {
963 aCache->GetInfoForFile(aFileName, cachedFaceList, ×tamp, &filesize);
964 }
965
966 struct stat s;
967 int statRetval = stat(aFileName.get(), &s);
968 if (!cachedFaceList.IsEmpty() && 0 == statRetval &&
969 s.st_mtime == timestamp && s.st_size == filesize)
970 {
971 LOG(("using cached font info for %s", aFileName.get()));
972 AppendFacesFromCachedFaceList(aFileName, cachedFaceList, aStdFile,
973 aVisibility);
974 return;
975 }
976
977 FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary();
978 FT_Face dummy;
979 if (FT_Err_Ok == FT_New_Face(ftLibrary, aFileName.get(), -1, &dummy)) {
980 LOG(("reading font info via FreeType for %s", aFileName.get()));
981 nsCString newFaceList;
982 timestamp = s.st_mtime;
983 filesize = s.st_size;
984 for (FT_Long i = 0; i < dummy->num_faces; i++) {
985 FT_Face face;
986 if (FT_Err_Ok != FT_New_Face(ftLibrary, aFileName.get(), i, &face)) {
987 continue;
988 }
989 AddFaceToList(aFileName, i, aStdFile, aVisibility, face, newFaceList);
990 FT_Done_Face(face);
991 }
992 FT_Done_Face(dummy);
993 if (aCache && 0 == statRetval && !newFaceList.IsEmpty()) {
994 aCache->CacheFileInfo(aFileName, newFaceList, timestamp, filesize);
995 }
996 }
997 }
998
999 void
FindFontsInOmnijar(FontNameCache * aCache)1000 gfxFT2FontList::FindFontsInOmnijar(FontNameCache *aCache)
1001 {
1002 bool jarChanged = false;
1003
1004 mozilla::scache::StartupCache* cache =
1005 mozilla::scache::StartupCache::GetSingleton();
1006 UniquePtr<char[]> cachedModifiedTimeBuf;
1007 uint32_t longSize;
1008 if (cache &&
1009 NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME,
1010 &cachedModifiedTimeBuf,
1011 &longSize)) &&
1012 longSize == sizeof(int64_t))
1013 {
1014 nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
1015 jarFile->GetLastModifiedTime(&mJarModifiedTime);
1016 if (mJarModifiedTime > *(int64_t*)cachedModifiedTimeBuf.get()) {
1017 jarChanged = true;
1018 }
1019 }
1020
1021 static const char* sJarSearchPaths[] = {
1022 "res/fonts/*.ttf$",
1023 };
1024 RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
1025 for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) {
1026 nsZipFind* find;
1027 if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) {
1028 const char* path;
1029 uint16_t len;
1030 while (NS_SUCCEEDED(find->FindNext(&path, &len))) {
1031 nsCString entryName(path, len);
1032 AppendFacesFromOmnijarEntry(reader, entryName, aCache,
1033 jarChanged);
1034 }
1035 delete find;
1036 }
1037 }
1038 }
1039
1040 // Given the freetype face corresponding to an entryName and face index,
1041 // add the face to the available font list and to the faceList string
1042 void
AddFaceToList(const nsCString & aEntryName,uint32_t aIndex,StandardFile aStdFile,FT2FontFamily::Visibility aVisibility,FT_Face aFace,nsCString & aFaceList)1043 gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
1044 StandardFile aStdFile,
1045 FT2FontFamily::Visibility aVisibility,
1046 FT_Face aFace,
1047 nsCString& aFaceList)
1048 {
1049 if (FT_Err_Ok != FT_Select_Charmap(aFace, FT_ENCODING_UNICODE)) {
1050 // ignore faces that don't support a Unicode charmap
1051 return;
1052 }
1053
1054 // build the font entry name and create an FT2FontEntry,
1055 // but do -not- keep a reference to the FT_Face
1056 RefPtr<FT2FontEntry> fe =
1057 CreateNamedFontEntry(aFace, aEntryName.get(), aIndex);
1058
1059 auto& fontFamilies =
1060 (aVisibility == FT2FontFamily::kHidden) ? mHiddenFontFamilies :
1061 mFontFamilies;
1062
1063 if (fe) {
1064 NS_ConvertUTF8toUTF16 name(aFace->family_name);
1065 BuildKeyNameFromFontName(name);
1066 RefPtr<gfxFontFamily> family = fontFamilies.GetWeak(name);
1067 if (!family) {
1068 family = new FT2FontFamily(name);
1069 fontFamilies.Put(name, family);
1070 if (mSkipSpaceLookupCheckFamilies.Contains(name)) {
1071 family->SetSkipSpaceFeatureCheck(true);
1072 }
1073 if (mBadUnderlineFamilyNames.Contains(name)) {
1074 family->SetBadUnderlineFamily();
1075 }
1076 }
1077 fe->mStandardFace = (aStdFile == kStandard);
1078 family->AddFontEntry(fe);
1079
1080 fe->CheckForBrokenFont(family);
1081
1082 AppendToFaceList(aFaceList, name, fe);
1083 if (LOG_ENABLED()) {
1084 LOG(("(fontinit) added (%s) to family (%s)"
1085 " with style: %s weight: %d stretch: %d",
1086 NS_ConvertUTF16toUTF8(fe->Name()).get(),
1087 NS_ConvertUTF16toUTF8(family->Name()).get(),
1088 fe->IsItalic() ? "italic" : "normal",
1089 fe->Weight(), fe->Stretch()));
1090 }
1091 }
1092 }
1093
1094 void
AppendFacesFromOmnijarEntry(nsZipArchive * aArchive,const nsCString & aEntryName,FontNameCache * aCache,bool aJarChanged)1095 gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive,
1096 const nsCString& aEntryName,
1097 FontNameCache *aCache,
1098 bool aJarChanged)
1099 {
1100 nsCString faceList;
1101 if (aCache && !aJarChanged) {
1102 uint32_t filesize, timestamp;
1103 aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize);
1104 if (faceList.Length() > 0) {
1105 AppendFacesFromCachedFaceList(aEntryName, faceList);
1106 return;
1107 }
1108 }
1109
1110 nsZipItem *item = aArchive->GetItem(aEntryName.get());
1111 NS_ASSERTION(item, "failed to find zip entry");
1112
1113 uint32_t bufSize = item->RealSize();
1114 // We use fallible allocation here; if there's not enough RAM, we'll simply
1115 // ignore the bundled fonts and fall back to the device's installed fonts.
1116 auto buf = MakeUniqueFallible<uint8_t[]>(bufSize);
1117 if (!buf) {
1118 return;
1119 }
1120
1121 nsZipCursor cursor(item, aArchive, buf.get(), bufSize);
1122 uint8_t* data = cursor.Copy(&bufSize);
1123 NS_ASSERTION(data && bufSize == item->RealSize(),
1124 "error reading bundled font");
1125 if (!data) {
1126 return;
1127 }
1128
1129 FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary();
1130
1131 FT_Face dummy;
1132 if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, 0, &dummy)) {
1133 return;
1134 }
1135
1136 for (FT_Long i = 0; i < dummy->num_faces; i++) {
1137 FT_Face face;
1138 if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, i, &face)) {
1139 continue;
1140 }
1141 AddFaceToList(aEntryName, i, kStandard, FT2FontFamily::kVisible,
1142 face, faceList);
1143 FT_Done_Face(face);
1144 }
1145
1146 FT_Done_Face(dummy);
1147
1148 if (aCache && !faceList.IsEmpty()) {
1149 aCache->CacheFileInfo(aEntryName, faceList, 0, bufSize);
1150 }
1151 }
1152
1153 // Called on each family after all fonts are added to the list;
1154 // this will sort faces to give priority to "standard" font files
1155 // if aUserArg is non-null (i.e. we're using it as a boolean flag)
1156 static void
FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey,RefPtr<gfxFontFamily> & aFamily,bool aSortFaces)1157 FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey,
1158 RefPtr<gfxFontFamily>& aFamily,
1159 bool aSortFaces)
1160 {
1161 gfxFontFamily *family = aFamily.get();
1162
1163 family->SetHasStyles(true);
1164
1165 if (aSortFaces) {
1166 family->SortAvailableFonts();
1167 }
1168 family->CheckForSimpleFamily();
1169 }
1170
1171 void
FindFonts()1172 gfxFT2FontList::FindFonts()
1173 {
1174 gfxFontCache *fc = gfxFontCache::GetCache();
1175 if (fc)
1176 fc->AgeAllGenerations();
1177 ClearLangGroupPrefFonts();
1178 mCodepointsWithNoFonts.reset();
1179
1180 mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls
1181 mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls
1182
1183 if (!XRE_IsParentProcess()) {
1184 // Content process: ask the Chrome process to give us the list
1185 InfallibleTArray<FontListEntry> fonts;
1186 mozilla::dom::ContentChild::GetSingleton()->SendReadFontList(&fonts);
1187 for (uint32_t i = 0, n = fonts.Length(); i < n; ++i) {
1188 // We don't need to identify "standard" font files here,
1189 // as the faces are already sorted.
1190 AppendFaceFromFontListEntry(fonts[i], kUnknown);
1191 }
1192 // Passing null for userdata tells Finalize that it does not need
1193 // to sort faces (because they were already sorted by chrome,
1194 // so we just maintain the existing order)
1195 for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1196 nsStringHashKey::KeyType key = iter.Key();
1197 RefPtr<gfxFontFamily>& family = iter.Data();
1198 FinalizeFamilyMemberList(key, family, /* aSortFaces */ false);
1199 }
1200 for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1201 nsStringHashKey::KeyType key = iter.Key();
1202 RefPtr<gfxFontFamily>& family = iter.Data();
1203 FinalizeFamilyMemberList(key, family, /* aSortFaces */ false );
1204 }
1205
1206 LOG(("got font list from chrome process: %d faces in %d families "
1207 "and %d in hidden families",
1208 fonts.Length(), mFontFamilies.Count(),
1209 mHiddenFontFamilies.Count()));
1210 return;
1211 }
1212
1213 // Chrome process: get the cached list (if any)
1214 if (!mFontNameCache) {
1215 mFontNameCache = MakeUnique<FontNameCache>();
1216 }
1217 mFontNameCache->Init();
1218
1219 // ANDROID_ROOT is the root of the android system, typically /system;
1220 // font files are in /$ANDROID_ROOT/fonts/
1221 nsCString root;
1222 char *androidRoot = PR_GetEnv("ANDROID_ROOT");
1223 if (androidRoot) {
1224 root = androidRoot;
1225 } else {
1226 root = NS_LITERAL_CSTRING("/system");
1227 }
1228 root.AppendLiteral("/fonts");
1229
1230 FindFontsInDir(root, mFontNameCache.get(), FT2FontFamily::kVisible);
1231
1232 if (mFontFamilies.Count() == 0) {
1233 // if we can't find/read the font directory, we are doomed!
1234 NS_RUNTIMEABORT("Could not read the system fonts directory");
1235 }
1236
1237 // Look for fonts stored in omnijar, unless we're on a low-memory
1238 // device where we don't want to spend the RAM to decompress them.
1239 // (Prefs may disable this, or force-enable it even with low memory.)
1240 bool lowmem;
1241 nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService();
1242 if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem &&
1243 Preferences::GetBool("gfx.bundled_fonts.enabled")) ||
1244 Preferences::GetBool("gfx.bundled_fonts.force-enabled")) {
1245 FindFontsInOmnijar(mFontNameCache.get());
1246 }
1247
1248 // Look for downloaded fonts in a profile-agnostic "fonts" directory.
1249 nsCOMPtr<nsIProperties> dirSvc =
1250 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
1251 if (dirSvc) {
1252 nsCOMPtr<nsIFile> appDir;
1253 nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
1254 NS_GET_IID(nsIFile), getter_AddRefs(appDir));
1255 if (NS_SUCCEEDED(rv)) {
1256 appDir->AppendNative(NS_LITERAL_CSTRING("fonts"));
1257 nsCString localPath;
1258 if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) {
1259 FindFontsInDir(localPath, mFontNameCache.get(),
1260 FT2FontFamily::kVisible);
1261 }
1262 }
1263 }
1264
1265 // look for locally-added fonts in a "fonts" subdir of the profile
1266 nsCOMPtr<nsIFile> localDir;
1267 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1268 getter_AddRefs(localDir));
1269 if (NS_SUCCEEDED(rv) &&
1270 NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
1271 nsCString localPath;
1272 rv = localDir->GetNativePath(localPath);
1273 if (NS_SUCCEEDED(rv)) {
1274 FindFontsInDir(localPath, mFontNameCache.get(),
1275 FT2FontFamily::kVisible);
1276 }
1277 }
1278
1279 // Finalize the families by sorting faces into standard order
1280 // and marking "simple" families.
1281 // Passing non-null userData here says that we want faces to be sorted.
1282 for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1283 nsStringHashKey::KeyType key = iter.Key();
1284 RefPtr<gfxFontFamily>& family = iter.Data();
1285 FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
1286 }
1287 for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1288 nsStringHashKey::KeyType key = iter.Key();
1289 RefPtr<gfxFontFamily>& family = iter.Data();
1290 FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
1291 }
1292 }
1293
1294 void
FindFontsInDir(const nsCString & aDir,FontNameCache * aFNC,FT2FontFamily::Visibility aVisibility)1295 gfxFT2FontList::FindFontsInDir(const nsCString& aDir,
1296 FontNameCache *aFNC,
1297 FT2FontFamily::Visibility aVisibility)
1298 {
1299 static const char* sStandardFonts[] = {
1300 "DroidSans.ttf",
1301 "DroidSans-Bold.ttf",
1302 "DroidSerif-Regular.ttf",
1303 "DroidSerif-Bold.ttf",
1304 "DroidSerif-Italic.ttf",
1305 "DroidSerif-BoldItalic.ttf",
1306 "DroidSansMono.ttf",
1307 "DroidSansArabic.ttf",
1308 "DroidSansHebrew.ttf",
1309 "DroidSansThai.ttf",
1310 "MTLmr3m.ttf",
1311 "MTLc3m.ttf",
1312 "NanumGothic.ttf",
1313 "DroidSansJapanese.ttf",
1314 "DroidSansFallback.ttf"
1315 };
1316
1317 DIR *d = opendir(aDir.get());
1318 if (!d) {
1319 return;
1320 }
1321
1322 struct dirent *ent = nullptr;
1323 while ((ent = readdir(d)) != nullptr) {
1324 const char *ext = strrchr(ent->d_name, '.');
1325 if (!ext) {
1326 continue;
1327 }
1328 if (strcasecmp(ext, ".ttf") == 0 ||
1329 strcasecmp(ext, ".otf") == 0 ||
1330 strcasecmp(ext, ".woff") == 0 ||
1331 strcasecmp(ext, ".ttc") == 0) {
1332 bool isStdFont = false;
1333 for (unsigned int i = 0;
1334 i < ArrayLength(sStandardFonts) && !isStdFont; i++) {
1335 isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0;
1336 }
1337
1338 nsCString s(aDir);
1339 s.Append('/');
1340 s.Append(ent->d_name);
1341
1342 // Add the face(s) from this file to our font list;
1343 // note that if we have cached info for this file in fnc,
1344 // and the file is unchanged, we won't actually need to read it.
1345 // If the file is new/changed, this will update the FontNameCache.
1346 AppendFacesFromFontFile(s, aFNC, isStdFont ? kStandard : kUnknown,
1347 aVisibility);
1348 }
1349 }
1350
1351 closedir(d);
1352 }
1353
1354 void
AppendFaceFromFontListEntry(const FontListEntry & aFLE,StandardFile aStdFile)1355 gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE,
1356 StandardFile aStdFile)
1357 {
1358 FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE);
1359 if (fe) {
1360 auto& fontFamilies =
1361 aFLE.isHidden() ? mHiddenFontFamilies : mFontFamilies;
1362 fe->mStandardFace = (aStdFile == kStandard);
1363 nsAutoString name(aFLE.familyName());
1364 RefPtr<gfxFontFamily> family = fontFamilies.GetWeak(name);
1365 if (!family) {
1366 family = new FT2FontFamily(name);
1367 fontFamilies.Put(name, family);
1368 if (mSkipSpaceLookupCheckFamilies.Contains(name)) {
1369 family->SetSkipSpaceFeatureCheck(true);
1370 }
1371 if (mBadUnderlineFamilyNames.Contains(name)) {
1372 family->SetBadUnderlineFamily();
1373 }
1374 }
1375 family->AddFontEntry(fe);
1376
1377 fe->CheckForBrokenFont(family);
1378 }
1379 }
1380
1381 void
GetSystemFontList(InfallibleTArray<FontListEntry> * retValue)1382 gfxFT2FontList::GetSystemFontList(InfallibleTArray<FontListEntry>* retValue)
1383 {
1384 for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1385 auto family = static_cast<FT2FontFamily*>(iter.Data().get());
1386 family->AddFacesToFontList(retValue, FT2FontFamily::kVisible);
1387 }
1388 for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1389 auto family = static_cast<FT2FontFamily*>(iter.Data().get());
1390 family->AddFacesToFontList(retValue, FT2FontFamily::kHidden);
1391 }
1392 }
1393
1394 static void
LoadSkipSpaceLookupCheck(nsTHashtable<nsStringHashKey> & aSkipSpaceLookupCheck)1395 LoadSkipSpaceLookupCheck(nsTHashtable<nsStringHashKey>& aSkipSpaceLookupCheck)
1396 {
1397 AutoTArray<nsString, 5> skiplist;
1398 gfxFontUtils::GetPrefsFontList(
1399 "font.whitelist.skip_default_features_space_check",
1400 skiplist);
1401 uint32_t numFonts = skiplist.Length();
1402 for (uint32_t i = 0; i < numFonts; i++) {
1403 ToLowerCase(skiplist[i]);
1404 aSkipSpaceLookupCheck.PutEntry(skiplist[i]);
1405 }
1406 }
1407
1408 void
PreloadAsUserFontFaces(nsStringHashKey::KeyType aKey,RefPtr<gfxFontFamily> & aFamily)1409 PreloadAsUserFontFaces(nsStringHashKey::KeyType aKey,
1410 RefPtr<gfxFontFamily>& aFamily)
1411 {
1412 gfxFontFamily *family = aFamily.get();
1413
1414 auto& faces = family->GetFontList();
1415 size_t count = faces.Length();
1416 for (size_t i = 0; i < count; ++i) {
1417 FT2FontEntry* fe = static_cast<FT2FontEntry*>(faces[i].get());
1418 if (fe->mFTFontIndex != 0) {
1419 NS_NOTREACHED("don't try to preload a multi-face font");
1420 continue;
1421 }
1422
1423 // XXX Should we move the i/o here off the main thread?
1424
1425 // Map the font data in fe->mFilename, so we can calculate its CRC32.
1426 int fd = open(fe->mFilename.get(), O_RDONLY);
1427 if (fd < 0) {
1428 continue;
1429 }
1430 struct stat buf;
1431 if (fstat(fd, &buf) != 0 || buf.st_size < 12) {
1432 close(fd);
1433 continue;
1434 }
1435 char* data = static_cast<char*>(
1436 mmap(0, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
1437 close(fd);
1438 if (data == MAP_FAILED) {
1439 continue;
1440 }
1441
1442 // Calculate CRC32
1443 uint32_t crc = crc32(0, nullptr, 0);
1444 crc = crc32(crc, (Bytef*)data, buf.st_size);
1445 munmap(data, buf.st_size);
1446
1447 #if 0
1448 ALOG("\n**** Preloading family [%s] face [%s] CRC32 [0x%08x]",
1449 NS_ConvertUTF16toUTF8(family->Name()).get(),
1450 fe->mFilename.get(),
1451 crc);
1452 #endif
1453
1454 fe->mUserFontData = MakeUnique<gfxUserFontData>();
1455 fe->mUserFontData->mRealName = fe->Name();
1456 fe->mUserFontData->mCRC32 = crc;
1457 fe->mUserFontData->mLength = buf.st_size;
1458
1459 // Stash it persistently in the user-font cache.
1460 gfxUserFontSet::UserFontCache::CacheFont(
1461 fe, gfxUserFontSet::UserFontCache::kPersistent);
1462 }
1463 }
1464
1465 nsresult
InitFontListForPlatform()1466 gfxFT2FontList::InitFontListForPlatform()
1467 {
1468 // reset hidden font list
1469 mHiddenFontFamilies.Clear();
1470
1471 LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies);
1472
1473 FindFonts();
1474
1475 for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1476 nsStringHashKey::KeyType key = iter.Key();
1477 RefPtr<gfxFontFamily>& family = iter.Data();
1478 PreloadAsUserFontFaces(key, family);
1479 }
1480 return NS_OK;
1481 }
1482
1483 // called for each family name, based on the assumption that the
1484 // first part of the full name is the family name
1485
1486 gfxFontEntry*
LookupLocalFont(const nsAString & aFontName,uint16_t aWeight,int16_t aStretch,uint8_t aStyle)1487 gfxFT2FontList::LookupLocalFont(const nsAString& aFontName,
1488 uint16_t aWeight,
1489 int16_t aStretch,
1490 uint8_t aStyle)
1491 {
1492 // walk over list of names
1493 FT2FontEntry* fontEntry = nullptr;
1494 nsString fullName(aFontName);
1495
1496 // Note that we only check mFontFamilies here, not mHiddenFontFamilies;
1497 // hence @font-face { src:local(...) } will not find hidden fonts.
1498 for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1499 // Check family name, based on the assumption that the
1500 // first part of the full name is the family name
1501 RefPtr<gfxFontFamily>& fontFamily = iter.Data();
1502
1503 // does the family name match up to the length of the family name?
1504 const nsString& family = fontFamily->Name();
1505 nsString fullNameFamily;
1506
1507 fullName.Left(fullNameFamily, family.Length());
1508
1509 // if so, iterate over faces in this family to see if there is a match
1510 if (family.Equals(fullNameFamily, nsCaseInsensitiveStringComparator())) {
1511 nsTArray<RefPtr<gfxFontEntry> >& fontList = fontFamily->GetFontList();
1512 int index, len = fontList.Length();
1513 for (index = 0; index < len; index++) {
1514 gfxFontEntry* fe = fontList[index];
1515 if (!fe) {
1516 continue;
1517 }
1518 if (fe->Name().Equals(fullName,
1519 nsCaseInsensitiveStringComparator())) {
1520 fontEntry = static_cast<FT2FontEntry*>(fe);
1521 goto searchDone;
1522 }
1523 }
1524 }
1525 }
1526
1527 searchDone:
1528 if (!fontEntry) {
1529 return nullptr;
1530 }
1531
1532 // Clone the font entry so that we can then set its style descriptors
1533 // from the userfont entry rather than the actual font.
1534
1535 // Ensure existence of mFTFace in the original entry
1536 fontEntry->CairoFontFace();
1537 if (!fontEntry->mFTFace) {
1538 return nullptr;
1539 }
1540
1541 FT2FontEntry* fe =
1542 FT2FontEntry::CreateFontEntry(fontEntry->mFTFace,
1543 fontEntry->mFilename.get(),
1544 fontEntry->mFTFontIndex,
1545 fontEntry->Name(), nullptr);
1546 if (fe) {
1547 fe->mStyle = aStyle;
1548 fe->mWeight = aWeight;
1549 fe->mStretch = aStretch;
1550 fe->mIsLocalUserFont = true;
1551 }
1552
1553 return fe;
1554 }
1555
1556 gfxFontFamily*
GetDefaultFontForPlatform(const gfxFontStyle * aStyle)1557 gfxFT2FontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
1558 {
1559 gfxFontFamily *ff = nullptr;
1560 #if defined(MOZ_WIDGET_ANDROID)
1561 ff = FindFamily(NS_LITERAL_STRING("Roboto"));
1562 if (!ff) {
1563 ff = FindFamily(NS_LITERAL_STRING("Droid Sans"));
1564 }
1565 #endif
1566 /* TODO: what about Qt or other platforms that may use this? */
1567 return ff;
1568 }
1569
1570 gfxFontEntry*
MakePlatformFont(const nsAString & aFontName,uint16_t aWeight,int16_t aStretch,uint8_t aStyle,const uint8_t * aFontData,uint32_t aLength)1571 gfxFT2FontList::MakePlatformFont(const nsAString& aFontName,
1572 uint16_t aWeight,
1573 int16_t aStretch,
1574 uint8_t aStyle,
1575 const uint8_t* aFontData,
1576 uint32_t aLength)
1577 {
1578 // The FT2 font needs the font data to persist, so we do NOT free it here
1579 // but instead pass ownership to the font entry.
1580 // Deallocation will happen later, when the font face is destroyed.
1581 return FT2FontEntry::CreateFontEntry(aFontName, aWeight, aStretch,
1582 aStyle, aFontData, aLength);
1583 }
1584
1585 void
GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily>> & aFamilyArray)1586 gfxFT2FontList::GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray)
1587 {
1588 for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1589 RefPtr<gfxFontFamily>& family = iter.Data();
1590 aFamilyArray.AppendElement(family);
1591 }
1592 for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) {
1593 RefPtr<gfxFontFamily>& family = iter.Data();
1594 aFamilyArray.AppendElement(family);
1595 }
1596 }
1597
1598 void
WillShutdown()1599 gfxFT2FontList::WillShutdown()
1600 {
1601 mozilla::scache::StartupCache* cache =
1602 mozilla::scache::StartupCache::GetSingleton();
1603 if (cache && mJarModifiedTime > 0) {
1604 cache->PutBuffer(JAR_LAST_MODIFED_TIME,
1605 (char*)&mJarModifiedTime, sizeof(mJarModifiedTime));
1606 }
1607 mFontNameCache = nullptr;
1608 }
1609