UNPKG

69.5 kBJavaScriptView Raw
1/**
2* Sutton SignWriting TrueType Font Module v1.4.3 (https://github.com/sutton-signwriting/font-ttf)
3* Author: Steve Slevinski (https://SteveSlevinski.me)
4* fsw.js is released under the MIT License.
5*/
6
7(function (global, factory) {
8 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
9 typeof define === 'function' && define.amd ? define(['exports'], factory) :
10 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.ssw = global.ssw || {}, global.ssw.ttf = global.ssw.ttf || {}, global.ssw.ttf.fsw = {})));
11})(this, (function (exports) { 'use strict';
12
13 /**
14 * Sutton SignWriting Core Module v1.5.5 (https://github.com/sutton-signwriting/core)
15 * Author: Steve Slevinski (https://SteveSlevinski.me)
16 * convert.mjs is released under the MIT License.
17 */
18 /**
19 * Function to convert an FSW symbol key to a 16-bit ID
20 * @function convert.key2id
21 * @param {string} key - FSW symbol key
22 * @returns {number} 16-bit ID
23 * @example
24 * convert.key2id('S10000')
25 *
26 * return 1
27 */
28
29
30 const key2id = key => 1 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16);
31
32 /* support ongoing development on https://patreon.com/signwriting */
33
34 let sizes = {};
35 const zoom = 2;
36 const bound = 76 * zoom;
37 let context;
38
39 /**
40 * Function that returns the size of a symbol using an id
41 * @function font.symbolSize
42 * @param {number} id - a 16-bit number of a symbol
43 * @returns {number[]} width and height of symbol
44 * @example
45 * font.symbolSize(1)
46 *
47 * return [15,30]
48 */
49 const symbolSize$1 = function (id) {
50 if (id in sizes) {
51 return [...sizes[id]];
52 }
53 if (!context) {
54 const canvaser = document.createElement("canvas");
55 canvaser.width = bound;
56 canvaser.height = bound;
57 context = canvaser.getContext("2d");
58 }
59 context.clearRect(0, 0, bound, bound);
60 context.font = 30 * zoom + "px 'SuttonSignWritingLine'";
61 context.fillText(String.fromCodePoint(id + 0xF0000), 0, 0);
62 const imgData = context.getImageData(0, 0, bound, bound).data;
63 let w, h, i, s;
64 wloop: for (w = bound - 1; w >= 0; w--) {
65 for (h = 0; h < bound; h += 1) {
66 for (s = 0; s < 4; s += 1) {
67 i = w * 4 + h * 4 * bound + s;
68 if (imgData[i]) {
69 break wloop;
70 }
71 }
72 }
73 }
74 var width = w;
75 hloop: for (h = bound - 1; h >= 0; h--) {
76 for (w = 0; w < width; w += 1) {
77 for (s = 0; s < 4; s += 1) {
78 i = w * 4 + h * 4 * bound + s;
79 if (imgData[i]) {
80 break hloop;
81 }
82 }
83 }
84 }
85 var height = h + 1;
86 width = Math.ceil(width / zoom);
87 height = Math.ceil(height / zoom);
88 // Rounding error in chrome. Manual fixes.
89 if (14394 == id) {
90 width = 19;
91 }
92 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)) {
93 width = 20;
94 }
95 if (31921 == id) {
96 width = 22;
97 }
98 if (38460 == id) {
99 width = 23;
100 }
101 if ([20164, 20212].includes(id)) {
102 width = 25;
103 }
104 if (31894 == id) {
105 width = 28;
106 }
107 if (46698 == id) {
108 width = 29;
109 }
110 if (29606 == id) {
111 width = 30;
112 }
113 if (44855 == id) {
114 width = 40;
115 }
116 if (32667 == id) {
117 width = 50;
118 }
119 if ([11088, 11474, 11490, 11506].includes(id)) {
120 height = 20;
121 }
122 if (6285 == id) {
123 height = 21;
124 }
125 if (40804 == id) {
126 height = 31;
127 }
128 if (41475 == id) {
129 height = 36;
130 }
131 // Error in chrome. Manual fix.
132 // if (width==0 && height==0) {
133 if (width == 0 && height == 0) {
134 const sizefix = {
135 9: [15, 30],
136 10: [21, 30],
137 11: [30, 15],
138 12: [30, 21],
139 13: [15, 30],
140 14: [21, 30]
141 };
142 if (id in sizefix) {
143 width = sizefix[id][0];
144 height = sizefix[id][1];
145 }
146 }
147 if (width == 0 && height == 0) {
148 return undefined;
149 }
150 sizes[id] = [width, height];
151 return [width, height];
152 };
153
154 /**
155 * Function that returns the size of a symbol using an FSW symbol key
156 * @function fsw.symbolSize
157 * @param {string} fsw - an FSW symbol key
158 * @returns {number[]} width and height of symbol
159 * @example
160 * fsw.symbolSize("S10000")
161 *
162 * return [15,30]
163 */
164 const symbolSize = function (fsw) {
165 return symbolSize$1(key2id(fsw));
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 */
178 const symbolLine$1 = 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 */
192 const symbolFill$1 = 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 */
207 const symbolText$1 = function (id) {
208 return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">${symbolFill$1(id)}</text>
209 <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">${symbolLine$1(id)}</text>`;
210 };
211
212 /**
213 * Function that returns a plane 15 character for a symbol line using an FSW symbol key
214 * @function fsw.symbolLine
215 * @param {string} fsw - an FSW symbol key
216 * @returns {string} character for symbol line
217 * @example
218 * fsw.symbolLine('S10000')
219 *
220 * return '󰀁'
221 */
222 const symbolLine = function (fsw) {
223 return symbolLine$1(key2id(fsw));
224 };
225
226 /**
227 * Function that returns a plane 16 character for a symbol fill using an FSW symbol key
228 * @function fsw.symbolFill
229 * @param {string} fsw - an FSW symbol key
230 * @returns {string} character for symbol fill
231 * @example
232 * font.symbolFill('S10000')
233 *
234 * return '􀀁'
235 */
236 const symbolFill = function (fsw) {
237 return symbolFill$1(key2id(fsw));
238 };
239
240 /**
241 * Function that creates two text elements for a symbol using an FSW symbol key
242 * @function fsw.symbolText
243 * @param {string} fsw - an FSW symbol key
244 * @returns {string} svg segment for line and fill
245 * @example
246 * fsw.symbolText('S10000')
247 *
248 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
249 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
250 */
251 const symbolText = function (fsw) {
252 return symbolText$1(key2id(fsw));
253 };
254
255 /**
256 * Sutton SignWriting Core Module v1.5.5 (https://github.com/sutton-signwriting/core)
257 * Author: Steve Slevinski (https://SteveSlevinski.me)
258 * style.mjs is released under the MIT License.
259 */
260
261 /**
262 * Object of regular expressions for style strings
263 *
264 * @alias style.re
265 * @type {object}
266 * @property {string} colorize - regular expression for colorize section
267 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
268 * @property {string} colorname - regular expression for css color name
269 * @property {string} padding - regular expression for padding section
270 * @property {string} zoom - regular expression for zoom section
271 * @property {string} classbase - regular expression for class name definition
272 * @property {string} id - regular expression for id definition
273 * @property {string} colorbase - regular expression for color hex or color name
274 * @property {string} color - regular expression for single color entry
275 * @property {string} colors - regular expression for double color entry
276 * @property {string} background - regular expression for background section
277 * @property {string} detail - regular expression for color details for line and optional fill
278 * @property {string} detailsym - regular expression for color details for individual symbols
279 * @property {string} classes - regular expression for one or more class names
280 * @property {string} full - full regular expression for style string
281 */
282 let re$2 = {
283 'colorize': 'C',
284 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
285 'colorname': '[a-zA-Z]+',
286 'padding': 'P[0-9]{2}',
287 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
288 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
289 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
290 };
291 re$2.colorbase = `(?:${re$2.colorhex}|${re$2.colorname})`;
292 re$2.color = `_${re$2.colorbase}_`;
293 re$2.colors = `_${re$2.colorbase}(?:,${re$2.colorbase})?_`;
294 re$2.background = `G${re$2.color}`;
295 re$2.detail = `D${re$2.colors}`;
296 re$2.detailsym = `D[0-9]{2}${re$2.colors}`;
297 re$2.classes = `${re$2.classbase}(?: ${re$2.classbase})*`;
298 re$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})!)?)?`;
299
300 const prefixColor$1 = color => {
301 const regex = new RegExp(`^${re$2.colorhex}$`);
302 return (regex.test(color) ? '#' : '') + color;
303 };
304
305 const definedProps$1 = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
306 /**
307 * Function to parse style string to object
308 * @function style.parse
309 * @param {string} styleString - a style string
310 * @returns {StyleObject} elements of style string
311 * @example
312 * style.parse('-CP10G_blue_D_red,Cyan_')
313 *
314 * return {
315 * 'colorize': true,
316 * 'padding': 10,
317 * 'background': 'blue',
318 * 'detail': ['red', 'Cyan']
319 * }
320 */
321
322
323 const parse$2 = styleString => {
324 const regex = `^${re$2.full}`;
325 const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
326 return definedProps$1({
327 'colorize': !m[1] ? undefined : !!m[1],
328 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
329 'background': !m[3] ? undefined : prefixColor$1(m[3].slice(2, -1)),
330 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor$1),
331 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
332 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re$2.detailsym, 'g')).map(val => {
333 const parts = val.split('_');
334 const detail = parts[1].split(',').map(prefixColor$1);
335 return {
336 'index': parseInt(parts[0].slice(1)),
337 'detail': detail
338 };
339 }),
340 'classes': !m[7] ? undefined : m[7],
341 'id': !m[8] ? undefined : m[8]
342 });
343 };
344
345 /**
346 * Function to compose style string from object
347 * @function style.compose
348 * @param {StyleObject} styleObject - an object of style options
349 * @returns {string} style string
350 * @example
351 * style.compose({
352 * 'colorize': true,
353 * 'padding': 10,
354 * 'background': 'blue',
355 * 'detail': ['red', 'Cyan'],
356 * 'zoom': 1.1,
357 * 'detailsym': [
358 * {
359 * 'index': 1,
360 * 'detail': ['#ff00ff']
361 * },
362 * {
363 * 'index': 2,
364 * 'detail': ['yellow', 'green']
365 * }
366 * ],
367 * 'classes': 'primary blinking',
368 * 'id': 'cursor'
369 * })
370 *
371 * return '-CP10G_blue_D_red,Cyan_Z1.1-D01_ff00ff_D02_yellow,green_-primary blinking!cursor!'
372 */
373
374 const compose = styleObject => {
375 if (typeof styleObject !== 'object' || styleObject === null) return undefined; // three sections
376
377 let style1 = '-';
378 style1 += !styleObject.colorize ? '' : 'C';
379 const padding = parseInt(styleObject.padding);
380 style1 += !padding || padding <= 0 || padding > 99 ? '' : 'P' + (padding > 9 ? padding : '0' + padding);
381 const background = !styleObject.background || !(typeof styleObject.background === 'string') ? undefined : styleObject.background.match(re$2.colorbase)[0];
382 style1 += !background ? '' : 'G_' + background + '_';
383 const detail1 = !styleObject.detail || !styleObject.detail[0] || !(typeof styleObject.detail[0] === 'string') ? undefined : styleObject.detail[0].match(re$2.colorbase)[0];
384 const detail2 = !styleObject.detail || !styleObject.detail[1] || !(typeof styleObject.detail[1] === 'string') ? undefined : styleObject.detail[1].match(re$2.colorbase)[0];
385
386 if (detail1) {
387 style1 += 'D_' + detail1;
388
389 if (detail2) {
390 style1 += ',' + detail2;
391 }
392
393 style1 += '_';
394 }
395
396 const zoom = styleObject.zoom === 'x' ? 'x' : parseFloat(styleObject.zoom);
397 style1 += !zoom || zoom <= 0 ? '' : 'Z' + zoom;
398 let style2 = '';
399 const detailsym = !styleObject.detailsym || !Array.isArray(styleObject.detailsym) ? [] : styleObject.detailsym.map(styleObject => {
400 const index = parseInt(styleObject.index);
401 if (!index || index <= 0 || index > 99) return '';
402 let style = 'D' + (index > 9 ? index : '0' + index);
403 const detail1 = !styleObject.detail || !styleObject.detail[0] ? undefined : styleObject.detail[0].match(re$2.colorbase)[0];
404 const detail2 = !styleObject.detail || !styleObject.detail[1] ? undefined : styleObject.detail[1].match(re$2.colorbase)[0];
405
406 if (detail1) {
407 style += '_' + detail1;
408
409 if (detail2) {
410 style += ',' + detail2;
411 }
412
413 style += '_';
414 }
415
416 return style;
417 });
418 style2 += detailsym.join('');
419 let style3 = '';
420 const classes = !styleObject.classes || !(typeof styleObject.classes === 'string') ? undefined : styleObject.classes.match(re$2.classes)[0];
421 style3 += !classes ? '' : classes;
422 const id = !styleObject.id || !(typeof styleObject.id === 'string') ? undefined : styleObject.id.match(re$2.id)[0];
423 style3 += classes || id ? '!' : '';
424 style3 += !id ? '' : id + '!';
425 return style1 + (style2 || style3 ? '-' + style2 : '') + (style3 ? '-' + style3 : '');
426 };
427
428 /* support ongoing development on https://patreon.com/signwriting */
429
430 /**
431 * Sutton SignWriting Core Module v1.5.5 (https://github.com/sutton-signwriting/core)
432 * Author: Steve Slevinski (https://SteveSlevinski.me)
433 * fsw.mjs is released under the MIT License.
434 */
435
436 /**
437 * Object of regular expressions for FSW strings
438 *
439 * @alias fsw.re
440 * @property {string} symbol - regular expressions for a symbol
441 * @property {string} coord - regular expressions for a coordinate
442 * @property {string} sort - regular expressions for the sorting marker
443 * @property {string} box - regular expression for a signbox marker
444 * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
445 * @property {string} spatial - regular expression for a symbol followed by a coordinate
446 * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
447 * @property {string} sign - regular expression for an optional prefix followed by a signbox
448 * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
449 */
450 let re$1 = {
451 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
452 'coord': '[0-9]{3}x[0-9]{3}',
453 'sort': 'A',
454 'box': '[BLMR]'
455 };
456 re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
457 re$1.spatial = `${re$1.symbol}${re$1.coord}`;
458 re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
459 re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
460 re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
461
462 /**
463 * Object of regular expressions for style strings
464 *
465 * @alias style.re
466 * @type {object}
467 * @property {string} colorize - regular expression for colorize section
468 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
469 * @property {string} colorname - regular expression for css color name
470 * @property {string} padding - regular expression for padding section
471 * @property {string} zoom - regular expression for zoom section
472 * @property {string} classbase - regular expression for class name definition
473 * @property {string} id - regular expression for id definition
474 * @property {string} colorbase - regular expression for color hex or color name
475 * @property {string} color - regular expression for single color entry
476 * @property {string} colors - regular expression for double color entry
477 * @property {string} background - regular expression for background section
478 * @property {string} detail - regular expression for color details for line and optional fill
479 * @property {string} detailsym - regular expression for color details for individual symbols
480 * @property {string} classes - regular expression for one or more class names
481 * @property {string} full - full regular expression for style string
482 */
483 let re = {
484 'colorize': 'C',
485 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
486 'colorname': '[a-zA-Z]+',
487 'padding': 'P[0-9]{2}',
488 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
489 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
490 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
491 };
492 re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
493 re.color = `_${re.colorbase}_`;
494 re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
495 re.background = `G${re.color}`;
496 re.detail = `D${re.colors}`;
497 re.detailsym = `D[0-9]{2}${re.colors}`;
498 re.classes = `${re.classbase}(?: ${re.classbase})*`;
499 re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
500
501 const prefixColor = color => {
502 const regex = new RegExp(`^${re.colorhex}$`);
503 return (regex.test(color) ? '#' : '') + color;
504 };
505
506 const definedProps = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
507 /**
508 * Function to parse style string to object
509 * @function style.parse
510 * @param {string} styleString - a style string
511 * @returns {StyleObject} elements of style string
512 * @example
513 * style.parse('-CP10G_blue_D_red,Cyan_')
514 *
515 * return {
516 * 'colorize': true,
517 * 'padding': 10,
518 * 'background': 'blue',
519 * 'detail': ['red', 'Cyan']
520 * }
521 */
522
523
524 const parse$1 = styleString => {
525 const regex = `^${re.full}`;
526 const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
527 return definedProps({
528 'colorize': !m[1] ? undefined : !!m[1],
529 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
530 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)),
531 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor),
532 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
533 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => {
534 const parts = val.split('_');
535 const detail = parts[1].split(',').map(prefixColor);
536 return {
537 'index': parseInt(parts[0].slice(1)),
538 'detail': detail
539 };
540 }),
541 'classes': !m[7] ? undefined : m[7],
542 'id': !m[8] ? undefined : m[8]
543 });
544 };
545
546 /** 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.
547 * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
548 * @module convert
549 */
550 /**
551 * Function to convert an FSW coordinate string to an array of x,y integers
552 * @function convert.fsw2coord
553 * @param {string} fswCoord - An FSW coordinate string
554 * @returns {number[]} Array of x,y integers
555 * @example
556 * convert.fsw2coord('500x500')
557 *
558 * return [500, 500]
559 */
560
561
562 const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
563
564 const parse = {
565 /**
566 * Function to parse an fsw symbol with optional coordinate and style string
567 * @function fsw.parse.symbol
568 * @param {string} fswSym - an fsw symbol
569 * @returns {SymbolObject} elements of fsw symbol
570 * @example
571 * fsw.parse.symbol('S10000500x500-C')
572 *
573 * return {
574 * 'symbol': 'S10000',
575 * 'coord': [500, 500],
576 * 'style': '-C'
577 * }
578 */
579 symbol: fswSym => {
580 const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
581 const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
582 return {
583 'symbol': symbol ? symbol[1] : undefined,
584 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
585 'style': symbol ? symbol[3] : undefined
586 };
587 },
588
589 /**
590 * Function to parse an fsw sign with style string
591 * @function fsw.parse.sign
592 * @param {string} fswSign - an fsw sign
593 * @returns { SignObject } elements of fsw sign
594 * @example
595 * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
596 *
597 * return {
598 * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
599 * box: 'M',
600 * max: [525, 535],
601 * spatials: [
602 * {
603 * symbol: 'S2e748',
604 * coord: [483, 510]
605 * },
606 * {
607 * symbol: 'S10011',
608 * coord: [501, 466]
609 * },
610 * {
611 * symbol: 'S2e704',
612 * coord: [510, 500]
613 * },
614 * {
615 * symbol: 'S10019',
616 * coord: [476, 475]
617 * }
618 * ],
619 * style: '-C'
620 * }
621 */
622 sign: fswSign => {
623 const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
624 const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
625
626 if (sign) {
627 return {
628 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
629 'box': sign[2][0],
630 'max': fsw2coord(sign[2].slice(1, 8)),
631 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
632 return {
633 symbol: m.slice(0, 6),
634 coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
635 };
636 }),
637 'style': sign[3]
638 };
639 } else {
640 return {};
641 }
642 },
643
644 /**
645 * Function to parse an fsw text
646 * @function fsw.parse.text
647 * @param {string} fswText - an fsw text
648 * @returns {string[]} fsw signs and punctuations
649 * @example
650 * fsw.parse.text('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496')
651 *
652 * return [
653 * 'AS14c20S27106M518x529S14c20481x471S27106503x489',
654 * 'AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468',
655 * 'S38800464x496'
656 * ]
657 */
658 text: fswText => {
659 if (typeof fswText !== 'string') return [];
660 const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
661 const matches = fswText.match(new RegExp(regex, 'g'));
662 return matches ? [...matches] : [];
663 }
664 };
665
666 /**
667 * Function to gather sizing information about an fsw sign or symbol
668 * @function fsw.info
669 * @param {string} fsw - an fsw sign or symbol
670 * @returns {SegmentInfo} information about the fsw string
671 * @example
672 * fsw.info('AS14c20S27106L518x529S14c20481x471S27106503x489-P10Z2')
673 *
674 * return {
675 * minX: 481,
676 * minY: 471,
677 * width: 37,
678 * height: 58,
679 * lane: -1,
680 * padding: 10,
681 * segment: 'sign',
682 * zoom: 2
683 * }
684 */
685
686 const info = fsw => {
687 let lanes = {
688 "B": 0,
689 "L": -1,
690 "M": 0,
691 "R": 1
692 };
693 let parsed = parse.sign(fsw);
694 let width, height, segment, x1, x2, y1, y2, lane;
695
696 if (parsed.spatials) {
697 x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
698 x2 = parsed.max[0];
699 width = x2 - x1;
700 y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
701 y2 = parsed.max[1];
702 height = y2 - y1;
703 segment = 'sign';
704 lane = parsed.box;
705 } else {
706 parsed = parse.symbol(fsw);
707 lane = "M";
708
709 if (parsed.coord) {
710 x1 = parsed.coord[0];
711 width = (500 - x1) * 2;
712 y1 = parsed.coord[1];
713 height = (500 - y1) * 2;
714 segment = 'symbol';
715 } else {
716 x1 = 490;
717 width = 20;
718 y1 = 490;
719 height = 20;
720 segment = 'none';
721 }
722 }
723
724 let style = parse$1(parsed.style);
725 let zoom = style.zoom || 1;
726 let padding = style.padding || 0;
727 return {
728 minX: x1,
729 minY: y1,
730 width: width,
731 height: height,
732 segment: segment,
733 lane: lanes[lane],
734 padding: padding,
735 zoom: zoom
736 };
737 };
738
739 const columnDefaults = {
740 'height': 500,
741 'width': 150,
742 'offset': 50,
743 'pad': 20,
744 'margin': 5,
745 'dynamic': false,
746 'background': undefined,
747 'punctuation': {
748 'spacing': true,
749 'pad': 30,
750 'pull': true
751 },
752 'style': {
753 'detail': ['black', 'white'],
754 'zoom': 1
755 }
756 };
757 /**
758 * Function to an object of column options with default values
759 *
760 * @function fsw.columnDefaultsMerge
761 * @param {ColumnOptions} options - object of column options
762 * @returns {ColumnOptions} object of column options merged with column defaults
763 * @example
764 * fsw.columnDefaultsMerge({height: 500,width:150})
765 *
766 * return {
767 * "height": 500,
768 * "width": 150,
769 * "offset": 50,
770 * "pad": 20,
771 * "margin": 5,
772 * "dynamic": false,
773 * "punctuation": {
774 * "spacing": true,
775 * "pad": 30,
776 * "pull": true
777 * },
778 * "style": {
779 * "detail": [
780 * "black",
781 * "white"
782 * ],
783 * "zoom": 1
784 * }
785 * }
786 */
787
788 const columnDefaultsMerge = options => {
789 if (typeof options !== 'object') options = {};
790 return { ...columnDefaults,
791 ...options,
792 punctuation: { ...columnDefaults.punctuation,
793 ...options.punctuation
794 },
795 style: { ...columnDefaults.style,
796 ...options.style
797 }
798 };
799 };
800 /**
801 * Function to transform an FSW text to an array of columns
802 *
803 * @function fsw.columns
804 * @param {string} fswText - FSW text of signs and punctuation
805 * @param {ColumnOptions} options - object of column options
806 * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
807 * @example
808 * fsw.columns('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496', {height: 500,width:150})
809 *
810 * return {
811 * "options": {
812 * "height": 500,
813 * "width": 150,
814 * "offset": 50,
815 * "pad": 20,
816 * "margin": 5,
817 * "dynamic": false,
818 * "punctuation": {
819 * "spacing": true,
820 * "pad": 30,
821 * "pull": true
822 * },
823 * "style": {
824 * "detail": [
825 * "black",
826 * "white"
827 * ],
828 * "zoom": 1
829 * }
830 * },
831 * "widths": [
832 * 150
833 * ],
834 * "columns": [
835 * [
836 * {
837 * "x": 56,
838 * "y": 20,
839 * "minX": 481,
840 * "minY": 471,
841 * "width": 37,
842 * "height": 58,
843 * "lane": 0,
844 * "padding": 0,
845 * "segment": "sign",
846 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
847 * "zoom": 1
848 * },
849 * {
850 * "x": 57,
851 * "y": 118,
852 * "minX": 482,
853 * "minY": 468,
854 * "width": 36,
855 * "height": 65,
856 * "lane": 0,
857 * "padding": 0,
858 * "segment": "sign",
859 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
860 * "zoom": 1
861 * },
862 * {
863 * "x": 39,
864 * "y": 203,
865 * "minX": 464,
866 * "minY": 496,
867 * "width": 72,
868 * "height": 8,
869 * "lane": 0,
870 * "padding": 0,
871 * "segment": "symbol",
872 * "text": "S38800464x496",
873 * "zoom": 1
874 * }
875 * ]
876 * ]
877 * }
878 */
879
880
881 const columns = (fswText, options) => {
882 if (typeof fswText !== 'string') return {};
883 const values = columnDefaultsMerge(options);
884 let input = parse.text(fswText);
885 let cursor = 0;
886 let cols = [];
887 let col = [];
888 let plus = 0;
889 let center = parseInt(values.width / 2);
890 let maxHeight = values.height - values.margin;
891 let pullable = true;
892 let finalize = false;
893
894 for (let val of input) {
895 let informed = info(val);
896 cursor += plus;
897
898 if (values.punctuation.spacing) {
899 cursor += informed.segment == 'sign' ? values.pad : 0;
900 } else {
901 cursor += values.pad;
902 }
903
904 finalize = cursor + informed.height > maxHeight;
905
906 if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
907 finalize = false;
908 pullable = false;
909 }
910
911 if (col.length == 0) {
912 finalize = false;
913 }
914
915 if (finalize) {
916 cursor = values.pad;
917 cols.push(col);
918 col = [];
919 pullable = true;
920 }
921
922 col.push(Object.assign(informed, {
923 x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
924 y: cursor,
925 text: val
926 }));
927 cursor += informed.height * informed.zoom * values.style.zoom;
928
929 if (values.punctuation.spacing) {
930 plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
931 } else {
932 plus = values.pad;
933 }
934 }
935
936 if (col.length) {
937 cols.push(col);
938 } // over height issue when pulling punctuation
939
940
941 if (values.punctuation.pull) {
942 for (let col of cols) {
943 let last = col[col.length - 1];
944 let diff = last.y + last.height - (values.height - values.margin);
945
946 if (diff > 0) {
947 let adj = parseInt(diff / col.length) + 1;
948
949 for (let i in col) {
950 col[i].y -= adj * i + adj;
951 }
952 }
953 }
954 } // contract, expand, adjust
955
956
957 let widths = [];
958
959 for (let col of cols) {
960 let min = [center - values.offset - values.pad];
961 let max = [center + values.offset + values.pad];
962
963 for (let item of col) {
964 min.push(item.x - values.pad);
965 max.push(item.x + item.width + values.pad);
966 }
967
968 min = Math.min(...min);
969 max = Math.max(...max);
970 let width = values.width;
971 let adj = 0;
972
973 if (!values.dynamic) {
974 adj = center - parseInt((min + max) / 2);
975 } else {
976 width = max - min;
977 adj = -min;
978 }
979
980 for (let item of col) {
981 item.x += adj;
982 }
983
984 widths.push(width);
985 }
986
987 return {
988 'options': values,
989 'widths': widths,
990 'columns': cols
991 };
992 };
993 /**
994 * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
995 * @alias fsw.category
996 * @type {number[]}
997 */
998
999 const category = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
1000 /**
1001 * Object of symbol ranges with starting and ending numbers.
1002 *
1003 * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
1004 * @alias fsw.ranges
1005 * @type {object}
1006 */
1007
1008 const ranges = {
1009 'all': [0x100, 0x38b],
1010 'writing': [0x100, 0x37e],
1011 'hand': [0x100, 0x204],
1012 'movement': [0x205, 0x2f6],
1013 'dynamic': [0x2f7, 0x2fe],
1014 'head': [0x2ff, 0x36c],
1015 'hcenter': [0x2ff, 0x36c],
1016 'vcenter': [0x2ff, 0x375],
1017 'trunk': [0x36d, 0x375],
1018 'limb': [0x376, 0x37e],
1019 'location': [0x37f, 0x386],
1020 'punctuation': [0x387, 0x38b]
1021 };
1022
1023 /**
1024 * Array of colors associated with the seven symbol categories.
1025 * @alias fsw.colors
1026 * @type {string[]}
1027 */
1028
1029 const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
1030 /**
1031 * Function that returns the standardized color for a symbol.
1032 * @function fsw.colorize
1033 * @param {string} key - an FSW symbol key
1034 * @returns {string} name of standardized color for symbol
1035 * @example
1036 * fsw.colorize('S10000')
1037 *
1038 * return '#0000CC'
1039 */
1040
1041 const colorize = key => {
1042 const parsed = parse.symbol(key);
1043 let color = '#000000';
1044
1045 if (parsed.symbol) {
1046 const dec = parseInt(parsed.symbol.slice(1, 4), 16);
1047 const index = category.findIndex(val => val > dec);
1048 color = colors[index < 0 ? 6 : index - 1];
1049 }
1050
1051 return color;
1052 };
1053
1054 /* support ongoing development on https://patreon.com/signwriting */
1055
1056 /**
1057 * Function that creates an SVG image from an FSW symbol key with an optional style string
1058 * @function fsw.symbolSvgBody
1059 * @param {string} fswSym - an FSW symbol key with optional style string
1060 * @returns {string} body of SVG for symbol
1061 * @example
1062 * fsw.symbolSvgBody('S10000')
1063 *
1064 * return `<text font-size="0">S10000</text>
1065 * <g transform="translate(500,500)">
1066 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
1067 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
1068 * </g>`
1069 */
1070 const symbolSvgBody = fswSym => {
1071 const parsed = parse.symbol(fswSym);
1072 const blank = '';
1073 if (!parsed.symbol) return blank;
1074 let styling = parse$2(parsed.style);
1075 let x1, y1, x2, y2;
1076 if (parsed.coord) {
1077 x1 = parsed.coord[0];
1078 y1 = parsed.coord[1];
1079 x2 = 500 + (500 - x1);
1080 y2 = 500 + (500 - y1);
1081 } else {
1082 let size = symbolSize(parsed.symbol);
1083 if (!size) return blank;
1084 x1 = 500 - parseInt((size[0] + 1) / 2);
1085 y1 = 500 - parseInt((size[1] + 1) / 2);
1086 x2 = 500 + (500 - x1);
1087 y2 = 500 + (500 - y1);
1088 }
1089 let symSvg = symbolText(parsed.symbol);
1090 symSvg = ` <g transform="translate(${x1},${y1})">
1091${symSvg}
1092 </g>`;
1093 let line;
1094 if (styling.colorize) {
1095 line = colorize(parsed.symbol);
1096 } else if (styling.detail) {
1097 line = styling.detail[0];
1098 }
1099 if (line) {
1100 symSvg = symSvg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${line}"`);
1101 }
1102 let fill = styling.detail && styling.detail[1];
1103 if (fill) {
1104 symSvg = symSvg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${fill}"`);
1105 }
1106 let background = '';
1107 if (styling.padding) {
1108 x1 -= styling.padding;
1109 y1 -= styling.padding;
1110 x2 += styling.padding;
1111 y2 += styling.padding;
1112 }
1113 if (styling.background) {
1114 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
1115 }
1116 return ` <text font-size="0">${fswSym}</text>${background}
1117${symSvg}`;
1118 };
1119
1120 /**
1121 * Function that creates an SVG image from an FSW symbol key with an optional style string
1122 * @function fsw.symbolSvg
1123 * @param {string} fswSym - an FSW symbol key with optional style string
1124 * @returns {string} SVG for symbol
1125 * @example
1126 * fsw.symbolSvg('S10000')
1127 *
1128 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="15" height="30" viewBox="500 500 15 30">
1129 * <text font-size="0">S10000</text>
1130 * <g transform="translate(500,500)">
1131 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
1132 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
1133 * </g>
1134 * </svg>`
1135 */
1136 const symbolSvg = fswSym => {
1137 const parsed = parse.symbol(fswSym);
1138 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
1139 if (!parsed.symbol) return blank;
1140 let styling = parse$2(parsed.style);
1141 let x1, y1, x2, y2;
1142 if (parsed.coord) {
1143 x1 = parsed.coord[0];
1144 y1 = parsed.coord[1];
1145 x2 = 500 + (500 - x1);
1146 y2 = 500 + (500 - y1);
1147 } else {
1148 let size = symbolSize(parsed.symbol);
1149 if (!size) return blank;
1150 x1 = parseInt(500 - size[0] / 2);
1151 y1 = parseInt(500 - size[1] / 2);
1152 x2 = x1 + size[0];
1153 y2 = y1 + size[1];
1154 }
1155 let classes = '';
1156 if (styling.classes) {
1157 classes = ` class="${styling.classes}"`;
1158 }
1159 let id = '';
1160 if (styling.id) {
1161 id = ` id="${styling.id}"`;
1162 }
1163 if (styling.padding) {
1164 x1 -= styling.padding;
1165 y1 -= styling.padding;
1166 x2 += styling.padding;
1167 y2 += styling.padding;
1168 }
1169 let sizing = '';
1170 if (styling.zoom != 'x') {
1171 sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
1172 }
1173 return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
1174${symbolSvgBody(fswSym)}
1175</svg>`;
1176 };
1177
1178 const symbolCanvas = function (fswSym) {
1179 const parsed = parse.symbol(fswSym);
1180 if (parsed.symbol) {
1181 let size = symbolSize(parsed.symbol);
1182 if (size) {
1183 const canvas = document.createElement('canvas');
1184 const context = canvas.getContext('2d');
1185 let styling = parse$2(parsed.style);
1186 let line = 'black';
1187 if (styling.colorize) {
1188 line = colorize(parsed.symbol);
1189 } else if (styling.detail) {
1190 line = styling.detail[0];
1191 }
1192 let fill = styling.detail && styling.detail[1] || 'white';
1193 let x1 = 500;
1194 let x2 = x1 + size[0];
1195 let y1 = 500;
1196 let y2 = y1 + size[1];
1197 if (styling.padding) {
1198 x1 -= styling.padding;
1199 y1 -= styling.padding;
1200 x2 += styling.padding;
1201 y2 += styling.padding;
1202 }
1203 let sizing = 1;
1204 if (styling.zoom != 'x') {
1205 sizing = styling.zoom;
1206 }
1207 let w = (x2 - x1) * sizing;
1208 let h = (y2 - y1) * sizing;
1209 canvas.width = w ? w : 1;
1210 canvas.height = h ? h : 1;
1211 if (styling.background) {
1212 context.rect(0, 0, w, h);
1213 context.fillStyle = styling.background;
1214 context.fill();
1215 }
1216 context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
1217 context.fillStyle = fill;
1218 context.fillText(symbolFill(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
1219 context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
1220 context.fillStyle = line;
1221 context.fillText(symbolLine(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
1222 return canvas;
1223 }
1224 }
1225 };
1226
1227 /**
1228 * Function that creates a PNG data url from an FSW symbol key with an optional style string
1229 * @function fsw.symbolPng
1230 * @param {string} fswSym - an FSW symbol key with optional style string
1231 * @returns {string} png image for symbol as data url
1232 * @example
1233 * fsw.symbolPng('S10000')
1234 *
1235 * return 'data:image/png;base64,iVBORw...'
1236 */
1237 const symbolPng = fswSym => {
1238 const canvas = symbolCanvas(fswSym);
1239 const png = canvas.toDataURL("image/png");
1240 canvas.remove();
1241 return png;
1242 };
1243
1244 const blank = null;
1245
1246 /**
1247 * Function that normalizes a symbol with a minimum coordinate for a center of 500,500
1248 * @function fsw.symbolNormalize
1249 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
1250 * @returns {string} normalized FSW symbol
1251 * @example
1252 * fsw.symbolNormalize('S10000-CP10G_green_Z2')
1253 *
1254 * return 'S10000493x485-CP10G_green_Z2'
1255 */
1256 const symbolNormalize = fswSym => {
1257 const parsed = parse.symbol(fswSym);
1258 if (parsed.symbol) {
1259 let size = symbolSize(parsed.symbol);
1260 if (size) {
1261 return `${parsed.symbol}${500 - parseInt((size[0] + 1) / 2)}x${500 - parseInt((size[1] + 1) / 2)}${parsed.style || ''}`;
1262 }
1263 } else {
1264 return blank;
1265 }
1266 };
1267
1268 /**
1269 * Function that creates an SVG image from an FSW sign with an optional style string
1270 * @function fsw.signSvgBody
1271 * @param {string} fswSign - an FSW sign with optional style string
1272 * @returns {string} body of SVG for sign
1273 * @example
1274 * fsw.signSvgBody('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1275 *
1276 * return `<text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text>
1277 * <g transform="translate(483,510)">
1278 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text>
1279 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text>
1280 * </g>
1281 * <g transform="translate(501,466)">
1282 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text>
1283 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text>
1284 * </g>
1285 * <g transform="translate(510,500)">
1286 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text>
1287 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text>
1288 * </g>
1289 * <g transform="translate(476,475)">
1290 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text>
1291 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text>
1292 * </g>`
1293 */
1294 const signSvgBody = fswSign => {
1295 let parsed = parse.sign(fswSign);
1296 const blank = '';
1297 if (parsed.spatials) {
1298 let styling = parse$2(parsed.style);
1299 if (styling.detailsym) {
1300 styling.detailsym.forEach(sym => {
1301 if (parsed.spatials[sym.index - 1]) {
1302 parsed.spatials[sym.index - 1].detail = sym.detail;
1303 }
1304 });
1305 }
1306 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1307 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1308 let x2 = parsed.max[0];
1309 let y2 = parsed.max[1];
1310 let background = '';
1311 if (styling.padding) {
1312 x1 -= styling.padding;
1313 y1 -= styling.padding;
1314 x2 += styling.padding;
1315 y2 += styling.padding;
1316 }
1317 if (styling.background) {
1318 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
1319 }
1320 let svg = ` <text font-size="0">${fswSign}</text>${background}`;
1321 const line = styling.detail && styling.detail[0];
1322 const fill = styling.detail && styling.detail[1];
1323 svg += '\n' + parsed.spatials.map(spatial => {
1324 let svg = symbolText(spatial.symbol);
1325 let symLine = line;
1326 if (spatial.detail) {
1327 symLine = spatial.detail[0];
1328 } else if (styling.colorize) {
1329 symLine = colorize(spatial.symbol);
1330 }
1331 if (symLine) {
1332 svg = svg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${symLine}"`);
1333 }
1334 let symFill = fill;
1335 if (spatial.detail && spatial.detail[1]) {
1336 symFill = spatial.detail[1];
1337 }
1338 if (symFill) {
1339 svg = svg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${symFill}"`);
1340 }
1341 return ` <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})">
1342${svg}
1343 </g>`;
1344 }).join('\n');
1345 return svg;
1346 }
1347 return blank;
1348 };
1349
1350 /**
1351 * Function that creates an SVG image from an FSW sign with an optional style string
1352 * @function fsw.signSvg
1353 * @param {string} fswSign - an FSW sign with optional style string
1354 * @returns {string} SVG for sign
1355 * @example
1356 * fsw.signSvg('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1357 *
1358 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="49" height="69" viewBox="476 466 49 69">
1359 * <text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text>
1360 * <g transform="translate(483,510)">
1361 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text>
1362 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text>
1363 * </g>
1364 * <g transform="translate(501,466)">
1365 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text>
1366 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text>
1367 * </g>
1368 * <g transform="translate(510,500)">
1369 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text>
1370 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text>
1371 * </g>
1372 * <g transform="translate(476,475)">
1373 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text>
1374 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text>
1375 * </g>
1376 * </svg>`
1377 */
1378 const signSvg = fswSign => {
1379 let parsed = parse.sign(fswSign);
1380 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
1381 if (parsed.spatials) {
1382 let styling = parse$2(parsed.style);
1383 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1384 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1385 let x2 = parsed.max[0];
1386 let y2 = parsed.max[1];
1387 let classes = '';
1388 if (styling.classes) {
1389 classes = ` class="${styling.classes}"`;
1390 }
1391 let id = '';
1392 if (styling.id) {
1393 id = ` id="${styling.id}"`;
1394 }
1395 if (styling.padding) {
1396 x1 -= styling.padding;
1397 y1 -= styling.padding;
1398 x2 += styling.padding;
1399 y2 += styling.padding;
1400 }
1401 let sizing = '';
1402 if (styling.zoom != 'x') {
1403 sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
1404 }
1405 let svg = `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
1406`;
1407 svg += signSvgBody(fswSign);
1408 svg += '\n</svg>';
1409 return svg;
1410 }
1411 return blank;
1412 };
1413
1414 const signCanvas = function (fswSign) {
1415 const parsed = parse.sign(fswSign);
1416 if (parsed.spatials) {
1417 const canvas = document.createElement('canvas');
1418 const context = canvas.getContext('2d');
1419 let styling = parse$2(parsed.style);
1420 if (styling.detailsym) {
1421 styling.detailsym.forEach(sym => {
1422 if (parsed.spatials[sym.index - 1]) {
1423 parsed.spatials[sym.index - 1].detail = sym.detail;
1424 }
1425 });
1426 }
1427 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
1428 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
1429 let x2 = parsed.max[0];
1430 let y2 = parsed.max[1];
1431 if (styling.padding) {
1432 x1 -= styling.padding;
1433 y1 -= styling.padding;
1434 x2 += styling.padding;
1435 y2 += styling.padding;
1436 }
1437 let sizing = 1;
1438 if (styling.zoom != 'x') {
1439 sizing = styling.zoom;
1440 }
1441 let w = (x2 - x1) * sizing;
1442 let h = (y2 - y1) * sizing;
1443 canvas.width = w ? w : 1;
1444 canvas.height = h ? h : 1;
1445 if (styling.background) {
1446 context.rect(0, 0, w, h);
1447 context.fillStyle = styling.background;
1448 context.fill();
1449 }
1450 const line = styling.detail && styling.detail[0] || "black";
1451 const fill = styling.detail && styling.detail[1] || "white";
1452 parsed.spatials.forEach(spatial => {
1453 let symLine = line;
1454 if (spatial.detail) {
1455 symLine = spatial.detail[0];
1456 } else if (styling.colorize) {
1457 symLine = colorize(spatial.symbol);
1458 }
1459 let symFill = fill;
1460 if (spatial.detail && spatial.detail[1]) {
1461 symFill = spatial.detail[1];
1462 }
1463 context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
1464 context.fillStyle = symFill;
1465 context.fillText(symbolFill(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
1466 context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
1467 context.fillStyle = symLine;
1468 context.fillText(symbolLine(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
1469 });
1470 return canvas;
1471 }
1472 };
1473
1474 /**
1475 * Function that creates a PNG data url from an FSW sign with an optional style string
1476 * @function fsw.signPng
1477 * @param {string} fswSign - an FSW sign with optional style string
1478 * @returns {string} png image for sign as data url
1479 * @example
1480 * fsw.signPng('M525x535S2e748483x510S10011501x466S20544510x500S10019476x475')
1481 *
1482 * return 'data:image/png;base64,iVBORw...'
1483 */
1484 const signPng = fswSign => {
1485 const canvas = signCanvas(fswSign);
1486 const png = canvas.toDataURL("image/png");
1487 canvas.remove();
1488 return png;
1489 };
1490
1491 /**
1492 * Function that normalizes an FSW sign for a center of 500,500
1493 * @function fsw.signNormalize
1494 * @param {string} fswSign - an FSW sign with optional style string
1495 * @returns {string} normalized FSW sign
1496 * @example
1497 * fsw.signNormalize('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1498 *
1499 * return 'M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
1500 */
1501 const signNormalize = fswSign => {
1502 const parsed = parse.sign(fswSign);
1503 if (parsed.spatials) {
1504 const symbolsizes = parsed.spatials.reduce((output, spatial) => {
1505 const size = symbolSize(spatial.symbol);
1506 output[spatial.symbol] = {
1507 width: size[0],
1508 height: size[1]
1509 };
1510 return output;
1511 }, {});
1512 const bbox = symbols => {
1513 const x1 = Math.min(...symbols.map(spatial => spatial.coord[0]));
1514 const y1 = Math.min(...symbols.map(spatial => spatial.coord[1]));
1515 const x2 = Math.max(...symbols.map(spatial => spatial.coord[0] + parseInt(symbolsizes[spatial.symbol].width)));
1516 const y2 = Math.max(...symbols.map(spatial => spatial.coord[1] + parseInt(symbolsizes[spatial.symbol].height)));
1517 return {
1518 x1: x1,
1519 y1: y1,
1520 x2: x2,
1521 y2: y2
1522 };
1523 };
1524 const hrange = ranges['hcenter'];
1525 const hsyms = parsed.spatials.filter(spatial => {
1526 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1527 return hrange[0] <= dec && hrange[1] >= dec;
1528 });
1529 const vrange = ranges['vcenter'];
1530 const vsyms = parsed.spatials.filter(spatial => {
1531 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1532 return vrange[0] <= dec && vrange[1] >= dec;
1533 });
1534 let abox = bbox(parsed.spatials);
1535 let max = [abox.x2, abox.y2];
1536 if (hsyms.length) {
1537 const hbox = bbox(hsyms);
1538 abox.x1 = hbox.x1;
1539 abox.x2 = hbox.x2;
1540 }
1541 if (vsyms.length) {
1542 const vbox = bbox(vsyms);
1543 abox.y1 = vbox.y1;
1544 abox.y2 = vbox.y2;
1545 }
1546 const offset = [parseInt((abox.x2 + abox.x1) / 2) - 500, parseInt((abox.y2 + abox.y1) / 2) - 500];
1547 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 || '');
1548 return fswout;
1549 }
1550 };
1551
1552 /**
1553 * Function that creates an SVG image for a column of FSW
1554 * @function fsw.columnSvg
1555 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
1556 * @param {ColumnOptions} options - an object of column options
1557 * @returns {string} column svg
1558 * @example
1559 * fsw.columnSvg([
1560 * {
1561 * "x": 56,
1562 * "y": 20,
1563 * "minX": 481,
1564 * "minY": 471,
1565 * "width": 37,
1566 * "height": 58,
1567 * "lane": 0,
1568 * "padding": 0,
1569 * "segment": "sign",
1570 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
1571 * "zoom": 1
1572 * },
1573 * {
1574 * "x": 57,
1575 * "y": 118,
1576 * "minX": 482,
1577 * "minY": 468,
1578 * "width": 36,
1579 * "height": 65,
1580 * "lane": 0,
1581 * "padding": 0,
1582 * "segment": "sign",
1583 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
1584 * "zoom": 1
1585 * },
1586 * {
1587 * "x": 39,
1588 * "y": 203,
1589 * "minX": 464,
1590 * "minY": 496,
1591 * "width": 72,
1592 * "height": 8,
1593 * "lane": 0,
1594 * "padding": 0,
1595 * "segment": "symbol",
1596 * "text": "S38800464x496",
1597 * "zoom": 1
1598 * }
1599 * ],
1600 * {
1601 * "height": 250,
1602 * "width": 150,
1603 * })
1604 *
1605 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
1606 * <g transform="translate(56,20) scale(1) translate(-481,-471) ">
1607 * <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
1608 * <g transform="translate(481,471)">
1609 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􁲡</text>
1610 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󱲡</text>
1611 * </g>
1612 * <g transform="translate(503,489)">
1613 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􈩧</text>
1614 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󸩧</text>
1615 * </g>
1616 * </g>
1617 * <g transform="translate(57,118) scale(1) translate(-482,-468) ">
1618 * <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
1619 * <g transform="translate(489,515)">
1620 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊫</text>
1621 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊫</text>
1622 * </g>
1623 * <g transform="translate(482,490)">
1624 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊢</text>
1625 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊢</text>
1626 * </g>
1627 * <g transform="translate(508,496)">
1628 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􆇡</text>
1629 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󶇡</text>
1630 * </g>
1631 * <g transform="translate(500,468)">
1632 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛕</text>
1633 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛕</text>
1634 * </g>
1635 * </g>
1636 * <g transform="translate(39,203) scale(1) translate(-464,-496) ">
1637 * <text font-size="0">S38800464x496-D_black,white_Z1</text>
1638 * <g transform="translate(464,496)">
1639 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􏌁</text>
1640 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󿌁</text>
1641 * </g>
1642 * </g>
1643 * </svg>`
1644 */
1645 const columnSvg = (fswColumn, options) => {
1646 //if (typeof fswColumn !== 'array') return blank;
1647 if (typeof options !== 'object') options = {};
1648 const values = Object.assign(columnDefaults, options);
1649 let x1 = 0;
1650 let y1 = 0;
1651 let x2 = values.width;
1652 let y2 = values.height;
1653 let background = '';
1654 if (values.background) {
1655 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${values.background};" />`;
1656 }
1657 let sizing = ` width="${values.width}" height="${values.height}"`;
1658 let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
1659 ${background}`;
1660 svg += fswColumn.map(item => {
1661 const dash = item.text.indexOf('-');
1662 if (dash > 0) {
1663 const itemStyle = item.text.substring(dash);
1664 const newStyle = {
1665 ...values.style,
1666 ...parse$2(itemStyle)
1667 };
1668 item.text = item.text.replace(itemStyle, compose(newStyle));
1669 } else {
1670 item.text += compose(values.style);
1671 }
1672 item.zoom = item.zoom * values.style.zoom;
1673 return '<g transform="translate(' + item.x + ',' + item.y + ') scale(' + item.zoom + ') translate(' + -item.minX + ',' + -item.minY + ') ">' + (item.segment == "sign" ? signSvgBody(item.text) : symbolSvgBody(item.text)) + '</g>';
1674 }).join('\n');
1675 svg += '\n</svg>';
1676 return svg;
1677 };
1678
1679 /**
1680 * Function that creates an array of SVG column images for an FSW text
1681 * @function fsw.columnsSvg
1682 * @param {string} fswText - a text of FSW signs and punctuation
1683 * @param {ColumnOptions} options - an object of column options
1684 * @returns {string[]} array of SVG columns
1685 * @example
1686 * fsw.columnsSvg('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
1687 * "height": 250,
1688 * "width": 150,
1689 * })
1690 *
1691 * return [`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
1692 * <g transform="translate(56,20) scale(1) translate(-481,-471) ">
1693 * <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
1694 * <g transform="translate(481,471)">
1695 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􁲡</text>
1696 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󱲡</text>
1697 * </g>
1698 * <g transform="translate(503,489)">
1699 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􈩧</text>
1700 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󸩧</text>
1701 * </g>
1702 * </g>
1703 * <g transform="translate(57,118) scale(1) translate(-482,-468) ">
1704 * <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
1705 * <g transform="translate(489,515)">
1706 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊫</text>
1707 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊫</text>
1708 * </g>
1709 * <g transform="translate(482,490)">
1710 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􃊢</text>
1711 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󳊢</text>
1712 * </g>
1713 * <g transform="translate(508,496)">
1714 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􆇡</text>
1715 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󶇡</text>
1716 * </g>
1717 * <g transform="translate(500,468)">
1718 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛕</text>
1719 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛕</text>
1720 * </g>
1721 * </g>
1722 * <g transform="translate(39,203) scale(1) translate(-464,-496) ">
1723 * <text font-size="0">S38800464x496-D_black,white_Z1</text>
1724 * <g transform="translate(464,496)">
1725 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􏌁</text>
1726 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󿌁</text>
1727 * </g>
1728 * </g>
1729 * </svg>`]
1730 */
1731 const columnsSvg = function (fswText, options) {
1732 if (typeof options !== 'object') options = {};
1733 let values = columns(fswText, options);
1734 let cols = values.columns.map((col, i) => {
1735 return columnSvg(col, {
1736 ...values.options,
1737 ...{
1738 width: values.widths[i]
1739 }
1740 });
1741 });
1742 return cols;
1743 };
1744
1745 const columnCanvas = function (fswColumn, options) {
1746 if (typeof options !== 'object') options = {};
1747 const values = Object.assign(columnDefaults, options);
1748 const canvas = document.createElement('canvas');
1749 canvas.width = values.width;
1750 canvas.height = values.height;
1751 const context = canvas.getContext('2d');
1752 if (values.background) {
1753 context.rect(0, 0, values.width, values.height);
1754 context.fillStyle = values.background;
1755 context.fill();
1756 }
1757 fswColumn.map(item => {
1758 const dash = item.text.indexOf('-');
1759 if (dash > 0) {
1760 const itemStyle = item.text.substring(dash);
1761 const newStyle = {
1762 ...values.style,
1763 ...parse$2(itemStyle)
1764 };
1765 item.text = item.text.replace(itemStyle, compose(newStyle));
1766 } else {
1767 item.text += compose(values.style);
1768 }
1769 item.zoom = item.zoom * values.style.zoom;
1770 let parsed = {};
1771 if (item.segment == "sign") {
1772 parsed = parse.sign(item.text);
1773 } else {
1774 let sym = parse.symbol(item.text);
1775 parsed.style = sym.style;
1776 parsed.spatials = [sym];
1777 }
1778 let styling = parse$2(parsed.style);
1779 if (styling.background) {
1780 context.fillStyle = styling.background;
1781 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);
1782 }
1783 if (styling.detailsym) {
1784 styling.detailsym.forEach(sym => {
1785 if (parsed.spatials[sym.index - 1]) {
1786 parsed.spatials[sym.index - 1].detail = sym.detail;
1787 }
1788 });
1789 }
1790 const line = styling.detail && styling.detail[0] || "black";
1791 const fill = styling.detail && styling.detail[1] || "white";
1792 parsed.spatials.forEach(spatial => {
1793 let symLine = line;
1794 if (spatial.detail) {
1795 symLine = spatial.detail[0];
1796 } else if (styling.colorize) {
1797 symLine = colorize(spatial.symbol);
1798 }
1799 let symFill = fill;
1800 if (spatial.detail && spatial.detail[1]) {
1801 symFill = spatial.detail[1];
1802 }
1803 context.font = 30 * item.zoom + "px 'SuttonSignWritingFill'";
1804 context.fillStyle = symFill;
1805 context.fillText(symbolFill(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
1806 context.font = 30 * item.zoom + "px 'SuttonSignWritingLine'";
1807 context.fillStyle = symLine;
1808 context.fillText(symbolLine(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
1809 });
1810 });
1811 return canvas;
1812 };
1813
1814 /**
1815 * Function that creates a PNG data url for a column of FSW
1816 * @function fsw.columnPng
1817 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
1818 * @param {ColumnOptions} options - an object of column options
1819 * @returns {string} column png data url
1820 * @example
1821 * fsw.columnPng([
1822 * {
1823 * "x": 56,
1824 * "y": 20,
1825 * "minX": 481,
1826 * "minY": 471,
1827 * "width": 37,
1828 * "height": 58,
1829 * "lane": 0,
1830 * "padding": 0,
1831 * "segment": "sign",
1832 * "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
1833 * "zoom": 1
1834 * },
1835 * {
1836 * "x": 57,
1837 * "y": 118,
1838 * "minX": 482,
1839 * "minY": 468,
1840 * "width": 36,
1841 * "height": 65,
1842 * "lane": 0,
1843 * "padding": 0,
1844 * "segment": "sign",
1845 * "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
1846 * "zoom": 1
1847 * },
1848 * {
1849 * "x": 39,
1850 * "y": 203,
1851 * "minX": 464,
1852 * "minY": 496,
1853 * "width": 72,
1854 * "height": 8,
1855 * "lane": 0,
1856 * "padding": 0,
1857 * "segment": "symbol",
1858 * "text": "S38800464x496",
1859 * "zoom": 1
1860 * }
1861 * ],
1862 * {
1863 * "height": 250,
1864 * "width": 150,
1865 * })
1866 *
1867 * return 'data:image/png;base64,iVBORw...'
1868 */
1869 const columnPng = (fswColumn, options) => {
1870 const canvas = columnCanvas(fswColumn, options);
1871 const png = canvas.toDataURL("image/png");
1872 canvas.remove();
1873 return png;
1874 };
1875
1876 /**
1877 * Function that creates an array of PNG data urls for an FSW text
1878 * @function fsw.columnsPng
1879 * @param {string} fswText - a text of FSW signs and punctuation
1880 * @param {ColumnOptions} options - an object of column options
1881 * @returns {string[]} array of PNG data urls
1882 * @example
1883 * fsw.columnsPng('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
1884 * "height": 250,
1885 * "width": 150,
1886 * })
1887 *
1888 * return ['data:image/png;base64,iVBORw...']
1889 */
1890 const columnsPng = function (fswText, options) {
1891 if (typeof options !== 'object') options = {};
1892 let values = columns(fswText, options);
1893 let cols = values.columns.map((col, i) => {
1894 return columnPng(col, {
1895 ...values.options,
1896 ...{
1897 width: values.widths[i]
1898 }
1899 });
1900 });
1901 return cols;
1902 };
1903
1904 exports.columnPng = columnPng;
1905 exports.columnSvg = columnSvg;
1906 exports.columnsPng = columnsPng;
1907 exports.columnsSvg = columnsSvg;
1908 exports.signNormalize = signNormalize;
1909 exports.signPng = signPng;
1910 exports.signSvg = signSvg;
1911 exports.signSvgBody = signSvgBody;
1912 exports.symbolFill = symbolFill;
1913 exports.symbolLine = symbolLine;
1914 exports.symbolNormalize = symbolNormalize;
1915 exports.symbolPng = symbolPng;
1916 exports.symbolSize = symbolSize;
1917 exports.symbolSvg = symbolSvg;
1918 exports.symbolSvgBody = symbolSvgBody;
1919 exports.symbolText = symbolText;
1920
1921 Object.defineProperty(exports, '__esModule', { value: true });
1922
1923}));
1924
1925/* support ongoing development on https://patreon.com/signwriting */