function editorCanvas(height, palette, noblink, preview, codepage, retina) { "use strict"; var canvas, ctx, imageData, image, undoQueue, overlays, mirror; canvas = ElementHelper.create("canvas", {"width": retina ? 1280 : 640, "height": retina ? height * 32 : height * 16, "style": {"width": "640px", "height": (height * 16) + "px", "verticalAlign": "bottom"}}); ctx = canvas.getContext("2d"); imageData = ctx.createImageData(retina ? 16 : 8, retina ? 32 : 16); image = new Uint8Array(80 * height * 3); undoQueue = []; overlays = {}; mirror = false; function draw(charCode, x, y, fg, bg) { imageData.data.set(codepage.bigFont(charCode, fg, bg), 0); ctx.putImageData(imageData, x * imageData.width, y * imageData.height); preview.draw(charCode, x, y, fg, bg); } 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 storeUndo(block) { undoQueue[0].push([block.charCode, block.foreground, block.background, block.index]); } function set(charCode, fg, bg, index) { image[index] = charCode; image[index + 1] = fg; image[index + 2] = bg; } function getBlock(blockX, blockY) { var index, textY, modBlockY, charCode, foreground, background, isBlocky, upperBlockColor, lowerBlockColor; textY = Math.floor(blockY / 2); modBlockY = blockY % 2; index = (textY * 80 + blockX) * 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: if (foreground === background) { isBlocky = true; upperBlockColor = foreground; lowerBlockColor = foreground; } else { isBlocky = false; } } return { "index": index, "textX": blockX, "textY": textY, "blockX": blockX, "blockY": blockY, "isUpperHalf": (modBlockY === 0), "isLowerHalf": (modBlockY === 1), "charCode": charCode, "foreground": foreground, "background": background, "isBlocky": isBlocky, "upperBlockColor": upperBlockColor, "lowerBlockColor": lowerBlockColor }; } function mirrorBlock(block) { if (block.blockX > 39) { return getBlock(39 - (block.blockX - 40), block.blockY); } return getBlock(40 + (39 - block.blockX), block.blockY); } function setTextBlock(block, charCode, fg, bg) { storeUndo(block); set(charCode, fg, bg, block.index); update(block.index); if (mirror) { block = mirrorBlock(block); storeUndo(block); set(charCode, fg, bg, block.index); update(block.index); } } function getTextBlock(textX, textY) { return getBlock(textX, textY * 2); } function getImageData(textX, textY, width, height) { var data, i, k, byteWidth, screenWidth; data = new Uint8Array(width * height * 3); byteWidth = width * 3; screenWidth = 80 * 3; for (i = 0, k = (textY * 80 + textX) * 3; i < data.length; i += byteWidth, k += screenWidth) { data.set(image.subarray(k, k + byteWidth), i); } return { "width": width, "height": height, "data": data }; } function putImageData(inputImageData, textX, textY, alpha) { var y, x, i, block; for (y = 0, i = 0; y < inputImageData.height; ++y) { if (textY + y >= height) { break; } if (textY + y >= 0) { for (x = 0; x < inputImageData.width; ++x, i += 3) { if (textX + x >= 0 && textX + x < 80) { block = getTextBlock(textX + x, textY + y); if (!alpha || inputImageData.data[i]) { setTextBlock(block, inputImageData.data[i], inputImageData.data[i + 1], inputImageData.data[i + 2]); update(block.index); } } } } else { i += inputImageData.width * 3; } } } function getHighestRow() { var i, max; max = 26; for (i = 0; i < image.length; i += 3) { if (image[i]) { max = Math.max(Math.ceil(i / 240), max); } } return max; } function renderImageData(inputImageData) { var imageDataCanvas, imageDataCtx, y, x, i; imageDataCanvas = ElementHelper.create("canvas", {"width": inputImageData.width * codepage.fontWidth, "height": inputImageData.height * codepage.fontHeight}); imageDataCtx = imageDataCanvas.getContext("2d"); for (y = 0, i = 0; y < inputImageData.height; ++y) { for (x = 0; x < inputImageData.width; ++x, i += 3) { if (inputImageData.data[i]) { imageData.data.set(codepage.bigFont(inputImageData.data[i], inputImageData.data[i + 1], inputImageData.data[i + 2]), 0); imageDataCtx.putImageData(imageData, x * codepage.fontWidth, y * codepage.fontHeight); } } } return imageDataCanvas; } function resolveConflict(blockIndex, colorBias, color) { var block; block = getBlock(blockIndex / 3 % 80, Math.floor(blockIndex / 3 / 80) * 2); if (block.background > 7) { if (block.isBlocky) { if (block.foreground > 7) { if (colorBias) { if (block.upperBlockColor === color && block.lowerBlockColor === color) { set(codepage.FULL_BLOCK, color, 0, block.index); } else if (block.upperBlockColor === color) { set(codepage.UPPER_HALF_BLOCK, block.upperBlockColor, block.lowerBlockColor - 8, block.index); } else if (block.lowerBlockColor === color) { set(codepage.LOWER_HALF_BLOCK, block.lowerBlockColor, block.upperBlockColor - 8, block.index); } else { set(image[block.index], block.foreground, block.background - 8, block.index); } } else { if (block.upperBlockColor === color && block.lowerBlockColor === color) { set(codepage.FULL_BLOCK, color, 0, block.index); } else if (block.upperBlockColor === color) { set(codepage.LOWER_HALF_BLOCK, block.lowerBlockColor, block.upperBlockColor - 8, block.index); } else if (block.lowerBlockColor === color) { set(codepage.UPPER_HALF_BLOCK, block.upperBlockColor, block.lowerBlockColor - 8, block.index); } else { set(image[block.index], block.foreground, block.background - 8, block.index); } } } else { if ((block.upperBlockColor === block.background) && (block.lowerBlockColor === block.background)) { set(codepage.FULL_BLOCK, block.background, block.foreground, block.index); } else if (block.upperBlockColor === block.background) { set(codepage.UPPER_HALF_BLOCK, block.background, block.foreground, block.index); } else if (block.lowerBlockColor === block.background) { set(codepage.LOWER_HALF_BLOCK, block.background, block.foreground, block.index); } else { set(codepage.FULL_BLOCK, block.foreground, block.background - 8, block.index); } } } else { set(image[block.index], block.foreground, block.background - 8, block.index); } } } function optimizeBlockAttributes(block, color) { if (block.isBlocky) { if (block.isUpperHalf) { if (block.lowerBlockColor === color) { set(codepage.FULL_BLOCK, color, block.background, block.index); } else { set(codepage.UPPER_HALF_BLOCK, color, block.lowerBlockColor, block.index); } } else { if (block.upperBlockColor === color) { set(codepage.FULL_BLOCK, color, block.background, block.index); } else { set(codepage.LOWER_HALF_BLOCK, color, block.upperBlockColor, block.index); } } } else { if (block.isUpperHalf) { set(codepage.UPPER_HALF_BLOCK, color, block.background, block.index); } else { set(codepage.LOWER_HALF_BLOCK, color, block.background, block.index); } } } function setBlock(block, color, colorBias, colorBiasColor) { storeUndo(block); optimizeBlockAttributes(block, color); if (!noblink) { resolveConflict(block.index, colorBias, colorBiasColor); } update(block.index); if (mirror) { block = mirrorBlock(block); storeUndo(block); optimizeBlockAttributes(block, color); if (!noblink) { resolveConflict(block.index, colorBias, colorBiasColor); } update(block.index); } } function setBlocks(colorBias, colorBiasColor, callback) { var i, minIndex, maxIndex; minIndex = image.length - 1; maxIndex = 0; callback(function (block, color) { storeUndo(block); optimizeBlockAttributes(block, color); if (block.index < minIndex) { minIndex = block.index; } if (block.index > maxIndex) { maxIndex = block.index; } if (mirror) { block = mirrorBlock(block); storeUndo(block); optimizeBlockAttributes(block, color); if (block.index < minIndex) { minIndex = block.index; } if (block.index > maxIndex) { maxIndex = block.index; } } }); for (i = minIndex; i <= maxIndex; i += 3) { if (!noblink) { resolveConflict(i, colorBias, colorBiasColor); } update(i); } } function setChar(block, charCode, color) { storeUndo(block); if (block.isBlocky) { if (block.isUpperHalf) { set(charCode, color, block.upperBlockColor, block.index); } else { set(charCode, color, block.lowerBlockColor, block.index); } } else { set(charCode, color, block.background, block.index); } if (!noblink) { resolveConflict(block.index, true, color); } update(block.index); if (mirror) { block = mirrorBlock(block); storeUndo(block); if (block.isBlocky) { if (block.isUpperHalf) { set(charCode, color, block.upperBlockColor, block.index); } else { set(charCode, color, block.lowerBlockColor, block.index); } } else { set(charCode, color, block.background, block.index); } if (!noblink) { resolveConflict(block.index, true, color); } update(block.index); } } function blockLine(from, to, callback, colorBias, colorBiasColor) { var x0, y0, x1, y1, dx, dy, sx, sy, err, e2, block, blocks, i; function setBlockLineBlock(blockLineBlock, color) { storeUndo(blockLineBlock); optimizeBlockAttributes(blockLineBlock, color); blocks.push(blockLineBlock.index); if (mirror) { blockLineBlock = mirrorBlock(block); storeUndo(blockLineBlock); optimizeBlockAttributes(blockLineBlock, color); blocks.push(blockLineBlock.index); } } 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; blocks = []; while (true) { block = getBlock(x0, y0); callback(block, setBlockLineBlock); if (x0 === x1 && y0 === y1) { for (i = 0; i < blocks.length; ++i) { if (!noblink) { resolveConflict(blocks[i], colorBias, colorBiasColor); } update(blocks[i]); } break; } e2 = err; if (e2 > -dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } } function startListening() { palette.startListening(); } function stopListening() { palette.stopListening(); } function clearImage() { var i; for (i = 0; i < image.length; i += 3) { image[i] = 0; image[i + 1] = 7; image[i + 2] = 0; } redraw(); } function init(divEditor) { palette.init(canvas, retina); preview.init(height, retina, codepage); clearImage(); function dispatchEvent(type, x, y, shiftKey, altKey) { var coord, evt, blockX, blockY; blockX = Math.floor((x - divEditor.offsetLeft) / 8); blockY = Math.floor((y - divEditor.offsetTop) / 8); coord = getBlock(blockX, blockY); coord.shiftKey = shiftKey; coord.altKey = altKey; evt = new CustomEvent(type, {"detail": coord}); canvas.dispatchEvent(evt); } divEditor.addEventListener("mousedown", function (evt) { evt.preventDefault(); dispatchEvent("canvasDown", evt.pageX, evt.pageY, evt.shiftKey, evt.altKey); }, false); divEditor.addEventListener("mouseup", function (evt) { evt.preventDefault(); dispatchEvent("canvasUp", evt.pageX, evt.pageY, evt.shiftKey, evt.altKey); }, false); divEditor.addEventListener("mousemove", function (evt) { var mouseButton; evt.preventDefault(); mouseButton = (evt.buttons !== undefined) ? evt.buttons : evt.which; if (mouseButton) { dispatchEvent("canvasDrag", evt.pageX, evt.pageY, evt.shiftKey, evt.altKey); } else { dispatchEvent("canvasMove", evt.pageX, evt.pageY); } }, false); divEditor.addEventListener("mouseout", function (evt) { evt.preventDefault(); canvas.dispatchEvent(new CustomEvent("canvasOut")); }, false); startListening(); canvas.style.position = "absolute"; canvas.style.left = "0px"; canvas.style.top = "0px"; divEditor.appendChild(canvas); } function undo() { var values, i; if (undoQueue.length) { values = undoQueue.shift(); for (i = values.length - 1; i >= 0; --i) { image[values[i][3]] = values[i][0]; image[values[i][3] + 1] = values[i][1]; image[values[i][3] + 2] = values[i][2]; update(values[i][3]); } return true; } return false; } function takeUndoSnapshot() { if (undoQueue.unshift([]) > 1000) { undoQueue.pop(); } } function clearUndoHistory() { while (undoQueue.length) { undoQueue.pop(); } } function removeOverlay(uid) { document.getElementById("editor").removeChild(overlays[uid]); delete overlays[uid]; } function addOverlay(overlayCanvas, uid) { if (overlays[uid]) { removeOverlay(uid); } overlayCanvas.style.position = "absolute"; if (retina) { overlayCanvas.style.width = overlayCanvas.width / 2 + "px"; overlayCanvas.style.height = overlayCanvas.height / 2 + "px"; } else { overlayCanvas.style.width = overlayCanvas.width + "px"; overlayCanvas.style.height = overlayCanvas.height + "px"; } overlayCanvas.style.left = "0px"; overlayCanvas.style.top = "0px"; document.getElementById("editor").appendChild(overlayCanvas); overlays[uid] = overlayCanvas; } function setMirror(value) { mirror = value; } return { "height": height, "palette": palette, "codepage": codepage, "retina": retina, "noblink": noblink, "init": init, "canvas": canvas, "clearImage": clearImage, "redraw": redraw, "image": image, "getBlock": getBlock, "setBlock": setBlock, "setBlocks": setBlocks, "getTextBlock": getTextBlock, "setTextBlock": setTextBlock, "getImageData": getImageData, "putImageData": putImageData, "getHighestRow": getHighestRow, "renderImageData": renderImageData, "blockLine": blockLine, "setChar": setChar, "takeUndoSnapshot": takeUndoSnapshot, "undo": undo, "clearUndoHistory": clearUndoHistory, "setMirror": setMirror, "addOverlay": addOverlay, "removeOverlay": removeOverlay, "stopListening": stopListening, "startListening": startListening }; }