@litecanvas/plugin-pixel-font
Version:
Plugin to let you render a pixel font to emulate retro vibes in your Litecanvas projects.
179 lines (153 loc) • 4.7 kB
JavaScript
import 'litecanvas'
export { font as PIXEL_FONT_BASIC } from './fonts/basic-8x8.js'
export { font as PIXEL_FONT_MINI } from './fonts/mini-4x6.js'
/**
* @typedef {object} LitecanvasPixelFont
* @property {string} id The font name (must be unique)
* @property {any[]} chars The list of font glyphs
* @property {number} first The first font glyph code
* @property {number} w The width of each glyph
* @property {number} h The height of each glyph
* @property {(engine: LitecanvasInstance, bitmap: any, color?: number) => void} render The font fglyph renderer
*/
/**
* @param {LitecanvasInstance} engine
* @param {object} config
* @param {boolean | number} config.cache
*/
export default plugin = (engine, { cache = true } = {}) => {
// litecanvas core methods
const _core_text = engine.text
const _core_textsize = engine.textsize
const _core_textalign = engine.textalign
const _core_textfont = engine.textfont
/** @type {Map<string, ImageBitmap} */
const cached = cache ? new Map() : null
let fontScale = 1
/** @type {LitecanvasPixelFont | null} */
let currentFont = null
DEV: {
if (cache) {
cacheExpiration = 5
console.log('[litecanvas/plugin-pixel-font] cache enabled!')
console.log(
'[litecanvas/plugin-pixel-font] cache expiration set to 5 seconds! (for test purposes)'
)
}
}
/**
* @param {number} value
*/
const setPixelFontScale = (value) => {
fontScale = ~~Math.round(value)
}
const setPixelFontAlign = () => {
return console.warn(
'[litecanvas/plugin-pixel-font] textalign() has not yet been implemented for pixel fonts'
)
}
/**
* @param {number} posx
* @param {number} posx
* @param {number} posy
* @param {(number|undefined)[]} bitmap
* @param {number?} color
*/
const renderChar = (posx, posy, bitmapChar, color = 3) => {
engine.push()
engine.translate(posx, posy)
engine.scale(fontScale)
currentFont.render(engine, bitmapChar, color)
engine.pop()
}
/**
* @param {number} x
* @param {number} y
* @param {string} str
* @param {number?} color
*/
const renderPixelText = (x, y, str, color = 3) => {
// check if stat(12) exists
if (null == engine.stat(12)) {
throw `renderPixelText requires Litecanvas v0.99 or later`
}
str += ''
if (!fontScale || !str.length) return
const charWidth = fontScale * currentFont.w
const charHeight = fontScale * (currentFont.h || currentFont.w)
for (let i = 0; i < str.length; i++) {
const char = str[i]
const charCode = char.charCodeAt()
const bitmap = currentFont.chars[charCode - currentFont.first]
if (bitmap) {
if (cache) {
const key = [
currentFont.id,
char,
~~color,
charWidth,
engine.stat(12).join(','),
].join(':')
// check the cache
if (!cached.has(key)) {
// create an char image and cache it
cached.set(
key,
engine.paint(charWidth, charHeight, () => {
renderChar(0, 0, bitmap, ~~color)
})
)
}
// get the cached char image
const img = cached.get(key)
// draw the char as image (better performance)
engine.image(x, y, img)
} else {
// with cached disabled, draw the char pixel by pixel
renderChar(x, y, bitmap, color)
}
}
x += charWidth
}
}
if (cache) {
// clear the cache every 1 minute
let intervalTime = 60 * 1000
// reduce this interval to 10 seconds in dev build
DEV: intervalTime = 10 * 1000
const intervalId = setInterval(() => {
cached.clear()
DEV: console.log('[litecanvas/plugin-pixel-font] cache cleared')
}, intervalTime)
engine.listen('quit', () => {
clearInterval(intervalId)
cached.clear()
})
// also clear the cache when pal() is called
const _core_pal = engine.pal
engine.def('pal', (colors) => {
cached.clear()
return _core_pal(colors)
})
}
/**
* @param {string | typeof defaultFont} font
*/
const textfont = (font) => {
if ('object' === typeof font) {
engine.def('text', renderPixelText)
engine.def('textsize', setPixelFontScale)
engine.def('textalign', setPixelFontAlign)
currentFont = font
setPixelFontScale(fontScale || 1)
} else {
engine.def('text', _core_text)
engine.def('textsize', _core_textsize)
engine.def('textalign', _core_textalign)
_core_textfont(font)
}
}
return {
textfont,
}
}