gfx/thebes/gfxFT2FontList.cpp
author moz-wptsync-bot <wptsync@mozilla.com>
Thu, 08 Nov 2018 08:22:51 +0000
changeset 506788 f62150dda3664620284888933718cef568d96558
parent 506571 f5c1dbff0c9afe001c1f32576058138c7b799d2b
child 508163 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1505638 [wpt PR 13972] - Update wpt metadata, a=testonly wpt-pr: 13972 wpt-type: metadata

/* -*- 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/FontPropertyTypes.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"
#include "nsReadableUtils.h"

#include "nsXULAppAPI.h"
#include <dirent.h>
#include <android/log.h>
#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 FT_MULTIPLE_MASTERS_H
#include "cairo-ft.h"

#include "gfxFT2FontList.h"
#include "gfxFT2Fonts.h"
#include "gfxFT2Utils.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 <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

using namespace mozilla;
using namespace mozilla::gfx;

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(nsACString &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:
    explicit AutoFTFace(FT2FontEntry* aFontEntry)
        : mFace(nullptr), mFontDataBuf(nullptr), mDataLength(0), mOwnsFace(false)
    {
        if (aFontEntry->mFTFace) {
            mFace = aFontEntry->mFTFace;
            return;
        }

        NS_ASSERTION(!aFontEntry->mFilename.IsEmpty(),
                     "can't use AutoFTFace for fonts without a filename");

        // 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<nsZipArchive> 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<uint8_t*>(malloc(bufSize));
            if (mFontDataBuf) {
                nsZipCursor cursor(item, reader, mFontDataBuf, bufSize);
                cursor.Copy(&bufSize);
                NS_ASSERTION(bufSize == item->RealSize(),
                             "error reading bundled font");
                mDataLength = bufSize;
                mFace = Factory::NewFTFaceFromData(nullptr, mFontDataBuf, bufSize, aFontEntry->mFTFontIndex);
                if (!mFace) {
                    NS_WARNING("failed to create freetype face");
                }
            }
        } else {
            mFace = Factory::NewFTFace(nullptr, aFontEntry->mFilename.get(), aFontEntry->mFTFontIndex);
            if (!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) {
            Factory::ReleaseFTFace(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; }
    uint32_t DataLength() const { return mDataLength; }

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.
    uint32_t mDataLength;  // Size of mFontDataBuf, if present.
    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(aStyle);
    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);

    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()
{
    if (mMMVar) {
        FT_Done_MM_Var(mFTFace->glyph->library, mMMVar);
    }

    // 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
}

gfxFontEntry*
FT2FontEntry::Clone() const
{
    MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
    FT2FontEntry* fe = new FT2FontEntry(Name());
    fe->mFilename = mFilename;
    fe->mFTFontIndex = mFTFontIndex;
    fe->mWeightRange = mWeightRange;
    fe->mStretchRange = mStretchRange;
    fe->mStyleRange = mStyleRange;
    return fe;
}

static cairo_user_data_key_t sFTFaceKey;

gfxFont*
FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle)
{
    cairo_scaled_font_t *scaledFont = CreateScaledFont(aFontStyle);
    if (!scaledFont) {
        return nullptr;
    }

    RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont);
    if (!unscaledFont) {
        unscaledFont =
            !mFilename.IsEmpty() && mFilename[0] == '/' ?
                new UnscaledFontFreeType(mFilename.BeginReading(),
                                         mFTFontIndex) :
                new UnscaledFontFreeType(mFTFace);
        mUnscaledFont = unscaledFont;
    }

    cairo_font_face_t* face = cairo_scaled_font_get_font_face(scaledFont);
    FT_Face ftFace = static_cast<FT_Face>(cairo_font_face_get_user_data(face, &sFTFaceKey));

    gfxFont *font = new gfxFT2Font(unscaledFont, scaledFont,
                                   ftFace ? ftFace : mFTFace,
                                   this, aFontStyle);
    cairo_scaled_font_destroy(scaledFont);
    return font;
}

/* static */
FT2FontEntry*
FT2FontEntry::CreateFontEntry(const nsACString& aFontName,
                              WeightRange aWeight,
                              StretchRange aStretch,
                              SlantStyleRange 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 = Factory::NewFTFaceFromData(nullptr, aFontData, aLength, 0);
    if (!face) {
        free((void*)aFontData);
        return nullptr;
    }
    if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
        Factory::ReleaseFTFace(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, aLength);
    if (fe) {
        fe->mStyleRange = aStyle;
        fe->mWeightRange = aWeight;
        fe->mStretchRange = aStretch;
        fe->mIsDataUserFont = true;
    }
    return fe;
}

