/**
* Sutton SignWriting TrueType Font Module v1.5.2 (https://github.com/sutton-signwriting/font-ttf)
* Author: Steve Slevinski  (https://SteveSlevinski.me)
* index.mjs is released under the MIT License.
*/

/**
 * 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;
let context;

/**
 * Function that returns the size of a symbol using an id
 * @function font.symbolSize
 * @param {number} id - a 16-bit number of a symbol
 * @returns {number[]} width and height of symbol
 * @example
 * font.symbolSize(1)
 * 
 * return [15,30]
 */
const symbolSize$2 = function (id) {
  if (id in sizes) {
    return [...sizes[id]];
  }
  if (!context) {
    const canvaser = document.createElement("canvas");
    canvaser.width = bound;
    canvaser.height = bound;
    context = canvaser.getContext("2d", {
      willReadFrequently: true
    });
  }
  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
 * @returns {string} character for symbol line
 * @example
 * font.symbolLine(1)
 * 
 * return '󰀁'
 */
const symbolLine$2 = 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
 * @returns {string} character for symbol fill
 * @example
 * font.symbolFill(1)
 * 
 * return '􀀁'
 */
const symbolFill$2 = 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
 * @returns {string} SVG segment for line and fill
 * @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$2 = function (id) {
  return `    <text class="sym-fill" fill="white" style="pointer-events:none;font-family:'SuttonSignWritingFill';font-size:30px;">${symbolFill$2(id)}</text>
    <text class="sym-line" fill="black" style="pointer-events:none;font-family:'SuttonSignWritingLine';font-size:30px;">${symbolLine$2(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$2(1)) {
    const cssCheck = setInterval(function () {
      if (symbolSize$2(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$2(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$2 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  cssAppend: cssAppend,
  cssLoaded: cssLoaded,
  cssLoadedLine: cssLoadedLine,
  cssLoadedFill: cssLoadedFill,
  symbolSize: symbolSize$2,
  symbolLine: symbolLine$2,
  symbolFill: symbolFill$2,
  symbolText: symbolText$2
});

/**
* Sutton SignWriting Core Module v1.5.11 (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$1 = 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$1 = coord => coord.map(num => num2swu$1(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$1 = 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$1(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);

/**
 * 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$1(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));

/* support ongoing development */
/*   https://patreon.com/signwriting */
/*   https://donate.sutton-signwriting.io */

/**
* Sutton SignWriting Core Module v1.5.11 (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
 * 
 * @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$1$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$1.prefix = `(?:${re$1$1.sort}(?:${re$1$1.symbol})+)`;
re$1$1.spatial = `${re$1$1.symbol}${re$1$1.coord}`;
re$1$1.signbox = `${re$1$1.box}${re$1$1.coord}(?:${re$1$1.spatial})*`;
re$1$1.sign = `${re$1$1.prefix}?${re$1$1.signbox}`;
re$1$1.sortable = `${re$1$1.prefix}${re$1$1.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$2 = color => {
  const regex = new RegExp(`^${re$3.colorhex}$`);
  return (regex.test(color) ? '#' : '') + color;
};
const definedProps$2 = 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$1$1 = styleString => {
  const regex = `^${re$3.full}`;
  const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
  return definedProps$2({
    'colorize': !m[1] ? undefined : !!m[1],
    'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
    'background': !m[3] ? undefined : prefixColor$2(m[3].slice(2, -1)),
    'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor$2),
    '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$2);
      return {
        'index': parseInt(parts[0].slice(1)),
        'detail': detail
      };
    }),
    'classes': !m[7] ? undefined : m[7],
    'id': !m[8] ? undefined : m[8]
  });
};

/** 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 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$3 = {
  /**
   * Function to parse an fsw symbol with optional coordinate and style string
   * @function fsw.parse.symbol
   * @param {string} fswSym - an fsw symbol
   * @returns {SymbolObject} elements of fsw symbol
   * @example
   * fsw.parse.symbol('S10000500x500-C')
   * 
   * return {
   *  'symbol': 'S10000',
   *  'coord': [500, 500],
   *  'style': '-C'
   * }
   */
  symbol: fswSym => {
    const regex = `^(${re$1$1.symbol})(${re$1$1.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 { SignObject } 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$1.prefix})?(${re$1$1.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 {string[]} 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$1$1.sign}(${re$3.full})?|${re$1$1.spatial}(${re$3.full})?)`;
    const matches = fswText.match(new RegExp(regex, 'g'));
    return matches ? [...matches] : [];
  }
};

const compose$2 = {
  /**
   * Function to compose an fsw symbol with optional coordinate and style string
   * @function fsw.compose.symbol
   * @param {SymbolObject} fswSymObject - an fsw symbol object
   * @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$1$1.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$1$1.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 {SignObject} fswSignObject - an fsw symbol object
   * @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$1$1.box);
    const x = (fswSignObject.max && fswSignObject.max[0] || '').toString();
    const y = (fswSignObject.max && fswSignObject.max[1] || '').toString();
    const max = ((x + 'x' + y).match(re$1$1.coord) || [''])[0] || '';
    if (!max) return undefined;
    let prefix = '';
    if (fswSignObject.sequence && Array.isArray(fswSignObject.sequence)) {
      prefix = fswSignObject.sequence.map(key => (key.match(re$1$1.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$1$1.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$1$1.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 {SegmentInfo} information about the fsw string
 * @example
 * fsw.info('AS14c20S27106L518x529S14c20481x471S27106503x489-P10Z2')
 * 
 * return {
 *   minX: 481,
 *   minY: 471,
 *   width: 37,
 *   height: 58,
 *   lane: -1,
 *   padding: 10,
 *   segment: 'sign',
 *   zoom: 2
 * }
 */
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$1$1(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 categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
 * @alias fsw.category
 * @type {number[]}
 */
const category$1 = [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$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]
};

/**
 * Array of colors associated with the seven symbol categories.
 * @alias fsw.colors
 * @type {string[]}
 */
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;
};

/* support ongoing development */
/*   https://patreon.com/signwriting */
/*   https://donate.sutton-signwriting.io */

/**
 * Function that returns the size of a symbol using an FSW symbol key
 * @function fsw.symbolSize
 * @param {string} fsw - an FSW symbol key
 * @returns {number[]} width and height of symbol
 * @example
 * fsw.symbolSize("S10000")
 * 
 * return [15,30]
 */
