// Copyright 2020 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxge/win32/cwin32_platform.h"

#include <array>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>

#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_codepage.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/raw_span.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/win/scoped_select_object.h"
#include "core/fxcrt/win/win_util.h"
#include "core/fxge/cfx_folderfontinfo.h"
#include "core/fxge/cfx_gemodule.h"

namespace {

struct Variant {
  const char* face_name_;
  const wchar_t* variant_name_;
};

constexpr Variant kVariantNames[] = {
    {"DFKai-SB", L"\x6A19\x6977\x9AD4"},
};

struct Substs {
  const char* name_;
  const char* win_name_;
  bool bold_;
  bool italic_;
};

constexpr auto kBase14Substs = std::to_array<const Substs>({
    {"Courier", "Courier New", false, false},
    {"Courier-Bold", "Courier New", true, false},
    {"Courier-BoldOblique", "Courier New", true, true},
    {"Courier-Oblique", "Courier New", false, true},
    {"Helvetica", "Arial", false, false},
    {"Helvetica-Bold", "Arial", true, false},
    {"Helvetica-BoldOblique", "Arial", true, true},
    {"Helvetica-Oblique", "Arial", false, true},
    {"Times-Roman", "Times New Roman", false, false},
    {"Times-Bold", "Times New Roman", true, false},
    {"Times-BoldItalic", "Times New Roman", true, true},
    {"Times-Italic", "Times New Roman", false, true},
});

struct FontNameMap {
  const char* sub_font_name_;
  const char* src_font_name_;
};

constexpr FontNameMap kJpFontNameMap[] = {
    {"MS Mincho", "Heiseimin-W3"},
    {"MS Gothic", "Jun101-Light"},
};

bool GetSubFontName(ByteString* name) {
  UNSAFE_TODO({
    for (size_t i = 0; i < std::size(kJpFontNameMap); ++i) {
      if (!FXSYS_stricmp(name->c_str(), kJpFontNameMap[i].src_font_name_)) {
        *name = kJpFontNameMap[i].sub_font_name_;
        return true;
      }
    }
  });
  return false;
}

// Wraps CreateFontA() so callers don't have to specify all the arguments.
HFONT Win32CreateFont(int weight,
                      bool italic,
                      FX_Charset charset,
                      int pitch_family,
                      const char* face) {
  return ::CreateFontA(-10, 0, 0, 0, weight, italic, 0, 0,
                       static_cast<int>(charset), OUT_TT_ONLY_PRECIS, 0, 0,
                       pitch_family, face);
}

class CFX_Win32FallbackFontInfo final : public CFX_FolderFontInfo {
 public:
  CFX_Win32FallbackFontInfo() = default;
  ~CFX_Win32FallbackFontInfo() override = default;

  // CFX_FolderFontInfo:
  void* MapFont(int weight,
                bool bItalic,
                FX_Charset charset,
                int pitch_family,
                const ByteString& face) override;
};

class CFX_Win32FontInfo final : public SystemFontInfoIface {
 public:
  CFX_Win32FontInfo();
  ~CFX_Win32FontInfo() override;

  // SystemFontInfoIface:
  void EnumFontList(CFX_FontMapper* pMapper) override;
  void* MapFont(int weight,
                bool bItalic,
                FX_Charset charset,
                int pitch_family,
                const ByteString& face) override;
  void* GetFont(const ByteString& face) override { return nullptr; }
  size_t GetFontData(void* hFont,
                     uint32_t table,
                     pdfium::span<uint8_t> buffer) override;
  bool GetFaceName(void* hFont, ByteString* name) override;
  bool GetFontCharset(void* hFont, FX_Charset* charset) override;
  void DeleteFont(void* hFont) override;

  void AddInstalledFont(const LOGFONTA* plf, uint32_t font_type);

 private:
  bool IsSupportedFont(const LOGFONTA* plf);
  void GetGBPreference(ByteString& face, int weight, int pitch_family);
  void GetJapanesePreference(ByteString& face, int weight, int pitch_family);
  ByteString FindFont(const ByteString& name);
  void* GetFontFromList(int weight,
                        bool italic,
                        FX_Charset charset,
                        int pitch_family,
                        pdfium::span<const char* const> font_faces);

