Move CkCanvas to new JS-interop (#19748)

* Move CkCanvas to new JS-interop
* Make SkiaObject compatible with JS-interop; text bindings
* fix drawVertices
This commit is contained in:
Yegor 2020-07-16 11:56:37 -07:00 committed by GitHub
parent 5a25a28174
commit 94b23abaae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1791 additions and 637 deletions

View File

@ -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', <js.JsObject?>[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', <dynamic>[
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', <dynamic>[
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', <dynamic>[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', <dynamic>[
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<Float32List>? colors,
List<Float32List>? colors,
ui.BlendMode blendMode,
) {
final CkImage skAtlas = atlas as CkImage;
skCanvas.callMethod('drawAtlas', <dynamic>[
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', <dynamic>[
skCanvas.drawCircle(
c.dx,
c.dy,
radius,
paint.skiaObject,
]);
);
}
void drawColor(ui.Color color, ui.BlendMode blendMode) {
skCanvas.callMethod('drawColorInt', <dynamic>[
skCanvas.drawColorInt(
color.value,
makeSkBlendMode(blendMode),
]);
toSkBlendMode(blendMode),
);
}
void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) {
skCanvas.callMethod('drawDRRect', <js.JsObject?>[
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', <dynamic>[
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', <dynamic>[
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', <dynamic>[
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', <dynamic>[
skCanvas.drawLine(
p1.dx,
p1.dy,
p2.dx,
p2.dy,
paint.skiaObject,
]);
);
}
void drawOval(ui.Rect rect, CkPaint paint) {
skCanvas.callMethod('drawOval', <js.JsObject?>[
makeSkRect(rect),
skCanvas.drawOval(
toSkRect(rect),
paint.skiaObject,
]);
);
}
void drawPaint(CkPaint paint) {
skCanvas.callMethod('drawPaint', <js.JsObject?>[paint.skiaObject]);
skCanvas.drawPaint(paint.skiaObject);
}
void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {
final CkParagraph skParagraph = paragraph as CkParagraph;
skCanvas.callMethod('drawParagraph', <dynamic>[
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', <js.JsObject?>[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', <js.JsObject?>[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<js.JsArray<double>>? points) {
skCanvas.callMethod('drawPoints', <dynamic>[
makeSkPointMode(pointMode),
Float32List points) {
skCanvas.drawPoints(
toSkPointMode(pointMode),
points,
paint.skiaObject,
]);
);
}
void drawRRect(ui.RRect rrect, CkPaint paint) {
skCanvas.callMethod('drawRRect', <js.JsObject?>[
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', <js.JsObject?>[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', <js.JsObject?>[
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', <int?>[count]);
void restoreToCount(int count) {
skCanvas.restoreToCount(count);
}
void rotate(double radians) {
skCanvas
.callMethod('rotate', <double>[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', <js.JsObject?>[
makeSkRect(bounds),
skCanvas.saveLayer(
toSkRect(bounds),
paint.skiaObject,
]);
);
}
void saveLayerWithoutBounds(CkPaint paint) {
skCanvas.callMethod('saveLayer', <js.JsObject?>[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',
<dynamic>[
null,
skImageFilter.skiaObject,
0,
makeSkRect(bounds),
],
return override.saveLayer(
null,
skImageFilter.skiaObject,
0,
toSkRect(bounds),
);
}
void scale(double sx, double sy) {
skCanvas.callMethod('scale', <double>[sx, sy]);
skCanvas.scale(sx, sy);
}
void skew(double sx, double sy) {
skCanvas.callMethod('skew', <double>[sx, sy]);
skCanvas.skew(sx, sy);
}
void transform(Float32List? matrix4) {
skCanvas.callMethod(
'concat', <js.JsArray<double>>[makeSkMatrixFromFloat32(matrix4)]);
void transform(Float32List matrix4) {
skCanvas.concat(toSkMatrixFromFloat32(matrix4));
}
void translate(double dx, double dy) {
skCanvas.callMethod('translate', <double>[dx, dy]);
skCanvas.translate(dx, dy);
}
void flush() {
skCanvas.callMethod('flush');
skCanvas.flush();
}
}

View File

@ -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<List<double>>? points) {
_canvas!.drawPoints(paint as CkPaint, pointMode, points as js.JsArray<js.JsArray<double>>?);
_canvas!.drawPoints(
paint as CkPaint,
pointMode,
points,
);
}
@override
@ -395,8 +392,8 @@ class CanvasKitCanvas implements ui.Canvas {
rectBuffer[index3] = rect.bottom;
}
final js.JsArray<Float32List>? colorBuffer =
colors.isEmpty ? null : makeColorList(colors);
final List<Float32List>? 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<Float32List>? colors,
List<Float32List>? colors,
ui.BlendMode blendMode,
) {
_canvas!.drawAtlasRaw(paint as CkPaint, atlas, rstTransforms, rects, colors, blendMode);

View File

@ -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<Float32List> positions,
List<Float32List>? textureCoordinates,
// TODO(yjbanov): make this Uint32Array when CanvasKit supports it.
List<Float32List>? 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<SkFontSlant> _skFontSlants = <SkFontSlant>[
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<SkFontWeight> _skFontWeights = <SkFontWeight>[
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<SkAffinity> _skAffinitys = <SkAffinity>[
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<SkTextDirection> _skTextDirections = <SkTextDirection>[
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<SkTextAlign> _skTextAligns = <SkTextAlign>[
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<SkRectHeightStyle> _skRectHeightStyles = <SkRectHeightStyle>[
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<SkRectWidthStyle> _skRectWidthStyles = <SkRectWidthStyle>[
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<SkVertexMode> _skVertexModes = <SkVertexMode>[
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<SkPointMode> _skPointModes = <SkPointMode>[
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<SkClipOp> _skClipOps = <SkClipOp>[
canvasKitJs.ClipOp.Difference,
canvasKitJs.ClipOp.Intersect,
];
SkClipOp toSkClipOp(ui.ClipOp clipOp) {
return _skClipOps[clipOp.index];
}
@JS()
@ -184,8 +512,8 @@ final List<SkBlurStyle> _skBlurStyles = <SkBlurStyle>[
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<SkTileMode> _skTileModes = <SkTileMode>[
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<Float32List> 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<ui.Color> colors) {
return result;
}
List<Float32List> toSkFloatColorList(List<ui.Color> colors) {
final int len = colors.length;
final List<Float32List> result = <Float32List>[];
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<Float32List> encodeRawColorList(Int32List rawColors) {
final int colorCount = rawColors.length;
final List<ui.Color> colors = <ui.Color>[];
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<ui.Offset> points) {
}
return skPoints;
}
// TODO(yjbanov): this is inefficient. We should be able to pass points
// as Float32List without a conversion.
List<Float32List> rawPointsToSkPoints2d(Float32List points) {
assert(points.length % 2 == 0);
final int pointLength = points.length ~/ 2;
final List<Float32List> result = <Float32List>[];
for (var i = 0; i < pointLength; i++) {
var x = i * 2;
var y = x + 1;
final Float32List skPoint = Float32List(2);
skPoint[0] = points[x];
skPoint[1] = points[y];
result.add(skPoint);
}
return result;
}
List<Float32List> toSkPoints2d(List<ui.Offset> offsets) {
final int len = offsets.length;
final List<Float32List> result = <Float32List>[];
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<int> 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<Float32List>? 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<String>? get fontFamilies;
external set fontFamilies(List<String>? 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<SkRect> 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<Uint8List> fonts);
}

View File

@ -6,7 +6,7 @@
part of engine;
/// A [ui.ColorFilter] backed by Skia's [CkColorFilter].
class CkColorFilter extends ResurrectableSkiaObject {
class CkColorFilter extends ResurrectableSkiaObject<SkColorFilter> {
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();
}
}

View File

@ -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();

View File

@ -41,7 +41,7 @@ class SkiaFontCollection {
final List<Uint8List> fontBuffers =
_registeredFonts.map<Uint8List>((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<ByteBuffer>((dynamic x) => x as ByteBuffer);
}
js.JsObject? skFontMgr;
SkFontMgr? skFontMgr;
}
/// Represents a font that has been registered.

View File

@ -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<SkImageFilter> 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

View File

@ -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);

View File

@ -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;
}

View File

@ -5,7 +5,7 @@
part of engine;
/// The CanvasKit implementation of [ui.MaskFilter].
class CkMaskFilter extends ResurrectableSkiaObject {
class CkMaskFilter extends ResurrectableSkiaObject<SkMaskFilter> {
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();
}
}

View File

@ -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);
}

View File

@ -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<SkPaint> 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);
}

View File

@ -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));
}

View File

@ -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<SkPicture> {
SkPictureSkiaObject(SkPicture picture) : super(picture);
@override
js.JsObject get legacySkiaObject => _jsObjectWrapper.wrapSkPicture(skiaObject);
@override
void delete() {
rawSkiaObject?.delete();
}
}

View File

@ -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'],
<double>[bounds.left, bounds.top, bounds.right, bounds.bottom]);
final js.JsObject skCanvas =
_recorder!.callMethod('beginRecording', <js.JsObject>[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;
}

View File

@ -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<T> {
/// 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<T> extends SkiaObject<T> {
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<T> extends SkiaObject<T> {
/// 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();

View File

@ -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;

View File

@ -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<String, dynamic> toCkTextStyle(
static SkTextStyleProperties toSkTextStyleProperties(
String? fontFamily,
double? fontSize,
ui.FontWeight? fontWeight,
ui.FontStyle? fontStyle,
) {
final Map<String, dynamic> skTextStyle = <String, dynamic>{};
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<String, dynamic> skParagraphStyle = <String, dynamic>{};
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>[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<ui.Shadow>? shadows,
List<ui.FontFeature>? 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<String?> fontFamilies = <String?>[fontFamily];
List<String> fontFamilies = <String>[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', <js.JsObject>[style]);
assert(skTextStyle != null);
return CkTextStyle._(canvasKitJs.TextStyle(properties));
}
CkTextStyle._(this.skTextStyle);
}
Map<String, js.JsObject?> toSkFontStyle(
SkFontStyle toSkFontStyle(
ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) {
Map<String, js.JsObject?> style = <String, js.JsObject?>{};
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<SkParagraph> 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 <ui.TextBox>[];
}
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<js.JsObject> skRects =
skiaObject.callMethod('getRectsForRange', <dynamic>[
List<SkRect> skRects = skiaObject.getRectsForRange(
start,
end,
heightStyle,
widthStyle,
]);
toSkRectHeightStyle(boxHeightStyle),
toSkRectWidthStyle(boxWidthStyle),
);
List<ui.TextBox> result = <ui.TextBox>[];
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', <double>[
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', <int>[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', <double>[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',
<js.JsObject?>[
_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', <String>[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', <js.JsObject?>[skStyle.skTextStyle]);
_paragraphBuilder.pushStyle(skStyle.skTextStyle);
}
}

View File

@ -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<Float32List> makeColorList(List<ui.Color> colors) {
var result = js.JsArray<Float32List>();
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', <dynamic>[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<double> makeSkPoint(ui.Offset point) {
@ -312,7 +288,7 @@ js.JsArray<double> makeSkiaColorStops(List<double>? 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(<String, Float32List>{
'ambient': makeFreshSkColor(inAmbient),
'spot': makeFreshSkColor(inSpot),
});
final SkTonalColors inTonalColors = SkTonalColors(
ambient: makeFreshSkColor(inAmbient),
spot: makeFreshSkColor(inSpot),
);
final js.JsObject tonalColors =
canvasKit.callMethod('computeTonalColors', <js.JsObject>[inTonalColors]);
final SkTonalColors tonalColors =
canvasKitJs.computeTonalColors(inTonalColors);
skCanvas.callMethod('drawShadow', <dynamic>[
path._legacyJsObject,
js.JsArray<double>.from(<double>[0, 0, devicePixelRatio * elevation]),
js.JsArray<double>.from(
<double>[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,
]);
);
}

View File

@ -5,17 +5,8 @@
part of engine;
js.JsArray<Float32List> _encodeRawColorList(Int32List rawColors) {
final int colorCount = rawColors.length;
final List<ui.Color> colors = <ui.Color>[];
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<js.JsArray<double>>? encodedPositions = encodePointList(positions);
final js.JsArray<js.JsArray<double>>? encodedTextures =
encodePointList(textureCoordinates);
final js.JsArray<Float32List>? 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<js.JsArray<double>>?,
encodeRawPointList(textureCoordinates) as js.JsArray<js.JsArray<double>>?,
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<js.JsArray<double>>? positions,
js.JsArray<js.JsArray<double>>? textureCoordinates,
js.JsArray<Float32List>? colors,
List<int>? 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', <dynamic>[
skVertexMode,
positions,
textureCoordinates,
colors,
indices,
]);
if (vertices != null) {
skVertices = vertices;
return true;
} else {
return false;
}
);
}
}

View File

@ -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,

View File

@ -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(<int>[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 <int>[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(<int>[
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,

View File

@ -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;

View File

@ -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
}

View File

@ -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<SkPaint> {
static int deleteCount = 0;
TestOneShotSkiaObject() : super(SkPaint());
@override
JsObject get legacySkiaObject => debugJsObjectWrapper.wrapSkPaint(skiaObject);
@override
void delete() {
rawSkiaObject?.delete();
deleteCount++;
}
}
class TestSkiaObject extends ResurrectableSkiaObject<SkPaint> {
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;
}