/* -*- 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/DebugOnly.h"
#include <algorithm>

#include "mozilla/Logging.h"
#include "mozilla/Sprintf.h"

#include "gfxGDIFontList.h"
#include "gfxWindowsPlatform.h"
#include "gfxUserFontSet.h"
#include "gfxFontUtils.h"
#include "gfxGDIFont.h"

#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "nsUnicharUtils.h"

#include "nsDirectoryServiceUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsISimpleEnumerator.h"
#include "nsIWindowsRegKey.h"
#include "gfxFontConstants.h"
#include "GeckoProfiler.h"

#include "mozilla/MemoryReporting.h"
#include "mozilla/Telemetry.h"

#include <usp10.h>

using namespace mozilla;
using namespace mozilla::gfx;

#define ROUND(x) floor((x) + 0.5)

#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \
                               LogLevel::Debug, args)
#define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \
                                   gfxPlatform::GetLog(eGfxLog_fontlist), \
                                   LogLevel::Debug)

#define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \
                                   gfxPlatform::GetLog(eGfxLog_cmapdata), \
                                   LogLevel::Debug)

static __inline void
BuildKeyNameFromFontName(nsAString &aName)
{
    if (aName.Length() >= LF_FACESIZE)
        aName.Truncate(LF_FACESIZE - 1);
    ToLowerCase(aName);
}

// Implementation of gfxPlatformFontList for Win32 GDI,
// using GDI font enumeration APIs to get the list of fonts

class WinUserFontData : public gfxUserFontData {
public:
    explicit WinUserFontData(HANDLE aFontRef)
        : mFontRef(aFontRef)
    { }

    virtual ~WinUserFontData()
    {
        DebugOnly<BOOL> success;
        success = RemoveFontMemResourceEx(mFontRef);
#if DEBUG
        if (!success) {
            char buf[256];
            SprintfLiteral(buf, "error deleting font handle (%p) - RemoveFontMemResourceEx failed", mFontRef);
            NS_ASSERTION(success, buf);
        }
#endif
    }

    HANDLE mFontRef;
};

BYTE 
FontTypeToOutPrecision(uint8_t fontType)
{
    BYTE ret;
    switch (fontType) {
    case GFX_FONT_TYPE_TT_OPENTYPE:
    case GFX_FONT_TYPE_TRUETYPE:
        ret = OUT_TT_ONLY_PRECIS;
        break;
    case GFX_FONT_TYPE_PS_OPENTYPE:
        ret = OUT_PS_ONLY_PRECIS;
        break;
    case GFX_FONT_TYPE_TYPE1:
        ret = OUT_OUTLINE_PRECIS;
        break;
    case GFX_FONT_TYPE_RASTER:
        ret = OUT_RASTER_PRECIS;
        break;
    case GFX_FONT_TYPE_DEVICE:
        ret = OUT_DEVICE_PRECIS;
        break;
    default:
        ret = OUT_DEFAULT_PRECIS;
    }
    return ret;
}

/***************************************************************
 *
 * GDIFontEntry
 *
 */

GDIFontEntry::GDIFontEntry(const nsAString& aFaceName,
                           gfxWindowsFontType aFontType,
                           SlantStyleRange aStyle,
                           WeightRange aWeight,
                           StretchRange aStretch,
                           gfxUserFontData *aUserFontData)
    : gfxFontEntry(aFaceName),
      mFontType(aFontType),
      mForceGDI(false),
      mUnicodeRanges()
{
    mUserFontData.reset(aUserFontData);
    mStyleRange = aStyle;
    mWeightRange = aWeight;
    mStretchRange = aStretch;
    if (IsType1())
        mForceGDI = true;
    mIsDataUserFont = aUserFontData != nullptr;

    InitLogFont(aFaceName, aFontType);
}

gfxFontEntry*
GDIFontEntry::Clone() const
{
    MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
    return new GDIFontEntry(Name(), mFontType, SlantStyle(), Weight(),
                            Stretch(), nullptr);
}

nsresult
GDIFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
{
    AUTO_PROFILER_LABEL("GDIFontEntry::ReadCMAP", OTHER);

    // attempt this once, if errors occur leave a blank cmap
    if (mCharacterMap) {
        return NS_OK;
    }

    // skip non-SFNT fonts completely
    if (mFontType != GFX_FONT_TYPE_PS_OPENTYPE && 
        mFontType != GFX_FONT_TYPE_TT_OPENTYPE &&
        mFontType != GFX_FONT_TYPE_TRUETYPE) 
    {
        mCharacterMap = new gfxCharacterMap();
        mCharacterMap->mBuildOnTheFly = true;
        return NS_ERROR_FAILURE;
    }

    RefPtr<gfxCharacterMap> charmap;
    nsresult rv;

    if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
                                                        mUVSOffset))) {
        rv = NS_OK;
    } else {
        uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
        charmap = new gfxCharacterMap();
        AutoTArray<uint8_t, 16384> cmap;
        rv = CopyFontTable(kCMAP, cmap);

        if (NS_SUCCEEDED(rv)) {
            rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(),
                                        *charmap, mUVSOffset);
        }
    }

    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();
        // For fonts where we failed to read the character map,
        // we can take a slow path to look up glyphs character by character
        mCharacterMap->mBuildOnTheFly = true;
    }

    LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
                  NS_ConvertUTF16toUTF8(mName).get(),
                  charmap->SizeOfIncludingThis(moz_malloc_size_of),
                  charmap->mHash, mCharacterMap == charmap ? " new" : ""));
    if (LOG_CMAPDATA_ENABLED()) {
        char prefix[256];
        SprintfLiteral(prefix, "(cmapdata) name: %.220s",
                       NS_ConvertUTF16toUTF8(mName).get());
        charmap->Dump(prefix, eGfxLog_cmapdata);
    }

    return rv;
}

