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
JavaScript
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