blob: 9f9440a8f6bbebebb32a60f298b19f55e337a722 [file] [log] [blame]
// 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/font_render_params.h"
#include <fontconfig/fontconfig.h>
#include "base/command_line.h"
#include "base/containers/mru_cache.h"
#include "base/hash.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "ui/gfx/display.h"
#include "ui/gfx/font.h"
#include "ui/gfx/linux_font_delegate.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/switches.h"
namespace gfx {
namespace {
#if defined(OS_CHROMEOS)
// A device scale factor for an internal display (if any)
// that is used to determine if subpixel positioning should be used.
float device_scale_factor_for_internal_display = 1.0f;
#endif
// Number of recent GetFontRenderParams() results to cache.
const size_t kCacheSize = 256;
// Cached result from a call to GetFontRenderParams().
struct QueryResult {
QueryResult(const FontRenderParams& params, const std::string& family)
: params(params),
family(family) {
}
~QueryResult() {}
FontRenderParams params;
std::string family;
};
// Keyed by hashes of FontRenderParamQuery structs from
// HashFontRenderParamsQuery().
typedef base::MRUCache<uint32, QueryResult> Cache;
// A cache and the lock that must be held while accessing it.
// GetFontRenderParams() is called by both the UI thread and the sandbox IPC
// thread.
struct SynchronizedCache {
SynchronizedCache() : cache(kCacheSize) {}
base::Lock lock;
Cache cache;
};
base::LazyInstance<SynchronizedCache>::Leaky g_synchronized_cache =
LAZY_INSTANCE_INITIALIZER;
// Converts Fontconfig FC_HINT_STYLE to FontRenderParams::Hinting.
FontRenderParams::Hinting ConvertFontconfigHintStyle(int hint_style) {
switch (hint_style) {
case FC_HINT_SLIGHT: return FontRenderParams::HINTING_SLIGHT;
case FC_HINT_MEDIUM: return FontRenderParams::HINTING_MEDIUM;
case FC_HINT_FULL: return FontRenderParams::HINTING_FULL;
default: return FontRenderParams::HINTING_NONE;
}
}
// Converts Fontconfig FC_RGBA to FontRenderParams::SubpixelRendering.
FontRenderParams::SubpixelRendering ConvertFontconfigRgba(int rgba) {
switch (rgba) {
case FC_RGBA_RGB: return FontRenderParams::SUBPIXEL_RENDERING_RGB;
case FC_RGBA_BGR: return FontRenderParams::SUBPIXEL_RENDERING_BGR;
case FC_RGBA_VRGB: return FontRenderParams::SUBPIXEL_RENDERING_VRGB;
case FC_RGBA_VBGR: return FontRenderParams::SUBPIXEL_RENDERING_VBGR;
default: return FontRenderParams::SUBPIXEL_RENDERING_NONE;
}
}
// Queries Fontconfig for rendering settings and updates |params_out| and
// |family_out| (if non-NULL). Returns false on failure.
bool QueryFontconfig(const FontRenderParamsQuery& query,
FontRenderParams* params_out,
std::string* family_out) {
struct FcPatternDeleter {
void operator()(FcPattern* ptr) const { FcPatternDestroy(ptr); }
};
typedef scoped_ptr<FcPattern, FcPatternDeleter> ScopedFcPattern;
ScopedFcPattern query_pattern(FcPatternCreate());
CHECK(query_pattern);
FcPatternAddBool(query_pattern.get(), FC_SCALABLE, FcTrue);
for (std::vector<std::string>::const_iterator it = query.families.begin();
it != query.families.end(); ++it) {
FcPatternAddString(query_pattern.get(),
FC_FAMILY, reinterpret_cast<const FcChar8*>(it->c_str()));
}
if (query.pixel_size > 0)
FcPatternAddDouble(query_pattern.get(), FC_PIXEL_SIZE, query.pixel_size);
if (query.point_size > 0)
FcPatternAddInteger(query_pattern.get(), FC_SIZE, query.point_size);
if (query.style >= 0) {
FcPatternAddInteger(query_pattern.get(), FC_SLANT,
(query.style & Font::ITALIC) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN);
FcPatternAddInteger(query_pattern.get(), FC_WEIGHT,
(query.style & Font::BOLD) ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL);
}
FcConfigSubstitute(NULL, query_pattern.get(), FcMatchPattern);
FcDefaultSubstitute(query_pattern.get());
ScopedFcPattern result_pattern;
if (query.is_empty()) {
// If the query was empty, call FcConfigSubstituteWithPat() to get a
// non-family- or size-specific configuration so it can be used as the
// default.
result_pattern.reset(FcPatternDuplicate(query_pattern.get()));
if (!result_pattern)
return false;
FcPatternDel(result_pattern.get(), FC_FAMILY);
FcPatternDel(result_pattern.get(), FC_PIXEL_SIZE);
FcPatternDel(result_pattern.get(), FC_SIZE);
FcConfigSubstituteWithPat(NULL, result_pattern.get(), query_pattern.get(),
FcMatchFont);
} else {
FcResult result;
result_pattern.reset(FcFontMatch(NULL, query_pattern.get(), &result));
if (!result_pattern)
return false;
}
DCHECK(result_pattern);
if (family_out) {
FcChar8* family = NULL;
FcPatternGetString(result_pattern.get(), FC_FAMILY, 0, &family);
if (family)
family_out->assign(reinterpret_cast<const char*>(family));
}
if (params_out) {
FcBool fc_antialias = 0;
if (FcPatternGetBool(result_pattern.get(), FC_ANTIALIAS, 0,
&fc_antialias) == FcResultMatch) {
params_out->antialiasing = fc_antialias;
}
FcBool fc_autohint = 0;
if (FcPatternGetBool(result_pattern.get(), FC_AUTOHINT, 0, &fc_autohint) ==
FcResultMatch) {
params_out->autohinter = fc_autohint;
}
FcBool fc_bitmap = 0;
if (FcPatternGetBool(result_pattern.get(), FC_EMBEDDED_BITMAP, 0,
&fc_bitmap) ==
FcResultMatch) {
params_out->use_bitmaps = fc_bitmap;
}
FcBool fc_hinting = 0;
if (FcPatternGetBool(result_pattern.get(), FC_HINTING, 0, &fc_hinting) ==
FcResultMatch) {
int fc_hint_style = FC_HINT_NONE;
if (fc_hinting) {
FcPatternGetInteger(
result_pattern.get(), FC_HINT_STYLE, 0, &fc_hint_style);
}
params_out->hinting = ConvertFontconfigHintStyle(fc_hint_style);
}
int fc_rgba = FC_RGBA_NONE;
if (FcPatternGetInteger(result_pattern.get(), FC_RGBA, 0, &fc_rgba) ==
FcResultMatch)
params_out->subpixel_rendering = ConvertFontconfigRgba(fc_rgba);
}
return true;
}
// Serialize |query| into a string and hash it to a value suitable for use as a
// cache key.
uint32 HashFontRenderParamsQuery(const FontRenderParamsQuery& query) {
return base::Hash(base::StringPrintf(
"%d|%d|%d|%d|%s|%f", query.for_web_contents, query.pixel_size,
query.point_size, query.style, JoinString(query.families, ',').c_str(),
query.device_scale_factor));
}
} // namespace
FontRenderParams GetFontRenderParams(const FontRenderParamsQuery& query,
std::string* family_out) {
FontRenderParamsQuery actual_query(query);
if (actual_query.device_scale_factor == 0) {
#if defined(OS_CHROMEOS)
actual_query.device_scale_factor = device_scale_factor_for_internal_display;
#else
// Linux does not support per-display DPI, so we use a slightly simpler
// code path than on Chrome OS to figure out the device scale factor.
gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
if (screen) {
gfx::Display display = screen->GetPrimaryDisplay();
actual_query.device_scale_factor = display.device_scale_factor();
}
#endif
}
const uint32 hash = HashFontRenderParamsQuery(actual_query);
SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
{
// Try to find a cached result so Fontconfig doesn't need to be queried.
base::AutoLock lock(synchronized_cache->lock);
Cache::const_iterator it = synchronized_cache->cache.Get(hash);
if (it != synchronized_cache->cache.end()) {
DVLOG(1) << "Returning cached params for " << hash;
const QueryResult& result = it->second;
if (family_out)
*family_out = result.family;
return result.params;
}
}
DVLOG(1) << "Computing params for " << hash;
if (family_out)
family_out->clear();
// Start with the delegate's settings, but let Fontconfig have the final say.
FontRenderParams params;
const LinuxFontDelegate* delegate = LinuxFontDelegate::instance();
if (delegate)
params = delegate->GetDefaultFontRenderParams();
QueryFontconfig(actual_query, &params, family_out);
if (!params.antialiasing) {
// Cairo forces full hinting when antialiasing is disabled, since anything
// less than that looks awful; do the same here. Requesting subpixel
// rendering or positioning doesn't make sense either.
params.hinting = FontRenderParams::HINTING_FULL;
params.subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
params.subpixel_positioning = false;
} else {
params.subpixel_positioning = actual_query.device_scale_factor > 1.0f;
// To enable subpixel positioning, we need to disable hinting.
if (params.subpixel_positioning)
params.hinting = FontRenderParams::HINTING_NONE;
}
// Use the first family from the list if Fontconfig didn't suggest a family.
if (family_out && family_out->empty() && !actual_query.families.empty())
*family_out = actual_query.families[0];
{
// Store the result. It's fine if this overwrites a result that was cached
// by a different thread in the meantime; the values should be identical.
base::AutoLock lock(synchronized_cache->lock);
synchronized_cache->cache.Put(hash,
QueryResult(params, family_out ? *family_out : std::string()));
}
return params;
}
void ClearFontRenderParamsCacheForTest() {
SynchronizedCache* synchronized_cache = g_synchronized_cache.Pointer();
base::AutoLock lock(synchronized_cache->lock);
synchronized_cache->cache.Clear();
}
#if defined(OS_CHROMEOS)
float GetFontRenderParamsDeviceScaleFactor() {
return device_scale_factor_for_internal_display;
}
void SetFontRenderParamsDeviceScaleFactor(float device_scale_factor) {
device_scale_factor_for_internal_display = device_scale_factor;
}
#endif
} // namespace gfx