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
diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
index 3426253..06d325e 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
@@ -176,8 +176,7 @@
     skCanvas.drawPicture(picture.skiaObject.skiaObject);
   }
 
-  void drawPoints(CkPaint paint, ui.PointMode pointMode,
-      Float32List points) {
+  void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
     skCanvas.drawPoints(
       toSkPointMode(pointMode),
       points,
@@ -230,24 +229,24 @@
 
   void saveLayer(ui.Rect bounds, CkPaint paint) {
     skCanvas.saveLayer(
-      toSkRect(bounds),
       paint.skiaObject,
+      toSkRect(bounds),
+      null,
+      null,
     );
   }
 
   void saveLayerWithoutBounds(CkPaint paint) {
-    final SkCanvasSaveLayerWithoutBoundsOverload override = skCanvas as SkCanvasSaveLayerWithoutBoundsOverload;
-    override.saveLayer(paint.skiaObject);
+    skCanvas.saveLayer(paint.skiaObject, null, null, null);
   }
 
   void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
-    final SkCanvasSaveLayerWithFilterOverload override = skCanvas as SkCanvasSaveLayerWithFilterOverload;
     final CkImageFilter skImageFilter = filter as CkImageFilter;
-    return override.saveLayer(
+    return skCanvas.saveLayer(
       null,
+      toSkRect(bounds),
       skImageFilter.skiaObject,
       0,
-      toSkRect(bounds),
     );
   }
 
diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
index 1252cba..09d67db 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
@@ -79,6 +79,10 @@
   external int get LineThroughDecoration;
   // End of text decoration enum.
 
+  external SkTextDecorationStyleEnum get DecorationStyle;
+  external SkTextBaselineEnum get TextBaseline;
+  external SkPlaceholderAlignmentEnum get PlaceholderAlignment;
+
   external SkFontMgrNamespace get SkFontMgr;
   external TypefaceFontProviderNamespace get TypefaceFontProvider;
   external int GetWebGLContext(
@@ -1015,12 +1019,12 @@
   external SkPath([SkPath? other]);
   external void setFillType(SkFillType fillType);
   external void addArc(
-    SkRect oval,
+    Float32List oval,
     double startAngleDegrees,
     double sweepAngleDegrees,
   );
   external void addOval(
-    SkRect oval,
+    Float32List oval,
     bool counterClockWise,
     int startIndex,
   );
@@ -1041,16 +1045,15 @@
     Float32List points,
     bool close,
   );
-  external void addRoundRect(
-    SkRect outerRect,
-    Float32List radii,
+  external void addRRect(
+    Float32List rrect,
     bool counterClockWise,
   );
   external void addRect(
-    SkRect rect,
+    Float32List rect,
   );
   external void arcToOval(
-    SkRect oval,
+    Float32List oval,
     double startAngleDegrees,
     double sweepAngleDegrees,
     bool forceMoveTo,
@@ -1084,7 +1087,7 @@
     double x3,
     double y3,
   );
-  external SkRect getBounds();
+  external Float32List getBounds();
   external void lineTo(double x, double y);
   external void moveTo(double x, double y);
   external void quadTo(
@@ -1156,88 +1159,46 @@
   external double length();
 }
 
-@JS()
-@anonymous
-class SkRect {
-  external factory SkRect({
-    required double fLeft,
-    required double fTop,
-    required double fRight,
-    required double fBottom,
-  });
-  external double get fLeft;
-  external double get fTop;
-  external double get fRight;
-  external double get fBottom;
+// TODO(hterkelsen): Use a shared malloc'ed array for performance.
+Float32List toSkRect(ui.Rect rect) {
+  final Float32List skRect = Float32List(4);
+  skRect[0] = rect.left;
+  skRect[1] = rect.top;
+  skRect[2] = rect.right;
+  skRect[3] = rect.bottom;
+  return skRect;
 }
 
-extension SkRectExtensions on SkRect {
-  ui.Rect toRect() {
-    return ui.Rect.fromLTRB(
-      this.fLeft,
-      this.fTop,
-      this.fRight,
-      this.fBottom,
-    );
-  }
+ui.Rect fromSkRect(Float32List skRect) {
+  return ui.Rect.fromLTRB(skRect[0], skRect[1], skRect[2], skRect[3]);
 }
 
-SkRect toSkRect(ui.Rect rect) {
-  return SkRect(
-    fLeft: rect.left,
-    fTop: rect.top,
-    fRight: rect.right,
-    fBottom: rect.bottom,
-  );
+// TODO(hterkelsen): Use a shared malloc'ed array for performance.
+Float32List toSkRRect(ui.RRect rrect) {
+  final Float32List skRRect = Float32List(12);
+  skRRect[0] = rrect.left;
+  skRRect[1] = rrect.top;
+  skRRect[2] = rrect.right;
+  skRRect[3] = rrect.bottom;
+  skRRect[4] = rrect.tlRadiusX;
+  skRRect[5] = rrect.tlRadiusY;
+  skRRect[6] = rrect.trRadiusX;
+  skRRect[7] = rrect.trRadiusY;
+  skRRect[8] = rrect.brRadiusX;
+  skRRect[9] = rrect.brRadiusY;
+  skRRect[10] = rrect.blRadiusX;
+  skRRect[11] = rrect.blRadiusY;
+  return skRRect;
 }
 
-@JS()
-@anonymous
-class SkRRect {
-  external factory SkRRect({
-    required SkRect rect,
-    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,
-  );
+// TODO(hterkelsen): Use a shared malloc'ed array for performance.
+Float32List toOuterSkRect(ui.RRect rrect) {
+  final Float32List skRect = Float32List(4);
+  skRect[0] = rrect.left;
+  skRect[1] = rrect.top;
+  skRect[2] = rrect.right;
+  skRect[3] = rrect.bottom;
+  return skRect;
 }
 
 /// Encodes a list of offsets to CanvasKit-compatible point array.
@@ -1263,9 +1224,9 @@
   assert(points.length % 2 == 0);
   final int pointLength = points.length ~/ 2;
   final List<Float32List> result = <Float32List>[];
-  for (var i = 0; i < pointLength; i++) {
-    var x = i * 2;
-    var y = x + 1;
+  for (int i = 0; i < pointLength; i++) {
+    int x = i * 2;
+    int y = x + 1;
     final Float32List skPoint = Float32List(2);
     skPoint[0] = points[x];
     skPoint[1] = points[y];
@@ -1277,7 +1238,7 @@
 List<Float32List> toSkPoints2d(List<ui.Offset> offsets) {
   final int len = offsets.length;
   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 Float32List skPoint = Float32List(2);
     skPoint[0] = offset.dx;
@@ -1299,7 +1260,7 @@
 @JS('window.flutterCanvasKit.SkPictureRecorder')
 class SkPictureRecorder {
   external SkPictureRecorder();
-  external SkCanvas beginRecording(SkRect bounds);
+  external SkCanvas beginRecording(Float32List bounds);
   external SkPicture finishRecordingAsPicture();
   external void delete();
 }
@@ -1319,17 +1280,17 @@
     bool doAntiAlias,
   );
   external void clipRRect(
-    SkRRect rrect,
+    Float32List rrect,
     SkClipOp clipOp,
     bool doAntiAlias,
   );
   external void clipRect(
-    SkRect rrect,
+    Float32List rrect,
     SkClipOp clipOp,
     bool doAntiAlias,
   );
   external void drawArc(
-    SkRect oval,
+    Float32List oval,
     double startAngleDegrees,
     double sweepAngleDegrees,
     bool useCenter,
@@ -1354,8 +1315,8 @@
     SkBlendMode blendMode,
   );
   external void drawDRRect(
-    SkRRect outer,
-    SkRRect inner,
+    Float32List outer,
+    Float32List inner,
     SkPaint paint,
   );
   external void drawImage(
@@ -1366,15 +1327,15 @@
   );
   external void drawImageRect(
     SkImage image,
-    SkRect src,
-    SkRect dst,
+    Float32List src,
+    Float32List dst,
     SkPaint paint,
     bool fastSample,
   );
   external void drawImageNine(
     SkImage image,
-    SkRect center,
-    SkRect dst,
+    Float32List center,
+    Float32List dst,
     SkPaint paint,
   );
   external void drawLine(
@@ -1385,7 +1346,7 @@
     SkPaint paint,
   );
   external void drawOval(
-    SkRect rect,
+    Float32List rect,
     SkPaint paint,
   );
   external void drawPaint(
@@ -1401,11 +1362,11 @@
     SkPaint paint,
   );
   external void drawRRect(
-    SkRRect rrect,
+    Float32List rrect,
     SkPaint paint,
   );
   external void drawRect(
-    SkRect rrect,
+    Float32List rrect,
     SkPaint paint,
   );
   external void drawShadow(
@@ -1425,8 +1386,10 @@
   external int save();
   external int getSaveCount();
   external void saveLayer(
-    SkRect bounds,
-    SkPaint paint,
+    SkPaint? paint,
+    Float32List? bounds,
+    SkImageFilter? backdrop,
+    int? flags,
   );
   external void restore();
   external void restoreToCount(int count);
@@ -1450,23 +1413,6 @@
 
 @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()
-@anonymous
 class SkPicture {
   external void delete();
 }
@@ -1493,6 +1439,7 @@
   external void pushPaintStyle(
       SkTextStyle textStyle, SkPaint foreground, SkPaint background);
   external void pop();
+  external void addPlaceholder(SkPlaceholderStyleProperties placeholderStyle);
   external SkParagraph build();
   external void delete();
 }
@@ -1504,71 +1451,164 @@
 @JS()
 @anonymous
 class SkParagraphStyleProperties {
-  external SkTextAlign? get textAlign;
   external set textAlign(SkTextAlign? value);
-
-  external SkTextDirection? get textDirection;
   external set textDirection(SkTextDirection? value);
-
-  external double? get heightMultiplier;
   external set heightMultiplier(double? value);
-
-  external int? get textHeightBehavior;
   external set textHeightBehavior(int? value);
-
-  external int? get maxLines;
   external set maxLines(int? value);
-
-  external String? get ellipsis;
   external set ellipsis(String? value);
-
-  external SkTextStyleProperties? get textStyle;
   external set textStyle(SkTextStyleProperties? value);
+  external set strutStyle(SkStrutStyleProperties? strutStyle);
 }
 
 @JS()
 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()
 @anonymous
 class SkTextStyleProperties {
-  external Float32List? get backgroundColor;
   external set backgroundColor(Float32List? value);
-
-  external Float32List? get color;
   external set color(Float32List? value);
-
-  external Float32List? get foregroundColor;
   external set foregroundColor(Float32List? value);
-
-  external int? get decoration;
   external set decoration(int? value);
-
-  external double? get decorationThickness;
   external set decorationThickness(double? value);
-
-  external double? get fontSize;
+  external set decorationColor(Float32List? value);
+  external set decorationStyle(SkTextDecorationStyle? value);
+  external set textBaseline(SkTextBaseline? value);
   external set fontSize(double? value);
-
-  external List<String>? get fontFamilies;
+  external set letterSpacing(double? value);
+  external set wordSpacing(double? value);
+  external set heightMultiplier(double? value);
+  external set locale(String? value);
   external set fontFamilies(List<String>? value);
-
-  external SkFontStyle? get fontStyle;
   external set fontStyle(SkFontStyle? value);
+  external set shadows(List<SkTextShadow>? value);
+  external set fontFeatures(List<SkFontFeature>? value);
+}
+
+@JS()
+@anonymous
+class SkStrutStyleProperties {
+  external set fontFamilies(List<String>? 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()
 @anonymous
 class SkFontStyle {
-  external SkFontWeight? get weight;
   external set weight(SkFontWeight? value);
-
-  external SkFontSlant? get slant;
   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()
+@anonymous
 class SkFontMgr {
   external String? getFamilyName(int fontId);
   external void delete();
@@ -1591,12 +1631,13 @@
   external double getMaxIntrinsicWidth();
   external double getMinIntrinsicWidth();
   external double getMaxWidth();
-  external List<SkRect> getRectsForRange(
+  external List<Float32List> getRectsForRange(
     int start,
     int end,
     SkRectHeightStyle heightStyle,
     SkRectWidthStyle widthStyle,
   );
+  external List<Float32List> getRectsForPlaceholders();
   external SkTextPosition getGlyphPositionAtCoordinate(
     double x,
     double y,
@@ -1649,7 +1690,8 @@
 Timer? _skObjectCollector;
 List<SkDeletable> _skObjectDeleteQueue = <SkDeletable>[];
 
-final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) {
+final SkObjectFinalizationRegistry skObjectFinalizationRegistry =
+    SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) {
   _skObjectDeleteQueue.add(deletable);
   _skObjectCollector ??= _scheduleSkObjectCollection();
 }));
@@ -1668,19 +1710,20 @@
 ///    yielding to the graphics system to render the frame on the screen if there
 ///    is a large number of objects to delete, causing jank.
 Timer _scheduleSkObjectCollection() => Timer(Duration.zero, () {
-  html.window.performance.mark('SkObject collection-start');
-  final int length = _skObjectDeleteQueue.length;
-  for (int i = 0; i < length; i++) {
-    _skObjectDeleteQueue[i].delete();
-  }
-  _skObjectDeleteQueue = <SkDeletable>[];
+      html.window.performance.mark('SkObject collection-start');
+      final int length = _skObjectDeleteQueue.length;
+      for (int i = 0; i < length; i++) {
+        _skObjectDeleteQueue[i].delete();
+      }
+      _skObjectDeleteQueue = <SkDeletable>[];
 
-  // Null out the timer so we can schedule a new one next time objects are
-  // scheduled for deletion.
-  _skObjectCollector = null;
-  html.window.performance.mark('SkObject collection-end');
-  html.window.performance.measure('SkObject collection', 'SkObject collection-start', 'SkObject collection-end');
-});
+      // Null out the timer so we can schedule a new one next time objects are
+      // scheduled for deletion.
+      _skObjectCollector = null;
+      html.window.performance.mark('SkObject collection-end');
+      html.window.performance.measure('SkObject collection',
+          'SkObject collection-start', 'SkObject collection-end');
+    });
 
 /// Any Skia object that has a `delete` method.
 @JS()
@@ -1716,7 +1759,8 @@
 external Object? get _finalizationRegistryConstructor;
 
 /// Whether the current browser supports `FinalizationRegistry`.
-bool browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null;
+bool browserSupportsFinalizationRegistry =
+    _finalizationRegistryConstructor != null;
 
 @JS()
 class SkData {
@@ -1741,7 +1785,7 @@
   external int get height;
   external bool get isEmpty;
   external bool get isOpaque;
-  external SkRect get bounds;
+  external Float32List get bounds;
   external int get width;
   external SkImageInfo makeAlphaType(SkAlphaType alphaType);
   external SkImageInfo makeColorSpace(SkColorSpace colorSpace);
diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart
index c1d91ad..1bfe7cf 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart
@@ -20,7 +20,7 @@
 /// NPM, update this URL to `https://unpkg.com/canvaskit-wasm@0.34.0/bin/`.
 const String canvasKitBaseUrl = String.fromEnvironment(
   '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.
diff --git a/lib/web_ui/lib/src/engine/canvaskit/path.dart b/lib/web_ui/lib/src/engine/canvaskit/path.dart
index 86a758b..810c76e 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/path.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/path.dart
@@ -11,11 +11,15 @@
 class CkPath implements ui.Path {
   final SkPath _skPath;
 
-  CkPath() : _skPath = SkPath(), _fillType = ui.PathFillType.nonZero {
+  CkPath()
+      : _skPath = SkPath(),
+        _fillType = ui.PathFillType.nonZero {
     _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));
   }
 
@@ -89,22 +93,10 @@
 
   @override
   void addRRect(ui.RRect rrect) {
-    final SkFloat32List skRadii = mallocFloat32List(8);
-    final Float32List radii = skRadii.toTypedArray();
-    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,
+    _skPath.addRRect(
+      toSkRRect(rrect),
       false,
     );
-    freeFloat32List(skRadii);
   }
 
   @override
@@ -195,7 +187,7 @@
   }
 
   @override
-  ui.Rect getBounds() => _skPath.getBounds().toRect();
+  ui.Rect getBounds() => fromSkRect(_skPath.getBounds());
 
   @override
   void lineTo(double x, double y) {
diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart b/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart
index fa6793d..126ffd9 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart
@@ -13,7 +13,7 @@
   CkCanvas beginRecording(ui.Rect bounds) {
     _cullRect = bounds;
     final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder();
-    final SkRect skRect = toSkRect(bounds);
+    final Float32List skRect = toSkRect(bounds);
     final SkCanvas skCanvas = recorder.beginRecording(skRect);
     return _recordingCanvas = CkCanvas(skCanvas);
   }
diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart
index 146a9f0..a033f78 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/text.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart
@@ -29,15 +29,23 @@
           textHeightBehavior,
           fontWeight,
           fontStyle,
+          strutStyle,
           ellipsis,
+          locale,
         ) {
     _textDirection = textDirection ?? ui.TextDirection.ltr;
     _fontFamily = fontFamily;
+    _fontSize = fontSize;
+    _fontWeight = fontWeight;
+    _fontStyle = fontStyle;
   }
 
   SkParagraphStyle skParagraphStyle;
   ui.TextDirection? _textDirection;
   String? _fontFamily;
+  double? _fontSize;
+  ui.FontWeight? _fontWeight;
+  ui.FontStyle? _fontStyle;
 
   static SkTextStyleProperties toSkTextStyleProperties(
     String? fontFamily,
@@ -63,6 +71,46 @@
     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(
     ui.TextAlign? textAlign,
     ui.TextDirection? textDirection,
@@ -73,7 +121,9 @@
     ui.TextHeightBehavior? textHeightBehavior,
     ui.FontWeight? fontWeight,
     ui.FontStyle? fontStyle,
+    ui.StrutStyle? strutStyle,
     String? ellipsis,
+    ui.Locale? locale,
   ) {
     final SkParagraphStyleProperties properties = SkParagraphStyleProperties();
 
@@ -85,6 +135,10 @@
       properties.textDirection = toSkTextDirection(textDirection);
     }
 
+    if (maxLines != null) {
+      properties.maxLines = maxLines;
+    }
+
     if (height != null) {
       properties.heightMultiplier = height;
     }
@@ -93,25 +147,52 @@
       properties.textHeightBehavior = textHeightBehavior.encode();
     }
 
-    if (maxLines != null) {
-      properties.maxLines = maxLines;
-    }
-
     if (ellipsis != null) {
       properties.ellipsis = ellipsis;
     }
 
+    if (strutStyle != null) {
+      properties.strutStyle = toSkStrutStyleProperties(strutStyle);
+    }
+
     properties.textStyle =
         toSkTextStyleProperties(fontFamily, fontSize, fontWeight, fontStyle);
 
     return canvasKit.ParagraphStyle(properties);
   }
+
+  CkTextStyle getTextStyle() {
+    return CkTextStyle(
+      fontFamily: _fontFamily,
+      fontSize: _fontSize,
+      fontWeight: _fontWeight,
+      fontStyle: _fontStyle,
+    );
+  }
 }
 
 class CkTextStyle implements ui.TextStyle {
   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? foreground;
+  List<ui.Shadow>? shadows;
+  List<ui.FontFeature>? fontFeatures;
 
   factory CkTextStyle({
     ui.Color? color,
@@ -162,10 +243,38 @@
       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) {
       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 ||
         !skiaFontCollection.registeredFamilies.contains(fontFamily)) {
       fontFamily = 'Roboto';
@@ -187,21 +296,103 @@
       properties.foregroundColor = makeFreshSkColor(foreground.color);
     }
 
-    // TODO(hterkelsen): Add support for
-    //   - decorationColor
-    //   - decorationStyle
-    //   - textBaseline
-    //   - letterSpacing
-    //   - wordSpacing
-    //   - height
-    //   - locale
-    //   - shadows
-    //   - fontFeatures
+    if (shadows != null) {
+      List<SkTextShadow> ckShadows = <SkTextShadow>[];
+      for (ui.Shadow shadow in shadows) {
+        final ckShadow = SkTextShadow();
+        ckShadow.color = makeFreshSkColor(shadow.color);
+        ckShadow.offset = toSkPoint(shadow.offset);
+        ckShadow.blurRadius = shadow.blurRadius;
+        ckShadows.add(ckShadow);
+      }
+      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._(
-        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,
+    );
   }
 
-  CkTextStyle._(this.skTextStyle, this.foreground, this.background);
+  /// 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.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) {
@@ -260,6 +451,9 @@
         case _ParagraphCommandType.pushStyle:
           builder.pushStyle(command.style!);
           break;
+        case _ParagraphCommandType.addPlaceholder:
+          builder._addPlaceholder(command.placeholderStyle!);
+          break;
       }
     }
 
@@ -304,10 +498,10 @@
   @override
   double get width => skiaObject.getMaxWidth();
 
-  // TODO(hterkelsen): Implement placeholders once it's in CanvasKit
   @override
   List<ui.TextBox> getBoxesForPlaceholders() {
-    return const <ui.TextBox>[];
+    List<List<double>> skRects = skiaObject.getRectsForPlaceholders();
+    return skRectsToTextBoxes(skRects);
   }
 
   @override
@@ -321,22 +515,26 @@
       return const <ui.TextBox>[];
     }
 
-    List<SkRect> skRects = skiaObject.getRectsForRange(
+    List<List<double>> skRects = skiaObject.getRectsForRange(
       start,
       end,
       toSkRectHeightStyle(boxHeightStyle),
       toSkRectWidthStyle(boxWidthStyle),
     );
 
+    return skRectsToTextBoxes(skRects);
+  }
+
+  List<ui.TextBox> skRectsToTextBoxes(List<List<double>> skRects) {
     List<ui.TextBox> result = <ui.TextBox>[];
 
     for (int i = 0; i < skRects.length; i++) {
-      final SkRect rect = skRects[i];
+      final List<double> rect = skRects[i];
       result.add(ui.TextBox.fromLTRBD(
-        rect.fLeft,
-        rect.fTop,
-        rect.fRight,
-        rect.fBottom,
+          rect[0],
+          rect[1],
+          rect[2],
+          rect[3],
         _paragraphStyle._textDirection!,
       ));
     }
@@ -404,16 +602,21 @@
   final SkParagraphBuilder _paragraphBuilder;
   final CkParagraphStyle _style;
   final List<_ParagraphCommand> _commands;
+  int _placeholderCount;
+  final List<double> _placeholderScales;
+  final List<CkTextStyle> _styleStack;
 
   CkParagraphBuilder(ui.ParagraphStyle style)
       : _commands = <_ParagraphCommand>[],
         _style = style as CkParagraphStyle,
+        _placeholderCount = 0,
+        _placeholderScales = <double>[],
+        _styleStack = <CkTextStyle>[],
         _paragraphBuilder = canvasKit.ParagraphBuilder.MakeFromFontProvider(
           style.skParagraphStyle,
           skiaFontCollection.fontProvider,
         );
 
-  // TODO(hterkelsen): Implement placeholders.
   @override
   void addPlaceholder(
     double width,
@@ -423,7 +626,44 @@
     double? baselineOffset,
     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
@@ -446,22 +686,28 @@
   }
 
   @override
-  int get placeholderCount => throw UnimplementedError('placeholderCount');
+  int get placeholderCount => _placeholderCount;
 
-  // TODO(hterkelsen): Implement this once CanvasKit exposes placeholders.
   @override
-  List<double> get placeholderScales => const <double>[];
+  List<double> get placeholderScales => _placeholderScales;
 
   @override
   void pop() {
     _commands.add(const _ParagraphCommand.pop());
+    _styleStack.removeLast();
     _paragraphBuilder.pop();
   }
 
+  CkTextStyle _peekStyle() =>
+      _styleStack.isEmpty ? _style.getTextStyle() : _styleStack.last;
+
   @override
   void pushStyle(ui.TextStyle style) {
-    final CkTextStyle skStyle = style as CkTextStyle;
-    _commands.add(_ParagraphCommand.pushStyle(skStyle));
+    final CkTextStyle baseStyle = _peekStyle();
+    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) {
       final SkPaint foreground = skStyle.foreground?.skiaObject ?? SkPaint();
       final SkPaint background = skStyle.background?.skiaObject ?? SkPaint();
@@ -477,20 +723,33 @@
   final _ParagraphCommandType type;
   final String? text;
   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)
-      : 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)
-      : this._(_ParagraphCommandType.pushStyle, null, style);
+      : this._(_ParagraphCommandType.pushStyle, null, style, null);
+
+  const _ParagraphCommand.addPlaceholder(
+      SkPlaceholderStyleProperties placeholderStyle)
+      : this._(
+            _ParagraphCommandType.addPlaceholder, null, null, placeholderStyle);
 }
 
 enum _ParagraphCommandType {
   addText,
   pop,
   pushStyle,
+  addPlaceholder,
 }
diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
index b03f352..28e1c11 100644
--- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
+++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart
@@ -51,7 +51,7 @@
     _toSkPointTests();
     _toSkColorStopsTests();
     _toSkMatrixFromFloat32Tests();
-    _skSkRectTests();
+    _toSkRectTests();
     _skVerticesTests();
     group('SkPath', () {
       _pathTests();
@@ -59,7 +59,10 @@
     group('SkCanvas', () {
       _canvasTests();
     });
-  // TODO: https://github.com/flutter/flutter/issues/60040
+    group('SkParagraph', () {
+      _textStyleTests();
+    });
+    // TODO: https://github.com/flutter/flutter/issues/60040
   }, skip: isIosSafari);
 }
 
@@ -203,11 +206,13 @@
 
 void _pathOpTests() {
   test('path op mapping is correct', () {
-    expect(canvasKit.PathOp.Difference.value, ui.PathOperation.difference.index);
+    expect(
+        canvasKit.PathOp.Difference.value, ui.PathOperation.difference.index);
     expect(canvasKit.PathOp.Intersect.value, ui.PathOperation.intersect.index);
     expect(canvasKit.PathOp.Union.value, ui.PathOperation.union.index);
     expect(canvasKit.PathOp.XOR.value, ui.PathOperation.xor.index);
-    expect(canvasKit.PathOp.ReverseDifference.value, ui.PathOperation.reverseDifference.index);
+    expect(canvasKit.PathOp.ReverseDifference.value,
+        ui.PathOperation.reverseDifference.index);
   });
 
   test('ui.PathOperation converts to SkPathOp', () {
@@ -269,8 +274,10 @@
 void _vertexModeTests() {
   test('vertex mode mapping is correct', () {
     expect(canvasKit.VertexMode.Triangles.value, ui.VertexMode.triangles.index);
-    expect(canvasKit.VertexMode.TrianglesStrip.value, ui.VertexMode.triangleStrip.index);
-    expect(canvasKit.VertexMode.TriangleFan.value, ui.VertexMode.triangleFan.index);
+    expect(canvasKit.VertexMode.TrianglesStrip.value,
+        ui.VertexMode.triangleStrip.index);
+    expect(canvasKit.VertexMode.TriangleFan.value,
+        ui.VertexMode.triangleFan.index);
   });
 
   test('ui.VertexMode converts to SkVertexMode', () {
@@ -282,7 +289,8 @@
 
 void _imageTests() {
   test('MakeAnimatedImageFromEncoded makes a non-animated image', () {
-    final SkAnimatedImage nonAnimated = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
+    final SkAnimatedImage nonAnimated =
+        canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
     expect(nonAnimated.getFrameCount(), 1);
     expect(nonAnimated.getRepetitionCount(), 0);
     expect(nonAnimated.width(), 1);
@@ -304,9 +312,10 @@
   });
 
   test('MakeAnimatedImageFromEncoded makes an animated image', () {
-    final SkAnimatedImage animated = canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif);
+    final SkAnimatedImage animated =
+        canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif);
     expect(animated.getFrameCount(), 3);
-    expect(animated.getRepetitionCount(), -1);  // animates forever
+    expect(animated.getRepetitionCount(), -1); // animates forever
     expect(animated.width(), 1);
     expect(animated.height(), 1);
     for (int i = 0; i < 100; i++) {
@@ -324,35 +333,39 @@
   });
 
   test('MakeRadialGradient', () {
-    expect(canvasKit.SkShader.MakeRadialGradient(
-      Float32List.fromList([1, 1]),
-      10.0,
-      <Float32List>[
-        Float32List.fromList([0, 0, 0, 1]),
-        Float32List.fromList([1, 1, 1, 1]),
-      ],
-      Float32List.fromList([0, 1]),
-      canvasKit.TileMode.Repeat,
-      toSkMatrixFromFloat32(Matrix4.identity().storage),
-      0,
-    ), isNotNull);
+    expect(
+        canvasKit.SkShader.MakeRadialGradient(
+          Float32List.fromList([1, 1]),
+          10.0,
+          <Float32List>[
+            Float32List.fromList([0, 0, 0, 1]),
+            Float32List.fromList([1, 1, 1, 1]),
+          ],
+          Float32List.fromList([0, 1]),
+          canvasKit.TileMode.Repeat,
+          toSkMatrixFromFloat32(Matrix4.identity().storage),
+          0,
+        ),
+        isNotNull);
   });
 
   test('MakeTwoPointConicalGradient', () {
-    expect(canvasKit.SkShader.MakeTwoPointConicalGradient(
-      Float32List.fromList([1, 1]),
-      10.0,
-      Float32List.fromList([1, 1]),
-      10.0,
-      <Float32List>[
-        Float32List.fromList([0, 0, 0, 1]),
-        Float32List.fromList([1, 1, 1, 1]),
-      ],
-      Float32List.fromList([0, 1]),
-      canvasKit.TileMode.Repeat,
-      toSkMatrixFromFloat32(Matrix4.identity().storage),
-      0,
-    ), isNotNull);
+    expect(
+        canvasKit.SkShader.MakeTwoPointConicalGradient(
+          Float32List.fromList([1, 1]),
+          10.0,
+          Float32List.fromList([1, 1]),
+          10.0,
+          <Float32List>[
+            Float32List.fromList([0, 0, 0, 1]),
+            Float32List.fromList([1, 1, 1, 1]),
+          ],
+          Float32List.fromList([0, 1]),
+          canvasKit.TileMode.Repeat,
+          toSkMatrixFromFloat32(Matrix4.identity().storage),
+          0,
+        ),
+        isNotNull);
   });
 }
 
@@ -398,11 +411,13 @@
 
 void _maskFilterTests() {
   test('MakeBlurMaskFilter', () {
-    expect(canvasKit.MakeBlurMaskFilter(
-      canvasKit.BlurStyle.Outer,
-      5.0,
-      false,
-    ), isNotNull);
+    expect(
+        canvasKit.MakeBlurMaskFilter(
+          canvasKit.BlurStyle.Outer,
+          5.0,
+          false,
+        ),
+        isNotNull);
   });
 }
 
@@ -543,18 +558,44 @@
       ..translate(1, 2, 3)
       ..rotateZ(4);
     expect(
-      toSkMatrixFromFloat32(matrix.storage),
-      Float32List.fromList(<double>[
-        -0.6536436080932617,
-        0.756802499294281,
+        toSkMatrixFromFloat32(matrix.storage),
+        Float32List.fromList(<double>[
+          -0.6536436080932617,
+          0.756802499294281,
+          1,
+          -0.756802499294281,
+          -0.6536436080932617,
+          2,
+          -0.0,
+          0,
+          1,
+        ]));
+  });
+}
+
+void _toSkRectTests() {
+  test('toSkRect', () {
+    expect(toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4)), [1, 2, 3, 4]);
+  });
+
+  test('fromSkRect', () {
+    expect(fromSkRect(Float32List.fromList([1, 2, 3, 4])),
+        ui.Rect.fromLTRB(1, 2, 3, 4));
+  });
+
+  test('toSkRRect', () {
+    expect(
+      toSkRRect(ui.RRect.fromLTRBAndCorners(
         1,
-        -0.756802499294281,
-        -0.6536436080932617,
         2,
-        -0.0,
-        0,
-        1,
-      ])
+        3,
+        4,
+        topLeft: ui.Radius.elliptical(5, 6),
+        topRight: ui.Radius.elliptical(7, 8),
+        bottomRight: ui.Radius.elliptical(9, 10),
+        bottomLeft: ui.Radius.elliptical(11, 12),
+      )),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
     );
   });
 }
@@ -581,7 +622,7 @@
 
   test('addArc', () {
     path.addArc(
-      SkRect(fLeft: 10, fTop: 20, fRight: 30, fBottom: 40),
+      toSkRect(ui.Rect.fromLTRB(10, 20, 30, 40)),
       1,
       5,
     );
@@ -589,7 +630,7 @@
 
   test('addOval', () {
     path.addOval(
-      SkRect(fLeft: 10, fTop: 20, fRight: 30, fBottom: 40),
+      toSkRect(ui.Rect.fromLTRB(10, 20, 30, 40)),
       false,
       1,
     );
@@ -608,36 +649,24 @@
     freeFloat32List(encodedPoints);
   });
 
-  test('addRoundRect', () {
+  test('addRRect', () {
     final ui.RRect rrect = ui.RRect.fromRectAndRadius(
       ui.Rect.fromLTRB(10, 10, 20, 20),
       ui.Radius.circular(3),
     );
-    final SkFloat32List skRadii = mallocFloat32List(8);
-    final Float32List radii = skRadii.toTypedArray();
-    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;
-    path.addRoundRect(
-      toOuterSkRect(rrect),
-      radii,
+    path.addRRect(
+      toSkRRect(rrect),
       false,
     );
-    freeFloat32List(skRadii);
   });
 
   test('addRect', () {
-    path.addRect(SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4));
+    path.addRect(toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4)));
   });
 
   test('arcTo', () {
     path.arcToOval(
-      SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4),
+      toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4)),
       5,
       40,
       false,
@@ -676,7 +705,7 @@
 
   test('getBounds', () {
     final SkPath testPath = _testClosedSkPath();
-    final ui.Rect bounds = testPath.getBounds().toRect();
+    final ui.Rect bounds = fromSkRect(testPath.getBounds());
     expect(bounds, const ui.Rect.fromLTRB(10, 10, 20, 20));
   });
 
@@ -726,13 +755,15 @@
 
   test('reset', () {
     final SkPath testPath = _testClosedSkPath();
-    expect(testPath.getBounds().toRect(), const ui.Rect.fromLTRB(10, 10, 20, 20));
+    expect(fromSkRect(testPath.getBounds()),
+        const ui.Rect.fromLTRB(10, 10, 20, 20));
     testPath.reset();
-    expect(testPath.getBounds().toRect(), ui.Rect.zero);
+    expect(fromSkRect(testPath.getBounds()), ui.Rect.zero);
   });
 
   test('toSVGString', () {
-    expect(_testClosedSkPath().toSVGString(), 'M10 10L20 10L20 20L10 20L10 10Z');
+    expect(
+        _testClosedSkPath().toSVGString(), 'M10 10L20 10L20 20L10 20L10 10Z');
   });
 
   test('isEmpty', () {
@@ -743,22 +774,24 @@
   test('copy', () {
     final SkPath original = _testClosedSkPath();
     final SkPath copy = original.copy();
-    expect(original.getBounds().toRect(), copy.getBounds().toRect());
+    expect(fromSkRect(original.getBounds()), fromSkRect(copy.getBounds()));
   });
 
   test('transform', () {
     path = _testClosedSkPath();
     path.transform(2, 0, 10, 0, 2, 10, 0, 0, 0);
-    final ui.Rect transformedBounds = path.getBounds().toRect();
+    final ui.Rect transformedBounds = fromSkRect(path.getBounds());
     expect(transformedBounds, ui.Rect.fromLTRB(30, 30, 50, 50));
   });
 
   test('SkContourMeasureIter/SkContourMeasure', () {
-    final SkContourMeasureIter iter = SkContourMeasureIter(_testClosedSkPath(), false, 0);
+    final SkContourMeasureIter iter =
+        SkContourMeasureIter(_testClosedSkPath(), false, 0);
     final SkContourMeasure measure1 = iter.next();
     expect(measure1.length(), 40);
     expect(measure1.getPosTan(5), Float32List.fromList(<double>[15, 10, 1, 0]));
-    expect(measure1.getPosTan(15), Float32List.fromList(<double>[20, 15, 0, 1]));
+    expect(
+        measure1.getPosTan(15), Float32List.fromList(<double>[20, 15, 0, 1]));
     expect(measure1.isClosed(), true);
 
     // Starting with a box path:
@@ -783,29 +816,13 @@
     //    |           |
     // 20 +-----------+
     final SkPath segment = measure1.getSegment(5, 15, true);
-    expect(segment.getBounds().toRect(), ui.Rect.fromLTRB(15, 10, 20, 15));
+    expect(fromSkRect(segment.getBounds()), ui.Rect.fromLTRB(15, 10, 20, 15));
 
     final SkContourMeasure measure2 = iter.next();
     expect(measure2, isNull);
   });
 }
 
-void _skSkRectTests() {
-  test('SkRect', () {
-    final SkRect rect = SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4);
-    expect(rect.fLeft, 1);
-    expect(rect.fTop, 2);
-    expect(rect.fRight, 3);
-    expect(rect.fBottom, 4);
-
-    final ui.Rect uiRect = rect.toRect();
-    expect(uiRect.left, 1);
-    expect(uiRect.top, 2);
-    expect(uiRect.right, 3);
-    expect(uiRect.bottom, 4);
-  });
-}
-
 SkVertices _testVertices() {
   return canvasKit.MakeSkVertices(
     canvasKit.VertexMode.Triangles,
@@ -840,12 +857,8 @@
 
   setUp(() {
     recorder = SkPictureRecorder();
-    canvas = recorder.beginRecording(SkRect(
-      fLeft: 0,
-      fTop: 0,
-      fRight: 100,
-      fBottom: 100,
-    ));
+    canvas =
+        recorder.beginRecording(toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100)));
   });
 
   tearDown(() {
@@ -866,33 +879,23 @@
 
   test('saveLayer', () {
     canvas.saveLayer(
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 100,
-        fBottom: 100,
-      ),
       SkPaint(),
+      toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100)),
+      null,
+      null,
     );
   });
 
-  test('SkCanvasSaveLayerWithoutBoundsOverload.saveLayer', () {
-    final SkCanvasSaveLayerWithoutBoundsOverload override = canvas as SkCanvasSaveLayerWithoutBoundsOverload;
-    override.saveLayer(SkPaint());
+  test('saveLayer without bounds', () {
+    canvas.saveLayer(SkPaint(), null, null, null);
   });
 
-  test('SkCanvasSaveLayerWithFilterOverload.saveLayer', () {
-    final SkCanvasSaveLayerWithFilterOverload override = canvas as SkCanvasSaveLayerWithFilterOverload;
-    override.saveLayer(
+  test('saveLayer with filter', () {
+    canvas.saveLayer(
       SkPaint(),
+      toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100)),
       canvasKit.SkImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null),
       0,
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 100,
-        fBottom: 100,
-      ),
     );
   });
 
@@ -910,22 +913,7 @@
 
   test('clipRRect', () {
     canvas.clipRRect(
-      SkRRect(
-        rect: SkRect(
-          fLeft: 0,
-          fTop: 0,
-          fRight: 100,
-          fBottom: 100,
-        ),
-        rx1: 1,
-        ry1: 2,
-        rx2: 3,
-        ry2: 4,
-        rx3: 5,
-        ry3: 6,
-        rx4: 7,
-        ry4: 8,
-      ),
+      Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]),
       canvasKit.ClipOp.Intersect,
       true,
     );
@@ -933,12 +921,7 @@
 
   test('clipRect', () {
     canvas.clipRect(
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 100,
-        fBottom: 100,
-      ),
+      Float32List.fromList([0, 0, 100, 100]),
       canvasKit.ClipOp.Intersect,
       true,
     );
@@ -946,12 +929,7 @@
 
   test('drawArc', () {
     canvas.drawArc(
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 100,
-        fBottom: 50,
-      ),
+      Float32List.fromList([0, 0, 100, 50]),
       0,
       100,
       true,
@@ -960,7 +938,8 @@
   });
 
   test('drawAtlas', () {
-    final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
+    final SkAnimatedImage image =
+        canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
     canvas.drawAtlas(
       image.getCurrentFrame(),
       Float32List.fromList([0, 0, 1, 1]),
@@ -984,44 +963,15 @@
 
   test('drawDRRect', () {
     canvas.drawDRRect(
-      SkRRect(
-        rect: SkRect(
-          fLeft: 0,
-          fTop: 0,
-          fRight: 100,
-          fBottom: 100,
-        ),
-        rx1: 1,
-        ry1: 2,
-        rx2: 3,
-        ry2: 4,
-        rx3: 5,
-        ry3: 6,
-        rx4: 7,
-        ry4: 8,
-      ),
-      SkRRect(
-        rect: SkRect(
-          fLeft: 20,
-          fTop: 20,
-          fRight: 80,
-          fBottom: 80,
-        ),
-        rx1: 1,
-        ry1: 2,
-        rx2: 3,
-        ry2: 4,
-        rx3: 5,
-        ry3: 6,
-        rx4: 7,
-        ry4: 8,
-      ),
+      Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]),
+      Float32List.fromList([20, 20, 80, 80, 1, 2, 3, 4, 5, 6, 7, 8]),
       SkPaint(),
     );
   });
 
   test('drawImage', () {
-    final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
+    final SkAnimatedImage image =
+        canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
     canvas.drawImage(
       image.getCurrentFrame(),
       10,
@@ -1031,22 +981,24 @@
   });
 
   test('drawImageRect', () {
-    final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
+    final SkAnimatedImage image =
+        canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
     canvas.drawImageRect(
       image.getCurrentFrame(),
-      SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1),
-      SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1),
+      Float32List.fromList([0, 0, 1, 1]),
+      Float32List.fromList([0, 0, 1, 1]),
       SkPaint(),
       false,
     );
   });
 
   test('drawImageNine', () {
-    final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
+    final SkAnimatedImage image =
+        canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage);
     canvas.drawImageNine(
       image.getCurrentFrame(),
-      SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1),
-      SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1),
+      Float32List.fromList([0, 0, 1, 1]),
+      Float32List.fromList([0, 0, 1, 1]),
       SkPaint(),
     );
   });
@@ -1056,7 +1008,7 @@
   });
 
   test('drawOval', () {
-    canvas.drawOval(SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), SkPaint());
+    canvas.drawOval(Float32List.fromList([0, 0, 1, 1]), SkPaint());
   });
 
   test('drawPaint', () {
@@ -1080,34 +1032,14 @@
 
   test('drawRRect', () {
     canvas.drawRRect(
-      SkRRect(
-        rect: SkRect(
-          fLeft: 0,
-          fTop: 0,
-          fRight: 100,
-          fBottom: 100,
-        ),
-        rx1: 1,
-        ry1: 2,
-        rx2: 3,
-        ry2: 4,
-        rx3: 5,
-        ry3: 6,
-        rx4: 7,
-        ry4: 8,
-      ),
+      Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]),
       SkPaint(),
     );
   });
 
   test('drawRect', () {
     canvas.drawRect(
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 100,
-        fBottom: 100,
-      ),
+      Float32List.fromList([0, 0, 100, 100]),
       SkPaint(),
     );
   });
@@ -1120,12 +1052,13 @@
       const double spotAlpha = 0.25;
 
       final SkPath path = _testClosedSkPath();
-      final ui.Rect bounds = path.getBounds().toRect();
+      final ui.Rect bounds = fromSkRect(path.getBounds());
       final double shadowX = (bounds.left + bounds.right) / 2.0;
       final double shadowY = bounds.top - 600.0;
 
       const ui.Color color = ui.Color(0xAABBCCDD);
-      ui.Color inAmbient = color.withAlpha((color.alpha * ambientAlpha).round());
+      ui.Color inAmbient =
+          color.withAlpha((color.alpha * ambientAlpha).round());
       ui.Color inSpot = color.withAlpha((color.alpha * spotAlpha).round());
 
       final SkTonalColors inTonalColors = SkTonalColors(
@@ -1138,8 +1071,7 @@
 
       canvas.drawShadow(
         path,
-        Float32List(3)
-          ..[2] = devicePixelRatio * elevation,
+        Float32List(3)..[2] = devicePixelRatio * elevation,
         Float32List(3)
           ..[0] = shadowX
           ..[1] = shadowY
@@ -1186,12 +1118,8 @@
 
   test('drawPicture', () {
     final SkPictureRecorder otherRecorder = SkPictureRecorder();
-    final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect(
-      fLeft: 0,
-      fTop: 0,
-      fRight: 100,
-      fBottom: 100,
-    ));
+    final SkCanvas otherCanvas =
+        otherRecorder.beginRecording(Float32List.fromList([0, 0, 100, 100]));
     otherCanvas.drawLine(0, 0, 10, 10, SkPaint());
     canvas.drawPicture(otherRecorder.finishRecordingAsPicture());
   });
@@ -1212,26 +1140,79 @@
 
   test('toImage.toByteData', () async {
     final SkPictureRecorder otherRecorder = SkPictureRecorder();
-    final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect(
-      fLeft: 0,
-      fTop: 0,
-      fRight: 1,
-      fBottom: 1,
-    ));
+    final SkCanvas otherCanvas =
+        otherRecorder.beginRecording(Float32List.fromList([0, 0, 1, 1]));
     otherCanvas.drawRect(
-      SkRect(
-        fLeft: 0,
-        fTop: 0,
-        fRight: 1,
-        fBottom: 1,
-      ),
+      Float32List.fromList([0, 0, 1, 1]),
       SkPaint(),
     );
-    final CkPicture picture = CkPicture(otherRecorder.finishRecordingAsPicture(), null);
+    final CkPicture picture =
+        CkPicture(otherRecorder.finishRecordingAsPicture(), null);
     final CkImage image = await picture.toImage(1, 1);
-    final ByteData rawData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
+    final ByteData rawData =
+        await image.toByteData(format: ui.ImageByteFormat.rawRgba);
     expect(rawData, isNotNull);
-    final ByteData pngData = await image.toByteData(format: ui.ImageByteFormat.png);
+    final ByteData pngData =
+        await image.toByteData(format: ui.ImageByteFormat.png);
     expect(pngData, isNotNull);
   });
 }
+
+void _textStyleTests() {
+  test('SkTextDecorationStyle mapping is correct', () {
+    expect(canvasKit.DecorationStyle.Solid.value,
+        ui.TextDecorationStyle.solid.index);
+    expect(canvasKit.DecorationStyle.Double.value,
+        ui.TextDecorationStyle.double.index);
+    expect(canvasKit.DecorationStyle.Dotted.value,
+        ui.TextDecorationStyle.dotted.index);
+    expect(canvasKit.DecorationStyle.Dashed.value,
+        ui.TextDecorationStyle.dashed.index);
+    expect(canvasKit.DecorationStyle.Wavy.value,
+        ui.TextDecorationStyle.wavy.index);
+  });
+
+  test('ui.TextDecorationStyle converts to SkTextDecorationStyle', () {
+    for (ui.TextDecorationStyle decorationStyle
+        in ui.TextDecorationStyle.values) {
+      expect(toSkTextDecorationStyle(decorationStyle).value,
+          decorationStyle.index);
+    }
+  });
+
+  test('SkTextBaseline mapping is correct', () {
+    expect(canvasKit.TextBaseline.Alphabetic.value,
+        ui.TextBaseline.alphabetic.index);
+    expect(canvasKit.TextBaseline.Ideographic.value,
+        ui.TextBaseline.ideographic.index);
+  });
+
+  test('ui.TextBaseline converts to SkTextBaseline', () {
+    for (ui.TextBaseline textBaseline in ui.TextBaseline.values) {
+      expect(toSkTextBaseline(textBaseline).value, textBaseline.index);
+    }
+  });
+
+  test('SkPlaceholderAlignment mapping is correct', () {
+    expect(canvasKit.PlaceholderAlignment.Baseline.value,
+        ui.PlaceholderAlignment.baseline.index);
+    expect(canvasKit.PlaceholderAlignment.AboveBaseline.value,
+        ui.PlaceholderAlignment.aboveBaseline.index);
+    expect(canvasKit.PlaceholderAlignment.BelowBaseline.value,
+        ui.PlaceholderAlignment.belowBaseline.index);
+    expect(canvasKit.PlaceholderAlignment.Top.value,
+        ui.PlaceholderAlignment.top.index);
+    expect(canvasKit.PlaceholderAlignment.Bottom.value,
+        ui.PlaceholderAlignment.bottom.index);
+    expect(canvasKit.PlaceholderAlignment.Middle.value,
+        ui.PlaceholderAlignment.middle.index);
+  });
+
+  test('ui.PlaceholderAlignment converts to SkPlaceholderAlignment', () {
+    for (ui.PlaceholderAlignment placeholderAlignment
+        in ui.PlaceholderAlignment.values) {
+      expect(toSkPlaceholderAlignment(placeholderAlignment).value,
+          placeholderAlignment.index);
+    }
+  });
+}