Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 29 additions & 2 deletions engine/src/flutter/lib/ui/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,20 @@ enum TextAlign {
end,
}

/// The behavior of soft hyphens (U+00AD) at a line break.
///
/// This does not change where lines break, since Flutter always treats U+00AD
/// as a line-break opportunity.
enum Hyphens {
/// A hyphen glyph is rendered at the end of a line that breaks on a soft
/// hyphen (U+00AD). Soft hyphens elsewhere remain invisible.
manual,

/// No hyphen glyph is rendered for soft hyphens. The line still breaks at
/// U+00AD; only the glyph is suppressed.
hidden,
}

/// A horizontal line used for aligning text.
enum TextBaseline {
/// The horizontal line used to align the bottom of glyphs for alphabetic characters.
Expand Down Expand Up @@ -1937,6 +1951,8 @@ class TextStyle {
//
// - Element 6: The encoded value of |textHeightBehavior|, except its leading
// distribution.
//
// - Element 7: The enum index of the |hyphens|.
Int32List _encodeParagraphStyle(
TextAlign? textAlign,
TextDirection? textDirection,
Expand All @@ -1950,8 +1966,9 @@ Int32List _encodeParagraphStyle(
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
Hyphens? hyphens,
) {
final result = Int32List(7); // also update paragraph_builder.cc
final result = Int32List(8); // also update paragraph_builder.cc
if (textAlign != null) {
result[0] |= 1 << 1;
result[1] = textAlign.index;
Expand Down Expand Up @@ -2002,6 +2019,10 @@ Int32List _encodeParagraphStyle(
result[0] |= 1 << 12;
// Passed separately to native.
}
if (hyphens != null) {
result[0] |= 1 << 13;
result[7] = hyphens.index;
}
return result;
}

Expand Down Expand Up @@ -2068,6 +2089,9 @@ class ParagraphStyle {
/// considered equivalent and turn off this behavior.
///
/// * `locale`: The locale used to select region-specific glyphs.
///
/// * `hyphens`: Whether to render a hyphen glyph for soft hyphens (U+00AD)
/// that fall at a line break. Defaults to [Hyphens.manual].
ParagraphStyle({
TextAlign? textAlign,
TextDirection? textDirection,
Expand All @@ -2081,6 +2105,7 @@ class ParagraphStyle {
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
Hyphens? hyphens,
}) : _encoded = _encodeParagraphStyle(
textAlign,
textDirection,
Expand All @@ -2094,6 +2119,7 @@ class ParagraphStyle {
strutStyle,
ellipsis,
locale,
hyphens,
),
_fontFamily = fontFamily,
_fontSize = fontSize,
Expand Down Expand Up @@ -2157,7 +2183,8 @@ class ParagraphStyle {
'height: ${_encoded[0] & 0x200 == 0x200 ? "${_height}x" : "unspecified"}, '
'strutStyle: ${_encoded[0] & 0x400 == 0x400 ? _strutStyle : "unspecified"}, '
'ellipsis: ${_encoded[0] & 0x800 == 0x800 ? '"$_ellipsis"' : "unspecified"}, '
'locale: ${_encoded[0] & 0x1000 == 0x1000 ? _locale : "unspecified"}'
'locale: ${_encoded[0] & 0x1000 == 0x1000 ? _locale : "unspecified"}, '
'hyphens: ${_encoded[0] & 0x2000 == 0x2000 ? Hyphens.values[_encoded[7]] : "unspecified"}'
')';
}
}
Expand Down
12 changes: 12 additions & 0 deletions engine/src/flutter/lib/ui/text/paragraph_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ const int kPSHeightIndex = 9;
const int kPSStrutStyleIndex = 10;
const int kPSEllipsisIndex = 11;
const int kPSLocaleIndex = 12;
// render_soft_hyphens is the first field whose mask bit and encode slot differ:
// it claims the next free mask bit (13), but stores its value in the next free
// encode slot (7), since slots 7-12 are reused only as mask-bit positions.
const int kPSRenderSoftHyphensIndex = 13;
const int kPSRenderSoftHyphensEncodeIndex = 7;

const int kPSTextAlignMask = 1 << kPSTextAlignIndex;
const int kPSTextDirectionMask = 1 << kPSTextDirectionIndex;
Expand All @@ -103,6 +108,7 @@ const int kPSTextHeightBehaviorMask = 1 << kPSTextHeightBehaviorIndex;
const int kPSStrutStyleMask = 1 << kPSStrutStyleIndex;
const int kPSEllipsisMask = 1 << kPSEllipsisIndex;
const int kPSLocaleMask = 1 << kPSLocaleIndex;
const int kPSRenderSoftHyphensMask = 1 << kPSRenderSoftHyphensIndex;

// TextShadows decoding

Expand Down Expand Up @@ -273,6 +279,12 @@ ParagraphBuilder::ParagraphBuilder(
if (mask & kPSMaxLinesMask) {
style.max_lines = encoded[kPSMaxLinesIndex];
}

if (mask & kPSRenderSoftHyphensMask) {
// Hyphens.manual (0) renders the soft hyphen glyph; Hyphens.hidden (1)
// suppresses it.
style.render_soft_hyphens = encoded[kPSRenderSoftHyphensEncodeIndex] == 0;
}
}

if (mask & kPSStrutStyleMask) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ class CanvasKitRenderer extends Renderer {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
}) => isWebParagraphEnabled
? WebParagraphStyle(
textAlign: textAlign,
Expand All @@ -426,6 +427,7 @@ class CanvasKitRenderer extends Renderer {
strutStyle: strutStyle as WebStrutStyle?,
ellipsis: ellipsis,
locale: locale,
hyphens: hyphens,
)
: CkParagraphStyle(
textAlign: textAlign,
Expand All @@ -440,6 +442,7 @@ class CanvasKitRenderer extends Renderer {
strutStyle: strutStyle,
ellipsis: ellipsis,
locale: locale,
hyphens: hyphens,
);

@override
Expand Down
19 changes: 16 additions & 3 deletions engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CkParagraphStyle implements ui.ParagraphStyle {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
}) : skParagraphStyle = toSkParagraphStyle(
textAlign,
textDirection,
Expand All @@ -59,7 +60,8 @@ class CkParagraphStyle implements ui.ParagraphStyle {
_textHeightBehavior = textHeightBehavior,
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale;
_locale = locale,
_hyphens = hyphens;

final SkParagraphStyle skParagraphStyle;

Expand All @@ -76,6 +78,14 @@ class CkParagraphStyle implements ui.ParagraphStyle {
final ui.StrutStyle? _strutStyle;
final String? _ellipsis;
final ui.Locale? _locale;
// TODO(dbebawy): hyphens is accepted and stored but not honored here yet.
// Neither Hyphens.manual nor Hyphens.hidden takes effect on CanvasKit until a
// renderSoftHyphens setter is exposed on the CanvasKit ParagraphStyle binding
// (the native engine wires this via SkParagraph::setRenderSoftHyphens).
// https://github.com/flutter/flutter/issues/193506
// Customizing the hyphen string is separate future work:
// https://github.com/flutter/flutter/issues/189617
final ui.Hyphens? _hyphens;

static SkTextStyleProperties toSkTextStyleProperties(
String? fontFamily,
Expand Down Expand Up @@ -263,7 +273,8 @@ class CkParagraphStyle implements ui.ParagraphStyle {
other._textHeightBehavior == _textHeightBehavior &&
other._strutStyle == _strutStyle &&
other._ellipsis == _ellipsis &&
other._locale == _locale;
other._locale == _locale &&
other._hyphens == _hyphens;
}

@override
Expand All @@ -282,6 +293,7 @@ class CkParagraphStyle implements ui.ParagraphStyle {
_strutStyle,
_ellipsis,
_locale,
_hyphens,
);
}

Expand All @@ -304,7 +316,8 @@ class CkParagraphStyle implements ui.ParagraphStyle {
'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, '
'strutStyle: ${_strutStyle ?? "unspecified"}, '
'ellipsis: ${_ellipsis != null ? '"$_ellipsis"' : "unspecified"}, '
'locale: ${_locale ?? "unspecified"}'
'locale: ${_locale ?? "unspecified"}, '
'hyphens: ${_hyphens ?? "unspecified"}'
')';
return true;
}());
Expand Down
1 change: 1 addition & 0 deletions engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ abstract class Renderer {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
});

ui.StrutStyle createStrutStyle({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
}) : _textAlign = textAlign,
_textDirection = textDirection,
_maxLines = maxLines,
Expand All @@ -737,7 +738,8 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
_fontStyle = fontStyle,
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale;
_locale = locale,
_hyphens = hyphens;

(ParagraphStyleHandle, SkwasmNativeTextStyle) createNative() {
final ParagraphStyleHandle handle = paragraphStyleCreate();
Expand Down Expand Up @@ -835,6 +837,14 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
final ui.StrutStyle? _strutStyle;
final String? _ellipsis;
final ui.Locale? _locale;
// TODO(dbebawy): hyphens is accepted and stored but not honored here yet.
// Neither Hyphens.manual nor Hyphens.hidden takes effect on Skwasm until a
// renderSoftHyphens setter is exposed on the Skwasm ParagraphStyle binding
// (the native engine wires this via SkParagraph::setRenderSoftHyphens).
// https://github.com/flutter/flutter/issues/193506
// Customizing the hyphen string is separate future work:
// https://github.com/flutter/flutter/issues/189617
final ui.Hyphens? _hyphens;

@override
bool operator ==(Object other) {
Expand All @@ -856,7 +866,8 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
other._textHeightBehavior == _textHeightBehavior &&
other._strutStyle == _strutStyle &&
other._ellipsis == _ellipsis &&
other._locale == _locale;
other._locale == _locale &&
other._hyphens == _hyphens;
}

@override
Expand All @@ -874,6 +885,7 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
_strutStyle,
_ellipsis,
_locale,
_hyphens,
);
}

Expand All @@ -896,7 +908,8 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle {
'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, '
'strutStyle: ${_strutStyle ?? "unspecified"}, '
'ellipsis: ${_ellipsis != null ? '"$_ellipsis"' : "unspecified"}, '
'locale: ${_locale ?? "unspecified"}'
'locale: ${_locale ?? "unspecified"}, '
'hyphens: ${_hyphens ?? "unspecified"}'
')';
return true;
}());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class SkwasmRenderer extends Renderer {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
}) => SkwasmParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
Expand All @@ -191,6 +192,7 @@ class SkwasmRenderer extends Renderer {
strutStyle: strutStyle,
ellipsis: ellipsis,
locale: locale,
hyphens: hyphens,
);

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class SkwasmRenderer extends Renderer {
ui.StrutStyle? strutStyle,
String? ellipsis,
ui.Locale? locale,
ui.Hyphens? hyphens,
}) {
throw UnimplementedError('Skwasm not implemented on this platform.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class WebParagraphStyle implements ui.ParagraphStyle {
ui.Color? color,
ui.StrutStyle? strutStyle,
this.textHeightBehavior,
this.hyphens,
}) : _strutStyle = strutStyle as WebStrutStyle?,
_textStyle = WebTextStyle(
fontFamily: fontFamily,
Expand All @@ -60,6 +61,11 @@ class WebParagraphStyle implements ui.ParagraphStyle {

final ui.TextHeightBehavior? textHeightBehavior;

// TODO(dbebawy): honor hyphens in the pure-Dart line-breaker/painter (no

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this Todo still valid?

@dbebawy dbebawy Jul 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still valid — WebParagraph is the experimental pure-Dart engine and doesn't emit a soft-hyphen glyph at breaks yet (no Skia path), so it accepts + stores the value. Not gated on Skia; it needs its own line-breaker/painter support.

Update: the TODO now points at #193506, which tracks the web side separately.

// soft-hyphen glyph emitted at a break yet). Accepted and stored for now.
// https://github.com/flutter/flutter/issues/193506
final ui.Hyphens? hyphens;

WebStrutStyle? get strutStyle => _strutStyle;
final WebStrutStyle? _strutStyle;

Expand All @@ -78,7 +84,8 @@ class WebParagraphStyle implements ui.ParagraphStyle {
ellipsis == other.ellipsis &&
textHeightBehavior == other.textHeightBehavior &&
_strutStyle == other._strutStyle &&
_textStyle == other._textStyle;
_textStyle == other._textStyle &&
hyphens == other.hyphens;
}

@override
Expand All @@ -91,6 +98,7 @@ class WebParagraphStyle implements ui.ParagraphStyle {
textHeightBehavior,
_strutStyle,
_textStyle,
hyphens,
);
}

Expand All @@ -105,6 +113,7 @@ class WebParagraphStyle implements ui.ParagraphStyle {
'maxLines: $maxLines, '
'ellipsis: $ellipsis, '
'textHeightBehavior: $textHeightBehavior, '
'hyphens: $hyphens, '
'strutStyle: $_strutStyle, '
'textAlign: $textAlign'
'textStyle: $_textStyle'
Expand Down
5 changes: 5 additions & 0 deletions engine/src/flutter/lib/web_ui/lib/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ abstract class ParagraphStyle {
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
Hyphens? hyphens,
}) => engine.renderer.createParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
Expand All @@ -394,6 +395,7 @@ abstract class ParagraphStyle {
strutStyle: strutStyle,
ellipsis: ellipsis,
locale: locale,
hyphens: hyphens,
);
}

Expand Down Expand Up @@ -459,6 +461,9 @@ abstract class StrutStyle {
// The order of this enum must match the order of the values in TextDirection.h's TextDirection.
enum TextDirection { rtl, ltr }

/// The behavior of soft hyphens (U+00AD) at a line break.
enum Hyphens { manual, hidden }
Comment thread
dbebawy marked this conversation as resolved.

class TextBox {
const TextBox.fromLTRBD(this.left, this.top, this.right, this.bottom, this.direction);
final double left;
Expand Down
Loading
Loading