Skip to content

Commit

Permalink
Add an API for variable font axes in TextStyle and connect it to SkPa…
Browse files Browse the repository at this point in the history
…ragraph (flutter#32245)
  • Loading branch information
jason-simmons authored Mar 28, 2022
1 parent 63cd383 commit d348e4d
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 11 deletions.
94 changes: 90 additions & 4 deletions lib/ui/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,67 @@ class FontFeature {
String toString() => "FontFeature('$feature', $value)";
}

/// An axis tag and value that can be used to customize variable fonts.
///
/// Some fonts are variable fonts that can generate a range of different
/// font faces by altering the values of the font's design axes.
///
/// See https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
///
/// Example:
/// `TextStyle(fontVariations: <FontVariation>[FontVariation('wght', 800.0)])`
class FontVariation {
/// Creates a [FontVariation] object, which can be added to a [TextStyle] to
/// change the variable attributes of a font.
///
/// `axis` is the four-character tag that identifies the design axis.
/// These tags are specified by font formats such as OpenType.
/// See https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg
///
/// `value` is the value that the axis will be set to. The behavior
/// depends on how the font implements the axis.
const FontVariation(
this.axis,
this.value,
) : assert(axis != null),
assert(axis.length == 4, 'Axis tag must be exactly four characters long.'),
assert(value != null);

/// The tag that identifies the design axis. Must consist of 4 ASCII
/// characters.
final String axis;

/// The value assigned to this design axis.
///
/// The range of usable values depends on the specification of the axis.
final double value;

static const int _kEncodedSize = 8;

void _encode(ByteData byteData) {
assert(axis.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F));
for (int i = 0; i < 4; i++) {
byteData.setUint8(i, axis.codeUnitAt(i));
}
byteData.setFloat32(4, value, _kFakeHostEndian);
}

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is FontVariation
&& other.axis == axis
&& other.value == value;
}

@override
int get hashCode => hashValues(axis, value);

@override
String toString() => "FontVariation('$axis', $value)";
}

