1 | /**
|
2 | * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core)
|
3 | * Author: Steve Slevinski (https://SteveSlevinski.me)
|
4 | * fsw.js is released under the MIT License.
|
5 | */
|
6 |
|
7 | (function (global, factory) {
|
8 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
9 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
10 | (global = global || self, factory((global.ssw = global.ssw || {}, global.ssw.fsw = {})));
|
11 | }(this, function (exports) { 'use strict';
|
12 |
|
13 | /**
|
14 | * Object of regular expressions for FSW strings
|
15 | *
|
16 | * { symbol, coord, sort, box, prefix, spatial, signbox, sign, sortable }
|
17 | * @alias fsw.re
|
18 | * @type {object}
|
19 | */
|
20 | let re = {
|
21 | 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
|
22 | 'coord': '[0-9]{3}x[0-9]{3}',
|
23 | 'sort': 'A',
|
24 | 'box': '[BLMR]'
|
25 | };
|
26 | re.prefix = `(?:${re.sort}(?:${re.symbol})+)`;
|
27 | re.spatial = `${re.symbol}${re.coord}`;
|
28 | re.signbox = `${re.box}${re.coord}(?:${re.spatial})*`;
|
29 | re.sign = `${re.prefix}?${re.signbox}`;
|
30 | re.sortable = `${re.prefix}${re.signbox}`;
|
31 |
|
32 | /**
|
33 | * Object of regular expressions for style strings
|
34 | *
|
35 | * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full }
|
36 | * @alias style.re
|
37 | * @type {object}
|
38 | */
|
39 | let re$1 = {
|
40 | 'colorize': 'C',
|
41 | 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
|
42 | 'colorname': '[a-zA-Z]+',
|
43 | 'padding': 'P[0-9]{2}',
|
44 | 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
|
45 | 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?',
|
46 | 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
|
47 | 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
|
48 | };
|
49 | re$1.colorbase = `(?:${re$1.colorhex}|${re$1.colorname})`;
|
50 | re$1.color = `_${re$1.colorbase}_`;
|
51 | re$1.colors = `_${re$1.colorbase}(?:,${re$1.colorbase})?_`;
|
52 | re$1.background = `G${re$1.color}`;
|
53 | re$1.detail = `D${re$1.colors}`;
|
54 | re$1.detailsym = `D[0-9]{2}${re$1.colors}`;
|
55 | re$1.classes = `${re$1.classbase}(?: ${re$1.classbase})*`;
|
56 | 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})!)?)?`;
|
57 |
|
58 | /** 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.
|
59 | * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2)
|
60 | * @module convert
|
61 | */
|
62 | /**
|
63 | * Function to convert an FSW coordinate string to an array of x,y integers
|
64 | * @function convert.fsw2coord
|
65 | * @param {string} fswCoord - An FSW coordinate string
|
66 | * @returns {number[]} Array of x,y integers
|
67 | * @example
|
68 | * convert.fsw2coord('500x500')
|
69 | *
|
70 | * return [500, 500]
|
71 | */
|
72 |
|
73 |
|
74 | const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
|
75 |
|
76 | const parse = {
|
77 | /**
|
78 | * Function to parse an fsw symbol with optional coordinate and style string
|
79 | * @function fsw.parse.symbol
|
80 | * @param {string} fswSym - an fsw symbol
|
81 | * @returns {object} elements of fsw symbol
|
82 | * @example
|
83 | * fsw.parse.symbol('S10000500x500-C')
|
84 | *
|
85 | * return {
|
86 | * 'symbol': 'S10000',
|
87 | * 'coord': [500, 500],
|
88 | * 'style': '-C'
|
89 | * }
|
90 | */
|
91 | symbol: fswSym => {
|
92 | const regex = `^(${re.symbol})(${re.coord})?(${re$1.full})?`;
|
93 | const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
|
94 | return {
|
95 | 'symbol': symbol ? symbol[1] : undefined,
|
96 | 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
|
97 | 'style': symbol ? symbol[3] : undefined
|
98 | };
|
99 | },
|
100 |
|
101 | /**
|
102 | * Function to parse an fsw sign with style string
|
103 | * @function fsw.parse.sign
|
104 | * @param {string} fswSign - an fsw sign
|
105 | * @returns {object} elements of fsw sign
|
106 | * @example
|
107 | * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
|
108 | *
|
109 | * return {
|
110 | * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
|
111 | * box: 'M',
|
112 | * max: [525, 535],
|
113 | * spatials: [
|
114 | * {
|
115 | * symbol: 'S2e748',
|
116 | * coord: [483, 510]
|
117 | * },
|
118 | * {
|
119 | * symbol: 'S10011',
|
120 | * coord: [501, 466]
|
121 | * },
|
122 | * {
|
123 | * symbol: 'S2e704',
|
124 | * coord: [510, 500]
|
125 | * },
|
126 | * {
|
127 | * symbol: 'S10019',
|
128 | * coord: [476, 475]
|
129 | * }
|
130 | * ],
|
131 | * style: '-C'
|
132 | * }
|
133 | */
|
134 | sign: fswSign => {
|
135 | const regex = `^(${re.prefix})?(${re.signbox})(${re$1.full})?`;
|
136 | const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
|
137 |
|
138 | if (sign) {
|
139 | return {
|
140 | 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
|
141 | 'box': sign[2][0],
|
142 | 'max': fsw2coord(sign[2].slice(1, 8)),
|
143 | 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
|
144 | return {
|
145 | symbol: m.slice(0, 6),
|
146 | coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
|
147 | };
|
148 | }),
|
149 | 'style': sign[3]
|
150 | };
|
151 | } else {
|
152 | return {};
|
153 | }
|
154 | }
|
155 | };
|
156 |
|
157 | const compose = {
|
158 | /**
|
159 | * Function to compose an fsw symbol with optional coordinate and style string
|
160 | * @function fsw.compose.symbol
|
161 | * @param {object} fswSymObject - an fsw symbol object
|
162 | * @param {string} fswSymObject.symbol - an fsw symbol key
|
163 | * @param {number[]} fswSymObject.coord - top-left coordinate of symbol with 500,500 center
|
164 | * @param {string} fswSymObject.style - a style string for custom appearance
|
165 | * @returns {string} an fsw symbol string
|
166 | * @example
|
167 | * fsw.compose.symbol({
|
168 | * 'symbol': 'S10000',
|
169 | * 'coord': [480, 480],
|
170 | * 'style': '-C'
|
171 | * })
|
172 | *
|
173 | * return 'S10000480x480-C'
|
174 | */
|
175 | symbol: fswSymObject => {
|
176 | if (typeof fswSymObject.symbol === 'string') {
|
177 | const symbol = (fswSymObject.symbol.match(re.symbol) || [''])[0];
|
178 |
|
179 | if (symbol) {
|
180 | const x = (fswSymObject.coord && fswSymObject.coord[0] || '').toString();
|
181 | const y = (fswSymObject.coord && fswSymObject.coord[1] || '').toString();
|
182 | const coord = ((x + 'x' + y).match(re.coord) || [''])[0] || '';
|
183 | const styleStr = typeof fswSymObject.style === 'string' && (fswSymObject.style.match(re$1.full) || [''])[0] || '';
|
184 | return symbol + coord + styleStr;
|
185 | }
|
186 | }
|
187 |
|
188 | return undefined;
|
189 | },
|
190 |
|
191 | /**
|
192 | * Function to compose an fsw sign with style string
|
193 | * @function fsw.compose.sign
|
194 | * @param {string[]} fswSignObject.sequence - an ordered array of symbols
|
195 | * @param {string} fswSignObject.box - a choice BLMR: horizontal Box, Left, Middle, and Right lane
|
196 | * @param {number[]} fswSymObject.max - max bottom left coordinate of the signbox space
|
197 | * @param {{symbol:string,coord:number[]}[]} fswSymObject.spatials - array of symbols with top-left coordinate placement
|
198 | * @param {string} fswSymObject.style - a style string for custom appearance
|
199 | * @returns {string} an fsw sign string
|
200 | * @example
|
201 | * fsw.compose.sign({
|
202 | * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
|
203 | * box: 'M',
|
204 | * max: [525, 535],
|
205 | * spatials: [
|
206 | * {
|
207 | * symbol: 'S2e748',
|
208 | * coord: [483, 510]
|
209 | * },
|
210 | * {
|
211 | * symbol: 'S10011',
|
212 | * coord: [501, 466]
|
213 | * },
|
214 | * {
|
215 | * symbol: 'S2e704',
|
216 | * coord: [510, 500]
|
217 | * },
|
218 | * {
|
219 | * symbol: 'S10019',
|
220 | * coord: [476, 475]
|
221 | * }
|
222 | * ],
|
223 | * style: '-C'
|
224 | * })
|
225 | *
|
226 | * return 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C'
|
227 | */
|
228 | sign: fswSignObject => {
|
229 | let box = typeof fswSignObject.box !== 'string' ? 'M' : (fswSignObject.box + 'M').match(re.box);
|
230 | const x = (fswSignObject.max && fswSignObject.max[0] || '').toString();
|
231 | const y = (fswSignObject.max && fswSignObject.max[1] || '').toString();
|
232 | const max = ((x + 'x' + y).match(re.coord) || [''])[0] || '';
|
233 | if (!max) return undefined;
|
234 | let prefix = '';
|
235 |
|
236 | if (fswSignObject.sequence && Array.isArray(fswSignObject.sequence)) {
|
237 | prefix = fswSignObject.sequence.map(key => (key.match(re.symbol) || [''])[0]).join('');
|
238 | prefix = prefix ? 'A' + prefix : '';
|
239 | }
|
240 |
|
241 | let signbox = '';
|
242 |
|
243 | if (fswSignObject.spatials && Array.isArray(fswSignObject.spatials)) {
|
244 | signbox = fswSignObject.spatials.map(spatial => {
|
245 | if (typeof spatial.symbol === 'string') {
|
246 | const symbol = (spatial.symbol.match(re.symbol) || [''])[0];
|
247 |
|
248 | if (symbol) {
|
249 | const x = (spatial.coord && spatial.coord[0] || '').toString();
|
250 | const y = (spatial.coord && spatial.coord[1] || '').toString();
|
251 | const coord = ((x + 'x' + y).match(re.coord) || [''])[0] || '';
|
252 |
|
253 | if (coord) {
|
254 | return symbol + coord;
|
255 | }
|
256 | }
|
257 | }
|
258 |
|
259 | return '';
|
260 | }).join('');
|
261 | }
|
262 |
|
263 | const styleStr = typeof fswSignObject.style === 'string' && (fswSignObject.style.match(re$1.full) || [''])[0] || '';
|
264 | return prefix + box + max + signbox + styleStr;
|
265 | }
|
266 | };
|
267 |
|
268 | /**
|
269 | * Array of numbers for kinds of symbols: writing, location, and punctuation.
|
270 | * @alias fsw.kind
|
271 | * @type {array}
|
272 | */
|
273 |
|
274 | const kind = [0x100, 0x37f, 0x387];
|
275 | /**
|
276 | * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
|
277 | * @alias fsw.category
|
278 | * @type {array}
|
279 | */
|
280 |
|
281 | const category = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
|
282 | /**
|
283 | * Array of numbers for the 30 symbol groups.
|
284 | * @alias fsw.group
|
285 | * @type {array}
|
286 | */
|
287 |
|
288 | const group = [0x100, 0x10e, 0x11e, 0x144, 0x14c, 0x186, 0x1a4, 0x1ba, 0x1cd, 0x1f5, 0x205, 0x216, 0x22a, 0x255, 0x265, 0x288, 0x2a6, 0x2b7, 0x2d5, 0x2e3, 0x2f7, 0x2ff, 0x30a, 0x32a, 0x33b, 0x359, 0x36d, 0x376, 0x37f, 0x387];
|
289 | /**
|
290 | * Object of symbol ranges with starting and ending numbers.
|
291 | *
|
292 | * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
|
293 | * @alias fsw.ranges
|
294 | * @type {object}
|
295 | */
|
296 |
|
297 | const ranges = {
|
298 | 'all': [0x100, 0x38b],
|
299 | 'writing': [0x100, 0x37e],
|
300 | 'hand': [0x100, 0x204],
|
301 | 'movement': [0x205, 0x2f6],
|
302 | 'dynamic': [0x2f7, 0x2fe],
|
303 | 'head': [0x2ff, 0x36c],
|
304 | 'hcenter': [0x2ff, 0x36c],
|
305 | 'vcenter': [0x2ff, 0x375],
|
306 | 'trunk': [0x36d, 0x375],
|
307 | 'limb': [0x376, 0x37e],
|
308 | 'location': [0x37f, 0x386],
|
309 | 'punctuation': [0x387, 0x38b]
|
310 | };
|
311 | /**
|
312 | * Function to test if symbol is of a certain type.
|
313 | * @function fsw.isType
|
314 | * @param {string} key - an FSW symbol key
|
315 | * @param {string} type - the name of a symbol range
|
316 | * @returns {boolean} is symbol of specified type
|
317 | * @example
|
318 | * fsw.isType('S10000', 'hand')
|
319 | *
|
320 | * return true
|
321 | */
|
322 |
|
323 | const isType = (key, type) => {
|
324 | const parsed = parse.symbol(key);
|
325 |
|
326 | if (parsed.symbol) {
|
327 | const dec = parseInt(parsed.symbol.slice(1, 4), 16);
|
328 | const range = ranges[type];
|
329 |
|
330 | if (range) {
|
331 | return range[0] <= dec && range[1] >= dec;
|
332 | }
|
333 | }
|
334 |
|
335 | return false;
|
336 | };
|
337 |
|
338 | /**
|
339 | * Array of colors associated with the seven symbol categories.
|
340 | * @alias fsw.colors
|
341 | * @type {array}
|
342 | */
|
343 |
|
344 | const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
|
345 | /**
|
346 | * Function that returns the standardized color for a symbol.
|
347 | * @function fsw.colorize
|
348 | * @param {string} key - an FSW symbol key
|
349 | * @returns {string} name of standardized color for symbol
|
350 | * @example
|
351 | * fsw.colorize('S10000')
|
352 | *
|
353 | * return '#0000CC'
|
354 | */
|
355 |
|
356 | const colorize = key => {
|
357 | const parsed = parse.symbol(key);
|
358 | let color = '#000000';
|
359 |
|
360 | if (parsed.symbol) {
|
361 | const dec = parseInt(parsed.symbol.slice(1, 4), 16);
|
362 | const index = category.findIndex(val => val > dec);
|
363 | color = colors[index < 0 ? 6 : index - 1];
|
364 | }
|
365 |
|
366 | return color;
|
367 | };
|
368 |
|
369 | exports.category = category;
|
370 | exports.colorize = colorize;
|
371 | exports.colors = colors;
|
372 | exports.compose = compose;
|
373 | exports.group = group;
|
374 | exports.isType = isType;
|
375 | exports.kind = kind;
|
376 | exports.parse = parse;
|
377 | exports.ranges = ranges;
|
378 | exports.re = re;
|
379 |
|
380 | Object.defineProperty(exports, '__esModule', { value: true });
|
381 |
|
382 | }));
|
383 |
|
384 | /* support ongoing development on https://patreon.com/signwriting */
|