// Copyright 2016 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/cfx_folderfontinfo.h"

#include <array>
#include <iterator>
#include <limits>
#include <utility>

#include "build/build_config.h"
#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/containers/contains.h"
#include "core/fxcrt/fixed_size_data_vector.h"
#include "core/fxcrt/fx_codepage.h"
#include "core/fxcrt/fx_extension.h"
#include "core/fxcrt/fx_folder.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/span_io.h"
#include "core/fxge/cfx_fontmapper.h"
#include "core/fxge/fx_font.h"

namespace {

struct FontSubst {
  const char* name_;
  const char* subst_name_;
};

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

// Used with std::unique_ptr to automatically call fclose().
struct FxFileCloser {
  inline void operator()(FILE* h) const {
    if (h) {
      fclose(h);
    }
  }
};

}  // namespace

// static
bool CFX_FolderFontInfo::FindFamilyNameMatch(
    ByteStringView family_name,
    const ByteString& installed_font_name) {
  std::optional<size_t> result = installed_font_name.Find(family_name, 0);
  if (!result.has_value()) {
    return false;
  }

  size_t next_index = result.value() + family_name.GetLength();
  // Rule out the case that |family_name| is a substring of
  // |installed_font_name| but their family names are actually different words.
  // For example: "Univers" and "Universal" are not a match because they have
  // different family names, but "Univers" and "Univers Bold" are a match.
  if (installed_font_name.IsValidIndex(next_index) &&
      FXSYS_IsLowerASCII(installed_font_name[next_index])) {
    return false;
  }

  return true;
}

// static
ByteString CFX_FolderFontInfo::ReadStringFromFile(FILE* pFile, uint32_t size) {
  ByteString result;
  {
    // Span's lifetime must end before ReleaseBuffer() below.
    pdfium::span<char> buffer = result.GetBuffer(size);

    if (fxcrt::spanread(buffer.first(size), pFile).size() != size) {
      return ByteString();
    }
  }
  result.ReleaseBuffer(size);
  return result;
}

// static
ByteString CFX_FolderFontInfo::LoadTableFromTT(FILE* pFile,
                                               const uint8_t* pTables,
                                               uint32_t nTables,
                                               uint32_t tag,
                                               FX_FILESIZE fileSize) {
  UNSAFE_TODO({
    for (uint32_t i = 0; i < nTables; i++) {
      // TODO(tsepez): use actual span.
      auto p = pdfium::span(pTables + i * 16, 16u);
      if (fxcrt::GetUInt32MSBFirst(p.first<4u>()) == tag) {
        uint32_t offset = fxcrt::GetUInt32MSBFirst(p.subspan<8u, 4u>());
        uint32_t size = fxcrt::GetUInt32MSBFirst(p.subspan<12u, 4u>());
        if (offset > std::numeric_limits<uint32_t>::max() - size ||
            static_cast<FX_FILESIZE>(offset + size) > fileSize ||
            fseek(pFile, offset, SEEK_SET) < 0) {
          return ByteString();
        }
        return ReadStringFromFile(pFile, size);
      }
    }
  });
  return ByteString();
}

CFX_FolderFontInfo::CFX_FolderFontInfo() = default;

CFX_FolderFontInfo::~CFX_FolderFontInfo() = default;

void CFX_FolderFontInfo::AddPath(const ByteString& path) {
  path_list_.push_back(path);
}

void CFX_FolderFontInfo::EnumFontList(CFX_FontMapper* pMapper) {
  mapper_ = pMapper;
  for (const auto& path : path_list_) {
    ScanPath(path);
  }
}

void CFX_FolderFontInfo::ScanPath(const ByteString& path) {
  std::unique_ptr<FX_Folder> handle = FX_Folder::OpenFolder(path);
  if (!handle) {
    return;
  }

  ByteString filename;
  bool bFolder;
  while (handle->GetNextFile(&filename, &bFolder)) {
    if (bFolder) {
      if (filename == "." || filename == "..") {
        continue;
      }
    } else {
      ByteString ext = filename.Last(4);
      ext.MakeLower();
      if (ext != ".ttf" && ext != ".ttc" && ext != ".otf") {
        continue;
      }
    }

    ByteString fullpath = path;
#if BUILDFLAG(IS_WIN)
    fullpath += "\\";
#else
    fullpath += "/";
#endif

    fullpath += filename;
    bFolder ? ScanPath(fullpath) : ScanFile(fullpath);
  }
}

