1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/system_fonts_win.h"
#include <windows.h>
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/scoped_select_object.h"
#include "ui/gfx/platform_font.h"
namespace gfx {
namespace win {
namespace {
class SystemFonts {
public:
const gfx::Font& GetFont(SystemFont system_font) {
if (!IsInitialized())
Initialize();
auto it = system_fonts_.find(system_font);
CHECK(it != system_fonts_.end())
<< "System font #" << static_cast<int>(system_font) << " not found!";
return it->second;
}
static SystemFonts* Instance() {
static base::NoDestructor<SystemFonts> instance;
return instance.get();
}
SystemFonts(const SystemFonts&) = delete;
SystemFonts& operator=(const SystemFonts&) = delete;
void ResetForTesting() {
SystemFonts::is_initialized_ = false;
SystemFonts::adjust_font_callback_ = nullptr;
SystemFonts::get_minimum_font_size_callback_ = nullptr;
system_fonts_.clear();
}
static int AdjustFontSize(int lf_height, int size_delta) {
// Extract out the sign of |lf_height| - we'll add it back later.
const int lf_sign = lf_height < 0 ? -1 : 1;
lf_height = std::abs(lf_height);
// Apply the size adjustment.
lf_height += size_delta;
// Make sure |lf_height| is not smaller than allowed min allowed font size.
int min_font_size = 0;
if (get_minimum_font_size_callback_) {
min_font_size = get_minimum_font_size_callback_();
DCHECK_GE(min_font_size, 0);
}
lf_height = std::max(min_font_size, lf_height);
// Add back the sign.
return lf_sign * lf_height;
}
static void AdjustLOGFONT(const FontAdjustment& font_adjustment,
LOGFONT* logfont) {
DCHECK_GT(font_adjustment.font_scale, 0.0);
LONG new_height =
base::ClampRound<LONG>(logfont->lfHeight * font_adjustment.font_scale);
if (logfont->lfHeight && !new_height)
new_height = logfont->lfHeight > 0 ? 1 : -1;
logfont->lfHeight = new_height;
if (!font_adjustment.font_family_override.empty()) {
auto result = UNSAFE_TODO(wcscpy_s(
logfont->lfFaceName, font_adjustment.font_family_override.c_str()));
DCHECK_EQ(0, result) << "Font name "
<< font_adjustment.font_family_override
<< " cannot be copied into LOGFONT structure.";
}
}
static Font GetFontFromLOGFONT(const LOGFONT& logfont) {
// Finds a matching font by triggering font mapping. The font mapper finds
// the closest physical font for a given logical font.
base::win::ScopedGDIObject<HFONT> font(::CreateFontIndirect(&logfont));
base::win::ScopedGetDC screen_dc(NULL);
base::win::ScopedSelectObject scoped_font(screen_dc, font.get());
DCHECK(font.get()) << "Font for '"
<< base::SysWideToUTF8(logfont.lfFaceName)
<< "' has an invalid handle.";
// Retrieve the name and height of the mapped font (physical font).
LOGFONT mapped_font_info;
GetObject(font.get(), sizeof(mapped_font_info), &mapped_font_info);
std::string font_name = base::SysWideToUTF8(mapped_font_info.lfFaceName);
TEXTMETRIC mapped_font_metrics;
GetTextMetrics(screen_dc, &mapped_font_metrics);
const int font_size =
std::max<int>(1, mapped_font_metrics.tmHeight -
mapped_font_metrics.tmInternalLeading);
Font system_font =
Font(PlatformFont::CreateFromNameAndSize(font_name, font_size));
// System fonts may have different styles when they are manually changed by
// the users (see crbug.com/989476).
Font::FontStyle style = logfont.lfItalic == 0 ? Font::FontStyle::NORMAL
: Font::FontStyle::ITALIC;
Font::Weight weight = logfont.lfWeight == 0
? Font::Weight::NORMAL
: static_cast<Font::Weight>(logfont.lfWeight);
if (style != Font::FontStyle::NORMAL || weight != Font::Weight::NORMAL)
system_font = system_font.Derive(0, style, weight);
return system_font;
}
static void SetGetMinimumFontSizeCallback(
GetMinimumFontSizeCallback callback) {
DCHECK(!SystemFonts::IsInitialized());
get_minimum_font_size_callback_ = callback;
}
static void SetAdjustFontCallback(AdjustFontCallback callback) {
DCHECK(!SystemFonts::IsInitialized());
adjust_font_callback_ = callback;
}
private:
friend base::NoDestructor<SystemFonts>;
SystemFonts() {}
void Initialize() {
TRACE_EVENT0("fonts", "gfx::SystemFonts::Initialize");
NONCLIENTMETRICS metrics = {};
metrics.cbSize = sizeof(metrics);
const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
metrics.cbSize, &metrics, 0);
DCHECK(success);
// NOTE(dfried): When rendering Chrome, we do all of our own font scaling
// based on a number of factors, but what Windows reports to us has some
// (but not all) of these factors baked in, and not in a way that is
// display-consistent.
//
// For example, if your system DPI is 192 (200%) but you connect a monitor
// with a standard DPI (100%) then even if Chrome starts on the second
// monitor, we will be told the system font is 24pt instead of 12pt.
// Conversely, if the system DPI is set to 96 (100%) but all of our monitors
// are currently at 150%, Windows will still report 12pt fonts.
//
// The same is true with Text Zoom (a new accessibility feature). If zoom is
// set to 150%, then Windows will report a font size of 18pt. But again, we
// already take Text Zoom into account when rendering, so we want to account
// for that.
//
// Our system fonts are in DIPs, so we must always take what Windows gives
// us, figure out which adjustments it's making (and undo them), make our
// own adjustments for localization (for example, we always render Hindi 25%
// larger for readability), and only then can we store (and report) the
// system fonts.
// Factor in/out scale adjustment that fall outside what we can access here.
// This includes l10n adjustments and those we have to ask UWP or other COM
// interfaces for (since we don't have dependencies on that code from this
// module, and don't want to implicitly invoke COM for testing purposes if
// we don't have to).
FontAdjustment font_adjustment;
if (adjust_font_callback_) {
adjust_font_callback_(font_adjustment);
}
// Factor out system DPI scale that Windows will include in reported font
// sizes. Note that these are (sadly) system-wide and do not reflect
// specific displays' DPI.
double system_scale = GetSystemScale();
font_adjustment.font_scale /= system_scale;
// Grab each of the fonts from the NONCLIENTMETRICS block, adjust it
// appropriately, and store it in the font table.
AddFont(SystemFont::kCaption, font_adjustment, &metrics.lfCaptionFont);
AddFont(SystemFont::kSmallCaption, font_adjustment,
&metrics.lfSmCaptionFont);
AddFont(SystemFont::kMenu, font_adjustment, &metrics.lfMenuFont);
AddFont(SystemFont::kMessage, font_adjustment, &metrics.lfMessageFont);
AddFont(SystemFont::kStatus, font_adjustment, &metrics.lfStatusFont);
is_initialized_ = true;
}
static bool IsInitialized() { return is_initialized_; }
void AddFont(SystemFont system_font,
const FontAdjustment& font_adjustment,
LOGFONT* logfont) {
TRACE_EVENT0("fonts", "gfx::SystemFonts::AddFont");
// Make adjustments to the font as necessary.
AdjustLOGFONT(font_adjustment, logfont);
// Cap at minimum font size.
logfont->lfHeight = AdjustFontSize(logfont->lfHeight, 0);
system_fonts_.emplace(system_font, GetFontFromLOGFONT(*logfont));
}
// Returns the system DPI scale (standard DPI being 1.0).
// TODO(dfried): move dpi.[h|cc] somewhere in base/win so we can share this
// logic. However, note that the similar function in dpi.h is used many places
// it ought not to be.
static double GetSystemScale() {
constexpr double kDefaultDPI = 96.0;
base::win::ScopedGetDC screen_dc(nullptr);
return ::GetDeviceCaps(screen_dc, LOGPIXELSY) / kDefaultDPI;
}
// Use a flat map for faster lookups.
base::flat_map<SystemFont, gfx::Font> system_fonts_;
static bool is_initialized_;
// Font adjustment callback.
static AdjustFontCallback adjust_font_callback_;
// Minimum size callback.
static GetMinimumFontSizeCallback get_minimum_font_size_callback_;
};
// static
bool SystemFonts::is_initialized_ = false;
// static
AdjustFontCallback SystemFonts::adjust_font_callback_ = nullptr;
// static
GetMinimumFontSizeCallback SystemFonts::get_minimum_font_size_callback_ =
nullptr;
} // namespace
void SetGetMinimumFontSizeCallback(GetMinimumFontSizeCallback callback) {
SystemFonts::SetGetMinimumFontSizeCallback(callback);
}
void SetAdjustFontCallback(AdjustFontCallback callback) {
SystemFonts::SetAdjustFontCallback(callback);
}
const Font& GetDefaultSystemFont() {
// The message font is the closest font for a default system font from the
// structure NONCLIENTMETRICS. The lfMessageFont field contains information
// about the logical font used to display text in message boxes.
return GetSystemFont(SystemFont::kMessage);
}
const Font& GetSystemFont(SystemFont system_font) {
return SystemFonts::Instance()->GetFont(system_font);
}
int AdjustFontSize(int lf_height, int size_delta) {
return SystemFonts::AdjustFontSize(lf_height, size_delta);
}
void AdjustLOGFONTForTesting(const FontAdjustment& font_adjustment,
LOGFONT* logfont) {
SystemFonts::AdjustLOGFONT(font_adjustment, logfont);
}
// Retrieve a FONT from a LOGFONT structure.
Font GetFontFromLOGFONTForTesting(const LOGFONT& logfont) {
return SystemFonts::GetFontFromLOGFONT(logfont);
}
void ResetSystemFontsForTesting() {
SystemFonts::Instance()->ResetForTesting();
}
} // namespace win
} // namespace gfx
|