asciibird/src/ascii.js

840 lines
28 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
// Although you can type in the input a bigger number than this anyway
export const maxBrushSize = 50;
// 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;
// Limits for undo and brush histories
export const maxBrushHistory = 200;
export const maxUndoHistory = 200;
export const tabLimit = 20;
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,
current: [],
layers: [{
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: [],
historyIndex: 0,
imageOverlay: {
url: null,
opacity: 95,
position: 'centered',
size: 100,
repeatx: true,
repeaty: true,
visible: false,
stretched: false,
},
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.layers[0].width && widthOfColCodes > 0) {
finalAscii.layers[0].width = maxWidthLoop - widthOfColCodes;
}
if (!finalAscii.layers[0].width && widthOfColCodes === 0) {
// Plain text
finalAscii.layers[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.layers[0].data[asciiY][asciiX - 1] = {
...curBlock,
};
break;
} // End Switch
} // End loop charPos
// First layer data generation
finalAscii.layers = [...fillNullBlocks(finalAscii.layers[0].height, finalAscii.layers[0]
.width, finalAscii.layers)];
// Store the ASCII and ensure we have no null blocks
finalAscii.layers = LZString.compressToUTF16(
JSON.stringify(finalAscii.layers),
);
// What we will see on the canvas
finalAscii.current = LZString.compressToUTF16(JSON.stringify(finalAscii.layers[0].data));
// We need to also store in the first undo history the original state
// finalAscii.history.push(finalAscii.layers);
// 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: [],
historyIndex: 0,
x: 247, // the dragable ascii canvas x
y: 24, // the dragable ascii canvas y
current: [],
layers: [{
label: forms.createAscii.title,
visible: true,
data: create2DArray(forms.createAscii.height),
width: forms.createAscii.width,
height: forms.createAscii.height,
}],
imageOverlay: {
url: null,
opacity: 95,
position: 'centered',
size: 100,
repeatx: true,
repeaty: true,
visible: false,
stretched: false,
},
selectedLayer: 0,
};
// Push all the default ASCII blocks
for (let x = 0; x < newAscii.layers[0].width; x++) {
for (let y = 0; y < newAscii.layers[0].height; y++) {
newAscii.layers[0].data[y].push({
...emptyBlock,
});
}
}
newAscii.layers = LZString.compressToUTF16(JSON.stringify(newAscii.layers));
newAscii.current = LZString.compressToUTF16(JSON.stringify(newAscii.layers[0].data));
// newAscii.history.push(newAscii.layers);
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 {
currentAsciiLayersWidthHeight
} = store.getters;
const blocks = mergeLayers();
const output = [];
let curBlock = false;
let pushString = '';
let prevBlock = {
bg: -1,
fg: -1
};
for (let y = 0; y <= currentAsciiLayersWidthHeight.height - 1; y++) {
for (let x = 0; x <= currentAsciiLayersWidthHeight.width - 1; x++) {
curBlock = {
...blocks[y][x]
};
// 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[y][x]
};
const zeroPad = (num, places) => String(num).padStart(places, '0');
if (curBlock.fg === undefined && curBlock.bg === undefined) {
output.push('\u0003');
} else {
if (curBlock.bg === undefined && curBlock.fg !== undefined) {
pushString = `\u0003${zeroPad(curBlock.fg, 2)}`;
}
if (curBlock.bg !== undefined && curBlock.fg !== undefined) {
// Asciiblaster export will check if the next char is a number and add 0 padding
// to the ,bg value, if we get that we can save some bytes on the bg char.
// if (blocks[y][x + 1].char && Number.parseInt(blocks[y][x + 1].char)) {
pushString = `\u0003${curBlock.fg},${zeroPad(curBlock.bg, 2)}`;
// } else {
// pushString = `\u0003${curBlock.fg},${curBlock.bg}`;
// }
}
if (curBlock.bg !== undefined && curBlock.fg === undefined) {
pushString = `\u0003,${zeroPad(curBlock.bg, 2)}`;
}
output.push(pushString);
}
}
// null .chars will end up as space
output.push(curBlock.char ?? ' ');
prevBlock = {
...blocks[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 (blocks[y] && y < blocks[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);
}
}
// 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]
}
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 mergeLayers = function (blocks = null) {
let mergedLayers = [];
// Position of the meta array
let x = 0;
let y = 0;
let z = 0;
// Draws the actual rectangle
let canvasX = 0;
let canvasY = 0;
let curBlock = null;
for (y = 0; y < store.getters.currentAsciiLayers[0].height + 1; y++) {
canvasY = blockHeight * y;
if (!mergedLayers[y]) {
mergedLayers[y] = [];
}
for (x = 0; x < store.getters.currentAsciiLayers[0].width + 1; x++) {
canvasX = blockWidth * x;
curBlock = {
...emptyBlock
};
// Loop layers
for (z = store.getters.currentAsciiLayers.length - 1; z >= 0; z--) {
if (store.getters.currentAsciiLayers[z].visible === false) {
continue;
}
if (
store.getters.currentAsciiLayers[z] &&
store.getters.currentAsciiLayers[z].data &&
store.getters.currentAsciiLayers[z].data[y] &&
store.getters.currentAsciiLayers[z].data[y][x]
) {
if (curBlock.char === undefined) {
curBlock.char = store.getters.currentAsciiLayers[z].data[y][x].char;
}
if (curBlock.fg === undefined) {
curBlock.fg = store.getters.currentAsciiLayers[z].data[y][x].fg;
}
if (curBlock.bg === undefined) {
curBlock.bg = store.getters.currentAsciiLayers[z].data[y][x].bg;
}
continue;
}
break;
}
mergedLayers[y][x] = {
...curBlock
}
}
}
return mergedLayers;
}
// Splash screen ascii encoded
export const splashAscii = JSON.parse(LZString.decompressFromEncodedURIComponent(
"NrDeCICMHNwLgBwBpwDNZwAwoMYAsBDAJ3nAAJwBfJCGeZNDbcfY0i62jB9eZ1knHJUaUbil5ZchQcM5j6EptLZCOouoiV8Vs9V0WMdLGexEGtRqSdVyN4q-1NrzCy5Ke39bnspt7XTV9jATN5IO1rUJdwhw9dMPtDeP9Ei2Co5zt0yM8A2OS-aOyfXISYpPcirO8Ix3KSupTi2rjqr0C2kJrOwu6Ogqr+-MqMvLTS+tSKnKmW3qHMgdGy6caupZHZ5p7BsYbWvs2JpvatyZ3l7bOTjfGZi5uH0+Hbo-v1+ABGSIBOBoAzgsflZ-ms8MC-g0AK6Q0ENAAucMkYPmgxBKJhyIwqKyAAdsfBcVc3BicQ0AJ6EuDE2QAe2ptNIsPRULWhzgZKJDQAJoyGgzWfC1gA7flrADk4uKSKFmPZ0qyAFNFbYAG6q2QquXk0Wat6ctnFHD6oQQnXctYEU3gWWVLk0g427X2o1ZDUWx1rF0WB1MoQkT3+8BUoNOsNrPkR4rW6NZUOu4Voyoi6EAGzTkVTGfK2bTgzzWfTmemecoAF0kGBHq9nndwys5rtG5dzi9jnX3g3rrXPosPhz9gq9qtkz2O32h2OaxPB6Pm+OBwspwuZ0uR02Se31y2npP51v68Pd7255u20fp9vu2ub9fj4u75fV-er8-D12H7ev6+X++L5+b6AX+wEfv2T6gQB4E-v+BormBfoNAAljaHKIWsJpxrY5qJvKMY2navpughxGyIRpKkaQOFEUmyqoTaAC2NqCrhurFECWFkfRnGkD6FG0eqzo2oGrGWsUCY0XhWQsZJbFZGKPFCHxmjoUBhoCbI1H8VJtjKRgqlZCJsliXRil9gZJEaaQADWNoKaJXrFDJ2lybYADuNocSmxZFjmpbFgWPlWIW-kZhWVaPjBkFwQeUHwXFsUxeeSWtile5nqlnbQWp8Vpaey6JVluVFYV+7JSV5VlZlVXpQVlUZbVG7VQ1+VNY1J6znVzVde1kU5aVLWdW1rUdTufUgdlE3FTVI3jWB02DWN379fVPWzctU0DWtQ2jRB6k6ZpzE2h5ZloZRQhMWZLIOcGmE3Q0Wkqed5nPShp02ndxmOVkj36c9sb3Ws5FPVZWUWfSdncYDMo2r93yvZ5UNfbdsM2gDyMNPZGN6mZenw6D4BpkjLkmbYzkgwdpBY24IWSCF0Rlt5fl00FDMBZW1a-vNW3DTtc0JatvNLVzAvdULe0LdtwuwRVYu7VFk3c4L8srXL-N5XzG1K2rWui71usa9L0Wy-rIuGxLPMq5tyvqyb61m3bmsOzNTsyy7RuK3r9tu4tFs2wbjse+DpDA39BNw-trlasTFNR6Qb3Q-JBFCWZRkk99tjU7HpPR2Zof45TYPPZ96fBhHwdCOjpeIjHYeF+AeORznpAeonunCTaEnVwrFfgFGbeyFX2cZ7IXfD8GZ0EwATDaAAsNoAPQ2gA7AstMYPTziMxY6-wJvqhlhztvu37OvO77CuS+Ll+W8fk9n3XpuP97BdP6-L9N6779f5-Qe3wHJ8b7+3PlLU+b9PbmyAQ-Y2gDVbgKvlbbW8D-4gOvnAj+CC76gKgcg4BPtsHoJ-pggBF9CF-wJtdbGale5D2fiPUgadx4NEbr3e+cchAlyYeCNGyc842gTlQpOZlYDvTMkTMyAju7FFsrjWu39gy0PkXtXu-dBG2AALQ2nEQPXiNpW5qNzjooQ2iDEGgdCCNSu84D70ENvGmQUWZ+TZmFI+JCCHW2gRAwOYCMEoPwWgiaDpyGeKCcovx39gngNCT3cJv8fFENicQ1BiCvYJLwTA0hHjcGeKSf4lJkCyHxI9rkjJ7ikG+PSV42BWSKk5MSfUypJSqmZMsvXThdDgwsOeiYqRpkjFQA7hI3h-Sx4dLCQTSRXDihZzGTEgmqAeFmXaUooGcjP7BkmbM6hz187rIevwyGZkZH9LYc3IQmyVnbIJssvZ3DArMw3qzLeAUmYlkcSWZx+ZXHJKwQE8paS6mNIaYCkJsTolXNBZU8FgSwWwqhcC7JiLalIoBSi4pCLkWYtRVi9FQK8UgrRUUolOCcXEsKSS7FlLcUEtJRS6lUT-rDNMUXAml1+nky2YZFOJyjpmRmZcmFBMunzMOf04V9ddmsKZb07Ciz+n8tuRC9h4AOUCszms3ukrnrlwRmZLyX0LETSsTYveLyd4OMeU455LiIpuL+ak+ltLyXOpqVS8ZkKPUMvhd6z1H9oWtMJXSslrrHVuqDeGl1-zQ3RuDVG2NDr40FJDYm7xEbk1prjRmhNWak2Zsjdm-NZjnoKt7uK5VWqCYnXZYM-pxzmUvQJpAG0lCZVcVESM3l-SW1TMFRKmt9bRlqqgpqzt9bTn0KEKo1tDDR3TqEFW+tJbIiGrAsap5B8zX2Ieaaq1G6bWczyb8-JqbC0nvTae6pAa-Vwt9T-f1w6b1etvTGnNZ683no-e+r9BbP0-u-bmv9gGAPAbfUB0DIHL1gcgxBlpUHYMwbKXBxDCGzKqsVVkbtnLbAVvruO4MbKB0auLqjMyij0PYcRu2+tFzyMPoJgAK2bZR-pOHlV4YaAuud4AyO9yXQTVjZyG19tQ0Rxta8LU7o+da-MrzfJSb3V821Py7XHug+B9TanNPwZZc+91T79PXp9QZu9GKw0Xu01p5DGmLM2as5Z+1KHVO2Yc9ZuzzmnNuc8y5+zHnvPuaPQFlTgXlMhcPVxgTE7wCMKw4Y+tPGdncvrVOntV7It8frmh3u0Wh2kAI1x+LBMekpdkEVmLpBB20aLQTAr9d0vKuS2VnTyrGNXWY-WiLE8bR1q443Ndu7bGbs0H1+TA393BbC6F0pfmvO+dm+Nqbc2gtLcm80mb83VvTc24tlbTTdumZfeZtby2JsnYW+tvb+LA2Ha2+d-bKafO3cu0639jn1ulptASKjXGaO8dE-XPLxX46iqS395Vn3+mlZy0pOV9b9U9dB4JuHgOhCZeegDxrDcEeRfB-WstgnUcExx1xyH1iJOk-65J+5bzLUjcp98jYnSjs7bu6+h7x2zvs429t07XPHs0rMy91zN3OcXf5wdwXbPmdPYF-+oX3OOdS7F-d-zIuWfXfl7z1X0vxey6E8qmryrsuVeh195H4AkcY7x2ltr33pVm5+89fR8PTcY46wKLHwY3d3LFTbs3VvOtLJI-0g3gmvfsXE9u8ntOo9U7k7mdmSmeei6uxLlXiuU+67T0ntXqemfZ+18rvPCv89K9Z1n4vFfNfp+e5novVeS8Z6Q8L6vMum8a+TzXtvfPjMKOB1xhrUO9f47txjnVQr+1cfYxhIPcWR+D7D30+tRve4W8H1P-CqdEuT995bvvZvV-G-ADR4b8ewqyeCuu0bimD1HF783hvnfXta9L+r7vj+5dv9b0-lvOuu-P8b9-g-l-h-v-u-pLkAb-oAZXh3sAeAdATnrXvfsYJICulBMwCgXpqHgcmZE7n7h7jXCJi7vPjPlxgTvXAfu9kQYfjcr3GPvXCHpFgvhRrIpvnqvgWsE2iwf0g7gTHVoJiIjymwbHhfhTjHufu8qftfp-pASAT-oXkgfXvAQXmXnXjATIXAYoZoWofIe3ggX-nISoQodoYYbocoa-qAbAeXloXoQaOgWHGpHYfjA4cuntI4epM4VYKgVVvXP7pjDvoPjwcJtwXolvmbuvlkIEWxsdLOmbnwZFlKHwlQb9qRoxKERjuEeqikWZOjoPr4WsBQc9BkbIJwf0jcifqFDJuapHiajHvTrfjodIQ0RYeoVYcYeYQYe0RAU0R0bnkYTYbIV0SYY0UMc0d0YMZ0UoS-r0Rrm4V4U1oJpEU4RNLMa4S4QrCsesWsR4RgXMvXAIfWmQeWuwcUIsdbkkQlqwdWq1mZCUfWiTpQYIf0pxvbnPofnQVEYQfKmkWvh9CQWbgwZ7q8Q8VUdTpJpIcIRIRUeFDfosMGEbm0dMcMRMdYWYYiaMSMT0YgaYVMViUiWiZifoeMfiUSbieiciQiaSZoBsdsfYcsVsXSZ4ZgZFsvvSWBNSQyTsTSUsWyayXRuQdgV8YHlwWOgKe1scVytccEXyt8YfgPofkUVRKKVxgUQTAqUIHERsr8cKdvmZHKb3KccGBqX4dqWbkbuUZ8hCTTuCXUSiTiYSZMQAQMQ6WAa0f0RoRSfabaY6e6W6a6aiZSSSZ6R6VAV6S6dEUMoMOyTyYyZsTGVye4b2kcWZO8YJmqeACmZFgaYCOKbYAslkf0jkYflmWsPcc9BVlKvmYujaHqYUeGf0hmYztKecfxn8bvkIT7s2W0q2YPgCQQSxjKVlv4VHnHlCeIVaVCTacGU6aGZYaob6XOf6UGfOX0YuSGVOT6audOeuX6XaWucuTMbybYYeVlFGXycqvsc7v2Z2cqoWTQtWTmW2vWQOWWQ+aQBeXgYkY8Uvs+dVloq+RdDDv3v+dxnvhjjWaqR9j+RlpBWIlBR8f0u+W2VeV+enHMUPgmaumTjUXYkNlhZfnTons6bOSubuVufuXiQGURS0QuaRRubRTud6WmYWaeUeXGRybSalnfv0uBZyYmYJs8X4LxdGUJWgceX2CxfMZFg2cwqBT8WZETh+Vcf0gpRjqWePtefwXBVgRpZFreYyrqcBb2TjNxcBQJbkYZX+bBTpcGHmR2XZXcRZdZQ0GpfXMWTDJ+Q5SaXQmhRyOadJpaWCROYRTOdRSRYxTReFWFWGVFcRdiZFXFdFQlbFfWjQdqlpZFkZe5fZUBU5VaLJbKcBbcVxphoPnpayulcGDxfBQcUxh5TqVKYKShWbkwYdF5XJaUTERjiybwRVQ0N1bhoBbEfeblcUNJXlTgb1bscquWc9IcYJiVfKd2YfnkRvv0v1fVtSD5RHqCcOaOSCSORaZOeRWScSVRWMWdRiYGXuZuXRfFRRUuTdVYXeTcZ1YPgtSOiNVkC1YqZWeFsBd9QGINRjm5UIk1RjmZUWUCWlZ9bYDxgAMzLoACsyiCNnhyNPcqNKB6NSq3JZ5gmRp41BZk1ONGVNoRVZuc1kW71FxYNg+5NGO1NLZdV++wFINnFHGSpLNyZwFZV9cM1EFQpY5gVFpQtu1h1wV25EViV91119FUtyVJ1lFIV51ytl1F15Jj6w+kpVZkZtZutbFwlHFolBtxtIlrFZtklgJlxhGMNrVwew1tNh+iFg+lNwYmWmN+k2NWQgAiaTAge3fBe22C+3oj+2ciB2yDB32ih1fDh1mLR2x1gzx1MmVXAUtZKX1rdaKWw7AUuXKps2yCZ1IUinM2j5A3EG21UyvWH4Q0Vnp1cYE1ZU1UV1CC7J+UKYBVi3+VHWPXy2hVJV90y1kU90xUD2K0PVy0j0q2S0KG0F+1I2rEm3m1G1L240r0YWm3L0nliVpkrV8Wk0GX60DWH2b3oXBi4FF2khJ1rCR2+hX3UJ30+1z1o0o3z0Y2v333v2BIP0ITf0b2r1b2L0AMW2n3GnIXF111m62XgMpVV0qLE173Bi52Ca72w0O244T5m700u1l2H7rWCZVWpmc3pHbUHX+Wi3YUJ4wnT391T3HUEmy13Vj0MPS1MND0T00Nq2q0a3D0HmAPiXb0LASX8N8NoS-1x2f0-0SN-3r1r1bXH3-3CPAOiNSPiPP2xlKOCMCPyMyNAMn3KNqMf0GNf0qO6MKMchCPmNaOVAWNQ3hxEOD4N1ZCF3tXUapHN19ywO6qNX1rO2LXCKeMEyu28jAUoOyDn3mVNkORyPkP4ViH7UiHR6HwS10NXVsOMP0NpMsMZO3VZOpM5MK3ZMMW5Pq2nVcOlPUO30mPmRiOJ1VOWMiOaMNPaPRMWA2Mh11NP1Y0L0aPNPdN6ONM9NR0dPtNGOG1mMDP9O9PqOTOqY30AJzPNVLW9xBNrCM17FLN60NXQNca+PLO2NH0QMY6OPtzuM13PR4ORYrPTI2i7ObP1pYO7WQki3xNPNd3JMC0cPcPsOD35Oj2FO920M8M-NFMFN5Mgt-NgsAucMVOsO-OAvfOwsLNuxIswIotVJovTQYulRYuVQ4vVR4t7gEtgFEsTgkuRLzX7PVX5Y2hQN-VtW4M53wOoPW3132OH7HOyC3MfPfna1cYPO9z80HNikd0UNn4vPjni1UMpNEVoUH4ZBoULXyugPjhoVylKuE0quUjbQtMzhoWFnqtOTat9VGvGWatrA0YGug1mvFC+OWv-JoU3J2t23WtZBkZOshwmvh6T3QvStlNK0wv-PetfPpOQtBvlO+sBuhufPhtAtcZjUnH5W9xcv8nuOZUSnZVhE3MBOuWJvPTJtUtm683KprPKqpV2PuP8vPShMesiuxM4XjmvPt3d0IuBvRv+sRsduxutvAtQvBvFN+vj0hslPttdtRs9thsjsttjuwvgvwtDsDvMOguV4fUZtdXAUcukBXNZBnPcvUuRMg7uP53eHKo7vQX7s5WruD4XMB5bNca0uZu1uiH1uBWNtX7QnjttuDv9uRvDtftLsLuZP-s-sAdws+uju-uLsQsQeAdQcgeztgdTvQegd9tAeduIeH4A3plMuyDXsyVmQJH9IqlCt7uO29yVsTKUv8Vssr7YfHuCbxuutam1r-UkMJPgkxNPuUPisKMADKrHwDAAEvxyfQAPLCcKMACq4nOj4AAAKtJzq7hZHmhWJx3WhVJ2pw0PJ5p2sHxzp5YmTmhdp6LSpwp3tFYmhQAMJmdrBCf6dZAABaNnxQTn9nmFynDQAAgs51kHpyZw0NZ257IAAJI+e2ChdBcGgWcNCqf+drAABiYXUE0Xawxn3HMndncXxQAAokl1F4Zw0AAOp5dCC5eRelclfgAADS0norlRW6O1dXj7iTg2DbErZD6XTXHHLXYrDXYl3n5X4Afn6XVnlXEXWXWQ43I3DQAAQmN5VwAEqVcAAilXvlBXawmig363HnawS3g3U3fXfDAA4pV5l9N6l2t5V4FxN7YAADLzeDc3cXfFA1fbeVczzveDfDdHfANpe-cn0De3eyD7fA+kD-dKc7VzHPuPOb0w8pfPMA--3w94Wce9eQ+kPt3dfWnlhAA"
));
export default createNewAscii;