Misc changes

This commit is contained in:
2022-02-01 01:57:45 +01:00
parent 4151df40b5
commit aaad8b1db6
5 changed files with 125 additions and 253 deletions

View File

@@ -1,27 +1,18 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for # Kanimaji
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for Add animated kanji strokes to your app!
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features ## Features
TODO: List what your package can do. Maybe include images, gifs, or videos. This library is a port of [Kanimaji][kanimaji], a library for animating kanji.
It provides a way to convert stroke data from [KanjiVG][kanjivg] into kanji animations.
This library ports this ability into flutter, and lets you choose speed, colors, and formats, in the form of a `Kanimaji` widget and a SVG/GIF generating function.
## Getting started ## Getting started
TODO: List prerequisites and provide or point to information on how to Start by adding the project to your pubspec.yaml.
start using the package.
## Usage ## Usage
@@ -37,4 +28,7 @@ const like = 'sample';
The [svg library used](lib/svg) is mostly a rewrite of pythons [svg.path][svg.path]. The [svg library used](lib/svg) is mostly a rewrite of pythons [svg.path][svg.path].
This is what kanimaji originally used for animation, and even thought there's a lot of svg path parsers in dart, I found none that was able to calculate the length of the path. If you do find one, please let me know! This is what kanimaji originally used for animation, and even thought there's a lot of svg path parsers in dart, I found none that was able to calculate the length of the path. If you do find one, please let me know!
Also, do note that most of the comments in the project is brought over from the python projects.
I've tried to adjust and remove some of them to make them more useful, but they shouldn't be trusted if there's doubt.
[svg.path]: https://pypi.org/project/svg.path/ [svg.path]: https://pypi.org/project/svg.path/

View File

