michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #if (MOZ_WIDGET_GTK == 2) michael@0: #include "gfxPlatformGtk.h" michael@0: #define gfxToolkitPlatform gfxPlatformGtk michael@0: #elif defined(MOZ_WIDGET_QT) michael@0: #include michael@0: #include "gfxQtPlatform.h" michael@0: #define gfxToolkitPlatform gfxQtPlatform michael@0: #elif defined(XP_WIN) michael@0: #include "gfxWindowsPlatform.h" michael@0: #define gfxToolkitPlatform gfxWindowsPlatform michael@0: #elif defined(ANDROID) michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "gfxAndroidPlatform.h" michael@0: #include "mozilla/Omnijar.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsNetUtil.h" michael@0: #define gfxToolkitPlatform gfxAndroidPlatform michael@0: #endif michael@0: michael@0: #ifdef ANDROID michael@0: #include "nsXULAppAPI.h" michael@0: #include michael@0: #include michael@0: #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args) michael@0: #endif michael@0: michael@0: #include "ft2build.h" michael@0: #include FT_FREETYPE_H michael@0: #include FT_TRUETYPE_TAGS_H michael@0: #include FT_TRUETYPE_TABLES_H michael@0: #include "cairo-ft.h" michael@0: michael@0: #include "gfxFT2FontList.h" michael@0: #include "gfxFT2Fonts.h" michael@0: #include "gfxUserFontSet.h" michael@0: #include "gfxFontUtils.h" michael@0: michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsUnicharUtils.h" michael@0: michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIMemory.h" michael@0: #include "gfxFontConstants.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/scache/StartupCache.h" michael@0: #include michael@0: michael@0: #ifdef XP_WIN michael@0: #include "nsIWindowsRegKey.h" michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetFontInfoLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("fontInfoLog"); michael@0: return sLog; michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(GetFontInfoLog(), PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(GetFontInfoLog(), PR_LOG_DEBUG) michael@0: michael@0: static cairo_user_data_key_t sFTUserFontDataKey; michael@0: michael@0: static __inline void michael@0: BuildKeyNameFromFontName(nsAString &aName) michael@0: { michael@0: #ifdef XP_WIN michael@0: if (aName.Length() >= LF_FACESIZE) michael@0: aName.Truncate(LF_FACESIZE - 1); michael@0: #endif michael@0: ToLowerCase(aName); michael@0: } michael@0: michael@0: // Helper to access the FT_Face for a given FT2FontEntry, michael@0: // creating a temporary face if the entry does not have one yet. michael@0: // This allows us to read font names, tables, etc if necessary michael@0: // without permanently instantiating a freetype face and consuming michael@0: // memory long-term. michael@0: // This may fail (resulting in a null FT_Face), e.g. if it fails to michael@0: // allocate memory to uncompress a font from omnijar. michael@0: class AutoFTFace { michael@0: public: michael@0: AutoFTFace(FT2FontEntry* aFontEntry) michael@0: : mFace(nullptr), mFontDataBuf(nullptr), mOwnsFace(false) michael@0: { michael@0: if (aFontEntry->mFTFace) { michael@0: mFace = aFontEntry->mFTFace; michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(), michael@0: "can't use AutoFTFace for fonts without a filename"); michael@0: FT_Library ft = gfxToolkitPlatform::GetPlatform()->GetFTLibrary(); michael@0: michael@0: // A relative path (no initial "/") means this is a resource in michael@0: // omnijar, not an installed font on the device. michael@0: // The NS_ASSERTIONs here should never fail, as the resource must have michael@0: // been read successfully during font-list initialization or we'd never michael@0: // have created the font entry. The only legitimate runtime failure michael@0: // here would be memory allocation, in which case mFace remains null. michael@0: if (aFontEntry->mFilename[0] != '/') { michael@0: nsRefPtr reader = michael@0: Omnijar::GetReader(Omnijar::Type::GRE); michael@0: nsZipItem *item = reader->GetItem(aFontEntry->mFilename.get()); michael@0: NS_ASSERTION(item, "failed to find zip entry"); michael@0: michael@0: uint32_t bufSize = item->RealSize(); michael@0: mFontDataBuf = static_cast(moz_malloc(bufSize)); michael@0: if (mFontDataBuf) { michael@0: nsZipCursor cursor(item, reader, mFontDataBuf, bufSize); michael@0: cursor.Copy(&bufSize); michael@0: NS_ASSERTION(bufSize == item->RealSize(), michael@0: "error reading bundled font"); michael@0: michael@0: if (FT_Err_Ok != FT_New_Memory_Face(ft, mFontDataBuf, bufSize, michael@0: aFontEntry->mFTFontIndex, michael@0: &mFace)) { michael@0: NS_WARNING("failed to create freetype face"); michael@0: } michael@0: } michael@0: } else { michael@0: if (FT_Err_Ok != FT_New_Face(ft, aFontEntry->mFilename.get(), michael@0: aFontEntry->mFTFontIndex, &mFace)) { michael@0: NS_WARNING("failed to create freetype face"); michael@0: } michael@0: } michael@0: if (FT_Err_Ok != FT_Select_Charmap(mFace, FT_ENCODING_UNICODE)) { michael@0: NS_WARNING("failed to select Unicode charmap"); michael@0: } michael@0: mOwnsFace = true; michael@0: } michael@0: michael@0: ~AutoFTFace() { michael@0: if (mFace && mOwnsFace) { michael@0: FT_Done_Face(mFace); michael@0: if (mFontDataBuf) { michael@0: moz_free(mFontDataBuf); michael@0: } michael@0: } michael@0: } michael@0: michael@0: operator FT_Face() { return mFace; } michael@0: michael@0: // If we 'forget' the FT_Face (used when ownership is handed over to Cairo), michael@0: // we do -not- free the mFontDataBuf (if used); that also becomes the michael@0: // responsibility of the new owner of the face. michael@0: FT_Face forget() { michael@0: NS_ASSERTION(mOwnsFace, "can't forget() when we didn't own the face"); michael@0: mOwnsFace = false; michael@0: return mFace; michael@0: } michael@0: michael@0: const uint8_t* FontData() const { return mFontDataBuf; } michael@0: michael@0: private: michael@0: FT_Face mFace; michael@0: uint8_t* mFontDataBuf; // Uncompressed data (for fonts stored in a JAR), michael@0: // or null for fonts instantiated from a file. michael@0: // If non-null, this must survive as long as the michael@0: // FT_Face. michael@0: bool mOwnsFace; michael@0: }; michael@0: michael@0: /* michael@0: * FT2FontEntry michael@0: * gfxFontEntry subclass corresponding to a specific face that can be michael@0: * rendered by freetype. This is associated with a face index in a michael@0: * file (normally a .ttf/.otf file holding a single face, but in principle michael@0: * there could be .ttc files with multiple faces). michael@0: * The FT2FontEntry can create the necessary FT_Face on demand, and can michael@0: * then create a Cairo font_face and scaled_font for drawing. michael@0: */ michael@0: michael@0: cairo_scaled_font_t * michael@0: FT2FontEntry::CreateScaledFont(const gfxFontStyle *aStyle) michael@0: { michael@0: cairo_font_face_t *cairoFace = CairoFontFace(); michael@0: if (!cairoFace) { michael@0: return nullptr; michael@0: } michael@0: michael@0: cairo_scaled_font_t *scaledFont = nullptr; michael@0: michael@0: cairo_matrix_t sizeMatrix; michael@0: cairo_matrix_t identityMatrix; michael@0: michael@0: // XXX deal with adjusted size michael@0: cairo_matrix_init_scale(&sizeMatrix, aStyle->size, aStyle->size); michael@0: cairo_matrix_init_identity(&identityMatrix); michael@0: michael@0: // synthetic oblique by skewing via the font matrix michael@0: bool needsOblique = !IsItalic() && michael@0: (aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)); michael@0: michael@0: if (needsOblique) { michael@0: const double kSkewFactor = 0.25; michael@0: michael@0: cairo_matrix_t style; michael@0: cairo_matrix_init(&style, michael@0: 1, //xx michael@0: 0, //yx michael@0: -1 * kSkewFactor, //xy michael@0: 1, //yy michael@0: 0, //x0 michael@0: 0); //y0 michael@0: cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); michael@0: } michael@0: michael@0: cairo_font_options_t *fontOptions = cairo_font_options_create(); michael@0: michael@0: if (gfxPlatform::GetPlatform()->RequiresLinearZoom()) { michael@0: cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF); michael@0: } michael@0: michael@0: scaledFont = cairo_scaled_font_create(cairoFace, michael@0: &sizeMatrix, michael@0: &identityMatrix, fontOptions); michael@0: cairo_font_options_destroy(fontOptions); michael@0: michael@0: NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS, michael@0: "Failed to make scaled font"); michael@0: michael@0: return scaledFont; michael@0: } michael@0: michael@0: FT2FontEntry::~FT2FontEntry() michael@0: { michael@0: // Do nothing for mFTFace here since FTFontDestroyFunc is called by cairo. michael@0: mFTFace = nullptr; michael@0: michael@0: #ifndef ANDROID michael@0: if (mFontFace) { michael@0: cairo_font_face_destroy(mFontFace); michael@0: mFontFace = nullptr; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: gfxFont* michael@0: FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) michael@0: { michael@0: cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle); michael@0: if (!scaledFont) { michael@0: return nullptr; michael@0: } michael@0: gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold); michael@0: cairo_scaled_font_destroy(scaledFont); michael@0: return font; michael@0: } michael@0: michael@0: /* static */ michael@0: FT2FontEntry* michael@0: FT2FontEntry::CreateFontEntry(const gfxProxyFontEntry &aProxyEntry, michael@0: const uint8_t *aFontData, michael@0: uint32_t aLength) michael@0: { michael@0: // Ownership of aFontData is passed in here; the fontEntry must michael@0: // retain it as long as the FT_Face needs it, and ensure it is michael@0: // eventually deleted. michael@0: FT_Face face; michael@0: FT_Error error = michael@0: FT_New_Memory_Face(gfxToolkitPlatform::GetPlatform()->GetFTLibrary(), michael@0: aFontData, aLength, 0, &face); michael@0: if (error != FT_Err_Ok) { michael@0: NS_Free((void*)aFontData); michael@0: return nullptr; michael@0: } michael@0: if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) { michael@0: FT_Done_Face(face); michael@0: NS_Free((void*)aFontData); michael@0: return nullptr; michael@0: } michael@0: // Create our FT2FontEntry, which inherits the name of the proxy michael@0: // as it's not guaranteed that the face has valid names (bug 737315) michael@0: FT2FontEntry* fe = michael@0: FT2FontEntry::CreateFontEntry(face, nullptr, 0, aProxyEntry.Name(), michael@0: aFontData); michael@0: if (fe) { michael@0: fe->mItalic = aProxyEntry.mItalic; michael@0: fe->mWeight = aProxyEntry.mWeight; michael@0: fe->mStretch = aProxyEntry.mStretch; michael@0: fe->mIsUserFont = true; michael@0: } michael@0: return fe; michael@0: } michael@0: michael@0: class FTUserFontData { michael@0: public: michael@0: FTUserFontData(FT_Face aFace, const uint8_t* aData) michael@0: : mFace(aFace), mFontData(aData) michael@0: { michael@0: } michael@0: michael@0: ~FTUserFontData() michael@0: { michael@0: FT_Done_Face(mFace); michael@0: if (mFontData) { michael@0: NS_Free((void*)mFontData); michael@0: } michael@0: } michael@0: michael@0: const uint8_t *FontData() const { return mFontData; } michael@0: michael@0: private: michael@0: FT_Face mFace; michael@0: const uint8_t *mFontData; michael@0: }; michael@0: michael@0: static void michael@0: FTFontDestroyFunc(void *data) michael@0: { michael@0: FTUserFontData *userFontData = static_cast(data); michael@0: delete userFontData; michael@0: } michael@0: michael@0: /* static */ michael@0: FT2FontEntry* michael@0: FT2FontEntry::CreateFontEntry(const FontListEntry& aFLE) michael@0: { michael@0: FT2FontEntry *fe = new FT2FontEntry(aFLE.faceName()); michael@0: fe->mFilename = aFLE.filepath(); michael@0: fe->mFTFontIndex = aFLE.index(); michael@0: fe->mWeight = aFLE.weight(); michael@0: fe->mStretch = aFLE.stretch(); michael@0: fe->mItalic = aFLE.italic(); michael@0: return fe; michael@0: } michael@0: michael@0: // Helpers to extract font entry properties from an FT_Face michael@0: static bool michael@0: FTFaceIsItalic(FT_Face aFace) michael@0: { michael@0: return !!(aFace->style_flags & FT_STYLE_FLAG_ITALIC); michael@0: } michael@0: michael@0: static uint16_t michael@0: FTFaceGetWeight(FT_Face aFace) michael@0: { michael@0: TT_OS2 *os2 = static_cast(FT_Get_Sfnt_Table(aFace, ft_sfnt_os2)); michael@0: uint16_t os2weight = 0; michael@0: if (os2 && os2->version != 0xffff) { michael@0: // Technically, only 100 to 900 are valid, but some fonts michael@0: // have this set wrong -- e.g. "Microsoft Logo Bold Italic" has michael@0: // it set to 6 instead of 600. We try to be nice and handle that michael@0: // as well. michael@0: if (os2->usWeightClass >= 100 && os2->usWeightClass <= 900) { michael@0: os2weight = os2->usWeightClass; michael@0: } else if (os2->usWeightClass >= 1 && os2->usWeightClass <= 9) { michael@0: os2weight = os2->usWeightClass * 100; michael@0: } michael@0: } michael@0: michael@0: uint16_t result; michael@0: if (os2weight != 0) { michael@0: result = os2weight; michael@0: } else if (aFace->style_flags & FT_STYLE_FLAG_BOLD) { michael@0: result = 700; michael@0: } else { michael@0: result = 400; michael@0: } michael@0: michael@0: NS_ASSERTION(result >= 100 && result <= 900, "Invalid weight in font!"); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // Used to create the font entry for installed faces on the device, michael@0: // when iterating over the fonts directories. michael@0: // We use the FT_Face to retrieve the details needed for the font entry, michael@0: // but unless we have been passed font data (i.e. for a user font), michael@0: // we do *not* save a reference to it, nor create a cairo face, michael@0: // as we don't want to keep a freetype face for every installed font michael@0: // permanently in memory. michael@0: /* static */ michael@0: FT2FontEntry* michael@0: FT2FontEntry::CreateFontEntry(FT_Face aFace, michael@0: const char* aFilename, uint8_t aIndex, michael@0: const nsAString& aName, michael@0: const uint8_t *aFontData) michael@0: { michael@0: FT2FontEntry *fe = new FT2FontEntry(aName); michael@0: fe->mItalic = FTFaceIsItalic(aFace); michael@0: fe->mWeight = FTFaceGetWeight(aFace); michael@0: fe->mFilename = aFilename; michael@0: fe->mFTFontIndex = aIndex; michael@0: michael@0: if (aFontData) { michael@0: fe->mFTFace = aFace; michael@0: int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ? michael@0: FT_LOAD_DEFAULT : michael@0: (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); michael@0: fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags); michael@0: FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData); michael@0: cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey, michael@0: userFontData, FTFontDestroyFunc); michael@0: } michael@0: michael@0: return fe; michael@0: } michael@0: michael@0: // construct font entry name for an installed font from names in the FT_Face, michael@0: // and then create our FT2FontEntry michael@0: static FT2FontEntry* michael@0: CreateNamedFontEntry(FT_Face aFace, const char* aFilename, uint8_t aIndex) michael@0: { michael@0: if (!aFace->family_name) { michael@0: return nullptr; michael@0: } michael@0: nsAutoString fontName; michael@0: AppendUTF8toUTF16(aFace->family_name, fontName); michael@0: if (aFace->style_name && strcmp("Regular", aFace->style_name)) { michael@0: fontName.AppendLiteral(" "); michael@0: AppendUTF8toUTF16(aFace->style_name, fontName); michael@0: } michael@0: return FT2FontEntry::CreateFontEntry(aFace, aFilename, aIndex, fontName); michael@0: } michael@0: michael@0: FT2FontEntry* michael@0: gfxFT2Font::GetFontEntry() michael@0: { michael@0: return static_cast (mFontEntry.get()); michael@0: } michael@0: michael@0: cairo_font_face_t * michael@0: FT2FontEntry::CairoFontFace() michael@0: { michael@0: if (!mFontFace) { michael@0: AutoFTFace face(this); michael@0: if (!face) { michael@0: return nullptr; michael@0: } michael@0: mFTFace = face.forget(); michael@0: int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ? michael@0: FT_LOAD_DEFAULT : michael@0: (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING); michael@0: mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags); michael@0: FTUserFontData *userFontData = new FTUserFontData(face, face.FontData()); michael@0: cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey, michael@0: userFontData, FTFontDestroyFunc); michael@0: } michael@0: return mFontFace; michael@0: } michael@0: michael@0: // Copied/modified from similar code in gfxMacPlatformFontList.mm: michael@0: // Complex scripts will not render correctly unless Graphite or OT michael@0: // layout tables are present. michael@0: // For OpenType, we also check that the GSUB table supports the relevant michael@0: // script tag, to avoid using things like Arial Unicode MS for Lao (it has michael@0: // the characters, but lacks OpenType support). michael@0: michael@0: // TODO: consider whether we should move this to gfxFontEntry and do similar michael@0: // cmap-masking on all platforms to avoid using fonts that won't shape michael@0: // properly. michael@0: michael@0: nsresult michael@0: FT2FontEntry::ReadCMAP(FontInfoData *aFontInfoData) michael@0: { michael@0: if (mCharacterMap) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr charmap = new gfxCharacterMap(); michael@0: michael@0: AutoFallibleTArray buffer; michael@0: nsresult rv = CopyFontTable(TTAG_cmap, buffer); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool unicodeFont; michael@0: bool symbolFont; michael@0: rv = gfxFontUtils::ReadCMAP(buffer.Elements(), buffer.Length(), michael@0: *charmap, mUVSOffset, michael@0: unicodeFont, symbolFont); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) { michael@0: // We assume a Graphite font knows what it's doing, michael@0: // and provides whatever shaping is needed for the michael@0: // characters it supports, so only check/clear the michael@0: // complex-script ranges for non-Graphite fonts michael@0: michael@0: // for layout support, check for the presence of opentype layout tables michael@0: bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B')); michael@0: michael@0: for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; michael@0: sr->rangeStart; sr++) { michael@0: // check to see if the cmap includes complex script codepoints michael@0: if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { michael@0: // We check for GSUB here, as GPOS alone would not be ok. michael@0: if (hasGSUB && SupportsScriptInGSUB(sr->tags)) { michael@0: continue; michael@0: } michael@0: charmap->ClearRange(sr->rangeStart, sr->rangeEnd); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: // Hack for the SamsungDevanagari font, bug 1012365: michael@0: // pretend the font supports U+0972. michael@0: if (!charmap->test(0x0972) && michael@0: charmap->test(0x0905) && charmap->test(0x0945)) { michael@0: charmap->set(0x0972); michael@0: } michael@0: #endif michael@0: michael@0: mHasCmapTable = NS_SUCCEEDED(rv); michael@0: if (mHasCmapTable) { michael@0: gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); michael@0: mCharacterMap = pfl->FindCharMap(charmap); michael@0: } else { michael@0: // if error occurred, initialize to null cmap michael@0: mCharacterMap = new gfxCharacterMap(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: FT2FontEntry::CopyFontTable(uint32_t aTableTag, michael@0: FallibleTArray& aBuffer) michael@0: { michael@0: AutoFTFace face(this); michael@0: if (!face) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: FT_Error status; michael@0: FT_ULong len = 0; michael@0: status = FT_Load_Sfnt_Table(face, aTableTag, 0, nullptr, &len); michael@0: if (status != FT_Err_Ok || len == 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!aBuffer.SetLength(len)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: uint8_t *buf = aBuffer.Elements(); michael@0: status = FT_Load_Sfnt_Table(face, aTableTag, 0, buf, &len); michael@0: NS_ENSURE_TRUE(status == FT_Err_Ok, NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: hb_blob_t* michael@0: FT2FontEntry::GetFontTable(uint32_t aTableTag) michael@0: { michael@0: if (mFontFace) { michael@0: // if there's a cairo font face, we may be able to return a blob michael@0: // that just wraps a range of the attached user font data michael@0: FTUserFontData *userFontData = static_cast( michael@0: cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey)); michael@0: if (userFontData && userFontData->FontData()) { michael@0: return GetTableFromFontData(userFontData->FontData(), aTableTag); michael@0: } michael@0: } michael@0: michael@0: // otherwise, use the default method (which in turn will call our michael@0: // implementation of CopyFontTable) michael@0: return gfxFontEntry::GetFontTable(aTableTag); michael@0: } michael@0: michael@0: void michael@0: FT2FontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: gfxFontEntry::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: aSizes->mFontListSize += michael@0: mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: } michael@0: michael@0: void michael@0: FT2FontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: /* michael@0: * FT2FontFamily michael@0: * A standard gfxFontFamily; just adds a method used to support sending michael@0: * the font list from chrome to content via IPC. michael@0: */ michael@0: michael@0: void michael@0: FT2FontFamily::AddFacesToFontList(InfallibleTArray* aFontList) michael@0: { michael@0: for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) { michael@0: const FT2FontEntry *fe = michael@0: static_cast(mAvailableFonts[i].get()); michael@0: if (!fe) { michael@0: continue; michael@0: } michael@0: michael@0: aFontList->AppendElement(FontListEntry(Name(), fe->Name(), michael@0: fe->mFilename, michael@0: fe->Weight(), fe->Stretch(), michael@0: fe->IsItalic(), michael@0: fe->mFTFontIndex)); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Startup cache support for the font list: michael@0: * We store the list of families and faces, with their style attributes and the michael@0: * corresponding font files, in the startup cache. michael@0: * This allows us to recreate the gfxFT2FontList collection of families and michael@0: * faces without instantiating Freetype faces for each font file (in order to michael@0: * find their attributes), leading to significantly quicker startup. michael@0: */ michael@0: michael@0: #define CACHE_KEY "font.cached-list" michael@0: michael@0: class FontNameCache { michael@0: public: michael@0: FontNameCache() michael@0: : mWriteNeeded(false) michael@0: { michael@0: mOps = (PLDHashTableOps) { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: StringHash, michael@0: HashMatchEntry, michael@0: MoveEntry, michael@0: PL_DHashClearEntryStub, michael@0: PL_DHashFinalizeStub, michael@0: nullptr michael@0: }; michael@0: michael@0: PL_DHashTableInit(&mMap, &mOps, nullptr, sizeof(FNCMapEntry), 0); michael@0: michael@0: NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, michael@0: "StartupCacheFontNameCache should only be used in chrome process"); michael@0: mCache = mozilla::scache::StartupCache::GetSingleton(); michael@0: michael@0: Init(); michael@0: } michael@0: michael@0: ~FontNameCache() michael@0: { michael@0: if (!mMap.ops) { michael@0: return; michael@0: } michael@0: if (!mWriteNeeded || !mCache) { michael@0: PL_DHashTableFinish(&mMap); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString buf; michael@0: PL_DHashTableEnumerate(&mMap, WriteOutMap, &buf); michael@0: PL_DHashTableFinish(&mMap); michael@0: mCache->PutBuffer(CACHE_KEY, buf.get(), buf.Length() + 1); michael@0: } michael@0: michael@0: void Init() michael@0: { michael@0: if (!mMap.ops || !mCache) { michael@0: return; michael@0: } michael@0: uint32_t size; michael@0: char* buf; michael@0: if (NS_FAILED(mCache->GetBuffer(CACHE_KEY, &buf, &size))) { michael@0: return; michael@0: } michael@0: michael@0: LOG(("got: %s from the cache", nsDependentCString(buf, size).get())); michael@0: michael@0: const char* beginning = buf; michael@0: const char* end = strchr(beginning, ';'); michael@0: while (end) { michael@0: nsCString filename(beginning, end - beginning); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ';'))) { michael@0: break; michael@0: } michael@0: nsCString faceList(beginning, end - beginning); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ';'))) { michael@0: break; michael@0: } michael@0: uint32_t timestamp = strtoul(beginning, nullptr, 10); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ';'))) { michael@0: break; michael@0: } michael@0: uint32_t filesize = strtoul(beginning, nullptr, 10); michael@0: michael@0: FNCMapEntry* mapEntry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mMap, filename.get(), PL_DHASH_ADD)); michael@0: if (mapEntry) { michael@0: mapEntry->mFilename.Assign(filename); michael@0: mapEntry->mTimestamp = timestamp; michael@0: mapEntry->mFilesize = filesize; michael@0: mapEntry->mFaces.Assign(faceList); michael@0: // entries from the startupcache are marked "non-existing" michael@0: // until we have confirmed that the file still exists michael@0: mapEntry->mFileExists = false; michael@0: } michael@0: michael@0: beginning = end + 1; michael@0: end = strchr(beginning, ';'); michael@0: } michael@0: michael@0: // Should we use free() or delete[] here? See bug 684700. michael@0: free(buf); michael@0: } michael@0: michael@0: virtual void michael@0: GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList, michael@0: uint32_t *aTimestamp, uint32_t *aFilesize) michael@0: { michael@0: if (!mMap.ops) { michael@0: return; michael@0: } michael@0: PLDHashEntryHdr *hdr = michael@0: PL_DHashTableOperate(&mMap, aFileName.get(), PL_DHASH_LOOKUP); michael@0: if (!hdr) { michael@0: return; michael@0: } michael@0: FNCMapEntry* entry = static_cast(hdr); michael@0: if (entry && entry->mFilesize) { michael@0: *aTimestamp = entry->mTimestamp; michael@0: *aFilesize = entry->mFilesize; michael@0: aFaceList.Assign(entry->mFaces); michael@0: // this entry does correspond to an existing file michael@0: // (although it might not be up-to-date, in which case michael@0: // it will get overwritten via CacheFileInfo) michael@0: entry->mFileExists = true; michael@0: } michael@0: } michael@0: michael@0: virtual void michael@0: CacheFileInfo(const nsCString& aFileName, const nsCString& aFaceList, michael@0: uint32_t aTimestamp, uint32_t aFilesize) michael@0: { michael@0: if (!mMap.ops) { michael@0: return; michael@0: } michael@0: FNCMapEntry* entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mMap, aFileName.get(), PL_DHASH_ADD)); michael@0: if (entry) { michael@0: entry->mFilename.Assign(aFileName); michael@0: entry->mTimestamp = aTimestamp; michael@0: entry->mFilesize = aFilesize; michael@0: entry->mFaces.Assign(aFaceList); michael@0: entry->mFileExists = true; michael@0: } michael@0: mWriteNeeded = true; michael@0: } michael@0: michael@0: private: michael@0: mozilla::scache::StartupCache* mCache; michael@0: PLDHashTable mMap; michael@0: bool mWriteNeeded; michael@0: michael@0: PLDHashTableOps mOps; michael@0: michael@0: static PLDHashOperator WriteOutMap(PLDHashTable *aTable, michael@0: PLDHashEntryHdr *aHdr, michael@0: uint32_t aNumber, void *aData) michael@0: { michael@0: FNCMapEntry* entry = static_cast(aHdr); michael@0: if (!entry->mFileExists) { michael@0: // skip writing entries for files that are no longer present michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsAutoCString* buf = reinterpret_cast(aData); michael@0: buf->Append(entry->mFilename); michael@0: buf->Append(';'); michael@0: buf->Append(entry->mFaces); michael@0: buf->Append(';'); michael@0: buf->AppendInt(entry->mTimestamp); michael@0: buf->Append(';'); michael@0: buf->AppendInt(entry->mFilesize); michael@0: buf->Append(';'); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: typedef struct : public PLDHashEntryHdr { michael@0: public: michael@0: nsCString mFilename; michael@0: uint32_t mTimestamp; michael@0: uint32_t mFilesize; michael@0: nsCString mFaces; michael@0: bool mFileExists; michael@0: } FNCMapEntry; michael@0: michael@0: static PLDHashNumber StringHash(PLDHashTable *table, const void *key) michael@0: { michael@0: return HashString(reinterpret_cast(key)); michael@0: } michael@0: michael@0: static bool HashMatchEntry(PLDHashTable *table, michael@0: const PLDHashEntryHdr *aHdr, const void *key) michael@0: { michael@0: const FNCMapEntry* entry = michael@0: static_cast(aHdr); michael@0: return entry->mFilename.Equals(reinterpret_cast(key)); michael@0: } michael@0: michael@0: static void MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *aFrom, michael@0: PLDHashEntryHdr *aTo) michael@0: { michael@0: FNCMapEntry* to = static_cast(aTo); michael@0: const FNCMapEntry* from = static_cast(aFrom); michael@0: to->mFilename.Assign(from->mFilename); michael@0: to->mTimestamp = from->mTimestamp; michael@0: to->mFilesize = from->mFilesize; michael@0: to->mFaces.Assign(from->mFaces); michael@0: to->mFileExists = from->mFileExists; michael@0: } michael@0: }; michael@0: michael@0: /*************************************************************** michael@0: * michael@0: * gfxFT2FontList michael@0: * michael@0: */ michael@0: michael@0: // For Mobile, we use gfxFT2Fonts, and we build the font list by directly michael@0: // scanning the system's Fonts directory for OpenType and TrueType files. michael@0: michael@0: gfxFT2FontList::gfxFT2FontList() michael@0: { michael@0: } michael@0: michael@0: void michael@0: gfxFT2FontList::AppendFacesFromCachedFaceList(const nsCString& aFileName, michael@0: bool aStdFile, michael@0: const nsCString& aFaceList) michael@0: { michael@0: const char *beginning = aFaceList.get(); michael@0: const char *end = strchr(beginning, ','); michael@0: while (end) { michael@0: nsString familyName = michael@0: NS_ConvertUTF8toUTF16(beginning, end - beginning); michael@0: ToLowerCase(familyName); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ','))) { michael@0: break; michael@0: } michael@0: nsString faceName = michael@0: NS_ConvertUTF8toUTF16(beginning, end - beginning); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ','))) { michael@0: break; michael@0: } michael@0: uint32_t index = strtoul(beginning, nullptr, 10); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ','))) { michael@0: break; michael@0: } michael@0: bool italic = (*beginning != '0'); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ','))) { michael@0: break; michael@0: } michael@0: uint32_t weight = strtoul(beginning, nullptr, 10); michael@0: beginning = end + 1; michael@0: if (!(end = strchr(beginning, ','))) { michael@0: break; michael@0: } michael@0: int32_t stretch = strtol(beginning, nullptr, 10); michael@0: michael@0: FontListEntry fle(familyName, faceName, aFileName, michael@0: weight, stretch, italic, index); michael@0: AppendFaceFromFontListEntry(fle, aStdFile); michael@0: michael@0: beginning = end + 1; michael@0: end = strchr(beginning, ','); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: AppendToFaceList(nsCString& aFaceList, michael@0: nsAString& aFamilyName, FT2FontEntry* aFontEntry) michael@0: { michael@0: aFaceList.Append(NS_ConvertUTF16toUTF8(aFamilyName)); michael@0: aFaceList.Append(','); michael@0: aFaceList.Append(NS_ConvertUTF16toUTF8(aFontEntry->Name())); michael@0: aFaceList.Append(','); michael@0: aFaceList.AppendInt(aFontEntry->mFTFontIndex); michael@0: aFaceList.Append(','); michael@0: aFaceList.Append(aFontEntry->IsItalic() ? '1' : '0'); michael@0: aFaceList.Append(','); michael@0: aFaceList.AppendInt(aFontEntry->Weight()); michael@0: aFaceList.Append(','); michael@0: aFaceList.AppendInt(aFontEntry->Stretch()); michael@0: aFaceList.Append(','); michael@0: } michael@0: michael@0: void michael@0: FT2FontEntry::CheckForBrokenFont(gfxFontFamily *aFamily) michael@0: { michael@0: // note if the family is in the "bad underline" blacklist michael@0: if (aFamily->IsBadUnderlineFamily()) { michael@0: mIsBadUnderlineFont = true; michael@0: } michael@0: michael@0: // bug 721719 - set the IgnoreGSUB flag on entries for Roboto michael@0: // because of unwanted on-by-default "ae" ligature. michael@0: // (See also AppendFaceFromFontListEntry.) michael@0: if (aFamily->Name().EqualsLiteral("roboto")) { michael@0: mIgnoreGSUB = true; michael@0: } michael@0: michael@0: // bug 706888 - set the IgnoreGSUB flag on the broken version of michael@0: // Droid Sans Arabic from certain phones, as identified by the michael@0: // font checksum in the 'head' table michael@0: else if (aFamily->Name().EqualsLiteral("droid sans arabic")) { michael@0: AutoFTFace face(this); michael@0: if (face) { michael@0: const TT_Header *head = static_cast michael@0: (FT_Get_Sfnt_Table(face, ft_sfnt_head)); michael@0: if (head && head->CheckSum_Adjust == 0xe445242) { michael@0: mIgnoreGSUB = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFT2FontList::AppendFacesFromFontFile(const nsCString& aFileName, michael@0: bool aStdFile, michael@0: FontNameCache *aCache) michael@0: { michael@0: nsCString faceList; michael@0: uint32_t filesize = 0, timestamp = 0; michael@0: if (aCache) { michael@0: aCache->GetInfoForFile(aFileName, faceList, ×tamp, &filesize); michael@0: } michael@0: michael@0: struct stat s; michael@0: int statRetval = stat(aFileName.get(), &s); michael@0: if (!faceList.IsEmpty() && 0 == statRetval && michael@0: s.st_mtime == timestamp && s.st_size == filesize) michael@0: { michael@0: LOG(("using cached font info for %s", aFileName.get())); michael@0: AppendFacesFromCachedFaceList(aFileName, aStdFile, faceList); michael@0: return; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: FT_Library ftLibrary = gfxWindowsPlatform::GetPlatform()->GetFTLibrary(); michael@0: #elif defined(ANDROID) michael@0: FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary(); michael@0: #endif michael@0: FT_Face dummy; michael@0: if (FT_Err_Ok == FT_New_Face(ftLibrary, aFileName.get(), -1, &dummy)) { michael@0: LOG(("reading font info via FreeType for %s", aFileName.get())); michael@0: nsCString faceList; michael@0: timestamp = s.st_mtime; michael@0: filesize = s.st_size; michael@0: for (FT_Long i = 0; i < dummy->num_faces; i++) { michael@0: FT_Face face; michael@0: if (FT_Err_Ok != FT_New_Face(ftLibrary, aFileName.get(), i, &face)) { michael@0: continue; michael@0: } michael@0: AddFaceToList(aFileName, i, aStdFile, face, faceList); michael@0: FT_Done_Face(face); michael@0: } michael@0: FT_Done_Face(dummy); michael@0: if (aCache && 0 == statRetval && !faceList.IsEmpty()) { michael@0: aCache->CacheFileInfo(aFileName, faceList, timestamp, filesize); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #define JAR_LAST_MODIFED_TIME "jar-last-modified-time" michael@0: michael@0: void michael@0: gfxFT2FontList::FindFontsInOmnijar(FontNameCache *aCache) michael@0: { michael@0: bool jarChanged = false; michael@0: michael@0: mozilla::scache::StartupCache* cache = michael@0: mozilla::scache::StartupCache::GetSingleton(); michael@0: char *cachedModifiedTimeBuf; michael@0: uint32_t longSize; michael@0: int64_t jarModifiedTime; michael@0: if (cache && michael@0: NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, michael@0: &cachedModifiedTimeBuf, michael@0: &longSize)) && michael@0: longSize == sizeof(int64_t)) michael@0: { michael@0: nsCOMPtr jarFile = Omnijar::GetPath(Omnijar::Type::GRE); michael@0: jarFile->GetLastModifiedTime(&jarModifiedTime); michael@0: if (jarModifiedTime > *(int64_t*)cachedModifiedTimeBuf) { michael@0: jarChanged = true; michael@0: } michael@0: } michael@0: michael@0: static const char* sJarSearchPaths[] = { michael@0: "res/fonts/*.ttf$", michael@0: }; michael@0: nsRefPtr reader = Omnijar::GetReader(Omnijar::Type::GRE); michael@0: for (unsigned i = 0; i < ArrayLength(sJarSearchPaths); i++) { michael@0: nsZipFind* find; michael@0: if (NS_SUCCEEDED(reader->FindInit(sJarSearchPaths[i], &find))) { michael@0: const char* path; michael@0: uint16_t len; michael@0: while (NS_SUCCEEDED(find->FindNext(&path, &len))) { michael@0: nsCString entryName(path, len); michael@0: AppendFacesFromOmnijarEntry(reader, entryName, aCache, michael@0: jarChanged); michael@0: } michael@0: delete find; michael@0: } michael@0: } michael@0: michael@0: if (cache) { michael@0: cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&jarModifiedTime, michael@0: sizeof(jarModifiedTime)); michael@0: } michael@0: } michael@0: michael@0: // Given the freetype face corresponding to an entryName and face index, michael@0: // add the face to the available font list and to the faceList string michael@0: void michael@0: gfxFT2FontList::AddFaceToList(const nsCString& aEntryName, uint32_t aIndex, michael@0: bool aStdFile, FT_Face aFace, michael@0: nsCString& aFaceList) michael@0: { michael@0: if (FT_Err_Ok != FT_Select_Charmap(aFace, FT_ENCODING_UNICODE)) { michael@0: // ignore faces that don't support a Unicode charmap michael@0: return; michael@0: } michael@0: michael@0: // build the font entry name and create an FT2FontEntry, michael@0: // but do -not- keep a reference to the FT_Face michael@0: FT2FontEntry* fe = michael@0: CreateNamedFontEntry(aFace, aEntryName.get(), aIndex); michael@0: michael@0: if (fe) { michael@0: NS_ConvertUTF8toUTF16 name(aFace->family_name); michael@0: BuildKeyNameFromFontName(name); michael@0: gfxFontFamily *family = mFontFamilies.GetWeak(name); michael@0: if (!family) { michael@0: family = new FT2FontFamily(name); michael@0: mFontFamilies.Put(name, family); michael@0: if (mSkipSpaceLookupCheckFamilies.Contains(name)) { michael@0: family->SetSkipSpaceFeatureCheck(true); michael@0: } michael@0: if (mBadUnderlineFamilyNames.Contains(name)) { michael@0: family->SetBadUnderlineFamily(); michael@0: } michael@0: } michael@0: fe->mStandardFace = aStdFile; michael@0: family->AddFontEntry(fe); michael@0: michael@0: fe->CheckForBrokenFont(family); michael@0: michael@0: AppendToFaceList(aFaceList, name, fe); michael@0: #ifdef PR_LOGGING michael@0: if (LOG_ENABLED()) { michael@0: LOG(("(fontinit) added (%s) to family (%s)" michael@0: " with style: %s weight: %d stretch: %d", michael@0: NS_ConvertUTF16toUTF8(fe->Name()).get(), michael@0: NS_ConvertUTF16toUTF8(family->Name()).get(), michael@0: fe->IsItalic() ? "italic" : "normal", michael@0: fe->Weight(), fe->Stretch())); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFT2FontList::AppendFacesFromOmnijarEntry(nsZipArchive* aArchive, michael@0: const nsCString& aEntryName, michael@0: FontNameCache *aCache, michael@0: bool aJarChanged) michael@0: { michael@0: nsCString faceList; michael@0: if (aCache && !aJarChanged) { michael@0: uint32_t filesize, timestamp; michael@0: aCache->GetInfoForFile(aEntryName, faceList, ×tamp, &filesize); michael@0: if (faceList.Length() > 0) { michael@0: AppendFacesFromCachedFaceList(aEntryName, true, faceList); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsZipItem *item = aArchive->GetItem(aEntryName.get()); michael@0: NS_ASSERTION(item, "failed to find zip entry"); michael@0: michael@0: uint32_t bufSize = item->RealSize(); michael@0: // We use fallible allocation here; if there's not enough RAM, we'll simply michael@0: // ignore the bundled fonts and fall back to the device's installed fonts. michael@0: nsAutoPtr buf(static_cast(moz_malloc(bufSize))); michael@0: if (!buf) { michael@0: return; michael@0: } michael@0: michael@0: nsZipCursor cursor(item, aArchive, buf, bufSize); michael@0: uint8_t* data = cursor.Copy(&bufSize); michael@0: NS_ASSERTION(data && bufSize == item->RealSize(), michael@0: "error reading bundled font"); michael@0: if (!data) { michael@0: return; michael@0: } michael@0: michael@0: FT_Library ftLibrary = gfxAndroidPlatform::GetPlatform()->GetFTLibrary(); michael@0: michael@0: FT_Face dummy; michael@0: if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf, bufSize, 0, &dummy)) { michael@0: return; michael@0: } michael@0: michael@0: for (FT_Long i = 0; i < dummy->num_faces; i++) { michael@0: FT_Face face; michael@0: if (FT_Err_Ok != FT_New_Memory_Face(ftLibrary, buf, bufSize, i, &face)) { michael@0: continue; michael@0: } michael@0: AddFaceToList(aEntryName, i, true, face, faceList); michael@0: FT_Done_Face(face); michael@0: } michael@0: michael@0: FT_Done_Face(dummy); michael@0: michael@0: if (aCache && !faceList.IsEmpty()) { michael@0: aCache->CacheFileInfo(aEntryName, faceList, 0, bufSize); michael@0: } michael@0: } michael@0: michael@0: // Called on each family after all fonts are added to the list; michael@0: // this will sort faces to give priority to "standard" font files michael@0: // if aUserArg is non-null (i.e. we're using it as a boolean flag) michael@0: static PLDHashOperator michael@0: FinalizeFamilyMemberList(nsStringHashKey::KeyType aKey, michael@0: nsRefPtr& aFamily, michael@0: void* aUserArg) michael@0: { michael@0: gfxFontFamily *family = aFamily.get(); michael@0: bool sortFaces = (aUserArg != nullptr); michael@0: michael@0: family->SetHasStyles(true); michael@0: michael@0: if (sortFaces) { michael@0: family->SortAvailableFonts(); michael@0: } michael@0: family->CheckForSimpleFamily(); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: gfxFT2FontList::FindFonts() michael@0: { michael@0: #ifdef XP_WIN michael@0: nsTArray searchPaths(3); michael@0: nsTArray fontPatterns(3); michael@0: fontPatterns.AppendElement(NS_LITERAL_STRING("\\*.ttf")); michael@0: fontPatterns.AppendElement(NS_LITERAL_STRING("\\*.ttc")); michael@0: fontPatterns.AppendElement(NS_LITERAL_STRING("\\*.otf")); michael@0: wchar_t pathBuf[256]; michael@0: SHGetSpecialFolderPathW(0, pathBuf, CSIDL_WINDOWS, 0); michael@0: searchPaths.AppendElement(pathBuf); michael@0: SHGetSpecialFolderPathW(0, pathBuf, CSIDL_FONTS, 0); michael@0: searchPaths.AppendElement(pathBuf); michael@0: nsCOMPtr resDir; michael@0: NS_GetSpecialDirectory(NS_APP_RES_DIR, getter_AddRefs(resDir)); michael@0: if (resDir) { michael@0: resDir->Append(NS_LITERAL_STRING("fonts")); michael@0: nsAutoString resPath; michael@0: resDir->GetPath(resPath); michael@0: searchPaths.AppendElement(resPath); michael@0: } michael@0: WIN32_FIND_DATAW results; michael@0: for (uint32_t i = 0; i < searchPaths.Length(); i++) { michael@0: const nsString& path(searchPaths[i]); michael@0: for (uint32_t j = 0; j < fontPatterns.Length(); j++) { michael@0: nsAutoString pattern(path); michael@0: pattern.Append(fontPatterns[j]); michael@0: HANDLE handle = FindFirstFileExW(pattern.get(), michael@0: FindExInfoStandard, michael@0: &results, michael@0: FindExSearchNameMatch, michael@0: nullptr, michael@0: 0); michael@0: bool moreFiles = handle != INVALID_HANDLE_VALUE; michael@0: while (moreFiles) { michael@0: nsAutoString filePath(path); michael@0: filePath.AppendLiteral("\\"); michael@0: filePath.Append(results.cFileName); michael@0: AppendFacesFromFontFile(NS_ConvertUTF16toUTF8(filePath)); michael@0: moreFiles = FindNextFile(handle, &results); michael@0: } michael@0: if (handle != INVALID_HANDLE_VALUE) michael@0: FindClose(handle); michael@0: } michael@0: } michael@0: #elif defined(ANDROID) michael@0: gfxFontCache *fc = gfxFontCache::GetCache(); michael@0: if (fc) michael@0: fc->AgeAllGenerations(); michael@0: mPrefFonts.Clear(); michael@0: mCodepointsWithNoFonts.reset(); michael@0: michael@0: mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls michael@0: mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: // Content process: ask the Chrome process to give us the list michael@0: InfallibleTArray fonts; michael@0: mozilla::dom::ContentChild::GetSingleton()->SendReadFontList(&fonts); michael@0: for (uint32_t i = 0, n = fonts.Length(); i < n; ++i) { michael@0: AppendFaceFromFontListEntry(fonts[i], false); michael@0: } michael@0: // Passing null for userdata tells Finalize that it does not need michael@0: // to sort faces (because they were already sorted by chrome, michael@0: // so we just maintain the existing order) michael@0: mFontFamilies.Enumerate(FinalizeFamilyMemberList, nullptr); michael@0: LOG(("got font list from chrome process: %d faces in %d families", michael@0: fonts.Length(), mFontFamilies.Count())); michael@0: return; michael@0: } michael@0: michael@0: // Chrome process: get the cached list (if any) michael@0: FontNameCache fnc; michael@0: michael@0: // ANDROID_ROOT is the root of the android system, typically /system; michael@0: // font files are in /$ANDROID_ROOT/fonts/ michael@0: nsCString root; michael@0: char *androidRoot = PR_GetEnv("ANDROID_ROOT"); michael@0: if (androidRoot) { michael@0: root = androidRoot; michael@0: } else { michael@0: root = NS_LITERAL_CSTRING("/system"); michael@0: } michael@0: root.Append("/fonts"); michael@0: michael@0: FindFontsInDir(root, &fnc); michael@0: michael@0: if (mFontFamilies.Count() == 0) { michael@0: // if we can't find/read the font directory, we are doomed! michael@0: NS_RUNTIMEABORT("Could not read the system fonts directory"); michael@0: } michael@0: #endif // XP_WIN && ANDROID michael@0: michael@0: // Look for fonts stored in omnijar, unless we're on a low-memory michael@0: // device where we don't want to spend the RAM to decompress them. michael@0: // (Prefs may disable this, or force-enable it even with low memory.) michael@0: bool lowmem; michael@0: nsCOMPtr mem = nsMemory::GetGlobalMemoryService(); michael@0: if ((NS_SUCCEEDED(mem->IsLowMemoryPlatform(&lowmem)) && !lowmem && michael@0: Preferences::GetBool("gfx.bundled_fonts.enabled")) || michael@0: Preferences::GetBool("gfx.bundled_fonts.force-enabled")) { michael@0: FindFontsInOmnijar(&fnc); michael@0: } michael@0: michael@0: // look for locally-added fonts in a "fonts" subdir of the profile michael@0: nsCOMPtr localDir; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, michael@0: getter_AddRefs(localDir)); michael@0: if (NS_SUCCEEDED(rv) && michael@0: NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("fonts")))) { michael@0: nsCString localPath; michael@0: rv = localDir->GetNativePath(localPath); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: FindFontsInDir(localPath, &fnc); michael@0: } michael@0: } michael@0: michael@0: // Finalize the families by sorting faces into standard order michael@0: // and marking "simple" families. michael@0: // Passing non-null userData here says that we want faces to be sorted. michael@0: mFontFamilies.Enumerate(FinalizeFamilyMemberList, this); michael@0: } michael@0: michael@0: #ifdef ANDROID michael@0: void michael@0: gfxFT2FontList::FindFontsInDir(const nsCString& aDir, FontNameCache *aFNC) michael@0: { michael@0: static const char* sStandardFonts[] = { michael@0: "DroidSans.ttf", michael@0: "DroidSans-Bold.ttf", michael@0: "DroidSerif-Regular.ttf", michael@0: "DroidSerif-Bold.ttf", michael@0: "DroidSerif-Italic.ttf", michael@0: "DroidSerif-BoldItalic.ttf", michael@0: "DroidSansMono.ttf", michael@0: "DroidSansArabic.ttf", michael@0: "DroidSansHebrew.ttf", michael@0: "DroidSansThai.ttf", michael@0: "MTLmr3m.ttf", michael@0: "MTLc3m.ttf", michael@0: "NanumGothic.ttf", michael@0: "DroidSansJapanese.ttf", michael@0: "DroidSansFallback.ttf" michael@0: }; michael@0: michael@0: DIR *d = opendir(aDir.get()); michael@0: if (!d) { michael@0: return; michael@0: } michael@0: michael@0: struct dirent *ent = nullptr; michael@0: while ((ent = readdir(d)) != nullptr) { michael@0: const char *ext = strrchr(ent->d_name, '.'); michael@0: if (!ext) { michael@0: continue; michael@0: } michael@0: if (strcasecmp(ext, ".ttf") == 0 || michael@0: strcasecmp(ext, ".otf") == 0 || michael@0: strcasecmp(ext, ".woff") == 0 || michael@0: strcasecmp(ext, ".ttc") == 0) { michael@0: bool isStdFont = false; michael@0: for (unsigned int i = 0; michael@0: i < ArrayLength(sStandardFonts) && !isStdFont; i++) { michael@0: isStdFont = strcmp(sStandardFonts[i], ent->d_name) == 0; michael@0: } michael@0: michael@0: nsCString s(aDir); michael@0: s.Append('/'); michael@0: s.Append(ent->d_name); michael@0: michael@0: // Add the face(s) from this file to our font list; michael@0: // note that if we have cached info for this file in fnc, michael@0: // and the file is unchanged, we won't actually need to read it. michael@0: // If the file is new/changed, this will update the FontNameCache. michael@0: AppendFacesFromFontFile(s, isStdFont, aFNC); michael@0: } michael@0: } michael@0: michael@0: closedir(d); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE, michael@0: bool aStdFile) michael@0: { michael@0: FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE); michael@0: if (fe) { michael@0: fe->mStandardFace = aStdFile; michael@0: nsAutoString name(aFLE.familyName()); michael@0: gfxFontFamily *family = mFontFamilies.GetWeak(name); michael@0: if (!family) { michael@0: family = new FT2FontFamily(name); michael@0: mFontFamilies.Put(name, family); michael@0: if (mSkipSpaceLookupCheckFamilies.Contains(name)) { michael@0: family->SetSkipSpaceFeatureCheck(true); michael@0: } michael@0: if (mBadUnderlineFamilyNames.Contains(name)) { michael@0: family->SetBadUnderlineFamily(); michael@0: } michael@0: } michael@0: family->AddFontEntry(fe); michael@0: michael@0: fe->CheckForBrokenFont(family); michael@0: } michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: AddFamilyToFontList(nsStringHashKey::KeyType aKey, michael@0: nsRefPtr& aFamily, michael@0: void* aUserArg) michael@0: { michael@0: InfallibleTArray* fontlist = michael@0: reinterpret_cast*>(aUserArg); michael@0: michael@0: FT2FontFamily *family = static_cast(aFamily.get()); michael@0: family->AddFacesToFontList(fontlist); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: gfxFT2FontList::GetFontList(InfallibleTArray* retValue) michael@0: { michael@0: mFontFamilies.Enumerate(AddFamilyToFontList, retValue); michael@0: } michael@0: michael@0: static void michael@0: LoadSkipSpaceLookupCheck(nsTHashtable& aSkipSpaceLookupCheck) michael@0: { michael@0: nsAutoTArray skiplist; michael@0: gfxFontUtils::GetPrefsFontList( michael@0: "font.whitelist.skip_default_features_space_check", michael@0: skiplist); michael@0: uint32_t numFonts = skiplist.Length(); michael@0: for (uint32_t i = 0; i < numFonts; i++) { michael@0: ToLowerCase(skiplist[i]); michael@0: aSkipSpaceLookupCheck.PutEntry(skiplist[i]); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: gfxFT2FontList::InitFontList() michael@0: { michael@0: // reset font lists michael@0: gfxPlatformFontList::InitFontList(); michael@0: michael@0: LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies); michael@0: michael@0: FindFonts(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct FullFontNameSearch { michael@0: FullFontNameSearch(const nsAString& aFullName) michael@0: : mFullName(aFullName), mFontEntry(nullptr) michael@0: { } michael@0: michael@0: nsString mFullName; michael@0: FT2FontEntry *mFontEntry; michael@0: }; michael@0: michael@0: // callback called for each family name, based on the assumption that the michael@0: // first part of the full name is the family name michael@0: static PLDHashOperator michael@0: FindFullName(nsStringHashKey::KeyType aKey, michael@0: nsRefPtr& aFontFamily, michael@0: void* userArg) michael@0: { michael@0: FullFontNameSearch *data = reinterpret_cast(userArg); michael@0: michael@0: // does the family name match up to the length of the family name? michael@0: const nsString& family = aFontFamily->Name(); michael@0: michael@0: nsString fullNameFamily; michael@0: data->mFullName.Left(fullNameFamily, family.Length()); michael@0: michael@0: // if so, iterate over faces in this family to see if there is a match michael@0: if (family.Equals(fullNameFamily, nsCaseInsensitiveStringComparator())) { michael@0: nsTArray >& fontList = aFontFamily->GetFontList(); michael@0: int index, len = fontList.Length(); michael@0: for (index = 0; index < len; index++) { michael@0: gfxFontEntry* fe = fontList[index]; michael@0: if (!fe) { michael@0: continue; michael@0: } michael@0: if (fe->Name().Equals(data->mFullName, michael@0: nsCaseInsensitiveStringComparator())) { michael@0: data->mFontEntry = static_cast(fe); michael@0: return PL_DHASH_STOP; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxFT2FontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, michael@0: const nsAString& aFontName) michael@0: { michael@0: // walk over list of names michael@0: FullFontNameSearch data(aFontName); michael@0: michael@0: mFontFamilies.Enumerate(FindFullName, &data); michael@0: michael@0: if (!data.mFontEntry) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Clone the font entry so that we can then set its style descriptors michael@0: // from the proxy rather than the actual font. michael@0: michael@0: // Ensure existence of mFTFace in the original entry michael@0: data.mFontEntry->CairoFontFace(); michael@0: if (!data.mFontEntry->mFTFace) { michael@0: return nullptr; michael@0: } michael@0: michael@0: FT2FontEntry* fe = michael@0: FT2FontEntry::CreateFontEntry(data.mFontEntry->mFTFace, michael@0: data.mFontEntry->mFilename.get(), michael@0: data.mFontEntry->mFTFontIndex, michael@0: data.mFontEntry->Name(), nullptr); michael@0: if (fe) { michael@0: fe->mItalic = aProxyEntry->mItalic; michael@0: fe->mWeight = aProxyEntry->mWeight; michael@0: fe->mStretch = aProxyEntry->mStretch; michael@0: fe->mIsUserFont = fe->mIsLocalUserFont = true; michael@0: } michael@0: michael@0: return fe; michael@0: } michael@0: michael@0: gfxFontFamily* michael@0: gfxFT2FontList::GetDefaultFont(const gfxFontStyle* aStyle) michael@0: { michael@0: #ifdef XP_WIN michael@0: HGDIOBJ hGDI = ::GetStockObject(SYSTEM_FONT); michael@0: LOGFONTW logFont; michael@0: if (hGDI && ::GetObjectW(hGDI, sizeof(logFont), &logFont)) { michael@0: nsAutoString resolvedName; michael@0: if (ResolveFontName(nsDependentString(logFont.lfFaceName), resolvedName)) { michael@0: return FindFamily(resolvedName); michael@0: } michael@0: } michael@0: #elif defined(MOZ_WIDGET_GONK) michael@0: nsAutoString resolvedName; michael@0: if (ResolveFontName(NS_LITERAL_STRING("Fira Sans OT"), resolvedName)) { michael@0: return FindFamily(resolvedName); michael@0: } michael@0: #elif defined(MOZ_WIDGET_ANDROID) michael@0: nsAutoString resolvedName; michael@0: if (ResolveFontName(NS_LITERAL_STRING("Roboto"), resolvedName) || michael@0: ResolveFontName(NS_LITERAL_STRING("Droid Sans"), resolvedName)) { michael@0: return FindFamily(resolvedName); michael@0: } michael@0: #endif michael@0: /* TODO: what about Qt or other platforms that may use this? */ michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxFT2FontList::MakePlatformFont(const gfxProxyFontEntry *aProxyEntry, michael@0: const uint8_t *aFontData, michael@0: uint32_t aLength) michael@0: { michael@0: // The FT2 font needs the font data to persist, so we do NOT free it here michael@0: // but instead pass ownership to the font entry. michael@0: // Deallocation will happen later, when the font face is destroyed. michael@0: return FT2FontEntry::CreateFontEntry(*aProxyEntry, aFontData, aLength); michael@0: } michael@0: