blob: a9ed3864198aff07d30ad0ff999e064ccec2d72e [file] [log] [blame]
// 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.
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:ui/ui.dart' as ui;
import '../vector_math.dart';
import 'canvaskit_api.dart';
import 'path_metrics.dart';
import 'skia_object_cache.dart';
/// An implementation of [ui.Path] which is backed by an `SkPath`.
///
/// The `SkPath` is required for `CkCanvas` methods which take a path.
class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
CkPath() : _fillType = ui.PathFillType.nonZero;
CkPath.from(CkPath other)
: _fillType = other.fillType,
super(other.skiaObject.copy()) {
skiaObject.setFillType(toSkFillType(_fillType));
}
CkPath.fromSkPath(SkPath skPath, this._fillType) : super(skPath) {
skiaObject.setFillType(toSkFillType(_fillType));
}
ui.PathFillType _fillType;
@override
ui.PathFillType get fillType => _fillType;
@override
set fillType(ui.PathFillType newFillType) {
if (_fillType == newFillType) {
return;
}
_fillType = newFillType;
skiaObject.setFillType(toSkFillType(newFillType));
}
@override
void addArc(ui.Rect oval, double startAngle, double sweepAngle) {
const double toDegrees = 180.0 / math.pi;
skiaObject.addArc(
toSkRect(oval),
startAngle * toDegrees,
sweepAngle * toDegrees,
);
}
@override
void addOval(ui.Rect oval) {
skiaObject.addOval(toSkRect(oval), false, 1);
}
@override
void addPath(ui.Path path, ui.Offset offset, {Float64List? matrix4}) {
List<double> skMatrix;
if (matrix4 == null) {
skMatrix = toSkMatrixFromFloat32(
Matrix4.translationValues(offset.dx, offset.dy, 0.0).storage);
} else {
skMatrix = toSkMatrixFromFloat64(matrix4);
skMatrix[2] += offset.dx;
skMatrix[5] += offset.dy;
}
final CkPath otherPath = path as CkPath;
skiaObject.addPath(
otherPath.skiaObject,
skMatrix[0],
skMatrix[1],
skMatrix[2],
skMatrix[3],
skMatrix[4],
skMatrix[5],
skMatrix[6],
skMatrix[7],
skMatrix[8],
false,
);
}
@override
void addPolygon(List<ui.Offset> points, bool close) {
assert(points != null); // ignore: unnecessary_null_comparison
final SkFloat32List encodedPoints = toMallocedSkPoints(points);
skiaObject.addPoly(encodedPoints.toTypedArray(), close);
freeFloat32List(encodedPoints);
}
@override
void addRRect(ui.RRect rrect) {
skiaObject.addRRect(
toSkRRect(rrect),
false,
);
}
@override
void addRect(ui.Rect rect) {
skiaObject.addRect(toSkRect(rect));
}
@override
void arcTo(
ui.Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) {
const double toDegrees = 180.0 / math.pi;
skiaObject.arcToOval(
toSkRect(rect),
startAngle * toDegrees,
sweepAngle * toDegrees,
forceMoveTo,
);
}
@override
void arcToPoint(ui.Offset arcEnd,
{ui.Radius radius = ui.Radius.zero,
double rotation = 0.0,
bool largeArc = false,
bool clockwise = true}) {
skiaObject.arcToRotated(
radius.x,
radius.y,
rotation,
!largeArc,
!clockwise,
arcEnd.dx,
arcEnd.dy,
);
}
@override
void close() {
skiaObject.close();
}
@override
ui.PathMetrics computeMetrics({bool forceClosed = false}) {
return CkPathMetrics(this, forceClosed);
}
@override
void conicTo(double x1, double y1, double x2, double y2, double w) {
skiaObject.conicTo(x1, y1, x2, y2, w);
}
@override
bool contains(ui.Offset point) {
return skiaObject.contains(point.dx, point.dy);
}
@override
void cubicTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
skiaObject.cubicTo(x1, y1, x2, y2, x3, y3);
}
@override
void extendWithPath(ui.Path path, ui.Offset offset, {Float64List? matrix4}) {
List<double> skMatrix;
if (matrix4 == null) {
skMatrix = toSkMatrixFromFloat32(
Matrix4.translationValues(offset.dx, offset.dy, 0.0).storage);
} else {
skMatrix = toSkMatrixFromFloat64(matrix4);
skMatrix[2] += offset.dx;
skMatrix[5] += offset.dy;
}
final CkPath otherPath = path as CkPath;
skiaObject.addPath(
otherPath.skiaObject,
skMatrix[0],
skMatrix[1],
skMatrix[2],
skMatrix[3],
skMatrix[4],
skMatrix[5],
skMatrix[6],
skMatrix[7],
skMatrix[8],
true,
);
}
@override
ui.Rect getBounds() => fromSkRect(skiaObject.getBounds());
@override
void lineTo(double x, double y) {
skiaObject.lineTo(x, y);
}
@override
void moveTo(double x, double y) {
skiaObject.moveTo(x, y);
}
@override
void quadraticBezierTo(double x1, double y1, double x2, double y2) {
skiaObject.quadTo(x1, y1, x2, y2);
}
@override
void relativeArcToPoint(ui.Offset arcEndDelta,
{ui.Radius radius = ui.Radius.zero,
double rotation = 0.0,
bool largeArc = false,
bool clockwise = true}) {
skiaObject.rArcTo(
radius.x,
radius.y,
rotation,
!largeArc,
!clockwise,
arcEndDelta.dx,
arcEndDelta.dy,
);
}
@override
void relativeConicTo(double x1, double y1, double x2, double y2, double w) {
skiaObject.rConicTo(x1, y1, x2, y2, w);
}
@override
void relativeCubicTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
skiaObject.rCubicTo(x1, y1, x2, y2, x3, y3);
}
@override
void relativeLineTo(double dx, double dy) {
skiaObject.rLineTo(dx, dy);
}
@override
void relativeMoveTo(double dx, double dy) {
skiaObject.rMoveTo(dx, dy);
}
@override
void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) {
skiaObject.rQuadTo(x1, y1, x2, y2);
}
@override
void reset() {
// Only reset the local field. Skia will reset its internal state via
// SkPath.reset() below.
_fillType = ui.PathFillType.nonZero;
skiaObject.reset();
}
@override
ui.Path shift(ui.Offset offset) {
// Since CanvasKit does not expose `SkPath.offset`, create a copy of this
// path and call `transform` on it.
final SkPath newPath = skiaObject.copy();
newPath.transform(1.0, 0.0, offset.dx, 0.0, 1.0, offset.dy, 0.0, 0.0, 0.0);
return CkPath.fromSkPath(newPath, _fillType);
}
static CkPath combine(
ui.PathOperation operation,
ui.Path uiPath1,
ui.Path uiPath2,
) {
final CkPath path1 = uiPath1 as CkPath;
final CkPath path2 = uiPath2 as CkPath;
final SkPath newPath = canvasKit.Path.MakeFromOp(
path1.skiaObject,
path2.skiaObject,
toSkPathOp(operation),
);
return CkPath.fromSkPath(newPath, path1._fillType);
}
@override
ui.Path transform(Float64List matrix4) {
final SkPath newPath = skiaObject.copy();
final Float32List m = toSkMatrixFromFloat64(matrix4);
newPath.transform(
m[0],
m[1],
m[2],
m[3],
m[4],
m[5],
m[6],
m[7],
m[8],
);
return CkPath.fromSkPath(newPath, _fillType);
}
String? toSvgString() {
return skiaObject.toSVGString();
}
/// Return `true` if this path contains no segments.
bool get isEmpty {
return skiaObject.isEmpty();
}
@override
bool get isResurrectionExpensive => true;
@override
SkPath createDefault() {
final SkPath path = SkPath();
path.setFillType(toSkFillType(_fillType));
return path;
}
List<dynamic>? _cachedCommands;
@override
void delete() {
_cachedCommands = skiaObject.toCmds();
rawSkiaObject?.delete();
}
@override
SkPath resurrect() {
final SkPath path = canvasKit.Path.MakeFromCmds(_cachedCommands!);
path.setFillType(toSkFillType(_fillType));
return path;
}
}