blob: d0736884e057f101cffe24590f7bde0d44533528 [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.
// ignore_for_file: camel_case_types, non_constant_identifier_names
import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:typed_data';
/// Determines the winding rule that decides how the interior of a Path is
/// calculated.
///
/// This enum is used by the [Path] constructor
// must match ordering in //third_party/skia/include/core/SkPathTypes.h
enum FillType {
/// The interior is defined by a non-zero sum of signed edge crossings.
nonZero,
/// The interior is defined by an odd number of edge crossings.
evenOdd,
}
/// A set of operations applied to two paths.
// Sync with //third_party/skia/include/pathops/SkPathOps.h
enum PathOp {
/// Subtracts the second path from the first.
difference,
/// Creates a new path representing the intersection of the first and second.
intersect,
/// Creates a new path representing the union of the first and second
/// (includive-or).
union,
/// Creates a new path representing the exclusive-or of two paths.
xor,
/// Creates a new path that subtracts the first path from the second.s
reversedDifference,
}
/// The commands used in a [Path] object.
///
/// This enumeration is a subset of the commands that SkPath supports.
// Sync with //third_party/skia/include/core/SkPathTypes.h
enum PathVerb {
/// Picks up the pen and moves it without drawing. Uses two point values.
moveTo,
/// A straight line from the current point to the specified point.
lineTo,
_quadTo,
_conicTo,
/// A cubic bezier curve from the current point.
///
/// The next two points are used as the first control point. The next two
/// points form the second control point. The next two points form the
/// target point.
cubicTo,
/// A straight line from the current point to the last [moveTo] point.
close,
}
/// A proxy class for [Path.replay].
///
/// Allows implementations to easily inspect the contents of a [Path].
abstract class PathProxy {
/// Picks up the pen and moves to absolute coordinates x,y.
void moveTo(double x, double y);
/// Draws a straight line from the current point to absolute coordinates x,y.
void lineTo(double x, double y);
/// Creates a cubic Bezier curve from the current point to point x3,y3 using
/// x1,y1 as the first control point and x2,y2 as the second.
void cubicTo(
double x1, double y1, double x2, double y2, double x3, double y3);
/// Draws a straight line from the current point to the last [moveTo] point.
void close();
/// Called by [Path.replay] to indicate that a new path is being played.
void reset() {}
}
/// A path proxy that can print the SVG path-data representation of this path.
class SvgPathProxy implements PathProxy {
final StringBuffer _buffer = StringBuffer();
@override
void reset() {
_buffer.clear();
}
@override
void close() {
_buffer.write('Z');
}
@override
void cubicTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
_buffer.write('C$x1,$y1 $x2,$y2 $x3,$y3');
}
@override
void lineTo(double x, double y) {
_buffer.write('L$x,$y');
}
@override
void moveTo(double x, double y) {
_buffer.write('M$x,$y');
}
@override
String toString() => _buffer.toString();
}
/// Creates a path object to operate on.
///
/// First, build up the path contours with the [moveTo], [lineTo], [cubicTo],
/// and [close] methods. All methods expect absolute coordinates.
///
/// Finally, use the [dispose] method to clean up native resources. After
/// [dispose] has been called, this class must not be used again.
class Path implements PathProxy {
/// Creates an empty path object with the specified fill type.
Path([FillType fillType = FillType.nonZero])
: _path = _createPathFn(fillType.index);
/// Creates a copy of this path.
factory Path.from(Path other) {
final Path result = Path(other.fillType);
other.replay(result);
return result;
}
/// The [FillType] of this path.
FillType get fillType {
assert(_path != null);
return FillType.values[_getFillTypeFn(_path!)];
}
ffi.Pointer<_SkPath>? _path;
ffi.Pointer<_PathData>? _pathData;
/// The number of points used by each [PathVerb].
static const Map<PathVerb, int> pointsPerVerb = <PathVerb, int>{
PathVerb.moveTo: 2,
PathVerb.lineTo: 2,
PathVerb.cubicTo: 6,
PathVerb.close: 0,
};
/// Makes the appropriate calls using [verbs] and [points] to replay this path
/// on [proxy].
///
/// Calls [PathProxy.reset] first if [reset] is true.
void replay(PathProxy proxy, {bool reset = true}) {
if (reset) {
proxy.reset();
}
int index = 0;
for (final PathVerb verb in verbs.toList()) {
switch (verb) {
case PathVerb.moveTo:
proxy.moveTo(points[index++], points[index++]);
case PathVerb.lineTo:
proxy.lineTo(points[index++], points[index++]);
case PathVerb._quadTo:
assert(false);
case PathVerb._conicTo:
assert(false);
case PathVerb.cubicTo:
proxy.cubicTo(
points[index++],
points[index++],
points[index++],
points[index++],
points[index++],
points[index++],
);
case PathVerb.close:
proxy.close();
}
}
assert(index == points.length);
}
/// The list of path verbs in this path.
///
/// This may not match the verbs supplied by calls to [moveTo], [lineTo],
/// [cubicTo], and [close] after [applyOp] is invoked.
///
/// This list determines the meaning of the [points] array.
Iterable<PathVerb> get verbs {
_updatePathData();
final int count = _pathData!.ref.verb_count;
return List<PathVerb>.generate(count, (int index) {
return PathVerb.values[_pathData!.ref.verbs.elementAt(index).value];
}, growable: false);
}
/// The list of points to use with [verbs].
///
/// Each verb uses a specific number of points, specified by the
/// [pointsPerVerb] map.
Float32List get points {
_updatePathData();
return _pathData!.ref.points.asTypedList(_pathData!.ref.point_count);
}
void _updatePathData() {
assert(_path != null);
_pathData ??= _dataFn(_path!);
}
void _resetPathData() {
if (_pathData != null) {
_destroyDataFn(_pathData!);
}
_pathData = null;
}
@override
void moveTo(double x, double y) {
assert(_path != null);
_resetPathData();
_moveToFn(_path!, x, y);
}
@override
void lineTo(double x, double y) {
assert(_path != null);
_resetPathData();
_lineToFn(_path!, x, y);
}
@override
void cubicTo(
double x1,
double y1,
double x2,
double y2,
double x3,
double y3,
) {
assert(_path != null);
_resetPathData();
_cubicToFn(_path!, x1, y1, x2, y2, x3, y3);
}
@override
void close() {
assert(_path != null);
_resetPathData();
_closeFn(_path!, true);
}
@override
void reset() {
assert(_path != null);
_resetPathData();
_resetFn(_path!);
}
/// Releases native resources.
///
/// After calling dispose, this class must not be used again.
void dispose() {
assert(_path != null);
_resetPathData();
_destroyFn(_path!);
_path = null;
}
/// Applies the operation described by [op] to this path using [other].
Path applyOp(Path other, PathOp op) {
assert(_path != null);
assert(other._path != null);
final Path result = Path.from(this);
_opFn(result._path!, other._path!, op.index);
return result;
}
}
// TODO(dnfield): Figure out where to put this.
// https://github.com/flutter/flutter/issues/99563
final ffi.DynamicLibrary _dylib = () {
if (Platform.isWindows) {
return ffi.DynamicLibrary.open('path_ops.dll');
} else if (Platform.isIOS || Platform.isMacOS) {
return ffi.DynamicLibrary.open('libpath_ops.dylib');
} else if (Platform.isAndroid || Platform.isLinux) {
return ffi.DynamicLibrary.open('libpath_ops.so');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
final class _SkPath extends ffi.Opaque {}
final class _PathData extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> verbs;
@ffi.Size()
external int verb_count;
external ffi.Pointer<ffi.Float> points;
@ffi.Size()
external int point_count;
}
typedef _CreatePathType = ffi.Pointer<_SkPath> Function(int);
typedef _create_path_type = ffi.Pointer<_SkPath> Function(ffi.Int);
final _CreatePathType _createPathFn =
_dylib.lookupFunction<_create_path_type, _CreatePathType>(
'CreatePath',
);
typedef _MoveToType = void Function(ffi.Pointer<_SkPath>, double, double);
typedef _move_to_type = ffi.Void Function(
ffi.Pointer<_SkPath>, ffi.Float, ffi.Float);
final _MoveToType _moveToFn = _dylib.lookupFunction<_move_to_type, _MoveToType>(
'MoveTo',
);
typedef _LineToType = void Function(ffi.Pointer<_SkPath>, double, double);
typedef _line_to_type = ffi.Void Function(
ffi.Pointer<_SkPath>, ffi.Float, ffi.Float);
final _LineToType _lineToFn = _dylib.lookupFunction<_line_to_type, _LineToType>(
'LineTo',
);
typedef _CubicToType = void Function(
ffi.Pointer<_SkPath>, double, double, double, double, double, double);
typedef _cubic_to_type = ffi.Void Function(ffi.Pointer<_SkPath>, ffi.Float,
ffi.Float, ffi.Float, ffi.Float, ffi.Float, ffi.Float);
final _CubicToType _cubicToFn =
_dylib.lookupFunction<_cubic_to_type, _CubicToType>('CubicTo');
typedef _CloseType = void Function(ffi.Pointer<_SkPath>, bool);
typedef _close_type = ffi.Void Function(ffi.Pointer<_SkPath>, ffi.Bool);
final _CloseType _closeFn =
_dylib.lookupFunction<_close_type, _CloseType>('Close');
typedef _ResetType = void Function(ffi.Pointer<_SkPath>);
typedef _reset_type = ffi.Void Function(ffi.Pointer<_SkPath>);
final _ResetType _resetFn =
_dylib.lookupFunction<_reset_type, _ResetType>('Reset');
typedef _DestroyType = void Function(ffi.Pointer<_SkPath>);
typedef _destroy_type = ffi.Void Function(ffi.Pointer<_SkPath>);
final _DestroyType _destroyFn =
_dylib.lookupFunction<_destroy_type, _DestroyType>('DestroyPath');
typedef _OpType = void Function(
ffi.Pointer<_SkPath>, ffi.Pointer<_SkPath>, int);
typedef _op_type = ffi.Void Function(
ffi.Pointer<_SkPath>, ffi.Pointer<_SkPath>, ffi.Int);
final _OpType _opFn = _dylib.lookupFunction<_op_type, _OpType>('Op');
typedef _PathDataType = ffi.Pointer<_PathData> Function(ffi.Pointer<_SkPath>);
typedef _path_data_type = ffi.Pointer<_PathData> Function(ffi.Pointer<_SkPath>);
final _PathDataType _dataFn =
_dylib.lookupFunction<_path_data_type, _PathDataType>('Data');
typedef _DestroyDataType = void Function(ffi.Pointer<_PathData>);
typedef _destroy_data_type = ffi.Void Function(ffi.Pointer<_PathData>);
final _DestroyDataType _destroyDataFn =
_dylib.lookupFunction<_destroy_data_type, _DestroyDataType>('DestroyData');
typedef _GetFillTypeType = int Function(ffi.Pointer<_SkPath>);
typedef _get_fill_type_type = ffi.Int32 Function(ffi.Pointer<_SkPath>);
final _GetFillTypeType _getFillTypeFn =
_dylib.lookupFunction<_get_fill_type_type, _GetFillTypeType>('GetFillType');