void CFX_FolderFontInfo::ScanFile(const ByteString& path) {
  std::unique_ptr<FILE, FxFileCloser> pFile(fopen(path.c_str(), "rb"));
  if (!pFile) {
    return;
  }

  fseek(pFile.get(), 0, SEEK_END);

  FX_FILESIZE filesize = ftell(pFile.get());
  fseek(pFile.get(), 0, SEEK_SET);

  uint8_t buffer[12];
  if (fxcrt::spanread(buffer, pFile.get()).size() != sizeof(buffer)) {
    return;
  }
  uint32_t magic = fxcrt::GetUInt32MSBFirst(pdfium::span(buffer).first<4u>());
  if (magic != SystemFontInfoIface::kTableTTCF) {
    ReportFace(path, pFile.get(), filesize, 0);
    return;
  }

  uint32_t nFaces =
      fxcrt::GetUInt32MSBFirst(pdfium::span(buffer).subspan<8u, 4u>());
  FX_SAFE_SIZE_T safe_face_bytes = nFaces;
  safe_face_bytes *= 4;
  if (!safe_face_bytes.IsValid()) {
    return;
  }

  auto offsets =
      FixedSizeDataVector<uint8_t>::Uninit(safe_face_bytes.ValueOrDie());
  if (fxcrt::spanread(offsets.span(), pFile.get()).size() != offsets.size()) {
    return;
  }

  for (uint32_t i = 0; i < nFaces; i++) {
    ReportFace(path, pFile.get(), filesize,
               fxcrt::GetUInt32MSBFirst(offsets.subspan(i * 4).first<4u>()));
  }
}

void CFX_FolderFontInfo::ReportFace(const ByteString& path,
                                    FILE* pFile,
                                    FX_FILESIZE filesize,
                                    uint32_t offset) {
  if (fseek(pFile, offset, SEEK_SET) < 0) {
    return;
  }

  uint8_t buffer[12];
  if (fxcrt::spanread(buffer, pFile).size() != sizeof(buffer)) {
    return;
  }

  uint32_t nTables =
      fxcrt::GetUInt16MSBFirst(pdfium::as_byte_span(buffer).subspan<4, 2>());
  ByteString tables = ReadStringFromFile(pFile, nTables * 16);
  if (tables.IsEmpty()) {
    return;
  }

  static constexpr uint32_t kNameTag =
      CFX_FontMapper::MakeTag('n', 'a', 'm', 'e');
  ByteString names = LoadTableFromTT(pFile, tables.unsigned_str(), nTables,
                                     kNameTag, filesize);
  if (names.IsEmpty()) {
    return;
  }

  ByteString facename = GetNameFromTT(names.unsigned_span(), 1);
  if (facename.IsEmpty()) {
    return;
  }

  ByteString style = GetNameFromTT(names.unsigned_span(), 2);
  if (style != "Regular") {
    facename += " " + style;
  }

  if (pdfium::Contains(font_list_, facename)) {
    return;
  }

  auto pInfo =
      std::make_unique<FontFaceInfo>(path, facename, tables, offset, filesize);
  static constexpr uint32_t kOs2Tag =
      CFX_FontMapper::MakeTag('O', 'S', '/', '2');
  ByteString os2 =
      LoadTableFromTT(pFile, tables.unsigned_str(), nTables, kOs2Tag, filesize);
  if (os2.GetLength() >= 86) {
    pdfium::span<const uint8_t> p = os2.unsigned_span().subspan(78u);
    // `codepages` corresponds to OS/2 table ulCodePageRange1.
    // See https://learn.microsoft.com/en-us/typography/opentype/spec/os2
    uint32_t codepages = fxcrt::GetUInt32MSBFirst(p.first<4u>());
    if (codepages & (1U << 1)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_EasternEuropean);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_EasternEuropean;
    }
    if (codepages & (1U << 2)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Cyrillic);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Cyrillic;
    }
    if (codepages & (1U << 3)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Greek);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Greek;
    }
    if (codepages & (1U << 4)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Turkish);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Turkish;
    }
    if (codepages & (1U << 5)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Hebrew);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Hebrew;
    }
    if (codepages & (1U << 6)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Arabic);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Arabic;
    }
    if (codepages & (1U << 7)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Baltic);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Baltic;
    }
    if (codepages & (1U << 8)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kMSWin_Vietnamese);
      pInfo->charsets_ |= FX_CharsetFlag::kMSWin_Vietnamese;
    }
    if (codepages & (1U << 16)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kThai);
      pInfo->charsets_ |= FX_CharsetFlag::kThai;
    }
    if (codepages & (1U << 17)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kShiftJIS);
      pInfo->charsets_ |= FX_CharsetFlag::kShiftJIS;
    }
    if (codepages & (1U << 18)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kChineseSimplified);
      pInfo->charsets_ |= FX_CharsetFlag::kChineseSimplified;
    }
    if (codepages & (1U << 19)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kHangul);
      pInfo->charsets_ |= FX_CharsetFlag::kHangul;
    }
    if (codepages & (1U << 20)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kChineseTraditional);
      pInfo->charsets_ |= FX_CharsetFlag::kChineseTraditional;
    }
    if (codepages & (1U << 21)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kJohab);
      pInfo->charsets_ |= FX_CharsetFlag::kJohab;
    }
    if (codepages & (1U << 30)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kOEM);
      pInfo->charsets_ |= FX_CharsetFlag::kOEM;
    }
    if (codepages & (1U << 31)) {
      mapper_->AddInstalledFont(facename, FX_Charset::kSymbol);
      pInfo->charsets_ |= FX_CharsetFlag::kSymbol;
    }
  }
  static constexpr uint32_t kMaxpTag =
      CFX_FontMapper::MakeTag('m', 'a', 'x', 'p');
  ByteString maxp = LoadTableFromTT(pFile, tables.unsigned_str(), nTables,
                                    kMaxpTag, filesize);
  if (maxp.GetLength() >= 6) {
    pdfium::span<const uint8_t> p = maxp.unsigned_span().subspan(4u);
    pInfo->glyph_count_ = fxcrt::GetUInt16MSBFirst(p.first<2u>());
  }
  mapper_->AddInstalledFont(facename, FX_Charset::kANSI);
  pInfo->charsets_ |= FX_CharsetFlag::kANSI;
  pInfo->styles_ = 0;
  if (style.Contains("Bold")) {
    pInfo->styles_ |= pdfium::kFontStyleForceBold;
  }
  if (style.Contains("Italic") || style.Contains("Oblique")) {
    pInfo->styles_ |= pdfium::kFontStyleItalic;
  }
  if (facename.Contains("Serif")) {
    pInfo->styles_ |= pdfium::kFontStyleSerif;
  }

  font_list_[facename] = std::move(pInfo);
}

