| // 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/platform_font_mac.h" |
| |
| #include <Cocoa/Cocoa.h> |
| #include <stddef.h> |
| |
| #include "base/mac/mac_util.h" |
| #include "base/stl_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/font.h" |
| |
| // TODO(tapted): Remove gfx:: prefixes. |
| namespace gfx { |
| |
| TEST(PlatformFontMacTest, DeriveFont) { |
| // Use a base font that support all traits. |
| gfx::Font base_font("Helvetica", 13); |
| |
| // Bold |
| gfx::Font bold_font( |
| base_font.Derive(0, gfx::Font::NORMAL, gfx::Font::Weight::BOLD)); |
| NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:bold_font.GetNativeFont()]; |
| EXPECT_EQ(NSBoldFontMask, traits); |
| |
| // Italic |
| gfx::Font italic_font( |
| base_font.Derive(0, gfx::Font::ITALIC, gfx::Font::Weight::NORMAL)); |
| traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:italic_font.GetNativeFont()]; |
| EXPECT_EQ(NSItalicFontMask, traits); |
| |
| // Bold italic |
| gfx::Font bold_italic_font( |
| base_font.Derive(0, gfx::Font::ITALIC, gfx::Font::Weight::BOLD)); |
| traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:bold_italic_font.GetNativeFont()]; |
| EXPECT_EQ(static_cast<NSFontTraitMask>(NSBoldFontMask | NSItalicFontMask), |
| traits); |
| } |
| |
| TEST(PlatformFontMacTest, DeriveFontUnderline) { |
| // Create a default font. |
| gfx::Font base_font; |
| |
| // Make the font underlined. |
| gfx::Font derived_font(base_font.Derive( |
| 0, base_font.GetStyle() | gfx::Font::UNDERLINE, base_font.GetWeight())); |
| |
| // Validate the derived font properties against its native font instance. |
| NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:derived_font.GetNativeFont()]; |
| gfx::Font::Weight actual_weight = (traits & NSFontBoldTrait) |
| ? gfx::Font::Weight::BOLD |
| : gfx::Font::Weight::NORMAL; |
| |
| int actual_style = gfx::Font::UNDERLINE; |
| if (traits & NSFontItalicTrait) |
| actual_style |= gfx::Font::ITALIC; |
| |
| EXPECT_TRUE(derived_font.GetStyle() & gfx::Font::UNDERLINE); |
| EXPECT_EQ(derived_font.GetStyle(), actual_style); |
| EXPECT_EQ(derived_font.GetWeight(), actual_weight); |
| } |
| |
| // Tests internal methods for extracting gfx::Font properties from the |
| // underlying CTFont representation. |
| TEST(PlatformFontMacTest, ConstructFromNativeFont) { |
| Font normal_font([NSFont fontWithName:@"Helvetica" size:12]); |
| EXPECT_EQ(12, normal_font.GetFontSize()); |
| EXPECT_EQ("Helvetica", normal_font.GetFontName()); |
| EXPECT_EQ(Font::NORMAL, normal_font.GetStyle()); |
| EXPECT_EQ(Font::Weight::NORMAL, normal_font.GetWeight()); |
| |
| Font bold_font([NSFont fontWithName:@"Helvetica-Bold" size:14]); |
| EXPECT_EQ(14, bold_font.GetFontSize()); |
| EXPECT_EQ("Helvetica", bold_font.GetFontName()); |
| EXPECT_EQ(Font::NORMAL, bold_font.GetStyle()); |
| EXPECT_EQ(Font::Weight::BOLD, bold_font.GetWeight()); |
| |
| Font italic_font([NSFont fontWithName:@"Helvetica-Oblique" size:14]); |
| EXPECT_EQ(14, italic_font.GetFontSize()); |
| EXPECT_EQ("Helvetica", italic_font.GetFontName()); |
| EXPECT_EQ(Font::ITALIC, italic_font.GetStyle()); |
| EXPECT_EQ(Font::Weight::NORMAL, italic_font.GetWeight()); |
| |
| Font bold_italic_font([NSFont fontWithName:@"Helvetica-BoldOblique" size:14]); |
| EXPECT_EQ(14, bold_italic_font.GetFontSize()); |
| EXPECT_EQ("Helvetica", bold_italic_font.GetFontName()); |
| EXPECT_EQ(Font::ITALIC, bold_italic_font.GetStyle()); |
| EXPECT_EQ(Font::Weight::BOLD, bold_italic_font.GetWeight()); |
| } |
| |
| // Specific test for the mapping from the NSFont weight API to gfx::Font::Weight |
| // values. |
| TEST(PlatformFontMacTest, FontWeightAPIConsistency) { |
| // Vanilla Helvetica only has bold and normal, so use a system font. |
| NSFont* ns_font = [NSFont systemFontOfSize:13]; |
| NSFontManager* manager = [NSFontManager sharedFontManager]; |
| |
| // -[NSFontManager convertWeight:ofFont] supposedly steps the font up and down |
| // in weight values according to a table at |
| // https://developer.apple.com/reference/appkit/nsfontmanager/1462321-convertweight |
| // Apple Terminology | ISO Equivalent |
| // 1. ultralight | none |
| // 2. thin | W1. ultralight |
| // 3. light, extralight | W2. extralight |
| // 4. book | W3. light |
| // 5. regular, plain, display, roman | W4. semilight |
| // 6. medium | W5. medium |
| // 7. demi, demibold | none |
| // 8. semi, semibold | W6. semibold |
| // 9. bold | W7. bold |
| // 10. extra, extrabold | W8. extrabold |
| // 11. heavy, heavyface | none |
| // 12. black, super | W9. ultrabold |
| // 13. ultra, ultrablack, fat | none |
| // 14. extrablack, obese, nord | none |
| EXPECT_EQ(Font::Weight::NORMAL, Font(ns_font).GetWeight()); // Row 5. |
| |
| // Ensure the Bold "symbolic" trait from the NSFont traits API maps correctly |
| // to the weight (non-symbolic) trait from the CTFont API. |
| NSFont* bold_ns_font = |
| [manager convertFont:ns_font toHaveTrait:NSFontBoldTrait]; |
| Font bold_font(bold_ns_font); |
| EXPECT_EQ(Font::Weight::BOLD, bold_font.GetWeight()); |
| |
| // No thin fonts on the lower rows of the table for San Francisco or earlier |
| // system fonts. |
| BOOL down = NO; |
| ns_font = [NSFont systemFontOfSize:13]; |
| for (int row = 4; row > 0; --row) { |
| SCOPED_TRACE(testing::Message() << "Row: " << row); |
| ns_font = [manager convertWeight:down ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::NORMAL, Font(ns_font).GetWeight()); |
| } |
| |
| BOOL up = YES; |
| // That is... unless we first go up by one and then down. A LIGHT and a THIN |
| // font reveal themselves somehow. Only tested on 10.12. |
| if (base::mac::IsAtLeastOS10_12()) { |
| ns_font = [NSFont systemFontOfSize:13]; |
| ns_font = [manager convertWeight:up ofFont:ns_font]; |
| ns_font = [manager convertWeight:down ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::LIGHT, Font(ns_font).GetWeight()); |
| ns_font = [manager convertWeight:down ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::THIN, Font(ns_font).GetWeight()); |
| } |
| |
| ns_font = [NSFont systemFontOfSize:13]; |
| |
| if (base::mac::IsOS10_11()) { |
| // On 10.11 the API jumps to BOLD, but has heavier weights as well. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::BOLD, Font(ns_font).GetWeight()); |
| ns_font = [manager convertWeight:up ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::EXTRA_BOLD, Font(ns_font).GetWeight()); |
| ns_font = [manager convertWeight:up ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); |
| return; |
| } |
| |
| // Each typeface maps weight notches differently, and the weight is actually a |
| // floating point value that may not map directly to a gfx::Font::Weight. For |
| // example San Francisco on macOS 10.12 goes up from 0 in the sequence: |
| // [0.23, 0.23, 0.3, 0.4, 0.56, 0.62, 0.62, ...] and has no "thin" weights. |
| // But also iterating over weights does weird stuff sometimes - occasionally |
| // the font goes italic, but going up another step goes back to non-italic, |
| // at a heavier weight. |
| |
| // NSCTFontUIUsageAttribute = CTFontMediumUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.23. |
| EXPECT_EQ(Font::Weight::MEDIUM, Font(ns_font).GetWeight()); // Row 6. |
| |
| // Goes italic: NSCTFontUIUsageAttribute = CTFontMediumItalicUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.23. |
| EXPECT_EQ(Font::Weight::MEDIUM, Font(ns_font).GetWeight()); // Row 7. |
| |
| // NSCTFontUIUsageAttribute = CTFontDemiUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.3. |
| if (base::mac::IsOS10_10()) { |
| // 10.10 is Helvetica Neue. It only has NORMAL, MEDIUM, BOLD and BLACK. |
| EXPECT_EQ(Font::Weight::BOLD, Font(ns_font).GetWeight()); // Row 8. |
| } else { |
| EXPECT_EQ(Font::Weight::SEMIBOLD, Font(ns_font).GetWeight()); // Row 8. |
| } |
| |
| // NSCTFontUIUsageAttribute = CTFontEmphasizedUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.4 on 10.11+. |
| |
| if (base::mac::IsOS10_10()) { |
| // Remaining rows are all BLACK on 10.10. |
| for (int row = 9; row <= 14; ++row) { |
| SCOPED_TRACE(testing::Message() << "Row: " << row); |
| ns_font = [manager convertWeight:up ofFont:ns_font]; |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); |
| } |
| return; |
| } |
| EXPECT_EQ(Font::Weight::BOLD, Font(ns_font).GetWeight()); // Row 9. |
| |
| // NSCTFontUIUsageAttribute = CTFontHeavyUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.56. |
| EXPECT_EQ(Font::Weight::EXTRA_BOLD, Font(ns_font).GetWeight()); // Row 10. |
| |
| // NSCTFontUIUsageAttribute = CTFontBlackUsage. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.62. |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); // Row 11. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.62. |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); // Row 12. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.62. |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); // Row 13. |
| ns_font = [manager convertWeight:up ofFont:ns_font]; // 0.62. |
| EXPECT_EQ(Font::Weight::BLACK, Font(ns_font).GetWeight()); // Row 14. |
| } |
| |
| // Test font derivation for fine-grained font weights. |
| TEST(PlatformFontMacTest, DerivedFineGrainedFonts) { |
| // Only test where San Francisco is available. |
| if (base::mac::IsAtMostOS10_10()) |
| return; |
| |
| using Weight = Font::Weight; |
| Font base([NSFont systemFontOfSize:13]); |
| |
| // The resulting, actual font weight after deriving |weight| from |base|. |
| auto DerivedWeight = [&](Weight weight) { |
| Font derived(base.Derive(0, 0, weight)); |
| // PlatformFont should always pass the requested weight, not what the OS |
| // could provide. This just checks a constructor argument, so not very |
| // interesting. |
| EXPECT_EQ(weight, derived.GetWeight()); |
| |
| // Return the weight enum value that PlatformFontMac internally derives from |
| // the floating point weight given by the kCTFontWeightTrait of |font|. Do |
| // this by creating a new font based only off the NSFont in |derived|. |
| return Font(derived.GetNativeFont()).GetWeight(); |
| }; |
| |
| // Only use NORMAL or BOLD as a base font. Mac font APIs go whacky otherwise. |
| // See comments in PlatformFontMac::DeriveFont(). |
| for (Weight base_weight : {Weight::NORMAL, Weight::BOLD}) { |
| SCOPED_TRACE(testing::Message() |
| << "BaseWeight: " << static_cast<int>(base_weight)); |
| if (base_weight != Weight::NORMAL) { |
| base = base.Derive(0, 0, base_weight); |
| EXPECT_EQ(base_weight, base.GetWeight()); |
| } |
| |
| // Normal and heavy weights map correctly on 10.11 and 10.12. |
| EXPECT_EQ(Weight::NORMAL, DerivedWeight(Weight::NORMAL)); |
| EXPECT_EQ(Weight::BOLD, DerivedWeight(Weight::BOLD)); |
| EXPECT_EQ(Weight::EXTRA_BOLD, DerivedWeight(Weight::EXTRA_BOLD)); |
| EXPECT_EQ(Weight::BLACK, DerivedWeight(Weight::BLACK)); |
| |
| if (base::mac::IsAtMostOS10_11()) { |
| // The fine-grained font weights on 10.11 are incomplete. |
| EXPECT_EQ(Weight::NORMAL, DerivedWeight(Weight::EXTRA_LIGHT)); |
| EXPECT_EQ(Weight::NORMAL, DerivedWeight(Weight::THIN)); |
| EXPECT_EQ(Weight::NORMAL, DerivedWeight(Weight::LIGHT)); |
| EXPECT_EQ(Weight::BOLD, DerivedWeight(Weight::MEDIUM)); |
| EXPECT_EQ(Weight::BOLD, DerivedWeight(Weight::SEMIBOLD)); |
| continue; |
| } |
| |
| // San Francisco doesn't offer anything between THIN and LIGHT. |
| EXPECT_EQ(Weight::THIN, DerivedWeight(Weight::EXTRA_LIGHT)); |
| |
| // All the rest should map correctly. |
| EXPECT_EQ(Weight::THIN, DerivedWeight(Weight::THIN)); |
| EXPECT_EQ(Weight::LIGHT, DerivedWeight(Weight::LIGHT)); |
| EXPECT_EQ(Weight::MEDIUM, DerivedWeight(Weight::MEDIUM)); |
| EXPECT_EQ(Weight::SEMIBOLD, DerivedWeight(Weight::SEMIBOLD)); |
| } |
| } |
| |
| // Ensures that the Font's reported height is consistent with the native font's |
| // ascender and descender metrics. |
| TEST(PlatformFontMacTest, ValidateFontHeight) { |
| // Use the default ResourceBundle system font. E.g. Helvetica Neue in 10.10, |
| // Lucida Grande before that, and San Francisco after. |
| gfx::Font default_font; |
| gfx::Font::FontStyle styles[] = {gfx::Font::NORMAL, gfx::Font::ITALIC, |
| gfx::Font::UNDERLINE}; |
| |
| for (size_t i = 0; i < base::size(styles); ++i) { |
| SCOPED_TRACE(testing::Message() << "Font::FontStyle: " << styles[i]); |
| // Include the range of sizes used by ResourceBundle::FontStyle (-1 to +8). |
| for (int delta = -1; delta <= 8; ++delta) { |
| gfx::Font font = |
| default_font.Derive(delta, styles[i], gfx::Font::Weight::NORMAL); |
| SCOPED_TRACE(testing::Message() << "FontSize(): " << font.GetFontSize()); |
| NSFont* native_font = font.GetNativeFont(); |
| |
| // Font height (an integer) should be the sum of these. |
| CGFloat ascender = [native_font ascender]; |
| CGFloat descender = [native_font descender]; |
| CGFloat leading = [native_font leading]; |
| |
| // NSFont always gives a negative value for descender. Others positive. |
| EXPECT_GE(0, descender); |
| EXPECT_LE(0, ascender); |
| EXPECT_LE(0, leading); |
| |
| int sum = ceil(ascender - descender + leading); |
| |
| // Text layout is performed using an integral baseline offset derived from |
| // the ascender. The height needs to be enough to fit the full descender |
| // (plus baseline). So the height depends on the rounding of the ascender, |
| // and can be as much as 1 greater than the simple sum of floats. |
| EXPECT_LE(sum, font.GetHeight()); |
| EXPECT_GE(sum + 1, font.GetHeight()); |
| |
| // Recreate the rounding performed for GetBaseLine(). |
| EXPECT_EQ(ceil(ceil(ascender) - descender + leading), font.GetHeight()); |
| } |
| } |
| } |
| |
| // Test to ensure we cater for the AppKit quirk that can make the font italic |
| // when asking for a fine-grained weight. See http://crbug.com/742261. Note that |
| // Appkit's bug was detected on macOS 10.10 which uses Helvetica Neue as the |
| // system font. |
| TEST(PlatformFontMacTest, DerivedSemiboldFontIsNotItalic) { |
| gfx::Font base_font; |
| { |
| NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:base_font.GetNativeFont()]; |
| ASSERT_FALSE(traits & NSItalicFontMask); |
| } |
| |
| gfx::Font semibold_font( |
| base_font.Derive(0, gfx::Font::NORMAL, gfx::Font::Weight::SEMIBOLD)); |
| NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| traitsOfFont:semibold_font.GetNativeFont()]; |
| EXPECT_FALSE(traits & NSItalicFontMask); |
| } |
| |
| } // namespace gfx |