UNPKG

144 kBJavaScriptView Raw
1/**
2* Sutton SignWriting TrueType Font Module v1.5.2 (https://github.com/sutton-signwriting/font-ttf)
3* Author: Steve Slevinski (https://SteveSlevinski.me)
4* index.mjs is released under the MIT License.
5*/
6
7/**
8 * Function that appends font-face CSS for the Sutton SignWriting fonts for system installed fonts, relative directory fonts, or content delivery network
9 * @function font.cssAppend
10 * @param {string} dir - an optional relative directory for font location
11 * @example
12 * font.cssAppend('./font/')
13 */
14const cssAppend = function (dir = '') {
15 const id = "SgnwFontCss";
16 if (!document.getElementById(id)) {
17 const style = document.createElement('style');
18 style.setAttribute("id", "SgnwFontCss");
19 style.appendChild(document.createTextNode(`
20 @font-face {
21 font-family: "SuttonSignWritingLine";
22 src:
23 local('SuttonSignWritingLine'),
24 ${dir ? `url('${dir}SuttonSignWritingLine.ttf') format('truetype'),` : ""}
25 url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingLine.ttf') format('truetype');
26 }
27 @font-face {
28 font-family: "SuttonSignWritingFill";
29 src:
30 local('SuttonSignWritingFill'),
31 ${dir ? `url('${dir}SuttonSignWritingFill.ttf') format('truetype'),` : ""}
32 url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingFill.ttf') format('truetype');
33 }
34 @font-face {
35 font-family: "SuttonSignWritingOneD";
36 src:
37 local('SuttonSignWritingOneD'),
38 ${dir ? `url('${dir}SuttonSignWritingOneD.ttf') format('truetype'),` : ""}
39 url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingOneD.ttf') format('truetype');
40 }
41 `));
42 document.head.appendChild(style);
43 }
44};
45
46let sizes = {};
47const zoom = 2;
48const bound = 76 * zoom;
49let context;
50
51/**
52 * Function that returns the size of a symbol using an id
53 * @function font.symbolSize
54 * @param {number} id - a 16-bit number of a symbol
55 * @returns {number[]} width and height of symbol
56 * @example
57 * font.symbolSize(1)
58 *
59 * return [15,30]
60 */
61const symbolSize$2 = function (id) {
62 if (id in sizes) {
63 return [...sizes[id]];
64 }
65 if (!context) {
66 const canvaser = document.createElement("canvas");
67 canvaser.width = bound;
68 canvaser.height = bound;
69 context = canvaser.getContext("2d", {
70 willReadFrequently: true
71 });
72 }
73 context.clearRect(0, 0, bound, bound);
74 context.font = 30 * zoom + "px 'SuttonSignWritingLine'";
75 context.fillText(String.fromCodePoint(id + 0xF0000), 0, 0);
76 const imgData = context.getImageData(0, 0, bound, bound).data;
77 let w, h, i, s;
78 wloop: for (w = bound - 1; w >= 0; w--) {
79 for (h = 0; h < bound; h += 1) {
80 for (s = 0; s < 4; s += 1) {
81 i = w * 4 + h * 4 * bound + s;
82 if (imgData[i]) {
83 break wloop;
84 }
85 }
86 }
87 }
88 var width = w;
89 hloop: for (h = bound - 1; h >= 0; h--) {
90 for (w = 0; w < width; w += 1) {
91 for (s = 0; s < 4; s += 1) {
92 i = w * 4 + h * 4 * bound + s;
93 if (imgData[i]) {
94 break hloop;
95 }
96 }
97 }
98 }
99 var height = h + 1;
100 width = Math.ceil(width / zoom);
101 height = Math.ceil(height / zoom);
102 // Rounding error in chrome. Manual fixes.
103 if (14394 == id) {
104 width = 19;
105 }
106 if ([10468, 10480, 10496, 10512, 10500, 10532, 10548, 10862, 10878, 10894, 11058, 11074, 11476, 11488, 11492, 11504, 11508, 11520, 10516, 10910, 10926, 11042, 11082, 10942].includes(id)) {
107 width = 20;
108 }
109 if (31921 == id) {
110 width = 22;
111 }
112 if (38460 == id) {
113 width = 23;
114 }
115 if ([20164, 20212].includes(id)) {
116 width = 25;
117 }
118 if (31894 == id) {
119 width = 28;
120 }
121 if (46698 == id) {
122 width = 29;
123 }
124 if (29606 == id) {
125 width = 30;
126 }
127 if (44855 == id) {
128 width = 40;
129 }
130 if (32667 == id) {
131 width = 50;
132 }
133 if ([11088, 11474, 11490, 11506].includes(id)) {
134 height = 20;
135 }
136 if (6285 == id) {
137 height = 21;
138 }
139 if (40804 == id) {
140 height = 31;
141 }
142 if (41475 == id) {
143 height = 36;
144 }
145 // Error in chrome. Manual fix.
146 // if (width==0 && height==0) {
147 if (width == 0 && height == 0) {
148 const sizefix = {
149 9: [15, 30],
150 10: [21, 30],
151 11: [30, 15],
152 12: [30, 21],
153 13: [15, 30],
154 14: [21, 30]
155 };
156 if (id in sizefix) {
157 width = sizefix[id][0];
158 height = sizefix[id][1];
159 }
160 }
161 if (width == 0 && height == 0) {
162 return undefined;
163 }
164 sizes[id] = [width, height];
165 return [width, height];
166};
167
168/**
169 * Function that returns a plane 15 character for a symbol line using an id
170 * @function font.symbolLine
171 * @param {number} id - a 16-bit number of a symbol
172 * @returns {string} character for symbol line
173 * @example
174 * font.symbolLine(1)
175 *
176 * return '󰀁'
177 */
178const symbolLine$2 = function (id) {
179 return String.fromCodePoint(id + 0xF0000);
180};
181
182/**
183 * Function that returns a plane 16 character for a symbol fill using an id
184 * @function font.symbolFill
185 * @param {number} id - a 16-bit number of a symbol
186 * @returns {string} character for symbol fill
187 * @example
188 * font.symbolFill(1)
189 *
190 * return '􀀁'
191 */
192const symbolFill$2 = function (id) {
193 return String.fromCodePoint(id + 0x100000);
194};
195
196/**
197 * Function that creates two text elements for a symbol using an id
198 * @function font.symbolText
199 * @param {number} id - a 16-bit number of a symbol
200 * @returns {string} SVG segment for line and fill
201 * @example
202 * font.symbolText(1)
203 *
204 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
205 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
206 */
207const symbolText$2 = function (id) {
208 return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">${symbolFill$2(id)}</text>
209 <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">${symbolLine$2(id)}</text>`;
210};
211
212/**
213 * Function that executes a callback function once the Sutton SignWriiting Line and Fill fonts are ready to use
214 * @function font.cssLoaded
215 * @param {function} callback - a callback function to execute when fonts are ready
216 * @example
217 * const callback = () => {
218 * console.log("Sutton SignWriting Line and Fill fonts are ready to use")
219 * }
220 *
221 * font.cssLoaded( callback )
222 */
223const cssLoaded = function (callback) {
224 let lineReady = false;
225 let fillReady = false;
226 cssLoadedLine(() => {
227 lineReady = true;
228 });
229 cssLoadedFill(() => {
230 fillReady = true;
231 });
232 const cssCheck = setInterval(function () {
233 if (lineReady && fillReady) {
234 clearInterval(cssCheck);
235 callback();
236 }
237 }, 100);
238};
239
240/**
241 * Function that executes a callback function once the Sutton SignWriiting Line font is ready to use
242 * @function font.cssLoadedLine
243 * @param {function} callback - a callback function to execute when line font is ready
244 * @example
245 * const callback = () => {
246 * console.log("Sutton SignWriting Line font is ready to use")
247 * }
248 *
249 * font.cssLoadedLine( callback )
250 */
251const cssLoadedLine = function (callback) {
252 if (!symbolSize$2(1)) {
253 const cssCheck = setInterval(function () {
254 if (symbolSize$2(1)) {
255 clearInterval(cssCheck);
256 callback();
257 }
258 }, 100);
259 } else {
260 callback();
261 }
262};
263
264/**
265 * Function that executes a callback function once the Sutton SignWriiting Fill font is ready to use
266 * @function font.cssLoadedFill
267 * @param {function} callback - a callback function to execute when fill font is ready
268 * @example
269 * const callback = () => {
270 * console.log("Sutton SignWriting Fill font is ready to use")
271 * }
272 *
273 * font.cssLoadedFill( callback )
274 */
275const cssLoadedFill = function (callback) {
276 const fillReady = function () {
277 const canvaser = document.createElement("canvas");
278 canvaser.width = 15;
279 canvaser.height = 30;
280 const context = canvaser.getContext("2d");
281 context.font = "30px 'SuttonSignWritingFill'";
282 context.fillText(symbolFill$2(1), 0, 0);
283 const imgData = context.getImageData(0, 0, 15, 30).data;
284 return !imgData.every(item => item === 0);
285 };
286 if (!fillReady()) {
287 const cssCheck = setInterval(function () {
288 if (fillReady()) {
289 clearInterval(cssCheck);
290 callback();
291 }
292 }, 100);
293 } else {
294 callback();
295 }
296};
297
298/** The font module contains functions for handing the TrueType fonts.
299 * @module font
300 */
301
302var index$2 = /*#__PURE__*/Object.freeze({
303 __proto__: null,
304 cssAppend: cssAppend,
305 cssLoaded: cssLoaded,
306 cssLoadedLine: cssLoadedLine,
307 cssLoadedFill: cssLoadedFill,
308 symbolSize: symbolSize$2,
309 symbolLine: symbolLine$2,
310 symbolFill: symbolFill$2,
311 symbolText: symbolText$2
312});
313
314/**
315* Sutton SignWriting Core Module v1.5.11 (https://github.com/sutton-signwriting/core)
316* Author: Steve Slevinski (https://SteveSlevinski.me)
317* convert.mjs is released under the MIT License.
318*/
319
320/**
321 * Function to convert a number to an SWU number character
322 * @function convert.num2swu
323 * @param {number} num - Integer value for number
324 * @returns {string} SWU number character
325 * @example
326 * convert.num2swu(500)
327 *
328 * return '𝤆'
329 */
330const num2swu$1 = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250);
331
332/**
333 * Function to convert an array of x,y integers to two SWU number characters
334 * @function convert.coord2swu
335 * @param {number[]} coord - Array of x,y integers
336 * @returns {string} Two SWU number character
337 * @example
338 * convert.coord2swu([500, 500])
339 *
340 * return '𝤆𝤆'
341 */
342const coord2swu$1 = coord => coord.map(num => num2swu$1(num)).join('');
343
344/**
345 * Function to convert an SWU symbol character to a code point on plane 4
346 * @function convert.swu2code
347 * @param {string} swuSym - SWU symbol character
348 * @returns {number} Code point on plane 4
349 * @example
350 * convert.swu2code('񀀁')
351 *
352 * return 0x40001
353 */
354const swu2code$1 = swuSym => parseInt(swuSym.codePointAt(0));
355
356/**
357 * Function to convert a code point on plane 4 to an SWU symbol character
358 * @function convert.code2swu
359 * @param {number} code - Code point on plane 4
360 * @returns {string} SWU symbol character
361 * @example
362 * convert.code2swu(0x40001)
363 *
364 * return '񀀁'
365 */
366const code2swu = code => String.fromCodePoint(code);
367
368/**
369 * Function to convert an SWU symbol character to a 16-bit ID
370 * @function convert.swu2id
371 * @param {string} swuSym - SWU symbol character
372 * @returns {number} 16-bit ID
373 * @example
374 * convert.swu2id('񀀁')
375 *
376 * return 1
377 */
378const swu2id = swuSym => swu2code$1(swuSym) - 0x40000;
379
380/**
381 * Function to convert an FSW symbol key to a 16-bit ID
382 * @function convert.key2id
383 * @param {string} key - FSW symbol key
384 * @returns {number} 16-bit ID
385 * @example
386 * convert.key2id('S10000')
387 *
388 * return 1
389 */
390const key2id = key => 1 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16);
391
392/**
393 * Function to convert an SWU symbol character to an FSW symbol key
394 * @function convert.swu2key
395 * @param {string} swuSym - SWU symbol character
396 * @returns {string} FSW symbol key
397 * @example
398 * convert.swu2key('񀀁')
399 *
400 * return 'S10000'
401 */
402const swu2key = swuSym => {
403 const symcode = swu2code$1(swuSym) - 0x40001;
404 const base = parseInt(symcode / 96);
405 const fill = parseInt((symcode - base * 96) / 16);
406 const rotation = parseInt(symcode - base * 96 - fill * 16);
407 return 'S' + (base + 0x100).toString(16) + fill.toString(16) + rotation.toString(16);
408};
409
410/**
411 * Function to convert an FSW symbol key to an SWU symbol character
412 * @function convert.key2swu
413 * @param {string} key - FSW symbol key
414 * @returns {string} SWU symbol character
415 * @example
416 * convert.key2swu('S10000')
417 *
418 * return '񀀁'
419 */
420const key2swu = key => code2swu(0x40001 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16));
421
422/* support ongoing development */
423/* https://patreon.com/signwriting */
424/* https://donate.sutton-signwriting.io */
425
426/**
427* Sutton SignWriting Core Module v1.5.11 (https://github.com/sutton-signwriting/core)
428* Author: Steve Slevinski (https://SteveSlevinski.me)
429* fsw.mjs is released under the MIT License.
430*/
431
432/**
433 * Object of regular expressions for FSW strings
434 *
435 * @alias fsw.re
436 * @property {string} symbol - regular expressions for a symbol
437 * @property {string} coord - regular expressions for a coordinate
438 * @property {string} sort - regular expressions for the sorting marker
439 * @property {string} box - regular expression for a signbox marker
440 * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
441 * @property {string} spatial - regular expression for a symbol followed by a coordinate
442 * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
443 * @property {string} sign - regular expression for an optional prefix followed by a signbox
444 * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
445 */
446let re$1$1 = {
447 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
448 'coord': '[0-9]{3}x[0-9]{3}',
449 'sort': 'A',
450 'box': '[BLMR]'
451};
452re$1$1.prefix = `(?:${re$1$1.sort}(?:${re$1$1.symbol})+)`;
453re$1$1.spatial = `${re$1$1.symbol}${re$1$1.coord}`;
454re$1$1.signbox = `${re$1$1.box}${re$1$1.coord}(?:${re$1$1.spatial})*`;
455re$1$1.sign = `${re$1$1.prefix}?${re$1$1.signbox}`;
456re$1$1.sortable = `${re$1$1.prefix}${re$1$1.signbox}`;
457
458/**
459 * Object of regular expressions for style strings
460 *
461 * @alias style.re
462 * @type {object}
463 * @property {string} colorize - regular expression for colorize section
464 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
465 * @property {string} colorname - regular expression for css color name
466 * @property {string} padding - regular expression for padding section
467 * @property {string} zoom - regular expression for zoom section
468 * @property {string} classbase - regular expression for class name definition
469 * @property {string} id - regular expression for id definition
470 * @property {string} colorbase - regular expression for color hex or color name
471 * @property {string} color - regular expression for single color entry
472 * @property {string} colors - regular expression for double color entry
473 * @property {string} background - regular expression for background section
474 * @property {string} detail - regular expression for color details for line and optional fill
475 * @property {string} detailsym - regular expression for color details for individual symbols
476 * @property {string} classes - regular expression for one or more class names
477 * @property {string} full - full regular expression for style string
478 */
479let re$3 = {
480 'colorize': 'C',
481 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
482 'colorname': '[a-zA-Z]+',
483 'padding': 'P[0-9]{2}',
484 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
485 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
486 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
487};
488re$3.colorbase = `(?:${re$3.colorhex}|${re$3.colorname})`;
489re$3.color = `_${re$3.colorbase}_`;
490re$3.colors = `_${re$3.colorbase}(?:,${re$3.colorbase})?_`;
491re$3.background = `G${re$3.color}`;
492re$3.detail = `D${re$3.colors}`;
493re$3.detailsym = `D[0-9]{2}${re$3.colors}`;
494re$3.classes = `${re$3.classbase}(?: ${re$3.classbase})*`;
495re$3.full = `-(${re$3.colorize})?(${re$3.padding})?(${re$3.background})?(${re$3.detail})?(${re$3.zoom})?(?:-((?:${re$3.detailsym})*))?(?:-(${re$3.classes})?!(?:(${re$3.id})!)?)?`;
496
497const prefixColor$2 = color => {
498 const regex = new RegExp(`^${re$3.colorhex}$`);
499 return (regex.test(color) ? '#' : '') + color;
500};
501const definedProps$2 = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
502
503/**
504 * Function to parse style string to object
505 * @function style.parse
506 * @param {string} styleString - a style string
507 * @returns {StyleObject} elements of style string
508 * @example
509 * style.parse('-CP10G_blue_D_red,Cyan_')
510 *
511 * return {
512 * 'colorize': true,
513 * 'padding': 10,
514 * 'background': 'blue',
515 * 'detail': ['red', 'Cyan']
516 * }
517 */
518const parse$1$1 = styleString => {
519 const regex = `^${re$3.full}`;
520 const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
521 return definedProps$2({
522 'colorize': !m[1] ? undefined : !!m[1],
523 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
524 'background': !m[3] ? undefined : prefixColor$2(m[3].slice(2, -1)),
525 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor$2),
526 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
527 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re$3.detailsym, 'g')).map(val => {
528 const parts = val.split('_');
529 const detail = parts[1].split(',').map(prefixColor$2);
530 return {
531 'index': parseInt(parts[0].slice(1)),
532 'detail': detail
533 };
534 }),
535 'classes': !m[7] ? undefined : m[7],
536 'id': !m[8] ? undefined : m[8]
537 });
538};
539
540/** 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.
541 * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
542 * @module convert
543 */
544
545/**
546 * Function to convert an FSW coordinate string to an array of x,y integers
547 * @function convert.fsw2coord
548 * @param {string} fswCoord - An FSW coordinate string
549 * @returns {number[]} Array of x,y integers
550 * @example
551 * convert.fsw2coord('500x500')
552 *
553 * return [500, 500]
554 */
555const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
556
557const parse$3 = {
558 /**
559 * Function to parse an fsw symbol with optional coordinate and style string
560 * @function fsw.parse.symbol
561 * @param {string} fswSym - an fsw symbol
562 * @returns {SymbolObject} elements of fsw symbol
563 * @example
564 * fsw.parse.symbol('S10000500x500-C')
565 *
566 * return {
567 * 'symbol': 'S10000',
568 * 'coord': [500, 500],
569 * 'style': '-C'
570 * }
571 */
572 symbol: fswSym => {
573 const regex = `^(${re$1$1.symbol})(${re$1$1.coord})?(${re$3.full})?`;
574 const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
575 return {
576 'symbol': symbol ? symbol[1] : undefined,
577 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
578 'style': symbol ? symbol[3] : undefined
579 };
580 },
581 /**
582 * Function to parse an fsw sign with style string
583 * @function fsw.parse.sign
584 * @param {string} fswSign - an fsw sign
585 * @returns { SignObject } elements of fsw sign
586 * @example
587 * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
588 *
589 * return {
590 * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
591 * box: 'M',
592 * max: [525, 535],
593 * spatials: [
594 * {
595 * symbol: 'S2e748',
596 * coord: [483, 510]
597 * },
598 * {
599 * symbol: 'S10011',
600 * coord: [501, 466]
601 * },
602 * {
603 * symbol: 'S2e704',
604 * coord: [510, 500]
605 * },
606 * {
607 * symbol: 'S10019',
608 * coord: [476, 475]
609 * }
610 * ],
611 * style: '-C'
612 * }
613 */
614 sign: fswSign => {
615 const regex = `^(${re$1$1.prefix})?(${re$1$1.signbox})(${re$3.full})?`;
616 const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
617 if (sign) {
618 return {
619 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
620 'box': sign[2][0],
621 'max': fsw2coord(sign[2].slice(1, 8)),
622 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
623 return {
624 symbol: m.slice(0, 6),
625 coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
626 };
627 }),
628 'style': sign[3]
629 };
630 } else {
631 return {};
632 }
633 },
634 /**
635 * Function to parse an fsw text
636 * @function fsw.parse.text
637 * @param {string} fswText - an fsw text
638 * @returns {string[]} fsw signs and punctuations
639 * @example
640 * fsw.parse.text('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496')
641 *
642 * return [
643 * 'AS14c20S27106M518x529S14c20481x471S27106503x489',
644 * 'AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468',
645 * 'S38800464x496'
646 * ]
647 */
648 text: fswText => {
649 if (typeof fswText !== 'string') return [];
650 const regex = `(${re$1$1.sign}(${re$3.full})?|${re$1$1.spatial}(${re$3.full})?)`;
651 const matches = fswText.match(new RegExp(regex, 'g'));
652 return matches ? [...matches] : [];
653 }
654};
655
656const compose$2 = {
657 /**
658 * Function to compose an fsw symbol with optional coordinate and style string
659 * @function fsw.compose.symbol
660 * @param {SymbolObject} fswSymObject - an fsw symbol object
661 * @returns {string} an fsw symbol string
662 * @example
663 * fsw.compose.symbol({
664 * 'symbol': 'S10000',
665 * 'coord': [480, 480],
666 * 'style': '-C'
667 * })
668 *
669 * return 'S10000480x480-C'
670 */
671 symbol: fswSymObject => {
672 if (typeof fswSymObject.symbol === 'string') {
673 const symbol = (fswSymObject.symbol.match(re$1$1.symbol) || [''])[0];
674 if (symbol) {
675 const x = (fswSymObject.coord && fswSymObject.coord[0] || '').toString();
676 const y = (fswSymObject.coord && fswSymObject.coord[1] || '').toString();
677 const coord = ((x + 'x' + y).match(re$1$1.coord) || [''])[0] || '';
678 const styleStr = typeof fswSymObject.style === 'string' && (fswSymObject.style.match(re$3.full) || [''])[0] || '';
679 return symbol + coord + styleStr;
680 }
681 }
682 return undefined;
683 },
684 /**
685 * Function to compose an fsw sign with style string
686 * @function fsw.compose.sign
687 * @param {SignObject} fswSignObject - an fsw symbol object
688 * @returns {string} an fsw sign string
689 * @example
690 * fsw.compose.sign({
691 * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
692 * box: 'M',
693 * max: [525, 535],
694 * spatials: [
695 * {
696 * symbol: 'S2e748',
697 * coord: [483, 510]
698 * },
699 * {
700 * symbol: 'S10011',
701 * coord: [501, 466]
702 * },
703 * {
704 * symbol: 'S2e704',
705 * coord: [510, 500]
706 * },
707 * {
708 * symbol: 'S10019',
709 * coord: [476, 475]
710 * }
711 * ],
712 * style: '-C'
713 * })
714 *
715 * return 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C'
716 */
717 sign: fswSignObject => {
718 let box = typeof fswSignObject.box !== 'string' ? 'M' : (fswSignObject.box + 'M').match(re$1$1.box);
719 const x = (fswSignObject.max && fswSignObject.max[0] || '').toString();
720 const y = (fswSignObject.max && fswSignObject.max[1] || '').toString();
721 const max = ((x + 'x' + y).match(re$1$1.coord) || [''])[0] || '';
722 if (!max) return undefined;
723 let prefix = '';
724 if (fswSignObject.sequence && Array.isArray(fswSignObject.sequence)) {
725 prefix = fswSignObject.sequence.map(key => (key.match(re$1$1.symbol) || [''])[0]).join('');
726 prefix = prefix ? 'A' + prefix : '';
727 }
728 let signbox = '';
729 if (fswSignObject.spatials && Array.isArray(fswSignObject.spatials)) {
730 signbox = fswSignObject.spatials.map(spatial => {
731 if (typeof spatial.symbol === 'string') {
732 const symbol = (spatial.symbol.match(re$1$1.symbol) || [''])[0];
733 if (symbol) {
734 const x = (spatial.coord && spatial.coord[0] || '').toString();
735 const y = (spatial.coord && spatial.coord[1] || '').toString();
736 const coord = ((x + 'x' + y).match(re$1$1.coord) || [''])[0] || '';
737 if (coord) {
738 return symbol + coord;
739 }
740 }
741 }
742 return '';
743 }).join('');
744 }
745 const styleStr = typeof fswSignObject.style === 'string' && (fswSignObject.style.match(re$3.full) || [''])[0] || '';
746 return prefix + box + max + signbox + styleStr;
747 }
748};
749
750/**
751 * Function to gather sizing information about an fsw sign or symbol
752 * @function fsw.info
753 * @param {string} fsw - an fsw sign or symbol
754 * @returns {SegmentInfo} information about the fsw string
755 * @example
756 * fsw.info('AS14c20S27106L518x529S14c20481x471S27106503x489-P10Z2')
757 *
758 * return {
759 * minX: 481,
760 * minY: 471,
761 * width: 37,
762 * height: 58,
763 * lane: -1,
764 * padding: 10,
765 * segment: 'sign',
766 * zoom: 2
767 * }
768 */
769const info$1 = fsw => {
770 let lanes = {
771 "B": 0,
772 "L": -1,
773 "M": 0,
774 "R": 1
775 };
776 let parsed = parse$3.sign(fsw);
777 let width, height, segment, x1, x2, y1, y2, lane;
778 if (parsed.spatials) {
779 x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
780 x2 = parsed.max[0];
781 width = x2 - x1;
782 y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
783 y2 = parsed.max[1];
784 height = y2 - y1;
785 segment = 'sign';
786 lane = parsed.box;
787 } else {
788 parsed = parse$3.symbol(fsw);
789 lane = "M";
790 if (parsed.coord) {
791 x1 = parsed.coord[0];
792 width = (500 - x1) * 2;
793 y1 = parsed.coord[1];
794 height = (500 - y1) * 2;
795 segment = 'symbol';
796 } else {
797 x1 = 490;
798 width = 20;
799 y1 = 490;
800 height = 20;
801 segment = 'none';
802 }
803 }
804 let style = parse$1$1(parsed.style);
805 let zoom = style.zoom || 1;
806 let padding = style.padding || 0;
807 return {
808 minX: x1,
809 minY: y1,
810 width: width,
811 height: height,
812 segment: segment,
813 lane: lanes[lane],
814 padding: padding,
815 zoom: zoom
816 };
817};
818
819const columnDefaults$1 = {
820 'height': 500,
821 'width': 150,
822 'offset': 50,
823 'pad': 20,
824 'margin': 5,
825 'dynamic': false,
826 'background': undefined,
827 'punctuation': {
828 'spacing': true,
829 'pad': 30,
830 'pull': true
831 },
832 'style': {
833 'detail': ['black', 'white'],
834 'zoom': 1
835 }
836};
837
838/**
839 * Function to an object of column options with default values
840 *
841 * @function fsw.columnDefaultsMerge
842 * @param {ColumnOptions} options - object of column options
843 * @returns {ColumnOptions} object of column options merged with column defaults
844 * @example
845 * fsw.columnDefaultsMerge({height: 500,width:150})
846 *
847 * return {
848 * "height": 500,
849 * "width": 150,
850 * "offset": 50,
851 * "pad": 20,
852 * "margin": 5,
853 * "dynamic": false,
854 * "punctuation": {
855 * "spacing": true,
856 * "pad": 30,
857 * "pull": true
858 * },
859 * "style": {
860 * "detail": [
861 * "black",
862 * "white"
863 * ],
864 * "zoom": 1
865 * }
866 * }
867 */
868const columnDefaultsMerge$1 = options => {
869 if (typeof options !== 'object') options = {};
870 return {
871 ...columnDefaults$1,
872 ...options,
873 punctuation: {
874 ...columnDefaults$1.punctuation,
875 ...options.punctuation
876 },
877 style: {
878 ...columnDefaults$1.style,
879 ...options.style
880 }
881 };
882};
883
884/**
885 * Function to transform an FSW text to an array of columns
886 *
887 * @function fsw.columns
888 * @param {string} fswText - FSW text of signs and punctuation
889 * @param {ColumnOptions} options - object of column options
890 * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
891 * @example
892 * fsw.columns('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496', {height: 500,width:150})
893 *
894 * return {
895 * "options": {
896 * "height": 500,
897 * "width": 150,
898 * "offset": 50,
899 * "pad": 20,
900 * "margin": 5,
901 * "dynamic": false,
902 * "punctuation": {
903 * "spacing": true,
904 * "pad": 30,
905 * "pull": true
906 * },
907 * "style": {
908 * "detail": [
909 * "black",
910 * "white"
911 * ],
912 * "zoom": 1
913 * }
914 * },
915 * "widths": [
916 * 150
917 * ],
918 * "columns": [
919 * [
920 * {
921 * "x": 56,
922 * "y": 20,
923 * "minX": 481,
924 * "minY": 471,
925 * "width": 37,
926 * "height": 58,
927 * "lane": 0,
928 * "padding": 0,
929 * "segment": "sign",
930 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
931 * "zoom": 1
932 * },
933 * {
934 * "x": 57,
935 * "y": 118,
936 * "minX": 482,
937 * "minY": 468,
938 * "width": 36,
939 * "height": 65,
940 * "lane": 0,
941 * "padding": 0,
942 * "segment": "sign",
943 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
944 * "zoom": 1
945 * },
946 * {
947 * "x": 39,
948 * "y": 203,
949 * "minX": 464,
950 * "minY": 496,
951 * "width": 72,
952 * "height": 8,
953 * "lane": 0,
954 * "padding": 0,
955 * "segment": "symbol",
956 * "text": "S38800464x496",
957 * "zoom": 1
958 * }
959 * ]
960 * ]
961 * }
962 */
963const columns$1 = (fswText, options) => {
964 if (typeof fswText !== 'string') return {};
965 const values = columnDefaultsMerge$1(options);
966 let input = parse$3.text(fswText);
967 let cursor = 0;
968 let cols = [];
969 let col = [];
970 let plus = 0;
971 let center = parseInt(values.width / 2);
972 let maxHeight = values.height - values.margin;
973 let pullable = true;
974 let finalize = false;
975 for (let val of input) {
976 let informed = info$1(val);
977 cursor += plus;
978 if (values.punctuation.spacing) {
979 cursor += informed.segment == 'sign' ? values.pad : 0;
980 } else {
981 cursor += values.pad;
982 }
983 finalize = cursor + informed.height > maxHeight;
984 if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
985 finalize = false;
986 pullable = false;
987 }
988 if (col.length == 0) {
989 finalize = false;
990 }
991 if (finalize) {
992 cursor = values.pad;
993 cols.push(col);
994 col = [];
995 pullable = true;
996 }
997 col.push(Object.assign(informed, {
998 x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
999 y: cursor,
1000 text: val
1001 }));
1002 cursor += informed.height * informed.zoom * values.style.zoom;
1003 if (values.punctuation.spacing) {
1004 plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
1005 } else {
1006 plus = values.pad;
1007 }
1008 }
1009 if (col.length) {
1010 cols.push(col);
1011 }
1012
1013 // over height issue when pulling punctuation
1014 if (values.punctuation.pull) {
1015 for (let col of cols) {
1016 let last = col[col.length - 1];
1017 let diff = last.y + last.height - (values.height - values.margin);
1018 if (diff > 0) {
1019 let adj = parseInt(diff / col.length) + 1;
1020 for (let i in col) {
1021 col[i].y -= adj * i + adj;
1022 }
1023 }
1024 }
1025 }
1026
1027 // contract, expand, adjust
1028 let widths = [];
1029 for (let col of cols) {
1030 let min = [center - values.offset - values.pad];
1031 let max = [center + values.offset + values.pad];
1032 for (let item of col) {
1033 min.push(item.x - values.pad);
1034 max.push(item.x + item.width + values.pad);
1035 }
1036 min = Math.min(...min);
1037 max = Math.max(...max);
1038 let width = values.width;
1039 let adj = 0;
1040 if (!values.dynamic) {
1041 adj = center - parseInt((min + max) / 2);
1042 } else {
1043 width = max - min;
1044 adj = -min;
1045 }
1046 for (let item of col) {
1047 item.x += adj;
1048 }
1049 widths.push(width);
1050 }
1051 return {
1052 'options': values,
1053 'widths': widths,
1054 'columns': cols
1055 };
1056};
1057
1058/**
1059 * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
1060 * @alias fsw.category
1061 * @type {number[]}
1062 */
1063const category$1 = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
1064
1065/**
1066 * Object of symbol ranges with starting and ending numbers.
1067 *
1068 * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
1069 * @alias fsw.ranges
1070 * @type {object}
1071 */
1072const ranges$1 = {
1073 'all': [0x100, 0x38b],
1074 'writing': [0x100, 0x37e],
1075 'hand': [0x100, 0x204],
1076 'movement': [0x205, 0x2f6],
1077 'dynamic': [0x2f7, 0x2fe],
1078 'head': [0x2ff, 0x36c],
1079 'hcenter': [0x2ff, 0x36c],
1080 'vcenter': [0x2ff, 0x375],
1081 'trunk': [0x36d, 0x375],
1082 'limb': [0x376, 0x37e],
1083 'location': [0x37f, 0x386],
1084 'punctuation': [0x387, 0x38b]
1085};
1086
1087/**
1088 * Array of colors associated with the seven symbol categories.
1089 * @alias fsw.colors
1090 * @type {string[]}
1091 */
1092const colors$1 = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
1093
1094/**
1095 * Function that returns the standardized color for a symbol.
1096 * @function fsw.colorize
1097 * @param {string} key - an FSW symbol key
1098 * @returns {string} name of standardized color for symbol
1099 * @example
1100 * fsw.colorize('S10000')
1101 *
1102 * return '#0000CC'
1103 */
1104const colorize$1 = key => {
1105 const parsed = parse$3.symbol(key);
1106 let color = '#000000';
1107 if (parsed.symbol) {
1108 const dec = parseInt(parsed.symbol.slice(1, 4), 16);
1109 const index = category$1.findIndex(val => val > dec);
1110 color = colors$1[index < 0 ? 6 : index - 1];
1111 }
1112 return color;
1113};
1114
1115/* support ongoing development */
1116/* https://patreon.com/signwriting */
1117/* https://donate.sutton-signwriting.io */
1118
1119/**
1120 * Function that returns the size of a symbol using an FSW symbol key
1121 * @function fsw.symbolSize
1122 * @param {string} fsw - an FSW symbol key
1123 * @returns {number[]} width and height of symbol
1124 * @example
1125 * fsw.symbolSize("S10000")
1126 *
1127 * return [15,30]
1128 */
1129const symbolSize$1 = function (fsw) {
1130 const parsed = parse$3.symbol(fsw);
1131 if (!parsed.symbol) {
1132 return undefined;
1133 }
1134 return symbolSize$2(key2id(fsw));
1135};
1136
1137/**
1138 * Function that returns a plane 15 character for a symbol line using an FSW symbol key
1139 * @function fsw.symbolLine
1140 * @param {string} fsw - an FSW symbol key
1141 * @returns {string} character for symbol line
1142 * @example
1143 * fsw.symbolLine('S10000')
1144 *
1145 * return '󰀁'
1146 */
1147const symbolLine$1 = function (fsw) {
1148 return symbolLine$2(key2id(fsw));
1149};
1150
1151/**
1152 * Function that returns a plane 16 character for a symbol fill using an FSW symbol key
1153 * @function fsw.symbolFill
1154 * @param {string} fsw - an FSW symbol key
1155 * @returns {string} character for symbol fill
1156 * @example
1157 * font.symbolFill('S10000')
1158 *
1159 * return '􀀁'
1160 */
1161const symbolFill$1 = function (fsw) {
1162 return symbolFill$2(key2id(fsw));
1163};
1164
1165/**
1166 * Function that creates two text elements for a symbol using an FSW symbol key
1167 * @function fsw.symbolText
1168 * @param {string} fsw - an FSW symbol key
1169 * @returns {string} svg segment for line and fill
1170 * @example
1171 * fsw.symbolText('S10000')
1172 *
1173 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
1174 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
1175 */
1176const symbolText$1 = function (fsw) {
1177 return symbolText$2(key2id(fsw));
1178};
1179
1180/**
1181* Sutton SignWriting Core Module v1.5.11 (https://github.com/sutton-signwriting/core)
1182* Author: Steve Slevinski (https://SteveSlevinski.me)
1183* style.mjs is released under the MIT License.
1184*/
1185
1186/**
1187 * Object of regular expressions for style strings
1188 *
1189 * @alias style.re
1190 * @type {object}
1191 * @property {string} colorize - regular expression for colorize section
1192 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
1193 * @property {string} colorname - regular expression for css color name
1194 * @property {string} padding - regular expression for padding section
1195 * @property {string} zoom - regular expression for zoom section
1196 * @property {string} classbase - regular expression for class name definition
1197 * @property {string} id - regular expression for id definition
1198 * @property {string} colorbase - regular expression for color hex or color name
1199 * @property {string} color - regular expression for single color entry
1200 * @property {string} colors - regular expression for double color entry
1201 * @property {string} background - regular expression for background section
1202 * @property {string} detail - regular expression for color details for line and optional fill
1203 * @property {string} detailsym - regular expression for color details for individual symbols
1204 * @property {string} classes - regular expression for one or more class names
1205 * @property {string} full - full regular expression for style string
1206 */
1207let re$2 = {
1208 'colorize': 'C',
1209 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
1210 'colorname': '[a-zA-Z]+',
1211 'padding': 'P[0-9]{2}',
1212 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
1213 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
1214 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
1215};
1216re$2.colorbase = `(?:${re$2.colorhex}|${re$2.colorname})`;
1217re$2.color = `_${re$2.colorbase}_`;
1218re$2.colors = `_${re$2.colorbase}(?:,${re$2.colorbase})?_`;
1219re$2.background = `G${re$2.color}`;
1220re$2.detail = `D${re$2.colors}`;
1221re$2.detailsym = `D[0-9]{2}${re$2.colors}`;
1222re$2.classes = `${re$2.classbase}(?: ${re$2.classbase})*`;
1223re$2.full = `-(${re$2.colorize})?(${re$2.padding})?(${re$2.background})?(${re$2.detail})?(${re$2.zoom})?(?:-((?:${re$2.detailsym})*))?(?:-(${re$2.classes})?!(?:(${re$2.id})!)?)?`;
1224
1225const prefixColor$1 = color => {
1226 const regex = new RegExp(`^${re$2.colorhex}$`);
1227 return (regex.test(color) ? '#' : '') + color;
1228};
1229const definedProps$1 = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
1230
1231/**
1232 * Function to parse style string to object
1233 * @function style.parse
1234 * @param {string} styleString - a style string
1235 * @returns {StyleObject} elements of style string
1236 * @example
1237 * style.parse('-CP10G_blue_D_red,Cyan_')
1238 *
1239 * return {
1240 * 'colorize': true,
1241 * 'padding': 10,
1242 * 'background': 'blue',
1243 * 'detail': ['red', 'Cyan']
1244 * }
1245 */
1246const parse$2 = styleString => {
1247 const regex = `^${re$2.full}`;
1248 const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
1249 return definedProps$1({
1250 'colorize': !m[1] ? undefined : !!m[1],
1251 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
1252 'background': !m[3] ? undefined : prefixColor$1(m[3].slice(2, -1)),
1253 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor$1),
1254 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
1255 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re$2.detailsym, 'g')).map(val => {
1256 const parts = val.split('_');
1257 const detail = parts[1].split(',').map(prefixColor$1);
1258 return {
1259 'index': parseInt(parts[0].slice(1)),
1260 'detail': detail
1261 };
1262 }),
1263 'classes': !m[7] ? undefined : m[7],
1264 'id': !m[8] ? undefined : m[8]
1265 });
1266};
1267
1268/**
1269 * Function to compose style string from object
1270 * @function style.compose
1271 * @param {StyleObject} styleObject - an object of style options
1272 * @returns {string} style string
1273 * @example
1274 * style.compose({
1275 * 'colorize': true,
1276 * 'padding': 10,
1277 * 'background': 'blue',
1278 * 'detail': ['red', 'Cyan'],
1279 * 'zoom': 1.1,
1280 * 'detailsym': [
1281 * {
1282 * 'index': 1,
1283 * 'detail': ['#ff00ff']
1284 * },
1285 * {
1286 * 'index': 2,
1287 * 'detail': ['yellow', 'green']
1288 * }
1289 * ],
1290 * 'classes': 'primary blinking',
1291 * 'id': 'cursor'
1292 * })
1293 *
1294 * return '-CP10G_blue_D_red,Cyan_Z1.1-D01_ff00ff_D02_yellow,green_-primary blinking!cursor!'
1295 */
1296const compose$1 = styleObject => {
1297 if (typeof styleObject !== 'object' || styleObject === null) return undefined;
1298
1299 // three sections
1300 let style1 = '-';
1301 style1 += !styleObject.colorize ? '' : 'C';
1302 const padding = parseInt(styleObject.padding);
1303 style1 += !padding || padding <= 0 || padding > 99 ? '' : 'P' + (padding > 9 ? padding : '0' + padding);
1304 const background = !styleObject.background || !(typeof styleObject.background === 'string') ? undefined : styleObject.background.match(re$2.colorbase)[0];
1305 style1 += !background ? '' : 'G_' + background + '_';
1306 const detail1 = !styleObject.detail || !styleObject.detail[0] || !(typeof styleObject.detail[0] === 'string') ? undefined : styleObject.detail[0].match(re$2.colorbase)[0];
1307 const detail2 = !styleObject.detail || !styleObject.detail[1] || !(typeof styleObject.detail[1] === 'string') ? undefined : styleObject.detail[1].match(re$2.colorbase)[0];
1308 if (detail1) {
1309 style1 += 'D_' + detail1;
1310 if (detail2) {
1311 style1 += ',' + detail2;
1312 }
1313 style1 += '_';
1314 }
1315 const zoom = styleObject.zoom === 'x' ? 'x' : parseFloat(styleObject.zoom);
1316 style1 += !zoom || zoom <= 0 ? '' : 'Z' + zoom;
1317 let style2 = '';
1318 const detailsym = !styleObject.detailsym || !Array.isArray(styleObject.detailsym) ? [] : styleObject.detailsym.map(styleObject => {
1319 const index = parseInt(styleObject.index);
1320 if (!index || index <= 0 || index > 99) return '';
1321 let style = 'D' + (index > 9 ? index : '0' + index);
1322 const detail1 = !styleObject.detail || !styleObject.detail[0] ? undefined : styleObject.detail[0].match(re$2.colorbase)[0];
1323 const detail2 = !styleObject.detail || !styleObject.detail[1] ? undefined : styleObject.detail[1].match(re$2.colorbase)[0];
1324 if (detail1) {
1325 style += '_' + detail1;
1326 if (detail2) {
1327 style += ',' + detail2;
1328 }
1329 style += '_';
1330 }
1331 return style;
1332 });
1333 style2 += detailsym.join('');
1334 let style3 = '';
1335 const classes = !styleObject.classes || !(typeof styleObject.classes === 'string') ? undefined : styleObject.classes.match(re$2.classes)[0];
1336 style3 += !classes ? '' : classes;
1337 const id = !styleObject.id || !(typeof styleObject.id === 'string') ? undefined : styleObject.id.match(re$2.id)[0];
1338 style3 += classes || id ? '!' : '';
1339 style3 += !id ? '' : id + '!';
1340 return style1 + (style2 || style3 ? '-' + style2 : '') + (style3 ? '-' + style3 : '');
1341};
1342
1343/* support ongoing development */
1344/* https://patreon.com/signwriting */
1345/* https://donate.sutton-signwriting.io */
1346
1347/**
1348 * Function that creates an SVG image from an FSW symbol key with an optional style string
1349 * @function fsw.symbolSvgBody
1350 * @param {string} fswSym - an FSW symbol key with optional style string
1351 * @returns {string} body of SVG for symbol
1352 * @example
1353 * fsw.symbolSvgBody('S10000')
1354 *
1355 * return `<text font-size="0">S10000</text>
1356 * <g transform="translate(500,500)">
1357 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
1358 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
1359 * </g>`
1360 */
1361const symbolSvgBody$1 = fswSym => {
1362 const parsed = parse$3.symbol(fswSym);
1363 const blank = '';
1364 if (!parsed.symbol) return blank;
1365 let styling = parse$2(parsed.style);
1366 let x1, y1, x2, y2;
1367 if (parsed.coord) {
1368 x1 = parsed.coord[0];
1369 y1 = parsed.coord[1];
1370 x2 = 500 + (500 - x1);
1371 y2 = 500 + (500 - y1);
1372 } else {
1373 let size = symbolSize$1(parsed.symbol);
1374 if (!size) return blank;
1375 x1 = 500 - parseInt((size[0] + 1) / 2);
1376 y1 = 500 - parseInt((size[1] + 1) / 2);
1377 x2 = 500 + (500 - x1);
1378 y2 = 500 + (500 - y1);
1379 }
1380 let symSvg = symbolText$1(parsed.symbol);
1381 symSvg = ` <g transform="translate(${x1},${y1})">
1382${symSvg}
1383 </g>`;
1384 let line;
1385 if (styling.colorize) {
1386 line = colorize$1(parsed.symbol);
1387 } else if (styling.detail) {
1388 line = styling.detail[0];
1389 }
1390 if (line) {
1391 symSvg = symSvg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${line}"`);
1392 }
1393 let fill = styling.detail && styling.detail[1];
1394 if (fill) {
1395 symSvg = symSvg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${fill}"`);
1396 }
1397 let background = '';
1398 if (styling.padding) {
1399 x1 -= styling.padding;
1400 y1 -= styling.padding;
1401 x2 += styling.padding;
1402 y2 += styling.padding;
1403 }
1404 if (styling.background) {
1405 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
1406 }
1407 return ` <text font-size="0">${fswSym}</text>${background}
1408${symSvg}`;
1409};
1410
1411/**
1412 * Function that creates an SVG image from an FSW symbol key with an optional style string
1413 * @function fsw.symbolSvg
1414 * @param {string} fswSym - an FSW symbol key with optional style string
1415 * @returns {string} SVG for symbol
1416 * @example
1417 * fsw.symbolSvg('S10000')
1418 *
1419 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="15" height="30" viewBox="500 500 15 30">
1420 * <text font-size="0">S10000</text>
1421 * <g transform="translate(500,500)">
1422 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
1423 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
1424 * </g>
1425 * </svg>`
1426 */
1427const symbolSvg$1 = fswSym => {
1428 const parsed = parse$3.symbol(fswSym);
1429 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
1430 if (!parsed.symbol) return blank;
1431 let styling = parse$2(parsed.style);
1432 let x1, y1, x2, y2;
1433 if (parsed.coord) {
1434 x1 = parsed.coord[0];
1435 y1 = parsed.coord[1];
1436 x2 = 500 + (500 - x1);
1437 y2 = 500 + (500 - y1);
1438 } else {
1439 let size = symbolSize$1(parsed.symbol);
1440 if (!size) return blank;
1441 x1 = parseInt(500 - size[0] / 2);
1442 y1 = parseInt(500 - size[1] / 2);
1443 x2 = x1 + size[0];
1444 y2 = y1 + size[1];
1445 }
1446 let classes = '';
1447 if (styling.classes) {
1448 classes = ` class="${styling.classes}"`;
1449 }
1450 let id = '';
1451 if (styling.id) {
1452 id = ` id="${styling.id}"`;
1453 }
1454 if (styling.padding) {
1455 x1 -= styling.padding;
1456 y1 -= styling.padding;
1457 x2 += styling.padding;
1458 y2 += styling.padding;
1459 }
1460 let sizing = '';
1461 if (styling.zoom != 'x') {
1462 sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
1463 }
1464 return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
1465${symbolSvgBody$1(fswSym)}
1466</svg>`;
1467};
1468
1469const symbolCanvas$1 = function (fswSym) {
1470 const parsed = parse$3.symbol(fswSym);
1471 if (parsed.symbol) {
1472 let size = symbolSize$1(parsed.symbol);
1473 if (size) {
1474 const canvas = document.createElement('canvas');
1475 const context = canvas.getContext('2d');
1476 let styling = parse$2(parsed.style);
1477 let line = 'black';
1478 if (styling.colorize) {
1479 line = colorize$1(parsed.symbol);
1480 } else if (styling.detail) {
1481 line = styling.detail[0];
1482 }
1483 let fill = styling.detail && styling.detail[1] || 'white';
1484 let x1 = 500;
1485 let x2 = x1 + size[0];
1486 let y1 = 500;
1487 let y2 = y1 + size[1];
1488 if (styling.padding) {
1489 x1 -= styling.padding;
1490 y1 -= styling.padding;
1491 x2 += styling.padding;
1492 y2 += styling.padding;
1493 }
1494 let sizing = 1;
1495 if (styling.zoom != 'x') {
1496 sizing = styling.zoom;
1497 }
1498 let w = (x2 - x1) * sizing;
1499 let h = (y2 - y1) * sizing;
1500 canvas.width = w ? w : 1;
1501 canvas.height = h ? h : 1;
1502 if (styling.background) {
1503 context.rect(0, 0, w, h);
1504 context.fillStyle = styling.background;
1505 context.fill();
1506 }
1507 context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
1508 context.fillStyle = fill;
1509 context.fillText(symbolFill$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
1510 context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
1511 context.fillStyle = line;
1512 context.fillText(symbolLine$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
1513 return canvas;
1514 }
1515 }
1516};
1517
1518/**
1519 * Function that creates a PNG data url from an FSW symbol key with an optional style string
1520 * @function fsw.symbolPng
1521 * @param {string} fswSym - an FSW symbol key with optional style string
1522 * @returns {string} png image for symbol as data url
1523 * @example
1524 * fsw.symbolPng('S10000')
1525 *
1526 * return 'data:image/png;base64,iVBORw...'
1527 */
1528const symbolPng$1 = fswSym => {
1529 const canvas = symbolCanvas$1(fswSym);
1530 const png = canvas.toDataURL("image/png");
1531 canvas.remove();
1532 return png;
1533};
1534
1535const blank$1 = null;
1536
1537/**
1538 * Function that normalizes a symbol with a minimum coordinate for a center of 500,500
1539 * @function fsw.symbolNormalize
1540 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1541 * @returns {string} normalized FSW symbol
1542 * @example
1543 * fsw.symbolNormalize('S10000-CP10G_green_Z2')
1544 *
1545 * return 'S10000493x485-CP10G_green_Z2'
1546 */
1547const symbolNormalize$1 = fswSym => {
1548 const parsed = parse$3.symbol(fswSym);
1549 if (parsed.symbol) {
1550 let size = symbolSize$1(parsed.symbol);
1551 if (size) {
1552 return `${parsed.symbol}${500 - parseInt((size[0] + 1) / 2)}x${500 - parseInt((size[1] + 1) / 2)}${parsed.style || ''}`;
1553 }
1554 } else {
1555 return blank$1;
1556 }
1557};
1558
1559/**
1560 * Function that mirrors a symbol
1561 * @function fsw.symbolMirror
1562 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1563 * @returns {string} mirrored FSW symbol
1564 * @example
1565 * fsw.symbolMirror('S10000')
1566 *
1567 * return 'S10008'
1568 */
1569const symbolMirror$1 = fswSym => {
1570 let parsed = parse$3.symbol(fswSym);
1571 if (!parsed.symbol) {
1572 return fswSym;
1573 }
1574 const size = symbolSize$1(parsed.symbol);
1575 if (!size) {
1576 return fswSym;
1577 }
1578 const base = parsed.symbol.slice(0, 4);
1579 let fill = parsed.symbol.slice(4, 5);
1580 let rot = parseInt(parsed.symbol.slice(5, 6), 16);
1581 const key1 = base + "08";
1582 const key2 = base + "18";
1583 var rAdd;
1584 if (symbolSize$1(key1) || symbolSize$1(key2)) {
1585 rAdd = 8;
1586 } else {
1587 if (rot === 0 || rot == 4) {
1588 rAdd = 0;
1589 }
1590 if (rot == 1 || rot == 5) {
1591 rAdd = 6;
1592 }
1593 if (rot == 2 || rot == 6) {
1594 rAdd = 4;
1595 }
1596 if (rot == 3 || rot == 7) {
1597 rAdd = 2;
1598 }
1599 }
1600 let key = '';
1601 while (!key || !symbolSize$1(key)) {
1602 rot += rAdd;
1603 if (rot > 7 && rAdd < 8) {
1604 rot = rot - 8;
1605 }
1606 if (rot > 15) {
1607 rot = rot - 16;
1608 }
1609 key = base + fill + rot.toString(16);
1610 }
1611 parsed.symbol = key;
1612 return compose$2.symbol(parsed);
1613};
1614
1615/**
1616 * Function that rotates a symbol
1617 * @function fsw.symbolRotate
1618 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1619 * @param {boolean} [clockwise=true] - rotate the symbol clockwise
1620 * @returns {string} rotated FSW symbol
1621 * @example
1622 * fsw.symbolRotate('S10000')
1623 *
1624 * return 'S10007'
1625 */
1626const symbolRotate$1 = (fswSym, clockwise = true) => {
1627 let parsed = parse$3.symbol(fswSym);
1628 if (!parsed.symbol) {
1629 return fswSym;
1630 }
1631 const size = symbolSize$1(parsed.symbol);
1632 if (!size) {
1633 return fswSym;
1634 }
1635 const step = clockwise ? 1 : -1;
1636 const base = parsed.symbol.slice(0, 4);
1637 let fill = parsed.symbol.slice(4, 5);
1638 let rot = parseInt(parsed.symbol.slice(5, 6), 16);
1639 let key = '';
1640 while (!key || !symbolSize$1(key)) {
1641 if (rot > 7) {
1642 rot += step;
1643 if (rot > 15) {
1644 rot = 8;
1645 }
1646 if (rot < 8) {
1647 rot = 15;
1648 }
1649 key = base + fill + rot.toString(16);
1650 } else {
1651 rot -= step;
1652 if (rot > 7) {
1653 rot = 0;
1654 }
1655 if (rot < 0) {
1656 rot = 7;
1657 }
1658 key = base + fill + rot;
1659 }
1660 }
1661 parsed.symbol = key;
1662 return compose$2.symbol(parsed);
1663};
1664
1665/**
1666 * Function that changes the fill of a symbol
1667 * @function fsw.symbolFlop
1668 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1669 * @param {boolean} [positive=true] - increase the symbol fill
1670 * @returns {string} FSW symbol with changed fill
1671 * @example
1672 * fsw.symbolFlop('S10000')
1673 *
1674 * return 'S10010'
1675 */
1676const symbolFlop$1 = (fswSym, positive = true) => {
1677 let parsed = parse$3.symbol(fswSym);
1678 if (!parsed.symbol) {
1679 return fswSym;
1680 }
1681 const size = symbolSize$1(parsed.symbol);
1682 if (!size) {
1683 return fswSym;
1684 }
1685 const step = positive ? 1 : -1;
1686 const base = parsed.symbol.slice(0, 4);
1687 let fill = parseInt(parsed.symbol.slice(4, 5));
1688 let rot = parsed.symbol.slice(5, 6);
1689 let key = '';
1690 while (!key || !symbolSize$1(key)) {
1691 fill += step;
1692 if (fill > 5) {
1693 fill = 0;
1694 }
1695 if (fill < 0) {
1696 fill = 5;
1697 }
1698 key = base + fill + rot;
1699 }
1700 parsed.symbol = key;
1701 return compose$2.symbol(parsed);
1702};
1703
1704/**
1705 * Function that changes the base of a symbol
1706 * @function fsw.symbolScroll
1707 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1708 * @param {boolean} [positive=true] - increase the symbol base
1709 * @returns {string} FSW symbol with changed base
1710 * @example
1711 * fsw.symbolScroll('S10000')
1712 *
1713 * return 'S10100'
1714 */
1715const symbolScroll$1 = (fswSym, positive = true) => {
1716 let parsed = parse$3.symbol(fswSym);
1717 if (!parsed.symbol) {
1718 return fswSym;
1719 }
1720 const size = symbolSize$1(parsed.symbol);
1721 if (!size) {
1722 return fswSym;
1723 }
1724 const step = positive ? 1 : -1;
1725 const base = parseInt(parsed.symbol.slice(1, 4), 16) + step;
1726 const fill = parsed.symbol.slice(4, 5);
1727 const rot = parsed.symbol.slice(5, 6);
1728 const key = 'S' + base.toString(16) + fill + rot;
1729 if (symbolSize$1(key)) {
1730 parsed.symbol = key;
1731 }
1732 return compose$2.symbol(parsed);
1733};
1734
1735/**
1736 * Function that creates an SVG image from an FSW sign with an optional style string
1737 * @function fsw.signSvgBody
1738 * @param {string} fswSign - an FSW sign with optional style string
1739 * @returns {string} body of SVG for sign
1740 * @example
1741 * fsw.signSvgBody('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1742 *
1743 * return `<text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text>
1744 * <g transform="translate(483,510)">
1745 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text>
1746 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text>
1747 * </g>
1748 * <g transform="translate(501,466)">
1749 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text>
1750 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text>
1751 * </g>
1752 * <g transform="translate(510,500)">
1753 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text>
1754 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text>
1755 * </g>
1756 * <g transform="translate(476,475)">
1757 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text>
1758 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text>
1759 * </g>`
1760 */
1761const signSvgBody$1 = fswSign => {
1762 let parsed = parse$3.sign(fswSign);
1763 const blank = '';
1764 if (parsed.spatials) {
1765 let styling = parse$2(parsed.style);
1766 if (styling.detailsym) {
1767 styling.detailsym.forEach(sym => {
1768 if (parsed.spatials[sym.index - 1]) {
1769 parsed.spatials[sym.index - 1].detail = sym.detail;
1770 }
1771 });
1772 }
1773 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1774 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1775 let x2 = parsed.max[0];
1776 let y2 = parsed.max[1];
1777 let background = '';
1778 if (styling.padding) {
1779 x1 -= styling.padding;
1780 y1 -= styling.padding;
1781 x2 += styling.padding;
1782 y2 += styling.padding;
1783 }
1784 if (styling.background) {
1785 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
1786 }
1787 let svg = ` <text font-size="0">${fswSign}</text>${background}`;
1788 const line = styling.detail && styling.detail[0];
1789 const fill = styling.detail && styling.detail[1];
1790 svg += '\n' + parsed.spatials.map(spatial => {
1791 let svg = symbolText$1(spatial.symbol);
1792 let symLine = line;
1793 if (spatial.detail) {
1794 symLine = spatial.detail[0];
1795 } else if (styling.colorize) {
1796 symLine = colorize$1(spatial.symbol);
1797 }
1798 if (symLine) {
1799 svg = svg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${symLine}"`);
1800 }
1801 let symFill = fill;
1802 if (spatial.detail && spatial.detail[1]) {
1803 symFill = spatial.detail[1];
1804 }
1805 if (symFill) {
1806 svg = svg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${symFill}"`);
1807 }
1808 return ` <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})">
1809${svg}
1810 </g>`;
1811 }).join('\n');
1812 return svg;
1813 }
1814 return blank;
1815};
1816
1817/**
1818 * Function that creates an SVG image from an FSW sign with an optional style string
1819 * @function fsw.signSvg
1820 * @param {string} fswSign - an FSW sign with optional style string
1821 * @returns {string} SVG for sign
1822 * @example
1823 * fsw.signSvg('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1824 *
1825 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="49" height="69" viewBox="476 466 49 69">
1826 * <text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text>
1827 * <g transform="translate(483,510)">
1828 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text>
1829 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text>
1830 * </g>
1831 * <g transform="translate(501,466)">
1832 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text>
1833 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text>
1834 * </g>
1835 * <g transform="translate(510,500)">
1836 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text>
1837 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text>
1838 * </g>
1839 * <g transform="translate(476,475)">
1840 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text>
1841 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text>
1842 * </g>
1843 * </svg>`
1844 */
1845const signSvg$1 = fswSign => {
1846 let parsed = parse$3.sign(fswSign);
1847 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
1848 if (parsed.spatials) {
1849 let styling = parse$2(parsed.style);
1850 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1851 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1852 let x2 = parsed.max[0];
1853 let y2 = parsed.max[1];
1854 let classes = '';
1855 if (styling.classes) {
1856 classes = ` class="${styling.classes}"`;
1857 }
1858 let id = '';
1859 if (styling.id) {
1860 id = ` id="${styling.id}"`;
1861 }
1862 if (styling.padding) {
1863 x1 -= styling.padding;
1864 y1 -= styling.padding;
1865 x2 += styling.padding;
1866 y2 += styling.padding;
1867 }
1868 let sizing = '';
1869 if (styling.zoom != 'x') {
1870 sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
1871 }
1872 let svg = `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
1873`;
1874 svg += signSvgBody$1(fswSign);
1875 svg += '\n</svg>';
1876 return svg;
1877 }
1878 return blank;
1879};
1880
1881const signCanvas$1 = function (fswSign) {
1882 const parsed = parse$3.sign(fswSign);
1883 if (parsed.spatials) {
1884 const canvas = document.createElement('canvas');
1885 const context = canvas.getContext('2d');
1886 let styling = parse$2(parsed.style);
1887 if (styling.detailsym) {
1888 styling.detailsym.forEach(sym => {
1889 if (parsed.spatials[sym.index - 1]) {
1890 parsed.spatials[sym.index - 1].detail = sym.detail;
1891 }
1892 });
1893 }
1894 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1895 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1896 let x2 = parsed.max[0];
1897 let y2 = parsed.max[1];
1898 if (styling.padding) {
1899 x1 -= styling.padding;
1900 y1 -= styling.padding;
1901 x2 += styling.padding;
1902 y2 += styling.padding;
1903 }
1904 let sizing = 1;
1905 if (styling.zoom != 'x') {
1906 sizing = styling.zoom;
1907 }
1908 let w = (x2 - x1) * sizing;
1909 let h = (y2 - y1) * sizing;
1910 canvas.width = w ? w : 1;
1911 canvas.height = h ? h : 1;
1912 if (styling.background) {
1913 context.rect(0, 0, w, h);
1914 context.fillStyle = styling.background;
1915 context.fill();
1916 }
1917 const line = styling.detail && styling.detail[0] || "black";
1918 const fill = styling.detail && styling.detail[1] || "white";
1919 parsed.spatials.forEach(spatial => {
1920 let symLine = line;
1921 if (spatial.detail) {
1922 symLine = spatial.detail[0];
1923 } else if (styling.colorize) {
1924 symLine = colorize$1(spatial.symbol);
1925 }
1926 let symFill = fill;
1927 if (spatial.detail && spatial.detail[1]) {
1928 symFill = spatial.detail[1];
1929 }
1930 context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
1931 context.fillStyle = symFill;
1932 context.fillText(symbolFill$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
1933 context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
1934 context.fillStyle = symLine;
1935 context.fillText(symbolLine$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
1936 });
1937 return canvas;
1938 }
1939};
1940
1941/**
1942 * Function that creates a PNG data url from an FSW sign with an optional style string
1943 * @function fsw.signPng
1944 * @param {string} fswSign - an FSW sign with optional style string
1945 * @returns {string} png image for sign as data url
1946 * @example
1947 * fsw.signPng('M525x535S2e748483x510S10011501x466S20544510x500S10019476x475')
1948 *
1949 * return 'data:image/png;base64,iVBORw...'
1950 */
1951const signPng$1 = fswSign => {
1952 const canvas = signCanvas$1(fswSign);
1953 const png = canvas.toDataURL("image/png");
1954 canvas.remove();
1955 return png;
1956};
1957
1958/**
1959 * Function that normalizes an FSW sign for a center of 500,500
1960 * @function fsw.signNormalize
1961 * @param {string} fswSign - an FSW sign with optional style string
1962 * @returns {string} normalized FSW sign
1963 * @example
1964 * fsw.signNormalize('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1965 *
1966 * return 'M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
1967 */
1968const signNormalize$1 = fswSign => {
1969 const parsed = parse$3.sign(fswSign);
1970 if (parsed.spatials) {
1971 const symbolsizes = parsed.spatials.reduce((output, spatial) => {
1972 const size = symbolSize$1(spatial.symbol);
1973 output[spatial.symbol] = {
1974 width: size[0],
1975 height: size[1]
1976 };
1977 return output;
1978 }, {});
1979 const bbox = symbols => {
1980 const x1 = Math.min(...symbols.map(spatial => spatial.coord[0]));
1981 const y1 = Math.min(...symbols.map(spatial => spatial.coord[1]));
1982 const x2 = Math.max(...symbols.map(spatial => spatial.coord[0] + parseInt(symbolsizes[spatial.symbol].width)));
1983 const y2 = Math.max(...symbols.map(spatial => spatial.coord[1] + parseInt(symbolsizes[spatial.symbol].height)));
1984 return {
1985 x1: x1,
1986 y1: y1,
1987 x2: x2,
1988 y2: y2
1989 };
1990 };
1991 const hrange = ranges$1['hcenter'];
1992 const hsyms = parsed.spatials.filter(spatial => {
1993 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1994 return hrange[0] <= dec && hrange[1] >= dec;
1995 });
1996 const vrange = ranges$1['vcenter'];
1997 const vsyms = parsed.spatials.filter(spatial => {
1998 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1999 return vrange[0] <= dec && vrange[1] >= dec;
2000 });
2001 let abox = bbox(parsed.spatials);
2002 let max = [abox.x2, abox.y2];
2003 if (hsyms.length) {
2004 const hbox = bbox(hsyms);
2005 abox.x1 = hbox.x1;
2006 abox.x2 = hbox.x2;
2007 }
2008 if (vsyms.length) {
2009 const vbox = bbox(vsyms);
2010 abox.y1 = vbox.y1;
2011 abox.y2 = vbox.y2;
2012 }
2013 const offset = [parseInt((abox.x2 + abox.x1) / 2) - 500, parseInt((abox.y2 + abox.y1) / 2) - 500];
2014 const fswout = (parsed.sequence ? 'A' + parsed.sequence.join('') : '') + parsed.box + (max[0] - offset[0]) + 'x' + (max[1] - offset[1]) + parsed.spatials.map(spatial => spatial.symbol + (spatial.coord[0] - offset[0]) + 'x' + (spatial.coord[1] - offset[1])).join('') + (parsed.style || '');
2015 return fswout;
2016 }
2017};
2018
2019/**
2020 * Function that creates an SVG image for a column of FSW
2021 * @function fsw.columnSvg
2022 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
2023 * @param {ColumnOptions} options - an object of column options
2024 * @returns {string} column svg
2025 * @example
2026 * fsw.columnSvg([
2027 * {
2028 * "x": 56,
2029 * "y": 20,
2030 * "minX": 481,
2031 * "minY": 471,
2032 * "width": 37,
2033 * "height": 58,
2034 * "lane": 0,
2035 * "padding": 0,
2036 * "segment": "sign",
2037 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
2038 * "zoom": 1
2039 * },
2040 * {
2041 * "x": 57,
2042 * "y": 118,
2043 * "minX": 482,
2044 * "minY": 468,
2045 * "width": 36,
2046 * "height": 65,
2047 * "lane": 0,
2048 * "padding": 0,
2049 * "segment": "sign",
2050 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
2051 * "zoom": 1
2052 * },
2053 * {
2054 * "x": 39,
2055 * "y": 203,
2056 * "minX": 464,
2057 * "minY": 496,
2058 * "width": 72,
2059 * "height": 8,
2060 * "lane": 0,
2061 * "padding": 0,
2062 * "segment": "symbol",
2063 * "text": "S38800464x496",
2064 * "zoom": 1
2065 * }
2066 * ],
2067 * {
2068 * "height": 250,
2069 * "width": 150,
2070 * })
2071 *
2072 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
2073 * <g transform="translate(56,20) scale(1) translate(-481,-471) ">
2074 * <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
2075 * <g transform="translate(481,471)">
2076 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􁲥</text>
2077 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󹲥</text>
2078 * </g>
2079 * <g transform="translate(503,489)">
2080 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􈊧</text>
2081 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󸊧</text>
2082 * </g>
2083 * </g>
2084 * <g transform="translate(57,118) scale(1) translate(-482,-468) ">
2085 * <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
2086 * <g transform="translate(489,515)">
2087 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊫</text>
2088 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊫</text>
2089 * </g>
2090 * <g transform="translate(482,490)">
2091 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊢</text>
2092 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊢</text>
2093 * </g>
2094 * <g transform="translate(508,496)">
2095 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􆇡</text>
2096 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󶇡</text>
2097 * </g>
2098 * <g transform="translate(500,468)">
2099 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛕</text>
2100 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛕</text>
2101 * </g>
2102 * </g>
2103 * <g transform="translate(39,203) scale(1) translate(-464,-496) ">
2104 * <text font-size="0">S38800464x496-D_black,white_Z1</text>
2105 * <g transform="translate(464,496)">
2106 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􏌁</text>
2107 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󿌁</text>
2108 * </g>
2109 * </g>
2110 * </svg>`
2111 */
2112const columnSvg$1 = (fswColumn, options) => {
2113 //if (typeof fswColumn !== 'array') return blank;
2114 if (typeof options !== 'object') options = {};
2115 const values = Object.assign(columnDefaults$1, options);
2116 let x1 = 0;
2117 let y1 = 0;
2118 let x2 = values.width;
2119 let y2 = values.height;
2120 let background = '';
2121 if (values.background) {
2122 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${values.background};" />`;
2123 }
2124 let sizing = ` width="${values.width}" height="${values.height}"`;
2125 let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
2126 ${background}`;
2127 svg += fswColumn.map(item => {
2128 const dash = item.text.indexOf('-');
2129 if (dash > 0) {
2130 const itemStyle = item.text.substring(dash);
2131 const newStyle = {
2132 ...values.style,
2133 ...parse$2(itemStyle)
2134 };
2135 item.text = item.text.replace(itemStyle, compose$1(newStyle));
2136 } else {
2137 item.text += compose$1(values.style);
2138 }
2139 item.zoom = item.zoom * values.style.zoom;
2140 return '<g transform="translate(' + item.x + ',' + item.y + ') scale(' + item.zoom + ') translate(' + -item.minX + ',' + -item.minY + ') ">' + (item.segment == "sign" ? signSvgBody$1(item.text) : symbolSvgBody$1(item.text)) + '</g>';
2141 }).join('\n');
2142 svg += '\n</svg>';
2143 return svg;
2144};
2145
2146/**
2147 * Function that creates an array of SVG column images for an FSW text
2148 * @function fsw.columnsSvg
2149 * @param {string} fswText - a text of FSW signs and punctuation
2150 * @param {ColumnOptions} options - an object of column options
2151 * @returns {string[]} array of SVG columns
2152 * @example
2153 * fsw.columnsSvg('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
2154 * "height": 250,
2155 * "width": 150,
2156 * })
2157 *
2158 * return [`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
2159 * <g transform="translate(56,20) scale(1) translate(-481,-471) ">
2160 * <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
2161 * <g transform="translate(481,471)">
2162 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􁲥</text>
2163 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󹲥</text>
2164 * </g>
2165 * <g transform="translate(503,489)">
2166 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􈊧</text>
2167 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󸊧</text>
2168 * </g>
2169 * </g>
2170 * <g transform="translate(57,118) scale(1) translate(-482,-468) ">
2171 * <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
2172 * <g transform="translate(489,515)">
2173 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊫</text>
2174 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊫</text>
2175 * </g>
2176 * <g transform="translate(482,490)">
2177 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊢</text>
2178 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊢</text>
2179 * </g>
2180 * <g transform="translate(508,496)">
2181 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􆇡</text>
2182 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󶇡</text>
2183 * </g>
2184 * <g transform="translate(500,468)">
2185 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛕</text>
2186 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛕</text>
2187 * </g>
2188 * </g>
2189 * <g transform="translate(39,203) scale(1) translate(-464,-496) ">
2190 * <text font-size="0">S38800464x496-D_black,white_Z1</text>
2191 * <g transform="translate(464,496)">
2192 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􏌁</text>
2193 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󿌁</text>
2194 * </g>
2195 * </g>
2196 * </svg>`]
2197 */
2198const columnsSvg$1 = function (fswText, options) {
2199 if (typeof options !== 'object') options = {};
2200 let values = columns$1(fswText, options);
2201 let cols = values.columns.map((col, i) => {
2202 return columnSvg$1(col, {
2203 ...values.options,
2204 ...{
2205 width: values.widths[i]
2206 }
2207 });
2208 });
2209 return cols;
2210};
2211
2212const columnCanvas$1 = function (fswColumn, options) {
2213 if (typeof options !== 'object') options = {};
2214 const values = Object.assign(columnDefaults$1, options);
2215 const canvas = document.createElement('canvas');
2216 canvas.width = values.width;
2217 canvas.height = values.height;
2218 const context = canvas.getContext('2d');
2219 if (values.background) {
2220 context.rect(0, 0, values.width, values.height);
2221 context.fillStyle = values.background;
2222 context.fill();
2223 }
2224 fswColumn.map(item => {
2225 const dash = item.text.indexOf('-');
2226 if (dash > 0) {
2227 const itemStyle = item.text.substring(dash);
2228 const newStyle = {
2229 ...values.style,
2230 ...parse$2(itemStyle)
2231 };
2232 item.text = item.text.replace(itemStyle, compose$1(newStyle));
2233 } else {
2234 item.text += compose$1(values.style);
2235 }
2236 item.zoom = item.zoom * values.style.zoom;
2237 let parsed = {};
2238 if (item.segment == "sign") {
2239 parsed = parse$3.sign(item.text);
2240 } else {
2241 let sym = parse$3.symbol(item.text);
2242 parsed.style = sym.style;
2243 parsed.spatials = [sym];
2244 }
2245 let styling = parse$2(parsed.style);
2246 if (styling.background) {
2247 context.fillStyle = styling.background;
2248 context.fillRect(item.x - styling.padding * item.zoom, item.y - styling.padding * item.zoom, (item.width + styling.padding * 2) * item.zoom, (item.height + styling.padding * 2) * item.zoom);
2249 }
2250 if (styling.detailsym) {
2251 styling.detailsym.forEach(sym => {
2252 if (parsed.spatials[sym.index - 1]) {
2253 parsed.spatials[sym.index - 1].detail = sym.detail;
2254 }
2255 });
2256 }
2257 const line = styling.detail && styling.detail[0] || "black";
2258 const fill = styling.detail && styling.detail[1] || "white";
2259 parsed.spatials.forEach(spatial => {
2260 let symLine = line;
2261 if (spatial.detail) {
2262 symLine = spatial.detail[0];
2263 } else if (styling.colorize) {
2264 symLine = colorize$1(spatial.symbol);
2265 }
2266 let symFill = fill;
2267 if (spatial.detail && spatial.detail[1]) {
2268 symFill = spatial.detail[1];
2269 }
2270 context.font = 30 * item.zoom + "px 'SuttonSignWritingFill'";
2271 context.fillStyle = symFill;
2272 context.fillText(symbolFill$1(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
2273 context.font = 30 * item.zoom + "px 'SuttonSignWritingLine'";
2274 context.fillStyle = symLine;
2275 context.fillText(symbolLine$1(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
2276 });
2277 });
2278 return canvas;
2279};
2280
2281/**
2282 * Function that creates a PNG data url for a column of FSW
2283 * @function fsw.columnPng
2284 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
2285 * @param {ColumnOptions} options - an object of column options
2286 * @returns {string} column png data url
2287 * @example
2288 * fsw.columnPng([
2289 * {
2290 * "x": 56,
2291 * "y": 20,
2292 * "minX": 481,
2293 * "minY": 471,
2294 * "width": 37,
2295 * "height": 58,
2296 * "lane": 0,
2297 * "padding": 0,
2298 * "segment": "sign",
2299 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
2300 * "zoom": 1
2301 * },
2302 * {
2303 * "x": 57,
2304 * "y": 118,
2305 * "minX": 482,
2306 * "minY": 468,
2307 * "width": 36,
2308 * "height": 65,
2309 * "lane": 0,
2310 * "padding": 0,
2311 * "segment": "sign",
2312 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
2313 * "zoom": 1
2314 * },
2315 * {
2316 * "x": 39,
2317 * "y": 203,
2318 * "minX": 464,
2319 * "minY": 496,
2320 * "width": 72,
2321 * "height": 8,
2322 * "lane": 0,
2323 * "padding": 0,
2324 * "segment": "symbol",
2325 * "text": "S38800464x496",
2326 * "zoom": 1
2327 * }
2328 * ],
2329 * {
2330 * "height": 250,
2331 * "width": 150,
2332 * })
2333 *
2334 * return 'data:image/png;base64,iVBORw...'
2335 */
2336const columnPng$1 = (fswColumn, options) => {
2337 const canvas = columnCanvas$1(fswColumn, options);
2338 const png = canvas.toDataURL("image/png");
2339 canvas.remove();
2340 return png;
2341};
2342
2343/**
2344 * Function that creates an array of PNG data urls for an FSW text
2345 * @function fsw.columnsPng
2346 * @param {string} fswText - a text of FSW signs and punctuation
2347 * @param {ColumnOptions} options - an object of column options
2348 * @returns {string[]} array of PNG data urls
2349 * @example
2350 * fsw.columnsPng('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
2351 * "height": 250,
2352 * "width": 150,
2353 * })
2354 *
2355 * return ['data:image/png;base64,iVBORw...']
2356 */
2357const columnsPng$1 = function (fswText, options) {
2358 if (typeof options !== 'object') options = {};
2359 let values = columns$1(fswText, options);
2360 let cols = values.columns.map((col, i) => {
2361 return columnPng$1(col, {
2362 ...values.options,
2363 ...{
2364 width: values.widths[i]
2365 }
2366 });
2367 });
2368 return cols;
2369};
2370
2371/** The fsw module contains functions for handling Formal SignWriitng in ASCII (FSW) characters.
2372 * [FSW characters definition](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-formal-signwriting-in-ascii)
2373 * @module fsw
2374 */
2375
2376var index$1 = /*#__PURE__*/Object.freeze({
2377 __proto__: null,
2378 symbolSize: symbolSize$1,
2379 symbolLine: symbolLine$1,
2380 symbolFill: symbolFill$1,
2381 symbolText: symbolText$1,
2382 symbolSvgBody: symbolSvgBody$1,
2383 symbolSvg: symbolSvg$1,
2384 symbolPng: symbolPng$1,
2385 symbolNormalize: symbolNormalize$1,
2386 symbolMirror: symbolMirror$1,
2387 symbolRotate: symbolRotate$1,
2388 symbolFlop: symbolFlop$1,
2389 symbolScroll: symbolScroll$1,
2390 signSvgBody: signSvgBody$1,
2391 signSvg: signSvg$1,
2392 signPng: signPng$1,
2393 signNormalize: signNormalize$1,
2394 columnSvg: columnSvg$1,
2395 columnsSvg: columnsSvg$1,
2396 columnPng: columnPng$1,
2397 columnsPng: columnsPng$1
2398});
2399
2400/**
2401* Sutton SignWriting Core Module v1.5.11 (https://github.com/sutton-signwriting/core)
2402* Author: Steve Slevinski (https://SteveSlevinski.me)
2403* swu.mjs is released under the MIT License.
2404*/
2405
2406/**
2407 * Object of regular expressions for SWU strings in UTF-16
2408 *
2409 * @alias swu.re
2410 * @property {string} symbol - regular expressions for a symbol
2411 * @property {string} coord - regular expressions for a coordinate
2412 * @property {string} sort - regular expressions for the sorting marker
2413 * @property {string} box - regular expression for a signbox marker
2414 * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
2415 * @property {string} spatial - regular expression for a symbol followed by a coordinate
2416 * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
2417 * @property {string} sign - regular expression for an optional prefix followed by a signbox
2418 * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
2419 */
2420let re$1 = {
2421 'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
2422 'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}',
2423 'sort': '\uD836\uDC00',
2424 'box': '\uD836[\uDC01-\uDC04]'
2425};
2426re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
2427re$1.spatial = `${re$1.symbol}${re$1.coord}`;
2428re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
2429re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
2430re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
2431
2432/**
2433 * Object of regular expressions for style strings
2434 *
2435 * @alias style.re
2436 * @type {object}
2437 * @property {string} colorize - regular expression for colorize section
2438 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
2439 * @property {string} colorname - regular expression for css color name
2440 * @property {string} padding - regular expression for padding section
2441 * @property {string} zoom - regular expression for zoom section
2442 * @property {string} classbase - regular expression for class name definition
2443 * @property {string} id - regular expression for id definition
2444 * @property {string} colorbase - regular expression for color hex or color name
2445 * @property {string} color - regular expression for single color entry
2446 * @property {string} colors - regular expression for double color entry
2447 * @property {string} background - regular expression for background section
2448 * @property {string} detail - regular expression for color details for line and optional fill
2449 * @property {string} detailsym - regular expression for color details for individual symbols
2450 * @property {string} classes - regular expression for one or more class names
2451 * @property {string} full - full regular expression for style string
2452 */
2453let re = {
2454 'colorize': 'C',
2455 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
2456 'colorname': '[a-zA-Z]+',
2457 'padding': 'P[0-9]{2}',
2458 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
2459 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
2460 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
2461};
2462re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
2463re.color = `_${re.colorbase}_`;
2464re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
2465re.background = `G${re.color}`;
2466re.detail = `D${re.colors}`;
2467re.detailsym = `D[0-9]{2}${re.colors}`;
2468re.classes = `${re.classbase}(?: ${re.classbase})*`;
2469re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
2470
2471const prefixColor = color => {
2472 const regex = new RegExp(`^${re.colorhex}$`);
2473 return (regex.test(color) ? '#' : '') + color;
2474};
2475const definedProps = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
2476
2477/**
2478 * Function to parse style string to object
2479 * @function style.parse
2480 * @param {string} styleString - a style string
2481 * @returns {StyleObject} elements of style string
2482 * @example
2483 * style.parse('-CP10G_blue_D_red,Cyan_')
2484 *
2485 * return {
2486 * 'colorize': true,
2487 * 'padding': 10,
2488 * 'background': 'blue',
2489 * 'detail': ['red', 'Cyan']
2490 * }
2491 */
2492const parse$1 = styleString => {
2493 const regex = `^${re.full}`;
2494 const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
2495 return definedProps({
2496 'colorize': !m[1] ? undefined : !!m[1],
2497 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
2498 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)),
2499 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor),
2500 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
2501 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => {
2502 const parts = val.split('_');
2503 const detail = parts[1].split(',').map(prefixColor);
2504 return {
2505 'index': parseInt(parts[0].slice(1)),
2506 'detail': detail
2507 };
2508 }),
2509 'classes': !m[7] ? undefined : m[7],
2510 'id': !m[8] ? undefined : m[8]
2511 });
2512};
2513
2514/** 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.
2515 * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
2516 * @module convert
2517 */
2518
2519/**
2520 * Function to convert an SWU number character to an integer
2521 * @function convert.swu2num
2522 * @param {string} swuNum - SWU number character
2523 * @returns {number} Integer value for number
2524 * @example
2525 * convert.swu2num('𝤆')
2526 *
2527 * return 500
2528 */
2529const swu2num = swuNum => parseInt(swuNum.codePointAt(0)) - 0x1D80C + 250;
2530
2531/**
2532 * Function to convert a number to an SWU number character
2533 * @function convert.num2swu
2534 * @param {number} num - Integer value for number
2535 * @returns {string} SWU number character
2536 * @example
2537 * convert.num2swu(500)
2538 *
2539 * return '𝤆'
2540 */
2541const num2swu = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250);
2542
2543/**
2544 * Function to convert two SWU number characters to an array of x,y integers
2545 * @function convert.swu2coord
2546 * @param {string} swuCoord - Two SWU number character
2547 * @returns {number[]} Array of x,y integers
2548 * @example
2549 * convert.swu2coord('𝤆𝤆')
2550 *
2551 * return [500, 500]
2552 */
2553const swu2coord = swuCoord => [swu2num(swuCoord.slice(0, 2)), swu2num(swuCoord.slice(2, 4))];
2554
2555/**
2556 * Function to convert an array of x,y integers to two SWU number characters
2557 * @function convert.coord2swu
2558 * @param {number[]} coord - Array of x,y integers
2559 * @returns {string} Two SWU number character
2560 * @example
2561 * convert.coord2swu([500, 500])
2562 *
2563 * return '𝤆𝤆'
2564 */
2565const coord2swu = coord => coord.map(num => num2swu(num)).join('');
2566
2567/**
2568 * Function to convert an SWU symbol character to a code point on plane 4
2569 * @function convert.swu2code
2570 * @param {string} swuSym - SWU symbol character
2571 * @returns {number} Code point on plane 4
2572 * @example
2573 * convert.swu2code('񀀁')
2574 *
2575 * return 0x40001
2576 */
2577const swu2code = swuSym => parseInt(swuSym.codePointAt(0));
2578
2579const parse = {
2580 /**
2581 * Function to parse an swu symbol with optional coordinate and style string
2582 * @function swu.parse.symbol
2583 * @param {string} swuSym - an swu symbol
2584 * @returns {SymbolObject} elements of swu symbol
2585 * @example
2586 * swu.parse.symbol('񀀁𝤆𝤆-C')
2587 *
2588 * return {
2589 * 'symbol': '񀀁',
2590 * 'coord': [500, 500],
2591 * 'style': '-C'
2592 * }
2593 */
2594 symbol: swuSym => {
2595 const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
2596 const symbol = typeof swuSym === 'string' ? swuSym.match(new RegExp(regex)) : undefined;
2597 return {
2598 'symbol': symbol ? symbol[1] : undefined,
2599 'coord': symbol && symbol[2] ? swu2coord(symbol[2]) : undefined,
2600 'style': symbol ? symbol[3] : undefined
2601 };
2602 },
2603 /**
2604 * Function to parse an swu sign with style string
2605 * @function swu.parse.sign
2606 * @param {string} swuSign - an swu sign
2607 * @returns {SignObject} elements of swu sign
2608 * @example
2609 * swu.parse.sign('𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭-C')
2610 *
2611 * return {
2612 * sequence: ['񀀒','񀀚','񋚥','񋛩'],
2613 * box: '𝠃',
2614 * max: [525, 535],
2615 * spatials: [
2616 * {
2617 * symbol: '񋛩',
2618 * coord: [483, 510]
2619 * },
2620 * {
2621 * symbol: '񀀒',
2622 * coord: [501, 466]
2623 * },
2624 * {
2625 * symbol: '񋚥',
2626 * coord: [510, 500]
2627 * },
2628 * {
2629 * symbol: '񀀚',
2630 * coord: [476, 475]
2631 * }
2632 * ],
2633 * style: '-C'
2634 * }
2635 */
2636 sign: swuSign => {
2637 const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
2638 const sign = typeof swuSign === 'string' ? swuSign.match(new RegExp(regex)) : undefined;
2639 if (sign) {
2640 return {
2641 'sequence': sign[1] ? sign[1].slice(2).match(/.{2}/g) : undefined,
2642 'box': sign[2].slice(0, 2),
2643 'max': swu2coord(sign[2].slice(2, 6)),
2644 'spatials': sign[2].length < 7 ? undefined : sign[2].slice(6).match(/(.{6})/g).map(m => {
2645 return {
2646 symbol: m.slice(0, 2),
2647 coord: swu2coord(m.slice(2))
2648 };
2649 }),
2650 'style': sign[3]
2651 };
2652 } else {
2653 return {};
2654 }
2655 },
2656 /**
2657 * Function to parse an swu text
2658 * @function swu.parse.text
2659 * @param {string} swuText - an swu text
2660 * @returns {string[]} swu signs and punctuations
2661 * @example
2662 * swu.parse.text('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂')
2663 *
2664 * return [
2665 * '𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻',
2666 * '𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦',
2667 * '񏌁𝣢𝤂'
2668 * ]
2669 */
2670 text: swuText => {
2671 if (typeof swuText !== 'string') return [];
2672 const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
2673 const matches = swuText.match(new RegExp(regex, 'g'));
2674 return matches ? [...matches] : [];
2675 }
2676};
2677
2678const compose = {
2679 /**
2680 * Function to compose an swu symbol with optional coordinate and style string
2681 * @function swu.compose.symbol
2682 * @param {SymbolObject} swuSymObject - an swu symbol object
2683 * @returns {string} an swu symbol string
2684 * @example
2685 * swu.compose.symbol({
2686 * 'symbol': '񀀁',
2687 * 'coord': [500, 500],
2688 * 'style': '-C'
2689 * })
2690 *
2691 * return '񀀁𝤆𝤆-C'
2692 */
2693 symbol: swuSymObject => {
2694 if (typeof swuSymObject !== 'object' || swuSymObject === null) return undefined;
2695 if (typeof swuSymObject.symbol === 'string') {
2696 const symbol = (swuSymObject.symbol.match(re$1.symbol) || [''])[0];
2697 if (symbol) {
2698 const x = swuSymObject.coord && swuSymObject.coord[0] || '';
2699 const y = swuSymObject.coord && swuSymObject.coord[1] || '';
2700 const coord = x && y ? coord2swu([x, y]) : '';
2701 const styleStr = typeof swuSymObject.style === 'string' && (swuSymObject.style.match(re.full) || [''])[0] || '';
2702 return symbol + coord + styleStr;
2703 }
2704 }
2705 return undefined;
2706 },
2707 /**
2708 * Function to compose an swu sign with style string
2709 * @function swu.compose.sign
2710 * @param {SignObject} swuSignObject - an swu sign object
2711 * @returns {string} an swu sign string
2712 * @example
2713 * swu.compose.sign({
2714 * sequence: ['񀀒','񀀚','񋚥','񋛩'],
2715 * box: '𝠃',
2716 * max: [525, 535],
2717 * spatials: [
2718 * {
2719 * symbol: '񋛩',
2720 * coord: [483, 510]
2721 * },
2722 * {
2723 * symbol: '񀀒',
2724 * coord: [501, 466]
2725 * },
2726 * {
2727 * symbol: '񋚥',
2728 * coord: [510, 500]
2729 * },
2730 * {
2731 * symbol: '񀀚',
2732 * coord: [476, 475]
2733 * }
2734 * ],
2735 * style: '-C'
2736 * })
2737 *
2738 * return '𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭-C'
2739 */
2740 sign: swuSignObject => {
2741 if (typeof swuSignObject !== 'object' || swuSignObject === null) return undefined;
2742 let box = typeof swuSignObject.box !== 'string' ? '𝠃' : (swuSignObject.box + '𝠃').match(re$1.box);
2743 const x = swuSignObject.max && swuSignObject.max[0] || '';
2744 const y = swuSignObject.max && swuSignObject.max[1] || '';
2745 const max = x && y ? coord2swu([x, y]) : undefined;
2746 if (!max) return undefined;
2747 let prefix = '';
2748 if (swuSignObject.sequence && Array.isArray(swuSignObject.sequence)) {
2749 prefix = swuSignObject.sequence.map(key => (key.match(re$1.symbol) || [''])[0]).join('');
2750 prefix = prefix ? '𝠀' + prefix : '';
2751 }
2752 let signbox = '';
2753 if (swuSignObject.spatials && Array.isArray(swuSignObject.spatials)) {
2754 signbox = swuSignObject.spatials.map(spatial => {
2755 if (typeof spatial.symbol === 'string') {
2756 const symbol = (spatial.symbol.match(re$1.symbol) || [''])[0];
2757 if (symbol) {
2758 const x = spatial.coord && spatial.coord[0] || '';
2759 const y = spatial.coord && spatial.coord[1] || '';
2760 const coord = x && y ? coord2swu([x, y]) : '';
2761 if (coord) {
2762 return symbol + coord;
2763 }
2764 }
2765 }
2766 return '';
2767 }).join('');
2768 }
2769 const styleStr = typeof swuSignObject.style === 'string' && (swuSignObject.style.match(re.full) || [''])[0] || '';
2770 return prefix + box + max + signbox + styleStr;
2771 }
2772};
2773
2774/**
2775 * Function to gather sizing information about an swu sign or symbol
2776 * @function swu.info
2777 * @param {string} swu - an swu sign or symbol
2778 * @returns {SegmentInfo} information about the swu string
2779 * @example
2780 * swu.info('𝠀񁲡񈩧𝠂𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻-P10Z2')
2781 *
2782 * return {
2783 * minX: 481,
2784 * minY: 471,
2785 * width: 37,
2786 * height: 58,
2787 * lane: -1,
2788 * padding: 10,
2789 * segment: 'sign',
2790 * zoom: 2
2791 * }
2792 */
2793const info = swu => {
2794 let lanes = {
2795 '𝠁': 0,
2796 '𝠂': -1,
2797 '𝠃': 0,
2798 '𝠄': 1
2799 };
2800 let parsed = parse.sign(swu);
2801 let width, height, segment, x1, x2, y1, y2, lane;
2802 if (parsed.spatials) {
2803 x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
2804 x2 = parsed.max[0];
2805 width = x2 - x1;
2806 y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
2807 y2 = parsed.max[1];
2808 height = y2 - y1;
2809 segment = 'sign';
2810 lane = parsed.box;
2811 } else {
2812 parsed = parse.symbol(swu);
2813 lane = "𝠃";
2814 if (parsed.coord) {
2815 x1 = parsed.coord[0];
2816 width = (500 - x1) * 2;
2817 y1 = parsed.coord[1];
2818 height = (500 - y1) * 2;
2819 segment = 'symbol';
2820 } else {
2821 x1 = 490;
2822 width = 20;
2823 y1 = 490;
2824 height = 20;
2825 segment = 'none';
2826 }
2827 }
2828 let style = parse$1(parsed.style);
2829 let zoom = style.zoom || 1;
2830 let padding = style.padding || 0;
2831 return {
2832 minX: x1,
2833 minY: y1,
2834 width: width,
2835 height: height,
2836 segment: segment,
2837 lane: lanes[lane],
2838 padding: padding,
2839 zoom: zoom
2840 };
2841};
2842
2843const columnDefaults = {
2844 'height': 500,
2845 'width': 150,
2846 'offset': 50,
2847 'pad': 20,
2848 'margin': 5,
2849 'dynamic': false,
2850 'background': undefined,
2851 'punctuation': {
2852 'spacing': true,
2853 'pad': 30,
2854 'pull': true
2855 },
2856 'style': {
2857 'detail': ['black', 'white'],
2858 'zoom': 1
2859 }
2860};
2861
2862/**
2863 * Function to an object of column options with default values
2864 *
2865 * @function swu.columnDefaultsMerge
2866 * @param {ColumnOptions} options - object of column options
2867 * @returns {ColumnOptions} object of column options merged with column defaults
2868 * @example
2869 * swu.columnDefaultsMerge({height: 500,width:150})
2870 *
2871 * return {
2872 * "height": 500,
2873 * "width": 150,
2874 * "offset": 50,
2875 * "pad": 20,
2876 * "margin": 5,
2877 * "dynamic": false,
2878 * "punctuation": {
2879 * "spacing": true,
2880 * "pad": 30,
2881 * "pull": true
2882 * },
2883 * "style": {
2884 * "detail": [
2885 * "black",
2886 * "white"
2887 * ],
2888 * "zoom": 1
2889 * }
2890 * }
2891 */
2892const columnDefaultsMerge = options => {
2893 if (typeof options !== 'object') options = {};
2894 return {
2895 ...columnDefaults,
2896 ...options,
2897 punctuation: {
2898 ...columnDefaults.punctuation,
2899 ...options.punctuation
2900 },
2901 style: {
2902 ...columnDefaults.style,
2903 ...options.style
2904 }
2905 };
2906};
2907
2908/**
2909 * Function to transform an SWU text to an array of columns
2910 *
2911 * @function swu.columns
2912 * @param {string} swuText - SWU text of signs and punctuation
2913 * @param {ColumnOptions} options - object of column options
2914 * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
2915 * @example
2916 * swu.columns('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂', {height: 500,width:150})
2917 *
2918 * return {
2919 * "options": {
2920 * "height": 500,
2921 * "width": 150,
2922 * "offset": 50,
2923 * "pad": 20,
2924 * "margin": 5,
2925 * "dynamic": false,
2926 * "punctuation": {
2927 * "spacing": true,
2928 * "pad": 30,
2929 * "pull": true
2930 * },
2931 * "style": {
2932 * "detail": [
2933 * "black",
2934 * "white"
2935 * ],
2936 * "zoom": 1
2937 * }
2938 * },
2939 * "widths": [
2940 * 150
2941 * ],
2942 * "columns": [
2943 * [
2944 * {
2945 * "x": 56,
2946 * "y": 20,
2947 * "minX": 481,
2948 * "minY": 471,
2949 * "width": 37,
2950 * "height": 58,
2951 * "lane": 0,
2952 * "padding": 0,
2953 * "segment": "sign",
2954 * "text": "𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻",
2955 * "zoom": 1
2956 * },
2957 * {
2958 * "x": 57,
2959 * "y": 118,
2960 * "minX": 482,
2961 * "minY": 468,
2962 * "width": 36,
2963 * "height": 65,
2964 * "lane": 0,
2965 * "padding": 0,
2966 * "segment": "sign",
2967 * "text": "𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦",
2968 * "zoom": 1
2969 * },
2970 * {
2971 * "x": 39,
2972 * "y": 203,
2973 * "minX": 464,
2974 * "minY": 496,
2975 * "width": 72,
2976 * "height": 8,
2977 * "lane": 0,
2978 * "padding": 0,
2979 * "segment": "symbol",
2980 * "text": "񏌁𝣢𝤂",
2981 * "zoom": 1
2982 * }
2983 * ]
2984 * ]
2985 * }
2986 */
2987const columns = (swuText, options) => {
2988 if (typeof swuText !== 'string') return {};
2989 const values = columnDefaultsMerge(options);
2990 let input = parse.text(swuText);
2991 let cursor = 0;
2992 let cols = [];
2993 let col = [];
2994 let plus = 0;
2995 let center = parseInt(values.width / 2);
2996 let maxHeight = values.height - values.margin;
2997 let pullable = true;
2998 let finalize = false;
2999 for (let val of input) {
3000 let informed = info(val);
3001 cursor += plus;
3002 if (values.punctuation.spacing) {
3003 cursor += informed.segment == 'sign' ? values.pad : 0;
3004 } else {
3005 cursor += values.pad;
3006 }
3007 finalize = cursor + informed.height > maxHeight;
3008 if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
3009 finalize = false;
3010 pullable = false;
3011 }
3012 if (col.length == 0) {
3013 finalize = false;
3014 }
3015 if (finalize) {
3016 cursor = values.pad;
3017 cols.push(col);
3018 col = [];
3019 pullable = true;
3020 }
3021 col.push(Object.assign(informed, {
3022 x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
3023 y: cursor,
3024 text: val
3025 }));
3026 cursor += informed.height * informed.zoom * values.style.zoom;
3027 if (values.punctuation.spacing) {
3028 plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
3029 } else {
3030 plus = values.pad;
3031 }
3032 }
3033 if (col.length) {
3034 cols.push(col);
3035 }
3036
3037 // over height issue when pulling punctuation
3038 if (values.punctuation.pull) {
3039 for (let col of cols) {
3040 let last = col[col.length - 1];
3041 let diff = last.y + last.height - (values.height - values.margin);
3042 if (diff > 0) {
3043 let adj = parseInt(diff / col.length) + 1;
3044 for (let i in col) {
3045 col[i].y -= adj * i + adj;
3046 }
3047 }
3048 }
3049 }
3050
3051 // contract, expand, adjust
3052 let widths = [];
3053 for (let col of cols) {
3054 let min = [center - values.offset - values.pad];
3055 let max = [center + values.offset + values.pad];
3056 for (let item of col) {
3057 min.push(item.x - values.pad);
3058 max.push(item.x + item.width + values.pad);
3059 }
3060 min = Math.min(...min);
3061 max = Math.max(...max);
3062 let width = values.width;
3063 let adj = 0;
3064 if (!values.dynamic) {
3065 adj = center - parseInt((min + max) / 2);
3066 } else {
3067 width = max - min;
3068 adj = -min;
3069 }
3070 for (let item of col) {
3071 item.x += adj;
3072 }
3073 widths.push(width);
3074 }
3075 return {
3076 'options': values,
3077 'widths': widths,
3078 'columns': cols
3079 };
3080};
3081
3082/**
3083 * Array of plane 4 code points for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
3084 * @alias swu.category
3085 * @type {array}
3086 */
3087const category = [0x40001, 0x461e1, 0x4bca1, 0x4bfa1, 0x4e8e1, 0x4efa1, 0x4f2a1];
3088
3089/**
3090 * Object of symbol ranges with starting and ending code points on plane 4.
3091 *
3092 * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
3093 * @alias swu.ranges
3094 * @type {object}
3095 */
3096const ranges = {
3097 'all': [0x40001, 0x4f480],
3098 'writing': [0x40001, 0x4efa0],
3099 'hand': [0x40001, 0x461e0],
3100 'movement': [0x461e1, 0x4bca0],
3101 'dynamic': [0x4bca1, 0x4bfa0],
3102 'head': [0x4bfa1, 0x4e8e0],
3103 'hcenter': [0x4bfa1, 0x4e8e0],
3104 'vcenter': [0x4bfa1, 0x4ec40],
3105 'trunk': [0x4e8e1, 0x4ec40],
3106 'limb': [0x4ec41, 0x4efa0],
3107 'location': [0x4efa1, 0x4f2a0],
3108 'punctuation': [0x4f2a1, 0x4f480]
3109};
3110
3111/**
3112 * Array of colors associated with the seven symbol categories.
3113 * @alias swu.colors
3114 * @type {array}
3115 */
3116const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
3117
3118/**
3119 * Function that returns the standardized color for a symbol.
3120 * @function swu.colorize
3121 * @param {string} swuSym - an SWU symbol character
3122 * @returns {string} name of standardized color for symbol
3123 * @example
3124 * swu.colorize('񀀁')
3125 *
3126 * return '#0000CC'
3127 */
3128const colorize = swuSym => {
3129 const parsed = parse.symbol(swuSym);
3130 let color = '#000000';
3131 if (parsed.symbol) {
3132 const code = swu2code(parsed.symbol);
3133 const index = category.findIndex(val => val > code);
3134 color = colors[index < 0 ? 6 : index - 1];
3135 }
3136 return color;
3137};
3138
3139/* support ongoing development */
3140/* https://patreon.com/signwriting */
3141/* https://donate.sutton-signwriting.io */
3142
3143/**
3144 * Function that returns the size of a symbol using an SWU symbol character
3145 * @function swu.symbolSize
3146 * @param {string} swu - an SWU symbol character
3147 * @returns {number[]} width and height of symbol
3148 * @example
3149 * swu.symbolSize("񀀁")
3150 *
3151 * return [15,30]
3152 */
3153const symbolSize = function (swu) {
3154 const parsed = parse.symbol(swu);
3155 if (!parsed.symbol) {
3156 return undefined;
3157 }
3158 return symbolSize$2(swu2id(swu));
3159};
3160
3161/**
3162 * Function that returns a plane 15 character for a symbol line using an SWU symbol character
3163 * @function swu.symbolLine
3164 * @param {string} swu - an SWU symbol character
3165 * @returns {string} character for symbol line
3166 * @example
3167 * swu.symbolLine('񀀁')
3168 *
3169 * return '󰀁'
3170 */
3171const symbolLine = function (swu) {
3172 return symbolLine$2(swu2id(swu));
3173};
3174
3175/**
3176 * Function that returns a plane 165 character for a symbol fill using an SWU symbol character
3177 * @function swu.symbolFill
3178 * @param {string} swu - an SWU symbol character
3179 * @returns {string} character for symbol fill
3180 * @example
3181 * swu.symbolFill('񀀁')
3182 *
3183 * return '􀀁'
3184 */
3185const symbolFill = function (swu) {
3186 return symbolFill$2(swu2id(swu));
3187};
3188
3189/**
3190 * Function that creates two text elements for a symbol using an SWU symbol character
3191 * @function swu.symbolText
3192 * @param {string} swu - an SWU symbol character
3193 * @returns {string} svg segment for line and fill
3194 * @example
3195 * swu.symbolText('񀀁')
3196 *
3197 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
3198 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
3199 */
3200const symbolText = function (swu) {
3201 return symbolText$2(swu2id(swu));
3202};
3203
3204/**
3205 * Function that creates an SVG image from an SWU symbol key with an optional style string
3206 * @function swu.symbolSvgBody
3207 * @param {string} swuSym - an SWU symbol key with optional style string
3208 * @returns {string} body of SVG for symbol
3209 * @example
3210 * swu.symbolSvgBody('S10000')
3211 *
3212 * return `<text font-size="0">S10000</text>
3213 * <g transform="translate(500,500)">
3214 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
3215 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
3216 * </g>`
3217 */
3218const symbolSvgBody = swuSym => {
3219 const parsed = parse.symbol(swuSym);
3220 const blank = '';
3221 if (!parsed.symbol) return blank;
3222 let styling = parse$2(parsed.style);
3223 let x1, y1, x2, y2;
3224 if (parsed.coord) {
3225 x1 = parsed.coord[0];
3226 y1 = parsed.coord[1];
3227 x2 = 500 + (500 - x1);
3228 y2 = 500 + (500 - y1);
3229 } else {
3230 let size = symbolSize(parsed.symbol);
3231 if (!size) return blank;
3232 x1 = 500 - parseInt((size[0] + 1) / 2);
3233 y1 = 500 - parseInt((size[1] + 1) / 2);
3234 x2 = 500 + (500 - x1);
3235 y2 = 500 + (500 - y1);
3236 }
3237 let symSvg = symbolText(parsed.symbol);
3238 symSvg = ` <g transform="translate(${x1},${y1})">
3239${symSvg}
3240 </g>`;
3241 let line;
3242 if (styling.colorize) {
3243 line = colorize(parsed.symbol);
3244 } else if (styling.detail) {
3245 line = styling.detail[0];
3246 }
3247 if (line) {
3248 symSvg = symSvg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${line}"`);
3249 }
3250 let fill = styling.detail && styling.detail[1];
3251 if (fill) {
3252 symSvg = symSvg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${fill}"`);
3253 }
3254 let background = '';
3255 if (styling.padding) {
3256 x1 -= styling.padding;
3257 y1 -= styling.padding;
3258 x2 += styling.padding;
3259 y2 += styling.padding;
3260 }
3261 if (styling.background) {
3262 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
3263 }
3264 return ` <text font-size="0">${swuSym}</text>${background}
3265${symSvg}`;
3266};
3267
3268/**
3269 * Function that creates an SVG image from an SWU symbol key with an optional style string
3270 * @function swu.symbolSvg
3271 * @param {string} swuSym - an SWU symbol key with optional style string
3272 * @returns {string} SVG for symbol
3273 * @example
3274 * swu.symbolSvg('S10000')
3275 *
3276 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="15" height="30" viewBox="500 500 15 30">
3277 * <text font-size="0">S10000</text>
3278 * <g transform="translate(500,500)">
3279 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
3280 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
3281 * </g>
3282 * </svg>`
3283 */
3284const symbolSvg = swuSym => {
3285 const parsed = parse.symbol(swuSym);
3286 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
3287 if (!parsed.symbol) return blank;
3288 let styling = parse$2(parsed.style);
3289 let x1, y1, x2, y2;
3290 if (parsed.coord) {
3291 x1 = parsed.coord[0];
3292 y1 = parsed.coord[1];
3293 x2 = 500 + (500 - x1);
3294 y2 = 500 + (500 - y1);
3295 } else {
3296 let size = symbolSize(parsed.symbol);
3297 if (!size) return blank;
3298 x1 = parseInt(500 - size[0] / 2);
3299 y1 = parseInt(500 - size[1] / 2);
3300 x2 = x1 + size[0];
3301 y2 = y1 + size[1];
3302 }
3303 let classes = '';
3304 if (styling.classes) {
3305 classes = ` class="${styling.classes}"`;
3306 }
3307 let id = '';
3308 if (styling.id) {
3309 id = ` id="${styling.id}"`;