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