/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * 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/TextUtils.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 "nsPresContext.h"
#include "gfxFontConstants.h"

#include "mozilla/MemoryReporting.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/StaticPrefs_gfx.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 nsACString& aFaceName,
                           gfxWindowsFontType aFontType, SlantStyleRange aStyle,
                           WeightRange aWeight, StretchRange aStretch,
                           gfxUserFontData* aUserFontData)
    : gfxFontEntry(aFaceName), mFontType(aFontType), mForceGDI(false) {
  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) {
    RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap();
    cmap->mBuildOnTheFly = true;
    if (mCharacterMap.compareExchange(nullptr, cmap.get())) {
      Unused << cmap.forget();
    }
    return NS_ERROR_FAILURE;
  }

  RefPtr<gfxCharacterMap> charmap;
  nsresult rv;

  uint32_t uvsOffset = 0;
  if (aFontInfoData &&
      (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) {
    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,
                                  uvsOffset);
    }
  }

  if (NS_SUCCEEDED(rv)) {
    gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
    charmap = pfl->FindCharMap(charmap);
    mHasCmapTable = true;
  } else {
    // if error occurred, initialize to null cmap
    charmap = 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
    charmap->mBuildOnTheFly = true;
  }
  if (mCharacterMap.compareExchange(nullptr, charmap.get())) {
    charmap.get()->AddRef();
  }

  LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zd hash: %8.8x%s\n",
                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", 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 (GetCharacterMap()->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, nullptr);
    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) {
      GetCharacterMap()->set(aCh);
      return true;
    }
  } else {
    // font had a cmap so simply check that
    return GetCharacterMap()->test(aCh);
  }

  return false;
}

void GDIFontEntry::InitLogFont(const nsACString& 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();

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

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

  return new GDIFontEntry(aName, aFontType, aStyle, aWeight, aStretch,
                          aUserFontData);
}

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

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

static bool ShouldIgnoreItalicStyle(const nsACString& 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.EqualsLiteral(
             "\xe3\x83\xa1\xe3\x82\xa4\xe3\x83\xaa\xe3\x82\xaa");
}

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

  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::FromInt(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(
      NS_ConvertUTF16toUTF8(lpelfe->elfFullName), feType,
      SlantStyleRange(italicStyle),
      WeightRange(FontWeight::FromInt(int32_t(logFont.lfWeight))),
      StretchRange(FontStretch::NORMAL), nullptr);
  if (!fe) {
    return 1;
  }

  ff->AddFontEntryLocked(fe);

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

void GDIFontFamily::FindStyleVariationsLocked(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;
  NS_ConvertUTF8toUTF16 name(mName);
  uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1);
  memcpy(logFont.lfFaceName, name.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\"", mName.get()));
  }

  ReleaseDC(nullptr, hdc);

  if (mIsBadUnderlineFamily) {
    SetBadUnderlineFonts();
  }

  CheckForSimpleFamily();
}

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

gfxGDIFontList::gfxGDIFontList() : mFontSubstitutes(32) {
#ifdef MOZ_BUNDLED_FONTS
  if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) {
    TimeStamp start = TimeStamp::Now();
    ActivateBundledFonts();
    TimeStamp end = TimeStamp::Now();
    Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE,
                          (end - start).ToMilliseconds());
  }
