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
This commit is contained in:
Gary Qian 2018-12-21 15:07:47 -08:00 committed by GitHub
parent 526b4bc357
commit 215ca15600
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 306 additions and 140 deletions

View File

@ -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<Shadow> a, List<Shadow> 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.

View File

@ -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<T>(List<T> a, List<T> 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<String> 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<String> 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<String> _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<Shadow>(_shadows, typedOther._shadows))
return false;
if (!_listEquals<String>(_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<dynamic> backgroundObjects, ByteData backgroundData, List<dynamic> foregroundObjects, ByteData foregroundData, ByteData shadowsData) native 'ParagraphBuilder_pushStyle';
void pushStyle(TextStyle style) {
final List<String> fullFontFamilies = <String>[];
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<dynamic> fontFamilies, double fontSize, double letterSpacing, double wordSpacing, double height, String locale, List<dynamic> backgroundObjects, ByteData backgroundData, List<dynamic> foregroundObjects, ByteData foregroundData, ByteData shadowsData) native 'ParagraphBuilder_pushStyle';
static String _encodeLocale(Locale locale) => locale?.toString() ?? '';

View File

@ -202,7 +202,7 @@ void decodeTextShadows(Dart_Handle shadows_data,
}
void ParagraphBuilder::pushStyle(tonic::Int32List& encoded,
const std::string& fontFamily,
const std::vector<std::string>& 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<txt::FontWeight>(encoded[tsFontWeightIndex]);
@ -252,9 +252,6 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded,
if (mask & tsFontStyleMask)
style.font_style = static_cast<txt::FontStyle>(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);
}

View File

@ -35,7 +35,7 @@ class ParagraphBuilder : public RefCountedDartWrappable<ParagraphBuilder> {
~ParagraphBuilder() override;
void pushStyle(tonic::Int32List& encoded,
const std::string& fontFamily,
const std::vector<std::string>& fontFamilies,
double fontSize,
double letterSpacing,
double wordSpacing,

View File

@ -16,6 +16,7 @@
#include "font_collection.h"
#include <algorithm>
#include <list>
#include <memory>
#include <mutex>
@ -36,14 +37,24 @@ const std::shared_ptr<minikin::FontFamily> g_null_family;
} // anonymous namespace
FontCollection::FamilyKey::FamilyKey(const std::vector<std::string>& 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<std::string>()(key.font_family) ^
return std::hash<std::string>()(key.font_families) ^
std::hash<std::string>()(key.locale);
}
@ -111,58 +122,72 @@ void FontCollection::DisableFontFallback() {
}
std::shared_ptr<minikin::FontCollection>
FontCollection::GetMinikinFontCollectionForFamily(
const std::string& font_family,
FontCollection::GetMinikinFontCollectionForFamilies(
const std::vector<std::string>& 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<SkFontMgr>& manager : GetFontManagerOrder()) {
std::shared_ptr<minikin::FontFamily> minikin_family =
CreateMinikinFontFamily(manager, font_family);
if (!minikin_family)
continue;
std::vector<std::shared_ptr<minikin::FontFamily>> minikin_families;
// Create a vector of font families for the Minikin font collection.
std::vector<std::shared_ptr<minikin::FontFamily>> 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::FontFamily> 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::FontFamily> 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<minikin::FontCollection>(std::move(minikin_families));
if (enable_font_fallback_) {
font_collection->set_fallback_font_provider(
std::make_unique<TxtFallbackFontProvider>(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<minikin::FontCollection>(std::move(minikin_families));
if (enable_font_fallback_) {
font_collection->set_fallback_font_provider(
std::make_unique<TxtFallbackFontProvider>(shared_from_this()));
}
const auto default_font_family = GetDefaultFontFamily();
if (font_family != default_font_family) {
std::shared_ptr<minikin::FontCollection> 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<minikin::FontFamily> FontCollection::FindFontFamilyInManagers(
const std::string& family_name) {
// Search for the font family in each font manager.
for (sk_sp<SkFontMgr>& manager : GetFontManagerOrder()) {
std::shared_ptr<minikin::FontFamily> minikin_family =
CreateMinikinFontFamily(manager, family_name);
if (!minikin_family)
continue;
return minikin_family;
}
return nullptr;
}
@ -254,8 +279,8 @@ FontCollection::GetFallbackFontFamily(const sk_sp<SkFontMgr>& 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;

View File

@ -45,8 +45,8 @@ class FontCollection : public std::enable_shared_from_this<FontCollection> {
void SetDynamicFontManager(sk_sp<SkFontMgr> font_manager);
void SetTestFontManager(sk_sp<SkFontMgr> font_manager);
std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamily(
const std::string& family,
std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamilies(
const std::vector<std::string>& 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<FontCollection> {
private:
struct FamilyKey {
FamilyKey(const std::string& family, const std::string& loc)
: font_family(family), locale(loc) {}
FamilyKey(const std::vector<std::string>& 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<FontCollection> {
std::vector<sk_sp<SkFontMgr>> GetFontManagerOrder() const;
std::shared_ptr<minikin::FontFamily> FindFontFamilyInManagers(
const std::string& family_name);
std::shared_ptr<minikin::FontFamily> CreateMinikinFontFamily(
const sk_sp<SkFontMgr>& manager,
const std::string& family_name);

View File

@ -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<SkTypeface> Paragraph::GetDefaultSkiaTypeface(const TextStyle& style) {

View File

@ -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<uint16_t> text_;

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#include <vector>
#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<std::string>({font_family});
result.font_size = font_size;
result.locale = locale;
result.height = line_height;

View File

@ -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;

View File

@ -22,7 +22,8 @@
namespace txt {
TextStyle::TextStyle() : font_family(GetDefaultFontFamily()) {}
TextStyle::TextStyle()
: font_families(std::vector<std::string>(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])

View File

@ -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<std::string> font_families;
double font_size = 14.0;
double letter_spacing = 0.0;
double word_spacing = 0.0;

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#include <iostream>
#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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(1, "Roboto");
// text_style.font_families = std::vector<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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