| // 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/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); |
| DCHECK(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 = 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::ScopedHFONT 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 |