Add kanjivg data
Build and test / build (push) Successful in 10m0s

This commit is contained in:
2026-03-03 13:47:59 +09:00
parent 8ba7c66e67
commit bbdb177fa4
20 changed files with 1195 additions and 1 deletions
+53
View File
@@ -0,0 +1,53 @@
import 'package:equatable/equatable.dart';
import 'kanjivg_path.dart';
import 'kanjivg_path_group.dart';
/// A full KanjiVG entry for a single character.
class KanjiVGEntry extends Equatable {
/// The kanji or character this entry belongs to.
final String character;
/// All stroke paths in drawing order.
///
/// Each path includes the rendered position of its stroke label.
final List<KanjiVGPath> paths;
/// The hierarchical group structure of the entry.
///
/// These are not really used in mugiten at the moment, so querying them is optional.
final List<KanjiVGPathGroup>? pathGroups;
KanjiVGEntry({
required this.character,
this.paths = const [],
this.pathGroups = const [],
}) : assert(
paths.isEmpty ||
(paths.first.pathId == 1 &&
paths.last.pathId == paths.length &&
paths.every((p) => p.pathId > 0)),
'Paths must be listed in a strictly growing order without holes, starting from pathId 1.',
);
@override
List<Object?> get props => [character, paths, pathGroups];
Map<String, dynamic> toJson() => {
'character': character,
'paths': paths.map((e) => e.toJson()).toList(),
'pathGroups': pathGroups?.map((e) => e.toJson()).toList(),
};
factory KanjiVGEntry.fromJson(Map<String, dynamic> json) => KanjiVGEntry(
character: json['character'] as String,
paths: ((json['paths'] as List<dynamic>?) ?? const [])
.map((e) => KanjiVGPath.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
pathGroups: ((json['pathGroups'] as List<dynamic>?))
?.map(
(e) => KanjiVGPathGroup.fromJson(Map<String, dynamic>.from(e as Map)),
)
.toList(),
);
}
+55
View File
@@ -0,0 +1,55 @@
import 'package:equatable/equatable.dart';
/// A stroke path from a KanjiVG entry.
class KanjiVGPath extends Equatable {
/// The path id within the KanjiVG entry.
final int pathId;
/// The optional KanjiVG stroke type.
final String? type;
/// The raw SVG `d` path string.
final String svgPath;
/// The x-coordinate of the rendered stroke-label position.
final double labelX;
/// The y-coordinate of the rendered stroke-label position.
final double labelY;
KanjiVGPath({
required this.pathId,
required this.type,
required this.svgPath,
required this.labelX,
required this.labelY,
}) : assert(pathId > 0, 'pathId must be a positive integer. Found $pathId.'),
assert(svgPath.isNotEmpty, 'svgPath cannot be empty.'),
assert(
labelX.isFinite,
'labelX must be a finite number. Found $labelX.',
),
assert(
labelY.isFinite,
'labelY must be a finite number. Found $labelY.',
);
@override
List<Object?> get props => [pathId, type, svgPath, labelX, labelY];
Map<String, dynamic> toJson() => {
'pathId': pathId,
'type': type,
'svgPath': svgPath,
'labelX': labelX,
'labelY': labelY,
};
factory KanjiVGPath.fromJson(Map<String, dynamic> json) => KanjiVGPath(
pathId: json['pathId'] as int,
type: json['type'] as String?,
svgPath: json['svgPath'] as String,
labelX: (json['labelX'] as num).toDouble(),
labelY: (json['labelY'] as num).toDouble(),
);
}
+100
View File
@@ -0,0 +1,100 @@
import 'package:equatable/equatable.dart';
import 'package:jadb/models/kanjivg/kanjivg_path.dart';
import 'kanjivg_path_group_position.dart';
import 'kanjivg_radical.dart';
/// A hierarchical path-group from a KanjiVG entry.
class KanjiVGPathGroup extends Equatable {
/// The path-group id within the entry.
final int groupId;
/// The paths directly contained in this group, in drawing order.
final List<KanjiVGPath> paths;
/// Nested child groups.
final List<KanjiVGPathGroup> children;
/// The value of the `kvg:element` attribute, if present.
final String? element;
/// The original element before simplification, if present.
final String? original;
/// Relative position of the group inside the character layout.
final KanjiVGPathGroupPosition? position;
/// Radical classification for the group.
final KanjiVGRadical? radical;
/// Part number for repeated elements, if present.
final int? part;
KanjiVGPathGroup({
required this.groupId,
this.paths = const [],
this.children = const [],
this.element,
this.original,
this.position,
this.radical,
this.part,
}) : assert(
groupId >= 0,
'groupId must be a non-negative integer. Found $groupId.',
),
assert(
paths.isEmpty ||
paths.fold<int>(
0,
(previousMax, path) => path.pathId > previousMax
? path.pathId
: throw ArgumentError(
'Paths must be listed in a strictly growing order without holes. Found pathId ${path.pathId} after $previousMax.',
),
) ==
paths.lastOrNull?.pathId,
);
@override
List<Object?> get props => [
groupId,
paths,
children,
element,
original,
position,
radical,
part,
];
Map<String, dynamic> toJson() => {
'groupId': groupId,
'paths': paths.map((e) => e.toJson()).toList(),
'children': children.map((e) => e.toJson()).toList(),
'element': element,
'original': original,
'position': position?.toJson(),
'radical': radical?.toJson(),
'part': part,
};
factory KanjiVGPathGroup.fromJson(
Map<String, dynamic> json,
) => KanjiVGPathGroup(
groupId: json['groupId'] as int,
paths: ((json['paths'] as List<dynamic>?) ?? const [])
.map((e) => KanjiVGPath.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
children: ((json['children'] as List<dynamic>?) ?? const [])
.map(
(e) => KanjiVGPathGroup.fromJson(Map<String, dynamic>.from(e as Map)),
)
.toList(),
element: json['element'] as String?,
original: json['original'] as String?,
position: KanjiVGPathGroupPosition.fromJson(json['position']),
radical: KanjiVGRadical.fromJson(json['radical']),
part: json['part'] as int?,
);
}
@@ -0,0 +1,41 @@
/// Relative position tags used by KanjiVG path-groups.
///
/// In the original SVG files these come from the `kvg:position` attribute.
/// The database stores the normalized enum name, while [svgValue] contains the
/// raw KanjiVG attribute value.
enum KanjiVGPathGroupPosition {
upperA(svgValue: '⿵A'),
upperB(svgValue: '⿵B'),
lower1(svgValue: '⿶1'),
lower2(svgValue: '⿶2'),
bottom(svgValue: 'bottom'),
kamae(svgValue: 'kamae'),
kamaec(svgValue: 'kamaec'),
left(svgValue: 'left'),
middle(svgValue: 'middle'),
nyo(svgValue: 'nyo'),
nyoc(svgValue: 'nyoc'),
right(svgValue: 'right'),
tare(svgValue: 'tare'),
tarec(svgValue: 'tarec'),
top(svgValue: 'top');
final String svgValue;
const KanjiVGPathGroupPosition({required this.svgValue});
/// Parses either the normalized enum name stored in the database/JSON, or
/// the raw KanjiVG SVG attribute value.
static KanjiVGPathGroupPosition fromString(String value) => values.firstWhere(
(e) => e.name == value || e.svgValue == value,
orElse: () => throw Exception('Unknown position: $value'),
);
static KanjiVGPathGroupPosition? fromNullableString(String? value) =>
value == null ? null : fromString(value);
Object? toJson() => name;
static KanjiVGPathGroupPosition? fromJson(Object? json) =>
fromNullableString(json as String?);
}
+20
View File
@@ -0,0 +1,20 @@
/// Radical classification tags used by KanjiVG path-groups.
enum KanjiVGRadical {
general,
jis,
nelson,
tradit;
static KanjiVGRadical fromString(String value) => values.firstWhere(
(e) => e.name == value,
orElse: () => throw Exception('Unknown radical: $value'),
);
static KanjiVGRadical? fromNullableString(String? value) =>
value == null ? null : fromString(value);
Object? toJson() => name;
static KanjiVGRadical? fromJson(Object? json) =>
fromNullableString(json as String?);
}
+2
View File
@@ -1,5 +1,6 @@
import 'package:jadb/table_names/jmdict.dart';
import 'package:jadb/table_names/kanjidic.dart';
import 'package:jadb/table_names/kanjivg.dart';
import 'package:jadb/table_names/radkfile.dart';
import 'package:jadb/table_names/tanos_jlpt.dart';
import 'package:sqflite_common/sqlite_api.dart';
@@ -21,6 +22,7 @@ Future<void> verifyTablesWithDbConnection(DatabaseExecutor db) async {
...KANJIDICTableNames.allTables,
...RADKFILETableNames.allTables,
...TanosJLPTTableNames.allTables,
...KanjiVGTableNames.allTables,
};
final missingTables = expectedTables.difference(tables);