[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 5 | #include "ui/gfx/platform_font_mac.h" |
| 6 | |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 7 | #include <Cocoa/Cocoa.h> |
avi | c89eb8d4 | 2015-12-23 08:08:18 | [diff] [blame] | 8 | #include <stddef.h> |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 9 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 10 | #import "base/mac/foundation_util.h" |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 11 | #include "base/mac/scoped_cftyperef.h" |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 12 | #include "testing/gtest/include/gtest/gtest.h" |
tapted | 574f09c | 2015-05-19 13:08:08 | [diff] [blame] | 13 | #include "ui/gfx/font.h" |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 14 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 15 | namespace gfx { |
| 16 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 17 | using Weight = Font::Weight; |
Avi Drissman | c2e39ed | 2020-08-07 19:25:30 | [diff] [blame] | 18 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 19 | TEST(PlatformFontMacTest, DeriveFont) { |
| 20 | // |weight_tri| is either -1, 0, or 1 meaning "light", "normal", or "bold". |
| 21 | auto CheckExpected = [](const Font& font, int weight_tri, bool isItalic) { |
| 22 | base::ScopedCFTypeRef<CFDictionaryRef> traits( |
| 23 | CTFontCopyTraits(base::mac::NSToCFCast(font.GetNativeFont()))); |
| 24 | DCHECK(traits); |
| 25 | |
| 26 | CFNumberRef cf_slant = base::mac::GetValueFromDictionary<CFNumberRef>( |
| 27 | traits, kCTFontSlantTrait); |
| 28 | CGFloat slant; |
| 29 | CFNumberGetValue(cf_slant, kCFNumberCGFloatType, &slant); |
| 30 | if (isItalic) |
| 31 | EXPECT_GT(slant, 0); |
| 32 | else |
| 33 | EXPECT_EQ(slant, 0); |
| 34 | |
| 35 | CFNumberRef cf_weight = base::mac::GetValueFromDictionary<CFNumberRef>( |
| 36 | traits, kCTFontWeightTrait); |
| 37 | CGFloat weight; |
| 38 | CFNumberGetValue(cf_weight, kCFNumberCGFloatType, &weight); |
| 39 | if (weight_tri < 0) |
| 40 | EXPECT_LT(weight, 0); |
| 41 | else if (weight_tri == 0) |
| 42 | EXPECT_EQ(weight, 0); |
| 43 | else |
| 44 | EXPECT_GT(weight, 0); |
| 45 | }; |
| 46 | |
| 47 | // Use a base font that support all traits. |
| 48 | Font base_font("Helvetica", 13); |
| 49 | { |
| 50 | SCOPED_TRACE("plain font"); |
| 51 | CheckExpected(base_font, 0, false); |
| 52 | } |
Dominique Fauteux-Chapleau | 2f7c32d | 2020-08-07 20:03:12 | [diff] [blame] | 53 | |
| 54 | // Italic |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 55 | Font italic_font(base_font.Derive(0, Font::ITALIC, Weight::NORMAL)); |
| 56 | { |
| 57 | SCOPED_TRACE("italic font"); |
| 58 | CheckExpected(italic_font, 0, true); |
| 59 | } |
| 60 | |
| 61 | // Bold |
| 62 | Font bold_font(base_font.Derive(0, Font::NORMAL, Weight::BOLD)); |
| 63 | { |
| 64 | SCOPED_TRACE("bold font"); |
| 65 | CheckExpected(bold_font, 1, false); |
| 66 | } |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 67 | |
| 68 | // Bold italic |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 69 | Font bold_italic_font(base_font.Derive(0, Font::ITALIC, Weight::BOLD)); |
| 70 | { |
| 71 | SCOPED_TRACE("bold italic font"); |
| 72 | CheckExpected(bold_italic_font, 1, true); |
| 73 | } |
| 74 | |
| 75 | // Non-existent thin will return the closest weight, light |
| 76 | Font thin_font(base_font.Derive(0, Font::NORMAL, Weight::THIN)); |
| 77 | { |
| 78 | SCOPED_TRACE("thin font"); |
| 79 | CheckExpected(thin_font, -1, false); |
| 80 | } |
| 81 | |
| 82 | // Non-existent black will return the closest weight, bold |
| 83 | Font black_font(base_font.Derive(0, Font::NORMAL, Weight::BLACK)); |
| 84 | { |
| 85 | SCOPED_TRACE("black font"); |
| 86 | CheckExpected(black_font, 1, false); |
| 87 | } |
[email protected] | 879ebb79 | 2012-10-03 05:50:56 | [diff] [blame] | 88 | } |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 89 | |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 90 | TEST(PlatformFontMacTest, DeriveFontUnderline) { |
| 91 | // Create a default font. |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 92 | Font base_font; |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 93 | |
| 94 | // Make the font underlined. |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 95 | Font derived_font(base_font.Derive(0, base_font.GetStyle() | Font::UNDERLINE, |
| 96 | base_font.GetWeight())); |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 97 | |
| 98 | // Validate the derived font properties against its native font instance. |
| 99 | NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| 100 | traitsOfFont:derived_font.GetNativeFont()]; |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 101 | Weight actual_weight = |
| 102 | (traits & NSFontBoldTrait) ? Weight::BOLD : Weight::NORMAL; |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 103 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 104 | int actual_style = Font::UNDERLINE; |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 105 | if (traits & NSFontItalicTrait) |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 106 | actual_style |= Font::ITALIC; |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 107 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 108 | EXPECT_TRUE(derived_font.GetStyle() & Font::UNDERLINE); |
karandeepb | f7e5e828 | 2016-08-09 02:18:11 | [diff] [blame] | 109 | EXPECT_EQ(derived_font.GetStyle(), actual_style); |
| 110 | EXPECT_EQ(derived_font.GetWeight(), actual_weight); |
| 111 | } |
| 112 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 113 | // Tests internal methods for extracting Font properties from the |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 114 | // underlying CTFont representation. |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 115 | TEST(PlatformFontMacTest, ConstructFromNativeFont) { |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 116 | Font light_font([NSFont fontWithName:@"Helvetica-Light" size:12]); |
| 117 | EXPECT_EQ(12, light_font.GetFontSize()); |
| 118 | EXPECT_EQ("Helvetica", light_font.GetFontName()); |
| 119 | EXPECT_EQ(Font::NORMAL, light_font.GetStyle()); |
| 120 | EXPECT_EQ(Weight::LIGHT, light_font.GetWeight()); |
| 121 | |
| 122 | Font light_italic_font([NSFont fontWithName:@"Helvetica-LightOblique" |
| 123 | size:14]); |
| 124 | EXPECT_EQ(14, light_italic_font.GetFontSize()); |
| 125 | EXPECT_EQ("Helvetica", light_italic_font.GetFontName()); |
| 126 | EXPECT_EQ(Font::ITALIC, light_italic_font.GetStyle()); |
| 127 | EXPECT_EQ(Weight::LIGHT, light_italic_font.GetWeight()); |
| 128 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 129 | Font normal_font([NSFont fontWithName:@"Helvetica" size:12]); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 130 | EXPECT_EQ(12, normal_font.GetFontSize()); |
| 131 | EXPECT_EQ("Helvetica", normal_font.GetFontName()); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 132 | EXPECT_EQ(Font::NORMAL, normal_font.GetStyle()); |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 133 | EXPECT_EQ(Weight::NORMAL, normal_font.GetWeight()); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 134 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 135 | Font italic_font([NSFont fontWithName:@"Helvetica-Oblique" size:14]); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 136 | EXPECT_EQ(14, italic_font.GetFontSize()); |
| 137 | EXPECT_EQ("Helvetica", italic_font.GetFontName()); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 138 | EXPECT_EQ(Font::ITALIC, italic_font.GetStyle()); |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 139 | EXPECT_EQ(Weight::NORMAL, italic_font.GetWeight()); |
| 140 | |
| 141 | Font bold_font([NSFont fontWithName:@"Helvetica-Bold" size:12]); |
| 142 | EXPECT_EQ(12, bold_font.GetFontSize()); |
| 143 | EXPECT_EQ("Helvetica", bold_font.GetFontName()); |
| 144 | EXPECT_EQ(Font::NORMAL, bold_font.GetStyle()); |
| 145 | EXPECT_EQ(Weight::BOLD, bold_font.GetWeight()); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 146 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 147 | Font bold_italic_font([NSFont fontWithName:@"Helvetica-BoldOblique" size:14]); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 148 | EXPECT_EQ(14, bold_italic_font.GetFontSize()); |
| 149 | EXPECT_EQ("Helvetica", bold_italic_font.GetFontName()); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 150 | EXPECT_EQ(Font::ITALIC, bold_italic_font.GetStyle()); |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 151 | EXPECT_EQ(Weight::BOLD, bold_italic_font.GetWeight()); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | // Test font derivation for fine-grained font weights. |
| 155 | TEST(PlatformFontMacTest, DerivedFineGrainedFonts) { |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 156 | // The resulting, actual font weight after deriving |weight| from |base|. |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 157 | auto DerivedIntWeight = [](Weight weight) { |
| 158 | Font base; // The default system font. |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 159 | Font derived(base.Derive(0, 0, weight)); |
| 160 | // PlatformFont should always pass the requested weight, not what the OS |
| 161 | // could provide. This just checks a constructor argument, so not very |
| 162 | // interesting. |
Avi Drissman | 7dd9b79a | 2020-08-04 20:45:52 | [diff] [blame] | 163 | EXPECT_EQ(static_cast<int>(weight), static_cast<int>(derived.GetWeight())); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 164 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 165 | return static_cast<int>(PlatformFontMac::GetFontWeightFromNSFontForTesting( |
| 166 | derived.GetNativeFont())); |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 167 | }; |
| 168 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 169 | EXPECT_EQ(static_cast<int>(Weight::THIN), DerivedIntWeight(Weight::THIN)); |
| 170 | EXPECT_EQ(static_cast<int>(Weight::EXTRA_LIGHT), |
| 171 | DerivedIntWeight(Weight::EXTRA_LIGHT)); |
| 172 | EXPECT_EQ(static_cast<int>(Weight::LIGHT), DerivedIntWeight(Weight::LIGHT)); |
| 173 | EXPECT_EQ(static_cast<int>(Weight::NORMAL), DerivedIntWeight(Weight::NORMAL)); |
| 174 | EXPECT_EQ(static_cast<int>(Weight::MEDIUM), DerivedIntWeight(Weight::MEDIUM)); |
Robert Sesek | 3ef5db7 | 2020-12-03 20:56:07 | [diff] [blame] | 175 | EXPECT_EQ(static_cast<int>(Weight::SEMIBOLD), |
| 176 | DerivedIntWeight(Weight::SEMIBOLD)); |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 177 | EXPECT_EQ(static_cast<int>(Weight::BOLD), DerivedIntWeight(Weight::BOLD)); |
| 178 | EXPECT_EQ(static_cast<int>(Weight::EXTRA_BOLD), |
| 179 | DerivedIntWeight(Weight::EXTRA_BOLD)); |
| 180 | EXPECT_EQ(static_cast<int>(Weight::BLACK), DerivedIntWeight(Weight::BLACK)); |
[email protected] | 358cef38 | 2013-05-02 22:07:00 | [diff] [blame] | 181 | } |
tapted | ef6c19f96 | 2015-07-28 01:30:29 | [diff] [blame] | 182 | |
| 183 | // Ensures that the Font's reported height is consistent with the native font's |
| 184 | // ascender and descender metrics. |
| 185 | TEST(PlatformFontMacTest, ValidateFontHeight) { |
Avi Drissman | c45f85f | 2022-05-10 22:07:02 | [diff] [blame] | 186 | // Use the default ResourceBundle system font (i.e. San Francisco). |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 187 | Font default_font; |
| 188 | Font::FontStyle styles[] = {Font::NORMAL, Font::ITALIC, Font::UNDERLINE}; |
tapted | ef6c19f96 | 2015-07-28 01:30:29 | [diff] [blame] | 189 | |
Avi Drissman | c45f85f | 2022-05-10 22:07:02 | [diff] [blame] | 190 | for (auto& style : styles) { |
| 191 | SCOPED_TRACE(testing::Message() << "Font::FontStyle: " << style); |
tapted | ef6c19f96 | 2015-07-28 01:30:29 | [diff] [blame] | 192 | // Include the range of sizes used by ResourceBundle::FontStyle (-1 to +8). |
| 193 | for (int delta = -1; delta <= 8; ++delta) { |
Avi Drissman | c45f85f | 2022-05-10 22:07:02 | [diff] [blame] | 194 | Font font = default_font.Derive(delta, style, Weight::NORMAL); |
tapted | ef6c19f96 | 2015-07-28 01:30:29 | [diff] [blame] | 195 | SCOPED_TRACE(testing::Message() << "FontSize(): " << font.GetFontSize()); |
| 196 | NSFont* native_font = font.GetNativeFont(); |
| 197 | |
| 198 | // Font height (an integer) should be the sum of these. |
| 199 | CGFloat ascender = [native_font ascender]; |
| 200 | CGFloat descender = [native_font descender]; |
| 201 | CGFloat leading = [native_font leading]; |
| 202 | |
| 203 | // NSFont always gives a negative value for descender. Others positive. |
| 204 | EXPECT_GE(0, descender); |
| 205 | EXPECT_LE(0, ascender); |
| 206 | EXPECT_LE(0, leading); |
| 207 | |
| 208 | int sum = ceil(ascender - descender + leading); |
| 209 | |
| 210 | // Text layout is performed using an integral baseline offset derived from |
| 211 | // the ascender. The height needs to be enough to fit the full descender |
| 212 | // (plus baseline). So the height depends on the rounding of the ascender, |
| 213 | // and can be as much as 1 greater than the simple sum of floats. |
| 214 | EXPECT_LE(sum, font.GetHeight()); |
| 215 | EXPECT_GE(sum + 1, font.GetHeight()); |
| 216 | |
| 217 | // Recreate the rounding performed for GetBaseLine(). |
| 218 | EXPECT_EQ(ceil(ceil(ascender) - descender + leading), font.GetHeight()); |
| 219 | } |
| 220 | } |
| 221 | } |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 222 | |
Mariia Leliuk | 515f516 | 2017-07-18 08:09:01 | [diff] [blame] | 223 | // Test to ensure we cater for the AppKit quirk that can make the font italic |
| 224 | // when asking for a fine-grained weight. See http://crbug.com/742261. Note that |
| 225 | // Appkit's bug was detected on macOS 10.10 which uses Helvetica Neue as the |
| 226 | // system font. |
| 227 | TEST(PlatformFontMacTest, DerivedSemiboldFontIsNotItalic) { |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 228 | Font base_font; |
Mariia Leliuk | 515f516 | 2017-07-18 08:09:01 | [diff] [blame] | 229 | { |
| 230 | NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| 231 | traitsOfFont:base_font.GetNativeFont()]; |
| 232 | ASSERT_FALSE(traits & NSItalicFontMask); |
| 233 | } |
| 234 | |
Avi Drissman | b0e1920 | 2020-08-10 18:41:09 | [diff] [blame] | 235 | Font semibold_font(base_font.Derive(0, Font::NORMAL, Weight::SEMIBOLD)); |
Mariia Leliuk | 515f516 | 2017-07-18 08:09:01 | [diff] [blame] | 236 | NSFontTraitMask traits = [[NSFontManager sharedFontManager] |
| 237 | traitsOfFont:semibold_font.GetNativeFont()]; |
| 238 | EXPECT_FALSE(traits & NSItalicFontMask); |
| 239 | } |
| 240 | |
tapted | 124eb0b5 | 2017-05-22 01:54:14 | [diff] [blame] | 241 | } // namespace gfx |