Files
nothoughts/node_modules/pixi.js/lib/scene/text-bitmap/DynamicBitmapFont.js
2025-08-04 18:57:35 +02:00

277 lines
11 KiB
JavaScript

'use strict';
var Color = require('../../color/Color.js');
var Rectangle = require('../../maths/shapes/Rectangle.js');
var CanvasPool = require('../../rendering/renderers/shared/texture/CanvasPool.js');
var ImageSource = require('../../rendering/renderers/shared/texture/sources/ImageSource.js');
var Texture = require('../../rendering/renderers/shared/texture/Texture.js');
var deprecation = require('../../utils/logging/deprecation.js');
var CanvasTextMetrics = require('../text/canvas/CanvasTextMetrics.js');
var fontStringFromTextStyle = require('../text/canvas/utils/fontStringFromTextStyle.js');
var getCanvasFillStyle = require('../text/canvas/utils/getCanvasFillStyle.js');
var TextStyle = require('../text/TextStyle.js');
var AbstractBitmapFont = require('./AbstractBitmapFont.js');
var resolveCharacters = require('./utils/resolveCharacters.js');
"use strict";
const _DynamicBitmapFont = class _DynamicBitmapFont extends AbstractBitmapFont.AbstractBitmapFont {
/**
* @param options - The options for the dynamic bitmap font.
*/
constructor(options) {
super();
/**
* this is a resolution modifier for the font size..
* texture resolution will also be used to scale texture according to its font size also
*/
this.resolution = 1;
/** The pages of the font. */
this.pages = [];
this._padding = 0;
this._measureCache = /* @__PURE__ */ Object.create(null);
this._currentChars = [];
this._currentX = 0;
this._currentY = 0;
this._currentPageIndex = -1;
this._skipKerning = false;
const dynamicOptions = { ..._DynamicBitmapFont.defaultOptions, ...options };
this._textureSize = dynamicOptions.textureSize;
this._mipmap = dynamicOptions.mipmap;
const style = dynamicOptions.style.clone();
if (dynamicOptions.overrideFill) {
style._fill.color = 16777215;
style._fill.alpha = 1;
style._fill.texture = Texture.Texture.WHITE;
style._fill.fill = null;
}
this.applyFillAsTint = dynamicOptions.overrideFill;
const requestedFontSize = style.fontSize;
style.fontSize = this.baseMeasurementFontSize;
const font = fontStringFromTextStyle.fontStringFromTextStyle(style);
if (dynamicOptions.overrideSize) {
if (style._stroke) {
style._stroke.width *= this.baseRenderedFontSize / requestedFontSize;
}
} else {
style.fontSize = this.baseRenderedFontSize = requestedFontSize;
}
this._style = style;
this._skipKerning = dynamicOptions.skipKerning ?? false;
this.resolution = dynamicOptions.resolution ?? 1;
this._padding = dynamicOptions.padding ?? 4;
this.fontMetrics = CanvasTextMetrics.CanvasTextMetrics.measureFont(font);
this.lineHeight = style.lineHeight || this.fontMetrics.fontSize || style.fontSize;
}
ensureCharacters(chars) {
const charList = resolveCharacters.resolveCharacters(chars).filter((char) => !this._currentChars.includes(char)).filter((char, index, self) => self.indexOf(char) === index);
if (!charList.length)
return;
this._currentChars = [...this._currentChars, ...charList];
let pageData;
if (this._currentPageIndex === -1) {
pageData = this._nextPage();
} else {
pageData = this.pages[this._currentPageIndex];
}
let { canvas, context } = pageData.canvasAndContext;
let textureSource = pageData.texture.source;
const style = this._style;
let currentX = this._currentX;
let currentY = this._currentY;
const fontScale = this.baseRenderedFontSize / this.baseMeasurementFontSize;
const padding = this._padding * fontScale;
const widthScale = style.fontStyle === "italic" ? 2 : 1;
let maxCharHeight = 0;
let skipTexture = false;
for (let i = 0; i < charList.length; i++) {
const char = charList[i];
const metrics = CanvasTextMetrics.CanvasTextMetrics.measureText(char, style, canvas, false);
metrics.lineHeight = metrics.height;
const width = widthScale * metrics.width * fontScale;
const height = metrics.height * fontScale;
const paddedWidth = width + padding * 2;
const paddedHeight = height + padding * 2;
skipTexture = false;
if (char !== "\n" && char !== "\r" && char !== " " && char !== " ") {
skipTexture = true;
maxCharHeight = Math.ceil(Math.max(paddedHeight, maxCharHeight));
}
if (currentX + paddedWidth > this._textureSize) {
currentY += maxCharHeight;
maxCharHeight = paddedHeight;
currentX = 0;
if (currentY + maxCharHeight > this._textureSize) {
textureSource.update();
const pageData2 = this._nextPage();
canvas = pageData2.canvasAndContext.canvas;
context = pageData2.canvasAndContext.context;
textureSource = pageData2.texture.source;
currentY = 0;
}
}
const xAdvance = width / fontScale - (style.dropShadow?.distance ?? 0) - (style._stroke?.width ?? 0);
this.chars[char] = {
id: char.codePointAt(0),
xOffset: -this._padding,
yOffset: -this._padding,
xAdvance,
kerning: {}
};
if (skipTexture) {
this._drawGlyph(
context,
metrics,
currentX + padding,
currentY + padding,
fontScale,
style
);
const px = textureSource.width * fontScale;
const py = textureSource.height * fontScale;
const frame = new Rectangle.Rectangle(
currentX / px * textureSource.width,
currentY / py * textureSource.height,
paddedWidth / px * textureSource.width,
paddedHeight / py * textureSource.height
);
this.chars[char].texture = new Texture.Texture({
source: textureSource,
frame
});
currentX += Math.ceil(paddedWidth);
}
}
textureSource.update();
this._currentX = currentX;
this._currentY = currentY;
this._skipKerning && this._applyKerning(charList, context);
}
/**
* @deprecated since 8.0.0
* The map of base page textures (i.e., sheets of glyphs).
*/
get pageTextures() {
deprecation.deprecation(deprecation.v8_0_0, "BitmapFont.pageTextures is deprecated, please use BitmapFont.pages instead.");
return this.pages;
}
_applyKerning(newChars, context) {
const measureCache = this._measureCache;
for (let i = 0; i < newChars.length; i++) {
const first = newChars[i];
for (let j = 0; j < this._currentChars.length; j++) {
const second = this._currentChars[j];
let c1 = measureCache[first];
if (!c1)
c1 = measureCache[first] = context.measureText(first).width;
let c2 = measureCache[second];
if (!c2)
c2 = measureCache[second] = context.measureText(second).width;
let total = context.measureText(first + second).width;
let amount = total - (c1 + c2);
if (amount) {
this.chars[first].kerning[second] = amount;
}
total = context.measureText(first + second).width;
amount = total - (c1 + c2);
if (amount) {
this.chars[second].kerning[first] = amount;
}
}
}
}
_nextPage() {
this._currentPageIndex++;
const textureResolution = this.resolution;
const canvasAndContext = CanvasPool.CanvasPool.getOptimalCanvasAndContext(
this._textureSize,
this._textureSize,
textureResolution
);
this._setupContext(canvasAndContext.context, this._style, textureResolution);
const resolution = textureResolution * (this.baseRenderedFontSize / this.baseMeasurementFontSize);
const texture = new Texture.Texture({
source: new ImageSource.ImageSource({
resource: canvasAndContext.canvas,
resolution,
alphaMode: "premultiply-alpha-on-upload",
autoGenerateMipmaps: this._mipmap
})
});
const pageData = {
canvasAndContext,
texture
};
this.pages[this._currentPageIndex] = pageData;
return pageData;
}
// canvas style!
_setupContext(context, style, resolution) {
style.fontSize = this.baseRenderedFontSize;
context.scale(resolution, resolution);
context.font = fontStringFromTextStyle.fontStringFromTextStyle(style);
style.fontSize = this.baseMeasurementFontSize;
context.textBaseline = style.textBaseline;
const stroke = style._stroke;
const strokeThickness = stroke?.width ?? 0;
if (stroke) {
context.lineWidth = strokeThickness;
context.lineJoin = stroke.join;
context.miterLimit = stroke.miterLimit;
context.strokeStyle = getCanvasFillStyle.getCanvasFillStyle(stroke, context);
}
if (style._fill) {
context.fillStyle = getCanvasFillStyle.getCanvasFillStyle(style._fill, context);
}
if (style.dropShadow) {
const shadowOptions = style.dropShadow;
const rgb = Color.Color.shared.setValue(shadowOptions.color).toArray();
const dropShadowBlur = shadowOptions.blur * resolution;
const dropShadowDistance = shadowOptions.distance * resolution;
context.shadowColor = `rgba(${rgb[0] * 255},${rgb[1] * 255},${rgb[2] * 255},${shadowOptions.alpha})`;
context.shadowBlur = dropShadowBlur;
context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance;
} else {
context.shadowColor = "black";
context.shadowBlur = 0;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
}
}
_drawGlyph(context, metrics, x, y, fontScale, style) {
const char = metrics.text;
const fontProperties = metrics.fontProperties;
const stroke = style._stroke;
const strokeThickness = (stroke?.width ?? 0) * fontScale;
const tx = x + strokeThickness / 2;
const ty = y - strokeThickness / 2;
const descent = fontProperties.descent * fontScale;
const lineHeight = metrics.lineHeight * fontScale;
if (style.stroke && strokeThickness) {
context.strokeText(char, tx, ty + lineHeight - descent);
}
if (style._fill) {
context.fillText(char, tx, ty + lineHeight - descent);
}
}
destroy() {
super.destroy();
for (let i = 0; i < this.pages.length; i++) {
const { canvasAndContext, texture } = this.pages[i];
canvasAndContext.canvas.width = canvasAndContext.canvas.width;
CanvasPool.CanvasPool.returnCanvasAndContext(canvasAndContext);
texture.destroy(true);
}
this.pages = null;
}
};
_DynamicBitmapFont.defaultOptions = {
textureSize: 512,
style: new TextStyle.TextStyle(),
mipmap: true
};
let DynamicBitmapFont = _DynamicBitmapFont;
exports.DynamicBitmapFont = DynamicBitmapFont;
//# sourceMappingURL=DynamicBitmapFont.js.map