hanzi-chai 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/affine.d.ts +8 -0
  2. package/dist/affine.js +153 -0
  3. package/dist/assembly.d.ts +63 -0
  4. package/dist/assembly.js +286 -0
  5. package/dist/bezier.d.ts +74 -0
  6. package/dist/bezier.js +140 -0
  7. package/dist/classifier.d.ts +109 -0
  8. package/dist/classifier.js +108 -0
  9. package/dist/component.d.ts +115 -0
  10. package/dist/component.js +278 -0
  11. package/dist/compound.d.ts +64 -0
  12. package/dist/compound.js +652 -0
  13. package/dist/config.d.ts +236 -0
  14. package/dist/config.js +48 -0
  15. package/dist/data/cjk.txt +20902 -0
  16. package/dist/data/data/cjk.txt +20902 -0
  17. package/dist/data/data/dictionary.txt +69429 -0
  18. package/dist/data/data/frequency.txt +263137 -0
  19. package/dist/data/data/key_distribution.txt +46 -0
  20. package/dist/data/data/pair_equivalence.txt +2116 -0
  21. package/dist/data/data/repertoire.json.deflate +0 -0
  22. package/dist/data/data/tygf.txt +8105 -0
  23. package/dist/data/dictionary.txt +69429 -0
  24. package/dist/data/frequency.txt +263137 -0
  25. package/dist/data/key_distribution.txt +46 -0
  26. package/dist/data/pair_equivalence.txt +2116 -0
  27. package/dist/data/repertoire.json.deflate +0 -0
  28. package/dist/data/tygf.txt +8105 -0
  29. package/dist/data.d.ts +195 -0
  30. package/dist/data.js +18 -0
  31. package/dist/degenerator.d.ts +21 -0
  32. package/dist/degenerator.js +134 -0
  33. package/dist/element.d.ts +56 -0
  34. package/dist/element.js +225 -0
  35. package/dist/equivalence.d.ts +21 -0
  36. package/dist/equivalence.js +74 -0
  37. package/dist/index.d.ts +45 -0
  38. package/dist/index.js +126 -0
  39. package/dist/index.mjs +3553 -0
  40. package/dist/mathjs.d.ts +10 -0
  41. package/dist/mathjs.js +9 -0
  42. package/dist/repertoire.d.ts +75 -0
  43. package/dist/repertoire.js +406 -0
  44. package/dist/selector.d.ts +110 -0
  45. package/dist/selector.js +310 -0
  46. package/dist/topology.d.ts +41 -0
  47. package/dist/topology.js +209 -0
  48. package/dist/transformer.d.ts +18 -0
  49. package/dist/transformer.js +111 -0
  50. package/dist/utils.d.ts +107 -0
  51. package/dist/utils.js +546 -0
  52. package/package.json +34 -0
