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
package/dist/data.d.ts ADDED
@@ -0,0 +1,195 @@
1
+ import type { BoundingBox } from "./bezier.js";
2
+ import type { Feature } from "./classifier.js";
3
+ export type N1 = [number];
4
+ export type N2 = [number, number];
5
+ export type N3 = [number, number, number];
6
+ export type N6 = [number, number, number, number, number, number];
7
+ /**
8
+ * 一条 SVG 路径命令
9
+ * h, v, c 的含义参见 SVG 规范
10
+ * z 和 c 的含义相同,但是只用于表示平撇、平点、平捺,这是为了区分
11
+ */
12
+ export type Draw = {
13
+ command: "h" | "v";
14
+ parameterList: N1;
15
+ } | {
16
+ command: "c" | "z";
17
+ parameterList: N6;
18
+ } | {
19
+ command: "a";
20
+ parameterList: N1;
21
+ };
22
+ export type Point = N2;
23
+ /**
24
+ * SVG 笔画
25
+ * feature: 笔画的种类
26
+ * start: 笔画的起点
27
+ * curveList: 笔画的命令列表
28
+ */
29
+ export interface SVGStroke {
30
+ feature: Feature;
31
+ start: Point;
32
+ curveList: Draw[];
33
+ }
34
+ /**
35
+ * 引用笔画
36
+ * index: 源字中笔画的索引
37
+ */
38
+ export interface ReferenceStroke {
39
+ feature: "reference";
40
+ index: number;
41
+ }
42
+ /**
43
+ * SVG 字形是一系列 SVG 笔画的列表
44
+ */
45
+ export type SVGGlyph = SVGStroke[];
46
+ export interface SVGGlyphWithBox {
47
+ strokes: SVGGlyph;
48
+ box: BoundingBox;
49
+ }
50
+ /**
51
+ * 广义的笔画,包括 SVG 笔画和引用笔画
52
+ */
53
+ export type Stroke = SVGStroke | ReferenceStroke;
54
+ /**
55
+ * 基本部件 BasicComponent
56
+ * tags: 部件的标签
57
+ * strokes: 部件包含的 SVG 笔画
58
+ */
59
+ export interface BasicComponent {
60
+ type: "basic_component";
61
+ tags?: string[];
62
+ strokes: SVGStroke[];
63
+ }
64
+ /**
65
+ * 派生部件 DerivedComponent
66
+ * tags: 部件的标签
67
+ * source: 部件的源字
68
+ * strokes: 部件包含的 SVG 笔画或引用笔画
69
+ *
70
+ * 引用的笔画的内容需要在渲染时从源字中获取
71
+ */
72
+ export interface DerivedComponent {
73
+ type: "derived_component";
74
+ tags?: string[];
75
+ source: string;
76
+ strokes: Stroke[];
77
+ }
78
+ /**
79
+ * 拼接部件 SplicedComponent
80
+ * 与复合体相同,但作为部件使用
81
+ */
82
+ export interface SplicedComponent extends Omit<Compound, "type"> {
83
+ type: "spliced_component";
84
+ }
85
+ /**
86
+ * 部件,包括基本部件和派生部件
87
+ */
88
+ export type Component = BasicComponent | DerivedComponent | SplicedComponent;
89
+ export declare const operators: readonly ["⿰", "⿱", "⿲", "⿳", "⿴", "⿵", "⿶", "⿷", "⿸", "⿹", "⿺", "⿻", "⿼", "⿽", "⿾", "⿿"];
90
+ /**
91
+ * 结构表示符
92
+ * 例如 ⿰、⿱ 等
93
+ * 符合 Unicode 中的 Ideography Description Characters
94
+ * 参见 https://en.wikipedia.org/wiki/Ideographic_Description_Characters_(Unicode_block)
95
+ */
96
+ export type Operator = (typeof operators)[number];
97
+ /**
98
+ * 一个笔画块
99
+ */
100
+ export interface Block {
101
+ index: number;
102
+ strokes: number;
103
+ }
104
+ /**
105
+ * 复合体的参数
106
+ * gap2: 第二部分复合体和之前的间距
107
+ * scale2: 第二部分复合体的缩放比例
108
+ * gap3: 第三部分复合体和之前的间距
109
+ * scale3: 第三部分复合体的缩放比例
110
+ */
111
+ export interface CompoundParameters {
112
+ gap2?: number;
113
+ scale2?: number;
114
+ gap3?: number;
115
+ scale3?: number;
116
+ }
117
+ /**
118
+ * 复合体 Compound
119
+ * operator: 结构表示符
120
+ * operandList: 部分列表,有可能是两部分,也可能是三部分(对于 ⿲、⿳)
121
+ * tags: 复合体的标签
122
+ * order: 笔画块的顺序
123
+ */
124
+ export interface Compound {
125
+ type: "compound";
126
+ operator: Operator;
127
+ operandList: string[];
128
+ tags?: string[];
129
+ order?: Block[];
130
+ parameters?: CompoundParameters;
131
+ }
132
+ /**
133
+ * 全等 Identity
134
+ * source: 全等的源字
135
+ * tags: 全等的标签
136
+ */
137
+ export interface Identity {
138
+ type: "identity";
139
+ tags?: string[];
140
+ source: string;
141
+ }
142
+ /**
143
+ * 一个字形可以是复合体、部件或全等
144
+ */
145
+ export type Glyph = Component | Compound | Identity;
146
+ /**
147
+ * 字符的读音
148
+ * pinyin: 字符的拼音
149
+ * importance: 读音占总读音的比例,范围为 0 到 100
150
+ */
151
+ export interface Reading {
152
+ pinyin: string;
153
+ importance: number;
154
+ }
155
+ /**
156
+ * 原始字符 PrimitiveCharacter
157
+ * unicode: 字符的 Unicode 编码
158
+ * tygf: 字符在通用规范汉字集中的类型,0 为不存在,1 为一级字,2 为二级字,3 为三级字
159
+ * gb2312: 字符在 GB2312 中的类型,0 为不存在,1 为一级字,2 为二级字
160
+ * name: 字符的别名(当字符是 PUA 时,这个别名会用来在界面上渲染)
161
+ * gf0014_id: 字符在 GF0014-2009 中的编号
162
+ * gf3001_id: 字符在 GF3001-1997 中的编号
163
+ * readings: 字符的读音列表
164
+ * glyphs: 字符的字形列表,其中每一个有可能是基本部件、派生部件或复合体
165
+ * ambiguous: 字符是否有分部歧义
166
+ */
167
+ export interface PrimitiveCharacter {
168
+ unicode: number;
169
+ tygf: 0 | 1 | 2 | 3;
170
+ gb2312: 0 | 1 | 2;
171
+ name: string | null;
172
+ gf0014_id: number | null;
173
+ gf3001_id: number | null;
174
+ readings: Reading[];
175
+ glyphs: Glyph[];
176
+ ambiguous: boolean;
177
+ }
178
+ /**
179
+ * 字符 Character
180
+ * 与原始字符相比,省略了 ambiguous 字段,并且将 glyphs 字段替换为唯一的一个 glyph
181
+ * 此时的 glyph 要么是基本部件,要么是复合体
182
+ */
183
+ export interface Character extends Omit<PrimitiveCharacter, "glyphs" | "ambiguous"> {
184
+ glyph: BasicComponent | Compound;
185
+ }
186
+ export interface ComponentCharacter extends Character {
187
+ glyph: BasicComponent;
188
+ }
189
+ export interface CompoundCharacter extends Character {
190
+ glyph: Compound;
191
+ }
192
+ /** 原始字符集,为字符名称到原始字符的映射 */
193
+ export type PrimitiveRepertoire = Record<string, PrimitiveCharacter>;
194
+ /** 字符集,为字符名称到字符的映射 */
195
+ export type Repertoire = Record<string, Character>;
package/dist/data.js ADDED
@@ -0,0 +1,18 @@
1
+ export const operators = [
2
+ "⿰",
3
+ "⿱",
4
+ "⿲",
5
+ "⿳",
6
+ "⿴",
7
+ "⿵",
8
+ "⿶",
9
+ "⿷",
10
+ "⿸",
11
+ "⿹",
12
+ "⿺",
13
+ "⿻",
14
+ "⿼",
15
+ "⿽",
16
+ "⿾",
17
+ "⿿",
18
+ ];
@@ -0,0 +1,21 @@
1
+ import type { RenderedGlyph } from "./topology.js";
2
+ import type { Degenerator } from "./config.js";
3
+ import type { ComputedComponent } from "./component.js";
4
+ export declare const indicesToBinary: (n: number) => (indices: number[]) => number;
5
+ export declare const binaryToIndices: (n: number) => (binary: number) => number[];
6
+ export declare const defaultDegenerator: Degenerator;
7
+ /**
8
+ * 给定一个部件和一个字根,找出这个部件所有包含这个字根的方式
9
+ * 如果部件不包含这个字根,就返回空列表
10
+ *
11
+ * @param config - 配置
12
+ * @param component - 待拆分部件
13
+ * @param root - 字根
14
+ */
15
+ export declare const generateSliceBinaries: (degenerator: Degenerator, component: ComputedComponent, root: ComputedComponent) => number[];
16
+ /**
17
+ * 字根认同的另一种算法,和上面的算法等价且更简单,但是效率比较差
18
+ * 这个算法是直接计算出一个部件在某个退化映射下的结果,然后和字根的结果比较
19
+ * 这种方式要求穷举一个部件中所有的笔画取幂集,复杂度为 O(2^n),所以目前只用于测试
20
+ */
21
+ export declare const degenerate: (degenerator: Degenerator, glyph: RenderedGlyph) => readonly [("横" | "提" | "竖" | "竖钩" | "撇" | "平撇" | "点" | "平点" | "捺" | "平捺" | "挑捺" | "横钩" | "横撇" | "横折" | "横折钩" | "横斜钩" | "横折提" | "横折折" | "横折弯" | "横撇弯钩" | "横折弯钩" | "横斜弯钩" | "横折折撇" | "横折折折" | "横折折折钩" | "竖提" | "竖折" | "竖弯" | "竖弯左" | "竖弯钩" | "竖折撇" | "竖折折钩" | "竖折折" | "撇点" | "撇折" | "弯钩" | "斜钩" | "撇钩" | "卧钩" | "圈" | "特殊笔画")[], import("./topology.js").Topology];
@@ -0,0 +1,134 @@
1
+ import { isEqual } from "lodash-es";
2
+ import { findTopology } from "./topology.js";
3
+ import { curveLength, isBoundedBy, isCollinear, sortTwoNumbers, } from "./bezier.js";
4
+ export const indicesToBinary = (n) => (indices) => {
5
+ let binaryCode = 0;
6
+ for (const index of indices) {
7
+ binaryCode += 1 << (n - index - 1);
8
+ }
9
+ return binaryCode;
10
+ };
11
+ export const binaryToIndices = (n) => (binary) => {
12
+ const indices = [...Array(n).keys()];
13
+ return indices.filter((index) => binary & (1 << (n - index - 1)));
14
+ };
15
+ export const defaultDegenerator = {
16
+ feature: {
17
+ 提: "横",
18
+ 捺: "点",
19
+ },
20
+ no_cross: false,
21
+ };
22
+ const strokeFeatureEqual = (degenerator, s1, s2) => {
23
+ const feature = degenerator.feature ?? {};
24
+ const d1 = feature[s1] ?? s1;
25
+ const d2 = feature[s2] ?? s2;
26
+ return d1 === d2;
27
+ };
28
+ /**
29
+ * 对于一些特殊的字根,一般性的字根认同规则可能不足以区分它们,需要特殊处理
30
+ * 这里判断了待拆分部件中的某些笔画究竟是不是这个字根
31
+ *
32
+ * @param component - 待拆分部件
33
+ * @param root - 字根
34
+ * @param indices - 笔画索引列表
35
+ */
36
+ const verifySpecialRoots = (component, root, indices) => {
37
+ if (["土", "士"].includes(root.name)) {
38
+ const [i1, _, i3] = indices;
39
+ const upperHeng = component.glyph[i1]?.curveList[0];
40
+ const lowerHeng = component.glyph[i3]?.curveList[0];
41
+ const lowerIsLonger = curveLength(upperHeng) < curveLength(lowerHeng);
42
+ return root.name === "土" ? lowerIsLonger : !lowerIsLonger;
43
+ }
44
+ if (["未", "末"].includes(root.name)) {
45
+ const [i1, i2] = indices;
46
+ const upperHeng = component.glyph[i1]?.curveList[0];
47
+ const lowerHeng = component.glyph[i2]?.curveList[0];
48
+ const lowerIsLonger = curveLength(upperHeng) < curveLength(lowerHeng);
49
+ return root.name === "未" ? lowerIsLonger : !lowerIsLonger;
50
+ }
51
+ if (["口", "囗"].includes(root.name)) {
52
+ if (["囗", "\ue02d"].includes(component.name)) {
53
+ return root.name === "囗";
54
+ }
55
+ const [i1, _, i3] = indices;
56
+ const upperLeft = component.glyph[i1]?.curveList[0]?.controls[0];
57
+ const lowerRight = component.glyph[i3]?.curveList[0]?.controls.at(-1);
58
+ const xrange = [upperLeft[0], lowerRight[0]];
59
+ const yrange = [upperLeft[1], lowerRight[1]];
60
+ const otherStrokes = component.glyph.filter((_, index) => !indices.includes(index));
61
+ const containsStroke = otherStrokes.some((stroke) => isBoundedBy(stroke, xrange, yrange));
62
+ return root.name === "囗" ? containsStroke : !containsStroke;
63
+ }
64
+ if (["\ue087" /* 木无十 */, "\ue43d" /* 全字头 */].includes(root.name)) {
65
+ const [i1] = indices;
66
+ const attachPoint = component.glyph[i1]?.curveList[0]?.controls[0];
67
+ const otherStrokes = component.glyph.filter((_, index) => !indices.includes(index));
68
+ const otherCurves = otherStrokes.flatMap((s) => s.curveList);
69
+ const pieAndNaIsSeparated = otherCurves.some((x) => x.type === "linear" &&
70
+ isCollinear(x.controls[0], x.controls[1], attachPoint));
71
+ return root.name === "\ue087" ? pieAndNaIsSeparated : !pieAndNaIsSeparated;
72
+ }
73
+ return true;
74
+ };
75
+ /**
76
+ * 给定一个部件和一个字根,找出这个部件所有包含这个字根的方式
77
+ * 如果部件不包含这个字根,就返回空列表
78
+ *
79
+ * @param config - 配置
80
+ * @param component - 待拆分部件
81
+ * @param root - 字根
82
+ */
83
+ export const generateSliceBinaries = (degenerator, component, root) => {
84
+ const { glyph: cglyph, topology: ctopology } = component;
85
+ const { glyph: rglyph, topology: rtopology } = root;
86
+ if (cglyph.length < rglyph.length)
87
+ return [];
88
+ let queue = [[]];
89
+ for (const [rIndex, rStroke] of rglyph.entries()) {
90
+ const rStrokeTopology = rtopology.matrix[rIndex]?.slice(0, rIndex);
91
+ const end = cglyph.length - rglyph.length + rIndex + 1;
92
+ for (let _ = queue.length; _ !== 0; --_) {
93
+ const indexList = queue.shift();
94
+ const start = indexList.length ? indexList.at(-1) + 1 : 0;
95
+ for (const [cIndex, cStroke] of cglyph.slice(start, end).entries()) {
96
+ if (!strokeFeatureEqual(degenerator, cStroke.feature, rStroke.feature))
97
+ continue;
98
+ const realIndex = cIndex + start;
99
+ const cStrokeTopology = ctopology.matrix[realIndex]?.filter((_, i) => indexList.includes(i));
100
+ if (!isEqual(cStrokeTopology, rStrokeTopology))
101
+ continue;
102
+ queue.push(indexList.concat(realIndex));
103
+ }
104
+ }
105
+ if (!queue)
106
+ return [];
107
+ }
108
+ if (degenerator.no_cross) {
109
+ const allindices = [...Array(cglyph.length).keys()];
110
+ queue = queue.filter((indices) => {
111
+ const others = allindices.filter((x) => !indices.includes(x));
112
+ const allCombinations = indices.flatMap((x) => others.map((y) => sortTwoNumbers([x, y])));
113
+ return allCombinations.every(([x, y]) => {
114
+ const relation = ctopology.matrix[y]?.[x];
115
+ return relation.every((cr) => cr.type !== "交");
116
+ });
117
+ });
118
+ }
119
+ return queue
120
+ .filter((x) => verifySpecialRoots(component, root, x))
121
+ .map(indicesToBinary(cglyph.length));
122
+ };
123
+ /**
124
+ * 字根认同的另一种算法,和上面的算法等价且更简单,但是效率比较差
125
+ * 这个算法是直接计算出一个部件在某个退化映射下的结果,然后和字根的结果比较
126
+ * 这种方式要求穷举一个部件中所有的笔画取幂集,复杂度为 O(2^n),所以目前只用于测试
127
+ */
128
+ export const degenerate = (degenerator, glyph) => {
129
+ const featureMap = degenerator.feature ?? {};
130
+ return [
131
+ glyph.map((x) => x.feature).map((x) => featureMap[x] || x),
132
+ findTopology(glyph),
133
+ ];
134
+ };
@@ -0,0 +1,56 @@
1
+ import type { Algebra, Rule } from "./config.js";
2
+ import type { CharacterResult } from "./assembly.js";
3
+ export interface Extra {
4
+ rootSequence: Map<string, number[]>;
5
+ }
6
+ interface Base {
7
+ type: string;
8
+ }
9
+ export declare const pronunciationElementTypes: readonly ["声母", "双拼声母", "韵母", "双拼韵母", "声调", "首字母", "末字母"];
10
+ export type PronunciationElementTypes = (typeof pronunciationElementTypes)[number];
11
+ export declare const defaultAlgebra: Record<PronunciationElementTypes, Rule[]>;
12
+ export declare const applyRules: (name: string, rules: Rule[], syllable: string) => string;
13
+ interface This extends Base {
14
+ type: "汉字";
15
+ }
16
+ interface Constant extends Base {
17
+ type: "固定";
18
+ key: string;
19
+ }
20
+ interface Structure extends Base {
21
+ type: "结构";
22
+ }
23
+ interface Pronunciation extends Base {
24
+ type: "字音";
25
+ subtype: PronunciationElementTypes;
26
+ }
27
+ interface Root extends Base {
28
+ type: "字根";
29
+ rootIndex: number;
30
+ }
31
+ interface Stroke extends Base {
32
+ type: "笔画";
33
+ rootIndex: number;
34
+ strokeIndex: number;
35
+ }
36
+ interface StrokePair extends Base {
37
+ type: "二笔";
38
+ rootIndex: number;
39
+ strokeIndex: number;
40
+ }
41
+ interface Custom extends Base {
42
+ type: "自定义";
43
+ subtype: string;
44
+ rootIndex: number;
45
+ }
46
+ interface Special extends Base {
47
+ type: "特殊";
48
+ subtype: string;
49
+ }
50
+ export type CodableObject = This | Constant | Structure | Pronunciation | Root | Stroke | StrokePair | Custom | Special;
51
+ export declare const renderName: (object: CodableObject) => string;
52
+ export declare const renderList: (object: CodableObject) => (string | number)[];
53
+ export declare const parseList: (value: (string | number)[]) => CodableObject;
54
+ export declare const algebraCache: Map<string, string>;
55
+ export declare const findElement: (object: CodableObject, result: CharacterResult, algebra: Algebra, extra: Extra) => string | undefined;
56
+ export {};
@@ -0,0 +1,225 @@
1
+ export const pronunciationElementTypes = [
2
+ "声母",
3
+ "双拼声母",
4
+ "韵母",
5
+ "双拼韵母",
6
+ "声调",
7
+ "首字母",
8
+ "末字母",
9
+ ];
10
+ const r = String.raw;
11
+ export const defaultAlgebra = {
12
+ 声母: [
13
+ { type: "xform", from: "^([bpmfdtnlgkhjqxzcsr]h?|^).+$", to: "$1" },
14
+ { type: "xform", from: "^$", to: "0" },
15
+ ],
16
+ 韵母: [
17
+ // 恢复 v
18
+ { type: "xform", from: "^([jqxy])u", to: "$1v" },
19
+ // 恢复合、齐、撮口的韵母形式
20
+ { type: "xform", from: "yv", to: "v" },
21
+ { type: "xform", from: "yi?", to: "i" },
22
+ { type: "xform", from: "wu?", to: "u" },
23
+ // 恢复 iou, uei, uen
24
+ { type: "xform", from: "iu", to: "iou" },
25
+ { type: "xform", from: "u([in])", to: "ue$1" },
26
+ { type: "xform", from: r `^.*?([aeêiouv].*|m|ng?)\d$`, to: "$1" },
27
+ ],
28
+ 双拼声母: [
29
+ { type: "xform", from: "^([bpmfdtnlgkhjqxzcsryw]h?|^).+$", to: "$1" },
30
+ { type: "xform", from: "^$", to: "0" },
31
+ ],
32
+ 双拼韵母: [{ type: "xform", from: r `^.*?([aeêiouv].*|m|ng?)\d$`, to: "$1" }],
33
+ 声调: [{ type: "xform", from: r `.+(\d)`, to: "$1" }],
34
+ 首字母: [{ type: "xform", from: r `^(.).+`, to: "$1" }],
35
+ 末字母: [{ type: "xform", from: r `.*(.)\d`, to: "$1" }],
36
+ };
37
+ export const applyRules = (name, rules, syllable) => {
38
+ let result = syllable;
39
+ for (const { type, from, to } of rules) {
40
+ switch (type) {
41
+ case "xform":
42
+ result = result.replace(new RegExp(from), to);
43
+ break;
44
+ case "xlit":
45
+ result = result.replace(new RegExp(`[${from}]`), (s) => {
46
+ const index = from.indexOf(s);
47
+ return to[index] || "";
48
+ });
49
+ break;
50
+ }
51
+ }
52
+ return `${name}-${result}`;
53
+ };
54
+ export const renderName = (object) => {
55
+ switch (object.type) {
56
+ case "汉字":
57
+ case "结构":
58
+ return object.type;
59
+ case "固定":
60
+ return object.key;
61
+ case "字音":
62
+ return object.subtype;
63
+ case "字根":
64
+ return `根 ${object.rootIndex}`;
65
+ case "笔画":
66
+ return `根 ${object.rootIndex} 笔 ${object.strokeIndex}`;
67
+ case "二笔":
68
+ return `根 ${object.rootIndex} 笔 (${object.strokeIndex * 2 - Math.sign(object.strokeIndex)}, ${object.strokeIndex * 2})`;
69
+ case "自定义":
70
+ return `${object.subtype} ${object.rootIndex}`;
71
+ case "特殊":
72
+ return object.subtype;
73
+ }
74
+ };
75
+ export const renderList = (object) => {
76
+ const list = [object.type];
77
+ switch (object.type) {
78
+ case "固定":
79
+ return [...list, object.key];
80
+ case "字音":
81
+ return [...list, object.subtype];
82
+ case "字根":
83
+ return [...list, object.rootIndex];
84
+ case "笔画":
85
+ return [...list, object.rootIndex, object.strokeIndex];
86
+ case "二笔":
87
+ return [...list, object.rootIndex, object.strokeIndex];
88
+ case "自定义":
89
+ return [...list, object.subtype, object.rootIndex];
90
+ case "特殊":
91
+ return [...list, object.subtype];
92
+ }
93
+ return list;
94
+ };
95
+ export const parseList = (value) => {
96
+ const type = value[0];
97
+ switch (type) {
98
+ case "固定":
99
+ return { type, key: value[1] };
100
+ case "字音":
101
+ return { type, subtype: value[1] };
102
+ case "字根":
103
+ return { type, rootIndex: value[1] };
104
+ case "笔画":
105
+ return {
106
+ type,
107
+ rootIndex: value[1],
108
+ strokeIndex: value[2],
109
+ };
110
+ case "二笔":
111
+ return {
112
+ type,
113
+ rootIndex: value[1],
114
+ strokeIndex: value[2],
115
+ };
116
+ case "自定义":
117
+ return {
118
+ type,
119
+ subtype: value[1],
120
+ rootIndex: value[2],
121
+ };
122
+ case "特殊":
123
+ return { type, subtype: value[1] };
124
+ }
125
+ return { type };
126
+ };
127
+ function signedIndex(a, i) {
128
+ return i >= 0 ? a[i - 1] : a[a.length + i];
129
+ }
130
+ export const algebraCache = new Map();
131
+ // 张码补码
132
+ const zhangmaSupplement = (result, { rootSequence }) => {
133
+ // 在补码时,竖钩视为竖,横折弯钩视为折
134
+ const coalesce = (n) => {
135
+ if (n === 6)
136
+ return 2;
137
+ if (n === 7)
138
+ return 5;
139
+ return n;
140
+ };
141
+ const { sequence } = result;
142
+ const first = rootSequence.get(sequence[0]);
143
+ const last = rootSequence.get(sequence[sequence.length - 1]);
144
+ const firstfirst = coalesce(first[0]);
145
+ const lastlast = coalesce(last.at(-1));
146
+ // 单笔画补码用 61 表示
147
+ if (sequence.length === 1 && first.length === 1) {
148
+ return "61";
149
+ }
150
+ // 并型和左下围,首 + 末
151
+ // 其他情况,末 + 首
152
+ return result.operator && /[⿰⿲⿺]/.test(result.operator)
153
+ ? `${firstfirst}${lastlast}`
154
+ : `${lastlast}${firstfirst}`;
155
+ };
156
+ // 张码判断是否为准码元
157
+ const zhangmaPseudoRoot = (result) => {
158
+ if (!("best" in result))
159
+ return "0";
160
+ const { best } = result;
161
+ return best.scheme.length === 2 && best.evaluation.get("能连不交")
162
+ ? "1"
163
+ : "0";
164
+ };
165
+ const specialElementHandlers = {
166
+ 张码补码: zhangmaSupplement,
167
+ 张码准码元: zhangmaPseudoRoot,
168
+ };
169
+ export const findElement = (object, result, algebra, extra) => {
170
+ const { pinyin, sequence } = result;
171
+ let root;
172
+ let strokes;
173
+ let name;
174
+ let hash;
175
+ let rules;
176
+ let transformed;
177
+ let stroke1;
178
+ let stroke2;
179
+ switch (object.type) {
180
+ case "汉字":
181
+ return result.char;
182
+ case "固定":
183
+ return object.key;
184
+ case "结构":
185
+ if ("operator" in result) {
186
+ return result.operator;
187
+ }
188
+ return undefined;
189
+ case "字音":
190
+ name = object.subtype;
191
+ hash = `${name}:${pinyin}`;
192
+ if (algebraCache.has(hash))
193
+ return algebraCache.get(hash);
194
+ rules = defaultAlgebra[name] || algebra?.[name];
195
+ transformed = applyRules(name, rules, pinyin);
196
+ algebraCache.set(hash, transformed);
197
+ return transformed;
198
+ case "字根":
199
+ return signedIndex(sequence, object.rootIndex);
200
+ case "笔画":
201
+ case "二笔":
202
+ root = signedIndex(sequence, object.rootIndex);
203
+ if (root === undefined)
204
+ return undefined;
205
+ strokes = extra.rootSequence.get(root);
206
+ if (strokes === undefined) {
207
+ if (Math.abs(object.strokeIndex) === 1)
208
+ return root;
209
+ return undefined;
210
+ }
211
+ if (object.type === "笔画") {
212
+ const number = signedIndex(strokes, object.strokeIndex);
213
+ return number?.toString();
214
+ }
215
+ stroke1 = signedIndex(strokes, object.strokeIndex * 2 - Math.sign(object.strokeIndex));
216
+ if (stroke1 === undefined)
217
+ return undefined;
218
+ stroke2 = signedIndex(strokes, object.strokeIndex * 2);
219
+ return [stroke1, stroke2 ?? 0].join("");
220
+ case "自定义":
221
+ return signedIndex(result.custom[object.subtype] ?? [], object.rootIndex);
222
+ case "特殊":
223
+ return specialElementHandlers[object.subtype]?.(result, extra);
224
+ }
225
+ };
@@ -0,0 +1,21 @@
1
+ export interface EquivalenceData {
2
+ user: string;
3
+ model: string;
4
+ data: (Pair & {
5
+ time: number;
6
+ })[];
7
+ }
8
+ type Relation<T> = (v1: T, v2: T) => boolean;
9
+ interface Model<T> {
10
+ group: Set<T>;
11
+ relation: Relation<T>;
12
+ }
13
+ export declare function partition<T>({ group, relation }: Model<T>): Set<T>[];
14
+ export interface Pair {
15
+ initial: number;
16
+ final: number;
17
+ }
18
+ export declare const distance: (p: Pair) => number;
19
+ export declare const displacement: (p: Pair) => readonly [number, number];
20
+ export declare const 手机五行七列: Model<Pair>;
21
+ export {};