const symbolSize$1 = function (fsw) {
  const parsed = parse$3.symbol(fsw);
  if (!parsed.symbol) {
    return undefined;
  }
  return symbolSize$2(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
 * @returns {string} character for symbol line
 * @example
 * fsw.symbolLine('S10000')
 * 
 * return '󰀁'
 */
const symbolLine$1 = function (fsw) {
  return symbolLine$2(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
 * @returns {string} character for symbol fill
 * @example
 * font.symbolFill('S10000')
 * 
 * return '􀀁'
 */
const symbolFill$1 = function (fsw) {
  return symbolFill$2(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
 * @returns {string} svg segment for line and fill
 * @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$2(key2id(fsw));
};

/**
* Sutton SignWriting Core Module v1.5.11 (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
 * 
 * @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$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)',
  'classbase': '-?[_a-zA-Z][_a-zA-Z0-9-]{0,100}',
  'id': '[a-zA-Z][_a-zA-Z0-9-]{0,100}'
};
re$2.colorbase = `(?:${re$2.colorhex}|${re$2.colorname})`;
re$2.color = `_${re$2.colorbase}_`;
re$2.colors = `_${re$2.colorbase}(?:,${re$2.colorbase})?_`;
re$2.background = `G${re$2.color}`;
re$2.detail = `D${re$2.colors}`;
re$2.detailsym = `D[0-9]{2}${re$2.colors}`;
re$2.classes = `${re$2.classbase}(?: ${re$2.classbase})*`;
re$2.full = `-(${re$2.colorize})?(${re$2.padding})?(${re$2.background})?(${re$2.detail})?(${re$2.zoom})?(?:-((?:${re$2.detailsym})*))?(?:-(${re$2.classes})?!(?:(${re$2.id})!)?)?`;

const prefixColor$1 = color => {
  const regex = new RegExp(`^${re$2.colorhex}$`);
  return (regex.test(color) ? '#' : '') + color;
};
const definedProps$1 = 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$2 = styleString => {
  const regex = `^${re$2.full}`;
  const m = (typeof styleString === 'string' ? styleString.match(new RegExp(regex)) : []) || [];
  return definedProps$1({
    'colorize': !m[1] ? undefined : !!m[1],
    'padding': !m[2] ? undefined : parseInt(m[2].slice(1)),
    'background': !m[3] ? undefined : prefixColor$1(m[3].slice(2, -1)),
    'detail': !m[4] ? undefined : m[4].slice(2, -1).split(',').map(prefixColor$1),
    'zoom': !m[5] ? undefined : m[5] === 'Zx' ? 'x' : parseFloat(m[5].slice(1)),
    'detailsym': !m[6] ? undefined : m[6].match(new RegExp(re$2.detailsym, 'g')).map(val => {
      const parts = val.split('_');
      const detail = parts[1].split(',').map(prefixColor$1);
      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$1 = 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$2.colorbase)[0];
  style1 += !background ? '' : 'G_' + background + '_';
  const detail1 = !styleObject.detail || !styleObject.detail[0] || !(typeof styleObject.detail[0] === 'string') ? undefined : styleObject.detail[0].match(re$2.colorbase)[0];
  const detail2 = !styleObject.detail || !styleObject.detail[1] || !(typeof styleObject.detail[1] === 'string') ? undefined : styleObject.detail[1].match(re$2.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$2.colorbase)[0];
    const detail2 = !styleObject.detail || !styleObject.detail[1] ? undefined : styleObject.detail[1].match(re$2.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$2.classes)[0];
  style3 += !classes ? '' : classes;
  const id = !styleObject.id || !(typeof styleObject.id === 'string') ? undefined : styleObject.id.match(re$2.id)[0];
  style3 += classes || id ? '!' : '';
  style3 += !id ? '' : id + '!';
  return style1 + (style2 || style3 ? '-' + style2 : '') + (style3 ? '-' + style3 : '');
};

/* support ongoing development */
/*   https://patreon.com/signwriting */
/*   https://donate.sutton-signwriting.io */

/**
 * Function that creates an SVG image from an FSW symbol key with an optional style string
 * @function fsw.symbolSvgBody
 * @param {string} fswSym - an FSW symbol key with optional style string
 * @returns {string} body of SVG for symbol
 * @example
 * fsw.symbolSvgBody('S10000')
 * 
 * return `<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>`
 */
const symbolSvgBody$1 = fswSym => {
  const parsed = parse$3.symbol(fswSym);
  const blank = '';
  if (!parsed.symbol) return blank;
  let styling = parse$2(parsed.style);
  let x1, y1, x2, y2;
  if (parsed.coord) {
    x1 = parsed.coord[0];
    y1 = parsed.coord[1];
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  } else {
    let size = symbolSize$1(parsed.symbol);
    if (!size) return blank;
    x1 = 500 - parseInt((size[0] + 1) / 2);
    y1 = 500 - parseInt((size[1] + 1) / 2);
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  }
  let symSvg = symbolText$1(parsed.symbol);
  symSvg = `  <g transform="translate(${x1},${y1})">
${symSvg}
  </g>`;
  let line;
  if (styling.colorize) {
    line = colorize$1(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 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};" />`;
  }
  return `  <text font-size="0">${fswSym}</text>${background}
${symSvg}`;
};

/**
 * 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
 * @returns {string} SVG for symbol
 * @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$1 = fswSym => {
  const parsed = parse$3.symbol(fswSym);
  const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
  if (!parsed.symbol) return blank;
  let styling = parse$2(parsed.style);
  let x1, y1, x2, y2;
  if (parsed.coord) {
    x1 = parsed.coord[0];
    y1 = parsed.coord[1];
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  } else {
    let size = symbolSize$1(parsed.symbol);
    if (!size) return blank;
    x1 = parseInt(500 - size[0] / 2);
    y1 = parseInt(500 - size[1] / 2);
    x2 = x1 + size[0];
    y2 = y1 + size[1];
  }
  let classes = '';
  if (styling.classes) {
    classes = ` class="${styling.classes}"`;
  }
  let id = '';
  if (styling.id) {
    id = ` id="${styling.id}"`;
  }
  if (styling.padding) {
    x1 -= styling.padding;
    y1 -= styling.padding;
    x2 += styling.padding;
    y2 += styling.padding;
  }
  let sizing = '';
  if (styling.zoom != 'x') {
    sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
  }
  return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
${symbolSvgBody$1(fswSym)}
</svg>`;
};

const symbolCanvas$1 = function (fswSym) {
  const parsed = parse$3.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$2(parsed.style);
      let line = 'black';
      if (styling.colorize) {
        line = colorize$1(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 PNG data url from an FSW symbol key with an optional style string
 * @function fsw.symbolPng
 * @param {string} fswSym - an FSW symbol key with optional style string
 * @returns {string} png image for symbol as data url
 * @example
 * fsw.symbolPng('S10000')
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const symbolPng$1 = fswSym => {
  const canvas = symbolCanvas$1(fswSym);
  const png = canvas.toDataURL("image/png");
  canvas.remove();
  return png;
};

const blank$1 = 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
 * @returns {string} normalized FSW symbol
 * @example
 * fsw.symbolNormalize('S10000-CP10G_green_Z2')
 * 
 * return 'S10000493x485-CP10G_green_Z2'
 */
const symbolNormalize$1 = fswSym => {
  const parsed = parse$3.symbol(fswSym);
  if (parsed.symbol) {
    let size = symbolSize$1(parsed.symbol);
    if (size) {
      return `${parsed.symbol}${500 - parseInt((size[0] + 1) / 2)}x${500 - parseInt((size[1] + 1) / 2)}${parsed.style || ''}`;
    }
  } else {
    return blank$1;
  }
};

/**
 * Function that mirrors a symbol
 * @function fsw.symbolMirror
 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
 * @returns {string} mirrored FSW symbol
 * @example
 * fsw.symbolMirror('S10000')
 * 
 * return 'S10008'
 */
const symbolMirror$1 = fswSym => {
  let parsed = parse$3.symbol(fswSym);
  if (!parsed.symbol) {
    return fswSym;
  }
  const size = symbolSize$1(parsed.symbol);
  if (!size) {
    return fswSym;
  }
  const base = parsed.symbol.slice(0, 4);
  let fill = parsed.symbol.slice(4, 5);
  let rot = parseInt(parsed.symbol.slice(5, 6), 16);
  const key1 = base + "08";
  const key2 = base + "18";
  var rAdd;
  if (symbolSize$1(key1) || symbolSize$1(key2)) {
    rAdd = 8;
  } else {
    if (rot === 0 || rot == 4) {
      rAdd = 0;
    }
    if (rot == 1 || rot == 5) {
      rAdd = 6;
    }
    if (rot == 2 || rot == 6) {
      rAdd = 4;
    }
    if (rot == 3 || rot == 7) {
      rAdd = 2;
    }
  }
  let key = '';
  while (!key || !symbolSize$1(key)) {
    rot += rAdd;
    if (rot > 7 && rAdd < 8) {
      rot = rot - 8;
    }
    if (rot > 15) {
      rot = rot - 16;
    }
    key = base + fill + rot.toString(16);
  }
  parsed.symbol = key;
  return compose$2.symbol(parsed);
};

/**
 * Function that rotates a symbol
 * @function fsw.symbolRotate
 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
 * @param {boolean} [clockwise=true] - rotate the symbol clockwise
 * @returns {string} rotated FSW symbol
 * @example
 * fsw.symbolRotate('S10000')
 * 
 * return 'S10007'
 */
const symbolRotate$1 = (fswSym, clockwise = true) => {
  let parsed = parse$3.symbol(fswSym);
  if (!parsed.symbol) {
    return fswSym;
  }
  const size = symbolSize$1(parsed.symbol);
  if (!size) {
    return fswSym;
  }
  const step = clockwise ? 1 : -1;
  const base = parsed.symbol.slice(0, 4);
  let fill = parsed.symbol.slice(4, 5);
  let rot = parseInt(parsed.symbol.slice(5, 6), 16);
  let key = '';
  while (!key || !symbolSize$1(key)) {
    if (rot > 7) {
      rot += step;
      if (rot > 15) {
        rot = 8;
      }
      if (rot < 8) {
        rot = 15;
      }
      key = base + fill + rot.toString(16);
    } else {
      rot -= step;
      if (rot > 7) {
        rot = 0;
      }
      if (rot < 0) {
        rot = 7;
      }
      key = base + fill + rot;
    }
  }
  parsed.symbol = key;
  return compose$2.symbol(parsed);
};

/**
 * Function that changes the fill of a symbol
 * @function fsw.symbolFlop
 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
 * @param {boolean} [positive=true] - increase the symbol fill
 * @returns {string} FSW symbol with changed fill
 * @example
 * fsw.symbolFlop('S10000')
 * 
 * return 'S10010'
 */
const symbolFlop$1 = (fswSym, positive = true) => {
  let parsed = parse$3.symbol(fswSym);
  if (!parsed.symbol) {
    return fswSym;
  }
  const size = symbolSize$1(parsed.symbol);
  if (!size) {
    return fswSym;
  }
  const step = positive ? 1 : -1;
  const base = parsed.symbol.slice(0, 4);
  let fill = parseInt(parsed.symbol.slice(4, 5));
  let rot = parsed.symbol.slice(5, 6);
  let key = '';
  while (!key || !symbolSize$1(key)) {
    fill += step;
    if (fill > 5) {
      fill = 0;
    }
    if (fill < 0) {
      fill = 5;
    }
    key = base + fill + rot;
  }
  parsed.symbol = key;
  return compose$2.symbol(parsed);
};

/**
 * Function that changes the base of a symbol
 * @function fsw.symbolScroll
 * @param {string} fswSym - an FSW symbol key with optional coordinate and style string
 * @param {boolean} [positive=true] - increase the symbol base
 * @returns {string} FSW symbol with changed base
 * @example
 * fsw.symbolScroll('S10000')
 * 
 * return 'S10100'
 */
const symbolScroll$1 = (fswSym, positive = true) => {
  let parsed = parse$3.symbol(fswSym);
  if (!parsed.symbol) {
    return fswSym;
  }
  const size = symbolSize$1(parsed.symbol);
  if (!size) {
    return fswSym;
  }
  const step = positive ? 1 : -1;
  const base = parseInt(parsed.symbol.slice(1, 4), 16) + step;
  const fill = parsed.symbol.slice(4, 5);
  const rot = parsed.symbol.slice(5, 6);
  const key = 'S' + base.toString(16) + fill + rot;
  if (symbolSize$1(key)) {
    parsed.symbol = key;
  }
  return compose$2.symbol(parsed);
};

/**
 * Function that creates an SVG image from an FSW sign with an optional style string
 * @function fsw.signSvgBody
 * @param {string} fswSign - an FSW sign with optional style string
 * @returns {string} body of SVG for sign
 * @example
 * fsw.signSvgBody('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
 * 
 * return `<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>`
 */
const signSvgBody$1 = fswSign => {
  let parsed = parse$3.sign(fswSign);
  const blank = '';
  if (parsed.spatials) {
    let styling = parse$2(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];
    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 svg = `  <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$1(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}"`);
      }
      return `  <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})">
${svg}
  </g>`;
    }).join('\n');
    return svg;
  }
  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
 * @returns {string} SVG for sign
 * @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$1 = fswSign => {
  let parsed = parse$3.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$2(parsed.style);
    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];
    let classes = '';
    if (styling.classes) {
      classes = ` class="${styling.classes}"`;
    }
    let id = '';
    if (styling.id) {
      id = ` id="${styling.id}"`;
    }
    if (styling.padding) {
      x1 -= styling.padding;
      y1 -= styling.padding;
      x2 += styling.padding;
      y2 += styling.padding;
    }
    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}">
`;
    svg += signSvgBody$1(fswSign);
    svg += '\n</svg>';
    return svg;
  }
  return blank;
};

const signCanvas$1 = function (fswSign) {
  const parsed = parse$3.sign(fswSign);
  if (parsed.spatials) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    let styling = parse$2(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.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$1(spatial.symbol);
      }
      let symFill = fill;
      if (spatial.detail && spatial.detail[1]) {
        symFill = spatial.detail[1];
      }
      context.font = 30 * sizing + "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 + "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 PNG data url from an FSW sign with an optional style string
 * @function fsw.signPng
 * @param {string} fswSign - an FSW sign with optional style string
 * @returns {string} png image for sign as data url
 * @example
 * fsw.signPng('M525x535S2e748483x510S10011501x466S20544510x500S10019476x475')
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const signPng$1 = fswSign => {
  const canvas = signCanvas$1(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
 * @returns {string} normalized FSW sign
 * @example
 * fsw.signNormalize('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
 * 
 * return 'M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475'
 */
const signNormalize$1 = fswSign => {
  const parsed = parse$3.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$1['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$1['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;
  }
};

/**
 * Function that creates an SVG image for a column of FSW
 * @function fsw.columnSvg
 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string} column svg
 * @example
 * fsw.columnSvg([
 *   {
 *     "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
 *   }
 * ],
 * {
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
 *   <g transform="translate(56,20) scale(1) translate(-481,-471) ">
 *     <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
 *     <g transform="translate(481,471)">
 *       <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(503,489)">
 *       <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>
 *   <g transform="translate(57,118) scale(1) translate(-482,-468) ">
 *     <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
 *     <g transform="translate(489,515)">
 *       <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(482,490)">
 *       <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(508,496)">
 *       <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(500,468)">
 *       <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>
 *   <g transform="translate(39,203) scale(1) translate(-464,-496) ">
 *     <text font-size="0">S38800464x496-D_black,white_Z1</text>
 *     <g transform="translate(464,496)">
 *       <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>
 * </svg>`
 */
const columnSvg$1 = (fswColumn, options) => {
  //if (typeof fswColumn !== 'array') return blank;
  if (typeof options !== 'object') options = {};
  const values = Object.assign(columnDefaults$1, options);
  let x1 = 0;
  let y1 = 0;
  let x2 = values.width;
  let y2 = values.height;
  let background = '';
  if (values.background) {
    background = `\n  <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${values.background};" />`;
  }
  let sizing = ` width="${values.width}" height="${values.height}"`;
  let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
  ${background}`;
  svg += fswColumn.map(item => {
    const dash = item.text.indexOf('-');
    if (dash > 0) {
      const itemStyle = item.text.substring(dash);
      const newStyle = {
        ...values.style,
        ...parse$2(itemStyle)
      };
      item.text = item.text.replace(itemStyle, compose$1(newStyle));
    } else {
      item.text += compose$1(values.style);
    }
    item.zoom = item.zoom * values.style.zoom;
    return '<g transform="translate(' + item.x + ',' + item.y + ') scale(' + item.zoom + ') translate(' + -item.minX + ',' + -item.minY + ') ">' + (item.segment == "sign" ? signSvgBody$1(item.text) : symbolSvgBody$1(item.text)) + '</g>';
  }).join('\n');
  svg += '\n</svg>';
  return svg;
};

/**
 * Function that creates an array of SVG column images for an FSW text
 * @function fsw.columnsSvg
 * @param {string} fswText - a text of FSW signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string[]} array of SVG columns
 * @example
 * fsw.columnsSvg('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return [`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
 *   <g transform="translate(56,20) scale(1) translate(-481,-471) ">
 *     <text font-size="0">AS14c20S27106M518x529S14c20481x471S27106503x489-D_black,white_Z1</text>
 *     <g transform="translate(481,471)">
 *       <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(503,489)">
 *       <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>
 *   <g transform="translate(57,118) scale(1) translate(-482,-468) ">
 *     <text font-size="0">AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468-D_black,white_Z1</text>
 *     <g transform="translate(489,515)">
 *       <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(482,490)">
 *       <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(508,496)">
 *       <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(500,468)">
 *       <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>
 *   <g transform="translate(39,203) scale(1) translate(-464,-496) ">
 *     <text font-size="0">S38800464x496-D_black,white_Z1</text>
 *     <g transform="translate(464,496)">
 *       <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>
 * </svg>`]
 */
const columnsSvg$1 = function (fswText, options) {
  if (typeof options !== 'object') options = {};
  let values = columns$1(fswText, options);
  let cols = values.columns.map((col, i) => {
    return columnSvg$1(col, {
      ...values.options,
      ...{
        width: values.widths[i]
      }
    });
  });
  return cols;
};

const columnCanvas$1 = function (fswColumn, options) {
  if (typeof options !== 'object') options = {};
  const values = Object.assign(columnDefaults$1, options);
  const canvas = document.createElement('canvas');
  canvas.width = values.width;
  canvas.height = values.height;
  const context = canvas.getContext('2d');
  if (values.background) {
    context.rect(0, 0, values.width, values.height);
    context.fillStyle = values.background;
    context.fill();
  }
  fswColumn.map(item => {
    const dash = item.text.indexOf('-');
    if (dash > 0) {
      const itemStyle = item.text.substring(dash);
      const newStyle = {
        ...values.style,
        ...parse$2(itemStyle)
      };
      item.text = item.text.replace(itemStyle, compose$1(newStyle));
    } else {
      item.text += compose$1(values.style);
    }
    item.zoom = item.zoom * values.style.zoom;
    let parsed = {};
    if (item.segment == "sign") {
      parsed = parse$3.sign(item.text);
    } else {
      let sym = parse$3.symbol(item.text);
      parsed.style = sym.style;
      parsed.spatials = [sym];
    }
    let styling = parse$2(parsed.style);
    if (styling.background) {
      context.fillStyle = styling.background;
      context.fillRect(item.x - styling.padding * item.zoom, item.y - styling.padding * item.zoom, (item.width + styling.padding * 2) * item.zoom, (item.height + styling.padding * 2) * item.zoom);
    }
    if (styling.detailsym) {
      styling.detailsym.forEach(sym => {
        if (parsed.spatials[sym.index - 1]) {
          parsed.spatials[sym.index - 1].detail = sym.detail;
        }
      });
    }
    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$1(spatial.symbol);
      }
      let symFill = fill;
      if (spatial.detail && spatial.detail[1]) {
        symFill = spatial.detail[1];
      }
      context.font = 30 * item.zoom + "px 'SuttonSignWritingFill'";
      context.fillStyle = symFill;
      context.fillText(symbolFill$1(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
      context.font = 30 * item.zoom + "px 'SuttonSignWritingLine'";
      context.fillStyle = symLine;
      context.fillText(symbolLine$1(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
    });
  });
  return canvas;
};

/**
 * Function that creates a PNG data url for a column of FSW
 * @function fsw.columnPng
 * @param {ColumnData} fswColumn - an array of objects with information about FSW signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string} column png data url
 * @example
 * fsw.columnPng([
 *   {
 *     "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
 *   }
 * ],
 * {
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const columnPng$1 = (fswColumn, options) => {
  const canvas = columnCanvas$1(fswColumn, options);
  const png = canvas.toDataURL("image/png");
  canvas.remove();
  return png;
};

/**
 * Function that creates an array of PNG data urls for an FSW text
 * @function fsw.columnsPng
 * @param {string} fswText - a text of FSW signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string[]} array of PNG data urls
 * @example
 * fsw.columnsPng('AS14c20S27106M518x529S14c20481x471S27106503x489 AS18701S1870aS2e734S20500M518x533S1870a489x515S18701482x490S20500508x496S2e734500x468 S38800464x496',{
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return ['data:image/png;base64,iVBORw...']
 */
const columnsPng$1 = function (fswText, options) {
  if (typeof options !== 'object') options = {};
  let values = columns$1(fswText, options);
  let cols = values.columns.map((col, i) => {
    return columnPng$1(col, {
      ...values.options,
      ...{
        width: values.widths[i]
      }
    });
  });
  return cols;
};

/** 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$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  symbolSize: symbolSize$1,
  symbolLine: symbolLine$1,
  symbolFill: symbolFill$1,
  symbolText: symbolText$1,
  symbolSvgBody: symbolSvgBody$1,
  symbolSvg: symbolSvg$1,
  symbolPng: symbolPng$1,
  symbolNormalize: symbolNormalize$1,
  symbolMirror: symbolMirror$1,
  symbolRotate: symbolRotate$1,
  symbolFlop: symbolFlop$1,
  symbolScroll: symbolScroll$1,
  signSvgBody: signSvgBody$1,
  signSvg: signSvg$1,
  signPng: signPng$1,
  signNormalize: signNormalize$1,
  columnSvg: columnSvg$1,
  columnsSvg: columnsSvg$1,
  columnPng: columnPng$1,
  columnsPng: columnsPng$1
});

/**
* Sutton SignWriting Core Module v1.5.11 (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
 * 
 * @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$1 = {
  'symbol': '(?:(?:\uD8C0[\uDC01-\uDFFF])|(?:[\uD8C1-\uD8FC][\uDC00-\uDFFF])|(?:\uD8FD[\uDC00-\uDC80]))',
  'coord': '(?:\uD836[\uDC0C-\uDDFF]){2}',
  'sort': '\uD836\uDC00',
  'box': '\uD836[\uDC01-\uDC04]'
};
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
 * 
 * @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 = {
  '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.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.classes})?!(?:(${re.id})!)?)?`;

const prefixColor = color => {
  const regex = new RegExp(`^${re.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$1 = styleString => {
  const regex = `^${re.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.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]
  });
};

/** 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 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 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));

const parse = {
  /**
   * Function to parse an swu symbol with optional coordinate and style string
   * @function swu.parse.symbol
   * @param {string} swuSym - an swu symbol
   * @returns {SymbolObject} elements of swu symbol
   * @example
   * swu.parse.symbol('񀀁𝤆𝤆-C')
   * 
   * return {
   *  'symbol': '񀀁',
   *  'coord': [500, 500],
   *  'style': '-C'
   * }
   */
  symbol: swuSym => {
    const regex = `^(${re$1.symbol})(${re$1.coord})?(${re.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 {SignObject} 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$1.prefix})?(${re$1.signbox})(${re.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 {};
    }
  },
  /**
   * Function to parse an swu text
   * @function swu.parse.text
   * @param {string} swuText - an swu text
   * @returns {string[]} swu signs and punctuations
   * @example
   * swu.parse.text('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂')
   * 
   * return [
   *  '𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻',
   *  '𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦',
   *  '񏌁𝣢𝤂'
   * ]
   */
  text: swuText => {
    if (typeof swuText !== 'string') return [];
    const regex = `(${re$1.sign}(${re.full})?|${re$1.spatial}(${re.full})?)`;
    const matches = swuText.match(new RegExp(regex, 'g'));
    return matches ? [...matches] : [];
  }
};

const compose = {
  /**
   * Function to compose an swu symbol with optional coordinate and style string
   * @function swu.compose.symbol
   * @param {SymbolObject} swuSymObject - an swu symbol object
   * @returns {string} an swu symbol string
   * @example
   * swu.compose.symbol({
   *  'symbol': '񀀁',
   *  'coord': [500, 500],
   *  'style': '-C'
   * })
   * 
   * return '񀀁𝤆𝤆-C'
   */
  symbol: swuSymObject => {
    if (typeof swuSymObject !== 'object' || swuSymObject === null) return undefined;
    if (typeof swuSymObject.symbol === 'string') {
      const symbol = (swuSymObject.symbol.match(re$1.symbol) || [''])[0];
      if (symbol) {
        const x = swuSymObject.coord && swuSymObject.coord[0] || '';
        const y = swuSymObject.coord && swuSymObject.coord[1] || '';
        const coord = x && y ? coord2swu([x, y]) : '';
        const styleStr = typeof swuSymObject.style === 'string' && (swuSymObject.style.match(re.full) || [''])[0] || '';
        return symbol + coord + styleStr;
      }
    }
    return undefined;
  },
  /**
   * Function to compose an swu sign with style string
   * @function swu.compose.sign
   * @param {SignObject} swuSignObject - an swu sign object
   * @returns {string} an swu sign string
   * @example
   * swu.compose.sign({
   *  sequence: ['񀀒','񀀚','񋚥','񋛩'],
   *  box: '𝠃',
   *  max: [525, 535],
   *  spatials: [
   *    {
   *      symbol: '񋛩',
   *      coord: [483, 510]
   *    },
   *    {
   *      symbol: '񀀒',
   *      coord: [501, 466]
   *    },
   *    {
   *      symbol: '񋚥',
   *      coord: [510, 500]
   *    },
   *    {
   *      symbol: '񀀚',
   *      coord: [476, 475]
   *    }
   *  ],
   *  style: '-C'
   * })
   * 
   * return '𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭-C'
   */
  sign: swuSignObject => {
    if (typeof swuSignObject !== 'object' || swuSignObject === null) return undefined;
    let box = typeof swuSignObject.box !== 'string' ? '𝠃' : (swuSignObject.box + '𝠃').match(re$1.box);
    const x = swuSignObject.max && swuSignObject.max[0] || '';
    const y = swuSignObject.max && swuSignObject.max[1] || '';
    const max = x && y ? coord2swu([x, y]) : undefined;
    if (!max) return undefined;
    let prefix = '';
    if (swuSignObject.sequence && Array.isArray(swuSignObject.sequence)) {
      prefix = swuSignObject.sequence.map(key => (key.match(re$1.symbol) || [''])[0]).join('');
      prefix = prefix ? '𝠀' + prefix : '';
    }
    let signbox = '';
    if (swuSignObject.spatials && Array.isArray(swuSignObject.spatials)) {
      signbox = swuSignObject.spatials.map(spatial => {
        if (typeof spatial.symbol === 'string') {
          const symbol = (spatial.symbol.match(re$1.symbol) || [''])[0];
          if (symbol) {
            const x = spatial.coord && spatial.coord[0] || '';
            const y = spatial.coord && spatial.coord[1] || '';
            const coord = x && y ? coord2swu([x, y]) : '';
            if (coord) {
              return symbol + coord;
            }
          }
        }
        return '';
      }).join('');
    }
    const styleStr = typeof swuSignObject.style === 'string' && (swuSignObject.style.match(re.full) || [''])[0] || '';
    return prefix + box + max + signbox + styleStr;
  }
};

/**
 * Function to gather sizing information about an swu sign or symbol
 * @function swu.info
 * @param {string} swu - an swu sign or symbol
 * @returns {SegmentInfo} information about the swu string
 * @example
 * swu.info('𝠀񁲡񈩧𝠂𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻-P10Z2')
 * 
 * return {
 *   minX: 481,
 *   minY: 471,
 *   width: 37,
 *   height: 58,
 *   lane: -1,
 *   padding: 10,
 *   segment: 'sign',
 *   zoom: 2
 * }
 */
const info = swu => {
  let lanes = {
    '𝠁': 0,
    '𝠂': -1,
    '𝠃': 0,
    '𝠄': 1
  };
  let parsed = parse.sign(swu);
  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.symbol(swu);
    lane = "𝠃";
    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$1(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 = {
  '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 swu.columnDefaultsMerge
 * @param {ColumnOptions} options - object of column options
 * @returns {ColumnOptions} object of column options merged with column defaults
 * @example
 * swu.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 = options => {
  if (typeof options !== 'object') options = {};
  return {
    ...columnDefaults,
    ...options,
    punctuation: {
      ...columnDefaults.punctuation,
      ...options.punctuation
    },
    style: {
      ...columnDefaults.style,
      ...options.style
    }
  };
};

/**
 * Function to transform an SWU text to an array of columns
 * 
 * @function swu.columns
 * @param {string} swuText - SWU 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
 * swu.columns('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂', {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": "𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻",
 *         "zoom": 1
 *       },
 *       {
 *         "x": 57,
 *         "y": 118,
 *         "minX": 482,
 *         "minY": 468,
 *         "width": 36,
 *         "height": 65,
 *         "lane": 0,
 *         "padding": 0,
 *         "segment": "sign",
 *         "text": "𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦",
 *         "zoom": 1
 *       },
 *       {
 *         "x": 39,
 *         "y": 203,
 *         "minX": 464,
 *         "minY": 496,
 *         "width": 72,
 *         "height": 8,
 *         "lane": 0,
 *         "padding": 0,
 *         "segment": "symbol",
 *         "text": "񏌁𝣢𝤂",
 *         "zoom": 1
 *       }
 *     ]
 *   ]
 * }
 */
const columns = (swuText, options) => {
  if (typeof swuText !== 'string') return {};
  const values = columnDefaultsMerge(options);
  let input = parse.text(swuText);
  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(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 plane 4 code points for categories of symbols: hand, movement, dynamics, head, trunk & limb, location, and punctuation.
 * @alias swu.category
 * @type {array}
 */
const category = [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 = {
  '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 = ['#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 = swuSym => {
  const parsed = parse.symbol(swuSym);
  let color = '#000000';
  if (parsed.symbol) {
    const code = swu2code(parsed.symbol);
    const index = category.findIndex(val => val > code);
    color = colors[index < 0 ? 6 : index - 1];
  }
  return color;
};

/* support ongoing development */
/*   https://patreon.com/signwriting */
/*   https://donate.sutton-signwriting.io */

/**
 * Function that returns the size of a symbol using an SWU symbol character
 * @function swu.symbolSize
 * @param {string} swu - an SWU symbol character
 * @returns {number[]} width and height of symbol
 * @example
 * swu.symbolSize("񀀁")
 * 
 * return [15,30]
 */
const symbolSize = function (swu) {
  const parsed = parse.symbol(swu);
  if (!parsed.symbol) {
    return undefined;
  }
  return symbolSize$2(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
 * @returns {string} character for symbol line
 * @example
 * swu.symbolLine('񀀁')
 * 
 * return '󰀁'
 */
const symbolLine = function (swu) {
  return symbolLine$2(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
 * @returns {string} character for symbol fill
 * @example
 * swu.symbolFill('񀀁')
 * 
 * return '􀀁'
 */
const symbolFill = function (swu) {
  return symbolFill$2(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
 * @returns {string} svg segment for line and fill
 * @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 = function (swu) {
  return symbolText$2(swu2id(swu));
};

/**
 * Function that creates an SVG image from an SWU symbol key with an optional style string
 * @function swu.symbolSvgBody
 * @param {string} swuSym - an SWU symbol key with optional style string
 * @returns {string} body of SVG for symbol
 * @example
 * swu.symbolSvgBody('S10000')
 * 
 * return `<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>`
 */
const symbolSvgBody = swuSym => {
  const parsed = parse.symbol(swuSym);
  const blank = '';
  if (!parsed.symbol) return blank;
  let styling = parse$2(parsed.style);
  let x1, y1, x2, y2;
  if (parsed.coord) {
    x1 = parsed.coord[0];
    y1 = parsed.coord[1];
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  } else {
    let size = symbolSize(parsed.symbol);
    if (!size) return blank;
    x1 = 500 - parseInt((size[0] + 1) / 2);
    y1 = 500 - parseInt((size[1] + 1) / 2);
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  }
  let symSvg = symbolText(parsed.symbol);
  symSvg = `  <g transform="translate(${x1},${y1})">
${symSvg}
  </g>`;
  let line;
  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 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};" />`;
  }
  return `  <text font-size="0">${swuSym}</text>${background}
${symSvg}`;
};

