Skip to content

Commit 1ddae7f

Browse files
Rusinomdebbar
authored andcommitted
WebParagraph initial commit (flutter#167559)
This is the current (initial) state of WebParagraph project which is an implementation of SkParagraph on top of TextCluster (https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md). Multilined text, mixed LTR/RTL text supported. --------- Co-authored-by: Mouad Debbar <[email protected]>
1 parent 0d77fa2 commit 1ddae7f

22 files changed

+2161
-86
lines changed

engine/src/flutter/ci/licenses_golden/licenses_flutter

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52823,7 +52823,12 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/flutter_view_ma
5282352823
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/global_html_attributes.dart + ../../../flutter/LICENSE
5282452824
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/hot_restart_cache_handler.dart + ../../../flutter/LICENSE
5282552825
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/style_manager.dart + ../../../flutter/LICENSE
52826+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/code_unit_flags.dart + ../../../flutter/LICENSE
52827+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/debug.dart + ../../../flutter/LICENSE
52828+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/layout.dart + ../../../flutter/LICENSE
52829+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/paint.dart + ../../../flutter/LICENSE
5282652830
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/paragraph.dart + ../../../flutter/LICENSE
52831+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/wrapper.dart + ../../../flutter/LICENSE
5282752832
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/window.dart + ../../../flutter/LICENSE
5282852833
ORIGIN: ../../../flutter/lib/web_ui/lib/text.dart + ../../../flutter/LICENSE
5282952834
ORIGIN: ../../../flutter/lib/web_ui/lib/tile_mode.dart + ../../../flutter/LICENSE
@@ -55896,7 +55901,12 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/flutter_view_mana
5589655901
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/global_html_attributes.dart
5589755902
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/hot_restart_cache_handler.dart
5589855903
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/style_manager.dart
55904+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/code_unit_flags.dart
55905+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/debug.dart
55906+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/layout.dart
55907+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/paint.dart
5589955908
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/paragraph.dart
55909+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/web_paragraph/wrapper.dart
5590055910
FILE: ../../../flutter/lib/web_ui/lib/src/engine/window.dart
5590155911
FILE: ../../../flutter/lib/web_ui/lib/text.dart
5590255912
FILE: ../../../flutter/lib/web_ui/lib/tile_mode.dart

engine/src/flutter/lib/web_ui/lib/src/engine.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,10 @@ export 'engine/view_embedder/flutter_view_manager.dart';
162162
export 'engine/view_embedder/global_html_attributes.dart';
163163
export 'engine/view_embedder/hot_restart_cache_handler.dart';
164164
export 'engine/view_embedder/style_manager.dart';
165+
export 'engine/web_paragraph/code_unit_flags.dart';
166+
export 'engine/web_paragraph/debug.dart';
167+
export 'engine/web_paragraph/layout.dart';
168+
export 'engine/web_paragraph/paint.dart';
165169
export 'engine/web_paragraph/paragraph.dart';
170+
export 'engine/web_paragraph/wrapper.dart';
166171
export 'engine/window.dart';

engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ extension type CanvasKit(JSObject _) implements JSObject {
9999
Uint16List? indices,
100100
) => _MakeVertices(mode, positions.toJS, textureCoordinates?.toJS, colors?.toJS, indices?.toJS);
101101

102+
external BidiNamespace get Bidi;
103+
104+
external CodeUnitsNamespace get CodeUnits;
105+
102106
external SkParagraphBuilderNamespace get ParagraphBuilder;
103107
external SkParagraphStyle ParagraphStyle(SkParagraphStyleProperties properties);
104108
external SkTextStyle TextStyle(SkTextStyleProperties properties);
@@ -1778,11 +1782,45 @@ extension type SkPicture(JSObject _) implements JSObject {
17781782

17791783
@JS('cullRect')
17801784
external JSFloat32Array _cullRect();
1785+
17811786
Float32List cullRect() => _cullRect().toDart;
17821787

17831788
external int approximateBytesUsed();
17841789
}
17851790

1791+
extension type BidiRegion(JSObject _) implements JSObject {
1792+
external int get start;
1793+
external int get end;
1794+
external int get level;
1795+
}
1796+
1797+
extension type BidiIndex(JSObject _) implements JSObject {
1798+
external int get index;
1799+
}
1800+
1801+
extension type BidiNamespace(JSObject _) implements JSObject {
1802+
@JS('getBidiRegions')
1803+
external JSArray<JSAny?> _getBidiRegions(String text, SkTextDirection dir);
1804+
List<BidiRegion> getBidiRegions(String text, ui.TextDirection dir) =>
1805+
_getBidiRegions(text, toSkTextDirection(dir)).toDart.cast<BidiRegion>();
1806+
1807+
@JS('reorderVisual')
1808+
// TODO(jlavrova): Use a JSInt32Array return type instead of `List<BidiIndex>`
1809+
external JSArray<JSAny?> _reorderVisual(JSUint8Array visuals);
1810+
List<BidiIndex> reorderVisual(Uint8List visuals) =>
1811+
_reorderVisual(visuals.toJS).toDart.cast<BidiIndex>();
1812+
}
1813+
1814+
extension type CodeUnitInfo(JSObject _) implements JSObject {
1815+
external int get flags;
1816+
}
1817+
1818+
extension type CodeUnitsNamespace(JSObject _) implements JSObject {
1819+
@JS('compute')
1820+
external JSArray<JSAny?> _compute(String text);
1821+
List<CodeUnitInfo> compute(String text) => _compute(text).toDart.cast<CodeUnitInfo>();
1822+
}
1823+
17861824
extension type SkParagraphBuilderNamespace(JSObject _) implements JSObject {
17871825
external SkParagraphBuilder MakeFromFontCollection(
17881826
SkParagraphStyle paragraphStyle,

engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -363,29 +363,31 @@ class CanvasKitRenderer implements Renderer {
363363
List<ui.Shadow>? shadows,
364364
List<ui.FontFeature>? fontFeatures,
365365
List<ui.FontVariation>? fontVariations,
366-
}) => CkTextStyle(
367-
color: color,
368-
decoration: decoration,
369-
decorationColor: decorationColor,
370-
decorationStyle: decorationStyle,
371-
decorationThickness: decorationThickness,
372-
fontWeight: fontWeight,
373-
fontStyle: fontStyle,
374-
textBaseline: textBaseline,
375-
fontFamily: fontFamily,
376-
fontFamilyFallback: fontFamilyFallback,
377-
fontSize: fontSize,
378-
letterSpacing: letterSpacing,
379-
wordSpacing: wordSpacing,
380-
height: height,
381-
leadingDistribution: leadingDistribution,
382-
locale: locale,
383-
background: background as CkPaint?,
384-
foreground: foreground as CkPaint?,
385-
shadows: shadows,
386-
fontFeatures: fontFeatures,
387-
fontVariations: fontVariations,
388-
);
366+
}) => isExperimentalWebParagraph
367+
? WebTextStyle(fontFamily: fontFamily, fontSize: fontSize, color: color)
368+
: CkTextStyle(
369+
color: color,
370+
decoration: decoration,
371+
decorationColor: decorationColor,
372+
decorationStyle: decorationStyle,
373+
decorationThickness: decorationThickness,
374+
fontWeight: fontWeight,
375+
fontStyle: fontStyle,
376+
textBaseline: textBaseline,
377+
fontFamily: fontFamily,
378+
fontFamilyFallback: fontFamilyFallback,
379+
fontSize: fontSize,
380+
letterSpacing: letterSpacing,
381+
wordSpacing: wordSpacing,
382+
height: height,
383+
leadingDistribution: leadingDistribution,
384+
locale: locale,
385+
background: background as CkPaint?,
386+
foreground: foreground as CkPaint?,
387+
shadows: shadows,
388+
fontFeatures: fontFeatures,
389+
fontVariations: fontVariations,
390+
);
389391