class FTUserFontData {
public:
    FTUserFontData(FT_Face aFace, const uint8_t* aData, uint32_t aLength)
        : mFace(aFace), mFontData(aData), mLength(aLength)
    {
    }

    ~FTUserFontData()
    {
        Factory::ReleaseFTFace(mFace);
        if (mFontData) {
            free((void*)mFontData);
        }
    }

    const uint8_t *FontData() const { return mFontData; }
    uint32_t Length() const { return mLength; }

private:
    FT_Face        mFace;
    const uint8_t *mFontData;
    uint32_t       mLength;
};

static void
FTFontDestroyFunc(void *data)
{
    FTUserFontData *userFontData = static_cast<FTUserFontData*>(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->mWeightRange = WeightRange::FromScalar(aFLE.weightRange());
    fe->mStretchRange = StretchRange::FromScalar(aFLE.stretchRange());
    fe->mStyleRange = SlantStyleRange::FromScalar(aFLE.styleRange());
    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 FontWeight
FTFaceGetWeight(FT_Face aFace)
{
    TT_OS2 *os2 = static_cast<TT_OS2*>(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 FontWeight(int(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 nsACString& aName,
                              const uint8_t* aFontData,
                              uint32_t aLength)
{
    FT2FontEntry *fe = new FT2FontEntry(aName);
    fe->mStyleRange = SlantStyleRange(FTFaceIsItalic(aFace)
                                      ? FontSlantStyle::Italic()
                                      : FontSlantStyle::Normal());
    fe->mWeightRange = WeightRange(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, nullptr, 0);
        FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData, aLength);
        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;
    }
    nsAutoCString fontName(aFace->family_name);
    if (aFace->style_name && strcmp("Regular", aFace->style_name)) {
        fontName.Append(' ');
        fontName.Append(aFace->style_name);
    }
    return FT2FontEntry::CreateFontEntry(aFace, aFilename, aIndex, fontName);
}

FT2FontEntry*
gfxFT2Font::GetFontEntry()
{
    return static_cast<FT2FontEntry*> (mFontEntry.get());
}

cairo_font_face_t *
FT2FontEntry::CairoFontFace(const gfxFontStyle* aStyle)
{
    // Create our basic (no-variations) mFontFace if not already present;
    // this also ensures we have mFTFace available.
    if (!mFontFace) {
        AutoFTFace face(this);
        if (!face) {
            return nullptr;
        }
        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, 
                                                          nullptr, 0);
        auto userFontData = new FTUserFontData(face, face.FontData(),
                                               face.DataLength());
        cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
                                      userFontData, FTFontDestroyFunc);
        mFTFace = face.forget();
    }

    // If variations are present, we will not use our cached mFontFace
    // but always create a new cairo_font_face_t because its FT_Face will
    // have custom variation coordinates applied.
    if ((!mVariationSettings.IsEmpty() ||
        (aStyle && !aStyle->variationSettings.IsEmpty())) &&
        (mFTFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
        int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
                    FT_LOAD_DEFAULT :
                    (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
        // Resolve variations from entry (descriptor) and style (property)
        AutoTArray<gfxFontVariation,8> settings;
        GetVariationsForStyle(settings, aStyle ? *aStyle : gfxFontStyle());
        AutoTArray<FT_Fixed,8> coords;
        gfxFT2FontBase::SetupVarCoords(GetMMVar(), settings, &coords);
        // Create a separate FT_Face because we need to apply custom
        // variation settings to it.
        FT_Face ftFace;
        if (!mFilename.IsEmpty() && mFilename[0] == '/') {
            ftFace = Factory::NewFTFace(nullptr, mFilename.get(), mFTFontIndex);
        } else {
            auto ufd = reinterpret_cast<FTUserFontData*>(
                cairo_font_face_get_user_data(mFontFace, &sFTUserFontDataKey));
            ftFace = Factory::NewFTFaceFromData(nullptr, ufd->FontData(),
                                                ufd->Length(), mFTFontIndex);
        }
        // The variation coordinates will actually be applied to ftFace by
        // gfxFT2FontBase::InitMetrics, so we don't need to do it here.
        cairo_font_face_t* cairoFace =
            cairo_ft_font_face_create_for_ft_face(ftFace, flags,
                                                  coords.Elements(),
                                                  coords.Length());
        // Set up user data to properly release the FT_Face when the cairo face
        // is deleted.
        if (cairo_font_face_set_user_data(cairoFace, &sFTFaceKey, ftFace,
                    (cairo_destroy_func_t)&Factory::ReleaseFTFace)) {
            // set_user_data failed! discard, and fall back to default face
            cairo_font_face_destroy(cairoFace);
            FT_Done_Face(ftFace);
        } else {
            return cairoFace;
        }
    }

    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<gfxCharacterMap> charmap = new gfxCharacterMap();

    AutoTArray<uint8_t, 16384> buffer;
    nsresult rv = CopyFontTable(TTAG_cmap, buffer);
    
    if (NS_SUCCEEDED(rv)) {
        rv = gfxFontUtils::ReadCMAP(buffer.Elements(), buffer.Length(),
                                    *charmap, mUVSOffset);
    }

    if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) {
        // For downloadable fonts, trust the author and don't
        // try to munge the cmap based on script shaping support.

        // We also 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)) {
                    continue;
                }
                charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
            }
        }
    }

