// Copyright (c) 2010 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 "gfx/font.h"

#include <gdk/gdk.h>
#include <map>
#include <pango/pango.h>

#include "base/logging.h"
#include "base/string_piece.h"
#include "base/sys_string_conversions.h"
#include "gfx/canvas.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "third_party/skia/include/core/SkPaint.h"

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.
const char* kFallbackFontFamilyName = "sans";

// Retrieves the pango metrics for a pango font description. Caches the metrics
// and never frees them. The metrics objects are relatively small and
// very expensive to look up.
static PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) {
  static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL;
  static PangoContext* context = NULL;

  if (!context) {
    context = gdk_pango_context_get_for_screen(gdk_screen_get_default());
    pango_context_set_language(context, pango_language_get_default());
  }

  if (!desc_to_metrics) {
    desc_to_metrics = new std::map<int, PangoFontMetrics*>();
  }

  int desc_hash = pango_font_description_hash(desc);
  std::map<int, PangoFontMetrics*>::iterator i =
      desc_to_metrics->find(desc_hash);

  if (i == desc_to_metrics->end()) {
    PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL);
    (*desc_to_metrics)[desc_hash] = metrics;
    return metrics;
  } else {
    return i->second;
  }
}

}  // namespace

namespace gfx {

Font::Font(const Font& other) {
  CopyFont(other);
}

Font& Font::operator=(const Font& other) {
  CopyFont(other);
  return *this;
}

Font::Font(SkTypeface* tf, const std::wstring& font_family, int font_size,
           int style)
    : typeface_helper_(new SkAutoUnref(tf)),
      typeface_(tf),
      font_family_(font_family),
      font_size_(font_size),
      style_(style),
      pango_metrics_inited_(false),
      avg_width_(0.0),
      underline_position_(0.0),
      underline_thickness_(0.0) {
  tf->ref();
  calculateMetrics();
}

void Font::calculateMetrics() {
  SkPaint paint;
  SkPaint::FontMetrics metrics;
  PaintSetup(&paint);
  paint.getFontMetrics(&metrics);

  ascent_ = SkScalarCeil(-metrics.fAscent);
  height_ = ascent_ + SkScalarCeil(metrics.fDescent);

}

void Font::CopyFont(const Font& other) {
  typeface_helper_.reset(new SkAutoUnref(other.typeface_));
  typeface_ = other.typeface_;
  typeface_->ref();
  font_family_ = other.font_family_;
  font_size_ = other.font_size_;
  style_ = other.style_;
  height_ = other.height_;
  ascent_ = other.ascent_;
  pango_metrics_inited_ = other.pango_metrics_inited_;
  avg_width_ = other.avg_width_;
  underline_position_ = other.underline_position_;
  underline_thickness_ = other.underline_thickness_;
}

int Font::height() const {
  return height_;
}

int Font::baseline() const {
  return ascent_;
}

int Font::ave_char_width() const {
  return SkScalarRound(avg_width());
}

Font Font::CreateFont(const std::wstring& font_family, int font_size) {
  DCHECK_GT(font_size, 0);
  std::wstring fallback;

  SkTypeface* tf = SkTypeface::CreateFromName(
      base::SysWideToUTF8(font_family).c_str(), SkTypeface::kNormal);
  if (!tf) {
    // A non-scalable font such as .pcf is specified. Falls back to a default
    // scalable font.
    tf = SkTypeface::CreateFromName(
        kFallbackFontFamilyName, SkTypeface::kNormal);
    CHECK(tf) << "Could not find any font: "
              << base::SysWideToUTF8(font_family)
              << ", " << kFallbackFontFamilyName;
    fallback = base::SysUTF8ToWide(kFallbackFontFamilyName);
  }
  SkAutoUnref tf_helper(tf);

  return Font(
      tf, fallback.empty() ? font_family : fallback, font_size, NORMAL);
}

Font Font::DeriveFont(int size_delta, int style) const {
  // If the delta is negative, if must not push the size below 1
  if (size_delta < 0) {
    DCHECK_LT(-size_delta, font_size_);
  }

  if (style == style_) {
    // Fast path, we just use the same typeface at a different size
    return Font(typeface_, font_family_, font_size_ + size_delta, style_);
  }

  // If the style has changed we may need to load a new face
  int skstyle = SkTypeface::kNormal;
  if (BOLD & style)
    skstyle |= SkTypeface::kBold;
  if (ITALIC & style)
    skstyle |= SkTypeface::kItalic;

  SkTypeface* tf = SkTypeface::CreateFromName(
      base::SysWideToUTF8(font_family_).c_str(),
      static_cast<SkTypeface::Style>(skstyle));
  SkAutoUnref tf_helper(tf);

  return Font(tf, font_family_, font_size_ + size_delta, style);
}

void Font::PaintSetup(SkPaint* paint) const {
  paint->setAntiAlias(false);
  paint->setSubpixelText(false);
  paint->setTextSize(SkFloatToScalar(font_size_ * Font::GetPangoScaleFactor()));
  paint->setTypeface(typeface_);
  paint->setFakeBoldText((BOLD & style_) && !typeface_->isBold());
  paint->setTextSkewX((ITALIC & style_) && !typeface_->isItalic() ?
                      -SK_Scalar1/4 : 0);
}

int Font::GetStringWidth(const std::wstring& text) const {
  int width = 0, height = 0;
  Canvas::SizeStringInt(text, *this, &width, &height, gfx::Canvas::NO_ELLIPSIS);
  return width;
}

void Font::InitPangoMetrics() {
  if (!pango_metrics_inited_) {
    pango_metrics_inited_ = true;
    PangoFontDescription* pango_desc = PangoFontFromGfxFont(*this);
    PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc);

    underline_position_ =
        pango_font_metrics_get_underline_position(pango_metrics);
    underline_position_ /= PANGO_SCALE;

    // todo(davemoore) Come up with a better solution.
    // This is a hack, but without doing this the underlines
    // we get end up fuzzy. So we align to the midpoint of a pixel.
    underline_position_ /= 2;

    underline_thickness_ =
        pango_font_metrics_get_underline_thickness(pango_metrics);
    underline_thickness_ /= PANGO_SCALE;

    // First get the pango based width
    double pango_width =
        pango_font_metrics_get_approximate_char_width(pango_metrics);
    pango_width /= PANGO_SCALE;

    // Yes, this is how Microsoft recommends calculating the dialog unit
    // conversions.
    int text_width = GetStringWidth(
        L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
    double dialog_units = (text_width / 26 + 1) / 2;
    avg_width_ = std::min(pango_width, dialog_units);
    pango_font_description_free(pango_desc);
  }
}

double Font::avg_width() const {
  const_cast<Font*>(this)->InitPangoMetrics();
  return avg_width_;
}

double Font::underline_position() const {
  const_cast<Font*>(this)->InitPangoMetrics();
  return underline_position_;
}

double Font::underline_thickness() const {
  const_cast<Font*>(this)->InitPangoMetrics();
  return underline_thickness_;
}

int Font::GetExpectedTextWidth(int length) const {
  double char_width = const_cast<Font*>(this)->avg_width();
  return round(static_cast<float>(length) * char_width);
}

int Font::style() const {
  return style_;
}

const std::wstring& Font::FontName() const {
  return font_family_;
}

int Font::FontSize() {
  return font_size_;
}

NativeFont Font::nativeFont() const {
  return typeface_;
}

}  // namespace gfx