void* CFX_FolderFontInfo::GetSubstFont(const ByteString& face) {
  for (size_t iBaseFont = 0; iBaseFont < std::size(kBase14Substs);
       iBaseFont++) {
    if (face == kBase14Substs[iBaseFont].name_) {
      return GetFont(kBase14Substs[iBaseFont].subst_name_);
    }
  }
  return nullptr;
}

void* CFX_FolderFontInfo::FindFont(int weight,
                                   bool italic,
                                   FX_Charset charset,
                                   int pitch_family,
                                   const ByteString& family,
                                   bool must_match_name) {
  FontFaceInfo* pFind = nullptr;
  FX_CharsetFlag charset_flag = FX_CharsetFlagForCharset(charset);
  int32_t iBestSimilar = 0;
  if (must_match_name) {
    // Try a direct lookup for either a perfect score or to determine a
    // baseline similarity score.
    auto direct_it = font_list_.find(family);
    if (direct_it != font_list_.end()) {
      FontFaceInfo* font = direct_it->second.get();
      if (font->IsEligibleForFindFont(charset_flag, charset)) {
        iBestSimilar = font->SimilarityScore(weight, italic, pitch_family,
                                             must_match_name);
        pFind = font;
        if (iBestSimilar == FontFaceInfo::kSimilarityScoreMax) {
          return font;
        }
      }
    }
  }

  ByteStringView bsFamily = family.AsStringView();
  for (const auto& it : font_list_) {
    FontFaceInfo* font = it.second.get();
    if (!font->IsEligibleForFindFont(charset_flag, charset)) {
      continue;
    }
    int32_t iSimilarValue = font->SimilarityScore(
        weight, italic, pitch_family,
        must_match_name &&
            bsFamily.GetLength() == font->face_name_.GetLength());
    if (IsBetterMatch(font, iSimilarValue, pFind, iBestSimilar, charset, family,
                      must_match_name)) {
      iBestSimilar = iSimilarValue;
      pFind = font;
    }
  }

  if (pFind) {
    return pFind;
  }

  if (charset == FX_Charset::kANSI && FontFamilyIsFixedPitch(pitch_family)) {
    auto* courier_new = GetFont("Courier New");
    if (courier_new) {
      return courier_new;
    }
  }

  return nullptr;
}

void* CFX_FolderFontInfo::MapFont(int weight,
                                  bool italic,
                                  FX_Charset charset,
                                  int pitch_family,
                                  const ByteString& face) {
  return nullptr;
}

