未验证 提交 ddf978b8 编写于 作者: H Harry Terkelsen 提交者: GitHub

Add more TextStyle support to Paragraph in CanvasKit mode (#21629)

* WIP on Paragraph

* WIP skparagraph

* Add more Paragraph features in CanvasKit mode

* Fix addRoundRect test

* Respond to review comments

* Remove unused (and potentially harmful) getters from Sk classes
上级 8ec9b3a1
...@@ -176,8 +176,7 @@ class CkCanvas { ...@@ -176,8 +176,7 @@ class CkCanvas {
skCanvas.drawPicture(picture.skiaObject.skiaObject); skCanvas.drawPicture(picture.skiaObject.skiaObject);
} }
void drawPoints(CkPaint paint, ui.PointMode pointMode, void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
Float32List points) {
skCanvas.drawPoints( skCanvas.drawPoints(
toSkPointMode(pointMode), toSkPointMode(pointMode),
points, points,
...@@ -230,24 +229,24 @@ class CkCanvas { ...@@ -230,24 +229,24 @@ class CkCanvas {
void saveLayer(ui.Rect bounds, CkPaint paint) { void saveLayer(ui.Rect bounds, CkPaint paint) {
skCanvas.saveLayer( skCanvas.saveLayer(
toSkRect(bounds),
paint.skiaObject, paint.skiaObject,
toSkRect(bounds),
null,
null,
); );
} }
void saveLayerWithoutBounds(CkPaint paint) { void saveLayerWithoutBounds(CkPaint paint) {
final SkCanvasSaveLayerWithoutBoundsOverload override = skCanvas as SkCanvasSaveLayerWithoutBoundsOverload; skCanvas.saveLayer(paint.skiaObject, null, null, null);
override.saveLayer(paint.skiaObject);
} }
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
final SkCanvasSaveLayerWithFilterOverload override = skCanvas as SkCanvasSaveLayerWithFilterOverload;
final CkImageFilter skImageFilter = filter as CkImageFilter; final CkImageFilter skImageFilter = filter as CkImageFilter;
return override.saveLayer( return skCanvas.saveLayer(
null, null,
toSkRect(bounds),
skImageFilter.skiaObject, skImageFilter.skiaObject,
0, 0,
toSkRect(bounds),
); );
} }
......
...@@ -79,6 +79,10 @@ class CanvasKit { ...@@ -79,6 +79,10 @@ class CanvasKit {
external int get LineThroughDecoration; external int get LineThroughDecoration;
// End of text decoration enum. // End of text decoration enum.
external SkTextDecorationStyleEnum get DecorationStyle;
external SkTextBaselineEnum get TextBaseline;
external SkPlaceholderAlignmentEnum get PlaceholderAlignment;
external SkFontMgrNamespace get SkFontMgr; external SkFontMgrNamespace get SkFontMgr;
external TypefaceFontProviderNamespace get TypefaceFontProvider; external TypefaceFontProviderNamespace get TypefaceFontProvider;
external int GetWebGLContext( external int GetWebGLContext(
...@@ -1015,12 +1019,12 @@ class SkPath { ...@@ -1015,12 +1019,12 @@ class SkPath {
external SkPath([SkPath? other]); external SkPath([SkPath? other]);
external void setFillType(SkFillType fillType); external void setFillType(SkFillType fillType);
external void addArc( external void addArc(
SkRect oval, Float32List oval,
double startAngleDegrees, double startAngleDegrees,
double sweepAngleDegrees, double sweepAngleDegrees,
); );
external void addOval( external void addOval(
SkRect oval, Float32List oval,
bool counterClockWise, bool counterClockWise,
int startIndex, int startIndex,
); );
...@@ -1041,16 +1045,15 @@ class SkPath { ...@@ -1041,16 +1045,15 @@ class SkPath {
Float32List points, Float32List points,
bool close, bool close,
); );
external void addRoundRect( external void addRRect(
SkRect outerRect, Float32List rrect,
Float32List radii,
bool counterClockWise, bool counterClockWise,
); );
external void addRect( external void addRect(
SkRect rect, Float32List rect,
); );
external void arcToOval( external void arcToOval(
SkRect oval, Float32List oval,
double startAngleDegrees, double startAngleDegrees,
double sweepAngleDegrees, double sweepAngleDegrees,
bool forceMoveTo, bool forceMoveTo,
...@@ -1084,7 +1087,7 @@ class SkPath { ...@@ -1084,7 +1087,7 @@ class SkPath {
double x3, double x3,
double y3, double y3,
); );
external SkRect getBounds(); external Float32List getBounds();
external void lineTo(double x, double y); external void lineTo(double x, double y);
external void moveTo(double x, double y); external void moveTo(double x, double y);
external void quadTo( external void quadTo(
...@@ -1156,88 +1159,46 @@ class SkContourMeasure { ...@@ -1156,88 +1159,46 @@ class SkContourMeasure {
external double length(); external double length();
} }
@JS() // TODO(hterkelsen): Use a shared malloc'ed array for performance.
@anonymous Float32List toSkRect(ui.Rect rect) {
class SkRect { final Float32List skRect = Float32List(4);
external factory SkRect({ skRect[0] = rect.left;
required double fLeft, skRect[1] = rect.top;
required double fTop, skRect[2] = rect.right;
required double fRight, skRect[3] = rect.bottom;
required double fBottom, return skRect;
}); }
external double get fLeft;
external double get fTop; ui.Rect fromSkRect(Float32List skRect) {
external double get fRight; return ui.Rect.fromLTRB(skRect[0], skRect[1], skRect[2], skRect[3]);
external double get fBottom; }
}
// TODO(hterkelsen): Use a shared malloc'ed array for performance.
extension SkRectExtensions on SkRect { Float32List toSkRRect(ui.RRect rrect) {
ui.Rect toRect() { final Float32List skRRect = Float32List(12);
return ui.Rect.fromLTRB( skRRect[0] = rrect.left;
this.fLeft, skRRect[1] = rrect.top;
this.fTop, skRRect[2] = rrect.right;
this.fRight, skRRect[3] = rrect.bottom;
this.fBottom, skRRect[4] = rrect.tlRadiusX;
); skRRect[5] = rrect.tlRadiusY;
} skRRect[6] = rrect.trRadiusX;
} skRRect[7] = rrect.trRadiusY;
skRRect[8] = rrect.brRadiusX;
SkRect toSkRect(ui.Rect rect) { skRRect[9] = rrect.brRadiusY;
return SkRect( skRRect[10] = rrect.blRadiusX;
fLeft: rect.left, skRRect[11] = rrect.blRadiusY;
fTop: rect.top, return skRRect;
fRight: rect.right, }
fBottom: rect.bottom,
); // TODO(hterkelsen): Use a shared malloc'ed array for performance.
} Float32List toOuterSkRect(ui.RRect rrect) {
final Float32List skRect = Float32List(4);
@JS() skRect[0] = rrect.left;
@anonymous skRect[1] = rrect.top;
class SkRRect { skRect[2] = rrect.right;
external factory SkRRect({ skRect[3] = rrect.bottom;
required SkRect rect, return skRect;
required double rx1,
required double ry1,
required double rx2,
required double ry2,
required double rx3,
required double ry3,
required double rx4,
required double ry4,
});
external SkRect get rect;
external double get rx1;
external double get ry1;
external double get rx2;
external double get ry2;
external double get rx3;
external double get ry3;
external double get rx4;
external double get ry4;
}
SkRRect toSkRRect(ui.RRect rrect) {
return SkRRect(
rect: toOuterSkRect(rrect),
rx1: rrect.tlRadiusX,
ry1: rrect.tlRadiusY,
rx2: rrect.trRadiusX,
ry2: rrect.trRadiusY,
rx3: rrect.brRadiusX,
ry3: rrect.brRadiusY,
rx4: rrect.blRadiusX,
ry4: rrect.blRadiusY,
);
}
SkRect toOuterSkRect(ui.RRect rrect) {
return SkRect(
fLeft: rrect.left,
fTop: rrect.top,
fRight: rrect.right,
fBottom: rrect.bottom,
);
} }
/// Encodes a list of offsets to CanvasKit-compatible point array. /// Encodes a list of offsets to CanvasKit-compatible point array.
...@@ -1263,9 +1224,9 @@ List<Float32List> rawPointsToSkPoints2d(Float32List points) { ...@@ -1263,9 +1224,9 @@ List<Float32List> rawPointsToSkPoints2d(Float32List points) {
assert(points.length % 2 == 0); assert(points.length % 2 == 0);
final int pointLength = points.length ~/ 2; final int pointLength = points.length ~/ 2;
final List<Float32List> result = <Float32List>[]; final List<Float32List> result = <Float32List>[];
for (var i = 0; i < pointLength; i++) { for (int i = 0; i < pointLength; i++) {
var x = i * 2; int x = i * 2;
var y = x + 1; int y = x + 1;
final Float32List skPoint = Float32List(2); final Float32List skPoint = Float32List(2);
skPoint[0] = points[x]; skPoint[0] = points[x];
skPoint[1] = points[y]; skPoint[1] = points[y];
...@@ -1277,7 +1238,7 @@ List<Float32List> rawPointsToSkPoints2d(Float32List points) { ...@@ -1277,7 +1238,7 @@ List<Float32List> rawPointsToSkPoints2d(Float32List points) {
List<Float32List> toSkPoints2d(List<ui.Offset> offsets) { List<Float32List> toSkPoints2d(List<ui.Offset> offsets) {
final int len = offsets.length; final int len = offsets.length;
final List<Float32List> result = <Float32List>[]; final List<Float32List> result = <Float32List>[];
for (var i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
final ui.Offset offset = offsets[i]; final ui.Offset offset = offsets[i];
final Float32List skPoint = Float32List(2); final Float32List skPoint = Float32List(2);
skPoint[0] = offset.dx; skPoint[0] = offset.dx;
...@@ -1299,7 +1260,7 @@ Uint16List toUint16List(List<int> ints) { ...@@ -1299,7 +1260,7 @@ Uint16List toUint16List(List<int> ints) {
@JS('window.flutterCanvasKit.SkPictureRecorder') @JS('window.flutterCanvasKit.SkPictureRecorder')
class SkPictureRecorder { class SkPictureRecorder {
external SkPictureRecorder(); external SkPictureRecorder();
external SkCanvas beginRecording(SkRect bounds); external SkCanvas beginRecording(Float32List bounds);
external SkPicture finishRecordingAsPicture(); external SkPicture finishRecordingAsPicture();
external void delete(); external void delete();
} }
...@@ -1319,17 +1280,17 @@ class SkCanvas { ...@@ -1319,17 +1280,17 @@ class SkCanvas {
bool doAntiAlias, bool doAntiAlias,
); );
external void clipRRect( external void clipRRect(
SkRRect rrect, Float32List rrect,
SkClipOp clipOp, SkClipOp clipOp,
bool doAntiAlias, bool doAntiAlias,
); );
external void clipRect( external void clipRect(
SkRect rrect, Float32List rrect,
SkClipOp clipOp, SkClipOp clipOp,
bool doAntiAlias, bool doAntiAlias,
); );
external void drawArc( external void drawArc(
SkRect oval, Float32List oval,
double startAngleDegrees, double startAngleDegrees,
double sweepAngleDegrees, double sweepAngleDegrees,
bool useCenter, bool useCenter,
...@@ -1354,8 +1315,8 @@ class SkCanvas { ...@@ -1354,8 +1315,8 @@ class SkCanvas {
SkBlendMode blendMode, SkBlendMode blendMode,
); );
external void drawDRRect( external void drawDRRect(
SkRRect outer, Float32List outer,
SkRRect inner, Float32List inner,
SkPaint paint, SkPaint paint,
); );
external void drawImage( external void drawImage(
...@@ -1366,15 +1327,15 @@ class SkCanvas { ...@@ -1366,15 +1327,15 @@ class SkCanvas {
); );
external void drawImageRect( external void drawImageRect(
SkImage image, SkImage image,
SkRect src, Float32List src,
SkRect dst, Float32List dst,
SkPaint paint, SkPaint paint,
bool fastSample, bool fastSample,
); );
external void drawImageNine( external void drawImageNine(
SkImage image, SkImage image,
SkRect center, Float32List center,
SkRect dst, Float32List dst,
SkPaint paint, SkPaint paint,
); );
external void drawLine( external void drawLine(
...@@ -1385,7 +1346,7 @@ class SkCanvas { ...@@ -1385,7 +1346,7 @@ class SkCanvas {
SkPaint paint, SkPaint paint,
); );
external void drawOval( external void drawOval(
SkRect rect, Float32List rect,
SkPaint paint, SkPaint paint,
); );
external void drawPaint( external void drawPaint(
...@@ -1401,11 +1362,11 @@ class SkCanvas { ...@@ -1401,11 +1362,11 @@ class SkCanvas {
SkPaint paint, SkPaint paint,
); );
external void drawRRect( external void drawRRect(
SkRRect rrect, Float32List rrect,
SkPaint paint, SkPaint paint,
); );
external void drawRect( external void drawRect(
SkRect rrect, Float32List rrect,
SkPaint paint, SkPaint paint,
); );
external void drawShadow( external void drawShadow(
...@@ -1425,8 +1386,10 @@ class SkCanvas { ...@@ -1425,8 +1386,10 @@ class SkCanvas {
external int save(); external int save();
external int getSaveCount(); external int getSaveCount();
external void saveLayer( external void saveLayer(
SkRect bounds, SkPaint? paint,
SkPaint paint, Float32List? bounds,
SkImageFilter? backdrop,
int? flags,
); );
external void restore(); external void restore();
external void restoreToCount(int count); external void restoreToCount(int count);
...@@ -1448,23 +1411,6 @@ class SkCanvas { ...@@ -1448,23 +1411,6 @@ class SkCanvas {
); );
} }
@JS()
@anonymous
class SkCanvasSaveLayerWithoutBoundsOverload {
external void saveLayer(SkPaint paint);
}
@JS()
@anonymous
class SkCanvasSaveLayerWithFilterOverload {
external void saveLayer(
SkPaint? paint,
SkImageFilter? imageFilter,
int flags,
SkRect rect,
);
}
@JS() @JS()
@anonymous @anonymous
class SkPicture { class SkPicture {
...@@ -1493,6 +1439,7 @@ class SkParagraphBuilder { ...@@ -1493,6 +1439,7 @@ class SkParagraphBuilder {
external void pushPaintStyle( external void pushPaintStyle(
SkTextStyle textStyle, SkPaint foreground, SkPaint background); SkTextStyle textStyle, SkPaint foreground, SkPaint background);
external void pop(); external void pop();
external void addPlaceholder(SkPlaceholderStyleProperties placeholderStyle);
external SkParagraph build(); external SkParagraph build();
external void delete(); external void delete();
} }
...@@ -1504,69 +1451,162 @@ class SkParagraphStyle {} ...@@ -1504,69 +1451,162 @@ class SkParagraphStyle {}
@JS() @JS()
@anonymous @anonymous
class SkParagraphStyleProperties { class SkParagraphStyleProperties {
external SkTextAlign? get textAlign;
external set textAlign(SkTextAlign? value); external set textAlign(SkTextAlign? value);
external SkTextDirection? get textDirection;
external set textDirection(SkTextDirection? value); external set textDirection(SkTextDirection? value);
external double? get heightMultiplier;
external set heightMultiplier(double? value); external set heightMultiplier(double? value);
external int? get textHeightBehavior;
external set textHeightBehavior(int? value); external set textHeightBehavior(int? value);
external int? get maxLines;
external set maxLines(int? value); external set maxLines(int? value);
external String? get ellipsis;
external set ellipsis(String? value); external set ellipsis(String? value);
external SkTextStyleProperties? get textStyle;
external set textStyle(SkTextStyleProperties? value); external set textStyle(SkTextStyleProperties? value);
external set strutStyle(SkStrutStyleProperties? strutStyle);
} }
@JS() @JS()
class SkTextStyle {} class SkTextStyle {}
@JS()
class SkTextDecorationStyleEnum {
external SkTextDecorationStyle get Solid;
external SkTextDecorationStyle get Double;
external SkTextDecorationStyle get Dotted;
external SkTextDecorationStyle get Dashed;
external SkTextDecorationStyle get Wavy;
}
@JS()
class SkTextDecorationStyle {
external int get value;
}
final List<SkTextDecorationStyle> _skTextDecorationStyles =
<SkTextDecorationStyle>[
canvasKit.DecorationStyle.Solid,
canvasKit.DecorationStyle.Double,
canvasKit.DecorationStyle.Dotted,
canvasKit.DecorationStyle.Dashed,
canvasKit.DecorationStyle.Wavy,
];
SkTextDecorationStyle toSkTextDecorationStyle(ui.TextDecorationStyle style) {
return _skTextDecorationStyles[style.index];
}
@JS()
class SkTextBaselineEnum {
external SkTextBaseline get Alphabetic;
external SkTextBaseline get Ideographic;
}
@JS()
class SkTextBaseline {
external int get value;
}
final List<SkTextBaseline> _skTextBaselines = <SkTextBaseline>[
canvasKit.TextBaseline.Alphabetic,
canvasKit.TextBaseline.Ideographic,
];
SkTextBaseline toSkTextBaseline(ui.TextBaseline baseline) {
return _skTextBaselines[baseline.index];
}
@JS()
class SkPlaceholderAlignmentEnum {
external SkPlaceholderAlignment get Baseline;
external SkPlaceholderAlignment get AboveBaseline;
external SkPlaceholderAlignment get BelowBaseline;
external SkPlaceholderAlignment get Top;
external SkPlaceholderAlignment get Bottom;
external SkPlaceholderAlignment get Middle;
}
@JS()
class SkPlaceholderAlignment {
external int get value;
}
final List<SkPlaceholderAlignment> _skPlaceholderAlignments =
<SkPlaceholderAlignment>[
canvasKit.PlaceholderAlignment.Baseline,
canvasKit.PlaceholderAlignment.AboveBaseline,
canvasKit.PlaceholderAlignment.BelowBaseline,
canvasKit.PlaceholderAlignment.Top,
canvasKit.PlaceholderAlignment.Bottom,
canvasKit.PlaceholderAlignment.Middle,
];
SkPlaceholderAlignment toSkPlaceholderAlignment(
ui.PlaceholderAlignment alignment) {
return _skPlaceholderAlignments[alignment.index];
}
@JS() @JS()
@anonymous @anonymous
class SkTextStyleProperties { class SkTextStyleProperties {
external Float32List? get backgroundColor;
external set backgroundColor(Float32List? value); external set backgroundColor(Float32List? value);
external Float32List? get color;
external set color(Float32List? value); external set color(Float32List? value);
external Float32List? get foregroundColor;
external set foregroundColor(Float32List? value); external set foregroundColor(Float32List? value);
external int? get decoration;
external set decoration(int? value); external set decoration(int? value);
external double? get decorationThickness;
external set decorationThickness(double? value); external set decorationThickness(double? value);
external set decorationColor(Float32List? value);
external double? get fontSize; external set decorationStyle(SkTextDecorationStyle? value);
external set textBaseline(SkTextBaseline? value);
external set fontSize(double? value); external set fontSize(double? value);
external set letterSpacing(double? value);
external List<String>? get fontFamilies; external set wordSpacing(double? value);
external set heightMultiplier(double? value);
external set locale(String? value);
external set fontFamilies(List<String>? value); external set fontFamilies(List<String>? value);
external set fontStyle(SkFontStyle? value);
external set shadows(List<SkTextShadow>? value);
external set fontFeatures(List<SkFontFeature>? value);
}
external SkFontStyle? get fontStyle; @JS()
@anonymous
class SkStrutStyleProperties {
external set fontFamilies(List<String>? value);
external set fontStyle(SkFontStyle? value); external set fontStyle(SkFontStyle? value);
external set fontSize(double? value);
external set heightMultiplier(double? value);
external set leading(double? value);
external set strutEnabled(bool? value);
external set forceStrutHeight(bool? value);
}
@JS()
@anonymous
class SkPlaceholderStyleProperties {
external set width(double? value);
external set height(double? value);
external set alignment(SkPlaceholderAlignment? value);
external set offset(double? value);
external set baseline(SkTextBaseline? value);
} }
@JS() @JS()
@anonymous @anonymous
class SkFontStyle { class SkFontStyle {
external SkFontWeight? get weight;
external set weight(SkFontWeight? value); external set weight(SkFontWeight? value);
external SkFontSlant? get slant;
external set slant(SkFontSlant? value); external set slant(SkFontSlant? value);
} }
@JS()
@anonymous
class SkTextShadow {
external set color(Float32List? value);
external set offset(Float32List? value);
external set blurRadius(double? value);
}
@JS()
@anonymous
class SkFontFeature {
external set name(String? value);
external set value(int? value);
}
@JS() @JS()
@anonymous @anonymous
class SkFontMgr { class SkFontMgr {
...@@ -1591,12 +1631,13 @@ class SkParagraph { ...@@ -1591,12 +1631,13 @@ class SkParagraph {
external double getMaxIntrinsicWidth(); external double getMaxIntrinsicWidth();
external double getMinIntrinsicWidth(); external double getMinIntrinsicWidth();
external double getMaxWidth(); external double getMaxWidth();
external List<SkRect> getRectsForRange( external List<Float32List> getRectsForRange(
int start, int start,
int end, int end,
SkRectHeightStyle heightStyle, SkRectHeightStyle heightStyle,
SkRectWidthStyle widthStyle, SkRectWidthStyle widthStyle,
); );
external List<Float32List> getRectsForPlaceholders();
external SkTextPosition getGlyphPositionAtCoordinate( external SkTextPosition getGlyphPositionAtCoordinate(
double x, double x,
double y, double y,
...@@ -1649,7 +1690,8 @@ class TypefaceFontProviderNamespace { ...@@ -1649,7 +1690,8 @@ class TypefaceFontProviderNamespace {
Timer? _skObjectCollector; Timer? _skObjectCollector;
List<SkDeletable> _skObjectDeleteQueue = <SkDeletable>[]; List<SkDeletable> _skObjectDeleteQueue = <SkDeletable>[];
final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) { final SkObjectFinalizationRegistry skObjectFinalizationRegistry =
SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) {
_skObjectDeleteQueue.add(deletable); _skObjectDeleteQueue.add(deletable);
_skObjectCollector ??= _scheduleSkObjectCollection(); _skObjectCollector ??= _scheduleSkObjectCollection();
})); }));
...@@ -1668,19 +1710,20 @@ final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinali ...@@ -1668,19 +1710,20 @@ final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinali
/// yielding to the graphics system to render the frame on the screen if there /// yielding to the graphics system to render the frame on the screen if there
/// is a large number of objects to delete, causing jank. /// is a large number of objects to delete, causing jank.
Timer _scheduleSkObjectCollection() => Timer(Duration.zero, () { Timer _scheduleSkObjectCollection() => Timer(Duration.zero, () {
html.window.performance.mark('SkObject collection-start'); html.window.performance.mark('SkObject collection-start');
final int length = _skObjectDeleteQueue.length; final int length = _skObjectDeleteQueue.length;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
_skObjectDeleteQueue[i].delete(); _skObjectDeleteQueue[i].delete();
} }
_skObjectDeleteQueue = <SkDeletable>[]; _skObjectDeleteQueue = <SkDeletable>[];
// Null out the timer so we can schedule a new one next time objects are // Null out the timer so we can schedule a new one next time objects are
// scheduled for deletion. // scheduled for deletion.
_skObjectCollector = null; _skObjectCollector = null;
html.window.performance.mark('SkObject collection-end'); html.window.performance.mark('SkObject collection-end');
html.window.performance.measure('SkObject collection', 'SkObject collection-start', 'SkObject collection-end'); html.window.performance.measure('SkObject collection',
}); 'SkObject collection-start', 'SkObject collection-end');
});
/// Any Skia object that has a `delete` method. /// Any Skia object that has a `delete` method.
@JS() @JS()
...@@ -1716,7 +1759,8 @@ class SkObjectFinalizationRegistry { ...@@ -1716,7 +1759,8 @@ class SkObjectFinalizationRegistry {
external Object? get _finalizationRegistryConstructor; external Object? get _finalizationRegistryConstructor;
/// Whether the current browser supports `FinalizationRegistry`. /// Whether the current browser supports `FinalizationRegistry`.
bool browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null; bool browserSupportsFinalizationRegistry =
_finalizationRegistryConstructor != null;
@JS() @JS()
class SkData { class SkData {
...@@ -1741,7 +1785,7 @@ class SkImageInfo { ...@@ -1741,7 +1785,7 @@ class SkImageInfo {
external int get height; external int get height;
external bool get isEmpty; external bool get isEmpty;
external bool get isOpaque; external bool get isOpaque;
external SkRect get bounds; external Float32List get bounds;
external int get width; external int get width;
external SkImageInfo makeAlphaType(SkAlphaType alphaType); external SkImageInfo makeAlphaType(SkAlphaType alphaType);
external SkImageInfo makeColorSpace(SkColorSpace colorSpace); external SkImageInfo makeColorSpace(SkColorSpace colorSpace);
......
...@@ -20,7 +20,7 @@ const bool canvasKitForceCpuOnly = ...@@ -20,7 +20,7 @@ const bool canvasKitForceCpuOnly =
/// NPM, update this URL to `https://unpkg.com/canvaskit-wasm@0.34.0/bin/`. /// NPM, update this URL to `https://unpkg.com/canvaskit-wasm@0.34.0/bin/`.
const String canvasKitBaseUrl = String.fromEnvironment( const String canvasKitBaseUrl = String.fromEnvironment(
'FLUTTER_WEB_CANVASKIT_URL', 'FLUTTER_WEB_CANVASKIT_URL',
defaultValue: 'https://unpkg.com/canvaskit-wasm@0.17.3/bin/', defaultValue: 'https://unpkg.com/canvaskit-wasm@0.18.1/bin/',
); );
/// Initialize CanvasKit. /// Initialize CanvasKit.
......
...@@ -11,11 +11,15 @@ part of engine; ...@@ -11,11 +11,15 @@ part of engine;
class CkPath implements ui.Path { class CkPath implements ui.Path {
final SkPath _skPath; final SkPath _skPath;
CkPath() : _skPath = SkPath(), _fillType = ui.PathFillType.nonZero { CkPath()
: _skPath = SkPath(),
_fillType = ui.PathFillType.nonZero {
_skPath.setFillType(toSkFillType(_fillType)); _skPath.setFillType(toSkFillType(_fillType));
} }
CkPath.from(CkPath other) : _skPath = SkPath(other._skPath), _fillType = other.fillType { CkPath.from(CkPath other)
: _skPath = SkPath(other._skPath),
_fillType = other.fillType {
_skPath.setFillType(toSkFillType(_fillType)); _skPath.setFillType(toSkFillType(_fillType));
} }
...@@ -89,22 +93,10 @@ class CkPath implements ui.Path { ...@@ -89,22 +93,10 @@ class CkPath implements ui.Path {
@override @override
void addRRect(ui.RRect rrect) { void addRRect(ui.RRect rrect) {
final SkFloat32List skRadii = mallocFloat32List(8); _skPath.addRRect(
final Float32List radii = skRadii.toTypedArray(); toSkRRect(rrect),
radii[0] = rrect.tlRadiusX;
radii[1] = rrect.tlRadiusY;
radii[2] = rrect.trRadiusX;
radii[3] = rrect.trRadiusY;
radii[4] = rrect.brRadiusX;
radii[5] = rrect.brRadiusY;
radii[6] = rrect.blRadiusX;
radii[7] = rrect.blRadiusY;
_skPath.addRoundRect(
toOuterSkRect(rrect),
radii,
false, false,
); );
freeFloat32List(skRadii);
} }
@override @override
...@@ -195,7 +187,7 @@ class CkPath implements ui.Path { ...@@ -195,7 +187,7 @@ class CkPath implements ui.Path {
} }
@override @override
ui.Rect getBounds() => _skPath.getBounds().toRect(); ui.Rect getBounds() => fromSkRect(_skPath.getBounds());
@override @override
void lineTo(double x, double y) { void lineTo(double x, double y) {
......
...@@ -13,7 +13,7 @@ class CkPictureRecorder implements ui.PictureRecorder { ...@@ -13,7 +13,7 @@ class CkPictureRecorder implements ui.PictureRecorder {
CkCanvas beginRecording(ui.Rect bounds) { CkCanvas beginRecording(ui.Rect bounds) {
_cullRect = bounds; _cullRect = bounds;
final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder(); final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder();
final SkRect skRect = toSkRect(bounds); final Float32List skRect = toSkRect(bounds);
final SkCanvas skCanvas = recorder.beginRecording(skRect); final SkCanvas skCanvas = recorder.beginRecording(skRect);
return _recordingCanvas = CkCanvas(skCanvas); return _recordingCanvas = CkCanvas(skCanvas);
} }
......
...@@ -29,15 +29,23 @@ class CkParagraphStyle implements ui.ParagraphStyle { ...@@ -29,15 +29,23 @@ class CkParagraphStyle implements ui.ParagraphStyle {
textHeightBehavior, textHeightBehavior,
fontWeight, fontWeight,
fontStyle, fontStyle,
strutStyle,
ellipsis, ellipsis,
locale,
) { ) {
_textDirection = textDirection ?? ui.TextDirection.ltr; _textDirection = textDirection ?? ui.TextDirection.ltr;
_fontFamily = fontFamily; _fontFamily = fontFamily;
_fontSize = fontSize;
_fontWeight = fontWeight;
_fontStyle = fontStyle;
} }
SkParagraphStyle skParagraphStyle; SkParagraphStyle skParagraphStyle;
ui.TextDirection? _textDirection; ui.TextDirection? _textDirection;
String? _fontFamily; String? _fontFamily;
double? _fontSize;
ui.FontWeight? _fontWeight;
ui.FontStyle? _fontStyle;
static SkTextStyleProperties toSkTextStyleProperties( static SkTextStyleProperties toSkTextStyleProperties(
String? fontFamily, String? fontFamily,
...@@ -63,6 +71,46 @@ class CkParagraphStyle implements ui.ParagraphStyle { ...@@ -63,6 +71,46 @@ class CkParagraphStyle implements ui.ParagraphStyle {
return skTextStyle; return skTextStyle;
} }
static SkStrutStyleProperties toSkStrutStyleProperties(ui.StrutStyle value) {
EngineStrutStyle style = value as EngineStrutStyle;
final SkStrutStyleProperties skStrutStyle = SkStrutStyleProperties();
if (style._fontFamily != null) {
final List<String> fontFamilies = <String>[style._fontFamily!];
if (style._fontFamilyFallback != null) {
fontFamilies.addAll(style._fontFamilyFallback!);
}
skStrutStyle.fontFamilies = fontFamilies;
} else {
// If no strut font family is given, default to Roboto.
skStrutStyle.fontFamilies = ['Roboto'];
}
if (style._fontSize != null) {
skStrutStyle.fontSize = style._fontSize;
}
if (style._height != null) {
skStrutStyle.heightMultiplier = style._height;
}
if (style._leading != null) {
skStrutStyle.leading = style._leading;
}
if (style._fontWeight != null || style._fontStyle != null) {
skStrutStyle.fontStyle =
toSkFontStyle(style._fontWeight, style._fontStyle);
}
if (style._forceStrutHeight != null) {
skStrutStyle.forceStrutHeight = style._forceStrutHeight;
}
skStrutStyle.strutEnabled = true;
return skStrutStyle;
}
static SkParagraphStyle toSkParagraphStyle( static SkParagraphStyle toSkParagraphStyle(
ui.TextAlign? textAlign, ui.TextAlign? textAlign,
ui.TextDirection? textDirection, ui.TextDirection? textDirection,
...@@ -73,7 +121,9 @@ class CkParagraphStyle implements ui.ParagraphStyle { ...@@ -73,7 +121,9 @@ class CkParagraphStyle implements ui.ParagraphStyle {
ui.TextHeightBehavior? textHeightBehavior, ui.TextHeightBehavior? textHeightBehavior,
ui.FontWeight? fontWeight, ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle, ui.FontStyle? fontStyle,
ui.StrutStyle? strutStyle,
String? ellipsis, String? ellipsis,
ui.Locale? locale,
) { ) {
final SkParagraphStyleProperties properties = SkParagraphStyleProperties(); final SkParagraphStyleProperties properties = SkParagraphStyleProperties();
...@@ -85,6 +135,10 @@ class CkParagraphStyle implements ui.ParagraphStyle { ...@@ -85,6 +135,10 @@ class CkParagraphStyle implements ui.ParagraphStyle {
properties.textDirection = toSkTextDirection(textDirection); properties.textDirection = toSkTextDirection(textDirection);
} }
if (maxLines != null) {
properties.maxLines = maxLines;
}
if (height != null) { if (height != null) {
properties.heightMultiplier = height; properties.heightMultiplier = height;
} }
...@@ -93,25 +147,52 @@ class CkParagraphStyle implements ui.ParagraphStyle { ...@@ -93,25 +147,52 @@ class CkParagraphStyle implements ui.ParagraphStyle {
properties.textHeightBehavior = textHeightBehavior.encode(); properties.textHeightBehavior = textHeightBehavior.encode();
} }
if (maxLines != null) {
properties.maxLines = maxLines;
}
if (ellipsis != null) { if (ellipsis != null) {
properties.ellipsis = ellipsis; properties.ellipsis = ellipsis;
} }
if (strutStyle != null) {
properties.strutStyle = toSkStrutStyleProperties(strutStyle);
}
properties.textStyle = properties.textStyle =
toSkTextStyleProperties(fontFamily, fontSize, fontWeight, fontStyle); toSkTextStyleProperties(fontFamily, fontSize, fontWeight, fontStyle);
return canvasKit.ParagraphStyle(properties); return canvasKit.ParagraphStyle(properties);
} }
CkTextStyle getTextStyle() {
return CkTextStyle(
fontFamily: _fontFamily,
fontSize: _fontSize,
fontWeight: _fontWeight,
fontStyle: _fontStyle,
);
}
} }
class CkTextStyle implements ui.TextStyle { class CkTextStyle implements ui.TextStyle {
SkTextStyle skTextStyle; SkTextStyle skTextStyle;
ui.Color? color;
ui.TextDecoration? decoration;
ui.Color? decorationColor;
ui.TextDecorationStyle? decorationStyle;
double? decorationThickness;
ui.FontWeight? fontWeight;
ui.FontStyle? fontStyle;
ui.TextBaseline? textBaseline;
String? fontFamily;
List<String>? fontFamilyFallback;
double? fontSize;
double? letterSpacing;
double? wordSpacing;
double? height;
ui.Locale? locale;
CkPaint? background; CkPaint? background;
CkPaint? foreground; CkPaint? foreground;
List<ui.Shadow>? shadows;
List<ui.FontFeature>? fontFeatures;
factory CkTextStyle({ factory CkTextStyle({
ui.Color? color, ui.Color? color,
...@@ -162,10 +243,38 @@ class CkTextStyle implements ui.TextStyle { ...@@ -162,10 +243,38 @@ class CkTextStyle implements ui.TextStyle {
properties.decorationThickness = decorationThickness; properties.decorationThickness = decorationThickness;
} }
if (decorationColor != null) {
properties.decorationColor = makeFreshSkColor(decorationColor);
}
if (decorationStyle != null) {
properties.decorationStyle = toSkTextDecorationStyle(decorationStyle);
}
if (textBaseline != null) {
properties.textBaseline = toSkTextBaseline(textBaseline);
}
if (fontSize != null) { if (fontSize != null) {
properties.fontSize = fontSize; properties.fontSize = fontSize;
} }
if (letterSpacing != null) {
properties.letterSpacing = letterSpacing;
}
if (wordSpacing != null) {
properties.wordSpacing = wordSpacing;
}
if (height != null) {
properties.heightMultiplier = height;
}
if (locale != null) {
properties.locale = locale.toLanguageTag();
}
if (fontFamily == null || if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) { !skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto'; fontFamily = 'Roboto';
...@@ -187,21 +296,103 @@ class CkTextStyle implements ui.TextStyle { ...@@ -187,21 +296,103 @@ class CkTextStyle implements ui.TextStyle {
properties.foregroundColor = makeFreshSkColor(foreground.color); properties.foregroundColor = makeFreshSkColor(foreground.color);
} }
// TODO(hterkelsen): Add support for if (shadows != null) {
// - decorationColor List<SkTextShadow> ckShadows = <SkTextShadow>[];
// - decorationStyle for (ui.Shadow shadow in shadows) {
// - textBaseline final ckShadow = SkTextShadow();
// - letterSpacing ckShadow.color = makeFreshSkColor(shadow.color);
// - wordSpacing ckShadow.offset = toSkPoint(shadow.offset);
// - height ckShadow.blurRadius = shadow.blurRadius;
// - locale ckShadows.add(ckShadow);
// - shadows }
// - fontFeatures properties.shadows = ckShadows;
}
if (fontFeatures != null) {
List<SkFontFeature> ckFontFeatures = <SkFontFeature>[];
for (ui.FontFeature fontFeature in fontFeatures) {
SkFontFeature ckFontFeature = SkFontFeature();
ckFontFeature.name = fontFeature.feature;
ckFontFeature.value = fontFeature.value;
ckFontFeatures.add(ckFontFeature);
}
properties.fontFeatures = ckFontFeatures;
}
return CkTextStyle._( return CkTextStyle._(
canvasKit.TextStyle(properties), foreground, background); canvasKit.TextStyle(properties),
color,
decoration,
decorationColor,
decorationStyle,
decorationThickness,
fontWeight,
fontStyle,
textBaseline,
fontFamily,
fontFamilyFallback,
fontSize,
letterSpacing,
wordSpacing,
height,
locale,
background,
foreground,
shadows,
fontFeatures,
);
}
/// Merges this text style with [other] and returns the new text style.
///
/// The values in this text style are used unless [other] specifically
/// overrides it.
CkTextStyle mergeWith(CkTextStyle other) {
return CkTextStyle(
color: other.color ?? color,
decoration: other.decoration ?? decoration,
decorationColor: other.decorationColor ?? decorationColor,
decorationStyle: other.decorationStyle ?? decorationStyle,
decorationThickness: other.decorationThickness ?? decorationThickness,
fontWeight: other.fontWeight ?? fontWeight,
fontStyle: other.fontStyle ?? fontStyle,
textBaseline: other.textBaseline ?? textBaseline,
fontFamily: other.fontFamily ?? fontFamily,
fontFamilyFallback: other.fontFamilyFallback ?? fontFamilyFallback,
fontSize: other.fontSize ?? fontSize,
letterSpacing: other.letterSpacing ?? letterSpacing,
wordSpacing: other.wordSpacing ?? wordSpacing,
height: other.height ?? height,
locale: other.locale ?? locale,
background: other.background ?? background,
foreground: other.foreground ?? foreground,
shadows: other.shadows ?? shadows,
fontFeatures: other.fontFeatures ?? fontFeatures,
);
} }
CkTextStyle._(this.skTextStyle, this.foreground, this.background); CkTextStyle._(
this.skTextStyle,
this.color,
this.decoration,
this.decorationColor,
this.decorationStyle,
this.decorationThickness,
this.fontWeight,
this.fontStyle,
this.textBaseline,
this.fontFamily,
this.fontFamilyFallback,
this.fontSize,
this.letterSpacing,
this.wordSpacing,
this.height,
this.locale,
this.background,
this.foreground,
this.shadows,
this.fontFeatures,
);
} }
SkFontStyle toSkFontStyle(ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) { SkFontStyle toSkFontStyle(ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) {
...@@ -260,6 +451,9 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph> ...@@ -260,6 +451,9 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph>
case _ParagraphCommandType.pushStyle: case _ParagraphCommandType.pushStyle:
builder.pushStyle(command.style!); builder.pushStyle(command.style!);
break; break;
case _ParagraphCommandType.addPlaceholder:
builder._addPlaceholder(command.placeholderStyle!);
break;
} }
} }
...@@ -304,10 +498,10 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph> ...@@ -304,10 +498,10 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph>
@override @override
double get width => skiaObject.getMaxWidth(); double get width => skiaObject.getMaxWidth();
// TODO(hterkelsen): Implement placeholders once it's in CanvasKit
@override @override
List<ui.TextBox> getBoxesForPlaceholders() { List<ui.TextBox> getBoxesForPlaceholders() {
return const <ui.TextBox>[]; List<List<double>> skRects = skiaObject.getRectsForPlaceholders();
return skRectsToTextBoxes(skRects);
} }
@override @override
...@@ -321,22 +515,26 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph> ...@@ -321,22 +515,26 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph>
return const <ui.TextBox>[]; return const <ui.TextBox>[];
} }
List<SkRect> skRects = skiaObject.getRectsForRange( List<List<double>> skRects = skiaObject.getRectsForRange(
start, start,
end, end,
toSkRectHeightStyle(boxHeightStyle), toSkRectHeightStyle(boxHeightStyle),
toSkRectWidthStyle(boxWidthStyle), toSkRectWidthStyle(boxWidthStyle),
); );
return skRectsToTextBoxes(skRects);
}
List<ui.TextBox> skRectsToTextBoxes(List<List<double>> skRects) {
List<ui.TextBox> result = <ui.TextBox>[]; List<ui.TextBox> result = <ui.TextBox>[];
for (int i = 0; i < skRects.length; i++) { for (int i = 0; i < skRects.length; i++) {
final SkRect rect = skRects[i]; final List<double> rect = skRects[i];
result.add(ui.TextBox.fromLTRBD( result.add(ui.TextBox.fromLTRBD(
rect.fLeft, rect[0],
rect.fTop, rect[1],
rect.fRight, rect[2],
rect.fBottom, rect[3],
_paragraphStyle._textDirection!, _paragraphStyle._textDirection!,
)); ));
} }
...@@ -404,16 +602,21 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { ...@@ -404,16 +602,21 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
final SkParagraphBuilder _paragraphBuilder; final SkParagraphBuilder _paragraphBuilder;
final CkParagraphStyle _style; final CkParagraphStyle _style;
final List<_ParagraphCommand> _commands; final List<_ParagraphCommand> _commands;
int _placeholderCount;
final List<double> _placeholderScales;
final List<CkTextStyle> _styleStack;
CkParagraphBuilder(ui.ParagraphStyle style) CkParagraphBuilder(ui.ParagraphStyle style)
: _commands = <_ParagraphCommand>[], : _commands = <_ParagraphCommand>[],
_style = style as CkParagraphStyle, _style = style as CkParagraphStyle,
_placeholderCount = 0,
_placeholderScales = <double>[],
_styleStack = <CkTextStyle>[],
_paragraphBuilder = canvasKit.ParagraphBuilder.MakeFromFontProvider( _paragraphBuilder = canvasKit.ParagraphBuilder.MakeFromFontProvider(
style.skParagraphStyle, style.skParagraphStyle,
skiaFontCollection.fontProvider, skiaFontCollection.fontProvider,
); );
// TODO(hterkelsen): Implement placeholders.
@override @override
void addPlaceholder( void addPlaceholder(
double width, double width,
...@@ -423,7 +626,44 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { ...@@ -423,7 +626,44 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
double? baselineOffset, double? baselineOffset,
ui.TextBaseline? baseline, ui.TextBaseline? baseline,
}) { }) {
throw UnimplementedError('addPlaceholder'); // Require a baseline to be specified if using a baseline-based alignment.
assert((alignment == ui.PlaceholderAlignment.aboveBaseline ||
alignment == ui.PlaceholderAlignment.belowBaseline ||
alignment == ui.PlaceholderAlignment.baseline)
? baseline != null
: true);
_placeholderCount++;
_placeholderScales.add(scale);
SkPlaceholderStyleProperties placeholderStyle = toSkPlaceholderStyle(
width * scale,
height * scale,
alignment,
(baselineOffset ?? height) * scale,
baseline ?? ui.TextBaseline.alphabetic,
);
_addPlaceholder(placeholderStyle);
}
void _addPlaceholder(SkPlaceholderStyleProperties placeholderStyle) {
_commands.add(_ParagraphCommand.addPlaceholder(placeholderStyle));
_paragraphBuilder.addPlaceholder(placeholderStyle);
}
static SkPlaceholderStyleProperties toSkPlaceholderStyle(
double width,
double height,
ui.PlaceholderAlignment alignment,
double baselineOffset,
ui.TextBaseline baseline,
) {
final properties = SkPlaceholderStyleProperties();
properties.width = width;
properties.height = height;
properties.alignment = toSkPlaceholderAlignment(alignment);
properties.offset = baselineOffset;
properties.baseline = toSkTextBaseline(baseline);
return properties;
} }
@override @override
...@@ -446,22 +686,28 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { ...@@ -446,22 +686,28 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
} }
@override @override
int get placeholderCount => throw UnimplementedError('placeholderCount'); int get placeholderCount => _placeholderCount;
// TODO(hterkelsen): Implement this once CanvasKit exposes placeholders.
@override @override
List<double> get placeholderScales => const <double>[]; List<double> get placeholderScales => _placeholderScales;
@override @override
void pop() { void pop() {
_commands.add(const _ParagraphCommand.pop()); _commands.add(const _ParagraphCommand.pop());
_styleStack.removeLast();
_paragraphBuilder.pop(); _paragraphBuilder.pop();
} }
CkTextStyle _peekStyle() =>
_styleStack.isEmpty ? _style.getTextStyle() : _styleStack.last;
@override @override
void pushStyle(ui.TextStyle style) { void pushStyle(ui.TextStyle style) {
final CkTextStyle skStyle = style as CkTextStyle; final CkTextStyle baseStyle = _peekStyle();
_commands.add(_ParagraphCommand.pushStyle(skStyle)); final CkTextStyle ckStyle = style as CkTextStyle;
final CkTextStyle skStyle = baseStyle.mergeWith(ckStyle);
_styleStack.add(skStyle);
_commands.add(_ParagraphCommand.pushStyle(ckStyle));
if (skStyle.foreground != null || skStyle.background != null) { if (skStyle.foreground != null || skStyle.background != null) {
final SkPaint foreground = skStyle.foreground?.skiaObject ?? SkPaint(); final SkPaint foreground = skStyle.foreground?.skiaObject ?? SkPaint();
final SkPaint background = skStyle.background?.skiaObject ?? SkPaint(); final SkPaint background = skStyle.background?.skiaObject ?? SkPaint();
...@@ -477,20 +723,33 @@ class _ParagraphCommand { ...@@ -477,20 +723,33 @@ class _ParagraphCommand {
final _ParagraphCommandType type; final _ParagraphCommandType type;
final String? text; final String? text;
final CkTextStyle? style; final CkTextStyle? style;
final SkPlaceholderStyleProperties? placeholderStyle;
const _ParagraphCommand._(this.type, this.text, this.style); const _ParagraphCommand._(
this.type,
this.text,
this.style,
this.placeholderStyle,
);
const _ParagraphCommand.addText(String text) const _ParagraphCommand.addText(String text)
: this._(_ParagraphCommandType.addText, text, null); : this._(_ParagraphCommandType.addText, text, null, null);
const _ParagraphCommand.pop() : this._(_ParagraphCommandType.pop, null, null); const _ParagraphCommand.pop()
: this._(_ParagraphCommandType.pop, null, null, null);
const _ParagraphCommand.pushStyle(CkTextStyle style) const _ParagraphCommand.pushStyle(CkTextStyle style)
: this._(_ParagraphCommandType.pushStyle, null, style); : this._(_ParagraphCommandType.pushStyle, null, style, null);
const _ParagraphCommand.addPlaceholder(
SkPlaceholderStyleProperties placeholderStyle)
: this._(
_ParagraphCommandType.addPlaceholder, null, null, placeholderStyle);
} }
enum _ParagraphCommandType { enum _ParagraphCommandType {
addText, addText,
pop, pop,
pushStyle, pushStyle,
addPlaceholder,
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册