@basementuniverse/image-font 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -2
- package/build/index.d.ts +31 -0
- package/build/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,11 +219,39 @@ type ImageFontRenderingOptions = {
|
|
|
219
219
|
* Default is 'top'
|
|
220
220
|
*/
|
|
221
221
|
baseLine?: 'top' | 'middle' | 'bottom';
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Color to apply to the text
|
|
225
|
+
*
|
|
226
|
+
* If not specified, no coloring is applied
|
|
227
|
+
*/
|
|
228
|
+
color?: string;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* How to apply the color
|
|
232
|
+
*
|
|
233
|
+
* Default is 'multiply'
|
|
234
|
+
*/
|
|
235
|
+
coloringMode?: ColoringMode;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Custom coloring function when coloringMode is 'custom'
|
|
239
|
+
*
|
|
240
|
+
* If coloringMode is 'custom' but no function is provided, falls back to
|
|
241
|
+
* 'multiply'
|
|
242
|
+
*/
|
|
243
|
+
coloringFunction?: (
|
|
244
|
+
context: CanvasRenderingContext2D,
|
|
245
|
+
texture: HTMLCanvasElement,
|
|
246
|
+
color: string
|
|
247
|
+
) => void;
|
|
222
248
|
};
|
|
223
249
|
```
|
|
224
250
|
|
|
225
|
-
## Utility
|
|
251
|
+
## Utility scripts
|
|
226
252
|
|
|
227
|
-
It can be rather tedious creating data for large image-fonts with lots of characters, so I vibe-coded a
|
|
253
|
+
It can be rather tedious creating data for large image-fonts with lots of characters, so I vibe-coded a utility script to help with that in './example/generate-data.html'.
|
|
228
254
|
|
|
229
255
|
You will need to run this using [http-server](https://www.npmjs.com/package/http-server) or a similar tool to serve the HTML file, because it loads images and renders/processes them using a canvas; your browser will complain about this if you open the HTML file directly.
|
|
256
|
+
|
|
257
|
+
We also have a script './example/rescale.js' which re-scales sizes and offsets in a configuration JSON file by a given factor, which is useful if you want to change the size of an existing font texture atlas without having to manually edit all the values.
|
package/build/index.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export type ImageFontCharacterConfig = {
|
|
|
56
56
|
*/
|
|
57
57
|
height?: number;
|
|
58
58
|
};
|
|
59
|
+
export type ColoringMode = 'multiply' | 'overlay' | 'hue' | 'custom';
|
|
59
60
|
export type ImageFontRenderingOptions = {
|
|
60
61
|
/**
|
|
61
62
|
* The scale factor to apply to the font when rendering
|
|
@@ -92,13 +93,43 @@ export type ImageFontRenderingOptions = {
|
|
|
92
93
|
* Default is 'top'
|
|
93
94
|
*/
|
|
94
95
|
baseLine?: 'top' | 'middle' | 'bottom';
|
|
96
|
+
/**
|
|
97
|
+
* Color to apply to the text
|
|
98
|
+
*
|
|
99
|
+
* If not specified, no coloring is applied
|
|
100
|
+
*/
|
|
101
|
+
color?: string;
|
|
102
|
+
/**
|
|
103
|
+
* How to apply the color
|
|
104
|
+
*
|
|
105
|
+
* Default is 'multiply'
|
|
106
|
+
*/
|
|
107
|
+
coloringMode?: ColoringMode;
|
|
108
|
+
/**
|
|
109
|
+
* Custom coloring function when coloringMode is 'custom'
|
|
110
|
+
*
|
|
111
|
+
* If coloringMode is 'custom' but no function is provided, falls back to
|
|
112
|
+
* 'multiply'
|
|
113
|
+
*/
|
|
114
|
+
coloringFunction?: (context: CanvasRenderingContext2D, texture: HTMLCanvasElement, color: string) => void;
|
|
95
115
|
};
|
|
96
116
|
export declare function isImageFontConfigData(value: unknown): value is ImageFontConfigData;
|
|
97
117
|
export declare class ImageFont {
|
|
118
|
+
private static readonly MAX_COLOR_CACHE_SIZE;
|
|
98
119
|
private static readonly DEFAULT_CONFIG;
|
|
99
120
|
private textures;
|
|
100
121
|
private config;
|
|
122
|
+
private colorCache;
|
|
101
123
|
constructor(textures: TextureAtlasMap, config: ImageFontConfig);
|
|
124
|
+
private getCacheKey;
|
|
125
|
+
/**
|
|
126
|
+
* Determine the coloring mode to use, falling back to 'multiply' if needed
|
|
127
|
+
*/
|
|
128
|
+
private getColoringMode;
|
|
129
|
+
/**
|
|
130
|
+
* Create a colored version of a texture using the specified coloring mode
|
|
131
|
+
*/
|
|
132
|
+
private createColoredTexture;
|
|
102
133
|
/**
|
|
103
134
|
* Calculate the width of a single character when rendered with this font
|
|
104
135
|
*/
|
package/build/index.js
CHANGED
|
@@ -26,7 +26,7 @@ return /******/ (() => { // webpackBootstrap
|
|
|
26
26
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
27
27
|
|
|
28
28
|
"use strict";
|
|
29
|
-
eval("{\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.imageFontContentProcessor = exports.ImageFont = exports.isImageFontConfigData = void 0;\nconst texture_atlas_1 = __webpack_require__(/*! @basementuniverse/texture-atlas */ \"./node_modules/@basementuniverse/texture-atlas/build/index.js\");\nconst vec_1 = __webpack_require__(/*! @basementuniverse/vec */ \"./node_modules/@basementuniverse/vec/vec.js\");\n// -----------------------------------------------------------------------------\n// TYPE GUARDS\n// -----------------------------------------------------------------------------\nfunction isImageFontConfigData(value) {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n if (!('textureAtlasSize' in value) ||\n typeof value.textureAtlasSize !== 'object' ||\n value.textureAtlasSize === null) {\n return false;\n }\n if (!('x' in value.textureAtlasSize) ||\n typeof value.textureAtlasSize.x !== 'number') {\n return false;\n }\n if (!('y' in value.textureAtlasSize) ||\n typeof value.textureAtlasSize.y !== 'number') {\n return false;\n }\n if ('offset' in value) {\n if (typeof value.offset !== 'object' || value.offset === null) {\n return false;\n }\n if (!('x' in value.offset) || typeof value.offset.x !== 'number') {\n return false;\n }\n if (!('y' in value.offset) || typeof value.offset.y !== 'number') {\n return false;\n }\n }\n if ('scale' in value && typeof value.scale !== 'number') {\n return false;\n }\n if ('defaultCharacterConfig' in value) {\n if (typeof value.defaultCharacterConfig !== 'object' ||\n value.defaultCharacterConfig === null) {\n return false;\n }\n if (!isImageFontCharacterConfigData(value.defaultCharacterConfig, false)) {\n return false;\n }\n }\n if (!('characters' in value) ||\n typeof value.characters !== 'object' ||\n value.characters === null) {\n return false;\n }\n for (const [char, config] of Object.entries(value.characters)) {\n if (typeof char !== 'string' || char.length !== 1) {\n return false; // Character keys must be single characters\n }\n if (!isImageFontCharacterConfigData(config)) {\n return false;\n }\n }\n return true;\n}\nexports.isImageFontConfigData = isImageFontConfigData;\nfunction isImageFontCharacterConfigData(value, includeTextureAtlasPosition = true) {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n if (includeTextureAtlasPosition) {\n if (!('textureAtlasPosition' in value) ||\n typeof value.textureAtlasPosition !== 'object' ||\n value.textureAtlasPosition === null) {\n return false;\n }\n if (!('x' in value.textureAtlasPosition) ||\n typeof value.textureAtlasPosition.x !== 'number') {\n return false;\n }\n if (!('y' in value.textureAtlasPosition) ||\n typeof value.textureAtlasPosition.y !== 'number') {\n return false;\n }\n }\n if ('offset' in value) {\n if (typeof value.offset !== 'object' || value.offset === null) {\n return false;\n }\n if (!('x' in value.offset) || typeof value.offset.x !== 'number') {\n return false;\n }\n if (!('y' in value.offset) || typeof value.offset.y !== 'number') {\n return false;\n }\n }\n if ('width' in value && typeof value.width !== 'number') {\n return false;\n }\n if ('height' in value && typeof value.height !== 'number') {\n return false;\n }\n return true;\n}\n// -----------------------------------------------------------------------------\n// IMAGE FONT CLASS\n// -----------------------------------------------------------------------------\nclass ImageFont {\n constructor(textures, config) {\n this.textures = textures;\n this.config = {\n ...ImageFont.DEFAULT_CONFIG,\n ...config,\n defaultCharacterConfig: {\n ...ImageFont.DEFAULT_CONFIG.defaultCharacterConfig,\n ...config.defaultCharacterConfig,\n },\n characters: {\n ...ImageFont.DEFAULT_CONFIG.characters,\n ...config.characters,\n },\n };\n }\n /**\n * Calculate the width of a single character when rendered with this font\n */\n measureCharacterWidth(character, options) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;\n const characterConfig = this.config.characters[character];\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n const texture = this.textures[character];\n let width = 0;\n if (options === null || options === void 0 ? void 0 : options.monospace) {\n if ((options === null || options === void 0 ? void 0 : options.kerning) !== undefined) {\n width = options.kerning;\n }\n else {\n width =\n (_e = (_d = (_c = this.config.defaultCharacterConfig) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : texture === null || texture === void 0 ? void 0 : texture.width) !== null && _e !== void 0 ? _e : 0;\n }\n }\n else {\n width =\n ((_j = (_h = (_f = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.width) !== null && _f !== void 0 ? _f : (_g = this.config.defaultCharacterConfig) === null || _g === void 0 ? void 0 : _g.width) !== null && _h !== void 0 ? _h : texture === null || texture === void 0 ? void 0 : texture.width) !== null && _j !== void 0 ? _j : 0) * ((_k = options === null || options === void 0 ? void 0 : options.kerning) !== null && _k !== void 0 ? _k : 1);\n }\n return width * actualScale;\n }\n /**\n * Calculate the height of a single character when rendered with this font\n */\n measureCharacterHeight(character, options) {\n var _a, _b, _c, _d, _e;\n const characterConfig = this.config.characters[character];\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n return (((_e = (_c = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.height) !== null && _c !== void 0 ? _c : (_d = this.config.defaultCharacterConfig) === null || _d === void 0 ? void 0 : _d.height) !== null && _e !== void 0 ? _e : 0) * actualScale);\n }\n /**\n * Get the width of a string of text when rendered with this font\n */\n measureText(text, options) {\n // When calculating the total width, ignore kerning for the last character\n const lastCharacterWidth = this.measureCharacterWidth(text[text.length - 1], {\n scale: options === null || options === void 0 ? void 0 : options.scale,\n });\n const width = text\n .split('')\n .slice(0, text.length - 1)\n .reduce((width, character) => width + this.measureCharacterWidth(character, options), 0) + lastCharacterWidth;\n const height = Math.max(...text\n .split('')\n .map(character => this.measureCharacterHeight(character, options)));\n return (0, vec_1.vec2)(width, height);\n }\n /**\n * Draw text on a canvas using this font\n */\n drawText(context, text, x, y, options) {\n var _a, _b, _c, _d, _e, _f;\n const size = this.measureText(text, options);\n let currentX = x;\n switch (options === null || options === void 0 ? void 0 : options.align) {\n case 'center':\n currentX -= size.x / 2;\n break;\n case 'right':\n currentX -= size.x;\n break;\n }\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n let actualY = y;\n switch (options === null || options === void 0 ? void 0 : options.baseLine) {\n case 'middle':\n actualY = y - size.y / 2;\n break;\n case 'bottom':\n actualY = y - size.y;\n break;\n }\n for (const character of text) {\n const characterWidth = this.measureCharacterWidth(character, options);\n const texture = this.textures[character];\n if (!texture) {\n currentX += characterWidth;\n continue;\n }\n const characterConfig = this.config.characters[character];\n const offset = vec_1.vec2.add((_c = this.config.offset) !== null && _c !== void 0 ? _c : (0, vec_1.vec2)(), (_f = (_d = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.offset) !== null && _d !== void 0 ? _d : (_e = this.config.defaultCharacterConfig) === null || _e === void 0 ? void 0 : _e.offset) !== null && _f !== void 0 ? _f : (0, vec_1.vec2)());\n context.drawImage(texture, currentX - offset.x * actualScale, actualY - offset.y * actualScale, texture.width * actualScale, texture.height * actualScale);\n currentX += characterWidth;\n }\n }\n}\nexports.ImageFont = ImageFont;\nImageFont.DEFAULT_CONFIG = {\n offset: (0, vec_1.vec2)(),\n scale: 1,\n defaultCharacterConfig: {\n offset: (0, vec_1.vec2)(),\n },\n characters: {},\n};\n// -----------------------------------------------------------------------------\n// CONTENT PROCESSOR\n// -----------------------------------------------------------------------------\n/**\n * Content Manager Processor for loading image fonts\n *\n * @see https://www.npmjs.com/package/@basementuniverse/content-manager\n */\nasync function imageFontContentProcessor(content, data, imageName) {\n var _a;\n if (!isImageFontConfigData(data.content)) {\n throw new Error('Invalid image font config');\n }\n const image = (_a = content[imageName]) === null || _a === void 0 ? void 0 : _a.content;\n if (!image) {\n throw new Error(`Image '${imageName}' not found`);\n }\n // Create the texture atlas\n const atlas = (0, texture_atlas_1.textureAtlas)(image, {\n relative: true,\n width: data.content.textureAtlasSize.x,\n height: data.content.textureAtlasSize.y,\n regions: Object.fromEntries(Object.entries(data.content.characters).map(([char, config]) => [\n char,\n {\n x: config.textureAtlasPosition.x,\n y: config.textureAtlasPosition.y,\n },\n ])),\n });\n // Create the image font\n const font = new ImageFont(atlas, data.content);\n // Store the font in the content manager\n content[data.name] = {\n name: data.name,\n type: 'json',\n content: font,\n status: 'processed',\n };\n}\nexports.imageFontContentProcessor = imageFontContentProcessor;\n\n\n//# sourceURL=webpack://@basementuniverse/image-font/./index.ts?\n}");
|
|
29
|
+
eval("{\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.imageFontContentProcessor = exports.ImageFont = exports.isImageFontConfigData = void 0;\nconst texture_atlas_1 = __webpack_require__(/*! @basementuniverse/texture-atlas */ \"./node_modules/@basementuniverse/texture-atlas/build/index.js\");\nconst vec_1 = __webpack_require__(/*! @basementuniverse/vec */ \"./node_modules/@basementuniverse/vec/vec.js\");\n// -----------------------------------------------------------------------------\n// TYPE GUARDS\n// -----------------------------------------------------------------------------\nfunction isImageFontConfigData(value) {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n if (!('textureAtlasSize' in value) ||\n typeof value.textureAtlasSize !== 'object' ||\n value.textureAtlasSize === null) {\n return false;\n }\n if (!('x' in value.textureAtlasSize) ||\n typeof value.textureAtlasSize.x !== 'number') {\n return false;\n }\n if (!('y' in value.textureAtlasSize) ||\n typeof value.textureAtlasSize.y !== 'number') {\n return false;\n }\n if ('offset' in value) {\n if (typeof value.offset !== 'object' || value.offset === null) {\n return false;\n }\n if (!('x' in value.offset) || typeof value.offset.x !== 'number') {\n return false;\n }\n if (!('y' in value.offset) || typeof value.offset.y !== 'number') {\n return false;\n }\n }\n if ('scale' in value && typeof value.scale !== 'number') {\n return false;\n }\n if ('defaultCharacterConfig' in value) {\n if (typeof value.defaultCharacterConfig !== 'object' ||\n value.defaultCharacterConfig === null) {\n return false;\n }\n if (!isImageFontCharacterConfigData(value.defaultCharacterConfig, false)) {\n return false;\n }\n }\n if (!('characters' in value) ||\n typeof value.characters !== 'object' ||\n value.characters === null) {\n return false;\n }\n for (const [char, config] of Object.entries(value.characters)) {\n if (typeof char !== 'string' || char.length !== 1) {\n return false; // Character keys must be single characters\n }\n if (!isImageFontCharacterConfigData(config)) {\n return false;\n }\n }\n return true;\n}\nexports.isImageFontConfigData = isImageFontConfigData;\nfunction isImageFontCharacterConfigData(value, includeTextureAtlasPosition = true) {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n if (includeTextureAtlasPosition) {\n if (!('textureAtlasPosition' in value) ||\n typeof value.textureAtlasPosition !== 'object' ||\n value.textureAtlasPosition === null) {\n return false;\n }\n if (!('x' in value.textureAtlasPosition) ||\n typeof value.textureAtlasPosition.x !== 'number') {\n return false;\n }\n if (!('y' in value.textureAtlasPosition) ||\n typeof value.textureAtlasPosition.y !== 'number') {\n return false;\n }\n }\n if ('offset' in value) {\n if (typeof value.offset !== 'object' || value.offset === null) {\n return false;\n }\n if (!('x' in value.offset) || typeof value.offset.x !== 'number') {\n return false;\n }\n if (!('y' in value.offset) || typeof value.offset.y !== 'number') {\n return false;\n }\n }\n if ('width' in value && typeof value.width !== 'number') {\n return false;\n }\n if ('height' in value && typeof value.height !== 'number') {\n return false;\n }\n return true;\n}\n// -----------------------------------------------------------------------------\n// IMAGE FONT CLASS\n// -----------------------------------------------------------------------------\nclass ImageFont {\n constructor(textures, config) {\n this.colorCache = new Map();\n this.textures = textures;\n this.config = {\n ...ImageFont.DEFAULT_CONFIG,\n ...config,\n defaultCharacterConfig: {\n ...ImageFont.DEFAULT_CONFIG.defaultCharacterConfig,\n ...config.defaultCharacterConfig,\n },\n characters: {\n ...ImageFont.DEFAULT_CONFIG.characters,\n ...config.characters,\n },\n };\n }\n getCacheKey(character, color, mode) {\n return `${character}:${color}:${mode}`;\n }\n /**\n * Determine the coloring mode to use, falling back to 'multiply' if needed\n */\n getColoringMode(options) {\n var _a;\n const mode = (_a = options.coloringMode) !== null && _a !== void 0 ? _a : 'multiply';\n // If mode is 'custom' but no coloring function is provided, we fall back\n // to 'multiply'\n if (mode === 'custom' && !options.coloringFunction) {\n return 'multiply';\n }\n return mode;\n }\n /**\n * Create a colored version of a texture using the specified coloring mode\n */\n createColoredTexture(texture, color, mode, coloringFunction) {\n const canvas = document.createElement('canvas');\n canvas.width = texture.width;\n canvas.height = texture.height;\n const context = canvas.getContext('2d');\n // Draw the original texture\n context.drawImage(texture, 0, 0);\n // Apply coloring based on mode\n switch (mode) {\n case 'multiply':\n case 'hue':\n // Use a second canvas to preserve transparency for multiply/hue modes\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = texture.width;\n tempCanvas.height = texture.height;\n const tempContext = tempCanvas.getContext('2d');\n // Draw the character on the temp canvas\n tempContext.drawImage(texture, 0, 0);\n // Apply the color effect (multiply or hue)\n tempContext.globalCompositeOperation = mode;\n tempContext.fillStyle = color;\n tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height);\n // Clear the main canvas and draw the colored result using source-atop\n // to preserve the original alpha channel\n context.clearRect(0, 0, canvas.width, canvas.height);\n context.drawImage(texture, 0, 0);\n context.globalCompositeOperation = 'source-atop';\n context.drawImage(tempCanvas, 0, 0);\n break;\n case 'overlay':\n context.globalCompositeOperation = 'source-atop';\n context.globalAlpha = 0.5;\n context.fillStyle = color;\n context.fillRect(0, 0, canvas.width, canvas.height);\n context.globalAlpha = 1.0;\n break;\n case 'custom':\n if (coloringFunction) {\n coloringFunction(context, texture, color);\n }\n break;\n }\n // Reset composite operation\n context.globalCompositeOperation = 'source-over';\n return canvas;\n }\n /**\n * Calculate the width of a single character when rendered with this font\n */\n measureCharacterWidth(character, options) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;\n const characterConfig = this.config.characters[character];\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n const texture = this.textures[character];\n let width = 0;\n if (options === null || options === void 0 ? void 0 : options.monospace) {\n if ((options === null || options === void 0 ? void 0 : options.kerning) !== undefined) {\n width = options.kerning;\n }\n else {\n width =\n (_e = (_d = (_c = this.config.defaultCharacterConfig) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : texture === null || texture === void 0 ? void 0 : texture.width) !== null && _e !== void 0 ? _e : 0;\n }\n }\n else {\n width =\n ((_j = (_h = (_f = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.width) !== null && _f !== void 0 ? _f : (_g = this.config.defaultCharacterConfig) === null || _g === void 0 ? void 0 : _g.width) !== null && _h !== void 0 ? _h : texture === null || texture === void 0 ? void 0 : texture.width) !== null && _j !== void 0 ? _j : 0) * ((_k = options === null || options === void 0 ? void 0 : options.kerning) !== null && _k !== void 0 ? _k : 1);\n }\n return width * actualScale;\n }\n /**\n * Calculate the height of a single character when rendered with this font\n */\n measureCharacterHeight(character, options) {\n var _a, _b, _c, _d, _e;\n const characterConfig = this.config.characters[character];\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n return (((_e = (_c = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.height) !== null && _c !== void 0 ? _c : (_d = this.config.defaultCharacterConfig) === null || _d === void 0 ? void 0 : _d.height) !== null && _e !== void 0 ? _e : 0) * actualScale);\n }\n /**\n * Get the width of a string of text when rendered with this font\n */\n measureText(text, options) {\n // When calculating the total width, ignore kerning for the last character\n const lastCharacterWidth = this.measureCharacterWidth(text[text.length - 1], {\n scale: options === null || options === void 0 ? void 0 : options.scale,\n });\n const width = text\n .split('')\n .slice(0, text.length - 1)\n .reduce((width, character) => width + this.measureCharacterWidth(character, options), 0) + lastCharacterWidth;\n const height = Math.max(...text\n .split('')\n .map(character => this.measureCharacterHeight(character, options)));\n return (0, vec_1.vec2)(width, height);\n }\n /**\n * Draw text on a canvas using this font\n */\n drawText(context, text, x, y, options) {\n var _a, _b, _c, _d, _e, _f;\n const size = this.measureText(text, options);\n let currentX = x;\n switch (options === null || options === void 0 ? void 0 : options.align) {\n case 'center':\n currentX -= size.x / 2;\n break;\n case 'right':\n currentX -= size.x;\n break;\n }\n const actualScale = ((_a = options === null || options === void 0 ? void 0 : options.scale) !== null && _a !== void 0 ? _a : 1) * ((_b = this.config.scale) !== null && _b !== void 0 ? _b : 1);\n let actualY = y;\n switch (options === null || options === void 0 ? void 0 : options.baseLine) {\n case 'middle':\n actualY = y - size.y / 2;\n break;\n case 'bottom':\n actualY = y - size.y;\n break;\n }\n for (const character of text) {\n const characterWidth = this.measureCharacterWidth(character, options);\n const texture = this.textures[character];\n if (!texture) {\n currentX += characterWidth;\n continue;\n }\n const characterConfig = this.config.characters[character];\n const offset = vec_1.vec2.add((_c = this.config.offset) !== null && _c !== void 0 ? _c : (0, vec_1.vec2)(), (_f = (_d = characterConfig === null || characterConfig === void 0 ? void 0 : characterConfig.offset) !== null && _d !== void 0 ? _d : (_e = this.config.defaultCharacterConfig) === null || _e === void 0 ? void 0 : _e.offset) !== null && _f !== void 0 ? _f : (0, vec_1.vec2)());\n let finalTexture = texture;\n // Apply coloring if color is provided\n if (options === null || options === void 0 ? void 0 : options.color) {\n const coloringMode = this.getColoringMode(options);\n const cacheKey = this.getCacheKey(character, options.color, coloringMode);\n // Check if colored texture is already cached\n if (this.colorCache.has(cacheKey)) {\n finalTexture = this.colorCache.get(cacheKey);\n }\n else {\n // Create colored texture and cache it\n finalTexture = this.createColoredTexture(texture, options.color, coloringMode, options.coloringFunction);\n // Manage cache size\n if (this.colorCache.size >= ImageFont.MAX_COLOR_CACHE_SIZE) {\n // Remove oldest entry (first entry in the Map)\n const firstKey = this.colorCache.keys().next().value;\n if (firstKey !== undefined) {\n this.colorCache.delete(firstKey);\n }\n }\n this.colorCache.set(cacheKey, finalTexture);\n }\n }\n context.drawImage(finalTexture, currentX - offset.x * actualScale, actualY - offset.y * actualScale, finalTexture.width * actualScale, finalTexture.height * actualScale);\n currentX += characterWidth;\n }\n }\n}\nexports.ImageFont = ImageFont;\nImageFont.MAX_COLOR_CACHE_SIZE = 1000;\nImageFont.DEFAULT_CONFIG = {\n offset: (0, vec_1.vec2)(),\n scale: 1,\n defaultCharacterConfig: {\n offset: (0, vec_1.vec2)(),\n },\n characters: {},\n};\n// -----------------------------------------------------------------------------\n// CONTENT PROCESSOR\n// -----------------------------------------------------------------------------\n/**\n * Content Manager Processor for loading image fonts\n *\n * @see https://www.npmjs.com/package/@basementuniverse/content-manager\n */\nasync function imageFontContentProcessor(content, data, imageName) {\n var _a;\n if (!isImageFontConfigData(data.content)) {\n throw new Error('Invalid image font config');\n }\n const image = (_a = content[imageName]) === null || _a === void 0 ? void 0 : _a.content;\n if (!image) {\n throw new Error(`Image '${imageName}' not found`);\n }\n // Create the texture atlas\n const atlas = (0, texture_atlas_1.textureAtlas)(image, {\n relative: true,\n width: data.content.textureAtlasSize.x,\n height: data.content.textureAtlasSize.y,\n regions: Object.fromEntries(Object.entries(data.content.characters).map(([char, config]) => [\n char,\n {\n x: config.textureAtlasPosition.x,\n y: config.textureAtlasPosition.y,\n },\n ])),\n });\n // Create the image font\n const font = new ImageFont(atlas, data.content);\n // Store the font in the content manager\n content[data.name] = {\n name: data.name,\n type: 'json',\n content: font,\n status: 'processed',\n };\n}\nexports.imageFontContentProcessor = imageFontContentProcessor;\n\n\n//# sourceURL=webpack://@basementuniverse/image-font/./index.ts?\n}");
|
|
30
30
|
|
|
31
31
|
/***/ }),
|
|
32
32
|
|
package/package.json
CHANGED