gfxFont *
GDIFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle)
{
    return new gfxGDIFont(this, aFontStyle);
}

nsresult
GDIFontEntry::CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>& aBuffer)
{
    if (!IsTrueType()) {
        return NS_ERROR_FAILURE;
    }

    AutoDC dc;
    AutoSelectFont font(dc.GetDC(), &mLogFont);
    if (font.IsValid()) {
        uint32_t tableSize =
            ::GetFontData(dc.GetDC(),
                          NativeEndian::swapToBigEndian(aTableTag),
                          0, nullptr, 0);
        if (tableSize != GDI_ERROR) {
            if (aBuffer.SetLength(tableSize, fallible)) {
                ::GetFontData(dc.GetDC(),
                              NativeEndian::swapToBigEndian(aTableTag), 0,
                              aBuffer.Elements(), tableSize);
                return NS_OK;
            }
            return NS_ERROR_OUT_OF_MEMORY;
        }
    }
    return NS_ERROR_FAILURE;
}

already_AddRefed<UnscaledFontGDI>
GDIFontEntry::LookupUnscaledFont(HFONT aFont)
{
    RefPtr<UnscaledFontGDI> unscaledFont(mUnscaledFont);
    if (!unscaledFont) {
        LOGFONT lf;
        GetObject(aFont, sizeof(LOGFONT), &lf);
        unscaledFont = new UnscaledFontGDI(lf);
        mUnscaledFont = unscaledFont;
    }

    return unscaledFont.forget();
}

void
GDIFontEntry::FillLogFont(LOGFONTW *aLogFont,
                          LONG aWeight,
                          gfxFloat aSize)
{
    memcpy(aLogFont, &mLogFont, sizeof(LOGFONTW));

    aLogFont->lfHeight = (LONG)-ROUND(aSize);

    if (aLogFont->lfHeight == 0) {
        aLogFont->lfHeight = -1;
    }

    // If a non-zero weight is passed in, use this to override the original
    // weight in the entry's logfont. This is used to control synthetic bolding
    // for installed families with no bold face, and for downloaded fonts
    // (but NOT for local user fonts, because it could cause a different,
    // glyph-incompatible face to be used)
    if (aWeight != 0) {
        aLogFont->lfWeight = aWeight;
    }

    // for non-local() user fonts, we never want to apply italics here;
    // if the face is described as italic, we should use it as-is,
    // and if it's not, but then the element is styled italic, we'll use
    // a cairo transform to create fake italic (oblique)
    if (mIsDataUserFont) {
        aLogFont->lfItalic = 0;
    }
}

#define MISSING_GLYPH 0x1F // glyph index returned for missing characters
                           // on WinXP with .fon fonts, but not Type1 (.pfb)

bool 
GDIFontEntry::TestCharacterMap(uint32_t aCh)
{
    if (!mCharacterMap) {
        ReadCMAP();
        NS_ASSERTION(mCharacterMap, "failed to initialize a character map");
    }

    if (mCharacterMap->mBuildOnTheFly) {
        if (aCh > 0xFFFF)
            return false;

        // previous code was using the group style
        gfxFontStyle fakeStyle;
        if (!IsUpright()) {
            fakeStyle.style = FontSlantStyle::Italic();
        }
        fakeStyle.weight = Weight().Min();

        RefPtr<gfxFont> tempFont = FindOrMakeFont(&fakeStyle, false);
        if (!tempFont || !tempFont->Valid())
            return false;
        gfxGDIFont *font = static_cast<gfxGDIFont*>(tempFont.get());

        HDC dc = GetDC((HWND)nullptr);
        SetGraphicsMode(dc, GM_ADVANCED);
        HFONT hfont = font->GetHFONT();
        HFONT oldFont = (HFONT)SelectObject(dc, hfont);

        wchar_t str[1] = { (wchar_t)aCh };
        WORD glyph[1];

        bool hasGlyph = false;

        // Bug 573038 - in some cases GetGlyphIndicesW returns 0xFFFF for a 
        // missing glyph or 0x1F in other cases to indicate the "invalid" 
        // glyph.  Map both cases to "not found"
        if (IsType1() || mForceGDI) {
            // Type1 fonts and uniscribe APIs don't get along.  
            // ScriptGetCMap will return E_HANDLE
            DWORD ret = GetGlyphIndicesW(dc, str, 1, 
                                         glyph, GGI_MARK_NONEXISTING_GLYPHS);
            if (ret != GDI_ERROR
                && glyph[0] != 0xFFFF
                && (IsType1() || glyph[0] != MISSING_GLYPH))
            {
                hasGlyph = true;
            }
        } else {
            // ScriptGetCMap works better than GetGlyphIndicesW 
            // for things like bitmap/vector fonts
            SCRIPT_CACHE sc = nullptr;
            HRESULT rv = ScriptGetCMap(dc, &sc, str, 1, 0, glyph);
            if (rv == S_OK)
                hasGlyph = true;
        }

        SelectObject(dc, oldFont);
        ReleaseDC(nullptr, dc);

        if (hasGlyph) {
            mCharacterMap->set(aCh);
            return true;
        }
    } else {
        // font had a cmap so simply check that
        return mCharacterMap->test(aCh);
    }

    return false;
}

