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"
20 #include <android/log.h>
21 #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args)
24 #include FT_FREETYPE_H
25 #include FT_TRUETYPE_TAGS_H
26 #include FT_TRUETYPE_TABLES_H
27 #include FT_MULTIPLE_MASTERS_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"
41 #include "nsUnicharUtils.h"
44 #include "nsDirectoryServiceUtils.h"
45 #include "nsDirectoryServiceDefs.h"
46 #include "nsAppDirectoryServiceDefs.h"
47 #include "nsIMemory.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"
60 #ifdef MOZ_WIDGET_ANDROID
61 # include "mozilla/jni/Utils.h"
65 using namespace mozilla
;
66 using namespace mozilla::gfx
;
68 static LazyLogModule
sFontInfoLog("fontInfoLog");
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
) {
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
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
) {
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
));
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
);
114 NS_WARNING("failed to create freetype face");
119 face
= Factory::NewSharedFTFace(nullptr, mFilename
.get(), mFTFontIndex
);
121 NS_WARNING("failed to create freetype face");
124 if (FT_Err_Ok
!= FT_Select_Charmap(face
->GetFace(), FT_ENCODING_UNICODE
) &&
126 FT_Select_Charmap(face
->GetFace(), FT_ENCODING_MS_SYMBOL
)) {
127 NS_WARNING("failed to select Unicode or symbol charmap");
135 return face
.forget();
138 FTUserFontData
* FT2FontEntry::GetUserFontData() {
139 if (mFTFace
&& mFTFace
->GetData()) {
140 return static_cast<FTUserFontData
*>(mFTFace
->GetData());
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() {
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
;
172 gfxFont
* FT2FontEntry::CreateFontInstance(const gfxFontStyle
* aStyle
) {
173 RefPtr
<SharedFTFace
> face
= GetFTFace(true);
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] == '/') {
192 Factory::NewSharedFTFace(nullptr, mFilename
.get(), mFTFontIndex
);
194 varFace
= face
->GetData()->CloneFace(mFTFontIndex
);
197 gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings
,
199 face
= std::move(varFace
);
204 int loadFlags
= gfxPlatform::GetPlatform()->FontHintingEnabled()
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
);
213 unscaledFont
= !mFilename
.IsEmpty() && mFilename
[0] == '/'
214 ? new UnscaledFontFreeType(mFilename
.BeginReading(),
215 mFTFontIndex
, mFTFace
)
216 : new UnscaledFontFreeType(mFTFace
);
217 mUnscaledFont
= unscaledFont
;
221 new gfxFT2Font(unscaledFont
, std::move(face
), this, aStyle
, loadFlags
);
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();
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)
240 FT2FontEntry::CreateFontEntry(aFontName
, nullptr, 0, nullptr);
243 fe
->mStyleRange
= aStyle
;
244 fe
->mWeightRange
= aWeight
;
245 fe
->mStretchRange
= aStretch
;
246 fe
->mIsDataUserFont
= true;
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());
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
272 87.5, // Semi-condensed
274 112.5, // Semi-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'));
283 const char* data
= hb_blob_get_data(blob
, &len
);
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.
317 FT2FontEntry
* FT2FontEntry::CreateFontEntry(const nsACString
& aName
,
318 const char* aFilename
,
320 const hb_face_t
* aFace
) {
321 FT2FontEntry
* fe
= new FT2FontEntry(aName
);
322 fe
->mFilename
= aFilename
;
323 fe
->mFTFontIndex
= aIndex
;
326 SetPropertiesFromFace(fe
, aFace
);
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());
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
353 nsresult
FT2FontEntry::ReadCMAP(FontInfoData
* aFontInfoData
) {
354 if (mCharacterMap
|| mShmemCharacterMap
) {
358 RefPtr
<gfxCharacterMap
> charmap
= new gfxCharacterMap();
360 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
361 hb_blob_t
* cmapBlob
= GetFontTable(TTAG_cmap
);
364 const char* data
= hb_blob_get_data(cmapBlob
, &length
);
365 rv
= gfxFontUtils::ReadCMAP((const uint8_t*)data
, length
, *charmap
,
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
)) {
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);
404 mHasCmapTable
= NS_SUCCEEDED(rv
);
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
414 mCharacterMap
= charmap
;
417 mCharacterMap
= pfl
->FindCharMap(charmap
);
420 // if error occurred, initialize to null cmap
421 mCharacterMap
= new gfxCharacterMap();
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
);
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");
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
));
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()) {
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
);
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();
479 // Read table tags in batches; 32 should be enough for most fonts in a
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;
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
);
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
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(),
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();
529 hb_blob_t
* result
= hb_face_reference_table(face
, aTableTag
);
530 hb_face_destroy(face
);
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;
545 mFTFace
->GetFace()->face_flags
& FT_FACE_FLAG_MULTIPLE_MASTERS
;
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()) {
558 FT_MM_Var
* mmVar
= GetMMVar();
562 gfxFT2Utils::GetVariationAxes(mmVar
, aAxes
);
565 void FT2FontEntry::GetVariationInstances(
566 nsTArray
<gfxFontVariationInstance
>& aInstances
) {
567 if (!HasVariations()) {
570 FT_MM_Var
* mmVar
= GetMMVar();
574 gfxFT2Utils::GetVariationInstances(this, mmVar
, aInstances
);
577 FT_MM_Var
* FT2FontEntry::GetMMVar() {
578 if (mMMVarInitialized
) {
581 mMMVarInitialized
= true;
582 RefPtr
<SharedFTFace
> face
= GetFTFace(true);
586 if (FT_Err_Ok
!= FT_Get_MM_Var(face
->GetFace(), &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
);
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());
619 aFontList
->AppendElement(FontListEntry(
620 Name(), fe
->Name(), fe
->mFilename
, fe
->Weight().AsScalar(),
621 fe
->Stretch().AsScalar(), fe
->SlantStyle().AsScalar(), fe
->mFTFontIndex
,
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
);
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()) {
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
{
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
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
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
) {
718 if (!mWriteNeeded
|| !mCache
) {
722 LOG(("Writing FontNameCache:"));
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",
739 mCache
->PutBuffer(CACHE_KEY
, UniquePtr
<char[]>(ToNewCString(buf
)),
741 mWriteNeeded
= false;
744 // This may be called more than once (if we re-load the font list).
752 if (NS_FAILED(mCache
->GetBuffer(CACHE_KEY
, &cur
, &size
))) {
753 LOG(("no cache of " CACHE_KEY
));
757 LOG(("got: %u bytes from the cache " CACHE_KEY
, size
));
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
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
));
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
) {
782 aEnd
= endOfField(aStart
);
788 const char* end
= endOfField(cur
);
789 nsCString
filename(cur
, end
- cur
);
790 if (!nextField(cur
, end
)) {
793 nsCString
faceList(cur
, end
- cur
);
794 if (!nextField(cur
, end
)) {
797 uint32_t timestamp
= strtoul(cur
, nullptr, 10);
798 if (!nextField(cur
, end
)) {
801 uint32_t filesize
= strtoul(cur
, nullptr, 10);
804 static_cast<FNCMapEntry
*>(mMap
.Add(filename
.get(), fallible
));
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;
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()));
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
));
837 entry
->mFilename
.Assign(aFileName
);
838 entry
->mTimestamp
= aTimestamp
;
839 entry
->mFilesize
= aFilesize
;
840 entry
->mFaces
.Assign(aFaceList
);
841 entry
->mFileExists
= true;
847 mozilla::scache::StartupCache
* mCache
;
851 PLDHashTableOps mOps
;
853 struct FNCMapEntry
: public PLDHashEntryHdr
{
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 /***************************************************************
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
{
899 explicit WillShutdownObserver(gfxFT2FontList
* aFontList
)
900 : mFontList(aFontList
) {}
903 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
905 obs
->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID
);
911 virtual ~WillShutdownObserver() = default;
913 gfxFT2FontList
* mFontList
;
916 NS_IMPL_ISUPPORTS(WillShutdownObserver
, nsIObserver
)
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();
924 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
929 gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) {
930 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
932 mObserver
= new WillShutdownObserver(this);
933 obs
->AddObserver(mObserver
, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID
, false);
937 gfxFT2FontList::~gfxFT2FontList() {
943 bool gfxFT2FontList::AppendFacesFromCachedFaceList(CollectFunc aCollectFace
,
944 const nsCString
& aFileName
,
945 const nsCString
& aFaceList
,
946 StandardFile aStdFile
) {
947 const char* start
= aFaceList
.get();
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
));
961 auto nextField
= [=](const char*& aStart
, const char*& aEnd
) -> bool {
964 aEnd
= endOfField(aStart
);
970 const char* end
= endOfField(start
);
971 nsAutoCString
familyName(start
, end
- start
);
972 nsAutoCString
key(familyName
);
975 if (!nextField(start
, end
)) {
978 nsAutoCString
faceName(start
, end
- start
);
980 if (!nextField(start
, end
)) {
983 uint32_t index
= strtoul(start
, nullptr, 10);
985 if (!nextField(start
, end
)) {
988 nsAutoCString
minStyle(start
, end
- start
);
989 nsAutoCString
maxStyle(minStyle
);
990 int32_t colon
= minStyle
.FindChar(FontNameCache::kRangeSep
);
992 maxStyle
.Assign(minStyle
.BeginReading() + colon
+ 1);
993 minStyle
.Truncate(colon
);
996 if (!nextField(start
, end
)) {
1000 float minWeight
= strtof(start
, &limit
);
1002 if (*limit
== FontNameCache::kRangeSep
&& limit
+ 1 < end
) {
1003 maxWeight
= strtof(limit
+ 1, nullptr);
1005 maxWeight
= minWeight
;
1008 if (!nextField(start
, end
)) {
1011 float minStretch
= strtof(start
, &limit
);
1013 if (*limit
== FontNameCache::kRangeSep
&& limit
+ 1 < end
) {
1014 maxStretch
= strtof(limit
+ 1, nullptr);
1016 maxStretch
= minStretch
;
1019 if (!nextField(start
, end
)) {
1022 nsAutoCString
psname(start
, end
- start
);
1024 if (!nextField(start
, end
)) {
1027 nsAutoCString
fullname(start
, end
- start
);
1029 if (!nextField(start
, end
)) {
1032 FontVisibility visibility
= FontVisibility(strtoul(start
, nullptr, 10));
1035 familyName
, faceName
, aFileName
,
1036 WeightRange(FontWeight(minWeight
), FontWeight(maxWeight
)).AsScalar(),
1037 StretchRange(FontStretch(minStretch
), FontStretch(maxStretch
))
1039 SlantStyleRange(FontSlantStyle::FromString(minStyle
.get()),
1040 FontSlantStyle::FromString(maxStyle
.get()))
1044 aCollectFace(fle
, psname
, fullname
, aStdFile
);
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")) {
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();
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) {
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;
1152 aCache
->GetInfoForFile(aFileName
, cachedFaceList
, ×tamp
, &filesize
);
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()));
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
,
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
;
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
)) {
1203 static const char* sJarSearchPaths
[] = {
1206 RefPtr
<nsZipArchive
> reader
= Omnijar::GetReader(Omnijar::Type::GRE
);
1207 for (unsigned i
= 0; i
< ArrayLength(sJarSearchPaths
); i
++) {
1209 if (NS_SUCCEEDED(reader
->FindInit(sJarSearchPaths
[i
], &find
))) {
1212 while (NS_SUCCEEDED(find
->FindNext(&path
, &len
))) {
1213 nsCString
entryName(path
, len
);
1214 AppendFacesFromOmnijarEntry(reader
, entryName
, aCache
, jarChanged
);
1221 static void GetName(hb_face_t
* aFace
, hb_ot_name_id_t aNameID
,
1222 nsACString
& aName
) {
1224 n
= hb_ot_name_get_utf8(aFace
, aNameID
, HB_LANGUAGE_INVALID
, &n
, nullptr);
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()) {
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
);
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
,
1288 CollectInitData(fle
, psname
, fullname
, aStdFile
);
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();
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
);
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
,
1326 if (aCache
&& !aJarChanged
) {
1327 uint32_t filesize
, timestamp
;
1328 aCache
->GetInfoForFile(aEntryName
, faceList
, ×tamp
, &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
)) {
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
));
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");
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"
1376 static void FinalizeFamilyMemberList(nsCStringHashKey::KeyType aKey
,
1377 const RefPtr
<gfxFontFamily
>& aFamily
,
1379 gfxFontFamily
* family
= aFamily
.get();
1381 family
->SetHasStyles(true);
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;
1415 if (jni::GetAPIVersion() >= 29) {
1416 void* handle
= dlopen("libandroid.so", RTLD_LAZY
| RTLD_LOCAL
);
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;
1439 bool useSystemFontAPI
= !!systemFontIterator_open
;
1440 if (useSystemFontAPI
) {
1441 void* iter
= systemFontIterator_open();
1443 void* font
= systemFontIterator_next(iter
);
1445 nsAutoCString
path(font_getFontFilePath(font
));
1446 AppendFacesFromFontFile(path
, mFontNameCache
.get(), kStandard
);
1448 font
= systemFontIterator_next(iter
);
1451 systemFontIterator_close(iter
);
1453 useSystemFontAPI
= false;
1457 if (!useSystemFontAPI
)
1460 // ANDROID_ROOT is the root of the android system, typically /system;
1461 // font files are in /$ANDROID_ROOT/fonts/
1463 char* androidRoot
= PR_GetEnv("ANDROID_ROOT");
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.)
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
);
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
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",
1559 "DroidSansJapanese.ttf",
1560 "DroidSansFallback.ttf"};
1562 DIR* d
= opendir(aDir
.get());
1567 struct dirent
* ent
= nullptr;
1568 while ((ent
= readdir(d
)) != nullptr) {
1569 const char* ext
= strrchr(ent
->d_name
, '.');
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
;
1578 isStdFont
= strcmp(sStandardFonts
[i
], ent
->d_name
) == 0;
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
);
1596 void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry
& aFLE
,
1597 StandardFile aStdFile
) {
1598 FT2FontEntry
* fe
= FT2FontEntry::CreateFontEntry(aFLE
);
1600 nsAutoCString
key(aFLE
.familyName());
1601 BuildKeyNameFromFontName(key
);
1602 fe
->mStandardFace
= (aStdFile
== kStandard
);
1603 RefPtr
<gfxFontFamily
> family
= mFontFamilies
.LookupOrInsertWith(key
, [&] {
1605 MakeRefPtr
<FT2FontFamily
>(aFLE
.familyName(), aFLE
.visibility());
1606 if (mSkipSpaceLookupCheckFamilies
.Contains(key
)) {
1607 family
->SetSkipSpaceFeatureCheck(true);
1609 if (mBadUnderlineFamilyNames
.ContainsSorted(key
)) {
1610 family
->SetBadUnderlineFamily();
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.
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);
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
1677 fontList
.entries().Length(), mFontFamilies
.Count()));
1678 fontList
.entries().Clear();
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.
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).
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
));
1714 FT2FontEntry::CreateFontEntry(desc
, desc
.get(), aFace
->mIndex
, nullptr);
1715 fe
->InitializeFrom(aFace
, aFamily
);
1716 fe
->CheckForBrokenFont(aFamily
->Key().AsString(list
));
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
)) {
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
];
1760 if (fe
->Name().Equals(aFontName
, nsCaseInsensitiveCStringComparator
)) {
1761 fontEntry
= static_cast<FT2FontEntry
*>(fe
);
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);
1782 FT2FontEntry
* fe
= FT2FontEntry::CreateFontEntry(
1783 fontEntry
->Name(), fontEntry
->mFilename
.get(), fontEntry
->mFTFontIndex
,
1786 fe
->mStyleRange
= aStyleForEntry
;
1787 fe
->mWeightRange
= aWeightForEntry
;
1788 fe
->mStretchRange
= aStretchForEntry
;
1789 fe
->mIsLocalUserFont
= true;
1795 FontFamily
gfxFT2FontList::GetDefaultFontForPlatform(
1796 nsPresContext
* aPresContext
, const gfxFontStyle
* aStyle
,
1797 nsAtom
* aLanguage
) {
1799 #if defined(MOZ_WIDGET_ANDROID)
1800 ff
= FindFamily(aPresContext
, "Roboto"_ns
);
1802 ff
= FindFamily(aPresContext
, "Droid Sans"_ns
);
1805 /* TODO: what about Qt or other platforms that may use this? */
1809 gfxFontEntry
* gfxFT2FontList::MakePlatformFont(const nsACString
& aFontName
,
1810 WeightRange aWeightForEntry
,
1811 StretchRange aStretchForEntry
,
1812 SlantStyleRange aStyleForEntry
,
1813 const uint8_t* aFontData
,
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"));
1831 mFontNameCache
= nullptr;