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 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <memory>
12 
13 #include "base/command_line.h"
14 #include "base/containers/mru_cache.h"
15 #include "base/hash/hash.h"
16 #include "base/lazy_instance.h"
17 #include "base/logging.h"
18 #include "base/macros.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/synchronization/lock.h"
22 #include "base/trace_event/trace_event.h"
23 #include "build/build_config.h"
24 #include "ui/gfx/font.h"
25 #include "ui/gfx/linux/fontconfig_util.h"
26 #include "ui/gfx/skia_font_delegate.h"
27 #include "ui/gfx/switches.h"
28 
29 namespace gfx {
30 
31 namespace {
32 
FontWeightToFCWeight(Font::Weight weight)33 int FontWeightToFCWeight(Font::Weight weight) {
34   const int weight_number = static_cast<int>(weight);
35   if (weight_number <= (static_cast<int>(Font::Weight::THIN) +
36                         static_cast<int>(Font::Weight::EXTRA_LIGHT)) /
37                            2)
38     return FC_WEIGHT_THIN;
39   else if (weight_number <= (static_cast<int>(Font::Weight::EXTRA_LIGHT) +
40                              static_cast<int>(Font::Weight::LIGHT)) /
41                                 2)
42     return FC_WEIGHT_ULTRALIGHT;
43   else if (weight_number <= (static_cast<int>(Font::Weight::LIGHT) +
44                              static_cast<int>(Font::Weight::NORMAL)) /
45                                 2)
46     return FC_WEIGHT_LIGHT;
47   else if (weight_number <= (static_cast<int>(Font::Weight::NORMAL) +
48                              static_cast<int>(Font::Weight::MEDIUM)) /
49                                 2)
50     return FC_WEIGHT_NORMAL;
51   else if (weight_number <= (static_cast<int>(Font::Weight::MEDIUM) +
52                              static_cast<int>(Font::Weight::SEMIBOLD)) /
53                                 2)
54     return FC_WEIGHT_MEDIUM;
55   else if (weight_number <= (static_cast<int>(Font::Weight::SEMIBOLD) +
56                              static_cast<int>(Font::Weight::BOLD)) /
57                                 2)
58     return FC_WEIGHT_DEMIBOLD;
59   else if (weight_number <= (static_cast<int>(Font::Weight::BOLD) +
60                              static_cast<int>(Font::Weight::EXTRA_BOLD)) /
61                                 2)
62     return FC_WEIGHT_BOLD;
63   else if (weight_number <= (static_cast<int>(Font::Weight::EXTRA_BOLD) +
64                              static_cast<int>(Font::Weight::BLACK)) /
65                                 2)
66     return FC_WEIGHT_ULTRABOLD;
67   else
68     return FC_WEIGHT_BLACK;
69 }
70 
71 // A device scale factor used to determine if subpixel positioning
72 // should be used.
73 float device_scale_factor_ = 1.0f;
74 
75 // Number of recent GetFontRenderParams() results to cache.
76 const size_t kCacheSize = 256;
77 
78 // Cached result from a call to GetFontRenderParams().
79 struct QueryResult {
QueryResultgfx::__anondb39d85d0111::QueryResult80   QueryResult(const FontRenderParams& params, const std::string& family)
81       : params(params),
82         family(family) {
83   }
~QueryResultgfx::__anondb39d85d0111::QueryResult84   ~QueryResult() {}
85 
86   FontRenderParams params;
87   std::string family;
88 };
89 
90 // Keyed by hashes of FontRenderParamQuery structs from
91 // HashFontRenderParamsQuery().
92 typedef base::MRUCache<uint32_t, QueryResult> Cache;
93 
94 // A cache and the lock that must be held while accessing it.
95 // GetFontRenderParams() is called by both the UI thread and the sandbox IPC
96 // thread.
97 struct SynchronizedCache {
SynchronizedCachegfx::__anondb39d85d0111::SynchronizedCache98   SynchronizedCache() : cache(kCacheSize) {}
99 
100   base::Lock lock;
101   Cache cache;
102 };
103 
104 base::LazyInstance<SynchronizedCache>::Leaky g_synchronized_cache =
105     LAZY_INSTANCE_INITIALIZER;
106 
107 // Queries Fontconfig for rendering settings and updates |params_out| and
108 // |family_out| (if non-NULL). Returns false on failure.
QueryFontconfig(const FontRenderParamsQuery & query,FontRenderParams * params_out,std::string * family_out)109 bool QueryFontconfig(const FontRenderParamsQuery& query,
110                      FontRenderParams* params_out,
111                      std::string* family_out) {
112   TRACE_EVENT0("fonts", "gfx::QueryFontconfig");
113 
114   ScopedFcPattern query_pattern(FcPatternCreate());
115   CHECK(query_pattern);
116 
117   FcPatternAddBool(query_pattern.get(), FC_SCALABLE, FcTrue);
118 
119   for (auto it = query.families.begin(); it != query.families.end(); ++it) {
120     FcPatternAddString(query_pattern.get(),
121         FC_FAMILY, reinterpret_cast<const FcChar8*>(it->c_str()));
122   }
123   if (query.pixel_size > 0)
124     FcPatternAddDouble(query_pattern.get(), FC_PIXEL_SIZE, query.pixel_size);
125   if (query.point_size > 0)
126     FcPatternAddInteger(query_pattern.get(), FC_SIZE, query.point_size);
127   if (query.style >= 0) {
128     FcPatternAddInteger(query_pattern.get(), FC_SLANT,
129         (query.style & Font::ITALIC) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN);
130   }
131   if (query.weight != Font::Weight::INVALID) {
132     FcPatternAddInteger(query_pattern.get(), FC_WEIGHT,
133                         FontWeightToFCWeight(query.weight));
134   }
135 
136   FcConfig* config = GetGlobalFontConfig();
137   FcConfigSubstitute(config, query_pattern.get(), FcMatchPattern);
138   FcDefaultSubstitute(query_pattern.get());
139 
140   ScopedFcPattern result_pattern;
141   if (query.is_empty()) {
142     // If the query was empty, call FcConfigSubstituteWithPat() to get a
143     // non-family- or size-specific configuration so it can be used as the
144     // default.
145     result_pattern.reset(FcPatternDuplicate(query_pattern.get()));
146     if (!result_pattern)
147       return false;
148     FcPatternDel(result_pattern.get(), FC_FAMILY);
149     FcPatternDel(result_pattern.get(), FC_PIXEL_SIZE);
150     FcPatternDel(result_pattern.get(), FC_SIZE);
151     FcConfigSubstituteWithPat(config, result_pattern.get(), query_pattern.get(),
152                               FcMatchFont);
153   } else {
154     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("fonts"), "FcFontMatch");
155     FcResult result;
156     result_pattern.reset(FcFontMatch(config, query_pattern.get(), &result));
157     if (!result_pattern)
158       return false;
159   }
160   DCHECK(result_pattern);
161 
162   if (family_out) {
163     FcChar8* family = NULL;
164     FcPatternGetString(result_pattern.get(), FC_FAMILY, 0, &family);
165     if (family)
166       family_out->assign(reinterpret_cast<const char*>(family));
167   }
168 
169   if (params_out)
170     GetFontRenderParamsFromFcPattern(result_pattern.get(), params_out);
171 
172   return true;
173 }
174 
175 // Serialize |query| into a string and hash it to a value suitable for use as a
176 // cache key.
HashFontRenderParamsQuery(const FontRenderParamsQuery & query)177 uint32_t HashFontRenderParamsQuery(const FontRenderParamsQuery& query) {
178   return base::Hash(base::StringPrintf(
179       "%d|%d|%d|%d|%s|%f", query.pixel_size, query.point_size, query.style,
180       static_cast<int>(query.weight),
181       base::JoinString(query.families, ",").c_str(),
182       query.device_scale_factor));
183 }
184 
185 }  // namespace
186 
GetFontRenderParams(const FontRenderParamsQuery & query,std::string * family_out)187 FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query,
188                                      std::string* family_out) {
189   TRACE_EVENT0("fonts", "gfx::GetFontRenderParams");
190 
191   FontRenderParamsQuery actual_query(query);
192   if (actual_query.device_scale_factor == 0)
193     actual_query.device_scale_factor = device_scale_factor_;
194 
195   const uint32_t hash = HashFontRenderParamsQuery(actual_query);
196   SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
197 
198   {
199     // Try to find a cached result so Fontconfig doesn't need to be queried.
200     base::AutoLock lock(synchronized_cache->lock);
201     Cache::const_iterator it = synchronized_cache->cache.Get(hash);
202     if (it != synchronized_cache->cache.end()) {
203       DVLOG(1) << "Returning cached params for " << hash;
204       const QueryResult& result = it->second;
205       if (family_out)
206         *family_out = result.family;
207       return result.params;
208     }
209   }
210 
211   DVLOG(1) << "Computing params for " << hash;
212   if (family_out)
213     family_out->clear();
214 
215   // Start with the delegate's settings, but let Fontconfig have the final say.
216   FontRenderParams params;
217   const SkiaFontDelegate* delegate = SkiaFontDelegate::instance();
218   if (delegate)
219     params = delegate->GetDefaultFontRenderParams();
220   QueryFontconfig(actual_query, &params, family_out);
221   if (!params.antialiasing) {
222     // Cairo forces full hinting when antialiasing is disabled, since anything
223     // less than that looks awful; do the same here. Requesting subpixel
224     // rendering or positioning doesn't make sense either.
225     params.hinting = FontRenderParams::HINTING_FULL;
226     params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
227     params.subpixel_positioning = false;
228   } else if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
229                  switches::kDisableFontSubpixelPositioning)) {
230 #if !defined(OS_CHROMEOS)
231     params.subpixel_positioning = actual_query.device_scale_factor > 1.0f;
232 #else
233     // We want to enable subpixel positioning for fractional dsf.
234     params.subpixel_positioning =
235         std::abs(std::round(actual_query.device_scale_factor) -
236                  actual_query.device_scale_factor) >
237         std::numeric_limits<float>::epsilon();
238 #endif  // !defined(OS_CHROMEOS)
239 
240     // To enable subpixel positioning, we need to disable hinting.
241     if (params.subpixel_positioning)
242       params.hinting = FontRenderParams::HINTING_NONE;
243   }
244 
245   // Use the first family from the list if Fontconfig didn't suggest a family.
246   if (family_out && family_out->empty() && !actual_query.families.empty())
247     *family_out = actual_query.families[0];
248 
249   {
250     // Store the result. It's fine if this overwrites a result that was cached
251     // by a different thread in the meantime; the values should be identical.
252     base::AutoLock lock(synchronized_cache->lock);
253     synchronized_cache->cache.Put(hash,
254         QueryResult(params, family_out ? *family_out : std::string()));
255   }
256 
257   return params;
258 }
259 
ClearFontRenderParamsCacheForTest()260 void ClearFontRenderParamsCacheForTest() {
261   SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
262   base::AutoLock lock(synchronized_cache->lock);
263   synchronized_cache->cache.Clear();
264 }
265 
GetFontRenderParamsDeviceScaleFactor()266 float GetFontRenderParamsDeviceScaleFactor() {
267   return device_scale_factor_;
268 }
269 
SetFontRenderParamsDeviceScaleFactor(float device_scale_factor)270 void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) {
271   device_scale_factor_ = device_scale_factor;
272 }
273 
274 }  // namespace gfx
275