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/font_render_params.h"
6
7#include <fontconfig/fontconfig.h>
8
9#include "base/command_line.h"
10#include "base/containers/mru_cache.h"
11#include "base/hash.h"
12#include "base/lazy_instance.h"
13#include "base/logging.h"
14#include "base/macros.h"
15#include "base/strings/string_util.h"
16#include "base/strings/stringprintf.h"
17#include "base/synchronization/lock.h"
18#include "ui/gfx/font.h"
19#include "ui/gfx/linux_font_delegate.h"
20#include "ui/gfx/switches.h"
21
22namespace gfx {
23
24namespace {
25
26#if defined(OS_CHROMEOS)
27// A device scale factor for an internal display (if any)
28// that is used to determine if subpixel positioning should be used.
29float device_scale_factor_for_internal_display = 1.0f;
30#endif
31
32// Keyed by hashes of FontRenderParamQuery structs from
33// HashFontRenderParamsQuery().
34typedef base::MRUCache<uint32, FontRenderParams> Cache;
35
36// Number of recent GetFontRenderParams() results to cache.
37const size_t kCacheSize = 20;
38
39// A cache and the lock that must be held while accessing it.
40// GetFontRenderParams() is called by both the UI thread and the sandbox IPC
41// thread.
42struct SynchronizedCache {
43  SynchronizedCache() : cache(kCacheSize) {}
44
45  base::Lock lock;
46  Cache cache;
47};
48
49base::LazyInstance<SynchronizedCache>::Leaky g_synchronized_cache =
50    LAZY_INSTANCE_INITIALIZER;
51
52bool IsBrowserTextSubpixelPositioningEnabled() {
53#if defined(OS_CHROMEOS)
54  return device_scale_factor_for_internal_display > 1.0f;
55#else
56  return false;
57#endif
58}
59
60// Converts Fontconfig FC_HINT_STYLE to FontRenderParams::Hinting.
61FontRenderParams::Hinting ConvertFontconfigHintStyle(int hint_style) {
62  switch (hint_style) {
63    case FC_HINT_SLIGHT: return FontRenderParams::HINTING_SLIGHT;
64    case FC_HINT_MEDIUM: return FontRenderParams::HINTING_MEDIUM;
65    case FC_HINT_FULL:   return FontRenderParams::HINTING_FULL;
66    default:             return FontRenderParams::HINTING_NONE;
67  }
68}
69
70// Converts Fontconfig FC_RGBA to FontRenderParams::SubpixelRendering.
71FontRenderParams::SubpixelRendering ConvertFontconfigRgba(int rgba) {
72  switch (rgba) {
73    case FC_RGBA_RGB:  return FontRenderParams::SUBPIXEL_RENDERING_RGB;
74    case FC_RGBA_BGR:  return FontRenderParams::SUBPIXEL_RENDERING_BGR;
75    case FC_RGBA_VRGB: return FontRenderParams::SUBPIXEL_RENDERING_VRGB;
76    case FC_RGBA_VBGR: return FontRenderParams::SUBPIXEL_RENDERING_VBGR;
77    default:           return FontRenderParams::SUBPIXEL_RENDERING_NONE;
78  }
79}
80
81// Queries Fontconfig for rendering settings and updates |params_out| and
82// |family_out| (if non-NULL). Returns false on failure.
83bool QueryFontconfig(const FontRenderParamsQuery& query,
84                     FontRenderParams* params_out,
85                     std::string* family_out) {
86  FcPattern* pattern = FcPatternCreate();
87  CHECK(pattern);
88
89  FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
90
91  for (std::vector<std::string>::const_iterator it = query.families.begin();
92       it != query.families.end(); ++it) {
93    FcPatternAddString(
94        pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>(it->c_str()));
95  }
96  if (query.pixel_size > 0)
97    FcPatternAddDouble(pattern, FC_PIXEL_SIZE, query.pixel_size);
98  if (query.point_size > 0)
99    FcPatternAddInteger(pattern, FC_SIZE, query.point_size);
100  if (query.style >= 0) {
101    FcPatternAddInteger(pattern, FC_SLANT,
102        (query.style & Font::ITALIC) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN);
103    FcPatternAddInteger(pattern, FC_WEIGHT,
104        (query.style & Font::BOLD) ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL);
105  }
106
107  FcConfigSubstitute(NULL, pattern, FcMatchPattern);
108  FcDefaultSubstitute(pattern);
109  FcResult result;
110  FcPattern* match = FcFontMatch(NULL, pattern, &result);
111  FcPatternDestroy(pattern);
112  if (!match)
113    return false;
114
115  if (family_out) {
116    FcChar8* family = NULL;
117    FcPatternGetString(match, FC_FAMILY, 0, &family);
118    if (family)
119      family_out->assign(reinterpret_cast<const char*>(family));
120  }
121
122  if (params_out) {
123    FcBool fc_antialias = 0;
124    if (FcPatternGetBool(match, FC_ANTIALIAS, 0, &fc_antialias) ==
125        FcResultMatch) {
126      params_out->antialiasing = fc_antialias;
127    }
128
129    FcBool fc_autohint = 0;
130    if (FcPatternGetBool(match, FC_AUTOHINT, 0, &fc_autohint) ==
131        FcResultMatch) {
132      params_out->autohinter = fc_autohint;
133    }
134
135    FcBool fc_bitmap = 0;
136    if (FcPatternGetBool(match, FC_EMBEDDED_BITMAP, 0, &fc_bitmap) ==
137        FcResultMatch) {
138      params_out->use_bitmaps = fc_bitmap;
139    }
140
141    FcBool fc_hinting = 0;
142    if (FcPatternGetBool(match, FC_HINTING, 0, &fc_hinting) == FcResultMatch) {
143      int fc_hint_style = FC_HINT_NONE;
144      if (fc_hinting)
145        FcPatternGetInteger(match, FC_HINT_STYLE, 0, &fc_hint_style);
146      params_out->hinting = ConvertFontconfigHintStyle(fc_hint_style);
147    }
148
149    int fc_rgba = FC_RGBA_NONE;
150    if (FcPatternGetInteger(match, FC_RGBA, 0, &fc_rgba) == FcResultMatch)
151      params_out->subpixel_rendering = ConvertFontconfigRgba(fc_rgba);
152  }
153
154  FcPatternDestroy(match);
155  return true;
156}
157
158// Serialize |query| into a string and hash it to a value suitable for use as a
159// cache key.
160uint32 HashFontRenderParamsQuery(const FontRenderParamsQuery& query) {
161  return base::Hash(base::StringPrintf("%d|%d|%d|%d|%s",
162      query.for_web_contents, query.pixel_size, query.point_size, query.style,
163      JoinString(query.families, ',').c_str()));
164}
165
166}  // namespace
167
168FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query,
169                                     std::string* family_out) {
170  const uint32 hash = HashFontRenderParamsQuery(query);
171  if (!family_out) {
172    // The family returned by Fontconfig isn't part of FontRenderParams, so we
173    // can only return a value from the cache if it wasn't requested.
174    SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
175    base::AutoLock lock(synchronized_cache->lock);
176    Cache::const_iterator it = synchronized_cache->cache.Get(hash);
177    if (it != synchronized_cache->cache.end()) {
178      DVLOG(1) << "Returning cached params for " << hash;
179      return it->second;
180    }
181  } else {
182    family_out->clear();
183  }
184  DVLOG(1) << "Computing params for " << hash
185           << (family_out ? " (family requested)" : "");
186
187  // Start with the delegate's settings, but let Fontconfig have the final say.
188  FontRenderParams params;
189  const LinuxFontDelegate* delegate = LinuxFontDelegate::instance();
190  if (delegate)
191    params = delegate->GetDefaultFontRenderParams();
192  QueryFontconfig(query, &params, family_out);
193  if (!params.antialiasing) {
194    // Cairo forces full hinting when antialiasing is disabled, since anything
195    // less than that looks awful; do the same here. Requesting subpixel
196    // rendering or positioning doesn't make sense either.
197    params.hinting = FontRenderParams::HINTING_FULL;
198    params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
199    params.subpixel_positioning = false;
200  } else {
201    // Fontconfig doesn't support configuring subpixel positioning; check a
202    // flag.
203    params.subpixel_positioning =
204        query.for_web_contents ?
205        CommandLine::ForCurrentProcess()->HasSwitch(
206            switches::kEnableWebkitTextSubpixelPositioning) :
207        IsBrowserTextSubpixelPositioningEnabled();
208
209    // To enable subpixel positioning, we need to disable hinting.
210    if (params.subpixel_positioning)
211      params.hinting = FontRenderParams::HINTING_NONE;
212  }
213
214  // Use the first family from the list if Fontconfig didn't suggest a family.
215  if (family_out && family_out->empty() && !query.families.empty())
216    *family_out = query.families[0];
217
218  // Store the computed struct. It's fine if this overwrites a struct that was
219  // cached by a different thread in the meantime; the values should be
220  // identical.
221  SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
222  base::AutoLock lock(synchronized_cache->lock);
223  synchronized_cache->cache.Put(hash, params);
224
225  return params;
226}
227
228void ClearFontRenderParamsCacheForTest() {
229  SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
230  base::AutoLock lock(synchronized_cache->lock);
231  synchronized_cache->cache.Clear();
232}
233
234#if defined(OS_CHROMEOS)
235void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) {
236  device_scale_factor_for_internal_display = device_scale_factor;
237}
238#endif
239
240}  // namespace gfx
241