void
GDIFontEntry::InitLogFont(const nsAString& aName,
                          gfxWindowsFontType aFontType)
{
#define CLIP_TURNOFF_FONTASSOCIATION 0x40

    mLogFont.lfHeight = -1;

    // Fill in logFont structure
    mLogFont.lfWidth          = 0;
    mLogFont.lfEscapement     = 0;
    mLogFont.lfOrientation    = 0;
    mLogFont.lfUnderline      = FALSE;
    mLogFont.lfStrikeOut      = FALSE;
    mLogFont.lfCharSet        = DEFAULT_CHARSET;
    mLogFont.lfOutPrecision   = FontTypeToOutPrecision(aFontType);
    mLogFont.lfClipPrecision  = CLIP_TURNOFF_FONTASSOCIATION;
    mLogFont.lfQuality        = DEFAULT_QUALITY;
    mLogFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
    // always force lfItalic if we want it.  Font selection code will
    // do its best to give us an italic font entry, but if no face exists
    // it may give us a regular one based on weight.  Windows should
    // do fake italic for us in that case.
    mLogFont.lfItalic         = !IsUpright();
    mLogFont.lfWeight         = Weight().Min().ToIntRounded();

    int len = std::min<int>(aName.Length(), LF_FACESIZE - 1);
    memcpy(&mLogFont.lfFaceName, aName.BeginReading(), len * sizeof(char16_t));
    mLogFont.lfFaceName[len] = '\0';
}

GDIFontEntry* 
GDIFontEntry::CreateFontEntry(const nsAString& aName,
                              gfxWindowsFontType aFontType,
                              SlantStyleRange aStyle,
                              WeightRange aWeight,
                              StretchRange aStretch,
                              gfxUserFontData* aUserFontData)
{
    // jtdfix - need to set charset, unicode ranges, pitch/family

    GDIFontEntry *fe = new GDIFontEntry(aName, aFontType, aStyle,
                                        aWeight, aStretch, aUserFontData);

    return fe;
}

void
GDIFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                     FontListSizes* aSizes) const
{
    aSizes->mFontListSize += aMallocSizeOf(this);
    AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}

/***************************************************************
 *
 * GDIFontFamily
 *
 */

static bool
ShouldIgnoreItalicStyle(const nsAString& aName)
{
    // Ignore italic style's "Meiryo" because "Meiryo (Bold) Italic" has
    // non-italic style glyphs as Japanese characters.  However, using it
    // causes serious problem if web pages wants some elements to be
    // different style from others only with font-style.  For example,
    // <em> and <i> should be rendered as italic in the default style.
    return aName.EqualsLiteral("Meiryo") ||
           aName.Equals(NS_LITERAL_STRING(u"\x30E1\x30A4\x30EA\x30AA"));
}

