// Copyright 2012 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/platform_font_skia.h"

#include <algorithm>
#include <string>

#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "skia/ext/font_utils.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkFontMetrics.h"
#include "third_party/skia/include/core/SkFontStyle.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/font_render_params.h"
#include "ui/gfx/text_utils.h"

#if BUILDFLAG(IS_WIN)
#include "ui/gfx/system_fonts_win.h"
#endif

#if BUILDFLAG(IS_LINUX)
#include "ui/linux/linux_ui.h"
#endif

namespace gfx {
namespace {

// The font family name which is used when a user's application font for
// GNOME/KDE is a non-scalable one. The name should be listed in the
// IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp.
#if BUILDFLAG(IS_ANDROID)
const char kFallbackFontFamilyName[] = "serif";
#else
const char kFallbackFontFamilyName[] = "sans";
#endif

constexpr SkGlyphID kUnsupportedGlyph = 0;

// The default font, used for the default constructor.
scoped_refptr<PlatformFontSkia>& GetDefaultFont() {
  static base::NoDestructor<scoped_refptr<PlatformFontSkia>> default_font;
  return *default_font;
}

// Creates a SkTypeface for the passed-in Font::FontStyle and family. If a
// fallback typeface is used instead of the requested family, |family| will be
// updated to contain the fallback's family name.
sk_sp<SkTypeface> CreateSkTypeface(bool italic,
                                   gfx::Font::Weight weight,
                                   std::string* family,
                                   bool* out_success) {
  DCHECK(family);
  TRACE_EVENT0("fonts", "gfx::CreateSkTypeface");

  const int font_weight = (weight == Font::Weight::INVALID)
                              ? static_cast<int>(Font::Weight::NORMAL)
                              : static_cast<int>(weight);
  SkFontStyle sk_style(
      font_weight, SkFontStyle::kNormal_Width,
      italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant);
  sk_sp<SkTypeface> typeface;
  {
    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("fonts"),
                 "skia::MakeTypefaceFromName", "family", *family);
    typeface = skia::MakeTypefaceFromName(family->c_str(), sk_style);
  }
  if (!typeface) {
    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("fonts"),
                 "skia::MakeTypefaceFromName", "family",
                 kFallbackFontFamilyName);
    // A non-scalable font such as .pcf is specified. Fall back to a default
    // scalable font.
    typeface = skia::MakeTypefaceFromName(kFallbackFontFamilyName, sk_style);
    if (!typeface) {
      *out_success = false;
      return nullptr;
    }
    *family = kFallbackFontFamilyName;
  }
  *out_success = true;
  return typeface;
}

}  // namespace

std::string* PlatformFontSkia::default_font_description_ = NULL;

////////////////////////////////////////////////////////////////////////////////
// PlatformFontSkia, public:

PlatformFontSkia::PlatformFontSkia() {
  EnsuresDefaultFontIsInitialized();
  InitFromPlatformFont(GetDefaultFont().get());
}

PlatformFontSkia::PlatformFontSkia(const std::string& font_name,
                                   int font_size_pixels) {
  FontRenderParamsQuery query;
  query.families.push_back(font_name);
  query.pixel_size = font_size_pixels;
  query.weight = Font::Weight::NORMAL;
  InitFromDetails(nullptr, font_name, font_size_pixels, Font::NORMAL,
                  query.weight, gfx::GetFontRenderParams(query, nullptr));
}