@@ -1,17 +1,15 @@
/// ignore_for_file: non_constant_identifier_names, avoid_print, unused_local_variable, dead_code, constant_identifier_names
import 'dart:io'; import 'dart:io';
import 'dart:math' show min, sqrt, pow; import 'dart:math' show min, sqrt, pow;
import '../svg/parser.dart'; import '../svg/parser.dart';
import '../common/Point.dart'; import '../common/Point.dart';
import 'bezier_cubic.dart' as bezier_cubic; import 'bezierCubic.dart' as bezier_cubic;
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
double _computePathLength(String path) => double _computePathLength(String path) =>
parse_path(path).size(error: 1e-8).toDouble(); parsePath(path).size(error: 1e-8).toDouble();
String _shescape(String path) => String _shescape(String path) =>
"'${path.replaceAll(RegExp(r"(?=['\\\\])"), "\\\\")}'"; "'${path.replaceAll(RegExp(r"(?=['\\\\])"), "\\\\")}'";
@@ -682,7 +680,7 @@ void createAnimation({
void main(List<String> args) { void main(List<String> args) {
// createAnimation('assets/kanjivg/kanji/0f9b1.svg'); // createAnimation('assets/kanjivg/kanji/0f9b1.svg');
const kanji = '情報科学'; const kanji = '実例';
final fileList = []; final fileList = [];
for (int k = 0; k < kanji.length; k++) { for (int k = 0; k < kanji.length; k++) {
createAnimation( createAnimation(

View File

@@ -1,9 +1,9 @@
/// SVG Path specification parser /// SVG Path specification parser
///
import '../common/Point.dart'; import '../common/Point.dart';
import 'path.dart'; import 'path.dart';
const COMMANDS = { const _commands = {
'M', 'M',
'm', 'm',
'Z', 'Z',
@@ -25,16 +25,17 @@ const COMMANDS = {
'A', 'A',
'a' 'a'
}; };
const UPPERCASE = {'M', 'Z', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A'};
final COMMAND_RE = RegExp("(?=[${COMMANDS.join('')}])"); // const _uppercaseCommands = {'M', 'Z', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A'};
final FLOAT_RE = RegExp(r"^[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?");
class ParserResult<T> { final _commandPattern = RegExp("(?=[${_commands.join('')}])");
final _floatPattern = RegExp(r"^[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?");
class _ParserResult<T> {
final T value; final T value;
final String remaining; final String remaining;
const ParserResult({required this.value, required this.remaining}); const _ParserResult({required this.value, required this.remaining});
} }
class InvalidPathError implements Exception { class InvalidPathError implements Exception {
@@ -50,7 +51,7 @@ class InvalidPathError implements Exception {
// s: Signed number or coordinate // s: Signed number or coordinate
// c: coordinate-pair, which is two coordinates/numbers, separated by whitespace // c: coordinate-pair, which is two coordinates/numbers, separated by whitespace
// f: A one character flag, doesn't need whitespace, 1 or 0 // f: A one character flag, doesn't need whitespace, 1 or 0
const ARGUMENT_SEQUENCE = { const _argumentSequence = {
"M": "c", "M": "c",
"Z": "", "Z": "",
"L": "c", "L": "c",
@@ -64,82 +65,83 @@ const ARGUMENT_SEQUENCE = {
}; };
/// Strips whitespace and commas /// Strips whitespace and commas
String strip_array(String arg_array) { String _stripArray(String stringToParse) {
// EBNF wsp:(#x20 | #x9 | #xD | #xA) + comma: 0x2C // EBNF wsp:(#x20 | #x9 | #xD | #xA) + comma: 0x2C
while (arg_array.isNotEmpty && ' \t\n\r,'.contains(arg_array[0])) { while (stringToParse.isNotEmpty && ' \t\n\r,'.contains(stringToParse[0])) {
arg_array = arg_array.substring(1); stringToParse = stringToParse.substring(1);
} }
return arg_array; return stringToParse;
} }
ParserResult<double> pop_number(String arg_array) { _ParserResult<double> _parseNumber(String stringToParse) {
final res = FLOAT_RE.firstMatch(arg_array); final res = _floatPattern.firstMatch(stringToParse);
if (res == null) { if (res == null) {
throw InvalidPathError("Expected a number, got '$arg_array'."); throw InvalidPathError("Expected a number, got '$stringToParse'.");
} }
final number = double.parse(res.group(0)!); final number = double.parse(res.group(0)!);
final start = res.start; final start = res.start;
final end = res.end; final end = res.end;
arg_array = arg_array.substring(0, start) + arg_array.substring(end); stringToParse =
arg_array = strip_array(arg_array); stringToParse.substring(0, start) + stringToParse.substring(end);
stringToParse = _stripArray(stringToParse);
return ParserResult(value: number, remaining: arg_array); return _ParserResult(value: number, remaining: stringToParse);
} }
ParserResult<double> pop_unsigned_number(arg_array) { _ParserResult<double> _parseUnsignedNumber(String stringToParse) {
final number = pop_number(arg_array); final number = _parseNumber(stringToParse);
if (number.value < 0) { if (number.value < 0) {
throw InvalidPathError("Expected a non-negative number, got '$number'."); throw InvalidPathError("Expected a non-negative number, got '$number'.");
} }
return number; return number;
} }
ParserResult<Point> pop_coordinate_pair(arg_array) { _ParserResult<Point> _parseCoordinatePair(String stringToParse) {
final x = pop_number(arg_array); final x = _parseNumber(stringToParse);
final y = pop_number(x.remaining); final y = _parseNumber(x.remaining);
return ParserResult(value: Point(x.value, y.value), remaining: y.remaining); return _ParserResult(value: Point(x.value, y.value), remaining: y.remaining);
} }
ParserResult<bool> pop_flag(String arg_array) { _ParserResult<bool> _parseflag(String stringToParse) {
final flag = arg_array[0]; final flag = stringToParse[0];
arg_array = arg_array.substring(1); stringToParse = stringToParse.substring(1);
arg_array = strip_array(arg_array); stringToParse = _stripArray(stringToParse);
if (flag == '0') return ParserResult(value: false, remaining: arg_array); if (flag == '0') return _ParserResult(value: false, remaining: stringToParse);
if (flag == '1') return ParserResult(value: true, remaining: arg_array); if (flag == '1') return _ParserResult(value: true, remaining: stringToParse);
throw InvalidPathError("Expected either 1 or 0, got '$flag'"); throw InvalidPathError("Expected either 1 or 0, got '$flag'");
} }
const FIELD_POPPERS = { const fieldParsers = {
"u": pop_unsigned_number, "u": _parseUnsignedNumber,
"s": pop_number, "s": _parseNumber,
"c": pop_coordinate_pair, "c": _parseCoordinatePair,
"f": pop_flag, "f": _parseflag,
}; };
class Command { class _Command {
final String command; final String command;
final String args; final String args;
const Command({required this.command, required this.args}); const _Command({required this.command, required this.args});
@override @override
String toString() => 'Command: $command $args'; String toString() => 'Command: $command $args';
} }
// Splits path into commands and arguments // Splits path into commands and arguments
List<Command> _commandify_path(String pathdef) { List<_Command> _commandifyPath(String pathdef) {
List<Command> tokens = []; List<_Command> tokens = [];
List<String> token = []; List<String> token = [];
for (String c in pathdef.split(COMMAND_RE)) { for (String c in pathdef.split(_commandPattern)) {
String x = c[0]; String x = c[0];
String? y = (c.length > 1) ? c.substring(1).trim() : null; String? y = (c.length > 1) ? c.substring(1).trim() : null;
if (!COMMANDS.contains(x)) { if (!_commands.contains(x)) {
throw InvalidPathError("Path does not start with a command: $pathdef"); throw InvalidPathError("Path does not start with a command: $pathdef");
} }
if (token.isNotEmpty) { if (token.isNotEmpty) {
tokens.add(Command(command: token[0], args: token[1])); tokens.add(_Command(command: token[0], args: token[1]));
// yield token; // yield token;
} }
if (x == "z" || x == "Z") { if (x == "z" || x == "Z") {
@@ -154,7 +156,7 @@ List<Command> _commandify_path(String pathdef) {
token.add(y); token.add(y);
} }
} }
tokens.add(Command(command: token[0], args: token[1])); tokens.add(_Command(command: token[0], args: token[1]));
// yield token; // yield token;
return tokens; return tokens;
} }
@@ -169,10 +171,9 @@ class Token {
String toString() => 'Token: $command ($args)'; String toString() => 'Token: $command ($args)';
} }
List<Token> _tokenize_path(String pathdef) { List<Token> _tokenizePath(String pathdef) {
List<Token> tokens = []; List<Token> tokens = [];
for (final token in _commandify_path(pathdef)) { for (final token in _commandifyPath(pathdef)) {
// _commandify_path(pathdef).forEach((List<String> token) {
String command = token.command; String command = token.command;
String args = token.args; String args = token.args;
@@ -184,22 +185,21 @@ List<Token> _tokenize_path(String pathdef) {
// For the rest of the commands, we parse the arguments and // For the rest of the commands, we parse the arguments and
// yield one command per full set of arguments // yield one command per full set of arguments
final String arg_sequence = ARGUMENT_SEQUENCE[command.toUpperCase()]!; final String stringToParse = _argumentSequence[command.toUpperCase()]!;
String arguments = args; String arguments = args;
while (arguments.isNotEmpty) { while (arguments.isNotEmpty) {
final List<Object> command_arguments = []; final List<Object> commandArguments = [];
for (final arg in arg_sequence.split('')) { for (final arg in stringToParse.split('')) {
try { try {
final result = FIELD_POPPERS[arg]!.call(arguments); final result = fieldParsers[arg]!.call(arguments);
arguments = result.remaining; arguments = result.remaining;
command_arguments.add(result.value); commandArguments.add(result.value);
} on InvalidPathError { } on InvalidPathError {
throw InvalidPathError("Invalid path element $command $args"); throw InvalidPathError("Invalid path element $command $args");
} }
} }
tokens.add(Token(command: command, args: command_arguments)); tokens.add(Token(command: command, args: commandArguments));
// yield (command,) + tuple(command_arguments)
// Implicit Moveto commands should be treated as Lineto commands. // Implicit Moveto commands should be treated as Lineto commands.
if (command == "m") { if (command == "m") {
@@ -212,71 +212,71 @@ List<Token> _tokenize_path(String pathdef) {
return tokens; return tokens;
} }
Path parse_path(String pathdef) { Path parsePath(String pathdef) {
final segments = Path(); final segments = Path();
Point? start_pos; Point? startPos;
String? last_command; String? lastCommand;
Point current_pos = Point.zero; Point currentPos = Point.zero;
for (final token in _tokenize_path(pathdef)) { for (final token in _tokenizePath(pathdef)) {
final command = token.command.toUpperCase(); final command = token.command.toUpperCase();
final absolute = token.command.toUpperCase() == token.command; final absolute = token.command.toUpperCase() == token.command;
if (command == "M") { if (command == "M") {
final pos = token.args[0] as Point; final pos = token.args[0] as Point;
if (absolute) { if (absolute) {
current_pos = pos; currentPos = pos;
} else { } else {
current_pos += pos; currentPos += pos;
} }
segments.add(Move(to: current_pos)); segments.add(Move(to: currentPos));
start_pos = current_pos; startPos = currentPos;
} else if (command == "Z") { } else if (command == "Z") {
// TODO Throw error if not available: // TODO Throw error if not available:
segments.add(Close(start: current_pos, end: start_pos!)); segments.add(Close(start: currentPos, end: startPos!));
current_pos = start_pos; currentPos = startPos;
} else if (command == "L") { } else if (command == "L") {
Point pos = token.args[0] as Point; Point pos = token.args[0] as Point;
if (!absolute) { if (!absolute) {
pos += current_pos; pos += currentPos;
} }
segments.add(Line(start: current_pos, end: pos)); segments.add(Line(start: currentPos, end: pos));
current_pos = pos; currentPos = pos;
} else if (command == "H") { } else if (command == "H") {
double hpos = token.args[0] as double; double hpos = token.args[0] as double;
if (!absolute) { if (!absolute) {
hpos += current_pos.x; hpos += currentPos.x;
} }
final pos = Point(hpos, current_pos.y); final pos = Point(hpos, currentPos.y);
segments.add(Line(start: current_pos, end: pos)); segments.add(Line(start: currentPos, end: pos));
current_pos = pos; currentPos = pos;
} else if (command == "V") { } else if (command == "V") {
double vpos = token.args[0] as double; double vpos = token.args[0] as double;
if (!absolute) { if (!absolute) {
vpos += current_pos.y; vpos += currentPos.y;
} }
final pos = Point(current_pos.x, vpos); final pos = Point(currentPos.x, vpos);
segments.add(Line(start: current_pos, end: pos)); segments.add(Line(start: currentPos, end: pos));
current_pos = pos; currentPos = pos;
} else if (command == "C") { } else if (command == "C") {
Point control1 = token.args[0] as Point; Point control1 = token.args[0] as Point;
Point control2 = token.args[1] as Point; Point control2 = token.args[1] as Point;
Point end = token.args[2] as Point; Point end = token.args[2] as Point;
if (!absolute) { if (!absolute) {
control1 += current_pos; control1 += currentPos;
control2 += current_pos; control2 += currentPos;
end += current_pos; end += currentPos;
} }
segments.add( segments.add(
CubicBezier( CubicBezier(
start: current_pos, start: currentPos,
control1: control1, control1: control1,
control2: control2, control2: control2,
end: end, end: end,
), ),
); );
current_pos = end; currentPos = end;
} else if (command == "S") { } else if (command == "S") {
// Smooth curve. First control point is the "reflection" of // Smooth curve. First control point is the "reflection" of
// the second control point in the previous path. // the second control point in the previous path.
@@ -284,73 +284,73 @@ Path parse_path(String pathdef) {
Point end = token.args[1] as Point; Point end = token.args[1] as Point;
if (!absolute) { if (!absolute) {
control2 += current_pos; control2 += currentPos;
end += current_pos; end += currentPos;
} }
late final Point control1; late final Point control1;
if (last_command == 'C' || last_command == 'S') { if (lastCommand == 'C' || lastCommand == 'S') {
// The first control point is assumed to be the reflection of // The first control point is assumed to be the reflection of
// the second control point on the previous command relative // the second control point on the previous command relative
// to the current point. // to the current point.
control1 = control1 =
current_pos + current_pos - (segments.last as CubicBezier).control2; currentPos + currentPos - (segments.last as CubicBezier).control2;
} else { } else {
// If there is no previous command or if the previous command // If there is no previous command or if the previous command
// was not an C, c, S or s, assume the first control point is // was not an C, c, S or s, assume the first control point is
// coincident with the current point. // coincident with the current point.
control1 = current_pos; control1 = currentPos;
} }
segments.add( segments.add(
CubicBezier( CubicBezier(
start: current_pos, start: currentPos,
control1: control1, control1: control1,
control2: control2, control2: control2,
end: end), end: end),
); );
current_pos = end; currentPos = end;
} else if (command == "Q") { } else if (command == "Q") {
Point control = token.args[0] as Point; Point control = token.args[0] as Point;
Point end = token.args[1] as Point; Point end = token.args[1] as Point;
if (!absolute) { if (!absolute) {
control += current_pos; control += currentPos;
end += current_pos; end += currentPos;
} }
segments.add( segments.add(
QuadraticBezier(start: current_pos, control: control, end: end), QuadraticBezier(start: currentPos, control: control, end: end),
); );
current_pos = end; currentPos = end;
} else if (command == "T") { } else if (command == "T") {
// Smooth curve. Control point is the "reflection" of // Smooth curve. Control point is the "reflection" of
// the second control point in the previous path. // the second control point in the previous path.
Point end = token.args[0] as Point; Point end = token.args[0] as Point;
if (!absolute) { if (!absolute) {
end += current_pos; end += currentPos;
} }
late final Point control; late final Point control;
if (last_command == "Q" || last_command == 'T') { if (lastCommand == "Q" || lastCommand == 'T') {
// The control point is assumed to be the reflection of // The control point is assumed to be the reflection of
// the control point on the previous command relative // the control point on the previous command relative
// to the current point. // to the current point.
control = current_pos + control = currentPos +
current_pos - currentPos -
(segments.last as QuadraticBezier).control; (segments.last as QuadraticBezier).control;
} else { } else {
// If there is no previous command or if the previous command // If there is no previous command or if the previous command
// was not an Q, q, T or t, assume the first control point is // was not an Q, q, T or t, assume the first control point is
// coincident with the current point. // coincident with the current point.
control = current_pos; control = currentPos;
} }
segments.add( segments.add(
QuadraticBezier(start: current_pos, control: control, end: end), QuadraticBezier(start: currentPos, control: control, end: end),
); );
current_pos = end; currentPos = end;
} else if (command == "A") { } else if (command == "A") {
// For some reason I implemented the Arc with a complex radius. // For some reason I implemented the Arc with a complex radius.
// That doesn't really make much sense, but... *shrugs* // That doesn't really make much sense, but... *shrugs*
@@ -361,12 +361,12 @@ Path parse_path(String pathdef) {
Point end = token.args[5] as Point; Point end = token.args[5] as Point;
if (!absolute) { if (!absolute) {
end += current_pos; end += currentPos;
} }
segments.add( segments.add(
Arc( Arc(
start: current_pos, start: currentPos,
radius: radius, radius: radius,
rotation: rotation, rotation: rotation,
arc: arc, arc: arc,
@@ -374,37 +374,12 @@ Path parse_path(String pathdef) {
end: end, end: end,
), ),
); );
current_pos = end; currentPos = end;
} }
// Finish up the loop in preparation for next command // Finish up the loop in preparation for next command
last_command = command; lastCommand = command;
} }
return segments; return segments;
} }
void main(List<String> args) {
// print(_commandify_path('M 10 10 C 20 20, 40 20, 50 10'));
// print(_tokenize_path('M 10 10 C 20 20, 40 20, 50 10'));
// print(_tokenize_path('M 10 80 Q 52.5 10, 95 80 T 180 80'));
// print(_tokenize_path("""
// M 10 315
// L 110 215
// A 30 50 0 0 1 162.55 162.45
// L 172.55 152.45
// A 30 50 -45 0 1 215.1 109.9
// L 315 10
// """));
print(parse_path('M 10 10 C 20 20, 40 20, 50 10'));
print(parse_path('M 10 80 Q 52.5 10, 95 80 T 180 80'));
print(parse_path("""
M 10 315
L 110 215
A 30 50 0 0 1 162.55 162.45
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10
"""));
}

View File

@@ -1,3 +1,6 @@
/// This file contains classes for the different types of SVG path segments as
/// well as a Path object that contains a sequence of path segments.
import 'dart:collection'; import 'dart:collection';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:math' show sqrt, sin, cos, acos, log, pi; import 'dart:math' show sqrt, sin, cos, acos, log, pi;
@@ -6,18 +9,9 @@ import 'package:bisection/extension.dart';
import '../common/Point.dart'; import '../common/Point.dart';
// try:
// from collections.abc import MutableSequence
// except ImportError:
// from collections import MutableSequence
// This file contains classes for the different types of SVG path segments as
// well as a Path object that contains a sequence of path segments.
double radians(num n) => n * pi / 180; double radians(num n) => n * pi / 180;
double degrees(num n) => n * 180 / pi; double degrees(num n) => n * 180 / pi;
const defaultMinDepth = 5; const defaultMinDepth = 5;
const defaultError = 1e-12; const defaultError = 1e-12;
@@ -101,11 +95,6 @@ class Linear extends SvgPath {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
// def __ne__(self, other):
// if not isinstance(other, Line):
// return NotImplemented
// return not self == other
@override @override
Point point(num pos) => start + (end - start).times(pos); Point point(num pos) => start + (end - start).times(pos);
@@ -126,8 +115,6 @@ class Line extends Linear {
String toString() { String toString() {
return "Line(start=$start, end=$end)"; return "Line(start=$start, end=$end)";
} }
// @override
// operator ==(covariant Line other) => start == other.start && end == other.end;
} }
class CubicBezier extends Bezier { class CubicBezier extends Bezier {
@@ -145,18 +132,6 @@ class CubicBezier extends Bezier {
String toString() => "CubicBezier(start=$start, control1=$control1, " String toString() => "CubicBezier(start=$start, control1=$control1, "
"control2=$control2, end=$end)"; "control2=$control2, end=$end)";
// @override
// operator ==(covariant CubicBezier other) =>
// start == other.start &&
// and end == other.end &&
// and control1 == other.control1 &&
// and control2 == other.control2;
// def __ne__(self, other):
// if not isinstance(other, CubicBezier):
// return NotImplemented
// return not self == other
@override @override
bool isSmoothFrom(Object? previous) => previous is CubicBezier bool isSmoothFrom(Object? previous) => previous is CubicBezier
? start == previous.end && ? start == previous.end &&
@@ -202,20 +177,6 @@ class QuadraticBezier extends Bezier {
String toString() => String toString() =>
"QuadraticBezier(start=$start, control=$control, end=$end)"; "QuadraticBezier(start=$start, control=$control, end=$end)";
// def __eq__(self, other):
// if not isinstance(other, QuadraticBezier):
// return NotImplemented
// return (
// self.start == other.start
// and self.end == other.end
// and self.control == other.control
// )
// def __ne__(self, other):
// if not isinstance(other, QuadraticBezier):
// return NotImplemented
// return not self == other
@override @override
bool isSmoothFrom(Object? previous) => previous is QuadraticBezier bool isSmoothFrom(Object? previous) => previous is QuadraticBezier
? start == previous.end && ? start == previous.end &&
@@ -286,30 +247,12 @@ class Arc extends SvgPath {
} }
@override @override
String toString() => "Arc(start=$start, radius=$radius, rotation=$rotation, " String toString() => 'Arc(start=$start, radius=$radius, rotation=$rotation, '
"arc=$arc, sweep=$sweep, end=$end)"; 'arc=$arc, sweep=$sweep, end=$end)';
// def __eq__(self, other):
// if not isinstance(other, Arc):
// return NotImplemented
// return (
// self.start == other.start
// and self.end == other.end
// and self.radius == other.radius
// and self.rotation == other.rotation
// and self.arc == other.arc
// and self.sweep == other.sweep
// )
// def __ne__(self, other):
// if not isinstance(other, Arc):
// return NotImplemented
// return not self == other
/// Conversion from endpoint to center parameterization
/// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
void _parameterize() { void _parameterize() {
// Conversion from endpoint to center parameterization
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// This is equivalent of omitting the segment, so do nothing // This is equivalent of omitting the segment, so do nothing
if (start == end) return; if (start == end) return;
@@ -439,22 +382,13 @@ class Arc extends SvgPath {
} }
} }
// Represents move commands. Does nothing, but is there to handle /// Represents move commands. Does nothing, but is there to handle
// paths that consist of only move commands, which is valid, but pointless. /// paths that consist of only move commands, which is valid, but pointless.
class Move extends SvgPath { class Move extends SvgPath {
const Move({required Point to}) : super(start: to, end: to); const Move({required Point to}) : super(start: to, end: to);
@override @override
String toString() => "Move(to=$start)"; String toString() => "Move(to=$start)";
// def __eq__(self, other):
// if not isinstance(other, Move):
// return NotImplemented
// return self.start == other.start
// def __ne__(self, other):
// if not isinstance(other, Move):
// return NotImplemented
// return not self == other
@override @override
Point point(num pos) => start; Point point(num pos) => start;
@@ -464,7 +398,7 @@ class Move extends SvgPath {
0; 0;
} }
// Represents the closepath command /// Represents the closepath command
class Close extends Linear { class Close extends Linear {
const Close({ const Close({
required Point start, required Point start,
@@ -616,33 +550,4 @@ class Path extends ListBase<SvgPath> {
return parts.join(" "); return parts.join(" ");
} }
// def __delitem__(self, index):
// del self._segments[index]
// self._length = None
// def reverse(self):
// # Reversing the order of a path would require reversing each element
// # as well. That's not implemented.
// raise NotImplementedError
// def __len__(self):
// return len(self._segments)
// def __eq__(self, other):
// if not isinstance(other, Path):
// return NotImplemented
// if len(self) != len(other):
// return False
// for s, o in zip(self._segments, other._segments):
// if not s == o:
// return False
// return True
// def __ne__(self, other):
// if not isinstance(other, Path):
// return NotImplemented
// return not self == other
} }