int CALLBACK
GDIFontFamily::FamilyAddStylesProc(const ENUMLOGFONTEXW *lpelfe,
                                        const NEWTEXTMETRICEXW *nmetrics,
                                        DWORD fontType, LPARAM data)
{
    const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;
    LOGFONTW logFont = lpelfe->elfLogFont;
    GDIFontFamily *ff = reinterpret_cast<GDIFontFamily*>(data);

    if (logFont.lfItalic && ShouldIgnoreItalicStyle(ff->mName)) {
        return 1;
    }

    // Some fonts claim to support things > 900, but we don't so clamp the sizes
    logFont.lfWeight = clamped(logFont.lfWeight, LONG(100), LONG(900));

    gfxWindowsFontType feType = GDIFontEntry::DetermineFontType(metrics, fontType);

    GDIFontEntry *fe = nullptr;
    for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) {
        fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get());
        if (feType > fe->mFontType) {
            // if the new type is better than the old one, remove the old entries
            ff->mAvailableFonts.RemoveElementAt(i);
            --i;
        } else if (feType < fe->mFontType) {
            // otherwise if the new type is worse, skip it
            return 1;
        }
    }

    for (uint32_t i = 0; i < ff->mAvailableFonts.Length(); ++i) {
        fe = static_cast<GDIFontEntry*>(ff->mAvailableFonts[i].get());
        // check if we already know about this face
        if (fe->Weight().Min() == FontWeight(int32_t(logFont.lfWeight)) &&
            fe->IsItalic() == (logFont.lfItalic == 0xFF)) {
            // update the charset bit here since this could be different
            // XXX Can we still do this now that we store mCharset
            // on the font family rather than the font entry?
            ff->mCharset.set(metrics.tmCharSet);
            return 1; 
        }
    }

    // We can't set the hasItalicFace flag correctly here,
    // because we might not have seen the family's italic face(s) yet.
    // So we'll set that flag for all members after loading all the faces.
    auto italicStyle = (logFont.lfItalic == 0xFF ?
                           FontSlantStyle::Italic() : FontSlantStyle::Normal());
    fe = GDIFontEntry::CreateFontEntry(nsDependentString(lpelfe->elfFullName),
                                       feType, SlantStyleRange(italicStyle),
                                       WeightRange(FontWeight(int32_t(logFont.lfWeight))),
                                       StretchRange(FontStretch::Normal()),
                                       nullptr);
    if (!fe)
        return 1;

    ff->AddFontEntry(fe);

    if (nmetrics->ntmFontSig.fsUsb[0] != 0x00000000 &&
        nmetrics->ntmFontSig.fsUsb[1] != 0x00000000 &&
        nmetrics->ntmFontSig.fsUsb[2] != 0x00000000 &&
        nmetrics->ntmFontSig.fsUsb[3] != 0x00000000) {

        // set the unicode ranges
        uint32_t x = 0;
        for (uint32_t i = 0; i < 4; ++i) {
            DWORD range = nmetrics->ntmFontSig.fsUsb[i];
            for (uint32_t k = 0; k < 32; ++k) {
                fe->mUnicodeRanges.set(x++, (range & (1 << k)) != 0);
            }
        }
    }

    if (LOG_FONTLIST_ENABLED()) {
        LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
             " with style: %s weight: %d stretch: normal",
             NS_ConvertUTF16toUTF8(fe->Name()).get(),
             NS_ConvertUTF16toUTF8(ff->Name()).get(),
             (logFont.lfItalic == 0xff) ? "italic" : "normal",
             logFont.lfWeight));
    }
    return 1;
}

void
GDIFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
{
    if (mHasStyles)
        return;
    mHasStyles = true;

    HDC hdc = GetDC(nullptr);
    SetGraphicsMode(hdc, GM_ADVANCED);

    LOGFONTW logFont;
    memset(&logFont, 0, sizeof(LOGFONTW));
    logFont.lfCharSet = DEFAULT_CHARSET;
    logFont.lfPitchAndFamily = 0;
    uint32_t l = std::min<uint32_t>(mName.Length(), LF_FACESIZE - 1);
    memcpy(logFont.lfFaceName, mName.get(), l * sizeof(char16_t));

    EnumFontFamiliesExW(hdc, &logFont,
                        (FONTENUMPROCW)GDIFontFamily::FamilyAddStylesProc,
                        (LPARAM)this, 0);
    if (LOG_FONTLIST_ENABLED() && mAvailableFonts.Length() == 0) {
        LOG_FONTLIST(("(fontlist) no styles available in family \"%s\"",
                      NS_ConvertUTF16toUTF8(mName).get()));
    }

    ReleaseDC(nullptr, hdc);

    if (mIsBadUnderlineFamily) {
        SetBadUnderlineFonts();
    }
}

/***************************************************************
 *
 * gfxGDIFontList
 *
 */

gfxGDIFontList::gfxGDIFontList()
    : mFontSubstitutes(32)
{
#ifdef MOZ_BUNDLED_FONTS
    ActivateBundledFonts();
#endif
}

static void
RemoveCharsetFromFontSubstitute(nsAString &aName)
{
    int32_t comma = aName.FindChar(char16_t(','));
    if (comma >= 0)
        aName.Truncate(comma);
}

#define MAX_VALUE_NAME 512
#define MAX_VALUE_DATA 512

