From 215ca15600887ffeffef2df3f216e0189159150e Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Fri, 21 Dec 2018 15:07:47 -0800 Subject: [PATCH] Support user-provided font-fallback. (#7241) * Support user-provided font-fallback. * Use tonic to pass to native * Handle font families as a fallback vector in LibTxt * Fix docs * Concatentate fontFamily lists in dart before passing to engine * Fix formatting * Reworked font family matching to search all managers * Fix formatting * Proper toString null checking to keep format consistent * Formatting * Move _listEquals out of textstyle as it is not specific to TextStyle and will later be used for paragraphStyle too --- lib/ui/painting.dart | 17 -- lib/ui/text.dart | 87 +++++++--- lib/ui/text/paragraph_builder.cc | 14 +- lib/ui/text/paragraph_builder.h | 2 +- third_party/txt/src/txt/font_collection.cc | 111 +++++++----- third_party/txt/src/txt/font_collection.h | 13 +- third_party/txt/src/txt/paragraph.cc | 9 +- third_party/txt/src/txt/paragraph.h | 1 + third_party/txt/src/txt/paragraph_style.cc | 4 +- third_party/txt/src/txt/styled_runs.h | 1 + third_party/txt/src/txt/text_style.cc | 9 +- third_party/txt/src/txt/text_style.h | 4 +- third_party/txt/tests/paragraph_unittests.cc | 174 +++++++++++++++---- 13 files changed, 306 insertions(+), 140 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 4feb65dffd5..9a89891ffd7 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3802,23 +3802,6 @@ class Shadow { @override int get hashCode => hashValues(color, offset, blurRadius); - /// Determines if lists [a] and [b] are deep equivalent. - /// - /// Returns true if the lists are both null, or if they are both non-null, have - /// the same length, and contain the same Shadows in the same order. Returns - /// false otherwise. - static bool _shadowsListEquals(List a, List b) { - // Compare _shadows - if (a == null) - return b == null; - if (b == null || a.length != b.length) - return false; - for (int index = 0; index < a.length; ++index) - if (a[index] != b[index]) - return false; - return true; - } - // Serialize [shadows] into ByteData. The format is a single uint_32_t at // the beginning indicating the number of shadows, followed by _kBytesPerShadow // bytes for each shadow. diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 5afd8c50ffc..24eebff1c20 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -218,6 +218,23 @@ enum TextDecorationStyle { wavy } +/// Determines if lists [a] and [b] are deep equivalent. +/// +/// Returns true if the lists are both null, or if they are both non-null, have +/// the same length, and contain the same elements in the same order. Returns +/// false otherwise. +bool _listEquals(List a, List b) { + if (a == null) + return b == null; + if (b == null || a.length != b.length) + return false; + for (int index = 0; index < a.length; index += 1) { + if (a[index] != b[index]) + return false; + } + return true; +} + // This encoding must match the C++ version of ParagraphBuilder::pushStyle. // // The encoded array buffer has 8 elements. @@ -252,6 +269,7 @@ Int32List _encodeTextStyle( FontStyle fontStyle, TextBaseline textBaseline, String fontFamily, + List fontFamilyFallback, double fontSize, double letterSpacing, double wordSpacing, @@ -290,7 +308,7 @@ Int32List _encodeTextStyle( result[0] |= 1 << 7; result[7] = textBaseline.index; } - if (fontFamily != null) { + if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) { result[0] |= 1 << 8; // Passed separately to native. } @@ -339,7 +357,15 @@ class TextStyle { /// * `decorationStyle`: The style in which to paint the text decorations (e.g., dashed). /// * `fontWeight`: The typeface thickness to use when painting the text (e.g., bold). /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g., italics). - /// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). + /// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). If a `fontFamilyFallback` is + /// provided and `fontFamily` is not, then the first font family in `fontFamilyFallback` will take the postion of + /// the preferred font family. When a higher priority font cannot be found or does not contain a glyph, a lower + /// priority font will be used. + /// * `fontFamilyFallback`: An ordered list of the names of the fonts to fallback on when a glyph cannot + /// be found in a higher priority font. When the `fontFamily` is null, the first font family in this list + /// is used as the preferred font. Internally, the 'fontFamily` is concatenated to the front of this list. + /// When no font family is provided through 'fontFamilyFallback' (null or empty) or `fontFamily`, then the + /// platform default font will be used. /// * `fontSize`: The size of glyphs (in logical pixels) to use when painting the text. /// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter. /// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word). @@ -357,6 +383,7 @@ class TextStyle { FontStyle fontStyle, TextBaseline textBaseline, String fontFamily, + List fontFamilyFallback, double fontSize, double letterSpacing, double wordSpacing, @@ -378,6 +405,7 @@ class TextStyle { fontStyle, textBaseline, fontFamily, + fontFamilyFallback, fontSize, letterSpacing, wordSpacing, @@ -388,6 +416,7 @@ class TextStyle { shadows, ), _fontFamily = fontFamily ?? '', + _fontFamilyFallback = fontFamilyFallback, _fontSize = fontSize, _letterSpacing = letterSpacing, _wordSpacing = wordSpacing, @@ -399,6 +428,7 @@ class TextStyle { final Int32List _encoded; final String _fontFamily; + final List _fontFamilyFallback; final double _fontSize; final double _letterSpacing; final double _wordSpacing; @@ -428,33 +458,39 @@ class TextStyle { if (_encoded[index] != typedOther._encoded[index]) return false; } - if (!Shadow._shadowsListEquals(_shadows, typedOther._shadows)) + if (!_listEquals(_shadows, typedOther._shadows)) + return false; + if (!_listEquals(_fontFamilyFallback, typedOther._fontFamilyFallback)) return false; return true; } @override - int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground); + int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, _shadows); @override String toString() { return 'TextStyle(' - 'color: ${ _encoded[0] & 0x00002 == 0x00002 ? new Color(_encoded[1]) : "unspecified"}, ' - 'decoration: ${ _encoded[0] & 0x00004 == 0x00004 ? new TextDecoration._(_encoded[2]) : "unspecified"}, ' - 'decorationColor: ${_encoded[0] & 0x00008 == 0x00008 ? new Color(_encoded[3]) : "unspecified"}, ' - 'decorationStyle: ${_encoded[0] & 0x00010 == 0x00010 ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, ' - 'fontWeight: ${ _encoded[0] & 0x00020 == 0x00020 ? FontWeight.values[_encoded[5]] : "unspecified"}, ' - 'fontStyle: ${ _encoded[0] & 0x00040 == 0x00040 ? FontStyle.values[_encoded[6]] : "unspecified"}, ' - 'textBaseline: ${ _encoded[0] & 0x00080 == 0x00080 ? TextBaseline.values[_encoded[7]] : "unspecified"}, ' - 'fontFamily: ${ _encoded[0] & 0x00100 == 0x00100 ? _fontFamily : "unspecified"}, ' - 'fontSize: ${ _encoded[0] & 0x00200 == 0x00200 ? _fontSize : "unspecified"}, ' - 'letterSpacing: ${ _encoded[0] & 0x00400 == 0x00400 ? "${_letterSpacing}x" : "unspecified"}, ' - 'wordSpacing: ${ _encoded[0] & 0x00800 == 0x00800 ? "${_wordSpacing}x" : "unspecified"}, ' - 'height: ${ _encoded[0] & 0x01000 == 0x01000 ? "${_height}x" : "unspecified"}, ' - 'locale: ${ _encoded[0] & 0x02000 == 0x02000 ? _locale : "unspecified"}, ' - 'background: ${ _encoded[0] & 0x04000 == 0x04000 ? _background : "unspecified"}, ' - 'foreground: ${ _encoded[0] & 0x08000 == 0x08000 ? _foreground : "unspecified"}, ' - 'shadows: ${ _encoded[0] & 0x10000 == 0x10000 ? _shadows : "unspecified"}' + 'color: ${ _encoded[0] & 0x00002 == 0x00002 ? new Color(_encoded[1]) : "unspecified"}, ' + 'decoration: ${ _encoded[0] & 0x00004 == 0x00004 ? new TextDecoration._(_encoded[2]) : "unspecified"}, ' + 'decorationColor: ${ _encoded[0] & 0x00008 == 0x00008 ? new Color(_encoded[3]) : "unspecified"}, ' + 'decorationStyle: ${ _encoded[0] & 0x00010 == 0x00010 ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, ' + 'fontWeight: ${ _encoded[0] & 0x00020 == 0x00020 ? FontWeight.values[_encoded[5]] : "unspecified"}, ' + 'fontStyle: ${ _encoded[0] & 0x00040 == 0x00040 ? FontStyle.values[_encoded[6]] : "unspecified"}, ' + 'textBaseline: ${ _encoded[0] & 0x00080 == 0x00080 ? TextBaseline.values[_encoded[7]] : "unspecified"}, ' + 'fontFamily: ${ _encoded[0] & 0x00100 == 0x00100 + && _fontFamily != null ? _fontFamily : "unspecified"}, ' + 'fontFamilyFallback: ${_encoded[0] & 0x00100 == 0x00100 + && _fontFamilyFallback != null + && _fontFamilyFallback.isNotEmpty ? _fontFamilyFallback : "unspecified"}, ' + 'fontSize: ${ _encoded[0] & 0x00200 == 0x00200 ? _fontSize : "unspecified"}, ' + 'letterSpacing: ${ _encoded[0] & 0x00400 == 0x00400 ? "${_letterSpacing}x" : "unspecified"}, ' + 'wordSpacing: ${ _encoded[0] & 0x00800 == 0x00800 ? "${_wordSpacing}x" : "unspecified"}, ' + 'height: ${ _encoded[0] & 0x01000 == 0x01000 ? "${_height}x" : "unspecified"}, ' + 'locale: ${ _encoded[0] & 0x02000 == 0x02000 ? _locale : "unspecified"}, ' + 'background: ${ _encoded[0] & 0x04000 == 0x04000 ? _background : "unspecified"}, ' + 'foreground: ${ _encoded[0] & 0x08000 == 0x08000 ? _foreground : "unspecified"}, ' + 'shadows: ${ _encoded[0] & 0x10000 == 0x10000 ? _shadows : "unspecified"}' ')'; } } @@ -1173,8 +1209,15 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 { /// Applies the given style to the added text until [pop] is called. /// /// See [pop] for details. - void pushStyle(TextStyle style) => _pushStyle(style._encoded, style._fontFamily, style._fontSize, style._letterSpacing, style._wordSpacing, style._height, _encodeLocale(style._locale), style._background?._objects, style._background?._data, style._foreground?._objects, style._foreground?._data, Shadow._encodeShadows(style._shadows)); - void _pushStyle(Int32List encoded, String fontFamily, double fontSize, double letterSpacing, double wordSpacing, double height, String locale, List backgroundObjects, ByteData backgroundData, List foregroundObjects, ByteData foregroundData, ByteData shadowsData) native 'ParagraphBuilder_pushStyle'; + void pushStyle(TextStyle style) { + final List fullFontFamilies = []; + if (style._fontFamily != null) + fullFontFamilies.add(style._fontFamily); + if (style._fontFamilyFallback != null) + fullFontFamilies.addAll(style._fontFamilyFallback); + _pushStyle(style._encoded, fullFontFamilies, style._fontSize, style._letterSpacing, style._wordSpacing, style._height, _encodeLocale(style._locale), style._background?._objects, style._background?._data, style._foreground?._objects, style._foreground?._data, Shadow._encodeShadows(style._shadows)); + } + void _pushStyle(Int32List encoded, List fontFamilies, double fontSize, double letterSpacing, double wordSpacing, double height, String locale, List backgroundObjects, ByteData backgroundData, List foregroundObjects, ByteData foregroundData, ByteData shadowsData) native 'ParagraphBuilder_pushStyle'; static String _encodeLocale(Locale locale) => locale?.toString() ?? ''; diff --git a/lib/ui/text/paragraph_builder.cc b/lib/ui/text/paragraph_builder.cc index bb822417082..61a1bc2d2df 100644 --- a/lib/ui/text/paragraph_builder.cc +++ b/lib/ui/text/paragraph_builder.cc @@ -202,7 +202,7 @@ void decodeTextShadows(Dart_Handle shadows_data, } void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, - const std::string& fontFamily, + const std::vector& fontFamilies, double fontSize, double letterSpacing, double wordSpacing, @@ -243,8 +243,8 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, // property wasn't wired up either. } - if (mask & (tsFontWeightMask | tsFontStyleMask | tsFontFamilyMask | - tsFontSizeMask | tsLetterSpacingMask | tsWordSpacingMask)) { + if (mask & (tsFontWeightMask | tsFontStyleMask | tsFontSizeMask | + tsLetterSpacingMask | tsWordSpacingMask)) { if (mask & tsFontWeightMask) style.font_weight = static_cast(encoded[tsFontWeightIndex]); @@ -252,9 +252,6 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, if (mask & tsFontStyleMask) style.font_style = static_cast(encoded[tsFontStyleIndex]); - if (mask & tsFontFamilyMask) - style.font_family = fontFamily; - if (mask & tsFontSizeMask) style.font_size = fontSize; @@ -293,6 +290,11 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, decodeTextShadows(shadows_data, style.text_shadows); } + if (mask & tsFontFamilyMask) { + style.font_families.insert(style.font_families.end(), fontFamilies.begin(), + fontFamilies.end()); + } + m_paragraphBuilder->PushStyle(style); } diff --git a/lib/ui/text/paragraph_builder.h b/lib/ui/text/paragraph_builder.h index 89383527c6d..69183557fa1 100644 --- a/lib/ui/text/paragraph_builder.h +++ b/lib/ui/text/paragraph_builder.h @@ -35,7 +35,7 @@ class ParagraphBuilder : public RefCountedDartWrappable { ~ParagraphBuilder() override; void pushStyle(tonic::Int32List& encoded, - const std::string& fontFamily, + const std::vector& fontFamilies, double fontSize, double letterSpacing, double wordSpacing, diff --git a/third_party/txt/src/txt/font_collection.cc b/third_party/txt/src/txt/font_collection.cc index 2be51f6312f..8cb7622fde0 100644 --- a/third_party/txt/src/txt/font_collection.cc +++ b/third_party/txt/src/txt/font_collection.cc @@ -16,6 +16,7 @@ #include "font_collection.h" +#include #include #include #include @@ -36,14 +37,24 @@ const std::shared_ptr g_null_family; } // anonymous namespace +FontCollection::FamilyKey::FamilyKey(const std::vector& families, + const std::string& loc) { + locale = loc; + + std::stringstream stream; + for_each(families.begin(), families.end(), + [&stream](const std::string& str) { stream << str << ','; }); + font_families = stream.str(); +} + bool FontCollection::FamilyKey::operator==( const FontCollection::FamilyKey& other) const { - return font_family == other.font_family && locale == other.locale; + return font_families == other.font_families && locale == other.locale; } size_t FontCollection::FamilyKey::Hasher::operator()( const FontCollection::FamilyKey& key) const { - return std::hash()(key.font_family) ^ + return std::hash()(key.font_families) ^ std::hash()(key.locale); } @@ -111,58 +122,72 @@ void FontCollection::DisableFontFallback() { } std::shared_ptr -FontCollection::GetMinikinFontCollectionForFamily( - const std::string& font_family, +FontCollection::GetMinikinFontCollectionForFamilies( + const std::vector& font_families, const std::string& locale) { // Look inside the font collections cache first. - FamilyKey family_key(font_family, locale); + FamilyKey family_key(font_families, locale); auto cached = font_collections_cache_.find(family_key); if (cached != font_collections_cache_.end()) { return cached->second; } - for (sk_sp& manager : GetFontManagerOrder()) { - std::shared_ptr minikin_family = - CreateMinikinFontFamily(manager, font_family); - if (!minikin_family) - continue; + std::vector> minikin_families; - // Create a vector of font families for the Minikin font collection. - std::vector> minikin_families = { - minikin_family, - }; - if (enable_font_fallback_) { - for (std::string fallback_family : fallback_fonts_for_locale_[locale]) { - auto it = fallback_fonts_.find(fallback_family); - if (it != fallback_fonts_.end()) { - minikin_families.push_back(it->second); - } + // Search for all user provided font families. + for (size_t fallback_index = 0; fallback_index < font_families.size(); + fallback_index++) { + std::shared_ptr minikin_family = + FindFontFamilyInManagers(font_families[fallback_index]); + if (minikin_family != nullptr) { + minikin_families.push_back(minikin_family); + } + } + // Search for default font family if no user font families were found. + if (minikin_families.empty()) { + const auto default_font_family = GetDefaultFontFamily(); + std::shared_ptr minikin_family = + FindFontFamilyInManagers(default_font_family); + if (minikin_family != nullptr) { + minikin_families.push_back(minikin_family); + } + } + // Default font family also not found. We fail to get a FontCollection. + if (minikin_families.empty()) { + return nullptr; + } + if (enable_font_fallback_) { + for (std::string fallback_family : fallback_fonts_for_locale_[locale]) { + auto it = fallback_fonts_.find(fallback_family); + if (it != fallback_fonts_.end()) { + minikin_families.push_back(it->second); } } - - // Create the minikin font collection. - auto font_collection = - std::make_shared(std::move(minikin_families)); - if (enable_font_fallback_) { - font_collection->set_fallback_font_provider( - std::make_unique(shared_from_this())); - } - - // Cache the font collection for future queries. - font_collections_cache_[family_key] = font_collection; - - return font_collection; + } + // Create the minikin font collection. + auto font_collection = + std::make_shared(std::move(minikin_families)); + if (enable_font_fallback_) { + font_collection->set_fallback_font_provider( + std::make_unique(shared_from_this())); } - const auto default_font_family = GetDefaultFontFamily(); - if (font_family != default_font_family) { - std::shared_ptr default_collection = - GetMinikinFontCollectionForFamily(default_font_family, ""); - font_collections_cache_[family_key] = default_collection; - return default_collection; - } + // Cache the font collection for future queries. + font_collections_cache_[family_key] = font_collection; - // No match found in any of our font managers. + return font_collection; +} + +std::shared_ptr FontCollection::FindFontFamilyInManagers( + const std::string& family_name) { + // Search for the font family in each font manager. + for (sk_sp& manager : GetFontManagerOrder()) { + std::shared_ptr minikin_family = + CreateMinikinFontFamily(manager, family_name); + if (!minikin_family) + continue; + return minikin_family; + } return nullptr; } @@ -254,8 +279,8 @@ FontCollection::GetFallbackFontFamily(const sk_sp& manager, auto insert_it = fallback_fonts_.insert(std::make_pair(family_name, minikin_family)); - // Clear the cache to force creation of new font collections that will include - // this fallback font. + // Clear the cache to force creation of new font collections that will + // include this fallback font. font_collections_cache_.clear(); return insert_it.first->second; diff --git a/third_party/txt/src/txt/font_collection.h b/third_party/txt/src/txt/font_collection.h index e3dfaa8d007..094d405ceaf 100644 --- a/third_party/txt/src/txt/font_collection.h +++ b/third_party/txt/src/txt/font_collection.h @@ -45,8 +45,8 @@ class FontCollection : public std::enable_shared_from_this { void SetDynamicFontManager(sk_sp font_manager); void SetTestFontManager(sk_sp font_manager); - std::shared_ptr GetMinikinFontCollectionForFamily( - const std::string& family, + std::shared_ptr GetMinikinFontCollectionForFamilies( + const std::vector& font_families, const std::string& locale); // Provides a FontFamily that contains glyphs for ch. This caches previously @@ -61,10 +61,10 @@ class FontCollection : public std::enable_shared_from_this { private: struct FamilyKey { - FamilyKey(const std::string& family, const std::string& loc) - : font_family(family), locale(loc) {} + FamilyKey(const std::vector& families, const std::string& loc); - std::string font_family; + // Concatenated string with all font families. + std::string font_families; std::string locale; bool operator==(const FamilyKey& other) const; @@ -100,6 +100,9 @@ class FontCollection : public std::enable_shared_from_this { std::vector> GetFontManagerOrder() const; + std::shared_ptr FindFontFamilyInManagers( + const std::string& family_name); + std::shared_ptr CreateMinikinFontFamily( const sk_sp& manager, const std::string& family_name); diff --git a/third_party/txt/src/txt/paragraph.cc b/third_party/txt/src/txt/paragraph.cc index db8cad8c30c..3e15ecec0f8 100644 --- a/third_party/txt/src/txt/paragraph.cc +++ b/third_party/txt/src/txt/paragraph.cc @@ -290,7 +290,10 @@ bool Paragraph::ComputeLineBreaks() { GetMinikinFontCollectionForStyle(run.style); if (collection == nullptr) { FML_LOG(INFO) << "Could not find font collection for family \"" - << run.style.font_family << "\"."; + << (run.style.font_families.empty() + ? "" + : run.style.font_families[0]) + << "\"."; return false; } size_t run_start = std::max(run.start, block_start) - block_start; @@ -900,8 +903,8 @@ Paragraph::GetMinikinFontCollectionForStyle(const TextStyle& style) { } } - return font_collection_->GetMinikinFontCollectionForFamily(style.font_family, - locale); + return font_collection_->GetMinikinFontCollectionForFamilies( + style.font_families, locale); } sk_sp Paragraph::GetDefaultSkiaTypeface(const TextStyle& style) { diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 850bd1b5b49..8195b5e3539 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -226,6 +226,7 @@ class Paragraph { FRIEND_TEST(ParagraphTest, UnderlineShiftParagraph); FRIEND_TEST(ParagraphTest, SimpleShadow); FRIEND_TEST(ParagraphTest, ComplexShadow); + FRIEND_TEST(ParagraphTest, FontFallbackParagraph); // Starting data to layout. std::vector text_; diff --git a/third_party/txt/src/txt/paragraph_style.cc b/third_party/txt/src/txt/paragraph_style.cc index fbb89cbfccd..f4849f4df66 100644 --- a/third_party/txt/src/txt/paragraph_style.cc +++ b/third_party/txt/src/txt/paragraph_style.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "paragraph_style.h" namespace txt { @@ -22,7 +24,7 @@ TextStyle ParagraphStyle::GetTextStyle() const { TextStyle result; result.font_weight = font_weight; result.font_style = font_style; - result.font_family = font_family; + result.font_families = std::vector({font_family}); result.font_size = font_size; result.locale = locale; result.height = line_height; diff --git a/third_party/txt/src/txt/styled_runs.h b/third_party/txt/src/txt/styled_runs.h index c3707c0c6a3..d123525e19c 100644 --- a/third_party/txt/src/txt/styled_runs.h +++ b/third_party/txt/src/txt/styled_runs.h @@ -81,6 +81,7 @@ class StyledRuns { FRIEND_TEST(ParagraphTest, Ellipsize); FRIEND_TEST(ParagraphTest, SimpleShadow); FRIEND_TEST(ParagraphTest, ComplexShadow); + FRIEND_TEST(ParagraphTest, FontFallbackParagraph); struct IndexedRun { size_t style_index = 0; diff --git a/third_party/txt/src/txt/text_style.cc b/third_party/txt/src/txt/text_style.cc index b2da694ef6a..194dde47882 100644 --- a/third_party/txt/src/txt/text_style.cc +++ b/third_party/txt/src/txt/text_style.cc @@ -22,7 +22,8 @@ namespace txt { -TextStyle::TextStyle() : font_family(GetDefaultFontFamily()) {} +TextStyle::TextStyle() + : font_families(std::vector(1, GetDefaultFontFamily())) {} bool TextStyle::equals(const TextStyle& other) const { if (color != other.color) @@ -39,8 +40,6 @@ bool TextStyle::equals(const TextStyle& other) const { return false; if (font_style != other.font_style) return false; - if (font_family != other.font_family) - return false; if (letter_spacing != other.letter_spacing) return false; if (word_spacing != other.word_spacing) @@ -53,6 +52,10 @@ bool TextStyle::equals(const TextStyle& other) const { return false; if (text_shadows.size() != other.text_shadows.size()) return false; + for (size_t font_index = 0; font_index < font_families.size(); ++font_index) { + if (font_families[font_index] != other.font_families[font_index]) + return false; + } for (size_t shadow_index = 0; shadow_index < text_shadows.size(); ++shadow_index) { if (text_shadows[shadow_index] != other.text_shadows[shadow_index]) diff --git a/third_party/txt/src/txt/text_style.h b/third_party/txt/src/txt/text_style.h index 7b0d351e8fa..f3c5bde46a7 100644 --- a/third_party/txt/src/txt/text_style.h +++ b/third_party/txt/src/txt/text_style.h @@ -43,7 +43,9 @@ class TextStyle { FontWeight font_weight = FontWeight::w400; FontStyle font_style = FontStyle::normal; TextBaseline text_baseline = TextBaseline::kAlphabetic; - std::string font_family; + // An ordered list of fonts in order of priority. The first font is more + // highly preferred than the last font. + std::vector font_families; double font_size = 14.0; double letter_spacing = 0.0; double word_spacing = 0.0; diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 2f8d44f78ae..963a5345e73 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "flutter/fml/logging.h" #include "render_test.h" #include "third_party/icu/source/common/unicode/unistr.h" @@ -40,7 +42,11 @@ TEST_F(ParagraphTest, SimpleParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + // We must supply a font here, as the default is Arial, and we do not + // include Arial in our test fonts as it is proprietary. We want it to + // be Arial default though as it is one of the most common fonts on host + // platforms. On real devices/apps, Arial should be able to be resolved. + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorBLACK; builder.PushStyle(text_style); builder.AddText(u16_text); @@ -73,7 +79,7 @@ TEST_F(ParagraphTest, SimpleRedParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorRED; builder.PushStyle(text_style); @@ -127,8 +133,9 @@ TEST_F(ParagraphTest, RainbowParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style1; + text_style1.font_families = std::vector(1, "Roboto"); text_style1.color = SK_ColorRED; - text_style1.font_family = "Roboto"; + builder.PushStyle(text_style1); builder.AddText(u16_text1); @@ -139,7 +146,7 @@ TEST_F(ParagraphTest, RainbowParagraph) { text_style2.word_spacing = 30; text_style2.font_weight = txt::FontWeight::w600; text_style2.color = SK_ColorGREEN; - text_style2.font_family = "Roboto"; + text_style2.font_families = std::vector(1, "Roboto"); text_style2.decoration = TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough; @@ -149,7 +156,7 @@ TEST_F(ParagraphTest, RainbowParagraph) { builder.AddText(u16_text2); txt::TextStyle text_style3; - text_style3.font_family = "Homemade Apple"; + text_style3.font_families = std::vector(1, "Homemade Apple"); builder.PushStyle(text_style3); builder.AddText(u16_text3); @@ -157,7 +164,7 @@ TEST_F(ParagraphTest, RainbowParagraph) { txt::TextStyle text_style4; text_style4.font_size = 14; text_style4.color = SK_ColorBLUE; - text_style4.font_family = "Roboto"; + text_style4.font_families = std::vector(1, "Roboto"); text_style4.decoration = TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough; @@ -174,7 +181,6 @@ TEST_F(ParagraphTest, RainbowParagraph) { auto paragraph = builder.Build(); paragraph->Layout(GetTestCanvasWidth()); - paragraph->Paint(GetCanvas(), 0, 0); u16_text1 += u16_text2 + u16_text3 + u16_text4; @@ -233,7 +239,7 @@ TEST_F(ParagraphTest, BoldParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 60; text_style.letter_spacing = 0; text_style.font_weight = txt::FontWeight::w900; @@ -289,7 +295,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(LeftAlignParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 26; text_style.letter_spacing = 1; text_style.word_spacing = 5; @@ -386,7 +392,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(RightAlignParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 26; text_style.letter_spacing = 1; text_style.word_spacing = 5; @@ -495,7 +501,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(CenterAlignParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 26; text_style.letter_spacing = 1; text_style.word_spacing = 5; @@ -603,7 +609,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(JustifyAlignParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 26; text_style.letter_spacing = 0; text_style.word_spacing = 5; @@ -670,7 +676,7 @@ TEST_F(ParagraphTest, DecorationsParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 26; text_style.letter_spacing = 0; text_style.word_spacing = 5; @@ -748,7 +754,7 @@ TEST_F(ParagraphTest, ItalicsParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorRED; text_style.font_size = 10; builder.PushStyle(text_style); @@ -795,7 +801,7 @@ TEST_F(ParagraphTest, ChineseParagraph) { text_style.color = SK_ColorBLACK; text_style.font_size = 35; text_style.letter_spacing = 2; - text_style.font_family = "Source Han Serif CN"; + text_style.font_families = std::vector(1, "Source Han Serif CN"); text_style.decoration = TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough; @@ -840,7 +846,7 @@ TEST_F(ParagraphTest, DISABLED_ArabicParagraph) { text_style.color = SK_ColorBLACK; text_style.font_size = 35; text_style.letter_spacing = 2; - text_style.font_family = "Katibeh"; + text_style.font_families = std::vector(1, "Katibeh"); text_style.decoration = TextDecoration::kUnderline | TextDecoration::kOverline | TextDecoration::kLineThrough; @@ -887,7 +893,7 @@ TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 1; text_style.word_spacing = 5; @@ -955,7 +961,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1085,7 +1091,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Noto Sans CJK JP"; + text_style.font_families = std::vector(1, "Noto Sans CJK JP"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1179,7 +1185,8 @@ TEST_F(ParagraphTest, txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); + // text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1330,7 +1337,7 @@ TEST_F(ParagraphTest, txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1481,7 +1488,7 @@ TEST_F(ParagraphTest, txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1626,7 +1633,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeCenterParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1734,7 +1741,7 @@ TEST_F(ParagraphTest, txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -1874,7 +1881,7 @@ TEST_F(ParagraphTest, GetWordBoundaryParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 52; text_style.letter_spacing = 1.19039; text_style.word_spacing = 5; @@ -1980,7 +1987,7 @@ TEST_F(ParagraphTest, SpacingParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 50; text_style.letter_spacing = 20; text_style.word_spacing = 0; @@ -2069,7 +2076,7 @@ TEST_F(ParagraphTest, LongWordParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 31; text_style.letter_spacing = 0; text_style.word_spacing = 0; @@ -2106,7 +2113,7 @@ TEST_F(ParagraphTest, KernScaleParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Droid Serif"; + text_style.font_families = std::vector(1, "Droid Serif"); text_style.font_size = 100 / scale; text_style.letter_spacing = 0; text_style.word_spacing = 0; @@ -2148,7 +2155,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(NewlineParagraph)) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 60; text_style.letter_spacing = 0; text_style.word_spacing = 0; @@ -2195,7 +2202,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(EmojiParagraph)) { txt::TextStyle text_style; text_style.color = SK_ColorBLACK; - text_style.font_family = "Noto Color Emoji"; + text_style.font_families = std::vector(1, "Noto Color Emoji"); text_style.font_size = 50; text_style.decoration = TextDecoration::kUnderline; builder.PushStyle(text_style); @@ -2239,7 +2246,7 @@ TEST_F(ParagraphTest, HyphenBreakParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 31; text_style.letter_spacing = 0; text_style.word_spacing = 0; @@ -2283,7 +2290,7 @@ TEST_F(ParagraphTest, RepeatLayoutParagraph) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.font_size = 31; text_style.letter_spacing = 0; text_style.word_spacing = 0; @@ -2343,7 +2350,7 @@ TEST_F(ParagraphTest, Ellipsize) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorBLACK; builder.PushStyle(text_style); builder.AddText(u16_text); @@ -2385,14 +2392,14 @@ TEST_F(ParagraphTest, UnderlineShiftParagraph) { txt::TextStyle text_style1; text_style1.color = SK_ColorBLACK; - text_style1.font_family = "Roboto"; + text_style1.font_families = std::vector(1, "Roboto"); builder.PushStyle(text_style1); builder.AddText(u16_text1); txt::TextStyle text_style2; text_style2.color = SK_ColorBLACK; - text_style2.font_family = "Roboto"; + text_style2.font_families = std::vector(1, "Roboto"); text_style2.decoration = TextDecoration::kUnderline; text_style2.decoration_color = SK_ColorBLACK; builder.PushStyle(text_style2); @@ -2454,7 +2461,7 @@ TEST_F(ParagraphTest, SimpleShadow) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorBLACK; text_style.text_shadows.emplace_back(SK_ColorBLACK, SkPoint::Make(2.0, 2.0), 1.0); @@ -2493,7 +2500,7 @@ TEST_F(ParagraphTest, ComplexShadow) { txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_family = "Roboto"; + text_style.font_families = std::vector(1, "Roboto"); text_style.color = SK_ColorBLACK; text_style.text_shadows.emplace_back(SK_ColorBLACK, SkPoint::Make(2.0, 2.0), 1.0); @@ -2571,7 +2578,7 @@ TEST_F(ParagraphTest, BaselineParagraph) { text_style.color = SK_ColorBLACK; text_style.font_size = 55; text_style.letter_spacing = 2; - text_style.font_family = "Source Han Serif CN"; + text_style.font_families = std::vector(1, "Source Han Serif CN"); text_style.decoration_style = txt::TextDecorationStyle::kSolid; text_style.decoration_color = SK_ColorBLACK; builder.PushStyle(text_style); @@ -2605,4 +2612,95 @@ TEST_F(ParagraphTest, BaselineParagraph) { ASSERT_TRUE(Snapshot()); } +TEST_F(ParagraphTest, FontFallbackParagraph) { + const char* text = "Roboto 字典 "; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + const char* text2 = "Homemade Apple 字典"; + icu_text = icu::UnicodeString::fromUTF8(text2); + std::u16string u16_text2(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + const char* text3 = "Chinese 字典"; + icu_text = icu::UnicodeString::fromUTF8(text3); + std::u16string u16_text3(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + // No chinese fallback provided, should not be able to render the chinese. + text_style.font_families = std::vector(1, "Not a real font"); + text_style.font_families.push_back("Also a fake font"); + text_style.font_families.push_back("So fake it is obvious"); + text_style.font_families.push_back("Next one should be a real font..."); + text_style.font_families.push_back("Roboto"); + text_style.font_families.push_back("another fake one in between"); + text_style.font_families.push_back("Homemade Apple"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text); + + // Japanese version of the chinese should be rendered. + text_style.font_families = std::vector(1, "Not a real font"); + text_style.font_families.push_back("Also a fake font"); + text_style.font_families.push_back("So fake it is obvious"); + text_style.font_families.push_back("Homemade Apple"); + text_style.font_families.push_back("Next one should be a real font..."); + text_style.font_families.push_back("Roboto"); + text_style.font_families.push_back("another fake one in between"); + text_style.font_families.push_back("Noto Sans CJK JP"); + text_style.font_families.push_back("Source Han Serif CN"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text2); + + // Chinese font defiend first + text_style.font_families = std::vector(1, "Not a real font"); + text_style.font_families.push_back("Also a fake font"); + text_style.font_families.push_back("So fake it is obvious"); + text_style.font_families.push_back("Homemade Apple"); + text_style.font_families.push_back("Next one should be a real font..."); + text_style.font_families.push_back("Roboto"); + text_style.font_families.push_back("another fake one in between"); + text_style.font_families.push_back("Source Han Serif CN"); + text_style.font_families.push_back("Noto Sans CJK JP"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text3); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth()); + + paragraph->Paint(GetCanvas(), 10.0, 15.0); + + ASSERT_TRUE(Snapshot()); + + ASSERT_EQ(paragraph->records_.size(), 5ull); + ASSERT_DOUBLE_EQ(paragraph->records_[0].GetRunWidth(), 64.19921875); + ASSERT_DOUBLE_EQ(paragraph->records_[1].GetRunWidth(), 167.1171875); + ASSERT_DOUBLE_EQ(paragraph->records_[2].GetRunWidth(), 167.1171875); + ASSERT_DOUBLE_EQ(paragraph->records_[3].GetRunWidth(), 90.24609375); + ASSERT_DOUBLE_EQ(paragraph->records_[4].GetRunWidth(), 90.24609375); + // When a different font is resolved, then the metrics are different. + ASSERT_TRUE(paragraph->records_[2].metrics().fTop - + paragraph->records_[4].metrics().fTop != + 0); + ASSERT_TRUE(paragraph->records_[2].metrics().fAscent - + paragraph->records_[4].metrics().fAscent != + 0); + ASSERT_TRUE(paragraph->records_[2].metrics().fDescent - + paragraph->records_[4].metrics().fDescent != + 0); + ASSERT_TRUE(paragraph->records_[2].metrics().fBottom - + paragraph->records_[4].metrics().fBottom != + 0); + ASSERT_TRUE(paragraph->records_[2].metrics().fAvgCharWidth - + paragraph->records_[4].metrics().fAvgCharWidth != + 0); +} + } // namespace txt