@sutton-signwriting/core
Version: 
a javascript package for node and browsers that supports general processing of the Sutton SignWriting script
1,753 lines (1,557 loc) âĒ 103 kB
JavaScript
/**
* Sutton SignWriting Core Module v1.4.2 (https://github.com/sutton-signwriting/core)
* Author: Steve Slevinski  (https://SteveSlevinski.me)
* core.mjs is released under the MIT License.
*/
/**
 * Object of regular expressions for FSW strings
 * 
 * @alias fsw.re
 * @property {string} symbol - regular expressions for a symbol
 * @property {string} coord - regular expressions for a coordinate
 * @property {string} sort - regular expressions for the sorting marker
 * @property {string} box - regular expression for a signbox marker
 * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
 * @property {string} spatial - regular expression for a symbol followed by a coordinate
 * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
 * @property {string} sign - regular expression for an optional prefix followed by a signbox
 * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
 */
let re$4 = {
  '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$4.prefix = `(?:${re$4.sort}(?:${re$4.symbol})+)`;
re$4.spatial = `${re$4.symbol}${re$4.coord}`;
re$4.signbox = `${re$4.box}${re$4.coord}(?:${re$4.spatial})*`;
re$4.sign = `${re$4.prefix}?${re$4.signbox}`;
re$4.sortable = `${re$4.prefix}${re$4.signbox}`;
/**
 * Object of regular expressions for style strings
 * 
 * @alias style.re
 * @type {object}
 * @property {string} colorize - regular expression for colorize section
 * @property {string} colorhex - regular expression for color hex values with 3 or 6 characters
 * @property {string} colorname - regular expression for css color name
 * @property {string} padding - regular expression for padding section
 * @property {string} zoom - regular expression for zoom section
 * @property {string} classbase - regular expression for class name definition
 * @property {string} id - regular expression for id definition
 * @property {string} colorbase - regular expression for color hex or color name
 * @property {string} color - regular expression for single color entry
 * @property {string} colors - regular expression for double color entry
 * @property {string} background - regular expression for background section
 * @property {string} detail - regular expression for color details for line and optional fill
 * @property {string} detailsym - regular expression for color details for individual symbols
 * @property {string} classes - regular expression for one or more class names
 * @property {string} full - full regular expression for style string
 */
let re$3 = {
  '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)',
  'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
  'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
};
re$3.colorbase = `(?:${re$3.colorhex}|${re$3.colorname})`;
re$3.color = `_${re$3.colorbase}_`;
re$3.colors = `_${re$3.colorbase}(?:,${re$3.colorbase})?_`;
re$3.background = `G${re$3.color}`;
re$3.detail = `D${re$3.colors}`;
re$3.detailsym = `D[0-9]{2}${re$3.colors}`;
re$3.classes = `${re$3.classbase}(?: ${re$3.classbase})*`;
re$3.full = `-(${re$3.colorize})?(${re$3.padding})?(${re$3.background})?(${re$3.detail})?(${re$3.zoom})?(?:-((?:${re$3.detailsym})*))?(?:-(${re$3.classes})?!(?:(${re$3.id})!)?)?`;
const prefixColor = color => {
  const regex = new RegExp(`^${re$3.colorhex}$`);
  return (regex.test(color) ? '#' : '') + color;
};
const definedProps = obj => Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== undefined));
/**
 * Function to parse style string to object
 * @function style.parse
 * @param {string} styleString - a style string
 * @returns {StyleObject} elements of style string
 * @example
 * style.parse('-CP10G_blue_D_red,Cyan_')
 * 
 * return {
 *  'colorize': true,
 *  'padding': 10,
 *  'background': 'blue',
 *  'detail': ['red', 'Cyan']
 * }
 */
const parse$4 = styleString => {
  const regex = `^${re$3.full}`;
  const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
  return definedProps({
    '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$3.detailsym, 'g')).map(val => {
      const parts = val.split('_');
      const detail = parts[1].split(',').map(prefixColor);
      return {
        'index': parseInt(parts[0].slice(1)),
        'detail': detail
      };
    }),
    'classes': !m[7] ? undefined : m[7],
    'id': !m[8] ? undefined : m[8]
  });
};
/**
 * Function to compose style string from object
 * @function style.compose
 * @param {StyleObject} styleObject - an object of style options
 * @returns {string} style string 
 * @example 
 * style.compose({
 *  'colorize': true,
 *  'padding': 10,
 *  'background': 'blue',
 *  'detail': ['red', 'Cyan'],
 *  'zoom': 1.1,
 *  'detailsym': [
 *    {
 *      'index': 1,
 *      'detail': ['#ff00ff']
 *    },
 *    {
 *      'index': 2,
 *      'detail': ['yellow', 'green']
 *    }
 *  ],
 *  'classes': 'primary blinking',
 *  'id': 'cursor'
 * })
 *
 * return '-CP10G_blue_D_red,Cyan_Z1.1-D01_ff00ff_D02_yellow,green_-primary blinking!cursor!'
 */
