var RETINA, Toolbar, Palette, Codepage; RETINA = window.devicePixelRatio > 1; function createElement(elementName, args) { "use strict"; var element; args = args || {}; element = document.createElement(elementName); Object.getOwnPropertyNames(args).forEach(function (name) { if (typeof args[name] === "object") { Object.getOwnPropertyNames(args[name]).forEach(function (subName) { element[name][subName] = args[name][subName]; }); } else { element[name] = args[name]; } }); return element; } Toolbar = (function () { "use strict"; var selected, shortcuts; shortcuts = []; function addTool(tool, shortcut) { var div, paragraph; function select() { if (selected && (selected.tool.uid === tool.uid)) { if (tool.modeChange) { tool.modeChange(); if (shortcut) { paragraph.textContent = tool.toString() + " (" + shortcut.symbol + ")"; } else { paragraph.textContent = tool.toString(); } } } else { if (tool.init()) { if (selected) { selected.div.className = "tool"; selected.tool.remove(); } selected = {"div": div, "tool": tool}; div.className = "tool selected"; } } } div = createElement("div", {"className": "tool"}); div.onclick = select; if (shortcut) { shortcuts[shortcut.keyCode] = select; paragraph = createElement("p", {"textContent": tool.toString() + " (" + shortcut.symbol + ")"}); } else { paragraph = createElement("p", {"textContent": tool.toString()}); } div.appendChild(paragraph); document.getElementById("tools").appendChild(div); return { "select": select }; } document.addEventListener("keypress", function (evt) { var keyCode; keyCode = evt.keyCode || evt.which; if (shortcuts[keyCode] && selected) { evt.preventDefault(); shortcuts[keyCode](evt.keyCode); } }, false); return { "addTool": addTool }; }()); Palette = (function () { "use strict"; var selected, canvas, COLORS; function egaRGB(value) { return new Uint8Array([ (((value & 32) >> 5) + ((value & 4) >> 1)) * 0x55, (((value & 16) >> 4) + ((value & 2))) * 0x55, (((value & 8) >> 3) + ((value & 1) << 1)) * 0x55, 255 ]); } COLORS = [0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63].map(egaRGB); function styleRGBA(rgba) { return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ", " + rgba[3] + ")"; } function drawSwatches(canvas) { var ctx, i; ctx = canvas.getContext("2d"); ctx = canvas.getContext("2d"); for (i = 0; i < 16; ++i) { ctx.fillStyle = styleRGBA(COLORS[i]); ctx.fillRect((i % 8) * canvas.width / 8, (i < 8) ? canvas.height / 4 : 0, canvas.width / 8, canvas.height / 4); } } function set(col) { var evt, ctx; ctx = canvas.getContext("2d"); ctx.fillStyle = styleRGBA(COLORS[col]); ctx.fillRect(0, canvas.height / 2, canvas.width, canvas.height / 2); selected = col; evt = new CustomEvent("colorChange", {"detail": selected}); document.dispatchEvent(evt); } document.addEventListener("DOMContentLoaded", function () { var divPalette; divPalette = document.getElementById("palette"); canvas = createElement("canvas", {"width": RETINA ? 320 : 160, "height": RETINA ? 320 : 160, "style": {"width": "160px", "height": "160px", "verticalAlign": "bottom"}}); drawSwatches(canvas); canvas.onclick = function (evt) { var x, y, col; x = evt.clientX - document.getElementById("toolkit").offsetLeft; y = evt.clientY - divPalette.offsetTop; col = (1 - Math.floor(y / 40)) * 8 + Math.floor(x / 20); if (col >= 0) { set(col); } }; set(15); divPalette.appendChild(canvas); }, false); document.addEventListener("keydown", function (evt) { var keyCode, modifier; keyCode = evt.keyCode || evt.which; modifier = evt.metaKey || evt.altKey || evt.ctrlKey; if (!modifier) { if (keyCode >= 49 && keyCode <= 56) { evt.preventDefault(); set(keyCode - 49 + (evt.shiftKey ? 8 : 0)); } else { switch (keyCode) { case 9: evt.preventDefault(); set((selected < 8) ? (selected + 8) : (selected - 8)); break; case 81: evt.preventDefault(); set((selected === 0) ? 15 : selected - 1); break; case 87: evt.preventDefault(); set((selected === 15) ? 0 : selected + 1); break; } } } }, false); function getSelected() { return selected; } return { "COLORS": COLORS, "getSelected": getSelected }; }()); Codepage = (function () { "use strict"; var BASE64_CHARS, FONT_80X25, FONT_80X25_SMALL, bigFontBuffer, smallFontBuffer, NULL, SPACE, UPPER_HALF_BLOCK, LOWER_HALF_BLOCK, LIGHT_SHADE, MEDIUM_SHADE, DARK_SHADE, FULL_BLOCK, BULLET_OPERATOR, MIDDLE_DOT, NO_BREAK_SPACE; NULL = 0; SPACE = 32; UPPER_HALF_BLOCK = 223; LOWER_HALF_BLOCK = 220; LIGHT_SHADE = 176; MEDIUM_SHADE = 177; DARK_SHADE = 178; FULL_BLOCK = 219; BULLET_OPERATOR = 249; MIDDLE_DOT = 250; NO_BREAK_SPACE = 255; BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; bigFontBuffer = []; smallFontBuffer = []; function base64ToBits(text) { var i, j, k, bytes16, bytes8, bits; bytes16 = new Uint32Array(1); bytes8 = new Uint8Array(text.length / 4 * 3); for (i = j = 0; i < text.length; bytes16[0] = 0) { bytes16[0] += (BASE64_CHARS.indexOf(text.charAt(i++)) & 63) << 18; bytes16[0] += (BASE64_CHARS.indexOf(text.charAt(i++)) & 63) << 12; bytes16[0] += (BASE64_CHARS.indexOf(text.charAt(i++)) & 63) << 6; bytes16[0] += BASE64_CHARS.indexOf(text.charAt(i++)) & 63; bytes8[j++] = (bytes16[0] >> 16) & 255; bytes8[j++] = (bytes16[0] >> 8) & 255; bytes8[j++] = bytes16[0] & 255; } bits = new Uint8Array(9 * 16 * 256); for (i = 0, k = 0; i < 9 * 16 * 256 / 8; ++i) { for (j = 7; j >= 0; --j) { bits[k++] = (bytes8[i] >> j) & 1; } } return bits; } FONT_80X25 = base64ToBits("AAAAAAAAAAAAAAAAAAAAAAAAAAAfkCpUCgV6mUCgT8AAAAAAAAAfn+23+/2G53+/z8AAAAAAAAAAAAbH8/n8/j4OAgAAAAAAAAAAAAEBwfH8fBwEAAAAAAAAAAAAAwPB453O5wwGB4AAAAAAAAAAAwPD8/3+fgwGB4AAAAAAAAAAAAAAAGB4PAwAAAAAAAAA/3+/3+/3+52Gw3O/3+/3+/3+AAAAAAAB4ZiEQjMPAAAAAAAA/3+/3+/2GmV6vUyw3+/3+/3+AAAHgcGhkeGYzGYzDwAAAAAAAAAPDMZjMZh4GD8GAwAAAAAAAAAPxmPxgMBgMDg8HAAAAAAAAAAfzGfzGYzGYzO53MwAAAAAAAAAAwGG2PHOPG2GAwAAAAAAAEAwHA8Hw/nw8HAwEAAAAAAAAAEBgcHh8/h8HgcBgEAAAAAAAAAGB4fgwGAwfh4GAAAAAAAAAAAZjMZjMZjMZgAZjMAAAAAAAAAf22222ew2Gw2Gw2AAAAAAAD4xjAODYxmMbBwDGMfAAAAAAAAAAAAAAAAA/n8/n8AAAAAAAAAGB4fgwGAwfh4GD8AAAAAAAAAGB4fgwGAwGAwGAwAAAAAAAAAGAwGAwGAwGD8PAwAAAAAAAAAAAAAAwDH8DAwAAAAAAAAAAAAAAAABgYH8YBgAAAAAAAAAAAAAAAAAAwGAwH8AAAAAAAAAAAAAAAABQbH8bBQAAAAAAAAAAAAAAAEBwOD4fH8/gAAAAAAAAAAAAA/n8fD4OBwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGB4PB4GAwGAAGAwAAAAAAADMZjMJAAAAAAAAAAAAAAAAAAAAADYbH8bDYbH8bDYAAAAAAGAwfGMwmAfAMBkMxj4GAwAAAAAAAAAwmMDAwMDAxkMAAAAAAAAAODYbBwdm4zGYzDsAAAAAAABgMBgYAAAAAAAAAAAAAAAAAAAADAwMBgMBgMBgGAYAAAAAAAAAMAwDAYDAYDAYGBgAAAAAAAAAAAAADMPH+PDMAAAAAAAAAAAAAAAAAwGD8GAwAAAAAAAAAAAAAAAAAAAAAAAwGAwMAAAAAAAAAAAAAAAH8AAAAAAAAAAAAAAAAAAAAAAAAAAAGAwAAAAAAAAAAAAAgMDAwMDAwEAAAAAAAAAAODYxmM1msxmMbBwAAAAAAAAAGBweAwGAwGAwGD8AAAAAAAAAfGMBgYGBgYGAxn8AAAAAAAAAfGMBgMPAMBgMxj4AAAAAAAAADA4PDYzH8DAYDA8AAAAAAAAA/mAwGA/AMBgMxj4AAAAAAAAAODAwGA/GMxmMxj4AAAAAAAAA/mMBgMDAwMBgMBgAAAAAAAAAfGMxmMfGMxmMxj4AAAAAAAAAfGMxmMfgMBgMDDwAAAAAAAAAAAAGAwAAAAAwGAAAAAAAAAAAAAAGAwAAAAAwGBgAAAAAAAAAAAMDAwMDAMAwDAMAAAAAAAAAAAAAD8AAAfgAAAAAAAAAAAAAADAMAwDAMDAwMDAAAAAAAAAAfGMxgYGAwGAAGAwAAAAAAAAAAD4xmM3m83m4wD4AAAAAAAAAEBwbGMxn8xmMxmMAAAAAAAAA/DMZjMfDMZjMZn4AAAAAAAAAPDMwmAwGAwGEZh4AAAAAAAAA+DYZjMZjMZjMbHwAAAAAAAAA/jMYjQeDQYDEZn8AAAAAAAAA/jMYjQeDQYDAYHgAAAAAAAAAPDMwmAwG8xmMZh0AAAAAAAAAxmMxmM/mMxmMxmMAAAAAAAAAPAwGAwGAwGAwGB4AAAAAAAAAHgYDAYDAYzGYzDwAAAAAAAAA5jMZjYeDwbDMZnMAAAAAAAAA8DAYDAYDAYDEZn8AAAAAAAAAxnc/n81mMxmMxmMAAAAAAAAAxnM9n83mcxmMxmMAAAAAAAAAfGMxmMxmMxmMxj4AAAAAAAAA/DMZjMfDAYDAYHgAAAAAAAAAfGMxmMxmMxms3j4DAcAAAAAA/DMZjMfDYZjMZnMAAAAAAAAAfGMxjAOAYBmMxj4AAAAAAAAAfj8WgwGAwGAwGB4AAAAAAAAAxmMxmMxmMxmMxj4AAAAAAAAAxmMxmMxmMxjYOAgAAAAAAAAAxmMxmM1ms1n87jYAAAAAAAAAxmMbD4OBwfDYxmMAAAAAAAAAZjMZjMPAwGAwGB4AAAAAAAAA/mMhgYGBgYGExn8AAAAAAAAAPBgMBgMBgMBgMB4AAAAAAAAAAEAwHAcBwHAcBgEAAAAAAAAAPAYDAYDAYDAYDB4AAAAAAEBwbGMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH+AAAABgGAYAAAAAAAAAAAAAAAAAAAAAAAAADwDD4zGYzDsAAAAAAAAA4DAYDwbDMZjMZj4AAAAAAAAAAAAAD4xmAwGAxj4AAAAAAAAAHAYDB4bGYzGYzDsAAAAAAAAAAAAAD4xn8wGAxj4AAAAAAAAAHBsMhgeBgMBgMDwAAAAAAAAAAAAADszGYzGYzD4DGYeAAAAA4DAYDYdjMZjMZnMAAAAAAAAAGAwABwGAwGAwGB4AAAAAAAAABgMAAcBgMBgMBgMZjMPAAAAA4DAYDMbDweDYZnMAAAAAAAAAOAwGAwGAwGAwGB4AAAAAAAAAAAAAHY/ms1ms1mMAAAAAAAAAAAAAG4ZjMZjMZjMAAAAAAAAAAAAAD4xmMxmMxj4AAAAAAAAAAAAAG4ZjMZjMZj4YDA8AAAAAAAAADszGYzGYzD4DAYHgAAAAAAAAG4djMYDAYHgAAAAAAAAAAAAAD4xjAOAYxj4AAAAAAAAAEBgMH4MBgMBgNg4AAAAAAAAAAAAAGYzGYzGYzDsAAAAAAAAAAAAAGMxmMxmMbBwAAAAAAAAAAAAAGMxms1ms/jYAAAAAAAAAAAAAGMbBwOBwbGMAAAAAAAAAAAAAGMxmMxmMxj8BgY+AAAAAAAAAH8zAwMDAxn8AAAAAAAAADgwGAwcAwGAwGAcAAAAAAAAAGAwGAwGAwGAwGAwAAAAAAAAAcAwGAwDgwGAwGDgAAAAAAADs3AAAAAAAAAAAAAAAAAAAAAAAAAAEBwbGMxmM/gAAAAAAAAAAPDMwmAwGAwGEZh4GDgAAAAAAzAAAGYzGYzGYzDsAAAAAAAAYGBgAD4xn8wGAxj4AAAAAAAAgODYADwDD4zGYzDsAAAAAAAAAzAAADwDD4zGYzDsAAAAAAADAMAwADwDD4zGYzDsAAAAAAABwbBwADwDD4zGYzDsAAAAAAAAAAAAAD4xmAwGAxj4GDgAAAAAgODYAD4xn8wGAxj4AAAAAAAAAxgAAD4xn8wGAxj4AAAAAAADAMAwAD4xn8wGAxj4AAAAAAAAAZgAABwGAwGAwGB4AAAAAAAAwPDMABwGAwGAwGB4AAAAAAADAMAwABwGAwGAwGB4AAAAAAAGMAAgODYxmM/mMxmMAAAAAAODYOAgODYxn8xmMxmMAAAAAADAwAH8ZjEaDwaDEZn8AAAAAAAAAAAAAHYNhsfmw2DcAAAAAAAAAPjYzGY/mYzGYzGcAAAAAAAAgODYAD4xmMxmMxj4AAAAAAAAAxgAAD4xmMxmMxj4AAAAAAADAMAwAD4xmMxmMxj4AAAAAAABgeGYAGYzGYzGYzDsAAAAAAADAMAwAGYzGYzGYzDsAAAAAAAAAxgAAGMxmMxmMxj8BgYeAAAGMAD4xmMxmMxmMxj4AAAAAAAGMAGMxmMxmMxmMxj4AAAAAAAAwGD4xmAwGAxj4GAwAAAAAAABwbDIYHgYDAYDA5n4AAAAAAAAAZjMPAwfgwfgwGAwAAAAAAAHwzGY+GIzG8zGYzGMAAAAAAAAcGwwGAwfgwGAw2DgAAAAAAAAwMDAADwDD4zGYzDsAAAAAAAAYGBgABwGAwGAwGB4AAAAAAAAwMDAAD4xmMxmMxj4AAAAAAAAwMDAAGYzGYzGYzDsAAAAAAAAAdm4AG4ZjMZjMZjMAAAAAAdm4AGM5ns/m8zmMxmMAAAAAAAAAPDYbB8AD8AAAAAAAAAAAAAAAODYbBwAD4AAAAAAAAAAAAAAAMBgABgMDAwGMxj4AAAAAAAAAAAAAAA/mAwGAwAAAAAAAAAAAAAAAAA/gMBgMBgAAAAAAAADA4DEZjYGBgYG4hgYGB8AAAADA4DEZjYGBgZmcmh+BgMAAAAAAGAwAAwGAwPB4PAwAAAAAAAAAAAAABsbGwbBsAAAAAAAAAAAAAAAAGwbBsbGwAAAAAAAAAESIESIESIESIESIESIESIESIVVUVVUVVUVVUVVUVVUVVUVVU3Tu3Tu3Tu3Tu3Tu3Tu3Tu3TuGAwGAwGAwGAwGAwGAwGAwGAwGAwGAwGAwGHwGAwGAwGAwGAwGAwGAwGHwGHwGAwGAwGAwGAwNhsNhsNhsNnsNhsNhsNhsNhsAAAAAAAAAAH8NhsNhsNhsNhsAAAAAAAHwGHwGAwGAwGAwGAwNhsNhsNnsBnsNhsNhsNhsNhsNhsNhsNhsNhsNhsNhsNhsNhsAAAAAAAH8BnsNhsNhsNhsNhsNhsNhsNnsBn8AAAAAAAAAAAANhsNhsNhsNn8AAAAAAAAAAAAGAwGAwGHwGHwAAAAAAAAAAAAAAAAAAAAAAHwGAwGAwGAwGAwGAwGAwGAwGA/AAAAAAAAAAAAGAwGAwGAwGH/AAAAAAAAAAAAAAAAAAAAAAH/GAwGAwGAwGAwGAwGAwGAwGA/GAwGAwGAwGAwAAAAAAAAAAH/AAAAAAAAAAAAGAwGAwGAwGH/GAwGAwGAwGAwGAwGAwGA/GA/GAwGAwGAwGAwNhsNhsNhsNhvNhsNhsNhsNhsNhsNhsNhvMB/AAAAAAAAAAAAAAAAAAAB/MBvNhsNhsNhsNhsNhsNhsNnvAH/AAAAAAAAAAAAAAAAAAAH/AHvNhsNhsNhsNhsNhsNhsNhvMBvNhsNhsNhsNhsAAAAAAAH/AH/AAAAAAAAAAAANhsNhsNnvAHvNhsNhsNhsNhsGAwGAwGH/AH/AAAAAAAAAAAANhsNhsNhsNn/AAAAAAAAAAAAAAAAAAAH/AH/GAwGAwGAwGAwAAAAAAAAAAH/NhsNhsNhsNhsNhsNhsNhsNh/AAAAAAAAAAAAGAwGAwGA/GA/AAAAAAAAAAAAAAAAAAAA/GA/GAwGAwGAwGAwAAAAAAAAAAB/NhsNhsNhsNhsNhsNhsNhsNn/NhsNhsNhsNhsGAwGAwGH/GH/GAwGAwGAwGAwGAwGAwGAwGHwAAAAAAAAAAAAAAAAAAAAAAA/GAwGAwGAwGAw////////////////////////AAAAAAAAAAH/////////////8Hg8Hg8Hg8Hg8Hg8Hg8Hg8HgD4fD4fD4fD4fD4fD4fD4fD4f//////////4AAAAAAAAAAAAAAAAAAAADs3Gw2Gw3DsAAAAAAAAAeGYzGY2GYxmMxmYAAAAAAAAA/mMxmAwGAwGAwGAAAAAAAAAAAAAAH8bDYbDYbDYAAAAAAAAA/mMYBgGAwMDAxn8AAAAAAAAAAAAAD82Gw2Gw2DgAAAAAAAAAAAAADMZjMZjMZj4YDAwAAAAAAAAdm4GAwGAwGAwAAAAAAAAAfgwPDMZjMZh4GD8AAAAAAAAAODYxmM/mMxmMbBwAAAAAAAAAODYxmMxjYbDYbHcAAAAAAAAAHhgGAYPjMZjMZh4AAAAAAAAAAAAAD82222z8AAAAAAAAAAAAAAGBj82228z8YGAAAAAAAAAAHBgYDAfDAYDAMA4AAAAAAAAAAD4xmMxmMxmMxmMAAAAAAAAAAAA/gAAH8AAA/gAAAAAAAAAAAAAGAwfgwGAAAD8AAAAAAAAAABgGAYBgYGBgAD8AAAAAAAAAAAYGBgYBgGAYAD8AAAAAAAAADg2GwwGAwGAwGAwGAwGAwGAwGAwGAwGAwGGw2GwcAAAAAAAAAAAAAwAD8AAwAAAAAAAAAAAAAAAADs3AAdm4AAAAAAAAAABwbDYOAAAAAAAAAAAAAAAAAAAAAAAAAAAAwGAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAeDAYDAYDHYbDYPA4AAAAAAADYNhsNhsNgAAAAAAAAAAAAAAB4ZgYGBkfgAAAAAAAAAAAAAAAAAAAfj8fj8fj8fgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); FONT_80X25_SMALL = base64ToBits("AAAAAACQD2Bvb/CfCu5EAATu5AAE6k4ABO5OAAAGYAD/+Z//AGmWAP+Waf8CSqQABKpEAAdESAAHVVoABEpEAAjOyAACbmIABOTkAAqqCgAHpiIwaGlhYAAA7gAE5OTgBOREQATkREAALyAAAE9AAAAI8AAAb2AAAE7gAADuQAAAAAAABEQEAAqgAAAE5OQARoQsQACSSQAE5JpQBIAAAAJERCAEIiJACk5KAABOQAAAAASAAA4AAAAABAAAJEgABKqkAAxETgAMJI4ADCQsAAiORAAOhCwABIykAA4kRAAEpKQABqYiAABAQAAAQEgAAkhCAADw8AAIQkgASiQEAATuhgAErqoADKysAASopAAMqqwADoyOAA6MiAAEqOQACq6qAA5ETgAOIqQACqyqAAiIjgAK7qoACu7qAASqpAAMrIgABKqmAAysqgAGhCwADkREAAqqpAAKqkQACq7kAAqkqgAKpEQADiSOAAZERgAAhEIABiImAEoAAAAAAADwCEAAAABqpgAIyqwAAGiGAAJqpgAAToYAAkZEAABKpiQIyqoABAREAAQERIAIrKoABEREAADuqgAAyqoAAEqkAADKyAAAamIAAMqIAABoLAAE5EQAAKqmAACqpAAAqu4AAKRKAACqRAAA4s4AAkxCAARERAAIRkgAbAAAAABKrgAEqKSACgqmACQk6OBKBuYACgbmAEIG5gAEBuYAAAaGSEoE6OAKBOjghATo4AoERABKBEQAhAREAKBK6gBASuoAJA6MjgBv5wAHr6sASgSqQKAEqkCEBKpASgqkAIQKpACgqkQAoEqqQKCqqkAARoZASoyOAKpORADKyrohJU5EgCQG5gAkBEQAJASqQCQKpABsDKoAbAru6gbmDgAEpA4AQEikAAAOiAAADiIARA4EjkQOCOQEBO5AAFhQAAChoAAUFBQUWlpaWtfX19dERERERExERETMRERVXVVVAA9VVQDMRERV3VVVVVVVVQD9VVVV3wAAVV8AAETMAAAADEREREcAAERPAAAAD0REREdERAAPAABET0RERHdERFVXVVVVdwAAAHdVVVX/AAAA/1VVVXdVVQD/AABV/1VVRP8AAFVfAAAA/0REAA9VVVVXAABEdwAAAHdERAAHVVVVX1VVRP9ERERMAAAAB0RE/////wAA///MzMzMMzMzM///AAAFqqUAaamSAA6oiAAA6qoA+EJI8AB6qkAAVVZIAKREQOSqpOBKrqpAaZZmkGQmmWAA+Z8AEmlkgAaOhgAGmZkADg4OAABOQOAAQkBgACQgYAJUREREREzABA4EAAWgWgBKQAAAAAZgAAAGAAADIqYgDKoAAEJGAAAAZmYAAAAAAA=="); function doubleScale(rgbaSource, fontWidth) { var byteWidth, doubledByteWidth, rgba, rgbaDoubled, startOfRow, i, k; byteWidth = fontWidth * 4; doubledByteWidth = byteWidth * 2; rgbaDoubled = new Uint8Array(rgbaSource.length * 4); for (i = 0, k = 0; i < rgbaSource.length; i += 4) { rgba = rgbaSource.subarray(i, i + 4); rgbaDoubled.set(rgba, k); k += 4; rgbaDoubled.set(rgba, k); k += 4; if ((i + 4) % byteWidth === 0) { startOfRow = k - doubledByteWidth; rgbaDoubled.set(rgbaDoubled.subarray(startOfRow, startOfRow + doubledByteWidth), k); k += doubledByteWidth; } } return rgbaDoubled; } function scaleCanvas(sourceData, width, height, chunkWidth, chunkHeight) { var destWidth, destHeight, destData, rgba, pixelRowOffset, chunkSize, i, j, k, x, y, r, g, b, a; rgba = new Uint8Array(4); destWidth = width / chunkWidth; destHeight = height / chunkHeight; destData = new Uint8Array(destWidth * destHeight * 4); pixelRowOffset = (width - chunkWidth) * 4; chunkSize = chunkWidth * chunkHeight; for (i = x = y = 0; i < destData.length; i += 4) { for (j = r = g = b = a = 0, k = (y * width * chunkHeight + x * chunkWidth) * 4; j < chunkSize; ++j) { r += sourceData[k++]; g += sourceData[k++]; b += sourceData[k++]; a += sourceData[k++]; if ((j + 1) % chunkWidth === 0) { k += pixelRowOffset; } } rgba[0] = Math.round(r / chunkSize); rgba[1] = Math.round(g / chunkSize); rgba[2] = Math.round(b / chunkSize); rgba[3] = Math.round(a / chunkSize); destData.set(rgba, i); if (++x === destWidth) { x = 0; ++y; } } return destData; } function getData(charCode, fg, bg, width, height, data, excludeNinthBit) { var fontBitWidth, rgbaOutput, i, j, k; fontBitWidth = width * height; rgbaOutput = new Uint8Array((excludeNinthBit ? width - 1 : width) * height * 4); for (i = 0, j = charCode * fontBitWidth, k = 0; i < fontBitWidth; ++i, ++j) { if (!excludeNinthBit || (i + 1) % 9 !== 0) { if (data[j]) { rgbaOutput.set(Palette.COLORS[fg], k); } else { rgbaOutput.set(Palette.COLORS[bg], k); } k += 4; } } return rgbaOutput; } function bigFont(charCode, fg, bg) { var bufferIndex; bufferIndex = charCode + (fg << 8) + (bg << 12); if (!bigFontBuffer[bufferIndex]) { bigFontBuffer[bufferIndex] = getData(charCode, fg, bg, 9, 16, FONT_80X25, true); if (RETINA) { bigFontBuffer[bufferIndex] = doubleScale(bigFontBuffer[bufferIndex], 8); } } return bigFontBuffer[bufferIndex]; } function smallFont(charCode, fg, bg) { var bufferIndex; bufferIndex = charCode + (fg << 8) + (bg << 12); if (!smallFontBuffer[bufferIndex]) { smallFontBuffer[bufferIndex] = getData(charCode, fg, bg, 4, 8, FONT_80X25_SMALL, false); if (!RETINA) { smallFontBuffer[bufferIndex] = scaleCanvas(smallFontBuffer[bufferIndex], 4, 8, 2, 2); } } return smallFontBuffer[bufferIndex]; } return { "bigFont": bigFont, "smallFont": smallFont, "NULL": NULL, "SPACE": SPACE, "UPPER_HALF_BLOCK": UPPER_HALF_BLOCK, "LOWER_HALF_BLOCK": LOWER_HALF_BLOCK, "LIGHT_SHADE": LIGHT_SHADE, "MEDIUM_SHADE": MEDIUM_SHADE, "DARK_SHADE": DARK_SHADE, "FULL_BLOCK": FULL_BLOCK, "BULLET_OPERATOR": BULLET_OPERATOR, "MIDDLE_DOT": MIDDLE_DOT, "NO_BREAK_SPACE": NO_BREAK_SPACE }; }()); function editorCanvas(height) { "use strict"; var canvas, previewCanvas, ctx, previewCtx, imageData, previewImageData, image; image = new Uint8Array(80 * height * 3); function draw(charCode, x, y, fg, bg) { previewImageData.data.set(Codepage.smallFont(charCode, fg, bg), 0); imageData.data.set(Codepage.bigFont(charCode, fg, bg), 0); previewCtx.putImageData(previewImageData, x * previewImageData.width, y * previewImageData.height); ctx.putImageData(imageData, x * imageData.width, y * imageData.height); } function update(index) { draw(image[index], (index / 3) % 80, Math.floor(index / 240), image[index + 1], image[index + 2]); } function redraw() { var i; for (i = 0; i < image.length; i += 3) { update(i); } } function set(charCode, fg, bg, index) { image[index] = charCode; image[index + 1] = fg; image[index + 2] = bg; update(index); } function get(coord) { var index, charCode, foreground, background, isBlocky, upperBlockColor, lowerBlockColor; index = (coord.textY * 80 + coord.textX) * 3; charCode = image[index]; foreground = image[index + 1]; background = image[index + 2]; switch (charCode) { case Codepage.NULL: case Codepage.SPACE: case Codepage.NO_BREAK_SPACE: upperBlockColor = background; lowerBlockColor = background; isBlocky = true; break; case Codepage.UPPER_HALF_BLOCK: upperBlockColor = foreground; lowerBlockColor = background; isBlocky = true; break; case Codepage.LOWER_HALF_BLOCK: upperBlockColor = background; lowerBlockColor = foreground; isBlocky = true; break; case Codepage.FULL_BLOCK: upperBlockColor = foreground; lowerBlockColor = foreground; isBlocky = true; break; default: isBlocky = false; } return { "charCode": charCode, "foreground": foreground, "background": background, "isBlocky": isBlocky, "upperBlockColor": upperBlockColor, "lowerBlockColor": lowerBlockColor }; } function setChunk(coord, color) { var block; block = get(coord); if (block.isBlocky) { if (coord.isUpperHalf) { if (block.lowerBlockColor === color) { set(Codepage.FULL_BLOCK, color, block.background, coord.index); } else { set(Codepage.UPPER_HALF_BLOCK, color, block.lowerBlockColor, coord.index); } } else { if (block.upperBlockColor === color) { set(Codepage.FULL_BLOCK, color, block.background, coord.index); } else { set(Codepage.LOWER_HALF_BLOCK, color, block.upperBlockColor, coord.index); } } } else { if (coord.isUpperHalf) { set(Codepage.UPPER_HALF_BLOCK, color, block.background, coord.index); } else { set(Codepage.LOWER_HALF_BLOCK, color, block.background, coord.index); } } } function getBlockCoord(blockX, blockY) { var x, y, textY, modBlockY; x = blockX * 8; y = blockY * 16; textY = Math.floor(blockY / 2); modBlockY = blockY % 2; return { "index": (textY * 80 + blockX) * 3, "x": x, "y": y, "textX": blockX, "textY": textY, "blockX": blockX, "blockY": blockY, "isUpperHalf": (modBlockY === 0), "isLowerHalf": (modBlockY === 1) }; } function chunkLine(from, to, callback) { var x0, y0, x1, y1, dx, dy, sx, sy, err, e2; x0 = from.blockX; y0 = from.blockY; x1 = to.blockX; y1 = to.blockY; dx = Math.abs(x1 - x0); sx = (x0 < x1) ? 1 : -1; dy = Math.abs(y1 - y0); sy = (y0 < y1) ? 1 : -1; err = ((dx > dy) ? dx : -dy) / 2; while (true) { callback(getBlockCoord(x0, y0)); if (x0 === x1 && y0 === y1) { break; } e2 = err; if (e2 > -dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } } function setChar(charCode, color, coord) { var block; block = get(coord); if (block.isBlocky) { if (coord.isUpperHalf) { set(charCode, color, block.upperBlockColor, coord.index); } else { set(charCode, color, block.lowerBlockColor, coord.index); } } else { set(charCode, color, block.background, coord.index); } } function resolveConflict(coord, bias) { var block; block = get(coord); if (block.background > 7) { if (block.isBlocky) { if (block.foreground > 7) { if (block.background === bias) { if (block.upperBlockColor === block.background) { set(Codepage.UPPER_HALF_BLOCK, block.background, block.foreground - 8, coord.index); } else { set(Codepage.LOWER_HALF_BLOCK, block.background, block.foreground - 8, coord.index); } } else { set(image[coord.index], block.foreground, block.background - 8, coord.index); } } else { if ((block.upperBlockColor === block.background) && (block.lowerBlockColor === block.background)) { set(Codepage.FULL_BLOCK, block.background, block.foreground, coord.index); } else if (block.upperBlockColor === block.background) { set(Codepage.UPPER_HALF_BLOCK, block.background, block.foreground, coord.index); } else if (block.lowerBlockColor === block.background) { set(Codepage.LOWER_HALF_BLOCK, block.background, block.foreground, coord.index); } else { set(Codepage.FULL_BLOCK, block.foreground, block.background - 8, coord.index); } } } else { set(image[coord.index], block.foreground, block.background - 8, coord.index); } } } function resolveConflicts(bias) { var i; for (i = 0; i < image.length; i += 3) { resolveConflict({"textX": (i / 3) % 80, "textY": Math.floor(i / 240), "index": i}, bias); } } function init(divEditor) { var mousedown; canvas = createElement("canvas", {"width": RETINA ? 1280 : 640, "height": RETINA ? height * 32 : height * 16, "style": {"width": "640px", "height": (height * 16) + "px", "verticalAlign": "bottom"}}); previewCanvas = createElement("canvas", {"width": RETINA ? 320 : 160, "height": RETINA ? height * 8 : height * 4, "style": {"width": "160px", "height": (height * 4) + "px", "verticalAlign": "bottom"}}); ctx = canvas.getContext("2d"); previewCtx = previewCanvas.getContext("2d"); imageData = ctx.createImageData(RETINA ? 16 : 8, RETINA ? 32 : 16); previewImageData = ctx.createImageData(RETINA ? 4 : 2, RETINA ? 8 : 4); redraw(); function getCoord(pageX, pageY) { var x, y, coord; x = pageX - divEditor.offsetLeft; y = pageY - divEditor.offsetTop; coord = getBlockCoord(Math.floor(x / 8), Math.floor(y / 8)); coord.x = x; coord.y = y; return coord; } canvas.addEventListener("mousedown", function (evt) { mousedown = true; document.dispatchEvent(new CustomEvent("canvasDown", {"detail": getCoord(evt.pageX, evt.pageY)})); }, false); canvas.addEventListener("mouseup", function () { mousedown = false; }, false); canvas.addEventListener("mousemove", function (evt) { document.dispatchEvent(new CustomEvent(mousedown ? "canvasDrag" : "canvasMove", {"detail": getCoord(evt.pageX, evt.pageY)})); }, false); canvas.addEventListener("mouseout", function () { mousedown = false; }, false); divEditor.appendChild(canvas); document.getElementById("preview").appendChild(previewCanvas); } return { "init": init, "height": height, "set": set, "get": get, "getBlockCoord": getBlockCoord, "setChunk": setChunk, "chunkLine": chunkLine, "setChar": setChar, "resolveConflict": resolveConflict, "resolveConflicts": resolveConflicts, "redraw": redraw, "image": image }; } function freehandTool(editor) { "use strict"; var currentColor, lastPoint, shiftDown; function colorChange(evt) { currentColor = evt.detail; } function blockyLine(from, to) { editor.chunkLine(from, to, function (coord) { editor.setChunk(coord, currentColor); editor.resolveConflict(coord, currentColor); }); } function canvasDown(evt) { if (shiftDown && lastPoint) { blockyLine(lastPoint, evt.detail); } else { editor.setChunk(evt.detail, currentColor); editor.resolveConflict(evt.detail, currentColor); } lastPoint = evt.detail; } function canvasDrag(evt) { blockyLine(lastPoint, evt.detail); lastPoint = evt.detail; } function keydown(evt) { switch (evt.keyCode || evt.which) { case 16: shiftDown = true; break; } } function keyup(evt) { switch (evt.keyCode || evt.which) { case 16: shiftDown = false; break; } } function init() { document.addEventListener("colorChange", colorChange, false); document.addEventListener("canvasDown", canvasDown, false); document.addEventListener("canvasDrag", canvasDrag, false); document.addEventListener("keydown", keydown, false); document.addEventListener("keyup", keyup, false); currentColor = Palette.getSelected(); return true; } function remove() { document.removeEventListener("colorChange", colorChange); document.removeEventListener("canvasDown", canvasDown); document.removeEventListener("canvasDrag", canvasDrag); document.removeEventListener("keydown", keydown); document.removeEventListener("keyup", keyup); } function toString() { return "Freehand"; } return { "init": init, "remove": remove, "toString": toString, "uid": "freehand" }; } function fillTool(editor) { "use strict"; var currentColor; function colorChange(evt) { currentColor = evt.detail; } function simpleFill(blockX, blockY, targetColor) { var coord, block, queue; queue = [editor.getBlockCoord(blockX, blockY)]; while (queue.length) { coord = queue.pop(); block = editor.get(coord); if ((coord.isUpperHalf && (block.upperBlockColor === targetColor)) || (coord.isLowerHalf && (block.lowerBlockColor === targetColor))) { editor.setChunk(coord, currentColor); if (coord.blockX > 0) { queue.push(editor.getBlockCoord(coord.blockX - 1, coord.blockY)); } if (coord.blockX < 79) { queue.push(editor.getBlockCoord(coord.blockX + 1, coord.blockY)); } if (coord.blockX > 0) { queue.push(editor.getBlockCoord(coord.blockX, coord.blockY - 1)); } if (coord.blockX < editor.height * 2 - 1) { queue.push(editor.getBlockCoord(coord.blockX, coord.blockY + 1)); } } } } function canvasDown(evt) { var block, targetColor; block = editor.get(evt.detail); if (block.isBlocky) { targetColor = evt.detail.isUpperHalf ? block.upperBlockColor : block.lowerBlockColor; if (targetColor !== currentColor) { simpleFill(evt.detail.blockX, evt.detail.blockY, targetColor); editor.resolveConflicts(currentColor); } } } function init() { document.addEventListener("canvasDown", canvasDown, false); document.addEventListener("colorChange", colorChange, false); currentColor = Palette.getSelected(); return true; } function remove() { document.removeEventListener("canvasDown", canvasDown); document.removeEventListener("colorChange", colorChange); } function toString() { return "Fill"; } return { "init": init, "remove": remove, "toString": toString, "uid": "fill" }; } function charBrushTool(name, editor, options) { "use strict"; var currentColor, shiftDown, lastPoint, mode; mode = 0; function colorChange(evt) { currentColor = evt.detail; } function charLine(from, to) { editor.chunkLine(from, to, function (coord) { editor.setChar(options[mode].charCode, currentColor, coord); editor.resolveConflict(coord, currentColor); }); } function canvasDown(evt) { if (shiftDown && lastPoint) { charLine(lastPoint, evt.detail); } else { editor.setChar(options[mode].charCode, currentColor, evt.detail); editor.resolveConflict(evt.detail, currentColor); } lastPoint = evt.detail; } function canvasDrag(evt) { charLine(lastPoint, evt.detail); lastPoint = evt.detail; } function keydown(evt) { switch (evt.keyCode || evt.which) { case 16: shiftDown = true; break; } } function keyup(evt) { switch (evt.keyCode || evt.which) { case 16: shiftDown = false; break; } } function init() { document.addEventListener("canvasDown", canvasDown, false); document.addEventListener("canvasDrag", canvasDrag, false); document.addEventListener("colorChange", colorChange, false); document.addEventListener("keydown", keydown, false); document.addEventListener("keyup", keyup, false); currentColor = Palette.getSelected(); return true; } function remove() { document.removeEventListener("canvasDown", canvasDown); document.removeEventListener("canvasDrag", canvasDrag); document.removeEventListener("colorChange", colorChange); document.removeEventListener("keydown", keydown); document.addEventListener("keyup", keyup); } function modeChange() { if (++mode === options.length) { mode = 0; } } function toString() { return options.length ? name + ": " + options[mode].name : name; } return { "init": init, "remove": remove, "modeChange": modeChange, "toString": toString, "uid": "charbrush-" + name }; } function clearTool(editor) { "use strict"; function init() { var i; for (i = 0; i < editor.image.length; ++i) { editor.image[i] = 0; } editor.redraw(); return false; } function toString() { return "Clear"; } return { "init": init, "toString": toString, "uid": "clear" }; } function exportTool(editor) { "use strict"; function toDataURL(bytes) { return "data:application/octet-stream;base64," + btoa(String.fromCharCode.apply(null, bytes)); } function highestRow(input) { reutu } function toBinFormat(input) { var output, inputIndex, outputIndex, highest, end; highest = 26; for (inputIndex = 0; inputIndex < input.length; inputIndex += 3) { if (input[inputIndex]) { highest = Math.max(Math.ceil(inputIndex / 240), highest); } } output = new Uint8Array((input.length / 3 * 2) + 11); output.set(new Uint8Array([88, 66, 73, 78, 26, 80, 0, highest, 0, 16, 0]), 0); for (inputIndex = 0, outputIndex = 11, end = highest * 80 * 3; inputIndex < end; inputIndex += 3, outputIndex += 2) { output[outputIndex] = input[inputIndex]; output[outputIndex + 1] = input[inputIndex + 1] + (input[inputIndex + 2] << 4); } return output; } function removeLink() { var divExport; divExport = document.getElementById("export"); if (divExport.firstChild) { divExport.removeChild(divExport.firstChild); } } function init() { var anchor; removeLink(); anchor = createElement("a", {"href": toDataURL(toBinFormat(editor.image)), "onclick": removeLink, "textContent": "Download", "download": "ansiedit.xb"}); document.getElementById("export").appendChild(anchor); return false; } function toString() { return "Export"; } return { "init": init, "toString": toString, "uid": "export" }; } document.addEventListener("DOMContentLoaded", function () { "use strict"; var editor; editor = editorCanvas(100); editor.init(document.getElementById("editor")); Toolbar.addTool(freehandTool(editor), {"keyCode": 102, "symbol": "f"}).select(); Toolbar.addTool(fillTool(editor), {"keyCode": 110, "symbol": "n"}); Toolbar.addTool(charBrushTool("Shading", editor, [{"charCode": Codepage.LIGHT_SHADE, "name": "Light"}, {"charCode": Codepage.MEDIUM_SHADE, "name": "Medium"}, {"charCode": Codepage.DARK_SHADE, "name": "Dark"}]), {"keyCode": 115, "symbol": "s"}); Toolbar.addTool(charBrushTool("Dot", editor, [{"charCode": Codepage.MIDDLE_DOT, "name": "Small"}, {"charCode": Codepage.BULLET_OPERATOR, "name": "Large"}]), {"keyCode": 100, "symbol": "d"}); Toolbar.addTool(clearTool(editor)); Toolbar.addTool(exportTool(editor), {"keyCode": 101, "symbol": "e"}); }, false);