#ifdef MOZ_WIDGET_ANDROID
    // Hack for the SamsungDevanagari font, bug 1012365:
    // pretend the font supports U+0972.
    if (!charmap->test(0x0972) &&
        charmap->test(0x0905) && charmap->test(0x0945)) {
        charmap->set(0x0972);
    }
#endif

    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<uint8_t>& 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<FTUserFontData*>(
            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);
}

bool
FT2FontEntry::HasVariations()
{
    if (mHasVariationsInitialized) {
        return mHasVariations;
    }
    mHasVariationsInitialized = true;

    AutoFTFace face(this);
    if (face) {
        mHasVariations =
            FT_Face(face)->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS;
    }

    return mHasVariations;
}

void
FT2FontEntry::GetVariationAxes(nsTArray<gfxFontVariationAxis>& aAxes)
{
    if (!HasVariations()) {
        return;
    }
    FT_MM_Var* mmVar = GetMMVar();
    if (!mmVar) {
        return;
    }
    gfxFT2Utils::GetVariationAxes(mmVar, aAxes);
}

void
FT2FontEntry::GetVariationInstances(
    nsTArray<gfxFontVariationInstance>& aInstances)
{
    if (!HasVariations()) {
        return;
    }
    FT_MM_Var* mmVar = GetMMVar();
    if (!mmVar) {
        return;
    }
    gfxFT2Utils::GetVariationInstances(this, mmVar, aInstances);
}

FT_MM_Var*
FT2FontEntry::GetMMVar()
{
    if (mMMVarInitialized) {
        return mMMVar;
    }
    mMMVarInitialized = true;
    AutoFTFace face(this);
    if (!face) {
        return nullptr;
    }
    if (FT_Err_Ok != FT_Get_MM_Var(face, &mMMVar)) {
        mMMVar = nullptr;
    }
    return mMMVar;
}

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<FontListEntry>* aFontList)
{
    for (int i = 0, n = mAvailableFonts.Length(); i < n; ++i) {
        const FT2FontEntry *fe =
            static_cast<const FT2FontEntry*>(mAvailableFonts[i].get());
        if (!fe) {
            continue;
        }

        aFontList->AppendElement(FontListEntry(Name(),
                                               fe->Name(),
                                               fe->mFilename,
                                               fe->Weight().AsScalar(),
                                               fe->Stretch().AsScalar(),
                                               fe->SlantStyle().AsScalar(),
                                               fe->mFTFontIndex));
    }
}

