/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "mozilla/Base64.h" #include "mozilla/MemoryReporting.h" #include "mozilla/dom/ContentChild.h" #include "gfxAndroidPlatform.h" #include "mozilla/Omnijar.h" #include "mozilla/UniquePtr.h" #include "mozilla/UniquePtrExtensions.h" #include "nsIInputStream.h" #define gfxToolkitPlatform gfxAndroidPlatform #include "nsXULAppAPI.h" #include #include #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args) #include "ft2build.h" #include FT_FREETYPE_H #include FT_TRUETYPE_TAGS_H #include FT_TRUETYPE_TABLES_H #include "cairo-ft.h" #include "gfxFT2FontList.h" #include "gfxFT2Fonts.h" #include "gfxUserFontSet.h" #include "gfxFontUtils.h" #include "nsServiceManagerUtils.h" #include "nsIObserverService.h" #include "nsTArray.h" #include "nsUnicharUtils.h" #include "nsCRT.h" #include "nsDirectoryServiceUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsISimpleEnumerator.h" #include "nsIMemory.h" #include "gfxFontConstants.h" #include "mozilla/Preferences.h" #include "mozilla/scache/StartupCache.h" #include #include #include using namespace mozilla; static LazyLogModule sFontInfoLog("fontInfoLog"); #undef LOG #define LOG(args) MOZ_LOG(sFontInfoLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(sFontInfoLog, mozilla::LogLevel::Debug) static cairo_user_data_key_t sFTUserFontDataKey; static __inline void BuildKeyNameFromFontName(nsAString &aName) { ToLowerCase(aName); } // Helper to access the FT_Face for a given FT2FontEntry, // creating a temporary face if the entry does not have one yet. // This allows us to read font names, tables, etc if necessary // without permanently instantiating a freetype face and consuming // memory long-term. // This may fail (resulting in a null FT_Face), e.g. if it fails to // allocate memory to uncompress a font from omnijar. class AutoFTFace { public: AutoFTFace(FT2FontEntry* aFontEntry) : mFace(nullptr), mFontDataBuf(nullptr), mOwnsFace(false) { if (aFontEntry->mFTFace) { mFace = aFontEntry->mFTFace; return; } NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(), "can't use AutoFTFace for fonts without a filename"); FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary(); // A relative path (no initial "/") means this is a resource in // omnijar, not an installed font on the device. // The NS_ASSERTIONs here should never fail, as the resource must have // been read successfully during font-list initialization or we'd never // have created the font entry. The only legitimate runtime failure // here would be memory allocation, in which case mFace remains null. if (aFontEntry->mFilename[0] != '/') { RefPtr reader = Omnijar::GetReader(Omnijar::Type::GRE); nsZipItem *item = reader->GetItem(aFontEntry->mFilename.get()); NS_ASSERTION(item, "failed to find zip entry"); uint32_t bufSize = item->RealSize(); mFontDataBuf = static_cast(malloc(bufSize)); if (mFontDataBuf) { nsZipCursor cursor(item, reader, mFontDataBuf, bufSize); cursor.Copy(&bufSize); NS_ASSERTION(bufSize == item->RealSize(), "error reading bundled font"); if (FT_Err_Ok != FT_New_Memory_Face(ft, mFontDataBuf, bufSize, aFontEntry->mFTFontIndex, &mFace)) { NS_WARNING("failed to create freetype face"); } } } else { if (FT_Err_Ok != FT_New_Face(ft, aFontEntry->mFilename.get(), aFontEntry->mFTFontIndex, &mFace)) { NS_WARNING("failed to create freetype face"); } } if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) { NS_WARNING("failed to select Unicode charmap"); } mOwnsFace = true; } ~AutoFTFace() { if (mFace && mOwnsFace) { FT_Done_Face(mFace); if (mFontDataBuf) { free(mFontDataBuf); } } } operator FT_Face() { return mFace; } // If we 'forget' the FT_Face (used when ownership is handed over to Cairo), // we do -not- free the mFontDataBuf (if used); that also becomes the // responsibility of the new owner of the face. FT_Face forget() { NS_ASSERTION(mOwnsFace, "can't forget() when we didn't own the face"); mOwnsFace = false; return mFace; } const uint8_t* FontData() const { return mFontDataBuf; } private: FT_Face mFace; uint8_t* mFontDataBuf; // Uncompressed data (for fonts stored in a JAR), // or null for fonts instantiated from a file. // If non-null, this must survive as long as the // FT_Face. bool mOwnsFace; }; /* * FT2FontEntry * gfxFontEntry subclass corresponding to a specific face that can be * rendered by freetype. This is associated with a face index in a * file (normally a .ttf/.otf file holding a single face, but in principle * there could be .ttc files with multiple faces). * The FT2FontEntry can create the necessary FT_Face on demand, and can * then create a Cairo font_face and scaled_font for drawing. */ cairo_scaled_font_t * FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle) { cairo_font_face_t *cairoFace = CairoFontFace(); if (!cairoFace) { return nullptr; } cairo_scaled_font_t *scaledFont = nullptr; cairo_matrix_t sizeMatrix; cairo_matrix_t identityMatrix; // XXX deal with adjusted size cairo_matrix_init_scale(&sizeMatrix, aStyle->size, aStyle->size); cairo_matrix_init_identity(&identityMatrix); // synthetic oblique by skewing via the font matrix bool needsOblique = IsUpright() && aStyle->style != NS_FONT_STYLE_NORMAL && aStyle->allowSyntheticStyle; if (needsOblique) { cairo_matrix_t style; cairo_matrix_init(&style, 1, //xx 0, //yx -1 * OBLIQUE_SKEW_FACTOR, //xy 1, //yy 0, //x0 0); //y0 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); } cairo_font_options_t *fontOptions = cairo_font_options_create(); if (gfxPlatform::GetPlatform()->RequiresLinearZoom()) { cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF); } scaledFont = cairo_scaled_font_create(cairoFace, &sizeMatrix, &identityMatrix, fontOptions); cairo_font_options_destroy(fontOptions); NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS, "Failed to make scaled font"); return scaledFont; } FT2FontEntry::~FT2FontEntry() { // Do nothing for mFTFace here since FTFontDestroyFunc is called by cairo. mFTFace = nullptr; #ifndef ANDROID if (mFontFace) { cairo_font_face_destroy(mFontFace); mFontFace = nullptr; } #endif } gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) { cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle); if (!scaledFont) { return nullptr; } gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold); cairo_scaled_font_destroy(scaledFont); return font; } /* static */ FT2FontEntry* FT2FontEntry::CreateFontEntry(const nsAString& aFontName, uint16_t aWeight, int16_t aStretch, uint8_t aStyle, const uint8_t* aFontData, uint32_t aLength) { // Ownership of aFontData is passed in here; the fontEntry must // retain it as long as the FT_Face needs it, and ensure it is // eventually deleted. FT_Face face; FT_Error error = FT_New_Memory_Face(gfxToolkitPlatform::GetPlatform()->GetFTLibrary(), aFontData, aLength, 0, &face); if (error != FT_Err_Ok) { free((void*)aFontData); return nullptr; } if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) { FT_Done_Face(face); free((void*)aFontData); return nullptr; } // Create our FT2FontEntry, which inherits the name of the userfont entry // as it's not guaranteed that the face has valid names (bug 737315) FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(face, nullptr, 0, aFontName, aFontData); if (fe) { fe->mStyle = aStyle; fe->mWeight = aWeight; fe->mStretch = aStretch; fe->mIsDataUserFont = true; } return fe; } class FTUserFontData { public: FTUserFontData(FT_Face aFace, const uint8_t* aData) : mFace(aFace), mFontData(aData) { } ~FTUserFontData() { FT_Done_Face(mFace); if (mFontData) { free((void*)mFontData); } } const uint8_t *FontData() const { return mFontData; } private: FT_Face mFace; const uint8_t *mFontData; }; static void FTFontDestroyFunc(void *data) { FTUserFontData *userFontData = static_cast(data); delete userFontData; } /* static */ FT2FontEntry* FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) { FT2FontEntry *fe = new FT2FontEntry(aFLE.faceName()); fe->mFilename = aFLE.filepath(); fe->mFTFontIndex = aFLE.index(); fe->mWeight = aFLE.weight(); fe->mStretch = aFLE.stretch(); fe->mStyle = (aFLE.italic() ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL); return fe; } // Helpers to extract font entry properties from an FT_Face static bool FTFaceIsItalic(FT_Face aFace) { return !!(aFace->style_flags & FT_STYLE_FLAG_ITALIC); } static uint16_t FTFaceGetWeight(FT_Face aFace) { TT_OS2 *os2 = static_cast(FT_Get_Sfnt_Table(aFace, ft_sfnt_os2)); uint16_t os2weight = 0; if (os2 && os2->version != 0xffff) { // Technically, only 100 to 900 are valid, but some fonts // have this set wrong -- e.g. "Microsoft Logo Bold Italic" has // it set to 6 instead of 600. We try to be nice and handle that // as well. if (os2->usWeightClass >= 100 && os2->usWeightClass <= 900) { os2weight = os2->usWeightClass; } else if (os2->usWeightClass >= 1 && os2->usWeightClass <= 9) { os2weight = os2->usWeightClass * 100; } } uint16_t result; if (os2weight != 0) { result = os2weight; } else if (aFace->style_flags & FT_STYLE_FLAG_BOLD) { result = 700; } else { result = 400; } NS_ASSERTION(result >= 100 && result <= 900, "Invalid weight in font!"); return result; } // Used to create the font entry for installed faces on the device, // when iterating over the fonts directories. // We use the FT_Face to retrieve the details needed for the font entry, // but unless we have been passed font data (i.e. for a user font), // we do *not* save a reference to it, nor create a cairo face, // as we don't want to keep a freetype face for every installed font // permanently in memory. /* static */ FT2FontEntry* FT2FontEntry::CreateFontEntry(FT_Face aFace, const char* aFilename, uint8_t aIndex, const nsAString& aName, const uint8_t* aFontData) { FT2FontEntry *fe = new FT2FontEntry(aName); fe->mStyle = (FTFaceIsItalic(aFace) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL); fe->mWeight = FTFaceGetWeight(aFace); fe->mFilename = aFilename; fe->mFTFontIndex = aIndex; if (aFontData) { fe->mFTFace = aFace; int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ? FT_LOAD_DEFAULT : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags); FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData); cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey, userFontData, FTFontDestroyFunc); } return fe; } // construct font entry name for an installed font from names in the FT_Face, // and then create our FT2FontEntry static FT2FontEntry* CreateNamedFontEntry(FT_Face aFace, const char* aFilename, uint8_t aIndex) { if (!aFace->family_name) { return nullptr; } nsAutoString fontName; AppendUTF8toUTF16(aFace->family_name, fontName); if (aFace->style_name && strcmp("Regular", aFace->style_name)) { fontName.Append(' '); AppendUTF8toUTF16(aFace->style_name, fontName); } return FT2FontEntry::CreateFontEntry(aFace, aFilename, aIndex, fontName); } FT2FontEntry* gfxFT2Font::GetFontEntry() { return static_cast (mFontEntry.get()); } cairo_font_face_t * FT2FontEntry::CairoFontFace() { if (!mFontFace) { AutoFTFace face(this); if (!face) { return nullptr; } mFTFace = face.forget(); int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ? FT_LOAD_DEFAULT : (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags); FTUserFontData *userFontData = new FTUserFontData(face, face.FontData()); cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey, userFontData, FTFontDestroyFunc); } return mFontFace; } // Copied/modified from similar code in gfxMacPlatformFontList.mm: // Complex scripts will not render correctly unless Graphite or OT // layout tables are present. // For OpenType, we also check that the GSUB table supports the relevant // script tag, to avoid using things like Arial Unicode MS for Lao (it has // the characters, but lacks OpenType support). // TODO: consider whether we should move this to gfxFontEntry and do similar // cmap-masking on all platforms to avoid using fonts that won't shape // properly. nsresult FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData) { if (mCharacterMap) { return NS_OK; } RefPtr charmap = new gfxCharacterMap(); AutoTArray buffer; nsresult rv = CopyFontTable(TTAG_cmap, buffer); if (NS_SUCCEEDED(rv)) { bool unicodeFont; bool symbolFont; rv = gfxFontUtils::ReadCMAP(buffer.Elements(), buffer.Length(), *charmap, mUVSOffset, unicodeFont, symbolFont); } if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) { // We assume a Graphite font knows what it's doing, // and provides whatever shaping is needed for the // characters it supports, so only check/clear the // complex-script ranges for non-Graphite fonts // for layout support, check for the presence of opentype layout tables bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B')); for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; sr->rangeStart; sr++) { // check to see if the cmap includes complex script codepoints if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { // We check for GSUB here, as GPOS alone would not be ok. if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { continue; } charmap->ClearRange(sr->rangeStart, sr->rangeEnd); } } } mHasCmapTable = NS_SUCCEEDED(rv); if (mHasCmapTable) { gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); mCharacterMap = pfl->FindCharMap(charmap); } else { // if error occurred, initialize to null cmap mCharacterMap = new gfxCharacterMap(); } return rv; } nsresult FT2FontEntry::CopyFontTable(uint32_t aTableTag, nsTArray& aBuffer) { AutoFTFace face(this); if (!face) { return NS_ERROR_FAILURE; } FT_Error status; FT_ULong len = 0; status = FT_Load_Sfnt_Table(face, aTableTag, 0, nullptr, &len); if (status != FT_Err_Ok || len == 0) { return NS_ERROR_FAILURE; } if (!aBuffer.SetLength(len, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } uint8_t *buf = aBuffer.Elements(); status = FT_Load_Sfnt_Table(face, aTableTag, 0, buf, &len); NS_ENSURE_TRUE(status == FT_Err_Ok, NS_ERROR_FAILURE); return NS_OK; } hb_blob_t* FT2FontEntry::GetFontTable(uint32_t aTableTag) { if (mFontFace) { // if there's a cairo font face, we may be able to return a blob // that just wraps a range of the attached user font data FTUserFontData *userFontData = static_cast( cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey)); if (userFontData && userFontData->FontData()) { return gfxFontUtils::GetTableFromFontData(userFontData->FontData(), aTableTag); } } // otherwise, use the default method (which in turn will call our // implementation of CopyFontTable) return gfxFontEntry::GetFontTable(aTableTag); } void FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, FontListSizes* aSizes) const { gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); aSizes->mFontListSize += mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); } void FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, FontListSizes* aSizes) const { aSizes->mFontListSize += aMallocSizeOf(this); AddSizeOfExcludingThis(aMallocSizeOf, aSizes); } /* * FT2FontFamily * A standard gfxFontFamily; just adds a method used to support sending * the font list from chrome to content via IPC. */ void FT2FontFamily::AddFacesToFontList(InfallibleTArray* aFontList, Visibility aVisibility) { for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) { const FT2FontEntry *fe = static_cast(mAvailableFonts[i].get()); if (!fe) { continue; } aFontList->AppendElement(FontListEntry(Name(), fe->Name(), fe->mFilename, fe->Weight(), fe->Stretch(), fe->mStyle, fe->mFTFontIndex, aVisibility == kHidden)); } } /* * Startup cache support for the font list: * We store the list of families and faces, with their style attributes and the * corresponding font files, in the startup cache. * This allows us to recreate the gfxFT2FontList collection of families and * faces without instantiating Freetype faces for each font file (in order to * find their attributes), leading to significantly quicker startup. */ #define CACHE_KEY "font.cached-list" class FontNameCache { public: // Creates the object but does NOT load the cached data from the startup // cache; call Init() after creation to do that. FontNameCache() : mMap(&mOps, sizeof(FNCMapEntry), 0) , mWriteNeeded(false) { // HACK ALERT: it's weird to assign |mOps| after we passed a pointer to // it to |mMap|'s constructor. A more normal approach here would be to // have a static |sOps| member. Unfortunately, this mysteriously but // consistently makes Fennec start-up slower, so we take this // unorthodox approach instead. It's safe because PLDHashTable's // constructor doesn't dereference the pointer; it just makes a copy of // it. mOps = (PLDHashTableOps) { StringHash, HashMatchEntry, MoveEntry, PLDHashTable::ClearEntryStub, nullptr }; MOZ_ASSERT(XRE_IsParentProcess(), "FontNameCache should only be used in chrome process"); mCache = mozilla::scache::StartupCache::GetSingleton(); } ~FontNameCache() { if (!mWriteNeeded || !mCache) { return; } nsAutoCString buf; for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast(iter.Get()); if (!entry->mFileExists) { // skip writing entries for files that are no longer present continue; } buf.Append(entry->mFilename); buf.Append(';'); buf.Append(entry->mFaces); buf.Append(';'); buf.AppendInt(entry->mTimestamp); buf.Append(';'); buf.AppendInt(entry->mFilesize); buf.Append(';'); } mCache->PutBuffer(CACHE_KEY, buf.get(), buf.Length() + 1); } // This may be called more than once (if we re-load the font list). void Init() { if (!mCache) { return; } uint32_t size; UniquePtr buf; if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &buf, &size))) { return; } LOG(("got: %s from the cache", nsDependentCString(buf.get(), size).get())); mMap.Clear(); mWriteNeeded = false; const char* beginning = buf.get(); const char* end = strchr(beginning, ';'); while (end) { nsCString filename(beginning, end - beginning); beginning = end + 1; if (!(end = strchr(beginning, ';'))) { break; } nsCString faceList(beginning, end - beginning); beginning = end + 1; if (!(end = strchr(beginning, ';'))) { break; } uint32_t timestamp = strtoul(beginning, nullptr, 10); beginning = end + 1; if (!(end = strchr(beginning, ';'))) { break; } uint32_t filesize = strtoul(beginning, nullptr, 10); auto mapEntry = static_cast(mMap.Add(filename.get(), fallible)); if (mapEntry) { mapEntry->mFilename.Assign(filename); mapEntry->mTimestamp = timestamp; mapEntry->mFilesize = filesize; mapEntry->mFaces.Assign(faceList); // entries from the startupcache are marked "non-existing" // until we have confirmed that the file still exists mapEntry->mFileExists = false; } beginning = end + 1; end = strchr(beginning, ';'); } } void GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList, uint32_t *aTimestamp, uint32_t *aFilesize) { auto entry = static_cast(mMap.Search(aFileName.get())); if (entry) { *aTimestamp = entry->mTimestamp; *aFilesize = entry->mFilesize; aFaceList.Assign(entry->mFaces); // this entry does correspond to an existing file // (although it might not be up-to-date, in which case // it will get overwritten via CacheFileInfo) entry->mFileExists = true; } } void CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList, uint32_t aTimestamp, uint32_t aFilesize) { auto entry = static_cast(mMap.Add(aFileName.get(), fallible)); if (entry) { entry->mFilename.Assign(aFileName); entry->mTimestamp = aTimestamp; entry->mFilesize = aFilesize; entry->mFaces.Assign(aFaceList); entry->mFileExists = true; } mWriteNeeded = true; } private: mozilla::scache::StartupCache* mCache; PLDHashTable mMap; bool mWriteNeeded; PLDHashTableOps mOps; typedef struct : public PLDHashEntryHdr { public: nsCString mFilename; uint32_t mTimestamp; uint32_t mFilesize; nsCString mFaces; bool mFileExists; } FNCMapEntry; static PLDHashNumber StringHash(const void *key) { return HashString(reinterpret_cast(key)); } static bool HashMatchEntry(const PLDHashEntryHdr *aHdr, const void *key) { const FNCMapEntry* entry = static_cast(aHdr); return entry->mFilename.Equals(reinterpret_cast(key)); } static void MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *aFrom, PLDHashEntryHdr *aTo) { FNCMapEntry* to = static_cast(aTo); const FNCMapEntry* from = static_cast(aFrom); to->mFilename.Assign(from->mFilename); to->mTimestamp = from->mTimestamp; to->mFilesize = from->mFilesize; to->mFaces.Assign(from->mFaces); to->mFileExists = from->mFileExists; } }; /*************************************************************** * * gfxFT2FontList * */ // For Mobile, we use gfxFT2Fonts, and we build the font list by directly // scanning the system's Fonts directory for OpenType and TrueType files. #define JAR_LAST_MODIFED_TIME "jar-last-modified-time" class WillShutdownObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER explicit WillShutdownObserver(gfxFT2FontList* aFontList) : mFontList(aFontList) { } void Remove() { nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); } mFontList = nullptr; } protected: virtual ~WillShutdownObserver() { } gfxFT2FontList *mFontList; }; NS_IMPL_ISUPPORTS(WillShutdownObserver, nsIObserver) NS_IMETHODIMP WillShutdownObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (!nsCRT::strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) { mFontList->WillShutdown(); } else { NS_NOTREACHED("unexpected notification topic"); } return NS_OK; } gfxFT2FontList::gfxFT2FontList() : mJarModifiedTime(0) { nsCOMPtr obs = services::GetObserverService(); if (obs) { mObserver = new WillShutdownObserver(this); obs->AddObserver(mObserver, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); } } gfxFT2FontList::~gfxFT2FontList() { if (mObserver) { mObserver->Remove(); } } void gfxFT2FontList::AppendFacesFromCachedFaceList( const nsCString& aFileName, const nsCString& aFaceList, StandardFile aStdFile, FT2FontFamily::Visibility aVisibility) { const char *beginning = aFaceList.get(); const char *end = strchr(beginning, ','); while (end) { NS_ConvertUTF8toUTF16 familyName(beginning, end - beginning); ToLowerCase(familyName); beginning = end + 1; if (!(end = strchr(beginning, ','))) { break; } NS_ConvertUTF8toUTF16 faceName(beginning, end - beginning); beginning = end + 1; if (!(end = strchr(beginning, ','))) { break; } uint32_t index = strtoul(beginning, nullptr, 10); beginning = end + 1; if (!(end = strchr(beginning, ','))) { break; } bool italic = (*beginning != '0'); beginning = end + 1; if (!(end = strchr(beginning, ','))) { break; } uint32_t weight = strtoul(beginning, nullptr, 10); beginning = end + 1; if (!(end = strchr(beginning, ','))) { break; } int32_t stretch = strtol(beginning, nullptr, 10); FontListEntry fle(familyName, faceName, aFileName, weight, stretch, italic, index, aVisibility == FT2FontFamily::kHidden); AppendFaceFromFontListEntry(fle, aStdFile); beginning = end + 1; end = strchr(beginning, ','); } } static void AppendToFaceList(nsCString& aFaceList, nsAString& aFamilyName, FT2FontEntry* aFontEntry) { aFaceList.Append(NS_ConvertUTF16toUTF8(aFamilyName)); aFaceList.Append(','); aFaceList.Append(NS_ConvertUTF16toUTF8(aFontEntry->Name())); aFaceList.Append(','); aFaceList.AppendInt(aFontEntry->mFTFontIndex); aFaceList.Append(','); aFaceList.Append(aFontEntry->IsItalic() ? '1' : '0'); aFaceList.Append(','); aFaceList.AppendInt(aFontEntry->Weight()); aFaceList.Append(','); aFaceList.AppendInt(aFontEntry->Stretch()); aFaceList.Append(','); } void FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily) { // note if the family is in the "bad underline" blacklist if (aFamily->IsBadUnderlineFamily()) { mIsBadUnderlineFont = true; } // bug 721719 - set the IgnoreGSUB flag on entries for Roboto // because of unwanted on-by-default "ae" ligature. // (See also AppendFaceFromFontListEntry.) if (aFamily->Name().EqualsLiteral("roboto")) { mIgnoreGSUB = true; } // bug 706888 - set the IgnoreGSUB flag on the broken version of // Droid Sans Arabic from certain phones, as identified by the // font checksum in the 'head' table else if (aFamily->Name().EqualsLiteral("droid sans arabic")) { AutoFTFace face(this); if (face) { const TT_Header *head = static_cast (FT_Get_Sfnt_Table(face, ft_sfnt_head)); if (head && head->CheckSum_Adjust == 0xe445242) { mIgnoreGSUB = true; } } } } void gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName, FontNameCache *aCache, StandardFile aStdFile, FT2FontFamily::Visibility aVisibility) { nsCString cachedFaceList; uint32_t filesize = 0, timestamp = 0; if (aCache) { aCache->GetInfoForFile(aFileName, cachedFaceList, ×tamp, &filesize); } struct stat s; int statRetval = stat(aFileName.get(), &s); if (!cachedFaceList.IsEmpty() && 0 == statRetval && s.st_mtime == timestamp && s.st_size == filesize) { LOG(("using cached font info for %s", aFileName.get())); AppendFacesFromCachedFaceList(aFileName, cachedFaceList, aStdFile, aVisibility); return; } FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary(); FT_Face dummy; if (FT_Err_Ok == FT_New_Face(ftLibrary, aFileName.get(), -1, &dummy)) { LOG(("reading font info via FreeType for %s", aFileName.get())); nsCString newFaceList; timestamp = s.st_mtime; filesize = s.st_size; for (FT_Long i = 0; i < dummy->num_faces; i++) { FT_Face face; if (FT_Err_Ok != FT_New_Face(ftLibrary, aFileName.get(), i, &face)) { continue; } AddFaceToList(aFileName, i, aStdFile, aVisibility, face, newFaceList); FT_Done_Face(face); } FT_Done_Face(dummy); if (aCache && 0 == statRetval && !newFaceList.IsEmpty()) { aCache->CacheFileInfo(aFileName, newFaceList, timestamp, filesize); } } } void gfxFT2FontList::FindFontsInOmnijar(FontNameCache *aCache) { bool jarChanged = false; mozilla::scache::StartupCache* cache = mozilla::scache::StartupCache::GetSingleton(); UniquePtr cachedModifiedTimeBuf; uint32_t longSize; if (cache && NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize)) && longSize == sizeof(int64_t)) { nsCOMPtr jarFile = Omnijar::GetPath(Omnijar::Type::GRE); jarFile->GetLastModifiedTime(&mJarModifiedTime); if (mJarModifiedTime > *(int64_t*)cachedModifiedTimeBuf.get()) { jarChanged = true; } } static const char* sJarSearchPaths[] = { "res/fonts/*.ttf$", }; RefPtr reader = Omnijar::GetReader(Omnijar::Type::GRE); for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) { nsZipFind* find; if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) { const char* path; uint16_t len; while (NS_SUCCEEDED(find->FindNext(&path, &len))) { nsCString entryName(path, len); AppendFacesFromOmnijarEntry(reader, entryName, aCache, jarChanged); } delete find; } } } // Given the freetype face corresponding to an entryName and face index, // add the face to the available font list and to the faceList string void gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex, StandardFile aStdFile, FT2FontFamily::Visibility aVisibility, FT_Face aFace, nsCString& aFaceList) { if (FT_Err_Ok != FT_Select_Charmap(aFace, FT_ENCODING_UNICODE)) { // ignore faces that don't support a Unicode charmap return; } // build the font entry name and create an FT2FontEntry, // but do -not- keep a reference to the FT_Face RefPtr fe = CreateNamedFontEntry(aFace, aEntryName.get(), aIndex); auto& fontFamilies = (aVisibility == FT2FontFamily::kHidden) ? mHiddenFontFamilies : mFontFamilies; if (fe) { NS_ConvertUTF8toUTF16 name(aFace->family_name); BuildKeyNameFromFontName(name); RefPtr family = fontFamilies.GetWeak(name); if (!family) { family = new FT2FontFamily(name); fontFamilies.Put(name, family); if (mSkipSpaceLookupCheckFamilies.Contains(name)) { family->SetSkipSpaceFeatureCheck(true); } if (mBadUnderlineFamilyNames.Contains(name)) { family->SetBadUnderlineFamily(); } } fe->mStandardFace = (aStdFile == kStandard); family->AddFontEntry(fe); fe->CheckForBrokenFont(family); AppendToFaceList(aFaceList, name, fe); if (LOG_ENABLED()) { LOG(("(fontinit) added (%s) to family (%s)" " with style: %s weight: %d stretch: %d", NS_ConvertUTF16toUTF8(fe->Name()).get(), NS_ConvertUTF16toUTF8(family->Name()).get(), fe->IsItalic() ? "italic" : "normal", fe->Weight(), fe->Stretch())); } } } void gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive, const nsCString& aEntryName, FontNameCache *aCache, bool aJarChanged) { nsCString faceList; if (aCache && !aJarChanged) { uint32_t filesize, timestamp; aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize); if (faceList.Length() > 0) { AppendFacesFromCachedFaceList(aEntryName, faceList); return; } } nsZipItem *item = aArchive->GetItem(aEntryName.get()); NS_ASSERTION(item, "failed to find zip entry"); uint32_t bufSize = item->RealSize(); // We use fallible allocation here; if there's not enough RAM, we'll simply // ignore the bundled fonts and fall back to the device's installed fonts. auto buf = MakeUniqueFallible(bufSize); if (!buf) { return; } nsZipCursor cursor(item, aArchive, buf.get(), bufSize); uint8_t* data = cursor.Copy(&bufSize); NS_ASSERTION(data && bufSize == item->RealSize(), "error reading bundled font"); if (!data) { return; } FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary(); FT_Face dummy; if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, 0, &dummy)) { return; } for (FT_Long i = 0; i < dummy->num_faces; i++) { FT_Face face; if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf.get(), bufSize, i, &face)) { continue; } AddFaceToList(aEntryName, i, kStandard, FT2FontFamily::kVisible, face, faceList); FT_Done_Face(face); } FT_Done_Face(dummy); if (aCache && !faceList.IsEmpty()) { aCache->CacheFileInfo(aEntryName, faceList, 0, bufSize); } } // Called on each family after all fonts are added to the list; // this will sort faces to give priority to "standard" font files // if aUserArg is non-null (i.e. we're using it as a boolean flag) static void FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey, RefPtr& aFamily, bool aSortFaces) { gfxFontFamily *family = aFamily.get(); family->SetHasStyles(true); if (aSortFaces) { family->SortAvailableFonts(); } family->CheckForSimpleFamily(); } void gfxFT2FontList::FindFonts() { gfxFontCache *fc = gfxFontCache::GetCache(); if (fc) fc->AgeAllGenerations(); ClearLangGroupPrefFonts(); mCodepointsWithNoFonts.reset(); mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls if (!XRE_IsParentProcess()) { // Content process: ask the Chrome process to give us the list InfallibleTArray fonts; mozilla::dom::ContentChild::GetSingleton()->SendReadFontList(&fonts); for (uint32_t i = 0, n = fonts.Length(); i < n; ++i) { // We don't need to identify "standard" font files here, // as the faces are already sorted. AppendFaceFromFontListEntry(fonts[i], kUnknown); } // Passing null for userdata tells Finalize that it does not need // to sort faces (because they were already sorted by chrome, // so we just maintain the existing order) for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { nsStringHashKey::KeyType key = iter.Key(); RefPtr& family = iter.Data(); FinalizeFamilyMemberList(key, family, /* aSortFaces */ false); } for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { nsStringHashKey::KeyType key = iter.Key(); RefPtr& family = iter.Data(); FinalizeFamilyMemberList(key, family, /* aSortFaces */ false ); } LOG(("got font list from chrome process: %d faces in %d families " "and %d in hidden families", fonts.Length(), mFontFamilies.Count(), mHiddenFontFamilies.Count())); return; } // Chrome process: get the cached list (if any) if (!mFontNameCache) { mFontNameCache = MakeUnique(); } mFontNameCache->Init(); // ANDROID_ROOT is the root of the android system, typically /system; // font files are in /$ANDROID_ROOT/fonts/ nsCString root; char *androidRoot = PR_GetEnv("ANDROID_ROOT"); if (androidRoot) { root = androidRoot; } else { root = NS_LITERAL_CSTRING("/system"); } root.AppendLiteral("/fonts"); FindFontsInDir(root, mFontNameCache.get(), FT2FontFamily::kVisible); if (mFontFamilies.Count() == 0) { // if we can't find/read the font directory, we are doomed! NS_RUNTIMEABORT("Could not read the system fonts directory"); } // Look for fonts stored in omnijar, unless we're on a low-memory // device where we don't want to spend the RAM to decompress them. // (Prefs may disable this, or force-enable it even with low memory.) bool lowmem; nsCOMPtr mem = nsMemory::GetGlobalMemoryService(); if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem && Preferences::GetBool("gfx.bundled_fonts.enabled")) || Preferences::GetBool("gfx.bundled_fonts.force-enabled")) { FindFontsInOmnijar(mFontNameCache.get()); } // Look for downloaded fonts in a profile-agnostic "fonts" directory. nsCOMPtr dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); if (dirSvc) { nsCOMPtr appDir; nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(appDir)); if (NS_SUCCEEDED(rv)) { appDir->AppendNative(NS_LITERAL_CSTRING("fonts")); nsCString localPath; if (NS_SUCCEEDED(appDir->GetNativePath(localPath))) { FindFontsInDir(localPath, mFontNameCache.get(), FT2FontFamily::kVisible); } } } // look for locally-added fonts in a "fonts" subdir of the profile nsCOMPtr localDir; nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(localDir)); if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("fonts")))) { nsCString localPath; rv = localDir->GetNativePath(localPath); if (NS_SUCCEEDED(rv)) { FindFontsInDir(localPath, mFontNameCache.get(), FT2FontFamily::kVisible); } } // Finalize the families by sorting faces into standard order // and marking "simple" families. // Passing non-null userData here says that we want faces to be sorted. for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { nsStringHashKey::KeyType key = iter.Key(); RefPtr& family = iter.Data(); FinalizeFamilyMemberList(key, family, /* aSortFaces */ true); } for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { nsStringHashKey::KeyType key = iter.Key(); RefPtr& family = iter.Data(); FinalizeFamilyMemberList(key, family, /* aSortFaces */ true); } } void gfxFT2FontList::FindFontsInDir(const nsCString& aDir, FontNameCache *aFNC, FT2FontFamily::Visibility aVisibility) { static const char* sStandardFonts[] = { "DroidSans.ttf", "DroidSans-Bold.ttf", "DroidSerif-Regular.ttf", "DroidSerif-Bold.ttf", "DroidSerif-Italic.ttf", "DroidSerif-BoldItalic.ttf", "DroidSansMono.ttf", "DroidSansArabic.ttf", "DroidSansHebrew.ttf", "DroidSansThai.ttf", "MTLmr3m.ttf", "MTLc3m.ttf", "NanumGothic.ttf", "DroidSansJapanese.ttf", "DroidSansFallback.ttf" }; DIR *d = opendir(aDir.get()); if (!d) { return; } struct dirent *ent = nullptr; while ((ent = readdir(d)) != nullptr) { const char *ext = strrchr(ent->d_name, '.'); if (!ext) { continue; } if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".otf") == 0 || strcasecmp(ext, ".woff") == 0 || strcasecmp(ext, ".ttc") == 0) { bool isStdFont = false; for (unsigned int i = 0; i < ArrayLength(sStandardFonts) && !isStdFont; i++) { isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0; } nsCString s(aDir); s.Append('/'); s.Append(ent->d_name); // Add the face(s) from this file to our font list; // note that if we have cached info for this file in fnc, // and the file is unchanged, we won't actually need to read it. // If the file is new/changed, this will update the FontNameCache. AppendFacesFromFontFile(s, aFNC, isStdFont ? kStandard : kUnknown, aVisibility); } } closedir(d); } void gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE, StandardFile aStdFile) { FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE); if (fe) { auto& fontFamilies = aFLE.isHidden() ? mHiddenFontFamilies : mFontFamilies; fe->mStandardFace = (aStdFile == kStandard); nsAutoString name(aFLE.familyName()); RefPtr family = fontFamilies.GetWeak(name); if (!family) { family = new FT2FontFamily(name); fontFamilies.Put(name, family); if (mSkipSpaceLookupCheckFamilies.Contains(name)) { family->SetSkipSpaceFeatureCheck(true); } if (mBadUnderlineFamilyNames.Contains(name)) { family->SetBadUnderlineFamily(); } } family->AddFontEntry(fe); fe->CheckForBrokenFont(family); } } void gfxFT2FontList::GetSystemFontList(InfallibleTArray* retValue) { for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { auto family = static_cast(iter.Data().get()); family->AddFacesToFontList(retValue, FT2FontFamily::kVisible); } for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { auto family = static_cast(iter.Data().get()); family->AddFacesToFontList(retValue, FT2FontFamily::kHidden); } } static void LoadSkipSpaceLookupCheck(nsTHashtable& aSkipSpaceLookupCheck) { AutoTArray skiplist; gfxFontUtils::GetPrefsFontList( "font.whitelist.skip_default_features_space_check", skiplist); uint32_t numFonts = skiplist.Length(); for (uint32_t i = 0; i < numFonts; i++) { ToLowerCase(skiplist[i]); aSkipSpaceLookupCheck.PutEntry(skiplist[i]); } } void PreloadAsUserFontFaces(nsStringHashKey::KeyType aKey, RefPtr& aFamily) { gfxFontFamily *family = aFamily.get(); auto& faces = family->GetFontList(); size_t count = faces.Length(); for (size_t i = 0; i < count; ++i) { FT2FontEntry* fe = static_cast(faces[i].get()); if (fe->mFTFontIndex != 0) { NS_NOTREACHED("don't try to preload a multi-face font"); continue; } // XXX Should we move the i/o here off the main thread? // Map the font data in fe->mFilename, so we can calculate its CRC32. int fd = open(fe->mFilename.get(), O_RDONLY); if (fd < 0) { continue; } struct stat buf; if (fstat(fd, &buf) != 0 || buf.st_size < 12) { close(fd); continue; } char* data = static_cast( mmap(0, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)); close(fd); if (data == MAP_FAILED) { continue; } // Calculate CRC32 uint32_t crc = crc32(0, nullptr, 0); crc = crc32(crc, (Bytef*)data, buf.st_size); munmap(data, buf.st_size); #if 0 ALOG("\n**** Preloading family [%s] face [%s] CRC32 [0x%08x]", NS_ConvertUTF16toUTF8(family->Name()).get(), fe->mFilename.get(), crc); #endif fe->mUserFontData = MakeUnique(); fe->mUserFontData->mRealName = fe->Name(); fe->mUserFontData->mCRC32 = crc; fe->mUserFontData->mLength = buf.st_size; // Stash it persistently in the user-font cache. gfxUserFontSet::UserFontCache::CacheFont( fe, gfxUserFontSet::UserFontCache::kPersistent); } } nsresult gfxFT2FontList::InitFontListForPlatform() { // reset hidden font list mHiddenFontFamilies.Clear(); LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies); FindFonts(); for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { nsStringHashKey::KeyType key = iter.Key(); RefPtr& family = iter.Data(); PreloadAsUserFontFaces(key, family); } return NS_OK; } // called for each family name, based on the assumption that the // first part of the full name is the family name gfxFontEntry* gfxFT2FontList::LookupLocalFont(const nsAString& aFontName, uint16_t aWeight, int16_t aStretch, uint8_t aStyle) { // walk over list of names FT2FontEntry* fontEntry = nullptr; nsString fullName(aFontName); // Note that we only check mFontFamilies here, not mHiddenFontFamilies; // hence @font-face { src:local(...) } will not find hidden fonts. for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { // Check family name, based on the assumption that the // first part of the full name is the family name RefPtr& fontFamily = iter.Data(); // does the family name match up to the length of the family name? const nsString& family = fontFamily->Name(); nsString fullNameFamily; fullName.Left(fullNameFamily, family.Length()); // if so, iterate over faces in this family to see if there is a match if (family.Equals(fullNameFamily, nsCaseInsensitiveStringComparator())) { nsTArray >& fontList = fontFamily->GetFontList(); int index, len = fontList.Length(); for (index = 0; index < len; index++) { gfxFontEntry* fe = fontList[index]; if (!fe) { continue; } if (fe->Name().Equals(fullName, nsCaseInsensitiveStringComparator())) { fontEntry = static_cast(fe); goto searchDone; } } } } searchDone: if (!fontEntry) { return nullptr; } // Clone the font entry so that we can then set its style descriptors // from the userfont entry rather than the actual font. // Ensure existence of mFTFace in the original entry fontEntry->CairoFontFace(); if (!fontEntry->mFTFace) { return nullptr; } FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(fontEntry->mFTFace, fontEntry->mFilename.get(), fontEntry->mFTFontIndex, fontEntry->Name(), nullptr); if (fe) { fe->mStyle = aStyle; fe->mWeight = aWeight; fe->mStretch = aStretch; fe->mIsLocalUserFont = true; } return fe; } gfxFontFamily* gfxFT2FontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle) { gfxFontFamily *ff = nullptr; /* TODO: what about Qt or other platforms that may use this? */ return ff; } gfxFontEntry* gfxFT2FontList::MakePlatformFont(const nsAString& aFontName, uint16_t aWeight, int16_t aStretch, uint8_t aStyle, const uint8_t* aFontData, uint32_t aLength) { // The FT2 font needs the font data to persist, so we do NOT free it here // but instead pass ownership to the font entry. // Deallocation will happen later, when the font face is destroyed. return FT2FontEntry::CreateFontEntry(aFontName, aWeight, aStretch, aStyle, aFontData, aLength); } void gfxFT2FontList::GetFontFamilyList(nsTArray >& aFamilyArray) { for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) { RefPtr& family = iter.Data(); aFamilyArray.AppendElement(family); } for (auto iter = mHiddenFontFamilies.Iter(); !iter.Done(); iter.Next()) { RefPtr& family = iter.Data(); aFamilyArray.AppendElement(family); } } void gfxFT2FontList::WillShutdown() { mozilla::scache::StartupCache* cache = mozilla::scache::StartupCache::GetSingleton(); if (cache && mJarModifiedTime > 0) { cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&mJarModifiedTime, sizeof(mJarModifiedTime)); } mFontNameCache = nullptr; }