Bug 1744135 Part 2: Annotate macOS font loading with font name if a crash happens...
[gecko.git] / gfx / thebes / gfxFT2FontList.cpp
blob93e7cd291e4910c421a66b853168c4a3687ae68f
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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"
7 #include "mozilla/Base64.h"
8 #include "mozilla/FontPropertyTypes.h"
9 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/dom/ContentChild.h"
12 #include "gfxAndroidPlatform.h"
13 #include "mozilla/Omnijar.h"
14 #include "mozilla/UniquePtr.h"
15 #include "mozilla/UniquePtrExtensions.h"
16 #include "nsReadableUtils.h"
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)
23 #include "ft2build.h"
24 #include FT_FREETYPE_H
25 #include FT_TRUETYPE_TAGS_H
26 #include FT_TRUETYPE_TABLES_H
27 #include FT_MULTIPLE_MASTERS_H
28 #include "cairo-ft.h"
30 #include "gfxFT2FontList.h"
31 #include "gfxFT2Fonts.h"
32 #include "gfxFT2Utils.h"
33 #include "gfxUserFontSet.h"
34 #include "gfxFontUtils.h"
35 #include "SharedFontList-impl.h"
36 #include "harfbuzz/hb-ot.h" // for name ID constants
38 #include "nsServiceManagerUtils.h"
39 #include "nsIObserverService.h"
40 #include "nsTArray.h"
41 #include "nsUnicharUtils.h"
42 #include "nsCRT.h"
44 #include "nsDirectoryServiceUtils.h"
45 #include "nsDirectoryServiceDefs.h"
46 #include "nsAppDirectoryServiceDefs.h"
47 #include "nsIMemory.h"
48 #include "nsMemory.h"
49 #include "nsPresContext.h"
50 #include "gfxFontConstants.h"
52 #include "mozilla/EndianUtils.h"
53 #include "mozilla/Preferences.h"
54 #include "mozilla/scache/StartupCache.h"
55 #include "mozilla/Telemetry.h"
56 #include <fcntl.h>
57 #include <sys/mman.h>
58 #include <sys/stat.h>
60 #ifdef MOZ_WIDGET_ANDROID
61 # include "mozilla/jni/Utils.h"
62 # include <dlfcn.h>
63 #endif
65 using namespace mozilla;
66 using namespace mozilla::gfx;
68 static LazyLogModule sFontInfoLog("fontInfoLog");
70 #undef LOG
71 #define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args)
72 #define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug)
74 static __inline void BuildKeyNameFromFontName(nsACString& aName) {
75 ToLowerCase(aName);
78 // Helper to access the FT_Face for a given FT2FontEntry,
79 // creating a temporary face if the entry does not have one yet.
80 // This allows us to read font names, tables, etc if necessary
81 // without permanently instantiating a freetype face and consuming
82 // memory long-term.
83 // This may fail (resulting in a null FT_Face), e.g. if it fails to
84 // allocate memory to uncompress a font from omnijar.
85 already_AddRefed<SharedFTFace> FT2FontEntry::GetFTFace(bool aCommit) {
86 if (mFTFace) {
87 return do_AddRef(mFTFace);
90 NS_ASSERTION(!mFilename.IsEmpty(),
91 "can't use GetFTFace for fonts without a filename");
93 // A relative path (no initial "/") means this is a resource in
94 // omnijar, not an installed font on the device.
95 // The NS_ASSERTIONs here should never fail, as the resource must have
96 // been read successfully during font-list initialization or we'd never
97 // have created the font entry. The only legitimate runtime failure
98 // here would be memory allocation, in which case mFace remains null.
99 RefPtr<SharedFTFace> face;
100 if (mFilename[0] != '/') {
101 RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
102 nsZipItem* item = reader->GetItem(mFilename.get());
103 NS_ASSERTION(item, "failed to find zip entry");
105 uint32_t bufSize = item->RealSize();
106 uint8_t* fontDataBuf = static_cast<uint8_t*>(malloc(bufSize));
107 if (fontDataBuf) {
108 nsZipCursor cursor(item, reader, fontDataBuf, bufSize);
109 cursor.Copy(&bufSize);
110 NS_ASSERTION(bufSize == item->RealSize(), "error reading bundled font");
111 RefPtr<FTUserFontData> ufd = new FTUserFontData(fontDataBuf, bufSize);
112 face = ufd->CloneFace(mFTFontIndex);
113 if (!face) {
114 NS_WARNING("failed to create freetype face");
115 return nullptr;
118 } else {
119 face = Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex);
120 if (!face) {
121 NS_WARNING("failed to create freetype face");
122 return nullptr;
124 if (FT_Err_Ok != FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) &&
125 FT_Err_Ok !=
126 FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL)) {
127 NS_WARNING("failed to select Unicode or symbol charmap");
131 if (aCommit) {
132 mFTFace = face;
135 return face.forget();
138 FTUserFontData* FT2FontEntry::GetUserFontData() {
139 if (mFTFace && mFTFace->GetData()) {
140 return static_cast<FTUserFontData*>(mFTFace->GetData());
142 return nullptr;
146 * FT2FontEntry
147 * gfxFontEntry subclass corresponding to a specific face that can be
148 * rendered by freetype. This is associated with a face index in a
149 * file (normally a .ttf/.otf file holding a single face, but in principle
150 * there could be .ttc files with multiple faces).
151 * The FT2FontEntry can create the necessary FT_Face on demand, and can
152 * then create a Cairo font_face and scaled_font for drawing.
155 FT2FontEntry::~FT2FontEntry() {
156 if (mMMVar) {
157 FT_Done_MM_Var(mFTFace->GetFace()->glyph->library, mMMVar);
161 gfxFontEntry* FT2FontEntry::Clone() const {
162 MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
163 FT2FontEntry* fe = new FT2FontEntry(Name());
164 fe->mFilename = mFilename;
165 fe->mFTFontIndex = mFTFontIndex;
166 fe->mWeightRange = mWeightRange;
167 fe->mStretchRange = mStretchRange;
168 fe->mStyleRange = mStyleRange;
169 return fe;
172 gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle* aStyle) {
173 RefPtr<SharedFTFace> face = GetFTFace(true);
174 if (!face) {
175 return nullptr;
178 if (face->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
179 // Resolve variations from entry (descriptor) and style (property)
180 AutoTArray<gfxFontVariation, 8> settings;
181 GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle());
183 // If variations are present, we will not use our cached mFTFace
184 // but always create a new one as it will have custom variation
185 // coordinates applied.
186 if (!settings.IsEmpty()) {
187 // Create a separate FT_Face because we need to apply custom
188 // variation settings to it.
189 RefPtr<SharedFTFace> varFace;
190 if (!mFilename.IsEmpty() && mFilename[0] == '/') {
191 varFace =
192 Factory::NewSharedFTFace(nullptr, mFilename.get(), mFTFontIndex);
193 } else {
194 varFace = face->GetData()->CloneFace(mFTFontIndex);
196 if (varFace) {
197 gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings,
198 varFace->GetFace());
199 face = std::move(varFace);
204 int loadFlags = gfxPlatform::GetPlatform()->FontHintingEnabled()
205 ? FT_LOAD_DEFAULT
206 : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
207 if (face->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) {
208 loadFlags &= ~FT_LOAD_NO_AUTOHINT;
211 RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont);
212 if (!unscaledFont) {
213 unscaledFont = !mFilename.IsEmpty() && mFilename[0] == '/'
214 ? new UnscaledFontFreeType(mFilename.BeginReading(),
215 mFTFontIndex, mFTFace)
216 : new UnscaledFontFreeType(mFTFace);
217 mUnscaledFont = unscaledFont;
220 gfxFont* font =
221 new gfxFT2Font(unscaledFont, std::move(face), this, aStyle, loadFlags);
222 return font;
225 /* static */
226 FT2FontEntry* FT2FontEntry::CreateFontEntry(
227 const nsACString& aFontName, WeightRange aWeight, StretchRange aStretch,
228 SlantStyleRange aStyle, const uint8_t* aFontData, uint32_t aLength) {
229 // Ownership of aFontData is passed in here; the fontEntry must
230 // retain it as long as the FT_Face needs it, and ensure it is
231 // eventually deleted.
232 RefPtr<FTUserFontData> ufd = new FTUserFontData(aFontData, aLength);
233 RefPtr<SharedFTFace> face = ufd->CloneFace();
234 if (!face) {
235 return nullptr;
237 // Create our FT2FontEntry, which inherits the name of the userfont entry
238 // as it's not guaranteed that the face has valid names (bug 737315)
239 FT2FontEntry* fe =
240 FT2FontEntry::CreateFontEntry(aFontName, nullptr, 0, nullptr);
241 if (fe) {
242 fe->mFTFace = face;
243 fe->mStyleRange = aStyle;
244 fe->mWeightRange = aWeight;
245 fe->mStretchRange = aStretch;
246 fe->mIsDataUserFont = true;
248 return fe;
251 /* static */
252 FT2FontEntry* FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) {
253 FT2FontEntry* fe = new FT2FontEntry(aFLE.faceName());
254 fe->mFilename = aFLE.filepath();
255 fe->mFTFontIndex = aFLE.index();
256 fe->mWeightRange = WeightRange::FromScalar(aFLE.weightRange());
257 fe->mStretchRange = StretchRange::FromScalar(aFLE.stretchRange());
258 fe->mStyleRange = SlantStyleRange::FromScalar(aFLE.styleRange());
259 return fe;
262 // Extract font entry properties from an hb_face_t
263 static void SetPropertiesFromFace(gfxFontEntry* aFontEntry,
264 const hb_face_t* aFace) {
265 // OS2 width class to CSS 'stretch' mapping from
266 // https://docs.microsoft.com/en-gb/typography/opentype/spec/os2#uswidthclass
267 const float kOS2WidthToStretch[] = {
268 100, // (invalid, treat as normal)
269 50, // Ultra-condensed
270 62.5, // Extra-condensed
271 75, // Condensed
272 87.5, // Semi-condensed
273 100, // Normal
274 112.5, // Semi-expanded
275 125, // Expanded
276 150, // Extra-expanded
277 200 // Ultra-expanded
280 // Get the macStyle field from the 'head' table
281 hb_blob_t* blob = hb_face_reference_table(aFace, HB_TAG('h', 'e', 'a', 'd'));
282 unsigned int len;
283 const char* data = hb_blob_get_data(blob, &len);
284 uint16_t style = 0;
285 if (len >= sizeof(HeadTable)) {
286 const HeadTable* head = reinterpret_cast<const HeadTable*>(data);
287 style = head->macStyle;
289 hb_blob_destroy(blob);
291 // Get the OS/2 table for weight & width fields
292 blob = hb_face_reference_table(aFace, HB_TAG('O', 'S', '/', '2'));
293 data = hb_blob_get_data(blob, &len);
294 uint16_t os2weight = 400;
295 float stretch = 100.0;
296 if (len >= offsetof(OS2Table, fsType)) {
297 const OS2Table* os2 = reinterpret_cast<const OS2Table*>(data);
298 os2weight = os2->usWeightClass;
299 uint16_t os2width = os2->usWidthClass;
300 if (os2width < ArrayLength(kOS2WidthToStretch)) {
301 stretch = kOS2WidthToStretch[os2width];
304 hb_blob_destroy(blob);
306 aFontEntry->mStyleRange = SlantStyleRange(
307 (style & 2) ? FontSlantStyle::Italic() : FontSlantStyle::Normal());
308 aFontEntry->mWeightRange = WeightRange(FontWeight(int(os2weight)));
309 aFontEntry->mStretchRange = StretchRange(FontStretch(stretch));
312 // Used to create the font entry for installed faces on the device,
313 // when iterating over the fonts directories.
314 // We use the hb_face_t to retrieve the details needed for the font entry,
315 // but do *not* save a reference to it, nor create a cairo face.
316 /* static */
317 FT2FontEntry* FT2FontEntry::CreateFontEntry(const nsACString& aName,
318 const char* aFilename,
319 uint8_t aIndex,
320 const hb_face_t* aFace) {
321 FT2FontEntry* fe = new FT2FontEntry(aName);
322 fe->mFilename = aFilename;
323 fe->mFTFontIndex = aIndex;
325 if (aFace) {
326 SetPropertiesFromFace(fe, aFace);
327 } else {
328 // If nullptr is passed for aFace, the caller is intending to override
329 // these attributes anyway. We just set defaults here to be safe.
330 fe->mStyleRange = SlantStyleRange(FontSlantStyle::Normal());
331 fe->mWeightRange = WeightRange(FontWeight::Normal());
332 fe->mStretchRange = StretchRange(FontStretch::Normal());
335 return fe;
338 FT2FontEntry* gfxFT2Font::GetFontEntry() {
339 return static_cast<FT2FontEntry*>(mFontEntry.get());
342 // Copied/modified from similar code in gfxMacPlatformFontList.mm:
343 // Complex scripts will not render correctly unless Graphite or OT
344 // layout tables are present.
345 // For OpenType, we also check that the GSUB table supports the relevant
346 // script tag, to avoid using things like Arial Unicode MS for Lao (it has
347 // the characters, but lacks OpenType support).
349 // TODO: consider whether we should move this to gfxFontEntry and do similar
350 // cmap-masking on all platforms to avoid using fonts that won't shape
351 // properly.
353 nsresult FT2FontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
354 if (mCharacterMap || mShmemCharacterMap) {
355 return NS_OK;
358 RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
360 nsresult rv = NS_ERROR_NOT_AVAILABLE;
361 hb_blob_t* cmapBlob = GetFontTable(TTAG_cmap);
362 if (cmapBlob) {
363 unsigned int length;
364 const char* data = hb_blob_get_data(cmapBlob, &length);
365 rv = gfxFontUtils::ReadCMAP((const uint8_t*)data, length, *charmap,
366 mUVSOffset);
367 hb_blob_destroy(cmapBlob);
370 if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) {
371 // For downloadable fonts, trust the author and don't
372 // try to munge the cmap based on script shaping support.
374 // We also assume a Graphite font knows what it's doing,
375 // and provides whatever shaping is needed for the
376 // characters it supports, so only check/clear the
377 // complex-script ranges for non-Graphite fonts
379 // for layout support, check for the presence of opentype layout tables
380 bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B'));
382 for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
383 sr->rangeStart; sr++) {
384 // check to see if the cmap includes complex script codepoints
385 if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
386 // We check for GSUB here, as GPOS alone would not be ok.
387 if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) {
388 continue;
390 charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
395 #ifdef MOZ_WIDGET_ANDROID
396 // Hack for the SamsungDevanagari font, bug 1012365:
397 // pretend the font supports U+0972.
398 if (!charmap->test(0x0972) && charmap->test(0x0905) &&
399 charmap->test(0x0945)) {
400 charmap->set(0x0972);
402 #endif
404 mHasCmapTable = NS_SUCCEEDED(rv);
406 if (mHasCmapTable) {
407 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
408 fontlist::FontList* sharedFontList = pfl->SharedFontList();
409 if (!IsUserFont() && mShmemFace) {
410 mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
411 if (!TrySetShmemCharacterMap()) {
412 // Temporarily retain charmap, until the shared version is
413 // ready for use.
414 mCharacterMap = charmap;
416 } else {
417 mCharacterMap = pfl->FindCharMap(charmap);
419 } else {
420 // if error occurred, initialize to null cmap
421 mCharacterMap = new gfxCharacterMap();
424 return rv;
427 hb_face_t* FT2FontEntry::CreateHBFace() const {
428 hb_face_t* result = nullptr;
430 if (mFilename[0] == '/') {
431 // An absolute path means a normal file in the filesystem, so we can use
432 // hb_blob_create_from_file to read it.
433 hb_blob_t* fileBlob = hb_blob_create_from_file(mFilename.get());
434 if (hb_blob_get_length(fileBlob) > 0) {
435 result = hb_face_create(fileBlob, mFTFontIndex);
437 hb_blob_destroy(fileBlob);
438 } else {
439 // A relative path means an omnijar resource, which we may need to
440 // decompress to a temporary buffer.
441 RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
442 nsZipItem* item = reader->GetItem(mFilename.get());
443 MOZ_ASSERT(item, "failed to find zip entry");
444 if (item) {
445 // TODO(jfkthame):
446 // Check whether the item is compressed; if not, we could just get a
447 // pointer without needing to allocate a buffer and copy the data.
448 // (Currently this configuration isn't used for Gecko on Android.)
449 uint32_t length = item->RealSize();
450 uint8_t* buffer = static_cast<uint8_t*>(malloc(length));
451 if (buffer) {
452 nsZipCursor cursor(item, reader, buffer, length);
453 cursor.Copy(&length);
454 MOZ_ASSERT(length == item->RealSize(), "error reading font");
455 if (length == item->RealSize()) {
456 hb_blob_t* blob =
457 hb_blob_create((const char*)buffer, length,
458 HB_MEMORY_MODE_READONLY, buffer, free);
459 result = hb_face_create(blob, mFTFontIndex);
460 hb_blob_destroy(blob);
466 return result;
469 bool FT2FontEntry::HasFontTable(uint32_t aTableTag) {
470 if (mAvailableTables.Count() > 0) {
471 return mAvailableTables.Contains(aTableTag);
474 // If we haven't created a FreeType face already, try to avoid that by
475 // reading the available table tags via harfbuzz and caching in a hashset.
476 if (!mFTFace && !mFilename.IsEmpty()) {
477 hb_face_t* face = CreateHBFace();
478 if (face) {
479 // Read table tags in batches; 32 should be enough for most fonts in a
480 // single operation.
481 const unsigned TAG_BUF_LENGTH = 32;
482 hb_tag_t tags[TAG_BUF_LENGTH];
483 unsigned int startOffset = 0;
484 unsigned int totalTables = 0;
485 do {
486 unsigned int count = TAG_BUF_LENGTH;
487 // Updates count to the number of table tags actually retrieved
488 totalTables = hb_face_get_table_tags(face, startOffset, &count, tags);
489 startOffset += count;
490 while (count-- > 0) {
491 mAvailableTables.Insert(tags[count]);
493 } while (startOffset < totalTables);
494 hb_face_destroy(face);
495 } else {
496 // Failed to create the HarfBuzz face! The font is probably broken.
497 // Put a dummy entry in mAvailableTables so that we don't bother
498 // re-trying here.
499 mAvailableTables.Insert(uint32_t(-1));
501 return mAvailableTables.Contains(aTableTag);
504 RefPtr<SharedFTFace> face = GetFTFace();
505 return gfxFT2FontEntryBase::FaceHasTable(face, aTableTag);
508 nsresult FT2FontEntry::CopyFontTable(uint32_t aTableTag,
509 nsTArray<uint8_t>& aBuffer) {
510 RefPtr<SharedFTFace> face = GetFTFace();
511 return gfxFT2FontEntryBase::CopyFaceTable(face, aTableTag, aBuffer);
514 hb_blob_t* FT2FontEntry::GetFontTable(uint32_t aTableTag) {
515 if (FTUserFontData* userFontData = GetUserFontData()) {
516 // If there's a cairo font face, we may be able to return a blob
517 // that just wraps a range of the attached user font data
518 if (userFontData->FontData()) {
519 return gfxFontUtils::GetTableFromFontData(userFontData->FontData(),
520 aTableTag);
524 // If the FT_Face hasn't been instantiated, try to read table directly
525 // via harfbuzz API to avoid expensive FT_Face creation.
526 if (!mFTFace && !mFilename.IsEmpty()) {
527 hb_face_t* face = CreateHBFace();
528 if (face) {
529 hb_blob_t* result = hb_face_reference_table(face, aTableTag);
530 hb_face_destroy(face);
531 return result;
535 // Otherwise, use the default method (which in turn will call our
536 // implementation of CopyFontTable).
537 return gfxFontEntry::GetFontTable(aTableTag);
540 bool FT2FontEntry::HasVariations() {
541 if (!mHasVariationsInitialized) {
542 mHasVariationsInitialized = true;
543 if (mFTFace) {
544 mHasVariations =
545 mFTFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS;
546 } else {
547 mHasVariations = gfxPlatform::GetPlatform()->HasVariationFontSupport() &&
548 HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'));
551 return mHasVariations;
554 void FT2FontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes) {
555 if (!HasVariations()) {
556 return;
558 FT_MM_Var* mmVar = GetMMVar();
559 if (!mmVar) {
560 return;
562 gfxFT2Utils::GetVariationAxes(mmVar, aAxes);
565 void FT2FontEntry::GetVariationInstances(
566 nsTArray<gfxFontVariationInstance>& aInstances) {
567 if (!HasVariations()) {
568 return;
570 FT_MM_Var* mmVar = GetMMVar();
571 if (!mmVar) {
572 return;
574 gfxFT2Utils::GetVariationInstances(this, mmVar, aInstances);
577 FT_MM_Var* FT2FontEntry::GetMMVar() {
578 if (mMMVarInitialized) {
579 return mMMVar;
581 mMMVarInitialized = true;
582 RefPtr<SharedFTFace> face = GetFTFace(true);
583 if (!face) {
584 return nullptr;
586 if (FT_Err_Ok != FT_Get_MM_Var(face->GetFace(), &mMMVar)) {
587 mMMVar = nullptr;
589 return mMMVar;
592 void FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
593 FontListSizes* aSizes) const {
594 gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
595 aSizes->mFontListSize +=
596 mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
599 void FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
600 FontListSizes* aSizes) const {
601 aSizes->mFontListSize += aMallocSizeOf(this);
602 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
606 * FT2FontFamily
607 * A standard gfxFontFamily; just adds a method used to support sending
608 * the font list from chrome to content via IPC.
611 void FT2FontFamily::AddFacesToFontList(nsTArray<FontListEntry>* aFontList) {
612 for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) {
613 const FT2FontEntry* fe =
614 static_cast<const FT2FontEntry*>(mAvailableFonts[i].get());
615 if (!fe) {
616 continue;
619 aFontList->AppendElement(FontListEntry(
620 Name(), fe->Name(), fe->mFilename, fe->Weight().AsScalar(),
621 fe->Stretch().AsScalar(), fe->SlantStyle().AsScalar(), fe->mFTFontIndex,
622 Visibility()));
627 * Startup cache support for the font list:
628 * We store the list of families and faces, with their style attributes and the
629 * corresponding font files, in the startup cache.
630 * This allows us to recreate the gfxFT2FontList collection of families and
631 * faces without instantiating Freetype faces for each font file (in order to
632 * find their attributes), leading to significantly quicker startup.
635 #define CACHE_KEY "font.cached-list"
637 void gfxFT2FontList::CollectInitData(const FontListEntry& aFLE,
638 const nsCString& aPSName,
639 const nsCString& aFullName,
640 StandardFile aStdFile) {
641 nsAutoCString key(aFLE.familyName());
642 BuildKeyNameFromFontName(key);
643 mFaceInitData
644 .LookupOrInsertWith(
645 key,
646 [&] {
647 mFamilyInitData.AppendElement(
648 fontlist::Family::InitData{key, aFLE.familyName()});
649 return MakeUnique<nsTArray<fontlist::Face::InitData>>();
651 ->AppendElement(fontlist::Face::InitData{
652 aFLE.filepath(), aFLE.index(), false,
653 WeightRange::FromScalar(aFLE.weightRange()),
654 StretchRange::FromScalar(aFLE.stretchRange()),
655 SlantStyleRange::FromScalar(aFLE.styleRange())});
656 nsAutoCString psname(aPSName), fullname(aFullName);
657 if (!psname.IsEmpty()) {
658 ToLowerCase(psname);
659 mLocalNameTable.InsertOrUpdate(
660 psname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath()));
662 if (!fullname.IsEmpty()) {
663 ToLowerCase(fullname);
664 if (fullname != psname) {
665 mLocalNameTable.InsertOrUpdate(
666 fullname, fontlist::LocalFaceRec::InitData(key, aFLE.filepath()));
671 class FontNameCache {
672 public:
673 // Delimiters used in the cached font-list records we store in startupCache
674 static const char kFileSep = 0x1c;
675 static const char kGroupSep = 0x1d;
676 static const char kRecordSep = 0x1e;
677 static const char kFieldSep = 0x1f;
679 // Separator for font property ranges; we only look for this within a
680 // field that holds a serialized FontPropertyValue or Range, so there's no
681 // risk of conflicting with printable characters in font names.
682 // Note that this must be a character that will terminate strtof() parsing
683 // of a number.
684 static const char kRangeSep = ':';
686 // Creates the object but does NOT load the cached data from the startup
687 // cache; call Init() after creation to do that.
688 FontNameCache() : mMap(&mOps, sizeof(FNCMapEntry), 0), mWriteNeeded(false) {
689 // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to
690 // it to |mMap|'s constructor. A more normal approach here would be to
691 // have a static |sOps| member. Unfortunately, this mysteriously but
692 // consistently makes Fennec start-up slower, so we take this
693 // unorthodox approach instead. It's safe because PLDHashTable's
694 // constructor doesn't dereference the pointer; it just makes a copy of
695 // it.
696 mOps = (PLDHashTableOps){StringHash, HashMatchEntry, MoveEntry,
697 PLDHashTable::ClearEntryStub, nullptr};
699 MOZ_ASSERT(XRE_IsParentProcess(),
700 "FontNameCache should only be used in chrome process");
701 mCache = mozilla::scache::StartupCache::GetSingleton();
704 ~FontNameCache() { WriteCache(); }
706 size_t EntryCount() const { return mMap.EntryCount(); }
708 void DropStaleEntries() {
709 for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
710 auto entry = static_cast<FNCMapEntry*>(iter.Get());
711 if (!entry->mFileExists) {
712 iter.Remove();
717 void WriteCache() {
718 if (!mWriteNeeded || !mCache) {
719 return;
722 LOG(("Writing FontNameCache:"));
723 nsAutoCString buf;
724 for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
725 auto entry = static_cast<FNCMapEntry*>(iter.Get());
726 MOZ_ASSERT(entry->mFileExists);
727 buf.Append(entry->mFilename);
728 buf.Append(kGroupSep);
729 buf.Append(entry->mFaces);
730 buf.Append(kGroupSep);
731 buf.AppendInt(entry->mTimestamp);
732 buf.Append(kGroupSep);
733 buf.AppendInt(entry->mFilesize);
734 buf.Append(kFileSep);
737 LOG(("putting FontNameCache to " CACHE_KEY ", length %zu",
738 buf.Length() + 1));
739 mCache->PutBuffer(CACHE_KEY, UniquePtr<char[]>(ToNewCString(buf)),
740 buf.Length() + 1);
741 mWriteNeeded = false;
744 // This may be called more than once (if we re-load the font list).
745 void Init() {
746 if (!mCache) {
747 return;
750 uint32_t size;
751 const char* cur;
752 if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &cur, &size))) {
753 LOG(("no cache of " CACHE_KEY));
754 return;
757 LOG(("got: %u bytes from the cache " CACHE_KEY, size));
759 mMap.Clear();
760 mWriteNeeded = false;
762 while (const char* fileEnd = strchr(cur, kFileSep)) {
763 // The cached record for one file is at [cur, fileEnd].
765 // Find end of field that starts at aStart, terminated by kGroupSep or
766 // end of record.
767 auto endOfField = [=](const char* aStart) -> const char* {
768 MOZ_ASSERT(aStart <= fileEnd);
769 const char* end = static_cast<const char*>(
770 memchr(aStart, kGroupSep, fileEnd - aStart));
771 if (end) {
772 return end;
774 return fileEnd;
777 // Advance aStart and aEnd to indicate the range of the next field and
778 // return true, or just return false if already at end of record.
779 auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool {
780 if (aEnd < fileEnd) {
781 aStart = aEnd + 1;
782 aEnd = endOfField(aStart);
783 return true;
785 return false;
788 const char* end = endOfField(cur);
789 nsCString filename(cur, end - cur);
790 if (!nextField(cur, end)) {
791 break;
793 nsCString faceList(cur, end - cur);
794 if (!nextField(cur, end)) {
795 break;
797 uint32_t timestamp = strtoul(cur, nullptr, 10);
798 if (!nextField(cur, end)) {
799 break;
801 uint32_t filesize = strtoul(cur, nullptr, 10);
803 auto mapEntry =
804 static_cast<FNCMapEntry*>(mMap.Add(filename.get(), fallible));
805 if (mapEntry) {
806 mapEntry->mFilename.Assign(filename);
807 mapEntry->mTimestamp = timestamp;
808 mapEntry->mFilesize = filesize;
809 mapEntry->mFaces.Assign(faceList);
810 // entries from the startupcache are marked "non-existing"
811 // until we have confirmed that the file still exists
812 mapEntry->mFileExists = false;
815 cur = fileEnd + 1;
819 void GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList,
820 uint32_t* aTimestamp, uint32_t* aFilesize) {
821 auto entry = static_cast<FNCMapEntry*>(mMap.Search(aFileName.get()));
822 if (entry) {
823 *aTimestamp = entry->mTimestamp;
824 *aFilesize = entry->mFilesize;
825 aFaceList.Assign(entry->mFaces);
826 // this entry does correspond to an existing file
827 // (although it might not be up-to-date, in which case
828 // it will get overwritten via CacheFileInfo)
829 entry->mFileExists = true;
833 void CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList,
834 uint32_t aTimestamp, uint32_t aFilesize) {
835 auto entry = static_cast<FNCMapEntry*>(mMap.Add(aFileName.get(), fallible));
836 if (entry) {
837 entry->mFilename.Assign(aFileName);
838 entry->mTimestamp = aTimestamp;
839 entry->mFilesize = aFilesize;
840 entry->mFaces.Assign(aFaceList);
841 entry->mFileExists = true;
843 mWriteNeeded = true;
846 private:
847 mozilla::scache::StartupCache* mCache;
848 PLDHashTable mMap;
849 bool mWriteNeeded;
851 PLDHashTableOps mOps;
853 struct FNCMapEntry : public PLDHashEntryHdr {
854 public:
855 nsCString mFilename;
856 uint32_t mTimestamp;
857 uint32_t mFilesize;
858 nsCString mFaces;
859 bool mFileExists;
862 static PLDHashNumber StringHash(const void* key) {
863 return HashString(reinterpret_cast<const char*>(key));
866 static bool HashMatchEntry(const PLDHashEntryHdr* aHdr, const void* key) {
867 const FNCMapEntry* entry = static_cast<const FNCMapEntry*>(aHdr);
868 return entry->mFilename.Equals(reinterpret_cast<const char*>(key));
871 static void MoveEntry(PLDHashTable* table, const PLDHashEntryHdr* aFrom,
872 PLDHashEntryHdr* aTo) {
873 FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo);
874 const FNCMapEntry* from = static_cast<const FNCMapEntry*>(aFrom);
875 to->mFilename.Assign(from->mFilename);
876 to->mTimestamp = from->mTimestamp;
877 to->mFilesize = from->mFilesize;
878 to->mFaces.Assign(from->mFaces);
879 to->mFileExists = from->mFileExists;
883 /***************************************************************
885 * gfxFT2FontList
889 // For Mobile, we use gfxFT2Fonts, and we build the font list by directly
890 // scanning the system's Fonts directory for OpenType and TrueType files.
892 #define JAR_LAST_MODIFED_TIME "jar-last-modified-time"
894 class WillShutdownObserver : public nsIObserver {
895 public:
896 NS_DECL_ISUPPORTS
897 NS_DECL_NSIOBSERVER
899 explicit WillShutdownObserver(gfxFT2FontList* aFontList)
900 : mFontList(aFontList) {}
902 void Remove() {
903 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
904 if (obs) {
905 obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
907 mFontList = nullptr;
910 protected:
911 virtual ~WillShutdownObserver() = default;
913 gfxFT2FontList* mFontList;
916 NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver)
918 NS_IMETHODIMP
919 WillShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
920 const char16_t* aData) {
921 if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
922 mFontList->WillShutdown();
923 } else {
924 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
926 return NS_OK;
929 gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) {
930 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
931 if (obs) {
932 mObserver = new WillShutdownObserver(this);
933 obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
937 gfxFT2FontList::~gfxFT2FontList() {
938 if (mObserver) {
939 mObserver->Remove();
943 bool gfxFT2FontList::AppendFacesFromCachedFaceList(CollectFunc aCollectFace,
944 const nsCString& aFileName,
945 const nsCString& aFaceList,
946 StandardFile aStdFile) {
947 const char* start = aFaceList.get();
948 int count = 0;
950 while (const char* recEnd = strchr(start, FontNameCache::kRecordSep)) {
951 auto endOfField = [=](const char* aStart) -> const char* {
952 MOZ_ASSERT(aStart <= recEnd);
953 const char* end = static_cast<const char*>(
954 memchr(aStart, FontNameCache::kFieldSep, recEnd - aStart));
955 if (end) {
956 return end;
958 return recEnd;
961 auto nextField = [=](const char*& aStart, const char*& aEnd) -> bool {
962 if (aEnd < recEnd) {
963 aStart = aEnd + 1;
964 aEnd = endOfField(aStart);
965 return true;
967 return false;
970 const char* end = endOfField(start);
971 nsAutoCString familyName(start, end - start);
972 nsAutoCString key(familyName);
973 ToLowerCase(key);
975 if (!nextField(start, end)) {
976 break;
978 nsAutoCString faceName(start, end - start);
980 if (!nextField(start, end)) {
981 break;
983 uint32_t index = strtoul(start, nullptr, 10);
985 if (!nextField(start, end)) {
986 break;
988 nsAutoCString minStyle(start, end - start);
989 nsAutoCString maxStyle(minStyle);
990 int32_t colon = minStyle.FindChar(FontNameCache::kRangeSep);
991 if (colon > 0) {
992 maxStyle.Assign(minStyle.BeginReading() + colon + 1);
993 minStyle.Truncate(colon);
996 if (!nextField(start, end)) {
997 break;
999 char* limit;
1000 float minWeight = strtof(start, &limit);
1001 float maxWeight;
1002 if (*limit == FontNameCache::kRangeSep && limit + 1 < end) {
1003 maxWeight = strtof(limit + 1, nullptr);
1004 } else {
1005 maxWeight = minWeight;
1008 if (!nextField(start, end)) {
1009 break;
1011 float minStretch = strtof(start, &limit);
1012 float maxStretch;
1013 if (*limit == FontNameCache::kRangeSep && limit + 1 < end) {
1014 maxStretch = strtof(limit + 1, nullptr);
1015 } else {
1016 maxStretch = minStretch;
1019 if (!nextField(start, end)) {
1020 break;
1022 nsAutoCString psname(start, end - start);
1024 if (!nextField(start, end)) {
1025 break;
1027 nsAutoCString fullname(start, end - start);
1029 if (!nextField(start, end)) {
1030 break;
1032 FontVisibility visibility = FontVisibility(strtoul(start, nullptr, 10));
1034 FontListEntry fle(
1035 familyName, faceName, aFileName,
1036 WeightRange(FontWeight(minWeight), FontWeight(maxWeight)).AsScalar(),
1037 StretchRange(FontStretch(minStretch), FontStretch(maxStretch))
1038 .AsScalar(),
1039 SlantStyleRange(FontSlantStyle::FromString(minStyle.get()),
1040 FontSlantStyle::FromString(maxStyle.get()))
1041 .AsScalar(),
1042 index, visibility);
1044 aCollectFace(fle, psname, fullname, aStdFile);
1045 count++;
1047 start = recEnd + 1;
1050 return count > 0;
1053 void FT2FontEntry::AppendToFaceList(nsCString& aFaceList,
1054 const nsACString& aFamilyName,
1055 const nsACString& aPSName,
1056 const nsACString& aFullName,
1057 FontVisibility aVisibility) {
1058 aFaceList.Append(aFamilyName);
1059 aFaceList.Append(FontNameCache::kFieldSep);
1060 aFaceList.Append(Name());
1061 aFaceList.Append(FontNameCache::kFieldSep);
1062 aFaceList.AppendInt(mFTFontIndex);
1063 aFaceList.Append(FontNameCache::kFieldSep);
1064 // Note that ToString() appends to the destination string without
1065 // replacing existing contents (see FontPropertyTypes.h)
1066 SlantStyle().Min().ToString(aFaceList);
1067 aFaceList.Append(FontNameCache::kRangeSep);
1068 SlantStyle().Max().ToString(aFaceList);
1069 aFaceList.Append(FontNameCache::kFieldSep);
1070 aFaceList.AppendFloat(Weight().Min().ToFloat());
1071 aFaceList.Append(FontNameCache::kRangeSep);
1072 aFaceList.AppendFloat(Weight().Max().ToFloat());
1073 aFaceList.Append(FontNameCache::kFieldSep);
1074 aFaceList.AppendFloat(Stretch().Min().Percentage());
1075 aFaceList.Append(FontNameCache::kRangeSep);
1076 aFaceList.AppendFloat(Stretch().Max().Percentage());
1077 aFaceList.Append(FontNameCache::kFieldSep);
1078 aFaceList.Append(aPSName);
1079 aFaceList.Append(FontNameCache::kFieldSep);
1080 aFaceList.Append(aFullName);
1081 aFaceList.Append(FontNameCache::kFieldSep);
1082 aFaceList.AppendInt(int(aVisibility));
1083 aFaceList.Append(FontNameCache::kRecordSep);
1086 void FT2FontEntry::CheckForBrokenFont(gfxFontFamily* aFamily) {
1087 // note if the family is in the "bad underline" blocklist
1088 if (aFamily->IsBadUnderlineFamily()) {
1089 mIsBadUnderlineFont = true;
1091 nsAutoCString familyKey(aFamily->Name());
1092 BuildKeyNameFromFontName(familyKey);
1093 CheckForBrokenFont(familyKey);
1096 void FT2FontEntry::CheckForBrokenFont(const nsACString& aFamilyKey) {
1097 // bug 721719 - set the IgnoreGSUB flag on entries for Roboto
1098 // because of unwanted on-by-default "ae" ligature.
1099 // (See also AppendFaceFromFontListEntry.)
1100 if (aFamilyKey.EqualsLiteral("roboto")) {
1101 mIgnoreGSUB = true;
1102 return;
1105 // bug 706888 - set the IgnoreGSUB flag on the broken version of
1106 // Droid Sans Arabic from certain phones, as identified by the
1107 // font checksum in the 'head' table
1108 if (aFamilyKey.EqualsLiteral("droid sans arabic")) {
1109 RefPtr<SharedFTFace> face = GetFTFace();
1110 if (face) {
1111 const TT_Header* head = static_cast<const TT_Header*>(
1112 FT_Get_Sfnt_Table(face->GetFace(), ft_sfnt_head));
1113 if (head && head->CheckSum_Adjust == 0xe445242) {
1114 mIgnoreGSUB = true;
1120 void gfxFT2FontList::AppendFacesFromBlob(
1121 const nsCString& aFileName, StandardFile aStdFile, hb_blob_t* aBlob,
1122 FontNameCache* aCache, uint32_t aTimestamp, uint32_t aFilesize) {
1123 nsCString newFaceList;
1124 uint32_t numFaces = 1;
1125 unsigned int length;
1126 const char* data = hb_blob_get_data(aBlob, &length);
1127 // Check for a possible TrueType Collection
1128 if (length >= sizeof(TTCHeader)) {
1129 const TTCHeader* ttc = reinterpret_cast<const TTCHeader*>(data);
1130 if (ttc->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) {
1131 numFaces = ttc->numFonts;
1134 for (unsigned int index = 0; index < numFaces; index++) {
1135 hb_face_t* face = hb_face_create(aBlob, index);
1136 if (face != hb_face_get_empty()) {
1137 AddFaceToList(aFileName, index, aStdFile, face, newFaceList);
1139 hb_face_destroy(face);
1141 if (aCache && !newFaceList.IsEmpty()) {
1142 aCache->CacheFileInfo(aFileName, newFaceList, aTimestamp, aFilesize);
1146 void gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName,
1147 FontNameCache* aCache,
1148 StandardFile aStdFile) {
1149 nsCString cachedFaceList;
1150 uint32_t filesize = 0, timestamp = 0;
1151 if (aCache) {
1152 aCache->GetInfoForFile(aFileName, cachedFaceList, &timestamp, &filesize);
1155 struct stat s;
1156 int statRetval = stat(aFileName.get(), &s);
1157 if (!cachedFaceList.IsEmpty() && 0 == statRetval &&
1158 uint32_t(s.st_mtime) == timestamp && s.st_size == filesize) {
1159 CollectFunc unshared =
1160 [](const FontListEntry& aFLE, const nsCString& aPSName,
1161 const nsCString& aFullName, StandardFile aStdFile) {
1162 PlatformFontList()->AppendFaceFromFontListEntry(aFLE, aStdFile);
1164 CollectFunc shared = [](const FontListEntry& aFLE, const nsCString& aPSName,
1165 const nsCString& aFullName, StandardFile aStdFile) {
1166 PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile);
1168 if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared,
1169 aFileName, cachedFaceList, aStdFile)) {
1170 LOG(("using cached font info for %s", aFileName.get()));
1171 return;
1175 hb_blob_t* fileBlob = hb_blob_create_from_file(aFileName.get());
1176 if (hb_blob_get_length(fileBlob) > 0) {
1177 LOG(("reading font info via harfbuzz for %s", aFileName.get()));
1178 AppendFacesFromBlob(aFileName, aStdFile, fileBlob,
1179 0 == statRetval ? aCache : nullptr, s.st_mtime,
1180 s.st_size);
1182 hb_blob_destroy(fileBlob);
1185 void gfxFT2FontList::FindFontsInOmnijar(FontNameCache* aCache) {
1186 bool jarChanged = false;
1188 mozilla::scache::StartupCache* cache =
1189 mozilla::scache::StartupCache::GetSingleton();
1190 const char* cachedModifiedTimeBuf;
1191 uint32_t longSize;
1192 if (cache &&
1193 NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME,
1194 &cachedModifiedTimeBuf, &longSize)) &&
1195 longSize == sizeof(int64_t)) {
1196 nsCOMPtr<nsIFile> jarFile = Omnijar::GetPath(Omnijar::Type::GRE);
1197 jarFile->GetLastModifiedTime(&mJarModifiedTime);
1198 if (mJarModifiedTime > LittleEndian::readInt64(cachedModifiedTimeBuf)) {
1199 jarChanged = true;
1203 static const char* sJarSearchPaths[] = {
1204 "res/fonts/*.ttf$",
1206 RefPtr<nsZipArchive> reader = Omnijar::GetReader(Omnijar::Type::GRE);
1207 for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) {
1208 nsZipFind* find;
1209 if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) {
1210 const char* path;
1211 uint16_t len;
1212 while (NS_SUCCEEDED(find->FindNext(&path, &len))) {
1213 nsCString entryName(path, len);
1214 AppendFacesFromOmnijarEntry(reader, entryName, aCache, jarChanged);
1216 delete find;
1221 static void GetName(hb_face_t* aFace, hb_ot_name_id_t aNameID,
1222 nsACString& aName) {
1223 unsigned int n = 0;
1224 n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n, nullptr);
1225 if (n) {
1226 aName.SetLength(n++); // increment n to account for NUL terminator
1227 n = hb_ot_name_get_utf8(aFace, aNameID, HB_LANGUAGE_INVALID, &n,
1228 aName.BeginWriting());
1232 // Given the harfbuzz face corresponding to an entryName and face index,
1233 // add the face to the available font list and to the faceList string
1234 void gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex,
1235 StandardFile aStdFile, hb_face_t* aFace,
1236 nsCString& aFaceList) {
1237 nsAutoCString familyName;
1238 bool preferTypographicNames = true;
1239 GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY, familyName);
1240 if (familyName.IsEmpty()) {
1241 preferTypographicNames = false;
1242 GetName(aFace, HB_OT_NAME_ID_FONT_FAMILY, familyName);
1244 if (familyName.IsEmpty()) {
1245 return;
1248 nsAutoCString fullname;
1249 GetName(aFace, HB_OT_NAME_ID_FULL_NAME, fullname);
1250 if (fullname.IsEmpty()) {
1251 // Construct fullname from family + style
1252 fullname = familyName;
1253 nsAutoCString styleName;
1254 if (preferTypographicNames) {
1255 GetName(aFace, HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, styleName);
1257 if (styleName.IsEmpty()) {
1258 GetName(aFace, HB_OT_NAME_ID_FONT_SUBFAMILY, styleName);
1260 if (!styleName.IsEmpty() && !styleName.EqualsLiteral("Regular")) {
1261 fullname.Append(' ');
1262 fullname.Append(styleName);
1266 // Build the font entry name and create an FT2FontEntry,
1267 // but do -not- keep a reference to the FT_Face.
1268 // (When using the shared font list, this entry will not be retained,
1269 // it is used only to call AppendToFaceList.)
1270 RefPtr<FT2FontEntry> fe =
1271 FT2FontEntry::CreateFontEntry(fullname, aEntryName.get(), aIndex, aFace);
1273 if (fe) {
1274 fe->mStandardFace = (aStdFile == kStandard);
1275 nsAutoCString familyKey(familyName);
1276 BuildKeyNameFromFontName(familyKey);
1278 FontVisibility visibility = FontVisibility::Unknown;
1280 nsAutoCString psname;
1281 GetName(aFace, HB_OT_NAME_ID_POSTSCRIPT_NAME, psname);
1283 if (SharedFontList()) {
1284 FontListEntry fle(familyName, fe->Name(), fe->mFilename,
1285 fe->Weight().AsScalar(), fe->Stretch().AsScalar(),
1286 fe->SlantStyle().AsScalar(), fe->mFTFontIndex,
1287 visibility);
1288 CollectInitData(fle, psname, fullname, aStdFile);
1289 } else {
1290 RefPtr<gfxFontFamily> family =
1291 mFontFamilies.LookupOrInsertWith(familyKey, [&] {
1292 auto family = MakeRefPtr<FT2FontFamily>(familyName, visibility);
1293 if (mSkipSpaceLookupCheckFamilies.Contains(familyKey)) {
1294 family->SetSkipSpaceFeatureCheck(true);
1296 if (mBadUnderlineFamilyNames.ContainsSorted(familyKey)) {
1297 family->SetBadUnderlineFamily();
1299 return family;
1301 family->AddFontEntry(fe);
1302 fe->CheckForBrokenFont(family);
1305 fe->AppendToFaceList(aFaceList, familyName, psname, fullname, visibility);
1306 if (LOG_ENABLED()) {
1307 nsAutoCString weightString;
1308 fe->Weight().ToString(weightString);
1309 nsAutoCString stretchString;
1310 fe->Stretch().ToString(stretchString);
1311 LOG(
1312 ("(fontinit) added (%s) to family (%s)"
1313 " with style: %s weight: %s stretch: %s",
1314 fe->Name().get(), familyName.get(),
1315 fe->IsItalic() ? "italic" : "normal", weightString.get(),
1316 stretchString.get()));
1321 void gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive,
1322 const nsCString& aEntryName,
1323 FontNameCache* aCache,
1324 bool aJarChanged) {
1325 nsCString faceList;
1326 if (aCache && !aJarChanged) {
1327 uint32_t filesize, timestamp;
1328 aCache->GetInfoForFile(aEntryName, faceList, &timestamp, &filesize);
1329 if (faceList.Length() > 0) {
1330 CollectFunc unshared =
1331 [](const FontListEntry& aFLE, const nsCString& aPSName,
1332 const nsCString& aFullName, StandardFile aStdFile) {
1333 PlatformFontList()->AppendFaceFromFontListEntry(aFLE, aStdFile);
1335 CollectFunc shared = [](const FontListEntry& aFLE,
1336 const nsCString& aPSName,
1337 const nsCString& aFullName,
1338 StandardFile aStdFile) {
1339 PlatformFontList()->CollectInitData(aFLE, aPSName, aFullName, aStdFile);
1341 if (AppendFacesFromCachedFaceList(SharedFontList() ? shared : unshared,
1342 aEntryName, faceList, kStandard)) {
1343 return;
1348 nsZipItem* item = aArchive->GetItem(aEntryName.get());
1349 NS_ASSERTION(item, "failed to find zip entry");
1351 uint32_t bufSize = item->RealSize();
1353 // We use fallible allocation here; if there's not enough RAM, we'll simply
1354 // ignore the bundled fonts and fall back to the device's installed fonts.
1355 char* buffer = static_cast<char*>(malloc(bufSize));
1356 if (!buffer) {
1357 return;
1360 nsZipCursor cursor(item, aArchive, (uint8_t*)buffer, bufSize);
1361 uint8_t* data = cursor.Copy(&bufSize);
1362 MOZ_ASSERT(data && bufSize == item->RealSize(), "error reading bundled font");
1363 if (!data) {
1364 return;
1367 hb_blob_t* blob =
1368 hb_blob_create(buffer, bufSize, HB_MEMORY_MODE_READONLY, buffer, free);
1369 AppendFacesFromBlob(aEntryName, kStandard, blob, aCache, 0, bufSize);
1370 hb_blob_destroy(blob);
1373 // Called on each family after all fonts are added to the list;
1374 // if aSortFaces is true this will sort faces to give priority to "standard"
1375 // font files.
1376 static void FinalizeFamilyMemberList(nsCStringHashKey::KeyType aKey,
1377 const RefPtr<gfxFontFamily>& aFamily,
1378 bool aSortFaces) {
1379 gfxFontFamily* family = aFamily.get();
1381 family->SetHasStyles(true);
1383 if (aSortFaces) {
1384 family->SortAvailableFonts();
1386 family->CheckForSimpleFamily();
1389 void gfxFT2FontList::FindFonts() {
1390 MOZ_ASSERT(XRE_IsParentProcess());
1392 // Chrome process: get the cached list (if any)
1393 if (!mFontNameCache) {
1394 mFontNameCache = MakeUnique<FontNameCache>();
1396 mFontNameCache->Init();
1398 #if defined(MOZ_WIDGET_ANDROID)
1399 // Android API 29+ provides system font and font matcher API for native code.
1400 typedef void* (*_ASystemFontIterator_open)();
1401 typedef void* (*_ASystemFontIterator_next)(void*);
1402 typedef void (*_ASystemFontIterator_close)(void*);
1403 typedef const char* (*_AFont_getFontFilePath)(const void*);
1404 typedef void (*_AFont_close)(void*);
1406 static _ASystemFontIterator_open systemFontIterator_open = nullptr;
1407 static _ASystemFontIterator_next systemFontIterator_next = nullptr;
1408 static _ASystemFontIterator_close systemFontIterator_close = nullptr;
1409 static _AFont_getFontFilePath font_getFontFilePath = nullptr;
1410 static _AFont_close font_close = nullptr;
1412 static bool firstTime = true;
1414 if (firstTime) {
1415 if (jni::GetAPIVersion() >= 29) {
1416 void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
1417 MOZ_ASSERT(handle);
1419 systemFontIterator_open =
1420 (_ASystemFontIterator_open)dlsym(handle, "ASystemFontIterator_open");
1421 systemFontIterator_next =
1422 (_ASystemFontIterator_next)dlsym(handle, "ASystemFontIterator_next");
1423 systemFontIterator_close = (_ASystemFontIterator_close)dlsym(
1424 handle, "ASystemFontIterator_close");
1425 font_getFontFilePath =
1426 (_AFont_getFontFilePath)dlsym(handle, "AFont_getFontFilePath");
1427 font_close = (_AFont_close)dlsym(handle, "AFont_close");
1429 if (NS_WARN_IF(!systemFontIterator_next) ||
1430 NS_WARN_IF(!systemFontIterator_close) ||
1431 NS_WARN_IF(!font_getFontFilePath) || NS_WARN_IF(!font_close)) {
1432 // Since any functions aren't resolved, use old way to enumerate fonts.
1433 systemFontIterator_open = nullptr;
1436 firstTime = false;
1439 bool useSystemFontAPI = !!systemFontIterator_open;
1440 if (useSystemFontAPI) {
1441 void* iter = systemFontIterator_open();
1442 if (iter) {
1443 void* font = systemFontIterator_next(iter);
1444 while (font) {
1445 nsAutoCString path(font_getFontFilePath(font));
1446 AppendFacesFromFontFile(path, mFontNameCache.get(), kStandard);
1447 font_close(font);
1448 font = systemFontIterator_next(iter);
1451 systemFontIterator_close(iter);
1452 } else {
1453 useSystemFontAPI = false;
1457 if (!useSystemFontAPI)
1458 #endif
1460 // ANDROID_ROOT is the root of the android system, typically /system;
1461 // font files are in /$ANDROID_ROOT/fonts/
1462 nsCString root;
1463 char* androidRoot = PR_GetEnv("ANDROID_ROOT");
1464 if (androidRoot) {
1465 root = androidRoot;
1466 } else {
1467 root = "/system"_ns;
1469 root.AppendLiteral("/fonts");
1471 FindFontsInDir(root, mFontNameCache.get());
1474 // Look for fonts stored in omnijar, unless we're on a low-memory
1475 // device where we don't want to spend the RAM to decompress them.
1476 // (Prefs may disable this, or force-enable it even with low memory.)
1477 bool lowmem;
1478 nsCOMPtr<nsIMemory> mem = nsMemory::GetGlobalMemoryService();
1479 if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() > 0 ||
1480 (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() < 0 &&
1481 NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem)) {
1482 TimeStamp start = TimeStamp::Now();
1483 FindFontsInOmnijar(mFontNameCache.get());
1484 TimeStamp end = TimeStamp::Now();
1485 Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
1486 (end - start).ToMilliseconds());
1489 // Look for downloaded fonts in a profile-agnostic "fonts" directory.
1490 nsCOMPtr<nsIProperties> dirSvc =
1491 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
1492 if (dirSvc) {
1493 nsCOMPtr<nsIFile> appDir;
1494 nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
1495 getter_AddRefs(appDir));
1496 if (NS_SUCCEEDED(rv)) {
1497 appDir->AppendNative("fonts"_ns);
1498 nsCString localPath;
1499 if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) {
1500 FindFontsInDir(localPath, mFontNameCache.get());
1505 // look for locally-added fonts in a "fonts" subdir of the profile
1506 nsCOMPtr<nsIFile> localDir;
1507 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1508 getter_AddRefs(localDir));
1509 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(u"fonts"_ns))) {
1510 nsCString localPath;
1511 rv = localDir->GetNativePath(localPath);
1512 if (NS_SUCCEEDED(rv)) {
1513 FindFontsInDir(localPath, mFontNameCache.get());
1517 mFontNameCache->DropStaleEntries();
1518 if (!mFontNameCache->EntryCount()) {
1519 // if we can't find any usable fonts, we are doomed!
1520 MOZ_CRASH("No font files found");
1523 // Write out FontCache data if needed
1524 WriteCache();
1527 void gfxFT2FontList::WriteCache() {
1528 if (mFontNameCache) {
1529 mFontNameCache->WriteCache();
1531 mozilla::scache::StartupCache* cache =
1532 mozilla::scache::StartupCache::GetSingleton();
1533 if (cache && mJarModifiedTime > 0) {
1534 const size_t bufSize = sizeof(mJarModifiedTime);
1535 auto buf = MakeUnique<char[]>(bufSize);
1536 LittleEndian::writeInt64(buf.get(), mJarModifiedTime);
1538 LOG(("WriteCache: putting Jar, length %zu", bufSize));
1539 cache->PutBuffer(JAR_LAST_MODIFED_TIME, std::move(buf), bufSize);
1541 LOG(("Done with writecache"));
1544 void gfxFT2FontList::FindFontsInDir(const nsCString& aDir,
1545 FontNameCache* aFNC) {
1546 static const char* sStandardFonts[] = {"DroidSans.ttf",
1547 "DroidSans-Bold.ttf",
1548 "DroidSerif-Regular.ttf",
1549 "DroidSerif-Bold.ttf",
1550 "DroidSerif-Italic.ttf",
1551 "DroidSerif-BoldItalic.ttf",
1552 "DroidSansMono.ttf",
1553 "DroidSansArabic.ttf",
1554 "DroidSansHebrew.ttf",
1555 "DroidSansThai.ttf",
1556 "MTLmr3m.ttf",
1557 "MTLc3m.ttf",
1558 "NanumGothic.ttf",
1559 "DroidSansJapanese.ttf",
1560 "DroidSansFallback.ttf"};
1562 DIR* d = opendir(aDir.get());
1563 if (!d) {
1564 return;
1567 struct dirent* ent = nullptr;
1568 while ((ent = readdir(d)) != nullptr) {
1569 const char* ext = strrchr(ent->d_name, '.');
1570 if (!ext) {
1571 continue;
1573 if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".otf") == 0 ||
1574 strcasecmp(ext, ".woff") == 0 || strcasecmp(ext, ".ttc") == 0) {
1575 bool isStdFont = false;
1576 for (unsigned int i = 0; i < ArrayLength(sStandardFonts) && !isStdFont;
1577 i++) {
1578 isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0;
1581 nsCString s(aDir);
1582 s.Append('/');
1583 s.Append(ent->d_name);
1585 // Add the face(s) from this file to our font list;
1586 // note that if we have cached info for this file in fnc,
1587 // and the file is unchanged, we won't actually need to read it.
1588 // If the file is new/changed, this will update the FontNameCache.
1589 AppendFacesFromFontFile(s, aFNC, isStdFont ? kStandard : kUnknown);
1593 closedir(d);
1596 void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE,
1597 StandardFile aStdFile) {
1598 FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE);
1599 if (fe) {
1600 nsAutoCString key(aFLE.familyName());
1601 BuildKeyNameFromFontName(key);
1602 fe->mStandardFace = (aStdFile == kStandard);
1603 RefPtr<gfxFontFamily> family = mFontFamilies.LookupOrInsertWith(key, [&] {
1604 auto family =
1605 MakeRefPtr<FT2FontFamily>(aFLE.familyName(), aFLE.visibility());
1606 if (mSkipSpaceLookupCheckFamilies.Contains(key)) {
1607 family->SetSkipSpaceFeatureCheck(true);
1609 if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
1610 family->SetBadUnderlineFamily();
1612 return family;
1614 family->AddFontEntry(fe);
1616 fe->CheckForBrokenFont(family);
1620 void gfxFT2FontList::ReadSystemFontList(dom::SystemFontList* aList) {
1621 for (const auto& entry : mFontFamilies) {
1622 auto family = static_cast<FT2FontFamily*>(entry.GetData().get());
1623 family->AddFacesToFontList(&aList->entries());
1627 static void LoadSkipSpaceLookupCheck(
1628 nsTHashSet<nsCString>& aSkipSpaceLookupCheck) {
1629 AutoTArray<nsCString, 5> skiplist;
1630 gfxFontUtils::GetPrefsFontList(
1631 "font.whitelist.skip_default_features_space_check", skiplist);
1632 uint32_t numFonts = skiplist.Length();
1633 for (uint32_t i = 0; i < numFonts; i++) {
1634 ToLowerCase(skiplist[i]);
1635 aSkipSpaceLookupCheck.Insert(skiplist[i]);
1639 nsresult gfxFT2FontList::InitFontListForPlatform() {
1640 LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies);
1642 if (XRE_IsParentProcess()) {
1643 // This will populate/update mFontNameCache and store it in the
1644 // startupCache for future startups.
1645 FindFonts();
1647 // Finalize the families by sorting faces into standard order
1648 // and marking "simple" families.
1649 for (const auto& entry : mFontFamilies) {
1650 nsCStringHashKey::KeyType key = entry.GetKey();
1651 const RefPtr<gfxFontFamily>& family = entry.GetData();
1652 FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
1655 return NS_OK;
1658 // Content process: use font list passed from the chrome process via
1659 // the GetXPCOMProcessAttributes message.
1660 auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList();
1661 for (FontListEntry& fle : fontList.entries()) {
1662 // We don't need to identify "standard" font files here,
1663 // as the faces are already sorted.
1664 AppendFaceFromFontListEntry(fle, kUnknown);
1667 // We don't need to sort faces (because they were already sorted by the
1668 // chrome process, so we just maintain the existing order)
1669 for (const auto& entry : mFontFamilies) {
1670 nsCStringHashKey::KeyType key = entry.GetKey();
1671 const RefPtr<gfxFontFamily>& family = entry.GetData();
1672 FinalizeFamilyMemberList(key, family, /* aSortFaces */ false);
1675 LOG(("got font list from chrome process: %" PRIdPTR " faces in %" PRIu32
1676 " families",
1677 fontList.entries().Length(), mFontFamilies.Count()));
1678 fontList.entries().Clear();
1680 return NS_OK;
1683 void gfxFT2FontList::InitSharedFontListForPlatform() {
1684 if (!XRE_IsParentProcess()) {
1685 // Content processes will access the shared-memory data created by the
1686 // parent, so don't need to scan for available fonts themselves.
1687 return;
1690 // This will populate mFontNameCache with entries for all the available font
1691 // files, and record them in mFamilies (unshared list) or mFamilyInitData and
1692 // mFaceInitData (shared font list).
1693 FindFonts();
1695 mozilla::fontlist::FontList* list = SharedFontList();
1696 list->SetFamilyNames(mFamilyInitData);
1698 auto families = list->Families();
1699 for (uint32_t i = 0; i < mFamilyInitData.Length(); i++) {
1700 auto faceList = mFaceInitData.Get(mFamilyInitData[i].mKey);
1701 MOZ_ASSERT(faceList);
1702 families[i].AddFaces(list, *faceList);
1705 mFamilyInitData.Clear();
1706 mFaceInitData.Clear();
1709 gfxFontEntry* gfxFT2FontList::CreateFontEntry(fontlist::Face* aFace,
1710 const fontlist::Family* aFamily) {
1711 fontlist::FontList* list = SharedFontList();
1712 nsAutoCString desc(aFace->mDescriptor.AsString(list));
1713 FT2FontEntry* fe =
1714 FT2FontEntry::CreateFontEntry(desc, desc.get(), aFace->mIndex, nullptr);
1715 fe->InitializeFrom(aFace, aFamily);
1716 fe->CheckForBrokenFont(aFamily->Key().AsString(list));
1717 return fe;
1720 // called for each family name, based on the assumption that the
1721 // first part of the full name is the family name
1723 gfxFontEntry* gfxFT2FontList::LookupLocalFont(nsPresContext* aPresContext,
1724 const nsACString& aFontName,
1725 WeightRange aWeightForEntry,
1726 StretchRange aStretchForEntry,
1727 SlantStyleRange aStyleForEntry) {
1728 if (SharedFontList()) {
1729 return LookupInSharedFaceNameList(aPresContext, aFontName, aWeightForEntry,
1730 aStretchForEntry, aStyleForEntry);
1732 // walk over list of names
1733 FT2FontEntry* fontEntry = nullptr;
1734 FontVisibility level =
1735 aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
1737 for (const RefPtr<gfxFontFamily>& fontFamily : mFontFamilies.Values()) {
1738 if (!IsVisibleToCSS(*fontFamily, level)) {
1739 continue;
1742 // Check family name, based on the assumption that the
1743 // first part of the full name is the family name
1745 // does the family name match up to the length of the family name?
1746 const nsCString& family = fontFamily->Name();
1748 const nsAutoCString fullNameFamily(
1749 Substring(aFontName, 0, family.Length()));
1751 // if so, iterate over faces in this family to see if there is a match
1752 if (family.Equals(fullNameFamily, nsCaseInsensitiveCStringComparator)) {
1753 nsTArray<RefPtr<gfxFontEntry>>& fontList = fontFamily->GetFontList();
1754 int index, len = fontList.Length();
1755 for (index = 0; index < len; index++) {
1756 gfxFontEntry* fe = fontList[index];
1757 if (!fe) {
1758 continue;
1760 if (fe->Name().Equals(aFontName, nsCaseInsensitiveCStringComparator)) {
1761 fontEntry = static_cast<FT2FontEntry*>(fe);
1762 goto searchDone;
1768 searchDone:
1769 if (!fontEntry) {
1770 return nullptr;
1773 // Clone the font entry so that we can then set its style descriptors
1774 // from the userfont entry rather than the actual font.
1776 // Ensure existence of mFTFace in the original entry
1777 RefPtr<SharedFTFace> face = fontEntry->GetFTFace(true);
1778 if (!face) {
1779 return nullptr;
1782 FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(
1783 fontEntry->Name(), fontEntry->mFilename.get(), fontEntry->mFTFontIndex,
1784 nullptr);
1785 if (fe) {
1786 fe->mStyleRange = aStyleForEntry;
1787 fe->mWeightRange = aWeightForEntry;
1788 fe->mStretchRange = aStretchForEntry;
1789 fe->mIsLocalUserFont = true;
1792 return fe;
1795 FontFamily gfxFT2FontList::GetDefaultFontForPlatform(
1796 nsPresContext* aPresContext, const gfxFontStyle* aStyle,
1797 nsAtom* aLanguage) {
1798 FontFamily ff;
1799 #if defined(MOZ_WIDGET_ANDROID)
1800 ff = FindFamily(aPresContext, "Roboto"_ns);
1801 if (ff.IsNull()) {
1802 ff = FindFamily(aPresContext, "Droid Sans"_ns);
1804 #endif
1805 /* TODO: what about Qt or other platforms that may use this? */
1806 return ff;
1809 gfxFontEntry* gfxFT2FontList::MakePlatformFont(const nsACString& aFontName,
1810 WeightRange aWeightForEntry,
1811 StretchRange aStretchForEntry,
1812 SlantStyleRange aStyleForEntry,
1813 const uint8_t* aFontData,
1814 uint32_t aLength) {
1815 // The FT2 font needs the font data to persist, so we do NOT free it here
1816 // but instead pass ownership to the font entry.
1817 // Deallocation will happen later, when the font face is destroyed.
1818 return FT2FontEntry::CreateFontEntry(aFontName, aWeightForEntry,
1819 aStretchForEntry, aStyleForEntry,
1820 aFontData, aLength);
1823 gfxFontFamily* gfxFT2FontList::CreateFontFamily(
1824 const nsACString& aName, FontVisibility aVisibility) const {
1825 return new FT2FontFamily(aName, aVisibility);
1828 void gfxFT2FontList::WillShutdown() {
1829 LOG(("WillShutdown"));
1830 WriteCache();
1831 mFontNameCache = nullptr;