@mirage-engine/painter 0.4.0 → 1.0.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.
@@ -0,0 +1,350 @@
1
+ import * as THREE from "three";
2
+ import { BoxStyles, ShaderHooks } from "../types";
3
+ import {
4
+ parsePixelValue,
5
+ parseColor,
6
+ parseLinearGradient,
7
+ } from "../tools/parser";
8
+ import { BoxShader, BoxChunk } from "../shaders/index";
9
+
10
+ export function createBoxMaterial(
11
+ styles: BoxStyles,
12
+ width: number,
13
+ height: number,
14
+ texture: THREE.Texture | null = null,
15
+ hooks?: ShaderHooks,
16
+ ): THREE.ShaderMaterial {
17
+ const hasTexture = texture !== null || !!styles.imageSrc;
18
+
19
+ let customUniformsCode = "";
20
+ const customUniforms: any = {};
21
+
22
+ if (hooks?.uniforms) {
23
+ for (const [key, value] of Object.entries(hooks.uniforms)) {
24
+ if (typeof value === "number") {
25
+ customUniformsCode += `uniform float ${key};\n`;
26
+ customUniforms[key] = { value };
27
+ } else if (Array.isArray(value)) {
28
+ if (value.length === 2) {
29
+ customUniformsCode += `uniform vec2 ${key};\n`;
30
+ customUniforms[key] = { value: new THREE.Vector2(...value) };
31
+ } else if (value.length === 3) {
32
+ customUniformsCode += `uniform vec3 ${key};\n`;
33
+ customUniforms[key] = { value: new THREE.Vector3(...value) };
34
+ } else if (value.length === 4) {
35
+ customUniformsCode += `uniform vec4 ${key};\n`;
36
+ customUniforms[key] = { value: new THREE.Vector4(...value) };
37
+ }
38
+ } else {
39
+ customUniformsCode += `uniform float ${key};\n`;
40
+ customUniforms[key] = { value };
41
+ }
42
+ }
43
+ }
44
+
45
+ const declChunk = (hasTexture ? BoxChunk.declChunk : "") + "\n" + customUniformsCode;
46
+ const baseUvCode = styles.isTraveler ? BoxChunk.uvChunk : "vec2 resultUv = vUv * uTextureRepeat + uTextureOffset;\n";
47
+ const uvChunk = hasTexture ? baseUvCode + (hooks?.uvModifier || "") : "";
48
+ const baseColorChunk = hasTexture ? BoxChunk.baseColorChunk : "";
49
+ const colorModChunk = hooks?.colorModifier || "";
50
+
51
+ const fragmentShader = BoxShader.fragmentShader
52
+ .replace("#INJECT_DECLARATIONS", declChunk)
53
+ .replace("#INJECT_UV_MODIFIER", uvChunk)
54
+ .replace("#INJECT_BASE_COLOR", baseColorChunk)
55
+ .replace("#INJECT_COLOR_MODIFIER", colorModChunk);
56
+
57
+ // uniform setting
58
+ const parsedBg = parseColor(styles.backgroundColor);
59
+ const parsedBorder = parseColor(styles.borderColor);
60
+ const uniforms = {
61
+ uSize: { value: new THREE.Vector2(width, height) },
62
+ uBgColor: {
63
+ value: new THREE.Vector4(
64
+ parsedBg.color.r,
65
+ parsedBg.color.g,
66
+ parsedBg.color.b,
67
+ parsedBg.alpha,
68
+ ),
69
+ },
70
+ uBorderColor: {
71
+ value: new THREE.Vector4(
72
+ parsedBorder.color.r,
73
+ parsedBorder.color.g,
74
+ parsedBorder.color.b,
75
+ parsedBorder.alpha,
76
+ ),
77
+ },
78
+ uBorderRadius: { value: new THREE.Vector4(0, 0, 0, 0) },
79
+ uBorderWidth: { value: parsePixelValue(styles.borderWidth) },
80
+ uOpacity: { value: styles.opacity ?? 1.0 },
81
+ uTexture: { value: null as THREE.Texture | null },
82
+ uTextureRepeat: { value: new THREE.Vector2(1.0, 1.0) },
83
+ uTextureOffset: { value: new THREE.Vector2(0.0, 0.0) },
84
+
85
+ uGradientCount: { value: 0 },
86
+ uGradientAngle: { value: 0 },
87
+ uGradientColors: {
88
+ value: Array.from({ length: 8 }, () => new THREE.Vector4(0, 0, 0, 0)),
89
+ },
90
+ uGradientStops: { value: new Float32Array(8) },
91
+ };
92
+ // border radius value initialize
93
+ setBorderRadius(uniforms.uBorderRadius.value, styles.borderRadius);
94
+
95
+ if (hasTexture) {
96
+ uniforms.uTexture.value = texture;
97
+ }
98
+
99
+ const material = new THREE.ShaderMaterial({
100
+ uniforms: { ...uniforms, ...customUniforms },
101
+ vertexShader: BoxShader.vertexShader,
102
+ fragmentShader: fragmentShader,
103
+ transparent: true,
104
+ side: THREE.FrontSide, // for better performance
105
+ });
106
+
107
+ // Initial gradient setup
108
+ if (styles.backgroundImage) {
109
+ setBoxUniforms(material, { backgroundImage: styles.backgroundImage });
110
+ }
111
+
112
+ return material;
113
+ }
114
+
115
+ export function updateBoxMaterial(
116
+ material: THREE.ShaderMaterial,
117
+ styles: BoxStyles,
118
+ width: number,
119
+ height: number,
120
+ texture?: THREE.Texture | null,
121
+ ) {
122
+ const parsedBorderWidth = parsePixelValue(styles.borderWidth);
123
+ setBoxUniforms(material, {
124
+ width,
125
+ height,
126
+ borderRadius: styles.borderRadius,
127
+ borderWidth: parsedBorderWidth,
128
+ backgroundColor: styles.backgroundColor,
129
+ borderColor: styles.borderColor,
130
+ opacity: styles.opacity,
131
+ texture,
132
+ backgroundImage: styles.backgroundImage,
133
+ });
134
+ }
135
+
136
+ export interface BoxUniformValues {
137
+ width?: number;
138
+ height?: number;
139
+ borderRadius?: string | number | [number, number, number, number];
140
+ borderWidth?: number;
141
+ backgroundColor?: THREE.Color | [number, number, number] | string;
142
+ borderColor?: THREE.Color | [number, number, number] | string;
143
+ opacity?: number;
144
+ bgOpacity?: number;
145
+ borderOpacity?: number;
146
+ texture?: THREE.Texture | null;
147
+ backgroundImage?: string;
148
+ [key: string]: any;
149
+ }
150
+
151
+ export function setBoxUniforms(
152
+ material: THREE.ShaderMaterial,
153
+ values: BoxUniformValues,
154
+ ) {
155
+ if (values.width !== undefined && values.height !== undefined) {
156
+ material.uniforms.uSize.value.set(values.width, values.height);
157
+ }
158
+ if (values.borderRadius !== undefined) {
159
+ setBorderRadius(material.uniforms.uBorderRadius.value, values.borderRadius);
160
+ }
161
+ if (values.borderWidth !== undefined) {
162
+ material.uniforms.uBorderWidth.value = values.borderWidth;
163
+ }
164
+
165
+ if (values.backgroundColor !== undefined) {
166
+ if (Array.isArray(values.backgroundColor)) {
167
+ const currentAlpha = material.uniforms.uBgColor.value.w;
168
+ material.uniforms.uBgColor.value.set(
169
+ values.backgroundColor[0],
170
+ values.backgroundColor[1],
171
+ values.backgroundColor[2],
172
+ currentAlpha,
173
+ );
174
+ } else if (typeof values.backgroundColor === "string") {
175
+ const parsed = parseColor(values.backgroundColor);
176
+ material.uniforms.uBgColor.value.set(
177
+ parsed.color.r,
178
+ parsed.color.g,
179
+ parsed.color.b,
180
+ parsed.alpha,
181
+ );
182
+ } else {
183
+ const currentAlpha = material.uniforms.uBgColor.value.w;
184
+ material.uniforms.uBgColor.value.set(
185
+ values.backgroundColor.r,
186
+ values.backgroundColor.g,
187
+ values.backgroundColor.b,
188
+ currentAlpha,
189
+ );
190
+ }
191
+ }
192
+
193
+ if (values.borderColor !== undefined) {
194
+ if (Array.isArray(values.borderColor)) {
195
+ const currentAlpha = material.uniforms.uBorderColor.value.w;
196
+ material.uniforms.uBorderColor.value.set(
197
+ values.borderColor[0],
198
+ values.borderColor[1],
199
+ values.borderColor[2],
200
+ currentAlpha,
201
+ );
202
+ } else if (typeof values.borderColor === "string") {
203
+ const parsed = parseColor(values.borderColor);
204
+ material.uniforms.uBorderColor.value.set(
205
+ parsed.color.r,
206
+ parsed.color.g,
207
+ parsed.color.b,
208
+ parsed.alpha,
209
+ );
210
+ } else {
211
+ const currentAlpha = material.uniforms.uBorderColor.value.w;
212
+ material.uniforms.uBorderColor.value.set(
213
+ values.borderColor.r,
214
+ values.borderColor.g,
215
+ values.borderColor.b,
216
+ currentAlpha,
217
+ );
218
+ }
219
+ }
220
+
221
+ if (values.opacity !== undefined) {
222
+ material.uniforms.uOpacity.value = values.opacity;
223
+ }
224
+ if (values.bgOpacity !== undefined) {
225
+ material.uniforms.uBgColor.value.w = values.bgOpacity;
226
+ }
227
+ if (values.borderOpacity !== undefined) {
228
+ material.uniforms.uBorderColor.value.w = values.borderOpacity;
229
+ }
230
+ if (material.uniforms.uTexture && values.texture !== undefined) {
231
+ material.uniforms.uTexture.value = values.texture;
232
+ }
233
+
234
+ const currentTex = values.texture !== undefined ? values.texture : material.uniforms.uTexture?.value;
235
+ if (currentTex && (currentTex.image instanceof ImageBitmap || currentTex.image instanceof HTMLImageElement || currentTex.image instanceof HTMLCanvasElement)) {
236
+ const imgWidth = currentTex.image.width;
237
+ const imgHeight = currentTex.image.height;
238
+ const domWidth = values.width ?? material.uniforms.uSize.value.x;
239
+ const domHeight = values.height ?? material.uniforms.uSize.value.y;
240
+
241
+ if (imgWidth && imgHeight && domWidth && domHeight) {
242
+ const imageAspect = imgWidth / imgHeight;
243
+ const domAspect = domWidth / domHeight;
244
+
245
+ if (imageAspect > domAspect) {
246
+ const repeatX = domAspect / imageAspect;
247
+ material.uniforms.uTextureRepeat.value.set(repeatX, 1);
248
+ material.uniforms.uTextureOffset.value.set((1 - repeatX) / 2, 0);
249
+ } else {
250
+ const repeatY = imageAspect / domAspect;
251
+ material.uniforms.uTextureRepeat.value.set(1, repeatY);
252
+ material.uniforms.uTextureOffset.value.set(0, (1 - repeatY) / 2);
253
+ }
254
+ }
255
+ } else if (material.uniforms.uTextureRepeat) {
256
+ material.uniforms.uTextureRepeat.value.set(1, 1);
257
+ material.uniforms.uTextureOffset.value.set(0, 0);
258
+ }
259
+
260
+ if (values.backgroundImage !== undefined) {
261
+ const gradient = parseLinearGradient(values.backgroundImage);
262
+ if (gradient) {
263
+ material.uniforms.uGradientCount.value = gradient.stops.length;
264
+ material.uniforms.uGradientAngle.value = gradient.angle;
265
+ for (let i = 0; i < 8; i++) {
266
+ if (i < gradient.stops.length) {
267
+ const s = gradient.stops[i];
268
+ material.uniforms.uGradientColors.value[i].set(
269
+ s.color.r,
270
+ s.color.g,
271
+ s.color.b,
272
+ s.alpha,
273
+ );
274
+ // console.log(s.color.r, s.color.g, s.color.b);
275
+ material.uniforms.uGradientStops.value[i] = s.stop;
276
+ } else {
277
+ material.uniforms.uGradientColors.value[i].set(0, 0, 0, 0);
278
+ material.uniforms.uGradientStops.value[i] = 1.0;
279
+ }
280
+ }
281
+ } else {
282
+ material.uniforms.uGradientCount.value = 0;
283
+ }
284
+ }
285
+
286
+ // Update dynamic custom uniforms
287
+ for (const key of Object.keys(values)) {
288
+ if (
289
+ key !== "width" &&
290
+ key !== "height" &&
291
+ key !== "borderRadius" &&
292
+ key !== "borderWidth" &&
293
+ key !== "backgroundColor" &&
294
+ key !== "borderColor" &&
295
+ key !== "opacity" &&
296
+ key !== "bgOpacity" &&
297
+ key !== "borderOpacity" &&
298
+ key !== "texture" &&
299
+ key !== "backgroundImage"
300
+ ) {
301
+ if (material.uniforms[key] !== undefined) {
302
+ if (
303
+ material.uniforms[key].value !== undefined &&
304
+ material.uniforms[key].value !== null &&
305
+ typeof material.uniforms[key].value.set === "function"
306
+ ) {
307
+ if (Array.isArray(values[key])) {
308
+ material.uniforms[key].value.set(...values[key]);
309
+ } else if (values[key] !== undefined) {
310
+ if (values[key].copy) {
311
+ material.uniforms[key].value.copy(values[key]);
312
+ } else {
313
+ material.uniforms[key].value = values[key];
314
+ }
315
+ }
316
+ } else {
317
+ material.uniforms[key].value = values[key];
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ function setBorderRadius(
325
+ target: THREE.Vector4,
326
+ radius?: string | number | [number, number, number, number],
327
+ ) {
328
+ if (radius === undefined || radius === null) {
329
+ target.set(0, 0, 0, 0);
330
+ return;
331
+ }
332
+
333
+ if (typeof radius === "number") {
334
+ target.set(radius, radius, radius, radius);
335
+ return;
336
+ }
337
+
338
+ if (Array.isArray(radius)) {
339
+ target.set(radius[0], radius[1], radius[2], radius[3]);
340
+ return;
341
+ }
342
+
343
+ const arr = radius.split("/")[0].trim().split(/\s+/);
344
+ const tl = parsePixelValue(arr[0]);
345
+ const tr = parsePixelValue(arr[1] ?? arr[0]);
346
+ const br = parsePixelValue(arr[2] ?? arr[0]);
347
+ const bl = parsePixelValue(arr[3] ?? arr[1] ?? arr[0]);
348
+
349
+ target.set(tl, tr, br, bl);
350
+ }
package/src/Painter.ts CHANGED
@@ -1,36 +1,40 @@
1
1
  import * as THREE from "three";
2
- import { BoxStyles, TextStyles, ShaderHooks} from "./types";
3
- import { createBoxMaterial, updateBoxMaterial } from "./BoxGenerator";
4
- import { createTextTexture } from "./TextGenerator";
2
+ import { BoxStyles, TextStyles, ShaderHooks } from "./types";
3
+ import {
4
+ createBoxMaterial,
5
+ updateBoxMaterial,
6
+ setBoxUniforms,
7
+ BoxUniformValues,
8
+ } from "./Box/BoxGenerator";
9
+ import { TextGenerator } from "./Text/TextGenerator";
5
10
 
6
11
  export const Painter = {
7
12
  create(
8
13
  type: "BOX" | "TEXT",
9
- styles: any,
14
+ styles: BoxStyles | TextStyles,
10
15
  content: string,
11
16
  width: number,
12
17
  height: number,
13
18
  quality: number = 2,
14
19
  texture: THREE.Texture | null = null,
15
- shaderHooks?: ShaderHooks
20
+ shaderHooks?: ShaderHooks,
16
21
  ): THREE.Material {
17
22
  if (type === "BOX") {
18
- return createBoxMaterial(styles as BoxStyles, width, height, texture, shaderHooks);
23
+ return createBoxMaterial(
24
+ styles as BoxStyles,
25
+ width,
26
+ height,
27
+ texture,
28
+ shaderHooks,
29
+ );
19
30
  } else if (type === "TEXT") {
20
- const texture = createTextTexture(
31
+ return new TextGenerator(
21
32
  content || "",
22
33
  styles as TextStyles,
23
34
  width,
24
35
  height,
25
36
  quality,
26
37
  );
27
-
28
- return new THREE.MeshBasicMaterial({
29
- map: texture,
30
- transparent: true,
31
- side: THREE.FrontSide,
32
- color: 0xffffff,
33
- });
34
38
  }
35
39
 
36
40
  return new THREE.MeshBasicMaterial({ visible: false });
@@ -39,12 +43,12 @@ export const Painter = {
39
43
  update(
40
44
  material: THREE.Material,
41
45
  type: "BOX" | "TEXT",
42
- styles: any,
46
+ styles: BoxStyles | TextStyles,
43
47
  content: string,
44
48
  width: number,
45
49
  height: number,
46
50
  quality: number = 2,
47
- texture?: THREE.Texture | null
51
+ texture?: THREE.Texture | null,
48
52
  ) {
49
53
  if (type === "BOX") {
50
54
  updateBoxMaterial(
@@ -52,25 +56,24 @@ export const Painter = {
52
56
  styles as BoxStyles,
53
57
  width,
54
58
  height,
55
- texture
59
+ texture,
56
60
  );
57
61
  } else if (type === "TEXT") {
58
- const basicMat = material as THREE.MeshBasicMaterial;
59
-
60
- if (basicMat.map) {
61
- basicMat.map.dispose();
62
- }
63
-
64
- const newTexture = createTextTexture(
62
+ const textMat = material as TextGenerator;
63
+ textMat.updateText(
65
64
  content || "",
66
65
  styles as TextStyles,
67
66
  width,
68
67
  height,
69
68
  quality,
70
69
  );
71
-
72
- basicMat.map = newTexture;
73
- basicMat.needsUpdate = true;
74
70
  }
75
71
  },
72
+
73
+ forceUpdateUniforms(
74
+ material: THREE.ShaderMaterial,
75
+ values: BoxUniformValues,
76
+ ) {
77
+ setBoxUniforms(material, values);
78
+ },
76
79
  };
@@ -0,0 +1,62 @@
1
+ export class FontMetricsManager {
2
+ private static cache: Map<string, number> = new Map();
3
+
4
+ /**
5
+ * Calculates or retrieves the exact alphabetic baseline (in pixels) for the given font.
6
+ * @param font CSS font shorthand string (e.g., "400 16px Inter")
7
+ * @returns Baseline Y offset in pixels
8
+ */
9
+ static getBaseline(font: string): number {
10
+ if (this.cache.has(font)) {
11
+ return this.cache.get(font)!;
12
+ }
13
+
14
+ const baseline = this.calculateBaselineFromDOM(font);
15
+ this.cache.set(font, baseline);
16
+ return baseline;
17
+ }
18
+
19
+ private static calculateBaselineFromDOM(font: string): number {
20
+ // Return 0 when running in a non‑DOM environment (e.g., server‑side rendering)
21
+ if (typeof document === "undefined") return 0;
22
+
23
+ const container = document.createElement("div");
24
+ const span = document.createElement("span");
25
+ const img = document.createElement("img"); // 1×1 transparent pixel
26
+
27
+ // Hide the helper elements and apply the target font
28
+ container.style.visibility = "hidden";
29
+ container.style.position = "absolute";
30
+ container.style.top = "0px";
31
+ container.style.left = "0px";
32
+ container.style.font = font;
33
+
34
+ // Reset padding/margin/border to isolate pure font metrics
35
+ container.style.margin = "0";
36
+ container.style.padding = "0";
37
+ container.style.border = "none";
38
+ span.style.margin = "0";
39
+ span.style.padding = "0";
40
+ span.style.border = "none";
41
+ span.style.lineHeight = "normal"; // Prevent line‑height interference
42
+
43
+ // Align the 1×1 image to the baseline of the span
44
+ img.width = 1;
45
+ img.height = 1;
46
+ img.style.verticalAlign = "baseline";
47
+
48
+ // Assemble the DOM structure
49
+ span.appendChild(document.createTextNode("Hidden Text"));
50
+ container.appendChild(span);
51
+ container.appendChild(img);
52
+
53
+ document.body.appendChild(container);
54
+
55
+ // Measure the distance from the top of the span to the top of the baseline‑aligned image
56
+ const baseline = img.offsetTop - span.offsetTop;
57
+
58
+ document.body.removeChild(container);
59
+
60
+ return baseline;
61
+ }
62
+ }
@@ -0,0 +1,123 @@
1
+ import * as THREE from "three";
2
+ import { TextStyles } from "../types";
3
+ import { FontMetricsManager } from "./FontMetricsManager";
4
+
5
+ export class TextGenerator extends THREE.MeshBasicMaterial {
6
+ private canvas: HTMLCanvasElement;
7
+ private ctx: CanvasRenderingContext2D;
8
+ private qualityFactor: number;
9
+
10
+ constructor(text: string, styles: TextStyles, rectWidth: number, rectHeight: number, qualityFactor: number = 2) {
11
+ super({
12
+ transparent: true,
13
+ side: THREE.FrontSide,
14
+ color: 0xffffff,
15
+ });
16
+
17
+ this.canvas = document.createElement("canvas");
18
+ this.ctx = this.canvas.getContext("2d")!;
19
+
20
+ if (!this.ctx) {
21
+ throw new Error("[Mirage] Failed to create canvas context");
22
+ }
23
+
24
+ this.qualityFactor = qualityFactor;
25
+
26
+ this.map = new THREE.CanvasTexture(this.canvas);
27
+ this.map.colorSpace = THREE.LinearSRGBColorSpace;
28
+ this.map.minFilter = THREE.LinearFilter;
29
+ this.map.magFilter = THREE.LinearFilter;
30
+
31
+ this.updateText(text, styles, rectWidth, rectHeight);
32
+ }
33
+
34
+ private wrapText(text: string, maxWidth: number): string[] {
35
+ const forcedLines = text.split("\n");
36
+ const resultLines: string[] = [];
37
+
38
+ forcedLines.forEach((line) => {
39
+ const words = line.match(/[^\s\-]+\-?|\-|\s+/g) || [];
40
+
41
+ if (words.length === 0) {
42
+ resultLines.push("");
43
+ return;
44
+ }
45
+
46
+ let currentLine = words[0];
47
+
48
+ for (let i = 1; i < words.length; i++) {
49
+ const word = words[i];
50
+ const width = this.ctx.measureText(currentLine + word).width;
51
+
52
+ if (width <= maxWidth + 2) {
53
+ currentLine += word;
54
+ } else {
55
+ if (currentLine) resultLines.push(currentLine);
56
+ currentLine = word.trimStart();
57
+ }
58
+ }
59
+
60
+ if (currentLine) {
61
+ resultLines.push(currentLine);
62
+ }
63
+ });
64
+
65
+ return resultLines;
66
+ }
67
+
68
+ public updateText(text: string, styles: TextStyles, rectWidth: number, rectHeight: number, qualityFactor?: number) {
69
+ if (qualityFactor !== undefined) {
70
+ this.qualityFactor = qualityFactor;
71
+ }
72
+
73
+ const pixelRatio = window.devicePixelRatio || 1;
74
+ const scale = pixelRatio * this.qualityFactor;
75
+
76
+ const newCanvasWidth = rectWidth * scale;
77
+ const newCanvasHeight = rectHeight * scale;
78
+
79
+ if (this.canvas.width !== newCanvasWidth || this.canvas.height !== newCanvasHeight) {
80
+ this.canvas.width = newCanvasWidth;
81
+ this.canvas.height = newCanvasHeight;
82
+ } else {
83
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
84
+ }
85
+
86
+ // Reset transform and apply scale
87
+ this.ctx.setTransform(scale, 0, 0, scale, 0, 0);
88
+
89
+ this.ctx.font = styles.font;
90
+ this.ctx.fillStyle = styles.color;
91
+ this.ctx.textBaseline = "alphabetic";
92
+ this.ctx.globalAlpha = 1;
93
+
94
+ const lines = this.wrapText(text, rectWidth);
95
+ const lineHeight = styles.lineHeight;
96
+ const baseline = FontMetricsManager.getBaseline(styles.font);
97
+
98
+ lines.forEach((line, index) => {
99
+ const y = index * lineHeight + baseline;
100
+
101
+ let x = 0;
102
+ if (styles.textAlign === "center") {
103
+ x = rectWidth / 2;
104
+ } else if (styles.textAlign === "right") {
105
+ x = rectWidth;
106
+ }
107
+
108
+ this.ctx.textAlign = styles.textAlign as CanvasTextAlign;
109
+ this.ctx.fillText(line, x, y);
110
+ });
111
+
112
+ if (this.map) {
113
+ this.map.needsUpdate = true;
114
+ }
115
+ }
116
+
117
+ public dispose() {
118
+ if (this.map) {
119
+ this.map.dispose();
120
+ }
121
+ super.dispose();
122
+ }
123
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*?raw" {
2
+ const content: string;
3
+ export default content;
4
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from "./TextGenerator";
1
+ export * from "./Text/TextGenerator";
2
2
  export * from "./types";
3
- export * from "./BoxGenerator";
4
- export * from "./Painter";
3
+ export * from "./Box/BoxGenerator";
4
+ export * from "./Painter";
5
+ export * from "./tools/parser";