213 lines
5.9 KiB
Dart
213 lines
5.9 KiB
Dart
import 'package:xml/xml.dart';
|
|
|
|
import 'svg_parser.dart';
|
|
import 'primitives/path.dart';
|
|
import 'primitives/point.dart';
|
|
|
|
/// Enum set in the kvg:position attribute, used by `<g>` elements in the KanjiVG SVG files.
|
|
enum KanjiPathGroupPosition {
|
|
bottom,
|
|
kamae,
|
|
kamaec,
|
|
left,
|
|
middle,
|
|
nyo,
|
|
nyoc,
|
|
right,
|
|
tare,
|
|
tarec,
|
|
top;
|
|
|
|
factory KanjiPathGroupPosition.fromString(String s) => switch (s) {
|
|
'bottom' => bottom,
|
|
'kamae' => kamae,
|
|
'kamaec' => kamaec,
|
|
'left' => left,
|
|
'middle' => middle,
|
|
'nyo' => nyo,
|
|
'nyoc' => nyoc,
|
|
'right' => right,
|
|
'tare' => tare,
|
|
'tarec' => tarec,
|
|
'top' => top,
|
|
_ => throw ArgumentError('Invalid position string: $s'),
|
|
};
|
|
|
|
@override
|
|
String toString() => switch (this) {
|
|
KanjiPathGroupPosition.bottom => 'bottom',
|
|
KanjiPathGroupPosition.kamae => 'kamae',
|
|
KanjiPathGroupPosition.kamaec => 'kamaec',
|
|
KanjiPathGroupPosition.left => 'left',
|
|
KanjiPathGroupPosition.middle => 'middle',
|
|
KanjiPathGroupPosition.nyo => 'nyo',
|
|
KanjiPathGroupPosition.nyoc => 'nyoc',
|
|
KanjiPathGroupPosition.right => 'right',
|
|
KanjiPathGroupPosition.tare => 'tare',
|
|
KanjiPathGroupPosition.tarec => 'tarec',
|
|
KanjiPathGroupPosition.top => 'top',
|
|
};
|
|
}
|
|
|
|
/// Contents of a \<g> element in the KanjiVG SVG files.
|
|
class KanjiPathGroupTreeNode {
|
|
final String id;
|
|
final List<KanjiPathGroupTreeNode> children;
|
|
final String? element;
|
|
final String? original;
|
|
final KanjiPathGroupPosition? position;
|
|
final String? radical;
|
|
final int? part;
|
|
|
|
KanjiPathGroupTreeNode({
|
|
required this.id,
|
|
this.children = const [],
|
|
this.element,
|
|
this.original,
|
|
this.position,
|
|
this.radical,
|
|
this.part,
|
|
});
|
|
}
|
|
|
|
/// Contents of a `<text>` element in the StrokeNumber's group in the KanjiVG SVG files
|
|
class KanjiStrokeNumber {
|
|
final int num;
|
|
final Point position;
|
|
|
|
KanjiStrokeNumber(this.num, this.position);
|
|
|
|
@override
|
|
String toString() =>
|
|
'KanjiStrokeNumber(number: $num, position: $position)';
|
|
}
|
|
|
|
/// Contents of a `<path>` element in the KanjiVG SVG files
|
|
class KanjiVGPath {
|
|
final String id;
|
|
final String type;
|
|
final Path svgPath;
|
|
|
|
KanjiVGPath({required this.id, required this.type, required this.svgPath});
|
|
|
|
@override
|
|
String toString() => 'KanjiVGPath(id: $id, type: $type, svgPath: $svgPath)';
|
|
}
|
|
|
|
/// Representation of the entire KanjiVG SVG file for a single kanji character
|
|
class KanjiVGKanji {
|
|
final String character;
|
|
final List<Path> paths;
|
|
final List<KanjiStrokeNumber> strokeNumbers;
|
|
final KanjiPathGroupTreeNode? pathGroups;
|
|
|
|
KanjiVGKanji({
|
|
required this.character,
|
|
required this.paths,
|
|
required this.strokeNumbers,
|
|
this.pathGroups,
|
|
});
|
|
}
|
|
|
|
/// Small wrapper returned by the parser. It contains the parsed paths and stroke
|
|
/// numbers and the kanji character string.
|
|
class KanjiVGItem {
|
|
final String character;
|
|
final List<KanjiVGPath> paths;
|
|
final List<KanjiStrokeNumber> strokeNumbers;
|
|
|
|
KanjiVGItem({
|
|
required this.character,
|
|
required this.paths,
|
|
required this.strokeNumbers,
|
|
});
|
|
|
|
/// Helper method to get attributes that may be in the kvg namespace or as `kvg:` prefixed attributes.
|
|
static String? _kvgAttr(XmlElement el, String name) {
|
|
final fromNs = el.getAttribute(
|
|
name,
|
|
namespace: 'http://kanjivg.tagaini.net',
|
|
);
|
|
if (fromNs != null) return fromNs;
|
|
return el.getAttribute('kvg:$name');
|
|
}
|
|
|
|
/// Parse the path data from the provided XML document and return a list of [Path]s.
|
|
static List<KanjiVGPath> _parsePaths(XmlDocument doc) {
|
|
final List<KanjiVGPath> paths = [];
|
|
for (final p in doc.findAllElements('path')) {
|
|
final d = p.getAttribute('d');
|
|
if (d == null || d.trim().isEmpty) {
|
|
continue;
|
|
}
|
|
final svgPath = parsePath(d);
|
|
final id = p.getAttribute('id') ?? '';
|
|
final type = _kvgAttr(p, 'type') ?? p.getAttribute('kvg:type') ?? '';
|
|
paths.add(KanjiVGPath(id: id, type: type, svgPath: svgPath));
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
/// Parse the stroke number group from the provided XML document and return a list of [KanjiStrokeNumber]s.
|
|
static List<KanjiStrokeNumber> _parseStrokeNumbers(XmlDocument doc) {
|
|
final List<KanjiStrokeNumber> strokeNumbers = [];
|
|
final strokeNumberGroup = doc.findAllElements('g').firstWhere((g) {
|
|
final id = g.getAttribute('id') ?? '';
|
|
return RegExp(r'^kvg:StrokeNumbers_').hasMatch(id);
|
|
}, orElse: () => XmlElement(XmlName('')));
|
|
|
|
if (strokeNumberGroup.name.local != '') {
|
|
for (final t in strokeNumberGroup.findAllElements('text')) {
|
|
final rawText = t.innerText.trim();
|
|
if (rawText.isEmpty) continue;
|
|
final numVal = int.tryParse(rawText);
|
|
if (numVal == null) continue;
|
|
|
|
final transform = t.getAttribute('transform') ?? '';
|
|
final numbers = transform
|
|
.replaceAll('matrix(1 0 0 1', '')
|
|
.replaceAll(')', '')
|
|
.trim()
|
|
.split(' ')
|
|
.map(num.parse)
|
|
.toList();
|
|
|
|
assert(
|
|
numbers.length >= 2,
|
|
'Expected at least 2 numbers in transform for stroke number position',
|
|
);
|
|
|
|
Point pos = Point(
|
|
numbers[numbers.length - 2],
|
|
numbers[numbers.length - 1],
|
|
);
|
|
|
|
strokeNumbers.add(KanjiStrokeNumber(numVal, pos));
|
|
}
|
|
}
|
|
|
|
return strokeNumbers;
|
|
}
|
|
|
|
/// Parse the provided KanjiVG SVG content and return a [KanjiVGItem].
|
|
factory KanjiVGItem.parseFromXml(String xmlContent) {
|
|
final XmlDocument doc = XmlDocument.parse(xmlContent);
|
|
|
|
XmlElement strokePathsGroup = doc
|
|
.findElements('svg')
|
|
.first
|
|
.findElements('g')
|
|
.first;
|
|
XmlElement kanjiGroup = strokePathsGroup.findElements('g').first;
|
|
final character = _kvgAttr(kanjiGroup, 'element') ?? '';
|
|
final paths = _parsePaths(doc);
|
|
final strokeNumbers = _parseStrokeNumbers(doc);
|
|
|
|
return KanjiVGItem(
|
|
character: character,
|
|
paths: paths,
|
|
strokeNumbers: strokeNumbers,
|
|
);
|
|
}
|
|
}
|