/*
 * 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<FNCMapEntry*>(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,
            UniquePtr<char[]>(ToNewCString(buf)), 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<char[]> 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<FNCMapEntry*>(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<FNCMapEntry*>(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<FNCMapEntry*>(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<const char*>(key));
    }

    static bool HashMatchEntry(const PLDHashEntryHdr *aHdr, const void *key)
    {
        const FNCMapEntry* entry =
            static_cast<const FNCMapEntry*>(aHdr);
        return entry->mFilename.Equals(reinterpret_cast<const char*>(key));
    }

    static void MoveEntry(PLDHashTable *table, const PLDHashEntryHdr *aFrom,
                          PLDHashEntryHdr *aTo)
    {
        FNCMapEntry* to = static_cast<FNCMapEntry*>(aTo);
        const FNCMapEntry* from = static_cast<const FNCMapEntry*>(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<nsIObserverService> 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 {
        MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
    }
    return NS_OK;
}

gfxFT2FontList::gfxFT2FontList()
    : mJarModifiedTime(0)
{
    nsCOMPtr<nsIObserverService> 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)
{
    const char *beginning = aFaceList.get();
    const char *end = strchr(beginning, ',');
    while (end) {
        nsAutoCString familyName(beginning, end - beginning);
        ToLowerCase(familyName);

        beginning = end + 1;
        if (!(end = strchr(beginning, ','))) {
            break;
        }
        nsAutoCString 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;
        }
        nsAutoCString minStyle(beginning, end - beginning);
        nsAutoCString maxStyle(minStyle);
        int32_t colon = minStyle.FindChar(':');
        if (colon > 0) {
            maxStyle.Assign(minStyle.BeginReading() + colon + 1);
            minStyle.Truncate(colon);
        }

        beginning = end + 1;
        if (!(end = strchr(beginning, ','))) {
            break;
        }
        char* limit;
        float minWeight = strtof(beginning, &limit);
        float maxWeight;
        if (*limit == ':' && limit + 1 < end) {
            maxWeight = strtof(limit + 1, nullptr);
        } else {
            maxWeight = minWeight;
        }

        beginning = end + 1;
        if (!(end = strchr(beginning, ','))) {
            break;
        }
        float minStretch = strtof(beginning, &limit);
        float maxStretch;
        if (*limit == ':' && limit + 1 < end) {
            maxStretch = strtof(limit + 1, nullptr);
        } else {
            maxStretch = minStretch;
        }

        FontListEntry fle(
            familyName, faceName, aFileName,
            WeightRange(FontWeight(minWeight),
                        FontWeight(maxWeight)).AsScalar(),
            StretchRange(FontStretch(minStretch),
                         FontStretch(maxStretch)).AsScalar(),
            SlantStyleRange(FontSlantStyle::FromString(minStyle.get()),
                            FontSlantStyle::FromString(maxStyle.get())).AsScalar(),
            index);
        AppendFaceFromFontListEntry(fle, aStdFile);

        beginning = end + 1;
        end = strchr(beginning, ',');
    }
}

static void
AppendToFaceList(nsCString& aFaceList,
                 nsACString& aFamilyName, FT2FontEntry* aFontEntry)
{
    aFaceList.Append(aFamilyName);
    aFaceList.Append(',');
    aFaceList.Append(aFontEntry->Name());
    aFaceList.Append(',');
    aFaceList.AppendInt(aFontEntry->mFTFontIndex);
    aFaceList.Append(',');
    // Note that ToString() appends to the destination string without
    // replacing existing contents (see FontPropertyTypes.h)
    aFontEntry->SlantStyle().Min().ToString(aFaceList);
    aFaceList.Append(':');
    aFontEntry->SlantStyle().Max().ToString(aFaceList);
    aFaceList.Append(',');
    aFaceList.AppendFloat(aFontEntry->Weight().Min().ToFloat());
    aFaceList.Append(':');
    aFaceList.AppendFloat(aFontEntry->Weight().Max().ToFloat());
    aFaceList.Append(',');
    aFaceList.AppendFloat(aFontEntry->Stretch().Min().Percentage());
    aFaceList.Append(':');
    aFaceList.AppendFloat(aFontEntry->Stretch().Max().Percentage());
    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<const TT_Header*>
                (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)
{
    nsCString cachedFaceList;
    uint32_t filesize = 0, timestamp = 0;
    if (aCache) {
        aCache->GetInfoForFile(aFileName, cachedFaceList, &timestamp, &filesize);
    }

    struct stat s;
    int statRetval = stat(aFileName.get(), &s);
    if (!cachedFaceList.IsEmpty() && 0 == statRetval &&
        uint32_t(s.st_mtime) == timestamp && s.st_size == filesize)
    {
        LOG(("using cached font info for %s", aFileName.get()));
        AppendFacesFromCachedFaceList(aFileName, cachedFaceList, aStdFile);
        return;
    }

    FT_Face dummy = Factory::NewFTFace(nullptr, aFileName.get(), -1);
    if (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 = Factory::NewFTFace(nullptr, aFileName.get(), i);
            if (!face) {
                continue;
            }
            AddFaceToList(aFileName, i, aStdFile, face, newFaceList);
            Factory::ReleaseFTFace(face);
        }
        Factory::ReleaseFTFace(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<char[]> cachedModifiedTimeBuf;
    uint32_t longSize;
    if (cache &&
        NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME,
                                      &cachedModifiedTimeBuf,
                                      &longSize)) &&
        longSize == sizeof(int64_t))
    {
        nsCOMPtr<nsIFile> 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<nsZipArchive> 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,
                              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<FT2FontEntry> fe =
        CreateNamedFontEntry(aFace, aEntryName.get(), aIndex);

    if (fe) {
        nsAutoCString name(aFace->family_name);
        BuildKeyNameFromFontName(name);
        RefPtr<gfxFontFamily> family = mFontFamilies.GetWeak(name);
        if (!family) {
            family = new FT2FontFamily(name);
            mFontFamilies.Put(name, family);
            if (mSkipSpaceLookupCheckFamilies.Contains(name)) {
                family->SetSkipSpaceFeatureCheck(true);
            }
            if (mBadUnderlineFamilyNames.ContainsSorted(name)) {
                family->SetBadUnderlineFamily();
            }
        }
        fe->mStandardFace = (aStdFile == kStandard);
        family->AddFontEntry(fe);

        fe->CheckForBrokenFont(family);

        AppendToFaceList(aFaceList, name, fe);
        if (LOG_ENABLED()) {
            nsAutoCString weightString;
            fe->Weight().ToString(weightString);
            nsAutoCString stretchString;
            fe->Stretch().ToString(stretchString);
            LOG(("(fontinit) added (%s) to family (%s)"
                 " with style: %s weight: %s stretch: %s",
                 fe->Name().get(),
                 family->Name().get(),
                 fe->IsItalic() ? "italic" : "normal",
                 weightString.get(),
                 stretchString.get()));
        }
    }
}

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, &timestamp, &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<uint8_t[]>(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_Face dummy = Factory::NewFTFaceFromData(nullptr, buf.get(), bufSize, 0);
    if (!dummy) {
        return;
    }

    for (FT_Long i = 0; i < dummy->num_faces; i++) {
        FT_Face face = Factory::NewFTFaceFromData(nullptr, buf.get(), bufSize, i);
        if (!face) {
            continue;
        }
        AddFaceToList(aEntryName, i, kStandard, face, faceList);
        Factory::ReleaseFTFace(face);
    }

    Factory::ReleaseFTFace(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(nsCStringHashKey::KeyType aKey,
                         RefPtr<gfxFontFamily>& 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<FontListEntry> 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()) {
            nsCStringHashKey::KeyType key = iter.Key();
            RefPtr<gfxFontFamily>& family = iter.Data();
            FinalizeFamilyMemberList(key, family, /* aSortFaces */ false);
        }

        LOG(("got font list from chrome process: %" PRIdPTR " faces in %"
             PRIu32 " families",
            fonts.Length(), mFontFamilies.Count()));
        return;
    }

    // Chrome process: get the cached list (if any)
    if (!mFontNameCache) {
        mFontNameCache = MakeUnique<FontNameCache>();
    }
    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());

    if (mFontFamilies.Count() == 0) {
        // if we can't find/read the font directory, we are doomed!
        MOZ_CRASH("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<nsIMemory> 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<nsIProperties> dirSvc =
      do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
    if (dirSvc) {
        nsCOMPtr<nsIFile> 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());
            }
        }
    }

    // look for locally-added fonts in a "fonts" subdir of the profile
    nsCOMPtr<nsIFile> 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());
        }
    }

    // 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()) {
        nsCStringHashKey::KeyType key = iter.Key();
        RefPtr<gfxFontFamily>& family = iter.Data();
        FinalizeFamilyMemberList(key, family, /* aSortFaces */ true);
    }
}