/**
 * Function that creates an SVG image from an SWU symbol key with an optional style string
 * @function swu.symbolSvg
 * @param {string} swuSym - an SWU symbol key with optional style string
 * @returns {string} SVG for symbol
 * @example
 * swu.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 = swuSym => {
  const parsed = parse.symbol(swuSym);
  const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
  if (!parsed.symbol) return blank;
  let styling = parse$2(parsed.style);
  let x1, y1, x2, y2;
  if (parsed.coord) {
    x1 = parsed.coord[0];
    y1 = parsed.coord[1];
    x2 = 500 + (500 - x1);
    y2 = 500 + (500 - y1);
  } else {
    let size = symbolSize(parsed.symbol);
    if (!size) return blank;
    x1 = parseInt(500 - size[0] / 2);
    y1 = parseInt(500 - size[1] / 2);
    x2 = x1 + size[0];
    y2 = y1 + size[1];
  }
  let classes = '';
  if (styling.classes) {
    classes = ` class="${styling.classes}"`;
  }
  let id = '';
  if (styling.id) {
    id = ` id="${styling.id}"`;
  }
  if (styling.padding) {
    x1 -= styling.padding;
    y1 -= styling.padding;
    x2 += styling.padding;
    y2 += styling.padding;
  }
  let sizing = '';
  if (styling.zoom != 'x') {
    sizing = ` width="${(x2 - x1) * (styling.zoom ? styling.zoom : 1)}" height="${(y2 - y1) * (styling.zoom ? styling.zoom : 1)}"`;
  }
  return `<svg${classes}${id} version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
${symbolSvgBody(swuSym)}
</svg>`;
};

const symbolCanvas = function (swuSym) {
  const parsed = parse.symbol(swuSym);
  if (parsed.symbol) {
    let size = symbolSize(parsed.symbol);
    if (size) {
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      let styling = parse$2(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(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
      context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
      context.fillStyle = line;
      context.fillText(symbolLine(parsed.symbol), (500 - x1) * sizing, (500 - y1) * sizing);
      return canvas;
    }
  }
};

/**
 * Function that creates a PNG data url from an SWU symbol character with an optional style string
 * @function swu.symbolPng
 * @param {string} swuSym - an SWU symbol character with optional style string
 * @returns {string} png image for symbol as data url
 * @example
 * swu.symbolPng('񀀁-CP10G_green_Z2')
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const symbolPng = swuSym => {
  const canvas = symbolCanvas(swuSym);
  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 swu.symbolNormalize
 * @param {string} swuSym - an SWU symbol character with optional coordinate and style string
 * @returns {string} normalized SWU symbol
 * @example
 * swu.symbolNormalize('񀀁')
 * 
 * return '񀀁𝣿𝣷'
 */