  const HDC dc_handle_;
  UnownedPtr<CFX_FontMapper> mapper_;
  ByteString last_family_;
  ByteString kai_ti_;
  ByteString fang_song_;
};

int CALLBACK FontEnumProc(const LOGFONTA* plf,
                          const TEXTMETRICA* lpntme,
                          uint32_t font_type,
                          LPARAM lParam) {
  CFX_Win32FontInfo* font_info = reinterpret_cast<CFX_Win32FontInfo*>(lParam);
  font_info->AddInstalledFont(plf, font_type);
  return 1;
}

CFX_Win32FontInfo::CFX_Win32FontInfo()
    : dc_handle_(CreateCompatibleDC(nullptr)) {}

CFX_Win32FontInfo::~CFX_Win32FontInfo() {
  DeleteDC(dc_handle_);
}

bool CFX_Win32FontInfo::IsSupportedFont(const LOGFONTA* plf) {
  HFONT hFont = CreateFontIndirectA(plf);
  bool ret = false;
  size_t font_size = GetFontData(hFont, SystemFontInfoIface::kTableNone, {});
  if (font_size != GDI_ERROR && font_size >= sizeof(uint32_t)) {
    uint32_t header;
    auto span = pdfium::as_writable_bytes(pdfium::span_from_ref(header));
    GetFontData(hFont, SystemFontInfoIface::kTableNone, span);
    header = fxcrt::GetUInt32MSBFirst(span);
    ret = header == FXBSTR_ID('O', 'T', 'T', 'O') ||
          header == FXBSTR_ID('t', 't', 'c', 'f') ||
          header == FXBSTR_ID('t', 'r', 'u', 'e') || header == 0x00010000 ||
          header == 0x00020000 ||
          (header & 0xFFFF0000) == FXBSTR_ID(0x80, 0x01, 0x00, 0x00) ||
          (header & 0xFFFF0000) == FXBSTR_ID('%', '!', 0, 0);
  }
  DeleteFont(hFont);
  return ret;
}

void CFX_Win32FontInfo::AddInstalledFont(const LOGFONTA* plf,
                                         uint32_t font_type) {
  ByteString name(plf->lfFaceName);
  if (name.GetLength() > 0 && name[0] == '@') {
    return;
  }

  if (name == last_family_) {
    mapper_->AddInstalledFont(name, FX_GetCharsetFromInt(plf->lfCharSet));
    return;
  }
  if (!(font_type & TRUETYPE_FONTTYPE)) {
    if (!(font_type & DEVICE_FONTTYPE) || !IsSupportedFont(plf)) {
      return;
    }
  }

  mapper_->AddInstalledFont(name, FX_GetCharsetFromInt(plf->lfCharSet));
  last_family_ = name;
}

void CFX_Win32FontInfo::EnumFontList(CFX_FontMapper* pMapper) {
  mapper_ = pMapper;
  LOGFONTA lf = {};  // Aggregate initialization.
  static_assert(std::is_aggregate_v<decltype(lf)>);
  lf.lfCharSet = static_cast<int>(FX_Charset::kDefault);
  lf.lfFaceName[0] = 0;
  lf.lfPitchAndFamily = 0;
  EnumFontFamiliesExA(dc_handle_, &lf,
                      reinterpret_cast<FONTENUMPROCA>(FontEnumProc),
                      reinterpret_cast<uintptr_t>(this), 0);
}

ByteString CFX_Win32FontInfo::FindFont(const ByteString& name) {
  if (!mapper_) {
    return name;
  }

  std::optional<ByteString> maybe_installed =
      mapper_->InstalledFontNameStartingWith(name);
  if (maybe_installed.has_value()) {
    return maybe_installed.value();
  }

  std::optional<ByteString> maybe_localized =
      mapper_->LocalizedFontNameStartingWith(name);
  if (maybe_localized.has_value()) {
    return maybe_localized.value();
  }

  return ByteString();
}

void* CFX_Win32FontInfo::GetFontFromList(
    int weight,
    bool italic,
    FX_Charset charset,
    int pitch_family,
    pdfium::span<const char* const> font_faces) {
  DCHECK(!font_faces.empty());

  // Initialization not needed because of DCHECK() above and the assignment in
  // the for-loop below.
  HFONT font;
  for (const char* face : font_faces) {
    font = Win32CreateFont(weight, italic, charset, pitch_family, face);
    ByteString actual_face;
    if (GetFaceName(font, &actual_face) && actual_face.EqualNoCase(face)) {
      break;
    }
  }
  return font;
}

void* CFX_Win32FallbackFontInfo::MapFont(int weight,
                                         bool bItalic,
                                         FX_Charset charset,
                                         int pitch_family,
                                         const ByteString& face) {
  void* font = GetSubstFont(face);
  if (font) {
    return font;
  }

  bool bCJK = true;
  switch (charset) {
    case FX_Charset::kShiftJIS:
    case FX_Charset::kChineseSimplified:
    case FX_Charset::kChineseTraditional:
    case FX_Charset::kHangul:
      break;
    default:
      bCJK = false;
      break;
  }
  return FindFont(weight, bItalic, charset, pitch_family, face, !bCJK);
}

void CFX_Win32FontInfo::GetGBPreference(ByteString& face,
                                        int weight,
                                        int pitch_family) {
  if (face.Contains("KaiTi") || face.Contains("\xbf\xac")) {
    if (kai_ti_.IsEmpty()) {
      kai_ti_ = FindFont("KaiTi");
      if (kai_ti_.IsEmpty()) {
        kai_ti_ = "SimSun";
      }
    }
    face = kai_ti_;
  } else if (face.Contains("FangSong") || face.Contains("\xb7\xc2\xcb\xce")) {
    if (fang_song_.IsEmpty()) {
      fang_song_ = FindFont("FangSong");
      if (fang_song_.IsEmpty()) {
        fang_song_ = "SimSun";
      }
    }
    face = fang_song_;
  } else if (face.Contains("SimSun") || face.Contains("\xcb\xce")) {
    face = "SimSun";
  } else if (face.Contains("SimHei") || face.Contains("\xba\xda")) {
    face = "SimHei";
  } else if (!(pitch_family & FF_ROMAN) && weight > 550) {
    face = "SimHei";
  } else {
    face = "SimSun";
  }
}

void CFX_Win32FontInfo::GetJapanesePreference(ByteString& face,
                                              int weight,
                                              int pitch_family) {
  if (face.Contains("Gothic") ||
      face.Contains("\x83\x53\x83\x56\x83\x62\x83\x4e")) {
    if (face.Contains("PGothic") ||
        face.Contains("\x82\x6f\x83\x53\x83\x56\x83\x62\x83\x4e")) {
      face = "MS PGothic";
    } else if (face.Contains("UI Gothic")) {
      face = "MS UI Gothic";
    } else {
      if (face.Contains("HGSGothicM") || face.Contains("HGMaruGothicMPRO")) {
        face = "MS PGothic";
      } else {
        face = "MS Gothic";
      }
    }
    return;
  }
  if (face.Contains("Mincho") || face.Contains("\x96\xbe\x92\xa9")) {
    if (face.Contains("PMincho") || face.Contains("\x82\x6f\x96\xbe\x92\xa9")) {
      face = "MS PMincho";
    } else {
      face = "MS Mincho";
    }
    return;
  }
  if (GetSubFontName(&face)) {
    return;
  }

  if (!(pitch_family & FF_ROMAN) && weight > 400) {
    face = "MS PGothic";
  } else {
    face = "MS PMincho";
  }
}

void* CFX_Win32FontInfo::MapFont(int weight,
                                 bool bItalic,
                                 FX_Charset charset,
                                 int pitch_family,
                                 const ByteString& face) {
  ByteString new_face = face;
  for (int iBaseFont = 0; iBaseFont < 12; iBaseFont++) {
    if (new_face == ByteStringView(kBase14Substs[iBaseFont].name_)) {
      new_face = kBase14Substs[iBaseFont].win_name_;
      weight = kBase14Substs[iBaseFont].bold_ ? FW_BOLD : FW_NORMAL;
      bItalic = kBase14Substs[iBaseFont].italic_;
      break;
    }
  }
  if (charset == FX_Charset::kANSI || charset == FX_Charset::kSymbol) {
    charset = FX_Charset::kDefault;
  }

  int subst_pitch_family;
  switch (charset) {
    case FX_Charset::kShiftJIS:
      subst_pitch_family = FF_ROMAN;
      break;
    case FX_Charset::kChineseTraditional:
    case FX_Charset::kHangul:
    case FX_Charset::kChineseSimplified:
      subst_pitch_family = 0;
      break;
    default:
      subst_pitch_family = pitch_family;
      break;
  }
  HFONT hFont = Win32CreateFont(weight, bItalic, charset, subst_pitch_family,
                                new_face.c_str());
  ByteString actual_new_face;
  if (GetFaceName(hFont, &actual_new_face) &&
      new_face.EqualNoCase(actual_new_face.AsStringView())) {
    return hFont;
  }

  WideString wsFace = WideString::FromDefANSI(actual_new_face.AsStringView());
  for (const Variant& variant : kVariantNames) {
    if (new_face != variant.face_name_) {
      continue;
    }

    WideString wsName(variant.variant_name_);
    if (wsFace == wsName) {
      return hFont;
    }
  }
  ::DeleteObject(hFont);
  if (charset == FX_Charset::kDefault) {
    return nullptr;
  }

  switch (charset) {
    case FX_Charset::kShiftJIS:
      GetJapanesePreference(new_face, weight, pitch_family);
      break;
    case FX_Charset::kChineseSimplified:
      GetGBPreference(new_face, weight, pitch_family);
      break;
    case FX_Charset::kHangul:
      new_face = "Gulim";
      break;
    case FX_Charset::kChineseTraditional: {
      static const char* const kMonospaceFonts[] = {"Microsoft YaHei",
                                                    "MingLiU"};
      static const char* const kProportionalFonts[] = {"Microsoft JHengHei",
                                                       "PMingLiU"};
      pdfium::span<const char* const> candidate_fonts =
          new_face.Contains("MSung") ? kMonospaceFonts : kProportionalFonts;
      return GetFontFromList(weight, bItalic, charset, subst_pitch_family,
                             candidate_fonts);
    }
    default:
      break;
  }
  return Win32CreateFont(weight, bItalic, charset, subst_pitch_family,
                         new_face.c_str());
}

void CFX_Win32FontInfo::DeleteFont(void* hFont) {
  ::DeleteObject(hFont);
}

size_t CFX_Win32FontInfo::GetFontData(void* hFont,
                                      uint32_t table,
                                      pdfium::span<uint8_t> buffer) {
  pdfium::ScopedSelectObject select_object(dc_handle_,
                                           static_cast<HFONT>(hFont));
  table = fxcrt::FromBE32(table);
  size_t size = ::GetFontData(dc_handle_, table, 0, buffer.data(),
                              pdfium::checked_cast<DWORD>(buffer.size()));
  return size != GDI_ERROR ? size : 0;
}

bool CFX_Win32FontInfo::GetFaceName(void* hFont, ByteString* name) {
  pdfium::ScopedSelectObject select_object(dc_handle_,
                                           static_cast<HFONT>(hFont));
  char facebuf[100];
  if (::GetTextFaceA(dc_handle_, std::size(facebuf), facebuf) == 0) {
    return false;
  }

  *name = facebuf;
  return true;
}

bool CFX_Win32FontInfo::GetFontCharset(void* hFont, FX_Charset* charset) {
  pdfium::ScopedSelectObject select_object(dc_handle_,
                                           static_cast<HFONT>(hFont));
  TEXTMETRIC tm;
  ::GetTextMetrics(dc_handle_, &tm);
  *charset = FX_GetCharsetFromInt(tm.tmCharSet);
  return true;
}

}  // namespace

