// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkFontConfigFreeTypeTools.h"

#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkSmartPointer.h"
#include "vtkTextProperty.h"

#include <cstdint>

#include <fontconfig/fontconfig.h>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkFontConfigFreeTypeTools);

namespace
{
// The FreeType face requester callback:
FT_Error vtkFontConfigFreeTypeToolsFaceRequester(
  FTC_FaceID face_id, FT_Library lib, FT_Pointer request_data, FT_Face* face)
{
  // Get a pointer to the current vtkFontConfigFreeTypeTools object
  vtkFontConfigFreeTypeTools* self = reinterpret_cast<vtkFontConfigFreeTypeTools*>(request_data);

  // Map the ID to a text property
  vtkSmartPointer<vtkTextProperty> tprop = vtkSmartPointer<vtkTextProperty>::New();
  self->MapIdToTextProperty(reinterpret_cast<intptr_t>(face_id), tprop);

  bool faceIsSet = self->GetForceCompiledFonts() || tprop->GetFontFamily() == VTK_FONT_FILE
    ? false
    : vtkFontConfigFreeTypeTools::LookupFaceFontConfig(tprop, lib, face);

  // Fall back to compiled fonts if lookup fails/compiled fonts are forced:
  if (!faceIsSet)
  {
    faceIsSet = vtkFontConfigFreeTypeTools::Superclass::LookupFace(tprop, lib, face);
  }

  if (!faceIsSet)
  {
    return static_cast<FT_Error>(1);
  }

  if (tprop->GetOrientation() != 0.0)
  {
    // FreeType documentation says that the transform should not be set
    // but we cache faces also by transform, so that there is a unique
    // (face, orientation) cache entry
    FT_Matrix matrix;
    float angle = vtkMath::RadiansFromDegrees(tprop->GetOrientation());
    matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
    matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
    matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
    matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
    FT_Set_Transform(*face, &matrix, nullptr);
  }

  return static_cast<FT_Error>(0);
}
} // end anon namespace

void vtkFontConfigFreeTypeTools::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

vtkFontConfigFreeTypeTools::vtkFontConfigFreeTypeTools() = default;

vtkFontConfigFreeTypeTools::~vtkFontConfigFreeTypeTools() = default;

FT_Error vtkFontConfigFreeTypeTools::CreateFTCManager()
{
  return FTC_Manager_New(*this->GetLibrary(), this->MaximumNumberOfFaces,
    this->MaximumNumberOfSizes, this->MaximumNumberOfBytes, vtkFontConfigFreeTypeToolsFaceRequester,
    static_cast<FT_Pointer>(this), this->CacheManager);
}

bool vtkFontConfigFreeTypeTools::LookupFaceFontConfig(
  vtkTextProperty* tprop, FT_Library lib, FT_Face* face)
{
  if (!FcInit())
  {
    return false;
  }

  // Query tprop
  const FcChar8* family = reinterpret_cast<const FcChar8*>(tprop->GetFontFamilyAsString());
  const double pointSize = static_cast<double>(tprop->GetFontSize());
  const int weight = tprop->GetBold() ? FC_WEIGHT_BOLD : FC_WEIGHT_MEDIUM;
  const int slant = tprop->GetItalic() ? FC_SLANT_ITALIC : FC_SLANT_ROMAN;

  // Build pattern
  FcPattern* pattern = FcPatternCreate();
  FcPatternAddString(pattern, FC_FAMILY, family);
  FcPatternAddDouble(pattern, FC_SIZE, pointSize);
  FcPatternAddInteger(pattern, FC_WEIGHT, weight);
  FcPatternAddInteger(pattern, FC_SLANT, slant);
  FcPatternAddBool(pattern, FC_SCALABLE, true);

  // Prefer fonts that have at least greek characters:
  FcCharSet* charSet = FcCharSetCreate();
  FcCharSetAddChar(charSet, static_cast<FcChar32>(948)); // lowercase delta
  FcPatternAddCharSet(pattern, FC_CHARSET, charSet);

  // Replace common font names, e.g. arial, times, etc -> sans, serif, etc
  FcConfigSubstitute(nullptr, pattern, FcMatchPattern);

  // Fill in any missing defaults:
  FcDefaultSubstitute(pattern);

  // Match pattern
  FcResult result;
  FcFontSet* fontMatches = FcFontSort(nullptr, pattern, false, nullptr, &result);
  FcPatternDestroy(pattern);
  pattern = nullptr;
  if (!fontMatches || fontMatches->nfont == 0)
  {
    if (fontMatches)
    {
      FcFontSetDestroy(fontMatches);
    }
    return false;
  }

  // Grab the first match that is scalable -- even though we've requested
  // scalable fonts in the match, FC seems to not weigh that option very heavily
  FcPattern* match = nullptr;
  for (int i = 0; i < fontMatches->nfont; ++i)
  {
    match = fontMatches->fonts[i];

    // Ensure that the match is scalable
    FcBool isScalable;
    if (FcPatternGetBool(match, FC_SCALABLE, 0, &isScalable) != FcResultMatch || !isScalable)
    {
      continue;
    }

    FcCharSet* currentFontCharSet;
    if (FcPatternGetCharSet(match, FC_CHARSET, 0, &currentFontCharSet) != FcResultMatch ||
      FcCharSetIntersectCount(charSet, currentFontCharSet) == 0)
    {
      continue;
    }

    break;
  }

  if (!match)
  {
    FcFontSetDestroy(fontMatches);
    FcCharSetDestroy(charSet);
    return false;
  }

  // Get filename. Do not free the filename string -- it is owned by FcPattern
  // "match". Likewise, do not use the filename after match is freed.
  FcChar8* filename;
  result = FcPatternGetString(match, FC_FILE, 0, &filename);

  FT_Error error = FT_New_Face(lib, reinterpret_cast<const char*>(filename), 0, face);

  if (!error)
  {
    vtkDebugWithObjectMacro(vtkFreeTypeTools::GetInstance(),
      << "Loading system font: " << reinterpret_cast<const char*>(filename));
  }

  FcCharSetDestroy(charSet);
  charSet = nullptr;
  FcFontSetDestroy(fontMatches);
  fontMatches = nullptr;

  return !error;
}
VTK_ABI_NAMESPACE_END