#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;
    NS_ConvertUTF16toUTF8 substitute(substituteName);
    NS_ConvertUTF16toUTF8 actual(actualFontName);
    if (!actual.IsEmpty() && (ff = mFontFamilies.GetWeak(actual))) {
      mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{ff});
    } else {
      mNonExistingFonts.AppendElement(substitute);
    }
  }

  // "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);
  NS_ConvertUTF16toUTF8 substitute(substituteName);
  if (!mFontSubstitutes.GetWeak(substitute)) {
    gfxFontFamily* ff;
    nsAutoString actualFontName;
    actualFontName.AssignLiteral("Courier New");
    BuildKeyNameFromFontName(actualFontName);
    NS_ConvertUTF16toUTF8 actual(actualFontName);
    ff = mFontFamilies.GetWeak(actual);
    if (ff) {
      mFontSubstitutes.InsertOrUpdate(substitute, RefPtr{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;
  (void)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);

  NS_ConvertUTF16toUTF8 key(name);

  gfxGDIFontList* fontList = PlatformFontList();
  fontList->mLock.AssertCurrentThreadIn();

  if (!fontList->mFontFamilies.Contains(key)) {
    NS_ConvertUTF16toUTF8 faceName(lf.lfFaceName);
    FontVisibility visibility = FontVisibility::Unknown;  // TODO
    RefPtr<GDIFontFamily> family = new GDIFontFamily(faceName, visibility);
    fontList->mFontFamilies.InsertOrUpdate(key, RefPtr{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.ContainsSorted(key)) {
      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(nsPresContext* aPresContext,
                                              const nsACString& aFontName,
                                              WeightRange aWeightForEntry,
                                              StretchRange aStretchForEntry,
                                              SlantStyleRange aStyleForEntry) {
  AutoLock lock(mLock);

  gfxFontEntry* 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 nsACString& 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(
      NS_ConvertUTF16toUTF8(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::FindAndAddFamiliesLocked(
    nsPresContext* aPresContext, StyleGenericFontFamily aGeneric,
    const nsACString& aFamily, nsTArray<FamilyAndGeneric>* aOutput,
    FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage,
    gfxFloat aDevToCssSize) {
  NS_ConvertUTF8toUTF16 key16(aFamily);
  BuildKeyNameFromFontName(key16);
  NS_ConvertUTF16toUTF8 keyName(key16);

  gfxFontFamily* ff = mFontSubstitutes.GetWeak(keyName);
  FontVisibility level =
      aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
  if (ff && IsVisibleToCSS(*ff, level)) {
    aOutput->AppendElement(FamilyAndGeneric(ff, aGeneric));
    return true;
  }

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

  return gfxPlatformFontList::FindAndAddFamiliesLocked(
      aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage,
      aDevToCssSize);
}

FontFamily gfxGDIFontList::GetDefaultFontForPlatform(
    nsPresContext* aPresContext, const gfxFontStyle* aStyle,
    nsAtom* aLanguage) {
  FontFamily ff;

  // 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(aPresContext,
                    NS_ConvertUTF16toUTF8(ncm.lfMessageFont.lfFaceName));
    if (!ff.IsNull()) {
      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(aPresContext, NS_ConvertUTF16toUTF8(logFont.lfFaceName));
  }

  return ff;
}

void gfxGDIFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                            FontListSizes* aSizes) const {
  gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);

  AutoLock lock(mLock);

  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() = default;

  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 nsACString& 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 nsACString& aFamilyName,
                              GDIFontInfo& aFontInfo)
      : mFamilyName(aFamilyName), mFontInfo(aFontInfo) {}

  nsCString mFamilyName;
  nsTArray<nsCString> mOtherFamilyNames;
  GDIFontInfo& mFontInfo;
  nsCString 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;
  NS_ConvertUTF16toUTF8 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) {
        gfxFontUtils::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);
      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.InsertOrUpdate(fontName, fontData);
  }

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

void GDIFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) {
  // iterate over the family
  LOGFONTW logFont;
  memset(&logFont, 0, sizeof(LOGFONTW));
  logFont.lfCharSet = DEFAULT_CHARSET;
  logFont.lfPitchAndFamily = 0;
  NS_ConvertUTF8toUTF16 name(aFamilyName);
  uint32_t l = std::min<uint32_t>(name.Length(), LF_FACESIZE - 1);
  memcpy(logFont.lfFaceName, name.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.InsertOrUpdate(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 nsACString& aName, FontVisibility aVisibility) const {
  return new GDIFontFamily(aName, aVisibility);
}

#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(u"fonts"_ns))) {
    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