/// Whether and how to align text horizontally.
// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
enum TextAlign {
Expand Down Expand Up @@ -1255,6 +1316,7 @@ Int32List _encodeTextStyle(
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
List<FontVariation>? fontVariations,
) {
final Int32List result = Int32List(9);
// The 0th bit of result[0] is reserved for leadingDistribution.
Expand Down Expand Up @@ -1330,6 +1392,10 @@ Int32List _encodeTextStyle(
result[0] |= 1 << 18;
// Passed separately to native.
}
if (fontVariations != null) {
result[0] |= 1 << 19;
// Passed separately to native.
}

return result;
}
Expand Down Expand Up @@ -1371,6 +1437,7 @@ class TextStyle {
/// * `background`: The paint drawn as a background for the text.
/// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
/// * `fontFeatures`: The font features that should be applied to the text.
/// * `fontVariations`: The font variations that should be applied to the text.
TextStyle({
Color? color,
TextDecoration? decoration,
Expand All @@ -1392,6 +1459,7 @@ class TextStyle {
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
List<FontVariation>? fontVariations,
}) : assert(color == null || foreground == null,
'Cannot provide both a color and a foreground\n'
'The color argument is just a shorthand for "foreground: Paint()..color = color".'
Expand All @@ -1416,6 +1484,7 @@ class TextStyle {
foreground,
shadows,
fontFeatures,
fontVariations,
),
_leadingDistribution = leadingDistribution,
_fontFamily = fontFamily ?? '',
Expand All @@ -1429,7 +1498,8 @@ class TextStyle {
_background = background,
_foreground = foreground,
_shadows = shadows,
_fontFeatures = fontFeatures;
_fontFeatures = fontFeatures,
_fontVariations = fontVariations;

final Int32List _encoded;
final String _fontFamily;
Expand All @@ -1444,6 +1514,7 @@ class TextStyle {
final Paint? _foreground;
final List<Shadow>? _shadows;
final List<FontFeature>? _fontFeatures;
final List<FontVariation>? _fontVariations;
final TextLeadingDistribution? _leadingDistribution;

@override
Expand All @@ -1464,11 +1535,12 @@ class TextStyle {
&& _listEquals<int>(other._encoded, _encoded)
&& _listEquals<Shadow>(other._shadows, _shadows)
&& _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
&& _listEquals<FontFeature>(other._fontFeatures, _fontFeatures);
&& _listEquals<FontFeature>(other._fontFeatures, _fontFeatures)
&& _listEquals<FontVariation>(other._fontVariations, _fontVariations);
}

@override
int get hashCode => hashValues(hashList(_encoded), _leadingDistribution, _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures));
int get hashCode => hashValues(hashList(_encoded), _leadingDistribution, _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures), hashList(_fontVariations));

@override
String toString() {
Expand Down Expand Up @@ -1496,7 +1568,8 @@ class TextStyle {
'background: ${ _encoded[0] & 0x08000 == 0x08000 ? _background : "unspecified"}, '
'foreground: ${ _encoded[0] & 0x10000 == 0x10000 ? _foreground : "unspecified"}, '
'shadows: ${ _encoded[0] & 0x20000 == 0x20000 ? _shadows : "unspecified"}, '
'fontFeatures: ${ _encoded[0] & 0x40000 == 0x40000 ? _fontFeatures : "unspecified"}'
'fontFeatures: ${ _encoded[0] & 0x40000 == 0x40000 ? _fontFeatures : "unspecified"}, '
'fontVariations: ${ _encoded[0] & 0x80000 == 0x80000 ? _fontVariations : "unspecified"}'
')';
}
}
Expand Down Expand Up @@ -2868,6 +2941,17 @@ class ParagraphBuilder extends NativeFieldWrapperClass1 {
}
}

ByteData? encodedFontVariations;
final List<FontVariation>? fontVariations = style._fontVariations;
if (fontVariations != null) {
encodedFontVariations = ByteData(fontVariations.length * FontVariation._kEncodedSize);
int byteOffset = 0;
for (final FontVariation variation in fontVariations) {
variation._encode(ByteData.view(encodedFontVariations.buffer, byteOffset, FontVariation._kEncodedSize));
byteOffset += FontVariation._kEncodedSize;
}
}

_pushStyle(
encoded,
fullFontFamilies,
Expand All @@ -2883,6 +2967,7 @@ class ParagraphBuilder extends NativeFieldWrapperClass1 {
style._foreground?._data,
Shadow._encodeShadows(style._shadows),
encodedFontFeatures,
encodedFontVariations,
);
}

Expand All @@ -2901,6 +2986,7 @@ class ParagraphBuilder extends NativeFieldWrapperClass1 {
ByteData? foregroundData,
ByteData shadowsData,
ByteData? fontFeaturesData,
ByteData? fontVariationsData,
) native 'ParagraphBuilder_pushStyle';

static String _encodeLocale(Locale? locale) => locale?.toString() ?? '';
Expand Down
31 changes: 30 additions & 1 deletion lib/ui/text/paragraph_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const int tsBackgroundIndex = 15;
const int tsForegroundIndex = 16;
const int tsTextShadowsIndex = 17;
const int tsFontFeaturesIndex = 18;
const int tsFontVariationsIndex = 19;

const int tsLeadingDistributionMask = 1 << tsLeadingDistributionIndex;
const int tsColorMask = 1 << tsColorIndex;
Expand All @@ -71,6 +72,7 @@ const int tsBackgroundMask = 1 << tsBackgroundIndex;
const int tsForegroundMask = 1 << tsForegroundIndex;
const int tsTextShadowsMask = 1 << tsTextShadowsIndex;
const int tsFontFeaturesMask = 1 << tsFontFeaturesIndex;
const int tsFontVariationsMask = 1 << tsFontVariationsIndex;

// ParagraphStyle

Expand Down Expand Up @@ -114,6 +116,10 @@ constexpr uint32_t kBlurOffset = 3;
constexpr uint32_t kBytesPerFontFeature = 8;
constexpr uint32_t kFontFeatureTagLength = 4;

// FontVariation decoding
constexpr uint32_t kBytesPerFontVariation = 8;
constexpr uint32_t kFontVariationTagLength = 4;

// Strut decoding
const int sFontWeightIndex = 0;
const int sFontStyleIndex = 1;
Expand Down Expand Up @@ -365,6 +371,24 @@ void decodeFontFeatures(Dart_Handle font_features_data,
}
}

void decodeFontVariations(Dart_Handle font_variations_data,
txt::FontVariations& font_variations) { // NOLINT
tonic::DartByteData byte_data(font_variations_data);
FML_CHECK(byte_data.length_in_bytes() % kBytesPerFontVariation == 0);

size_t variation_count = byte_data.length_in_bytes() / kBytesPerFontVariation;
for (size_t variation_index = 0; variation_index < variation_count;
++variation_index) {
size_t variation_offset = variation_index * kBytesPerFontVariation;
const char* variation_bytes =
static_cast<const char*>(byte_data.data()) + variation_offset;
std::string tag(variation_bytes, kFontVariationTagLength);
float value = *(reinterpret_cast<const float*>(variation_bytes +
kFontVariationTagLength));
font_variations.SetAxisValue(tag, value);
}
}

void ParagraphBuilder::pushStyle(tonic::Int32List& encoded,
const std::vector<std::string>& fontFamilies,
double fontSize,
Expand All @@ -378,7 +402,8 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded,
Dart_Handle foreground_objects,
Dart_Handle foreground_data,
Dart_Handle shadows_data,
Dart_Handle font_features_data) {
Dart_Handle font_features_data,
Dart_Handle font_variations_data) {
FML_DCHECK(encoded.num_elements() == 9);

int32_t mask = encoded[0];
Expand Down Expand Up @@ -483,6 +508,10 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded,
decodeFontFeatures(font_features_data, style.font_features);
}

if (mask & tsFontVariationsMask) {
decodeFontVariations(font_variations_data, style.font_variations);
}

m_paragraphBuilder->PushStyle(style);
}

Expand Down
3 changes: 2 additions & 1 deletion lib/ui/text/paragraph_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class ParagraphBuilder : public RefCountedDartWrappable<ParagraphBuilder> {
Dart_Handle foreground_objects,
Dart_Handle foreground_data,
Dart_Handle shadows_data,
Dart_Handle font_features_data);
Dart_Handle font_features_data,
Dart_Handle font_variations_data);

void pop();

Expand Down
30 changes: 30 additions & 0 deletions lib/web_ui/lib/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,33 @@ class FontFeature {
String toString() => "FontFeature('$feature', $value)";
}

class FontVariation {
const FontVariation(
this.axis,
this.value,
) : assert(axis != null),
assert(axis.length == 4, 'Axis tag must be exactly four characters long.'),
assert(value != null);

final String axis;
final double value;

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is FontVariation
&& other.axis == axis
&& other.value == value;
}

@override
int get hashCode => hashValues(axis, value);

@override
String toString() => "FontVariation('$axis', $value)";
}

// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
enum TextAlign {
left,
Expand Down Expand Up @@ -312,6 +339,9 @@ abstract class TextStyle {
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
// TODO(jsimmons): implement fontVariations for web
// ignore: avoid_unused_constructor_parameters
List<FontVariation>? fontVariations,
}) {
if (engine.useCanvasKit) {
return engine.CkTextStyle(
Expand Down
Loading

0 comments on commit d348e4d

Please sign in to comment.