part of dart._engine;
// 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.




/// An implementation of [ui.Path] which is backed by an `SkPath`.
///
/// The `SkPath` is required for `CkCanvas` methods which take a path.
class CkPath implements DisposablePath {
  factory CkPath() {
    final SkPath skPath = SkPath();
    skPath.setFillType(toSkFillType(ui.PathFillType.nonZero));
    return CkPath._(skPath, ui.PathFillType.nonZero);
  }

  factory CkPath.from(CkPath other) {
    final SkPath skPath = other.skiaObject.copy();
    skPath.setFillType(toSkFillType(other._fillType));
    return CkPath._(skPath, other._fillType);
  }

  factory CkPath.fromSkPath(SkPath skPath, ui.PathFillType fillType) {
    skPath.setFillType(toSkFillType(fillType));
    return CkPath._(skPath, fillType);
  }

  CkPath._(SkPath nativeObject, this._fillType) {
    _ref = UniqueRef<SkPath>(this, nativeObject, 'Path');
  }

  late final UniqueRef<SkPath> _ref;

  SkPath get skiaObject => _ref.nativeObject;

  ui.PathFillType _fillType;

  @override
  void dispose() {
    _ref.dispose();
  }

  @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) {
    final SkFloat32List encodedPoints = toMallocedSkPoints(points);
    skiaObject.addPoly(encodedPoints.toTypedArray(), close);
    free(encodedPoints);
  }

  @override
  void addRRect(ui.RRect rrect) {
    skiaObject.addRRect(toSkRRect(rrect), false);
  }

  @override
  void addRSuperellipse(ui.RSuperellipse rsuperellipse) {
    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
    addPath((path as LazyPath).builtPath, offset);
  }

  @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
  CkPathMetrics 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
  CkPath shift(ui.Offset offset) {
    // `SkPath.transform` mutates the existing path, so create a copy and call
    // `transform` on the copy.
    final SkPath shiftedPath = skiaObject.copy();
    shiftedPath.transform(1.0, 0.0, offset.dx, 0.0, 1.0, offset.dy, 0.0, 0.0, 1.0);
    return CkPath.fromSkPath(shiftedPath, _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);
  }

  @override
  String toSvgString() {
    return skiaObject.toSVGString();
  }

  /// Return `true` if this path contains no segments.
  bool get isEmpty {
    return skiaObject.isEmpty();
  }
}

class CkPathConstructors implements DisposablePathConstructors {
  @override
  CkPath createNew() => CkPath();

  @override
  CkPath combinePaths(ui.PathOperation operation, DisposablePath path1, DisposablePath path2) {
    return CkPath.combine(operation, path1 as CkPath, path2 as CkPath);
  }
}
