treewide: dart format + analyze + some other fixes

This commit is contained in:
2025-08-04 21:11:37 +02:00
parent 9f9757d710
commit c83821fa38
10 changed files with 863 additions and 844 deletions

View File

@@ -8,10 +8,11 @@ class Point {
const Point.from({this.x = 0, this.y = 0}); const Point.from({this.x = 0, this.y = 0});
static const zero = Point(0, 0); static const zero = Point(0, 0);
operator +(covariant Point p) => Point(x + p.x, y + p.y); Point operator +(covariant Point p) => Point(x + p.x, y + p.y);
operator -(covariant Point p) => Point(x - p.x, y - p.y); Point operator -(covariant Point p) => Point(x - p.x, y - p.y);
operator *(covariant Point p) => Point(x * p.x, y * p.y); Point operator *(covariant Point p) => Point(x * p.x, y * p.y);
operator /(covariant Point p) => Point(x / p.x, y / p.y); Point operator /(covariant Point p) => Point(x / p.x, y / p.y);
Point operator -() => Point(-x, -y);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>

View File

@@ -1,4 +1,4 @@
library kanimaji; library;
/// A Calculator. /// A Calculator.
class Calculator { class Calculator {

View File

@@ -6,21 +6,19 @@ import '../common/point.dart';
import 'bezier_cubic.dart' as bezier_cubic; import 'bezier_cubic.dart' as bezier_cubic;
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
import 'package:path/path.dart';
double _computePathLength(String path) => double _computePathLength(String path) =>
parsePath(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"(?=['\\\\])"), "\\\\")}'";
extension _Dedent on String { extension _Dedent on String {
String get dedented { String get dedented {
final withoutEmptyLines = final withoutEmptyLines = split('\n').where((l) => l.isNotEmpty).toList();
this.split('\n').where((l) => l.isNotEmpty).toList();
final whitespaceAmounts = [ final whitespaceAmounts = [
for (final line in withoutEmptyLines) for (final line in withoutEmptyLines)
line.split('').takeWhile((c) => c == ' ').length line.split('').takeWhile((c) => c == ' ').length,
]; ];
final whitespaceToRemove = whitespaceAmounts.reduce(min); final whitespaceToRemove = whitespaceAmounts.reduce(min);
return withoutEmptyLines return withoutEmptyLines
@@ -63,40 +61,34 @@ const pt2 = Point(1, 1);
// class { // class {
// } // }
enum TimingFunction { enum TimingFunction { linear, ease, easeIn, easeInOut, easeOut }
linear,
ease,
easeIn,
easeInOut,
easeOut,
}
extension Funcs on TimingFunction { extension Funcs on TimingFunction {
double Function(double) get func => { double Function(double) get func => {
TimingFunction.linear: (double x) => x, TimingFunction.linear: (double x) => x,
TimingFunction.ease: (double x) => TimingFunction.ease: (double x) =>
bezier_cubic.value(pt1, easeCt1, easeCt2, pt2, x), bezier_cubic.value(pt1, easeCt1, easeCt2, pt2, x),
TimingFunction.easeIn: (double x) => TimingFunction.easeIn: (double x) =>
bezier_cubic.value(pt1, easeInCt1, easeInCt2, pt2, x), bezier_cubic.value(pt1, easeInCt1, easeInCt2, pt2, x),
TimingFunction.easeInOut: (double x) => TimingFunction.easeInOut: (double x) =>
bezier_cubic.value(pt1, easeInOutCt1, easeInOutCt2, pt2, x), bezier_cubic.value(pt1, easeInOutCt1, easeInOutCt2, pt2, x),
TimingFunction.easeOut: (double x) => TimingFunction.easeOut: (double x) =>
bezier_cubic.value(pt1, easeOutCt1, easeOutCt2, pt2, x), bezier_cubic.value(pt1, easeOutCt1, easeOutCt2, pt2, x),
}[this]!; }[this]!;
String get name => { String get name => {
TimingFunction.linear: 'linear', TimingFunction.linear: 'linear',
TimingFunction.ease: 'ease', TimingFunction.ease: 'ease',
TimingFunction.easeIn: 'ease-in', TimingFunction.easeIn: 'ease-in',
TimingFunction.easeInOut: 'ease-in-out', TimingFunction.easeInOut: 'ease-in-out',
TimingFunction.easeOut: 'ease-out', TimingFunction.easeOut: 'ease-out',
}[this]!; }[this]!;
} }
// we will need this to deal with svg // we will need this to deal with svg
const namespaces = { const namespaces = {
'n': 'http://www.w3.org/2000/svg', 'n': 'http://www.w3.org/2000/svg',
'xlink': 'http://www.w3.org/1999/xlink' 'xlink': 'http://www.w3.org/1999/xlink',
}; };
// etree.register_namespace("xlink","http://www.w3.org/1999/xlink") // etree.register_namespace("xlink","http://www.w3.org/1999/xlink")
// final parser = etree.XMLParser(remove_blank_text=true); // final parser = etree.XMLParser(remove_blank_text=true);
@@ -124,26 +116,36 @@ double timeRescale(interval) => pow(2 * interval, 2.0 / 3).toDouble();
/// clear all extra elements this program may have previously added /// clear all extra elements this program may have previously added
void clearPreviousElements(XmlDocument doc) { void clearPreviousElements(XmlDocument doc) {
for (final XmlNode el in doc for (final XmlNode el
.getElement('svg', namespace: namespaces['n']) in doc
?.getElement('style', namespace: namespaces['n']) .getElement('svg', namespace: namespaces['n'])
?.children ?? ?.getElement('style', namespace: namespaces['n'])
[]) { ?.children ??
[]) {
if (RegExp(r'-Kanimaji$').hasMatch(el.getAttribute('id') ?? '')) { if (RegExp(r'-Kanimaji$').hasMatch(el.getAttribute('id') ?? '')) {
el.parent!.children.remove(el); el.parent!.children.remove(el);
} }
} }
for (final XmlNode g in doc for (final XmlNode g
.getElement('svg', namespace: namespaces['n']) in doc
?.getElement('g', namespace: namespaces['n']) .getElement('svg', namespace: namespaces['n'])
?.children ?? ?.getElement('g', namespace: namespaces['n'])
[]) { ?.children ??
[]) {
if (RegExp(r'-Kanimaji$').hasMatch(g.getAttribute('id') ?? '')) { if (RegExp(r'-Kanimaji$').hasMatch(g.getAttribute('id') ?? '')) {
g.parent!.children.remove(g); g.parent!.children.remove(g);
} }
} }
} }
String basename(String path) {
final int lastSlash = path.lastIndexOf(Platform.pathSeparator);
if (lastSlash == -1) {
return path;
}
return path.substring(lastSlash + 1);
}
/// Note: setting any color to transparent will result in a much bigger /// Note: setting any color to transparent will result in a much bigger
/// filesize for GIFs. /// filesize for GIFs.
void createAnimation({ void createAnimation({
@@ -190,7 +192,8 @@ void createAnimation({
'g', 'g',
attributes: { attributes: {
'id': 'kvg:$baseid-$id-Kanimaji', 'id': 'kvg:$baseid-$id-Kanimaji',
'style': 'fill:none;' 'style':
'fill:none;'
'stroke:$color;' 'stroke:$color;'
'stroke-width:$width;' 'stroke-width:$width;'
'stroke-linecap:round;' 'stroke-linecap:round;'
@@ -232,11 +235,12 @@ void createAnimation({
double tottime = 0; double tottime = 0;
// for (final g in doc.xpath("/n:svg/n:g", namespaces=namespaces) { // for (final g in doc.xpath("/n:svg/n:g", namespaces=namespaces) {
for (final XmlNode g in doc for (final XmlNode g
.getElement('svg', namespace: namespaces['n']) in doc
?.getElement('g', namespace: namespaces['n']) .getElement('svg', namespace: namespaces['n'])
?.children ?? ?.getElement('g', namespace: namespaces['n'])
[]) { ?.children ??
[]) {
if (RegExp(r'^kvg:StrokeNumbers_').hasMatch(g.getAttribute('id') ?? '')) { if (RegExp(r'^kvg:StrokeNumbers_').hasMatch(g.getAttribute('id') ?? '')) {
continue; continue;
} }
@@ -266,9 +270,7 @@ void createAnimation({
'\n/* CSS automatically generated by kanimaji.py, do not edit! */\n'; '\n/* CSS automatically generated by kanimaji.py, do not edit! */\n';
if (GENERATE_SVG) animatedCss = cssHeader; if (GENERATE_SVG) animatedCss = cssHeader;
if (GENERATE_JS_SVG) { if (GENERATE_JS_SVG) {
jsAnimatedCss += cssHeader + jsAnimatedCss += '''$cssHeader .backward {\n
'''
.backward {\n
animation-direction: reverse !important;\n animation-direction: reverse !important;\n
} }
'''; ''';
@@ -287,18 +289,20 @@ void createAnimation({
double elapsedtime = 0; double elapsedtime = 0;
// add css elements for all strokes // add css elements for all strokes
for (final XmlNode g in doc for (final XmlNode g
.getElement('svg', namespace: namespaces['n'])! in doc
.findElements('g', namespace: namespaces['n'])) { .getElement('svg', namespace: namespaces['n'])!
.findElements('g', namespace: namespaces['n'])) {
// for (final g in doc.xpath("/n:svg/n:g", namespaces=namespaces)){ // for (final g in doc.xpath("/n:svg/n:g", namespaces=namespaces)){
final groupid = g.getAttribute('id') ?? ''; final groupid = g.getAttribute('id') ?? '';
if (RegExp(r'^kvg:StrokeNumbers_').hasMatch(groupid)) { if (RegExp(r'^kvg:StrokeNumbers_').hasMatch(groupid)) {
final String rule = ''' final String rule =
'''
#${groupid.replaceAll(':', '\\3a ')} { #${groupid.replaceAll(':', '\\3a ')} {
display: none; display: none;
} }
''' '''
.dedented; .dedented;
if (GENERATE_SVG) animatedCss += rule; if (GENERATE_SVG) animatedCss += rule;
if (GENERATE_JS_SVG) jsAnimatedCss += rule; if (GENERATE_JS_SVG) jsAnimatedCss += rule;
if (GENERATE_GIF) { if (GENERATE_GIF) {
@@ -310,13 +314,14 @@ void createAnimation({
} }
final gidcss = groupid.replaceAll(':', '\\3a '); final gidcss = groupid.replaceAll(':', '\\3a ');
final rule = ''' final rule =
'''
#$gidcss { #$gidcss {
stroke-width: ${strokeBorderWidth.toStringAsFixed(1)}px !important; stroke-width: ${strokeBorderWidth.toStringAsFixed(1)}px !important;
stroke: $strokeBorderColor !important; stroke: $strokeBorderColor !important;
} }
''' '''
.dedented; .dedented;
if (GENERATE_SVG) animatedCss += rule; if (GENERATE_SVG) animatedCss += rule;
if (GENERATE_JS_SVG) jsAnimatedCss += rule; if (GENERATE_JS_SVG) jsAnimatedCss += rule;
@@ -380,7 +385,8 @@ void createAnimation({
if (GENERATE_SVG) { if (GENERATE_SVG) {
// animation stroke progression // animation stroke progression
animatedCss += ''' animatedCss +=
'''
@keyframes strike-$pathname { @keyframes strike-$pathname {
0% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; } 0% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; }
${animStart.toStringAsFixed(3)}% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; } ${animStart.toStringAsFixed(3)}% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; }
@@ -398,11 +404,12 @@ void createAnimation({
showhide-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite; showhide-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
} }
''' '''
.dedented; .dedented;
if (showBrush) { if (showBrush) {
// brush element visibility // brush element visibility
animatedCss += ''' animatedCss +=
'''
@keyframes showhide-brush-$pathname { @keyframes showhide-brush-$pathname {
${animStart.toStringAsFixed(3)}% { visibility: hidden; } ${animStart.toStringAsFixed(3)}% { visibility: hidden; }
${animEnd.toStringAsFixed(3)}% { visibility: visible; } ${animEnd.toStringAsFixed(3)}% { visibility: visible; }
@@ -414,7 +421,7 @@ void createAnimation({
showhide-brush-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite; showhide-brush-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
} }
''' '''
.dedented; .dedented;
} }
} }
@@ -423,17 +430,19 @@ void createAnimation({
// brush and background hidden by default // brush and background hidden by default
if (showBrush) { if (showBrush) {
jsAnimatedCss += ''' jsAnimatedCss +=
'''
#$brushPathidcss, #$brushBorderPathidcss, #$bgPathidcss { #$brushPathidcss, #$brushBorderPathidcss, #$bgPathidcss {
visibility: hidden; visibility: hidden;
} }
''' '''
.dedented; .dedented;
} }
// hide stroke after current element // hide stroke after current element
const afterCurrent = '[class *= "current"]'; const afterCurrent = '[class *= "current"]';
jsAnimatedCss += ''' jsAnimatedCss +=
'''
$afterCurrent ~ #$animPathidcss { $afterCurrent ~ #$animPathidcss {
visibility: hidden; visibility: hidden;
} }
@@ -451,9 +460,10 @@ void createAnimation({
animation: strike-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1; animation: strike-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1;
} }
''' '''
.dedented; .dedented;
if (showBrush) { if (showBrush) {
jsAnimatedCss += ''' jsAnimatedCss +=
'''
@keyframes strike-brush-$pathname { @keyframes strike-brush-$pathname {
0% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; } 0% { stroke-dashoffset: ${pathlen.toStringAsFixed(3)}; }
100% { stroke-dashoffset: 0.4; } 100% { stroke-dashoffset: 0.4; }
@@ -464,7 +474,7 @@ void createAnimation({
animation: strike-brush-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1; animation: strike-brush-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1;
} }
''' '''
.dedented; .dedented;
} }
} }
@@ -473,7 +483,7 @@ void createAnimation({
final time = k * GIF_FRAME_DURATION; final time = k * GIF_FRAME_DURATION;
final reltime = time * tottime / animationTime; // unscaled time final reltime = time * tottime / animationTime; // unscaled time
staticCss[k] = staticCss[k]! + '\n/* stroke $pathid */\n'; staticCss[k] = '${staticCss[k]!}\n/* stroke $pathid */\n';
String rule = ''; String rule = '';
@@ -486,7 +496,8 @@ void createAnimation({
rule += ", #$brushPathidcss, #$brushBorderPathidcss"; rule += ", #$brushPathidcss, #$brushBorderPathidcss";
} }
staticCss[k] = staticCss[k]! + staticCss[k] =
staticCss[k]! +
''' '''
%$rule { %$rule {
visibility: hidden; visibility: hidden;
@@ -501,7 +512,8 @@ void createAnimation({
rule += ", #$brushPathidcss, #$brushBorderPathidcss"; rule += ", #$brushPathidcss, #$brushBorderPathidcss";
} }
staticCss[k] = staticCss[k]! + staticCss[k] =
staticCss[k]! +
''' '''
$rule { $rule {
visibility: hidden; visibility: hidden;
@@ -513,7 +525,8 @@ void createAnimation({
((reltime - elapsedtime) / (newelapsedtime - elapsedtime)); ((reltime - elapsedtime) / (newelapsedtime - elapsedtime));
final progression = timingFunction.func(intervalprop); final progression = timingFunction.func(intervalprop);
staticCss[k] = staticCss[k]! + staticCss[k] =
staticCss[k]! +
''' '''
#$animPathidcss { #$animPathidcss {
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${(pathlen + 0.002).toStringAsFixed(3)}; stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${(pathlen + 0.002).toStringAsFixed(3)};
@@ -523,7 +536,8 @@ void createAnimation({
''' '''
.dedented; .dedented;
if (showBrush) { if (showBrush) {
staticCss[k] = staticCss[k]! + staticCss[k] =
staticCss[k]! +
''' '''
#$brushPathidcss, #$brushBorderPathidcss { #$brushPathidcss, #$brushBorderPathidcss {
stroke-dasharray: 0.001 ${(pathlen + 0.002).toStringAsFixed(3)}; stroke-dasharray: 0.001 ${(pathlen + 0.002).toStringAsFixed(3)};
@@ -554,13 +568,13 @@ void createAnimation({
if (GENERATE_SVG) { if (GENERATE_SVG) {
print(animatedCss); print(animatedCss);
final builder = XmlBuilder(); final builder = XmlBuilder();
final style = (builder final style =
..element( (builder..element(
'style', 'style',
attributes: {'id': "style-Kanimaji", 'type': 'text/css'}, attributes: {'id': "style-Kanimaji", 'type': 'text/css'},
nest: animatedCss, nest: animatedCss,
)) ))
.buildFragment(); .buildFragment();
doc.root.firstElementChild!.children.insert(0, style); doc.root.firstElementChild!.children.insert(0, style);
File(outputFile).writeAsStringSync(doc.toXmlString(pretty: true)); File(outputFile).writeAsStringSync(doc.toXmlString(pretty: true));
doc.root.children.removeAt(0); doc.root.children.removeAt(0);
@@ -684,16 +698,15 @@ void main(List<String> args) {
final fileList = []; final fileList = [];
for (int k = 0; k < kanji.length; k++) { for (int k = 0; k < kanji.length; k++) {
createAnimation( createAnimation(
inputFile: 'assets/kanjivg/kanji/${kanji.codeUnits[k].toRadixString(16).padLeft(5, '0')}.svg', inputFile:
outputFile: '${k+1}.svg', 'assets/kanjivg/kanji/${kanji.codeUnits[k].toRadixString(16).padLeft(5, '0')}.svg',
outputFile: '${k + 1}.svg',
); );
fileList.add('${k+1}.svg'); fileList.add('${k + 1}.svg');
} }
File('index.html').writeAsStringSync( File('index.html').writeAsStringSync(
'<html>' + '<html>${fileList.map((e) => File(e).readAsStringSync().replaceAll(']>', '')).join('\n')}</html>',
fileList.map((e) => File(e).readAsStringSync().replaceAll(']>', '')).join('\n') +
'</html>'
); );
// createAnimation( // createAnimation(
// inputFile: 'assets/kanjivg/kanji/060c5.svg', // inputFile: 'assets/kanjivg/kanji/060c5.svg',

View File

@@ -1,7 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import '../common/point.dart'; import '../common/point.dart';
// class Point { // class Point {
@@ -31,7 +29,8 @@ double time(Point pt1, Point ct1, Point ct2, Point pt2, double x) {
final num c = 3 * ct2.x - 3 * pt2.x; final num c = 3 * ct2.x - 3 * pt2.x;
final num d = pt2.x - x; final num d = pt2.x - x;
if (a.abs() < 0.000000001) { // quadratic if (a.abs() < 0.000000001) {
// quadratic
if (b.abs() < 0.000000001) return -d / c; // linear if (b.abs() < 0.000000001) return -d / c; // linear
final qb = c / b; final qb = c / b;
@@ -45,7 +44,8 @@ double time(Point pt1, Point ct1, Point ct2, Point pt2, double x) {
final addcoef = -b / (3 * a); final addcoef = -b / (3 * a);
final lmbd = sq(q) / 4 + cb(p) / 27; final lmbd = sq(q) / 4 + cb(p) / 27;
if (lmbd >= 0) { // real if (lmbd >= 0) {
// real
final sqlambda = sqrt(lmbd); final sqlambda = sqrt(lmbd);
final tmp = thrt(-q / 2 + (q < 0 ? sqlambda : -sqlambda)); final tmp = thrt(-q / 2 + (q < 0 ? sqlambda : -sqlambda));
return tmp - p / (3 * tmp) + addcoef; return tmp - p / (3 * tmp) + addcoef;

View File

@@ -1,6 +1,7 @@
/// SVG Path specification parser /// SVG Path specification parser
/// ///
/// See https://pypi.org/project/svg.path/ for the original implementation. /// See https://pypi.org/project/svg.path/ for the original implementation.
library;
import '../common/point.dart'; import '../common/point.dart';
import 'path.dart'; import 'path.dart';
@@ -25,7 +26,7 @@ const _commands = {
'T', 'T',
't', 't',
'A', 'A',
'a' 'a',
}; };
// const _uppercaseCommands = {'M', 'Z', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A'}; // const _uppercaseCommands = {'M', 'Z', 'L', 'H', 'V', 'C', 'S', 'Q', 'T', 'A'};
@@ -181,8 +182,9 @@ class Token {
other is Token && other is Token &&
command == other.command && command == other.command &&
args.length == other.args.length && args.length == other.args.length &&
![for (int i = 0; i < args.length; i++) args[i] == other.args[i]] ![
.any((b) => !b); for (int i = 0; i < args.length; i++) args[i] == other.args[i],
].any((b) => !b);
@override @override
int get hashCode => command.hashCode ^ args.hashCode; int get hashCode => command.hashCode ^ args.hashCode;
@@ -358,7 +360,8 @@ Path parsePath(String pathdef) {
// 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 = currentPos + control =
currentPos +
currentPos - currentPos -
(segments.last as QuadraticBezier).control; (segments.last as QuadraticBezier).control;
} else { } else {

View File

@@ -1,5 +1,6 @@
/// This file contains classes for the different types of SVG path segments as /// 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. /// well as a Path object that contains a sequence of path segments.
library;
import 'dart:collection'; import 'dart:collection';
import 'dart:math' as math; import 'dart:math' as math;
@@ -69,10 +70,7 @@ abstract class SvgPath {
final Point start; final Point start;
final Point end; final Point end;
const SvgPath({ const SvgPath({required this.start, required this.end});
required this.start,
required this.end,
});
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -89,10 +87,7 @@ abstract class SvgPath {
} }
abstract class Bezier extends SvgPath { abstract class Bezier extends SvgPath {
const Bezier({ const Bezier({required super.start, required super.end});
required Point start,
required Point end,
}) : super(start: start, end: end);
@override @override
bool operator ==(Object other) => other is Bezier && super == other; bool operator ==(Object other) => other is Bezier && super == other;
@@ -107,10 +102,7 @@ abstract class Bezier extends SvgPath {
/// A straight line /// A straight line
/// The base for Line() and Close(). /// The base for Line() and Close().
class Linear extends SvgPath { class Linear extends SvgPath {
const Linear({ const Linear({required super.start, required super.end});
required Point start,
required Point end,
}) : super(start: start, end: end);
@override @override
bool operator ==(Object other) => other is Linear && super == other; bool operator ==(Object other) => other is Linear && super == other;
@@ -129,10 +121,7 @@ class Linear extends SvgPath {
} }
class Line extends Linear { class Line extends Linear {
const Line({ const Line({required super.start, required super.end});
required Point start,
required Point end,
}) : super(start: start, end: end);
@override @override
bool operator ==(Object other) => other is Line && super == other; bool operator ==(Object other) => other is Line && super == other;
@@ -151,11 +140,11 @@ class CubicBezier extends Bezier {
final Point control2; final Point control2;
const CubicBezier({ const CubicBezier({
required Point start, required super.start,
required this.control1, required this.control1,
required this.control2, required this.control2,
required Point end, required super.end,
}) : super(start: start, end: end); });
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -168,13 +157,14 @@ class CubicBezier extends Bezier {
int get hashCode => super.hashCode ^ control1.hashCode ^ control2.hashCode; 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)";
@override @override
bool isSmoothFrom(Object? previous) => previous is CubicBezier bool isSmoothFrom(Object? previous) => previous is CubicBezier
? start == previous.end && ? start == previous.end &&
control1 - start == previous.end - previous.control2 control1 - start == previous.end - previous.control2
: control1 == start; : control1 == start;
@override @override
@@ -205,10 +195,10 @@ class QuadraticBezier extends Bezier {
final Point control; final Point control;
const QuadraticBezier({ const QuadraticBezier({
required Point start, required super.start,
required Point end, required super.end,
required this.control, required this.control,
}) : super(start: start, end: end); });
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -224,7 +214,7 @@ class QuadraticBezier extends Bezier {
@override @override
bool isSmoothFrom(Object? previous) => previous is QuadraticBezier bool isSmoothFrom(Object? previous) => previous is QuadraticBezier
? start == previous.end && ? start == previous.end &&
(control - start) == (previous.end - previous.control) (control - start) == (previous.end - previous.control)
: control == start; : control == start;
@override @override
@@ -258,7 +248,8 @@ class QuadraticBezier extends Bezier {
final num c2 = 2 * sqrt(C); final num c2 = 2 * sqrt(C);
final num bA = B / a2; final num bA = B / a2;
s = (a32 * sabc + s =
(a32 * sabc +
a2 * B * (sabc - c2) + a2 * B * (sabc - c2) +
(4 * C * A - (B * B)) * log((2 * a2 + bA + sabc) / (bA + c2))) / (4 * C * A - (B * B)) * log((2 * a2 + bA + sabc) / (bA + c2))) /
(4 * a32); (4 * a32);
@@ -280,13 +271,13 @@ class Arc extends SvgPath {
// late num delta; // late num delta;
const Arc({ const Arc({
required Point start, required super.start,
required Point end, required super.end,
required this.radius, required this.radius,
required this.rotation, required this.rotation,
required this.arc, required this.arc,
required this.sweep, required this.sweep,
}) : super(start: start, end: end); });
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -306,10 +297,10 @@ class Arc extends SvgPath {
sweep.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)';
// Conversion from endpoint to center parameterization // Conversion from endpoint to center parameterization
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
num get _cosr => cos(radians(rotation)); num get _cosr => cos(radians(rotation));
@@ -337,15 +328,16 @@ class Arc extends SvgPath {
num get _cyprim => -c * _ry * _x1prim / _rx; num get _cyprim => -c * _ry * _x1prim / _rx;
num get radiusScale { num get radiusScale {
final rs = (_x1primSq / (radius.x * radius.x)) + final rs =
(_x1primSq / (radius.x * radius.x)) +
(_y1primSq / (radius.y * radius.y)); (_y1primSq / (radius.y * radius.y));
return rs > 1 ? sqrt(rs) : 1; return rs > 1 ? sqrt(rs) : 1;
} }
Point get center => Point( Point get center => Point(
(_cosr * _cxprim - _sinr * _cyprim) + ((start.x + end.x) / 2), (_cosr * _cxprim - _sinr * _cyprim) + ((start.x + end.x) / 2),
(_sinr * _cxprim + _cosr * _cyprim) + ((start.y + end.y) / 2), (_sinr * _cxprim + _cosr * _cyprim) + ((start.y + end.y) / 2),
); );
num get theta { num get theta {
final num n = sqrt(_ux * _ux + _uy * _uy); final num n = sqrt(_ux * _ux + _uy * _uy);
@@ -365,7 +357,8 @@ class Arc extends SvgPath {
d = -1.0; d = -1.0;
} }
return ((((_ux * _vy - _uy * _vx) < 0) ? -1 : 1) * degrees(acos(d))) % 360 - (!sweep ? 360 : 0); return ((((_ux * _vy - _uy * _vx) < 0) ? -1 : 1) * degrees(acos(d))) % 360 -
(!sweep ? 360 : 0);
} }
@override @override
@@ -375,7 +368,7 @@ class Arc extends SvgPath {
// This should be treated as a straight line // This should be treated as a straight line
if (this.radius.x == 0 || this.radius.y == 0) { if (this.radius.x == 0 || this.radius.y == 0) {
return start + (end - start) * pos; return start + (end - start).times(pos);
} }
final angle = radians(theta + pos * delta); final angle = radians(theta + pos * delta);
@@ -415,14 +408,15 @@ class Arc extends SvgPath {
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,
);
} }
} }
@@ -449,10 +443,7 @@ class Move extends SvgPath {
/// Represents the closepath command /// Represents the closepath command
class Close extends Linear { class Close extends Linear {
const Close({ const Close({required super.start, required super.end});
required Point start,
required Point end,
}) : super(start: start, end: end);
@override @override
bool operator ==(Object other) => other is Close && super == other; bool operator ==(Object other) => other is Close && super == other;
@@ -502,12 +493,14 @@ class Path extends ListBase<SvgPath> {
String toString() => String toString() =>
'Path(${[for (final s in segments) s.toString()].join(", ")})'; 'Path(${[for (final s in segments) s.toString()].join(", ")})';
void _calcLengths( void _calcLengths({
{num error = defaultError, int minDepth = defaultMinDepth}) { num error = defaultError,
int minDepth = defaultMinDepth,
}) {
if (_memoizedLength != null) return; if (_memoizedLength != null) return;
final lengths = [ final lengths = [
for (final s in segments) s!.size(error: error, minDepth: minDepth) for (final s in segments) s!.size(error: error, minDepth: minDepth),
]; ];
_memoizedLength = lengths.reduce((a, b) => a + b); _memoizedLength = lengths.reduce((a, b) => a + b);
if (_memoizedLength == 0) { if (_memoizedLength == 0) {
@@ -552,7 +545,7 @@ class Path extends ListBase<SvgPath> {
return segments[i]!.point(segmentPos); return segments[i]!.point(segmentPos);
} }
num size({error = defaultError, minDepth = defaultMinDepth}) { num size({double error = defaultError, int minDepth = defaultMinDepth}) {
_calcLengths(error: error, minDepth: minDepth); _calcLengths(error: error, minDepth: minDepth);
return _memoizedLength!; return _memoizedLength!;
} }

View File

@@ -1,15 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
extension _Hexcode on Color {
String get hexcode => '#${value.toRadixString(16).padLeft(8, '0')}';
}
class Kanimaji extends StatelessWidget { class Kanimaji extends StatelessWidget {
final String kanji; final String kanji;
const Kanimaji({ const Kanimaji({super.key, required this.kanji});
Key? key,
required this.kanji,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -6,16 +6,17 @@ import 'package:kanimaji/svg/path.dart';
void main() { void main() {
group("Examples from the SVG spec", () { group("Examples from the SVG spec", () {
test( test(
"[Path 1]: MLLz", "[Path 1]: MLLz",
() => expect( () => expect(
parsePath("M 100 100 L 300 100 L 200 300 z"), parsePath("M 100 100 L 300 100 L 200 300 z"),
Path.fromSegments(const [ Path.fromSegments(const [
Move(to: Point(100, 100)), Move(to: Point(100, 100)),
Line(start: Point(100, 100), end: Point(300, 100)), Line(start: Point(100, 100), end: Point(300, 100)),
Line(start: Point(300, 100), end: Point(200, 300)), Line(start: Point(300, 100), end: Point(200, 300)),
Close(start: Point(200, 300), end: Point(100, 100)), Close(start: Point(200, 300), end: Point(100, 100)),
]), ]),
)); ),
);
// //
test( test(
@@ -160,10 +161,11 @@ void main() {
Path.fromSegments(const [ Path.fromSegments(const [
Move(to: Point(600, 800)), Move(to: Point(600, 800)),
CubicBezier( CubicBezier(
start: Point(600, 800), start: Point(600, 800),
control1: Point(625, 700), control1: Point(625, 700),
control2: Point(725, 700), control2: Point(725, 700),
end: Point(750, 800)), end: Point(750, 800),
),
CubicBezier( CubicBezier(
start: Point(750, 800), start: Point(750, 800),
control1: Point(775, 900), control1: Point(775, 900),
@@ -347,9 +349,10 @@ void main() {
Path.fromSegments(const [ Path.fromSegments(const [
Move(to: Point(100, 200)), Move(to: Point(100, 200)),
QuadraticBezier( QuadraticBezier(
start: Point(100, 200), start: Point(100, 200),
control: Point(100, 200), control: Point(100, 200),
end: Point(250, 200)), end: Point(250, 200),
),
]), ]),
), ),
); );
@@ -361,9 +364,10 @@ void main() {
Path.fromSegments(const [ Path.fromSegments(const [
Move(to: Point(100, 200)), Move(to: Point(100, 200)),
QuadraticBezier( QuadraticBezier(
start: Point(100, 200), start: Point(100, 200),
control: Point(100, 200), control: Point(100, 200),
end: Point(250, 200)), end: Point(250, 200),
),
]), ]),
), ),
); );
@@ -382,12 +386,12 @@ void main() {
() => () =>
// It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported. // It can be e or E, the plus is optional, and a minimum of +/-3.4e38 must be supported.
expect( expect(
parsePath("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38"), parsePath("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38"),
Path.fromSegments(const [ Path.fromSegments(const [
Move(to: Point(-3.4e38, 3.4e38)), Move(to: Point(-3.4e38, 3.4e38)),
Line(start: Point(-3.4e38, 3.4e38), end: Point(-3.4e-38, 3.4e-38)) Line(start: Point(-3.4e38, 3.4e38), end: Point(-3.4e-38, 3.4e-38)),
]), ]),
), ),
); );
test( test(
@@ -405,15 +409,17 @@ void main() {
); );
test('svg.path library, issue 45', () { test('svg.path library, issue 45', () {
final path = parsePath("m 1672.2372,-54.8161 " final path = parsePath(
"a 14.5445,14.5445 0 0 0 -11.3152,23.6652 " "m 1672.2372,-54.8161 "
"l 27.2573,27.2572 27.2572,-27.2572 " "a 14.5445,14.5445 0 0 0 -11.3152,23.6652 "
"a 14.5445,14.5445 0 0 0 -11.3012,-23.634 " "l 27.2573,27.2572 27.2572,-27.2572 "
"a 14.5445,14.5445 0 0 0 -11.414,5.4625 " "a 14.5445,14.5445 0 0 0 -11.3012,-23.634 "
"l -4.542,4.5420 " "a 14.5445,14.5445 0 0 0 -11.414,5.4625 "
"l -4.5437,-4.5420 " "l -4.542,4.5420 "
"a 14.5445,14.5445 0 0 0 -11.3984,-5.4937 " "l -4.5437,-4.5420 "
"z"); "a 14.5445,14.5445 0 0 0 -11.3984,-5.4937 "
"z",
);
expect(path.d(), contains("A 14.5445,14.5445 0 0,0 1672.2372,-54.8161 Z")); expect(path.d(), contains("A 14.5445,14.5445 0 0,0 1672.2372,-54.8161 Z"));
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,8 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:kanimaji/common/point.dart'; import 'package:kanimaji/common/point.dart';
import 'package:kanimaji/svg/parser.dart' show Command, Token, commandifyPath, parsePath, tokenizePath; import 'package:kanimaji/svg/parser.dart'
show Command, Token, commandifyPath, parsePath, tokenizePath;
class TokenizerTest { class TokenizerTest {
final String pathdef; final String pathdef;
@@ -30,7 +31,7 @@ final List<TokenizerTest> tokenizerTests = [
Token(command: "M", args: [Point(100, 100)]), Token(command: "M", args: [Point(100, 100)]),
Token(command: "L", args: [Point(300, 100)]), Token(command: "L", args: [Point(300, 100)]),
Token(command: "L", args: [Point(200, 300)]), Token(command: "L", args: [Point(200, 300)]),
Token(command: "z", args: []) Token(command: "z", args: []),
], ],
), ),
const TokenizerTest( const TokenizerTest(
@@ -42,17 +43,22 @@ final List<TokenizerTest> tokenizerTests = [
Command(command: "M", args: "5 1"), Command(command: "M", args: "5 1"),
Command(command: "v", args: "7.344"), Command(command: "v", args: "7.344"),
Command( Command(
command: "A", args: "3.574 3.574 0 003.5 8 3.515 3.515 0 000 11.5"), command: "A",
args: "3.574 3.574 0 003.5 8 3.515 3.515 0 000 11.5",
),
Command(command: "C", args: "0 13.421 1.579 15 3.5 15"), Command(command: "C", args: "0 13.421 1.579 15 3.5 15"),
Command(command: "A", args: "3.517 3.517 0 007 11.531"), Command(command: "A", args: "3.517 3.517 0 007 11.531"),
Command(command: "v", args: "-7.53"), Command(command: "v", args: "-7.53"),
Command(command: "h", args: "6"), Command(command: "h", args: "6"),
Command(command: "v", args: "4.343"), Command(command: "v", args: "4.343"),
Command( Command(
command: "A", args: "3.574 3.574 0 0011.5 8 3.515 3.515 0 008 11.5"), command: "A",
args: "3.574 3.574 0 0011.5 8 3.515 3.515 0 008 11.5",
),
Command( Command(
command: "c", command: "c",
args: "0 1.921 1.579 3.5 3.5 3.5 1.9 0 3.465 -1.546 3.5 -3.437"), args: "0 1.921 1.579 3.5 3.5 3.5 1.9 0 3.465 -1.546 3.5 -3.437",
),
Command(command: "V", args: "1"), Command(command: "V", args: "1"),
Command(command: "z", args: ""), Command(command: "z", args: ""),
], ],
@@ -61,26 +67,36 @@ final List<TokenizerTest> tokenizerTests = [
Token(command: "v", args: [7.344]), Token(command: "v", args: [7.344]),
Token(command: "A", args: [3.574, 3.574, 0, false, false, Point(3.5, 8)]), Token(command: "A", args: [3.574, 3.574, 0, false, false, Point(3.5, 8)]),
Token( Token(
command: "A", args: [3.515, 3.515, 0, false, false, Point(0, 11.5)]), command: "A",
args: [3.515, 3.515, 0, false, false, Point(0, 11.5)],
),
Token( Token(
command: "C", command: "C",
args: [Point(0, 13.421), Point(1.579, 15), Point(3.5, 15)]), args: [Point(0, 13.421), Point(1.579, 15), Point(3.5, 15)],
),
Token( Token(
command: "A", command: "A",
args: [3.517, 3.517, 0, false, false, Point(7, 11.531)]), args: [3.517, 3.517, 0, false, false, Point(7, 11.531)],
),
Token(command: "v", args: [-7.53]), Token(command: "v", args: [-7.53]),
Token(command: "h", args: [6]), Token(command: "h", args: [6]),
Token(command: "v", args: [4.343]), Token(command: "v", args: [4.343]),
Token( Token(
command: "A", args: [3.574, 3.574, 0, false, false, Point(11.5, 8)]), command: "A",
args: [3.574, 3.574, 0, false, false, Point(11.5, 8)],
),
Token( Token(
command: "A", args: [3.515, 3.515, 0, false, false, Point(8, 11.5)]), command: "A",
args: [3.515, 3.515, 0, false, false, Point(8, 11.5)],
),
Token( Token(
command: "c", command: "c",
args: [Point(0, 1.921), Point(1.579, 3.5), Point(3.5, 3.5)]), args: [Point(0, 1.921), Point(1.579, 3.5), Point(3.5, 3.5)],
),
Token( Token(
command: "c", command: "c",
args: [Point(1.9, 0), Point(3.465, -1.546), Point(3.5, -3.437)]), args: [Point(1.9, 0), Point(3.465, -1.546), Point(3.5, -3.437)],
),
Token(command: "V", args: [1]), Token(command: "V", args: [1]),
Token(command: "z", args: []), Token(command: "z", args: []),
], ],