@osbjs/osbjs
Version:
a minimalist osu! storyboarding framework
157 lines (156 loc) • 7.08 kB
JavaScript
;
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;