From 94b23abaae1f1e9c1937c2f2dc41146b7f9d37ad Mon Sep 17 00:00:00 2001 From: Yegor Date: Thu, 16 Jul 2020 11:56:37 -0700 Subject: [PATCH] Move CkCanvas to new JS-interop (#19748) * Move CkCanvas to new JS-interop * Make SkiaObject compatible with JS-interop; text bindings * fix drawVertices --- .../lib/src/engine/compositor/canvas.dart | 216 +++-- .../engine/compositor/canvas_kit_canvas.dart | 41 +- .../src/engine/compositor/canvaskit_api.dart | 771 +++++++++++++++++- .../src/engine/compositor/color_filter.dart | 21 +- .../src/engine/compositor/embedded_views.dart | 4 +- .../lib/src/engine/compositor/fonts.dart | 4 +- .../src/engine/compositor/image_filter.dart | 20 +- .../lib/src/engine/compositor/layer.dart | 18 +- .../compositor/layer_scene_builder.dart | 9 +- .../src/engine/compositor/mask_filter.dart | 24 +- .../src/engine/compositor/n_way_canvas.dart | 8 +- .../lib/src/engine/compositor/painting.dart | 74 +- .../lib/src/engine/compositor/path.dart | 3 - .../lib/src/engine/compositor/picture.dart | 21 +- .../engine/compositor/picture_recorder.dart | 31 +- .../engine/compositor/skia_object_cache.dart | 91 ++- .../lib/src/engine/compositor/surface.dart | 4 +- .../lib/src/engine/compositor/text.dart | 295 +++---- .../lib/src/engine/compositor/util.dart | 69 +- .../lib/src/engine/compositor/vertices.dart | 75 +- lib/web_ui/lib/src/engine/shader.dart | 11 +- .../test/canvaskit/canvaskit_api_test.dart | 516 +++++++++++- lib/web_ui/test/canvaskit/common.dart | 11 + .../test/canvaskit/path_metrics_test.dart | 8 +- .../canvaskit/skia_objects_cache_test.dart | 83 +- 25 files changed, 1791 insertions(+), 637 deletions(-) create mode 100644 lib/web_ui/test/canvaskit/common.dart diff --git a/lib/web_ui/lib/src/engine/compositor/canvas.dart b/lib/web_ui/lib/src/engine/compositor/canvas.dart index 7233331d744..08222ad3a51 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvas.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvas.dart @@ -6,49 +6,41 @@ part of engine; /// A Dart wrapper around Skia's SKCanvas. class CkCanvas { - final js.JsObject skCanvas; + final SkCanvas skCanvas; CkCanvas(this.skCanvas); - int? get saveCount => skCanvas.callMethod('getSaveCount'); + int? get saveCount => skCanvas.getSaveCount(); void clear(ui.Color color) { - setSharedSkColor1(color); - skCanvas.callMethod('clear', [sharedSkColor1]); + skCanvas.clear(toSharedSkColor1(color)); } + static final SkClipOp _clipOpIntersect = canvasKitJs.ClipOp.Intersect; + void clipPath(ui.Path path, bool doAntiAlias) { - final CkPath skPath = path as CkPath; - final js.JsObject? intersectClipOp = canvasKit['ClipOp']['Intersect']; - skCanvas.callMethod('clipPath', [ - skPath._legacyJsObject, - intersectClipOp, + final CkPath ckPath = path as CkPath; + skCanvas.clipPath( + ckPath._skPath, + _clipOpIntersect, doAntiAlias, - ]); + ); } void clipRRect(ui.RRect rrect, bool doAntiAlias) { - final js.JsObject? intersectClipOp = canvasKit['ClipOp']['Intersect']; - skCanvas.callMethod('clipRRect', [ - makeSkRRect(rrect), - intersectClipOp, + skCanvas.clipRRect( + toSkRRect(rrect), + _clipOpIntersect, doAntiAlias, - ]); + ); } void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) { - js.JsObject? skClipOp; - switch (clipOp) { - case ui.ClipOp.difference: - skClipOp = canvasKit['ClipOp']['Difference']; - break; - case ui.ClipOp.intersect: - skClipOp = canvasKit['ClipOp']['Intersect']; - break; - } - - skCanvas.callMethod( - 'clipRect', [makeSkRect(rect), skClipOp, doAntiAlias]); + skCanvas.clipRect( + toSkRect(rect), + toSkClipOp(clipOp), + doAntiAlias, + ); } void drawArc( @@ -59,13 +51,13 @@ class CkCanvas { CkPaint paint, ) { const double toDegrees = 180 / math.pi; - skCanvas.callMethod('drawArc', [ - makeSkRect(oval), + skCanvas.drawArc( + toSkRect(oval), startAngle * toDegrees, sweepAngle * toDegrees, useCenter, paint.skiaObject, - ]); + ); } void drawAtlasRaw( @@ -73,140 +65,131 @@ class CkCanvas { ui.Image atlas, Float32List rstTransforms, Float32List rects, - js.JsArray? colors, + List? colors, ui.BlendMode blendMode, ) { final CkImage skAtlas = atlas as CkImage; - skCanvas.callMethod('drawAtlas', [ - skAtlas.legacyJsObject, + skCanvas.drawAtlas( + skAtlas.skImage, rects, rstTransforms, paint.skiaObject, - makeSkBlendMode(blendMode), + toSkBlendMode(blendMode), colors, - ]); + ); } void drawCircle(ui.Offset c, double radius, CkPaint paint) { - skCanvas.callMethod('drawCircle', [ + skCanvas.drawCircle( c.dx, c.dy, radius, paint.skiaObject, - ]); + ); } void drawColor(ui.Color color, ui.BlendMode blendMode) { - skCanvas.callMethod('drawColorInt', [ + skCanvas.drawColorInt( color.value, - makeSkBlendMode(blendMode), - ]); + toSkBlendMode(blendMode), + ); } void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) { - skCanvas.callMethod('drawDRRect', [ - makeSkRRect(outer), - makeSkRRect(inner), + skCanvas.drawDRRect( + toSkRRect(outer), + toSkRRect(inner), paint.skiaObject, - ]); + ); } void drawImage(ui.Image image, ui.Offset offset, CkPaint paint) { final CkImage skImage = image as CkImage; - skCanvas.callMethod('drawImage', [ - skImage.legacyJsObject, + skCanvas.drawImage( + skImage.skImage, offset.dx, offset.dy, paint.skiaObject, - ]); + ); } void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, CkPaint paint) { final CkImage skImage = image as CkImage; - skCanvas.callMethod('drawImageRect', [ - skImage.legacyJsObject, - makeSkRect(src), - makeSkRect(dst), + skCanvas.drawImageRect( + skImage.skImage, + toSkRect(src), + toSkRect(dst), paint.skiaObject, false, - ]); + ); } void drawImageNine( ui.Image image, ui.Rect center, ui.Rect dst, CkPaint paint) { final CkImage skImage = image as CkImage; - skCanvas.callMethod('drawImageNine', [ - skImage.legacyJsObject, - makeSkRect(center), - makeSkRect(dst), + skCanvas.drawImageNine( + skImage.skImage, + toSkRect(center), + toSkRect(dst), paint.skiaObject, - ]); + ); } void drawLine(ui.Offset p1, ui.Offset p2, CkPaint paint) { - skCanvas.callMethod('drawLine', [ + skCanvas.drawLine( p1.dx, p1.dy, p2.dx, p2.dy, paint.skiaObject, - ]); + ); } void drawOval(ui.Rect rect, CkPaint paint) { - skCanvas.callMethod('drawOval', [ - makeSkRect(rect), + skCanvas.drawOval( + toSkRect(rect), paint.skiaObject, - ]); + ); } void drawPaint(CkPaint paint) { - skCanvas.callMethod('drawPaint', [paint.skiaObject]); + skCanvas.drawPaint(paint.skiaObject); } - void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - final CkParagraph skParagraph = paragraph as CkParagraph; - skCanvas.callMethod('drawParagraph', [ - skParagraph.skiaObject, + void drawParagraph(CkParagraph paragraph, ui.Offset offset) { + skCanvas.drawParagraph( + _jsObjectWrapper.unwrapSkParagraph(paragraph.legacySkiaObject), offset.dx, offset.dy, - ]); + ); } - void drawPath(ui.Path path, CkPaint paint) { - final js.JsObject? skPaint = paint.skiaObject; - final CkPath enginePath = path as CkPath; - final js.JsObject? skPath = enginePath._legacyJsObject; - skCanvas.callMethod('drawPath', [skPath, skPaint]); + void drawPath(CkPath path, CkPaint paint) { + skCanvas.drawPath(path._skPath, paint.skiaObject); } - void drawPicture(ui.Picture picture) { - final CkPicture skPicture = picture as CkPicture; - skCanvas.callMethod( - 'drawPicture', [skPicture.skPicture.skiaObject]); + void drawPicture(CkPicture picture) { + skCanvas.drawPicture(picture._skPicture); } - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/58824 void drawPoints(CkPaint paint, ui.PointMode pointMode, - js.JsArray>? points) { - skCanvas.callMethod('drawPoints', [ - makeSkPointMode(pointMode), + Float32List points) { + skCanvas.drawPoints( + toSkPointMode(pointMode), points, paint.skiaObject, - ]); + ); } void drawRRect(ui.RRect rrect, CkPaint paint) { - skCanvas.callMethod('drawRRect', [ - makeSkRRect(rrect), + skCanvas.drawRRect( + toSkRRect(rrect), paint.skiaObject, - ]); + ); } void drawRect(ui.Rect rect, CkPaint paint) { - final js.JsObject skRect = makeSkRect(rect); - final js.JsObject? skPaint = paint.skiaObject; - skCanvas.callMethod('drawRect', [skRect, skPaint]); + skCanvas.drawRect(toSkRect(rect), paint.skiaObject); } void drawShadow(ui.Path path, ui.Color color, double elevation, @@ -218,72 +201,69 @@ class CkCanvas { void drawVertices( ui.Vertices vertices, ui.BlendMode blendMode, CkPaint paint) { CkVertices skVertices = vertices as CkVertices; - skCanvas.callMethod('drawVertices', [ + skCanvas.drawVertices( skVertices.skVertices, - makeSkBlendMode(blendMode), - paint.skiaObject - ]); + toSkBlendMode(blendMode), + paint.skiaObject, + ); } void restore() { - skCanvas.callMethod('restore'); + skCanvas.restore(); } - void restoreToCount(int? count) { - skCanvas.callMethod('restoreToCount', [count]); + void restoreToCount(int count) { + skCanvas.restoreToCount(count); } void rotate(double radians) { - skCanvas - .callMethod('rotate', [radians * 180.0 / math.pi, 0.0, 0.0]); + skCanvas.rotate(radians * 180.0 / math.pi, 0.0, 0.0); } - int? save() { - return skCanvas.callMethod('save'); + int save() { + return skCanvas.save(); } void saveLayer(ui.Rect bounds, CkPaint paint) { - skCanvas.callMethod('saveLayer', [ - makeSkRect(bounds), + skCanvas.saveLayer( + toSkRect(bounds), paint.skiaObject, - ]); + ); } void saveLayerWithoutBounds(CkPaint paint) { - skCanvas.callMethod('saveLayer', [paint.skiaObject]); + final SkCanvasSaveLayerWithoutBoundsOverride override = _jsObjectWrapper.castToSkCanvasSaveLayerWithoutBoundsOverride(skCanvas); + override.saveLayer(paint.skiaObject); } void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { + final SkCanvasSaveLayerWithFilterOverride override = _jsObjectWrapper.castToSkCanvasSaveLayerWithFilterOverride(skCanvas); final CkImageFilter skImageFilter = filter as CkImageFilter; - return skCanvas.callMethod( - 'saveLayer', - [ - null, - skImageFilter.skiaObject, - 0, - makeSkRect(bounds), - ], + return override.saveLayer( + null, + skImageFilter.skiaObject, + 0, + toSkRect(bounds), ); } void scale(double sx, double sy) { - skCanvas.callMethod('scale', [sx, sy]); + skCanvas.scale(sx, sy); } void skew(double sx, double sy) { - skCanvas.callMethod('skew', [sx, sy]); + skCanvas.skew(sx, sy); } - void transform(Float32List? matrix4) { - skCanvas.callMethod( - 'concat', >[makeSkMatrixFromFloat32(matrix4)]); + void transform(Float32List matrix4) { + skCanvas.concat(toSkMatrixFromFloat32(matrix4)); } void translate(double dx, double dy) { - skCanvas.callMethod('translate', [dx, dy]); + skCanvas.translate(dx, dy); } void flush() { - skCanvas.callMethod('flush'); + skCanvas.flush(); } } diff --git a/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart b/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart index 5fed3f9aef3..bb8c2eff475 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart @@ -234,11 +234,7 @@ class CanvasKitCanvas implements ui.Canvas { // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side assert(paint != null); // ignore: unnecessary_null_comparison - _drawPath(path, paint); - } - - void _drawPath(ui.Path path, ui.Paint paint) { - _canvas!.drawPath(path, paint as CkPaint); + _canvas!.drawPath(path as CkPath, paint as CkPaint); } @override @@ -289,11 +285,7 @@ class CanvasKitCanvas implements ui.Canvas { void drawPicture(ui.Picture picture) { // ignore: unnecessary_null_comparison assert(picture != null); // picture is checked on the engine side - _drawPicture(picture); - } - - void _drawPicture(ui.Picture picture) { - _canvas!.drawPicture(picture); + _canvas!.drawPicture(picture as CkPicture); } @override @@ -304,7 +296,7 @@ class CanvasKitCanvas implements ui.Canvas { } void _drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - _canvas!.drawParagraph(paragraph, offset); + _canvas!.drawParagraph(paragraph as CkParagraph, offset); } @override @@ -313,7 +305,13 @@ class CanvasKitCanvas implements ui.Canvas { assert(pointMode != null); // ignore: unnecessary_null_comparison assert(points != null); // ignore: unnecessary_null_comparison assert(paint != null); // ignore: unnecessary_null_comparison - _drawPoints(paint, pointMode, encodePointList(points)); + final SkFloat32List skPoints = toMallocedSkPoints(points); + _canvas!.drawPoints( + paint as CkPaint, + pointMode, + skPoints.toTypedArray(), + ); + freeFloat32List(skPoints); } @override @@ -325,12 +323,11 @@ class CanvasKitCanvas implements ui.Canvas { if (points.length % 2 != 0) { throw ArgumentError('"points" must have an even number of values.'); } - _drawPoints(paint, pointMode, encodeRawPointList(points)); - } - - void _drawPoints( - ui.Paint paint, ui.PointMode pointMode, List>? points) { - _canvas!.drawPoints(paint as CkPaint, pointMode, points as js.JsArray>?); + _canvas!.drawPoints( + paint as CkPaint, + pointMode, + points, + ); } @override @@ -395,8 +392,8 @@ class CanvasKitCanvas implements ui.Canvas { rectBuffer[index3] = rect.bottom; } - final js.JsArray? colorBuffer = - colors.isEmpty ? null : makeColorList(colors); + final List? colorBuffer = + colors.isEmpty ? null : toSkFloatColorList(colors); _drawAtlas( paint, atlas, rstTransformBuffer, rectBuffer, colorBuffer, blendMode); @@ -429,7 +426,7 @@ class CanvasKitCanvas implements ui.Canvas { throw ArgumentError( 'If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".'); - _drawAtlas(paint, atlas, rstTransforms, rects, _encodeRawColorList(colors), + _drawAtlas(paint, atlas, rstTransforms, rects, encodeRawColorList(colors), blendMode); } @@ -439,7 +436,7 @@ class CanvasKitCanvas implements ui.Canvas { ui.Image atlas, Float32List rstTransforms, Float32List rects, - js.JsArray? colors, + List? colors, ui.BlendMode blendMode, ) { _canvas!.drawAtlasRaw(paint as CkPaint, atlas, rstTransforms, rects, colors, blendMode); diff --git a/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart b/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart index 3e4b59046b8..51c3149973a 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvaskit_api.dart @@ -3,6 +3,9 @@ // found in the LICENSE file. /// Bindings for CanvasKit JavaScript API. +/// +/// Prefer keeping the originl CanvasKit names so it is easier to locate +/// the API behind these bindings in the Skia source code. part of engine; final js.JsObject _jsWindow = js.JsObject.fromBrowserObject(html.window); @@ -37,12 +40,26 @@ class JsObjectWrapper { external set skImageFilter(SkImageFilter? filter); external set skPath(SkPath? path); external set skImage(SkImage? image); + external SkCanvas? get skCanvas; + external set skCanvas(SkCanvas? canvas); + external SkPicture? get skPicture; + external set skPicture(SkPicture? picture); + external SkParagraph? get skParagraph; + external set skParagraph(SkParagraph? paragraph); } /// Reads [JsObjectWrapper.skPath] as [SkPathArcToPointOverload]. @JS('window.flutter_js_object_wrapper.skPath') external SkPathArcToPointOverload get _skPathArcToPointOverload; +/// Reads [JsObjectWrapper.skCanvas] as [SkCanvasSaveLayerWithoutBoundsOverride]. +@JS('window.flutter_js_object_wrapper.skCanvas') +external SkCanvasSaveLayerWithoutBoundsOverride get _skCanvasSaveLayerWithoutBoundsOverride; + +/// Reads [JsObjectWrapper.skCanvas] as [SkCanvasSaveLayerWithFilterOverride]. +@JS('window.flutter_js_object_wrapper.skCanvas') +external SkCanvasSaveLayerWithFilterOverride get _skCanvasSaveLayerWithFilterOverride; + /// Specific methods that wrap `@JS`-backed objects into a [js.JsObject] /// for use with legacy `dart:js` API. extension JsObjectWrappers on JsObjectWrapper { @@ -88,12 +105,61 @@ extension JsObjectWrappers on JsObjectWrapper { return wrapped; } + js.JsObject wrapSkPicture(SkPicture picture) { + _jsObjectWrapper.skPicture = picture; + js.JsObject wrapped = _jsObjectWrapperLegacy['skPicture']; + _jsObjectWrapper.skPicture = null; + return wrapped; + } + + SkPicture unwrapSkPicture(js.JsObject wrapped) { + _jsObjectWrapperLegacy['skPicture'] = wrapped; + final SkPicture unwrapped = _jsObjectWrapper.skPicture!; + _jsObjectWrapper.skPicture = null; + return unwrapped; + } + + js.JsObject wrapSkParagraph(SkParagraph paragraph) { + _jsObjectWrapper.skParagraph = paragraph; + js.JsObject wrapped = _jsObjectWrapperLegacy['skParagraph']; + _jsObjectWrapper.skParagraph = null; + return wrapped; + } + + SkParagraph unwrapSkParagraph(js.JsObject wrapped) { + _jsObjectWrapperLegacy['skParagraph'] = wrapped; + final SkParagraph unwrapped = _jsObjectWrapper.skParagraph!; + _jsObjectWrapper.skParagraph = null; + return unwrapped; + } + + SkCanvas unwrapSkCanvas(js.JsObject wrapped) { + _jsObjectWrapperLegacy['skCanvas'] = wrapped; + final SkCanvas unwrapped = _jsObjectWrapper.skCanvas!; + _jsObjectWrapper.skCanvas = null; + return unwrapped; + } + SkPathArcToPointOverload castToSkPathArcToPointOverload(SkPath path) { _jsObjectWrapper.skPath = path; final SkPathArcToPointOverload overload = _skPathArcToPointOverload; _jsObjectWrapper.skPath = null; return overload; } + + SkCanvasSaveLayerWithoutBoundsOverride castToSkCanvasSaveLayerWithoutBoundsOverride(SkCanvas canvas) { + _jsObjectWrapper.skCanvas = canvas; + final SkCanvasSaveLayerWithoutBoundsOverride overload = _skCanvasSaveLayerWithoutBoundsOverride; + _jsObjectWrapper.skCanvas = null; + return overload; + } + + SkCanvasSaveLayerWithFilterOverride castToSkCanvasSaveLayerWithFilterOverride(SkCanvas canvas) { + _jsObjectWrapper.skCanvas = canvas; + final SkCanvasSaveLayerWithFilterOverride overload = _skCanvasSaveLayerWithFilterOverride; + _jsObjectWrapper.skCanvas = null; + return overload; + } } @JS('window.flutter_canvas_kit') @@ -110,12 +176,274 @@ class CanvasKit { external SkTileModeEnum get TileMode; external SkFillTypeEnum get FillType; external SkPathOpEnum get PathOp; + external SkClipOpEnum get ClipOp; + external SkPointModeEnum get PointMode; + external SkVertexModeEnum get VertexMode; + external SkRectHeightStyleEnum get RectHeightStyle; + external SkRectWidthStyleEnum get RectWidthStyle; + external SkAffinityEnum get Affinity; + external SkTextAlignEnum get TextAlign; + external SkTextDirectionEnum get TextDirection; + external SkFontWeightEnum get FontWeight; + external SkFontSlantEnum get FontSlant; external SkAnimatedImage MakeAnimatedImageFromEncoded(Uint8List imageData); external SkShaderNamespace get SkShader; external SkMaskFilter MakeBlurMaskFilter(SkBlurStyle blurStyle, double sigma, bool respectCTM); external SkColorFilterNamespace get SkColorFilter; external SkImageFilterNamespace get SkImageFilter; external SkPath MakePathFromOp(SkPath path1, SkPath path2, SkPathOp pathOp); + external SkTonalColors computeTonalColors(SkTonalColors inTonalColors); + external SkVertices MakeSkVertices( + SkVertexMode mode, + List positions, + List? textureCoordinates, + // TODO(yjbanov): make this Uint32Array when CanvasKit supports it. + List? colors, + Uint16List? indices, + ); + external SkParagraphBuilderNamespace get ParagraphBuilder; + external SkParagraphStyle ParagraphStyle(SkParagraphStyleProperties properties); + external SkTextStyle TextStyle(SkTextStyleProperties properties); + + // Text decoration enum is embedded in the CanvasKit object itself. + external int get NoDecoration; + external int get UnderlineDecoration; + external int get OverlineDecoration; + external int get LineThroughDecoration; + // End of text decoration enum. + + external SkFontMgrNamespace get SkFontMgr; +} + +@JS() +class SkFontSlantEnum { + external SkFontSlant get Upright; + external SkFontSlant get Italic; +} + +@JS() +class SkFontSlant { + external int get value; +} + +final List _skFontSlants = [ + canvasKitJs.FontSlant.Upright, + canvasKitJs.FontSlant.Italic, +]; + +SkFontSlant toSkFontSlant(ui.FontStyle style) { + return _skFontSlants[style.index]; +} + +@JS() +class SkFontWeightEnum { + external SkFontWeight get Thin; + external SkFontWeight get ExtraLight; + external SkFontWeight get Light; + external SkFontWeight get Normal; + external SkFontWeight get Medium; + external SkFontWeight get SemiBold; + external SkFontWeight get Bold; + external SkFontWeight get ExtraBold; + external SkFontWeight get ExtraBlack; +} + +@JS() +class SkFontWeight { + external int get value; +} + +final List _skFontWeights = [ + canvasKitJs.FontWeight.Thin, + canvasKitJs.FontWeight.ExtraLight, + canvasKitJs.FontWeight.Light, + canvasKitJs.FontWeight.Normal, + canvasKitJs.FontWeight.Medium, + canvasKitJs.FontWeight.SemiBold, + canvasKitJs.FontWeight.Bold, + canvasKitJs.FontWeight.ExtraBold, + canvasKitJs.FontWeight.ExtraBlack, +]; + +SkFontWeight toSkFontWeight(ui.FontWeight weight) { + return _skFontWeights[weight.index]; +} + +@JS() +class SkAffinityEnum { + external SkAffinity get Upstream; + external SkAffinity get Downstream; +} + +@JS() +class SkAffinity { + external int get value; +} + +final List _skAffinitys = [ + canvasKitJs.Affinity.Upstream, + canvasKitJs.Affinity.Downstream, +]; + +SkAffinity toSkAffinity(ui.TextAffinity affinity) { + return _skAffinitys[affinity.index]; +} + +@JS() +class SkTextDirectionEnum { + external SkTextDirection get RTL; + external SkTextDirection get LTR; +} + +@JS() +class SkTextDirection { + external int get value; +} + +// Flutter enumerates text directions as RTL, LTR, while CanvasKit +// enumerates them LTR, RTL. +final List _skTextDirections = [ + canvasKitJs.TextDirection.RTL, + canvasKitJs.TextDirection.LTR, +]; + +SkTextDirection toSkTextDirection(ui.TextDirection direction) { + return _skTextDirections[direction.index]; +} + +@JS() +class SkTextAlignEnum { + external SkTextAlign get Left; + external SkTextAlign get Right; + external SkTextAlign get Center; + external SkTextAlign get Justify; + external SkTextAlign get Start; + external SkTextAlign get End; +} + +@JS() +class SkTextAlign { + external int get value; +} + +final List _skTextAligns = [ + canvasKitJs.TextAlign.Left, + canvasKitJs.TextAlign.Right, + canvasKitJs.TextAlign.Center, + canvasKitJs.TextAlign.Justify, + canvasKitJs.TextAlign.Start, + canvasKitJs.TextAlign.End, +]; + +SkTextAlign toSkTextAlign(ui.TextAlign align) { + return _skTextAligns[align.index]; +} + +@JS() +class SkRectHeightStyleEnum { + // TODO(yjbanov): support all styles + external SkRectHeightStyle get Tight; + external SkRectHeightStyle get Max; +} + +@JS() +class SkRectHeightStyle { + external int get value; +} + +final List _skRectHeightStyles = [ + canvasKitJs.RectHeightStyle.Tight, + canvasKitJs.RectHeightStyle.Max, +]; + +SkRectHeightStyle toSkRectHeightStyle(ui.BoxHeightStyle style) { + final int index = style.index; + return _skRectHeightStyles[index < 2 ? index : 0]; +} + +@JS() +class SkRectWidthStyleEnum { + external SkRectWidthStyle get Tight; + external SkRectWidthStyle get Max; +} + +@JS() +class SkRectWidthStyle { + external int get value; +} + +final List _skRectWidthStyles = [ + canvasKitJs.RectWidthStyle.Tight, + canvasKitJs.RectWidthStyle.Max, +]; + +SkRectWidthStyle toSkRectWidthStyle(ui.BoxWidthStyle style) { + final int index = style.index; + return _skRectWidthStyles[index < 2 ? index : 0]; +} + +@JS() +class SkVertexModeEnum { + external SkVertexMode get Triangles; + external SkVertexMode get TrianglesStrip; + external SkVertexMode get TriangleFan; +} + +@JS() +class SkVertexMode { + external int get value; +} + +final List _skVertexModes = [ + canvasKitJs.VertexMode.Triangles, + canvasKitJs.VertexMode.TrianglesStrip, + canvasKitJs.VertexMode.TriangleFan, +]; + +SkVertexMode toSkVertexMode(ui.VertexMode mode) { + return _skVertexModes[mode.index]; +} + +@JS() +class SkPointModeEnum { + external SkPointMode get Points; + external SkPointMode get Lines; + external SkPointMode get Polygon; +} + +@JS() +class SkPointMode { + external int get value; +} + +final List _skPointModes = [ + canvasKitJs.PointMode.Points, + canvasKitJs.PointMode.Lines, + canvasKitJs.PointMode.Polygon, +]; + +SkPointMode toSkPointMode(ui.PointMode mode) { + return _skPointModes[mode.index]; +} + +@JS() +class SkClipOpEnum { + external SkClipOp get Difference; + external SkClipOp get Intersect; +} + +@JS() +class SkClipOp { + external int get value; +} + +final List _skClipOps = [ + canvasKitJs.ClipOp.Difference, + canvasKitJs.ClipOp.Intersect, +]; + +SkClipOp toSkClipOp(ui.ClipOp clipOp) { + return _skClipOps[clipOp.index]; } @JS() @@ -184,8 +512,8 @@ final List _skBlurStyles = [ canvasKitJs.BlurStyle.Inner, ]; -SkBlurStyle toSkBlurStyle(ui.BlurStyle strokeCap) { - return _skBlurStyles[strokeCap.index]; +SkBlurStyle toSkBlurStyle(ui.BlurStyle style) { + return _skBlurStyles[style.index]; } @JS() @@ -370,8 +698,8 @@ final List _skTileModes = [ canvasKitJs.TileMode.Mirror, ]; -SkTileMode toSkTileMode(ui.TileMode filterQuality) { - return _skTileModes[filterQuality.index]; +SkTileMode toSkTileMode(ui.TileMode mode) { + return _skTileModes[mode.index]; } @JS() @@ -403,7 +731,7 @@ class SkShaderNamespace { external SkShader MakeLinearGradient( Float32List from, // 2-element array Float32List to, // 2-element array - Uint32List colors, + List colors, Float32List colorStops, SkTileMode tileMode, ); @@ -457,10 +785,13 @@ class SkPaint { external void setColorFilter(SkColorFilter? colorFilter); external void setStrokeMiter(double miterLimit); external void setImageFilter(SkImageFilter? imageFilter); + external void delete(); } @JS() -class SkMaskFilter {} +class SkMaskFilter { + external void delete(); +} @JS() class SkColorFilterNamespace { @@ -473,7 +804,9 @@ class SkColorFilterNamespace { } @JS() -class SkColorFilter {} +class SkColorFilter { + external void delete(); +} @JS() class SkImageFilterNamespace { @@ -492,7 +825,9 @@ class SkImageFilterNamespace { } @JS() -class SkImageFilter {} +class SkImageFilter { + external void delete(); +} /// Converts a 4x4 Flutter matrix (represented as a [Float32List]) to an /// SkMatrix, which is a 3x3 transform matrix. @@ -647,6 +982,30 @@ Uint32List toSkIntColorList(List colors) { return result; } +List toSkFloatColorList(List colors) { + final int len = colors.length; + final List result = []; + for (int i = 0; i < len; i++) { + final Float32List array = Float32List(4); + final ui.Color color = colors[i]; + array[0] = color.red / 255.0; + array[1] = color.green / 255.0; + array[2] = color.blue / 255.0; + array[3] = color.alpha / 255.0; + result.add(array); + } + return result; +} + +List encodeRawColorList(Int32List rawColors) { + final int colorCount = rawColors.length; + final List colors = []; + for (int i = 0; i < colorCount; ++i) { + colors.add(ui.Color(rawColors[i])); + } + return toSkFloatColorList(colors); +} + @JS('window.flutter_canvas_kit.SkPath') class SkPath { external SkPath([SkPath? other]); @@ -834,6 +1193,46 @@ SkRect toSkRect(ui.Rect rect) { ); } +@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, @@ -859,3 +1258,359 @@ SkFloat32List toMallocedSkPoints(List points) { } return skPoints; } + +// TODO(yjbanov): this is inefficient. We should be able to pass points +// as Float32List without a conversion. +List rawPointsToSkPoints2d(Float32List points) { + assert(points.length % 2 == 0); + final int pointLength = points.length ~/ 2; + final List result = []; + for (var i = 0; i < pointLength; i++) { + var x = i * 2; + var y = x + 1; + final Float32List skPoint = Float32List(2); + skPoint[0] = points[x]; + skPoint[1] = points[y]; + result.add(skPoint); + } + return result; +} + +List toSkPoints2d(List offsets) { + final int len = offsets.length; + final List result = []; + for (var i = 0; i < len; i++) { + final ui.Offset offset = offsets[i]; + final Float32List skPoint = Float32List(2); + skPoint[0] = offset.dx; + skPoint[1] = offset.dy; + result.add(skPoint); + } + return result; +} + +Uint16List toUint16List(List ints) { + final int len = ints.length; + final Uint16List result = Uint16List(len); + for (int i = 0; i < len; i++) { + result[i] = ints[i]; + } + return result; +} + +@JS('window.flutter_canvas_kit.SkPictureRecorder') +class SkPictureRecorder { + external SkPictureRecorder(); + external SkCanvas beginRecording(SkRect bounds); + external SkPicture finishRecordingAsPicture(); + external void delete(); +} + +@JS() +class SkCanvas { + external void clear(Float32List color); + external void clipPath( + SkPath path, + SkClipOp clipOp, + bool doAntiAlias, + ); + external void clipRRect( + SkRRect rrect, + SkClipOp clipOp, + bool doAntiAlias, + ); + external void clipRect( + SkRect rrect, + SkClipOp clipOp, + bool doAntiAlias, + ); + external void drawArc( + SkRect oval, + double startAngleDegrees, + double sweepAngleDegrees, + bool useCenter, + SkPaint paint, + ); + external void drawAtlas( + SkImage image, + Float32List rects, + Float32List rstTransforms, + SkPaint paint, + SkBlendMode blendMode, + List? colors, + ); + external void drawCircle( + double x, + double y, + double radius, + SkPaint paint, + ); + external void drawColorInt( + int color, + SkBlendMode blendMode, + ); + external void drawDRRect( + SkRRect outer, + SkRRect inner, + SkPaint paint, + ); + external void drawImage( + SkImage image, + double x, + double y, + SkPaint paint, + ); + external void drawImageRect( + SkImage image, + SkRect src, + SkRect dst, + SkPaint paint, + bool fastSample, + ); + external void drawImageNine( + SkImage image, + SkRect center, + SkRect dst, + SkPaint paint, + ); + external void drawLine( + double x1, + double y1, + double x2, + double y2, + SkPaint paint, + ); + external void drawOval( + SkRect rect, + SkPaint paint, + ); + external void drawPaint( + SkPaint paint, + ); + external void drawPath( + SkPath path, + SkPaint paint, + ); + external void drawPoints( + SkPointMode pointMode, + Float32List points, + SkPaint paint, + ); + external void drawRRect( + SkRRect rrect, + SkPaint paint, + ); + external void drawRect( + SkRect rrect, + SkPaint paint, + ); + external void drawShadow( + SkPath path, + Float32List zPlaneParams, + Float32List lightPos, + double lightRadius, + Float32List ambientColor, + Float32List spotColor, + int flags, + ); + external void drawVertices( + SkVertices vertices, + SkBlendMode blendMode, + SkPaint paint, + ); + external int save(); + external int getSaveCount(); + external void saveLayer( + SkRect bounds, + SkPaint paint, + ); + external void restore(); + external void restoreToCount(int count); + external void rotate( + double angleDegrees, + double px, + double py, + ); + external void scale(double x, double y); + external void skew(double x, double y); + external void concat(Float32List matrix); + external void translate(double x, double y); + external void flush(); + external void drawPicture(SkPicture picture); + external void drawParagraph( + SkParagraph paragraph, + double x, + double y, + ); +} + +@JS() +class SkCanvasSaveLayerWithoutBoundsOverride { + external void saveLayer(SkPaint paint); +} + +@JS() +class SkCanvasSaveLayerWithFilterOverride { + external void saveLayer( + SkPaint? paint, + SkImageFilter? imageFilter, + int flags, + SkRect rect, + ); +} + +@JS() +class SkPicture { + external void delete(); +} + +@JS() +class SkParagraphBuilderNamespace { + external SkParagraphBuilder Make( + SkParagraphStyle paragraphStyle, + SkFontMgr? fontManager, + ); +} + +@JS() +class SkParagraphBuilder { + external void addText(String text); + external void pushStyle(SkTextStyle textStyle); + external void pop(); + external SkParagraph build(); + external void delete(); +} + +@JS() +class SkParagraphStyle { +} + +@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); +} + +@JS() +class SkTextStyle { + +} + +@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 fontSize(double? value); + + external List? get fontFamilies; + external set fontFamilies(List? value); + + external SkFontStyle? get fontStyle; + external set fontStyle(SkFontStyle? value); +} + +@JS() +@anonymous +class SkFontStyle { + external SkFontWeight? get weight; + external set weight(SkFontWeight? value); + + external SkFontSlant? get slant; + external set slant(SkFontSlant? value); +} + +@JS() +class SkFontMgr { + +} + +@JS() +class SkParagraph { + external double getAlphabeticBaseline(); + external bool didExceedMaxLines(); + external double getHeight(); + external double getIdeographicBaseline(); + external double getLongestLine(); + external double getMaxIntrinsicWidth(); + external double getMinIntrinsicWidth(); + external double getMaxWidth(); + external List getRectsForRange( + int start, + int end, + SkRectHeightStyle heightStyle, + SkRectWidthStyle widthStyle, + ); + external SkTextPosition getGlyphPositionAtCoordinate( + double x, + double y, + ); + external SkTextRange getWordBoundary(int position); + external void layout(double width); + external void delete(); +} + +@JS() +class SkTextPosition { + external SkAffinity get affinity; + external int get pos; +} + +@JS() +class SkTextRange { + external int get start; + external int get end; +} + +@JS() +class SkVertices { } + +@JS() +@anonymous +class SkTonalColors { + external factory SkTonalColors({ + required Float32List ambient, + required Float32List spot, + }); + external Float32List get ambient; + external Float32List get spot; +} + +@JS() +class SkFontMgrNamespace { + // TODO(yjbanov): can this be made non-null? It returns null in our unit-tests right now. + external SkFontMgr? FromData(List fonts); +} diff --git a/lib/web_ui/lib/src/engine/compositor/color_filter.dart b/lib/web_ui/lib/src/engine/compositor/color_filter.dart index d80cac8f303..ecdda8df0d7 100644 --- a/lib/web_ui/lib/src/engine/compositor/color_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/color_filter.dart @@ -6,7 +6,7 @@ part of engine; /// A [ui.ColorFilter] backed by Skia's [CkColorFilter]. -class CkColorFilter extends ResurrectableSkiaObject { +class CkColorFilter extends ResurrectableSkiaObject { final EngineColorFilter _engineFilter; CkColorFilter.mode(EngineColorFilter filter) : _engineFilter = filter; @@ -19,9 +19,7 @@ class CkColorFilter extends ResurrectableSkiaObject { CkColorFilter.srgbToLinearGamma(EngineColorFilter filter) : _engineFilter = filter; - SkColorFilter? _skColorFilter; - - js.JsObject _createSkiaObjectFromFilter() { + SkColorFilter _createSkiaObjectFromFilter() { SkColorFilter skColorFilter; switch (_engineFilter._type) { case EngineColorFilter._TypeMode: @@ -48,17 +46,24 @@ class CkColorFilter extends ResurrectableSkiaObject { throw StateError( 'Unknown mode ${_engineFilter._type} for ColorFilter.'); } - _skColorFilter = skColorFilter; - return _jsObjectWrapper.wrapSkColorFilter(skColorFilter); + return skColorFilter; } @override - js.JsObject createDefault() { + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkColorFilter(skiaObject); + + @override + SkColorFilter createDefault() { return _createSkiaObjectFromFilter(); } @override - js.JsObject resurrect() { + SkColorFilter resurrect() { return _createSkiaObjectFromFilter(); } + + @override + void delete() { + rawSkiaObject?.delete(); + } } diff --git a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart index dfa1ff947ef..3b034869901 100644 --- a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart @@ -334,7 +334,9 @@ class HtmlViewEmbedder { final SurfaceFrame frame = _overlays[viewId]!.surface.acquireFrame(_frameSize); final CkCanvas canvas = frame.skiaCanvas; - canvas.drawPicture(_pictureRecorders[viewId]!.endRecording()); + canvas.drawPicture( + _pictureRecorders[viewId]!.endRecording() as CkPicture, + ); frame.submit(); } _pictureRecorders.clear(); diff --git a/lib/web_ui/lib/src/engine/compositor/fonts.dart b/lib/web_ui/lib/src/engine/compositor/fonts.dart index 1bce459d220..48c414ea2c0 100644 --- a/lib/web_ui/lib/src/engine/compositor/fonts.dart +++ b/lib/web_ui/lib/src/engine/compositor/fonts.dart @@ -41,7 +41,7 @@ class SkiaFontCollection { final List fontBuffers = _registeredFonts.map((f) => f!.bytes).toList(); - skFontMgr = canvasKit['SkFontMgr'].callMethod('FromData', fontBuffers); + skFontMgr = canvasKitJs.SkFontMgr.FromData(fontBuffers); } /// Loads all of the unloaded fonts in [_unloadedFonts] and adds them @@ -178,7 +178,7 @@ class SkiaFontCollection { .then((dynamic x) => x as ByteBuffer); } - js.JsObject? skFontMgr; + SkFontMgr? skFontMgr; } /// Represents a font that has been registered. diff --git a/lib/web_ui/lib/src/engine/compositor/image_filter.dart b/lib/web_ui/lib/src/engine/compositor/image_filter.dart index f0f46af81f2..236443d5c0c 100644 --- a/lib/web_ui/lib/src/engine/compositor/image_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/image_filter.dart @@ -7,7 +7,7 @@ part of engine; /// The CanvasKit implementation of [ui.ImageFilter]. /// /// Currently only supports `blur`. -class CkImageFilter extends ResurrectableSkiaObject implements ui.ImageFilter { +class CkImageFilter extends ResurrectableSkiaObject implements ui.ImageFilter { CkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) : _sigmaX = sigmaX, _sigmaY = sigmaY; @@ -15,23 +15,27 @@ class CkImageFilter extends ResurrectableSkiaObject implements ui.ImageFilter { final double _sigmaX; final double _sigmaY; - SkImageFilter? _skImageFilter; + @override + SkImageFilter createDefault() => _initSkiaObject(); @override - js.JsObject createDefault() => _initSkiaObject(); + SkImageFilter resurrect() => _initSkiaObject(); @override - js.JsObject resurrect() => _initSkiaObject(); + void delete() { + rawSkiaObject?.delete(); + } - js.JsObject _initSkiaObject() { - final SkImageFilter skImageFilter = canvasKitJs.SkImageFilter.MakeBlur( + @override + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkImageFilter(skiaObject); + + SkImageFilter _initSkiaObject() { + return canvasKitJs.SkImageFilter.MakeBlur( _sigmaX, _sigmaY, canvasKitJs.TileMode.Clamp, null, ); - _skImageFilter = skImageFilter; - return _jsObjectWrapper.wrapSkImageFilter(skImageFilter); } @override diff --git a/lib/web_ui/lib/src/engine/compositor/layer.dart b/lib/web_ui/lib/src/engine/compositor/layer.dart index 5a1cc18168e..80581ac0fae 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer.dart @@ -402,10 +402,10 @@ class PictureLayer extends Layer { /// on the given elevation. class PhysicalShapeLayer extends ContainerLayer implements ui.PhysicalShapeEngineLayer { - final double? _elevation; - final ui.Color? _color; + final double _elevation; + final ui.Color _color; final ui.Color? _shadowColor; - final ui.Path? _path; + final CkPath _path; final ui.Clip _clipBehavior; PhysicalShapeLayer( @@ -420,7 +420,7 @@ class PhysicalShapeLayer extends ContainerLayer void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollChildren(prerollContext, matrix); - paintBounds = _path!.getBounds(); + paintBounds = _path.getBounds(); if (_elevation == 0.0) { // No need to extend the paint bounds if there is no shadow. return; @@ -480,16 +480,16 @@ class PhysicalShapeLayer extends ContainerLayer assert(needsPainting); if (_elevation != 0) { - drawShadow(paintContext.leafNodesCanvas!, _path!, _shadowColor!, _elevation!, - _color!.alpha != 0xff); + drawShadow(paintContext.leafNodesCanvas!, _path, _shadowColor!, _elevation, + _color.alpha != 0xff); } - final ui.Paint paint = ui.Paint()..color = _color!; + final ui.Paint paint = ui.Paint()..color = _color; if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) { - paintContext.leafNodesCanvas!.drawPath(_path!, paint as CkPaint); + paintContext.leafNodesCanvas!.drawPath(_path, paint as CkPaint); } - final int? saveCount = paintContext.internalNodesCanvas.save(); + final int saveCount = paintContext.internalNodesCanvas.save(); switch (_clipBehavior) { case ui.Clip.hardEdge: paintContext.internalNodesCanvas.clipPath(_path, false); diff --git a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart index 54515622563..9dda02dd3ac 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart @@ -185,8 +185,13 @@ class LayerSceneBuilder implements ui.SceneBuilder { ui.Clip clipBehavior = ui.Clip.none, ui.EngineLayer? oldLayer, }) { - final PhysicalShapeLayer layer = - PhysicalShapeLayer(elevation, color, shadowColor, path, clipBehavior); + final PhysicalShapeLayer layer = PhysicalShapeLayer( + elevation, + color, + shadowColor, + path as CkPath, + clipBehavior, + ); pushLayer(layer); return layer; } diff --git a/lib/web_ui/lib/src/engine/compositor/mask_filter.dart b/lib/web_ui/lib/src/engine/compositor/mask_filter.dart index e88040f17e8..967fe744038 100644 --- a/lib/web_ui/lib/src/engine/compositor/mask_filter.dart +++ b/lib/web_ui/lib/src/engine/compositor/mask_filter.dart @@ -5,7 +5,7 @@ part of engine; /// The CanvasKit implementation of [ui.MaskFilter]. -class CkMaskFilter extends ResurrectableSkiaObject { +class CkMaskFilter extends ResurrectableSkiaObject { CkMaskFilter.blur(ui.BlurStyle blurStyle, double sigma) : _blurStyle = blurStyle, _sigma = sigma; @@ -13,21 +13,25 @@ class CkMaskFilter extends ResurrectableSkiaObject { final ui.BlurStyle _blurStyle; final double _sigma; - SkMaskFilter? _skMaskFilter; + @override + SkMaskFilter createDefault() => _initSkiaObject(); @override - js.JsObject createDefault() => _initSkiaObject(); + SkMaskFilter resurrect() => _initSkiaObject(); - @override - js.JsObject resurrect() => _initSkiaObject(); - - js.JsObject _initSkiaObject() { - final SkMaskFilter skMaskFilter = canvasKitJs.MakeBlurMaskFilter( + SkMaskFilter _initSkiaObject() { + return canvasKitJs.MakeBlurMaskFilter( toSkBlurStyle(_blurStyle), _sigma, true, ); - _skMaskFilter = skMaskFilter; - return _jsObjectWrapper.wrapSkMaskFilter(skMaskFilter); + } + + @override + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkMaskFilter(skiaObject); + + @override + void delete() { + rawSkiaObject?.delete(); } } diff --git a/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart b/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart index 1a5c14eac59..bbba6c6fa48 100644 --- a/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart +++ b/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart @@ -14,8 +14,8 @@ class CkNWayCanvas { } /// Calls [save] on all canvases. - int? save() { - int? saveCount; + int save() { + int saveCount = 0; for (int i = 0; i < _canvases.length; i++) { saveCount = _canvases[i]!.save(); } @@ -44,7 +44,7 @@ class CkNWayCanvas { } /// Calls [restoreToCount] on all canvases. - void restoreToCount(int? count) { + void restoreToCount(int count) { for (int i = 0; i < _canvases.length; i++) { _canvases[i]!.restoreToCount(count); } @@ -58,7 +58,7 @@ class CkNWayCanvas { } /// Calls [transform] on all canvases. - void transform(Float32List? matrix) { + void transform(Float32List matrix) { for (int i = 0; i < _canvases.length; i++) { _canvases[i]!.transform(matrix); } diff --git a/lib/web_ui/lib/src/engine/compositor/painting.dart b/lib/web_ui/lib/src/engine/compositor/painting.dart index e3453e16fdc..df3d6c6b105 100644 --- a/lib/web_ui/lib/src/engine/compositor/painting.dart +++ b/lib/web_ui/lib/src/engine/compositor/painting.dart @@ -8,7 +8,7 @@ part of engine; /// /// This class is backed by a Skia object that must be explicitly /// deleted to avoid a memory leak. This is done by extending [SkiaObject]. -class CkPaint extends ResurrectableSkiaObject implements ui.Paint { +class CkPaint extends ResurrectableSkiaObject implements ui.Paint { CkPaint(); static const ui.Color _defaultPaintColor = ui.Color(0xFF000000); @@ -21,7 +21,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _blendMode = value; - _skPaint.setBlendMode(toSkBlendMode(value)); + skiaObject.setBlendMode(toSkBlendMode(value)); } ui.BlendMode _blendMode = ui.BlendMode.srcOver; @@ -35,7 +35,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _style = value; - _skPaint.setStyle(toSkPaintStyle(value)); + skiaObject.setStyle(toSkPaintStyle(value)); } ui.PaintingStyle _style = ui.PaintingStyle.fill; @@ -48,7 +48,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _strokeWidth = value; - _skPaint.setStrokeWidth(value); + skiaObject.setStrokeWidth(value); } double _strokeWidth = 0.0; @@ -61,7 +61,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _strokeCap = value; - _skPaint.setStrokeCap(toSkStrokeCap(value)); + skiaObject.setStrokeCap(toSkStrokeCap(value)); } ui.StrokeCap _strokeCap = ui.StrokeCap.butt; @@ -74,7 +74,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _strokeJoin = value; - _skPaint.setStrokeJoin(toSkStrokeJoin(value)); + skiaObject.setStrokeJoin(toSkStrokeJoin(value)); } ui.StrokeJoin _strokeJoin = ui.StrokeJoin.miter; @@ -87,7 +87,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _isAntiAlias = value; - _skPaint.setAntiAlias(value); + skiaObject.setAntiAlias(value); } bool _isAntiAlias = true; @@ -100,7 +100,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _color = value; - _skPaint.setColorInt(value.value); + skiaObject.setColorInt(value.value); } ui.Color _color = _defaultPaintColor; @@ -123,7 +123,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _shader = value as EngineShader?; - _skPaint.setShader(_shader?.createSkiaShader()); + skiaObject.setShader(_shader?.createSkiaShader()); } EngineShader? _shader; @@ -144,7 +144,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { } else { _ckMaskFilter = null; } - _skPaint.setMaskFilter(_ckMaskFilter?._skMaskFilter); + skiaObject.setMaskFilter(_ckMaskFilter?.skiaObject); } ui.MaskFilter? _maskFilter; @@ -158,7 +158,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _filterQuality = value; - _skPaint.setFilterQuality(toSkFilterQuality(value)); + skiaObject.setFilterQuality(toSkFilterQuality(value)); } ui.FilterQuality _filterQuality = ui.FilterQuality.none; @@ -173,7 +173,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { final EngineColorFilter? engineValue = value as EngineColorFilter?; _colorFilter = engineValue; _ckColorFilter = engineValue?._toCkColorFilter(); - _skPaint.setColorFilter(_ckColorFilter?._skColorFilter); + skiaObject.setColorFilter(_ckColorFilter?.skiaObject); } EngineColorFilter? _colorFilter; @@ -187,7 +187,7 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _strokeMiterLimit = value; - _skPaint.setStrokeMiter(value); + skiaObject.setStrokeMiter(value); } double _strokeMiterLimit = 0.0; @@ -200,34 +200,40 @@ class CkPaint extends ResurrectableSkiaObject implements ui.Paint { return; } _imageFilter = value as CkImageFilter?; - _skPaint.setImageFilter(_imageFilter?._skImageFilter); + skiaObject.setImageFilter(_imageFilter?.skiaObject); } CkImageFilter? _imageFilter; - late SkPaint _skPaint; - @override - js.JsObject createDefault() { - _skPaint = SkPaint(); - _skPaint.setAntiAlias(_isAntiAlias); - _skPaint.setColorInt(_color.value); - return _jsObjectWrapper.wrapSkPaint(_skPaint); + SkPaint createDefault() { + final SkPaint paint = SkPaint(); + paint.setAntiAlias(_isAntiAlias); + paint.setColorInt(_color.value); + return paint; } @override - js.JsObject resurrect() { - _skPaint = SkPaint(); - _skPaint.setBlendMode(toSkBlendMode(_blendMode)); - _skPaint.setStyle(toSkPaintStyle(_style)); - _skPaint.setStrokeWidth(_strokeWidth); - _skPaint.setAntiAlias(_isAntiAlias); - _skPaint.setColorInt(_color.value); - _skPaint.setShader(_shader?.createSkiaShader()); - _skPaint.setMaskFilter(_ckMaskFilter?._skMaskFilter); - _skPaint.setColorFilter(_ckColorFilter?._skColorFilter); - _skPaint.setImageFilter(_imageFilter?._skImageFilter); - _skPaint.setFilterQuality(toSkFilterQuality(_filterQuality)); - return _jsObjectWrapper.wrapSkPaint(_skPaint); + SkPaint resurrect() { + final SkPaint paint = SkPaint(); + paint.setBlendMode(toSkBlendMode(_blendMode)); + paint.setStyle(toSkPaintStyle(_style)); + paint.setStrokeWidth(_strokeWidth); + paint.setAntiAlias(_isAntiAlias); + paint.setColorInt(_color.value); + paint.setShader(_shader?.createSkiaShader()); + paint.setMaskFilter(_ckMaskFilter?.skiaObject); + paint.setColorFilter(_ckColorFilter?.skiaObject); + paint.setImageFilter(_imageFilter?.skiaObject); + paint.setFilterQuality(toSkFilterQuality(_filterQuality)); + return paint; } + + @override + void delete() { + rawSkiaObject?.delete(); + } + + @override + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkPaint(skiaObject); } diff --git a/lib/web_ui/lib/src/engine/compositor/path.dart b/lib/web_ui/lib/src/engine/compositor/path.dart index 4cdc5b994b3..70396c1d500 100644 --- a/lib/web_ui/lib/src/engine/compositor/path.dart +++ b/lib/web_ui/lib/src/engine/compositor/path.dart @@ -10,9 +10,6 @@ part of engine; class CkPath implements ui.Path { final SkPath _skPath; - // TODO(yjbanov): remove this once we're fully @JS-ified. - late final js.JsObject _legacyJsObject = _jsObjectWrapper.wrapSkPath(_skPath); - CkPath() : _skPath = SkPath(), _fillType = ui.PathFillType.nonZero { _skPath.setFillType(toSkFillType(_fillType)); } diff --git a/lib/web_ui/lib/src/engine/compositor/picture.dart b/lib/web_ui/lib/src/engine/compositor/picture.dart index c5099d3b121..ad9405f99e2 100644 --- a/lib/web_ui/lib/src/engine/compositor/picture.dart +++ b/lib/web_ui/lib/src/engine/compositor/picture.dart @@ -5,17 +5,20 @@ part of engine; class CkPicture implements ui.Picture { - final SkiaObject skPicture; + final SkPicture _skPicture; + final SkiaObject skiaObject; final ui.Rect? cullRect; - CkPicture(this.skPicture, this.cullRect); + CkPicture(SkPicture picture, this.cullRect) + : _skPicture = picture, + skiaObject = SkPictureSkiaObject(picture); @override int get approximateBytesUsed => 0; @override void dispose() { - skPicture.delete(); + skiaObject.delete(); } @override @@ -23,3 +26,15 @@ class CkPicture implements ui.Picture { throw UnsupportedError('Picture.toImage not yet implemented for CanvasKit and HTML'); } } + +class SkPictureSkiaObject extends OneShotSkiaObject { + SkPictureSkiaObject(SkPicture picture) : super(picture); + + @override + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkPicture(skiaObject); + + @override + void delete() { + rawSkiaObject?.delete(); + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart b/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart index 19b7e5a86a3..9f97d193fcc 100644 --- a/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart +++ b/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart @@ -6,32 +6,33 @@ part of engine; class CkPictureRecorder implements ui.PictureRecorder { ui.Rect? _cullRect; - js.JsObject? _recorder; + SkPictureRecorder? _skRecorder; CkCanvas? _recordingCanvas; - CkCanvas? beginRecording(ui.Rect bounds) { + CkCanvas beginRecording(ui.Rect bounds) { _cullRect = bounds; - _recorder = js.JsObject(canvasKit['SkPictureRecorder']); - final js.JsObject skRect = js.JsObject(canvasKit['LTRBRect'], - [bounds.left, bounds.top, bounds.right, bounds.bottom]); - final js.JsObject skCanvas = - _recorder!.callMethod('beginRecording', [skRect]); - _recordingCanvas = CkCanvas(skCanvas); - return _recordingCanvas; + final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder(); + final SkRect skRect = toSkRect(bounds); + final SkCanvas skCanvas = recorder.beginRecording(skRect); + return _recordingCanvas = CkCanvas(skCanvas); } CkCanvas? get recordingCanvas => _recordingCanvas; @override ui.Picture endRecording() { - final js.JsObject? skPicture = - _recorder!.callMethod('finishRecordingAsPicture'); - _recorder!.callMethod('delete'); - _recorder = null; + final SkPictureRecorder? recorder = _skRecorder; - return CkPicture(OneShotSkiaObject(skPicture), _cullRect); + if (recorder == null) { + throw StateError('PictureRecorder is not recording'); + } + + final SkPicture skPicture = recorder.finishRecordingAsPicture(); + recorder.delete(); + _skRecorder = null; + return CkPicture(skPicture, _cullRect); } @override - bool get isRecording => _recorder != null; + bool get isRecording => _skRecorder != null; } diff --git a/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart b/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart index d46f9ecaf3f..3d9708f2ce9 100644 --- a/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/compositor/skia_object_cache.dart @@ -76,20 +76,30 @@ class SkiaObjectCache { final SkiaObject oldObject = _itemQueue.removeLast(); _itemMap.remove(oldObject); oldObject.delete(); + oldObject.didDelete(); } } } -/// An object backed by a [js.JsObject] mapped onto a Skia C++ object in the +/// An object backed by a JavaScript object mapped onto a Skia C++ object in the /// WebAssembly heap. /// /// These objects are automatically deleted when no longer used. -abstract class SkiaObject { +abstract class SkiaObject { /// The JavaScript object that's mapped onto a Skia C++ object in the WebAssembly heap. - js.JsObject? get skiaObject; + T get skiaObject; + + /// The legacy view on the [skiaObject]. + // TODO(yjbanov): remove this after completing JS-interop migration. + js.JsObject? get legacySkiaObject; /// Deletes the associated C++ object from the WebAssembly heap. void delete(); + + /// Lifecycle method called immediately after calling [delete]. + /// + /// This method is used to + void didDelete(); } /// A [SkiaObject] that can resurrect its C++ counterpart. @@ -113,9 +123,9 @@ abstract class SkiaObject { /// [resurrect] method. /// - Final delete: if a Dart object is never reused, it is GC'd after its /// underlying C++ object is deleted. This is implemented by [SkiaObjects]. -abstract class ResurrectableSkiaObject extends SkiaObject { +abstract class ResurrectableSkiaObject extends SkiaObject { ResurrectableSkiaObject() { - _skiaObject = createDefault(); + rawSkiaObject = createDefault(); if (isResurrectionExpensive) { SkiaObjects.manageExpensive(this); } else { @@ -124,20 +134,33 @@ abstract class ResurrectableSkiaObject extends SkiaObject { } @override - js.JsObject get skiaObject { - if (_skiaObject == null) { - _skiaObject = resurrect(); - if (isResurrectionExpensive) { - SkiaObjects.manageExpensive(this); - } else { - SkiaObjects.manageResurrectable(this); - } + T get skiaObject => rawSkiaObject ?? _doResurrect(); + + T _doResurrect() { + final T skiaObject = resurrect(); + rawSkiaObject = skiaObject; + if (isResurrectionExpensive) { + SkiaObjects.manageExpensive(this); + } else { + SkiaObjects.manageResurrectable(this); } - return _skiaObject!; + return skiaObject; } - /// Do not use this field outside this class. Use [skiaObject] instead. - js.JsObject? _skiaObject; + @override + void didDelete() { + rawSkiaObject = null; + } + + /// Returns the current skia object as is without attempting to + /// resurrect it. + /// + /// If the returned value is `null`, the corresponding C++ object has + /// been deleted. + /// + /// Use this field instead of the [skiaObject] getter when implementing + /// the [delete] method. + T? rawSkiaObject; /// Instantiates a new Skia-backed JavaScript object containing default /// values. @@ -145,22 +168,16 @@ abstract class ResurrectableSkiaObject extends SkiaObject { /// The object is expected to represent Flutter's defaults. If Skia uses /// different defaults from those used by Flutter, this method is expected /// initialize the object to Flutter's defaults. - js.JsObject createDefault(); + T createDefault(); /// Creates a new Skia-backed JavaScript object containing data representing /// the current state of the Dart object. - js.JsObject resurrect(); + T resurrect(); /// Whether or not it is expensive to resurrect this object. /// /// Defaults to false. bool get isResurrectionExpensive => false; - - @override - void delete() { - _skiaObject!.callMethod('delete'); - _skiaObject = null; - } } // TODO(hterkelsen): [OneShotSkiaObject] is dangerous because it might delete @@ -168,26 +185,33 @@ abstract class ResurrectableSkiaObject extends SkiaObject { // use. This issue discusses ways to address this: // https://github.com/flutter/flutter/issues/60401 /// A [SkiaObject] which is deleted once and cannot be used again. -class OneShotSkiaObject extends SkiaObject { - js.JsObject? _skiaObject; +abstract class OneShotSkiaObject extends SkiaObject { + /// Returns the current skia object as is without attempting to + /// resurrect it. + /// + /// If the returned value is `null`, the corresponding C++ object has + /// been deleted. + /// + /// Use this field instead of the [skiaObject] getter when implementing + /// the [delete] method. + T? rawSkiaObject; - OneShotSkiaObject(this._skiaObject) { + OneShotSkiaObject(this.rawSkiaObject) { SkiaObjects.manageOneShot(this); } @override - js.JsObject? get skiaObject { - if (_skiaObject == null) { + T get skiaObject { + if (rawSkiaObject == null) { throw StateError('Attempting to use a Skia object that has been freed.'); } SkiaObjects.oneShotCache.markUsed(this); - return _skiaObject; + return rawSkiaObject!; } @override - void delete() { - _skiaObject!.callMethod('delete'); - _skiaObject = null; + void didDelete() { + rawSkiaObject = null; } } @@ -267,6 +291,7 @@ class SkiaObjects { for (int i = 0; i < resurrectableObjects.length; i++) { final SkiaObject object = resurrectableObjects[i]; object.delete(); + object.didDelete(); } resurrectableObjects.clear(); diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index 516c1b82d0b..c0d8b217fa6 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -169,7 +169,9 @@ class CkSurface { CkCanvas getCanvas() { final js.JsObject skCanvas = _surface.callMethod('getCanvas'); - return CkCanvas(skCanvas); + return CkCanvas( + _jsObjectWrapper.unwrapSkCanvas(skCanvas), + ); } int get context => _glContext; diff --git a/lib/web_ui/lib/src/engine/compositor/text.dart b/lib/web_ui/lib/src/engine/compositor/text.dart index 1713c297d46..149e2fa6c87 100644 --- a/lib/web_ui/lib/src/engine/compositor/text.dart +++ b/lib/web_ui/lib/src/engine/compositor/text.dart @@ -18,41 +18,40 @@ class CkParagraphStyle implements ui.ParagraphStyle { ui.StrutStyle? strutStyle, String? ellipsis, ui.Locale? locale, - }) { - skParagraphStyle = toCkParagraphStyle( - textAlign, - textDirection, - maxLines, - fontFamily, - fontSize, - height, - textHeightBehavior, - fontWeight, - fontStyle, - ellipsis, - ); + }) : skParagraphStyle = toSkParagraphStyle( + textAlign, + textDirection, + maxLines, + fontFamily, + fontSize, + height, + textHeightBehavior, + fontWeight, + fontStyle, + ellipsis, + ) { assert(skParagraphStyle != null); _textDirection = textDirection ?? ui.TextDirection.ltr; _fontFamily = fontFamily; } - js.JsObject? skParagraphStyle; + SkParagraphStyle skParagraphStyle; ui.TextDirection? _textDirection; String? _fontFamily; - static Map toCkTextStyle( + static SkTextStyleProperties toSkTextStyleProperties( String? fontFamily, double? fontSize, ui.FontWeight? fontWeight, ui.FontStyle? fontStyle, ) { - final Map skTextStyle = {}; + final SkTextStyleProperties skTextStyle = SkTextStyleProperties(); if (fontWeight != null || fontStyle != null) { - skTextStyle['fontStyle'] = toSkFontStyle(fontWeight, fontStyle); + skTextStyle.fontStyle = toSkFontStyle(fontWeight, fontStyle); } if (fontSize != null) { - skTextStyle['fontSize'] = fontSize; + skTextStyle.fontSize = fontSize; } if (fontFamily == null || @@ -60,14 +59,14 @@ class CkParagraphStyle implements ui.ParagraphStyle { fontFamily = 'Roboto'; } if (skiaFontCollection.fontFamilyOverrides.containsKey(fontFamily)) { - fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily]; + fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily]!; } - skTextStyle['fontFamilies'] = [fontFamily]; + skTextStyle.fontFamilies = [fontFamily]; return skTextStyle; } - static js.JsObject? toCkParagraphStyle( + static SkParagraphStyle toSkParagraphStyle( ui.TextAlign? textAlign, ui.TextDirection? textDirection, int? maxLines, @@ -79,70 +78,43 @@ class CkParagraphStyle implements ui.ParagraphStyle { ui.FontStyle? fontStyle, String? ellipsis, ) { - final Map skParagraphStyle = {}; + final SkParagraphStyleProperties properties = SkParagraphStyleProperties(); if (textAlign != null) { - switch (textAlign) { - case ui.TextAlign.left: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Left']; - break; - case ui.TextAlign.right: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Right']; - break; - case ui.TextAlign.center: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Center']; - break; - case ui.TextAlign.justify: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Justify']; - break; - case ui.TextAlign.start: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Start']; - break; - case ui.TextAlign.end: - skParagraphStyle['textAlign'] = canvasKit['TextAlign']['End']; - break; - } + properties.textAlign = toSkTextAlign(textAlign); } if (textDirection != null) { - switch (textDirection) { - case ui.TextDirection.ltr: - skParagraphStyle['textDirection'] = canvasKit['TextDirection']['LTR']; - break; - case ui.TextDirection.rtl: - skParagraphStyle['textDirection'] = canvasKit['TextDirection']['RTL']; - break; - } + properties.textDirection = toSkTextDirection(textDirection); } if (height != null) { - skParagraphStyle['heightMultiplier'] = height; + properties.heightMultiplier = height; } if (textHeightBehavior != null) { - skParagraphStyle['textHeightBehavior'] = textHeightBehavior.encode(); + properties.textHeightBehavior = textHeightBehavior.encode(); } if (maxLines != null) { - skParagraphStyle['maxLines'] = maxLines; + properties.maxLines = maxLines; } if (ellipsis != null) { - skParagraphStyle['ellipsis'] = ellipsis; + properties.ellipsis = ellipsis; } - skParagraphStyle['textStyle'] = - toCkTextStyle(fontFamily, fontSize, fontWeight, fontStyle); + properties.textStyle = + toSkTextStyleProperties(fontFamily, fontSize, fontWeight, fontStyle); - return canvasKit.callMethod( - 'ParagraphStyle', [js.JsObject.jsify(skParagraphStyle)]); + return canvasKitJs.ParagraphStyle(properties); } } class CkTextStyle implements ui.TextStyle { - js.JsObject? skTextStyle; + SkTextStyle skTextStyle; - CkTextStyle({ + factory CkTextStyle({ ui.Color? color, ui.TextDecoration? decoration, ui.Color? decorationColor, @@ -163,36 +135,36 @@ class CkTextStyle implements ui.TextStyle { List? shadows, List? fontFeatures, }) { - final js.JsObject style = js.JsObject(js.context['Object']); + final SkTextStyleProperties properties = SkTextStyleProperties(); if (background != null) { - style['backgroundColor'] = makeFreshSkColor(background.color); + properties.backgroundColor = makeFreshSkColor(background.color); } if (color != null) { - style['color'] = makeFreshSkColor(color); + properties.color = makeFreshSkColor(color); } if (decoration != null) { - int decorationValue = canvasKit['NoDecoration']; + int decorationValue = canvasKitJs.NoDecoration; if (decoration.contains(ui.TextDecoration.underline)) { - decorationValue |= canvasKit['UnderlineDecoration']; + decorationValue |= canvasKitJs.UnderlineDecoration; } if (decoration.contains(ui.TextDecoration.overline)) { - decorationValue |= canvasKit['OverlineDecoration']; + decorationValue |= canvasKitJs.OverlineDecoration; } if (decoration.contains(ui.TextDecoration.lineThrough)) { - decorationValue |= canvasKit['LineThroughDecoration']; + decorationValue |= canvasKitJs.LineThroughDecoration; } - style['decoration'] = decorationValue; + properties.decoration = decorationValue; } if (decorationThickness != null) { - style['decorationThickness'] = decorationThickness; + properties.decorationThickness = decorationThickness; } if (fontSize != null) { - style['fontSize'] = fontSize; + properties.fontSize = fontSize; } if (fontFamily == null || @@ -201,22 +173,22 @@ class CkTextStyle implements ui.TextStyle { } if (skiaFontCollection.fontFamilyOverrides.containsKey(fontFamily)) { - fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily]; + fontFamily = skiaFontCollection.fontFamilyOverrides[fontFamily]!; } - List fontFamilies = [fontFamily]; + List fontFamilies = [fontFamily]; if (fontFamilyFallback != null && !fontFamilyFallback.every((font) => fontFamily == font)) { fontFamilies.addAll(fontFamilyFallback); } - style['fontFamilies'] = js.JsArray.from(fontFamilies); + properties.fontFamilies = fontFamilies; if (fontWeight != null || fontStyle != null) { - style['fontStyle'] = toSkFontStyle(fontWeight, fontStyle); + properties.fontStyle = toSkFontStyle(fontWeight, fontStyle); } if (foreground != null) { - style['foregroundColor'] = makeFreshSkColor(foreground.color); + properties.foregroundColor = makeFreshSkColor(foreground.color); } // TODO(hterkelsen): Add support for @@ -229,67 +201,32 @@ class CkTextStyle implements ui.TextStyle { // - locale // - shadows // - fontFeatures - skTextStyle = canvasKit.callMethod('TextStyle', [style]); - assert(skTextStyle != null); + return CkTextStyle._(canvasKitJs.TextStyle(properties)); } + + CkTextStyle._(this.skTextStyle); } -Map toSkFontStyle( +SkFontStyle toSkFontStyle( ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) { - Map style = {}; + final style = SkFontStyle(); if (fontWeight != null) { - switch (fontWeight) { - case ui.FontWeight.w100: - style['weight'] = canvasKit['FontWeight']['Thin']; - break; - case ui.FontWeight.w200: - style['weight'] = canvasKit['FontWeight']['ExtraLight']; - break; - case ui.FontWeight.w300: - style['weight'] = canvasKit['FontWeight']['Light']; - break; - case ui.FontWeight.w400: - style['weight'] = canvasKit['FontWeight']['Normal']; - break; - case ui.FontWeight.w500: - style['weight'] = canvasKit['FontWeight']['Medium']; - break; - case ui.FontWeight.w600: - style['weight'] = canvasKit['FontWeight']['SemiBold']; - break; - case ui.FontWeight.w700: - style['weight'] = canvasKit['FontWeight']['Bold']; - break; - case ui.FontWeight.w800: - style['weight'] = canvasKit['FontWeight']['ExtraBold']; - break; - case ui.FontWeight.w900: - style['weight'] = canvasKit['FontWeight']['ExtraBlack']; - break; - } + style.weight = toSkFontWeight(fontWeight); } - if (fontStyle != null) { - switch (fontStyle) { - case ui.FontStyle.normal: - style['slant'] = canvasKit['FontSlant']['Upright']; - break; - case ui.FontStyle.italic: - style['slant'] = canvasKit['FontSlant']['Italic']; - break; - } + style.slant = toSkFontSlant(fontStyle); } return style; } -class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { +class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { CkParagraph( this._initialParagraph, this._paragraphStyle, this._paragraphCommands); /// The result of calling `build()` on the JS CkParagraphBuilder. /// /// This may be invalidated later. - final js.JsObject _initialParagraph; + final SkParagraph _initialParagraph; /// The paragraph style used to build this paragraph. /// @@ -310,10 +247,10 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { ui.ParagraphConstraints? _lastLayoutConstraints; @override - js.JsObject createDefault() => _initialParagraph; + SkParagraph createDefault() => _initialParagraph; @override - js.JsObject resurrect() { + SkParagraph resurrect() { final builder = CkParagraphBuilder(_paragraphStyle); for (_ParagraphCommand command in _paragraphCommands) { switch (command.type) { @@ -329,43 +266,51 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { } } - final js.JsObject result = builder._buildCkParagraph(); + final SkParagraph result = builder._buildCkParagraph(); if (_lastLayoutConstraints != null) { // We need to set the Skia object early so layout works. - _skiaObject = result; + rawSkiaObject = result; this.layout(_lastLayoutConstraints!); } return result; } + @override + void delete() { + rawSkiaObject?.delete(); + } + + @override + js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkParagraph(skiaObject); + @override bool get isResurrectionExpensive => true; @override double get alphabeticBaseline => - skiaObject.callMethod('getAlphabeticBaseline'); + skiaObject.getAlphabeticBaseline(); @override - bool get didExceedMaxLines => skiaObject.callMethod('didExceedMaxLines'); + bool get didExceedMaxLines => skiaObject.didExceedMaxLines(); @override - double get height => skiaObject.callMethod('getHeight'); + double get height => skiaObject.getHeight(); @override double get ideographicBaseline => - skiaObject.callMethod('getIdeographicBaseline'); + skiaObject.getIdeographicBaseline(); @override - double get longestLine => skiaObject.callMethod('getLongestLine'); + double get longestLine => skiaObject.getLongestLine(); @override - double get maxIntrinsicWidth => skiaObject.callMethod('getMaxIntrinsicWidth'); + double get maxIntrinsicWidth => skiaObject.getMaxIntrinsicWidth(); @override - double get minIntrinsicWidth => skiaObject.callMethod('getMinIntrinsicWidth'); + double get minIntrinsicWidth => skiaObject.getMinIntrinsicWidth(); @override - double get width => skiaObject.callMethod('getMaxWidth'); + double get width => skiaObject.getMaxWidth(); // TODO(hterkelsen): Implement placeholders once it's in CanvasKit @override @@ -384,49 +329,22 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { return const []; } - js.JsObject? heightStyle; - switch (boxHeightStyle) { - case ui.BoxHeightStyle.tight: - heightStyle = canvasKit['RectHeightStyle']['Tight']; - break; - case ui.BoxHeightStyle.max: - heightStyle = canvasKit['RectHeightStyle']['Max']; - break; - default: - // TODO(hterkelsen): Support all height styles - html.window.console.warn( - 'We do not support $boxHeightStyle. Defaulting to BoxHeightStyle.tight'); - heightStyle = canvasKit['RectHeightStyle']['Tight']; - break; - } - - js.JsObject? widthStyle; - switch (boxWidthStyle) { - case ui.BoxWidthStyle.tight: - widthStyle = canvasKit['RectWidthStyle']['Tight']; - break; - case ui.BoxWidthStyle.max: - widthStyle = canvasKit['RectWidthStyle']['Max']; - break; - } - - List skRects = - skiaObject.callMethod('getRectsForRange', [ + List skRects = skiaObject.getRectsForRange( start, end, - heightStyle, - widthStyle, - ]); + toSkRectHeightStyle(boxHeightStyle), + toSkRectWidthStyle(boxWidthStyle), + ); List result = []; for (int i = 0; i < skRects.length; i++) { - final js.JsObject rect = skRects[i]; + final SkRect rect = skRects[i]; result.add(ui.TextBox.fromLTRBD( - rect['fLeft'], - rect['fTop'], - rect['fRight'], - rect['fBottom'], + rect.fLeft, + rect.fTop, + rect.fRight, + rect.fBottom, _paragraphStyle._textDirection!, )); } @@ -436,19 +354,18 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { @override ui.TextPosition getPositionForOffset(ui.Offset offset) { - js.JsObject positionWithAffinity = - skiaObject.callMethod('getGlyphPositionAtCoordinate', [ + final SkTextPosition positionWithAffinity = + skiaObject.getGlyphPositionAtCoordinate( offset.dx, offset.dy, - ]); + ); return fromPositionWithAffinity(positionWithAffinity); } @override ui.TextRange getWordBoundary(ui.TextPosition position) { - js.JsObject skRange = - skiaObject.callMethod('getWordBoundary', [position.offset]); - return ui.TextRange(start: skRange['start'], end: skRange['end']); + final SkTextRange skRange = skiaObject.getWordBoundary(position.offset); + return ui.TextRange(start: skRange.start, end: skRange.end); } @override @@ -469,7 +386,7 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { // TODO(het): CanvasKit throws an exception when laid out with // a font that wasn't registered. try { - skiaObject.callMethod('layout', [width]); + skiaObject.layout(width); } catch (e) { html.window.console.warn('CanvasKit threw an exception while laying ' 'out the paragraph. The font was "${_paragraphStyle._fontFamily}". ' @@ -492,21 +409,17 @@ class CkParagraph extends ResurrectableSkiaObject implements ui.Paragraph { } class CkParagraphBuilder implements ui.ParagraphBuilder { - js.JsObject? _paragraphBuilder; + final SkParagraphBuilder _paragraphBuilder; final CkParagraphStyle _style; final List<_ParagraphCommand> _commands; CkParagraphBuilder(ui.ParagraphStyle style) : _commands = <_ParagraphCommand>[], - _style = style as CkParagraphStyle { - _paragraphBuilder = canvasKit['ParagraphBuilder'].callMethod( - 'Make', - [ - _style.skParagraphStyle, - skiaFontCollection.skFontMgr, - ], - ); - } + _style = style as CkParagraphStyle, + _paragraphBuilder = canvasKitJs.ParagraphBuilder.Make( + style.skParagraphStyle, + skiaFontCollection.skFontMgr, + ); // TODO(hterkelsen): Implement placeholders. @override @@ -524,7 +437,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { @override void addText(String text) { _commands.add(_ParagraphCommand.addText(text)); - _paragraphBuilder!.callMethod('addText', [text]); + _paragraphBuilder.addText(text); } @override @@ -534,10 +447,9 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { } /// Builds the CkParagraph with the builder and deletes the builder. - js.JsObject _buildCkParagraph() { - final js.JsObject result = _paragraphBuilder!.callMethod('build'); - _paragraphBuilder!.callMethod('delete'); - _paragraphBuilder = null; + SkParagraph _buildCkParagraph() { + final SkParagraph result = _paragraphBuilder.build(); + _paragraphBuilder.delete(); return result; } @@ -551,15 +463,14 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { @override void pop() { _commands.add(const _ParagraphCommand.pop()); - _paragraphBuilder!.callMethod('pop'); + _paragraphBuilder.pop(); } @override void pushStyle(ui.TextStyle style) { final CkTextStyle skStyle = style as CkTextStyle; _commands.add(_ParagraphCommand.pushStyle(skStyle)); - _paragraphBuilder! - .callMethod('pushStyle', [skStyle.skTextStyle]); + _paragraphBuilder.pushStyle(skStyle.skTextStyle); } } diff --git a/lib/web_ui/lib/src/engine/compositor/util.dart b/lib/web_ui/lib/src/engine/compositor/util.dart index 03111a5e407..5a72cef3183 100644 --- a/lib/web_ui/lib/src/engine/compositor/util.dart +++ b/lib/web_ui/lib/src/engine/compositor/util.dart @@ -15,22 +15,6 @@ class CanvasKitError extends Error { String toString() => 'CanvasKitError: $message'; } -/// Converts a list of [ui.Color] into the 2d array expected by CanvasKit. -js.JsArray makeColorList(List colors) { - var result = js.JsArray(); - result.length = colors.length; - for (var i = 0; i < colors.length; i++) { - var color = colors[i]; - var jsColor = Float32List(4); - jsColor[0] = color.red / 255.0; - jsColor[1] = color.green / 255.0; - jsColor[2] = color.blue / 255.0; - jsColor[3] = color.alpha / 255.0; - result[i] = jsColor; - } - return result; -} - js.JsObject _mallocColorArray() { return canvasKit .callMethod('Malloc', [js.context['Float32Array'], 4]); @@ -107,20 +91,12 @@ ui.Rect fromSkRect(js.JsObject skRect) { ); } -ui.TextPosition fromPositionWithAffinity(js.JsObject positionWithAffinity) { - if (positionWithAffinity['affinity'] == canvasKit['Affinity']['Upstream']) { - return ui.TextPosition( - offset: positionWithAffinity['pos'], - affinity: ui.TextAffinity.upstream, - ); - } else { - assert(positionWithAffinity['affinity'] == - canvasKit['Affinity']['Downstream']); - return ui.TextPosition( - offset: positionWithAffinity['pos'], - affinity: ui.TextAffinity.downstream, - ); - } +ui.TextPosition fromPositionWithAffinity(SkTextPosition positionWithAffinity) { + final ui.TextAffinity affinity = ui.TextAffinity.values[positionWithAffinity.affinity.value]; + return ui.TextPosition( + offset: positionWithAffinity.pos, + affinity: affinity, + ); } js.JsArray makeSkPoint(ui.Offset point) { @@ -312,7 +288,7 @@ js.JsArray makeSkiaColorStops(List? colorStops) { } void drawSkShadow( - js.JsObject skCanvas, + SkCanvas skCanvas, CkPath path, ui.Color color, double elevation, @@ -331,22 +307,25 @@ void drawSkShadow( ui.Color inAmbient = color.withAlpha((color.alpha * ambientAlpha).round()); ui.Color inSpot = color.withAlpha((color.alpha * spotAlpha).round()); - final js.JsObject inTonalColors = js.JsObject.jsify({ - 'ambient': makeFreshSkColor(inAmbient), - 'spot': makeFreshSkColor(inSpot), - }); + final SkTonalColors inTonalColors = SkTonalColors( + ambient: makeFreshSkColor(inAmbient), + spot: makeFreshSkColor(inSpot), + ); - final js.JsObject tonalColors = - canvasKit.callMethod('computeTonalColors', [inTonalColors]); + final SkTonalColors tonalColors = + canvasKitJs.computeTonalColors(inTonalColors); - skCanvas.callMethod('drawShadow', [ - path._legacyJsObject, - js.JsArray.from([0, 0, devicePixelRatio * elevation]), - js.JsArray.from( - [shadowX, shadowY, devicePixelRatio * kLightHeight]), + skCanvas.drawShadow( + path._skPath, + Float32List(3) + ..[2] = devicePixelRatio * elevation, + Float32List(3) + ..[0] = shadowX + ..[1] = shadowY + ..[2] = devicePixelRatio * kLightHeight, devicePixelRatio * kLightRadius, - tonalColors['ambient'], - tonalColors['spot'], + tonalColors.ambient, + tonalColors.spot, flags, - ]); + ); } diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index d6d3e9b68ca..e50f8efaa7a 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -5,17 +5,8 @@ part of engine; -js.JsArray _encodeRawColorList(Int32List rawColors) { - final int colorCount = rawColors.length; - final List colors = []; - for (int i = 0; i < colorCount; ++i) { - colors.add(ui.Color(rawColors[i])); - } - return makeColorList(colors); -} - class CkVertices implements ui.Vertices { - js.JsObject? skVertices; + late SkVertices skVertices; CkVertices( ui.VertexMode mode, @@ -36,13 +27,13 @@ class CkVertices implements ui.Vertices { throw ArgumentError( '"indices" values must be valid indices in the positions list.'); - final js.JsArray>? encodedPositions = encodePointList(positions); - final js.JsArray>? encodedTextures = - encodePointList(textureCoordinates); - final js.JsArray? encodedColors = - colors != null ? makeColorList(colors) : null; - if (!_init(mode, encodedPositions, encodedTextures, encodedColors, indices)) - throw ArgumentError('Invalid configuration for vertices.'); + skVertices = canvasKitJs.MakeSkVertices( + toSkVertexMode(mode), + toSkPoints2d(positions), + textureCoordinates != null ? toSkPoints2d(textureCoordinates) : null, + colors != null ? toSkFloatColorList(colors) : null, + indices != null ? toUint16List(indices) : null, + ); } CkVertices.raw( @@ -64,50 +55,12 @@ class CkVertices implements ui.Vertices { throw ArgumentError( '"indices" values must be valid indices in the positions list.'); - if (!_init( - mode, - encodeRawPointList(positions) as js.JsArray>?, - encodeRawPointList(textureCoordinates) as js.JsArray>?, - colors != null ? _encodeRawColorList(colors) : null, + skVertices = canvasKitJs.MakeSkVertices( + toSkVertexMode(mode), + rawPointsToSkPoints2d(positions), + textureCoordinates != null ? rawPointsToSkPoints2d(textureCoordinates) : null, + colors != null ? encodeRawColorList(colors) : null, indices, - )) { - throw ArgumentError('Invalid configuration for vertices.'); - } - } - - bool _init( - ui.VertexMode mode, - js.JsArray>? positions, - js.JsArray>? textureCoordinates, - js.JsArray? colors, - List? indices) { - js.JsObject? skVertexMode; - switch (mode) { - case ui.VertexMode.triangles: - skVertexMode = canvasKit['VertexMode']['Triangles']; - break; - case ui.VertexMode.triangleStrip: - skVertexMode = canvasKit['VertexMode']['TrianglesStrip']; - break; - case ui.VertexMode.triangleFan: - skVertexMode = canvasKit['VertexMode']['TriangleFan']; - break; - } - - final js.JsObject? vertices = - canvasKit.callMethod('MakeSkVertices', [ - skVertexMode, - positions, - textureCoordinates, - colors, - indices, - ]); - - if (vertices != null) { - skVertices = vertices; - return true; - } else { - return false; - } + ); } } diff --git a/lib/web_ui/lib/src/engine/shader.dart b/lib/web_ui/lib/src/engine/shader.dart index a8c7a700144..41d66e02fe2 100644 --- a/lib/web_ui/lib/src/engine/shader.dart +++ b/lib/web_ui/lib/src/engine/shader.dart @@ -161,7 +161,7 @@ class GradientLinear extends EngineGradient { return canvasKitJs.SkShader.MakeLinearGradient( toSkPoint(from), toSkPoint(to), - toSkIntColorList(colors), + toSkFloatColorList(colors), toSkColorStops(colorStops), toSkTileMode(tileMode), ); @@ -212,12 +212,10 @@ class GradientRadial extends EngineGradient { SkShader createSkiaShader() { assert(experimentalUseSkia); - var jsColors = makeColorList(colors); - return canvasKitJs.SkShader.MakeRadialGradient( toSkPoint(center), radius, - jsColors, + toSkFloatColorList(colors), toSkColorStops(colorStops), toSkTileMode(tileMode), matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, @@ -248,15 +246,12 @@ class GradientConical extends EngineGradient { @override SkShader createSkiaShader() { assert(experimentalUseSkia); - - var jsColors = makeColorList(colors); - return canvasKitJs.SkShader.MakeTwoPointConicalGradient( toSkPoint(focal), focalRadius, toSkPoint(center), radius, - jsColors, + toSkFloatColorList(colors), toSkColorStops(colorStops), toSkTileMode(tileMode), matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 9d80af08203..d5d9a333215 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -10,6 +10,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import 'common.dart'; + void main() { group('CanvasKit API', () { setUpAll(() async { @@ -27,6 +29,11 @@ void main() { _filterQualityTests(); _blurStyleTests(); _tileModeTests(); + _fillTypeTests(); + _pathOpTests(); + _clipOpTests(); + _pointModeTests(); + _vertexModeTests(); _imageTests(); _shaderTests(); _paintTests(); @@ -39,14 +46,15 @@ void main() { _toSkColorStopsTests(); _toSkMatrixFromFloat32Tests(); _skSkRectTests(); + _skVerticesTests(); group('SkPath', () { _pathTests(); }); - }, - // This test failed on iOS Safari. - // TODO: https://github.com/flutter/flutter/issues/60040 - skip: (browserEngine == BrowserEngine.webkit && - operatingSystem == OperatingSystem.iOs)); + group('SkCanvas', () { + _canvasTests(); + }); + // TODO: https://github.com/flutter/flutter/issues/60040 + }, skip: isIosSafari); } void _blendModeTests() { @@ -174,6 +182,77 @@ void _tileModeTests() { }); } +void _fillTypeTests() { + test('fill type mapping is correct', () { + expect(canvasKitJs.FillType.Winding.value, ui.PathFillType.nonZero.index); + expect(canvasKitJs.FillType.EvenOdd.value, ui.PathFillType.evenOdd.index); + }); + + test('ui.PathFillType converts to SkFillType', () { + for (ui.PathFillType type in ui.PathFillType.values) { + expect(toSkFillType(type).value, type.index); + } + }); +} + +void _pathOpTests() { + // TODO(yjbanov): https://github.com/flutter/flutter/issues/61403 + // test('path op mapping is correct', () { + // expect(canvasKitJs.PathOp.Difference.value, ui.PathOperation.difference.index); + // expect(canvasKitJs.PathOp.Intersect.value, ui.PathOperation.intersect.index); + // expect(canvasKitJs.PathOp.Union.value, ui.PathOperation.union.index); + // expect(canvasKitJs.PathOp.XOR.value, ui.PathOperation.xor.index); + // expect(canvasKitJs.PathOp.ReverseDifference, ui.PathOperation.reverseDifference.index); + // }); + + // test('ui.PathOperation converts to SkPathOp', () { + // for (ui.PathOperation op in ui.PathOperation.values) { + // expect(toSkPathOp(op).value, op.index); + // } + // }); +} + +void _clipOpTests() { + test('clip op mapping is correct', () { + expect(canvasKitJs.ClipOp.Difference.value, ui.ClipOp.difference.index); + expect(canvasKitJs.ClipOp.Intersect.value, ui.ClipOp.intersect.index); + }); + + test('ui.ClipOp converts to SkClipOp', () { + for (ui.ClipOp op in ui.ClipOp.values) { + expect(toSkClipOp(op).value, op.index); + } + }); +} + +void _pointModeTests() { + test('point mode mapping is correct', () { + expect(canvasKitJs.PointMode.Points.value, ui.PointMode.points.index); + expect(canvasKitJs.PointMode.Lines.value, ui.PointMode.lines.index); + expect(canvasKitJs.PointMode.Polygon.value, ui.PointMode.polygon.index); + }); + + test('ui.PointMode converts to SkPointMode', () { + for (ui.PointMode op in ui.PointMode.values) { + expect(toSkPointMode(op).value, op.index); + } + }); +} + +void _vertexModeTests() { + test('vertex mode mapping is correct', () { + expect(canvasKitJs.VertexMode.Triangles.value, ui.VertexMode.triangles.index); + expect(canvasKitJs.VertexMode.TrianglesStrip.value, ui.VertexMode.triangleStrip.index); + expect(canvasKitJs.VertexMode.TriangleFan.value, ui.VertexMode.triangleFan.index); + }); + + test('ui.VertexMode converts to SkVertexMode', () { + for (ui.VertexMode op in ui.VertexMode.values) { + expect(toSkVertexMode(op).value, op.index); + } + }); +} + void _imageTests() { test('MakeAnimatedImageFromEncoded makes a non-animated image', () { final SkAnimatedImage nonAnimated = canvasKitJs.MakeAnimatedImageFromEncoded(kTransparentImage); @@ -247,7 +326,9 @@ SkShader _makeTestShader() { return canvasKitJs.SkShader.MakeLinearGradient( Float32List.fromList([0, 0]), Float32List.fromList([1, 1]), - Uint32List.fromList([0x000000FF]), + [ + Float32List.fromList([255, 0, 0, 255]), + ], Float32List.fromList([0, 1]), canvasKitJs.TileMode.Repeat, ); @@ -444,6 +525,15 @@ void _toSkMatrixFromFloat32Tests() { }); } +SkPath _testClosedSkPath() { + return SkPath() + ..moveTo(10, 10) + ..lineTo(20, 10) + ..lineTo(20, 20) + ..lineTo(10, 20) + ..close(); +} + void _pathTests() { SkPath path; @@ -471,15 +561,6 @@ void _pathTests() { ); }); - SkPath _testClosedSkPath() { - return SkPath() - ..moveTo(10, 10) - ..lineTo(20, 10) - ..lineTo(20, 20) - ..lineTo(10, 20) - ..close(); - } - test('addPath', () { path.addPath(_testClosedSkPath(), 1, 0, 0, 0, 1, 0, 0, 0, 0, false); }); @@ -692,6 +773,411 @@ void _skSkRectTests() { }); } +SkVertices _testVertices() { + return canvasKitJs.MakeSkVertices( + canvasKitJs.VertexMode.Triangles, + [ + Float32List.fromList([0, 0]), + Float32List.fromList([10, 10]), + Float32List.fromList([0, 20]), + ], + [ + Float32List.fromList([0, 0]), + Float32List.fromList([10, 10]), + Float32List.fromList([0, 20]), + ], + [ + Float32List.fromList([255, 0, 0, 255]), + Float32List.fromList([0, 255, 0, 255]), + Float32List.fromList([0, 0, 255, 255]), + ], + Uint16List.fromList([0, 1, 2]), + ); +} + +void _skVerticesTests() { + test('SkVertices', () { + expect(_testVertices(), isNotNull); + }); +} + +void _canvasTests() { + SkPictureRecorder recorder; + SkCanvas canvas; + + setUp(() { + recorder = SkPictureRecorder(); + canvas = recorder.beginRecording(SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + )); + }); + + tearDown(() { + expect(recorder.finishRecordingAsPicture(), isNotNull); + }); + + test('save/getSaveCount/restore/restoreToCount', () { + expect(canvas.save(), 1); + expect(canvas.save(), 2); + expect(canvas.save(), 3); + expect(canvas.save(), 4); + expect(canvas.getSaveCount(), 5); + canvas.restoreToCount(2); + expect(canvas.getSaveCount(), 2); + canvas.restore(); + expect(canvas.getSaveCount(), 1); + }); + + test('saveLayer', () { + canvas.saveLayer( + SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + ), + SkPaint(), + ); + }); + + test('SkCanvasSaveLayerWithoutBoundsOverride.saveLayer', () { + final SkCanvasSaveLayerWithoutBoundsOverride override = debugJsObjectWrapper.castToSkCanvasSaveLayerWithoutBoundsOverride(canvas); + override.saveLayer(SkPaint()); + }); + + test('SkCanvasSaveLayerWithFilterOverride.saveLayer', () { + final SkCanvasSaveLayerWithFilterOverride override = debugJsObjectWrapper.castToSkCanvasSaveLayerWithFilterOverride(canvas); + override.saveLayer( + SkPaint(), + canvasKitJs.SkImageFilter.MakeBlur(1, 2, canvasKitJs.TileMode.Repeat, null), + 0, + SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + ), + ); + }); + + test('clear', () { + canvas.clear(Float32List.fromList([0, 0, 0, 0])); + }); + + test('clipPath', () { + canvas.clipPath( + _testClosedSkPath(), + canvasKitJs.ClipOp.Intersect, + true, + ); + }); + + 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, + ), + canvasKitJs.ClipOp.Intersect, + true, + ); + }); + + test('clipRect', () { + canvas.clipRect( + SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + ), + canvasKitJs.ClipOp.Intersect, + true, + ); + }); + + test('drawArc', () { + canvas.drawArc( + SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 50, + ), + 0, + 100, + true, + SkPaint(), + ); + }); + + test('drawAtlas', () { + final SkAnimatedImage image = canvasKitJs.MakeAnimatedImageFromEncoded(kTransparentImage); + canvas.drawAtlas( + image.getCurrentFrame(), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([1, 0, 2, 3]), + SkPaint(), + canvasKitJs.BlendMode.SrcOver, + [ + Float32List.fromList([0, 0, 0, 1]), + Float32List.fromList([1, 1, 1, 1]), + ], + ); + }); + + test('drawCircle', () { + canvas.drawCircle(1, 2, 3, SkPaint()); + }); + + test('drawColorInt', () { + canvas.drawColorInt(0xFFFFFFFF, canvasKitJs.BlendMode.SoftLight); + }); + + 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, + ), + SkPaint(), + ); + }); + + test('drawImage', () { + final SkAnimatedImage image = canvasKitJs.MakeAnimatedImageFromEncoded(kTransparentImage); + canvas.drawImage( + image.getCurrentFrame(), + 10, + 20, + SkPaint(), + ); + }); + + test('drawImageRect', () { + final SkAnimatedImage image = canvasKitJs.MakeAnimatedImageFromEncoded(kTransparentImage); + canvas.drawImageRect( + image.getCurrentFrame(), + SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + SkPaint(), + false, + ); + }); + + test('drawImageNine', () { + final SkAnimatedImage image = canvasKitJs.MakeAnimatedImageFromEncoded(kTransparentImage); + canvas.drawImageNine( + image.getCurrentFrame(), + SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + SkPaint(), + ); + }); + + test('drawLine', () { + canvas.drawLine(0, 1, 2, 3, SkPaint()); + }); + + test('drawOval', () { + canvas.drawOval(SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), SkPaint()); + }); + + test('drawPaint', () { + canvas.drawPaint(SkPaint()); + }); + + test('drawPath', () { + canvas.drawPath( + _testClosedSkPath(), + SkPaint(), + ); + }); + + test('drawPoints', () { + canvas.drawPoints( + canvasKitJs.PointMode.Lines, + Float32List.fromList([0, 0, 10, 10, 0, 10]), + SkPaint(), + ); + }); + + 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, + ), + SkPaint(), + ); + }); + + test('drawRect', () { + canvas.drawRect( + SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + ), + SkPaint(), + ); + }); + + test('drawShadow', () { + for (int flags in const [0x01, 0x00]) { + const double devicePixelRatio = 2.0; + const double elevation = 4.0; + const double ambientAlpha = 0.039; + const double spotAlpha = 0.25; + + final SkPath path = _testClosedSkPath(); + final ui.Rect bounds = path.getBounds().toRect(); + 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 inSpot = color.withAlpha((color.alpha * spotAlpha).round()); + + final SkTonalColors inTonalColors = SkTonalColors( + ambient: makeFreshSkColor(inAmbient), + spot: makeFreshSkColor(inSpot), + ); + + final SkTonalColors tonalColors = + canvasKitJs.computeTonalColors(inTonalColors); + + canvas.drawShadow( + path, + Float32List(3) + ..[2] = devicePixelRatio * elevation, + Float32List(3) + ..[0] = shadowX + ..[1] = shadowY + ..[2] = devicePixelRatio * kLightHeight, + devicePixelRatio * kLightRadius, + tonalColors.ambient, + tonalColors.spot, + flags, + ); + } + }); + + test('drawVertices', () { + canvas.drawVertices( + _testVertices(), + canvasKitJs.BlendMode.SrcOver, + SkPaint(), + ); + }); + + test('rotate', () { + canvas.rotate(5, 10, 20); + }); + + test('scale', () { + canvas.scale(2, 3); + }); + + test('skew', () { + canvas.skew(4, 5); + }); + + test('concat', () { + canvas.concat(toSkMatrixFromFloat32(Matrix4.identity().storage)); + }); + + test('translate', () { + canvas.translate(4, 5); + }); + + test('flush', () { + canvas.flush(); + }); + + test('drawPicture', () { + final SkPictureRecorder otherRecorder = SkPictureRecorder(); + final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect( + fLeft: 0, + fTop: 0, + fRight: 100, + fBottom: 100, + )); + otherCanvas.drawLine(0, 0, 10, 10, SkPaint()); + canvas.drawPicture(otherRecorder.finishRecordingAsPicture()); + }); + + test('drawParagraph', () { + final CkParagraphBuilder builder = CkParagraphBuilder( + CkParagraphStyle(), + ); + builder.addText('Hello'); + final CkParagraph paragraph = builder.build(); + paragraph.layout(const ui.ParagraphConstraints(width: 100)); + canvas.drawParagraph( + debugJsObjectWrapper.unwrapSkParagraph(paragraph.legacySkiaObject), + 10, + 20, + ); + }); +} + final Uint8List kTransparentImage = Uint8List.fromList([ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart new file mode 100644 index 00000000000..1d7731c9abd --- /dev/null +++ b/lib/web_ui/test/canvaskit/common.dart @@ -0,0 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'package:ui/src/engine.dart'; + +/// Whether we are running on iOS Safari. +// TODO: https://github.com/flutter/flutter/issues/60040 +bool get isIosSafari => browserEngine == BrowserEngine.webkit && + operatingSystem == OperatingSystem.iOs; diff --git a/lib/web_ui/test/canvaskit/path_metrics_test.dart b/lib/web_ui/test/canvaskit/path_metrics_test.dart index a41cd59689b..62bde94a867 100644 --- a/lib/web_ui/test/canvaskit/path_metrics_test.dart +++ b/lib/web_ui/test/canvaskit/path_metrics_test.dart @@ -8,6 +8,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import 'common.dart'; + void main() { group('Path Metrics', () { setUpAll(() async { @@ -65,9 +67,5 @@ void main() { expect(() => iter1.current, throwsRangeError); expect(() => iter2.current, throwsRangeError); }); - }, - // This test failed on iOS Safari. - // TODO: https://github.com/flutter/flutter/issues/60040 - skip: (browserEngine == BrowserEngine.webkit && - operatingSystem == OperatingSystem.iOs)); + }, skip: isIosSafari); // TODO: https://github.com/flutter/flutter/issues/60040 } diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index 3999907c600..7bd6236b4e5 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -8,10 +8,25 @@ import 'dart:js'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; import 'package:ui/src/engine.dart'; +import 'common.dart'; + void main() { + group('skia_objects_cache', () { + _tests(); + // TODO: https://github.com/flutter/flutter/issues/60040 + }, skip: isIosSafari); +} + +void _tests() { SkiaObjects.maximumCacheSize = 4; + + setUpAll(() async { + await ui.webOnlyInitializePlatform(); + }); + group(ResurrectableSkiaObject, () { test('implements create, cache, delete, resurrect, delete lifecycle', () { int addPostFrameCallbackCount = 0; @@ -30,7 +45,7 @@ void main() { expect(testObject.deleteCount, 0); // Check that the getter does not have side-effects - final JsObject skiaObject1 = testObject.skiaObject; + final JsObject skiaObject1 = testObject.legacySkiaObject; expect(skiaObject1, isNotNull); expect(SkiaObjects.resurrectableObjects.single, testObject); expect(testObject.createDefaultCount, 1); @@ -46,7 +61,7 @@ void main() { expect(testObject.deleteCount, 1); // Trigger resurrect - final JsObject skiaObject2 = testObject.skiaObject; + final JsObject skiaObject2 = testObject.legacySkiaObject; expect(skiaObject2, isNotNull); expect(skiaObject2, isNot(same(skiaObject1))); expect(SkiaObjects.resurrectableObjects.single, testObject); @@ -96,20 +111,12 @@ void main() { group(OneShotSkiaObject, () { test('is added to SkiaObjects cache', () { - int deleteCount = 0; - JsObject _makeJsObject() { - return JsObject.jsify({ - 'delete': allowInterop(() { - deleteCount++; - }), - }); - } - - OneShotSkiaObject object1 = OneShotSkiaObject(_makeJsObject()); + TestOneShotSkiaObject.deleteCount = 0; + OneShotSkiaObject object1 = TestOneShotSkiaObject(); expect(SkiaObjects.oneShotCache.length, 1); expect(SkiaObjects.oneShotCache.debugContains(object1), isTrue); - OneShotSkiaObject object2 = OneShotSkiaObject(_makeJsObject()); + OneShotSkiaObject object2 = TestOneShotSkiaObject(); expect(SkiaObjects.oneShotCache.length, 2); expect(SkiaObjects.oneShotCache.debugContains(object2), isTrue); @@ -119,14 +126,14 @@ void main() { expect(SkiaObjects.oneShotCache.debugContains(object2), isTrue); // Add 3 more objects to the cache to overflow it. - OneShotSkiaObject(_makeJsObject()); - OneShotSkiaObject(_makeJsObject()); - OneShotSkiaObject(_makeJsObject()); + TestOneShotSkiaObject(); + TestOneShotSkiaObject(); + TestOneShotSkiaObject(); expect(SkiaObjects.oneShotCache.length, 5); expect(SkiaObjects.cachesToResize.length, 1); SkiaObjects.postFrameCleanUp(); - expect(deleteCount, 2); + expect(TestOneShotSkiaObject.deleteCount, 2); expect(SkiaObjects.oneShotCache.length, 3); expect(SkiaObjects.oneShotCache.debugContains(object1), isFalse); expect(SkiaObjects.oneShotCache.debugContains(object2), isFalse); @@ -134,7 +141,22 @@ void main() { }); } -class TestSkiaObject extends ResurrectableSkiaObject { +class TestOneShotSkiaObject extends OneShotSkiaObject { + static int deleteCount = 0; + + TestOneShotSkiaObject() : super(SkPaint()); + + @override + JsObject get legacySkiaObject => debugJsObjectWrapper.wrapSkPaint(skiaObject); + + @override + void delete() { + rawSkiaObject?.delete(); + deleteCount++; + } +} + +class TestSkiaObject extends ResurrectableSkiaObject { int createDefaultCount = 0; int resurrectCount = 0; int deleteCount = 0; @@ -143,26 +165,27 @@ class TestSkiaObject extends ResurrectableSkiaObject { TestSkiaObject({this.isExpensive = false}); - JsObject _makeJsObject() { - return JsObject.jsify({ - 'delete': allowInterop(() { - deleteCount++; - }), - }); - } - @override - JsObject createDefault() { + SkPaint createDefault() { createDefaultCount++; - return _makeJsObject(); + return SkPaint(); } @override - JsObject resurrect() { + SkPaint resurrect() { resurrectCount++; - return _makeJsObject(); + return SkPaint(); } + @override + void delete() { + rawSkiaObject?.delete(); + deleteCount++; + } + + @override + JsObject get legacySkiaObject => debugJsObjectWrapper.wrapSkPaint(skiaObject); + @override bool get isResurrectionExpensive => isExpensive; }