@zseven-w/pen-renderer 0.6.0 → 0.7.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/src/renderer.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { CanvasKit, Surface } from 'canvaskit-wasm'
2
- import type { PenDocument, PenNode } from '@zseven-w/pen-types'
1
+ import type { CanvasKit, Surface } from 'canvaskit-wasm';
2
+ import type { PenDocument, PenNode } from '@zseven-w/pen-types';
3
3
  import {
4
4
  getActivePageChildren,
5
5
  getAllChildren,
@@ -12,23 +12,19 @@ import {
12
12
  FRAME_LABEL_OFFSET_Y,
13
13
  FRAME_LABEL_COLOR,
14
14
  setRootChildrenProvider,
15
- } from '@zseven-w/pen-core'
16
- import { SkiaNodeRenderer } from './node-renderer.js'
17
- import { SpatialIndex } from './spatial-index.js'
15
+ } from '@zseven-w/pen-core';
16
+ import { SkiaNodeRenderer } from './node-renderer.js';
17
+ import { SpatialIndex } from './spatial-index.js';
18
18
  import {
19
19
  flattenToRenderNodes,
20
20
  resolveRefs,
21
21
  premeasureTextHeights,
22
22
  collectReusableIds,
23
23
  collectInstanceIds,
24
- } from './document-flattener.js'
25
- import { parseColor } from './paint-utils.js'
26
- import {
27
- viewportMatrix,
28
- screenToScene,
29
- zoomToPoint as vpZoomToPoint,
30
- } from './viewport.js'
31
- import type { RenderNode, PenRendererOptions, ViewportState } from './types.js'
24
+ } from './document-flattener.js';
25
+ import { parseColor } from './paint-utils.js';
26
+ import { viewportMatrix, screenToScene, zoomToPoint as vpZoomToPoint } from './viewport.js';
27
+ import type { RenderNode, PenRendererOptions, ViewportState } from './types.js';
32
28
 
33
29
  /**
34
30
  * Standalone read-only renderer for OpenPencil (.op) design files.
@@ -46,41 +42,41 @@ import type { RenderNode, PenRendererOptions, ViewportState } from './types.js'
46
42
  * ```
47
43
  */
48
44
  export class PenRenderer {
49
- private ck: CanvasKit
50
- private surface: Surface | null = null
51
- private canvasEl: HTMLCanvasElement | null = null
52
- private nodeRenderer: SkiaNodeRenderer
53
- private spatialIndex = new SpatialIndex()
54
- private renderNodes: RenderNode[] = []
55
- private options: PenRendererOptions
45
+ private ck: CanvasKit;
46
+ private surface: Surface | null = null;
47
+ private canvasEl: HTMLCanvasElement | null = null;
48
+ private nodeRenderer: SkiaNodeRenderer;
49
+ private spatialIndex = new SpatialIndex();
50
+ private renderNodes: RenderNode[] = [];
51
+ private options: PenRendererOptions;
56
52
 
57
53
  // Component/instance IDs for colored frame labels
58
- private reusableIds = new Set<string>()
59
- private instanceIds = new Set<string>()
54
+ private reusableIds = new Set<string>();
55
+ private instanceIds = new Set<string>();
60
56
 
61
57
  // Viewport
62
- private _zoom = 1
63
- private _panX = 0
64
- private _panY = 0
65
- private dirty = true
66
- private animFrameId = 0
58
+ private _zoom = 1;
59
+ private _panX = 0;
60
+ private _panY = 0;
61
+ private dirty = true;
62
+ private animFrameId = 0;
67
63
 
68
64
  // Document
69
- private document: PenDocument | null = null
70
- private activePageId: string | null = null
65
+ private document: PenDocument | null = null;
66
+ private activePageId: string | null = null;
71
67
 
72
68
  constructor(ck: CanvasKit, options?: PenRendererOptions) {
73
- this.ck = ck
74
- this.options = options ?? {}
69
+ this.ck = ck;
70
+ this.options = options ?? {};
75
71
  this.nodeRenderer = new SkiaNodeRenderer(ck, {
76
72
  fontBasePath: this.options.fontBasePath,
77
73
  googleFontsCssUrl: this.options.googleFontsCssUrl,
78
- })
74
+ });
79
75
  if (this.options.iconLookup) {
80
- this.nodeRenderer.setIconLookup(this.options.iconLookup)
76
+ this.nodeRenderer.setIconLookup(this.options.iconLookup);
81
77
  }
