UNPKG

32.4 kBJavaScriptView Raw
1/**
2* Sutton SignWriting TrueType Font Module v1.1.0 (https://github.com/sutton-signwriting/font-ttf)
3* Author: Steve Slevinski (https://SteveSlevinski.me)
4* fsw.mjs is released under the MIT License.
5*/
6
7/**
8* Sutton SignWriting Core Module v1.1.0 (https://github.com/sutton-signwriting/core)
9* Author: Steve Slevinski (https://SteveSlevinski.me)
10* convert.mjs is released under the MIT License.
11*/
12/**
13 * Function to convert an FSW symbol key to a 16-bit ID
14 * @function convert.key2id
15 * @param {string} key - FSW symbol key
16 * @returns {number} 16-bit ID
17 * @example
18 * convert.key2id('S10000')
19 *
20 * return 1
21 */
22
23
24const key2id = key => 1 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16);
25/* support ongoing development on https://patreon.com/signwriting */
26
27let sizes = {};
28const zoom = 2;
29const bound = 76 * zoom;
30const canvaser = document.createElement("canvas");
31canvaser.width = bound;
32canvaser.height = bound;
33const context = canvaser.getContext("2d");
34/**
35 * Function that returns the size of a symbol using an id
36 * @function font.symbolSize
37 * @param {number} id - a 16-bit number of a symbol
38 * @example
39 * font.symbolSize(1)
40 *
41 * return [15,30]
42 */
43
44const symbolSize = function (id) {
45 if (id in sizes) {
46 return [...sizes[id]];
47 }
48
49 context.clearRect(0, 0, bound, bound);
50 context.font = 30 * zoom + "px 'SuttonSignWritingLine'";
51 context.fillText(String.fromCodePoint(id + 0xF0000), 0, 0);
52 const imgData = context.getImageData(0, 0, bound, bound).data;
53 let w, h, i, s;
54
55 wloop: for (w = bound - 1; w >= 0; w--) {
56 for (h = 0; h < bound; h += 1) {
57 for (s = 0; s < 4; s += 1) {
58 i = w * 4 + h * 4 * bound + s;
59
60 if (imgData[i]) {
61 break wloop;
62 }
63 }
64 }
65 }
66
67 var width = w;
68
69 hloop: for (h = bound - 1; h >= 0; h--) {
70 for (w = 0; w < width; w += 1) {
71 for (s = 0; s < 4; s += 1) {
72 i = w * 4 + h * 4 * bound + s;
73
74 if (imgData[i]) {
75 break hloop;
76 }
77 }
78 }
79 }
80
81 var height = h + 1;
82 width = Math.ceil(width / zoom);
83 height = Math.ceil(height / zoom); // Rounding error in chrome. Manual fixes.
84
85 if (14394 == id) {
86 width = 19;
87 }
88
89 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)) {
90 width = 20;
91 }
92
93 if (31921 == id) {
94 width = 22;
95 }
96
97 if (38460 == id) {
98 width = 23;
99 }
100
101 if ([20164, 20212].includes(id)) {
102 width = 25;
103 }
104
105 if (31894 == id) {
106 width = 28;
107 }
108
109 if (46698 == id) {
110 width = 29;
111 }
112
113 if (29606 == id) {
114 width = 30;
115 }
116
117 if (44855 == id) {
118 width = 40;
119 }
120
121 if (32667 == id) {
122 width = 50;
123 }
124
125 if ([11088, 11474, 11490, 11506].includes(id)) {
126 height = 20;
127 }
128
129 if (6285 == id) {
130 height = 21;
131 }
132
133 if (40804 == id) {
134 height = 31;
135 }
136
137 if (41475 == id) {
138 height = 36;
139 } // Error in chrome. Manual fix.
140 // if (width==0 && height==0) {
141
142
143 if (width == 0 && height == 0) {
144 const sizefix = {
145 9: [15, 30],
146 10: [21, 30],
147 11: [30, 15],
148 12: [30, 21],
149 13: [15, 30],
150 14: [21, 30]
151 };
152
153 if (id in sizefix) {
154 width = sizefix[id][0];
155 height = sizefix[id][1];
156 }
157 }
158
159 if (width == 0 && height == 0) {
160 return undefined;
161 }
162
163 sizes[id] = [width, height];
164 return [width, height];
165};
166
167/**
168 * Function that returns the size of a symbol using an FSW symbol key
169 * @function fsw.symbolSize
170 * @param {string} fsw - an FSW symbol key
171 * @example
172 * fsw.symbolSize("S10000")
173 *
174 * return [15,30]
175 */
176
177const symbolSize$1 = function (fsw) {
178 return symbolSize(key2id(fsw));
179};
180
181/**
182 * Function that returns a plane 15 character for a symbol line using an id
183 * @function font.symbolLine
184 * @param {number} id - a 16-bit number of a symbol
185 * @example
186 * font.symbolLine(1)
187 *
188 * return '󰀁'
189 */
190const symbolLine = function (id) {
191 return String.fromCodePoint(id + 0xF0000);
192};
193/**
194 * Function that returns a plane 16 character for a symbol fill using an id
195 * @function font.symbolFill
196 * @param {number} id - a 16-bit number of a symbol
197 * @example
198 * font.symbolFill(1)
199 *
200 * return '􀀁'
201 */
202
203
204const symbolFill = function (id) {
205 return String.fromCodePoint(id + 0x100000);
206};
207/**
208 * Function that creates two text elements for a symbol using an id
209 * @function font.symbolText
210 * @param {number} id - a 16-bit number of a symbol
211 * @example
212 * font.symbolText(1)
213 *
214 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
215 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
216 */
217
218
219const symbolText = function (id) {
220 return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">${symbolFill(id)}</text>
221 <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">${symbolLine(id)}</text>`;
222};
223
224/**
225 * Function that returns a plane 15 character for a symbol line using an FSW symbol key
226 * @function fsw.symbolLine
227 * @param {string} fsw - an FSW symbol key
228 * @example
229 * fsw.symbolLine('S10000')
230 *
231 * return '󰀁'
232 */
233
234const symbolLine$1 = function (fsw) {
235 return symbolLine(key2id(fsw));
236};
237/**
238 * Function that returns a plane 16 character for a symbol fill using an FSW symbol key
239 * @function fsw.symbolFill
240 * @param {string} fsw - an FSW symbol key
241 * @example
242 * font.symbolFill('S10000')
243 *
244 * return '􀀁'
245 */
246
247
248const symbolFill$1 = function (fsw) {
249 return symbolFill(key2id(fsw));
250};
251/**
252 * Function that creates two text elements for a symbol using an FSW symbol key
253 * @function fsw.symbolText
254 * @param {string} fsw - an FSW symbol key
255 * @example
256 * fsw.symbolText('S10000')
257 *
258 * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
259 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>`
260 */
261
262
263const symbolText$1 = function (fsw) {
264 return symbolText(key2id(fsw));
265};
266
267/**
268* Sutton SignWriting Core Module v1.1.0 (https://github.com/sutton-signwriting/core)
269* Author: Steve Slevinski (https://SteveSlevinski.me)
270* style.mjs is released under the MIT License.
271*/
272
273/**
274 * Object of regular expressions for style strings
275 *
276 * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full }
277 * @alias style.re
278 * @type {object}
279 */
280let re = {
281 'colorize': 'C',
282 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
283 'colorname': '[a-zA-Z]+',
284 'padding': 'P[0-9]{2}',
285 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
286 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?',
287 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
288 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
289};
290re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
291re.color = `_${re.colorbase}_`;
292re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
293re.background = `G${re.color}`;
294re.detail = `D${re.colors}`;
295re.detailsym = `D[0-9]{2}${re.colors}`;
296re.classes = `${re.classbase}(?: ${re.classbase})*`;
297re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*)((?:${re.zoomsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
298
299const prefixColor = color => {
300 const regex = new RegExp(`^${re.colorhex}$`);
301 return (regex.test(color) ? '#' : '') + color;
302};
303/**
304 * Function to parse style string to object
305 * @function style.parse
306 * @param {string} styleString - a style string
307 * @returns {object} elements of style string
308 * @example
309 * style.parse('-CP10G_blue_D_red,Cyan_')
310 *
311 * return {
312 * 'colorize': true,
313 * 'padding': 10,
314 * 'background': 'blue',
315 * 'detail': ['red', 'Cyan']
316 * }
317 */
318
319
320const parse = styleString => {
321 const regex = `^${re.full}`;
322 const m = typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : [];
323 return {
324 'colorize': !m[1] ? undefined : !!m[1],
325 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
326 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)),
327 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor),
328 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
329 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => {
330 const parts = val.split('_');
331 const detail = parts[1].split(',').map(prefixColor);
332 return {
333 'index': parseInt(parts[0].slice(1)),
334 'detail': detail
335 };
336 }),
337 'zoomsym': !m[7] ? undefined : m[7].match(new RegExp(re.zoomsym, 'g')).map(val => {
338 const parts = val.split(',');
339 return {
340 'index': parseInt(parts[0].slice(1)),
341 'zoom': parseFloat(parts[1]),
342 'offset': !parts[2] ? undefined : parts[2].split('x').map(val => parseInt(val) - 500)
343 };
344 }),
345 'classes': !m[8] ? undefined : m[8],
346 'id': !m[9] ? undefined : m[9]
347 };
348};
349/* support ongoing development on https://patreon.com/signwriting */
350
351/**
352* Sutton SignWriting Core Module v1.1.0 (https://github.com/sutton-signwriting/core)
353* Author: Steve Slevinski (https://SteveSlevinski.me)
354* fsw.mjs is released under the MIT License.
355*/
356
357/**
358 * Object of regular expressions for FSW strings
359 *
360 * { symbol, coord, sort, box, prefix, spatial, signbox, sign, term }
361 * @alias fsw.re
362 * @type {object}
363 */
364let re$1 = {
365 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]',
366 'coord': '[0-9]{3}x[0-9]{3}',
367 'sort': 'A',
368 'box': '[BLMR]'
369};
370re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
371re$1.spatial = `${re$1.symbol}${re$1.coord}`;
372re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
373re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
374re$1.term = `${re$1.prefix}${re$1.signbox}`;
375/**
376 * Object of regular expressions for style strings
377 *
378 * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full }
379 * @alias style.re
380 * @type {object}
381 */
382
383let re$1$1 = {
384 'colorize': 'C',
385 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
386 'colorname': '[a-zA-Z]+',
387 'padding': 'P[0-9]{2}',
388 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
389 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?',
390 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
391 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
392};
393re$1$1.colorbase = `(?:${re$1$1.colorhex}|${re$1$1.colorname})`;
394re$1$1.color = `_${re$1$1.colorbase}_`;
395re$1$1.colors = `_${re$1$1.colorbase}(?:,${re$1$1.colorbase})?_`;
396re$1$1.background = `G${re$1$1.color}`;
397re$1$1.detail = `D${re$1$1.colors}`;
398re$1$1.detailsym = `D[0-9]{2}${re$1$1.colors}`;
399re$1$1.classes = `${re$1$1.classbase}(?: ${re$1$1.classbase})*`;
400re$1$1.full = `-(${re$1$1.colorize})?(${re$1$1.padding})?(${re$1$1.background})?(${re$1$1.detail})?(${re$1$1.zoom})?(?:-((?:${re$1$1.detailsym})*)((?:${re$1$1.zoomsym})*))?(?:-(${re$1$1.classes})?!(?:(${re$1$1.id})!)?)?`;
401/** 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.
402 * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2)
403 * @module convert
404 */
405
406/**
407 * Function to convert an FSW coordinate string to an array of x,y integers
408 * @function convert.fsw2coord
409 * @param {string} fswCoord - An FSW coordinate string
410 * @returns {number[]} Array of x,y integers
411 * @example
412 * convert.fsw2coord('500x500')
413 *
414 * return [500, 500]
415 */
416
417const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num));
418
419const parse$1 = {
420 /**
421 * Function to parse an fsw symbol with optional coordinate and style string
422 * @function fsw.parse.symbol
423 * @param {string} fswSym - an fsw symbol
424 * @returns {object} elements of fsw symbol
425 * @example
426 * fsw.parse.symbol('S10000500x500-C')
427 *
428 * return {
429 * 'symbol': 'S10000',
430 * 'coord': [500, 500],
431 * 'style': '-C'
432 * }
433 */
434 symbol: fswSym => {
435 const regex = `^(${re$1.symbol})(${re$1.coord})?(${re$1$1.full})?`;
436 const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined;
437 return {
438 'symbol': symbol ? symbol[1] : undefined,
439 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined,
440 'style': symbol ? symbol[3] : undefined
441 };
442 },
443
444 /**
445 * Function to parse an fsw sign with style string
446 * @function fsw.parse.sign
447 * @param {string} fswSign - an fsw sign
448 * @returns {object} elements of fsw sign
449 * @example
450 * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C')
451 *
452 * return {
453 * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'],
454 * box: 'M',
455 * max: [525, 535],
456 * spatials: [
457 * {
458 * symbol: 'S2e748',
459 * coord: [483, 510]
460 * },
461 * {
462 * symbol: 'S10011',
463 * coord: [501, 466]
464 * },
465 * {
466 * symbol: 'S2e704',
467 * coord: [510, 500]
468 * },
469 * {
470 * symbol: 'S10019',
471 * coord: [476, 475]
472 * }
473 * ],
474 * style: '-C'
475 * }
476 */
477 sign: fswSign => {
478 const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re$1$1.full})?`;
479 const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined;
480
481 if (sign) {
482 return {
483 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined,
484 'box': sign[2][0],
485 'max': fsw2coord(sign[2].slice(1, 8)),
486 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => {
487 return {
488 symbol: m.slice(0, 6),
489 coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))]
490 };
491 }),
492 'style': sign[3]
493 };
494 } else {
495 return {};
496 }
497 }
498};
499/**
500 * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
501 * @alias fsw.category
502 * @type {array}
503 */
504
505const category = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
506/**
507 * Object of symbol ranges with starting and ending numbers.
508 *
509 * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
510 * @alias fsw.ranges
511 * @type {object}
512 */
513
514const ranges = {
515 'all': [0x100, 0x38b],
516 'writing': [0x100, 0x37e],
517 'hand': [0x100, 0x204],
518 'movement': [0x205, 0x2f6],
519 'dynamic': [0x2f7, 0x2fe],
520 'head': [0x2ff, 0x36c],
521 'hcenter': [0x2ff, 0x36c],
522 'vcenter': [0x2ff, 0x375],
523 'trunk': [0x36d, 0x375],
524 'limb': [0x376, 0x37e],
525 'location': [0x37f, 0x386],
526 'punctuation': [0x387, 0x38b]
527};
528/**
529 * Array of colors associated with the seven symbol categories.
530 * @alias fsw.colors
531 * @type {array}
532 */
533
534
535const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
536/**
537 * Function that returns the standardized color for a symbol.
538 * @function fsw.colorize
539 * @param {string} key - an FSW symbol key
540 * @returns {string} name of standardized color for symbol
541 * @example
542 * fsw.colorize('S10000')
543 *
544 * return '#0000CC'
545 */
546
547const colorize = key => {
548 const parsed = parse$1.symbol(key);
549 let color = '#000000';
550
551 if (parsed.symbol) {
552 const dec = parseInt(parsed.symbol.slice(1, 4), 16);
553 const index = category.findIndex(val => val > dec);
554 color = colors[index < 0 ? 6 : index - 1];
555 }
556
557 return color;
558};
559/* support ongoing development on https://patreon.com/signwriting */
560
561/**
562 * Function that creates an SVG image from an FSW symbol key with an optional style string
563 * @function fsw.symbolSvg
564 * @param {string} fswSym - an FSW symbol key with optional style string
565 * @example
566 * fsw.symbolSvg('S10000')
567 *
568 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="15" height="30" viewBox="500 500 15 30">
569 * <text font-size="0">S10000</text>
570 * <g transform="translate(500,500)">
571 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text>
572 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>
573 * </g>
574 * </svg>`
575 */
576
577const symbolSvg = fswSym => {
578 const parsed = parse$1.symbol(fswSym);
579 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
580
581 if (parsed.symbol) {
582 let size = symbolSize$1(parsed.symbol);
583
584 if (size) {
585 let styling = parse(parsed.style);
586 let line;
587 let symSvg = symbolText$1(parsed.symbol);
588 symSvg = ` <g transform="translate(500,500)">
589${symSvg}
590 </g>`;
591
592 if (styling.colorize) {
593 line = colorize(parsed.symbol);
594 } else if (styling.detail) {
595 line = styling.detail[0];
596 }
597
598 if (line) {
599 symSvg = symSvg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${line}"`);
600 }
601
602 let fill = styling.detail && styling.detail[1];
603
604 if (fill) {
605 symSvg = symSvg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${fill}"`);
606 }
607
608 let x1 = 500;
609 let y1 = 500;
610 let background = '';
611
612 if (styling.padding) {
613 x1 -= styling.padding;
614 y1 -= styling.padding;
615 size[0] += styling.padding * 2;
616 size[1] += styling.padding * 2;
617 }
618
619 if (styling.background) {
620 background = `\n <rect x="${x1}" y="${y1}" width="${size[0]}" height="${size[1]}" style="fill:${styling.background};" />`;
621 }
622
623 let sizing = '';
624
625 if (styling.zoom != 'x') {
626 sizing = ` width="${size[0] * (styling.zoom ? styling.zoom : 1)}" height="${size[1] * (styling.zoom ? styling.zoom : 1)}"`;
627 }
628
629 let classes = '';
630
631 if (styling.classes) {
632 classes = ` class="${styling.classes}"`;
633 }
634
635 let id = '';
636
637 if (styling.id) {
638 id = ` id="${styling.id}"`;
639 }
640
641 return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${size[0]} ${size[1]}">
642 <text font-size="0">${fswSym}</text>${background}
643${symSvg}
644</svg>`;
645 }
646 }
647
648 return blank;
649};
650
651const symbolCanvas = function (fswSym) {
652 const parsed = parse$1.symbol(fswSym);
653
654 if (parsed.symbol) {
655 let size = symbolSize$1(parsed.symbol);
656
657 if (size) {
658 const canvas = document.createElement('canvas');
659 const context = canvas.getContext('2d');
660 let styling = parse(parsed.style);
661 let line = 'black';
662
663 if (styling.colorize) {
664 line = colorize(parsed.symbol);
665 } else if (styling.detail) {
666 line = styling.detail[0];
667 }
668
669 let fill = styling.detail && styling.detail[1] || 'white';
670 let x1 = 500;
671 let x2 = x1 + size[0];
672 let y1 = 500;
673 let y2 = y1 + size[1];
674
675 if (styling.padding) {
676 x1 -= styling.padding;
677 y1 -= styling.padding;
678 x2 += styling.padding;
679 y2 += styling.padding;
680 }
681
682 let sizing = 1;
683
684 if (styling.zoom != 'x') {
685 sizing = styling.zoom;
686 }
687
688 let w = (x2 - x1) * sizing;
689 let h = (y2 - y1) * sizing;
690 canvas.width = w ? w : 1;
691 canvas.height = h ? h : 1;
692
693 if (styling.background) {
694 context.rect(0, 0, w, h);
695 context.fillStyle = styling.background;
696 context.fill();
697 }
698
699 context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
700 context.fillStyle = fill;
701 context.fillText(symbolFill$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
702 context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
703 context.fillStyle = line;
704 context.fillText(symbolLine$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
705 return canvas;
706 }
707 }
708};
709/**
710 * Function that creates a binary PNG image from an FSW symbol key with an optional stle string
711 * @function fsw.symbolPng
712 * @param {string} fswSym - an FSW symbol key with optional style string
713 * @example
714 * fsw.symbolPng('S10000')
715 *
716 * return 'data:image/png;base64,iVBORw...'
717 */
718
719
720const symbolPng = fswSym => {
721 const canvas = symbolCanvas(fswSym);
722 const png = canvas.toDataURL("image/png");
723 canvas.remove();
724 return png;
725};
726
727const blank = null;
728/**
729 * Function that normalizes a symbol with a minimum coordinate for a center of 500,500
730 * @function fsw.symbolNormalize
731 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
732 * @example
733 * fsw.symbolNormalize('S10000-CP10G_green_Z2')
734 *
735 * return 'S10000493x485-CP10G_green_Z2'
736 */
737
738const symbolNormalize = fswSym => {
739 const parsed = parse$1.symbol(fswSym);
740
741 if (parsed.symbol) {
742 let size = symbolSize$1(parsed.symbol);
743
744 if (size) {
745 return `${parsed.symbol}${500 - parseInt(size[0] / 2)}x${500 - parseInt(size[1] / 2)}${parsed.style || ''}`;
746 }
747 } else {
748 return blank;
749 }
750};
751
752/**
753 * Function that creates an SVG image from an FSW sign with an optional style string
754 * @function fsw.signSvg
755 * @param {string} fswSign - an FSW sign with optional style string
756 * @example
757 * fsw.signSvg('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
758 *
759 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="49" height="69" viewBox="476 466 49 69">
760 * <text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text>
761 * <g transform="translate(483,510)">
762 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text>
763 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text>
764 * </g>
765 * <g transform="translate(501,466)">
766 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text>
767 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text>
768 * </g>
769 * <g transform="translate(510,500)">
770 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text>
771 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text>
772 * </g>
773 * <g transform="translate(476,475)">
774 * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text>
775 * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text>
776 * </g>
777 * </svg>`
778 */
779
780const signSvg = fswSign => {
781 let parsed = parse$1.sign(fswSign);
782 const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
783
784 if (parsed.spatials) {
785 let styling = parse(parsed.style);
786
787 if (styling.detailsym) {
788 styling.detailsym.forEach(sym => {
789 if (parsed.spatials[sym.index - 1]) {
790 parsed.spatials[sym.index - 1].detail = sym.detail;
791 }
792 });
793 }
794
795 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
796 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
797 let x2 = parsed.max[0];
798 let y2 = parsed.max[1];
799
800 if (styling.zoomsym) {
801 styling.zoomsym.forEach(sym => {
802 if (parsed.spatials[sym.index - 1]) {
803 parsed.spatials[sym.index - 1].zoom = sym.zoom;
804
805 if (sym.offset) {
806 parsed.spatials[sym.index - 1].coord[0] += sym.offset[0];
807 parsed.spatials[sym.index - 1].coord[1] += sym.offset[1];
808 }
809
810 let size = symbolSize$1(parsed.spatials[sym.index - 1].symbol);
811 x2 = Math.max(x2, parsed.spatials[sym.index - 1].coord[0] + size[0] * sym.zoom);
812 y2 = Math.max(y2, parsed.spatials[sym.index - 1].coord[1] + size[1] * sym.zoom);
813 }
814 });
815 }
816
817 let classes = '';
818
819 if (styling.classes) {
820 classes = ` class="${styling.classes}"`;
821 }
822
823 let id = '';
824
825 if (styling.id) {
826 id = ` id="${styling.id}"`;
827 }
828
829 let background = '';
830
831 if (styling.padding) {
832 x1 -= styling.padding;
833 y1 -= styling.padding;
834 x2 += styling.padding;
835 y2 += styling.padding;
836 }
837
838 if (styling.background) {
839 background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`;
840 }
841
842 let sizing = '';
843
844 if (styling.zoom != 'x') {
845 sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
846 }
847
848 let svg = `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
849 <text font-size="0">${fswSign}</text>${background}`;
850 const line = styling.detail && styling.detail[0];
851 const fill = styling.detail && styling.detail[1];
852 svg += '\n' + parsed.spatials.map(spatial => {
853 let svg = symbolText$1(spatial.symbol);
854 let symLine = line;
855
856 if (spatial.detail) {
857 symLine = spatial.detail[0];
858 } else if (styling.colorize) {
859 symLine = colorize(spatial.symbol);
860 }
861
862 if (symLine) {
863 svg = svg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${symLine}"`);
864 }
865
866 let symFill = fill;
867
868 if (spatial.detail && spatial.detail[1]) {
869 symFill = spatial.detail[1];
870 }
871
872 if (symFill) {
873 svg = svg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${symFill}"`);
874 }
875
876 if (spatial.zoom) {
877 svg = `<g transform="scale(${spatial.zoom})">${svg}</g>`;
878 }
879
880 return ` <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})">
881${svg}
882 </g>`;
883 }).join('\n');
884 svg += '\n</svg>';
885 return svg;
886 }
887
888 return blank;
889};
890
891const signCanvas = function (fswSign) {
892 const parsed = parse$1.sign(fswSign);
893
894 if (parsed.spatials) {
895 const canvas = document.createElement('canvas');
896 const context = canvas.getContext('2d');
897 let styling = parse(parsed.style);
898
899 if (styling.detailsym) {
900 styling.detailsym.forEach(sym => {
901 if (parsed.spatials[sym.index - 1]) {
902 parsed.spatials[sym.index - 1].detail = sym.detail;
903 }
904 });
905 }
906
907 let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
908 let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
909 let x2 = parsed.max[0];
910 let y2 = parsed.max[1];
911
912 if (styling.zoomsym) {
913 styling.zoomsym.forEach(sym => {
914 if (parsed.spatials[sym.index - 1]) {
915 parsed.spatials[sym.index - 1].zoom = sym.zoom;
916
917 if (sym.offset) {
918 parsed.spatials[sym.index - 1].coord[0] += sym.offset[0];
919 parsed.spatials[sym.index - 1].coord[1] += sym.offset[1];
920 }
921
922 let size = symbolSize$1(parsed.spatials[sym.index - 1].symbol);
923 x2 = Math.max(x2, parsed.spatials[sym.index - 1].coord[0] + size[0] * sym.zoom);
924 y2 = Math.max(y2, parsed.spatials[sym.index - 1].coord[1] + size[1] * sym.zoom);
925 }
926 });
927 }
928
929 if (styling.padding) {
930 x1 -= styling.padding;
931 y1 -= styling.padding;
932 x2 += styling.padding;
933 y2 += styling.padding;
934 }
935
936 let sizing = 1;
937
938 if (styling.zoom != 'x') {
939 sizing = styling.zoom;
940 }
941
942 let w = (x2 - x1) * sizing;
943 let h = (y2 - y1) * sizing;
944 canvas.width = w ? w : 1;
945 canvas.height = h ? h : 1;
946
947 if (styling.background) {
948 context.rect(0, 0, w, h);
949 context.fillStyle = styling.background;
950 context.fill();
951 }
952
953 const line = styling.detail && styling.detail[0] || "black";
954 const fill = styling.detail && styling.detail[1] || "white";
955 parsed.spatials.forEach(spatial => {
956 let symLine = line;
957
958 if (spatial.detail) {
959 symLine = spatial.detail[0];
960 } else if (styling.colorize) {
961 symLine = colorize(spatial.symbol);
962 }
963
964 let symFill = fill;
965
966 if (spatial.detail && spatial.detail[1]) {
967 symFill = spatial.detail[1];
968 }
969
970 let symZoom = spatial.zoom || 1;
971 context.font = 30 * sizing * symZoom + "px 'SuttonSignWritingFill'";
972 context.fillStyle = symFill;
973 context.fillText(symbolFill$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
974 context.font = 30 * sizing * symZoom + "px 'SuttonSignWritingLine'";
975 context.fillStyle = symLine;
976 context.fillText(symbolLine$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
977 });
978 return canvas;
979 }
980};
981/**
982 * Function that creates a binary PNG image from an FSW sign with an optional style string
983 * @function fsw.signPng
984 * @param {string} fswSign - an FSW sign with optional style string
985 * @example
986 * fsw.signPng('M525x535S2e748483x510S10011501x466S20544510x500S10019476x475')
987 *
988 * return 'data:image/png;base64,iVBORw...'
989 */
990
991
992const signPng = fswSign => {
993 const canvas = signCanvas(fswSign);
994 const png = canvas.toDataURL("image/png");
995 canvas.remove();
996 return png;
997};
998
999/**
1000 * Function that normalizes an FSW sign for a center of 500,500
1001 * @function fsw.signNormalize
1002 * @param {string} fswSign - an FSW sign with optional style string
1003 * @example
1004 * fsw.signNormalize('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
1005 *
1006 * return 'M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
1007 */
1008
1009const signNormalize = fswSign => {
1010 const parsed = parse$1.sign(fswSign);
1011
1012 if (parsed.spatials) {
1013 const symbolsizes = parsed.spatials.reduce((output, spatial) => {
1014 const size = symbolSize$1(spatial.symbol);
1015 output[spatial.symbol] = {
1016 width: size[0],
1017 height: size[1]
1018 };
1019 return output;
1020 }, {});
1021
1022 const bbox = symbols => {
1023 const x1 = Math.min(...symbols.map(spatial => spatial.coord[0]));
1024 const y1 = Math.min(...symbols.map(spatial => spatial.coord[1]));
1025 const x2 = Math.max(...symbols.map(spatial => spatial.coord[0] + parseInt(symbolsizes[spatial.symbol].width)));
1026 const y2 = Math.max(...symbols.map(spatial => spatial.coord[1] + parseInt(symbolsizes[spatial.symbol].height)));
1027 return {
1028 x1: x1,
1029 y1: y1,
1030 x2: x2,
1031 y2: y2
1032 };
1033 };
1034
1035 const hrange = ranges['hcenter'];
1036 const hsyms = parsed.spatials.filter(spatial => {
1037 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1038 return hrange[0] <= dec && hrange[1] >= dec;
1039 });
1040 const vrange = ranges['vcenter'];
1041 const vsyms = parsed.spatials.filter(spatial => {
1042 const dec = parseInt(spatial.symbol.slice(1, 4), 16);
1043 return vrange[0] <= dec && vrange[1] >= dec;
1044 });
1045 let abox = bbox(parsed.spatials);
1046 let max = [abox.x2, abox.y2];
1047
1048 if (hsyms.length) {
1049 const hbox = bbox(hsyms);
1050 abox.x1 = hbox.x1;
1051 abox.x2 = hbox.x2;
1052 }
1053
1054 if (vsyms.length) {
1055 const vbox = bbox(vsyms);
1056 abox.y1 = vbox.y1;
1057 abox.y2 = vbox.y2;
1058 }
1059
1060 const offset = [parseInt((abox.x2 + abox.x1) / 2) - 500, parseInt((abox.y2 + abox.y1) / 2) - 500];
1061 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 || '');
1062 return fswout;
1063 }
1064};
1065
1066export { signNormalize, signPng, signSvg, symbolFill$1 as symbolFill, symbolLine$1 as symbolLine, symbolNormalize, symbolPng, symbolSize$1 as symbolSize, symbolSvg, symbolText$1 as symbolText };
1067
1068/* support ongoing development on https://patreon.com/signwriting */