const symbolNormalize = swuSym => {
  const parsed = parse.symbol(swuSym);
  if (parsed.symbol) {
    let size = symbolSize(parsed.symbol);
    if (size) {
      return `${parsed.symbol}${coord2swu$1([500 - parseInt((size[0] + 1) / 2), 500 - parseInt((size[1] + 1) / 2)])}${parsed.style || ''}`;
    }
  } else {
    return blank;
  }
};

/**
 * Function that mirrors a symbol
 * @function swu.symbolMirror
 * @param {string} swuSym - an SWU symbol with optional coordinate and style string
 * @returns {string} mirrored SWU symbol
 * @example
 * swu.symbolMirror('񀀁')
 * 
 * return '񀀉'
 */
const symbolMirror = swuSym => {
  let parsed = parse.symbol(swuSym);
  if (!parsed.symbol) {
    return swuSym;
  }
  const size = symbolSize(parsed.symbol);
  if (!size) {
    return swuSym;
  }
  parsed.symbol = swu2key(parsed.symbol);
  const base = parsed.symbol.slice(0, 4);
  let fill = parsed.symbol.slice(4, 5);
  let rot = parseInt(parsed.symbol.slice(5, 6), 16);
  const key1 = base + "08";
  const key2 = base + "18";
  var rAdd;
  if (symbolSize(key2swu(key1)) || symbolSize(key2swu(key2))) {
    rAdd = 8;
  } else {
    if (rot === 0 || rot == 4) {
      rAdd = 0;
    }
    if (rot == 1 || rot == 5) {
      rAdd = 6;
    }
    if (rot == 2 || rot == 6) {
      rAdd = 4;
    }
    if (rot == 3 || rot == 7) {
      rAdd = 2;
    }
  }
  let key = '';
  while (!key || !symbolSize(key2swu(key))) {
    rot += rAdd;
    if (rot > 7 && rAdd < 8) {
      rot = rot - 8;
    }
    if (rot > 15) {
      rot = rot - 16;
    }
    key = base + fill + rot.toString(16);
  }
  parsed.symbol = key2swu(key);
  return compose.symbol(parsed);
};