CWin32Platform::CWin32Platform() = default;

CWin32Platform::~CWin32Platform() = default;

void CWin32Platform::Init() {
  if (pdfium::IsUser32AndGdi32Available()) {
    gdiplus_ext_.Load();
  }
}

void CWin32Platform::Terminate() {}

std::unique_ptr<SystemFontInfoIface>
CWin32Platform::CreateDefaultSystemFontInfo() {
  auto** user_paths = CFX_GEModule::Get()->GetUserFontPaths();
  if (user_paths) {
    auto font_info = std::make_unique<CFX_Win32FallbackFontInfo>();
    UNSAFE_TODO({
      for (; *user_paths; user_paths++) {
        font_info->AddPath(*user_paths);
      }
    });
    return font_info;
  }

  if (pdfium::IsUser32AndGdi32Available()) {
    return std::make_unique<CFX_Win32FontInfo>();
  }

  // Select the fallback font information class if GDI is disabled.
  auto fallback_info = std::make_unique<CFX_Win32FallbackFontInfo>();
  // Construct the font path manually, SHGetKnownFolderPath won't work under
  // a restrictive sandbox.
  CHAR windows_path[MAX_PATH] = {};
  DWORD path_len = ::GetWindowsDirectoryA(windows_path, MAX_PATH);
  if (path_len > 0 && path_len < MAX_PATH) {
    ByteString fonts_path(windows_path);
    fonts_path += "\\Fonts";
    fallback_info->AddPath(fonts_path);
  }
  return fallback_info;
}

// static
std::unique_ptr<CFX_GEModule::PlatformIface>
CFX_GEModule::PlatformIface::Create() {
  return std::make_unique<CWin32Platform>();
}
