UNPKG

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