void
gfxFT2FontList::FindFontsInDir(const nsCString& aDir,
                               FontNameCache *aFNC)
{
    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);
        }
    }

    closedir(d);
}

void
gfxFT2FontList::AppendFaceFromFontListEntry(const FontListEntry& aFLE,
                                            StandardFile aStdFile)
{
    FT2FontEntry* fe = FT2FontEntry::CreateFontEntry(aFLE);
    if (fe) {
        fe->mStandardFace = (aStdFile == kStandard);
        RefPtr<gfxFontFamily> family = mFontFamilies.GetWeak(aFLE.familyName());
        if (!family) {
            family = new FT2FontFamily(aFLE.familyName());
            mFontFamilies.Put(aFLE.familyName(), family);
            if (mSkipSpaceLookupCheckFamilies.Contains(aFLE.familyName())) {
                family->SetSkipSpaceFeatureCheck(true);
            }
            if (mBadUnderlineFamilyNames.ContainsSorted(aFLE.familyName())) {
                family->SetBadUnderlineFamily();
            }
        }
        family->AddFontEntry(fe);

        fe->CheckForBrokenFont(family);
    }
}

void
gfxFT2FontList::GetSystemFontList(InfallibleTArray<FontListEntry>* retValue)
{
    for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
        auto family = static_cast<FT2FontFamily*>(iter.Data().get());
        family->AddFacesToFontList(retValue);
    }
}

static void
LoadSkipSpaceLookupCheck(nsTHashtable<nsCStringHashKey>& aSkipSpaceLookupCheck)
{
    AutoTArray<nsCString, 5> 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]);
    }
}

nsresult
gfxFT2FontList::InitFontListForPlatform()
{
    LoadSkipSpaceLookupCheck(mSkipSpaceLookupCheckFamilies);

    FindFonts();

    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 nsACString& aFontName,
                                WeightRange aWeightForEntry,
                                StretchRange aStretchForEntry,
                                SlantStyleRange aStyleForEntry)
{
    // walk over list of names
    FT2FontEntry* fontEntry = nullptr;

    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<gfxFontFamily>& fontFamily = iter.Data();

        // does the family name match up to the length of the family name?
        const nsCString& family = fontFamily->Name();

        const nsAutoCString fullNameFamily(
            Substring(aFontName, 0, family.Length()));

        // if so, iterate over faces in this family to see if there is a match
        if (family.Equals(fullNameFamily, nsCaseInsensitiveCStringComparator())) {
            nsTArray<RefPtr<gfxFontEntry> >& 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(aFontName,
                                      nsCaseInsensitiveCStringComparator())) {
                    fontEntry = static_cast<FT2FontEntry*>(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->mStyleRange = aStyleForEntry;
        fe->mWeightRange = aWeightForEntry;
        fe->mStretchRange = aStretchForEntry;
        fe->mIsLocalUserFont = true;
    }

    return fe;
}

gfxFontFamily*
gfxFT2FontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
{
    gfxFontFamily *ff = nullptr;
#if defined(MOZ_WIDGET_ANDROID)
    ff = FindFamily(NS_LITERAL_CSTRING("Roboto"));
    if (!ff) {
        ff = FindFamily(NS_LITERAL_CSTRING("Droid Sans"));
    }
#endif
    /* TODO: what about Qt or other platforms that may use this? */
    return ff;
}

gfxFontEntry*
gfxFT2FontList::MakePlatformFont(const nsACString& aFontName,
                                 WeightRange aWeightForEntry,
                                 StretchRange aStretchForEntry,
                                 SlantStyleRange aStyleForEntry,
                                 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,
                                         aWeightForEntry,
                                         aStretchForEntry,
                                         aStyleForEntry,
                                         aFontData, aLength);
}

void
gfxFT2FontList::GetFontFamilyList(nsTArray<RefPtr<gfxFontFamily> >& aFamilyArray)
{
    for (auto iter = mFontFamilies.Iter(); !iter.Done(); iter.Next()) {
        RefPtr<gfxFontFamily>& family = iter.Data();
        aFamilyArray.AppendElement(family);
    }
}

gfxFontFamily*
gfxFT2FontList::CreateFontFamily(const nsACString& aName) const
{
    return new FT2FontFamily(aName);
}

void
gfxFT2FontList::WillShutdown()
{
    mozilla::scache::StartupCache* cache =
        mozilla::scache::StartupCache::GetSingleton();
    if (cache && mJarModifiedTime > 0) {
        const size_t bufSize = sizeof(mJarModifiedTime);
        auto buf = MakeUnique<char[]>(bufSize);
        memcpy(buf.get(), &mJarModifiedTime, bufSize);

        cache->PutBuffer(JAR_LAST_MODIFED_TIME,
                         std::move(buf), bufSize);
    }
    mFontNameCache = nullptr;
}