82
78
  if (this.options.devicePixelRatio !== undefined) {
83
- this.nodeRenderer.devicePixelRatio = this.options.devicePixelRatio
79
+ this.nodeRenderer.devicePixelRatio = this.options.devicePixelRatio;
84
80
  }
85
81
  }
86
82
 
@@ -89,47 +85,50 @@ export class PenRenderer {
89
85
  // ---------------------------------------------------------------------------
90
86
 
91
87
  init(canvas: HTMLCanvasElement) {
92
- this.canvasEl = canvas
93
- const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1
94
- canvas.width = canvas.clientWidth * dpr
95
- canvas.height = canvas.clientHeight * dpr
96
-
97
- this.surface = this.ck.MakeWebGLCanvasSurface(canvas)
98
- if (!this.surface) this.surface = this.ck.MakeSWCanvasSurface(canvas)
99
- if (!this.surface) { console.error('PenRenderer: Failed to create surface'); return }
88
+ this.canvasEl = canvas;
89
+ const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1;
90
+ canvas.width = canvas.clientWidth * dpr;
91
+ canvas.height = canvas.clientHeight * dpr;
92
+
93
+ this.surface = this.ck.MakeWebGLCanvasSurface(canvas);
94
+ if (!this.surface) this.surface = this.ck.MakeSWCanvasSurface(canvas);
95
+ if (!this.surface) {
96
+ console.error('PenRenderer: Failed to create surface');
97
+ return;
98
+ }
100
99
 
101
- this.nodeRenderer.init()
102
- this.nodeRenderer.setRedrawCallback(() => this.markDirty())
103
- ;(this.nodeRenderer as any).textRenderer._onFontLoaded = () => this.markDirty()
100
+ this.nodeRenderer.init();
101
+ this.nodeRenderer.setRedrawCallback(() => this.markDirty());
102
+ (this.nodeRenderer as any).textRenderer._onFontLoaded = () => this.markDirty();
104
103
 
105
104
  // Pre-load default fonts
106
- const defaultFonts = this.options.defaultFonts ?? ['Inter', 'Noto Sans SC']
105
+ const defaultFonts = this.options.defaultFonts ?? ['Inter', 'Noto Sans SC'];
107
106
  for (const font of defaultFonts) {
108
- this.nodeRenderer.fontManager.ensureFont(font).then(() => this.markDirty())
107
+ this.nodeRenderer.fontManager.ensureFont(font).then(() => this.markDirty());
109
108
  }
110
109
 
111
110
  // Wire up root children provider for layout engine fill-width fallback
112
- setRootChildrenProvider(() => this.document?.children ?? [])
111
+ setRootChildrenProvider(() => this.document?.children ?? []);
113
112
 
114
- this.startRenderLoop()
113
+ this.startRenderLoop();
115
114
  }
116
115
 
117
116
  dispose() {
118
- if (this.animFrameId) cancelAnimationFrame(this.animFrameId)
119
- this.nodeRenderer.dispose()
120
- this.surface?.delete()
121
- this.surface = null
117
+ if (this.animFrameId) cancelAnimationFrame(this.animFrameId);
118
+ this.nodeRenderer.dispose();
119
+ this.surface?.delete();
120
+ this.surface = null;
122
121
  }
123
122
 
124
123
  resize(width: number, height: number) {
125
- if (!this.canvasEl) return
126
- const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1
127
- this.canvasEl.width = width * dpr
128
- this.canvasEl.height = height * dpr
129
- this.surface?.delete()
130
- this.surface = this.ck.MakeWebGLCanvasSurface(this.canvasEl)
131
- if (!this.surface) this.surface = this.ck.MakeSWCanvasSurface(this.canvasEl)
132
- this.markDirty()
124
+ if (!this.canvasEl) return;
125
+ const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1;
126
+ this.canvasEl.width = width * dpr;
127
+ this.canvasEl.height = height * dpr;
128
+ this.surface?.delete();
129
+ this.surface = this.ck.MakeWebGLCanvasSurface(this.canvasEl);
130
+ if (!this.surface) this.surface = this.ck.MakeSWCanvasSurface(this.canvasEl);
131
+ this.markDirty();
133
132
  }
134
133
 
135
134
  // ---------------------------------------------------------------------------
@@ -137,13 +136,13 @@ export class PenRenderer {
137
136
  // ---------------------------------------------------------------------------
138
137
 
139
138
  setDocument(doc: PenDocument) {
140
- this.document = doc
141
- this.activePageId = doc.pages?.[0]?.id ?? null
142
- this.syncFromDocument()
139
+ this.document = doc;
140
+ this.activePageId = doc.pages?.[0]?.id ?? null;
141
+ this.syncFromDocument();
143
142
  }
144
143
 
145
144
  getDocument(): PenDocument | null {
146
- return this.document
145
+ return this.document;
147
146
  }
148
147
 
149
148
  // ---------------------------------------------------------------------------
@@ -151,16 +150,16 @@ export class PenRenderer {
151
150
  // ---------------------------------------------------------------------------
152
151
 
153
152
  setPage(pageId: string) {
154
- this.activePageId = pageId
155
- this.syncFromDocument()
153
+ this.activePageId = pageId;
154
+ this.syncFromDocument();
156
155
  }
157
156
 
158
157
  getPageIds(): string[] {
159
- return this.document?.pages?.map(p => p.id) ?? []
158
+ return this.document?.pages?.map((p) => p.id) ?? [];
160
159
  }
161
160
 
162
161
  getActivePageId(): string | null {
163
- return this.activePageId
162
+ return this.activePageId;
164
163
  }
165
164
 
166
165
  // ---------------------------------------------------------------------------
@@ -168,54 +167,60 @@ export class PenRenderer {
168
167
  // ---------------------------------------------------------------------------
169
168
 
170
169
  setViewport(zoom: number, panX: number, panY: number) {
171
- this._zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom))
172
- this._panX = panX
173
- this._panY = panY
174
- this.markDirty()
170
+ this._zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom));
171
+ this._panX = panX;
172
+ this._panY = panY;
173
+ this.markDirty();
175
174
  }
