UNPKG

retrolib

Version:

Render low-res scenes to the canvas in a retro 8-bit era style. Aseprite exported animation wrapper, scene management, sound and image management, particle support.

365 lines 12.7 kB
import FontData from "./FontData"; import fontCodepage437 from './fonts/default.json'; import codelist from './codepage'; import { getContext, getImage } from './images'; var fontCanvas = null; var ctx = null; var fontList = []; function loadDefaultFonts() { var loadedFont = loadFromJSON(fontCodepage437); if (loadedFont) { fontList.push(loadedFont); } } function loadFromJSON(fontJson) { try { var fontData = Object.assign(new FontData(), fontJson); fontData.image = new Image(); fontData.image.src = 'data:image/png;base64,' + fontData.imagedata; return fontData; } catch (e) { throw new Error(e); } } function fonts() { if (Object.keys(fontList).length === 0) { loadDefaultFonts(); } return Object.keys(fontList).map(function (m) { return fontList[m]; }); } function hexToRgba(hex) { if (hex.length === 7) { hex += 'ff'; } else if (hex.length === 8) { hex += '0'; } var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), a: parseInt(result[4], 16) } : null; } function rgbaToHex(rgb) { var r = rgb.r.toString(16); var g = rgb.g.toString(16); var b = rgb.b.toString(16); var a = rgb.a.toString(16); r = r.length === 1 ? '0' + r : r; g = g.length === 1 ? '0' + g : g; b = b.length === 1 ? '0' + b : b; a = a.length === 1 ? '0' + a : a; return "#".concat(r).concat(g).concat(b).concat(a); } function colorLerp(color1, color2, t) { return { r: Math.floor(color1.r + (color2.r - color1.r) * t), g: Math.floor(color1.g + (color2.g - color1.g) * t), b: Math.floor(color1.b + (color2.b - color1.b) * t), a: color1.a }; } function imageToBase64(img, outputFormat) { outputFormat = outputFormat ? outputFormat : 'image/png'; try { var canvas = document.createElement('canvas'); var contx = canvas.getContext('2d'); canvas.height = img.height; canvas.width = img.width; contx.drawImage(img, 0, 0); var data = canvas.toDataURL(outputFormat); var index = data.indexOf(';base64,') + ';base64,'.length; return data.slice(index); } catch (_a) { console.error('Cannot do this outside of the DOM yet.'); /* const canvas: Canvas = createCanvas(img.width, img.height) const contx = canvas.getContext('2d'); canvas.height = img.height canvas.width = img.width contx.drawImage(img, 0, 0) const data = canvas.toDataURL(outputFormat) const index = data.indexOf(';base64,') + ';base64,'.length return data.slice(index)*/ } } /** * Get base64 image data and build a precompiled font JSON object. * @param imageName * @param max_y * @param cw Character width. * @param ch Character height. * @returns */ function codepageAndBitmaptoJSON(imageName, max_y, cw, ch) { return new Promise(function (resolve, reject) { try { var sx = 0; // Source X var sy = 0; // Source Y cw = cw ? cw : 9; // Character Width ch = ch ? ch : 16; // Character Height var codepage = []; var imagedata = null; var image = new Image(); imagedata = imageToBase64(getImage(imageName)); image.src = 'data:image/png;base64,' + imagedata; max_y = max_y ? max_y : image.height; var _loop_1 = function (code) { var codeitem = codelist.filter(function (f) { return f.codenumber === code; }); if (codeitem.length > 0) { codeitem[0].rect = undefined; // { x: sx, y: sy, w: cw, h: ch } //TODO: Make this a parameter to control whether we auto-generate rects codepage.push(codeitem[0]); } sx += cw; if (sx >= image.width) { sx = 0; if (max_y && sy + ch < max_y) { sy += ch; } } if (sy >= image.height) { sy = 0; } }; for (var code = 0; code < 256; code++) { _loop_1(code); } resolve({ cwidth: cw, cheight: ch, codepage: codepage, imagedata: imagedata }); } catch (e) { reject(e); } }); } function textHeight(text, font) { if (!font && fontList.length > 0) { font = fontList[0]; } else if (!font || fontList.length === 0) { throw new Error('Font parameter empty and default fonts are not loaded.'); } try { if (text.length === 0) { return 0; } var txt = text.split('\n'); return txt.length * font.cheight; } catch (_a) { return 0; } } function textWidth(text, font) { if (!font && fontList.length > 0) { font = fontList[0]; } else if (!font || fontList.length === 0) { throw new Error('Font parameter empty and default fonts are not loaded.'); } try { if (text.length === 0) { return 0; } var maxw = 0; var txt = text.split('\n'); var _loop_2 = function (index) { var line = txt[index]; var linewidth = 0; var _loop_3 = function (char) { var glyph = font.codepage.filter(function (f) { return f.codenumber === line.charCodeAt(char); }); var rect = glyph.length > 0 ? glyph[0].rect : null; if (rect) { linewidth += rect.w; } else { linewidth += font.cwidth; } }; for (var char = 0; char < line.length; char++) { _loop_3(char); } if (linewidth > maxw) { maxw = linewidth; } }; for (var index in txt) { _loop_2(index); } return maxw; } catch (_a) { return 0; } } /** * Draws the specified text on the canvas. * * @param {HTMLCanvasElement} ctx 2d context to draw text on. * @param {number} x Left location for text. * @param {number} y Top location for text * @param {string} text Text to be drawn on canvas. * @param {ColorRGBA} color Colour to use (white if undefined). * @param {FontData} font Font to use (default DOS codepage 437 font if undefined). * @param {object} effects Any effects and parameters to apply when rendering this text. * @returns {Rect} */ export function drawTextCtx(context, x, y, text, color, font) { var oldCtx = ctx; ctx = context; var retVal = drawText(x, y, text, color, font); ctx = oldCtx; return retVal; } /** * Draws the specified text on the canvas. * @param x Left location for text. * @param y Top location for text. * @param text Text to be drawn on canvas. * @param color Colour to use (white if undefined). * @param font Font to use (default DOS codepage 437 font if undefined). * @returns Rect object with the x, y, width, height of the text drawn. */ function drawText(x, y, text, color, font /*, effects: object*/) { if (typeof color === 'string') { color = hexToRgba(color); } if (text.length === 0) { return { x: x, y: y, w: 0, h: 0 }; } //effects = effects ? effects : {} if (!font && fontList.length > 0) { font = fontList[0]; } else if (!font || fontList.length === 0) { throw new Error('Font parameter empty and default fonts are not loaded.'); } if (!color) { color = hexToRgba('#ffffffff'); } if (!font || !font.codepage || !font.imagedata || !font.image || !font.cwidth || !font.cheight) { throw new Error('Invalid font or font not loaded.'); } var textwidth = textWidth(text, font); var textheight = textHeight(text, font); if (!fontCanvas) { fontCanvas = document.createElement('canvas'); fontCanvas.id = 'fontCanvas'; } if (!ctx) { ctx = getContext(); } fontCanvas.width = textwidth; fontCanvas.height = textheight; var fontctx = fontCanvas.getContext('2d'); fontctx.clearRect(0, 0, textwidth, textheight); var dx = 0; var maxdx = 0; var rows = text.split('\n'); var dy = 0; var _loop_4 = function (rowIndex) { var txt = rows[rowIndex]; var _loop_5 = function (index) { var glyphs = font.codepage.filter(function (f) { return f.symbol === txt[index]; }); var glyph = null; if (glyphs.length === 0) { glyph = font.codepage.filter(function (f) { return f.codenumber === txt[index].charCodeAt(0); }); } else { glyph = glyphs[0]; } var rect = glyph ? glyph.rect : null; if (rect) { fontctx.drawImage(font.image, rect.x, rect.y, rect.w, rect.h, dx, dy, rect.w, rect.h); dx += rect.w; } else { console.log('Error finding value in codepage for', txt[index], "(".concat(txt[index].charCodeAt(0), ")")); } }; for (var index = 0; index < txt.length; index++) { _loop_5(index); } dy += font.cheight; if (dx > maxdx) { maxdx = dx; } dx = 0; }; for (var rowIndex in rows) { _loop_4(rowIndex); } textwidth = maxdx; var imageData = null; if (textwidth > 0) { try { imageData = fontctx.getImageData(0, 0, textwidth, textheight); } catch (_a) { console.log('Error getting image data:', textwidth, textheight); return; } var pixels = imageData.data; for (var py = 0; py < textheight; py++) { for (var px = 0; px < textwidth; px++) { var pixel = getPixelAtRgba(pixels, px, py, textwidth); // if (Object.keys(effects).length > 0) { // let setDefaultPixel = true // if (effects.gradient && pixel && pixel.a > 0) { // const vertical = effects.gradient.horizontal ? false : true // const lerpT = vertical ? py / textheight : px / textwidth // const gradientColour: ColorRGBA = HexToRgba(effects.gradient.color) // const lerpColr: ColorRGBA = ColorLerp(color, gradientColour, lerpT) // SetPixelAtRgba(pixels, lerpColr, px, py, textwidth) // setDefaultPixel = false // } // if (pixel && pixel.a > 0 && setDefaultPixel) { // SetPixelAtRgba(pixels, color, px, py, textwidth) // } // } else { if (pixel && pixel.a > 0) { setPixelAtRgba(pixels, color, px, py, textwidth); } // } } } var newImageData = new ImageData(pixels, textwidth, textheight); fontctx.clearRect(0, 0, textwidth, textheight); // if (effects.background) { // ctx.fillStyle = effects.background.colour // ctx.fillRect(x, y, textwidth, textheight) // } fontctx.putImageData(newImageData, 0, 0); ctx.drawImage(fontCanvas, 0, 0, textwidth, textheight, x, y, textwidth, textheight); } return { x: x, y: y, w: textwidth, h: textheight }; } function setPixelAtRgba(pixels, color, x, y, pixelswidth) { var offset = (x + pixelswidth * y) * 4; if (offset < 0 || offset >= pixels.length) { return false; } pixels[offset] = color.r; pixels[offset + 1] = color.g; pixels[offset + 2] = color.b; pixels[offset + 3] = color.a; return true; } function getPixelAtRgba(pixels, x, y, pixelswidth) { var offset = (x + pixelswidth * y) * 4; if (offset < 0 || offset >= pixels.length) { return null; } return { r: pixels[offset], g: pixels[offset + 1], b: pixels[offset + 2], a: pixels[offset + 3] }; } export function getCtx() { return ctx; } export function setCtx(context) { ctx = context; } export { loadFromJSON, loadDefaultFonts, fonts, colorLerp, rgbaToHex, hexToRgba, imageToBase64, codepageAndBitmaptoJSON, textHeight, textWidth, drawText }; //# sourceMappingURL=font.js.map