braille-codec 0.0.1-rc1
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.
- package/.github/workflows/ci.yml +80 -0
- package/LICENSE +201 -0
- package/README.md +10 -0
- package/bin/braille-decode +66 -0
- package/dist/decoder/constants/char_english.d.ts +1 -0
- package/dist/decoder/constants/char_shortcut.d.ts +1 -0
- package/dist/decoder/constants/choseong.d.ts +1 -0
- package/dist/decoder/constants/index.d.ts +10 -0
- package/dist/decoder/constants/indicators.d.ts +6 -0
- package/dist/decoder/constants/jongseong.d.ts +1 -0
- package/dist/decoder/constants/jungsong.d.ts +1 -0
- package/dist/decoder/constants/number.d.ts +1 -0
- package/dist/decoder/constants/symbol.d.ts +1 -0
- package/dist/decoder/constants/utils.d.ts +3 -0
- package/dist/decoder/index.d.ts +51 -0
- package/dist/decoder/index.test.d.ts +1 -0
- package/dist/index.cjs +770 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.mjs +768 -0
- package/package.json +38 -0
- package/rollup.config.js +18 -0
- package/src/decoder/constants/char_english.ts +30 -0
- package/src/decoder/constants/char_shortcut.ts +33 -0
- package/src/decoder/constants/choseong.ts +18 -0
- package/src/decoder/constants/index.ts +110 -0
- package/src/decoder/constants/indicators.ts +8 -0
- package/src/decoder/constants/jongseong.ts +19 -0
- package/src/decoder/constants/jungsong.ts +25 -0
- package/src/decoder/constants/number.ts +14 -0
- package/src/decoder/constants/symbol.ts +34 -0
- package/src/decoder/constants/utils.ts +14 -0
- package/src/decoder/index.test.ts +77 -0
- package/src/decoder/index.ts +527 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +8 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
const BRAILLE_UNICODE_START = 0x2800;
|
|
2
|
+
function decodeUnicode(char) {
|
|
3
|
+
const code = char.charCodeAt(0);
|
|
4
|
+
if (code >= BRAILLE_UNICODE_START && code <= BRAILLE_UNICODE_START + 0x3f) {
|
|
5
|
+
return code - BRAILLE_UNICODE_START;
|
|
6
|
+
}
|
|
7
|
+
return -1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const KOREAN_CHOSEONG = {
|
|
11
|
+
[decodeUnicode('⠈')]: 'ㄱ',
|
|
12
|
+
[decodeUnicode('⠉')]: 'ㄴ',
|
|
13
|
+
[decodeUnicode('⠊')]: 'ㄷ',
|
|
14
|
+
[decodeUnicode('⠐')]: 'ㄹ',
|
|
15
|
+
[decodeUnicode('⠑')]: 'ㅁ',
|
|
16
|
+
[decodeUnicode('⠘')]: 'ㅂ',
|
|
17
|
+
[decodeUnicode('⠠')]: 'ㅅ',
|
|
18
|
+
// [decodeUnicode('')]: 'ㅇ', // skip ㅇ of choseong
|
|
19
|
+
[decodeUnicode('⠨')]: 'ㅈ',
|
|
20
|
+
[decodeUnicode('⠰')]: 'ㅊ',
|
|
21
|
+
[decodeUnicode('⠋')]: 'ㅋ',
|
|
22
|
+
[decodeUnicode('⠓')]: 'ㅌ',
|
|
23
|
+
[decodeUnicode('⠙')]: 'ㅍ',
|
|
24
|
+
[decodeUnicode('⠚')]: 'ㅎ',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const KOREAN_JUNGSEONG = {
|
|
28
|
+
[`${decodeUnicode('⠣')}`]: 'ㅏ',
|
|
29
|
+
[`${decodeUnicode('⠜')}`]: 'ㅑ',
|
|
30
|
+
[`${decodeUnicode('⠎')}`]: 'ㅓ',
|
|
31
|
+
[`${decodeUnicode('⠱')}`]: 'ㅕ',
|
|
32
|
+
[`${decodeUnicode('⠥')}`]: 'ㅗ',
|
|
33
|
+
[`${decodeUnicode('⠬')}`]: 'ㅛ',
|
|
34
|
+
[`${decodeUnicode('⠍')}`]: 'ㅜ',
|
|
35
|
+
[`${decodeUnicode('⠩')}`]: 'ㅠ',
|
|
36
|
+
[`${decodeUnicode('⠪')}`]: 'ㅡ',
|
|
37
|
+
[`${decodeUnicode('⠕')}`]: 'ㅣ',
|
|
38
|
+
[`${decodeUnicode('⠗')}`]: 'ㅐ',
|
|
39
|
+
[`${decodeUnicode('⠝')}`]: 'ㅔ',
|
|
40
|
+
[`${decodeUnicode('⠽')}`]: 'ㅚ',
|
|
41
|
+
[`${decodeUnicode('⠧')}`]: 'ㅘ',
|
|
42
|
+
[`${decodeUnicode('⠏')}`]: 'ㅝ',
|
|
43
|
+
[`${decodeUnicode('⠺')}`]: 'ㅢ',
|
|
44
|
+
[`${decodeUnicode('⠌')}`]: 'ㅖ',
|
|
45
|
+
[`${decodeUnicode('⠍')},${decodeUnicode('⠗')}`]: 'ㅟ',
|
|
46
|
+
[`${decodeUnicode('⠜')},${decodeUnicode('⠗')}`]: 'ㅒ',
|
|
47
|
+
[`${decodeUnicode('⠧')},${decodeUnicode('⠗')}`]: 'ㅙ',
|
|
48
|
+
[`${decodeUnicode('⠏')},${decodeUnicode('⠗')}`]: 'ㅞ',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const KOREAN_JONGSEONG = {
|
|
52
|
+
[decodeUnicode('⠁')]: 'ㄱ',
|
|
53
|
+
[decodeUnicode('⠒')]: 'ㄴ',
|
|
54
|
+
[decodeUnicode('⠔')]: 'ㄷ',
|
|
55
|
+
[decodeUnicode('⠂')]: 'ㄹ',
|
|
56
|
+
[decodeUnicode('⠢')]: 'ㅁ',
|
|
57
|
+
[decodeUnicode('⠃')]: 'ㅂ',
|
|
58
|
+
[decodeUnicode('⠄')]: 'ㅅ',
|
|
59
|
+
[decodeUnicode('⠌')]: 'ㅆ',
|
|
60
|
+
[decodeUnicode('⠶')]: 'ㅇ',
|
|
61
|
+
[decodeUnicode('⠅')]: 'ㅈ',
|
|
62
|
+
[decodeUnicode('⠆')]: 'ㅊ',
|
|
63
|
+
[decodeUnicode('⠖')]: 'ㅋ',
|
|
64
|
+
[decodeUnicode('⠦')]: 'ㅌ',
|
|
65
|
+
[decodeUnicode('⠲')]: 'ㅍ',
|
|
66
|
+
[decodeUnicode('⠴')]: 'ㅎ',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const KOREAN_SHORTCUTS = {
|
|
70
|
+
[`${decodeUnicode('⠫')}`]: '가',
|
|
71
|
+
[`${decodeUnicode('⠉')}`]: '나',
|
|
72
|
+
[`${decodeUnicode('⠊')}`]: '다',
|
|
73
|
+
[`${decodeUnicode('⠑')}`]: '마',
|
|
74
|
+
[`${decodeUnicode('⠘')}`]: '바',
|
|
75
|
+
[`${decodeUnicode('⠇')}`]: '사',
|
|
76
|
+
[`${decodeUnicode('⠨')}`]: '자',
|
|
77
|
+
[`${decodeUnicode('⠋')}`]: '카',
|
|
78
|
+
[`${decodeUnicode('⠓')}`]: '타',
|
|
79
|
+
[`${decodeUnicode('⠙')}`]: '파',
|
|
80
|
+
[`${decodeUnicode('⠚')}`]: '하',
|
|
81
|
+
[`${decodeUnicode('⠸')},${decodeUnicode('⠎')}`]: '것',
|
|
82
|
+
[`${decodeUnicode('⠹')}`]: '억',
|
|
83
|
+
[`${decodeUnicode('⠾')}`]: '언',
|
|
84
|
+
[`${decodeUnicode('⠞')}`]: '얼',
|
|
85
|
+
[`${decodeUnicode('⠡')}`]: '연',
|
|
86
|
+
[`${decodeUnicode('⠳')}`]: '열',
|
|
87
|
+
[`${decodeUnicode('⠻')}`]: '영',
|
|
88
|
+
[`${decodeUnicode('⠭')}`]: '옥',
|
|
89
|
+
[`${decodeUnicode('⠷')}`]: '온',
|
|
90
|
+
[`${decodeUnicode('⠿')}`]: '옹',
|
|
91
|
+
[`${decodeUnicode('⠛')}`]: '운',
|
|
92
|
+
[`${decodeUnicode('⠯')}`]: '울',
|
|
93
|
+
[`${decodeUnicode('⠵')}`]: '은',
|
|
94
|
+
[`${decodeUnicode('⠮')}`]: '을',
|
|
95
|
+
[`${decodeUnicode('⠟')}`]: '인',
|
|
96
|
+
[`${decodeUnicode('⠠')},${decodeUnicode('⠻')}`]: '성',
|
|
97
|
+
[`${decodeUnicode('⠨')},${decodeUnicode('⠻')}`]: '정',
|
|
98
|
+
[`${decodeUnicode('⠰')},${decodeUnicode('⠻')}`]: '청',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const ENGLISH_ALPHABET = {
|
|
102
|
+
[decodeUnicode('⠁')]: 'a',
|
|
103
|
+
[decodeUnicode('⠃')]: 'b',
|
|
104
|
+
[decodeUnicode('⠉')]: 'c',
|
|
105
|
+
[decodeUnicode('⠙')]: 'd',
|
|
106
|
+
[decodeUnicode('⠑')]: 'e',
|
|
107
|
+
[decodeUnicode('⠋')]: 'f',
|
|
108
|
+
[decodeUnicode('⠛')]: 'g',
|
|
109
|
+
[decodeUnicode('⠓')]: 'h',
|
|
110
|
+
[decodeUnicode('⠊')]: 'i',
|
|
111
|
+
[decodeUnicode('⠚')]: 'j',
|
|
112
|
+
[decodeUnicode('⠅')]: 'k',
|
|
113
|
+
[decodeUnicode('⠇')]: 'l',
|
|
114
|
+
[decodeUnicode('⠍')]: 'm',
|
|
115
|
+
[decodeUnicode('⠝')]: 'n',
|
|
116
|
+
[decodeUnicode('⠕')]: 'o',
|
|
117
|
+
[decodeUnicode('⠏')]: 'p',
|
|
118
|
+
[decodeUnicode('⠟')]: 'q',
|
|
119
|
+
[decodeUnicode('⠗')]: 'r',
|
|
120
|
+
[decodeUnicode('⠎')]: 's',
|
|
121
|
+
[decodeUnicode('⠞')]: 't',
|
|
122
|
+
[decodeUnicode('⠥')]: 'u',
|
|
123
|
+
[decodeUnicode('⠧')]: 'v',
|
|
124
|
+
[decodeUnicode('⠺')]: 'w',
|
|
125
|
+
[decodeUnicode('⠭')]: 'x',
|
|
126
|
+
[decodeUnicode('⠽')]: 'y',
|
|
127
|
+
[decodeUnicode('⠵')]: 'z',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const NUMBERS = {
|
|
131
|
+
[decodeUnicode('⠁')]: '1',
|
|
132
|
+
[decodeUnicode('⠃')]: '2',
|
|
133
|
+
[decodeUnicode('⠉')]: '3',
|
|
134
|
+
[decodeUnicode('⠙')]: '4',
|
|
135
|
+
[decodeUnicode('⠑')]: '5',
|
|
136
|
+
[decodeUnicode('⠋')]: '6',
|
|
137
|
+
[decodeUnicode('⠛')]: '7',
|
|
138
|
+
[decodeUnicode('⠓')]: '8',
|
|
139
|
+
[decodeUnicode('⠊')]: '9',
|
|
140
|
+
[decodeUnicode('⠚')]: '0',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const SYMBOLS = {
|
|
144
|
+
[`${decodeUnicode('⠖')}`]: '!',
|
|
145
|
+
[`${decodeUnicode('⠲')}`]: '.',
|
|
146
|
+
[`${decodeUnicode('⠐')}`]: ',',
|
|
147
|
+
[`${decodeUnicode('⠦')}`]: '?',
|
|
148
|
+
[`${decodeUnicode('⠐')},${decodeUnicode('⠂')}`]: ':',
|
|
149
|
+
[`${decodeUnicode('⠰')},${decodeUnicode('⠆')}`]: ';',
|
|
150
|
+
[`${decodeUnicode('⠤')}`]: '-',
|
|
151
|
+
[`${decodeUnicode('⠤')},${decodeUnicode('⠤')}`]: '―',
|
|
152
|
+
[`${decodeUnicode('⠦')}`]: '"', // opening
|
|
153
|
+
[`${decodeUnicode('⠴')}`]: '"', // closing
|
|
154
|
+
[`${decodeUnicode('⠠')},${decodeUnicode('⠦')}`]: "'",
|
|
155
|
+
[`${decodeUnicode('⠈')},${decodeUnicode('⠔')}`]: '~',
|
|
156
|
+
[`${decodeUnicode('⠲')},${decodeUnicode('⠲')},${decodeUnicode('⠲')}`]: '…',
|
|
157
|
+
[`${decodeUnicode('⠠')},${decodeUnicode('⠠')},${decodeUnicode('⠠')}`]: '⋯',
|
|
158
|
+
[`${decodeUnicode('⠦')},${decodeUnicode('⠄')}`]: '(',
|
|
159
|
+
[`${decodeUnicode('⠠')},${decodeUnicode('⠴')}`]: ')',
|
|
160
|
+
[`${decodeUnicode('⠦')},${decodeUnicode('⠂')}`]: '{',
|
|
161
|
+
[`${decodeUnicode('⠐')},${decodeUnicode('⠴')}`]: '}',
|
|
162
|
+
[`${decodeUnicode('⠦')},${decodeUnicode('⠆')}`]: '[',
|
|
163
|
+
[`${decodeUnicode('⠰')},${decodeUnicode('⠴')}`]: ']',
|
|
164
|
+
[`${decodeUnicode('⠐')},${decodeUnicode('⠆')}`]: '·',
|
|
165
|
+
[`${decodeUnicode('⠐')},${decodeUnicode('⠦')}`]: '「',
|
|
166
|
+
[`${decodeUnicode('⠴')},${decodeUnicode('⠂')}`]: '」',
|
|
167
|
+
[`${decodeUnicode('⠰')},${decodeUnicode('⠦')}`]: '『',
|
|
168
|
+
[`${decodeUnicode('⠴')},${decodeUnicode('⠆')}`]: '』',
|
|
169
|
+
[`${decodeUnicode('⠸')},${decodeUnicode('⠌')}`]: '/',
|
|
170
|
+
[`${decodeUnicode('⠐')},${decodeUnicode('⠶')}`]: '〈',
|
|
171
|
+
[`${decodeUnicode('⠶')},${decodeUnicode('⠂')}`]: '〉',
|
|
172
|
+
[`${decodeUnicode('⠰')},${decodeUnicode('⠶')}`]: '《',
|
|
173
|
+
[`${decodeUnicode('⠶')},${decodeUnicode('⠆')}`]: '》',
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const NUMBER_INDICATOR = decodeUnicode('⠼');
|
|
177
|
+
const ENGLISH_INDICATOR = decodeUnicode('⠴');
|
|
178
|
+
const ENGLISH_TERMINATOR = decodeUnicode('⠲');
|
|
179
|
+
const UPPERCASE_INDICATOR = decodeUnicode('⠠');
|
|
180
|
+
const KOREAN_PART_INDICATOR = decodeUnicode('⠿');
|
|
181
|
+
const KOREAN_CONSONANT_INDICATOR = decodeUnicode('⠸');
|
|
182
|
+
|
|
183
|
+
// ASCII Braille to Unicode mapping (Standard North American Braille ASCII)
|
|
184
|
+
// Reference: https://en.wikipedia.org/wiki/Braille_ASCII
|
|
185
|
+
const ASCII_BRAILLE_MAP = {
|
|
186
|
+
' ': decodeUnicode('⠀'),
|
|
187
|
+
'!': decodeUnicode('⠮'),
|
|
188
|
+
'"': decodeUnicode('⠐'),
|
|
189
|
+
'#': decodeUnicode('⠼'),
|
|
190
|
+
'$': decodeUnicode('⠫'),
|
|
191
|
+
'%': decodeUnicode('⠩'),
|
|
192
|
+
'&': decodeUnicode('⠯'),
|
|
193
|
+
'\'': decodeUnicode('⠄'),
|
|
194
|
+
'(': decodeUnicode('⠷'),
|
|
195
|
+
')': decodeUnicode('⠾'),
|
|
196
|
+
'*': decodeUnicode('⠡'),
|
|
197
|
+
'+': decodeUnicode('⠬'),
|
|
198
|
+
',': decodeUnicode('⠠'),
|
|
199
|
+
'-': decodeUnicode('⠤'),
|
|
200
|
+
'.': decodeUnicode('⠨'),
|
|
201
|
+
'/': decodeUnicode('⠌'),
|
|
202
|
+
'0': decodeUnicode('⠴'),
|
|
203
|
+
'1': decodeUnicode('⠂'),
|
|
204
|
+
'2': decodeUnicode('⠆'),
|
|
205
|
+
'3': decodeUnicode('⠒'),
|
|
206
|
+
'4': decodeUnicode('⠲'),
|
|
207
|
+
'5': decodeUnicode('⠢'),
|
|
208
|
+
'6': decodeUnicode('⠖'),
|
|
209
|
+
'7': decodeUnicode('⠶'),
|
|
210
|
+
'8': decodeUnicode('⠦'),
|
|
211
|
+
'9': decodeUnicode('⠔'),
|
|
212
|
+
':': decodeUnicode('⠱'),
|
|
213
|
+
';': decodeUnicode('⠰'),
|
|
214
|
+
'<': decodeUnicode('⠣'),
|
|
215
|
+
'=': decodeUnicode('⠿'),
|
|
216
|
+
'>': decodeUnicode('⠜'),
|
|
217
|
+
'?': decodeUnicode('⠹'),
|
|
218
|
+
'@': decodeUnicode('⠈'),
|
|
219
|
+
'A': decodeUnicode('⠁'),
|
|
220
|
+
'B': decodeUnicode('⠃'),
|
|
221
|
+
'C': decodeUnicode('⠉'),
|
|
222
|
+
'D': decodeUnicode('⠙'),
|
|
223
|
+
'E': decodeUnicode('⠑'),
|
|
224
|
+
'F': decodeUnicode('⠋'),
|
|
225
|
+
'G': decodeUnicode('⠛'),
|
|
226
|
+
'H': decodeUnicode('⠓'),
|
|
227
|
+
'I': decodeUnicode('⠊'),
|
|
228
|
+
'J': decodeUnicode('⠚'),
|
|
229
|
+
'K': decodeUnicode('⠅'),
|
|
230
|
+
'L': decodeUnicode('⠇'),
|
|
231
|
+
'M': decodeUnicode('⠍'),
|
|
232
|
+
'N': decodeUnicode('⠝'),
|
|
233
|
+
'O': decodeUnicode('⠕'),
|
|
234
|
+
'P': decodeUnicode('⠏'),
|
|
235
|
+
'Q': decodeUnicode('⠟'),
|
|
236
|
+
'R': decodeUnicode('⠗'),
|
|
237
|
+
'S': decodeUnicode('⠎'),
|
|
238
|
+
'T': decodeUnicode('⠞'),
|
|
239
|
+
'U': decodeUnicode('⠥'),
|
|
240
|
+
'V': decodeUnicode('⠧'),
|
|
241
|
+
'W': decodeUnicode('⠺'),
|
|
242
|
+
'X': decodeUnicode('⠭'),
|
|
243
|
+
'Y': decodeUnicode('⠽'),
|
|
244
|
+
'Z': decodeUnicode('⠵'),
|
|
245
|
+
'[': decodeUnicode('⠪'),
|
|
246
|
+
'\\': decodeUnicode('⠳'),
|
|
247
|
+
']': decodeUnicode('⠻'),
|
|
248
|
+
'^': decodeUnicode('⠘'),
|
|
249
|
+
'_': decodeUnicode('⠸'),
|
|
250
|
+
a: decodeUnicode('⠁'),
|
|
251
|
+
b: decodeUnicode('⠃'),
|
|
252
|
+
c: decodeUnicode('⠉'),
|
|
253
|
+
d: decodeUnicode('⠙'),
|
|
254
|
+
e: decodeUnicode('⠑'),
|
|
255
|
+
f: decodeUnicode('⠋'),
|
|
256
|
+
g: decodeUnicode('⠛'),
|
|
257
|
+
h: decodeUnicode('⠓'),
|
|
258
|
+
i: decodeUnicode('⠊'),
|
|
259
|
+
j: decodeUnicode('⠚'),
|
|
260
|
+
k: decodeUnicode('⠅'),
|
|
261
|
+
l: decodeUnicode('⠇'),
|
|
262
|
+
m: decodeUnicode('⠍'),
|
|
263
|
+
n: decodeUnicode('⠝'),
|
|
264
|
+
o: decodeUnicode('⠕'),
|
|
265
|
+
p: decodeUnicode('⠏'),
|
|
266
|
+
q: decodeUnicode('⠟'),
|
|
267
|
+
r: decodeUnicode('⠗'),
|
|
268
|
+
s: decodeUnicode('⠎'),
|
|
269
|
+
t: decodeUnicode('⠞'),
|
|
270
|
+
u: decodeUnicode('⠥'),
|
|
271
|
+
v: decodeUnicode('⠧'),
|
|
272
|
+
w: decodeUnicode('⠺'),
|
|
273
|
+
x: decodeUnicode('⠭'),
|
|
274
|
+
y: decodeUnicode('⠽'),
|
|
275
|
+
z: decodeUnicode('⠵'),
|
|
276
|
+
'{': decodeUnicode('⠪'),
|
|
277
|
+
'|': decodeUnicode('⠳'),
|
|
278
|
+
'}': decodeUnicode('⠻'),
|
|
279
|
+
'~': decodeUnicode('⠘'),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Braille Decoder
|
|
284
|
+
* Supports English, Special Characters, and Korean.
|
|
285
|
+
* Reference: braillify/libs/braillify/src
|
|
286
|
+
*/
|
|
287
|
+
class Decoder {
|
|
288
|
+
// Pre-computed lookup tables for efficient access
|
|
289
|
+
jungseongMap = new Map();
|
|
290
|
+
shortcutMap = new Map();
|
|
291
|
+
symbolMap = new Map();
|
|
292
|
+
// Korean composition maps
|
|
293
|
+
CHOSEONG = 'ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ';
|
|
294
|
+
JUNGSEONG = 'ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ';
|
|
295
|
+
JONGSEONG = ['', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'];
|
|
296
|
+
choMap = {};
|
|
297
|
+
jungMap = {};
|
|
298
|
+
jongMap = {};
|
|
299
|
+
constructor() {
|
|
300
|
+
this.initializeLookupTables();
|
|
301
|
+
this.CHOSEONG.split('').forEach((c, i) => this.choMap[c] = i);
|
|
302
|
+
this.JUNGSEONG.split('').forEach((c, i) => this.jungMap[c] = i);
|
|
303
|
+
this.JONGSEONG.forEach((c, i) => this.jongMap[c] = i);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Initialize lookup tables from constants for efficient decoding
|
|
307
|
+
*/
|
|
308
|
+
initializeLookupTables() {
|
|
309
|
+
// Build jungseong lookup table
|
|
310
|
+
for (const [key, text] of Object.entries(KOREAN_JUNGSEONG)) {
|
|
311
|
+
const dots = key.split(',').map(Number);
|
|
312
|
+
const firstDot = dots[0].toString();
|
|
313
|
+
if (!this.jungseongMap.has(firstDot)) {
|
|
314
|
+
this.jungseongMap.set(firstDot, []);
|
|
315
|
+
}
|
|
316
|
+
this.jungseongMap.get(firstDot).push({ text, len: dots.length, key });
|
|
317
|
+
}
|
|
318
|
+
// Sort by length descending (try longer patterns first)
|
|
319
|
+
for (const entries of this.jungseongMap.values()) {
|
|
320
|
+
entries.sort((a, b) => b.len - a.len);
|
|
321
|
+
}
|
|
322
|
+
// Build shortcut lookup table
|
|
323
|
+
for (const [key, text] of Object.entries(KOREAN_SHORTCUTS)) {
|
|
324
|
+
const dots = key.split(',').map(Number);
|
|
325
|
+
const firstDot = dots[0].toString();
|
|
326
|
+
if (!this.shortcutMap.has(firstDot)) {
|
|
327
|
+
this.shortcutMap.set(firstDot, []);
|
|
328
|
+
}
|
|
329
|
+
this.shortcutMap.get(firstDot).push({ text, len: dots.length, key });
|
|
330
|
+
}
|
|
331
|
+
for (const entries of this.shortcutMap.values()) {
|
|
332
|
+
entries.sort((a, b) => b.len - a.len);
|
|
333
|
+
}
|
|
334
|
+
// Build symbol lookup table
|
|
335
|
+
for (const [key, text] of Object.entries(SYMBOLS)) {
|
|
336
|
+
const dots = key.split(',').map(Number);
|
|
337
|
+
const firstDot = dots[0].toString();
|
|
338
|
+
if (!this.symbolMap.has(firstDot)) {
|
|
339
|
+
this.symbolMap.set(firstDot, []);
|
|
340
|
+
}
|
|
341
|
+
this.symbolMap.get(firstDot).push({ text, len: dots.length, key });
|
|
342
|
+
}
|
|
343
|
+
for (const entries of this.symbolMap.values()) {
|
|
344
|
+
entries.sort((a, b) => b.len - a.len);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Converts ASCII Braille (BRL) to Unicode Braille.
|
|
349
|
+
* Example: asciiBrailleToUnicode('abcd') -> '⠁⠃⠉⠙'
|
|
350
|
+
*/
|
|
351
|
+
asciiBrailleToUnicode(input) {
|
|
352
|
+
return input
|
|
353
|
+
.split('')
|
|
354
|
+
.map((char) => {
|
|
355
|
+
const dotPattern = ASCII_BRAILLE_MAP[char];
|
|
356
|
+
if (dotPattern === undefined)
|
|
357
|
+
return char;
|
|
358
|
+
return String.fromCharCode(BRAILLE_UNICODE_START + dotPattern);
|
|
359
|
+
})
|
|
360
|
+
.join('');
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Translates Unicode Braille to Text.
|
|
364
|
+
* Supports Korean, English, Numbers, and Symbols.
|
|
365
|
+
*/
|
|
366
|
+
translateToText(input) {
|
|
367
|
+
return input.split('\n').map(v => this.translateToTextOneLine(v)).join('\n');
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Compose Korean syllable from jamo
|
|
371
|
+
*/
|
|
372
|
+
composeKoreanSyllable(cho, jung, jong = '') {
|
|
373
|
+
const choIdx = this.choMap[cho];
|
|
374
|
+
const jungIdx = this.jungMap[jung];
|
|
375
|
+
const jongIdx = this.jongMap[jong] || 0;
|
|
376
|
+
if (choIdx !== undefined && jungIdx !== undefined) {
|
|
377
|
+
return String.fromCharCode(0xAC00 + (choIdx * 21 + jungIdx) * 28 + jongIdx);
|
|
378
|
+
}
|
|
379
|
+
return cho + jung + jong;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Decompose Korean syllable to jamo
|
|
383
|
+
*/
|
|
384
|
+
decomposeSyllable(syllable) {
|
|
385
|
+
const code = syllable.charCodeAt(0);
|
|
386
|
+
if (code >= 0xAC00 && code <= 0xD7A3) {
|
|
387
|
+
const offset = code - 0xAC00;
|
|
388
|
+
const choIdx = Math.floor(offset / (21 * 28));
|
|
389
|
+
const jungIdx = Math.floor((offset % (21 * 28)) / 28);
|
|
390
|
+
const jongIdx = offset % 28;
|
|
391
|
+
return [this.CHOSEONG[choIdx], this.JUNGSEONG[jungIdx], this.JONGSEONG[jongIdx]];
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Add jongseong to existing syllable
|
|
397
|
+
*/
|
|
398
|
+
addJongseongToSyllable(syllable, jong) {
|
|
399
|
+
const decomposed = this.decomposeSyllable(syllable);
|
|
400
|
+
if (decomposed) {
|
|
401
|
+
const [cho, jung, _] = decomposed;
|
|
402
|
+
return this.composeKoreanSyllable(cho, jung, jong);
|
|
403
|
+
}
|
|
404
|
+
return syllable + jong;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Translates Unicode Braille to Text (single line).
|
|
408
|
+
* Supports Korean, English, Numbers, and Symbols.
|
|
409
|
+
*/
|
|
410
|
+
translateToTextOneLine(input) {
|
|
411
|
+
const dots = input.split('').map((char) => {
|
|
412
|
+
const code = char.charCodeAt(0);
|
|
413
|
+
if (code >= BRAILLE_UNICODE_START && code <= BRAILLE_UNICODE_START + 0x3f) {
|
|
414
|
+
return code - BRAILLE_UNICODE_START;
|
|
415
|
+
}
|
|
416
|
+
if (char === '\n')
|
|
417
|
+
return 255;
|
|
418
|
+
return -1; // Not a braille character
|
|
419
|
+
});
|
|
420
|
+
let result = '';
|
|
421
|
+
let i = 0;
|
|
422
|
+
let isEnglishMode = false;
|
|
423
|
+
let isNumberMode = false;
|
|
424
|
+
let pendingKoreanCho = ''; // Pending Korean choseong
|
|
425
|
+
let pendingKoreanJung = ''; // Pending Korean jungseong
|
|
426
|
+
const flushPendingKorean = () => {
|
|
427
|
+
if (pendingKoreanCho && pendingKoreanJung) {
|
|
428
|
+
result += this.composeKoreanSyllable(pendingKoreanCho, pendingKoreanJung);
|
|
429
|
+
pendingKoreanCho = '';
|
|
430
|
+
pendingKoreanJung = '';
|
|
431
|
+
}
|
|
432
|
+
else if (pendingKoreanCho) {
|
|
433
|
+
result += pendingKoreanCho;
|
|
434
|
+
pendingKoreanCho = '';
|
|
435
|
+
}
|
|
436
|
+
else if (pendingKoreanJung) {
|
|
437
|
+
result += this.composeKoreanSyllable('ㅇ', pendingKoreanJung);
|
|
438
|
+
pendingKoreanJung = '';
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
while (i < dots.length) {
|
|
442
|
+
const dot = dots[i];
|
|
443
|
+
if (dot === -1) {
|
|
444
|
+
flushPendingKorean();
|
|
445
|
+
result += input[i];
|
|
446
|
+
isNumberMode = false;
|
|
447
|
+
i++;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (dot === 255) {
|
|
451
|
+
flushPendingKorean();
|
|
452
|
+
result += '\n';
|
|
453
|
+
isNumberMode = false;
|
|
454
|
+
i++;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
// Indicators
|
|
458
|
+
if (dot === NUMBER_INDICATOR) {
|
|
459
|
+
flushPendingKorean();
|
|
460
|
+
isNumberMode = true;
|
|
461
|
+
i++;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (dot === ENGLISH_INDICATOR) {
|
|
465
|
+
flushPendingKorean();
|
|
466
|
+
isEnglishMode = true;
|
|
467
|
+
i++;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (dot === ENGLISH_TERMINATOR && isEnglishMode) {
|
|
471
|
+
isEnglishMode = false;
|
|
472
|
+
i++;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (dot === 0) {
|
|
476
|
+
// Space
|
|
477
|
+
flushPendingKorean();
|
|
478
|
+
// Check if we should skip space after number mode before Korean text (not English)
|
|
479
|
+
if (isNumberMode && i + 1 < dots.length) {
|
|
480
|
+
const nextDot = dots[i + 1];
|
|
481
|
+
// Check if next is Korean (shortcut, choseong, jungseong, or jongseong)
|
|
482
|
+
// But NOT English indicator
|
|
483
|
+
const isNextKorean = (this.matchShortcut(dots, i + 1) !== null ||
|
|
484
|
+
KOREAN_CHOSEONG[nextDot] !== undefined ||
|
|
485
|
+
this.matchJungseong(dots, i + 1) !== null ||
|
|
486
|
+
KOREAN_JONGSEONG[nextDot] !== undefined) &&
|
|
487
|
+
nextDot !== ENGLISH_INDICATOR;
|
|
488
|
+
if (isNextKorean) {
|
|
489
|
+
// Skip space between number and Korean
|
|
490
|
+
isNumberMode = false;
|
|
491
|
+
i++;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
result += ' ';
|
|
496
|
+
isNumberMode = false;
|
|
497
|
+
i++;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
// Number Mode
|
|
501
|
+
if (isNumberMode) {
|
|
502
|
+
if (NUMBERS[dot]) {
|
|
503
|
+
result += NUMBERS[dot];
|
|
504
|
+
i++;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
else if (dot === 2) { // Comma in number mode
|
|
508
|
+
result += ',';
|
|
509
|
+
i++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
else if (dot === 50) { // Dot in number mode
|
|
513
|
+
result += '.';
|
|
514
|
+
i++;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
else if (dot === 36) { // Hyphen in number mode
|
|
518
|
+
result += '‐'; // U+2010
|
|
519
|
+
i++;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
else if (dot === KOREAN_CONSONANT_INDICATOR) {
|
|
523
|
+
// ⠸ in number mode starts asterisk sequence
|
|
524
|
+
i++;
|
|
525
|
+
// All following ⠢ are asterisks
|
|
526
|
+
while (i < dots.length && dots[i] === 34) { // ⠢
|
|
527
|
+
result += '∗'; // U+2217
|
|
528
|
+
i++;
|
|
529
|
+
}
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
else if (dot === 7) { // ⠇ in number mode (end marker?)
|
|
533
|
+
// Skip or handle as needed
|
|
534
|
+
i++;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
isNumberMode = false;
|
|
539
|
+
// Fall through
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// English Mode
|
|
543
|
+
if (isEnglishMode) {
|
|
544
|
+
let isUpper = false;
|
|
545
|
+
if (dot === UPPERCASE_INDICATOR) {
|
|
546
|
+
isUpper = true;
|
|
547
|
+
i++;
|
|
548
|
+
// Check for double uppercase (word)
|
|
549
|
+
if (i < dots.length && dots[i] === UPPERCASE_INDICATOR) {
|
|
550
|
+
i++;
|
|
551
|
+
// Word uppercase
|
|
552
|
+
while (i < dots.length && dots[i] !== 0 && dots[i] !== ENGLISH_TERMINATOR) {
|
|
553
|
+
const d = dots[i];
|
|
554
|
+
const char = ENGLISH_ALPHABET[d];
|
|
555
|
+
if (char) {
|
|
556
|
+
result += char.toUpperCase();
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
// Try symbols in English mode
|
|
560
|
+
const sym = this.matchSymbol(dots, i);
|
|
561
|
+
if (sym) {
|
|
562
|
+
result += sym.text;
|
|
563
|
+
i += sym.len - 1;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
i++;
|
|
567
|
+
}
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const charDot = isUpper ? dots[i] : dot;
|
|
572
|
+
const char = ENGLISH_ALPHABET[charDot];
|
|
573
|
+
if (char) {
|
|
574
|
+
result += isUpper ? char.toUpperCase() : char;
|
|
575
|
+
i++;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// Symbols (Check multi-dot symbols first)
|
|
580
|
+
// But check if this could be Korean choseong followed by jungseong or shortcut
|
|
581
|
+
const sym = this.matchSymbol(dots, i);
|
|
582
|
+
if (sym) {
|
|
583
|
+
// Check if this dot is also a choseong and followed by jungseong or shortcut
|
|
584
|
+
if (KOREAN_CHOSEONG[dot] && i + 1 < dots.length) {
|
|
585
|
+
const nextJung = this.matchJungseong(dots, i + 1);
|
|
586
|
+
const nextShortcut = this.matchShortcut(dots, i + 1);
|
|
587
|
+
if (nextJung || nextShortcut) {
|
|
588
|
+
// This is choseong, not symbol
|
|
589
|
+
flushPendingKorean();
|
|
590
|
+
pendingKoreanCho = KOREAN_CHOSEONG[dot];
|
|
591
|
+
i++;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
flushPendingKorean();
|
|
596
|
+
result += sym.text;
|
|
597
|
+
i += sym.len;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
// Korean Mode
|
|
601
|
+
// 1. Shortcuts (Check multi-dot shortcuts first)
|
|
602
|
+
const shortcut = this.matchShortcut(dots, i);
|
|
603
|
+
if (shortcut) {
|
|
604
|
+
// Check if there's a pending choseong (e.g., ㅅ + 옥 = 속, ㄴ + 영 = 녕)
|
|
605
|
+
if (pendingKoreanCho) {
|
|
606
|
+
// Decompose shortcut and combine with pending choseong
|
|
607
|
+
const decomposed = this.decomposeSyllable(shortcut.text);
|
|
608
|
+
if (decomposed && decomposed[0] === 'ㅇ') {
|
|
609
|
+
// Replace ㅇ with pending choseong
|
|
610
|
+
result += this.composeKoreanSyllable(pendingKoreanCho, decomposed[1], decomposed[2]);
|
|
611
|
+
pendingKoreanCho = '';
|
|
612
|
+
pendingKoreanJung = '';
|
|
613
|
+
i += shortcut.len;
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// Check if this is a single-char shortcut that could be choseong
|
|
618
|
+
const singleCharShortcuts = ['가', '나', '다', '마', '바', '사', '자', '카', '타', '파', '하'];
|
|
619
|
+
if (shortcut.len === 1 && singleCharShortcuts.includes(shortcut.text)) {
|
|
620
|
+
// Check if next is a shortcut or jungseong (NOT jongseong)
|
|
621
|
+
if (i + 1 < dots.length) {
|
|
622
|
+
const nextShortcut = this.matchShortcut(dots, i + 1);
|
|
623
|
+
const nextJung = this.matchJungseong(dots, i + 1);
|
|
624
|
+
// If followed by shortcut or jungseong, interpret as choseong
|
|
625
|
+
if (nextShortcut || nextJung) {
|
|
626
|
+
if (KOREAN_CHOSEONG[dot]) {
|
|
627
|
+
flushPendingKorean();
|
|
628
|
+
pendingKoreanCho = KOREAN_CHOSEONG[dot];
|
|
629
|
+
i++;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Shortcut is a complete syllable
|
|
636
|
+
// Check if next is jongseong
|
|
637
|
+
if (i + 1 < dots.length && KOREAN_JONGSEONG[dots[i + 1]]) {
|
|
638
|
+
// Add jongseong to shortcut
|
|
639
|
+
flushPendingKorean();
|
|
640
|
+
result += this.addJongseongToSyllable(shortcut.text, KOREAN_JONGSEONG[dots[i + 1]]);
|
|
641
|
+
i += shortcut.len + 1;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
flushPendingKorean();
|
|
645
|
+
result += shortcut.text;
|
|
646
|
+
i += shortcut.len;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// 2. Choseong
|
|
650
|
+
if (KOREAN_CHOSEONG[dot]) {
|
|
651
|
+
flushPendingKorean();
|
|
652
|
+
pendingKoreanCho = KOREAN_CHOSEONG[dot];
|
|
653
|
+
i++;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
// 3. Jungseong
|
|
657
|
+
const jung = this.matchJungseong(dots, i);
|
|
658
|
+
if (jung) {
|
|
659
|
+
if (pendingKoreanCho && !pendingKoreanJung) {
|
|
660
|
+
// Choseong + Jungseong
|
|
661
|
+
pendingKoreanJung = jung.text;
|
|
662
|
+
}
|
|
663
|
+
else if (pendingKoreanCho && pendingKoreanJung) {
|
|
664
|
+
// Already have cho + jung, flush and start new syllable
|
|
665
|
+
flushPendingKorean();
|
|
666
|
+
pendingKoreanCho = 'ㅇ';
|
|
667
|
+
pendingKoreanJung = jung.text;
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
// Jungseong alone (implicit ㅇ)
|
|
671
|
+
flushPendingKorean();
|
|
672
|
+
pendingKoreanCho = 'ㅇ';
|
|
673
|
+
pendingKoreanJung = jung.text;
|
|
674
|
+
}
|
|
675
|
+
i += jung.len;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
// 4. Jongseong
|
|
679
|
+
if (KOREAN_JONGSEONG[dot]) {
|
|
680
|
+
if (pendingKoreanCho && pendingKoreanJung) {
|
|
681
|
+
// Complete syllable with jongseong
|
|
682
|
+
result += this.composeKoreanSyllable(pendingKoreanCho, pendingKoreanJung, KOREAN_JONGSEONG[dot]);
|
|
683
|
+
pendingKoreanCho = '';
|
|
684
|
+
pendingKoreanJung = '';
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
// Jongseong without pending syllable
|
|
688
|
+
flushPendingKorean();
|
|
689
|
+
result += KOREAN_JONGSEONG[dot];
|
|
690
|
+
}
|
|
691
|
+
i++;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
// 5. Korean Part/Consonant Indicators
|
|
695
|
+
if (dot === KOREAN_PART_INDICATOR || dot === KOREAN_CONSONANT_INDICATOR) {
|
|
696
|
+
flushPendingKorean();
|
|
697
|
+
i++;
|
|
698
|
+
if (i < dots.length) {
|
|
699
|
+
const nextDot = dots[i];
|
|
700
|
+
// Try to find in choseong or jongseong maps
|
|
701
|
+
const char = KOREAN_CHOSEONG[nextDot] || KOREAN_JONGSEONG[nextDot];
|
|
702
|
+
if (char) {
|
|
703
|
+
result += char;
|
|
704
|
+
i++;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
// Unknown pattern
|
|
711
|
+
flushPendingKorean();
|
|
712
|
+
i++;
|
|
713
|
+
}
|
|
714
|
+
flushPendingKorean();
|
|
715
|
+
return result;
|
|
716
|
+
}
|
|
717
|
+
matchJungseong(dots, index) {
|
|
718
|
+
const firstDot = dots[index].toString();
|
|
719
|
+
const candidates = this.jungseongMap.get(firstDot);
|
|
720
|
+
if (!candidates)
|
|
721
|
+
return null;
|
|
722
|
+
// Try patterns from longest to shortest
|
|
723
|
+
for (const candidate of candidates) {
|
|
724
|
+
if (index + candidate.len <= dots.length) {
|
|
725
|
+
// Build the key to match
|
|
726
|
+
const key = dots.slice(index, index + candidate.len).join(',');
|
|
727
|
+
if (candidate.key === key) {
|
|
728
|
+
return { text: candidate.text, len: candidate.len };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
matchShortcut(dots, index) {
|
|
735
|
+
const firstDot = dots[index].toString();
|
|
736
|
+
const candidates = this.shortcutMap.get(firstDot);
|
|
737
|
+
if (!candidates)
|
|
738
|
+
return null;
|
|
739
|
+
// Try patterns from longest to shortest
|
|
740
|
+
for (const candidate of candidates) {
|
|
741
|
+
if (index + candidate.len <= dots.length) {
|
|
742
|
+
const key = dots.slice(index, index + candidate.len).join(',');
|
|
743
|
+
if (candidate.key === key) {
|
|
744
|
+
return { text: candidate.text, len: candidate.len };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
matchSymbol(dots, index) {
|
|
751
|
+
const firstDot = dots[index].toString();
|
|
752
|
+
const candidates = this.symbolMap.get(firstDot);
|
|
753
|
+
if (!candidates)
|
|
754
|
+
return null;
|
|
755
|
+
// Try patterns from longest to shortest
|
|
756
|
+
for (const candidate of candidates) {
|
|
757
|
+
if (index + candidate.len <= dots.length) {
|
|
758
|
+
const key = dots.slice(index, index + candidate.len).join(',');
|
|
759
|
+
if (candidate.key === key) {
|
|
760
|
+
return { text: candidate.text, len: candidate.len };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export { Decoder };
|