const compose$4 = styleObject => {
  if (typeof styleObject !== 'object' || styleObject === null) return undefined; // three sections
  let style1 = '-';
  style1 += !styleObject.colorize ? '' : 'C';
  const padding = parseInt(styleObject.padding);
  style1 += !padding || padding <= 0 || padding > 99 ? '' : 'P' + (padding > 9 ? padding : '0' + padding);
  const background = !styleObject.background || !(typeof styleObject.background === 'string') ? undefined : styleObject.background.match(re$3.colorbase)[0];
  style1 += !background ? '' : 'G_' + background + '_';
  const detail1 = !styleObject.detail || !styleObject.detail[0] || !(typeof styleObject.detail[0] === 'string') ? undefined : styleObject.detail[0].match(re$3.colorbase)[0];
  const detail2 = !styleObject.detail || !styleObject.detail[1] || !(typeof styleObject.detail[1] === 'string') ? undefined : styleObject.detail[1].match(re$3.colorbase)[0];
  if (detail1) {
    style1 += 'D_' + detail1;
    if (detail2) {
      style1 += ',' + detail2;
    }
    style1 += '_';
  }
  const zoom = styleObject.zoom === 'x' ? 'x' : parseFloat(styleObject.zoom);
  style1 += !zoom || zoom <= 0 ? '' : 'Z' + zoom;
  let style2 = '';
  const detailsym = !styleObject.detailsym || !Array.isArray(styleObject.detailsym) ? [] : styleObject.detailsym.map(styleObject => {
    const index = parseInt(styleObject.index);
    if (!index || index <= 0 || index > 99) return '';
    let style = 'D' + (index > 9 ? index : '0' + index);
    const detail1 = !styleObject.detail || !styleObject.detail[0] ? undefined : styleObject.detail[0].match(re$3.colorbase)[0];
    const detail2 = !styleObject.detail || !styleObject.detail[1] ? undefined : styleObject.detail[1].match(re$3.colorbase)[0];
    if (detail1) {
      style += '_' + detail1;
      if (detail2) {
        style += ',' + detail2;
      }
      style += '_';
    }
    return style;
  });
  style2 += detailsym.join('');
  let style3 = '';
  const classes = !styleObject.classes || !(typeof styleObject.classes === 'string') ? undefined : styleObject.classes.match(re$3.classes)[0];
  style3 += !classes ? '' : classes;
  const id = !styleObject.id || !(typeof styleObject.id === 'string') ? undefined : styleObject.id.match(re$3.id)[0];
  style3 += classes || id ? '!' : '';
  style3 += !id ? '' : id + '!';
  return style1 + (style2 || style3 ? '-' + style2 : '') + (style3 ? '-' + style3 : '');
};
/** The style module contains regular expressions and functions for parsing and composing style strings.
 * [Style string definition](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-styling-string)
 * @module style
 */