@@ -0,0 +1,278 @@
1
+ import { defaultDegenerator, generateSliceBinaries } from "./degenerator.js";
2
+ import { select } from "./selector.js";
3
+ import { bisectLeft, bisectRight } from "d3-array";
4
+ import { findTopology, renderSVGGlyph } from "./topology.js";
5
+ import { mergeClassifier } from "./classifier.js";
6
+ import { isComponent } from "./utils.js";
7
+ import { getGlyphBoundingBox, recursiveRenderCompound } from "./compound.js";
8
+ import { affineMerge } from "./affine.js";
9
+ export class InvalidGlyphError extends Error {
10
+ }
11
+ export const makeIntervalSum = (strokes, rootsSet) => {
12
+ const array = [...Array(strokes).keys()].map((x) => 1 << x).reverse();
13
+ const intervalSums = new Set();
14
+ for (let start = 0; start !== strokes - 1; ++start) {
15
+ let sum = array[start];
16
+ for (let end = start + 1; end !== strokes; ++end) {
17
+ sum += array[end];
18
+ if (rootsSet.has(sum)) {
19
+ intervalSums.add(sum);
20
+ }
21
+ }
22
+ }
23
+ return intervalSums;
24
+ };
25
+ /**
26
+ * 根据一个部件中包含的所有字根的情况,导出所有可能的拆分方案
27
+ *
28
+ * @param strokes - 部件笔画数
29
+ * @param roots - 部件包含的字根列表,其中每个字根用二进制表示
30
+ *
31
+ * 函数通过递归的方式,每次选取剩余部分的第一笔,然后在字根列表中找到包含这个笔画的所有字根
32
+ * 将这些可能性与此前已经拆分的部分组合,得到新的拆分方案
33
+ * 直到所有的笔画都使用完毕
34
+ */
35
+ export const generateSchemes = (strokes, roots, requiredRoots) => {
36
+ const schemeList = [];
37
+ const total = (1 << strokes) - 1;
38
+ const intervalSums = makeIntervalSum(strokes, requiredRoots);
39
+ const combineNext = (partialSum, scheme, revcumsum) => {
40
+ const restBin = total - partialSum;
41
+ const restBin1st = 1 << (restBin.toString(2).length - 1);
42
+ const start = bisectLeft(roots, restBin1st);
43
+ const end = bisectRight(roots, restBin);
44
+ for (const binary of roots.slice(start, end)) {
45
+ if ((partialSum & binary) !== 0)
46
+ continue;
47
+ const newPartialSum = partialSum + binary;
48
+ const newRevcumsum = revcumsum.map((x) => x + binary);
49
+ const newScheme = scheme.concat(binary);
50
+ if (newRevcumsum.some((x) => intervalSums.has(x))) {
51
+ continue;
52
+ }
53
+ newRevcumsum.push(binary);
54
+ if (newPartialSum === total) {
55
+ schemeList.push(newScheme);
56
+ }
57
+ else {
58
+ combineNext(newPartialSum, newScheme, newRevcumsum);
59
+ }
60
+ }
61
+ };
62
+ combineNext(0, [], []);
63
+ return schemeList;
64
+ };
65
+ export class NoSchemeError extends Error {
66
+ }
67
+ export class MultipleSchemeError extends Error {
68
+ }
69
+ /**
70
+ * 通过自动拆分算法,给定字根列表,对部件进行拆分
71
+ * @param component - 部件
72
+ * @param rootData - 字根列表
73
+ * @param config - 配置
74
+ * @param classifier - 笔画分类器
75
+ *
76
+ * 如果拆分唯一,则返回拆分结果;否则返回错误
77
+ *
78
+ * @returns 拆分结果
79
+ * @throws NoSchemeError 无拆分方案
80
+ * @throws MultipleSchemeError 多个拆分方案
81
+ */
82
+ export const getComponentScheme = (component, rootData, config, classifier) => {
83
+ const currentRoots = new Set([...config.roots.keys()]);
84
+ const optionalRoots = new Set([...config.optionalRoots.keys()]);
85
+ const requiredRoots = new Set([...currentRoots].filter((x) => !optionalRoots.has(x)));
86
+ if (requiredRoots.has(component.name))
87
+ return {
88
+ strokes: component.glyph.length,
89
+ sequence: [component.name],
90
+ full: [component.name],
91
+ operator: undefined,
92
+ };
93
+ if (config.analysis.serializer === "erbi") {
94
+ const firstStroke = component.glyph[0];
95
+ const secondStroke = component.glyph[1];
96
+ const first = classifier[firstStroke.feature].toString();
97
+ const second = secondStroke
98
+ ? classifier[secondStroke.feature].toString()
99
+ : "0";
100
+ const sequence = [first + second];
101
+ if (component.glyph.length > 2) {
102
+ const lastStroke = component.glyph[component.glyph.length - 1];
103
+ const last = classifier[lastStroke.feature].toString();
104
+ sequence.push(last);
105
+ }
106
+ return {
107
+ strokes: component.glyph.length,
108
+ sequence,
109
+ full: sequence,
110
+ operator: undefined,
111
+ };
112
+ }
113
+ const rootMap = new Map(component.glyph.map((stroke, index) => {
114
+ const binary = 1 << (component.glyph.length - 1 - index);
115
+ return [binary, classifier[stroke.feature].toString()];
116
+ }));
117
+ for (const root of rootData) {
118
+ const binaries = generateSliceBinaries(config.analysis?.degenerator ?? defaultDegenerator, component, root);
119
+ binaries.forEach((binary) => rootMap.set(binary, root.name));
120
+ }
121
+ const roots = Array.from(rootMap.keys()).sort((a, b) => a - b);
122
+ const requiredRootsBinary = new Set([...rootMap].filter(([K, v]) => requiredRoots.has(v)).map(([K, v]) => K));
123
+ const schemeList = generateSchemes(component.glyph.length, roots, requiredRootsBinary);
124
+ if (schemeList.length === 0) {
125
+ return new NoSchemeError();
126
+ }
127
+ const selectResult = select(config, component, schemeList, rootMap, requiredRootsBinary);
128
+ if (selectResult instanceof Error) {
129
+ return selectResult;
130
+ }
131
+ const best = selectResult.find((x) => x.scheme.every((y) => currentRoots.has(rootMap.get(y)) || /\d/.test(rootMap.get(y))));
132
+ let sequence = best.scheme.map((n) => rootMap.get(n));
133
+ return {
134
+ sequence,
135
+ full: [...sequence],
136
+ strokes: component.glyph.length,
137
+ map: rootMap,
138
+ best,
139
+ schemes: selectResult,
140
+ operator: undefined,
141
+ };
142
+ };
143
+ /**
144
+ * 递归渲染一个部件(基本部件或者衍生部件)
145
+ * 如果是基本部件就直接返回,如果是衍生部件则先渲染源字的图形,然后解引用得到这个部件的图形
146
+ *
147
+ * @param component - 部件
148
+ * @param repertoire - 原始字符集
149
+ * @param glyphCache - 部件缓存
150
+ *
151
+ * @returns 部件的 SVG 图形
152
+ * @throws InvalidGlyphError 无法渲染
153
+ */
154
+ export const recursiveRenderComponent = (component, repertoire, glyphCache = new Map(), depth = 0) => {
155
+ if (depth > 100) {
156
+ console.error("Recursion depth exceeded", component);
157
+ return new InvalidGlyphError();
158
+ }
159
+ if (component.type === "basic_component")
160
+ return component.strokes;
161
+ if (component.type === "derived_component") {
162
+ const sourceComponent = repertoire[component.source]?.glyphs?.find?.(isComponent);
163
+ if (sourceComponent === undefined)
164
+ return new InvalidGlyphError();
165
+ const sourceGlyph = recursiveRenderComponent(sourceComponent, repertoire, glyphCache, depth + 1);
166
+ if (sourceGlyph instanceof InvalidGlyphError)
167
+ return sourceGlyph;
168
+ const glyph = [];
169
+ component.strokes.forEach((x) => {
170
+ if (x.feature === "reference") {
171
+ const sourceStroke = sourceGlyph[x.index];
172
+ // 允许指标越界
173
+ if (sourceStroke === undefined)
174
+ return;
175
+ glyph.push(sourceStroke);
176
+ }
177
+ else {
178
+ glyph.push(x);
179
+ }
180
+ });
181
+ return glyph;
182
+ }
183
+ if (component.type === "spliced_component") {
184
+ const glyphs = [];
185
+ for (const name of component.operandList) {
186
+ const sourceComponent = repertoire[name]?.glyphs?.find?.(isComponent);
187
+ if (sourceComponent === undefined)
188
+ return new InvalidGlyphError();
189
+ const sourceGlyph = recursiveRenderComponent(sourceComponent, repertoire, glyphCache, depth + 1);
190
+ if (sourceGlyph instanceof InvalidGlyphError)
191
+ return sourceGlyph;
192
+ const box = getGlyphBoundingBox(sourceGlyph);
193
+ glyphs.push({ strokes: sourceGlyph, box });
194
+ }
195
+ const asCompound = { ...component, type: "compound" };
196
+ return affineMerge(asCompound, glyphs).strokes;
197
+ }
198
+ if (component.type === "identity") {
199
+ const sourceComponent = repertoire[component.source]?.glyphs?.find?.(isComponent);
200
+ if (sourceComponent === undefined)
201
+ return new InvalidGlyphError();
202
+ return recursiveRenderComponent(sourceComponent, repertoire, glyphCache, depth + 1);
203
+ }
204
+ return new InvalidGlyphError();
205
+ };
206
+ /**
207
+ * 把一个基本部件从 SVG 图形出发,先渲染成 Bezier 图形,然后计算拓扑
208
+ *
209
+ * 这样,便于后续的字根认同计算
210
+ */
211
+ export const computeComponent = (name, svgglyph) => {
212
+ const glyph = renderSVGGlyph(svgglyph);
213
+ const topology = findTopology(glyph);
214
+ const cache = { glyph, topology, name };
215
+ return cache;
216
+ };
217
+ /**
218
+ * 将所有的字根都计算成 ComputedComponent
219
+ *
220
+ * @param repertoire - 字符集
221
+ * @param config - 配置
222
+ *
223
+ * @returns 所有计算后字根的列表
224
+ */
225
+ export const renderRootList = (repertoire, elements) => {
226
+ const rootList = new Map();
227
+ for (const root of elements) {
228
+ const glyph = repertoire[root]?.glyph;
229
+ if (glyph === undefined)
230
+ continue;
231
+ if (glyph.type === "basic_component") {
232
+ rootList.set(root, computeComponent(root, glyph.strokes));
233
+ }
234
+ else {
235
+ const rendered = recursiveRenderCompound(glyph, repertoire);
236
+ if (rendered instanceof Error)
237
+ continue;
238
+ rootList.set(root, computeComponent(root, rendered.strokes));
239
+ }
240
+ }
241
+ return rootList;
242
+ };
243
+ /**
244
+ * 对所有部件进行拆分
245
+ *
246
+ * @param repertoire - 字符集
247
+ * @param config - 配置
248
+ *
249
+ * @returns 拆分结果和无法拆分的汉字列表
250
+ */
251
+ export const disassembleComponents = (repertoire, config, characters, components) => {
252
+ const rootMap = renderRootList(repertoire, [
253
+ ...config.roots.keys(),
254
+ ...config.optionalRoots.keys(),
255
+ ]);
256
+ const rootList = [...rootMap.values()];
257
+ const classifier = mergeClassifier(config.analysis?.classifier);
258
+ const result = [];
259
+ const error = [];
260
+ Object.entries(repertoire).forEach(([name, character]) => {
261
+ if (character.glyph?.type !== "basic_component" || !components.has(name))
262
+ return;
263
+ const cache = rootMap.has(name)
264
+ ? rootMap.get(name)
265
+ : computeComponent(name, character.glyph.strokes);
266
+ const scheme = getComponentScheme(cache, rootList, config, classifier);
267
+ if (scheme instanceof Error) {
268
+ error.push(name);
269
+ }
270
+ else {
271
+ result.push([name, scheme]);
272
+ }
273
+ });
274
+ result.sort((a, b) => {
275
+ return a[1].strokes - b[1].strokes;
276
+ });
277
+ return [new Map(result), error];
278
+ };
@@ -0,0 +1,64 @@
1
+ import type { AnalysisConfig } from "./repertoire.js";
2
+ import type { ComponentResults, ComponentAnalysis, CommonAnalysis } from "./component.js";
3
+ import { InvalidGlyphError } from "./component.js";
4
+ import type { Compound, Repertoire, Operator, SVGGlyph, CompoundCharacter, SVGGlyphWithBox } from "./data.js";
5
+ import type { BoundingBox } from "./bezier.js";
6
+ export type CompoundResults = Map<string, CompoundAnalysis | ComponentAnalysis>;
7
+ type PartitionResult = ComponentAnalysis | CompoundAnalysis;
8
+ export type CompoundAnalysis = CompoundBasicAnalysis | CompoundGenuineAnalysis;
9
+ /**
10
+ * 复合体本身是字根字,没有拆分细节
11
+ */
12
+ interface CompoundBasicAnalysis extends CommonAnalysis {
13
+ operator: Operator;
14
+ }
15
+ /**
16
+ * 复合体通过自动拆分算法导出的拆分结果
17
+ */
18
+ interface CompoundGenuineAnalysis extends CompoundBasicAnalysis {
19
+ operandResults: PartitionResult[];
20
+ }
21
+ export declare const getGlyphBoundingBox: (glyph: SVGGlyph) => BoundingBox;
22
+ /**
23
+ * 将复合体递归渲染为 SVG 图形
24
+ *
25
+ * @param compound - 复合体
26
+ * @param repertoire - 原始字符集
27
+ *
28
+ * @returns SVG 图形
29
+ * @throws InvalidGlyphError 无法渲染
30
+ */
31
+ export declare const recursiveRenderCompound: (compound: Compound, repertoire: Repertoire, glyphCache?: Map<string, SVGGlyphWithBox>) => SVGGlyphWithBox | InvalidGlyphError;
32
+ /**
33
+ * 将复合体递归渲染为 SVG 图形
34
+ *
35
+ * @param compound - 复合体
36
+ * @param repertoire - 原始字符集
37
+ *
38
+ * @returns SVG 图形
39
+ * @throws InvalidGlyphError 无法渲染
40
+ */
41
+ export declare const recursiveRenderStrokeSequence: (compound: Compound, repertoire: Repertoire, sequenceCache?: Map<string, string>) => string | InvalidGlyphError;
42
+ /**
43
+ * 对字符集进行拓扑排序,得到复合体的拆分顺序
44
+ *
45
+ * @param repertoire - 字符集
46
+ *
47
+ * @returns 拓扑排序后的复合体
48
+ *
49
+ * @remarks 这个实现目前比较低效,需要改进
50
+ */
51
+ export declare const topologicalSort: (repertoire: Repertoire, required: Set<string>, config: AnalysisConfig) => Map<string, CompoundCharacter>;
52
+ type Serializer = (r: PartitionResult[], g: Compound, c: AnalysisConfig) => CompoundAnalysis;
53
+ export declare const serializerTypes: readonly ["sequential", "zhangma", "zhenma", "snow2", "xkjd", "shouyou", "feihua", "erbi"];
54
+ export type SerializerType = (typeof serializerTypes)[number];
55
+ export declare const serializerMap: Record<SerializerType, Serializer>;
56
+ /**
57
+ * 对复合体进行拆分
58
+ *
59
+ * @param repertoire - 字符集
60
+ * @param config - 配置
61
+ * @param componentResults - 部件拆分结果
62
+ */
63
+ export declare const disassembleCompounds: (repertoire: Repertoire, config: AnalysisConfig, componentResults: ComponentResults, characters: string[], requiredCompounds: Set<string>) => readonly [CompoundResults, string[]];
64
+ export {};