// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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_linux.h"

#include <algorithm>
#include <string>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkFontStyle.h"
#include "third_party/skia/include/core/SkPaint.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/linux_font_delegate.h"
#include "ui/gfx/text_utils.h"

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 defined(OS_ANDROID)
const char* kFallbackFontFamilyName = "serif";
#else
const char* kFallbackFontFamilyName = "sans";
#endif

// The default font, used for the default constructor.
base::LazyInstance<scoped_refptr<PlatformFontLinux>>::Leaky g_default_font =
    LAZY_INSTANCE_INITIALIZER;

// 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) {
  DCHECK(family);

  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 =
      SkTypeface::MakeFromName(family->c_str(), sk_style);
  if (!typeface) {
    // A non-scalable font such as .pcf is specified. Fall back to a default
    // scalable font.
    typeface = sk_sp<SkTypeface>(SkTypeface::MakeFromName(
        kFallbackFontFamilyName, sk_style));
    CHECK(typeface) << "Could not find any font: " << *family << ", "
                    << kFallbackFontFamilyName;
    *family = kFallbackFontFamilyName;
  }
  return typeface;
}

}  // namespace

#if defined(OS_CHROMEOS)
std::string* PlatformFontLinux::default_font_description_ = NULL;
#endif

////////////////////////////////////////////////////////////////////////////////
// PlatformFontLinux, public:

PlatformFontLinux::PlatformFontLinux() {
  if (!g_default_font.Get()) {
    std::string family = kFallbackFontFamilyName;
    int size_pixels = 12;
    int style = Font::NORMAL;
    Font::Weight weight = Font::Weight::NORMAL;
    FontRenderParams params;

#if defined(OS_CHROMEOS)
    // On Chrome OS, a FontList font description string is stored as a
    // translatable resource and passed in via SetDefaultFontDescription().
    if (default_font_description_) {
      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
    // On Linux, LinuxFontDelegate is used to query the native toolkit (e.g.
    // GTK+) for the default UI font.
    const LinuxFontDelegate* delegate = LinuxFontDelegate::instance();
    if (delegate) {
      delegate->GetDefaultFontDescription(&family, &size_pixels, &style,
                                          &weight, &params);
    }
#endif

    g_default_font.Get() = new PlatformFontLinux(
        CreateSkTypeface(style & Font::ITALIC, weight, &family), family,
        size_pixels, style, weight, params);
  }

  InitFromPlatformFont(g_default_font.Get().get());
}

PlatformFontLinux::PlatformFontLinux(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, NULL));
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontLinux, PlatformFont implementation:

// static
void PlatformFontLinux::ReloadDefaultFont() {
  // Reset the scoped_refptr.
  g_default_font.Get() = nullptr;
}

#if defined(OS_CHROMEOS)
// static
void PlatformFontLinux::SetDefaultFontDescription(
    const std::string& font_description) {
  delete default_font_description_;
  default_font_description_ = new std::string(font_description);
}

#endif

Font PlatformFontLinux::DeriveFont(int size_delta,
                                   int style,
                                   Font::Weight weight) const {
  const int new_size = font_size_pixels_ + size_delta;
  DCHECK_GT(new_size, 0);

  // If the style changed, we may need to load a new face.
  std::string new_family = font_family_;
  sk_sp<SkTypeface> typeface =
      (weight == weight_ && style == style_)
          ? typeface_
          : CreateSkTypeface(style, weight, &new_family);

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

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

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

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

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

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

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

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

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

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

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

const FontRenderParams& PlatformFontLinux::GetFontRenderParams() {
  float current_scale_factor = GetFontRenderParamsDeviceScaleFactor();
  if (current_scale_factor != device_scale_factor_) {
    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;
  }
  return font_render_params_;
}

////////////////////////////////////////////////////////////////////////////////
// PlatformFontLinux, private:

PlatformFontLinux::PlatformFontLinux(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);
}

PlatformFontLinux::~PlatformFontLinux() {}

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

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

  font_size_pixels_ = font_size_pixels;
  style_ = style;
  weight_ = weight;
  device_scale_factor_ = GetFontRenderParamsDeviceScaleFactor();
  font_render_params_ = render_params;
}

void PlatformFontLinux::InitFromPlatformFont(const PlatformFontLinux* other) {
  typeface_ = other->typeface_;
  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_;
  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 PlatformFontLinux::ComputeMetricsIfNecessary() {
  if (metrics_need_computation_) {
    metrics_need_computation_ = false;

    SkPaint paint;
    paint.setAntiAlias(false);
    paint.setSubpixelText(false);
    paint.setTextSize(font_size_pixels_);
    paint.setTypeface(typeface_);
    paint.setFakeBoldText(weight_ >= Font::Weight::BOLD &&
                          !typeface_->isBold());
    paint.setTextSkewX((Font::ITALIC & style_) && !typeface_->isItalic() ?
                        -SK_Scalar1/4 : 0);
    SkPaint::FontMetrics metrics;
    paint.getFontMetrics(&metrics);
    ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent);
    height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent);
    cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight);
    average_width_pixels_ = SkScalarToDouble(metrics.fAvgCharWidth);
  }
}

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

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

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

}  // namespace gfx
