Several changes
This commit is contained in:
41
lib/common/Point.dart
Normal file
41
lib/common/Point.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
class Point {
|
||||||
|
final num x;
|
||||||
|
final num y;
|
||||||
|
|
||||||
|
const Point(this.x, this.y);
|
||||||
|
const Point.from({this.x = 0, this.y = 0});
|
||||||
|
static const zero = Point(0, 0);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Point addX(num n) => Point(x + n, y);
|
||||||
|
Point addY(num n) => Point(x, y + n);
|
||||||
|
Point add(num n) => Point(x + n, y + n);
|
||||||
|
|
||||||
|
Point subtractX(num n) => Point(x - n, y);
|
||||||
|
Point subtractY(num n) => Point(x, y - n);
|
||||||
|
Point subtractXY(num n) => Point(x - n, y - n);
|
||||||
|
|
||||||
|
Point xSubtract(num n) => Point(n - x, y);
|
||||||
|
Point ySubtract(num n) => Point(x, n - y);
|
||||||
|
Point xySubtract(num n) => Point(n - x, n - y);
|
||||||
|
|
||||||
|
Point timesX(num n) => Point(x * n, y);
|
||||||
|
Point timesY(num n) => Point(x, y * n);
|
||||||
|
Point times(num n) => Point(x * n, y * n);
|
||||||
|
|
||||||
|
Point dividesX(num n) => Point(x / n, y);
|
||||||
|
Point dividesY(num n) => Point(x, y / n);
|
||||||
|
Point divides(num n) => Point(x / n, y / n);
|
||||||
|
|
||||||
|
Point pow(int n) => Point(math.pow(x, n), math.pow(y, n));
|
||||||
|
double abs() => math.sqrt(x * x + y * y);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '($x,$y)';
|
||||||
|
}
|
@@ -1,60 +1,99 @@
|
|||||||
/// ignore_for_file: non_constant_identifier_names, avoid_print, unused_local_variable, dead_code, constant_identifier_names
|
/// 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;
|
import 'dart:math' show min, sqrt, pow;
|
||||||
|
|
||||||
import '../svg/parser.dart';
|
import '../svg/parser.dart';
|
||||||
|
import '../common/Point.dart';
|
||||||
|
|
||||||
import 'bezier_cubic.dart' as bezier_cubic;
|
import 'bezier_cubic.dart' as bezier_cubic;
|
||||||
import 'settings.dart';
|
|
||||||
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();
|
parse_path(path).size(error: 1e-8).toDouble();
|
||||||
|
|
||||||
String shescape(path) => "'${path.replace(RegExp(r"(?=['\\\\])"), "\\\\")}'";
|
String _shescape(String path) =>
|
||||||
|
"'${path.replaceAll(RegExp(r"(?=['\\\\])"), "\\\\")}'";
|
||||||
|
|
||||||
String dedent(String s) {
|
extension _Dedent on String {
|
||||||
|
String get dedented {
|
||||||
final withoutEmptyLines =
|
final withoutEmptyLines =
|
||||||
s.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.map((l) => l.replaceRange(0, whitespaceToRemove, '')).join('\n');
|
return withoutEmptyLines
|
||||||
|
.map((l) => l.replaceRange(0, whitespaceToRemove, ''))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsAnimationElement {
|
||||||
|
final XmlDocumentFragment bg;
|
||||||
|
final XmlDocumentFragment anim;
|
||||||
|
final XmlDocumentFragment? brush;
|
||||||
|
final XmlDocumentFragment? brushBorder;
|
||||||
|
|
||||||
|
/// the time set (as default) for each animation
|
||||||
|
final num time;
|
||||||
|
|
||||||
|
const JsAnimationElement({
|
||||||
|
required this.bg,
|
||||||
|
required this.anim,
|
||||||
|
required this.time,
|
||||||
|
this.brush,
|
||||||
|
this.brushBorder,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ease, ease-in, etc:
|
// ease, ease-in, etc:
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function#ease
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function#ease
|
||||||
const pt1 = bezier_cubic.Point(0, 0);
|
const pt1 = Point(0, 0);
|
||||||
const easeCt1 = bezier_cubic.Point(0.25, 0.1);
|
const easeCt1 = Point(0.25, 0.1);
|
||||||
const easeCt2 = bezier_cubic.Point(0.25, 1.0);
|
const easeCt2 = Point(0.25, 1.0);
|
||||||
const easeInCt1 = bezier_cubic.Point(0.42, 0.0);
|
const easeInCt1 = Point(0.42, 0.0);
|
||||||
const easeInCt2 = bezier_cubic.Point(1.0, 1.0);
|
const easeInCt2 = Point(1.0, 1.0);
|
||||||
const easeInOutCt1 = bezier_cubic.Point(0.42, 0.0);
|
const easeInOutCt1 = Point(0.42, 0.0);
|
||||||
const easeInOutCt2 = bezier_cubic.Point(0.58, 1.0);
|
const easeInOutCt2 = Point(0.58, 1.0);
|
||||||
const easeOutCt1 = bezier_cubic.Point(0.0, 0.0);
|
const easeOutCt1 = Point(0.0, 0.0);
|
||||||
const easeOutCt2 = bezier_cubic.Point(0.58, 1.0);
|
const easeOutCt2 = Point(0.58, 1.0);
|
||||||
const pt2 = bezier_cubic.Point(1, 1);
|
const pt2 = Point(1, 1);
|
||||||
|
|
||||||
double linear(x) => x;
|
// class {
|
||||||
double ease(x) => bezier_cubic.value(pt1, easeCt1, easeCt2, pt2, x);
|
// }
|
||||||
double easeIn(x) => bezier_cubic.value(pt1, easeInCt1, easeInCt2, pt2, x);
|
|
||||||
double easeInOut(x) =>
|
|
||||||
bezier_cubic.value(pt1, easeInOutCt1, easeInOutCt2, pt2, x);
|
|
||||||
double easeOut(x) => bezier_cubic.value(pt1, easeOutCt1, easeOutCt2, pt2, x);
|
|
||||||
|
|
||||||
const Map<String, double Function(double)> timingFunctions = {
|
enum TimingFunction {
|
||||||
'linear': linear,
|
linear,
|
||||||
'ease': ease,
|
ease,
|
||||||
'ease-in': easeIn,
|
easeIn,
|
||||||
'ease-in-out': easeInOut,
|
easeInOut,
|
||||||
'ease-out': easeOut
|
easeOut,
|
||||||
};
|
}
|
||||||
|
|
||||||
final myTimingFunction = timingFunctions[TIMING_FUNCTION]!;
|
extension Funcs on TimingFunction {
|
||||||
|
double Function(double) get func => {
|
||||||
|
TimingFunction.linear: (double x) => x,
|
||||||
|
TimingFunction.ease: (double x) =>
|
||||||
|
bezier_cubic.value(pt1, easeCt1, easeCt2, pt2, x),
|
||||||
|
TimingFunction.easeIn: (double x) =>
|
||||||
|
bezier_cubic.value(pt1, easeInCt1, easeInCt2, pt2, x),
|
||||||
|
TimingFunction.easeInOut: (double x) =>
|
||||||
|
bezier_cubic.value(pt1, easeInOutCt1, easeInOutCt2, pt2, x),
|
||||||
|
TimingFunction.easeOut: (double x) =>
|
||||||
|
bezier_cubic.value(pt1, easeOutCt1, easeOutCt2, pt2, x),
|
||||||
|
}[this]!;
|
||||||
|
|
||||||
|
String get name => {
|
||||||
|
TimingFunction.linear: 'linear',
|
||||||
|
TimingFunction.ease: 'ease',
|
||||||
|
TimingFunction.easeIn: 'ease-in',
|
||||||
|
TimingFunction.easeInOut: 'ease-in-out',
|
||||||
|
TimingFunction.easeOut: 'ease-out',
|
||||||
|
}[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
// we will need this to deal with svg
|
// we will need this to deal with svg
|
||||||
const namespaces = {
|
const namespaces = {
|
||||||
@@ -64,19 +103,29 @@ const namespaces = {
|
|||||||
// 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);
|
||||||
|
|
||||||
void createAnimation(String filename) {
|
// gif settings
|
||||||
print('processing $filename');
|
// const DELETE_TEMPORARY_FILES = false;
|
||||||
final String filenameNoext = filename.replaceAll(RegExp(r'\.[^\.]+$'), '');
|
const GIF_SIZE = 150;
|
||||||
final String baseid = basename(filenameNoext);
|
const GIF_FRAME_DURATION = 0.04;
|
||||||
|
const GIF_BACKGROUND_COLOR = '#ddf';
|
||||||
|
// set to true to allow transparent background, much bigger file!
|
||||||
|
// const GIF_ALLOW_TRANSPARENT = false;
|
||||||
|
|
||||||
// load xml
|
// edit here to decide what will be generated
|
||||||
final XmlDocument doc = XmlDocument.parse(File(filename).readAsStringSync());
|
const GENERATE_SVG = true;
|
||||||
|
const GENERATE_JS_SVG = true;
|
||||||
|
const GENERATE_GIF = true;
|
||||||
|
|
||||||
// for xlink namespace introduction
|
/// sqrt, ie a stroke 4 times the length is drawn
|
||||||
doc.rootElement.setAttribute('xmlns:xlink', namespaces['xlink']);
|
/// at twice the speed, in twice the time.
|
||||||
doc.rootElement.setAttribute('xlink:used', '');
|
double strokeLengthToDuration(double length) => sqrt(length) / 8;
|
||||||
|
|
||||||
// clear all extra elements this program may have previously added
|
/// global time rescale, let's make animation a bit
|
||||||
|
/// faster when there are many strokes.
|
||||||
|
double timeRescale(interval) => pow(2 * interval, 2.0 / 3).toDouble();
|
||||||
|
|
||||||
|
/// clear all extra elements this program may have previously added
|
||||||
|
void clearPreviousElements(XmlDocument doc) {
|
||||||
for (final XmlNode el in doc
|
for (final XmlNode el in doc
|
||||||
.getElement('svg', namespace: namespaces['n'])
|
.getElement('svg', namespace: namespaces['n'])
|
||||||
?.getElement('style', namespace: namespaces['n'])
|
?.getElement('style', namespace: namespaces['n'])
|
||||||
@@ -95,10 +144,49 @@ void createAnimation(String filename) {
|
|||||||
g.parent!.children.remove(g);
|
g.parent!.children.remove(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create groups with a copies (references actually) of the paths
|
/// Note: setting any color to transparent will result in a much bigger
|
||||||
XmlDocumentFragment pathCopyGroup(
|
/// filesize for GIFs.
|
||||||
{required String id, required String color, required double width}) {
|
void createAnimation({
|
||||||
|
required String inputFile,
|
||||||
|
String? outputFile,
|
||||||
|
TimingFunction timingFunction = TimingFunction.easeInOut,
|
||||||
|
double strokeBorderWidth = 4.5,
|
||||||
|
double strokeUnfilledWidth = 3,
|
||||||
|
double strokeFilledWidth = 3.1,
|
||||||
|
bool showBrush = true,
|
||||||
|
bool showBrushFrontBorder = true,
|
||||||
|
double brushWidth = 5.5,
|
||||||
|
double brushBorderWidth = 7,
|
||||||
|
double waitAfter = 1.5,
|
||||||
|
String strokeBorderColor = '#666',
|
||||||
|
String strokeUnfilledColor = '#EEE',
|
||||||
|
String strokeFillingColor = '#F00',
|
||||||
|
String strokeFilledColor = '#000',
|
||||||
|
String brushColor = '#F00',
|
||||||
|
String brushBorderColor = '#666',
|
||||||
|
}) {
|
||||||
|
print('processing $inputFile');
|
||||||
|
final String filenameNoext = inputFile.replaceAll(RegExp(r'\.[^\.]+$'), '');
|
||||||
|
outputFile ??= '${filenameNoext}_anim.svg';
|
||||||
|
final String baseid = basename(filenameNoext);
|
||||||
|
|
||||||
|
// load xml
|
||||||
|
final XmlDocument doc = XmlDocument.parse(File(inputFile).readAsStringSync());
|
||||||
|
|
||||||
|
// for xlink namespace introduction
|
||||||
|
doc.rootElement.setAttribute('xmlns:xlink', namespaces['xlink']);
|
||||||
|
doc.rootElement.setAttribute('xlink:used', '');
|
||||||
|
|
||||||
|
clearPreviousElements(doc);
|
||||||
|
|
||||||
|
/// create groups with a copies (references actually) of the paths
|
||||||
|
XmlDocumentFragment pathCopyGroup({
|
||||||
|
required String id,
|
||||||
|
required String color,
|
||||||
|
required double width,
|
||||||
|
}) {
|
||||||
final builder = XmlBuilder();
|
final builder = XmlBuilder();
|
||||||
builder.element(
|
builder.element(
|
||||||
'g',
|
'g',
|
||||||
@@ -117,27 +205,27 @@ void createAnimation(String filename) {
|
|||||||
|
|
||||||
final bgGroup = pathCopyGroup(
|
final bgGroup = pathCopyGroup(
|
||||||
id: 'bg',
|
id: 'bg',
|
||||||
color: STOKE_UNFILLED_COLOR,
|
color: strokeUnfilledColor,
|
||||||
width: STOKE_UNFILLED_WIDTH,
|
width: strokeUnfilledWidth,
|
||||||
);
|
);
|
||||||
final animGroup = pathCopyGroup(
|
final animGroup = pathCopyGroup(
|
||||||
id: 'anim',
|
id: 'anim',
|
||||||
color: STOKE_FILLED_COLOR,
|
color: strokeFilledColor,
|
||||||
width: STOKE_FILLED_WIDTH,
|
width: strokeFilledWidth,
|
||||||
);
|
);
|
||||||
|
|
||||||
late final XmlDocumentFragment brushGroup;
|
late final XmlDocumentFragment brushGroup;
|
||||||
late final XmlDocumentFragment brushBrdGroup;
|
late final XmlDocumentFragment brushBrdGroup;
|
||||||
if (SHOW_BRUSH) {
|
if (showBrush) {
|
||||||
brushGroup = pathCopyGroup(
|
brushGroup = pathCopyGroup(
|
||||||
id: 'brush',
|
id: 'brush',
|
||||||
color: BRUSH_COLOR,
|
color: brushColor,
|
||||||
width: BRUSH_WIDTH,
|
width: brushWidth,
|
||||||
);
|
);
|
||||||
brushBrdGroup = pathCopyGroup(
|
brushBrdGroup = pathCopyGroup(
|
||||||
id: 'brush-brd',
|
id: 'brush-brd',
|
||||||
color: BRUSH_BORDER_COLOR,
|
color: brushBorderColor,
|
||||||
width: BRUSH_BORDER_WIDTH,
|
width: brushBorderWidth,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,22 +244,23 @@ void createAnimation(String filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final p in g.findAllElements('path', namespace: namespaces['n'])) {
|
for (final p in g.findAllElements('path', namespace: namespaces['n'])) {
|
||||||
final pathlen = computePathLength(p.getAttribute('d')!);
|
final pathlen = _computePathLength(p.getAttribute('d')!);
|
||||||
final duration = stroke_length_to_duration(pathlen);
|
final duration = strokeLengthToDuration(pathlen);
|
||||||
totlen += pathlen;
|
totlen += pathlen;
|
||||||
tottime += duration;
|
tottime += duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double animationTime = time_rescale(tottime); // math.pow(3 * tottime, 2.0/3)
|
double animationTime = timeRescale(tottime); // math.pow(3 * tottime, 2.0/3)
|
||||||
tottime += WAIT_AFTER * tottime / animationTime;
|
tottime += waitAfter * tottime / animationTime;
|
||||||
final double actualAnimationTime = animationTime;
|
final double actualAnimationTime = animationTime;
|
||||||
animationTime += WAIT_AFTER;
|
animationTime += waitAfter;
|
||||||
|
|
||||||
final Map<int, String> staticCss = {};
|
final Map<int, String> staticCss = {};
|
||||||
late String animatedCss;
|
late String animatedCss;
|
||||||
final jsAnimationElements = []; // collect the ids of animating elements
|
|
||||||
final jsAnimationTimes = []; // the time set (as default) for each animation
|
/// collect the ids of animating elements
|
||||||
|
final List<JsAnimationElement> jsAnimationElements = [];
|
||||||
|
|
||||||
String jsAnimatedCss = '';
|
String jsAnimatedCss = '';
|
||||||
|
|
||||||
@@ -186,19 +275,19 @@ void createAnimation(String filename) {
|
|||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
}
|
}
|
||||||
|
late final int lastFrameIndex;
|
||||||
|
late final double lastFrameDelay;
|
||||||
if (GENERATE_GIF) {
|
if (GENERATE_GIF) {
|
||||||
// final static_css = {};
|
// final static_css = {};
|
||||||
final last_frame_index = actualAnimationTime ~/ GIF_FRAME_DURATION + 1;
|
lastFrameIndex = actualAnimationTime ~/ GIF_FRAME_DURATION + 1;
|
||||||
for (int i = 0; i < last_frame_index + 1; i++) {
|
for (int i = 0; i < lastFrameIndex + 1; i++) {
|
||||||
staticCss[i] = cssHeader;
|
staticCss[i] = cssHeader;
|
||||||
}
|
}
|
||||||
final last_frame_delay =
|
lastFrameDelay = animationTime - lastFrameIndex * GIF_FRAME_DURATION;
|
||||||
animationTime - last_frame_index * GIF_FRAME_DURATION;
|
|
||||||
}
|
}
|
||||||
double elapsedlen = 0;
|
double elapsedlen = 0;
|
||||||
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 in doc
|
||||||
.getElement('svg', namespace: namespaces['n'])!
|
.getElement('svg', namespace: namespaces['n'])!
|
||||||
@@ -206,11 +295,12 @@ void createAnimation(String filename) {
|
|||||||
// 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 = dedent('''
|
final String rule = '''
|
||||||
#${groupid.replaceAll(':', '\\3a ')} {
|
#${groupid.replaceAll(':', '\\3a ')} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.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) {
|
||||||
@@ -222,12 +312,13 @@ void createAnimation(String filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final gidcss = groupid.replaceAll(':', '\\3a ');
|
final gidcss = groupid.replaceAll(':', '\\3a ');
|
||||||
final rule = dedent('''
|
final rule = '''
|
||||||
#$gidcss {
|
#$gidcss {
|
||||||
stroke-width: ${STOKE_BORDER_WIDTH.toStringAsFixed(1)}px !important;
|
stroke-width: ${strokeBorderWidth.toStringAsFixed(1)}px !important;
|
||||||
stroke: $STOKE_BORDER_COLOR !important;
|
stroke: $strokeBorderColor !important;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
|
|
||||||
if (GENERATE_SVG) animatedCss += rule;
|
if (GENERATE_SVG) animatedCss += rule;
|
||||||
if (GENERATE_JS_SVG) jsAnimatedCss += rule;
|
if (GENERATE_JS_SVG) jsAnimatedCss += rule;
|
||||||
@@ -241,39 +332,47 @@ void createAnimation(String filename) {
|
|||||||
final pathid = p.getAttribute('id') as String;
|
final pathid = p.getAttribute('id') as String;
|
||||||
final pathidcss = pathid.replaceAll(':', '\\3a ');
|
final pathidcss = pathid.replaceAll(':', '\\3a ');
|
||||||
|
|
||||||
if (GENERATE_JS_SVG) jsAnimationElements.add({});
|
XmlDocumentFragment addHref(String suffix, XmlDocumentFragment parent) {
|
||||||
|
|
||||||
void addHref(String suffix, XmlDocumentFragment element) {
|
|
||||||
final builder = XmlBuilder();
|
final builder = XmlBuilder();
|
||||||
builder.element(
|
builder.element(
|
||||||
'use',
|
'use',
|
||||||
attributes: {'id': '$pathid-$suffix', 'xlink:href': '#$pathid'},
|
attributes: {'id': '$pathid-$suffix', 'xlink:href': '#$pathid'},
|
||||||
);
|
);
|
||||||
final ref = builder.buildFragment();
|
final ref = builder.buildFragment();
|
||||||
element.firstElementChild!.children.add(ref);
|
parent.firstElementChild!.children.add(ref);
|
||||||
if (GENERATE_JS_SVG) jsAnimationElements.last[suffix] = ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String bgPathidcss = '$pathidcss-bg';
|
final String bgPathidcss = '$pathidcss-bg';
|
||||||
final String animPathidcss = '$pathidcss-anim';
|
final String animPathidcss = '$pathidcss-anim';
|
||||||
final String brushPathidcss = '$pathidcss-brush';
|
final String brushPathidcss = '$pathidcss-brush';
|
||||||
final String brushBrdPathidcss = '$pathidcss-brush-brd';
|
final String brushBorderPathidcss = '$pathidcss-brush-brd';
|
||||||
|
|
||||||
addHref('bg', bgGroup);
|
final bgGroupElement = addHref('bg', bgGroup);
|
||||||
addHref('anim', animGroup);
|
final animGroupElement = addHref('anim', animGroup);
|
||||||
|
XmlDocumentFragment? brushGroupElement;
|
||||||
if (SHOW_BRUSH) {
|
XmlDocumentFragment? brushBorderGroupElement;
|
||||||
addHref('brush', brushGroup);
|
if (showBrush) {
|
||||||
addHref('brush-brd', brushBrdGroup);
|
brushGroupElement = addHref('brush', brushGroup);
|
||||||
|
brushBorderGroupElement = addHref('brush-brd', brushBrdGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
final pathname = pathid.replaceAll(RegExp(r'^kvg:'), '');
|
final pathname = pathid.replaceAll(RegExp(r'^kvg:'), '');
|
||||||
final pathlen = computePathLength(p.getAttribute('d') as String);
|
final pathlen = _computePathLength(p.getAttribute('d') as String);
|
||||||
final duration = stroke_length_to_duration(pathlen);
|
final duration = strokeLengthToDuration(pathlen);
|
||||||
final relduration = duration * tottime / animationTime; // unscaled time
|
final relativeDuration =
|
||||||
|
duration * tottime / animationTime; // unscaled time
|
||||||
|
|
||||||
if (GENERATE_JS_SVG) {
|
if (GENERATE_JS_SVG) {
|
||||||
jsAnimationTimes.add(relduration);
|
jsAnimationElements.add(
|
||||||
|
JsAnimationElement(
|
||||||
|
bg: bgGroupElement,
|
||||||
|
anim: animGroupElement,
|
||||||
|
brush: brushGroupElement,
|
||||||
|
brushBorder: brushBorderGroupElement,
|
||||||
|
time: relativeDuration,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final newelapsedlen = elapsedlen + pathlen;
|
final newelapsedlen = elapsedlen + pathlen;
|
||||||
@@ -283,7 +382,7 @@ void createAnimation(String filename) {
|
|||||||
|
|
||||||
if (GENERATE_SVG) {
|
if (GENERATE_SVG) {
|
||||||
// animation stroke progression
|
// animation stroke progression
|
||||||
animatedCss += dedent('''
|
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)}; }
|
||||||
@@ -292,52 +391,55 @@ void createAnimation(String filename) {
|
|||||||
}
|
}
|
||||||
@keyframes showhide-$pathname {
|
@keyframes showhide-$pathname {
|
||||||
${animStart.toStringAsFixed(3)}% { visibility: hidden; }
|
${animStart.toStringAsFixed(3)}% { visibility: hidden; }
|
||||||
${animEnd.toStringAsFixed(3)}% { stroke: $STOKE_FILLING_COLOR; }
|
${animEnd.toStringAsFixed(3)}% { stroke: $strokeFillingColor; }
|
||||||
}
|
}
|
||||||
#$animPathidcss {
|
#$animPathidcss {
|
||||||
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${pathlen.toStringAsFixed(3)};
|
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${pathlen.toStringAsFixed(3)};
|
||||||
stroke-dashoffset: 0;
|
stroke-dashoffset: 0;
|
||||||
animation: strike-$pathname ${animationTime.toStringAsFixed(3)}s $TIMING_FUNCTION infinite,
|
animation: strike-$pathname ${animationTime.toStringAsFixed(3)}s ${timingFunction.name} infinite,
|
||||||
showhide-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
|
showhide-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
|
|
||||||
if (SHOW_BRUSH) {
|
if (showBrush) {
|
||||||
// brush element visibility
|
// brush element visibility
|
||||||
animatedCss += dedent('''
|
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; }
|
||||||
100% { visibility: hidden; }
|
100% { visibility: hidden; }
|
||||||
}
|
}
|
||||||
#$brushPathidcss, #$brushBrdPathidcss {
|
#$brushPathidcss, #$brushBorderPathidcss {
|
||||||
stroke-dasharray: 0 ${pathlen.toStringAsFixed(3)};
|
stroke-dasharray: 0 ${pathlen.toStringAsFixed(3)};
|
||||||
animation: strike-$pathname ${animationTime.toStringAsFixed(3)}s $TIMING_FUNCTION infinite,
|
animation: strike-$pathname ${animationTime.toStringAsFixed(3)}s ${timingFunction.name} infinite,
|
||||||
showhide-brush-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
|
showhide-brush-$pathname ${animationTime.toStringAsFixed(3)}s step-start infinite;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GENERATE_JS_SVG) {
|
if (GENERATE_JS_SVG) {
|
||||||
jsAnimatedCss += '\n/* stroke $pathid */';
|
jsAnimatedCss += '\n/* stroke $pathid */\n';
|
||||||
|
|
||||||
// brush and background hidden by default
|
// brush and background hidden by default
|
||||||
if (SHOW_BRUSH) {
|
if (showBrush) {
|
||||||
jsAnimatedCss += dedent('''
|
jsAnimatedCss += '''
|
||||||
#$brushPathidcss, #$brushBrdPathidcss, #$bgPathidcss {
|
#$brushPathidcss, #$brushBorderPathidcss, #$bgPathidcss {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide stroke after current element
|
// hide stroke after current element
|
||||||
const after_curr = '[class *= "current"]';
|
const afterCurrent = '[class *= "current"]';
|
||||||
jsAnimatedCss += dedent('''
|
jsAnimatedCss += '''
|
||||||
$after_curr ~ #$animPathidcss {
|
$afterCurrent ~ #$animPathidcss {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
$after_curr ~ #$bgPathidcss, #$bgPathidcss.animate {
|
$afterCurrent ~ #$bgPathidcss, #$bgPathidcss.animate {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@keyframes strike-$pathname {
|
@keyframes strike-$pathname {
|
||||||
@@ -345,24 +447,26 @@ void createAnimation(String filename) {
|
|||||||
100% { stroke-dashoffset: 0; }
|
100% { stroke-dashoffset: 0; }
|
||||||
}
|
}
|
||||||
#$animPathidcss.animate {
|
#$animPathidcss.animate {
|
||||||
stroke: $STOKE_FILLING_COLOR;
|
stroke: $strokeFillingColor;
|
||||||
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${pathlen.toStringAsFixed(3)};
|
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${pathlen.toStringAsFixed(3)};
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
animation: strike-$pathname ${relduration.toStringAsFixed(3)}s $TIMING_FUNCTION forwards 1;
|
animation: strike-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
if (SHOW_BRUSH) {
|
.dedented;
|
||||||
jsAnimatedCss += dedent('''
|
if (showBrush) {
|
||||||
|
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; }
|
||||||
}
|
}
|
||||||
#$brushPathidcss.animate.brush, #$brushBrdPathidcss.animate.brush {
|
#$brushPathidcss.animate.brush, #$brushBorderPathidcss.animate.brush {
|
||||||
stroke-dasharray: 0 ${pathlen.toStringAsFixed(3)};
|
stroke-dasharray: 0 ${pathlen.toStringAsFixed(3)};
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
animation: strike-brush-$pathname ${relduration.toStringAsFixed(3)}s $TIMING_FUNCTION forwards 1;
|
animation: strike-brush-$pathname ${relativeDuration.toStringAsFixed(3)}s ${timingFunction.name} forwards 1;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,51 +484,55 @@ void createAnimation(String filename) {
|
|||||||
// just hide everything
|
// just hide everything
|
||||||
rule += "#$animPathidcss";
|
rule += "#$animPathidcss";
|
||||||
|
|
||||||
if (SHOW_BRUSH) {
|
if (showBrush) {
|
||||||
rule += ", #$brushPathidcss, #$brushBrdPathidcss";
|
rule += ", #$brushPathidcss, #$brushBorderPathidcss";
|
||||||
}
|
}
|
||||||
|
|
||||||
staticCss[k] = staticCss[k]! +
|
staticCss[k] = staticCss[k]! +
|
||||||
dedent('''
|
'''
|
||||||
%$rule {
|
%$rule {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
} else if (reltime > newelapsedtime) {
|
} else if (reltime > newelapsedtime) {
|
||||||
// just hide the brush, and bg
|
// just hide the brush, and bg
|
||||||
rule += "#$bgPathidcss";
|
rule += "#$bgPathidcss";
|
||||||
|
|
||||||
if (SHOW_BRUSH) {
|
if (showBrush) {
|
||||||
rule += ", #$brushPathidcss, #$brushBrdPathidcss";
|
rule += ", #$brushPathidcss, #$brushBorderPathidcss";
|
||||||
}
|
}
|
||||||
|
|
||||||
staticCss[k] = staticCss[k]! +
|
staticCss[k] = staticCss[k]! +
|
||||||
dedent('''
|
'''
|
||||||
$rule {
|
$rule {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
} else {
|
} else {
|
||||||
final intervalprop =
|
final intervalprop =
|
||||||
((reltime - elapsedtime) / (newelapsedtime - elapsedtime));
|
((reltime - elapsedtime) / (newelapsedtime - elapsedtime));
|
||||||
final progression = myTimingFunction(intervalprop);
|
final progression = timingFunction.func(intervalprop);
|
||||||
|
|
||||||
staticCss[k] = staticCss[k]! +
|
staticCss[k] = staticCss[k]! +
|
||||||
dedent('''
|
'''
|
||||||
#$animPathidcss {
|
#$animPathidcss {
|
||||||
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${(pathlen + 0.002).toStringAsFixed(3)};
|
stroke-dasharray: ${pathlen.toStringAsFixed(3)} ${(pathlen + 0.002).toStringAsFixed(3)};
|
||||||
stroke-dashoffset: ${(pathlen * (1 - progression) + 0.0015).toStringAsFixed(4)};
|
stroke-dashoffset: ${(pathlen * (1 - progression) + 0.0015).toStringAsFixed(4)};
|
||||||
stroke: $STOKE_FILLING_COLOR;
|
stroke: $strokeFillingColor;
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
if (SHOW_BRUSH) {
|
.dedented;
|
||||||
|
if (showBrush) {
|
||||||
staticCss[k] = staticCss[k]! +
|
staticCss[k] = staticCss[k]! +
|
||||||
dedent('''
|
'''
|
||||||
#$brushPathidcss, #$brushBrdPathidcss {
|
#$brushPathidcss, #$brushBorderPathidcss {
|
||||||
stroke-dasharray: 0.001 ${(pathlen + 0.002).toStringAsFixed(3)};
|
stroke-dasharray: 0.001 ${(pathlen + 0.002).toStringAsFixed(3)};
|
||||||
stroke-dashoffset: ${(pathlen * (1 - progression) + 0.0015).toStringAsFixed(4)};
|
stroke-dashoffset: ${(pathlen * (1 - progression) + 0.0015).toStringAsFixed(4)};
|
||||||
}
|
}
|
||||||
''');
|
'''
|
||||||
|
.dedented;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,11 +547,11 @@ void createAnimation(String filename) {
|
|||||||
doc.root.firstElementChild?.children.add(g);
|
doc.root.firstElementChild?.children.add(g);
|
||||||
|
|
||||||
// insert groups
|
// insert groups
|
||||||
if (SHOW_BRUSH && !SHOW_BRUSH_FRONT_BORDER) addGroup(brushBrdGroup);
|
if (showBrush && !showBrushFrontBorder) addGroup(brushBrdGroup);
|
||||||
addGroup(bgGroup);
|
addGroup(bgGroup);
|
||||||
if (SHOW_BRUSH && SHOW_BRUSH_FRONT_BORDER) addGroup(brushBrdGroup);
|
if (showBrush && showBrushFrontBorder) addGroup(brushBrdGroup);
|
||||||
addGroup(animGroup);
|
addGroup(animGroup);
|
||||||
if (SHOW_BRUSH) addGroup(brushGroup);
|
if (showBrush) addGroup(brushGroup);
|
||||||
|
|
||||||
if (GENERATE_SVG) {
|
if (GENERATE_SVG) {
|
||||||
print(animatedCss);
|
print(animatedCss);
|
||||||
@@ -456,10 +564,9 @@ void createAnimation(String filename) {
|
|||||||
))
|
))
|
||||||
.buildFragment();
|
.buildFragment();
|
||||||
doc.root.firstElementChild!.children.insert(0, style);
|
doc.root.firstElementChild!.children.insert(0, style);
|
||||||
final svgfile = '${filenameNoext}_anim.svg';
|
File(outputFile).writeAsStringSync(doc.toXmlString(pretty: true));
|
||||||
File(svgfile).writeAsStringSync(doc.toXmlString(pretty: true));
|
|
||||||
doc.root.children.removeAt(0);
|
doc.root.children.removeAt(0);
|
||||||
print('written $svgfile');
|
print('written $outputFile');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GENERATE_GIF) {
|
if (GENERATE_GIF) {
|
||||||
@@ -573,9 +680,25 @@ void createAnimation(String filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main(List<String> args) {
|
void main(List<String> args) {
|
||||||
if (!timingFunctions.keys.contains(TIMING_FUNCTION)) {
|
|
||||||
throw 'Sorry, invalid timing function "$TIMING_FUNCTION"';
|
|
||||||
}
|
|
||||||
// createAnimation('assets/kanjivg/kanji/0f9b1.svg');
|
// createAnimation('assets/kanjivg/kanji/0f9b1.svg');
|
||||||
createAnimation('assets/kanjivg/kanji/04f5c.svg');
|
|
||||||
|
const kanji = '情報科学';
|
||||||
|
final fileList = [];
|
||||||
|
for (int k = 0; k < kanji.length; k++) {
|
||||||
|
createAnimation(
|
||||||
|
inputFile: 'assets/kanjivg/kanji/${kanji.codeUnits[k].toRadixString(16).padLeft(5, '0')}.svg',
|
||||||
|
outputFile: '${k+1}.svg',
|
||||||
|
);
|
||||||
|
fileList.add('${k+1}.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
File('index.html').writeAsStringSync(
|
||||||
|
'<html>' +
|
||||||
|
fileList.map((e) => File(e).readAsStringSync().replaceAll(']>', '')).join('\n') +
|
||||||
|
'</html>'
|
||||||
|
);
|
||||||
|
// createAnimation(
|
||||||
|
// inputFile: 'assets/kanjivg/kanji/060c5.svg',
|
||||||
|
// outputFile: 'test.svg',
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
class Point {
|
|
||||||
final double x;
|
|
||||||
final double y;
|
|
||||||
|
|
||||||
const Point(this.x, this.y);
|
import '../common/Point.dart';
|
||||||
|
|
||||||
@override
|
// class Point {
|
||||||
String toString() => '($x,$y)';
|
// final double x;
|
||||||
}
|
// final double y;
|
||||||
|
|
||||||
|
// const Point(this.x, this.y);
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// String toString() => '($x,$y)';
|
||||||
|
// }
|
||||||
|
|
||||||
double thrt(double x) =>
|
double thrt(double x) =>
|
||||||
x > 0 ? math.pow(x, 1.0 / 3).toDouble() : -math.pow(-x, 1.0 / 3).toDouble();
|
x > 0 ? math.pow(x, 1.0 / 3).toDouble() : -math.pow(-x, 1.0 / 3).toDouble();
|
||||||
@@ -23,10 +26,10 @@ double cb(x) => x * x * x;
|
|||||||
/// x(t) = t^3 T + 3t^2(1-t) U + 3t(1-t)^2 V + (1-t)^3 W
|
/// x(t) = t^3 T + 3t^2(1-t) U + 3t(1-t)^2 V + (1-t)^3 W
|
||||||
double time(Point pt1, Point ct1, Point ct2, Point pt2, double x) {
|
double time(Point pt1, Point ct1, Point ct2, Point pt2, double x) {
|
||||||
// var C = Cubic, a,b,c,d,p,q,lambda,sqlambda,tmp,addcoef,t,qb,qc,norm,angle,fact;
|
// var C = Cubic, a,b,c,d,p,q,lambda,sqlambda,tmp,addcoef,t,qb,qc,norm,angle,fact;
|
||||||
final double a = pt1.x - 3 * ct1.x + 3 * ct2.x - pt2.x;
|
final num a = pt1.x - 3 * ct1.x + 3 * ct2.x - pt2.x;
|
||||||
final double b = 3 * ct1.x - 6 * ct2.x + 3 * pt2.x;
|
final num b = 3 * ct1.x - 6 * ct2.x + 3 * pt2.x;
|
||||||
final double c = 3 * ct2.x - 3 * pt2.x;
|
final num c = 3 * ct2.x - 3 * pt2.x;
|
||||||
final double 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
|
||||||
|
@@ -1,56 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
// *_BORDER_WIDTH is the width INCLUDING the border.
|
|
||||||
const STOKE_BORDER_WIDTH = 4.5;
|
|
||||||
const STOKE_BORDER_COLOR = "#666";
|
|
||||||
const STOKE_UNFILLED_COLOR = "#eee";
|
|
||||||
const double STOKE_UNFILLED_WIDTH = 3;
|
|
||||||
const STOKE_FILLING_COLOR = "#f00";
|
|
||||||
const STOKE_FILLED_COLOR = "#000";
|
|
||||||
const double STOKE_FILLED_WIDTH = 3.1;
|
|
||||||
|
|
||||||
// brush settings
|
|
||||||
const SHOW_BRUSH = true;
|
|
||||||
const SHOW_BRUSH_FRONT_BORDER = true;
|
|
||||||
const BRUSH_COLOR = "#f00";
|
|
||||||
const double BRUSH_WIDTH = 5.5;
|
|
||||||
const BRUSH_BORDER_COLOR = "#666";
|
|
||||||
const double BRUSH_BORDER_WIDTH = 7;
|
|
||||||
|
|
||||||
const WAIT_AFTER = 1.5;
|
|
||||||
|
|
||||||
// gif settings
|
|
||||||
const DELETE_TEMPORARY_FILES = false;
|
|
||||||
const GIF_SIZE = 150;
|
|
||||||
const GIF_FRAME_DURATION = 0.04;
|
|
||||||
const GIF_BACKGROUND_COLOR = '#ddf';
|
|
||||||
// set to true to allow transparent background, much bigger file!
|
|
||||||
const GIF_ALLOW_TRANSPARENT = false;
|
|
||||||
|
|
||||||
// edit here to decide what will be generated
|
|
||||||
const GENERATE_SVG = true;
|
|
||||||
const GENERATE_JS_SVG = true;
|
|
||||||
const GENERATE_GIF = true;
|
|
||||||
|
|
||||||
// sqrt, ie a stroke 4 times the length is drawn
|
|
||||||
// at twice the speed, in twice the time.
|
|
||||||
double stroke_length_to_duration(double length) => sqrt(length) / 8;
|
|
||||||
|
|
||||||
// global time rescale, let's make animation a bit
|
|
||||||
// faster when there are many strokes.
|
|
||||||
double time_rescale(interval) => pow(2 * interval, 2.0 / 3).toDouble();
|
|
||||||
|
|
||||||
// Possibilities are linear, ease, ease-in, ease-in-out, ease-out, see
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function
|
|
||||||
// for more info.
|
|
||||||
const TIMING_FUNCTION = "ease-in-out";
|
|
||||||
|
|
||||||
//
|
|
||||||
// colorful debug settings
|
|
||||||
//
|
|
||||||
// STOKE_BORDER_COLOR = "#00f"
|
|
||||||
// STOKE_UNFILLED_COLOR = "#ff0"
|
|
||||||
// STOKE_FILLING_COLOR = "#f00"
|
|
||||||
// STOKE_FILLED_COLOR = "#000"
|
|
||||||
// BRUSH_COLOR = "#0ff"
|
|
||||||
// BRUSH_BORDER_COLOR = "#0f0"
|
|
@@ -1,7 +1,7 @@
|
|||||||
/// SVG Path specification parser
|
/// SVG Path specification parser
|
||||||
///
|
///
|
||||||
import 'path.dart'
|
import '../common/Point.dart';
|
||||||
show Arc, Close, CubicBezier, Line, Move, Path, Point, QuadraticBezier;
|
import 'path.dart';
|
||||||
|
|
||||||
const COMMANDS = {
|
const COMMANDS = {
|
||||||
'M',
|
'M',
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
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;
|
||||||
import 'package:bisect/bisect.dart';
|
|
||||||
|
import 'package:bisection/extension.dart';
|
||||||
|
|
||||||
|
import '../common/Point.dart';
|
||||||
|
|
||||||
// try:
|
// try:
|
||||||
// from collections.abc import MutableSequence
|
// from collections.abc import MutableSequence
|
||||||
@@ -14,45 +17,6 @@ import 'package:bisect/bisect.dart';
|
|||||||
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;
|
||||||
|
|
||||||
class Point {
|
|
||||||
final num x;
|
|
||||||
final num y;
|
|
||||||
|
|
||||||
const Point(this.x, this.y);
|
|
||||||
const Point.from({this.x = 0, this.y = 0});
|
|
||||||
static const zero = Point(0, 0);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Point addX(num n) => Point(x + n, y);
|
|
||||||
Point addY(num n) => Point(x, y + n);
|
|
||||||
Point add(num n) => Point(x + n, y + n);
|
|
||||||
|
|
||||||
Point subtractX(num n) => Point(x - n, y);
|
|
||||||
Point subtractY(num n) => Point(x, y - n);
|
|
||||||
Point subtractXY(num n) => Point(x - n, y - n);
|
|
||||||
|
|
||||||
Point xSubtract(num n) => Point(n - x, y);
|
|
||||||
Point ySubtract(num n) => Point(x, n - y);
|
|
||||||
Point xySubtract(num n) => Point(n - x, n - y);
|
|
||||||
|
|
||||||
Point timesX(num n) => Point(x * n, y);
|
|
||||||
Point timesY(num n) => Point(x, y * n);
|
|
||||||
Point times(num n) => Point(x * n, y * n);
|
|
||||||
|
|
||||||
Point dividesX(num n) => Point(x / n, y);
|
|
||||||
Point dividesY(num n) => Point(x, y / n);
|
|
||||||
Point divides(num n) => Point(x / n, y / n);
|
|
||||||
|
|
||||||
Point pow(int n) => Point(math.pow(x, n), math.pow(y, n));
|
|
||||||
double abs() => math.sqrt(x * x + y * y);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => '($x,$y)';
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMinDepth = 5;
|
const defaultMinDepth = 5;
|
||||||
const defaultError = 1e-12;
|
const defaultError = 1e-12;
|
||||||
@@ -496,7 +460,8 @@ class Move extends SvgPath {
|
|||||||
Point point(num pos) => start;
|
Point point(num pos) => start;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double size({double error = defaultError, int minDepth = defaultMinDepth}) => 0;
|
double size({double error = defaultError, int minDepth = defaultMinDepth}) =>
|
||||||
|
0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents the closepath command
|
// Represents the closepath command
|
||||||
@@ -517,7 +482,7 @@ class Close extends Linear {
|
|||||||
|
|
||||||
/// A Path is a sequence of path segments
|
/// A Path is a sequence of path segments
|
||||||
class Path extends ListBase<SvgPath> {
|
class Path extends ListBase<SvgPath> {
|
||||||
late final List<SvgPath> segments;
|
late final List<SvgPath?> segments;
|
||||||
List<num>? _memoizedLengths;
|
List<num>? _memoizedLengths;
|
||||||
num? _memoizedLength;
|
num? _memoizedLength;
|
||||||
final List<num> _fractions = [];
|
final List<num> _fractions = [];
|
||||||
@@ -527,7 +492,7 @@ class Path extends ListBase<SvgPath> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SvgPath operator [](int index) => segments[index];
|
SvgPath operator [](int index) => segments[index]!;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void operator []=(int index, SvgPath value) {
|
void operator []=(int index, SvgPath value) {
|
||||||
@@ -545,11 +510,12 @@ 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({double error = defaultError, int minDepth = defaultMinDepth}) {
|
void _calcLengths(
|
||||||
|
{double 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) {
|
||||||
@@ -569,29 +535,29 @@ class Path extends ListBase<SvgPath> {
|
|||||||
Point point({required num pos, double error = defaultError}) {
|
Point point({required num pos, double error = defaultError}) {
|
||||||
// Shortcuts
|
// Shortcuts
|
||||||
if (pos == 0.0) {
|
if (pos == 0.0) {
|
||||||
return segments[0].point(pos);
|
return segments[0]!.point(pos);
|
||||||
}
|
}
|
||||||
if (pos == 1.0) {
|
if (pos == 1.0) {
|
||||||
return segments.last.point(pos);
|
return segments.last!.point(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
_calcLengths(error: error);
|
_calcLengths(error: error);
|
||||||
|
|
||||||
// Fix for paths of length 0 (i.e. points)
|
// Fix for paths of length 0 (i.e. points)
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return segments[0].point(0.0);
|
return segments[0]!.point(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find which segment the point we search for is located on:
|
// Find which segment the point we search for is located on:
|
||||||
late final num segmentPos;
|
late final num segmentPos;
|
||||||
int i = _fractions.bisect(pos);
|
int i = _fractions.bisectRight(pos);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
segmentPos = pos / _fractions[0];
|
segmentPos = pos / _fractions[0];
|
||||||
} else {
|
} else {
|
||||||
segmentPos =
|
segmentPos =
|
||||||
(pos - _fractions[i - 1]) / (_fractions[i] - _fractions[i - 1]);
|
(pos - _fractions[i - 1]) / (_fractions[i] - _fractions[i - 1]);
|
||||||
}
|
}
|
||||||
return segments[i].point(segmentPos);
|
return segments[i]!.point(segmentPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
num size({error = defaultError, minDepth = defaultMinDepth}) {
|
num size({error = defaultError, minDepth = defaultMinDepth}) {
|
||||||
|
18
lib/widgets/kanjimaji.dart
Normal file
18
lib/widgets/kanjimaji.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension _Hexcode on Color {
|
||||||
|
String get hexcode => '#${value.toRadixString(16).padLeft(8, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Kanimaji extends StatelessWidget {
|
||||||
|
final String kanji;
|
||||||
|
const Kanimaji({
|
||||||
|
Key? key,
|
||||||
|
required this.kanji,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
@@ -4,11 +4,11 @@ version: 0.0.1
|
|||||||
homepage:
|
homepage:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.15.1 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bisect: ^1.0.2
|
bisection: ^0.4.3+1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
xml: ^5.3.1
|
xml: ^5.3.1
|
||||||
|
Reference in New Issue
Block a user