176
175
 
177
176
  getViewport(): ViewportState {
178
- return { zoom: this._zoom, panX: this._panX, panY: this._panY }
177
+ return { zoom: this._zoom, panX: this._panX, panY: this._panY };
179
178
  }
180
179
 
181
180
  zoomToFit(padding = 64) {
182
- if (!this.canvasEl || this.renderNodes.length === 0) return
183
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
181
+ if (!this.canvasEl || this.renderNodes.length === 0) return;
182
+ let minX = Infinity,
183
+ minY = Infinity,
184
+ maxX = -Infinity,
185
+ maxY = -Infinity;
184
186
  for (const rn of this.renderNodes) {
185
- if (rn.clipRect) continue
186
- minX = Math.min(minX, rn.absX)
187
- minY = Math.min(minY, rn.absY)
188
- maxX = Math.max(maxX, rn.absX + rn.absW)
189
- maxY = Math.max(maxY, rn.absY + rn.absH)
187
+ if (rn.clipRect) continue;
188
+ minX = Math.min(minX, rn.absX);
189
+ minY = Math.min(minY, rn.absY);
190
+ maxX = Math.max(maxX, rn.absX + rn.absW);
191
+ maxY = Math.max(maxY, rn.absY + rn.absH);
190
192
  }
191
- if (!isFinite(minX)) return
193
+ if (!isFinite(minX)) return;
192
194
 
193
- const contentW = maxX - minX
194
- const contentH = maxY - minY
195
- const canvasW = this.canvasEl.clientWidth
196
- const canvasH = this.canvasEl.clientHeight
195
+ const contentW = maxX - minX;
196
+ const contentH = maxY - minY;
197
+ const canvasW = this.canvasEl.clientWidth;
198
+ const canvasH = this.canvasEl.clientHeight;
197
199
  const zoom = Math.min(
198
200
  (canvasW - padding * 2) / contentW,
199
201
  (canvasH - padding * 2) / contentH,
200
202
  2,
201
- )
202
- const panX = (canvasW - contentW * zoom) / 2 - minX * zoom
203
- const panY = (canvasH - contentH * zoom) / 2 - minY * zoom
204
- this.setViewport(zoom, panX, panY)
203
+ );
204
+ const panX = (canvasW - contentW * zoom) / 2 - minX * zoom;
205
+ const panY = (canvasH - contentH * zoom) / 2 - minY * zoom;
206
+ this.setViewport(zoom, panX, panY);
205
207
  }
206
208
 
