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,8 @@
1
+ import type { Compound, SVGGlyphWithBox } from "./data.js";
2
+ /**
3
+ * 给定复合体数据和各部分渲染后的 SVG 图形,返回合并后的 SVG 图形
4
+ * @param compound - 复合体数据
5
+ * @param glyphList - 各部分渲染后的 SVG 图形
6
+ * @returns 合并后的 SVG 图形
7
+ */
8
+ export declare function affineMerge(compound: Compound, glyphList: SVGGlyphWithBox[]): SVGGlyphWithBox;
package/dist/affine.js ADDED
@@ -0,0 +1,153 @@
1
+ import { add } from "./mathjs.js";
2
+ import { cloneDeep } from "lodash-es";
3
+ class Affine {
4
+ xscale;
5
+ yscale;
6
+ translate;
7
+ constructor(xscale, yscale, translate = [0, 0]) {
8
+ this.xscale = xscale;
9
+ this.yscale = yscale;
10
+ this.translate = translate;
11
+ }
12
+ transformDraw(draw) {
13
+ const next = cloneDeep(draw);
14
+ switch (next.command) {
15
+ case "h":
16
+ next.parameterList[0] *= this.xscale;
17
+ break;
18
+ case "v":
19
+ next.parameterList[0] *= this.yscale;
20
+ break;
21
+ case "c":
22
+ case "z":
23
+ for (const index of [0, 2, 4]) {
24
+ next.parameterList[index] *= this.xscale;
25
+ next.parameterList[index + 1] *= this.yscale;
26
+ }
27
+ break;
28
+ }
29
+ return next;
30
+ }
31
+ transformSVGStroke(stroke) {
32
+ const [x, y] = stroke.start;
33
+ const start = add([x * this.xscale, y * this.yscale], this.translate);
34
+ const next = {
35
+ ...stroke,
36
+ start,
37
+ curveList: stroke.curveList.map((c) => this.transformDraw(c)),
38
+ };
39
+ return next;
40
+ }
41
+ transformSVGGlyph(glyph) {
42
+ return glyph.map((x) => this.transformSVGStroke(x));
43
+ }
44
+ }
45
+ const id = new Affine(1, 1);
46
+ const left = new Affine(0.5, 1);
47
+ const right = new Affine(0.5, 1, [50, 0]);
48
+ const top = new Affine(1, 0.5);
49
+ const bottom = new Affine(1, 0.5, [0, 50]);
50
+ const leftThird = new Affine(0.33, 1);
51
+ const centerThird = new Affine(0.33, 1, [33, 0]);
52
+ const rightThird = new Affine(0.33, 1, [66, 0]);
53
+ const topThird = new Affine(1, 0.33);
54
+ const middleThird = new Affine(1, 0.33, [0, 33]);
55
+ const bottomThird = new Affine(1, 0.33, [0, 66]);
56
+ const affineMap = {
57
+ "⿰": [left, right],
58
+ "⿱": [top, bottom],
59
+ "⿲": [leftThird, centerThird, rightThird],
60
+ "⿳": [topThird, middleThird, bottomThird],
61
+ "⿴": [id, new Affine(0.5, 0.5, [25, 25])],
62
+ "⿵": [id, new Affine(0.5, 0.5, [25, 40])],
63
+ "⿶": [id, new Affine(0.5, 0.5, [25, 10])],
64
+ "⿷": [id, new Affine(0.5, 0.5, [40, 25])],
65
+ "⿸": [id, new Affine(0.5, 0.5, [40, 40])],
66
+ "⿹": [id, new Affine(0.5, 0.5, [10, 40])],
67
+ "⿺": [id, new Affine(0.5, 0.5, [40, 10])],
68
+ "⿼": [id, new Affine(0.5, 0.5, [10, 25])],
69
+ "⿽": [id, new Affine(0.5, 0.5, [10, 10])],
70
+ "⿻": [id, id],
71
+ "⿾": [id],
72
+ "⿿": [id],
73
+ };
74
+ /**
75
+ * 给定复合体数据和各部分渲染后的 SVG 图形,返回合并后的 SVG 图形
76
+ * @param compound - 复合体数据
77
+ * @param glyphList - 各部分渲染后的 SVG 图形
78
+ * @returns 合并后的 SVG 图形
79
+ */
80
+ export function affineMerge(compound, glyphList) {
81
+ const { operator, order, parameters } = compound;
82
+ const transformedGlyphs = [];
83
+ const boundingBox = { x: [0, 100], y: [0, 100] };
84
+ if (["⿰", "⿲", "⿱", "⿳"].includes(operator)) {
85
+ // 上下、上中下、左右、左中右,直接拼接
86
+ let mainAxisOffset = 0;
87
+ const isLR = ["⿰", "⿲"].includes(operator);
88
+ for (const [index, { strokes, box }] of glyphList.entries()) {
89
+ const mainAxisLength = isLR ? box.x[1] - box.x[0] : box.y[1] - box.y[0];
90
+ if (index === 0) {
91
+ transformedGlyphs.push(structuredClone(strokes));
92
+ boundingBox.x = structuredClone(box.x);
93
+ boundingBox.y = structuredClone(box.y);
94
+ mainAxisOffset = isLR ? box.x[1] : box.y[1];
95
+ continue;
96
+ }
97
+ let gap = 20;
98
+ if (index === 1 && parameters?.gap2 !== undefined) {
99
+ gap = parameters.gap2;
100
+ }
101
+ else if (index === 2 && parameters?.gap3 !== undefined) {
102
+ gap = parameters.gap3;
103
+ }
104
+ mainAxisOffset += gap;
105
+ const increase = gap + mainAxisLength;
106
+ let affine;
107
+ if (isLR) {
108
+ affine = new Affine(1, 1, [mainAxisOffset - box.x[0], 0]);
109
+ boundingBox.x[1] += increase;
110
+ boundingBox.y[0] = Math.min(boundingBox.y[0], box.y[0]);
111
+ boundingBox.y[1] = Math.max(boundingBox.y[1], box.y[1]);
112
+ }
113
+ else {
114
+ affine = new Affine(1, 1, [0, mainAxisOffset - box.y[0]]);
115
+ boundingBox.y[1] += increase;
116
+ boundingBox.x[0] = Math.min(boundingBox.x[0], box.x[0]);
117
+ boundingBox.x[1] = Math.max(boundingBox.x[1], box.x[1]);
118
+ }
119
+ transformedGlyphs.push(affine.transformSVGGlyph(strokes));
120
+ mainAxisOffset += mainAxisLength;
121
+ }
122
+ }
123
+ else {
124
+ // 包围或结构,暂时还没有优化拼接的算法,用原来的仿射变换算法
125
+ for (const [index, affine] of affineMap[operator].entries()) {
126
+ const transformed = affine.transformSVGGlyph(glyphList[index].strokes);
127
+ transformedGlyphs.push(transformed);
128
+ }
129
+ }
130
+ const result = [];
131
+ if (order === undefined) {
132
+ result.push(...transformedGlyphs.flat());
133
+ }
134
+ else {
135
+ for (const { index, strokes } of order) {
136
+ const glyph = transformedGlyphs[index];
137
+ if (glyph === undefined)
138
+ continue;
139
+ if (strokes === 0) {
140
+ result.push(...glyph);
141
+ }
142
+ else {
143
+ result.push(...glyph.slice(0, strokes));
144
+ transformedGlyphs[index] = glyph.slice(strokes);
145
+ }
146
+ }
147
+ }
148
+ const merged = {
149
+ strokes: result,
150
+ box: boundingBox,
151
+ };
152
+ return merged;
153
+ }
@@ -0,0 +1,63 @@
1
+ import { type Algebra, type EncoderConfig, type Keyboard } from "./config.js";
2
+ import type { ComponentAnalysis } from "./component.js";
3
+ import type { CompoundAnalysis } from "./compound.js";
4
+ import type { Repertoire } from "./data.js";
5
+ import type { AnalysisResult } from "./repertoire.js";
6
+ import type { Dictionary, CustomElementMap } from "./utils.js";
7
+ export declare const getPriorityMap: (priorityShortCodes: [string, string, number][]) => Map<string, number>;
8
+ /**
9
+ * 代表了一个有字音、有字形的汉字的中间结果
10
+ * 由拆分结果 [`ComponentResult`](#componentresult) 或 [`CompoundResult`](#compoundresult) 与字音组成
11
+ */
12
+ export type CharacterResult = (ComponentAnalysis | CompoundAnalysis) & {
13
+ char: string;
14
+ pinyin: string;
15
+ frequency: number;
16
+ custom: Record<string, string[]>;
17
+ };
18
+ export type IndexedElement = string | {
19
+ element: string;
20
+ index: number;
21
+ };
22
+ export interface Assembly {
23
+ name: string;
24
+ pinyin_list: string[];
25
+ sequence: IndexedElement[];
26
+ frequency: number;
27
+ level?: number;
28
+ }
29
+ export type AssemblyResult = Assembly[];
30
+ interface AssembleConfig {
31
+ encoder: EncoderConfig;
32
+ keyboard: Keyboard;
33
+ algebra: Algebra;
34
+ priority: [string, string, number][];
35
+ }
36
+ /**
37
+ * 给定一个拆分结果,返回所有可能的编码
38
+ *
39
+ * @param repertoire - 字符集
40
+ * @param config - 配置
41
+ * @param characters - 需要编码的汉字列表
42
+ * @param analysisResult - 分析结果
43
+ *
44
+ * @returns 组装结果
45
+ */
46
+ export declare const assemble: (repertoire: Repertoire, config: AssembleConfig, characters: string[], dictionary: Dictionary, adaptedFrequency: Map<string, number>, analysisResult: AnalysisResult, customElements: Record<string, CustomElementMap>) => AssemblyResult;
47
+ export declare const stringifySequence: (result: AssemblyResult) => {
48
+ sequence: string;
49
+ name: string;
50
+ pinyin_list: string[];
51
+ frequency: number;
52
+ level?: number;
53
+ }[];
54
+ export declare const summarize: (elements: (IndexedElement | undefined)[]) => string;
55
+ export interface DictEntry {
56
+ name: string;
57
+ full: string;
58
+ full_rank: number;
59
+ short: string;
60
+ short_rank: number;
61
+ }
62
+ export type EncodeResult = DictEntry[];
63
+ export {};
@@ -0,0 +1,286 @@
1
+ import { isMerge, } from "./config.js";
2
+ import { algebraCache, findElement } from "./element.js";
3
+ export const getPriorityMap = (priorityShortCodes) => {
4
+ return new Map(priorityShortCodes.map(([word, pinyin_list, level]) => {
5
+ const hash = `${word}-${pinyin_list}`;
6
+ return [hash, level];
7
+ }));
8
+ };
9
+ const table = {
10
+ 是: (t, v) => t === v,
11
+ 不是: (t, v) => t !== v,
12
+ 匹配: (t, v) => t !== undefined && new RegExp(v).test(t),
13
+ 不匹配: (t, v) => t !== undefined && !new RegExp(v).test(t),
14
+ 编码匹配: (t, v, m) => t !== undefined && new RegExp(v).test(m[t]),
15
+ 编码不匹配: (t, v, m) => t !== undefined && !new RegExp(v).test(m[t]),
16
+ 存在: (t) => t !== undefined,
17
+ 不存在: (t) => t === undefined,
18
+ };
19
+ /**
20
+ * 给定一个条件,判断是否满足
21
+ *
22
+ * @param condition - 条件
23
+ * @param result - 中间结果
24
+ * @param config - 配置
25
+ * @param extra - 额外信息
26
+ * @param totalMapping - 映射
27
+ */
28
+ const satisfy = (condition, result, config, extra, totalMapping) => {
29
+ const { object, operator } = condition;
30
+ const target = findElement(object, result, config, extra);
31
+ const fn = table[operator];
32
+ if ("value" in condition) {
33
+ return fn(target, condition.value, totalMapping);
34
+ }
35
+ return fn(target, null, totalMapping);
36
+ };
37
+ const compile = (keyboard, encoder, algebra) => {
38
+ const { mapping } = keyboard;
39
+ const alphabet = keyboard.alphabet ?? "";
40
+ const totalMapping = {};
41
+ return (result, data, extra) => {
42
+ let node = "s0";
43
+ const codes = [];
44
+ while (node) {
45
+ if (node.startsWith("s")) {
46
+ const source = encoder.sources[node];
47
+ const { object, next, index } = source;
48
+ if (node !== "s0") {
49
+ const element = findElement(object, result, algebra, extra);
50
+ // 检查元素或键位是否有效
51
+ if (element === undefined) {
52
+ node = next;
53
+ continue;
54
+ }
55
+ // 如果是固定编码,直接加入
56
+ if (element.length === 1 && alphabet.includes(element)) {
57
+ codes.push(element);
58
+ node = next;
59
+ continue;
60
+ }
61
+ let mapped = mapping[element];
62
+ let groupedElement = element;
63
+ if (mapped === undefined) {
64
+ node = next;
65
+ continue;
66
+ }
67
+ while (isMerge(mapped)) {
68
+ groupedElement = mapped.element;
69
+ mapped = mapping[mapped.element];
70
+ }
71
+ if (index === undefined) {
72
+ // 如果没有定义指标,就是全取
73
+ for (const [index, key] of Array.from(mapped).entries()) {
74
+ codes.push(
75
+ // typeof key === "string"
76
+ // ? { element: groupedElement, index }
77
+ // : key,
78
+ { element: groupedElement, index });
79
+ }
80
+ }
81
+ else {
82
+ // 检查指标是否有效
83
+ const key = mapped[index];
84
+ if (key !== undefined) {
85
+ codes.push(
86
+ // typeof key === "string"
87
+ // ? { element: groupedElement, index }
88
+ // : key,
89
+ { element: groupedElement, index });
90
+ }
91
+ }
92
+ }
93
+ node = next;
94
+ }
95
+ else {
96
+ const condition = encoder.conditions[node];
97
+ if (satisfy(condition, result, algebra, extra, totalMapping)) {
98
+ node = condition.positive;
99
+ }
100
+ else {
101
+ node = condition.negative;
102
+ }
103
+ }
104
+ }
105
+ return codes.slice(0, encoder.max_length ?? codes.length);
106
+ };
107
+ };
108
+ const signedIndex = (elements, index) => {
109
+ const order = index.codePointAt(0) - "a".codePointAt(0);
110
+ const signedOrder = order < 13 ? order : order - 26;
111
+ return elements.at(signedOrder);
112
+ };
113
+ const gather = (totalElements, rules) => {
114
+ const result = [];
115
+ let matched = false;
116
+ for (const rule of rules) {
117
+ if ("length_equal" in rule) {
118
+ matched = totalElements.length === rule.length_equal;
119
+ }
120
+ else if ("length_in_range" in rule) {
121
+ matched =
122
+ totalElements.length >= rule.length_in_range[0] &&
123
+ totalElements.length <= rule.length_in_range[1];
124
+ }
125
+ if (matched) {
126
+ const tokens = Array.from(rule.formula);
127
+ for (let i = 0; i < tokens.length; i = i + 2) {
128
+ const charIndex = tokens[i].toLowerCase();
129
+ const elementIndex = tokens[i + 1];
130
+ const elements = signedIndex(totalElements, charIndex);
131
+ if (elements === undefined)
132
+ continue;
133
+ const element = signedIndex(elements, elementIndex);
134
+ if (element === undefined)
135
+ continue;
136
+ result.push(element);
137
+ }
138
+ break;
139
+ }
140
+ }
141
+ if (matched)
142
+ return result;
143
+ };
144
+ /**
145
+ * 给定一个拆分结果,返回所有可能的编码
146
+ *
147
+ * @param repertoire - 字符集
148
+ * @param config - 配置
149
+ * @param characters - 需要编码的汉字列表
150
+ * @param analysisResult - 分析结果
151
+ *
152
+ * @returns 组装结果
153
+ */
154
+ export const assemble = (repertoire, config, characters, dictionary, adaptedFrequency, analysisResult, customElements) => {
155
+ const { customized, compoundResults } = analysisResult;
156
+ const extra = { rootSequence: analysisResult.rootSequence };
157
+ const func = compile(config.keyboard, config.encoder, config.algebra);
158
+ const result = [];
159
+ algebraCache.clear();
160
+ const characterCache = new Map();
161
+ const customLookup = (character) => Object.fromEntries(Object.entries(customElements).map(([name, map]) => {
162
+ return [name, (map[character] ?? []).map((x) => `${name}-${x}`)];
163
+ }));
164
+ // 一字词
165
+ for (const character of characters) {
166
+ const frequency = adaptedFrequency.get(character) ?? 0;
167
+ // TODO: 支持多个拆分结果
168
+ const shapeInfo = customized.get(character) || compoundResults.get(character);
169
+ if (shapeInfo === undefined)
170
+ continue;
171
+ const final = [];
172
+ if (character in repertoire === false)
173
+ continue;
174
+ const readings = [...repertoire[character].readings];
175
+ if (readings.length === 0) {
176
+ readings.push({ pinyin: "", importance: 100 });
177
+ }
178
+ for (const reading of readings) {
179
+ const result = {
180
+ char: character,
181
+ pinyin: reading.pinyin,
182
+ frequency: Math.round(frequency * (reading.importance / 100)),
183
+ ...shapeInfo,
184
+ custom: customLookup(character),
185
+ };
186
+ const elements = func(result, repertoire, extra);
187
+ characterCache.set(`${character}:${reading.pinyin}`, elements);
188
+ const summary = summarize(elements);
189
+ let isDuplicated = false;
190
+ for (const previous of final) {
191
+ if (summarize(previous.sequence) === summary) {
192
+ previous.frequency += result.frequency;
193
+ previous.pinyin_list.push(reading.pinyin);
194
+ isDuplicated = true;
195
+ break;
196
+ }
197
+ }
198
+ if (!isDuplicated) {
199
+ final.push({
200
+ name: character,
201
+ sequence: elements,
202
+ frequency: result.frequency,
203
+ pinyin_list: [reading.pinyin],
204
+ });
205
+ }
206
+ }
207
+ result.push(...final);
208
+ }
209
+ const rules = config.encoder.rules;
210
+ if (!rules)
211
+ return result;
212
+ const knownWords = new Set();
213
+ // 多字词
214
+ for (const [word, pinyin] of dictionary) {
215
+ const frequency = adaptedFrequency.get(word) ?? 0;
216
+ const characters = Array.from(word);
217
+ const syllables = pinyin.split(" ");
218
+ let valid = true;
219
+ const totalElements = [];
220
+ for (const [i, character] of characters.entries()) {
221
+ const pinyin = syllables[i];
222
+ // 复用已有的编码
223
+ const hash = `${character}:${pinyin}`;
224
+ let elements = characterCache.get(hash);
225
+ if (elements !== undefined) {
226
+ totalElements.push(elements);
227
+ continue;
228
+ }
229
+ const shapeInfo = customized.get(character) || compoundResults.get(character);
230
+ if (shapeInfo === undefined) {
231
+ valid = false;
232
+ break;
233
+ }
234
+ const result = {
235
+ char: character,
236
+ pinyin,
237
+ frequency,
238
+ ...shapeInfo,
239
+ custom: customLookup(character),
240
+ };
241
+ elements = func(result, repertoire, extra);
242
+ totalElements.push(elements);
243
+ }
244
+ if (!valid)
245
+ continue;
246
+ const wordElements = gather(totalElements, rules);
247
+ if (wordElements === undefined)
248
+ continue;
249
+ const hash = `${word}:${summarize(wordElements)}`;
250
+ if (knownWords.has(hash))
251
+ continue;
252
+ // 多音的多字词数量很少,所以不再计算分频比例,直接设为 100
253
+ result.push({
254
+ name: word,
255
+ sequence: wordElements,
256
+ frequency,
257
+ pinyin_list: [pinyin],
258
+ });
259
+ knownWords.add(hash);
260
+ }
261
+ const priorityMap = getPriorityMap(config.priority);
262
+ return result.map((x) => {
263
+ const hash = `${x.name}-${x.pinyin_list.join(",")}`;
264
+ const level = priorityMap.get(hash);
265
+ return level !== undefined ? { ...x, level } : x;
266
+ });
267
+ };
268
+ export const stringifySequence = (result) => {
269
+ return result.map((x) => {
270
+ return { ...x, sequence: summarize(x.sequence) };
271
+ });
272
+ };
273
+ export const summarize = (elements) => {
274
+ return elements
275
+ .map((x) => {
276
+ if (x === undefined)
277
+ return "ε";
278
+ if (typeof x === "string")
279
+ return x;
280
+ if (x.index === 0) {
281
+ return x.element;
282
+ }
283
+ return `${x.element}.${x.index}`;
284
+ })
285
+ .join(" ");
286
+ };
@@ -0,0 +1,74 @@
1
+ import type { Draw, Point, SVGStroke } from "./data.js";
2
+ /**
3
+ * 一段 Bezier 曲线的主要朝向
4
+ * 例如,横笔画的朝向是水平的,竖笔画的朝向是垂直的
5
+ * 而撇和捺笔画的朝向可能是水平或垂直的,取决于它是平撇还是撇、平捺还是捺
6
+ */
7
+ type Orientation = "horizontal" | "vertical";
8
+ /**
9
+ * 一次 Bezier 曲线
10
+ * 用于表示横、竖等笔画
11
+ */
12
+ interface LinearCurve {
13
+ type: "linear";
14
+ orientation: Orientation;
15
+ controls: [Point, Point];
16
+ }
17
+ /**
18
+ * 三次 Bezier 曲线
19
+ * 用于表示撇、捺等笔画
20
+ */
21
+ interface CubicCurve {
22
+ type: "cubic";
23
+ orientation: Orientation;
24
+ controls: [Point, Point, Point, Point];
25
+ }
26
+ /**
27
+ * 圆弧曲线
28
+ * 用于表示笔画「圈」
29
+ */
30
+ interface ArcCurve {
31
+ type: "arc";
32
+ orientation: Orientation;
33
+ controls: [Point, Point];
34
+ }
35
+ /**
36
+ * Bezier 曲线,可能为一次、三次或圆弧
37
+ */
38
+ type Curve = LinearCurve | CubicCurve | ArcCurve;
39
+ /**
40
+ * 渲染后的笔画
41
+ * 这个类型和 SVGStroke 的区别是,这个类型包含了一系列 Bezier 曲线,而 SVGStroke 包含了一系列 SVG 命令
42
+ * Bezier 曲线里每一段的起点和终点都是显式写出的,所以比较适合于计算
43
+ */
44
+ interface RenderedStroke {
45
+ feature: string;
46
+ curveList: Curve[];
47
+ }
48
+ export declare const sortTwoNumbers: (ar: [number, number]) => [number, number];
49
+ declare const getBoundingBox: (a: Curve) => [Point, Point];
50
+ declare const getIntervalOnOrientation: (a: Curve) => [Interval, Interval];
51
+ declare const makeCurve: (start: Point, { command, parameterList }: Draw) => Curve;
52
+ declare const render: ({ feature, start, curveList }: SVGStroke) => RenderedStroke;
53
+ declare const area: (p: Point, q: Point) => number;
54
+ type Interval = [number, number];
55
+ interface BoundingBox {
56
+ x: Interval;
57
+ y: Interval;
58
+ }
59
+ type Position = -1 | -0.5 | 0 | 0.5 | 1;
60
+ declare const getIntervalPosition: (i: Interval, j: Interval) => Position;
61
+ declare const findCrossPoint: (a: Curve, at: Interval, b: Curve, bt: Interval) => Point | undefined;
62
+ /**
63
+ * Determine if the point is on a given segment
64
+ * @param from - starting point
65
+ * @param to - end point
66
+ * @param point - another point
67
+ * @returns
68
+ */
69
+ declare const isCollinear: (from: Point, to: Point, point: Point) => boolean;
70
+ declare const curveLength: (curve: Curve) => number;
71
+ declare const isBoundedBy: (stroke: RenderedStroke, xrange: Interval, yrange: Interval) => boolean;
72
+ export { getBoundingBox, getIntervalOnOrientation, getIntervalPosition, render, makeCurve, area, curveLength, findCrossPoint, isCollinear, isBoundedBy, };
73
+ export type { LinearCurve, CubicCurve, Curve, RenderedStroke };
74
+ export type { Interval, Position, BoundingBox };