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

#include "ui/gfx/harfbuzz_font_skia.h"

#include <stddef.h>
#include <stdint.h>

#include <limits>
#include <map>

#include "base/check_op.h"
#include "base/containers/heap_array.h"
#include "base/containers/lru_cache.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/skia_util.h"

namespace gfx {

namespace {

class TypefaceData;

// Maps from code points to glyph indices in a font.
using GlyphCache = std::map<uint32_t, uint16_t>;

// Wraps a custom user data attached to a hb_font object. Font data provider for
// HarfBuzz using Skia. Copied from Blink.
// TODO(ckocagil): Eliminate the duplication. http://crbug.com/368375
struct FontData {
  explicit FontData(GlyphCache* glyph_cache) : glyph_cache_(glyph_cache) {}

  SkFont font_;
  raw_ptr<GlyphCache> glyph_cache_;
};

// Deletes the object at the given pointer after casting it to the given type.
template<typename Type>
void DeleteByType(void* data) {
  Type* typed_data = reinterpret_cast<Type*>(data);
  delete typed_data;
}

template<typename Type>
void DeleteArrayByType(void* data) {
  Type* typed_data = reinterpret_cast<Type*>(data);
  delete[] typed_data;
}

// Outputs the |width| and |extents| of the glyph with index |codepoint| in
// |paint|'s font.
void GetGlyphWidthAndExtents(const SkFont& font,
                             hb_codepoint_t codepoint,
                             hb_position_t* width,
                             hb_glyph_extents_t* extents) {
  DCHECK_LE(codepoint, std::numeric_limits<uint16_t>::max());

  SkScalar sk_width;
  SkRect sk_bounds;
  uint16_t glyph = static_cast<uint16_t>(codepoint);

  font.getWidthsBounds({&glyph, 1}, {&sk_width, 1}, {&sk_bounds, 1}, nullptr);
  if (width)
    *width = SkiaScalarToHarfBuzzUnits(sk_width);
  if (extents) {
    // Invert y-axis because Skia is y-grows-down but we set up HarfBuzz to be
    // y-grows-up.
    extents->x_bearing = SkiaScalarToHarfBuzzUnits(sk_bounds.fLeft);
    extents->y_bearing = SkiaScalarToHarfBuzzUnits(-sk_bounds.fTop);
    extents->width = SkiaScalarToHarfBuzzUnits(sk_bounds.width());
    extents->height = SkiaScalarToHarfBuzzUnits(-sk_bounds.height());
  }
}

// Writes the |glyph| index for the given |unicode| code point. Returns whether
// the glyph exists, i.e. it is not a missing glyph.
hb_bool_t GetGlyph(hb_font_t* font,
                   void* data,
                   hb_codepoint_t unicode,
                   hb_codepoint_t variation_selector,
                   hb_codepoint_t* glyph,
                   void* user_data) {
  FontData* font_data = reinterpret_cast<FontData*>(data);
  GlyphCache* cache = font_data->glyph_cache_;

  GlyphCache::iterator iter = cache->find(unicode);
  if (iter == cache->end()) {
    auto result = cache->insert(
        std::make_pair(unicode, font_data->font_.unicharToGlyph(unicode)));
    DCHECK(result.second);
    iter = result.first;
  }

  *glyph = iter->second;
  return !!*glyph;
}

hb_bool_t GetNominalGlyph(hb_font_t* font,
                          void* data,
                          hb_codepoint_t unicode,
                          hb_codepoint_t* glyph,
                          void* user_data) {
  return GetGlyph(font, data, unicode, 0, glyph, user_data);
}

// Returns the horizontal advance value of the |glyph|.
hb_position_t GetGlyphHorizontalAdvance(hb_font_t* font,
                                        void* data,
                                        hb_codepoint_t glyph,
                                        void* user_data) {
  FontData* font_data = reinterpret_cast<FontData*>(data);
  hb_position_t advance = 0;

  GetGlyphWidthAndExtents(font_data->font_, glyph, &advance, 0);
  return advance;
}

hb_bool_t GetGlyphHorizontalOrigin(hb_font_t* font,
                                   void* data,
                                   hb_codepoint_t glyph,
                                   hb_position_t* x,
                                   hb_position_t* y,
                                   void* user_data) {
  // Just return true, like the HarfBuzz-FreeType implementation.
  return true;
}

hb_position_t GetGlyphKerning(FontData* font_data,
                              hb_codepoint_t first_glyph,
                              hb_codepoint_t second_glyph) {
  SkTypeface* typeface = font_data->font_.getTypeface();
  const uint16_t glyphs[2] = { static_cast<uint16_t>(first_glyph),
                               static_cast<uint16_t>(second_glyph) };
  int32_t kerning_adjustments[1] = { 0 };

  if (!typeface->getKerningPairAdjustments(glyphs, kerning_adjustments))
    return 0;

  SkScalar upm = SkIntToScalar(typeface->getUnitsPerEm());
  SkScalar size = font_data->font_.getSize();
  return SkiaScalarToHarfBuzzUnits(SkIntToScalar(kerning_adjustments[0]) *
                                   size / upm);
}

hb_position_t GetGlyphHorizontalKerning(hb_font_t* font,
                                        void* data,
                                        hb_codepoint_t left_glyph,
                                        hb_codepoint_t right_glyph,
                                        void* user_data) {
  FontData* font_data = reinterpret_cast<FontData*>(data);
  return GetGlyphKerning(font_data, left_glyph, right_glyph);
}

hb_position_t GetGlyphVerticalKerning(hb_font_t* font,
                                      void* data,
                                      hb_codepoint_t top_glyph,
                                      hb_codepoint_t bottom_glyph,
                                      void* user_data) {
  FontData* font_data = reinterpret_cast<FontData*>(data);
  return GetGlyphKerning(font_data, top_glyph, bottom_glyph);
}

// Writes the |extents| of |glyph|.
hb_bool_t GetGlyphExtents(hb_font_t* font,
                          void* data,
                          hb_codepoint_t glyph,
                          hb_glyph_extents_t* extents,
                          void* user_data) {
  FontData* font_data = reinterpret_cast<FontData*>(data);

  GetGlyphWidthAndExtents(font_data->font_, glyph, 0, extents);
  return true;
}

class FontFuncs {
 public:
  FontFuncs() : font_funcs_(hb_font_funcs_create()) {
    hb_font_funcs_set_variation_glyph_func(font_funcs_, GetGlyph, 0, 0);
    hb_font_funcs_set_nominal_glyph_func(font_funcs_, GetNominalGlyph, 0, 0);
    hb_font_funcs_set_glyph_h_advance_func(
        font_funcs_, GetGlyphHorizontalAdvance, 0, 0);
    hb_font_funcs_set_glyph_h_kerning_func(
        font_funcs_, GetGlyphHorizontalKerning, 0, 0);
    hb_font_funcs_set_glyph_h_origin_func(
        font_funcs_, GetGlyphHorizontalOrigin, 0, 0);
    hb_font_funcs_set_glyph_v_kerning_func(
        font_funcs_, GetGlyphVerticalKerning, 0, 0);
    hb_font_funcs_set_glyph_extents_func(
        font_funcs_, GetGlyphExtents, 0, 0);
    hb_font_funcs_make_immutable(font_funcs_);
  }

