Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Strut implementation #7414

Merged
merged 27 commits into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
09f9aba
Initial strut implementation.
GaryQian Jan 9, 2019
d3196e5
Refactor strut calculation into own method.
GaryQian Jan 9, 2019
51a28f1
Prevent negative leakage and organization
GaryQian Jan 9, 2019
c3589ef
Working strut
GaryQian Jan 9, 2019
85be39a
Merge branch 'master' of github.com:flutter/engine into strut
GaryQian Jan 10, 2019
bf54ba8
Dumping to check cirrus
GaryQian Jan 10, 2019
178b3f6
Disable running strut tests as they are currently platform dependent
GaryQian Jan 10, 2019
ab89175
Simplify line height calculation
GaryQian Jan 10, 2019
953ba8a
API rework part 1
GaryQian Jan 11, 2019
a4aa81e
Modified API for separate strut properties and interitance.
GaryQian Jan 11, 2019
caa8568
Analyzer
GaryQian Jan 11, 2019
8840c92
Fix comments and nits
GaryQian Jan 14, 2019
b255a06
Support font fallback in strut
GaryQian Jan 14, 2019
408da28
Use list of font families for strut
GaryQian Jan 14, 2019
5b1d986
Merge branch 'master' of github.com:flutter/engine into strut
GaryQian Jan 14, 2019
f792504
Working strut, tests still flake
GaryQian Jan 15, 2019
c1bc471
Locally passing strut tests
GaryQian Jan 15, 2019
8a65bc1
Disable strut tests to avoid platform differences.
GaryQian Jan 15, 2019
f85ec73
Add additional docs
GaryQian Jan 16, 2019
d99d432
Fix minor issues
GaryQian Jan 16, 2019
e7753ff
Improve comments
GaryQian Jan 17, 2019
b19fcef
Merge branch 'master' of github.com:flutter/engine into strut
GaryQian Jan 18, 2019
b4813a2
Remove negative check in strut lineHeight
GaryQian Jan 18, 2019
aab2040
Change line_height to height
GaryQian Jan 28, 2019
b6a7bf2
Change a few lineHeight->height that were missed
GaryQian Jan 28, 2019
7968c51
Remove extra setting of strut properties:
GaryQian Jan 29, 2019
bd21fff
Merge branch 'master' of github.com:flutter/engine into strut
GaryQian Jan 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 221 additions & 31 deletions lib/ui/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ class TextStyle {

// This encoding must match the C++ version ParagraphBuilder::build.
//
// The encoded array buffer has 5 elements.
// The encoded array buffer has 6 elements.
//
// - Element 0: A bit mask indicating which fields are non-null.
// Bit 0 is unused. Bits 1-n are set if the corresponding index in the
Expand All @@ -506,21 +506,24 @@ class TextStyle {
//
// - Element 1: The enum index of the |textAlign|.
//
// - Element 2: The index of the |fontWeight|.
// - Element 2: The enum index of the |textDirection|.
//
// - Element 3: The enum index of the |fontStyle|.
// - Element 3: The index of the |fontWeight|.
//
// - Element 4: The value of |maxLines|.
// - Element 4: The enum index of the |fontStyle|.
//
// - Element 5: The value of |maxLines|.
//
Int32List _encodeParagraphStyle(
TextAlign textAlign,
TextDirection textDirection,
FontWeight fontWeight,
FontStyle fontStyle,
int maxLines,
String fontFamily,
double fontSize,
double lineHeight,
double height,
FontWeight fontWeight,
FontStyle fontStyle,
StrutStyle strutStyle,
String ellipsis,
Locale locale,
) {
Expand Down Expand Up @@ -553,18 +556,22 @@ Int32List _encodeParagraphStyle(
result[0] |= 1 << 7;
// Passed separately to native.
}
if (lineHeight != null) {
if (height != null) {
result[0] |= 1 << 8;
// Passed separately to native.
}
if (ellipsis != null) {
if (strutStyle != null) {
result[0] |= 1 << 9;
// Passed separately to native.
}
if (locale != null) {
if (ellipsis != null) {
result[0] |= 1 << 10;
// Passed separately to native.
}
if (locale != null) {
result[0] |= 1 << 11;
// Passed separately to native.
}
return result;
}

Expand All @@ -584,12 +591,6 @@ class ParagraphStyle {
/// directionality of the paragraph, as well as the meaning of
/// [TextAlign.start] and [TextAlign.end] in the `textAlign` field.
///
/// * `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).
///
/// * `maxLines`: The maximum number of lines painted. Lines beyond this
/// number are silently dropped. For example, if `maxLines` is 1, then only
/// one line is rendered. If `maxLines` is null, but `ellipsis` is not null,
Expand All @@ -603,8 +604,23 @@ class ParagraphStyle {
/// * `fontSize`: The size of glyphs (in logical pixels) to use when painting
/// the text.
///
/// * `lineHeight`: The minimum height of the line boxes, as a multiple of the
/// font size.
/// * `height`: The minimum height of the line boxes, as a multiple of the
/// font size. The lines of the paragraph will be at least
/// `(height + leading) * fontSize` tall when fontSize
/// is not null. When fontSize is null, there is no minimum line height. Tall
/// glyphs due to baseline alignment or large [TextStyle.fontSize] may cause
/// the actual line height after layout to be taller than specified here.
/// [fontSize] must be provided for this property to take effect.
///
/// * `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).
///
/// * `strutStyle`: The properties of the strut. Strut defines a set of minimum
/// vertical line height related metrics and can be used to obtain more
/// advanced line spacing behavior.
///
/// * `ellipsis`: String used to ellipsize overflowing text. If `maxLines` is
/// not null, then the `ellipsis`, if any, is applied to the last rendered
Expand All @@ -619,36 +635,40 @@ class ParagraphStyle {
ParagraphStyle({
TextAlign textAlign,
TextDirection textDirection,
FontWeight fontWeight,
FontStyle fontStyle,
int maxLines,
String fontFamily,
double fontSize,
double lineHeight,
double height,
FontWeight fontWeight,
FontStyle fontStyle,
StrutStyle strutStyle,
String ellipsis,
Locale locale,
}) : _encoded = _encodeParagraphStyle(
textAlign,
textDirection,
fontWeight,
fontStyle,
maxLines,
fontFamily,
fontSize,
lineHeight,
height,
fontWeight,
fontStyle,
strutStyle,
ellipsis,
locale,
),
_fontFamily = fontFamily,
_fontSize = fontSize,
_lineHeight = lineHeight,
_height = height,
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale;

final Int32List _encoded;
final String _fontFamily;
final double _fontSize;
final double _lineHeight;
final double _height;
final StrutStyle _strutStyle;
final String _ellipsis;
final Locale _locale;

Expand All @@ -661,7 +681,8 @@ class ParagraphStyle {
final ParagraphStyle typedOther = other;
if (_fontFamily != typedOther._fontFamily ||
_fontSize != typedOther._fontSize ||
_lineHeight != typedOther._lineHeight ||
_height != typedOther._height ||
_strutStyle != typedOther._strutStyle ||
_ellipsis != typedOther._ellipsis ||
_locale != typedOther._locale)
return false;
Expand All @@ -673,7 +694,7 @@ class ParagraphStyle {
}

@override
int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _lineHeight, _ellipsis, _locale);
int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _height, _ellipsis, _locale);

@override
String toString() {
Expand All @@ -685,13 +706,172 @@ class ParagraphStyle {
'maxLines: ${ _encoded[0] & 0x020 == 0x020 ? _encoded[5] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x040 == 0x040 ? _fontFamily : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x080 == 0x080 ? _fontSize : "unspecified"}, '
'lineHeight: ${ _encoded[0] & 0x100 == 0x100 ? "${_lineHeight}x" : "unspecified"}, '
'height: ${ _encoded[0] & 0x100 == 0x100 ? "${_height}x" : "unspecified"}, '
'ellipsis: ${ _encoded[0] & 0x200 == 0x200 ? "\"$_ellipsis\"" : "unspecified"}, '
'locale: ${ _encoded[0] & 0x400 == 0x400 ? _locale : "unspecified"}'
')';
}
}

// Serialize strut properties into ByteData. This encoding errs towards
// compactness. The first 8 bits is a bitmask that records which properties
// are null. The rest of the values are encoded in the same order encountered
// in the bitmask. The final returned value truncates any unused bytes
// at the end.
//
// We serialize this more thoroughly than ParagraphStyle because it is
// much more likely that the strut is empty/null and we wish to add
// minimal overhead for non-strut cases.
ByteData _encodeStrut(
String fontFamily,
List<String> fontFamilyFallback,
double fontSize,
double height,
double leading,
FontWeight fontWeight,
FontStyle fontStyle,
bool forceStrutHeight) {
if (fontFamily == null &&
fontSize == null &&
height == null &&
leading == null &&
fontWeight == null &&
fontStyle == null &&
forceStrutHeight == null)
return ByteData(0);

final ByteData data = ByteData(15); // Max size is 15 bytes
int bitmask = 0; // 8 bit mask
int byteCount = 1;
if (fontWeight != null) {
bitmask |= 1 << 0;
data.setInt8(byteCount, fontWeight.index);
byteCount += 1;
}
if (fontStyle != null) {
bitmask |= 1 << 1;
data.setInt8(byteCount, fontStyle.index);
byteCount += 1;
}
if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)){
bitmask |= 1 << 2;
// passed separately to native
}
if (fontSize != null) {
bitmask |= 1 << 3;
data.setFloat32(byteCount, fontSize, _kFakeHostEndian);
byteCount += 4;
}
if (height != null) {
bitmask |= 1 << 4;
data.setFloat32(byteCount, height, _kFakeHostEndian);
byteCount += 4;
}
if (leading != null) {
bitmask |= 1 << 5;
data.setFloat32(byteCount, leading, _kFakeHostEndian);
byteCount += 4;
}
if (forceStrutHeight != null) {
bitmask |= 1 << 6;
// We store this boolean directly in the bitmask since there is
// extra space in the 16 bit int.
bitmask |= (forceStrutHeight ? 1 : 0) << 7;
}

data.setInt8(0, bitmask);

return ByteData.view(data.buffer, 0, byteCount);
}

class StrutStyle {
/// Creates a new StrutStyle object.
///
/// * `fontFamily`: The name of the font to use when painting the text (e.g.,
/// Roboto).
///
/// * `fontFamilyFallback`: An ordered list of font family names that will be searched for when
/// the font in `fontFamily` cannot be found.
///
/// * `fontSize`: The size of glyphs (in logical pixels) to use when painting
/// the text.
///
/// * `lineHeight`: The minimum height of the line boxes, as a multiple of the
/// font size. The lines of the paragraph will be at least
/// `(lineHeight + leading) * fontSize` tall when fontSize
/// is not null. When fontSize is null, there is no minimum line height. Tall
/// glyphs due to baseline alignment or large [TextStyle.fontSize] may cause
/// the actual line height after layout to be taller than specified here.
/// [fontSize] must be provided for this property to take effect.
///
/// * `leading`: The minimum amount of leading between lines as a multiple of
/// the font size. [fontSize] must be provided for this property to take effect.
///
/// * `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).
///
/// * `forceStrutHeight`: When true, the paragraph will force all lines to be exactly
/// `(lineHeight + leading) * fontSize` tall from baseline to baseline.
/// [TextStyle] is no longer able to influence the line height, and any tall
/// glyphs may overlap with lines above. If a [fontFamily] is specified, the
/// total ascent of the first line will be the min of the `Ascent + half-leading`
/// of the [fontFamily] and `(lineHeight + leading) * fontSize`. Otherwise, it
/// will be determined by the Ascent + half-leading of the first text.
StrutStyle({
String fontFamily,
List<String> fontFamilyFallback,
double fontSize,
double height,
double leading,
FontWeight fontWeight,
FontStyle fontStyle,
bool forceStrutHeight,
}) : _encoded = _encodeStrut(
fontFamily,
fontFamilyFallback,
fontSize,
height,
leading,
fontWeight,
fontStyle,
forceStrutHeight,
),
_fontFamily = fontFamily,
_fontFamilyFallback = fontFamilyFallback;

final ByteData _encoded; // Most of the data for strut is encoded.
final String _fontFamily;
final List<String> _fontFamilyFallback;


@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final StrutStyle typedOther = other;
if (_fontFamily != typedOther._fontFamily)
return false;
final Int8List encodedList = _encoded.buffer.asInt8List();
final Int8List otherEncodedList = typedOther._encoded.buffer.asInt8List();
for (int index = 0; index < _encoded.lengthInBytes; index += 1) {
if (encodedList[index] != otherEncodedList[index])
return false;
}
if (!_listEquals<String>(_fontFamilyFallback, typedOther._fontFamilyFallback))
return false;
return true;
}

@override
int get hashCode => hashValues(hashList(_encoded.buffer.asInt8List()), _fontFamily);

}

/// A direction in which text flows.
///
/// Some languages are written from the left to the right (for example, English,
Expand Down Expand Up @@ -1207,8 +1387,18 @@ class ParagraphBuilder extends NativeFieldWrapperClass2 {
/// Creates a [ParagraphBuilder] object, which is used to create a
/// [Paragraph].
@pragma('vm:entry-point')
ParagraphBuilder(ParagraphStyle style) { _constructor(style._encoded, style._fontFamily, style._fontSize, style._lineHeight, style._ellipsis, _encodeLocale(style._locale)); }
void _constructor(Int32List encoded, String fontFamily, double fontSize, double lineHeight, String ellipsis, String locale) native 'ParagraphBuilder_constructor';
ParagraphBuilder(ParagraphStyle style) {
List<String> strutFontFamilies;
if (style._strutStyle != null) {
strutFontFamilies = <String>[];
if (style._strutStyle._fontFamily != null)
strutFontFamilies.add(style._strutStyle._fontFamily);
if (style._strutStyle._fontFamilyFallback != null)
strutFontFamilies.addAll(style._strutStyle._fontFamilyFallback);
}
_constructor(style._encoded, style._strutStyle?._encoded, style._fontFamily, strutFontFamilies, style._fontSize, style._height, style._ellipsis, _encodeLocale(style._locale));
}
void _constructor(Int32List encoded, ByteData strutData, String fontFamily, List<dynamic> strutFontFamily, double fontSize, double height, String ellipsis, String locale) native 'ParagraphBuilder_constructor';

/// Applies the given style to the added text until [pop] is called.
///
Expand Down
Loading