void* CFX_FolderFontInfo::GetFont(const ByteString& face) {
  auto it = font_list_.find(face);
  return it != font_list_.end() ? it->second.get() : nullptr;
}

size_t CFX_FolderFontInfo::GetFontData(void* hFont,
                                       uint32_t table,
                                       pdfium::span<uint8_t> buffer) {
  if (!hFont) {
    return 0;
  }

  const FontFaceInfo* font = static_cast<FontFaceInfo*>(hFont);
  uint32_t datasize = 0;
  uint32_t offset = 0;
  if (table == SystemFontInfoIface::kTableNone) {
    datasize = font->font_offset_ ? 0 : font->file_size_;
  } else if (table == SystemFontInfoIface::kTableTTCF) {
    datasize = font->font_offset_ ? font->file_size_ : 0;
  } else {
    size_t nTables = font->font_tables_.GetLength() / 16;
    for (size_t i = 0; i < nTables; i++) {
      // TODO(tsepez): iterate over span.
      pdfium::span<const uint8_t> p =
          font->font_tables_.unsigned_span().subspan(i * 16);
      if (fxcrt::GetUInt32MSBFirst(p.first<4u>()) == table) {
        offset = fxcrt::GetUInt32MSBFirst(p.subspan<8u, 4u>());
        datasize = fxcrt::GetUInt32MSBFirst(p.subspan<12u, 4u>());
      }
    }
  }

  if (!datasize || buffer.size() < datasize) {
    return datasize;
  }

  std::unique_ptr<FILE, FxFileCloser> pFile(
      fopen(font->file_path_.c_str(), "rb"));
  if (!pFile) {
    return 0;
  }

  if (fseek(pFile.get(), offset, SEEK_SET) < 0) {
    return 0;
  }
  if (fxcrt::spanread(buffer.first(datasize), pFile.get()).size() != datasize) {
    return 0;
  }
  return datasize;
}

void CFX_FolderFontInfo::DeleteFont(void* hFont) {}

bool CFX_FolderFontInfo::GetFaceName(void* hFont, ByteString* name) {
  if (!hFont) {
    return false;
  }
  *name = static_cast<FontFaceInfo*>(hFont)->face_name_;
  return true;
}

bool CFX_FolderFontInfo::GetFontCharset(void* hFont, FX_Charset* charset) {
  return false;
}

CFX_FolderFontInfo::FontFaceInfo::FontFaceInfo(ByteString filePath,
                                               ByteString faceName,
                                               ByteString fontTables,
                                               uint32_t fontOffset,
                                               uint32_t fileSize)
    : file_path_(filePath),
      face_name_(faceName),
      font_tables_(fontTables),
      font_offset_(fontOffset),
      file_size_(fileSize) {}

bool CFX_FolderFontInfo::FontFaceInfo::IsEligibleForFindFont(
    FX_CharsetFlag flag,
    FX_Charset charset) const {
  return (charsets_ & flag) || charset == FX_Charset::kDefault;
}

int32_t CFX_FolderFontInfo::FontFaceInfo::SimilarityScore(
    int weight,
    bool italic,
    int pitch_family,
    bool exact_match_bonus) const {
  int32_t score = 0;
  if (FontStyleIsForceBold(styles_) == (weight > 400)) {
    score += 16;
  }
  if (FontStyleIsItalic(styles_) == italic) {
    score += 16;
  }
  if (FontStyleIsSerif(styles_) == FontFamilyIsRoman(pitch_family)) {
    score += 16;
  }
  if (FontStyleIsScript(styles_) == FontFamilyIsScript(pitch_family)) {
    score += 8;
  }
  if (FontStyleIsFixedPitch(styles_) == FontFamilyIsFixedPitch(pitch_family)) {
    score += 8;
  }
  if (exact_match_bonus) {
    score += 4;
  }
  DCHECK_LE(score, kSimilarityScoreMax);
  return score;
}

bool CFX_FolderFontInfo::IsBetterMatch(const FontFaceInfo* candidate,
                                       int32_t candidate_score,
                                       const FontFaceInfo* current_best,
                                       int32_t current_best_score,
                                       FX_Charset charset,
                                       const ByteString& family,
                                       bool must_match_name) const {
  // Avoid the relatively expensive FindFamilyNameMatch() unless there might
  // be a better match.
  if (candidate_score <= current_best_score) {
    return false;
  }
  if (!must_match_name) {
    return true;
  }
  return FindFamilyNameMatch(family.AsStringView(), candidate->face_name_);
}
