1 | /**
|
2 | * Sutton SignWriting Core Module v1.4.2 (https://github.com/sutton-signwriting/core)
|
3 | * Author: Steve Slevinski (https://SteveSlevinski.me)
|
4 | * fsw.mjs is released under the MIT License.
|
5 | */
|
6 |
|
7 | /**
|
8 | * Object of regular expressions for FSW strings
|
9 | *
|
10 | * @alias fsw.re
|
11 | * @property {string} symbol - regular expressions for a symbol
|
12 | * @property {string} coord - regular expressions for a coordinate
|
13 | * @property {string} sort - regular expressions for the sorting marker
|
14 | * @property {string} box - regular expression for a signbox marker
|
15 | * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
|
16 | * @property {string} spatial - regular expression for a symbol followed by a coordinate
|
17 | * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
|
18 | * @property {string} sign - regular expression for an optional prefix followed by a signbox
|
19 | * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
|
20 | */
|
21 | let re$1 = {
|
22 | 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
|
23 | 'coord': '[0-9]{3}x[0-9]{3}',
|
24 | 'sort': 'A',
|
25 | 'box': '[BLMR]'
|
26 | };
|
27 | re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
|
28 | re$1.spatial = `${re$1.symbol}${re$1.coord}`;
|
29 | re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
|
30 | re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
|
31 | re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
|
32 |
|
33 | /**
|
34 | * Object of regular expressions for style strings
|
35 | *
|
36 | * @alias style.re
|
37 | * @type {object}
|
38 | * @property {string} colorize - regular expression for colorize section
|
39 | * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
|
40 | * @property {string} colorname - regular expression for css color name
|
41 | * @property {string} padding - regular expression for padding section
|
42 | * @property {string} zoom - regular expression for zoom section
|
43 | * @property {string} classbase - regular expression for class name definition
|
44 | * @property {string} id - regular expression for id definition
|
45 | * @property {string} colorbase - regular expression for color hex or color name
|
46 | * @property {string} color - regular expression for single color entry
|
47 | * @property {string} colors - regular expression for double color entry
|
48 | * @property {string} background - regular expression for background section
|
49 | * @property {string} detail - regular expression for color details for line and optional fill
|
50 | * @property {string} detailsym - regular expression for color details for individual symbols
|
51 | * @property {string} classes - regular expression for one or more class names
|
52 | * @property {string} full - full regular expression for style string
|
53 | */
|
54 | let re = {
|
55 | 'colorize': 'C',
|
56 | 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
|
57 | 'colorname': '[a-zA-Z]+',
|
58 | 'padding': 'P[0-9]{2}',
|
59 | 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
|
60 | 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
|
61 | 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
|
62 | };
|
63 | re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
|
64 | re.color = `_${re.colorbase}_`;
|
65 | re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
|
66 | re.background = `G${re.color}`;
|
67 | re.detail = `D${re.colors}`;
|
68 | re.detailsym = `D[0-9]{2}${re.colors}`;
|
69 | re.classes = `${re.classbase}(?: ${re.classbase})*`;
|
70 | re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
|
71 |
|
72 | const prefixColor = color => {
|
73 | const regex = new RegExp(`^${re.colorhex}$`);
|
74 | return (regex.test(color) ? '#' : '') + color;
|
75 | };
|
76 |
|
77 | const definedProps = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
|
78 | /**
|
79 | * Function to parse style string to object
|
80 | * @function style.parse
|
81 | * @param {string} styleString - a style string
|
82 | * @returns {StyleObject} elements of style string
|
83 | * @example
|
84 | * style.parse('-CP10G_blue_D_red,Cyan_')
|
85 | *
|
86 | * return {
|
87 | * 'colorize': true,
|
88 | * 'padding': 10,
|
89 | * 'background': 'blue',
|
90 | * 'detail': ['red', 'Cyan']
|
91 | * }
|
92 | */
|
93 |
|
94 |
|
95 | const parse$1 = styleString => {
|
96 | const regex = `^${re.full}`;
|
97 | const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
|
98 | return definedProps({
|
99 | 'colorize': !m[1] ? undefined : !!m[1],
|
100 | 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
|
101 | 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)),
|
102 | 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor),
|
103 | 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
|
104 | 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => {
|
105 | const parts = val.split('_');
|
106 | const detail = parts[1].split(',').map(prefixColor);
|
107 | return {
|
108 | 'index': parseInt(parts[0].slice(1)),
|
109 | 'detail': detail
|
110 | };
|
111 | }),
|
112 | 'classes': !m[7] ? undefined : m[7],
|
113 | 'id': !m[8] ? undefined : m[8]
|
114 | });
|
115 | };
|
116 |
|
117 | /** 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.
|
118 | * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
|
119 | * @module convert
|
120 | */
|
121 | /**
|
122 | * Function to convert an FSW coordinate string to an array of x,y integers
|
123 | * @function convert.fsw2coord
|
124 | * @param {string} fswCoord - An FSW coordinate string
|
125 | * @returns {number[]} Array of x,y integers
|
126 | * @example
|
127 | * convert.fsw2coord('500x500')
|
128 | *
|
129 | * return [500, 500]
|
130 | */
|
131 |
|
132 |
|
133 | const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
|
134 |
|
135 | const parse = {
|
136 | /**
|
137 | * Function to parse an fsw symbol with optional coordinate and style string
|
138 | * @function fsw.parse.symbol
|
139 | * @param {string} fswSym - an fsw symbol
|
140 | * @returns {object} elements of fsw symbol
|
141 | * @example
|
142 | * fsw.parse.symbol('S10000500x500-C')
|
143 | *
|
144 | * return {
|
145 | * 'symbol': 'S10000',
|
146 | * 'coord': [500, 500],
|
147 | * 'style': '-C'
|
148 | * }
|
149 | */
|
150 | symbol: fswSym => {
|
151 | const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
|
152 | const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
|
153 | return {
|
154 | 'symbol': symbol ? symbol[1] : undefined,
|
155 | 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
|
156 | 'style': symbol ? symbol[3] : undefined
|
157 | };
|
158 | },
|
159 |
|
160 | /**
|
161 | * Function to parse an fsw sign with style string
|
162 | * @function fsw.parse.sign
|
163 | * @param {string} fswSign - an fsw sign
|
164 | * @returns {object} elements of fsw sign
|
165 | * @example
|
166 | * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
|
167 | *
|
168 | * return {
|
169 | * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
|
170 | * box: 'M',
|
171 | * max: [525, 535],
|
172 | * spatials: [
|
173 | * {
|
174 | * symbol: 'S2e748',
|
175 | * coord: [483, 510]
|
176 | * },
|
177 | * {
|
178 | * symbol: 'S10011',
|
179 | * coord: [501, 466]
|
180 | * },
|
181 | * {
|
182 | * symbol: 'S2e704',
|
183 | * coord: [510, 500]
|
184 | * },
|
185 | * {
|
186 | * symbol: 'S10019',
|
187 | * coord: [476, 475]
|
188 | * }
|
189 | * ],
|
190 | * style: '-C'
|
191 | * }
|
192 | */
|
193 | sign: fswSign => {
|
194 | const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
|
195 | const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
|
196 |
|
197 | if (sign) {
|
198 | return {
|
199 | 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
|
200 | 'box': sign[2][0],
|
201 | 'max': fsw2coord(sign[2].slice(1, 8)),
|
202 | 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
|
203 | return {
|
204 | symbol: m.slice(0, 6),
|
205 | coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
|
206 | };
|
207 | }),
|
208 | 'style': sign[3]
|
209 | };
|
210 | } else {
|
211 | return {};
|
212 | }
|
213 | },
|
214 |
|
215 | /**
|
216 | * Function to parse an fsw text
|
217 | * @function fsw.parse.text
|
218 | * @param {string} fswText - an fsw text
|
219 | * @returns {array} fsw signs and punctuations
|
220 | * @example
|
221 | * fsw.parse.text('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496')
|
222 | *
|
223 | * return [
|
224 | * 'AS14c20S27106M518x529S14c20481x471S27106503x489',
|
225 | * 'AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468',
|
226 | * 'S38800464x496'
|
227 | * ]
|
228 | */
|
229 | text: fswText => {
|
230 | if (typeof fswText !== 'string') return [];
|
231 | const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
|
232 | const matches = fswText.match(new RegExp(regex, 'g'));
|
233 | return matches ? [...matches] : [];
|
234 | }
|
235 | };
|
236 |
|
237 | const compose = {
|
238 | /**
|
239 | * Function to compose an fsw symbol with optional coordinate and style string
|
240 | * @function fsw.compose.symbol
|
241 | * @param {object} fswSymObject - an fsw symbol object
|
242 | * @param {string} fswSymObject.symbol - an fsw symbol key
|
243 | * @param {number[]} fswSymObject.coord - top-left coordinate of symbol
|
244 | * @param {string} fswSymObject.style - a style string for custom appearance
|
245 | * @returns {string} an fsw symbol string
|
246 | * @example
|
247 | * fsw.compose.symbol({
|
248 | * 'symbol': 'S10000',
|
249 | * 'coord': [480, 480],
|
250 | * 'style': '-C'
|
251 | * })
|
252 | *
|
253 | * return 'S10000480x480-C'
|
254 | */
|
255 | symbol: fswSymObject => {
|
256 | if (typeof fswSymObject.symbol === 'string') {
|
257 | const symbol = (fswSymObject.symbol.match(re$1.symbol) || [''])[0];
|
258 |
|
259 | if (symbol) {
|
260 | const x = (fswSymObject.coord && fswSymObject.coord[0] || '').toString();
|
261 | const y = (fswSymObject.coord && fswSymObject.coord[1] || '').toString();
|
262 | const coord = ((x + 'x' + y).match(re$1.coord) || [''])[0] || '';
|
263 | const styleStr = typeof fswSymObject.style === 'string' && (fswSymObject.style.match(re.full) || [''])[0] || '';
|
264 | return symbol + coord + styleStr;
|
265 | }
|
266 | }
|
267 |
|
268 | return undefined;
|
269 | },
|
270 |
|
271 | /**
|
272 | * Function to compose an fsw sign with style string
|
273 | * @function fsw.compose.sign
|
274 | * @param {object} fswSymObject - an fsw sign object
|
275 | * @param {string[]} fswSignObject.sequence - an ordered array of symbols
|
276 | * @param {string} fswSignObject.box - a choice BLMR: horizontal Box, Left, Middle, and Right lane
|
277 | * @param {number[]} fswSignObject.max - max bottom-right coordinate of the signbox space
|
278 | * @param {{symbol:string,coord:number[]}[]} fswSignObject.spatials - array of symbols with top-left coordinate placement
|
279 | * @param {string} fswSignObject.style - a style string for custom appearance
|
280 | * @returns {string} an fsw sign string
|
281 | * @example
|
282 | * fsw.compose.sign({
|
283 | * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
|
284 | * box: 'M',
|
285 | * max: [525, 535],
|
286 | * spatials: [
|
287 | * {
|
288 | * symbol: 'S2e748',
|
289 | * coord: [483, 510]
|
290 | * },
|
291 | * {
|
292 | * symbol: 'S10011',
|
293 | * coord: [501, 466]
|
294 | * },
|
295 | * {
|
296 | * symbol: 'S2e704',
|
297 | * coord: [510, 500]
|
298 | * },
|
299 | * {
|
300 | * symbol: 'S10019',
|
301 | * coord: [476, 475]
|
302 | * }
|
303 | * ],
|
304 | * style: '-C'
|
305 | * })
|
306 | *
|
307 | * return 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C'
|
308 | */
|
309 | sign: fswSignObject => {
|
310 | let box = typeof fswSignObject.box !== 'string' ? 'M' : (fswSignObject.box + 'M').match(re$1.box);
|
311 | const x = (fswSignObject.max && fswSignObject.max[0] || '').toString();
|
312 | const y = (fswSignObject.max && fswSignObject.max[1] || '').toString();
|
313 | const max = ((x + 'x' + y).match(re$1.coord) || [''])[0] || '';
|
314 | if (!max) return undefined;
|
315 | let prefix = '';
|
316 |
|
317 | if (fswSignObject.sequence && Array.isArray(fswSignObject.sequence)) {
|
318 | prefix = fswSignObject.sequence.map(key => (key.match(re$1.symbol) || [''])[0]).join('');
|
319 | prefix = prefix ? 'A' + prefix : '';
|
320 | }
|
321 |
|
322 | let signbox = '';
|
323 |
|
324 | if (fswSignObject.spatials && Array.isArray(fswSignObject.spatials)) {
|
325 | signbox = fswSignObject.spatials.map(spatial => {
|
326 | if (typeof spatial.symbol === 'string') {
|
327 | const symbol = (spatial.symbol.match(re$1.symbol) || [''])[0];
|
328 |
|
329 | if (symbol) {
|
330 | const x = (spatial.coord && spatial.coord[0] || '').toString();
|
331 | const y = (spatial.coord && spatial.coord[1] || '').toString();
|
332 | const coord = ((x + 'x' + y).match(re$1.coord) || [''])[0] || '';
|
333 |
|
334 | if (coord) {
|
335 | return symbol + coord;
|
336 | }
|
337 | }
|
338 | }
|
339 |
|
340 | return '';
|
341 | }).join('');
|
342 | }
|
343 |
|
344 | const styleStr = typeof fswSignObject.style === 'string' && (fswSignObject.style.match(re.full) || [''])[0] || '';
|
345 | return prefix + box + max + signbox + styleStr;
|
346 | }
|
347 | };
|
348 |
|
349 | /**
|
350 | * Function to gather sizing information about an fsw sign or symbol
|
351 | * @function fsw.info
|
352 | * @param {string} fsw - an fsw sign or symbol
|
353 | * @returns {object} information about the fsw string
|
354 | * @example
|
355 | * fsw.info('AS14c20S27106L518x529S14c20481x471S27106503x489-P10Z2')
|
356 | *
|
357 | * return {
|
358 | * minX: 481,
|
359 | * minY: 471,
|
360 | * width: 37,
|
361 | * height: 58,
|
362 | * zoom: 2,
|
363 | * padding: 10,
|
364 | * segment: 'sign',
|
365 | * lane: -1
|
366 | * }
|
367 | */
|
368 |
|
369 | const info = fsw => {
|
370 | let lanes = {
|
371 | "B": 0,
|
372 | "L": -1,
|
373 | "M": 0,
|
374 | "R": 1
|
375 | };
|
376 | let parsed = parse.sign(fsw);
|
377 | let width, height, segment, x1, x2, y1, y2, lane;
|
378 |
|
379 | if (parsed.spatials) {
|
380 | x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
|
381 | x2 = parsed.max[0];
|
382 | width = x2 - x1;
|
383 | y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
|
384 | y2 = parsed.max[1];
|
385 | height = y2 - y1;
|
386 | segment = 'sign';
|
387 | lane = parsed.box;
|
388 | } else {
|
389 | parsed = parse.symbol(fsw);
|
390 | lane = "M";
|
391 |
|
392 | if (parsed.coord) {
|
393 | x1 = parsed.coord[0];
|
394 | width = (500 - x1) * 2;
|
395 | y1 = parsed.coord[1];
|
396 | height = (500 - y1) * 2;
|
397 | segment = 'symbol';
|
398 | } else {
|
399 | x1 = 490;
|
400 | width = 20;
|
401 | y1 = 490;
|
402 | height = 20;
|
403 | segment = 'none';
|
404 | }
|
405 | }
|
406 |
|
407 | let style = parse$1(parsed.style);
|
408 | let zoom = style.zoom || 1;
|
409 | let padding = style.padding || 0;
|
410 | return {
|
411 | minX: x1,
|
412 | minY: y1,
|
413 | width: width,
|
414 | height: height,
|
415 | segment: segment,
|
416 | lane: lanes[lane],
|
417 | padding: padding,
|
418 | zoom: zoom
|
419 | };
|
420 | };
|
421 |
|
422 | const columnDefaults = {
|
423 | 'height': 500,
|
424 | 'width': 150,
|
425 | 'offset': 50,
|
426 | 'pad': 20,
|
427 | 'margin': 5,
|
428 | 'dynamic': false,
|
429 | 'background': undefined,
|
430 | 'punctuation': {
|
431 | 'spacing': true,
|
432 | 'pad': 30,
|
433 | 'pull': true
|
434 | },
|
435 | 'style': {
|
436 | 'detail': ['black', 'white'],
|
437 | 'zoom': 1
|
438 | }
|
439 | };
|
440 | /**
|
441 | * Function to an object of column options with default values
|
442 | *
|
443 | * @function fsw.columnDefaultsMerge
|
444 | * @param {ColumnOptions} options - object of column options
|
445 | * @returns {ColumnOptions} object of column options merged with column defaults
|
446 | * @example
|
447 | * fsw.columnDefaultsMerge({height: 500,width:150})
|
448 | *
|
449 | * return {
|
450 | * "height": 500,
|
451 | * "width": 150,
|
452 | * "offset": 50,
|
453 | * "pad": 20,
|
454 | * "margin": 5,
|
455 | * "dynamic": false,
|
456 | * "punctuation": {
|
457 | * "spacing": true,
|
458 | * "pad": 30,
|
459 | * "pull": true
|
460 | * },
|
461 | * "style": {
|
462 | * "detail": [
|
463 | * "black",
|
464 | * "white"
|
465 | * ],
|
466 | * "zoom": 1
|
467 | * }
|
468 | * }
|
469 | */
|
470 |
|
471 | const columnDefaultsMerge = options => {
|
472 | if (typeof options !== 'object') options = {};
|
473 | return { ...columnDefaults,
|
474 | ...options,
|
475 | punctuation: { ...columnDefaults.punctuation,
|
476 | ...options.punctuation
|
477 | },
|
478 | style: { ...columnDefaults.style,
|
479 | ...options.style
|
480 | }
|
481 | };
|
482 | };
|
483 | /**
|
484 | * Function to transform an FSW text to an array of columns
|
485 | *
|
486 | * @function fsw.columns
|
487 | * @param {string} fswText - FSW text of signs and punctuation
|
488 | * @param {ColumnOptions} options - object of column options
|
489 | * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
|
490 | * @example
|
491 | * fsw.columns('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496', {height: 500,width:150})
|
492 | *
|
493 | * return {
|
494 | * "options": {
|
495 | * "height": 500,
|
496 | * "width": 150,
|
497 | * "offset": 50,
|
498 | * "pad": 20,
|
499 | * "margin": 5,
|
500 | * "dynamic": false,
|
501 | * "punctuation": {
|
502 | * "spacing": true,
|
503 | * "pad": 30,
|
504 | * "pull": true
|
505 | * },
|
506 | * "style": {
|
507 | * "detail": [
|
508 | * "black",
|
509 | * "white"
|
510 | * ],
|
511 | * "zoom": 1
|
512 | * }
|
513 | * },
|
514 | * "widths": [
|
515 | * 150
|
516 | * ],
|
517 | * "columns": [
|
518 | * [
|
519 | * {
|
520 | * "x": 56,
|
521 | * "y": 20,
|
522 | * "minX": 481,
|
523 | * "minY": 471,
|
524 | * "width": 37,
|
525 | * "height": 58,
|
526 | * "lane": 0,
|
527 | * "padding": 0,
|
528 | * "segment": "sign",
|
529 | * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
|
530 | * "zoom": 1
|
531 | * },
|
532 | * {
|
533 | * "x": 57,
|
534 | * "y": 118,
|
535 | * "minX": 482,
|
536 | * "minY": 468,
|
537 | * "width": 36,
|
538 | * "height": 65,
|
539 | * "lane": 0,
|
540 | * "padding": 0,
|
541 | * "segment": "sign",
|
542 | * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
|
543 | * "zoom": 1
|
544 | * },
|
545 | * {
|
546 | * "x": 39,
|
547 | * "y": 203,
|
548 | * "minX": 464,
|
549 | * "minY": 496,
|
550 | * "width": 72,
|
551 | * "height": 8,
|
552 | * "lane": 0,
|
553 | * "padding": 0,
|
554 | * "segment": "symbol",
|
555 | * "text": "S38800464x496",
|
556 | * "zoom": 1
|
557 | * }
|
558 | * ]
|
559 | * ]
|
560 | * }
|
561 | */
|
562 |
|
563 |
|
564 | const columns = (fswText, options) => {
|
565 | if (typeof fswText !== 'string') return {};
|
566 | const values = columnDefaultsMerge(options);
|
567 | let input = parse.text(fswText);
|
568 | let cursor = 0;
|
569 | let cols = [];
|
570 | let col = [];
|
571 | let plus = 0;
|
572 | let center = parseInt(values.width / 2);
|
573 | let maxHeight = values.height - values.margin;
|
574 | let pullable = true;
|
575 | let finalize = false;
|
576 |
|
577 | for (let val of input) {
|
578 | let informed = info(val);
|
579 | cursor += plus;
|
580 |
|
581 | if (values.punctuation.spacing) {
|
582 | cursor += informed.segment == 'sign' ? values.pad : 0;
|
583 | } else {
|
584 | cursor += values.pad;
|
585 | }
|
586 |
|
587 | finalize = cursor + informed.height > maxHeight;
|
588 |
|
589 | if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
|
590 | finalize = false;
|
591 | pullable = false;
|
592 | }
|
593 |
|
594 | if (col.length == 0) {
|
595 | finalize = false;
|
596 | }
|
597 |
|
598 | if (finalize) {
|
599 | cursor = values.pad;
|
600 | cols.push(col);
|
601 | col = [];
|
602 | pullable = true;
|
603 | }
|
604 |
|
605 | col.push(Object.assign(informed, {
|
606 | x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
|
607 | y: cursor,
|
608 | text: val
|
609 | }));
|
610 | cursor += informed.height * informed.zoom * values.style.zoom;
|
611 |
|
612 | if (values.punctuation.spacing) {
|
613 | plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
|
614 | } else {
|
615 | plus = values.pad;
|
616 | }
|
617 | }
|
618 |
|
619 | if (col.length) {
|
620 | cols.push(col);
|
621 | } // over height issue when pulling punctuation
|
622 |
|
623 |
|
624 | if (values.punctuation.pull) {
|
625 | for (let col of cols) {
|
626 | let last = col[col.length - 1];
|
627 | let diff = last.y + last.height - (values.height - values.margin);
|
628 |
|
629 | if (diff > 0) {
|
630 | let adj = parseInt(diff / col.length) + 1;
|
631 |
|
632 | for (let i in col) {
|
633 | col[i].y -= adj * i + adj;
|
634 | }
|
635 | }
|
636 | }
|
637 | } // contract, expand, adjust
|
638 |
|
639 |
|
640 | let widths = [];
|
641 |
|
642 | for (let col of cols) {
|
643 | let min = [center - values.offset - values.pad];
|
644 | let max = [center + values.offset + values.pad];
|
645 |
|
646 | for (let item of col) {
|
647 | min.push(item.x - values.pad);
|
648 | max.push(item.x + item.width + values.pad);
|
649 | }
|
650 |
|
651 | min = Math.min(...min);
|
652 | max = Math.max(...max);
|
653 | let width = values.width;
|
654 | let adj = 0;
|
655 |
|
656 | if (!values.dynamic) {
|
657 | adj = center - parseInt((min + max) / 2);
|
658 | } else {
|
659 | width = max - min;
|
660 | adj = -min;
|
661 | }
|
662 |
|
663 | for (let item of col) {
|
664 | item.x += adj;
|
665 | }
|
666 |
|
667 | widths.push(width);
|
668 | }
|
669 |
|
670 | return {
|
671 | 'options': values,
|
672 | 'widths': widths,
|
673 | 'columns': cols
|
674 | };
|
675 | };
|
676 |
|
677 | /**
|
678 | * Array of numbers for kinds of symbols: writing, location, and punctuation.
|
679 | * @alias fsw.kind
|
680 | * @type {array}
|
681 | */
|
682 |
|
683 | const kind = [0x100, 0x37f, 0x387];
|
684 | /**
|
685 | * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
|
686 | * @alias fsw.category
|
687 | * @type {array}
|
688 | */
|
689 |
|
690 | const category = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
|
691 | /**
|
692 | * Array of numbers for the 30 symbol groups.
|
693 | * @alias fsw.group
|
694 | * @type {array}
|
695 | */
|
696 |
|
697 | 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];
|
698 | /**
|
699 | * Object of symbol ranges with starting and ending numbers.
|
700 | *
|
701 | * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
|
702 | * @alias fsw.ranges
|
703 | * @type {object}
|
704 | */
|
705 |
|
706 | const ranges = {
|
707 | 'all': [0x100, 0x38b],
|
708 | 'writing': [0x100, 0x37e],
|
709 | 'hand': [0x100, 0x204],
|
710 | 'movement': [0x205, 0x2f6],
|
711 | 'dynamic': [0x2f7, 0x2fe],
|
712 | 'head': [0x2ff, 0x36c],
|
713 | 'hcenter': [0x2ff, 0x36c],
|
714 | 'vcenter': [0x2ff, 0x375],
|
715 | 'trunk': [0x36d, 0x375],
|
716 | 'limb': [0x376, 0x37e],
|
717 | 'location': [0x37f, 0x386],
|
718 | 'punctuation': [0x387, 0x38b]
|
719 | };
|
720 | /**
|
721 | * Function to test if symbol is of a certain type.
|
722 | * @function fsw.isType
|
723 | * @param {string} key - an FSW symbol key
|
724 | * @param {string} type - the name of a symbol range
|
725 | * @returns {boolean} is symbol of specified type
|
726 | * @example
|
727 | * fsw.isType('S10000', 'hand')
|
728 | *
|
729 | * return true
|
730 | */
|
731 |
|
732 | const isType = (key, type) => {
|
733 | const parsed = parse.symbol(key);
|
734 |
|
735 | if (parsed.symbol) {
|
736 | const dec = parseInt(parsed.symbol.slice(1, 4), 16);
|
737 | const range = ranges[type];
|
738 |
|
739 | if (range) {
|
740 | return range[0] <= dec && range[1] >= dec;
|
741 | }
|
742 | }
|
743 |
|
744 | return false;
|
745 | };
|
746 |
|
747 | /**
|
748 | * Array of colors associated with the seven symbol categories.
|
749 | * @alias fsw.colors
|
750 | * @type {array}
|
751 | */
|
752 |
|
753 | const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
|
754 | /**
|
755 | * Function that returns the standardized color for a symbol.
|
756 | * @function fsw.colorize
|
757 | * @param {string} key - an FSW symbol key
|
758 | * @returns {string} name of standardized color for symbol
|
759 | * @example
|
760 | * fsw.colorize('S10000')
|
761 | *
|
762 | * return '#0000CC'
|
763 | */
|
764 |
|
765 | const colorize = key => {
|
766 | const parsed = parse.symbol(key);
|
767 | let color = '#000000';
|
768 |
|
769 | if (parsed.symbol) {
|
770 | const dec = parseInt(parsed.symbol.slice(1, 4), 16);
|
771 | const index = category.findIndex(val => val > dec);
|
772 | color = colors[index < 0 ? 6 : index - 1];
|
773 | }
|
774 |
|
775 | return color;
|
776 | };
|
777 |
|
778 | export { category, colorize, colors, columnDefaults, columnDefaultsMerge, columns, compose, group, info, isType, kind, parse, ranges, re$1 as re };
|
779 |
|
780 | /* support ongoing development on https://patreon.com/signwriting */
|