1 | /**
|
2 | * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core)
|
3 | * Author: Steve Slevinski (https://SteveSlevinski.me)
|
4 | * swu.mjs is released under the MIT License.
|
5 | */
|
6 |
|
7 | /**
|
8 | * Object of regular expressions for SWU strings in UTF-16
|
9 | *
|
10 | * { symbol, coord, sort, box, prefix, spatial, signbox, sign, sortable }
|
11 | * @alias swu.re
|
12 | * @type {object}
|
13 | */
|
14 | let re = {
|
15 | 'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
|
16 | 'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}',
|
17 | 'sort': '\uD836\uDC00',
|
18 | 'box': '\uD836[\uDC01-\uDC04]'
|
19 | };
|
20 | re.prefix = `(?:${re.sort}(?:${re.symbol})+)`;
|
21 | re.spatial = `${re.symbol}${re.coord}`;
|
22 | re.signbox = `${re.box}${re.coord}(?:${re.spatial})*`;
|
23 | re.sign = `${re.prefix}?${re.signbox}`;
|
24 | re.sortable = `${re.prefix}${re.signbox}`;
|
25 |
|
26 | /**
|
27 | * Object of regular expressions for style strings
|
28 | *
|
29 | * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full }
|
30 | * @alias style.re
|
31 | * @type {object}
|
32 | */
|
33 | let re$1 = {
|
34 | 'colorize': 'C',
|
35 | 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
|
36 | 'colorname': '[a-zA-Z]+',
|
37 | 'padding': 'P[0-9]{2}',
|
38 | 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
|
39 | 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?',
|
40 | 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
|
41 | 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
|
42 | };
|
43 | re$1.colorbase = `(?:${re$1.colorhex}|${re$1.colorname})`;
|
44 | re$1.color = `_${re$1.colorbase}_`;
|
45 | re$1.colors = `_${re$1.colorbase}(?:,${re$1.colorbase})?_`;
|
46 | re$1.background = `G${re$1.color}`;
|
47 | re$1.detail = `D${re$1.colors}`;
|
48 | re$1.detailsym = `D[0-9]{2}${re$1.colors}`;
|
49 | re$1.classes = `${re$1.classbase}(?: ${re$1.classbase})*`;
|
50 | re$1.full = `-(${re$1.colorize})?(${re$1.padding})?(${re$1.background})?(${re$1.detail})?(${re$1.zoom})?(?:-((?:${re$1.detailsym})*)((?:${re$1.zoomsym})*))?(?:-(${re$1.classes})?!(?:(${re$1.id})!)?)?`;
|
51 |
|
52 | /** The convert module contains functions to convert between Formal SignWriitng in ASCII (FSW) and SignWriting in Unicode (SWU) characters, along with other types of data.
|
53 | * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2)
|
54 | * @module convert
|
55 | */
|
56 | /**
|
57 | * Function to convert an SWU number character to an integer
|
58 | * @function convert.swu2num
|
59 | * @param {string} swuNum - SWU number character
|
60 | * @returns {number} Integer value for number
|
61 | * @example
|
62 | * convert.swu2num('𝤆')
|
63 | *
|
64 | * return 500
|
65 | */
|
66 |
|
67 |
|
68 | const swu2num = swuNum => parseInt(swuNum.codePointAt(0)) - 0x1D80C + 250;
|
69 | /**
|
70 | * Function to convert a number to an SWU number character
|
71 | * @function convert.num2swu
|
72 | * @param {number} num - Integer value for number
|
73 | * @returns {string} SWU number character
|
74 | * @example
|
75 | * convert.num2swu(500)
|
76 | *
|
77 | * return '𝤆'
|
78 | */
|
79 |
|
80 |
|
81 | const num2swu = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250);
|
82 | /**
|
83 | * Function to convert two SWU number characters to an array of x,y integers
|
84 | * @function convert.swu2coord
|
85 | * @param {string} swuCoord - Two SWU number character
|
86 | * @returns {number[]} Array of x,y integers
|
87 | * @example
|
88 | * convert.swu2coord('𝤆𝤆')
|
89 | *
|
90 | * return [500, 500]
|
91 | */
|
92 |
|
93 |
|
94 | const swu2coord = swuCoord => [swu2num(swuCoord.slice(0, 2)), swu2num(swuCoord.slice(2, 4))];
|
95 | /**
|
96 | * Function to convert an array of x,y integers to two SWU number characters
|
97 | * @function convert.coord2swu
|
98 | * @param {number[]} coord - Array of x,y integers
|
99 | * @returns {string} Two SWU number character
|
100 | * @example
|
101 | * convert.coord2swu([500, 500])
|
102 | *
|
103 | * return '𝤆𝤆'
|
104 | */
|
105 |
|
106 |
|
107 | const coord2swu = coord => coord.map(num => num2swu(num)).join('');
|
108 | /**
|
109 | * Function to convert an SWU symbol character to a code point on plane 4
|
110 | * @function convert.swu2code
|
111 | * @param {string} swuSym - SWU symbol character
|
112 | * @returns {number} Code point on plane 4
|
113 | * @example
|
114 | * convert.swu2code('')
|
115 | *
|
116 | * return 0x40001
|
117 | */
|
118 |
|
119 |
|
120 | const swu2code = swuSym => parseInt(swuSym.codePointAt(0));
|
121 |
|
122 | const parse = {
|
123 | /**
|
124 | * Function to parse an swu symbol with optional coordinate and style string
|
125 | * @function swu.parse.symbol
|
126 | * @param {string} swuSym - an swu symbol
|
127 | * @returns {object} elements of swu symbol
|
128 | * @example
|
129 | * swu.parse.symbol('𝤆𝤆-C')
|
130 | *
|
131 | * return {
|
132 | * 'symbol': '',
|
133 | * 'coord': [500, 500],
|
134 | * 'style': '-C'
|
135 | * }
|
136 | */
|
137 | symbol: swuSym => {
|
138 | const regex = `^(${re.symbol})(${re.coord})?(${re$1.full})?`;
|
139 | const symbol = typeof swuSym === 'string' ? swuSym.match(new RegExp(regex)) : undefined;
|
140 | return {
|
141 | 'symbol': symbol ? symbol[1] : undefined,
|
142 | 'coord': symbol && symbol[2] ? swu2coord(symbol[2]) : undefined,
|
143 | 'style': symbol ? symbol[3] : undefined
|
144 | };
|
145 | },
|
146 |
|
147 | /**
|
148 | * Function to parse an swu sign with style string
|
149 | * @function swu.parse.sign
|
150 | * @param {string} swuSign - an swu sign
|
151 | * @returns {object} elements of swu sign
|
152 | * @example
|
153 | * swu.parse.sign('𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭-C')
|
154 | *
|
155 | * return {
|
156 | * sequence: ['','','','''],
|
157 | * box: '𝠃',
|
158 | * max: [525, 535],
|
159 | * spatials: [
|
160 | * {
|
161 | * symbol: '',
|
162 | * coord: [483, 510]
|
163 | * },
|
164 | * {
|
165 | * symbol: '',
|
166 | * coord: [501, 466]
|
167 | * },
|
168 | * {
|
169 | * symbol: '',
|
170 | * coord: [510, 500]
|
171 | * },
|
172 | * {
|
173 | * symbol: '',
|
174 | * coord: [476, 475]
|
175 | * }
|
176 | * ],
|
177 | * style: '-C'
|
178 | * }
|
179 | */
|
180 | sign: swuSign => {
|
181 | const regex = `^(${re.prefix})?(${re.signbox})(${re$1.full})?`;
|
182 | const sign = typeof swuSign === 'string' ? swuSign.match(new RegExp(regex)) : undefined;
|
183 |
|
184 | if (sign) {
|
185 | return {
|
186 | 'sequence': sign[1] ? sign[1].slice(2).match(/.{2}/g) : undefined,
|
187 | 'box': sign[2].slice(0, 2),
|
188 | 'max': swu2coord(sign[2].slice(2, 6)),
|
189 | 'spatials': sign[2].length < 7 ? undefined : sign[2].slice(6).match(/(.{6})/g).map(m => {
|
190 | return {
|
191 | symbol: m.slice(0, 2),
|
192 | coord: swu2coord(m.slice(2))
|
193 | };
|
194 | }),
|
195 | 'style': sign[3]
|
196 | };
|
197 | } else {
|
198 | return {};
|
199 | }
|
200 | }
|
201 | };
|
202 | /**
|
203 | * Function to encode SWU characters using the UTF-16 escape format.
|
204 | * @function swu.encode
|
205 | * @param {string} swu - SWU characters
|
206 | * @returns {string} UTF-16 escape format
|
207 | * @example
|
208 | * swu.encode('𝤆𝤆')
|
209 | *
|
210 | * return '\\uD8C0\\uDC01\\uD836\\uDD06\\uD836\\uDD06'
|
211 | */
|
212 |
|
213 | const encode = text => text.replace(/[\u007F-\uFFFF]/g, function (chr) {
|
214 | return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4).toUpperCase();
|
215 | });
|
216 | /**
|
217 | * Function to decode UTF-16 escape format to SWU characters.
|
218 | * @function swu.decode
|
219 | * @param {string} encoded - UTF-16 escape format
|
220 | * @returns {string} SWU characters
|
221 | * @example
|
222 | * swu.decode('\\uD8C0\\uDC01\\uD836\\uDD06\\uD836\\uDD06')
|
223 | *
|
224 | * return '𝤆𝤆'
|
225 | */
|
226 |
|
227 |
|
228 | const decode = encoded => encoded.replace(/\\u([0-9A-F]{4})/g, function (match, chr) {
|
229 | return String.fromCharCode(parseInt(chr, 16));
|
230 | });
|
231 | /**
|
232 | * Function to decompose an SWU character into UTF-16 surrogate pairs.
|
233 | * @function swu.pair
|
234 | * @param {string} swuChar - an SWU character
|
235 | * @returns {string[]} an array of UTF-16 surrogate pairs
|
236 | * @example
|
237 | * swu.pair('')
|
238 | *
|
239 | * return ['D8C0', 'DC01']
|
240 | */
|
241 |
|
242 |
|
243 | const pair = swuChar => [swuChar.charCodeAt(0).toString(16).toUpperCase(), swuChar.charCodeAt(1).toString(16).toUpperCase()];
|
244 |
|
245 | const compose = {
|
246 | /**
|
247 | * Function to compose an swu symbol with optional coordinate and style string
|
248 | * @function swu.compose.symbol
|
249 | * @param {object} swuSymObject - an swu symbol object
|
250 | * @param {string} swuSymObject.symbol - an swu symbol key
|
251 | * @param {number[]} swuSymObject.coord - top-left coordinate of symbol with 500,500 center
|
252 | * @param {string} swuSymObject.style - a style string for custom appearance
|
253 | * @returns {string} an swu symbol string
|
254 | * @example
|
255 | * swu.compose.symbol({
|
256 | * 'symbol': '',
|
257 | * 'coord': [500, 500],
|
258 | * 'style': '-C'
|
259 | * })
|
260 | *
|
261 | * return '𝤆𝤆-C'
|
262 | */
|
263 | symbol: swuSymObject => {
|
264 | if (typeof swuSymObject !== 'object' || swuSymObject === null) return undefined;
|
265 |
|
266 | if (typeof swuSymObject.symbol === 'string') {
|
267 | const symbol = (swuSymObject.symbol.match(re.symbol) || [''])[0];
|
268 |
|
269 | if (symbol) {
|
270 | const x = swuSymObject.coord && swuSymObject.coord[0] || '';
|
271 | const y = swuSymObject.coord && swuSymObject.coord[1] || '';
|
272 | const coord = x && y ? coord2swu([x, y]) : '';
|
273 | const styleStr = typeof swuSymObject.style === 'string' && (swuSymObject.style.match(re$1.full) || [''])[0] || '';
|
274 | return symbol + coord + styleStr;
|
275 | }
|
276 | }
|
277 |
|
278 | return undefined;
|
279 | },
|
280 |
|
281 | /**
|
282 | * Function to compose an swu sign with style string
|
283 | * @function swu.compose.sign
|
284 | * @param {string[]} swuSignObject.sequence - an ordered array of symbols
|
285 | * @param {string} swuSignObject.box - a choice BLMR: horizontal Box, Left, Middle, and Right lane
|
286 | * @param {number[]} swuSymObject.max - max bottom left coordinate of the signbox space
|
287 | * @param {{symbol:string,coord:number[]}[]} swuSymObject.spatials - array of symbols with top-left coordinate placement
|
288 | * @param {string} swuSymObject.style - a style string for custom appearance
|
289 | * @returns {string} an swu sign string
|
290 | * @example
|
291 | * swu.compose.sign({
|
292 | * sequence: ['','','','''],
|
293 | * box: '𝠃',
|
294 | * max: [525, 535],
|
295 | * spatials: [
|
296 | * {
|
297 | * symbol: '',
|
298 | * coord: [483, 510]
|
299 | * },
|
300 | * {
|
301 | * symbol: '',
|
302 | * coord: [501, 466]
|
303 | * },
|
304 | * {
|
305 | * symbol: '',
|
306 | * coord: [510, 500]
|
307 | * },
|
308 | * {
|
309 | * symbol: '',
|
310 | * coord: [476, 475]
|
311 | * }
|
312 | * ],
|
313 | * style: '-C'
|
314 | * })
|
315 | *
|
316 | * return '𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭-C'
|
317 | */
|
318 | sign: swuSignObject => {
|
319 | if (typeof swuSignObject !== 'object' || swuSignObject === null) return undefined;
|
320 | let box = typeof swuSignObject.box !== 'string' ? '𝠃' : (swuSignObject.box + '𝠃').match(re.box);
|
321 | const x = swuSignObject.max && swuSignObject.max[0] || '';
|
322 | const y = swuSignObject.max && swuSignObject.max[1] || '';
|
323 | const max = x && y ? coord2swu([x, y]) : undefined;
|
324 | if (!max) return undefined;
|
325 | let prefix = '';
|
326 |
|
327 | if (swuSignObject.sequence && Array.isArray(swuSignObject.sequence)) {
|
328 | prefix = swuSignObject.sequence.map(key => (key.match(re.symbol) || [''])[0]).join('');
|
329 | prefix = prefix ? '𝠀' + prefix : '';
|
330 | }
|
331 |
|
332 | let signbox = '';
|
333 |
|
334 | if (swuSignObject.spatials && Array.isArray(swuSignObject.spatials)) {
|
335 | signbox = swuSignObject.spatials.map(spatial => {
|
336 | if (typeof spatial.symbol === 'string') {
|
337 | const symbol = (spatial.symbol.match(re.symbol) || [''])[0];
|
338 |
|
339 | if (symbol) {
|
340 | const x = spatial.coord && spatial.coord[0] || '';
|
341 | const y = spatial.coord && spatial.coord[1] || '';
|
342 | const coord = x && y ? coord2swu([x, y]) : '';
|
343 |
|
344 | if (coord) {
|
345 | return symbol + coord;
|
346 | }
|
347 | }
|
348 | }
|
349 |
|
350 | return '';
|
351 | }).join('');
|
352 | }
|
353 |
|
354 | const styleStr = typeof swuSignObject.style === 'string' && (swuSignObject.style.match(re$1.full) || [''])[0] || '';
|
355 | return prefix + box + max + signbox + styleStr;
|
356 | }
|
357 | };
|
358 |
|
359 | /**
|
360 | * Array of plane 4 code points for kinds of symbols: writing, location, and punctuation.
|
361 | * @alias swu.kind
|
362 | * @type {array}
|
363 | */
|
364 |
|
365 | const kind = [0x40001, 0x4efa1, 0x4f2a1];
|
366 | /**
|
367 | * Array of plane 4 code points for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
|
368 | * @alias swu.category
|
369 | * @type {array}
|
370 | */
|
371 |
|
372 | const category = [0x40001, 0x461e1, 0x4bca1, 0x4bfa1, 0x4e8e1, 0x4efa1, 0x4f2a1];
|
373 | /**
|
374 | * Array of plane 4 code points for the 30 symbol groups.
|
375 | * @alias swu.group
|
376 | * @type {array}
|
377 | */
|
378 |
|
379 | const group = [0x40001, 0x40541, 0x40b41, 0x41981, 0x41c81, 0x43241, 0x43d81, 0x445c1, 0x44ce1, 0x45be1, 0x461e1, 0x46841, 0x46fc1, 0x47fe1, 0x485e1, 0x49301, 0x49e41, 0x4a4a1, 0x4afe1, 0x4b521, 0x4bca1, 0x4bfa1, 0x4c3c1, 0x4cfc1, 0x4d621, 0x4e161, 0x4e8e1, 0x4ec41, 0x4efa1, 0x4f2a1];
|
380 | /**
|
381 | * Object of symbol ranges with starting and ending code points on plane 4.
|
382 | *
|
383 | * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
|
384 | * @alias swu.ranges
|
385 | * @type {object}
|
386 | */
|
387 |
|
388 | const ranges = {
|
389 | 'all': [0x40001, 0x4f480],
|
390 | 'writing': [0x40001, 0x4efa0],
|
391 | 'hand': [0x40001, 0x461e0],
|
392 | 'movement': [0x461e1, 0x4bca0],
|
393 | 'dynamic': [0x4bca1, 0x4bfa0],
|
394 | 'head': [0x4bfa1, 0x4e8e0],
|
395 | 'hcenter': [0x4bfa1, 0x4e8e0],
|
396 | 'vcenter': [0x4bfa1, 0x4ec40],
|
397 | 'trunk': [0x4e8e1, 0x4ec40],
|
398 | 'limb': [0x4ec41, 0x4efa0],
|
399 | 'location': [0x4efa1, 0x4f2a0],
|
400 | 'punctuation': [0x4f2a1, 0x4f480]
|
401 | };
|
402 | /**
|
403 | * Function to test if symbol is of a certain type.
|
404 | * @function swu.isType
|
405 | * @param {string} swuSym - an SWU symbol character
|
406 | * @param {string} type - the name of a symbol range
|
407 | * @returns {boolean} is symbol of specified type
|
408 | * @example
|
409 | * swu.isType('', 'hand')
|
410 | *
|
411 | * return true
|
412 | */
|
413 |
|
414 | const isType = (swuSym, type) => {
|
415 | const parsed = parse.symbol(swuSym);
|
416 |
|
417 | if (parsed.symbol) {
|
418 | const code = swu2code(parsed.symbol);
|
419 | const range = ranges[type];
|
420 |
|
421 | if (range) {
|
422 | return range[0] <= code && range[1] >= code;
|
423 | }
|
424 | }
|
425 |
|
426 | return false;
|
427 | };
|
428 |
|
429 | /**
|
430 | * Array of colors associated with the seven symbol categories.
|
431 | * @alias swu.colors
|
432 | * @type {array}
|
433 | */
|
434 |
|
435 | const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
|
436 | /**
|
437 | * Function that returns the standardized color for a symbol.
|
438 | * @function swu.colorize
|
439 | * @param {string} swuSym - an SWU symbol character
|
440 | * @returns {string} name of standardized color for symbol
|
441 | * @example
|
442 | * swu.colorize('')
|
443 | *
|
444 | * return '#0000CC'
|
445 | */
|
446 |
|
447 | const colorize = swuSym => {
|
448 | const parsed = parse.symbol(swuSym);
|
449 | let color = '#000000';
|
450 |
|
451 | if (parsed.symbol) {
|
452 | const code = swu2code(parsed.symbol);
|
453 | const index = category.findIndex(val => val > code);
|
454 | color = colors[index < 0 ? 6 : index - 1];
|
455 | }
|
456 |
|
457 | return color;
|
458 | };
|
459 |
|
460 | export { category, colorize, colors, compose, decode, encode, group, isType, kind, pair, parse, ranges, re };
|
461 |
|
462 | /* support ongoing development on https://patreon.com/signwriting */
|