var index$5 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  re: re$3,
  parse: parse$4,
  compose: compose$4
});
/**
 * Object of regular expressions for SWU strings in UTF-16
 * 
 * @alias swu.re
 * @property {string} symbol - regular expressions for a symbol
 * @property {string} coord - regular expressions for a coordinate
 * @property {string} sort - regular expressions for the sorting marker
 * @property {string} box - regular expression for a signbox marker
 * @property {string} prefix - regular expression for a sorting marker followed by one or more symbols
 * @property {string} spatial - regular expression for a symbol followed by a coordinate
 * @property {string} signbox - regular expression for a signbox marker, max coordinate and zero or more spatial symbols
 * @property {string} sign - regular expression for an optional prefix followed by a signbox
 * @property {string} sortable - regular expression for a mandatory prefix followed by a signbox
 */
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}`;
/** 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-09.html#name-characters)
 * @module convert
 */
/**
 * Function to convert an SWU structural marker to FSW equivalent
 * @function convert.swu2mark
 * @param {string} swuMark - character for SWU structural marker
 * @returns {string} FSW structural marker
 * @example
 * convert.swu2mark('ð ')
 * 
 * return 'A'
 */
const swu2mark = swuMark => {
  return {
    'ð ': 'A',
    'ð ': 'B',
    'ð ': 'L',
    'ð ': 'M',
    'ð ': 'R'
  }[swuMark];
};
/**
 * Function to convert an FSW structural marker to SWU equivalent
 * @function convert.mark2swu
 * @param {string} fswMark - character for FSW structural marker
 * @returns {string} SWU structural marker
 * @example
 * convert.mark2swu('A')
 * 
 * return 'ð '
 */
const mark2swu = fswMark => {
  return {
    'A': 'ð ',
    'B': 'ð ',
    'L': 'ð ',
    'M': 'ð ',
    'R': 'ð '
  }[fswMark];
};
/**
 * 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 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 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 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 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));
/**
 * Function to convert an array of x,y integers to an FSW coordinate string
 * @function convert.coord2fsw
 * @param {number[]} coord - Array of x,y integers
 * @returns {string} An FSW coordinate string
 * @example
 * convert.coord2fsw([500, 500])
 * 
 * return '500x500'
 */
const coord2fsw = coord => coord.join('x');
/**
 * 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 a code point on plane 4 to an SWU symbol character
 * @function convert.code2swu
 * @param {number} code - Code point on plane 4
 * @returns {string} SWU symbol character
 * @example
 * convert.code2swu(0x40001)
 * 
 * return 'ņ'
 */
const code2swu = code => String.fromCodePoint(code);
/**
 * 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 a 16-bit ID to an SWU symbol character
 * @function convert.id2swu
 * @param {number} id - 16-bit ID
 * @returns {string} SWU symbol character
 * @example
 * convert.id2swu(1)
 * 
 * return 'ņ'
 */
const id2swu = id => code2swu(id + 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);
/**
 * Function to convert a 16-bit ID to an FSW symbol key
 * @function convert.id2key
 * @param {number} id - 16-bit ID
 * @returns {string} FSW symbol key
 * @example
 * convert.id2key(1)
 * 
 * return 'S10000'
 */
const id2key = id => {
  const symcode = id - 1;
  const base = parseInt(symcode / 96);
  const fill = parseInt((symcode - base * 96) / 16);
  const rotation = parseInt(symcode - base * 96 - fill * 16);
  return 'S' + (base + 0x100).toString(16) + fill.toString(16) + rotation.toString(16);
};
/**
 * Function to convert an SWU symbol character to an FSW symbol key
 * @function convert.swu2key
 * @param {string} swuSym - SWU symbol character
 * @returns {string} FSW symbol key
 * @example
 * convert.swu2key('ņ')
 * 
 * return 'S10000'
 */
const swu2key = swuSym => {
  const symcode = swu2code(swuSym) - 0x40001;
  const base = parseInt(symcode / 96);
  const fill = parseInt((symcode - base * 96) / 16);
  const rotation = parseInt(symcode - base * 96 - fill * 16);
  return 'S' + (base + 0x100).toString(16) + fill.toString(16) + rotation.toString(16);
};
/**
 * Function to convert an FSW symbol key to an SWU symbol character
 * @function convert.key2swu
 * @param {string} key - FSW symbol key
 * @returns {string} SWU symbol character
 * @example
 * convert.key2swu('S10000')
 * 
 * return 'ņ'
 */
const key2swu = key => code2swu(0x40001 + (parseInt(key.slice(1, 4), 16) - 256) * 96 + parseInt(key.slice(4, 5), 16) * 16 + parseInt(key.slice(5, 6), 16));
/**
 * Function to convert SWU text to FSW text
 * @function convert.swu2fsw
 * @param {string} swuText - SWU text
 * @returns {string} FSW text
 * @example
 * convert.swu2fsw('ð ņņņĨņĐð ðĪðĪĐņĐðĢĩðĪņðĪðĢĪņĨðĪðĪņðĢŪðĢ')
 * 
 * return 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
 */
const swu2fsw = swuText => {
  if (!swuText) return '';
  let fsw = swuText.replace(/ð /g, "A").replace(/ð /g, "B").replace(/ð /g, "L").replace(/ð /g, "M").replace(/ð /g, "R");
  const syms = fsw.match(new RegExp(re$2.symbol, 'g'));
  if (syms) {
    syms.forEach(function (sym) {
      fsw = fsw.replace(sym, swu2key(sym));
    });
  }
  const coords = fsw.match(new RegExp(re$2.coord, 'g'));
  if (coords) {
    coords.forEach(function (coord) {
      fsw = fsw.replace(coord, swu2coord(coord).join('x'));
    });
  }
  return fsw;
};
/**
 * Function to convert FSW text to SWU text
 * @function convert.fsw2swu
 * @param {string} fswText - FSW text
 * @returns {string} SWU text
 * @example
 * convert.fsw2swu('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
 * 
 * return 'ð ņņņĨņĐð ðĪðĪĐņĐðĢĩðĪņðĪðĢĪņĨðĪðĪņðĢŪðĢ'
 */
const fsw2swu = fswText => {
  if (!fswText) return '';
  const prefixes = fswText.match(new RegExp(re$4.prefix, 'g'));
  if (prefixes) {
    prefixes.forEach(function (prefix) {
      fswText = fswText.replace(prefix, 'ð ' + prefix.slice(1).match(/.{6}/g).map(key => key2swu(key)).join(''));
    });
  }
  const boxes = fswText.match(new RegExp(re$4.box + re$4.coord, 'g'));
  if (boxes) {
    boxes.forEach(function (boxes) {
      fswText = fswText.replace(boxes, mark2swu(boxes.slice(0, 1)) + coord2swu(fsw2coord(boxes.slice(1, 8))));
    });
  }
  const spatials = fswText.match(new RegExp(re$4.spatial, 'g'));
  if (spatials) {
    spatials.forEach(function (spatial) {
      fswText = fswText.replace(spatial, key2swu(spatial.slice(0, 6)) + coord2swu(fsw2coord(spatial.slice(6, 13))));
    });
  }
  return fswText;
};
var index$4 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  swu2mark: swu2mark,
  mark2swu: mark2swu,
  swu2num: swu2num,
  num2swu: num2swu,
  swu2coord: swu2coord,
  coord2swu: coord2swu,
  fsw2coord: fsw2coord,
  coord2fsw: coord2fsw,
  swu2code: swu2code,
  code2swu: code2swu,
  swu2id: swu2id,
  id2swu: id2swu,
  key2id: key2id,
  id2key: id2key,
  swu2key: swu2key,
  key2swu: key2swu,
  swu2fsw: swu2fsw,
  fsw2swu: fsw2swu
});
const parse$3 = {
  /**
   * 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$4.symbol})(${re$4.coord})?(${re$3.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$4.prefix})?(${re$4.signbox})(${re$3.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 {};
    }
  },
  /**
   * Function to parse an fsw text
   * @function fsw.parse.text
   * @param {string} fswText - an fsw text
   * @returns {array} fsw signs and punctuations
   * @example
   * fsw.parse.text('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496')
   * 
   * return [
   *  'AS14c20S27106M518x529S14c20481x471S27106503x489',
   *  'AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468',
   *  'S38800464x496'
   * ]
   */
  text: fswText => {
    if (typeof fswText !== 'string') return [];
    const regex = `(${re$4.sign}(${re$3.full})?|${re$4.spatial}(${re$3.full})?)`;
    const matches = fswText.match(new RegExp(regex, 'g'));
    return matches ? [...matches] : [];
  }
};
const compose$3 = {
  /**
   * Function to compose an fsw symbol with optional coordinate and style string
   * @function fsw.compose.symbol
   * @param {object} fswSymObject - an fsw symbol object
   * @param {string} fswSymObject.symbol - an fsw symbol key
   * @param {number[]} fswSymObject.coord - top-left coordinate of symbol
   * @param {string} fswSymObject.style - a style string for custom appearance
   * @returns {string} an fsw symbol string
   * @example
   * fsw.compose.symbol({
   *  'symbol': 'S10000',
   *  'coord': [480, 480],
   *  'style': '-C'
   * })
   * 
   * return 'S10000480x480-C'
   */
  symbol: fswSymObject => {
    if (typeof fswSymObject.symbol === 'string') {
      const symbol = (fswSymObject.symbol.match(re$4.symbol) || [''])[0];
      if (symbol) {
        const x = (fswSymObject.coord && fswSymObject.coord[0] || '').toString();
        const y = (fswSymObject.coord && fswSymObject.coord[1] || '').toString();
        const coord = ((x + 'x' + y).match(re$4.coord) || [''])[0] || '';
        const styleStr = typeof fswSymObject.style === 'string' && (fswSymObject.style.match(re$3.full) || [''])[0] || '';
        return symbol + coord + styleStr;
      }
    }
    return undefined;
  },
  /**
   * Function to compose an fsw sign with style string
   * @function fsw.compose.sign
   * @param {object} fswSymObject - an fsw sign object
   * @param {string[]} fswSignObject.sequence - an ordered array of symbols
   * @param {string} fswSignObject.box - a choice BLMR: horizontal Box, Left, Middle, and Right lane
   * @param {number[]} fswSignObject.max - max bottom-right coordinate of the signbox space
   * @param {{symbol:string,coord:number[]}[]} fswSignObject.spatials - array of symbols with top-left coordinate placement
   * @param {string} fswSignObject.style - a style string for custom appearance
   * @returns {string} an fsw sign string
   * @example
   * fsw.compose.sign({
   *  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'
   * })
   * 
   * return 'AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475-C'
   */
  sign: fswSignObject => {
    let box = typeof fswSignObject.box !== 'string' ? 'M' : (fswSignObject.box + 'M').match(re$4.box);
    const x = (fswSignObject.max && fswSignObject.max[0] || '').toString();
    const y = (fswSignObject.max && fswSignObject.max[1] || '').toString();
    const max = ((x + 'x' + y).match(re$4.coord) || [''])[0] || '';
    if (!max) return undefined;
    let prefix = '';
    if (fswSignObject.sequence && Array.isArray(fswSignObject.sequence)) {
      prefix = fswSignObject.sequence.map(key => (key.match(re$4.symbol) || [''])[0]).join('');
      prefix = prefix ? 'A' + prefix : '';
    }
    let signbox = '';
    if (fswSignObject.spatials && Array.isArray(fswSignObject.spatials)) {
      signbox = fswSignObject.spatials.map(spatial => {
        if (typeof spatial.symbol === 'string') {
          const symbol = (spatial.symbol.match(re$4.symbol) || [''])[0];
          if (symbol) {
            const x = (spatial.coord && spatial.coord[0] || '').toString();
            const y = (spatial.coord && spatial.coord[1] || '').toString();
            const coord = ((x + 'x' + y).match(re$4.coord) || [''])[0] || '';
            if (coord) {
              return symbol + coord;
            }
          }
        }
        return '';
      }).join('');
    }
    const styleStr = typeof fswSignObject.style === 'string' && (fswSignObject.style.match(re$3.full) || [''])[0] || '';
    return prefix + box + max + signbox + styleStr;
  }
};
/**
 * Function to gather sizing information about an fsw sign or symbol
 * @function fsw.info
 * @param {string} fsw - an fsw sign or symbol
 * @returns {object} information about the fsw string
 * @example
 * fsw.info('AS14c20S27106L518x529S14c20481x471S27106503x489-P10Z2')
 * 
 * return {
 *   minX: 481,
 *   minY: 471,
 *   width: 37,
 *   height: 58,
 *   zoom: 2,
 *   padding: 10,
 *   segment: 'sign',
 *   lane: -1
 * }
 */
const info$1 = fsw => {
  let lanes = {
    "B": 0,
    "L": -1,
    "M": 0,
    "R": 1
  };
  let parsed = parse$3.sign(fsw);
  let width, height, segment, x1, x2, y1, y2, lane;
  if (parsed.spatials) {
    x1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[0]));
    x2 = parsed.max[0];
    width = x2 - x1;
    y1 = Math.min(...parsed.spatials.map(spatial => spatial.coord[1]));
    y2 = parsed.max[1];
    height = y2 - y1;
    segment = 'sign';
    lane = parsed.box;
  } else {
    parsed = parse$3.symbol(fsw);
    lane = "M";
    if (parsed.coord) {
      x1 = parsed.coord[0];
      width = (500 - x1) * 2;
      y1 = parsed.coord[1];
      height = (500 - y1) * 2;
      segment = 'symbol';
    } else {
      x1 = 490;
      width = 20;
      y1 = 490;
      height = 20;
      segment = 'none';
    }
  }
  let style = parse$4(parsed.style);
  let zoom = style.zoom || 1;
  let padding = style.padding || 0;
  return {
    minX: x1,
    minY: y1,
    width: width,
    height: height,
    segment: segment,
    lane: lanes[lane],
    padding: padding,
    zoom: zoom
  };
};
const columnDefaults$1 = {
  'height': 500,
  'width': 150,
  'offset': 50,
  'pad': 20,
  'margin': 5,
  'dynamic': false,
  'background': undefined,
  'punctuation': {
    'spacing': true,
    'pad': 30,
    'pull': true
  },
  'style': {
    'detail': ['black', 'white'],
    'zoom': 1
  }
};
/**
 * Function to an object of column options with default values
 * 
 * @function fsw.columnDefaultsMerge
 * @param {ColumnOptions} options - object of column options
 * @returns {ColumnOptions} object of column options merged with column defaults
 * @example
 * fsw.columnDefaultsMerge({height: 500,width:150})
 * 
 * return {
 *   "height": 500,
 *   "width": 150,
 *   "offset": 50,
 *   "pad": 20,
 *   "margin": 5,
 *   "dynamic": false,
 *   "punctuation": {
 *     "spacing": true,
 *     "pad": 30,
 *     "pull": true
 *   },
 *   "style": {
 *     "detail": [
 *       "black",
 *       "white"
 *     ],
 *     "zoom": 1
 *   }
 * }
 */
const columnDefaultsMerge$1 = options => {
  if (typeof options !== 'object') options = {};
  return { ...columnDefaults$1,
    ...options,
    punctuation: { ...columnDefaults$1.punctuation,
      ...options.punctuation
    },
    style: { ...columnDefaults$1.style,
      ...options.style
    }
  };
};
/**
 * Function to transform an FSW text to an array of columns
 * 
 * @function fsw.columns
 * @param {string} fswText - FSW text of signs and punctuation
 * @param {ColumnOptions} options - object of column options
 * @returns {{options:ColumnOptions,widths:number[],columns:ColumnData}} object of column options, widths array, and column data
 * @example
 * fsw.columns('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496', {height: 500,width:150})
 * 
 * return {
 *   "options": {
 *     "height": 500,
 *     "width": 150,
 *     "offset": 50,
 *     "pad": 20,
 *     "margin": 5,
 *     "dynamic": false,
 *     "punctuation": {
 *       "spacing": true,
 *       "pad": 30,
 *       "pull": true
 *     },
 *     "style": {
 *       "detail": [
 *         "black",
 *         "white"
 *       ],
 *       "zoom": 1
 *     }
 *   },
 *   "widths": [
 *     150
 *   ],
 *   "columns": [
 *     [
 *       {
 *         "x": 56,
 *         "y": 20,
 *         "minX": 481,
 *         "minY": 471,
 *         "width": 37,
 *         "height": 58,
 *         "lane": 0,
 *         "padding": 0,
 *         "segment": "sign",
 *         "text": "AS14c20S27106M518x529S14c20481x471S27106503x489",
 *         "zoom": 1
 *       },
 *       {
 *         "x": 57,
 *         "y": 118,
 *         "minX": 482,
 *         "minY": 468,
 *         "width": 36,
 *         "height": 65,
 *         "lane": 0,
 *         "padding": 0,
 *         "segment": "sign",
 *         "text": "AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468",
 *         "zoom": 1
 *       },
 *       {
 *         "x": 39,
 *         "y": 203,
 *         "minX": 464,
 *         "minY": 496,
 *         "width": 72,
 *         "height": 8,
 *         "lane": 0,
 *         "padding": 0,
 *         "segment": "symbol",
 *         "text": "S38800464x496",
 *         "zoom": 1
 *       }
 *     ]
 *   ]
 * }
 */
const columns$1 = (fswText, options) => {
  if (typeof fswText !== 'string') return {};
  const values = columnDefaultsMerge$1(options);
  let input = parse$3.text(fswText);
  let cursor = 0;
  let cols = [];
  let col = [];
  let plus = 0;
  let center = parseInt(values.width / 2);
  let maxHeight = values.height - values.margin;
  let pullable = true;
  let finalize = false;
  for (let val of input) {
    let informed = info$1(val);
    cursor += plus;
    if (values.punctuation.spacing) {
      cursor += informed.segment == 'sign' ? values.pad : 0;
    } else {
      cursor += values.pad;
    }
    finalize = cursor + informed.height > maxHeight;
    if (finalize && informed.segment == 'symbol' && values.punctuation.pull && pullable) {
      finalize = false;
      pullable = false;
    }
    if (col.length == 0) {
      finalize = false;
    }
    if (finalize) {
      cursor = values.pad;
      cols.push(col);
      col = [];
      pullable = true;
    }
    col.push(Object.assign(informed, {
      x: center + values.offset * informed.lane - (500 - informed.minX) * informed.zoom * values.style.zoom,
      y: cursor,
      text: val
    }));
    cursor += informed.height * informed.zoom * values.style.zoom;
    if (values.punctuation.spacing) {
      plus = informed.segment == 'sign' ? values.pad : values.punctuation.pad;
    } else {
      plus = values.pad;
    }
  }
  if (col.length) {
    cols.push(col);
  } // over height issue when pulling punctuation
  if (values.punctuation.pull) {
    for (let col of cols) {
      let last = col[col.length - 1];
      let diff = last.y + last.height - (values.height - values.margin);
      if (diff > 0) {
        let adj = parseInt(diff / col.length) + 1;
        for (let i in col) {
          col[i].y -= adj * i + adj;
        }
      }
    }
  } // contract, expand, adjust
  let widths = [];
  for (let col of cols) {
    let min = [center - values.offset - values.pad];
    let max = [center + values.offset + values.pad];
    for (let item of col) {
      min.push(item.x - values.pad);
      max.push(item.x + item.width + values.pad);
    }
    min = Math.min(...min);
    max = Math.max(...max);
    let width = values.width;
    let adj = 0;
    if (!values.dynamic) {
      adj = center - parseInt((min + max) / 2);
    } else {
      width = max - min;
      adj = -min;
    }
    for (let item of col) {
      item.x += adj;
    }
    widths.push(width);
  }
  return {
    'options': values,
    'widths': widths,
    'columns': cols
  };
};
/**
 * Array of numbers for kinds of symbols: writing, location, and punctuation.
 * @alias fsw.kind
 * @type {array}
 */
const kind$1 = [0x100, 0x37f, 0x387];
/**
 * Array of numbers for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
 * @alias fsw.category
 * @type {array}
 */
const category$1 = [0x100, 0x205, 0x2f7, 0x2ff, 0x36d, 0x37f, 0x387];
/**
 * Array of numbers for the 30 symbol groups.
 * @alias fsw.group
 * @type {array}
 */
const group$1 = [0x100, 0x10e, 0x11e, 0x144, 0x14c, 0x186, 0x1a4, 0x1ba, 0x1cd, 0x1f5, 0x205, 0x216, 0x22a, 0x255, 0x265, 0x288, 0x2a6, 0x2b7, 0x2d5, 0x2e3, 0x2f7, 0x2ff, 0x30a, 0x32a, 0x33b, 0x359, 0x36d, 0x376, 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$1 = {
  '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]
};
/**
 * Function to test if symbol is of a certain type.
 * @function fsw.isType
 * @param {string} key - an FSW symbol key
 * @param {string} type - the name of a symbol range
 * @returns {boolean} is symbol of specified type
 * @example
 * fsw.isType('S10000', 'hand')
 * 
 * return true
 */
const isType$1 = (key, type) => {
  const parsed = parse$3.symbol(key);
  if (parsed.symbol) {
    const dec = parseInt(parsed.symbol.slice(1, 4), 16);
    const range = ranges$1[type];
    if (range) {
      return range[0] <= dec && range[1] >= dec;
    }
  }
  return false;
};
/**
 * Array of colors associated with the seven symbol categories.
 * @alias fsw.colors
 * @type {array}
 */
const colors$1 = ['#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$1 = key => {
  const parsed = parse$3.symbol(key);
  let color = '#000000';
  if (parsed.symbol) {
    const dec = parseInt(parsed.symbol.slice(1, 4), 16);
    const index = category$1.findIndex(val => val > dec);
    color = colors$1[index < 0 ? 6 : index - 1];
  }
  return color;
};
/** 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-09.html#name-formal-signwriting-in-ascii)
 * @module fsw
 */
var index$3 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  re: re$4,
  parse: parse$3,
  compose: compose$3,
  info: info$1,
  columnDefaults: columnDefaults$1,
  columnDefaultsMerge: columnDefaultsMerge$1,
  columns: columns$1,
  kind: kind$1,
  category: category$1,
  group: group$1,
  ranges: ranges$1,
  isType: isType$1,
  colors: colors$1,
  colorize: colorize$1
});
/**
 * Object of regular expressions for FSW query strings
 * 
 *   { base, coord, var, symbol, range, item, list, prefix, signbox, full }
 * @alias fswquery.re
 * @type {object}
 */
let re$1 = {
  'base': '[123][0-9a-f]{2}',
  'coord': '(?:[0-9]{3}x[0-9]{3})?',
  'var': 'V[0-9]+'
};
re$1.symbol = `S${re$1.base}[0-5u][0-9a-fu]`;
re$1.range = `R${re$1.base}t${re$1.base}`;
re$1.item = `(?:${re$1.symbol}|${re$1.range})`;
re$1.list = `${re$1.item}(?:o${re$1.item})*`;
re$1.prefix = `(?:A(?:${re$1.list})+)?T`;
re$1.signbox = `(?:${re$1.list}${re$1.coord})*`;
re$1.full = `Q(${re$1.prefix})?(${re$1.signbox})?(${re$1.var})?(-?)`;
const parsePrefix$1 = text => {
  return {
    required: true,
    parts: text == 'T' ? undefined : text.match(new RegExp(`${re$1.list}`, 'g')).map(part => {
      if (part.includes('o')) {
        return ['or'].concat(part.match(new RegExp(`(${re$1.item})`, 'g')).map(part => part[0] == 'S' ? part : part.slice(1).split('t')));
      } else {
        return part[0] == 'S' ? part : part.slice(1).split('t');
      }
    })
  };
};
const parseSignbox$1 = text => {
  return text.match(new RegExp(`(${re$1.list}${re$1.coord})`, 'g')).map(part => {
    let coord, front;
    if (part.includes('x')) {
      coord = fsw2coord(part.slice(-7));
      front = part.slice(0, -7);
    } else {
      front = part;
    }
    if (front.includes('o')) {
      return {
        or: front.split('o').map(part => {
          if (part.includes('S')) {
            return part;
          } else {
            return part.slice(1).split('t');
          }
        }),
        coord,
        coord
      };
    } else if (front.includes('S')) {
      return {
        symbol: front,
        coord: coord
      };
    } else {
      return {
        range: front.slice(1).split('t'),
        coord: coord
      };
    }
  });
};
/**
 * Function to parse FSW query string to object
 * @function fswquery.parse
 * @param {string} fswQueryString - an FSW query string
 * @returns {object} elements of an FSW query string
 * @example
 * fswquery.parse('QAS10000S10500oS20500oR2fft304TS100uuR205t206oS207uu510x510V5-')
 * 
 * return {
 *   "query": true,
 *   "prefix": {
 *     "required": true,
 *     "parts": [
 *       "S10000",
 *       [
 *         "or",
 *         "S10500",
 *         "S20500",
 *         [
 *           "2ff",
 *           "304"
 *         ]
 *       ]
 *     ]
 *   },
 *   "signbox": [
 *     {
 *       "symbol": "S100uu"
 *     },
 *     {
 *       "or": [
 *         [
 *           "205",
 *           "206"
 *         ],
 *         "S207uu"
 *       ],
 *       "coord": [
 *         510,
 *         510
 *       ]
 *     }
 *   ],
 *   "variance": 5,
 *   "style": true
 * }
 */
const parse$2 = fswQueryString => {
  const query = typeof fswQueryString === 'string' ? fswQueryString.match(new RegExp(`^${re$1.full}`)) : undefined;
  return {
    'query': query ? true : undefined,
    'prefix': query && query[1] ? parsePrefix$1(query[1]) : undefined,
    'signbox': query && query[2] ? parseSignbox$1(query[2]) : undefined,
    'variance': query && query[3] ? parseInt(query[3].slice(1)) : undefined,
    'style': query && query[4] ? true : undefined
  };
};
/**
 * Function to compose FSW query string from object
 * @function fswquery.compose
 * @param {object} fswQueryObject - an object of style options
 * @param {boolean} fswQueryObject.query - required true for FSW query object
 * @param {object} fswQueryObject.prefix - an object for prefix elements
 * @param {boolean} fswQueryObject.prefix.required - true if sorting prefix is required
 * @param {(string|string[]|(string|string[])[])[]} fswQueryObject.prefix.parts - array of symbol strings, range arrays, and OR arrays of strings and range arrays
 * @param {({symbol:string,coord:number[]}|{range:string[],coord:number[]}|{or:(string|string[])[],coord:number[]})[]} fswQueryObject.signbox - array of objects for symbols, ranges, and list of symbols or ranges, with optional coordinates
 * @param {number} fswQueryObject.variance - amount that x or y coordinates can vary and find a match, defaults to 20
 * @param {boolean} fswQueryObject.style - boolean value for including style string in matches
 * @returns {string} FSW query string 
 * @example 
 * fswquery.compose({
 *  query: true,
 *  prefix: {
 *    required: true,
 *    parts: [
 *      'S10000',
 *      ['100', '204'],
 *      'S20500'
 *    ]
 *  },
 *  signbox: [
 *    { symbol: 'S20000' },
 *    {
 *      range: ['100', '105'],
 *      coord: [500, 500]
 *    }
 *  ],
 *  variance: 5,
 *  style: true
 * })
 *
 * return 'QAS10000R100t204S20500TS20000R100t105500x500V5-'
 */
const compose$2 = fswQueryObject => {
  if (!fswQueryObject || !fswQueryObject.query) {
    return undefined;
  }
  let query = 'Q';
  if (fswQueryObject.prefix && fswQueryObject.prefix.required) {
    if (Array.isArray(fswQueryObject.prefix.parts)) {
      query += 'A';
      query += fswQueryObject.prefix.parts.map(part => {
        if (typeof part === 'string') {
          return part;
        } else {
          if (Array.isArray(part) && part.length == 2) {
            return `R${part[0]}t${part[1]}`;
          } else if (Array.isArray(part) && part.length > 2 && part[0] == 'or') {
            part.shift();
            return part.map(part => {
              if (typeof part === 'string') {
                return part;
              } else {
                if (Array.isArray(part) && part.length == 2) {
                  return `R${part[0]}t${part[1]}`;
                }
              }
            }).join('o');
          }
        }
      }).join('');
    }
    query += 'T';
  }
  if (Array.isArray(fswQueryObject.signbox)) {
    query += fswQueryObject.signbox.map(part => {
      let out;
      if (part.or) {
        out = part.or.map(item => {
          if (typeof item === 'string') {
            return item;
          } else {
            if (Array.isArray(item) && item.length == 2) {
              return `R${item[0]}t${item[1]}`;
            }
          }
        }).join('o');
      } else if (part.symbol) {
        out = part.symbol;
      } else {
        if (part.range && Array.isArray(part.range) && part.range.length == 2) {
          out = `R${part.range[0]}t${part.range[1]}`;
        }
      }
      return out + (Array.isArray(part.coord) && part.coord.length == 2 ? part.coord.join('x') : '');
    }).join('');
  }
  query += fswQueryObject.style ? '-' : '';
  query = query.match(new RegExp(`^${re$1.full}`))[0];
  return query;
};
/**
 * Function to convert an FSW sign to a query string
 * 
 * For the flags parameter, use one or more of the following.
 * - A: exact symbol in temporal prefix
 * - a: general symbol in temporal prefix
 * - S: exact symbol in spatial signbox
 * - s: general symbol in spatial signbox
 * - L: spatial signbox symbol at location
 * @function fswquery.fsw2query
 * @param {string} fswSign - FSW sign
 * @param {string} flags - flags for query string creation
 * @returns {string} FSW query string
 * @example
 * fswquery.fsw2query('AS10011S10019S2e704S2e748M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475', 'ASL')
 * 
 * return 'QAS10011S10019S2e704S2e748TS2e748483x510S10011501x466S2e704510x500S10019476x475'
 */
const fsw2query = (fswSign, flags) => {
  let query = '';
  const parsed = parse$3.sign(fswSign);
  if (parsed.box) {
    const A_flag = flags.indexOf('A') > -1;
    const a_flag = flags.indexOf('a') > -1;
    const S_flag = flags.indexOf('S') > -1;
    const s_flag = flags.indexOf('s') > -1;
    const L_flag = flags.indexOf('L') > -1;
    if (A_flag || a_flag || S_flag || s_flag) {
      if ((A_flag || a_flag) && parsed.sequence) {
        query += 'A';
        query += parsed.sequence.map(sym => sym.slice(0, 4) + (a_flag ? 'uu' : sym.slice(4, 6))).join('');
        query += 'T';
      }
      if ((S_flag || s_flag) && parsed.spatials) {
        query += parsed.spatials.map(spatial => spatial.symbol.slice(0, 4) + (s_flag ? 'uu' : spatial.symbol.slice(4, 6)) + (L_flag ? spatial.coord.join('x') : '')).join('');
      }
    }
    return query ? "Q" + query : undefined;
  } else {
    return undefined;
  }
};
//needs rewritten, but it works
/**
 * Function to transform a range to a regular expression
 * @function fswquery.range
 * @param {(number|string)} min - either a decimal number or hexidecimal string
 * @param {(number|string)} max - either a decimal number or hexidecimal string
 * @param {boolean?} hex - if true, the regular expression will match a hexidecimal range
 * @returns {string} a regular expression that matches a range
 * @example
 * fswquery.range(500,750)
 * 
 * return '(([56][0-9][0-9])|(7[0-4][0-9])|(750))'
 * @example
 * fswquery.range('100','10e',true)
 * 
 * return '10[0-9a-e]'
 */
const range$1 = (min, max, hex) => {
  let pattern;
  let re;
  let diff;
  let tmax;
  let cnt;
  let minV;
  let maxV;
  if (!hex) {
    hex = '';
  }
  min = ("000" + min).slice(-3);
  max = '' + max;
  pattern = '';
  if (min === max) {
    return min;
  } //ending pattern will be series of connected OR ranges
  re = []; //first pattern+  10's don't match and the min 1's are not zero
  //odd number to 9
  if (!(min[0] == max[0] && min[1] == max[1])) {
    if (min[2] != '0') {
      pattern = min[0] + min[1];
      if (hex) {
        //switch for dex
        switch (min[2]) {
          case "f":
            pattern += 'f';
            break;
          case "e":
            pattern += '[ef]';
            break;
          case "d":
          case "c":
          case "b":
          case "a":
            pattern += '[' + min[2] + '-f]';
            break;
          default:
            switch (min[2]) {
              case "9":
                pattern += '[9a-f]';
                break;
              case "8":
                pattern += '[89a-f]';
                break;
              default:
                pattern += '[' + min[2] + '-9a-f]';
                break;
            }
            break;
        }
        diff = 15 - parseInt(min[2], 16) + 1;
        min = '' + (parseInt(min, 16) + diff).toString(16);
        re.push(pattern);
      } else {
        //switch for dex
        switch (min[2]) {
          case "9":
            pattern += '9';
            break;
          case "8":
            pattern += '[89]';
            break;
          default:
            pattern += '[' + min[2] + '-9]';
            break;
        }
        diff = 9 - min[2] + 1;
        min = '' + (min * 1 + diff);
        re.push(pattern);
      }
    }
  }
  pattern = ''; //if hundreds are different, get odd to 99 or ff
  if (min[0] != max[0]) {
    if (min[1] != '0') {
      if (hex) {
        //scrape to ff
        pattern = min[0];
        switch (min[1]) {
          case "f":
            pattern += 'f';
            break;
          case "e":
            pattern += '[ef]';
            break;
          case "d":
          case "c":
          case "b":
          case "a":
            pattern += '[' + min[1] + '-f]';
            break;
          case "9":
            pattern += '[9a-f]';
            break;
          case "8":
            pattern += '[89a-f]';
            break;
          default:
            pattern += '[' + min[1] + '-9a-f]';
            break;
        }
        pattern += '[0-9a-f]';
        diff = 15 - parseInt(min[1], 16) + 1;
        min = '' + (parseInt(min, 16) + diff * 16).toString(16);
        re.push(pattern);
      } else {
        //scrape to 99
        pattern = min[0];
        diff = 9 - min[1] + 1;
        switch (min[1]) {
          case "9":
            pattern += '9';
            break;
          case "8":
            pattern += '[89]';
            break;
          default:
            pattern += '[' + min[1] + '-9]';
            break;
        }
        pattern += '[0-9]';
        diff = 9 - min[1] + 1;
        min = '' + (min * 1 + diff * 10);
        re.push(pattern);
      }
    }
  }
  pattern = ''; //if hundreds are different, get to same
  if (min[0] != max[0]) {
    if (hex) {
      diff = parseInt(max[0], 16) - parseInt(min[0], 16);
      tmax = (parseInt(min[0], 16) + diff - 1).toString(16);
      switch (diff) {
        case 1:
          pattern = min[0];
          break;
        case 2:
          pattern = '[' + min[0] + tmax + ']';
          break;
        default:
          if (parseInt(min[0], 16) > 9) {
            minV = 'h';
          } else {
            minV = 'd';
          }
          if (parseInt(tmax, 16) > 9) {
            maxV = 'h';
          } else {
            maxV = 'd';
          }
          switch (minV + maxV) {
            case "dd":
              pattern += '[' + min[0] + '-' + tmax + ']';
              break;
            case "dh":
              diff = 9 - min[0]; //firs get up to 9
              switch (diff) {
                case 0:
                  pattern += '[9';
                  break;
                case 1:
                  pattern += '[89';
                  break;
                default:
                  pattern += '[' + min[0] + '-9';
                  break;
              }
              switch (tmax[0]) {
                case 'a':
                  pattern += 'a]';
                  break;
                case 'b':
                  pattern +=