UNPKG

@osbjs/osbjs

Version:

a minimalist osu! storyboarding framework

157 lines (156 loc) 7.08 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TextureGenerator = void 0; const canvas_1 = require("canvas"); const fs_extra_1 = require("fs-extra"); const path_1 = __importDefault(require("path")); const Texture_1 = require("./Texture"); const Core_1 = require("../Core"); class TextureGenerator { /** * * @param folderPath full path to the folder that will be used to save generated text images. * @param osbFolderPath relative path to the folder that will be used to save generated text images. For example: `sb/lyrics` * @param fontProps font properties for the generated text images. */ constructor(folderPath, osbFolderPath, fontProps) { this.fontProps = { fontName: 'Arial', fontSize: 72, }; this._cache = []; this.folderPath = folderPath; this.osbFolderPath = osbFolderPath; this.fontProps = { ...this.fontProps, ...fontProps }; } /** alias for node-canvas' `registerFont` */ registerFont(fontPath, family, weight, style) { (0, canvas_1.registerFont)(fontPath, { family, weight, style }); } /** * Generate and save text image. * Returns a Texture that contains info about the width, height and osb path of the generated image. * @param text Text to be rendered. * @param color Text color. * @param offset Offset will be 0 on each side if not set. * @param withBoundingBox Whether bounding box should be included in the calculation or not. * If you are creating any sorts of per character effect then this should be true * and the Origin should be set to TopLeft, TopCenter or TopRight depends on your need * so that the text can be aligned properly. * However if you just wanna generate a whole line of text then just leave it as false * so your texture doesn't have extra unncessary paddings. */ generateTexture(text, color = { r: 0, g: 0, b: 0, }, offset = { top: 0, bottom: 0, left: 0, right: 0, }, withBoundingBox = false) { const defaultOffset = { top: 0, bottom: 0, left: 0, right: 0, }, defaultColor = { r: 0, g: 0, b: 0, }; const _offset = { ...defaultOffset, ...offset }, _color = { ...defaultColor, ...color, }; let texture = this._getTexture(text, _color, _offset); if (texture) return texture; const measure = this._measureText(text); const height = !withBoundingBox ? measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent + _offset.top + _offset.bottom : Math.abs(measure.actualBoundingBoxAscent) + measure.actualBoundingBoxDescent + _offset.top + _offset.bottom; const width = !withBoundingBox ? measure.actualBoundingBoxLeft + measure.actualBoundingBoxRight + _offset.left + _offset.right : Math.abs(measure.actualBoundingBoxLeft) + measure.actualBoundingBoxRight + _offset.left + _offset.right; const canvas = (0, canvas_1.createCanvas)(width, height); const ctx = canvas.getContext('2d'); ctx.font = `${this.fontProps.fontSize}px "${this.fontProps.fontName}"`; ctx.textBaseline = 'top'; ctx.fillStyle = (0, Core_1.rgbToHex)(_color.r, _color.g, _color.b); const x = !withBoundingBox ? _offset.left + measure.actualBoundingBoxLeft : _offset.left, y = !withBoundingBox ? _offset.top + measure.actualBoundingBoxAscent : _offset.top; ctx.fillText(text, x, y); const texturePath = path_1.default.join(this.folderPath, this.osbFolderPath, `_${this._cache.length}.png`); this._saveTexture(canvas.toDataURL('image/png'), texturePath); texture = new Texture_1.Texture(text, texturePath, path_1.default.join(this.osbFolderPath, `_${this._cache.length}.png`), _color, _offset); this._cache.push(texture); return texture; } _getTexture(text, color, offset) { return this._cache.find((t) => t.text == text && t.color.r == color.r && t.color.g == color.g && t.color.b == color.b && t.offset.top == offset.top && t.offset.bottom == offset.bottom && t.offset.left == offset.left && t.offset.right == offset.right); } /** alias for fs-extra' `emptyDirSync` */ emptyDir() { (0, fs_extra_1.emptyDirSync)(path_1.default.join(this.folderPath, this.osbFolderPath)); } /** * Return text dimensions without caching it. * @param text text to be measured * @param offset offset will be 0 on each side if not set. * @param withBoundingBox Whether bounding box should be included in the calculation or not. * If you are creating any sorts of per character effect then this should be true * and the Origin should be set to TopLeft, TopCenter or TopRight depends on your need * so that the text can be aligned properly. */ getTextDimensions(text, offset = { top: 0, bottom: 0, left: 0, right: 0, }, withBoundingBox = false) { const defaultOffset = { top: 0, bottom: 0, left: 0, right: 0, }; const _offset = { ...defaultOffset, ...offset }; const measure = this._measureText(text); const height = !withBoundingBox ? measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent + _offset.top + _offset.bottom : Math.abs(measure.actualBoundingBoxAscent) + measure.actualBoundingBoxDescent + _offset.top + _offset.bottom; const width = !withBoundingBox ? measure.actualBoundingBoxLeft + measure.actualBoundingBoxRight + _offset.left + _offset.right : Math.abs(measure.actualBoundingBoxLeft) + measure.actualBoundingBoxRight + _offset.left + _offset.right; return { width, height }; } _measureText(text) { const canvas = (0, canvas_1.createCanvas)(5000, 5000); const ctx = canvas.getContext('2d'); ctx.font = `${this.fontProps.fontSize}px "${this.fontProps.fontName}"`; ctx.textBaseline = 'top'; return ctx.measureText(text); } _saveTexture(uri, path) { let regExMatches = uri.match('data:(image/.*);base64,(.*)'); if (regExMatches) { let buffer = Buffer.from(regExMatches[2], 'base64'); (0, fs_extra_1.outputFileSync)(path, buffer); } else { throw new Error('Invalid image URI.'); } } } exports.TextureGenerator = TextureGenerator;