/**
 * Function that rotates a symbol
 * @function swu.symbolRotate
 * @param {string} swuSym - an SWU symbol with optional coordinate and style string
 * @param {boolean} [clockwise=true] - rotate the symbol clockwise
 * @returns {string} rotated SWU symbol
 * @example
 * swu.symbolRotate('񀀁')
 * 
 * return '񀀈'
 */
const symbolRotate = (swuSym, clockwise = true) => {
  let parsed = parse.symbol(swuSym);
  if (!parsed.symbol) {
    return swuSym;
  }
  const size = symbolSize(parsed.symbol);
  if (!size) {
    return swuSym;
  }
  parsed.symbol = swu2key(parsed.symbol);
  const step = clockwise ? 1 : -1;
  const base = parsed.symbol.slice(0, 4);
  let fill = parsed.symbol.slice(4, 5);
  let rot = parseInt(parsed.symbol.slice(5, 6), 16);
  let key = '';
  while (!key || !symbolSize(key2swu(key))) {
    if (rot > 7) {
      rot += step;
      if (rot > 15) {
        rot = 8;
      }
      if (rot < 8) {
        rot = 15;
      }
      key = base + fill + rot.toString(16);
    } else {
      rot -= step;
      if (rot > 7) {
        rot = 0;
      }
      if (rot < 0) {
        rot = 7;
      }
      key = base + fill + rot;
    }
  }
  parsed.symbol = key2swu(key);
  return compose.symbol(parsed);
};