nsresult
gfxGDIFontList::GetFontSubstitutes()
{
    HKEY hKey;
    DWORD i, rv, lenAlias, lenActual, valueType;
    WCHAR aliasName[MAX_VALUE_NAME];
    WCHAR actualName[MAX_VALUE_DATA];

    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, 
          L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes",
          0, KEY_READ, &hKey) != ERROR_SUCCESS)
    {
        return NS_ERROR_FAILURE;
    }

    for (i = 0, rv = ERROR_SUCCESS; rv != ERROR_NO_MORE_ITEMS; i++) {
        aliasName[0] = 0;
        lenAlias = ArrayLength(aliasName);
        actualName[0] = 0;
        lenActual = sizeof(actualName);
        rv = RegEnumValueW(hKey, i, aliasName, &lenAlias, nullptr, &valueType,
                (LPBYTE)actualName, &lenActual);

        if (rv != ERROR_SUCCESS || valueType != REG_SZ || lenAlias == 0) {
            continue;
        }

        if (aliasName[0] == WCHAR('@')) {
            continue;
        }

        nsAutoString substituteName((char16_t*) aliasName);
        nsAutoString actualFontName((char16_t*) actualName);
        RemoveCharsetFromFontSubstitute(substituteName);
        BuildKeyNameFromFontName(substituteName);
        RemoveCharsetFromFontSubstitute(actualFontName);
        BuildKeyNameFromFontName(actualFontName);
        gfxFontFamily *ff;
        if (!actualFontName.IsEmpty() && 
            (ff = mFontFamilies.GetWeak(actualFontName))) {
            mFontSubstitutes.Put(substituteName, ff);
        } else {
            mNonExistingFonts.AppendElement(substituteName);
        }
    }

    // "Courier" on a default Windows install is an ugly bitmap font.
    // If there is no substitution for Courier in the registry
    // substitute "Courier" with "Courier New".
    nsAutoString substituteName;
    substituteName.AssignLiteral("Courier");
    BuildKeyNameFromFontName(substituteName);
    if (!mFontSubstitutes.GetWeak(substituteName)) {
        gfxFontFamily *ff;
        nsAutoString actualFontName;
        actualFontName.AssignLiteral("Courier New");
        BuildKeyNameFromFontName(actualFontName);
        ff = mFontFamilies.GetWeak(actualFontName);
        if (ff) {
            mFontSubstitutes.Put(substituteName, ff);
        }
    }
    return NS_OK;
}

nsresult
gfxGDIFontList::InitFontListForPlatform()
{
    Telemetry::AutoTimer<Telemetry::GDI_INITFONTLIST_TOTAL> timer;

    mFontSubstitutes.Clear();
    mNonExistingFonts.Clear();

    // iterate over available families
    LOGFONTW logfont;
    memset(&logfont, 0, sizeof(logfont));
    logfont.lfCharSet = DEFAULT_CHARSET;

    AutoDC hdc;
    int result = EnumFontFamiliesExW(hdc.GetDC(), &logfont,
                                     (FONTENUMPROCW)&EnumFontFamExProc,
                                     0, 0);

    GetFontSubstitutes();

    GetPrefsAndStartLoader();

    return NS_OK;
}

int CALLBACK
gfxGDIFontList::EnumFontFamExProc(ENUMLOGFONTEXW *lpelfe,
                                      NEWTEXTMETRICEXW *lpntme,
                                      DWORD fontType,
                                      LPARAM lParam)
{
    const NEWTEXTMETRICW& metrics = lpntme->ntmTm;
    const LOGFONTW& lf = lpelfe->elfLogFont;

    if (lf.lfFaceName[0] == '@') {
        return 1;
    }

    nsAutoString name(lf.lfFaceName);
    BuildKeyNameFromFontName(name);

    gfxGDIFontList *fontList = PlatformFontList();

    if (!fontList->mFontFamilies.GetWeak(name)) {
        nsDependentString faceName(lf.lfFaceName);
        RefPtr<GDIFontFamily> family = new GDIFontFamily(faceName);
        fontList->mFontFamilies.Put(name, family);

        // if locale is such that CJK font names are the default coming from
        // GDI, then if a family name is non-ASCII immediately read in other
        // family names.  This assures that MS Gothic, MS Mincho are all found
        // before lookups begin.
        if (!IsASCII(faceName)) {
            family->ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList());
        }

        if (fontList->mBadUnderlineFamilyNames.Contains(name))
            family->SetBadUnderlineFamily();

        family->mWindowsFamily = lf.lfPitchAndFamily & 0xF0;
        family->mWindowsPitch = lf.lfPitchAndFamily & 0x0F;

        // mark the charset bit
        family->mCharset.set(metrics.tmCharSet);
    }

    return 1;
}

gfxFontEntry* 
gfxGDIFontList::LookupLocalFont(const nsAString& aFontName,
                                WeightRange aWeightForEntry,
                                StretchRange aStretchForEntry,
                                SlantStyleRange aStyleForEntry)
{
    gfxFontEntry *lookup;

    lookup = LookupInFaceNameLists(aFontName);
    if (!lookup) {
        return nullptr;
    }

    bool isCFF = false; // jtdfix -- need to determine this
    
    // use the face name from the lookup font entry, which will be the localized
    // face name which GDI mapping tables use (e.g. with the system locale set to
    // Dutch, a fullname of 'Arial Bold' will find a font entry with the face name
    // 'Arial Vet' which can be used as a key in GDI font lookups).
    GDIFontEntry *fe = GDIFontEntry::CreateFontEntry(lookup->Name(), 
        gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE : GFX_FONT_TYPE_TRUETYPE) /*type*/, 
        lookup->SlantStyle(), lookup->Weight(), aStretchForEntry, nullptr);

    if (!fe)
        return nullptr;

    fe->mIsLocalUserFont = true;

    // make the new font entry match the userfont entry style characteristics
    fe->mWeightRange = aWeightForEntry;
    fe->mStyleRange = aStyleForEntry;
    fe->mStretchRange = aStretchForEntry;

    return fe;
}