  FontFuncs(const FontFuncs&) = delete;
  FontFuncs& operator=(const FontFuncs&) = delete;

  ~FontFuncs() {
    hb_font_funcs_destroy(font_funcs_);
  }

  hb_font_funcs_t* get() { return font_funcs_; }

 private:
  raw_ptr<hb_font_funcs_t> font_funcs_;
};

FontFuncs& GetFontFuncs() {
  static base::NoDestructor<FontFuncs> font_funcs;
  return *font_funcs;
}

// Returns the raw data of the font table |tag|.
hb_blob_t* GetFontTable(hb_face_t* face, hb_tag_t tag, void* user_data) {
  SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data);

  auto buffer = base::HeapArray<char>::Uninit(typeface->getTableSize(tag));
  // If the buffer has no data, then `getTableSize` produced a size of 0, and
  // the buffer allocated no memory as a result.
  if (buffer.empty()) {
    return nullptr;
  }
  size_t actual_size =
      typeface->getTableData(tag, 0, buffer.size(), buffer.data());
  if (buffer.size() != actual_size) {
    return nullptr;
  }

  auto unowned_buffer = std::move(buffer).leak();
  char* buffer_raw = unowned_buffer.data();
  return hb_blob_create(buffer_raw,
                        static_cast<uint32_t>(unowned_buffer.size()),
                        HB_MEMORY_MODE_WRITABLE, buffer_raw,
                        base::HeapArray<char>::DeleteLeakedData);
}

// For a given skia typeface, maps to its harfbuzz face and its glyphs cache.
class TypefaceData {
 public:
  explicit TypefaceData(sk_sp<SkTypeface> skia_face) : sk_typeface_(skia_face) {
    face_ = hb_face_create_for_tables(GetFontTable, skia_face.get(), nullptr);
    DCHECK(face_);
  }