/**
 * Function that changes the fill of a symbol
 * @function swu.symbolFlop
 * @param {string} swuSym - an SWU symbol with optional coordinate and style string
 * @param {boolean} [positive=true] - increase the symbol fill
 * @returns {string} SWU symbol with changed fill
 * @example
 * swu.symbolFlop('񀀁')
 * 
 * return '񀀑'
 */
const symbolFlop = (swuSym, positive = true) => {
  let parsed = parse.symbol(swuSym);
  if (!parsed.symbol) {
    return swuSym;
  }
  const size = symbolSize(parsed.symbol);
  if (!size) {
    return swuSym;
  }
  parsed.symbol = swu2key(parsed.symbol);
  const step = positive ? 1 : -1;
  const base = parsed.symbol.slice(0, 4);
  let fill = parseInt(parsed.symbol.slice(4, 5));
  let rot = parsed.symbol.slice(5, 6);
  let key = '';
  while (!key || !symbolSize(key2swu(key))) {
    fill += step;
    if (fill > 5) {
      fill = 0;
    }
    if (fill < 0) {
      fill = 5;
    }
    key = base + fill + rot;
  }
  parsed.symbol = key2swu(key);
  return compose.symbol(parsed);
};

/**
 * Function that changes the base of a symbol
 * @function swu.symbolScroll
 * @param {string} swuSym - an SWU symbol with optional coordinate and style string
 * @param {boolean} [positive=true] - increase the symbol base
 * @returns {string} SWU symbol with changed base
 * @example
 * swu.symbolScroll('񀀁')
 * 
 * return '񀁡'
 */