// If aFontData contains only a MS/Symbol cmap subtable, not MS/Unicode,
// we modify the subtable header to mark it as Unicode instead, because
// otherwise GDI will refuse to load the font.
// NOTE that this function does not bounds-check every access to the font data.
// This is OK because we only use it on data that has already been validated
// by OTS, and therefore we will not hit out-of-bounds accesses here.
static bool
FixupSymbolEncodedFont(uint8_t* aFontData, uint32_t aLength)
{
    struct CmapHeader {
        AutoSwap_PRUint16 version;
        AutoSwap_PRUint16 numTables;
    };
    struct CmapEncodingRecord {
        AutoSwap_PRUint16 platformID;
        AutoSwap_PRUint16 encodingID;
        AutoSwap_PRUint32 offset;
    };
    const uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
    const TableDirEntry* dir =
        gfxFontUtils::FindTableDirEntry(aFontData, kCMAP);
    if (dir && uint32_t(dir->length) >= sizeof(CmapHeader)) {
        CmapHeader *cmap =
            reinterpret_cast<CmapHeader*>(aFontData + uint32_t(dir->offset));
        CmapEncodingRecord *encRec =
            reinterpret_cast<CmapEncodingRecord*>(cmap + 1);
        int32_t symbolSubtable = -1;
        for (uint32_t i = 0; i < (uint16_t)cmap->numTables; ++i) {
            if (uint16_t(encRec[i].platformID) !=
                gfxFontUtils::PLATFORM_ID_MICROSOFT) {
                continue; // only interested in MS platform
            }
            if (uint16_t(encRec[i].encodingID) ==
                gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP) {
                // We've got a Microsoft/Unicode table, so don't interfere.
                symbolSubtable = -1;
                break;
            }
            if (uint16_t(encRec[i].encodingID) ==
                gfxFontUtils::ENCODING_ID_MICROSOFT_SYMBOL) {
                // Found a symbol subtable; remember it for possible fixup,
                // but if we subsequently find a Microsoft/Unicode subtable,
                // we'll cancel this.
                symbolSubtable = i;
            }
        }
        if (symbolSubtable != -1) {
            // We found a windows/symbol cmap table, and no windows/unicode one;
            // change the encoding ID so that AddFontMemResourceEx will accept it
            encRec[symbolSubtable].encodingID =
                gfxFontUtils::ENCODING_ID_MICROSOFT_UNICODEBMP;
            return true;
        }
    }
    return false;
}

gfxFontEntry*
gfxGDIFontList::MakePlatformFont(const nsAString& aFontName,
                                 WeightRange aWeightForEntry,
                                 StretchRange aStretchForEntry,
                                 SlantStyleRange aStyleForEntry,
                                 const uint8_t* aFontData,
                                 uint32_t aLength)
{
    // MakePlatformFont is responsible for deleting the font data with free
    // so we set up a stack object to ensure it is freed even if we take an
    // early exit
    struct FontDataDeleter {
        explicit FontDataDeleter(const uint8_t* aFontData)
            : mFontData(aFontData) { }
        ~FontDataDeleter() { free((void*)mFontData); }
        const uint8_t *mFontData;
    };
    FontDataDeleter autoDelete(aFontData);

    bool isCFF = gfxFontUtils::IsCffFont(aFontData);

    nsresult rv;
    HANDLE fontRef = nullptr;

    nsAutoString uniqueName;
    rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
    if (NS_FAILED(rv))
        return nullptr;

    FallibleTArray<uint8_t> newFontData;

    rv = gfxFontUtils::RenameFont(uniqueName, aFontData, aLength, &newFontData);

    if (NS_FAILED(rv))
        return nullptr;
        
    DWORD numFonts = 0;

    uint8_t *fontData = reinterpret_cast<uint8_t*> (newFontData.Elements());
    uint32_t fontLength = newFontData.Length();
    NS_ASSERTION(fontData, "null font data after renaming");

    // http://msdn.microsoft.com/en-us/library/ms533942(VS.85).aspx
    // "A font that is added by AddFontMemResourceEx is always private 
    //  to the process that made the call and is not enumerable."
    fontRef = AddFontMemResourceEx(fontData, fontLength, 
                                    0 /* reserved */, &numFonts);
    if (!fontRef) {
        if (FixupSymbolEncodedFont(fontData, fontLength)) {
            fontRef = AddFontMemResourceEx(fontData, fontLength, 0, &numFonts);
        }
    }
    if (!fontRef) {
        return nullptr;
    }

    // only load fonts with a single face contained in the data
    // AddFontMemResourceEx generates an additional face name for
    // vertical text if the font supports vertical writing but since
    // the font is referenced via the name this can be ignored
    if (fontRef && numFonts > 2) {
        RemoveFontMemResourceEx(fontRef);
        return nullptr;
    }

    // make a new font entry using the unique name
    WinUserFontData *winUserFontData = new WinUserFontData(fontRef);
    GDIFontEntry *fe = GDIFontEntry::CreateFontEntry(uniqueName,
        gfxWindowsFontType(isCFF ? GFX_FONT_TYPE_PS_OPENTYPE : GFX_FONT_TYPE_TRUETYPE) /*type*/,
        aStyleForEntry, aWeightForEntry, aStretchForEntry, winUserFontData);

    if (fe) {
      fe->mIsDataUserFont = true;
    }

    return fe;
}