PlatformFontSkia::PlatformFontSkia(
    sk_sp<SkTypeface> typeface,
    int font_size_pixels,
    const std::optional<FontRenderParams>& params) {
  DCHECK(typeface);

  SkString family_name;
  typeface->getFamilyName(&family_name);

  SkFontStyle font_style = typeface->fontStyle();
  Font::Weight font_weight = FontWeightFromInt(font_style.weight());

  int style = typeface->isItalic() ? Font::ITALIC : Font::NORMAL;

  FontRenderParams actual_render_params;
  if (!params) {
    FontRenderParamsQuery query;
    query.families.push_back(family_name.c_str());
    query.pixel_size = font_size_pixels;
    query.weight = font_weight;
    actual_render_params = gfx::GetFontRenderParams(query, nullptr);
  } else {
    actual_render_params = params.value();
  }

  InitFromDetails(std::move(typeface), family_name.c_str(), font_size_pixels,
                  style, font_weight, actual_render_params);
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontSkia, PlatformFont implementation:

// static
void PlatformFontSkia::EnsuresDefaultFontIsInitialized() {
  if (GetDefaultFont()) {
    return;
  }

  std::string family = kFallbackFontFamilyName;
  int size_pixels = PlatformFont::kDefaultBaseFontSize;
  int style = Font::NORMAL;
  Font::Weight weight = Font::Weight::NORMAL;
  FontRenderParams params;

#if BUILDFLAG(IS_WIN)
  // On windows, the system default font is retrieved by using the GDI API
  // SystemParametersInfo(...) (see struct NONCLIENTMETRICS). The font
  // properties need to be converted as close as possible to a skia font.
  // The style must be kept (see http://crbug/989476).
  gfx::Font system_font = win::GetDefaultSystemFont();
  family = system_font.GetFontName();
  size_pixels = system_font.GetFontSize();
  style = system_font.GetStyle();
  weight = system_font.GetWeight();
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_LINUX)
  // On Linux, LinuxUi is used to query the native toolkit (e.g.
  // GTK) for the default UI font.
  if (auto* linux_ui = ui::LinuxUi::instance()) {
    const auto& font_settings = linux_ui->GetDefaultFontDescription();
    family = font_settings.family;
    size_pixels = font_settings.size_pixels;
    style = font_settings.style;
    weight = static_cast<Font::Weight>(font_settings.weight);
    params = linux_ui->GetDefaultFontRenderParams();
  } else
#endif
      if (default_font_description_) {
#if BUILDFLAG(IS_CHROMEOS)
    // On ChromeOS, a FontList font description string is stored as a
    // translatable resource and passed in via SetDefaultFontDescription().
    FontRenderParamsQuery query;
    CHECK(FontList::ParseDescription(*default_font_description_,
                                     &query.families, &query.style,
                                     &query.pixel_size, &query.weight))
        << "Failed to parse font description " << *default_font_description_;
    params = gfx::GetFontRenderParams(query, &family);
    size_pixels = query.pixel_size;
    style = query.style;
    weight = query.weight;
#else
        NOTREACHED();
#endif
  } else {
    params = gfx::GetFontRenderParams(FontRenderParamsQuery(), nullptr);
  }

  bool success = false;
  sk_sp<SkTypeface> typeface =
      CreateSkTypeface(style & Font::ITALIC, weight, &family, &success);

  // It's possible that the Skia interface is not longer able to proxy queries
  // to the browser process which make all requests to fail. Calling
  // MakeDefault() will try to get the default typeface; in case of failure it
  // returns an instance of SkEmptyTypeface. MakeDefault() should never fail.
  // See https://crbug.com/1287371 for details.
  if (!success) {
    typeface = skia::DefaultTypeface();
  }

  // Ensure there is a typeface available. If none is available, there is
  // nothing we can do about it and Chrome won't be able to work.
  CHECK(typeface.get()) << "No typeface available";

  GetDefaultFont() = new PlatformFontSkia(std::move(typeface), family,
                                          size_pixels, style, weight, params);
}

// static
void PlatformFontSkia::ReloadDefaultFont() {
  // Reset the scoped_refptr.
  GetDefaultFont() = nullptr;
}

// static
void PlatformFontSkia::SetDefaultFontDescription(
    const std::string& font_description) {
  delete default_font_description_;
  default_font_description_ = new std::string(font_description);
}

Font PlatformFontSkia::DeriveFont(int size_delta,
                                  int style,
                                  Font::Weight weight) const {
#if BUILDFLAG(IS_WIN)
  const int new_size = win::AdjustFontSize(font_size_pixels_, size_delta);
#else
  const int new_size = font_size_pixels_ + size_delta;
#endif

  DCHECK_GT(new_size, 0);

  // If the style changed, we may need to load a new face.
  std::string new_family = font_family_;
  bool success = true;
  sk_sp<SkTypeface> typeface =
      (weight == weight_ && style == style_)
          ? typeface_
          : CreateSkTypeface(style, weight, &new_family, &success);
  if (!success) {
    LOG(ERROR) << "Could not find any font: " << new_family << ", "
               << kFallbackFontFamilyName << ". Falling back to the default";
    return Font(new PlatformFontSkia);
  }

  FontRenderParamsQuery query;
  query.families.push_back(new_family);
  query.pixel_size = new_size;
  query.style = style;

  return Font(new PlatformFontSkia(std::move(typeface), new_family, new_size,
                                   style, weight,
                                   gfx::GetFontRenderParams(query, NULL)));
}

