UNPKG

@sutton-signwriting/font-ttf

Version:

a javascript package for the web components and browser that generates SVG and PNG images for individual symbols and complete signs

1,578 lines (1,372 loc) 68.6 kB
/** * Sutton SignWriting TrueType Font Module v1.2.0 (https://github.com/sutton-signwriting/font-ttf) * Author: Steve Slevinski (https://SteveSlevinski.me) * index.js is released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory((global.ssw = global.ssw || {}, global.ssw.ttf = {}))); }(this, (function (exports) { 'use strict'; /** * Function that appends font-face CSS for the Sutton SignWriting fonts for system installed fonts, relative directory fonts, or content delivery network * @function font.cssAppend * @param {string} dir - an optional relative directory for font location * @example * font.cssAppend('./font/') */ const cssAppend = function (dir = '') { const id = "SgnwFontCss"; if (!document.getElementById(id)) { const style = document.createElement('style'); style.setAttribute("id", "SgnwFontCss"); style.appendChild(document.createTextNode(` @font-face { font-family: "SuttonSignWritingLine"; src: local('SuttonSignWritingLine'), ${dir ? `url('${dir}SuttonSignWritingLine.ttf') format('truetype'),` : ""} url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingLine.ttf') format('truetype'); } @font-face { font-family: "SuttonSignWritingFill"; src: local('SuttonSignWritingFill'), ${dir ? `url('${dir}SuttonSignWritingFill.ttf') format('truetype'),` : ""} url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingFill.ttf') format('truetype'); } @font-face { font-family: "SuttonSignWritingOneD"; src: local('SuttonSignWritingOneD'), ${dir ? `url('${dir}SuttonSignWritingOneD.ttf') format('truetype'),` : ""} url('https://cdn.jsdelivr.net/npm/@sutton-signwriting/font-ttf@1.0.0/font/SuttonSignWritingOneD.ttf') format('truetype'); } `)); document.head.appendChild(style); } }; let sizes = {}; const zoom = 2; const bound = 76 * zoom; const canvaser = document.createElement("canvas"); canvaser.width = bound; canvaser.height = bound; const context = canvaser.getContext("2d"); /** * Function that returns the size of a symbol using an id * @function font.symbolSize * @param {number} id - a 16-bit number of a symbol * @example * font.symbolSize(1) * * return [15,30] */ const symbolSize = function (id) { if (id in sizes) { return [...sizes[id]]; } context.clearRect(0, 0, bound, bound); context.font = 30 * zoom + "px 'SuttonSignWritingLine'"; context.fillText(String.fromCodePoint(id + 0xF0000), 0, 0); const imgData = context.getImageData(0, 0, bound, bound).data; let w, h, i, s; wloop: for (w = bound - 1; w >= 0; w--) { for (h = 0; h < bound; h += 1) { for (s = 0; s < 4; s += 1) { i = w * 4 + h * 4 * bound + s; if (imgData[i]) { break wloop; } } } } var width = w; hloop: for (h = bound - 1; h >= 0; h--) { for (w = 0; w < width; w += 1) { for (s = 0; s < 4; s += 1) { i = w * 4 + h * 4 * bound + s; if (imgData[i]) { break hloop; } } } } var height = h + 1; width = Math.ceil(width / zoom); height = Math.ceil(height / zoom); // Rounding error in chrome. Manual fixes. if (14394 == id) { width = 19; } 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)) { width = 20; } if (31921 == id) { width = 22; } if (38460 == id) { width = 23; } if ([20164, 20212].includes(id)) { width = 25; } if (31894 == id) { width = 28; } if (46698 == id) { width = 29; } if (29606 == id) { width = 30; } if (44855 == id) { width = 40; } if (32667 == id) { width = 50; } if ([11088, 11474, 11490, 11506].includes(id)) { height = 20; } if (6285 == id) { height = 21; } if (40804 == id) { height = 31; } if (41475 == id) { height = 36; } // Error in chrome. Manual fix. // if (width==0 && height==0) { if (width == 0 && height == 0) { const sizefix = { 9: [15, 30], 10: [21, 30], 11: [30, 15], 12: [30, 21], 13: [15, 30], 14: [21, 30] }; if (id in sizefix) { width = sizefix[id][0]; height = sizefix[id][1]; } } if (width == 0 && height == 0) { return undefined; } sizes[id] = [width, height]; return [width, height]; }; /** * Function that returns a plane 15 character for a symbol line using an id * @function font.symbolLine * @param {number} id - a 16-bit number of a symbol * @example * font.symbolLine(1) * * return '󰀁' */ const symbolLine = function (id) { return String.fromCodePoint(id + 0xF0000); }; /** * Function that returns a plane 16 character for a symbol fill using an id * @function font.symbolFill * @param {number} id - a 16-bit number of a symbol * @example * font.symbolFill(1) * * return '􀀁' */ const symbolFill = function (id) { return String.fromCodePoint(id + 0x100000); }; /** * Function that creates two text elements for a symbol using an id * @function font.symbolText * @param {number} id - a 16-bit number of a symbol * @example * font.symbolText(1) * * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>` */ const symbolText = function (id) { return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">${symbolFill(id)}</text> <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">${symbolLine(id)}</text>`; }; /** * Function that executes a callback function once the Sutton SignWriiting Line and Fill fonts are ready to use * @function font.cssLoaded * @param {function} callback - a callback function to execute when fonts are ready * @example * const callback = () => { * console.log("Sutton SignWriting Line and Fill fonts are ready to use") * } * * font.cssLoaded( callback ) */ const cssLoaded = function (callback) { let lineReady = false; let fillReady = false; cssLoadedLine(() => { lineReady = true; }); cssLoadedFill(() => { fillReady = true; }); const cssCheck = setInterval(function () { if (lineReady && fillReady) { clearInterval(cssCheck); callback(); } }, 100); }; /** * Function that executes a callback function once the Sutton SignWriiting Line font is ready to use * @function font.cssLoadedLine * @param {function} callback - a callback function to execute when line font is ready * @example * const callback = () => { * console.log("Sutton SignWriting Line font is ready to use") * } * * font.cssLoadedLine( callback ) */ const cssLoadedLine = function (callback) { if (!symbolSize(1)) { const cssCheck = setInterval(function () { if (symbolSize(1)) { clearInterval(cssCheck); callback(); } }, 100); } else { callback(); } }; /** * Function that executes a callback function once the Sutton SignWriiting Fill font is ready to use * @function font.cssLoadedFill * @param {function} callback - a callback function to execute when fill font is ready * @example * const callback = () => { * console.log("Sutton SignWriting Fill font is ready to use") * } * * font.cssLoadedFill( callback ) */ const cssLoadedFill = function (callback) { const fillReady = function () { const canvaser = document.createElement("canvas"); canvaser.width = 15; canvaser.height = 30; const context = canvaser.getContext("2d"); context.font = "30px 'SuttonSignWritingFill'"; context.fillText(symbolFill(1), 0, 0); const imgData = context.getImageData(0, 0, 15, 30).data; return !imgData.every(item => item === 0); }; if (!fillReady()) { const cssCheck = setInterval(function () { if (fillReady()) { clearInterval(cssCheck); callback(); } }, 100); } else { callback(); } }; /** The font module contains functions for handing the TrueType fonts. * @module font */ var index = /*#__PURE__*/Object.freeze({ __proto__: null, cssAppend: cssAppend, cssLoaded: cssLoaded, cssLoadedLine: cssLoadedLine, cssLoadedFill: cssLoadedFill, symbolSize: symbolSize, symbolLine: symbolLine, symbolFill: symbolFill, symbolText: symbolText }); /** * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core) * Author: Steve Slevinski (https://SteveSlevinski.me) * convert.mjs is released under the MIT License. */ /** * Function to convert a number to an SWU number character * @function convert.num2swu * @param {number} num - Integer value for number * @returns {string} SWU number character * @example * convert.num2swu(500) * * return '𝤆' */ const num2swu = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250); /** * Function to convert an array of x,y integers to two SWU number characters * @function convert.coord2swu * @param {number[]} coord - Array of x,y integers * @returns {string} Two SWU number character * @example * convert.coord2swu([500, 500]) * * return '𝤆𝤆' */ const coord2swu = coord => coord.map(num => num2swu(num)).join(''); /** * Function to convert an SWU symbol character to a code point on plane 4 * @function convert.swu2code * @param {string} swuSym - SWU symbol character * @returns {number} Code point on plane 4 * @example * convert.swu2code('񀀁') * * return 0x40001 */ const swu2code = swuSym => parseInt(swuSym.codePointAt(0)); /** * Function to convert an SWU symbol character to a 16-bit ID * @function convert.swu2id * @param {string} swuSym - SWU symbol character * @returns {number} 16-bit ID * @example * convert.swu2id('񀀁') * * return 1 */ const swu2id = swuSym => swu2code(swuSym) - 0x40000; /** * Function to convert an FSW symbol key to a 16-bit ID * @function convert.key2id * @param {string} key - FSW symbol key * @returns {number} 16-bit ID * @example * convert.key2id('S10000') * * return 1 */ 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); /* support ongoing development on https://patreon.com/signwriting */ /** * Function that returns the size of a symbol using an FSW symbol key * @function fsw.symbolSize * @param {string} fsw - an FSW symbol key * @example * fsw.symbolSize("S10000") * * return [15,30] */ const symbolSize$1 = function (fsw) { return symbolSize(key2id(fsw)); }; /** * Function that returns a plane 15 character for a symbol line using an FSW symbol key * @function fsw.symbolLine * @param {string} fsw - an FSW symbol key * @example * fsw.symbolLine('S10000') * * return '󰀁' */ const symbolLine$1 = function (fsw) { return symbolLine(key2id(fsw)); }; /** * Function that returns a plane 16 character for a symbol fill using an FSW symbol key * @function fsw.symbolFill * @param {string} fsw - an FSW symbol key * @example * font.symbolFill('S10000') * * return '􀀁' */ const symbolFill$1 = function (fsw) { return symbolFill(key2id(fsw)); }; /** * Function that creates two text elements for a symbol using an FSW symbol key * @function fsw.symbolText * @param {string} fsw - an FSW symbol key * @example * fsw.symbolText('S10000') * * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>` */ const symbolText$1 = function (fsw) { return symbolText(key2id(fsw)); }; /** * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core) * Author: Steve Slevinski (https://SteveSlevinski.me) * style.mjs is released under the MIT License. */ /** * Object of regular expressions for style strings * * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full } * @alias style.re * @type {object} */ let re = { 'colorize': 'C', 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}', 'colorname': '[a-zA-Z]+', 'padding': 'P[0-9]{2}', 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)', 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?', 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}', 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}' }; re.colorbase = `(?:${re.colorhex}|${re.colorname})`; re.color = `_${re.colorbase}_`; re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`; re.background = `G${re.color}`; re.detail = `D${re.colors}`; re.detailsym = `D[0-9]{2}${re.colors}`; re.classes = `${re.classbase}(?: ${re.classbase})*`; re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*)((?:${re.zoomsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`; const prefixColor = color => { const regex = new RegExp(`^${re.colorhex}$`); return (regex.test(color) ? '#' : '') + color; }; /** * Function to parse style string to object * @function style.parse * @param {string} styleString - a style string * @returns {object} elements of style string * @example * style.parse('-CP10G_blue_D_red,Cyan_') * * return { * 'colorize': true, * 'padding': 10, * 'background': 'blue', * 'detail': ['red', 'Cyan'] * } */ const parse = styleString => { const regex = `^${re.full}`; const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || []; return { 'colorize': !m[1] ? undefined : !!m[1], 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)), 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)), 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor), 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)), 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => { const parts = val.split('_'); const detail = parts[1].split(',').map(prefixColor); return { 'index': parseInt(parts[0].slice(1)), 'detail': detail }; }), 'zoomsym': !m[7] ? undefined : m[7].match(new RegExp(re.zoomsym, 'g')).map(val => { const parts = val.split(','); return { 'index': parseInt(parts[0].slice(1)), 'zoom': parseFloat(parts[1]), 'offset': !parts[2] ? undefined : parts[2].split('x').map(val => parseInt(val) - 500) }; }), 'classes': !m[8] ? undefined : m[8], 'id': !m[9] ? undefined : m[9] }; }; /* support ongoing development on https://patreon.com/signwriting */ /** * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core) * Author: Steve Slevinski (https://SteveSlevinski.me) * fsw.mjs is released under the MIT License. */ /** * Object of regular expressions for FSW strings * * { symbol, coord, sort, box, prefix, spatial, signbox, sign, sortable } * @alias fsw.re * @type {object} */ let re$1 = { 'symbol': 'S[123][0-9a-f]{2}[0-5][0-9a-f]', 'coord': '[0-9]{3}x[0-9]{3}', 'sort': 'A', 'box': '[BLMR]' }; re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`; re$1.spatial = `${re$1.symbol}${re$1.coord}`; re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`; re$1.sign = `${re$1.prefix}?${re$1.signbox}`; re$1.sortable = `${re$1.prefix}${re$1.signbox}`; /** * Object of regular expressions for style strings * * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full } * @alias style.re * @type {object} */ let re$1$1 = { 'colorize': 'C', 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}', 'colorname': '[a-zA-Z]+', 'padding': 'P[0-9]{2}', 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)', 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?', 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}', 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}' }; re$1$1.colorbase = `(?:${re$1$1.colorhex}|${re$1$1.colorname})`; re$1$1.color = `_${re$1$1.colorbase}_`; re$1$1.colors = `_${re$1$1.colorbase}(?:,${re$1$1.colorbase})?_`; re$1$1.background = `G${re$1$1.color}`; re$1$1.detail = `D${re$1$1.colors}`; re$1$1.detailsym = `D[0-9]{2}${re$1$1.colors}`; re$1$1.classes = `${re$1$1.classbase}(?: ${re$1$1.classbase})*`; 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})!)?)?`; /** 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. * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2) * @module convert */ /** * Function to convert an FSW coordinate string to an array of x,y integers * @function convert.fsw2coord * @param {string} fswCoord - An FSW coordinate string * @returns {number[]} Array of x,y integers * @example * convert.fsw2coord('500x500') * * return [500, 500] */ const fsw2coord = fswCoord => fswCoord.split('x').map(num => parseInt(num)); const parse$1 = { /** * Function to parse an fsw symbol with optional coordinate and style string * @function fsw.parse.symbol * @param {string} fswSym - an fsw symbol * @returns {object} elements of fsw symbol * @example * fsw.parse.symbol('S10000500x500-C') * * return { * 'symbol': 'S10000', * 'coord': [500, 500], * 'style': '-C' * } */ symbol: fswSym => { const regex = `^(${re$1.symbol})(${re$1.coord})?(${re$1$1.full})?`; const symbol = typeof fswSym === 'string' ? fswSym.match(new RegExp(regex)) : undefined; return { 'symbol': symbol ? symbol[1] : undefined, 'coord': symbol && symbol[2] ? fsw2coord(symbol[2]) : undefined, 'style': symbol ? symbol[3] : undefined }; }, /** * Function to parse an fsw sign with style string * @function fsw.parse.sign * @param {string} fswSign - an fsw sign * @returns {object} elements of fsw sign * @example * fsw.parse.sign('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C') * * return { * sequence: ['S10011', 'S10019', 'S2e704', 'S2e748'], * box: 'M', * max: [525, 535], * spatials: [ * { * symbol: 'S2e748', * coord: [483, 510] * }, * { * symbol: 'S10011', * coord: [501, 466] * }, * { * symbol: 'S2e704', * coord: [510, 500] * }, * { * symbol: 'S10019', * coord: [476, 475] * } * ], * style: '-C' * } */ sign: fswSign => { const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re$1$1.full})?`; const sign = typeof fswSign === 'string' ? fswSign.match(new RegExp(regex)) : undefined; if (sign) { return { 'sequence': sign[1] ? sign[1].slice(1).match(/.{6}/g) : undefined, 'box': sign[2][0], 'max': fsw2coord(sign[2].slice(1, 8)), 'spatials': sign[2].length < 9 ? undefined : sign[2].slice(8).match(/(.{13})/g).map(m => { return { symbol: m.slice(0, 6), coord: [parseInt(m.slice(6, 9)), parseInt(m.slice(10, 13))] }; }), 'style': sign[3] }; } else { return {}; } } }; /** * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation. * @alias fsw.category * @type {array} */ const category = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387]; /** * Object of symbol ranges with starting and ending numbers. * * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation } * @alias fsw.ranges * @type {object} */ const ranges = { 'all': [0x100, 0x38b], 'writing': [0x100, 0x37e], 'hand': [0x100, 0x204], 'movement': [0x205, 0x2f6], 'dynamic': [0x2f7, 0x2fe], 'head': [0x2ff, 0x36c], 'hcenter': [0x2ff, 0x36c], 'vcenter': [0x2ff, 0x375], 'trunk': [0x36d, 0x375], 'limb': [0x376, 0x37e], 'location': [0x37f, 0x386], 'punctuation': [0x387, 0x38b] }; /** * Array of colors associated with the seven symbol categories. * @alias fsw.colors * @type {array} */ const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900']; /** * Function that returns the standardized color for a symbol. * @function fsw.colorize * @param {string} key - an FSW symbol key * @returns {string} name of standardized color for symbol * @example * fsw.colorize('S10000') * * return '#0000CC' */ const colorize = key => { const parsed = parse$1.symbol(key); let color = '#000000'; if (parsed.symbol) { const dec = parseInt(parsed.symbol.slice(1, 4), 16); const index = category.findIndex(val => val > dec); color = colors[index < 0 ? 6 : index - 1]; } return color; }; /* support ongoing development on https://patreon.com/signwriting */ /** * Function that creates an SVG image from an FSW symbol key with an optional style string * @function fsw.symbolSvg * @param {string} fswSym - an FSW symbol key with optional style string * @example * fsw.symbolSvg('S10000') * * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="15" height="30" viewBox="500 500 15 30"> * <text font-size="0">S10000</text> * <g transform="translate(500,500)"> * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text> * </g> * </svg>` */ const symbolSvg = fswSym => { const parsed = parse$1.symbol(fswSym); const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>'; if (parsed.symbol) { let size = symbolSize$1(parsed.symbol); if (size) { let styling = parse(parsed.style); let line; let symSvg = symbolText$1(parsed.symbol); symSvg = ` <g transform="translate(500,500)"> ${symSvg} </g>`; if (styling.colorize) { line = colorize(parsed.symbol); } else if (styling.detail) { line = styling.detail[0]; } if (line) { symSvg = symSvg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${line}"`); } let fill = styling.detail && styling.detail[1]; if (fill) { symSvg = symSvg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${fill}"`); } let x1 = 500; let y1 = 500; let background = ''; if (styling.padding) { x1 -= styling.padding; y1 -= styling.padding; size[0] += styling.padding * 2; size[1] += styling.padding * 2; } if (styling.background) { background = `\n <rect x="${x1}" y="${y1}" width="${size[0]}" height="${size[1]}" style="fill:${styling.background};" />`; } let sizing = ''; if (styling.zoom != 'x') { sizing = ` width="${size[0] * (styling.zoom ? styling.zoom : 1)}" height="${size[1] * (styling.zoom ? styling.zoom : 1)}"`; } let classes = ''; if (styling.classes) { classes = ` class="${styling.classes}"`; } let id = ''; if (styling.id) { id = ` id="${styling.id}"`; } return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${size[0]} ${size[1]}"> <text font-size="0">${fswSym}</text>${background} ${symSvg} </svg>`; } } return blank; }; const symbolCanvas = function (fswSym) { const parsed = parse$1.symbol(fswSym); if (parsed.symbol) { let size = symbolSize$1(parsed.symbol); if (size) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); let styling = parse(parsed.style); let line = 'black'; if (styling.colorize) { line = colorize(parsed.symbol); } else if (styling.detail) { line = styling.detail[0]; } let fill = styling.detail && styling.detail[1] || 'white'; let x1 = 500; let x2 = x1 + size[0]; let y1 = 500; let y2 = y1 + size[1]; if (styling.padding) { x1 -= styling.padding; y1 -= styling.padding; x2 += styling.padding; y2 += styling.padding; } let sizing = 1; if (styling.zoom != 'x') { sizing = styling.zoom; } let w = (x2 - x1) * sizing; let h = (y2 - y1) * sizing; canvas.width = w ? w : 1; canvas.height = h ? h : 1; if (styling.background) { context.rect(0, 0, w, h); context.fillStyle = styling.background; context.fill(); } context.font = 30 * sizing + "px 'SuttonSignWritingFill'"; context.fillStyle = fill; context.fillText(symbolFill$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing); context.font = 30 * sizing + "px 'SuttonSignWritingLine'"; context.fillStyle = line; context.fillText(symbolLine$1(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing); return canvas; } } }; /** * Function that creates a binary PNG image from an FSW symbol key with an optional stle string * @function fsw.symbolPng * @param {string} fswSym - an FSW symbol key with optional style string * @example * fsw.symbolPng('S10000') * * return 'data:image/png;base64,iVBORw...' */ const symbolPng = fswSym => { const canvas = symbolCanvas(fswSym); const png = canvas.toDataURL("image/png"); canvas.remove(); return png; }; const blank = null; /** * Function that normalizes a symbol with a minimum coordinate for a center of 500,500 * @function fsw.symbolNormalize * @param {string} fswSym - an FSW symbol key with optional coordinate and style string * @example * fsw.symbolNormalize('S10000-CP10G_green_Z2') * * return 'S10000493x485-CP10G_green_Z2' */ const symbolNormalize = fswSym => { const parsed = parse$1.symbol(fswSym); if (parsed.symbol) { let size = symbolSize$1(parsed.symbol); if (size) { return `${parsed.symbol}${500 - parseInt(size[0] / 2)}x${500 - parseInt(size[1] / 2)}${parsed.style || ''}`; } } else { return blank; } }; /** * Function that creates an SVG image from an FSW sign with an optional style string * @function fsw.signSvg * @param {string} fswSign - an FSW sign with optional style string * @example * fsw.signSvg('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475') * * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="49" height="69" viewBox="476 466 49 69"> * <text font-size="0">M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475</text> * <g transform="translate(483,510)"> * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋛩</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻛩</text> * </g> * <g transform="translate(501,466)"> * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀒</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀒</text> * </g> * <g transform="translate(510,500)"> * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􋚥</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󻚥</text> * </g> * <g transform="translate(476,475)"> * <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀚</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀚</text> * </g> * </svg>` */ const signSvg = fswSign => { let parsed = parse$1.sign(fswSign); const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>'; if (parsed.spatials) { let styling = parse(parsed.style); if (styling.detailsym) { styling.detailsym.forEach(sym => { if (parsed.spatials[sym.index - 1]) { parsed.spatials[sym.index - 1].detail = sym.detail; } }); } let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0])); let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1])); let x2 = parsed.max[0]; let y2 = parsed.max[1]; if (styling.zoomsym) { styling.zoomsym.forEach(sym => { if (parsed.spatials[sym.index - 1]) { parsed.spatials[sym.index - 1].zoom = sym.zoom; if (sym.offset) { parsed.spatials[sym.index - 1].coord[0] += sym.offset[0]; parsed.spatials[sym.index - 1].coord[1] += sym.offset[1]; } let size = symbolSize$1(parsed.spatials[sym.index - 1].symbol); x2 = Math.max(x2, parsed.spatials[sym.index - 1].coord[0] + size[0] * sym.zoom); y2 = Math.max(y2, parsed.spatials[sym.index - 1].coord[1] + size[1] * sym.zoom); } }); } let classes = ''; if (styling.classes) { classes = ` class="${styling.classes}"`; } let id = ''; if (styling.id) { id = ` id="${styling.id}"`; } let background = ''; if (styling.padding) { x1 -= styling.padding; y1 -= styling.padding; x2 += styling.padding; y2 += styling.padding; } if (styling.background) { background = `\n <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${styling.background};" />`; } let sizing = ''; if (styling.zoom != 'x') { sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`; } let svg = `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}"> <text font-size="0">${fswSign}</text>${background}`; const line = styling.detail && styling.detail[0]; const fill = styling.detail && styling.detail[1]; svg += '\n' + parsed.spatials.map(spatial => { let svg = symbolText$1(spatial.symbol); let symLine = line; if (spatial.detail) { symLine = spatial.detail[0]; } else if (styling.colorize) { symLine = colorize(spatial.symbol); } if (symLine) { svg = svg.replace(/class="sym-line" fill="black"/, `class="sym-line" fill="${symLine}"`); } let symFill = fill; if (spatial.detail && spatial.detail[1]) { symFill = spatial.detail[1]; } if (symFill) { svg = svg.replace(/class="sym-fill" fill="white"/, `class="sym-fill" fill="${symFill}"`); } if (spatial.zoom) { svg = `<g transform="scale(${spatial.zoom})">${svg}</g>`; } return ` <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})"> ${svg} </g>`; }).join('\n'); svg += '\n</svg>'; return svg; } return blank; }; const signCanvas = function (fswSign) { const parsed = parse$1.sign(fswSign); if (parsed.spatials) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); let styling = parse(parsed.style); if (styling.detailsym) { styling.detailsym.forEach(sym => { if (parsed.spatials[sym.index - 1]) { parsed.spatials[sym.index - 1].detail = sym.detail; } }); } let x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0])); let y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1])); let x2 = parsed.max[0]; let y2 = parsed.max[1]; if (styling.zoomsym) { styling.zoomsym.forEach(sym => { if (parsed.spatials[sym.index - 1]) { parsed.spatials[sym.index - 1].zoom = sym.zoom; if (sym.offset) { parsed.spatials[sym.index - 1].coord[0] += sym.offset[0]; parsed.spatials[sym.index - 1].coord[1] += sym.offset[1]; } let size = symbolSize$1(parsed.spatials[sym.index - 1].symbol); x2 = Math.max(x2, parsed.spatials[sym.index - 1].coord[0] + size[0] * sym.zoom); y2 = Math.max(y2, parsed.spatials[sym.index - 1].coord[1] + size[1] * sym.zoom); } }); } if (styling.padding) { x1 -= styling.padding; y1 -= styling.padding; x2 += styling.padding; y2 += styling.padding; } let sizing = 1; if (styling.zoom != 'x') { sizing = styling.zoom; } let w = (x2 - x1) * sizing; let h = (y2 - y1) * sizing; canvas.width = w ? w : 1; canvas.height = h ? h : 1; if (styling.background) { context.rect(0, 0, w, h); context.fillStyle = styling.background; context.fill(); } const line = styling.detail && styling.detail[0] || "black"; const fill = styling.detail && styling.detail[1] || "white"; parsed.spatials.forEach(spatial => { let symLine = line; if (spatial.detail) { symLine = spatial.detail[0]; } else if (styling.colorize) { symLine = colorize(spatial.symbol); } let symFill = fill; if (spatial.detail && spatial.detail[1]) { symFill = spatial.detail[1]; } let symZoom = spatial.zoom || 1; context.font = 30 * sizing * symZoom + "px 'SuttonSignWritingFill'"; context.fillStyle = symFill; context.fillText(symbolFill$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing); context.font = 30 * sizing * symZoom + "px 'SuttonSignWritingLine'"; context.fillStyle = symLine; context.fillText(symbolLine$1(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing); }); return canvas; } }; /** * Function that creates a binary PNG image from an FSW sign with an optional style string * @function fsw.signPng * @param {string} fswSign - an FSW sign with optional style string * @example * fsw.signPng('M525x535S2e748483x510S10011501x466S20544510x500S10019476x475') * * return 'data:image/png;base64,iVBORw...' */ const signPng = fswSign => { const canvas = signCanvas(fswSign); const png = canvas.toDataURL("image/png"); canvas.remove(); return png; }; /** * Function that normalizes an FSW sign for a center of 500,500 * @function fsw.signNormalize * @param {string} fswSign - an FSW sign with optional style string * @example * fsw.signNormalize('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475') * * return 'M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475' */ const signNormalize = fswSign => { const parsed = parse$1.sign(fswSign); if (parsed.spatials) { const symbolsizes = parsed.spatials.reduce((output, spatial) => { const size = symbolSize$1(spatial.symbol); output[spatial.symbol] = { width: size[0], height: size[1] }; return output; }, {}); const bbox = symbols => { const x1 = Math.min(...symbols.map(spatial => spatial.coord[0])); const y1 = Math.min(...symbols.map(spatial => spatial.coord[1])); const x2 = Math.max(...symbols.map(spatial => spatial.coord[0] + parseInt(symbolsizes[spatial.symbol].width))); const y2 = Math.max(...symbols.map(spatial => spatial.coord[1] + parseInt(symbolsizes[spatial.symbol].height))); return { x1: x1, y1: y1, x2: x2, y2: y2 }; }; const hrange = ranges['hcenter']; const hsyms = parsed.spatials.filter(spatial => { const dec = parseInt(spatial.symbol.slice(1, 4), 16); return hrange[0] <= dec && hrange[1] >= dec; }); const vrange = ranges['vcenter']; const vsyms = parsed.spatials.filter(spatial => { const dec = parseInt(spatial.symbol.slice(1, 4), 16); return vrange[0] <= dec && vrange[1] >= dec; }); let abox = bbox(parsed.spatials); let max = [abox.x2, abox.y2]; if (hsyms.length) { const hbox = bbox(hsyms); abox.x1 = hbox.x1; abox.x2 = hbox.x2; } if (vsyms.length) { const vbox = bbox(vsyms); abox.y1 = vbox.y1; abox.y2 = vbox.y2; } const offset = [parseInt((abox.x2 + abox.x1) / 2) - 500, parseInt((abox.y2 + abox.y1) / 2) - 500]; 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 || ''); return fswout; } }; /** The fsw module contains functions for handling Formal SignWriitng in ASCII (FSW) characters. * [FSW characters definition](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2.1) * @module fsw */ var index$1 = /*#__PURE__*/Object.freeze({ __proto__: null, symbolSize: symbolSize$1, symbolLine: symbolLine$1, symbolFill: symbolFill$1, symbolText: symbolText$1, symbolSvg: symbolSvg, symbolPng: symbolPng, symbolNormalize: symbolNormalize, signSvg: signSvg, signPng: signPng, signNormalize: signNormalize }); /** * Function that returns the size of a symbol using an SWU symbol character * @function swu.symbolSize * @param {string} swu - an SWU symbol character * @example * swu.symbolSize("񀀁") * * return [15,30] */ const symbolSize$2 = function (swu) { return symbolSize(swu2id(swu)); }; /** * Function that returns a plane 15 character for a symbol line using an SWU symbol character * @function swu.symbolLine * @param {string} swu - an SWU symbol character * @example * swu.symbolLine('񀀁') * * return '󰀁' */ const symbolLine$2 = function (swu) { return symbolLine(swu2id(swu)); }; /** * Function that returns a plane 165 character for a symbol fill using an SWU symbol character * @function swu.symbolFill * @param {string} swu - an SWU symbol character * @example * swu.symbolFill('񀀁') * * return '􀀁' */ const symbolFill$2 = function (swu) { return symbolFill(swu2id(swu)); }; /** * Function that creates two text elements for a symbol using an SWU symbol character * @function swu.symbolText * @param {string} swu - an SWU symbol character * @example * swu.symbolText('񀀁') * * return ` <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">􀀁</text> * <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">󰀁</text>` */ const symbolText$2 = function (swu) { return symbolText(swu2id(swu)); }; /** * Sutton SignWriting Core Module v1.2.0 (https://github.com/sutton-signwriting/core) * Author: Steve Slevinski (https://SteveSlevinski.me) * swu.mjs is released under the MIT License. */ /** * Object of regular expressions for SWU strings in UTF-16 * * { symbol, coord, sort, box, prefix, spatial, signbox, sign, sortable } * @alias swu.re * @type {object} */ let re$2 = { 'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))', 'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}', 'sort': '\uD836\uDC00', 'box': '\uD836[\uDC01-\uDC04]' }; re$2.prefix = `(?:${re$2.sort}(?:${re$2.symbol})+)`; re$2.spatial = `${re$2.symbol}${re$2.coord}`; re$2.signbox = `${re$2.box}${re$2.coord}(?:${re$2.spatial})*`; re$2.sign = `${re$2.prefix}?${re$2.signbox}`; re$2.sortable = `${re$2.prefix}${re$2.signbox}`; /** * Object of regular expressions for style strings * * { colorize, colorhex, colorname, padding, zoom, zoomsym, classbase, id, colorbase, color, colors, background, detail, detailsym, classes, full } * @alias style.re * @type {object} */ let re$1$2 = { 'colorize': 'C', 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}', 'colorname': '[a-zA-Z]+', 'padding': 'P[0-9]{2}', 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)', 'zoomsym': 'Z[0-9]{2},[0-9]+(?:\\.[0-9]+)?(?:,[0-9]{3}x[0-9]{3})?', 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}', 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}' }; re$1$2.colorbase = `(?:${re$1$2.colorhex}|${re$1$2.colorname})`; re$1$2.color = `_${re$1$2.colorbase}_`; re$1$2.colors = `_${re$1$2.colorbase}(?:,${re$1$2.colorbase})?_`; re$1$2.background = `G${re$1$2.color}`; re$1$2.detail = `D${re$1$2.colors}`; re$1$2.detailsym = `D[0-9]{2}${re$1$2.colors}`; re$1$2.classes = `${re$1$2.classbase}(?: ${re$1$2.classbase})*`; re$1$2.full = `-(${re$1$2.colorize})?(${re$1$2.padding})?(${re$1$2.background})?(${re$1$2.detail})?(${re$1$2.zoom})?(?:-((?:${re$1$2.detailsym})*)((?:${re$1$2.zoomsym})*))?(?:-(${re$1$2.classes})?!(?:(${re$1$2.id})!)?)?`; /** 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. * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-07.html#rfc.section.2.2) * @module convert */ /** * Function to convert an SWU number character to an integer * @function convert.swu2num * @param {string} swuNum - SWU number character * @returns {number} Integer value for number * @example * convert.swu2num('𝤆') * * return 500 */ const swu2num = swuNum => parseInt(swuNum.codePointAt(0)) - 0x1D80C + 250; /** * Function to convert two SWU number characters to an array of x,y integers * @function convert.swu2coord * @param {string} swuCoord - Two SWU number character * @returns {number[]} Array of x,y integers * @example * convert.swu2coord('𝤆𝤆') * * return [500, 500] */ const swu2coord = swuCoord => [swu2num(swuCoord.slice(0, 2)), swu2num(swuCoord.slice(2, 4))]; /** * Function to convert an SWU symbol character to a code point on plane 4 * @function convert.swu2code * @param {string} swuSym - SWU symbol character * @returns {number} Code point on plane 4 * @example * convert.swu2code('񀀁') * * return 0x40001 */ const swu2code$1 = swuSym => parseInt(swuSym.codePointAt(0)); const parse$2 = { /** * Function to parse an swu symbol with optional coordinate and style string * @function swu.parse.symbol * @param {string} swuSym - an swu symbol * @returns {object} elements of swu symbol * @example * swu.parse.symbol('񀀁𝤆𝤆-C') * * return { * 'symbol': '񀀁', * 'coord': [500, 500], * 'style': '-C' * } */ symbol: swuSym => { const regex = `^(${re$2.symbol})(${re$2.coord})?(${re$1$2.full})?`; const symbol = typeof swuSym === 'string' ? swuSym.match(new RegExp(regex)) : undefined; return { 'symbol': symbol ? symbol[1] : undefined, 'coord': symbol && symbol[2] ? swu2coord(symbol[2]) : undefined, 'style': symbol ? symbol[3] : undefined }; }, /** * Function to parse an swu sign with style string * @function swu.parse.sign * @param {string} swuSign - an swu sign * @returns {object} elements of swu sign * @example * swu.parse.sign('𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭-C') * * return { * sequence: ['񀀒','񀀚','񋚥','񋛩''], * box: '𝠃', * max: [525, 535], * spatials: [ * { * symbol: '񋛩', * coord: [483, 510] * }, * { * symbol: '񀀒', * coord: [501, 466] * }, * { * symbol: '񋚥', * coord: [510, 500] * }, * { * symbol: '񀀚', * coord: [476, 475] * } * ], * style: '-C' * } */ sign: swuSign => { const regex = `^(${re$2.prefix})?(${re$2.signbox})(${re$1$2.full})?`; const sign = typeof swuSign === 'string' ? swuSign.match(new RegExp(regex)) : undefined; if (sign) { return { 'sequence': sign[1] ? sign[1].slice(2).match(/.{2}/g) : undefined, 'box': sign[2].slice(0, 2), 'max': swu2coord(sign[2].slice(2, 6)), 'spatials': sign[2].length < 7 ? undefined : sign[2].slice(6).match(/(.{6})/g).map(m => { return { symbol: m.slice(0, 2), coord: swu2coord(m.slice(2)) }; }), 'style': sign[3] }; } else { return {}; } } }; /** * Array of plane 4 code points for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation. * @alias swu.category * @type {array} */ const category$1 = [0x40001, 0x461e1, 0x4bca1, 0x4bfa1, 0x4e8e1, 0x4efa1, 0x4f2a1]; /** * Object of symbol ranges with starting and ending code points on plane 4. * * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation } * @alias swu.ranges * @type {object} */ const ranges$1 = { 'all': [0x40001, 0x4f480], 'writing': [0x40001, 0x4efa0], 'hand': [0x40001, 0x461e0], 'movement': [0x461e1, 0x4bca0], 'dynamic': [0x4bca1, 0x4bfa0], 'head': [0x4bfa1, 0x4e8e0], 'hcenter': [0x4bfa1, 0x4e8e0], 'vcenter': [0x4bfa1, 0x4ec40], 'trunk': [0x4e8e1, 0x4ec40], 'limb': [0x4ec41, 0x4efa0], 'location': [0x4efa1, 0x4f2a0], 'punctuation': [0x4f2a1, 0x4f480] }; /** * Array of colors associated with the seven symbol categories. * @alias swu.colors * @type {array} */ const colors$1 = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900']; /** * Function that returns the standardized color for a symbol. * @function swu.colorize * @param {string} swuSym - an SWU symbol character * @returns {string} name of standardized color for symbol * @example * swu.colorize('񀀁') * * return '#0000CC' */ const colorize$1 = swuSym => { const parsed = parse$2.symbol(swuSym); let color = '#000000'; if (parsed.symbol) { const code =