1 | /**
|
2 | * Sutton SignWriting Core Module v1.4.2 (https://github.com/sutton-signwriting/core)
|
3 | * Author: Steve Slevinski (https://SteveSlevinski.me)
|
4 | * swu.mjs is released under the MIT License.
|
5 | */
|
6 |
|
7 | /**
|
8 | * Object of regular expressions for SWU strings in UTF-16
|
9 | *
|
10 | * @alias swu.re
|
11 | * @property {string} symbol - regular expressions for a symbol
|
12 | * @property {string} coord - regular expressions for a coordinate
|
13 | * @property {string} sort - regular expressions for the sorting marker
|
14 | * @property {string} box - regular expression for a signbox marker
|
15 | * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
|
16 | * @property {string} spatial - regular expression for a symbol followed by a coordinate
|
17 | * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
|
18 | * @property {string} sign - regular expression for an optional prefix followed by a signbox
|
19 | * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
|
20 | */
|
21 | let re$1 = {
|
22 | 'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
|
23 | 'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}',
|
24 | 'sort': '\uD836\uDC00',
|
25 | 'box': '\uD836[\uDC01-\uDC04]'
|
26 | };
|
27 | re$1.prefix = `(?:${re$1.sort}(?:${re$1.symbol})+)`;
|
28 | re$1.spatial = `${re$1.symbol}${re$1.coord}`;
|
29 | re$1.signbox = `${re$1.box}${re$1.coord}(?:${re$1.spatial})*`;
|
30 | re$1.sign = `${re$1.prefix}?${re$1.signbox}`;
|
31 | re$1.sortable = `${re$1.prefix}${re$1.signbox}`;
|
32 |
|
33 | /**
|
34 | * Object of regular expressions for style strings
|
35 | *
|
36 | * @alias style.re
|
37 | * @type {object}
|
38 | * @property {string} colorize - regular expression for colorize section
|
39 | * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
|
40 | * @property {string} colorname - regular expression for css color name
|
41 | * @property {string} padding - regular expression for padding section
|
42 | * @property {string} zoom - regular expression for zoom section
|
43 | * @property {string} classbase - regular expression for class name definition
|
44 | * @property {string} id - regular expression for id definition
|
45 | * @property {string} colorbase - regular expression for color hex or color name
|
46 | * @property {string} color - regular expression for single color entry
|
47 | * @property {string} colors - regular expression for double color entry
|
48 | * @property {string} background - regular expression for background section
|
49 | * @property {string} detail - regular expression for color details for line and optional fill
|
50 | * @property {string} detailsym - regular expression for color details for individual symbols
|
51 | * @property {string} classes - regular expression for one or more class names
|
52 | * @property {string} full - full regular expression for style string
|
53 | */
|
54 | let re = {
|
55 | 'colorize': 'C',
|
56 | 'colorhex': '(?:[0-9a-fA-F]{3}){1,2}',
|
57 | 'colorname': '[a-zA-Z]+',
|
58 | 'padding': 'P[0-9]{2}',
|
59 | 'zoom': 'Z(?:[0-9]+(?:\\.[0-9]+)?|x)',
|
60 | 'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
|
61 | 'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
|
62 | };
|
63 | re.colorbase = `(?:${re.colorhex}|${re.colorname})`;
|
64 | re.color = `_${re.colorbase}_`;
|
65 | re.colors = `_${re.colorbase}(?:,${re.colorbase})?_`;
|
66 | re.background = `G${re.color}`;
|
67 | re.detail = `D${re.colors}`;
|
68 | re.detailsym = `D[0-9]{2}${re.colors}`;
|
69 | re.classes = `${re.classbase}(?: ${re.classbase})*`;
|
70 | re.full = `-(${re.colorize})?(${re.padding})?(${re.background})?(${re.detail})?(${re.zoom})?(?:-((?:${re.detailsym})*))?(?:-(${re.classes})?!(?:(${re.id})!)?)?`;
|
71 |
|
72 | const prefixColor = color => {
|
73 | const regex = new RegExp(`^${re.colorhex}$`);
|
74 | return (regex.test(color) ? '#' : '') + color;
|
75 | };
|
76 |
|
77 | const definedProps = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
|
78 | /**
|
79 | * Function to parse style string to object
|
80 | * @function style.parse
|
81 | * @param {string} styleString - a style string
|
82 | * @returns {StyleObject} elements of style string
|
83 | * @example
|
84 | * style.parse('-CP10G_blue_D_red,Cyan_')
|
85 | *
|
86 | * return {
|
87 | * 'colorize': true,
|
88 | * 'padding': 10,
|
89 | * 'background': 'blue',
|
90 | * 'detail': ['red', 'Cyan']
|
91 | * }
|
92 | */
|
93 |
|
94 |
|
95 | const parse$1 = styleString => {
|
96 | const regex = `^${re.full}`;
|
97 | const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
|
98 | return definedProps({
|
99 | 'colorize': !m[1] ? undefined : !!m[1],
|
100 | 'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
|
101 | 'background': !m[3] ? undefined : prefixColor(m[3].slice(2, -1)),
|
102 | 'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor),
|
103 | 'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
|
104 | 'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re.detailsym, 'g')).map(val => {
|
105 | const parts = val.split('_');
|
106 | const detail = parts[1].split(',').map(prefixColor);
|
107 | return {
|
108 | 'index': parseInt(parts[0].slice(1)),
|
109 | 'detail': detail
|
110 | };
|
111 | }),
|
112 | 'classes': !m[7] ? undefined : m[7],
|
113 | 'id': !m[8] ? undefined : m[8]
|
114 | });
|
115 | };
|
116 |
|
117 | /** 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.
|
118 | * [Characters set definitions](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-characters)
|
119 | * @module convert
|
120 | */
|
121 | /**
|
122 | * Function to convert an SWU number character to an integer
|
123 | * @function convert.swu2num
|
124 | * @param {string} swuNum - SWU number character
|
125 | * @returns {number} Integer value for number
|
126 | * @example
|
127 | * convert.swu2num('𝤆')
|
128 | *
|
129 | * return 500
|
130 | */
|
131 |
|
132 |
|
133 | const swu2num = swuNum => parseInt(swuNum.codePointAt(0)) - 0x1D80C + 250;
|
134 | /**
|
135 | * Function to convert a number to an SWU number character
|
136 | * @function convert.num2swu
|
137 | * @param {number} num - Integer value for number
|
138 | * @returns {string} SWU number character
|
139 | * @example
|
140 | * convert.num2swu(500)
|
141 | *
|
142 | * return '𝤆'
|
143 | */
|
144 |
|
145 |
|
146 | const num2swu = num => String.fromCodePoint(0x1D80C + parseInt(num) - 250);
|
147 | /**
|
148 | * Function to convert two SWU number characters to an array of x,y integers
|
149 | * @function convert.swu2coord
|
150 | * @param {string} swuCoord - Two SWU number character
|
151 | * @returns {number[]} Array of x,y integers
|
152 | * @example
|
153 | * convert.swu2coord('𝤆𝤆')
|
154 | *
|
155 | * return [500, 500]
|
156 | */
|
157 |
|
158 |
|
159 | const swu2coord = swuCoord => [swu2num(swuCoord.slice(0, 2)), swu2num(swuCoord.slice(2, 4))];
|
160 | /**
|
161 | * Function to convert an array of x,y integers to two SWU number characters
|
162 | * @function convert.coord2swu
|
163 | * @param {number[]} coord - Array of x,y integers
|
164 | * @returns {string} Two SWU number character
|
165 | * @example
|
166 | * convert.coord2swu([500, 500])
|
167 | *
|
168 | * return '𝤆𝤆'
|
169 | */
|
170 |
|
171 |
|
172 | const coord2swu = coord => coord.map(num => num2swu(num)).join('');
|
173 | /**
|
174 | * Function to convert an SWU symbol character to a code point on plane 4
|
175 | * @function convert.swu2code
|
176 | * @param {string} swuSym - SWU symbol character
|
177 | * @returns {number} Code point on plane 4
|
178 | * @example
|
179 | * convert.swu2code('')
|
180 | *
|
181 | * return 0x40001
|
182 | */
|
183 |
|
184 |
|
185 | const swu2code = swuSym => parseInt(swuSym.codePointAt(0));
|
186 |
|
187 | const parse = {
|
188 | /**
|
189 | * Function to parse an swu symbol with optional coordinate and style string
|
190 | * @function swu.parse.symbol
|
191 | * @param {string} swuSym - an swu symbol
|
192 | * @returns {object} elements of swu symbol
|
193 | * @example
|
194 | * swu.parse.symbol('𝤆𝤆-C')
|
195 | *
|
196 | * return {
|
197 | * 'symbol': '',
|
198 | * 'coord': [500, 500],
|
199 | * 'style': '-C'
|
200 | * }
|
201 | */
|
202 | symbol: swuSym => {
|
203 | const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.full})?`;
|
204 | const symbol = typeof swuSym === 'string' ? swuSym.match(new RegExp(regex)) : undefined;
|
205 | return {
|
206 | 'symbol': symbol ? symbol[1] : undefined,
|
207 | 'coord': symbol && symbol[2] ? swu2coord(symbol[2]) : undefined,
|
208 | 'style': symbol ? symbol[3] : undefined
|
209 | };
|
210 | },
|
211 |
|
212 | /**
|
213 | * Function to parse an swu sign with style string
|
214 | * @function swu.parse.sign
|
215 | * @param {string} swuSign - an swu sign
|
216 | * @returns {object} elements of swu sign
|
217 | * @example
|
218 | * swu.parse.sign('𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭-C')
|
219 | *
|
220 | * return {
|
221 | * sequence: ['','','',''],
|
222 | * box: '𝠃',
|
223 | * max: [525, 535],
|
224 | * spatials: [
|
225 | * {
|
226 | * symbol: '',
|
227 | * coord: [483, 510]
|
228 | * },
|
229 | * {
|
230 | * symbol: '',
|
231 | * coord: [501, 466]
|
232 | * },
|
233 | * {
|
234 | * symbol: '',
|
235 | * coord: [510, 500]
|
236 | * },
|
237 | * {
|
238 | * symbol: '',
|
239 | * coord: [476, 475]
|
240 | * }
|
241 | * ],
|
242 | * style: '-C'
|
243 | * }
|
244 | */
|
245 | sign: swuSign => {
|
246 | const regex = `^(${re$1.prefix})?(${re$1.signbox})(${re.full})?`;
|
247 | const sign = typeof swuSign === 'string' ? swuSign.match(new RegExp(regex)) : undefined;
|
248 |
|
249 | if (sign) {
|
250 | return {
|
251 | 'sequence': sign[1] ? sign[1].slice(2).match(/.{2}/g) : undefined,
|
252 | 'box': sign[2].slice(0, 2),
|
253 | 'max': swu2coord(sign[2].slice(2, 6)),
|
254 | 'spatials': sign[2].length < 7 ? undefined : sign[2].slice(6).match(/(.{6})/g).map(m => {
|
255 | return {
|
256 | symbol: m.slice(0, 2),
|
257 | coord: swu2coord(m.slice(2))
|
258 | };
|
259 | }),
|
260 | 'style': sign[3]
|
261 | };
|
262 | } else {
|
263 | return {};
|
264 | }
|
265 | },
|
266 |
|
267 | /**
|
268 | * Function to parse an swu text
|
269 | * @function swu.parse.text
|
270 | * @param {string} swuText - an swu text
|
271 | * @returns {array} swu signs and punctuations
|
272 | * @example
|
273 | * swu.parse.text('𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻 𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦 𝣢𝤂')
|
274 | *
|
275 | * return [
|
276 | * '𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻',
|
277 | * '𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦',
|
278 | * '𝣢𝤂'
|
279 | * ]
|
280 | */
|
281 | text: swuText => {
|
282 | if (typeof swuText !== 'string') return [];
|
283 | const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
|
284 | const matches = swuText.match(new RegExp(regex, 'g'));
|
285 | return matches ? [...matches] : [];
|
286 | }
|
287 | };
|
288 | /**
|
289 | * Function to encode SWU characters using the UTF-16 escape format.
|
290 | * @function swu.encode
|
291 | * @param {string} swu - SWU characters
|
292 | * @returns {string} UTF-16 escape format
|
293 | * @example
|
294 | * swu.encode('𝤆𝤆')
|
295 | *
|
296 | * return '\\uD8C0\\uDC01\\uD836\\uDD06\\uD836\\uDD06'
|
297 | */
|
298 |
|
299 | const encode = text => text.replace(/[\u007F-\uFFFF]/g, function (chr) {
|
300 | return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4).toUpperCase();
|
301 | });
|
302 | /**
|
303 | * Function to decode UTF-16 escape format to SWU characters.
|
304 | * @function swu.decode
|
305 | * @param {string} encoded - UTF-16 escape format
|
306 | * @returns {string} SWU characters
|
307 | * @example
|
308 | * swu.decode('\\uD8C0\\uDC01\\uD836\\uDD06\\uD836\\uDD06')
|
309 | *
|
310 | * return '𝤆𝤆'
|
311 | */
|
312 |
|
313 |
|
314 | const decode = encoded => encoded.replace(/\\u([0-9A-F]{4})/g, function (match, chr) {
|
315 | return String.fromCharCode(parseInt(chr, 16));
|
316 | });
|
317 | /**
|
318 | * Function to decompose an SWU character into UTF-16 surrogate pairs.
|
319 | * @function swu.pair
|
320 | * @param {string} swuChar - an SWU character
|
321 | * @returns {string[]} an array of UTF-16 surrogate pairs
|
322 | * @example
|
323 | * swu.pair('')
|
324 | *
|
325 | * return ['D8C0', 'DC01']
|
326 | */
|
327 |
|
328 |
|
329 | const pair = swuChar => [swuChar.charCodeAt(0).toString(16).toUpperCase(), swuChar.charCodeAt(1).toString(16).toUpperCase()];
|
330 |
|
331 | const compose = {
|
332 | /**
|
333 | * Function to compose an swu symbol with optional coordinate and style string
|
334 | * @function swu.compose.symbol
|
335 | * @param {object} swuSymObject - an swu symbol object
|
336 | * @param {string} swuSymObject.symbol - an swu symbol key
|
337 | * @param {number[]} swuSymObject.coord - top-left coordinate of symbol with 500,500 center
|
338 | * @param {string} swuSymObject.style - a style string for custom appearance
|
339 | * @returns {string} an swu symbol string
|
340 | * @example
|
341 | * swu.compose.symbol({
|
342 | * 'symbol': '',
|
343 | * 'coord': [500, 500],
|
344 | * 'style': '-C'
|
345 | * })
|
346 | *
|
347 | * return '𝤆𝤆-C'
|
348 | */
|
349 | symbol: swuSymObject => {
|
350 | if (typeof swuSymObject !== 'object' || swuSymObject === null) return undefined;
|
351 |
|
352 | if (typeof swuSymObject.symbol === 'string') {
|
353 | const symbol = (swuSymObject.symbol.match(re$1.symbol) || [''])[0];
|
354 |
|
355 | if (symbol) {
|
356 | const x = swuSymObject.coord && swuSymObject.coord[0] || '';
|
357 | const y = swuSymObject.coord && swuSymObject.coord[1] || '';
|
358 | const coord = x && y ? coord2swu([x, y]) : '';
|
359 | const styleStr = typeof swuSymObject.style === 'string' && (swuSymObject.style.match(re.full) || [''])[0] || '';
|
360 | return symbol + coord + styleStr;
|
361 | }
|
362 | }
|
363 |
|
364 | return undefined;
|
365 | },
|
366 |
|
367 | /**
|
368 | * Function to compose an swu sign with style string
|
369 | * @function swu.compose.sign
|
370 | * @param {object} swuSignObject - an swu sign object
|
371 | * @param {string[]} swuSignObject.sequence - an ordered array of symbols
|
372 | * @param {string} swuSignObject.box - a choice of signbox marker: horizontal Box, Left, Middle, and Right lane
|
373 | * @param {number[]} swuSignObject.max - max bottom-right coordinate of the signbox space
|
374 | * @param {{symbol:string,coord:number[]}[]} swuSignObject.spatials - array of symbols with top-left coordinate placement
|
375 | * @param {string} swuSignObject.style - a style string for custom appearance
|
376 | * @returns {string} an swu sign string
|
377 | * @example
|
378 | * swu.compose.sign({
|
379 | * sequence: ['','','',''],
|
380 | * box: '𝠃',
|
381 | * max: [525, 535],
|
382 | * spatials: [
|
383 | * {
|
384 | * symbol: '',
|
385 | * coord: [483, 510]
|
386 | * },
|
387 | * {
|
388 | * symbol: '',
|
389 | * coord: [501, 466]
|
390 | * },
|
391 | * {
|
392 | * symbol: '',
|
393 | * coord: [510, 500]
|
394 | * },
|
395 | * {
|
396 | * symbol: '',
|
397 | * coord: [476, 475]
|
398 | * }
|
399 | * ],
|
400 | * style: '-C'
|
401 | * })
|
402 | *
|
403 | * return '𝠀𝠃𝤟𝤩𝣵𝤐𝤇𝣤𝤐𝤆𝣮𝣭-C'
|
404 | */
|
405 | sign: swuSignObject => {
|
406 | if (typeof swuSignObject !== 'object' || swuSignObject === null) return undefined;
|
407 | let box = typeof swuSignObject.box !== 'string' ? '𝠃' : (swuSignObject.box + '𝠃').match(re$1.box);
|
408 | const x = swuSignObject.max && swuSignObject.max[0] || '';
|
409 | const y = swuSignObject.max && swuSignObject.max[1] || '';
|
410 | const max = x && y ? coord2swu([x, y]) : undefined;
|
411 | if (!max) return undefined;
|
412 | let prefix = '';
|
413 |
|
414 | if (swuSignObject.sequence && Array.isArray(swuSignObject.sequence)) {
|
415 | prefix = swuSignObject.sequence.map(key => (key.match(re$1.symbol) || [''])[0]).join('');
|
416 | prefix = prefix ? '𝠀' + prefix : '';
|
417 | }
|
418 |
|
419 | let signbox = '';
|
420 |
|
421 | if (swuSignObject.spatials && Array.isArray(swuSignObject.spatials)) {
|
422 | signbox = swuSignObject.spatials.map(spatial => {
|
423 | if (typeof spatial.symbol === 'string') {
|
424 | const symbol = (spatial.symbol.match(re$1.symbol) || [''])[0];
|
425 |
|
426 | if (symbol) {
|
427 | const x = spatial.coord && spatial.coord[0] || '';
|
428 | const y = spatial.coord && spatial.coord[1] || '';
|
429 | const coord = x && y ? coord2swu([x, y]) : '';
|
430 |
|
431 | if (coord) {
|
432 | return symbol + coord;
|
433 | }
|
434 | }
|
435 | }
|
436 |
|
437 | return '';
|
438 | }).join('');
|
439 | }
|
440 |
|
441 | const styleStr = typeof swuSignObject.style === 'string' && (swuSignObject.style.match(re.full) || [''])[0] || '';
|
442 | return prefix + box + max + signbox + styleStr;
|
443 | }
|
444 | };
|
445 |
|
446 | /**
|
447 | * Function to gather sizing information about an swu sign or symbol
|
448 | * @function swu.info
|
449 | * @param {string} swu - an swu sign or symbol
|
450 | * @returns {object} information about the swu string
|
451 | * @example
|
452 | * swu.info('𝠀𝠂𝤘𝤣𝣳𝣩𝤉𝣻-P10Z2')
|
453 | *
|
454 | * return {
|
455 | * minX: 481,
|
456 | * minY: 471,
|
457 | * width: 37,
|
458 | * height: 58,
|
459 | * segment: 'sign',
|
460 | * lane: -1
|
461 | * padding: 10,
|
462 | * zoom: 2
|
463 | * }
|
464 | */
|
465 |
|
466 | const info = swu => {
|
467 | let lanes = {
|
468 | '𝠁': 0,
|
469 | '𝠂': -1,
|
470 | '𝠃': 0,
|
471 | '𝠄': 1
|
472 | };
|
473 | let parsed = parse.sign(swu);
|
474 | let width, height, segment, x1, x2, y1, y2, lane;
|
475 |
|
476 | if (parsed.spatials) {
|
477 | x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
|
478 | x2 = parsed.max[0];
|
479 | width = x2 - x1;
|
480 | y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
|
481 | y2 = parsed.max[1];
|
482 | height = y2 - y1;
|
483 | segment = 'sign';
|
484 | lane = parsed.box;
|
485 | } else {
|
486 | parsed = parse.symbol(swu);
|
487 | lane = "𝠃";
|
488 |
|
489 | if (parsed.coord) {
|
490 | x1 = parsed.coord[0];
|
491 | width = (500 - x1) * 2;
|
492 | y1 = parsed.coord[1];
|
493 | height = (500 - y1) * 2;
|
494 | segment = 'symbol';
|
495 | } else {
|
496 | x1 = 490;
|
497 | width = 20;
|
498 | y1 = 490;
|
499 | height = 20;
|
500 | segment = 'none';
|
501 | }
|
502 | }
|
503 |
|
504 | let style = parse$1(parsed.style);
|
505 | let zoom = style.zoom || 1;
|
506 | let padding = style.padding || 0;
|
507 | return {
|
508 | minX: x1,
|
509 | minY: y1,
|
510 | width: width,
|
511 | height: height,
|
512 | segment: segment,
|
513 | lane: lanes[lane],
|
514 | padding: padding,
|
515 | zoom: zoom
|
516 | };
|
517 | };
|
518 |
|
519 | const columnDefaults = {
|
520 | 'height': 500,
|
521 | 'width': 150,
|
522 | 'offset': 50,
|
523 | 'pad': 20,
|
524 | 'margin': 5,
|
525 | 'dynamic': false,
|
526 | 'background': undefined,
|
527 | 'punctuation': {
|
528 | 'spacing': true,
|
529 | 'pad': 30,
|
530 | 'pull': true
|
531 | },
|
532 | 'style': {
|
533 | 'detail': ['black', 'white'],
|
534 | 'zoom': 1
|
535 | }
|
536 | };
|
537 | /**
|
538 | * Function to an object of column options with default values
|
539 | *
|
540 | * @function swu.columnDefaultsMerge
|
541 | * @param {ColumnOptions} options - object of column options
|
542 | * @returns {ColumnOptions} object of column options merged with column defaults
|
543 | * @example
|
544 | * swu.columnDefaultsMerge({height: 500,width:150})
|
545 | *
|
546 | * return {
|
547 | * "height": 500,
|
548 | * "width": 150,
|
549 | * "offset": 50,
|
550 | * "pad": 20,
|
551 | * "margin": 5,
|
552 | * "dynamic": false,
|
553 | * "punctuation": {
|
554 | * "spacing": true,
|
555 | * "pad": 30,
|
556 | * "pull": true
|
557 | * },
|
558 | * "style": {
|
559 | * "detail": [
|
560 | * "black",
|
561 | * "white"
|
562 | * ],
|
563 | * "zoom": 1
|
564 | * }
|
565 | * }
|
566 | */
|
567 |
|
568 | const columnDefaultsMerge = options => {
|
569 | if (typeof options !== 'object') options = {};
|
570 | return { ...columnDefaults,
|
571 | ...options,
|
572 | punctuation: { ...columnDefaults.punctuation,
|
573 | ...options.punctuation
|
574 | },
|
575 | style: { ...columnDefaults.style,
|
576 | ...options.style
|
577 | }
|
578 | };
|
579 | };
|
580 | /**
|
581 | * Function to transform an SWU text to an array of columns
|
582 | *
|
583 | * @function swu.columns
|
584 | * @param {string} swuText - SWU text of signs and punctuation
|
585 | * @param {ColumnOptions} options - object of column options
|
586 | * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
|
587 | * @example
|
588 | * swu.columns('𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻 𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦 𝣢𝤂', {height: 500,width:150})
|
589 | *
|
590 | * return {
|
591 | * "options": {
|
592 | * "height": 500,
|
593 | * "width": 150,
|
594 | * "offset": 50,
|
595 | * "pad": 20,
|
596 | * "margin": 5,
|
597 | * "dynamic": false,
|
598 | * "punctuation": {
|
599 | * "spacing": true,
|
600 | * "pad": 30,
|
601 | * "pull": true
|
602 | * },
|
603 | * "style": {
|
604 | * "detail": [
|
605 | * "black",
|
606 | * "white"
|
607 | * ],
|
608 | * "zoom": 1
|
609 | * }
|
610 | * },
|
611 | * "widths": [
|
612 | * 150
|
613 | * ],
|
614 | * "columns": [
|
615 | * [
|
616 | * {
|
617 | * "x": 56,
|
618 | * "y": 20,
|
619 | * "minX": 481,
|
620 | * "minY": 471,
|
621 | * "width": 37,
|
622 | * "height": 58,
|
623 | * "lane": 0,
|
624 | * "padding": 0,
|
625 | * "segment": "sign",
|
626 | * "text": "𝠀𝠃𝤘𝤣𝣳𝣩𝤉𝣻",
|
627 | * "zoom": 1
|
628 | * },
|
629 | * {
|
630 | * "x": 57,
|
631 | * "y": 118,
|
632 | * "minX": 482,
|
633 | * "minY": 468,
|
634 | * "width": 36,
|
635 | * "height": 65,
|
636 | * "lane": 0,
|
637 | * "padding": 0,
|
638 | * "segment": "sign",
|
639 | * "text": "𝠀𝠃𝤘𝤧𝣻𝤕𝣴𝣼𝤎𝤂𝤆𝣦",
|
640 | * "zoom": 1
|
641 | * },
|
642 | * {
|
643 | * "x": 39,
|
644 | * "y": 203,
|
645 | * "minX": 464,
|
646 | * "minY": 496,
|
647 | * "width": 72,
|
648 | * "height": 8,
|
649 | * "lane": 0,
|
650 | * "padding": 0,
|
651 | * "segment": "symbol",
|
652 | * "text": "𝣢𝤂",
|
653 | * "zoom": 1
|
654 | * }
|
655 | * ]
|
656 | * ]
|
657 | * }
|
658 | */
|
659 |
|
660 |
|
661 | const columns = (swuText, options) => {
|
662 | if (typeof swuText !== 'string') return {};
|
663 | const values = columnDefaultsMerge(options);
|
664 | let input = parse.text(swuText);
|
665 | let cursor = 0;
|
666 | let cols = [];
|
667 | let col = [];
|
668 | let plus = 0;
|
669 | let center = parseInt(values.width / 2);
|
670 | let maxHeight = values.height - values.margin;
|
671 | let pullable = true;
|
672 | let finalize = false;
|
673 |
|
674 | for (let val of input) {
|
675 | let informed = info(val);
|
676 | cursor += plus;
|
677 |
|
678 | if (values.punctuation.spacing) {
|
679 | cursor += informed.segment == 'sign' ? values.pad : 0;
|
680 | } else {
|
681 | cursor += values.pad;
|
682 | }
|
683 |
|
684 | finalize = cursor + informed.height > maxHeight;
|
685 |
|
686 | if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
|
687 | finalize = false;
|
688 | pullable = false;
|
689 | }
|
690 |
|
691 | if (col.length == 0) {
|
692 | finalize = false;
|
693 | }
|
694 |
|
695 | if (finalize) {
|
696 | cursor = values.pad;
|
697 | cols.push(col);
|
698 | col = [];
|
699 | pullable = true;
|
700 | }
|
701 |
|
702 | col.push(Object.assign(informed, {
|
703 | x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
|
704 | y: cursor,
|
705 | text: val
|
706 | }));
|
707 | cursor += informed.height * informed.zoom * values.style.zoom;
|
708 |
|
709 | if (values.punctuation.spacing) {
|
710 | plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
|
711 | } else {
|
712 | plus = values.pad;
|
713 | }
|
714 | }
|
715 |
|
716 | if (col.length) {
|
717 | cols.push(col);
|
718 | } // over height issue when pulling punctuation
|
719 |
|
720 |
|
721 | if (values.punctuation.pull) {
|
722 | for (let col of cols) {
|
723 | let last = col[col.length - 1];
|
724 | let diff = last.y + last.height - (values.height - values.margin);
|
725 |
|
726 | if (diff > 0) {
|
727 | let adj = parseInt(diff / col.length) + 1;
|
728 |
|
729 | for (let i in col) {
|
730 | col[i].y -= adj * i + adj;
|
731 | }
|
732 | }
|
733 | }
|
734 | } // contract, expand, adjust
|
735 |
|
736 |
|
737 | let widths = [];
|
738 |
|
739 | for (let col of cols) {
|
740 | let min = [center - values.offset - values.pad];
|
741 | let max = [center + values.offset + values.pad];
|
742 |
|
743 | for (let item of col) {
|
744 | min.push(item.x - values.pad);
|
745 | max.push(item.x + item.width + values.pad);
|
746 | }
|
747 |
|
748 | min = Math.min(...min);
|
749 | max = Math.max(...max);
|
750 | let width = values.width;
|
751 | let adj = 0;
|
752 |
|
753 | if (!values.dynamic) {
|
754 | adj = center - parseInt((min + max) / 2);
|
755 | } else {
|
756 | width = max - min;
|
757 | adj = -min;
|
758 | }
|
759 |
|
760 | for (let item of col) {
|
761 | item.x += adj;
|
762 | }
|
763 |
|
764 | widths.push(width);
|
765 | }
|
766 |
|
767 | return {
|
768 | 'options': values,
|
769 | 'widths': widths,
|
770 | 'columns': cols
|
771 | };
|
772 | };
|
773 |
|
774 | /**
|
775 | * Array of plane 4 code points for kinds of symbols: writing, location, and punctuation.
|
776 | * @alias swu.kind
|
777 | * @type {array}
|
778 | */
|
779 |
|
780 | const kind = [0x40001, 0x4efa1, 0x4f2a1];
|
781 | /**
|
782 | * Array of plane 4 code points for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
|
783 | * @alias swu.category
|
784 | * @type {array}
|
785 | */
|
786 |
|
787 | const category = [0x40001, 0x461e1, 0x4bca1, 0x4bfa1, 0x4e8e1, 0x4efa1, 0x4f2a1];
|
788 | /**
|
789 | * Array of plane 4 code points for the 30 symbol groups.
|
790 | * @alias swu.group
|
791 | * @type {array}
|
792 | */
|
793 |
|
794 | const group = [0x40001, 0x40541, 0x40b41, 0x41981, 0x41c81, 0x43241, 0x43d81, 0x445c1, 0x44ce1, 0x45be1, 0x461e1, 0x46841, 0x46fc1, 0x47fe1, 0x485e1, 0x49301, 0x49e41, 0x4a4a1, 0x4afe1, 0x4b521, 0x4bca1, 0x4bfa1, 0x4c3c1, 0x4cfc1, 0x4d621, 0x4e161, 0x4e8e1, 0x4ec41, 0x4efa1, 0x4f2a1];
|
795 | /**
|
796 | * Object of symbol ranges with starting and ending code points on plane 4.
|
797 | *
|
798 | * { all, writing, hand, movement, dynamic, head, hcenter, vcenter, trunk, limb, location, punctuation }
|
799 | * @alias swu.ranges
|
800 | * @type {object}
|
801 | */
|
802 |
|
803 | const ranges = {
|
804 | 'all': [0x40001, 0x4f480],
|
805 | 'writing': [0x40001, 0x4efa0],
|
806 | 'hand': [0x40001, 0x461e0],
|
807 | 'movement': [0x461e1, 0x4bca0],
|
808 | 'dynamic': [0x4bca1, 0x4bfa0],
|
809 | 'head': [0x4bfa1, 0x4e8e0],
|
810 | 'hcenter': [0x4bfa1, 0x4e8e0],
|
811 | 'vcenter': [0x4bfa1, 0x4ec40],
|
812 | 'trunk': [0x4e8e1, 0x4ec40],
|
813 | 'limb': [0x4ec41, 0x4efa0],
|
814 | 'location': [0x4efa1, 0x4f2a0],
|
815 | 'punctuation': [0x4f2a1, 0x4f480]
|
816 | };
|
817 | /**
|
818 | * Function to test if symbol is of a certain type.
|
819 | * @function swu.isType
|
820 | * @param {string} swuSym - an SWU symbol character
|
821 | * @param {string} type - the name of a symbol range
|
822 | * @returns {boolean} is symbol of specified type
|
823 | * @example
|
824 | * swu.isType('', 'hand')
|
825 | *
|
826 | * return true
|
827 | */
|
828 |
|
829 | const isType = (swuSym, type) => {
|
830 | const parsed = parse.symbol(swuSym);
|
831 |
|
832 | if (parsed.symbol) {
|
833 | const code = swu2code(parsed.symbol);
|
834 | const range = ranges[type];
|
835 |
|
836 | if (range) {
|
837 | return range[0] <= code && range[1] >= code;
|
838 | }
|
839 | }
|
840 |
|
841 | return false;
|
842 | };
|
843 |
|
844 | /**
|
845 | * Array of colors associated with the seven symbol categories.
|
846 | * @alias swu.colors
|
847 | * @type {array}
|
848 | */
|
849 |
|
850 | const colors = ['#0000CC', '#CC0000', '#FF0099', '#006600', '#000000', '#884411', '#FF9900'];
|
851 | /**
|
852 | * Function that returns the standardized color for a symbol.
|
853 | * @function swu.colorize
|
854 | * @param {string} swuSym - an SWU symbol character
|
855 | * @returns {string} name of standardized color for symbol
|
856 | * @example
|
857 | * swu.colorize('')
|
858 | *
|
859 | * return '#0000CC'
|
860 | */
|
861 |
|
862 | const colorize = swuSym => {
|
863 | const parsed = parse.symbol(swuSym);
|
864 | let color = '#000000';
|
865 |
|
866 | if (parsed.symbol) {
|
867 | const code = swu2code(parsed.symbol);
|
868 | const index = category.findIndex(val => val > code);
|
869 | color = colors[index < 0 ? 6 : index - 1];
|
870 | }
|
871 |
|
872 | return color;
|
873 | };
|
874 |
|
875 | export { category, colorize, colors, columnDefaults, columnDefaultsMerge, columns, compose, decode, encode, group, info, isType, kind, pair, parse, ranges, re$1 as re };
|
876 |
|
877 | /* support ongoing development on https://patreon.com/signwriting */
|