asciibird/src/ascii.js

739 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import LZString from 'lz-string';
import store from './store';
// 0 => 'white',
// 1 => 'black',
// 2 => 'navy',
// 3 => 'green',
// 4 => 'red',
// 5 => 'brown',
// 6 => 'purple',
// 7 => 'olive',
// 8 => 'yellow', # dark yellow
// 9 => 'lime', # ltgreen
// 10 => 'teal',
// 11 => 'cyan',
// 12 => 'blue', # ltblue,
// 13 => 'fuchsia', # pink
// 14 => 'grey',
// 15 => 'lightgrey',
export const mircColours99 = [
'rgb(255,255,255)',
'rgb(0,0,0)',
'rgb(0,0,127)',
'rgb(0,147,0)',
'rgb(255,0,0)',
'rgb(127,0,0)',
'rgb(156,0,156)',
'rgb(252,127,0)',
'rgb(255,255,0)',
'rgb(0,252,0)',
'rgb(0,147,147)',
'rgb(0,255,255)',
'rgb(0,0,252)',
'rgb(255,0,255)',
'rgb(127,127,127)',
'rgb(210,210,210)',
"#470000",
"#472100",
"#474700",
"#324700",
"#004700",
"#00472c",
"#004747",
"#002747",
"#000047",
"#2e0047",
"#470047",
"#47002a",
"#740000",
"#743a00",
"#747400",
"#517400",
"#007400",
"#007449",
"#007474",
"#004074",
"#000074",
"#4b0074",
"#740074",
"#740045",
"#b50000",
"#b56300",
"#b5b500",
"#7db500",
"#00b500",
"#00b571",
"#00b5b5",
"#0063b5",
"#0000b5",
"#7500b5",
"#b500b5",
"#b5006b",
"#ff0000",
"#ff8c00",
"#ffff00",
"#b2ff00",
"#00ff00",
"#00ffa0",
"#00ffff",
"#008cff",
"#0000ff",
"#a500ff",
"#ff00ff",
"#ff0098",
"#ff5959",
"#ffb459",
"#ffff71",
"#cfff60",
"#6fff6f",
"#65ffc9",
"#6dffff",
"#59b4ff",
"#5959ff",
"#c459ff",
"#ff66ff",
"#ff59bc",
"#ff9c9c",
"#ffd39c",
"#ffff9c",
"#e2ff9c",
"#9cff9c",
"#9cffdb",
"#9cffff",
"#9cd3ff",
"#9c9cff",
"#dc9cff",
"#ff9cff",
"#ff94d3",
"#000000",
"#131313",
"#282828",
"#363636",
"#4d4d4d",
"#656565",
"#818181",
"#9f9f9f",
"#bcbcbc",
"#e2e2e2",
"#ffffff",
];
// How big the brush size can get
export const maxBrushSize = 15;
// Chars that end up in the toolbar
export const charCodes = [' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-',
'.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '{', '|', '}', '~', 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è',
'ï', 'î', 'ì', 'Ä', 'Å', 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', 'ø', '£',
'Ø', '×', 'ƒ', 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '®', '¬', '½', '¼', '¡', '«',
'»', '░', '▒', '▓', '│', '┤', 'Á', 'Â', 'À', '©', '╣', '║', '╗', '╝', '¢', '¥', '┐', '└',
'┴', '┬', '├', '─', '┼', 'ã', 'Ã', '╚', '╔', '╩', '╦', '╠', '═', '╬', '¤', 'ð', 'Ð', 'Ê',
'Ë', 'È', 'ı', 'Í', 'Î', 'Ï', '┘', '┌', '█', '▄', '¦', 'Ì', '▀', 'Ó', 'ß', 'Ô', 'Ò', 'õ',
'Õ', 'µ', 'þ', 'Þ', 'Ú', 'Û', 'Ù', 'ý', 'Ý', '¯', '´', '≡', '±', '‗', '¾', '¶', '§', '÷',
'¸', '°', '¨', '·', '¹', '³', '²',
];
// Toolbar icons
export const toolbarIcons = [{
name: 'default',
icon: 'mouse-pointer',
fa: 'fas',
},
{
name: 'select',
icon: 'square',
fa: 'far',
},
{
name: 'text',
icon: 'font',
fa: 'fas',
},
{
name: 'fill',
icon: 'fill-drip',
fa: 'fas',
},
{
name: 'brush',
icon: 'paint-brush',
fa: 'fas',
},
{
name: 'dropper',
icon: 'eye-dropper',
fa: 'fas',
},
{
name: 'eraser',
icon: 'eraser',
fa: 'fas',
},
{
name: 'fill-eraser',
icon: 'fill',
fa: 'fas',
},
];
export const emptyBlock = {
bg: null,
fg: null,
char: null,
};
export const create2DArray = (rows) => {
const arr = [];
for (let i = 0; i < rows; i++) {
arr[i] = [];
}
return arr;
};
// Width and height of the ASCII blocks
// they seem to be 8x15 in asciiblaster
export const blockWidth = 8;
export const blockHeight = 15;
export const parseMircAscii = async (content, title) => {
const MIRC_MAX_COLOURS = mircColours99.length;
// The current state of the Colours
let curBlock = {
...emptyBlock,
};
const contents = content;
const filename = title;
// set asciiImport as the entire file contents as a string
const asciiImport = contents
.split('\u0003\u0003')
.join('\u0003')
.split('\u000F').join('')
.split('\u0003\n')
.join('\n')
.split('\u0002\u0003')
.join('\u0003');
// This will end up in the asciibirdMeta
const finalAscii = {
title: filename,
// key: store.getters.nextTabValue,
// blockWidth: blockWidth * store.getters.blockSizeMultiplier,
// blockHeight: blockHeight * store.getters.blockSizeMultiplier,
blocks: [{
label: filename,
visible: true,
data: create2DArray(asciiImport.split('\n').length),
width: false, // defined in: switch (curChar) case "\n":
height: asciiImport.split('\n').length,
}],
history: [],
redo: [],
x: blockWidth * 35, // the dragable ascii canvas x
y: blockHeight * 2, // the dragable ascii canvas y
selectedLayer: 0,
};
// Turn the entire ascii string into an array
let asciiStringArray = asciiImport.split('');
const linesArray = asciiImport.split('\n');
// The proper X and Y value of the block inside the ASCII
let asciiX = 0;
let asciiY = 0;
// used to determine colours
let colourChar1 = null;
let colourChar2 = null;
let parsedColour = null;
// This variable just counts the amount of colour and char codes to minus
// to get the real width
let widthOfColCodes = 0;
// Better for colourful asciis
let maxWidthLoop = 0;
// Used before the loop, better for plain text
let maxWidthFound = 0;
for (let i = 0; i < linesArray.length; i++) {
if (linesArray[i].length > maxWidthFound) {
maxWidthFound = linesArray[i].length;
}
}
while (asciiStringArray.length) {
const curChar = asciiStringArray[0];
// Defining a small finite state machine
// to detect the colour code
switch (curChar) {
case '\n':
// Reset the colours here on a new line
curBlock = {
...emptyBlock,
};
if (linesArray[asciiY] && linesArray[asciiY].length > maxWidthLoop) {
maxWidthLoop = linesArray[asciiY].length;
}
// the Y value of the ascii
asciiY++;
// Calculate widths mirc asciis vs plain text
if (!finalAscii.blocks[0].width && widthOfColCodes > 0) {
finalAscii.blocks[0].width = maxWidthLoop - widthOfColCodes;
}
if (!finalAscii.blocks[0].width && widthOfColCodes === 0) {
// Plain text
finalAscii.blocks[0].width = maxWidthFound;
}
// Resets the X value
asciiX = 0;
asciiStringArray.shift();
widthOfColCodes = 0;
break;
case '\u0003':
// Remove the colour char
asciiStringArray.shift();
widthOfColCodes++;
// Attempt to work out bg
colourChar1 = `${asciiStringArray[0]}`;
colourChar2 = `${asciiStringArray[1]}`;
parsedColour = parseInt(`${colourChar1}${colourChar2}`);
// Work out the 01, 02 double digit codes
if (parseInt(colourChar1) === 0 && parseInt(colourChar2) >= 0) {
asciiStringArray.shift();
}
if (Number.isNaN(parsedColour)) {
curBlock.bg = parseInt(colourChar1);
widthOfColCodes += 1;
asciiStringArray.shift();
} else if (parsedColour <= MIRC_MAX_COLOURS && parsedColour >= 0) {
curBlock.fg = parseInt(parsedColour);
widthOfColCodes += parsedColour.toString().length;
asciiStringArray = asciiStringArray.slice(
parsedColour.toString().length,
asciiStringArray.length,
);
}
// No background colour
if (asciiStringArray[0] !== ',') {
break;
} else {
// Remove , from array
widthOfColCodes += 1;
asciiStringArray.shift();
}
// Attempt to work out bg
colourChar1 = `${asciiStringArray[0]}`;
colourChar2 = `${asciiStringArray[1]}`;
parsedColour = parseInt(`${colourChar1}${colourChar2}`);
if (
!Number.isNaN(colourChar1) &&
!Number.isNaN(colourChar2) &&
parseInt(colourChar2) > parseInt(colourChar1) &&
!Number.isNaN(parsedColour) &&
parseInt(parsedColour) < 10
) {
parsedColour = parseInt(colourChar2);
widthOfColCodes += 1;
asciiStringArray.shift();
}
if (
parseInt(colourChar2) === parseInt(colourChar1) &&
parseInt(parsedColour) < 10
) {
parsedColour = parseInt(colourChar1);
asciiStringArray.shift();
asciiStringArray.shift();
widthOfColCodes += 2;
curBlock.bg = parseInt(colourChar1);
break;
}
if (Number.isNaN(parsedColour)) {
curBlock.bg = parseInt(colourChar1);
widthOfColCodes += 1;
asciiStringArray.shift();
} else if (parsedColour <= MIRC_MAX_COLOURS && parsedColour >= 0) {
curBlock.bg = parseInt(parsedColour);
widthOfColCodes += parsedColour.toString().length;
asciiStringArray = asciiStringArray.slice(
parsedColour.toString().length,
asciiStringArray.length,
);
break;
}
break;
default:
curBlock.char = curChar;
asciiStringArray.shift();
asciiX++;
finalAscii.blocks[0].data[asciiY][asciiX - 1] = {
...curBlock,
};
break;
} // End Switch
} // End loop charPos
finalAscii.blocks = [...fillNullBlocks(finalAscii.blocks[0].height, finalAscii.blocks[0]
.width, finalAscii.blocks)];
// Store the ASCII and ensure we have no null blocks
finalAscii.blocks = LZString.compressToUTF16(
JSON.stringify(finalAscii.blocks),
);
// We need to also store in the first undo history the original state
finalAscii.history.push(finalAscii.blocks);
// Save ASCII to storage
store.commit('newAsciibirdMeta', finalAscii);
return true;
};
// Creates new blank ASCII
export const createNewAscii = (forms) => {
const newAscii = {
title: forms.createAscii.title,
history: [],
redo: [],
x: 247, // the dragable ascii canvas x
y: 24, // the dragable ascii canvas y
blocks: [{
label: forms.createAscii.title,
visible: true,
data: create2DArray(forms.createAscii.height),
width: forms.createAscii.width,
height: forms.createAscii.height,
}],
selectedLayer: 0,
};
// Push all the default ASCII blocks
for (let x = 0; x < newAscii.blocks[0].width; x++) {
for (let y = 0; y < newAscii.blocks[0].height; y++) {
newAscii.blocks[0].data[y].push({
...emptyBlock,
});
}
}
newAscii.blocks = LZString.compressToUTF16(JSON.stringify(newAscii.blocks));
newAscii.history.push(newAscii.blocks);
store.commit('newAsciibirdMeta', newAscii);
store.commit('closeModal', 'new-ascii');
return true;
};
// Converts ASCIIBIRD blocks to mIRC colours
export const exportMirc = () => {
const {
currentAscii
} = store.getters;
const blocks = [...store.getters.currentAsciiLayers];
const output = [];
let curBlock = false;
let currentLayer = 0;
let prevBlock = {
bg: -1,
fg: -1
};
for (let y = 0; y <= blocks[0].data.length - 1; y++) {
if (y >= currentAscii.height) {
continue;
}
for (let x = 0; x <= blocks[0].data[y].length - 1; x++) {
if (x >= currentAscii.width) {
continue;
}
for (let i = blocks.length - 1; i >= 0; i--) {
if (blocks[i].visible === true) {
currentLayer = i;
if (
blocks[i].data &&
blocks[i].data[y] &&
blocks[i].data[y][x] &&
i > 0 &&
JSON.stringify(blocks[i].data[y][x]) ===
JSON.stringify(emptyBlock)
) {
continue;
} else if (
// Otherwise if we are on the very first layer we need to render it
blocks[i].data &&
blocks[i].data[y] &&
blocks[i].data[y][x]
) {
curBlock = {
...blocks[i].data[y][x]
};
break;
}
}
}
// If we have a difference between our previous block
// we'll put a colour codes and continue as normal
if (curBlock.bg !== prevBlock.bg || curBlock.fg !== prevBlock.fg) {
curBlock = {
...blocks[currentLayer].data[y][x]
};
const zeroPad = (num, places) => String(num).padStart(places, '0');
output.push(
`\u0003${zeroPad(
curBlock.fg ?? store.getters.options.defaultFg,
2,
)},${zeroPad(curBlock.bg ?? store.getters.options.defaultBg, 2)}`,
);
}
// null .chars will end up as space
output.push(curBlock.char ?? ' ');
prevBlock = blocks[currentLayer].data[y][x];
}
// We can never have a -1 colour code so we'll always
// write one at the start of each line
prevBlock = {
bg: -1,
fg: -1
};
// New line except for the very last line
if (y < blocks[currentLayer].data[y].length - 1) {
output.push('\n');
}
}
// Download to a txt file
// Check if txt already exists and append it
const filename = currentAscii.title.slice(currentAscii.title.length - 3) === 'txt' ?
currentAscii.title :
`${currentAscii.title}.txt`;
return {
filename,
output
}
}
// Download a string to a file with a filename
export const downloadFile = (content, filename, contentType) => {
const a = document.createElement('a');
const file = new Blob([content], {
type: contentType
});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
};
export const checkForGetRequest = async () => {
const asciiUrlCdn = new URL(location.href).searchParams.get('ascii');
if (asciiUrlCdn) {
const res = await fetch(`https://ascii.jewbird.live/${asciiUrlCdn}`, {
method: 'GET',
headers: {
Accept: 'text/plain',
},
});
const asciiData = await res.text();
parseMircAscii(asciiData, asciiUrlCdn);
return;
}
const asciiUrl = new URL(location.href).searchParams.get('ircwatch');
if (asciiUrl) {
const res = await fetch(`https://irc.watch/ascii/txt/${asciiUrl}`, {
method: 'GET',
headers: {
Accept: 'text/plain',
},
});
const asciiData = await res.text();
parseMircAscii(asciiData, asciiUrl);
return;
}
const haxAscii = new URL(location.href).searchParams.get('haxAscii');
if (haxAscii) {
const res = await fetch(`https://art.h4x.life/${haxAscii}`, {
method: 'GET',
headers: {
Accept: 'text/plain',
},
});
// Considers paths
const asciiName = haxAscii.split('/').pop();
const asciiData = await res.text();
parseMircAscii(asciiData, asciiName);
return;
}
}
// Hashing algo to detect duplicate brushes
// from https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
export const cyrb53 = function (str, seed = 1337) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
// Mostly plain text asciis wont have all their blocks
// so this will fix that
export const fillNullBlocks = function (height, width, layerData = null) {
// Probably used on irc import to make the blocks proper,
// especially with plain text ascii
if (layerData === null) {
var layers = [...store.getters.currentAsciiLayers]
} else {
var layers = [...layerData]
}
// if (height === false || width === false) {
// height = store.getters.currentAscii.height;
// width = getBlocksWidth(store.getters.currentAsciiLayers[store.getters.currentAscii
// .selectedLayer].data)
// }
for (let i = 0; i <= layers.length - 1; i++) {
let blocks = layers[i].data;
for (let y = 0; y < height; y++) {
// New row
if (!blocks[y]) {
blocks[y] = [];
for (let x = 0; x < width; x++) {
blocks[y][x] = {
...emptyBlock
};
}
} else {
// no new rows but new cols
for (let x = 0; x < width; x++) {
if (blocks[y] && !blocks[y][x]) {
blocks[y][x] = {
...emptyBlock
};
}
}
}
}
// Update layer with new blocks
layers[i].data = [...blocks]
layers[i].width = width
layers[i].height = height
}
return layers
}
// Sometimes if we copy blocks the initial Y values will be null
// and cause an error when trying to calculate width
// So we get the longest x length
export const getBlocksWidth = function (blocks) {
let maxWidth = 0;
for (let y = 0; y < blocks.length; y++) {
if (!blocks[y]) {
continue
}
if (blocks[y] && blocks[y].length > maxWidth) {
maxWidth = blocks[y].length
}
}
return maxWidth
}
// This removes the null blocks from our copy and paste
// to make sure it's centered better
export const filterNullBlocks = function (blocks) {
let newBlocks = [];
let y;
blocks = blocks.filter(function (item) {
return item !== null
});
for (y = 0; y < blocks.length; y++) {
newBlocks[y] = (blocks[y].filter(function (item) {
return item !== null
}))
}
return newBlocks
}
// Function to check if the left and top values are visible on the screen
export const checkVisible = function (bottom, top) {
var viewHeight = Math.max(
document.documentElement.clientHeight,
window.innerHeight
);
return !(bottom < 0 || top - viewHeight >= 0);
}
export const splashAscii = JSON.parse(LZString.decompressFromEncodedURIComponent(
"NrDeCICMHNwLgAwBpwDNaJQYwBYEMAnecAAnAF8kIZ5k0M7dDizLqGV1bt8i5SKVKB3rdwTPgLbCxXTON4tB7WSIlLpNeXMaL+rIVro6ezfcpna1eqYZEmFZ2yqtj15zfetODL498lfS383GyCjTgCNO1VQnwsI0Xl3Z2DIuMCErwzov3TksKzYgvjPYt1SmNcSzLLqitqqkJrctKSG1sSHFPDslo8m-I6BvPbTRtHuwrrm4dSuqJG2qcrJxfm+ud7y8c7N3aWFnMP9xwnl9e36g42ds727nqLr+5PH6cGx19uXp5mhm5XWaA57A75AgHg0GQv6fFbnI79H5g2FrY7ImEfNFIiFfVEXdG4+EPX5Ygk46F4smIraU4lvUmrcm0-5Upk0kGs+kYtkI074jlQrmXOki4WE0US8UU6UsuFi+VSxUy5Vy7Fq5mc1Va9U6zVC7UG3VG-UC-nU83sy1895W202xkOlEW+0k512x1uzEe91O71+3lewMM31B7lEhXGs2ukP+sOR00uz2x4M88PPAB2AFcADY5-LZvMHQs58gAXSQYETPrjKfTsr1gujybTCabSdDdbb1vjSqjHdrrb71YD9cNzc7Q5V-ZrqYjw-bs7HM9H3ZjU41i9XC57XZ36-n05HvaPW5Pm93G8bl8PF4PktPN4fd5bt+v94bJrPe8fH-HA7nZ931fICvyfT8J0HN8wL-Fdz2AydoMgwCIIA5djx-F9ENA5D0O-K8YJA1ClzXIj-xI-cyLgzCEKgnC0NI7DiO3X8qIwgjcMYujmPgwimPIlisO4gTeM4yj+OojiGPE4TJKQ6TWIk9j5Io1iSwLXN81eEty0rOT6NUoSUJEmi+Nk5SDME2jjP0njTObABOcz8JUqyzJsiy7KkwzrLw8CTO8h0nNTQBE0meYLuTCuoIq4jyXMs0SFKMvzYM8gLXMSnz3JStj4q8jL7KS3zYpypS8vShLCqysTFOc-zbIq-LKsCzK3Jq5KSs6mS4vqtKGv6vrBvKgbhqG3rRom8aptSyaZum3L5rKxa6rm1aFrWpaNpW9ads23btr2w6DuOnqttO-bzqO871KSG73B0isq1mi7SpO17Lrerrao+n6cpiyj-qRKKqkBuZgZcUGbnBsazveuGvo67q-u2yHCNRlJoa0dGbExjBsacXH4HxyRCbgYnWopqq2qK7KEeKpG6dphmAcHUmrvh5nvo5rnGfa4Dyf4NnPs5xGeZF+mxcl0WXwF8BSdlgqWqpymldVxX1eajWmu1xrdZG5bft5mm+aZqWJel83LdNi3ratk37eNx3qqd6nnbd12PZVrW9Zhl6jfdr3NaDuobrkO69AevTffZ-3PeV+O1eDn2sdZ8LU+i9OQcziHs8sWX5dzlPDfF23S4dj0FbT8786ryLa-WAvQvrkvy7jxOdf12HY8DjPq5Rwu8YHomh79luXZ75P5pr3u65nhuk870ezbL8eE6uaes6buf0Ub5Gt8nrux4DteF+j4Xl9bifF5jo+2+96-8gARhuQAQ0gfZ-vkAANJ35ub-pQ-lIf9lQAJsEA7EICnBgPJBAyQUDEQwOIHAvoAAOYKKQ37SlQRLLBpscE1TwQpAhqkiEsRISeMhP4KFTioYeGhD46GYLQYQphxCWGkLYeQjhlCuHUJ4bQvh9CBEd0sKHDA4czCRyegbbmNtL4n2EUXWem8lE533qolRecR4b3UfPbeQNm76L0WDAxMtz6yNXu3ZRpFtGaLUbYjRiju7yIPkvcxx9LEP2LqxSuRj+ZaP8XYxxO8TF2wsffM+fdIkOMHoEmJ0TD4XzCafZ6N9EkVwCfEsmGTdFWOCQo6RTiPERJkaE9x4T4Gvx-l-KpgDBGVP-vUqoH85AIIZM0jArSfjtPgJ0q43S4C9OeP0wZdRhnATGYRCZzYplJhmR6OZDoFkkn6VQpBPSn6NPARLEZwDtl7NNjsrZBz9k1UOdAk5CkzkVOOTc05FyynJLEZpDSRZtKaV0lIhJbi76POHrEv5mSbFBMMbkkFOi8mguMfkr5fj-lZLhT4yFsKSkrweb4tG2SIXgrBcUwp5SCm3yvriwlDJEXYtMQizFOLgVQqRain50LXGlIZXSxyVLaWeJRXIopKSzHMqJTSqGITvi7xyeSoV6KMa-KZfSgVBLJadIwU0jZkyVXTLVbMjV8ytWLJ1csvVbSDVdKNX0k1QyzWjItcqpIVyOlWpcEsw1Nrxn2ssI641SRVnmudaqn16q-WaoDdqz1PC1lwBwXIBhVQI0YCoUqlwMb4BerqIm8Noa6m4KEcqKN2Ic3kjzYiAtKCs25pLfmsthaK3FuwVWtaoj4DiL4JIzleLpVAriWK+xnbBUiuFVKyVOM+1JMZakilgL2UStZcOlxo7+Wkonb2gdBMh3pKia2kdfLZXOMXmSrtLNKUHsybOrdPL5XfLlTCk968F39qncujdXjz3buJWkllO7+6Hu7R2-dR7N3csPIqmpoDvUtJdUG3V4H9WQadaB31sH-XwcDYh4NyGIOoag+hmDdq4PYYQ7hpD+GUOEbQ8RjDpGsPrJw5RvD1GCO0aI-RkjjGyPMYowMsDmGPWcdNdBrj5G+OsYE+xqjwmaNpt-iB-j8a3X5DjRm-BtbqhFuKMppTinmiqb2vWuAjaG3vMei2klp6AVfpM1ivd1Lv2WbM94v907xUYrXT+0z8Lx2fqM-iy9-6203sHUukmK6gq+dXb+pzr6L07V3T2tl7nrOuZc8e7zD6uX2ZfU+4z8XzPRb83egLyX10zrs2iwrj650-Ci1ZsdLn202Y5b7d1PHuOScE41-jrWWvNdE3RrrDGetMb6yxgbbHbWUaa5a3j7WhtCZG8Jsb1q5sOtdVoBrnWZsrfGwtmTE3VscbaztkTa2luxsBp0sNqbTvPHOxJlNJ3rvRtu9Um7RqzsPdqU93jL3nuXde8B97nHPsfe+7JxTqa5OMJrRDq9mZnm3Rh-dfTUdeWlcc251H1WP17zR1lyryL0exbq9l+9uXiCiux7VydDmkslZS8Vwztn8cU4swTnHi7ieC0CyTjn7P-Mkgq15pMfPMv06x3FmrMqqd0-S558XqXnpi-l8Ft9aWyvS8S7Ls9KuuflcV5znn3PQWdNJut+be2Num5Nx1s3luLdTcm4d7bVvbf7bE-bzby2jujfN4th3NvXde62277DgfPfW+98H2b-v3c+7D5HoPseQ9O8d370PAfjt3Zj0m9NSfNmp8E2GlZPDpOVshwpkvzCy+sIr+wqvnCa-Pq0Np3TOmEefJl7TpnjPCd5bZ9rsLzPyes8pzlofROR-d7H7rnvpPRc6-1xPufyuof5Y89KtXIWEsY7xyL-vQud8K+RxL32gv9-b87yzw-SOadK8vwVyXuOye77PwP298+Mtr+vxrpf1Pb9H834JovVmhuBiQBeiIBvuu2KeUe4exuGeie4BB2EBcBsByeSBueqBUB8eEekBce2BCeKB+BiBBBCBxBLuhBZBJB3WM2V2j292X2f2-GAO-2zulB5BpBFBvWgG2eNBRydytyly9yLEM2zQQhHuO0jecOEcLed+KOW+shD+gu7+vOs+csPmDOg+He6hXek+76ahL+GhehN+K+y+4W9en+F+U8f+M+uhw++hAuRWH+a0x+yh0+e+I8ihph-O7ehhJhGWJ+chcW7hb+9hEWgRquwRw29+6BgBkRYB2KsRFm8RGBuBWBUReBrBLB7B-WRBbBORGRuRHB6RHooOIOgMqmxRwE5RmRg22ReRtRBRfBqkIh0e5yDRghAhJ4TRkaWeuy3BLRb2PR-RPB-BrRHR7RP4nRmBoR0OryYcEhEiUhi+5hz+NhWh86B+6unhDhbeWxe0Th1ho+thBx3hUuq+4RQR6xXhFh+x4+hxNxqxKheuveV+IRlhVW8hzhWuLhT+j+Mh7x1x2htxAJxxmuxhbxVhp+mh5+KxZhGx2xLxFxWRhRQWzRwKM2P2Rx0RkJNmiROBqRKRNR9R+RiJVRERJJ02SJ1RFJpJzYlRYmyaNu9JyBVJ5JZJduzJAGYxU4ExyRwhnJHJIx4xfJQGjRQpDSApXJopAxwxvBMpsJ4hry8OeYHy0hSxkWZxYRCJ8JzxHhcJOpfhfxEJqpUxoJIJ3+zmBp-hrh-xC+9x16mpOpxpLiexhpdh1pDxU+nxnpjxXxWJPxBhMJlxjhrx3x+p4JG+9p5x2pkZUmzBhJdRyJ4e6JdxmJ-p2J3xMBaBBJxJRJlJrJsZ2Z8ZuZOZ1JhZJZcZ5ZBZFZRZpZ4AYavJ4p-JspIpDZwpbRLZYpTZbZnZox7ZUpzZ3ZgpvZQx-Z0pcpMOsxCpkhSpBmixsJjpKpc56ppxEZGpUZ0uoZVpoWa5y5253pqhLpGJyxh5fp0JR5yZZ5ShK5O5P+wJX+C56+5pYZj5m5lpIZyhq5N5AZCZ1iHxJpd5s5I5TgABCejJmZ7J+Z1ZVZZZ35gmRuohGZSReJCFuJWZkFlZ6FaFmF0FWFLJxZuFNZbJZJ+edBfZXZo5gFZFFFPZA5EpQ5fR5FlFjF1FDFzFVFg5NFjZLF7FXFtFqp8pWkippYM5t5RpS5f5olV54li5klZpwur5vpYu85AFMFT5qlL5FpGlalb5Ml95OxYlslJxUlgZupvhwZvxPhH5RhBlppulWpX0nBDJ3RTJeZXBeFhFblEF2FXl+FUFPlGF3l7lBFnlflOFgVvlYV-lIVAVwVEVOFdZoh9ZHFrZrFTF3FbFvFPFnF6VWVqVGV2VyVaVuVOVKVJVhVpVeVRVBVGW-FxYCxIl0lu51l-59VxlSlLVelfe2lB555JlllFlXpTpbh+ltlDpw1yldlVlI10Zk141o1Ols101-VRl1e3wwF1QDlzlHlrlQV214VMVwB6Z8FR1KJKF4Fu1kVsV0V51oV+111V18BW1D1O1T1e1oBXV-GcFJ1xQ3JeJiVmVVVxVZVQNFV5VgNINwNYNkNAN0NHZ-1sN+V8NlViNoNMNpFKNjKNVby05iOX5E1S1TVEljVU1fVhlBNrp3VQJUJx5bVuNc1RNC1JNNlDN15M17VeNpNxNLN+NnNy17NTN3hG1aBoFiFwtuJotaRLlL1F1B1vpyFaZClRq0+OJ8tqZQuyt+JZ1Buh1X1Gtktm1z1+tr1UtN1d1UVZtl1TxH1MtqtSZlNKtp561kpw5Kl6tttNpKZDtbt7pcR71sFqNzt6NaNEN-t9FCNQd4NEdUNyNwd0dkdId1ySVGN45Tyk58x2NreNNmxfNzVtNi1HNzNvNdNP5cKmdvV+5xd9NbNRdrNudjNOdWd1d3NBdg1bpddhNn5KmhenW4tutj1ht0tptFtdpnGStvtU2o9CtvGE9Ntit1tnts9b1k9I9c9x5Xt09Dtctat2t0Bx1O9Otm9B9u9mB1BgxYFetCRY9rtwOY9VCQsIakRt9Bij9eiz9oKr92K79Fmn9wK39Vmv9Nm-9QugDpRPCd9XRdeltme3xGm6m19maED-CCD8m5e8DqDpeaDY5MxKdAlU5QlONDded-NtdXN+dVdhD9dZdhd5DMWmOFddDz5kRpDxDVDbdDVHdlDZNrVY19xPpM9bpYD7DMDOeItTlZ9fdQtojIj6eYjBtMjD549K9PVa9ijdtW9stR9yRyji9fDy92jG9ohWjWtS9Vtejq9C9RjOjJjxt91-dJt1j5tQO9959iFqFaxujFj89U9Kj7t9tZjXjpjSj5jPtxjftATqjhjwTljoTHjfj7jkTnjcTF9ITCjYTPjjtdFCdcN4dUd2T8dfQP1L4mNglypzdPNnDHVldzD5T2d7dNdBDw99DclDD5lYJrTRDF5bjjTbTFDpdvT3DVTZTOhFNaTG5l929aevRP9imBe0jLjmttjNjcjA99jQ93j3tSTUTKTMTPVtJsjczzjp1BzEt4jqJYzvpJ9v22z4TQTGzCTVj8Tx5uzRRXtxF+9GjSF7zrjPwFzkCaz69sT9ztzAL0TAdMduTsdOTDzgT-jVzIzNzpzyTV9MLUL1zyLQL0LiTCLmzSLmLHt1NCVBLOtf1xlRTuDJTZDrDXD81FLJD7THDgzpTjLtpWuDTzTqpChZlNDXTjDPLLTvLXLbLbDdT9LTLAzX9oDfzkrqT6zWL5NmBXtqzg9t1tBgOSrarKzjjTB9BeemrDBT9ErL9Brb9RrCaLzurUDhEPzsC5r4mlrIDFR9rdrcDNJjrLrzrSYTzDoqaDkcwYarrdzFrmzwD7rwLtrqtMzkzhzJzUbezMbRtoLcd0r-zGLgLsr+LRLhL4exLSN4LubCbkLodLtZz2LxbAbWzKLcLaLabKbIL6LqLuL31TthbEWpLadeDGd-T9TtLPTnbIrNLLD3btTTdcunLcrgrVLlTHTkDTTM73TArs7-LY7w7Yr-bslgt+z0bvjNb5bdblbDbajJb5laJ7zPdEbp9G7sbFr4DTrTjbrt7Hr-r8jwb97obOLEz4bJ7kjf9Jr4rN9P7Uzf7gHyTwDzQPrkRDkb7RM6lYbZbz7+7h9bziHe9yHmB7q8VGbOtETu7MreL276t2bgd+beTjbGT+TTbmTYdRHELxHB7DtBHYLFbOHW79bqbuHLHtb1bqj9HibLirbTadVK7gnXbA7In1TjdTDwnYn1DQrC7fLcnQ7EnfbQnJ58jc7S7infTL7kga13Ha1CHKHyR+n8r8LbHe7rHzHZnHHpn07x7SHx9Zrjup7Ib1rjnX7xzl7vdS7kHO7nHlnPn1nyb7HMHobcH77sH-737Y9YHyT0XmzsX4XQHh7QbEXIFbnnn8bSzdjCztZNH3HBbvnTHtHr7pbxXvpeXuXmbkxlXPJ1Xv1tXP1DX5HZHpHJHidsLhXWHBXgXfn+HtXhTydemqd-H6dDLq7o3ynmnk7Snkn43M3orQZnV8nsJzp3LS3qni763a3KlanE7MZrnszcbyz2XirGr6rx3yr53SbUr7X3XHXJnFnQmTndnhnnz8zmXizXX13jHt30+oXgbZb8XobgP27f3wXIPKXQuwP4ToHYJUPfnoPCPEPZ7lzl3p3aPqP2X6HWb9XTXD3P3X32H+P1HxPebzbDHZPPHVHpPFHObVPdPFP+XzXbX9Pn3V3bPN3FX10A3zeQ3g37bs303gvk3gjvbwvwrYvy7c3Y3gJnT4727O323G3ivW3l5tDj5YO4denr3N7Wr6P73DjZ3+vJ3GPRvF3pv7P33BPrPHPhvF7GXdvR35vevWgnrJIrvsvYPQXiP3x7v3zj7Kl3vwHEP-vQXcPhXvvVwEf3daXyPvztvh3WXTvJvLvDn1PTPWTLP6flHhPVvAXufePnP2PGHRfJfVXxfZfpfNX5fVflfdX1fdftfjXLXRXeHJXPVWm3PTeza83Qvovff1LE3-fU3EvGnQ-7DK3ml0HU-SvQX0-KvfnDkAATMI3jP0mH36-kGH794pqn6ayRcnwnx94fwb878f8b0nxfw74n1fx7953f236o0Zy989x8y-ygU99e+e2f-n0A0j854gtHwO6pcgBLfILvf0RYP8eu4zZ-gZ1f6wCvmFtLHhX2QE18UBDfNAU33nbgCkuZbXrvX0wEZ8GehfDAbj3SbM8iBJPTPq10IE09COFAtPtQPkZ4Da+-XLBnz1qojce+I-OltwJ7YD8peg-fgb3yEGl0J+WlZJnP02aSDcBUXaDoHxwGlcFBrfI9m9xv4n8D+7ne3t-3j6aDHeUjSNroOv7aCbeJgy3joPS56DjBGgiwUYMMG39IBd3KtlYMv7OC1BrLfzgXwt458vBbgs3r4PMGx8XO2aWBlp1rwYNK84Qlaug2iEoN528ghLmV3-7cIkG4OSIWEJiERCMhUQ2ITkMyG5DsheQwoQUOKHpCl28QkLhDw75sCeeODNtuS0EHD8x+4vJoZL2EGNCRBLQ0fkINGYSCx6O+XYvd1AFQCVBEAkYUoLAEOCiepg7wafzsHqCXBcw8-v4NmE2Cj+iwvwa4Lz4+Cth0wgrksM2HrCAh2vLAZMJ-5P84BqHY4R50647CzBuwg4ba115UCyBNArPrT3oHPDyulAj4T8NoH2DRh4wtJl8IYEvDs+fwynr8LeF0DwRjPRge8JhHEDUBSI9AZo0GE3DPBIIqoVpAnK1Dhu-PLgZ0J4GEi+B7Q0kSLw6EUiyRwrHoVIL6HQCLhMAy4W-1UEPD0RQwmzolwSGAjHB8HK4VoMOErDzhCAjYQKOsFCiYipw7YQ93cHCijhzIo5uyKmF3CxRfIywaKIWGrCORAImQdqMUF0ccezfYEZ8INHkCER3ws0SCLZFWi0Rgwo0ZCLhHQioR5PC0caPwGkC7RLo+0aCPhFOiIR1vZUTMIDHPQ+OfPeoQIPDFtDyRVI1obwIU5EjKRUY8flSmwE6juR+w9UcsJVHyjN2moybD3QVY-9rRTgjMQ8PFEKiyxOYisR5ytYAC5RiZG0cWMVFrN0xuYkUa2PMFFj92bIqsfyPbGCjVRtgpsVKKHH3CMRroxvu6JNGvCHRzo30bCO9Gq9zOI4oMXOMREoj1xBAsEauPNHbjMRU4rcTOL9G7jnhnYpcaeKs51pO+cxPEWGMjHNCEx946MV0KfHxiXxJIxMf-iqqvN6xjY88XiV36WAo++3AwX2KzHwCWRJYyCaBMDEwT-RsE0sQOLWHQSU++-QCSHyQmb0gJKrJ4RqM3oFjhx+E9ri2J7Fqjlx8EscbhMQnzDMxlE7MTWSQGojfxDYrscxKXEejjxXo9iYePnFcSFxs47iWuNs4sSmJjY3iWJP3E+iBJO4qSXuLdGGiJJjomSSeNYkXiyJcE4MVeN541DbxsYmTs+I-ExjiRcY98Y+IMnrsEJdE3sYRMY4yiJR5YqicRNEI1j+AjBPVnWOM6YcDGKkjwchIsnh5nJOXdyckSwl79QhGEhyW2JImDi-xQkpkeBPsmeSRJwk3kZZNIlRTwpL-HukIzSHJDzK5Q8HpyIqHQMkhvCFIcELKmloKp5aKqcXhynTt8pXvSoSVMQZ1SWpWQ0oUUI6klDcp7UnqfkK6kDSPeDU+HsHxKnb9ipYU6qppNxGhjhKDQgyfpNMlLTDJD4ladzA14JsteqUwcelOonWdbJuonZgBJol7DCxEU7VlNg35JSzx3klMXqPunKDKxJU78WFzj79idaIUiRiAN2mOTtpSEmKZKKIk2sLsF0kGdhLcngydWoMkAWyICkvTGRCMhkUjNlGQzLpgkycXJNNG3DyJak7GZ6PUl4zCZSkziQpP4l8StR3IpUSTMxnTjyZPE0mUeOJn4yiZuMiiQTLZksy2R4kmmSS2mkcD8R0vQWfNOWmLS1pBkjaRTy2nxS6SMfc6e9OlnXDvJw0nka9MeledAZK46UWdOumqSAZh0iYfrOGEfs-pfvVCShNVYrC4ZwMkAVbOhkgSfpkUpycdM5kMz6ZPMySXTPRk69VZO0uWWBJ9kuTkGnUvqcHNKmtSg53U+qaNMmnjSg+hUgqXHMTmq1spvUsOanLallDo5n-LkTnKKlJz9Rk09wcrNjnJd45jUsuSNIrkqz-uec0ufnPTbZzQ5Gc3bpRBDHaS5pEY3SS3LFkizXxe3Ryt9L9m0T-Jzsq9hDNCk4TzZjcr1qPMeHjzh5AcwKcayrmfVp5tYlYdZJxmbzTp0rA6ZTO1nJSbpOsz3pXPrnt9mpEcwaWnP6khzI5zc3KvDORkQTPZ0kl+bJInHyT3ZiktTIPI+mzzMJs87mR-KxlMyOJzM0BRArfnKTj5d0tWQ9INn7zhxQCkgZ-J-GHzdZt0jWVvMwWGzq5PkvWYgtHGsziFBCgucAtpldyHQbc7vkLM7lGSb54AHTs9MAH2yh5J0h4bbNRlgyJ5i8yPuhNNmFybWZRfhXmNlmTTH5KM-2YjMkULzpFz8-+cwtSFxCQha82+YuJrkJy65WishbwsvlqL9F18puXosMV3zjF98t3gosyld0lFDC8xQYrsUmKr5Di5xWYuMWuSoONskRUIpUW6KbFRivxaYoCVOLXFQS+xSEvKl5Ss5i8kubnPPmCLDWE01RY4rCWhLklaSouVEo0Xlyz52S7RbXNiW5KClp8vJZoqKWFcU5ti8JZVPDmpKXFonReNQoE50LVpEsqeb4tRmqZAh686wZwp4VQyuFnii+RdOEVDKOlJRUZX0pPkCKklfCiZWhLmVtL+lky7hV9NYUmzfpCs3sWhxYVf9O6NU6tDUoiXpyql1Uw5dUuOW1KTltUi5UcsqWXL7lty-xY8sCXh0JFzJJhV-LJnILkRm4j2d8o3EQM3lImVpQcpuXnK7lzy4JQ8vBVPKYVLyuFVCuXmJD4lkKlJaivSVXLQVEKhFWio-qZKplAjLJUNPxWAMKlsK05WCopXYqqV5K65dSrpW0qsVjKvZSZNaGNLOBtCnaJ9MWVTLZlKK5ZYMv5WrLdlKRD-ovPcW8rvFDrBZRMxmVSrvZRK6Ze0smUjKhVPK1VXKuAkirelsip+URR2Uo8aV8Ko1YipxUYroVJq3FZavNXoq6ltqzFSyspUMrjVzq01dartVmqPV7qh1T-LWUjyZVjq+lUypdXBq3Vrqq1eGptWeqfVOqnleZMgX-Lfl38xNRjPIUHioF1MtNX8tdleyQFGa8BfmrAVFqE1Oa1+SmtQUoK815aqtaWvfloDRZrA7Edg35k6T6FvWMVYqqBUyzf5SSrtQ+wDVzzFVfKzVQPJAmxrZVyq4VYavVXjK1VE6odQarek9KvFwy2dSOoFVjr+FDEyVRasjVeq91PqslSGsDXMrfVTq0NRGovVRrvVu6q9furvWHqB1R6sNQ+tvUnrj1Z6oNe+u6XzK51Y8hdaupX7frL1wG69QerfWfrT1z6kDZBofmLqghsBEFaBvvXIbH1f62DS+tQ0QboNYG19fauw1PrCN6GnDShow0waSNaG9dTevw00bo1BG4jURqo3gbaN1GujSxuY1sbONrGgkVz2qFd8mld4nuVMu3VZSzZfkuKRJsRkdq9VT09ZY7JNmPzpNomhjSpqY14auNHG7jZpu03qatNumnTVhvY36bjNhmjTSZrI24bTNemqzQZos2kaKN9GtTTZqvUfLe1lGxeWtWi6Ty8YOGv1r5vCg4bPN-m6KMFpBihaIY4WvOKB3jVkxotMMmHkBuqBh9DwyWh8Klq1XTr51O6sLYopy1JAw+rmnzRLHS3KgSt2IMreSAq2IgqtfQGrcUDq1JbRYDWhLabGa2b8mtHW4rZ1ta3daaobW-Lb1oUj9a5Aw2orT1q60TbxtU2vrYNtUijaPF02obbNpYjzbYtfc3uYRHZUCzhZQmhtetoVSCrvN-6tGXbO1Wzzx1x2lZTOpAEOajNzm+7XZvc0Aa7tj2xzZOts23azND2z7dZte0vaftH2xje9vM0A6QdQO57V9r+2Q7Qd328Hdluh25aE2fa3sdyqy3r8bW6OwDZusR2-q3NF0zHajIJ2TKiduO8VRjt9bk6bt7WybTNpp02A1qqOhbXjsJ0U78drOsZfzEi1YwWt6CALdTpAleaxtIqwXQtuF087Mt9W5bSeFW0y6pdP4WXXTrm1y6pwCuxbUrsV0rbldKWrXWlp13ShVdtOtXZro13S69dpWs3QLyxEvIZp7c-BoJr22sqHdxk53XpP21O7Xdjut3V7s90+6Xd3c93f7u91+6KmC0oPR7uD01Nw9UewPb7uj0h6NtEenUozsHVraBda-NEmv3Z0QxM9MMrpYHOih56l52esTUTEL1+sy94UCvQXv53C6c9IE8XerqN2m6Td8ui3ZVrb3VaO9tWrvZLpb0q6e9jWvvdrqH266R9+ugfQ3uN1N7W9Y+83bPvK2Kcttra1aQHvj27aw9MexPdJ031x7I9O+-fWvtX177D9G+k-bHoP3H7L94nU-Vfu31n6t9lLe-bvuv3n6n9F+l-Q-sHbP679t+x-b-q-3v6f9H+7-X-uAOAHQDQByAxAegMAG39VCvmVjW23NK3xn++pTxpQMgHYD-+tA5ysoXYGpOMBnAztqP1gG4D+By3W2rIOkHyD6B0Pa-poO4HKDDB4gzfuoNsGoDWB9g4QYIOcGODRB5A3QdQM8H+D9u1g3weEO8cEDxTDuaIfoNcHeD3BigyvrEOKHaDCezAyIbwPyHNDTB7QxIaUMYHwDCh4wzoeUNyHxDBhwQxof0NqH195h1Q4wbMNCHLD6how6YcMNUGLDthkg14ccMeHmDl4vjdeNmllggAA"
));
export default createNewAscii;