| // Copyright 2017 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 "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/chrome_typography.h" |
| #include "chrome/browser/ui/views/chrome_typography_provider.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/default_style.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/strings/grit/app_locale_settings.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/style/typography_provider.h" |
| |
| #if defined(OS_MACOSX) |
| #include "base/mac/mac_util.h" |
| #endif |
| |
| #if defined(OS_WIN) |
| #include "base/win/windows_version.h" |
| #include "ui/gfx/win/direct_write.h" |
| #endif |
| |
| namespace { |
| |
| // Constant from the Harmony spec. |
| constexpr int kHarmonyTitleSize = 15; |
| } // namespace |
| |
| class LayoutProviderTest : public testing::Test { |
| public: |
| LayoutProviderTest() {} |
| |
| #if defined(OS_WIN) |
| protected: |
| static void SetUpTestCase() { |
| // The expected case is to have DirectWrite enabled; the fallback gives |
| // different font heights. However, only use DirectWrite on Windows 10 and |
| // later, since it's known to have flaky results on Windows 7. See |
| // http://crbug.com/759870. |
| if (base::win::GetVersion() >= base::win::VERSION_WIN10) |
| gfx::win::MaybeInitializeDirectWrite(); |
| } |
| #endif |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(LayoutProviderTest); |
| }; |
| |
| // Check legacy font sizes. No new code should be using these constants, but if |
| // these tests ever fail it probably means something in the old UI will have |
| // changed by mistake. |
| // Disabled since this relies on machine configuration. http://crbug.com/701241. |
| TEST_F(LayoutProviderTest, DISABLED_LegacyFontSizeConstants) { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| gfx::FontList label_font = rb.GetFontListWithDelta(ui::kLabelFontSizeDelta); |
| |
| EXPECT_EQ(12, label_font.GetFontSize()); |
| EXPECT_EQ(15, label_font.GetHeight()); |
| EXPECT_EQ(12, label_font.GetBaseline()); |
| EXPECT_EQ(9, label_font.GetCapHeight()); |
| // Note some Windows bots report 11,13,11,9 for the above. |
| // TODO(tapted): Smoke them out and figure out why. |
| |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(10, label_font.GetExpectedTextWidth(1)); |
| #else |
| EXPECT_EQ(6, label_font.GetExpectedTextWidth(1)); |
| // Some Windows bots may say 5. |
| #endif |
| |
| gfx::FontList title_font = rb.GetFontListWithDelta(ui::kTitleFontSizeDelta); |
| |
| #if defined(OS_WIN) |
| EXPECT_EQ(15, title_font.GetFontSize()); |
| EXPECT_EQ(20, title_font.GetHeight()); |
| EXPECT_EQ(17, title_font.GetBaseline()); |
| EXPECT_EQ(11, title_font.GetCapHeight()); |
| #elif defined(OS_MACOSX) |
| EXPECT_EQ(14, title_font.GetFontSize()); |
| EXPECT_EQ(17, title_font.GetHeight()); |
| EXPECT_EQ(14, title_font.GetBaseline()); |
| if (base::mac::IsOS10_9()) { |
| EXPECT_EQ(11, title_font.GetCapHeight()); |
| } else { |
| EXPECT_EQ(10, title_font.GetCapHeight()); |
| } |
| #else |
| EXPECT_EQ(15, title_font.GetFontSize()); |
| EXPECT_EQ(18, title_font.GetHeight()); |
| EXPECT_EQ(14, title_font.GetBaseline()); |
| EXPECT_EQ(11, title_font.GetCapHeight()); |
| #endif |
| |
| #if defined(OS_MACOSX) |
| if (base::mac::IsOS10_9()) { |
| EXPECT_EQ(7, title_font.GetExpectedTextWidth(1)); |
| } else { |
| EXPECT_EQ(12, title_font.GetExpectedTextWidth(1)); |
| } |
| #else |
| EXPECT_EQ(8, title_font.GetExpectedTextWidth(1)); |
| #endif |
| |
| gfx::FontList small_font = rb.GetFontList(ui::ResourceBundle::SmallFont); |
| gfx::FontList base_font = rb.GetFontList(ui::ResourceBundle::BaseFont); |
| gfx::FontList bold_font = rb.GetFontList(ui::ResourceBundle::BoldFont); |
| gfx::FontList medium_font = rb.GetFontList(ui::ResourceBundle::MediumFont); |
| gfx::FontList medium_bold_font = |
| rb.GetFontList(ui::ResourceBundle::MediumBoldFont); |
| gfx::FontList large_font = rb.GetFontList(ui::ResourceBundle::LargeFont); |
| |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(12, small_font.GetFontSize()); |
| EXPECT_EQ(13, base_font.GetFontSize()); |
| EXPECT_EQ(13, bold_font.GetFontSize()); |
| EXPECT_EQ(16, medium_font.GetFontSize()); |
| EXPECT_EQ(16, medium_bold_font.GetFontSize()); |
| EXPECT_EQ(21, large_font.GetFontSize()); |
| #else |
| EXPECT_EQ(11, small_font.GetFontSize()); |
| EXPECT_EQ(12, base_font.GetFontSize()); |
| EXPECT_EQ(12, bold_font.GetFontSize()); |
| EXPECT_EQ(15, medium_font.GetFontSize()); |
| EXPECT_EQ(15, medium_bold_font.GetFontSize()); |
| EXPECT_EQ(20, large_font.GetFontSize()); |
| #endif |
| } |
| |
| // Check that asking for fonts of a given size match the Harmony spec. If these |
| // tests fail, the Harmony TypographyProvider needs to be updated to handle the |
| // new font properties. For example, when title_font.GetHeight() returns 19, the |
| // Harmony TypographyProvider adds 3 to obtain its target height of 22. If a |
| // platform starts returning 18 in a standard configuration then the |
| // TypographyProvider must add 4 instead. We do this so that Chrome adapts |
| // correctly to _non-standard_ system font configurations on user machines. |
| // Disabled since this relies on machine configuration. http://crbug.com/701241. |
| TEST_F(LayoutProviderTest, DISABLED_RequestFontBySize) { |
| #if defined(OS_MACOSX) |
| constexpr int kBase = 13; |
| #else |
| constexpr int kBase = 12; |
| #endif |
| // Harmony spec. |
| constexpr int kHeadline = 20; |
| constexpr int kTitle = kHarmonyTitleSize; // Leading 22. |
| constexpr int kBody1 = 13; // Leading 20. |
| constexpr int kBody2 = 12; // Leading 20. |
| constexpr int kButton = 12; |
| |
| #if defined(OS_WIN) |
| constexpr gfx::Font::Weight kButtonWeight = gfx::Font::Weight::BOLD; |
| #else |
| constexpr gfx::Font::Weight kButtonWeight = gfx::Font::Weight::MEDIUM; |
| #endif |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| |
| gfx::FontList headline_font = rb.GetFontListWithDelta(kHeadline - kBase); |
| gfx::FontList title_font = rb.GetFontListWithDelta(kTitle - kBase); |
| gfx::FontList body1_font = rb.GetFontListWithDelta(kBody1 - kBase); |
| gfx::FontList body2_font = rb.GetFontListWithDelta(kBody2 - kBase); |
| gfx::FontList button_font = rb.GetFontListWithDelta( |
| kButton - kBase, gfx::Font::NORMAL, kButtonWeight); |
| |
| // The following checks on leading don't need to match the spec. Instead, it |
| // means Label::SetLineHeight() needs to be used to increase it. But what we |
| // are really interested in is the delta between GetFontSize() and GetHeight() |
| // since that (plus a fixed constant) determines how the leading should change |
| // when a larger font is configured in the OS. |
| |
| EXPECT_EQ(kHeadline, headline_font.GetFontSize()); |
| |
| // Headline leading not specified (multiline should be rare). |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(25, headline_font.GetHeight()); |
| #elif defined(OS_WIN) |
| EXPECT_EQ(ChromeTypographyProvider::GetPlatformFontHeight(CONTEXT_HEADLINE), |
| headline_font.GetHeight()); |
| #else |
| EXPECT_EQ(24, headline_font.GetHeight()); |
| #endif |
| |
| EXPECT_EQ(kTitle, title_font.GetFontSize()); |
| |
| // Title font leading should be 22. |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(19, title_font.GetHeight()); // i.e. Add 3 to obtain line height. |
| #elif defined(OS_WIN) |
| EXPECT_EQ(20, title_font.GetHeight()); // Add 2. |
| #else |
| EXPECT_EQ(18, title_font.GetHeight()); // Add 4. |
| #endif |
| |
| EXPECT_EQ(kBody1, body1_font.GetFontSize()); |
| |
| // Body1 font leading should be 20. |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(16, body1_font.GetHeight()); // Add 4. |
| #elif defined(OS_WIN) |
| EXPECT_EQ( |
| ChromeTypographyProvider::GetPlatformFontHeight(CONTEXT_BODY_TEXT_LARGE), |
| body1_font.GetHeight()); |
| #else // Linux. |
| EXPECT_EQ(17, body1_font.GetHeight()); // Add 3. |
| #endif |
| |
| EXPECT_EQ(kBody2, body2_font.GetFontSize()); |
| |
| // Body2 font leading should be 20. |
| #if defined(OS_WIN) |
| EXPECT_EQ( |
| ChromeTypographyProvider::GetPlatformFontHeight(CONTEXT_BODY_TEXT_SMALL), |
| body2_font.GetHeight()); |
| #else |
| EXPECT_EQ(15, body2_font.GetHeight()); // Other platforms: Add 5. |
| #endif |
| |
| EXPECT_EQ(kButton, button_font.GetFontSize()); |
| |
| // Button leading not specified (shouldn't be needed: no multiline buttons). |
| #if defined(OS_WIN) |
| EXPECT_EQ( |
| ChromeTypographyProvider::GetPlatformFontHeight(CONTEXT_BODY_TEXT_SMALL), |
| button_font.GetHeight()); |
| #else |
| EXPECT_EQ(15, button_font.GetHeight()); |
| #endif |
| } |
| |
| // Test that the default TypographyProvider correctly maps TextContexts relative |
| // to the "base" font in the manner that legacy toolkit-views code expects. This |
| // reads the base font configuration at runtime, and only tests font sizes, so |
| // should be robust against platform changes. |
| TEST_F(LayoutProviderTest, FontSizeRelativeToBase) { |
| using views::style::GetFont; |
| |
| constexpr int kStyle = views::style::STYLE_PRIMARY; |
| |
| std::unique_ptr<views::LayoutProvider> layout_provider = |
| ChromeLayoutProvider::CreateLayoutProvider(); |
| |
| // Everything's measured relative to a default-constructed FontList. |
| // On Mac, subtract one since that is 13pt instead of 12pt. |
| #if defined(OS_MACOSX) |
| const int twelve = gfx::FontList().GetFontSize() - 1; |
| #else |
| const int twelve = gfx::FontList().GetFontSize(); |
| #endif |
| |
| EXPECT_EQ(twelve, GetFont(CONTEXT_BODY_TEXT_SMALL, kStyle).GetFontSize()); |
| EXPECT_EQ(twelve, GetFont(views::style::CONTEXT_LABEL, kStyle).GetFontSize()); |
| EXPECT_EQ(twelve, |
| GetFont(views::style::CONTEXT_TEXTFIELD, kStyle).GetFontSize()); |
| EXPECT_EQ(twelve, |
| GetFont(views::style::CONTEXT_BUTTON, kStyle).GetFontSize()); |
| |
| // E.g. Headline should give a 20pt font. |
| EXPECT_EQ(twelve + 8, GetFont(CONTEXT_HEADLINE, kStyle).GetFontSize()); |
| // Titles should be 15pt. Etc. |
| EXPECT_EQ(twelve + 3, |
| GetFont(views::style::CONTEXT_DIALOG_TITLE, kStyle).GetFontSize()); |
| EXPECT_EQ(twelve + 1, GetFont(CONTEXT_BODY_TEXT_LARGE, kStyle).GetFontSize()); |
| } |
| |
| // Ensure that line height can be overridden by Chrome's TypographyProvider for |
| // for the standard set of styles. This varies by platform and test machine |
| // configuration. Generally, for a particular platform configuration, there |
| // should be a consistent increase in line height when compared to the height of |
| // a given font. |
| TEST_F(LayoutProviderTest, TypographyLineHeight) { |
| constexpr int kStyle = views::style::STYLE_PRIMARY; |
| |
| std::unique_ptr<views::LayoutProvider> layout_provider = |
| ChromeLayoutProvider::CreateLayoutProvider(); |
| |
| constexpr struct { |
| int context; |
| int min; |
| int max; |
| } kExpectedIncreases[] = {{CONTEXT_HEADLINE, 4, 8}, |
| {views::style::CONTEXT_DIALOG_TITLE, 1, 4}, |
| {CONTEXT_BODY_TEXT_LARGE, 2, 4}, |
| {CONTEXT_BODY_TEXT_SMALL, 4, 5}}; |
| |
| for (size_t i = 0; i < base::size(kExpectedIncreases); ++i) { |
| SCOPED_TRACE(testing::Message() << "Testing index: " << i); |
| const auto& increase = kExpectedIncreases[i]; |
| const gfx::FontList& font = views::style::GetFont(increase.context, kStyle); |
| int line_spacing = views::style::GetLineHeight(increase.context, kStyle); |
| EXPECT_GE(increase.max, line_spacing - font.GetHeight()); |
| EXPECT_LE(increase.min, line_spacing - font.GetHeight()); |
| } |
| |
| // Buttons should specify zero line height (i.e. use the font's height) so |
| // buttons have flexibility to configure their own spacing. |
| EXPECT_EQ(0, |
| views::style::GetLineHeight(views::style::CONTEXT_BUTTON, kStyle)); |
| EXPECT_EQ( |
| 0, views::style::GetLineHeight(views::style::CONTEXT_BUTTON_MD, kStyle)); |
| } |
| |
| // Ensure that line heights reported in a default bot configuration match the |
| // Harmony spec. This test will only run if it detects that the current machine |
| // has the default OS configuration. |
| TEST_F(LayoutProviderTest, ExplicitTypographyLineHeight) { |
| std::unique_ptr<views::LayoutProvider> layout_provider = |
| ChromeLayoutProvider::CreateLayoutProvider(); |
| |
| constexpr int kStyle = views::style::STYLE_PRIMARY; |
| if (views::style::GetFont(views::style::CONTEXT_DIALOG_TITLE, kStyle) |
| .GetFontSize() != kHarmonyTitleSize) { |
| LOG(WARNING) << "Skipping: Test machine not in default configuration."; |
| return; |
| } |
| |
| // Line heights from the Harmony spec. |
| constexpr int kBodyLineHeight = 20; |
| constexpr struct { |
| int context; |
| int line_height; |
| } kHarmonyHeights[] = {{CONTEXT_HEADLINE, 32}, |
| {views::style::CONTEXT_DIALOG_TITLE, 22}, |
| {CONTEXT_BODY_TEXT_LARGE, kBodyLineHeight}, |
| {CONTEXT_BODY_TEXT_SMALL, kBodyLineHeight}}; |
| |
| for (size_t i = 0; i < base::size(kHarmonyHeights); ++i) { |
| SCOPED_TRACE(testing::Message() << "Testing index: " << i); |
| EXPECT_EQ(kHarmonyHeights[i].line_height, |
| views::style::GetLineHeight(kHarmonyHeights[i].context, kStyle)); |
| |
| views::Label label(base::ASCIIToUTF16("test"), kHarmonyHeights[i].context); |
| label.SizeToPreferredSize(); |
| EXPECT_EQ(kHarmonyHeights[i].line_height, label.height()); |
| } |
| |
| // TODO(tapted): Pass in contexts to StyledLabel instead. Currently they are |
| // stuck on style::CONTEXT_LABEL. That only matches the default line height in |
| // ChromeTypographyProvider::GetLineHeight(), which is body text. |
| EXPECT_EQ(kBodyLineHeight, |
| views::style::GetLineHeight(views::style::CONTEXT_LABEL, kStyle)); |
| views::StyledLabel styled_label(base::ASCIIToUTF16("test"), nullptr); |
| constexpr int kStyledLabelWidth = 200; // Enough to avoid wrapping. |
| styled_label.SizeToFit(kStyledLabelWidth); |
| EXPECT_EQ(kBodyLineHeight, styled_label.height()); |
| |
| // Adding a link should not change the size. |
| styled_label.AddStyleRange( |
| gfx::Range(0, 2), views::StyledLabel::RangeStyleInfo::CreateForLink()); |
| styled_label.SizeToFit(kStyledLabelWidth); |
| EXPECT_EQ(kBodyLineHeight, styled_label.height()); |
| } |
| |
| // Only run explicit font-size checks on ChromeOS. Elsewhere, font sizes can be |
| // affected by bot configuration, but ChromeOS controls this in the |
| // ResourceBundle. Also on other platforms font metrics change a lot across OS |
| // versions, but on ChromeOS, there is only one OS version, so we can rely on |
| // consistent behavior. Also ChromeOS is the only place where |
| // IDS_UI_FONT_FAMILY_CROS works, which this test uses to control results. |
| #if defined(OS_CHROMEOS) |
| |
| // Ensure the omnibox font is always 14pt, even in Hebrew. On ChromeOS, Hebrew |
| // has a larger default font size applied from the resource bundle, but the |
| // Omnibox font configuration ignores it. |
| TEST_F(LayoutProviderTest, OmniboxFontAlways14) { |
| constexpr int kOmniboxHeight = 24; |
| constexpr int kDecorationHeight = 14; |
| constexpr int kOmniboxDesiredSize = 14; |
| constexpr int kDecorationRequestedSize = 11; |
| |
| auto& bundle = ui::ResourceBundle::GetSharedInstance(); |
| |
| auto set_system_font = [&bundle](const char* font) { |
| bundle.OverrideLocaleStringResource(IDS_UI_FONT_FAMILY_CROS, |
| base::ASCIIToUTF16(font)); |
| bundle.ReloadFonts(); |
| return gfx::FontList().GetFontSize(); |
| }; |
| |
| int base_font_size = set_system_font("Roboto, 12px"); |
| EXPECT_EQ(12, base_font_size); |
| EXPECT_EQ(base_font_size, bundle.GetFontListWithDelta(0).GetFontSize()); |
| EXPECT_EQ(14 - base_font_size, GetFontSizeDeltaBoundedByAvailableHeight( |
| kOmniboxHeight, kOmniboxDesiredSize)); |
| EXPECT_EQ(11 - base_font_size, |
| GetFontSizeDeltaBoundedByAvailableHeight(kDecorationHeight, |
| kDecorationRequestedSize)); |
| |
| // Ensure there is a threshold where the font actually shrinks. |
| int latin_height_threshold = kOmniboxHeight; |
| for (; latin_height_threshold > 0; --latin_height_threshold) { |
| if (kOmniboxDesiredSize - base_font_size != |
| GetFontSizeDeltaBoundedByAvailableHeight(latin_height_threshold, |
| kOmniboxDesiredSize)) |
| break; |
| } |
| // The threshold should always be the same, but the value depends on font |
| // metrics. Check for some sane value. This should only change if Roboto |
| // itself changes. |
| EXPECT_EQ(16, latin_height_threshold); |
| |
| // Switch to Hebrew settings. |
| base_font_size = set_system_font("Roboto, Noto Sans Hebrew, 13px"); |
| EXPECT_EQ(13, gfx::FontList().GetFontSize()); |
| EXPECT_EQ(base_font_size, bundle.GetFontListWithDelta(0).GetFontSize()); |
| |
| // The base font size has increased, but the delta returned should still |
| // result in a 14pt font. |
| EXPECT_EQ(14 - base_font_size, GetFontSizeDeltaBoundedByAvailableHeight( |
| kOmniboxHeight, kOmniboxDesiredSize)); |
| EXPECT_EQ(11 - base_font_size, |
| GetFontSizeDeltaBoundedByAvailableHeight(kDecorationHeight, |
| kDecorationRequestedSize)); |
| } |
| |
| #endif // OS_CHROMEOS |