  TypefaceData(TypefaceData&& data) {
    face_ = data.face_;
    glyphs_ = std::move(data.glyphs_);
    data.face_ = nullptr;
  }

  TypefaceData(const TypefaceData&) = delete;
  TypefaceData& operator=(const TypefaceData&) = delete;

  ~TypefaceData() {
    auto* tmp = face_.get();
    face_ = nullptr;
    hb_face_destroy(tmp);
  }

  hb_face_t* face() { return face_; }
  GlyphCache* glyphs() { return &glyphs_; }

 private:
  TypefaceData() = delete;

  GlyphCache glyphs_;
  raw_ptr<hb_face_t> face_ = nullptr;

  // The skia typeface must outlive |face_| since it's being used by harfbuzz.
  sk_sp<SkTypeface> sk_typeface_;
};

}  // namespace

// Creates a HarfBuzz font from the given Skia face and text size.
hb_font_t* CreateHarfBuzzFont(sk_sp<SkTypeface> skia_face,
                              SkScalar text_size,
                              const FontRenderParams& params,
                              bool subpixel_rendering_suppressed) {
  // A cache from Skia font to harfbuzz typeface information.
  using TypefaceCache = base::LRUCache<SkTypefaceID, TypefaceData>;

  constexpr int kTypefaceCacheSize = 64;
  static base::NoDestructor<TypefaceCache> face_caches(kTypefaceCacheSize);

  TypefaceCache* typeface_cache = face_caches.get();
  TypefaceCache::iterator typeface_data =
      typeface_cache->Get(skia_face->uniqueID());
  if (typeface_data == typeface_cache->end()) {
    TypefaceData new_typeface_data(skia_face);
    typeface_data = typeface_cache->Put(skia_face->uniqueID(),
                                        std::move(new_typeface_data));
  }

  DCHECK(typeface_data->second.face());
  hb_font_t* harfbuzz_font = hb_font_create(typeface_data->second.face());

  const int scale = SkiaScalarToHarfBuzzUnits(text_size);
  hb_font_set_scale(harfbuzz_font, scale, scale);
  FontData* hb_font_data = new FontData(typeface_data->second.glyphs());
  hb_font_data->font_.setTypeface(std::move(skia_face));
  hb_font_data->font_.setSize(text_size);
  // TODO(ckocagil): Do we need to update these params later?
  internal::ApplyRenderParams(params, subpixel_rendering_suppressed,
                              &hb_font_data->font_);
  hb_font_set_funcs(harfbuzz_font, GetFontFuncs().get(), hb_font_data,
                    DeleteByType<FontData>);
  hb_font_make_immutable(harfbuzz_font);
  return harfbuzz_font;
}

}  // namespace gfx
