@basementuniverse/image-font 1.0.2 → 1.1.0

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 CHANGED
@@ -219,6 +219,32 @@ 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
 
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basementuniverse/image-font",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A component for rendering text using image fonts",
5
5
  "author": "Gordon Larrigan <gordonlarrigan@gmail.com> (https://gordonlarrigan.com)",
6
6
  "license": "MIT",