| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/style/typography.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <optional> |
| |
| #include "base/check_op.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/sequence_checker.h" |
| #include "base/thread_annotations.h" |
| #include "base/types/cxx23_to_underlying.h" |
| #include "ui/gfx/font.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/views/controls/label.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| enum class FontFamily { kGoogleSans, kRoboto }; |
| |
| struct FontInfo { |
| FontFamily family; |
| gfx::Font::FontStyle style; |
| int size; |
| gfx::Font::Weight weight; |
| int line_height; |
| }; |
| |
| std::vector<std::string> FontNames(FontFamily family) { |
| // Fallback to the appropriate Noto Sans happens through the linux font |
| // fallback logic. |
| switch (family) { |
| case FontFamily::kGoogleSans: |
| return {"Google Sans", "Roboto"}; |
| case FontFamily::kRoboto: |
| return {"Roboto"}; |
| } |
| } |
| |
| // Maps legacy typography tokens to their cros.sys equivalents. |
| constexpr auto kTokenEquivalents = |
| base::MakeFixedFlatMap<TypographyToken, TypographyToken>({ |
| {TypographyToken::kLegacyDisplay1, TypographyToken::kCrosDisplay1}, |
| {TypographyToken::kLegacyDisplay2, TypographyToken::kCrosDisplay2}, |
| {TypographyToken::kLegacyDisplay3, TypographyToken::kCrosDisplay3}, |
| {TypographyToken::kLegacyDisplay4, TypographyToken::kCrosDisplay4}, |
| {TypographyToken::kLegacyDisplay5, TypographyToken::kCrosDisplay5}, |
| {TypographyToken::kLegacyDisplay6, TypographyToken::kCrosDisplay6}, |
| {TypographyToken::kLegacyDisplay7, TypographyToken::kCrosDisplay7}, |
| |
| {TypographyToken::kLegacyTitle1, TypographyToken::kCrosTitle1}, |
| // Since `kCrosTitle1` is Google Sans Text, its appropriate for both. |
| {TypographyToken::kLegacyTitle2, TypographyToken::kCrosTitle1}, |
| |
| {TypographyToken::kLegacyHeadline1, TypographyToken::kCrosHeadline1}, |
| // Since `kCrosHeadline1` is Google Sans Text, its appropriate for both. |
| {TypographyToken::kLegacyHeadline2, TypographyToken::kCrosHeadline1}, |
| |
| {TypographyToken::kLegacyButton1, TypographyToken::kCrosButton1}, |
| {TypographyToken::kLegacyButton2, TypographyToken::kCrosButton2}, |
| |
| {TypographyToken::kLegacyBody1, TypographyToken::kCrosBody1}, |
| {TypographyToken::kLegacyBody2, TypographyToken::kCrosBody2}, |
| |
| {TypographyToken::kLegacyAnnotation1, |
| TypographyToken::kCrosAnnotation1}, |
| {TypographyToken::kLegacyAnnotation2, |
| TypographyToken::kCrosAnnotation2}, |
| |
| {TypographyToken::kLegacyLabel1, TypographyToken::kCrosLabel1}, |
| {TypographyToken::kLegacyLabel2, TypographyToken::kCrosLabel2}, |
| }); |
| |
| // Returns a map of tokens to `FontInfo`. |
| base::fixed_flat_map<TypographyToken, FontInfo, 41> MapFonts() { |
| return base::MakeFixedFlatMap<TypographyToken, FontInfo>({ |
| /* Legacy tokens */ |
| {TypographyToken::kLegacyDisplay1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 44, |
| gfx::Font::Weight::MEDIUM, 52}}, |
| {TypographyToken::kLegacyDisplay2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 36, |
| gfx::Font::Weight::MEDIUM, 44}}, |
| {TypographyToken::kLegacyDisplay3, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 32, |
| gfx::Font::Weight::MEDIUM, 40}}, |
| {TypographyToken::kLegacyDisplay4, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 28, |
| gfx::Font::Weight::MEDIUM, 36}}, |
| {TypographyToken::kLegacyDisplay5, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 24, |
| gfx::Font::Weight::MEDIUM, 32}}, |
| {TypographyToken::kLegacyDisplay6, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 22, |
| gfx::Font::Weight::MEDIUM, 28}}, |
| {TypographyToken::kLegacyDisplay7, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 18, |
| gfx::Font::Weight::MEDIUM, 24}}, |
| |
| {TypographyToken::kLegacyTitle1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 16, |
| gfx::Font::Weight::MEDIUM, 24}}, |
| {TypographyToken::kLegacyTitle2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 16, gfx::Font::Weight::MEDIUM, |
| 24}}, |
| |
| {TypographyToken::kLegacyHeadline1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 15, |
| gfx::Font::Weight::MEDIUM, 22}}, |
| {TypographyToken::kLegacyHeadline2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 15, gfx::Font::Weight::MEDIUM, |
| 22}}, |
| |
| {TypographyToken::kLegacyButton1, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 14, gfx::Font::Weight::MEDIUM, |
| 20}}, |
| {TypographyToken::kLegacyButton2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 13, gfx::Font::Weight::MEDIUM, |
| 20}}, |
| |
| {TypographyToken::kLegacyBody1, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 14, gfx::Font::Weight::NORMAL, |
| 20}}, |
| {TypographyToken::kLegacyBody2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 13, gfx::Font::Weight::NORMAL, |
| 20}}, |
| |
| {TypographyToken::kLegacyAnnotation1, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 12, gfx::Font::Weight::NORMAL, |
| 18}}, |
| {TypographyToken::kLegacyAnnotation2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 11, gfx::Font::Weight::NORMAL, |
| 16}}, |
| |
| {TypographyToken::kLegacyLabel1, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 10, gfx::Font::Weight::MEDIUM, |
| 10}}, |
| {TypographyToken::kLegacyLabel2, |
| {FontFamily::kRoboto, gfx::Font::NORMAL, 10, gfx::Font::Weight::NORMAL, |
| 10}}, |
| |
| /* cros.typography tokens */ |
| /* Google Sans */ |
| {TypographyToken::kCrosDisplay0, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 52, |
| gfx::Font::Weight::MEDIUM, 60}}, |
| {TypographyToken::kCrosDisplay1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 44, |
| gfx::Font::Weight::MEDIUM, 52}}, |
| {TypographyToken::kCrosDisplay2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 36, |
| gfx::Font::Weight::MEDIUM, 44}}, |
| {TypographyToken::kCrosDisplay3, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 32, |
| gfx::Font::Weight::MEDIUM, 40}}, |
| {TypographyToken::kCrosDisplay3Regular, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 32, |
| gfx::Font::Weight::NORMAL, 40}}, |
| {TypographyToken::kCrosDisplay4, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 28, |
| gfx::Font::Weight::MEDIUM, 36}}, |
| {TypographyToken::kCrosDisplay5, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 24, |
| gfx::Font::Weight::MEDIUM, 32}}, |
| {TypographyToken::kCrosDisplay6, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 22, |
| gfx::Font::Weight::MEDIUM, 28}}, |
| {TypographyToken::kCrosDisplay6Regular, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 22, |
| gfx::Font::Weight::NORMAL, 28}}, |
| {TypographyToken::kCrosDisplay7, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 18, |
| gfx::Font::Weight::MEDIUM, 24}}, |
| |
| /* TODO(b/256663656): Fix to render in Google Sans Text */ |
| {TypographyToken::kCrosTitle1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 16, |
| gfx::Font::Weight::MEDIUM, 24}}, |
| {TypographyToken::kCrosTitle2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 13, gfx::Font::Weight::BOLD, |
| 20}}, |
| {TypographyToken::kCrosHeadline1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 15, |
| gfx::Font::Weight::MEDIUM, 22}}, |
| |
| {TypographyToken::kCrosButton1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 14, |
| gfx::Font::Weight::MEDIUM, 20}}, |
| {TypographyToken::kCrosButton2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 13, |
| gfx::Font::Weight::MEDIUM, 20}}, |
| |
| {TypographyToken::kCrosBody0, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 16, |
| gfx::Font::Weight::NORMAL, 24}}, |
| {TypographyToken::kCrosBody1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 14, |
| gfx::Font::Weight::NORMAL, 20}}, |
| {TypographyToken::kCrosBody2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 13, |
| gfx::Font::Weight::NORMAL, 20}}, |
| |
| {TypographyToken::kCrosAnnotation1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 12, |
| gfx::Font::Weight::NORMAL, 18}}, |
| {TypographyToken::kCrosAnnotation2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 11, |
| gfx::Font::Weight::NORMAL, 16}}, |
| |
| {TypographyToken::kCrosLabel1, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 10, |
| gfx::Font::Weight::MEDIUM, 10}}, |
| {TypographyToken::kCrosLabel2, |
| {FontFamily::kGoogleSans, gfx::Font::NORMAL, 10, |
| gfx::Font::Weight::NORMAL, 10}}, |
| }); |
| } |
| |
| class TypographyProviderImpl : public TypographyProvider { |
| public: |
| TypographyProviderImpl() : font_map_(MapFonts()) {} |
| |
| ~TypographyProviderImpl() override = default; |
| |
| gfx::FontList ResolveTypographyToken(TypographyToken token) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| TypographyToken converted_token = ConvertToken(token); |
| CHECK_LE(converted_token, TypographyToken::kMaxValue); |
| |
| // Constructing an entirely new `gfx::FontList` for each call is very slow, |
| // as each caller will need to resolve the font separately - a slow |
| // operation - and have separate caches for calculated font metrics like |
| // height and baseline. |
| // As `gfx::FontList` is internally a `scoped_refptr`, return a copy of a |
| // cached `gfx::FontList` instead of constructing a new one, so callers can |
| // share font resolution and font metrics. |
| std::optional<gfx::FontList>& list = |
| font_list_cache_[base::to_underlying(converted_token)]; |
| if (!list.has_value()) { |
| const FontInfo& info = LookupInfo(converted_token); |
| list.emplace(FontNames(info.family), info.style, info.size, info.weight); |
| } |
| return *list; |
| } |
| |
| int ResolveLineHeight(TypographyToken token) const override { |
| return LookupInfo(ConvertToken(token)).line_height; |
| } |
| |
| private: |
| // Does not perform token conversion. |
| const FontInfo& LookupInfo(TypographyToken token) const { |
| const auto iter = font_map_.find(token); |
| if (iter == font_map_.end()) { |
| NOTREACHED() << "Tried to resolve unmapped token"; |
| } |
| return iter->second; |
| } |
| |
| // Returns the equivalient cros.sys token for a legacy token if styles should |
| // be converted. |
| TypographyToken ConvertToken(TypographyToken token) const { |
| if (token > TypographyToken::kLastLegacyToken) { |
| return token; |
| } |
| |
| const auto iter = kTokenEquivalents.find(token); |
| if (iter == kTokenEquivalents.end()) { |
| NOTREACHED() << "Missing a mapping for legacy token " |
| << static_cast<int>(token); |
| } |
| |
| return iter->second; |
| } |
| |
| const base::fixed_flat_map<TypographyToken, FontInfo, 41> font_map_; |
| |
| static constexpr size_t kNumTokens = |
| base::to_underlying(TypographyToken::kMaxValue) + 1; |
| mutable std::array<std::optional<gfx::FontList>, kNumTokens> font_list_cache_ |
| GUARDED_BY_CONTEXT(sequence_checker_); |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| } // namespace |
| |
| // static |
| const TypographyProvider* TypographyProvider::Get() { |
| static base::NoDestructor<TypographyProviderImpl> typography_provider; |
| return typography_provider.get(); |
| } |
| |
| TypographyProvider::~TypographyProvider() = default; |
| |
| void TypographyProvider::StyleLabel(TypographyToken token, |
| views::Label& label) const { |
| label.SetFontList(ResolveTypographyToken(token)); |
| label.SetLineHeight(ResolveLineHeight(token)); |
| } |
| |
| } // namespace ash |