bool
gfxGDIFontList::FindAndAddFamilies(const nsAString& aFamily,
                                   nsTArray<FamilyAndGeneric>* aOutput,
                                   FindFamiliesFlags aFlags,
                                   gfxFontStyle* aStyle,
                                   gfxFloat aDevToCssSize)
{
    nsAutoString keyName(aFamily);
    BuildKeyNameFromFontName(keyName);

    gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName);
    if (ff) {
        aOutput->AppendElement(FamilyAndGeneric(ff));
        return true;
    }

    if (mNonExistingFonts.Contains(keyName)) {
        return false;
    }

    return gfxPlatformFontList::FindAndAddFamilies(aFamily,
                                                   aOutput,
                                                   aFlags,
                                                   aStyle,
                                                   aDevToCssSize);
}

gfxFontFamily*
gfxGDIFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
{
    gfxFontFamily *ff = nullptr;

    // this really shouldn't fail to find a font....
    NONCLIENTMETRICSW ncm;
    ncm.cbSize = sizeof(ncm);
    BOOL status = ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, 
                                          sizeof(ncm), &ncm, 0);
    if (status) {
        ff = FindFamily(nsDependentString(ncm.lfMessageFont.lfFaceName));
        if (ff) {
            return ff;
        }
    }

    // ...but just in case, try another (long-deprecated) approach as well
    HGDIOBJ hGDI = ::GetStockObject(DEFAULT_GUI_FONT);
    LOGFONTW logFont;
    if (hGDI && ::GetObjectW(hGDI, sizeof(logFont), &logFont)) {
        ff = FindFamily(nsDependentString(logFont.lfFaceName));
    }

    return ff;
}

void
gfxGDIFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                       FontListSizes* aSizes) const
{
    gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
    aSizes->mFontListSize +=
        SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf);
    aSizes->mFontListSize +=
        mNonExistingFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
    for (uint32_t i = 0; i < mNonExistingFonts.Length(); ++i) {
        aSizes->mFontListSize +=
            mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
    }
}

void
gfxGDIFontList::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                       FontListSizes* aSizes) const
{
    aSizes->mFontListSize += aMallocSizeOf(this);
    AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}

// used to load system-wide font info on off-main thread
class GDIFontInfo : public FontInfoData {
public:
    GDIFontInfo(bool aLoadOtherNames,
                bool aLoadFaceNames,
                bool aLoadCmaps) :
        FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
    {}

    virtual ~GDIFontInfo() {}

    virtual void Load() {
        mHdc = GetDC(nullptr);
        SetGraphicsMode(mHdc, GM_ADVANCED);
        FontInfoData::Load();
        ReleaseDC(nullptr, mHdc);
    }

    // loads font data for all members of a given family
    virtual void LoadFontFamilyData(const nsAString& aFamilyName);

    // callback for GDI EnumFontFamiliesExW call
    static int CALLBACK EnumerateFontsForFamily(const ENUMLOGFONTEXW *lpelfe,
                                                const NEWTEXTMETRICEXW *nmetrics,
                                                DWORD fontType, LPARAM data);

    HDC mHdc;
};

struct EnumerateFontsForFamilyData {
    EnumerateFontsForFamilyData(const nsAString& aFamilyName,
                                GDIFontInfo& aFontInfo)
        : mFamilyName(aFamilyName), mFontInfo(aFontInfo)
    {}

    nsString mFamilyName;
    nsTArray<nsString> mOtherFamilyNames;
    GDIFontInfo& mFontInfo;
    nsString mPreviousFontName;
};