const symbolScroll = (swuSym, positive = true) => {
  let parsed = parse.symbol(swuSym);
  if (!parsed.symbol) {
    return swuSym;
  }
  const size = symbolSize(parsed.symbol);
  if (!size) {
    return swuSym;
  }
  parsed.symbol = swu2key(parsed.symbol);
  const step = positive ? 1 : -1;
  const base = parseInt(parsed.symbol.slice(1, 4), 16) + step;
  const fill = parsed.symbol.slice(4, 5);
  const rot = parsed.symbol.slice(5, 6);
  const key = 'S' + base.toString(16) + fill + rot;
  if (key.length == 6 && symbolSize(key2swu(key))) {
    parsed.symbol = key;
  }
  parsed.symbol = key2swu(parsed.symbol);
  return compose.symbol(parsed);
};

/**
 * Function that creates an SVG image from an SWU sign with an optional style string
 * @function swu.signSvgBody
 * @param {string} swuSign - an SWU sign with optional style string
 * @returns {string} body of SVG for sign
 * @example
 * swu.signSvgBody('M525x535S2e748483x510S10011501x466S2e704510x500S10019476x475')
 * 
 * return `<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>`
 */
const signSvgBody = swuSign => {
  let parsed = parse.sign(swuSign);
  const blank = '';
  if (parsed.spatials) {
    let styling = parse$2(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];
    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 svg = `  <text font-size="0">${swuSign}</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(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}"`);
      }
      return `  <g transform="translate(${spatial.coord[0]},${spatial.coord[1]})">
${svg}
  </g>`;
    }).join('\n');
    return svg;
  }
  return blank;
};

/**
 * Function that creates an SVG image from an SWU sign with an optional style string
 * @function swu.signSvg
 * @param {string} swuSign - an SWU sign with optional style string
 * @returns {string} SVG for sign
 * @example
 * swu.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 = swuSign => {
  let parsed = parse.sign(swuSign);
  const blank = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>';
  if (parsed.spatials) {
    let styling = parse$2(parsed.style);
    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];
    let classes = '';
    if (styling.classes) {
      classes = ` class="${styling.classes}"`;
    }
    let id = '';
    if (styling.id) {
      id = ` id="${styling.id}"`;
    }
    if (styling.padding) {
      x1 -= styling.padding;
      y1 -= styling.padding;
      x2 += styling.padding;
      y2 += styling.padding;
    }
    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}">
`;
    svg += signSvgBody(swuSign);
    svg += '\n</svg>';
    return svg;
  }
  return blank;
};

const signCanvas = function (swuSign) {
  const parsed = parse.sign(swuSign);
  if (parsed.spatials) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    let styling = parse$2(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.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];
      }
      context.font = 30 * sizing + "px 'SuttonSignWritingFill'";
      context.fillStyle = symFill;
      context.fillText(symbolFill(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
      context.font = 30 * sizing + "px 'SuttonSignWritingLine'";
      context.fillStyle = symLine;
      context.fillText(symbolLine(spatial.symbol), (spatial.coord[0] - x1) * sizing, (spatial.coord[1] - y1) * sizing);
    });
    return canvas;
  }
};

/**
 * Function that creates a PNG data url from an SWU sign with an optional style string
 * @function swu.signPng
 * @param {string} swuSign - an SWU sign with optional style string
 * @returns {string} png image for sign as data url
 * @example
 * swu.signPng('𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭')
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const signPng = swuSign => {
  const canvas = signCanvas(swuSign);
  const png = canvas.toDataURL("image/png");
  canvas.remove();
  return png;
};

/**
 * Function that normalizes an SWU sign for a center of 500,500
 * @function swu.signNormalize
 * @param {string} swuSign - an SWU sign with optional style string
 * @returns {string} normalized SWU sign
 * @example
 * swu.signNormalize('𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭')
 * 
 * return '𝠀񀀒񀀚񋚥񋛩𝠃𝤟𝤩񋛩𝣵𝤐񀀒𝤇𝣤񋚥𝤐𝤆񀀚𝣮𝣭'
 */