390392
@override
391393
ui.ParagraphStyle createParagraphStyle({
@@ -402,7 +404,12 @@ class CanvasKitRenderer implements Renderer {
402404
String? ellipsis,
403405
ui.Locale? locale,
404406
}) => isExperimentalWebParagraph
405-
? WebParagraphStyle()
407+
? WebParagraphStyle(
408+
textDirection: textDirection,
409+
textAlign: textAlign,
410+
fontFamily: fontFamily,
411+
fontSize: fontSize,
412+
)
406413
: CkParagraphStyle(
407414
textAlign: textAlign,
408415
textDirection: textDirection,
@@ -445,7 +452,7 @@ class CanvasKitRenderer implements Renderer {
445452

446453
@override
447454
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) =>
448-
isExperimentalWebParagraph ? WebParagraphBuilder() : CkParagraphBuilder(style);
455+
isExperimentalWebParagraph ? WebParagraphBuilder(style) : CkParagraphBuilder(style);
449456

450457
// TODO(harryterkelsen): Merge this logic with the async logic in
451458
// [EngineScene], https://github.com/flutter/flutter/issues/142072.

engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/text_fragmenter.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ Uint32List fragmentUsingIntlSegmenter(String text, IntlSegmenterGranularity gran
138138
}
139139

140140
// These are the soft/hard line break values expected by Skia's SkParagraph.
141-
const int _kSoftLineBreak = 0;
142-
const int _kHardLineBreak = 1;
141+
const int kSoftLineBreak = 0;
142+
const int kHardLineBreak = 100;
143143

144144
final DomV8BreakIterator _v8LineBreaker = createV8BreakIterator();
145145

@@ -154,15 +154,15 @@ Uint32List fragmentUsingV8LineBreaker(String text) {
154154
final Uint32List typedArray = Uint32List(size);
155155

156156
typedArray[0] = 0; // start index
157-
typedArray[1] = _kSoftLineBreak; // break type
157+
typedArray[1] = kSoftLineBreak; // break type
158158

159159
for (int i = 0; i < fragments.length; i++) {
160160
final LineBreakFragment fragment = fragments[i];
161161
final int uint32Index = 2 + i * 2;
162162
typedArray[uint32Index] = fragment.end;
163163
typedArray[uint32Index + 1] = fragment.type == LineBreakType.mandatory
164-
? _kHardLineBreak
165-
: _kSoftLineBreak;
164+
? kHardLineBreak
165+
: kSoftLineBreak;
166166
}
167167

168168
return typedArray;

engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,8 @@ extension type DomCanvasRenderingContext2D._(JSObject _) implements JSObject {
10381038
);
10391039
external void strokeText(String text, num x, num y);
10401040
external set globalAlpha(num? value);
1041+
1042+
external void fillTextCluster(DomTextCluster textCluster, double x, double y);
10411043
}
10421044

10431045
@JS('ImageBitmapRenderingContext')
@@ -1477,6 +1479,21 @@ DomText createDomText(String data) => domDocument.createTextNode(data);
14771479
@JS('TextMetrics')
14781480
extension type DomTextMetrics._(JSObject _) implements JSObject {
14791481
external double? get width;
1482+
1483+
@JS('getTextClusters')
1484+
external JSArray<JSAny?> _getTextClusters();
1485+
List<DomTextCluster> getTextClusters() => _getTextClusters().toDart.cast<DomTextCluster>();
1486+
1487+
external DomRectReadOnly getActualBoundingBox(int begin, int end);
1488+
1489+
external double get fontBoundingBoxAscent;
1490+
1491+
external double get fontBoundingBoxDescent;
1492+
1493+
@JS('getSelectionRects')
1494+
external JSArray<JSAny> _getSelectionRects(int begin, int end);
1495+
List<DomRectReadOnly> getSelectionRects(int begin, int end) =>
1496+
_getSelectionRects(begin, end).toDart.cast<DomRectReadOnly>();
14801497
}
14811498

14821499
@JS('DOMException')
@@ -2543,3 +2560,13 @@ extension JSArrayExtension on JSArray<JSAny?> {
25432560
// TODO(srujzs): Delete this when we add `JSArray.length` in the SDK.
25442561
external int get length;
25452562
}
2563+
2564+
@JS('TextCluster')
2565+
extension type DomTextCluster._(JSObject _) implements JSObject {
2566+
// TODO(jlavrova): This has been renamed to `start` in the spec.
2567+
// See: https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md
2568+
external int get begin;
2569+
external int get end;
2570+
external double get x;
2571+
external double get y;
2572+
}

engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
1111
import 'package:ui/ui.dart' as ui;
1212
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
1313

14-
const int _kSoftLineBreak = 0;
15-
const int _kHardLineBreak = 100;
16-
1714
final List<String> _testFonts = <String>['FlutterTest', 'Ahem'];
1815
List<String> _computeEffectiveFontFamilies(List<String> fontFamilies) {
1916
if (!ui_web.TestEnvironment.instance.forceTestFonts) {
@@ -1050,8 +1047,8 @@ class SkwasmParagraphBuilder extends SkwasmObjectWrapper<RawParagraphBuilder>
10501047
final LineBreakFragment fragment = lineBreaks[i];
10511048
lineBreakPointer[i + 1].position = fragment.end;
10521049
lineBreakPointer[i + 1].lineBreakType = fragment.type == LineBreakType.mandatory
1053-
? _kHardLineBreak
1054-
: _kSoftLineBreak;
1050+
? kHardLineBreak
1051+
: kSoftLineBreak;
10551052
}
10561053
paragraphBuilderSetLineBreaksUtf16(handle, lineBreakBuffer);
10571054
lineBreakBufferFree(lineBreakBuffer);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../canvaskit/canvaskit_api.dart';
6+
import '../canvaskit/text_fragmenter.dart';
7+
import 'paragraph.dart';
8+
9+
class CodeUnitFlags {
10+
CodeUnitFlags(this._value);
11+
12+
static List<CodeUnitFlags> extractForParagraph(WebParagraph paragraph) {
13+
final List<CodeUnitInfo> ckFlags = canvasKit.CodeUnits.compute(paragraph.text);
14+
assert(ckFlags.length == (paragraph.text.length + 1));
15+
16+
final codeUnitFlags = ckFlags.map((info) => CodeUnitFlags(info.flags)).toList();
17+
18+
// Get text segmentation resuls using browser APIs.
19+
final SegmentationResult result = segmentText(paragraph.text);
20+
21+
// Fill out grapheme flags
22+
for (final grapheme in result.graphemes) {
23+
codeUnitFlags[grapheme].graphemeStart = true;
24+
}
25+
// Fill out word flags
26+
for (final word in result.words) {
27+
codeUnitFlags[word].wordBreak = true;
28+
}
29+
// Fill out line break flags
30+
for (int index = 0; index < result.breaks.length; index += 2) {
31+
final int lineBreak = result.breaks[index];
32+
if (result.breaks[index + 1] == kSoftLineBreak) {
33+
codeUnitFlags[lineBreak].softLineBreak = true;
34+
} else {
35+
codeUnitFlags[lineBreak].hardLineBreak = true;
36+
}
37+
}
38+
return codeUnitFlags;
39+
}
40+
41+
bool get isWhitespace => hasFlag(kWhitespaceFlag);
42+
set whitespace(bool enable) => _setFlag(kWhitespaceFlag, enable);
43+
44+
bool get isGraphemeStart => hasFlag(kGraphemeFlag);
45+
set graphemeStart(bool enable) => _setFlag(kGraphemeFlag, enable);
46+
47+
bool get isSoftLineBreak => hasFlag(kSoftLineBreakFlag);
48+
set softLineBreak(bool enable) => _setFlag(kSoftLineBreakFlag, enable);
49+
50+
bool get isHardLineBreak => hasFlag(kHardLineBreakFlag);
51+
set hardLineBreak(bool enable) => _setFlag(kHardLineBreakFlag, enable);
52+
53+
bool get isWordBreak => hasFlag(kWordBreakFlag);
54+
set wordBreak(bool enable) => _setFlag(kWordBreakFlag, enable);
55+
56+
bool hasFlag(int flag) {
57+
return (_value & flag) != 0;
58+
}
59+
60+
void _setFlag(int flag, bool enable) {
61+
_value = enable ? (_value | flag) : (_value & ~flag);
62+
}
63+
64+
int _value;
65+
66+
@override
67+
String toString() {
68+
return [
69+
if (isWhitespace) 'whitespace',
70+
if (isGraphemeStart) 'grapheme',
71+
if (isSoftLineBreak) 'softBreak',
72+
if (isHardLineBreak) 'hardBreak',
73+
if (isWordBreak) 'word',
74+
].join(' ');
75+
}
76+
77+
static const int kWhitespaceFlag = 1 << 0;
78+
static const int kGraphemeFlag = 1 << 1;
79+
static const int kSoftLineBreakFlag = 1 << 2;
80+
static const int kHardLineBreakFlag = 1 << 3;
81+
static const int kWordBreakFlag = 1 << 4;
82+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
class WebParagraphDebug {
6+
static const bool logging = false;
7+
8+
static void log(String arg) {
9+
if (logging) {
10+
print(arg);
11+
}
12+
}
13+
14+
static void error(String arg) {
15+
print('ERROR: $arg');
16+
}
17+
}

0 commit comments

Comments
 (0)