UNPKG

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