Commit bc62dfea authored by tapted's avatar tapted Committed by Commit bot

Support finer grained font weights on Mac.

We want to explore a MEDIUM weight for button text under Harmony. This
is consistent with MD and helps compensate for buttons not using solid
black for the font color.

The San Francisco font in 10.11 and 10.12 has good support for finer
grained weights and is the font Chrome UI is mostly concerned with, so
focus testing on that but survey expectations for all supported OS
versions.

BUG=691891

Review-Url: https://codereview.chromium.org/2869803005
Cr-Commit-Position: refs/heads/master@{#473457}
parent 3cb12d3b
......@@ -8,7 +8,9 @@
#include <Cocoa/Cocoa.h>
#include "base/mac/scoped_nsobject.h"
#import "base/mac/foundation_util.h"
#include "base/mac/scoped_cftyperef.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/gfx/canvas.h"
......@@ -19,6 +21,60 @@ namespace gfx {
namespace {
// How to get from NORMAL weight to a fine-grained font weight using calls to
// -[NSFontManager convertWeight:(BOOL)upFlag ofFont:(NSFont)].
struct WeightSolver {
int steps_up; // Times to call with upFlag:YES.
int steps_down; // Times to call with upFlag:NO.
// Either NORMAL or BOLD: whether to set the NSBoldFontMask symbolic trait.
Font::Weight nearest;
};
// Solve changes to the font weight according to the following table, from
// https://developer.apple.com/reference/appkit/nsfontmanager/1462321-convertweight
// 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
WeightSolver WeightChangeFromNormal(Font::Weight desired) {
using Weight = Font::Weight;
switch (desired) {
case Weight::THIN:
// It's weird, but to get LIGHT and THIN fonts, first go up a step.
// Without this, the font stays stuck at NORMAL. See
// PlatformFontMacTest, FontWeightAPIConsistency.
return {1, 3, Weight::NORMAL};
case Weight::EXTRA_LIGHT:
return {1, 2, Weight::NORMAL};
case Weight::LIGHT:
return {1, 1, Weight::NORMAL};
case Weight::NORMAL:
return {0, 0, Weight::NORMAL};
case Weight::MEDIUM:
return {1, 0, Weight::NORMAL};
case Weight::SEMIBOLD:
return {0, 1, Weight::BOLD};
case Weight::BOLD:
return {0, 0, Weight::BOLD};
case Weight::EXTRA_BOLD:
return {1, 0, Weight::BOLD};
case Weight::BLACK:
return {3, 0, Weight::BOLD}; // Skip row 12.
case Weight::INVALID:
return {0, 0, Weight::NORMAL};
}
}
// Returns the font style for |font|. Disregards Font::UNDERLINE, since NSFont
// does not support it as a trait.
int GetFontStyleFromNSFont(NSFont* font) {
......@@ -31,8 +87,56 @@ int GetFontStyleFromNSFont(NSFont* font) {
// Returns the Font weight for |font|.
Font::Weight GetFontWeightFromNSFont(NSFont* font) {
NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits];
return (traits & NSFontBoldTrait) ? Font::Weight::BOLD : Font::Weight::NORMAL;
if (!font)
return Font::Weight::INVALID;
// Map CoreText weights in a manner similar to ct_weight_to_fontstyle() from
// SkFontHost_mac.cpp, but adjust for MEDIUM so that the San Francisco's
// custom MEDIUM weight can be picked out. San Francisco has weights:
// [0.23, 0.23, 0.3, 0.4, 0.56, 0.62, 0.62, ...] (no thin weights).
// See PlatformFontMacTest.FontWeightAPIConsistency for details.
// Note that the table Skia uses is also determined by experiment.
constexpr struct {
CGFloat ct_weight;
Font::Weight gfx_weight;
} weight_map[] = {
// Missing: Apple "ultralight".
{-0.70, Font::Weight::THIN},
{-0.50, Font::Weight::EXTRA_LIGHT},
{-0.23, Font::Weight::LIGHT},
{0.00, Font::Weight::NORMAL},
{0.23, Font::Weight::MEDIUM}, // Note: adjusted from 0.20 vs Skia.
// Missing: Apple "demibold".
{0.30, Font::Weight::SEMIBOLD},
{0.40, Font::Weight::BOLD},
{0.60, Font::Weight::EXTRA_BOLD},
// Missing: Apple "heavyface".
// Values will be capped to BLACK (this entry is here for consistency).
{0.80, Font::Weight::BLACK},
// Missing: Apple "ultrablack".
// Missing: Apple "extrablack".
};
base::ScopedCFTypeRef<CFDictionaryRef> traits(
CTFontCopyTraits(base::mac::NSToCFCast(font)));
DCHECK(traits);
CFNumberRef cf_weight = base::mac::GetValueFromDictionary<CFNumberRef>(
traits, kCTFontWeightTrait);
// A missing weight attribute just means 0 -> NORMAL.
if (!cf_weight)
return Font::Weight::NORMAL;
// Documentation is vague about what sized floating point type should be used.
// However, numeric_limits::epsilon() for 64-bit types is too small to match
// the above table, so use 32-bit float.
float weight_value;
Boolean success =
CFNumberGetValue(cf_weight, kCFNumberFloatType, &weight_value);
DCHECK(success);
for (const auto& item : weight_map) {
if (weight_value - item.ct_weight <= std::numeric_limits<float>::epsilon())
return item.gfx_weight;
}
return Font::Weight::BLACK;
}
// Returns an autoreleased NSFont created with the passed-in specifications.
......@@ -100,24 +204,43 @@ Font PlatformFontMac::DeriveFont(int size_delta,
Font::Weight weight) const {
// For some reason, creating fonts using the NSFontDescriptor API's seem to be
// unreliable. Hence use the NSFontManager.
NSFont* derived_font = native_font_;
NSFont* derived = native_font_;
NSFontManager* font_manager = [NSFontManager sharedFontManager];
NSFontTraitMask bold_trait_mask =
weight >= Font::Weight::BOLD ? NSBoldFontMask : NSUnboldFontMask;
derived_font =
[font_manager convertFont:derived_font toHaveTrait:bold_trait_mask];
if (weight != font_weight_) {
// Find a font without any bold traits. Ideally, all bold traits are
// removed here, but non-symbolic traits are read-only properties of a
// particular set of glyphs. And attempting to "reset" the attribute with a
// new font descriptor will lose internal properties that Mac decorates its
// UI fonts with. E.g., solving the plans below from NORMAL result in a
// CTFontDescriptor attribute entry of NSCTFontUIUsageAttribute in
// CTFont{Regular,Medium,Demi,Emphasized,Heavy,Black}Usage. Attempting to
// "solve" weights starting at other than NORMAL has unpredictable results.
if (font_weight_ != Font::Weight::NORMAL)
derived = [font_manager convertFont:derived toHaveTrait:NSUnboldFontMask];
DLOG_IF(WARNING, GetFontWeightFromNSFont(derived) != Font::Weight::NORMAL)
<< "Deriving from a font with an internal unmodifiable weight.";
WeightSolver plan = WeightChangeFromNormal(weight);
if (plan.nearest == Font::Weight::BOLD)
derived = [font_manager convertFont:derived toHaveTrait:NSBoldFontMask];
for (int i = 0; i < plan.steps_up; ++i)
derived = [font_manager convertWeight:YES ofFont:derived];
for (int i = 0; i < plan.steps_down; ++i)
derived = [font_manager convertWeight:NO ofFont:derived];
}
if (style != font_style_) {
NSFontTraitMask italic_trait_mask =
(style & Font::ITALIC) ? NSItalicFontMask : NSUnitalicFontMask;
derived_font =
[font_manager convertFont:derived_font toHaveTrait:italic_trait_mask];
derived = [font_manager convertFont:derived toHaveTrait:italic_trait_mask];
}
derived_font =
[font_manager convertFont:derived_font toSize:font_size_ + size_delta];
if (size_delta != 0)
derived = [font_manager convertFont:derived toSize:font_size_ + size_delta];
return Font(new PlatformFontMac(derived_font, font_name_,
font_size_ + size_delta, style, weight));
return Font(new PlatformFontMac(derived, font_name_, font_size_ + size_delta,
style, weight));
}
int PlatformFontMac::GetHeight() {
......
This diff is collapsed.
......@@ -9,9 +9,9 @@
namespace views {
namespace examples {
ExampleComboboxModel::ExampleComboboxModel(const char** strings, int count)
: strings_(strings), count_(count) {
}
ExampleComboboxModel::ExampleComboboxModel(const char* const* strings,
int count)
: strings_(strings), count_(count) {}
ExampleComboboxModel::~ExampleComboboxModel() {
}
......
......@@ -13,7 +13,7 @@ namespace examples {
class ExampleComboboxModel : public ui::ComboboxModel {
public:
ExampleComboboxModel(const char** strings, int count);
ExampleComboboxModel(const char* const* strings, int count);
~ExampleComboboxModel() override;
// ui::ComboboxModel:
......@@ -21,8 +21,8 @@ class ExampleComboboxModel : public ui::ComboboxModel {
base::string16 GetItemAt(int index) override;
private:
const char** strings_;
int count_;
const char* const* const strings_;
const int count_;
DISALLOW_COPY_AND_ASSIGN(ExampleComboboxModel);
};
......
......@@ -50,6 +50,17 @@ const char* kTextExamples[] = { "Short", "Long", "Ampersands", "RTL Hebrew", };
const char* kElideBehaviors[] = { "Elide", "No Elide", "Fade", };
const char* kPrefixOptions[] = { "Default", "Show", "Hide", };
const char* kHorizontalAligments[] = { "Default", "Left", "Center", "Right", };
constexpr const char* kWeightLabels[] = {
"Thin", "Extra Light", "Light", "Normal", "Medium",
"Semibold", "Bold", "Extra Bold", "Black",
};
constexpr gfx::Font::Weight kFontWeights[]{
gfx::Font::Weight::THIN, gfx::Font::Weight::EXTRA_LIGHT,
gfx::Font::Weight::LIGHT, gfx::Font::Weight::NORMAL,
gfx::Font::Weight::MEDIUM, gfx::Font::Weight::SEMIBOLD,
gfx::Font::Weight::BOLD, gfx::Font::Weight::EXTRA_BOLD,
gfx::Font::Weight::BLACK,
};
// Toggles bit |flag| on |flags| based on state of |checkbox|.
void SetFlagFromCheckbox(Checkbox* checkbox, int* flags, int flag) {
......@@ -87,17 +98,24 @@ class TextExample::TextExampleView : public View {
void set_elide(gfx::ElideBehavior elide) { elide_ = elide; }
int GetStyle() const { return font_list_.GetFontStyle(); }
void SetStyle(int style) { font_list_ = font_list_.DeriveWithStyle(style); }
void SetStyle(int style) {
base_font_ = base_font_.DeriveWithStyle(style);
font_list_ = font_list_.DeriveWithStyle(style);
}
gfx::Font::Weight GetWeight() const { return font_list_.GetFontWeight(); }
void SetWeight(gfx::Font::Weight weight) {
font_list_ = font_list_.DeriveWithWeight(weight);
font_list_ = base_font_.DeriveWithWeight(weight);
}
private:
// The font used for drawing the text.
gfx::FontList font_list_;
// The font without any bold attributes. Mac font APIs struggle to derive UI
// fonts from a base font that isn't NORMAL or BOLD.
gfx::FontList base_font_;
// The text to draw.
base::string16 text_;
......@@ -126,7 +144,7 @@ Checkbox* TextExample::AddCheckbox(GridLayout* layout, const char* name) {
Combobox* TextExample::AddCombobox(GridLayout* layout,
const char* name,
const char** strings,
const char* const* strings,
int count) {
layout->StartRow(0, 0);
layout->AddView(new Label(base::ASCIIToUTF16(name)));
......@@ -163,11 +181,13 @@ void TextExample::CreateExampleView(View* container) {
arraysize(kPrefixOptions));
text_cb_ = AddCombobox(layout, "Example Text", kTextExamples,
arraysize(kTextExamples));
weight_cb_ = AddCombobox(layout, "Font Weight", kWeightLabels,
arraysize(kWeightLabels));
weight_cb_->SelectValue(base::ASCIIToUTF16("Normal"));
layout->StartRow(0, 0);
multiline_checkbox_ = AddCheckbox(layout, "Multiline");
break_checkbox_ = AddCheckbox(layout, "Character Break");
bold_checkbox_ = AddCheckbox(layout, "Bold");
italic_checkbox_ = AddCheckbox(layout, "Italic");
underline_checkbox_ = AddCheckbox(layout, "Underline");
......@@ -191,8 +211,6 @@ void TextExample::ButtonPressed(Button* button, const ui::Event& event) {
SetFlagFromCheckbox(underline_checkbox_, &style, gfx::Font::UNDERLINE);
text_view_->set_flags(flags);
text_view_->SetStyle(style);
text_view_->SetWeight(bold_checkbox_->checked() ? gfx::Font::Weight::BOLD
: gfx::Font::Weight::NORMAL);
text_view_->SchedulePaint();
}
......@@ -254,6 +272,8 @@ void TextExample::OnPerformAction(Combobox* combobox) {
flags |= gfx::Canvas::HIDE_PREFIX;
break;
}
} else if (combobox == weight_cb_) {
text_view_->SetWeight(kFontWeights[combobox->selected_index()]);
}
text_view_->set_flags(flags);
text_view_->SchedulePaint();
......
......@@ -38,7 +38,7 @@ class VIEWS_EXAMPLES_EXPORT TextExample : public ExampleBase,
// Creates and adds a combobox to the layout.
Combobox* AddCombobox(GridLayout* layout,
const char* name,
const char** strings,
const char* const* strings,
int count);
// ButtonListener:
......@@ -63,15 +63,15 @@ class VIEWS_EXAMPLES_EXPORT TextExample : public ExampleBase,
// Combo box to choose one of the sample texts.
Combobox* text_cb_;
// Combo box to choose a font weight.
Combobox* weight_cb_;
// Check box to enable/disable multiline text drawing.
Checkbox* multiline_checkbox_;
// Check box to enable/disable character break behavior.
Checkbox* break_checkbox_;
// Check box to enable/disable bold style.
Checkbox* bold_checkbox_;
// Check box to enable/disable italic style.
Checkbox* italic_checkbox_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment