Add some tests and fix several bugs

This commit is contained in:
2022-02-01 23:39:56 +01:00
parent aaad8b1db6
commit 97e886d7ea
6 changed files with 452 additions and 72 deletions

View File

@@ -13,6 +13,13 @@ class Point {
operator *(covariant Point p) => Point(x * p.x, y * p.y); operator *(covariant Point p) => Point(x * p.x, y * p.y);
operator /(covariant Point p) => Point(x / p.x, y / p.y); operator /(covariant Point p) => Point(x / p.x, y / p.y);
@override
bool operator ==(Object other) =>
other is Point && x == other.x && y == other.y;
@override
int get hashCode => x.hashCode ^ y.hashCode;
Point addX(num n) => Point(x + n, y); Point addX(num n) => Point(x + n, y);
Point addY(num n) => Point(x, y + n); Point addY(num n) => Point(x, y + n);
Point add(num n) => Point(x + n, y + n); Point add(num n) => Point(x + n, y + n);
@@ -38,4 +45,4 @@ class Point {
@override @override
String toString() => '($x,$y)'; String toString() => '($x,$y)';
} }

View File

@@ -73,13 +73,13 @@ String _stripArray(String stringToParse) {
return stringToParse; return stringToParse;
} }
_ParserResult<double> _parseNumber(String stringToParse) { _ParserResult<num> _parseNumber(String stringToParse) {
final res = _floatPattern.firstMatch(stringToParse); final res = _floatPattern.firstMatch(stringToParse);
if (res == null) { if (res == null) {
throw InvalidPathError("Expected a number, got '$stringToParse'."); throw InvalidPathError("Expected a number, got '$stringToParse'.");
} }
final number = double.parse(res.group(0)!); final number = num.parse(res.group(0)!);
final start = res.start; final start = res.start;
final end = res.end; final end = res.end;
stringToParse = stringToParse =
@@ -89,7 +89,7 @@ _ParserResult<double> _parseNumber(String stringToParse) {
return _ParserResult(value: number, remaining: stringToParse); return _ParserResult(value: number, remaining: stringToParse);
} }
_ParserResult<double> _parseUnsignedNumber(String stringToParse) { _ParserResult<num> _parseUnsignedNumber(String stringToParse) {
final number = _parseNumber(stringToParse); 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'.");
@@ -137,6 +137,7 @@ List<_Command> _commandifyPath(String pathdef) {
for (String c in pathdef.split(_commandPattern)) { 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");
} }
@@ -146,9 +147,8 @@ List<_Command> _commandifyPath(String pathdef) {
} }
if (x == "z" || x == "Z") { if (x == "z" || x == "Z") {
// The end command takes no arguments, so add a blank one // The end command takes no arguments, so add a blank one
token.addAll([x, ""]); token = [x, ""];
} else { } else {
// token = [x, x.substring(1).trim()];
token = [x]; token = [x];
} }
@@ -242,7 +242,7 @@ Path parsePath(String pathdef) {
segments.add(Line(start: currentPos, end: pos)); segments.add(Line(start: currentPos, end: pos));
currentPos = pos; currentPos = pos;
} else if (command == "H") { } else if (command == "H") {
double hpos = token.args[0] as double; num hpos = token.args[0] as num;
if (!absolute) { if (!absolute) {
hpos += currentPos.x; hpos += currentPos.x;
} }
@@ -250,7 +250,7 @@ Path parsePath(String pathdef) {
segments.add(Line(start: currentPos, end: pos)); segments.add(Line(start: currentPos, end: pos));
currentPos = pos; currentPos = pos;
} else if (command == "V") { } else if (command == "V") {
double vpos = token.args[0] as double; num vpos = token.args[0] as num;
if (!absolute) { if (!absolute) {
vpos += currentPos.y; vpos += currentPos.y;
} }
@@ -304,10 +304,11 @@ Path parsePath(String pathdef) {
} }
segments.add( segments.add(
CubicBezier( CubicBezier(
start: currentPos, start: currentPos,
control1: control1, control1: control1,
control2: control2, control2: control2,
end: end), end: end,
),
); );
currentPos = end; currentPos = end;
} else if (command == "Q") { } else if (command == "Q") {
@@ -354,8 +355,8 @@ Path parsePath(String pathdef) {
} 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*
final radius = Point(token.args[0] as double, token.args[1] as double); final radius = Point(token.args[0] as num, token.args[1] as num);
final rotation = token.args[2] as double; final rotation = token.args[2] as num;
final arc = token.args[3] as bool; final arc = token.args[3] as bool;
final sweep = token.args[4] as bool; final sweep = token.args[4] as bool;
Point end = token.args[5] as Point; Point end = token.args[5] as Point;

View File

@@ -9,30 +9,34 @@ import 'package:bisection/extension.dart';
import '../common/Point.dart'; import '../common/Point.dart';
double radians(num n) => n * pi / 180; num radians(num n) => n * pi / 180;
double degrees(num n) => n * 180 / pi; num degrees(num n) => n * 180 / pi;
const defaultMinDepth = 5; const defaultMinDepth = 5;
const defaultError = 1e-12; const defaultError = 1e-12;
extension _RemovePointIfInt on num {
num get removePointIfInt => truncate() == this ? truncate() : this;
}
/// Recursively approximates the length by straight lines /// Recursively approximates the length by straight lines
double segmentLength({ num segmentLength({
required SvgPath curve, required SvgPath curve,
required num start, required num start,
required num end, required num end,
required Point startPoint, required Point startPoint,
required Point endPoint, required Point endPoint,
required double error, required num error,
required int minDepth, required int minDepth,
required double depth, required num depth,
}) { }) {
num mid = (start + end) / 2; num mid = (start + end) / 2;
Point midPoint = curve.point(mid); Point midPoint = curve.point(mid);
double length = (endPoint - startPoint).abs(); num length = (endPoint - startPoint).abs();
double firstHalf = (midPoint - startPoint).abs(); num firstHalf = (midPoint - startPoint).abs();
double secondHalf = (endPoint - midPoint).abs(); num secondHalf = (endPoint - midPoint).abs();
double length2 = firstHalf + secondHalf; num length2 = firstHalf + secondHalf;
if ((length2 - length > error) || (depth < minDepth)) { if ((length2 - length > error) || (depth < minDepth)) {
// Calculate the length of each segment: // Calculate the length of each segment:
depth += 1; depth += 1;
@@ -70,11 +74,18 @@ abstract class SvgPath {
required this.end, required this.end,
}); });
@override
bool operator ==(Object other) =>
other is SvgPath && start == other.start && end == other.end;
@override
int get hashCode => start.hashCode ^ end.hashCode;
/// Calculate the x,y position at a certain position of the path /// Calculate the x,y position at a certain position of the path
Point point(num pos); Point point(num pos);
/// Calculate the length of the path up to a certain position /// Calculate the length of the path up to a certain position
double size({double error = defaultError, int minDepth = defaultMinDepth}); num size({num error = defaultError, int minDepth = defaultMinDepth});
} }
abstract class Bezier extends SvgPath { abstract class Bezier extends SvgPath {
@@ -83,6 +94,12 @@ abstract class Bezier extends SvgPath {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
@override
bool operator ==(Object other) => other is Bezier && super == other;
@override
int get hashCode => super.hashCode + 0;
/// Checks if this segment would be a smooth segment following the previous /// Checks if this segment would be a smooth segment following the previous
bool isSmoothFrom(Object? previous); bool isSmoothFrom(Object? previous);
} }
@@ -95,11 +112,17 @@ class Linear extends SvgPath {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
@override
bool operator ==(Object other) => other is Linear && super == other;
@override
int get hashCode => super.hashCode + 0;
@override @override
Point point(num pos) => start + (end - start).times(pos); Point point(num pos) => start + (end - start).times(pos);
@override @override
double size({double error = defaultError, int minDepth = defaultMinDepth}) { num size({num error = defaultError, int minDepth = defaultMinDepth}) {
final distance = end - start; final distance = end - start;
return sqrt(distance.x * distance.x + distance.y * distance.y); return sqrt(distance.x * distance.x + distance.y * distance.y);
} }
@@ -111,6 +134,12 @@ class Line extends Linear {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
@override
bool operator ==(Object other) => other is Line && super == other;
@override
int get hashCode => super.hashCode + 0;
@override @override
String toString() { String toString() {
return "Line(start=$start, end=$end)"; return "Line(start=$start, end=$end)";
@@ -128,6 +157,16 @@ class CubicBezier extends Bezier {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
@override
bool operator ==(Object other) =>
other is CubicBezier &&
control1 == other.control1 &&
control2 == other.control2 &&
super == other;
@override
int get hashCode => super.hashCode ^ control1.hashCode ^ control2.hashCode;
@override @override
String toString() => "CubicBezier(start=$start, control1=$control1, " String toString() => "CubicBezier(start=$start, control1=$control1, "
"control2=$control2, end=$end)"; "control2=$control2, end=$end)";
@@ -146,18 +185,19 @@ class CubicBezier extends Bezier {
end.times(math.pow(pos, 3)); end.times(math.pow(pos, 3));
@override @override
double size({double error = defaultError, int minDepth = defaultMinDepth}) { num size({num error = defaultError, int minDepth = defaultMinDepth}) {
final startPoint = point(0); final startPoint = point(0);
final endPoint = point(1); final endPoint = point(1);
return segmentLength( return segmentLength(
curve: this, curve: this,
start: 0, start: 0,
end: 1, end: 1,
startPoint: startPoint, startPoint: startPoint,
endPoint: endPoint, endPoint: endPoint,
error: error, error: error,
minDepth: minDepth, minDepth: minDepth,
depth: 0); depth: 0,
);
} }
} }
@@ -168,10 +208,14 @@ class QuadraticBezier extends Bezier {
required Point start, required Point start,
required Point end, required Point end,
required this.control, required this.control,
}) : super( }) : super(start: start, end: end);
start: start,
end: end, @override
); bool operator ==(Object other) =>
other is QuadraticBezier && control == other.control && super == other;
@override
int get hashCode => super.hashCode ^ control.hashCode;
@override @override
String toString() => String toString() =>
@@ -190,12 +234,12 @@ class QuadraticBezier extends Bezier {
end.times(math.pow(pos, 2)); end.times(math.pow(pos, 2));
@override @override
double size({double error = defaultError, int minDepth = defaultMinDepth}) { num size({num error = defaultError, int minDepth = defaultMinDepth}) {
final Point a = start - control.times(2) + end; final Point a = start - control.times(2) + end;
final Point b = (control - start).times(2); final Point b = (control - start).times(2);
final num aDotB = a.x * b.x + a.y * b.y; final num aDotB = a.x * b.x + a.y * b.y;
late final double s; late final num s;
if (a.abs() < 1e-12) { if (a.abs() < 1e-12) {
s = b.abs(); s = b.abs();
} else if ((aDotB + a.abs() * b.abs()).abs() < 1e-12) { } else if ((aDotB + a.abs() * b.abs()).abs() < 1e-12) {
@@ -208,11 +252,11 @@ class QuadraticBezier extends Bezier {
final num B = 4 * (a.x * b.x + a.y * b.y); final num B = 4 * (a.x * b.x + a.y * b.y);
final num C = b.x * b.x + b.y * b.y; final num C = b.x * b.x + b.y * b.y;
final double sabc = 2 * sqrt(A + B + C); final num sabc = 2 * sqrt(A + B + C);
final double a2 = sqrt(A); final num a2 = sqrt(A);
final double a32 = 2 * A * a2; final num a32 = 2 * A * a2;
final double c2 = 2 * sqrt(C); final num c2 = 2 * sqrt(C);
final double bA = B / a2; final num bA = B / a2;
s = (a32 * sabc + s = (a32 * sabc +
a2 * B * (sabc - c2) + a2 * B * (sabc - c2) +
@@ -227,7 +271,7 @@ class QuadraticBezier extends Bezier {
/// large and sweep are 1 or 0 (True/False also work) /// large and sweep are 1 or 0 (True/False also work)
class Arc extends SvgPath { class Arc extends SvgPath {
final Point radius; final Point radius;
final double rotation; final num rotation;
final bool arc; final bool arc;
final bool sweep; final bool sweep;
late final num radiusScale; late final num radiusScale;
@@ -246,6 +290,23 @@ class Arc extends SvgPath {
_parameterize(); _parameterize();
} }
@override
bool operator ==(Object other) =>
other is Arc &&
radius == other.radius &&
rotation == other.rotation &&
arc == other.arc &&
sweep == other.sweep &&
super == other;
@override
int get hashCode =>
super.hashCode ^
radius.hashCode ^
rotation.hashCode ^
arc.hashCode ^
sweep.hashCode;
@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)';
@@ -289,7 +350,7 @@ class Arc extends SvgPath {
final t1 = rxSq * y1primSq; final t1 = rxSq * y1primSq;
final t2 = rySq * x1primSq; final t2 = rySq * x1primSq;
double c = sqrt(((rxSq * rySq - t1 - t2) / (t1 + t2)).abs()); num c = sqrt(((rxSq * rySq - t1 - t2) / (t1 + t2)).abs());
if (arc == sweep) { if (arc == sweep) {
c = -c; c = -c;
@@ -352,7 +413,7 @@ class Arc extends SvgPath {
/// integration, and in that case it's simpler to just do a geometric /// integration, and in that case it's simpler to just do a geometric
/// approximation, as for cubic bezier curves. /// approximation, as for cubic bezier curves.
@override @override
double size({double error = defaultError, minDepth = defaultMinDepth}) { num size({num error = defaultError, minDepth = defaultMinDepth}) {
// This is equivalent of omitting the segment // This is equivalent of omitting the segment
if (start == end) return 0; if (start == end) return 0;
@@ -387,6 +448,13 @@ class Arc extends SvgPath {
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
bool operator ==(Object other) => other is Move && super == other;
@override
int get hashCode => super.hashCode + 0;
@override @override
String toString() => "Move(to=$start)"; String toString() => "Move(to=$start)";
@@ -394,8 +462,7 @@ class Move extends SvgPath {
Point point(num pos) => start; Point point(num pos) => start;
@override @override
double size({double error = defaultError, int minDepth = defaultMinDepth}) => num size({num error = defaultError, int minDepth = defaultMinDepth}) => 0;
0;
} }
/// Represents the closepath command /// Represents the closepath command
@@ -405,10 +472,12 @@ class Close extends Linear {
required Point end, required Point end,
}) : super(start: start, end: end); }) : super(start: start, end: end);
// def __eq__(self, other):
// if not isinstance(other, Close): @override
// return NotImplemented bool operator ==(Object other) => other is Close && super == other;
// return self.start == other.start and self.end == other.end
@override
int get hashCode => super.hashCode + 0;
@override @override
String toString() => "Close(start=$start, end=$end)"; String toString() => "Close(start=$start, end=$end)";
@@ -425,6 +494,14 @@ class Path extends ListBase<SvgPath> {
segments = []; segments = [];
} }
Path.fromSegments(this.segments);
@override
bool operator ==(Object other) => other is Path && segments == other.segments;
@override
int get hashCode => segments.hashCode;
@override @override
SvgPath operator [](int index) => segments[index]!; SvgPath operator [](int index) => segments[index]!;
@@ -445,7 +522,7 @@ class Path extends ListBase<SvgPath> {
'Path(${[for (final s in segments) s.toString()].join(", ")})'; 'Path(${[for (final s in segments) s.toString()].join(", ")})';
void _calcLengths( void _calcLengths(
{double error = defaultError, int minDepth = defaultMinDepth}) { {num error = defaultError, int minDepth = defaultMinDepth}) {
if (_memoizedLength != null) return; if (_memoizedLength != null) return;
final lengths = [ final lengths = [
@@ -466,7 +543,7 @@ class Path extends ListBase<SvgPath> {
} }
} }
Point point({required num pos, double error = defaultError}) { Point point({required num pos, num error = defaultError}) {
// Shortcuts // Shortcuts
if (pos == 0.0) { if (pos == 0.0) {
return segments[0]!.point(pos); return segments[0]!.point(pos);
@@ -505,7 +582,7 @@ class Path extends ListBase<SvgPath> {
SvgPath? previousSegment; SvgPath? previousSegment;
final end = last.end; final end = last.end;
String formatNumber(num n) => n.toString(); String formatNumber(num n) => n.removePointIfInt.toString();
String coord(Point p) => '${formatNumber(p.x)},${formatNumber(p.y)}'; String coord(Point p) => '${formatNumber(p.x)},${formatNumber(p.y)}';
for (final segment in this) { for (final segment in this) {
@@ -518,7 +595,7 @@ class Path extends ListBase<SvgPath> {
} else if (segment is Move || } else if (segment is Move ||
(currentPos != start) || (currentPos != start) ||
(start == end && previousSegment is! Move)) { (start == end && previousSegment is! Move)) {
parts.add("M ${coord(start)}"); parts.add("M ${coord(segment.start)}");
} }
if (segment is Line) { if (segment is Line) {
@@ -540,7 +617,7 @@ class Path extends ListBase<SvgPath> {
} else if (segment is Arc) { } else if (segment is Arc) {
parts.add( parts.add(
"A ${coord(segment.radius)} ${formatNumber(segment.rotation)} " "A ${coord(segment.radius)} ${formatNumber(segment.rotation)} "
"${(segment.arc ? 1 : 0).toDouble},${(segment.sweep ? 1 : 0).toDouble} ${coord(end)}", "${segment.arc ? 1 : 0},${segment.sweep ? 1 : 0} ${coord(segment.end)}",
); );
} }
@@ -548,6 +625,6 @@ class Path extends ListBase<SvgPath> {
previousSegment = segment; previousSegment = segment;
} }
return parts.join(" "); return parts.join(" ").toUpperCase();
} }
} }

View File

@@ -1,12 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kanimaji/kanimaji.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

279
test/svg/parser_test.dart Normal file
View File

@@ -0,0 +1,279 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kanimaji/common/Point.dart';
import 'package:kanimaji/svg/parser.dart';
import 'package:kanimaji/svg/path.dart';
void main() {
/// """Examples from the SVG spec"""
test("svg_examples", () {
Path path1 = parsePath("M 100 100 L 300 100 L 200 300 z");
expect(
path1,
Path.fromSegments([
const Move(to: Point(100, 100)),
const Line(start: Point(100, 100), end: Point(300, 100)),
const Line(start: Point(300, 100), end: Point(200, 300)),
const Close(start: Point(200, 300), end: Point(100, 100)),
]),
);
// for Z command behavior when there is multiple subpaths
path1 = parsePath("M 0 0 L 50 20 M 100 100 L 300 100 L 200 300 z");
expect(
path1,
Path.fromSegments(const [
Move(to: Point.zero),
Line(start: Point.zero, end: Point(50, 20)),
Move(to: Point(100, 100)),
Line(start: Point(100, 100), end: Point(300, 100)),
Line(start: Point(300, 100), end: Point(200, 300)),
Close(start: Point(200, 300), end: Point(100, 100)),
]),
);
path1 = parsePath("M 100 100 L 200 200");
Path path2 = parsePath("M100 100L200 200");
expect(path1, path2);
path1 = parsePath("M 100 200 L 200 100 L -100 -200");
path2 = parsePath("M 100 200 L 200 100 -100 -200");
expect(path1, path2);
path1 = parsePath("""M100,200 C100,100 250,100 250,200
S400,300 400,200""");
expect(
path1,
Path.fromSegments(const [
Move(to: Point(100, 200)),
CubicBezier(
start: Point(100, 200),
control1: Point(100, 100),
control2: Point(250, 100),
end: Point(250, 200),
),
CubicBezier(
start: Point(250, 200),
control1: Point(250, 300),
control2: Point(400, 300),
end: Point(400, 200),
),
]),
);
path1 = parsePath("M100,200 C100,100 400,100 400,200");
expect(
path1,
Path.fromSegments(const [
Move(to: Point(100, 200)),
CubicBezier(
start: Point(100, 200),
control1: Point(100, 100),
control2: Point(400, 100),
end: Point(400, 200),
),
]),
);
path1 = parsePath("M100,500 C25,400 475,400 400,500");
expect(
path1,
Path.fromSegments(const [
Move(to: Point(100, 500)),
CubicBezier(
start: Point(100, 500),
control1: Point(25, 400),
control2: Point(475, 400),
end: Point(400, 500),
),
]),
);
// path1 = parse_path("M100,800 C175,700 325,700 400,800")
// self.assertEqual(
// path1,
// Path(
// Move(100 + 800j),
// CubicBezier(100 + 800j, 175 + 700j, 325 + 700j, 400 + 800j),
// ),
// )
// path1 = parse_path("M600,200 C675,100 975,100 900,200")
// self.assertEqual(
// path1,
// Path(
// Move(600 + 200j),
// CubicBezier(600 + 200j, 675 + 100j, 975 + 100j, 900 + 200j),
// ),
// )
// path1 = parse_path("M600,500 C600,350 900,650 900,500")
// self.assertEqual(
// path1,
// Path(
// Move(600 + 500j),
// CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j),
// ),
// )
// path1 = parse_path(
// """M600,800 C625,700 725,700 750,800
// S875,900 900,800"""
// )
// self.assertEqual(
// path1,
// Path(
// Move(600 + 800j),
// CubicBezier(600 + 800j, 625 + 700j, 725 + 700j, 750 + 800j),
// CubicBezier(750 + 800j, 775 + 900j, 875 + 900j, 900 + 800j),
// ),
// )
// path1 = parse_path("M200,300 Q400,50 600,300 T1000,300")
// self.assertEqual(
// path1,
// Path(
// Move(200 + 300j),
// QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j),
// QuadraticBezier(600 + 300j, 800 + 550j, 1000 + 300j),
// ),
// )
// path1 = parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z")
// self.assertEqual(
// path1,
// Path(
// Move(300 + 200j),
// Line(300 + 200j, 150 + 200j),
// Arc(150 + 200j, 150 + 150j, 0, 1, 0, 300 + 50j),
// Close(300 + 50j, 300 + 200j),
// ),
// )
// path1 = parse_path("M275,175 v-150 a150,150 0 0,0 -150,150 z")
// self.assertEqual(
// path1,
// Path(
// Move(275 + 175j),
// Line(275 + 175j, 275 + 25j),
// Arc(275 + 25j, 150 + 150j, 0, 0, 0, 125 + 175j),
// Close(125 + 175j, 275 + 175j),
// ),
// )
// path1 = parse_path("M275,175 v-150 a150,150 0 0,0 -150,150 L 275,175 z")
// self.assertEqual(
// path1,
// Path(
// Move(275 + 175j),
// Line(275 + 175j, 275 + 25j),
// Arc(275 + 25j, 150 + 150j, 0, 0, 0, 125 + 175j),
// Line(125 + 175j, 275 + 175j),
// Close(275 + 175j, 275 + 175j),
// ),
// )
// path1 = parse_path(
// """M600,350 l 50,-25
// a25,25 -30 0,1 50,-25 l 50,-25
// a25,50 -30 0,1 50,-25 l 50,-25
// a25,75 -30 0,1 50,-25 l 50,-25
// a25,100 -30 0,1 50,-25 l 50,-25"""
// )
// self.assertEqual(
// path1,
// Path(
// Move(600 + 350j),
// Line(600 + 350j, 650 + 325j),
// Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j),
// Line(700 + 300j, 750 + 275j),
// Arc(750 + 275j, 25 + 50j, -30, 0, 1, 800 + 250j),
// Line(800 + 250j, 850 + 225j),
// Arc(850 + 225j, 25 + 75j, -30, 0, 1, 900 + 200j),
// Line(900 + 200j, 950 + 175j),
// Arc(950 + 175j, 25 + 100j, -30, 0, 1, 1000 + 150j),
// Line(1000 + 150j, 1050 + 125j),
// ),
// );
});
// def test_others(self):
// # Other paths that need testing:
// # Relative moveto:
// path1 = parse_path("M 0 0 L 50 20 m 50 80 L 300 100 L 200 300 z")
// self.assertEqual(
// path1,
// Path(
// Move(0j),
// Line(0 + 0j, 50 + 20j),
// Move(100 + 100j),
// Line(100 + 100j, 300 + 100j),
// Line(300 + 100j, 200 + 300j),
// Close(200 + 300j, 100 + 100j),
// ),
// )
// # Initial smooth and relative CubicBezier
// path1 = parse_path("""M100,200 s 150,-100 150,0""")
// self.assertEqual(
// path1,
// Path(
// Move(100 + 200j),
// CubicBezier(100 + 200j, 100 + 200j, 250 + 100j, 250 + 200j),
// ),
// )
// # Initial smooth and relative QuadraticBezier
// path1 = parse_path("""M100,200 t 150,0""")
// self.assertEqual(
// path1,
// Path(Move(100 + 200j), QuadraticBezier(100 + 200j, 100 + 200j, 250 + 200j)),
// )
// # Relative QuadraticBezier
// path1 = parse_path("""M100,200 q 0,0 150,0""")
// self.assertEqual(
// path1,
// Path(Move(100 + 200j), QuadraticBezier(100 + 200j, 100 + 200j, 250 + 200j)),
// )
// def test_negative(self):
// """You don't need spaces before a minus-sign"""
// path1 = parse_path("M100,200c10-5,20-10,30-20")
// path2 = parse_path("M 100 200 c 10 -5 20 -10 30 -20")
// self.assertEqual(path1, path2)
// def test_numbers(self):
// """Exponents and other number format cases"""
// # It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported.
// path1 = parse_path("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38")
// path2 = Path(
// Move(-3.4e38 + 3.4e38j), Line(-3.4e38 + 3.4e38j, -3.4e-38 + 3.4e-38j)
// )
// self.assertEqual(path1, path2)
// def test_errors(self):
// self.assertRaises(ValueError, parse_path, "M 100 100 L 200 200 Z 100 200")
// def test_non_path(self):
// # It's possible in SVG to create paths that has zero length,
// # we need to handle that.
// path = parse_path("M10.236,100.184")
// self.assertEqual(path.d(), "M 10.236,100.184")
// def test_issue_45(self):
// path = parse_path(
// "m 1672.2372,-54.8161 "
// "a 14.5445,14.5445 0 0 0 -11.3152,23.6652 "
// "l 27.2573,27.2572 27.2572,-27.2572 "
// "a 14.5445,14.5445 0 0 0 -11.3012,-23.634 "
// "a 14.5445,14.5445 0 0 0 -11.414,5.4625 "
// "l -4.542,4.5420 "
// "l -4.5437,-4.5420 "
// "a 14.5445,14.5445 0 0 0 -11.3984,-5.4937 "
// "z"
// )
// self.assertIn("A 14.5445,14.5445 0 0,0 1672.24,-54.8161 Z", path.d())
}

28
test/svg/svg_test.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kanimaji/svg/parser.dart';
void main() {
test('Test SVG Paths', () {
final paths = [
"M 100,100 L 300,100 L 200,300 Z",
"M 0,0 L 50,20 M 100,100 L 300,100 L 200,300 Z",
"M 100,100 L 200,200",
"M 100,200 L 200,100 L -100,-200",
"M 100,200 C 100,100 250,100 250,200 S 400,300 400,200",
"M 100,200 C 100,100 400,100 400,200",
"M 100,500 C 25,400 475,400 400,500",
"M 100,800 C 175,700 325,700 400,800",
"M 600,200 C 675,100 975,100 900,200",
"M 600,500 C 600,350 900,650 900,500",
"M 600,800 C 625,700 725,700 750,800 S 875,900 900,800",
"M 200,300 Q 400,50 600,300 T 1000,300",
"M -3.4E+38,3.4E+38 L -3.4E-38,3.4E-38",
"M 0,0 L 50,20 M 50,20 L 200,100 Z",
"M 600,350 L 650,325 A 25,25 -30 0,1 700,300 L 750,275",
];
for (final path in paths) {
expect(parsePath(path).d(), path);
}
});
}