const signNormalize = swuSign => {
  const parsed = parse.sign(swuSign);
  if (parsed.spatials) {
    const symbolsizes = parsed.spatials.reduce((output, spatial) => {
      const size = symbolSize(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 swuout = (parsed.sequence ? '𝠀' + parsed.sequence.join('') : '') + parsed.box + coord2swu$1([max[0] - offset[0], max[1] - offset[1]]) + parsed.spatials.map(spatial => spatial.symbol + coord2swu$1([spatial.coord[0] - offset[0], spatial.coord[1] - offset[1]])).join('') + (parsed.style || '');
    return swuout;
  }
};

/**
 * Function that creates an SVG image for a column of SWU
 * @function swu.columnSvg
 * @param {ColumnData} swuColumn - an array of objects with information about FSW signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string} column svg
 * @example
 * swu.columnSvg([
 *   {
 *     "x": 56,
 *     "y": 20,
 *     "minX": 481,
 *     "minY": 471,
 *     "width": 37,
 *     "height": 58,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "sign",
 *     "text": "𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻",
 *     "zoom": 1
 *   },
 *   {
 *     "x": 57,
 *     "y": 118,
 *     "minX": 482,
 *     "minY": 468,
 *     "width": 36,
 *     "height": 65,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "sign",
 *     "text": "𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦",
 *     "zoom": 1
 *   },
 *   {
 *     "x": 39,
 *     "y": 203,
 *     "minX": 464,
 *     "minY": 496,
 *     "width": 72,
 *     "height": 8,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "symbol",
 *     "text": "񏌁𝣢𝤂",
 *     "zoom": 1
 *   }
 * ],
 * {
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
 *   <g transform="translate(56,20) scale(1) translate(-481,-471) ">
 *     <text font-size="0">𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻-D_black,white_Z1</text>
 *     <g transform="translate(481,471)">
 *       <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(503,489)">
 *       <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>
 *   <g transform="translate(57,118) scale(1) translate(-482,-468) ">
 *     <text font-size="0">𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦-D_black,white_Z1</text>
 *     <g transform="translate(489,515)">
 *       <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(482,490)">
 *       <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(508,496)">
 *       <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(500,468)">
 *       <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>
 *   <g transform="translate(39,203) scale(1) translate(-464,-496) ">
 *     <text font-size="0">񏌁𝣢𝤂-D_black,white_Z1</text>
 *     <g transform="translate(464,496)">
 *       <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>
 * </svg>`
 */
const columnSvg = (swuColumn, options) => {
  //if (typeof swuColumn !== 'array') return blank;
  if (typeof options !== 'object') options = {};
  const values = Object.assign(columnDefaults, options);
  let x1 = 0;
  let y1 = 0;
  let x2 = values.width;
  let y2 = values.height;
  let background = '';
  if (values.background) {
    background = `\n  <rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" style="fill:${values.background};" />`;
  }
  let sizing = ` width="${values.width}" height="${values.height}"`;
  let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg"${sizing} viewBox="${x1} ${y1} ${x2 - x1} ${y2 - y1}">
  <text font-size="0">${x1}</text>${background}`;
  svg += swuColumn.map(item => {
    const dash = item.text.indexOf('-');
    if (dash > 0) {
      const itemStyle = item.text.substring(dash);
      const newStyle = {
        ...values.style,
        ...parse$2(itemStyle)
      };
      item.text = item.text.replace(itemStyle, compose$1(newStyle));
    } else {
      item.text += compose$1(values.style);
    }
    item.zoom = item.zoom * values.style.zoom;
    return '<g transform="translate(' + item.x + ',' + item.y + ') scale(' + item.zoom + ') translate(' + -item.minX + ',' + -item.minY + ') ">' + (item.segment == "sign" ? signSvgBody(item.text) : symbolSvgBody(item.text)) + '</g>';
  }).join('\n');
  svg += '\n</svg>';
  return svg;
};

/**
 * Function that creates an array of SVG column images for an SWU text
 * @function swu.columnsSvg
 * @param {string} swuText - a text of SWU signs and punctuation
 * @param {ColumnOptions} options - an object of column options
 * @returns {string[]} array of SVG columns
 * @example
 * swu.columnsSvg('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂',{
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return [`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="150" height="250" viewBox="0 0 150 250">
 *   <g transform="translate(56,20) scale(1) translate(-481,-471) ">
 *     <text font-size="0">𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻-D_black,white_Z1</text>
 *     <g transform="translate(481,471)">
 *       <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(503,489)">
 *       <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>
 *   <g transform="translate(57,118) scale(1) translate(-482,-468) ">
 *     <text font-size="0">𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦-D_black,white_Z1</text>
 *     <g transform="translate(489,515)">
 *       <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(482,490)">
 *       <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(508,496)">
 *       <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(500,468)">
 *       <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>
 *   <g transform="translate(39,203) scale(1) translate(-464,-496) ">
 *     <text font-size="0">񏌁𝣢𝤂-D_black,white_Z1</text>
 *     <g transform="translate(464,496)">
 *       <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>
 * </svg>`]
 */
const columnsSvg = function (swuText, options) {
  if (typeof options !== 'object') options = {};
  let values = columns(swuText, options);
  let cols = values.columns.map((col, i) => {
    return columnSvg(col, {
      ...values.options,
      ...{
        width: values.widths[i]
      }
    });
  });
  return cols;
};

const columnCanvas = function (swuColumn, options) {
  if (typeof options !== 'object') options = {};
  const values = Object.assign(columnDefaults, options);
  const canvas = document.createElement('canvas');
  canvas.width = values.width;
  canvas.height = values.height;
  const context = canvas.getContext('2d');
  if (values.background) {
    context.rect(0, 0, values.width, values.height);
    context.fillStyle = values.background;
    context.fill();
  }
  swuColumn.map(item => {
    const dash = item.text.indexOf('-');
    if (dash > 0) {
      const itemStyle = item.text.substring(dash);
      const newStyle = {
        ...values.style,
        ...parse$2(itemStyle)
      };
      item.text = item.text.replace(itemStyle, compose$1(newStyle));
    } else {
      item.text += compose$1(values.style);
    }
    item.zoom = item.zoom * values.style.zoom;
    let parsed = {};
    if (item.segment == "sign") {
      parsed = parse.sign(item.text);
    } else {
      let sym = parse.symbol(item.text);
      parsed.style = sym.style;
      parsed.spatials = [sym];
    }
    let styling = parse$2(parsed.style);
    if (styling.background) {
      context.fillStyle = styling.background;
      context.fillRect(item.x - styling.padding * item.zoom, item.y - styling.padding * item.zoom, (item.width + styling.padding * 2) * item.zoom, (item.height + styling.padding * 2) * item.zoom);
    }
    if (styling.detailsym) {
      styling.detailsym.forEach(sym => {
        if (parsed.spatials[sym.index - 1]) {
          parsed.spatials[sym.index - 1].detail = sym.detail;
        }
      });
    }
    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];
      }
      context.font = 30 * item.zoom + "px 'SuttonSignWritingFill'";
      context.fillStyle = symFill;
      context.fillText(symbolFill(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
      context.font = 30 * item.zoom + "px 'SuttonSignWritingLine'";
      context.fillStyle = symLine;
      context.fillText(symbolLine(spatial.symbol), item.x + (spatial.coord[0] - item.minX) * item.zoom, item.y + (spatial.coord[1] - item.minY) * item.zoom);
    });
  });
  return canvas;
};

/**
 * Function that creates a PNG data url for a column of SWU
 * @function swu.columnPng
 * @param {ColumnData} swuColumn - an array of SWU signs and punctuation with coordinates
 * @param {ColumnOptions} options - an object of column options
 * @returns {string} column png data url
 * @example
 * swu.columnPng([
 *   {
 *     "x": 56,
 *     "y": 20,
 *     "minX": 481,
 *     "minY": 471,
 *     "width": 37,
 *     "height": 58,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "sign",
 *     "text": "𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻",
 *     "zoom": 1
 *   },
 *   {
 *     "x": 57,
 *     "y": 118,
 *     "minX": 482,
 *     "minY": 468,
 *     "width": 36,
 *     "height": 65,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "sign",
 *     "text": "𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦",
 *     "zoom": 1
 *   },
 *   {
 *     "x": 39,
 *     "y": 203,
 *     "minX": 464,
 *     "minY": 496,
 *     "width": 72,
 *     "height": 8,
 *     "lane": 0,
 *     "padding": 0,
 *     "segment": "symbol",
 *     "text": "񏌁𝣢𝤂",
 *     "zoom": 1
 *   }
 * ],
 * {
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return 'data:image/png;base64,iVBORw...'
 */
const columnPng = (swuColumn, options) => {
  const canvas = columnCanvas(swuColumn, options);
  const png = canvas.toDataURL("image/png");
  canvas.remove();
  return png;
};

/**
 * Function that creates an SVG image for a column of SWU
 * @function swu.columnsPng
 * @param {string} swuText - an array of SWU signs and punctuation with coordinates
 * @param {ColumnOptions} options - an object of column options
 * @returns {string[]} array of PNG data urls
 * @example
 * swu.columnsPng('𝠀񁲡񈩧𝠃𝤘𝤣񁲡𝣳𝣩񈩧𝤉𝣻 𝠀񃊢񃊫񋛕񆇡𝠃𝤘𝤧񃊫𝣻𝤕񃊢𝣴𝣼񆇡𝤎𝤂񋛕𝤆𝣦 񏌁𝣢𝤂',{
 *   "height": 250,
 *   "width": 150,
 * })
 * 
 * return ['data:image/png;base64,iVBORw...']
 */
const columnsPng = function (swuText, options) {
  if (typeof options !== 'object') options = {};
  let values = columns(swuText, options);
  let cols = values.columns.map((col, i) => {
    return columnPng(col, {
      ...values.options,
      ...{
        width: values.widths[i]
      }
    });
  });
  return cols;
};

/** The swu module contains functions for handling SignWriting in Unicode (SWU) characters.
 * [SWU characters definition](https://tools.ietf.org/id/draft-slevinski-formal-signwriting-09.html#name-signwriting-in-unicode-swu)
 * @module swu
 */

var index = /*#__PURE__*/Object.freeze({
  __proto__: null,
  symbolSize: symbolSize,
  symbolLine: symbolLine,
  symbolFill: symbolFill,
  symbolText: symbolText,
  symbolSvgBody: symbolSvgBody,
  symbolSvg: symbolSvg,
  symbolPng: symbolPng,
  symbolNormalize: symbolNormalize,
  symbolMirror: symbolMirror,
  symbolRotate: symbolRotate,
  symbolFlop: symbolFlop,
  symbolScroll: symbolScroll,
  signSvgBody: signSvgBody,
  signSvg: signSvg,
  signPng: signPng,
  signNormalize: signNormalize,
  columnSvg: columnSvg,
  columnsSvg: columnsSvg,
  columnPng: columnPng,
  columnsPng: columnsPng
});

export { index$2 as font, index$1 as fsw, index as swu };

/* support ongoing development on https://patreon.com/signwriting */