207
209
  zoomToPoint(screenX: number, screenY: number, newZoom: number) {
208
- if (!this.canvasEl) return
209
- const rect = this.canvasEl.getBoundingClientRect()
210
+ if (!this.canvasEl) return;
211
+ const rect = this.canvasEl.getBoundingClientRect();
210
212
  const vp = vpZoomToPoint(
211
213
  { zoom: this._zoom, panX: this._panX, panY: this._panY },
212
- screenX, screenY, rect, newZoom,
213
- )
214
- this.setViewport(vp.zoom, vp.panX, vp.panY)
214
+ screenX,
215
+ screenY,
216
+ rect,
217
+ newZoom,
218
+ );
219
+ this.setViewport(vp.zoom, vp.panX, vp.panY);
215
220
  }
216
221
 
217
222
  pan(dx: number, dy: number) {
218
- this.setViewport(this._zoom, this._panX + dx, this._panY + dy)
223
+ this.setViewport(this._zoom, this._panX + dx, this._panY + dy);
219
224
  }
220
225
 
221
226
  // ---------------------------------------------------------------------------
@@ -223,8 +228,8 @@ export class PenRenderer {
223
228
  // ---------------------------------------------------------------------------
224
229
 
225
230
  setThemeVariant(variant: Record<string, string>) {
226
- this.options.themeVariant = variant
227
- this.syncFromDocument()
231
+ this.options.themeVariant = variant;
232
+ this.syncFromDocument();
228
233
  }
229
234
 
230
235
  // ---------------------------------------------------------------------------
@@ -232,17 +237,21 @@ export class PenRenderer {
232
237
  // ---------------------------------------------------------------------------
233
238
 
234
239
  hitTest(screenX: number, screenY: number): PenNode | null {
235
- if (!this.canvasEl) return null
236
- const rect = this.canvasEl.getBoundingClientRect()
237
- const scene = screenToScene(screenX, screenY, rect, { zoom: this._zoom, panX: this._panX, panY: this._panY })
238
- const hits = this.spatialIndex.hitTest(scene.x, scene.y)
239
- return hits.length > 0 ? hits[0].node : null
240
+ if (!this.canvasEl) return null;
241
+ const rect = this.canvasEl.getBoundingClientRect();
242
+ const scene = screenToScene(screenX, screenY, rect, {
243
+ zoom: this._zoom,
244
+ panX: this._panX,
245
+ panY: this._panY,
246
+ });
247
+ const hits = this.spatialIndex.hitTest(scene.x, scene.y);
248
+ return hits.length > 0 ? hits[0].node : null;
240
249
  }
241
250
 
242
251
  getNodeBounds(nodeId: string): { x: number; y: number; w: number; h: number } | null {
243
- const rn = this.spatialIndex.get(nodeId)
244
- if (!rn) return null
245
- return { x: rn.absX, y: rn.absY, w: rn.absW, h: rn.absH }
252
+ const rn = this.spatialIndex.get(nodeId);
253
+ if (!rn) return null;
254
+ return { x: rn.absX, y: rn.absY, w: rn.absW, h: rn.absH };
246
255
  }
247
256
 
248
257
  // ---------------------------------------------------------------------------
@@ -250,31 +259,31 @@ export class PenRenderer {
250
259
  // ---------------------------------------------------------------------------
251
260
 
252
261
  private syncFromDocument() {
253
- if (!this.document) return
254
- const pageChildren = getActivePageChildren(this.document, this.activePageId)
255
- const allNodes = getAllChildren(this.document)
262
+ if (!this.document) return;
263
+ const pageChildren = getActivePageChildren(this.document, this.activePageId);
264
+ const allNodes = getAllChildren(this.document);
256
265
 
257
266
  // Collect reusable/instance IDs
258
- this.reusableIds.clear()
259
- this.instanceIds.clear()
260
- collectReusableIds(pageChildren, this.reusableIds)
261
- collectInstanceIds(pageChildren, this.instanceIds)
267
+ this.reusableIds.clear();
268
+ this.instanceIds.clear();
269
+ collectReusableIds(pageChildren, this.reusableIds);
270
+ collectInstanceIds(pageChildren, this.instanceIds);
262
271
 
263
272
  // Resolve refs
264
- const resolved = resolveRefs(pageChildren, allNodes)
273
+ const resolved = resolveRefs(pageChildren, allNodes);
265
274
 
266
275
  // Resolve design variables
267
- const variables = this.document.variables ?? {}
268
- const themes = this.document.themes
269
- const activeTheme = this.options.themeVariant ?? getDefaultTheme(themes)
270
- const variableResolved = resolved.map((n) => resolveNodeForCanvas(n, variables, activeTheme))
276
+ const variables = this.document.variables ?? {};
277
+ const themes = this.document.themes;
278
+ const activeTheme = this.options.themeVariant ?? getDefaultTheme(themes);
279
+ const variableResolved = resolved.map((n) => resolveNodeForCanvas(n, variables, activeTheme));
271
280
 
272
281
  // Pre-measure text heights
273
- const measured = premeasureTextHeights(variableResolved)
282
+ const measured = premeasureTextHeights(variableResolved);
274
283
 
275
- this.renderNodes = flattenToRenderNodes(measured)
276
- this.spatialIndex.rebuild(this.renderNodes)
277
- this.markDirty()
284
+ this.renderNodes = flattenToRenderNodes(measured);
285
+ this.spatialIndex.rebuild(this.renderNodes);
286
+ this.markDirty();
278
287
  }
279
288
 
280
289
  // ---------------------------------------------------------------------------
@@ -282,93 +291,105 @@ export class PenRenderer {
282
291
  // ---------------------------------------------------------------------------
283
292
 
284
293
  private markDirty() {
285
- this.dirty = true
294
+ this.dirty = true;
286
295
  }
287
296
 
288
297
  private startRenderLoop() {
289
298
  const loop = () => {
290
- this.animFrameId = requestAnimationFrame(loop)
291
- if (!this.dirty || !this.surface) return
292
- this.dirty = false
293
- this.render()
294
- }
295
- this.animFrameId = requestAnimationFrame(loop)
299
+ this.animFrameId = requestAnimationFrame(loop);
300
+ if (!this.dirty || !this.surface) return;
301
+ this.dirty = false;
302
+ this.render();
303
+ };
304
+ this.animFrameId = requestAnimationFrame(loop);
296
305
  }
297
306
 
298
307
  render() {
299
- if (!this.surface || !this.canvasEl) return
300
- const canvas = this.surface.getCanvas()
301
- const ck = this.ck
302
- const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1
308
+ if (!this.surface || !this.canvasEl) return;
309
+ const canvas = this.surface.getCanvas();
310
+ const ck = this.ck;
311
+ const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1;
303
312
 
304
313
  // Clear
305
- const bgColor = this.options.backgroundColor ?? CANVAS_BACKGROUND_DARK
306
- canvas.clear(parseColor(ck, bgColor))
314
+ const bgColor = this.options.backgroundColor ?? CANVAS_BACKGROUND_DARK;
315
+ canvas.clear(parseColor(ck, bgColor));
307
316
 
308
317
  // Apply viewport transform
309
- canvas.save()
310
- canvas.scale(dpr, dpr)
311
- canvas.concat(viewportMatrix({ zoom: this._zoom, panX: this._panX, panY: this._panY }))
318
+ canvas.save();
319
+ canvas.scale(dpr, dpr);
320
+ canvas.concat(viewportMatrix({ zoom: this._zoom, panX: this._panX, panY: this._panY }));
312
321
 
313
322
  // Pass current zoom to renderer
314
- this.nodeRenderer.zoom = this._zoom
323
+ this.nodeRenderer.zoom = this._zoom;
315
324
 
316
325
  // Draw all render nodes
317
326
  for (const rn of this.renderNodes) {
318
- this.nodeRenderer.drawNode(canvas, rn)
327
+ this.nodeRenderer.drawNode(canvas, rn);
319
328
  }
320
329
 
321
330
  // Draw frame labels for root frames + reusable + instances
322
331
  for (const rn of this.renderNodes) {
323
- if (!rn.node.name) continue
324
- const isRootFrame = rn.node.type === 'frame' && !rn.clipRect
325
- const isReusable = this.reusableIds.has(rn.node.id)
326
- const isInstance = this.instanceIds.has(rn.node.id)
327
- if (!isRootFrame && !isReusable && !isInstance) continue
328
- this.drawFrameLabel(canvas, rn.node.name, rn.absX, rn.absY)
332
+ if (!rn.node.name) continue;
333
+ const isRootFrame = rn.node.type === 'frame' && !rn.clipRect;
334
+ const isReusable = this.reusableIds.has(rn.node.id);
335
+ const isInstance = this.instanceIds.has(rn.node.id);
336
+ if (!isRootFrame && !isReusable && !isInstance) continue;
337
+ this.drawFrameLabel(canvas, rn.node.name, rn.absX, rn.absY);
329
338
  }
330
339
 
331
- canvas.restore()
332
- this.surface.flush()
340
+ canvas.restore();
341
+ this.surface.flush();
333
342
  }
334
343
 
335
344
  /** Simple frame label drawing for read-only renderer. */
336
- private drawFrameLabel(canvas: ReturnType<Surface['getCanvas']>, name: string, x: number, y: number) {
337
- const ck = this.ck
338
- const fontSize = FRAME_LABEL_FONT_SIZE / this._zoom
339
- const offsetY = FRAME_LABEL_OFFSET_Y / this._zoom
345
+ private drawFrameLabel(
346
+ canvas: ReturnType<Surface['getCanvas']>,
347
+ name: string,
348
+ x: number,
349
+ y: number,
350
+ ) {
351
+ const ck = this.ck;
352
+ const fontSize = FRAME_LABEL_FONT_SIZE / this._zoom;
353
+ const offsetY = FRAME_LABEL_OFFSET_Y / this._zoom;
340
354
 
341
355
  // Use Canvas 2D to rasterize the label text
342
- const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1
343
- const scale = Math.min(this._zoom * dpr, 4)
344
- const tmp = document.createElement('canvas')
345
- const textW = Math.ceil(name.length * fontSize * 0.7 * scale) + 4
346
- const textH = Math.ceil(fontSize * 1.4 * scale) + 4
347
- tmp.width = textW
348
- tmp.height = textH
349
- const ctx = tmp.getContext('2d')!
350
- ctx.scale(scale, scale)
351
- ctx.font = `500 ${fontSize}px Inter, system-ui, sans-serif`
352
- ctx.fillStyle = FRAME_LABEL_COLOR
353
- ctx.textBaseline = 'top'
354
- ctx.fillText(name, 0, 0)
355
-
356
- const imageData = ctx.getImageData(0, 0, textW, textH)
356
+ const dpr = this.options.devicePixelRatio ?? window.devicePixelRatio ?? 1;
357
+ const scale = Math.min(this._zoom * dpr, 4);
358
+ const tmp = document.createElement('canvas');
359
+ const textW = Math.ceil(name.length * fontSize * 0.7 * scale) + 4;
360
+ const textH = Math.ceil(fontSize * 1.4 * scale) + 4;
361
+ tmp.width = textW;
362
+ tmp.height = textH;
363
+ const ctx = tmp.getContext('2d')!;
364
+ ctx.scale(scale, scale);
365
+ ctx.font = `500 ${fontSize}px Inter, system-ui, sans-serif`;
366
+ ctx.fillStyle = FRAME_LABEL_COLOR;
367
+ ctx.textBaseline = 'top';
368
+ ctx.fillText(name, 0, 0);
369
+
370
+ const imageData = ctx.getImageData(0, 0, textW, textH);
357
371
  const img = ck.MakeImage(
358
- { width: textW, height: textH, alphaType: ck.AlphaType.Unpremul, colorType: ck.ColorType.RGBA_8888, colorSpace: ck.ColorSpace.SRGB },
359
- imageData.data, textW * 4,
360
- )
372
+ {
373
+ width: textW,
374
+ height: textH,
375
+ alphaType: ck.AlphaType.Unpremul,
376
+ colorType: ck.ColorType.RGBA_8888,
377
+ colorSpace: ck.ColorSpace.SRGB,
378
+ },
379
+ imageData.data,
380
+ textW * 4,
381
+ );
361
382
  if (img) {
362
- const paint = new ck.Paint()
363
- paint.setAntiAlias(true)
383
+ const paint = new ck.Paint();
384
+ paint.setAntiAlias(true);
364
385
  canvas.drawImageRect(
365
386
  img,
366
387
  ck.LTRBRect(0, 0, textW, textH),
367
388
  ck.LTRBRect(x, y - offsetY - fontSize * 1.2, x + textW / scale, y - offsetY),
368
389
  paint,
369
- )
370
- paint.delete()
371
- img.delete()
390
+ );
391
+ paint.delete();
392
+ img.delete();
372
393
  }
373
394
  }
374
395
  }