int PlatformFontSkia::GetHeight() {
  ComputeMetricsIfNecessary();
  return height_pixels_;
}

Font::Weight PlatformFontSkia::GetWeight() const {
  return weight_;
}

int PlatformFontSkia::GetBaseline() {
  ComputeMetricsIfNecessary();
  return ascent_pixels_;
}

int PlatformFontSkia::GetCapHeight() {
  ComputeMetricsIfNecessary();
  return cap_height_pixels_;
}

int PlatformFontSkia::GetExpectedTextWidth(int length) {
  ComputeMetricsIfNecessary();
  return round(static_cast<float>(length) * average_width_pixels_);
}

int PlatformFontSkia::GetStyle() const {
  return style_;
}

const std::string& PlatformFontSkia::GetFontName() const {
  return font_family_;
}

std::string PlatformFontSkia::GetActualFontName() const {
  SkString family_name;
  typeface_->getFamilyName(&family_name);
  return family_name.c_str();
}

std::vector<std::string> PlatformFontSkia::GetActualFontNames() const {
  std::vector<std::string> names;
  names.push_back(GetActualFontName());

  sk_sp<SkTypeface::LocalizedStrings> family_names(
      typeface_->createFamilyNameIterator());
  SkTypeface::LocalizedString family_name;
  while (family_names->next(&family_name)) {
    names.push_back(family_name.fString.c_str());
  }
  return names;
}

int PlatformFontSkia::GetFontSize() const {
  return font_size_pixels_;
}

const FontRenderParams& PlatformFontSkia::GetFontRenderParams() {
  TRACE_EVENT0("fonts", "PlatformFontSkia::GetFontRenderParams");
  float current_scale_factor = GetFontRenderParamsDeviceScaleFactor();
  bool force_query = false;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  const bool current_subpixel_rendering_enabled =
      gfx::GetFontRenderParamsSubpixelRenderingEnabled();
  if (current_subpixel_rendering_enabled != subpixel_rendering_enabled_) {
    force_query = true;
  }
#endif

  if (current_scale_factor != device_scale_factor_ || force_query) {
    FontRenderParamsQuery query;
    query.families.push_back(font_family_);
    query.pixel_size = font_size_pixels_;
    query.style = style_;
    query.weight = weight_;
    query.device_scale_factor = current_scale_factor;
    font_render_params_ = gfx::GetFontRenderParams(query, nullptr);
    device_scale_factor_ = current_scale_factor;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
    subpixel_rendering_enabled_ = current_subpixel_rendering_enabled;
#endif
  }
  return font_render_params_;
}