int CALLBACK GDIFontInfo::EnumerateFontsForFamily(
                 const ENUMLOGFONTEXW *lpelfe,
                 const NEWTEXTMETRICEXW *nmetrics,
                 DWORD fontType, LPARAM data)
{
    EnumerateFontsForFamilyData *famData =
        reinterpret_cast<EnumerateFontsForFamilyData*>(data);
    HDC hdc = famData->mFontInfo.mHdc;
    LOGFONTW logFont = lpelfe->elfLogFont;
    const NEWTEXTMETRICW& metrics = nmetrics->ntmTm;

    AutoSelectFont font(hdc, &logFont);
    if (!font.IsValid()) {
        return 1;
    }

    FontFaceData fontData;
    nsDependentString fontName(lpelfe->elfFullName);

    // callback called for each style-charset so return if style already seen
    if (fontName.Equals(famData->mPreviousFontName)) {
        return 1;
    }
    famData->mPreviousFontName = fontName;
    famData->mFontInfo.mLoadStats.fonts++;

    // read name table info
    bool nameDataLoaded = false;
    if (famData->mFontInfo.mLoadFaceNames || famData->mFontInfo.mLoadOtherNames) {
        uint32_t kNAME =
            NativeEndian::swapToBigEndian(TRUETYPE_TAG('n','a','m','e'));
        uint32_t nameSize;
        AutoTArray<uint8_t, 1024> nameData;

        nameSize = ::GetFontData(hdc, kNAME, 0, nullptr, 0);
        if (nameSize != GDI_ERROR &&
            nameSize > 0 &&
            nameData.SetLength(nameSize, fallible)) {
            ::GetFontData(hdc, kNAME, 0, nameData.Elements(), nameSize);

            // face names
            if (famData->mFontInfo.mLoadFaceNames) {
                gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), nameSize,
                                                gfxFontUtils::NAME_ID_FULL,
                                                fontData.mFullName);
                gfxFontUtils::ReadCanonicalName((const char*)(nameData.Elements()), nameSize,
                                                gfxFontUtils::NAME_ID_POSTSCRIPT,
                                                fontData.mPostscriptName);
                nameDataLoaded = true;
                famData->mFontInfo.mLoadStats.facenames++;
            }

            // other family names
            if (famData->mFontInfo.mLoadOtherNames) {
                gfxFontFamily::ReadOtherFamilyNamesForFace(famData->mFamilyName,
                                                           (const char*)(nameData.Elements()),
                                                           nameSize,
                                                           famData->mOtherFamilyNames,
                                                           false);
            }
        }
    }

    // read cmap
    bool cmapLoaded = false;
    gfxWindowsFontType feType =
        GDIFontEntry::DetermineFontType(metrics, fontType);
    if (famData->mFontInfo.mLoadCmaps &&
        (feType == GFX_FONT_TYPE_PS_OPENTYPE ||
         feType == GFX_FONT_TYPE_TT_OPENTYPE ||
         feType == GFX_FONT_TYPE_TRUETYPE))
    {
        uint32_t kCMAP =
            NativeEndian::swapToBigEndian(TRUETYPE_TAG('c','m','a','p'));
        uint32_t cmapSize;
        AutoTArray<uint8_t, 1024> cmapData;

        cmapSize = ::GetFontData(hdc, kCMAP, 0, nullptr, 0);
        if (cmapSize != GDI_ERROR &&
            cmapSize > 0 &&
            cmapData.SetLength(cmapSize, fallible)) {
            ::GetFontData(hdc, kCMAP, 0, cmapData.Elements(), cmapSize);
            bool cmapLoaded = false;
            RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
            uint32_t offset;

            if (NS_SUCCEEDED(gfxFontUtils::ReadCMAP(cmapData.Elements(),
                                                    cmapSize, *charmap,
                                                    offset))) {
                fontData.mCharacterMap = charmap;
                fontData.mUVSOffset = offset;
                cmapLoaded = true;
                famData->mFontInfo.mLoadStats.cmaps++;
            }
        }
    }

    if (cmapLoaded || nameDataLoaded) {
        famData->mFontInfo.mFontFaceData.Put(fontName, fontData);
    }

    return famData->mFontInfo.mCanceled ? 0 : 1;
}

void
GDIFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
{
    // iterate over the family
    LOGFONTW logFont;
    memset(&logFont, 0, sizeof(LOGFONTW));
    logFont.lfCharSet = DEFAULT_CHARSET;
    logFont.lfPitchAndFamily = 0;
    uint32_t l = std::min<uint32_t>(aFamilyName.Length(), LF_FACESIZE - 1);
    memcpy(logFont.lfFaceName, aFamilyName.BeginReading(), l * sizeof(char16_t));

    EnumerateFontsForFamilyData data(aFamilyName, *this);

    EnumFontFamiliesExW(mHdc, &logFont,
                        (FONTENUMPROCW)GDIFontInfo::EnumerateFontsForFamily,
                        (LPARAM)(&data), 0);

    // if found other names, insert them
    if (data.mOtherFamilyNames.Length() != 0) {
        mOtherFamilyNames.Put(aFamilyName, data.mOtherFamilyNames);
        mLoadStats.othernames += data.mOtherFamilyNames.Length();
    }
}

already_AddRefed<FontInfoData>
gfxGDIFontList::CreateFontInfoData()
{
    bool loadCmaps = !UsesSystemFallback() ||
        gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();

    RefPtr<GDIFontInfo> fi =
        new GDIFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);

    return fi.forget();
}

gfxFontFamily*
gfxGDIFontList::CreateFontFamily(const nsAString& aName) const
{
    return new GDIFontFamily(aName);
}

#ifdef MOZ_BUNDLED_FONTS

void
gfxGDIFontList::ActivateBundledFonts()
{
    nsCOMPtr<nsIFile> localDir;
    nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
    if (NS_FAILED(rv)) {
        return;
    }
    if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
        return;
    }
    bool isDir;
    if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
        return;
    }

    nsCOMPtr<nsIDirectoryEnumerator> e;
    rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
    if (NS_FAILED(rv)) {
        return;
    }

    nsCOMPtr<nsIFile> file;
    while (NS_SUCCEEDED(e->GetNextFile(getter_AddRefs(file))) && file) {
        nsAutoString path;
        if (NS_FAILED(file->GetPath(path))) {
            continue;
        }
        AddFontResourceExW(path.get(), FR_PRIVATE, nullptr);
    }
}

#endif
