1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/gfx/platform_font_pango.h"
6
7#include <pango/pango.h>
8
9#include <algorithm>
10#include <string>
11
12#include "base/logging.h"
13#include "base/strings/string_piece.h"
14#include "base/strings/string_split.h"
15#include "base/strings/utf_string_conversions.h"
16#include "third_party/skia/include/core/SkPaint.h"
17#include "third_party/skia/include/core/SkString.h"
18#include "third_party/skia/include/core/SkTypeface.h"
19#include "ui/gfx/canvas.h"
20#include "ui/gfx/font.h"
21#include "ui/gfx/font_list.h"
22#include "ui/gfx/linux_font_delegate.h"
23#include "ui/gfx/pango_util.h"
24#include "ui/gfx/text_utils.h"
25
26namespace {
27
28// The font family name which is used when a user's application font for
29// GNOME/KDE is a non-scalable one. The name should be listed in the
30// IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp.
31const char* kFallbackFontFamilyName = "sans";
32
33// Creates a SkTypeface for the passed-in Font::FontStyle and family. If a
34// fallback typeface is used instead of the requested family, |family| will be
35// updated to contain the fallback's family name.
36skia::RefPtr<SkTypeface> CreateSkTypeface(int style, std::string* family) {
37  DCHECK(family);
38
39  int skia_style = SkTypeface::kNormal;
40  if (gfx::Font::BOLD & style)
41    skia_style |= SkTypeface::kBold;
42  if (gfx::Font::ITALIC & style)
43    skia_style |= SkTypeface::kItalic;
44
45  skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(SkTypeface::CreateFromName(
46      family->c_str(), static_cast<SkTypeface::Style>(skia_style)));
47  if (!typeface) {
48    // A non-scalable font such as .pcf is specified. Fall back to a default
49    // scalable font.
50    typeface = skia::AdoptRef(SkTypeface::CreateFromName(
51        kFallbackFontFamilyName, static_cast<SkTypeface::Style>(skia_style)));
52    CHECK(typeface) << "Could not find any font: " << family << ", "
53                    << kFallbackFontFamilyName;
54    *family = kFallbackFontFamilyName;
55  }
56  return typeface;
57}
58
59}  // namespace
60
61namespace gfx {
62
63// static
64Font* PlatformFontPango::default_font_ = NULL;
65
66#if defined(OS_CHROMEOS)
67// static
68std::string* PlatformFontPango::default_font_description_ = NULL;
69#endif
70
71////////////////////////////////////////////////////////////////////////////////
72// PlatformFontPango, public:
73
74PlatformFontPango::PlatformFontPango() {
75  if (!default_font_) {
76    scoped_ptr<ScopedPangoFontDescription> description;
77#if defined(OS_CHROMEOS)
78    CHECK(default_font_description_);
79    description.reset(
80        new ScopedPangoFontDescription(*default_font_description_));
81#else
82    const gfx::LinuxFontDelegate* delegate = gfx::LinuxFontDelegate::instance();
83    if (delegate)
84      description = delegate->GetDefaultPangoFontDescription();
85#endif
86    if (!description || !description->get())
87      description.reset(new ScopedPangoFontDescription("sans 10"));
88    default_font_ = new Font(description->get());
89  }
90
91  InitFromPlatformFont(
92      static_cast<PlatformFontPango*>(default_font_->platform_font()));
93}
94
95PlatformFontPango::PlatformFontPango(NativeFont native_font) {
96  FontRenderParamsQuery query(false);
97  base::SplitString(pango_font_description_get_family(native_font), ',',
98                    &query.families);
99
100  const int pango_size =
101      pango_font_description_get_size(native_font) / PANGO_SCALE;
102  if (pango_font_description_get_size_is_absolute(native_font))
103    query.pixel_size = pango_size;
104  else
105    query.point_size = pango_size;
106
107  query.style = gfx::Font::NORMAL;
108  // TODO(davemoore): Support weights other than bold?
109  if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD)
110    query.style |= gfx::Font::BOLD;
111  // TODO(davemoore): What about PANGO_STYLE_OBLIQUE?
112  if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC)
113    query.style |= gfx::Font::ITALIC;
114
115  std::string font_family;
116  const FontRenderParams params = gfx::GetFontRenderParams(query, &font_family);
117  InitFromDetails(skia::RefPtr<SkTypeface>(), font_family,
118                  gfx::GetPangoFontSizeInPixels(native_font),
119                  query.style, params);
120}
121
122PlatformFontPango::PlatformFontPango(const std::string& font_name,
123                                     int font_size_pixels) {
124  FontRenderParamsQuery query(false);
125  query.families.push_back(font_name);
126  query.pixel_size = font_size_pixels;
127  query.style = gfx::Font::NORMAL;
128  InitFromDetails(skia::RefPtr<SkTypeface>(), font_name, font_size_pixels,
129                  query.style, gfx::GetFontRenderParams(query, NULL));
130}
131
132////////////////////////////////////////////////////////////////////////////////
133// PlatformFontPango, PlatformFont implementation:
134
135// static
136void PlatformFontPango::ReloadDefaultFont() {
137  delete default_font_;
138  default_font_ = NULL;
139}
140
141#if defined(OS_CHROMEOS)
142// static
143void PlatformFontPango::SetDefaultFontDescription(
144    const std::string& font_description) {
145  delete default_font_description_;
146  default_font_description_ = new std::string(font_description);
147}
148
149#endif
150
151Font PlatformFontPango::DeriveFont(int size_delta, int style) const {
152  const int new_size = font_size_pixels_ + size_delta;
153  DCHECK_GT(new_size, 0);
154
155  // If the style changed, we may need to load a new face.
156  std::string new_family = font_family_;
157  skia::RefPtr<SkTypeface> typeface =
158      (style == style_) ? typeface_ : CreateSkTypeface(style, &new_family);
159
160  FontRenderParamsQuery query(false);
161  query.families.push_back(new_family);
162  query.pixel_size = new_size;
163  query.style = style;
164
165  return Font(new PlatformFontPango(typeface, new_family, new_size, style,
166                                    gfx::GetFontRenderParams(query, NULL)));
167}
168
169int PlatformFontPango::GetHeight() const {
170  return height_pixels_;
171}
172
173int PlatformFontPango::GetBaseline() const {
174  return ascent_pixels_;
175}
176
177int PlatformFontPango::GetCapHeight() const {
178  return cap_height_pixels_;
179}
180
181int PlatformFontPango::GetExpectedTextWidth(int length) const {
182  double char_width = const_cast<PlatformFontPango*>(this)->GetAverageWidth();
183  return round(static_cast<float>(length) * char_width);
184}
185
186int PlatformFontPango::GetStyle() const {
187  return style_;
188}
189
190std::string PlatformFontPango::GetFontName() const {
191  return font_family_;
192}
193
194std::string PlatformFontPango::GetActualFontNameForTesting() const {
195  SkString family_name;
196  typeface_->getFamilyName(&family_name);
197  return family_name.c_str();
198}
199
200int PlatformFontPango::GetFontSize() const {
201  return font_size_pixels_;
202}
203
204const FontRenderParams& PlatformFontPango::GetFontRenderParams() const {
205  return font_render_params_;
206}
207
208NativeFont PlatformFontPango::GetNativeFont() const {
209  PangoFontDescription* pfd = pango_font_description_new();
210  pango_font_description_set_family(pfd, GetFontName().c_str());
211  // Set the absolute size to avoid overflowing UI elements.
212  // pango_font_description_set_absolute_size() takes a size in Pango units.
213  // There are PANGO_SCALE Pango units in one device unit.  Screen output
214  // devices use pixels as their device units.
215  pango_font_description_set_absolute_size(
216      pfd, font_size_pixels_ * PANGO_SCALE);
217
218  switch (GetStyle()) {
219    case gfx::Font::NORMAL:
220      // Nothing to do, should already be PANGO_STYLE_NORMAL.
221      break;
222    case gfx::Font::BOLD:
223      pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD);
224      break;
225    case gfx::Font::ITALIC:
226      pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC);
227      break;
228    case gfx::Font::UNDERLINE:
229      // TODO(deanm): How to do underline?  Where do we use it?  Probably have
230      // to paint it ourselves, see pango_font_metrics_get_underline_position.
231      break;
232  }
233
234  return pfd;
235}
236
237////////////////////////////////////////////////////////////////////////////////
238// PlatformFontPango, private:
239
240PlatformFontPango::PlatformFontPango(const skia::RefPtr<SkTypeface>& typeface,
241                                     const std::string& name,
242                                     int size_pixels,
243                                     int style,
244                                     const FontRenderParams& render_params) {
245  InitFromDetails(typeface, name, size_pixels, style, render_params);
246}
247
248PlatformFontPango::~PlatformFontPango() {}
249
250void PlatformFontPango::InitFromDetails(
251    const skia::RefPtr<SkTypeface>& typeface,
252    const std::string& font_family,
253    int font_size_pixels,
254    int style,
255    const FontRenderParams& render_params) {
256  DCHECK_GT(font_size_pixels, 0);
257
258  font_family_ = font_family;
259  typeface_ = typeface ? typeface : CreateSkTypeface(style, &font_family_);
260
261  font_size_pixels_ = font_size_pixels;
262  style_ = style;
263  font_render_params_ = render_params;
264
265  SkPaint paint;
266  SkPaint::FontMetrics metrics;
267  PaintSetup(&paint);
268  paint.getFontMetrics(&metrics);
269  ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent);
270  height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent);
271  cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight);
272
273  pango_metrics_inited_ = false;
274  average_width_pixels_ = 0.0f;
275}
276
277void PlatformFontPango::InitFromPlatformFont(const PlatformFontPango* other) {
278  typeface_ = other->typeface_;
279  font_family_ = other->font_family_;
280  font_size_pixels_ = other->font_size_pixels_;
281  style_ = other->style_;
282  font_render_params_ = other->font_render_params_;
283  ascent_pixels_ = other->ascent_pixels_;
284  height_pixels_ = other->height_pixels_;
285  cap_height_pixels_ = other->cap_height_pixels_;
286  pango_metrics_inited_ = other->pango_metrics_inited_;
287  average_width_pixels_ = other->average_width_pixels_;
288}
289
290void PlatformFontPango::PaintSetup(SkPaint* paint) const {
291  paint->setAntiAlias(false);
292  paint->setSubpixelText(false);
293  paint->setTextSize(font_size_pixels_);
294  paint->setTypeface(typeface_.get());
295  paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold());
296  paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ?
297                      -SK_Scalar1/4 : 0);
298}
299
300void PlatformFontPango::InitPangoMetrics() {
301  if (!pango_metrics_inited_) {
302    pango_metrics_inited_ = true;
303    ScopedPangoFontDescription pango_desc(GetNativeFont());
304    PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc.get());
305
306    // First get the Pango-based width (converting from Pango units to pixels).
307    const double pango_width_pixels =
308        pango_font_metrics_get_approximate_char_width(pango_metrics) /
309        PANGO_SCALE;
310
311    // Yes, this is how Microsoft recommends calculating the dialog unit
312    // conversions.
313    const int text_width_pixels = GetStringWidth(
314        base::ASCIIToUTF16(
315            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"),
316        FontList(Font(this)));
317    const double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2;
318    average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels);
319  }
320}
321
322double PlatformFontPango::GetAverageWidth() const {
323  const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
324  return average_width_pixels_;
325}
326
327////////////////////////////////////////////////////////////////////////////////
328// PlatformFont, public:
329
330// static
331PlatformFont* PlatformFont::CreateDefault() {
332  return new PlatformFontPango;
333}
334
335// static
336PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
337  return new PlatformFontPango(native_font);
338}
339
340// static
341PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
342                                                  int font_size) {
343  return new PlatformFontPango(font_name, font_size);
344}
345
346}  // namespace gfx
347