sk_sp<SkTypeface> PlatformFontSkia::GetNativeSkTypeface() const {
  DCHECK(typeface_);
  return sk_sp<SkTypeface>(typeface_);
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontSkia, private:

PlatformFontSkia::PlatformFontSkia(sk_sp<SkTypeface> typeface,
                                   const std::string& family,
                                   int size_pixels,
                                   int style,
                                   Font::Weight weight,
                                   const FontRenderParams& render_params) {
  InitFromDetails(std::move(typeface), family, size_pixels, style, weight,
                  render_params);
}

PlatformFontSkia::~PlatformFontSkia() {}

void PlatformFontSkia::InitFromDetails(sk_sp<SkTypeface> typeface,
                                       const std::string& font_family,
                                       int font_size_pixels,
                                       int style,
                                       Font::Weight weight,
                                       const FontRenderParams& render_params) {
  TRACE_EVENT0("fonts", "PlatformFontSkia::InitFromDetails");
  DCHECK_GT(font_size_pixels, 0);

  font_family_ = font_family;
  bool success = true;
  typeface_ = typeface ? std::move(typeface)
                       : CreateSkTypeface(style & Font::ITALIC, weight,
                                          &font_family_, &success);

  if (!success) {
    EnsuresDefaultFontIsInitialized();
    InitFromPlatformFont(GetDefaultFont().get());
    return;
  }

  set_typeface_unique_id(typeface_->uniqueID());
  font_size_pixels_ = font_size_pixels;
  style_ = style;
  weight_ = weight;
  device_scale_factor_ = GetFontRenderParamsDeviceScaleFactor();
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  subpixel_rendering_enabled_ =
      gfx::GetFontRenderParamsSubpixelRenderingEnabled();
#endif
  font_render_params_ = render_params;
}

void PlatformFontSkia::InitFromPlatformFont(const PlatformFontSkia* other) {
  TRACE_EVENT0("fonts", "PlatformFontSkia::InitFromPlatformFont");
  typeface_ = other->typeface_;
  set_typeface_unique_id(other->typeface_unique_id());
  font_family_ = other->font_family_;
  font_size_pixels_ = other->font_size_pixels_;
  style_ = other->style_;
  weight_ = other->weight_;
  device_scale_factor_ = other->device_scale_factor_;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  subpixel_rendering_enabled_ = other->subpixel_rendering_enabled_;
#endif
  font_render_params_ = other->font_render_params_;

  if (!other->metrics_need_computation_) {
    metrics_need_computation_ = false;
    ascent_pixels_ = other->ascent_pixels_;
    height_pixels_ = other->height_pixels_;
    cap_height_pixels_ = other->cap_height_pixels_;
    average_width_pixels_ = other->average_width_pixels_;
  }
}

void PlatformFontSkia::ComputeMetricsIfNecessary() {
  if (metrics_need_computation_) {
    TRACE_EVENT0("fonts", "PlatformFontSkia::ComputeMetricsIfNecessary");

    metrics_need_computation_ = false;

    SkFont font(typeface_, font_size_pixels_);
    const FontRenderParams& params = GetFontRenderParams();
    if (!params.antialiasing) {
      font.setEdging(SkFont::Edging::kAlias);
    } else if (params.subpixel_rendering ==
               FontRenderParams::SUBPIXEL_RENDERING_NONE) {
      font.setEdging(SkFont::Edging::kAntiAlias);
    } else {
      font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
    }

    font.setEmbolden(weight_ >= Font::Weight::BOLD && !typeface_->isBold());
    font.setSkewX((Font::ITALIC & style_) && !typeface_->isItalic()
                      ? -SK_Scalar1 / 4
                      : 0);
    SkFontMetrics metrics;
    font.getMetrics(&metrics);
    ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent);
    cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight);

    // There is a mismatch between the way the PlatformFontWin was computing the
    // font height in pixel. The font height may vary by one pixel due to
    // decimal rounding.
    //     Windows Skia implements : ceil(descent - ascent)
    //     Linux Skia implements   : ceil(-ascent) + ceil(descent)
    // TODO(etienneb): Make both implementation consistent and fix the broken
    // unittests.
#if BUILDFLAG(IS_WIN)
    height_pixels_ = SkScalarCeilToInt(metrics.fDescent - metrics.fAscent);
#else
    height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent);
#endif

    if (metrics.fAvgCharWidth) {
      average_width_pixels_ = SkScalarToDouble(metrics.fAvgCharWidth);
    } else {
      // Some Skia fonts manager do not compute the average character size
      // (e.g. Direct Write). The following code computes the average character
      // width the same way Blink (e.g. SimpleFontData) does. Use the width of
      // the letter 'x' when available, otherwise use the max character width.
      SkGlyphID glyph = typeface_->unicharToGlyph('x');
      if (glyph != kUnsupportedGlyph) {
        SkScalar sk_width = font.getWidth(glyph);
        average_width_pixels_ = SkScalarToDouble(sk_width);
      }
      if (!average_width_pixels_) {
        if (metrics.fMaxCharWidth) {
          average_width_pixels_ = SkScalarToDouble(metrics.fMaxCharWidth);
        } else {
          // Older version of the DirectWrite API doesn't implement support for
          // max char width. Fall back on a multiple of the ascent. This is
          // entirely arbitrary but comes pretty close to the expected value in
          // most cases.
          average_width_pixels_ = ascent_pixels_ * 2;
        }
      }
    }
    DCHECK_NE(average_width_pixels_, 0);
  }
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFont, public:

// static
PlatformFont* PlatformFont::CreateDefault() {
  return new PlatformFontSkia;
}

// static
PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
                                                  int font_size) {
  TRACE_EVENT0("fonts", "PlatformFont::CreateFromNameAndSize");
  return new PlatformFontSkia(font_name, font_size);
}

// static
PlatformFont* PlatformFont::CreateFromSkTypeface(
    sk_sp<SkTypeface> typeface,
    int font_size_pixels,
    const std::optional<FontRenderParams>& params) {
  TRACE_EVENT0("fonts", "PlatformFont::CreateFromSkTypeface");
  return new PlatformFontSkia(typeface, font_size_pixels, params);
}

}  // namespace gfx
