@al8b/screen 0.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.
Files changed (68) hide show
  1. package/README.md +23 -0
  2. package/dist/core/base-screen.d.mts +84 -0
  3. package/dist/core/base-screen.d.ts +84 -0
  4. package/dist/core/base-screen.js +419 -0
  5. package/dist/core/base-screen.js.map +1 -0
  6. package/dist/core/base-screen.mjs +396 -0
  7. package/dist/core/base-screen.mjs.map +1 -0
  8. package/dist/core/index.d.mts +10 -0
  9. package/dist/core/index.d.ts +10 -0
  10. package/dist/core/index.js +1208 -0
  11. package/dist/core/index.js.map +1 -0
  12. package/dist/core/index.mjs +1183 -0
  13. package/dist/core/index.mjs.map +1 -0
  14. package/dist/core/screen.d.mts +15 -0
  15. package/dist/core/screen.d.ts +15 -0
  16. package/dist/core/screen.js +1209 -0
  17. package/dist/core/screen.js.map +1 -0
  18. package/dist/core/screen.mjs +1184 -0
  19. package/dist/core/screen.mjs.map +1 -0
  20. package/dist/drawing/primitives-screen.d.mts +28 -0
  21. package/dist/drawing/primitives-screen.d.ts +28 -0
  22. package/dist/drawing/primitives-screen.js +685 -0
  23. package/dist/drawing/primitives-screen.js.map +1 -0
  24. package/dist/drawing/primitives-screen.mjs +662 -0
  25. package/dist/drawing/primitives-screen.mjs.map +1 -0
  26. package/dist/drawing/sprite-screen.d.mts +41 -0
  27. package/dist/drawing/sprite-screen.d.ts +41 -0
  28. package/dist/drawing/sprite-screen.js +853 -0
  29. package/dist/drawing/sprite-screen.js.map +1 -0
  30. package/dist/drawing/sprite-screen.mjs +830 -0
  31. package/dist/drawing/sprite-screen.mjs.map +1 -0
  32. package/dist/drawing/text-screen.d.mts +19 -0
  33. package/dist/drawing/text-screen.d.ts +19 -0
  34. package/dist/drawing/text-screen.js +909 -0
  35. package/dist/drawing/text-screen.js.map +1 -0
  36. package/dist/drawing/text-screen.mjs +884 -0
  37. package/dist/drawing/text-screen.mjs.map +1 -0
  38. package/dist/index.d.mts +10 -0
  39. package/dist/index.d.ts +10 -0
  40. package/dist/index.js +1210 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/index.mjs +1184 -0
  43. package/dist/index.mjs.map +1 -0
  44. package/dist/tri/index.d.mts +3 -0
  45. package/dist/tri/index.d.ts +3 -0
  46. package/dist/tri/index.js +231 -0
  47. package/dist/tri/index.js.map +1 -0
  48. package/dist/tri/index.mjs +203 -0
  49. package/dist/tri/index.mjs.map +1 -0
  50. package/dist/tri/triangle-screen.d.mts +16 -0
  51. package/dist/tri/triangle-screen.d.ts +16 -0
  52. package/dist/tri/triangle-screen.js +1147 -0
  53. package/dist/tri/triangle-screen.js.map +1 -0
  54. package/dist/tri/triangle-screen.mjs +1122 -0
  55. package/dist/tri/triangle-screen.mjs.map +1 -0
  56. package/dist/tri/ttri.d.mts +71 -0
  57. package/dist/tri/ttri.d.ts +71 -0
  58. package/dist/tri/ttri.js +229 -0
  59. package/dist/tri/ttri.js.map +1 -0
  60. package/dist/tri/ttri.mjs +203 -0
  61. package/dist/tri/ttri.mjs.map +1 -0
  62. package/dist/types/index.d.mts +64 -0
  63. package/dist/types/index.d.ts +64 -0
  64. package/dist/types/index.js +19 -0
  65. package/dist/types/index.js.map +1 -0
  66. package/dist/types/index.mjs +1 -0
  67. package/dist/types/index.mjs.map +1 -0
  68. package/package.json +37 -0
@@ -0,0 +1,884 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/drawing/sprite-screen.ts
5
+ import { APIErrorCode as APIErrorCode3, reportRuntimeError as reportRuntimeError3 } from "@al8b/diagnostics";
6
+
7
+ // src/drawing/primitives-screen.ts
8
+ import { APIErrorCode as APIErrorCode2, reportRuntimeError as reportRuntimeError2 } from "@al8b/diagnostics";
9
+
10
+ // src/core/base-screen.ts
11
+ import { APIErrorCode, createDiagnostic, formatForBrowser, reportRuntimeError } from "@al8b/diagnostics";
12
+
13
+ // src/tri/ttri.ts
14
+ var ZBuffer = class {
15
+ static {
16
+ __name(this, "ZBuffer");
17
+ }
18
+ buffer;
19
+ width;
20
+ height;
21
+ constructor(width, height) {
22
+ this.width = width;
23
+ this.height = height;
24
+ this.buffer = new Float32Array(width * height);
25
+ }
26
+ clear() {
27
+ this.buffer.fill(0);
28
+ }
29
+ get(x, y) {
30
+ return this.buffer[y * this.width + x] || 0;
31
+ }
32
+ set(x, y, z) {
33
+ this.buffer[y * this.width + x] = z;
34
+ }
35
+ resize(width, height) {
36
+ if (this.width !== width || this.height !== height) {
37
+ this.width = width;
38
+ this.height = height;
39
+ this.buffer = new Float32Array(width * height);
40
+ }
41
+ }
42
+ };
43
+
44
+ // src/core/base-screen.ts
45
+ var BaseScreen = class {
46
+ static {
47
+ __name(this, "BaseScreen");
48
+ }
49
+ canvas;
50
+ context;
51
+ runtime;
52
+ width;
53
+ height;
54
+ // Drawing state
55
+ alpha = 1;
56
+ pixelated = 1;
57
+ line_width = 1;
58
+ font = "BitCell";
59
+ // Transformations
60
+ translation_x = 0;
61
+ translation_y = 0;
62
+ rotation = 0;
63
+ scale_x = 1;
64
+ scale_y = 1;
65
+ screen_transform = false;
66
+ // Object transformations
67
+ object_rotation = 0;
68
+ object_scale_x = 1;
69
+ object_scale_y = 1;
70
+ anchor_x = 0;
71
+ anchor_y = 0;
72
+ // Blending + font caches
73
+ blending = {};
74
+ font_load_requested = {};
75
+ font_loaded = {};
76
+ // Interface cache
77
+ interfaceCache = null;
78
+ // Cursor management
79
+ cursor = "default";
80
+ cursor_visibility = "auto";
81
+ last_mouse_move = Date.now();
82
+ // 3D helper
83
+ zBuffer;
84
+ constructor(options = {}) {
85
+ this.runtime = options.runtime;
86
+ if (options.canvas) {
87
+ this.canvas = options.canvas;
88
+ if (this.canvas.width === 0 || this.canvas.height === 0) {
89
+ this.canvas.width = options.width || 1080;
90
+ this.canvas.height = options.height || 1920;
91
+ }
92
+ } else {
93
+ this.canvas = document.createElement("canvas");
94
+ this.canvas.width = options.width || 1080;
95
+ this.canvas.height = options.height || 1920;
96
+ }
97
+ this.initContext();
98
+ this.blending = {
99
+ normal: "source-over",
100
+ additive: "lighter"
101
+ };
102
+ const blendModes = [
103
+ "source-over",
104
+ "source-in",
105
+ "source-out",
106
+ "source-atop",
107
+ "destination-over",
108
+ "destination-in",
109
+ "destination-out",
110
+ "destination-atop",
111
+ "lighter",
112
+ "copy",
113
+ "xor",
114
+ "multiply",
115
+ "screen",
116
+ "overlay",
117
+ "darken",
118
+ "lighten",
119
+ "color-dodge",
120
+ "color-burn",
121
+ "hard-light",
122
+ "soft-light",
123
+ "difference",
124
+ "exclusion",
125
+ "hue",
126
+ "saturation",
127
+ "color",
128
+ "luminosity"
129
+ ];
130
+ for (const mode of blendModes) {
131
+ this.blending[mode] = mode;
132
+ }
133
+ this.loadFont(this.font);
134
+ this.zBuffer = new ZBuffer(this.canvas.width, this.canvas.height);
135
+ this.cursor = "default";
136
+ this.canvas.addEventListener("mousemove", () => {
137
+ this.last_mouse_move = Date.now();
138
+ if (this.cursor !== "default" && this.cursor_visibility === "auto") {
139
+ this.cursor = "default";
140
+ this.canvas.style.cursor = "default";
141
+ }
142
+ });
143
+ this.canvas.addEventListener("contextrestored", () => {
144
+ this.initContext();
145
+ });
146
+ setInterval(() => this.checkMouseCursor(), 1e3);
147
+ this.cursor_visibility = "auto";
148
+ }
149
+ initContext() {
150
+ const ctx = this.canvas.getContext("2d", {
151
+ alpha: false
152
+ });
153
+ if (!ctx) {
154
+ const diagnostic = createDiagnostic(APIErrorCode.E7001);
155
+ const formatted = formatForBrowser(diagnostic);
156
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7001, {});
157
+ throw new Error(formatted);
158
+ }
159
+ if (ctx !== this.context) {
160
+ this.context = ctx;
161
+ } else {
162
+ this.context.restore();
163
+ }
164
+ this.context.save();
165
+ this.context.translate(this.canvas.width / 2, this.canvas.height / 2);
166
+ const ratio = Math.min(this.canvas.width / 200, this.canvas.height / 200);
167
+ this.context.scale(ratio, ratio);
168
+ this.width = this.canvas.width / ratio;
169
+ this.height = this.canvas.height / ratio;
170
+ this.context.lineCap = "round";
171
+ }
172
+ /**
173
+ * Initialize draw state (called before each draw frame)
174
+ */
175
+ initDraw() {
176
+ this.alpha = 1;
177
+ this.line_width = 1;
178
+ }
179
+ /**
180
+ * Update interface dimensions (called before each draw frame)
181
+ */
182
+ updateInterface() {
183
+ if (this.interfaceCache) {
184
+ this.interfaceCache.width = this.width;
185
+ this.interfaceCache.height = this.height;
186
+ }
187
+ }
188
+ clear(color) {
189
+ this.context.globalAlpha = 1;
190
+ this.context.globalCompositeOperation = "source-over";
191
+ this.context.fillStyle = color || "#000";
192
+ this.context.strokeStyle = color || "#000";
193
+ this.context.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
194
+ this.zBuffer.clear();
195
+ }
196
+ setColor(color) {
197
+ if (!color) return;
198
+ if (!Number.isNaN(Number.parseInt(String(color)))) {
199
+ const num = Number.parseInt(String(color));
200
+ const r = Math.floor(num / 100) % 10 / 9 * 255;
201
+ const g = Math.floor(num / 10) % 10 / 9 * 255;
202
+ const b = num % 10 / 9 * 255;
203
+ const c = 4278190080 + (r << 16) + (g << 8) + b;
204
+ const hex = "#" + c.toString(16).substring(2, 8);
205
+ this.context.fillStyle = hex;
206
+ this.context.strokeStyle = hex;
207
+ } else if (typeof color === "string") {
208
+ const isValidColor = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color) || /^rgb\(|^rgba\(|^hsl\(|^hsla\(/.test(color) || /^(red|green|blue|yellow|cyan|magenta|black|white|gray|grey|orange|pink|purple|brown|transparent)$/i.test(color);
209
+ if (!isValidColor) {
210
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7003, {
211
+ color
212
+ });
213
+ return;
214
+ }
215
+ this.context.fillStyle = color;
216
+ this.context.strokeStyle = color;
217
+ }
218
+ }
219
+ setAlpha(alpha) {
220
+ this.alpha = alpha;
221
+ }
222
+ setPixelated(pixelated) {
223
+ this.pixelated = pixelated;
224
+ }
225
+ setBlending(blending) {
226
+ const blend = this.blending[blending || "normal"];
227
+ if (!blend) {
228
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7007, {
229
+ blendMode: blending
230
+ });
231
+ this.context.globalCompositeOperation = "source-over";
232
+ return;
233
+ }
234
+ this.context.globalCompositeOperation = blend;
235
+ }
236
+ setLineWidth(width) {
237
+ this.line_width = width;
238
+ }
239
+ setLineDash(dash) {
240
+ if (!Array.isArray(dash)) {
241
+ this.context.setLineDash([]);
242
+ } else {
243
+ this.context.setLineDash(dash);
244
+ }
245
+ }
246
+ setLinearGradient(x1, y1, x2, y2, c1, c2) {
247
+ const grd = this.context.createLinearGradient(x1, -y1, x2, -y2);
248
+ grd.addColorStop(0, c1);
249
+ grd.addColorStop(1, c2);
250
+ this.context.fillStyle = grd;
251
+ this.context.strokeStyle = grd;
252
+ }
253
+ setRadialGradient(x, y, radius, c1, c2) {
254
+ const grd = this.context.createRadialGradient(x, -y, 0, x, -y, radius);
255
+ grd.addColorStop(0, c1);
256
+ grd.addColorStop(1, c2);
257
+ this.context.fillStyle = grd;
258
+ this.context.strokeStyle = grd;
259
+ }
260
+ setFont(font) {
261
+ this.font = font || "Verdana";
262
+ this.loadFont(this.font);
263
+ }
264
+ loadFont(font = "BitCell") {
265
+ if (this.font_load_requested[font]) {
266
+ return;
267
+ }
268
+ this.font_load_requested[font] = true;
269
+ try {
270
+ document.fonts?.load?.(`16pt ${font}`).catch(() => {
271
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7006, {
272
+ font
273
+ });
274
+ });
275
+ } catch {
276
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7006, {
277
+ font
278
+ });
279
+ }
280
+ }
281
+ isFontReady(font = this.font) {
282
+ if (this.font_loaded[font]) {
283
+ return 1;
284
+ }
285
+ try {
286
+ const ready = document.fonts?.check?.(`16pt ${font}`) ?? true;
287
+ if (ready) {
288
+ this.font_loaded[font] = true;
289
+ }
290
+ return ready ? 1 : 0;
291
+ } catch {
292
+ return 1;
293
+ }
294
+ }
295
+ setTranslation(tx, ty) {
296
+ this.translation_x = isFinite(tx) ? tx : 0;
297
+ this.translation_y = isFinite(ty) ? ty : 0;
298
+ this.updateScreenTransform();
299
+ }
300
+ setScale(x, y) {
301
+ this.scale_x = isFinite(x) && x !== 0 ? x : 1;
302
+ this.scale_y = isFinite(y) && y !== 0 ? y : 1;
303
+ this.updateScreenTransform();
304
+ }
305
+ setRotation(rotation) {
306
+ this.rotation = isFinite(rotation) ? rotation : 0;
307
+ this.updateScreenTransform();
308
+ }
309
+ updateScreenTransform() {
310
+ this.screen_transform = this.translation_x !== 0 || this.translation_y !== 0 || this.scale_x !== 1 || this.scale_y !== 1 || this.rotation !== 0;
311
+ }
312
+ setDrawAnchor(ax, ay) {
313
+ this.anchor_x = typeof ax === "number" ? ax : 0;
314
+ this.anchor_y = typeof ay === "number" ? ay : 0;
315
+ }
316
+ setDrawRotation(rotation) {
317
+ this.object_rotation = rotation;
318
+ }
319
+ setDrawScale(x, y = x) {
320
+ this.object_scale_x = x;
321
+ this.object_scale_y = y;
322
+ }
323
+ initDrawOp(x, y, object_transform = true) {
324
+ let res = false;
325
+ if (this.screen_transform) {
326
+ this.context.save();
327
+ res = true;
328
+ this.context.translate(this.translation_x, -this.translation_y);
329
+ this.context.scale(this.scale_x, this.scale_y);
330
+ this.context.rotate(-this.rotation / 180 * Math.PI);
331
+ this.context.translate(x, y);
332
+ }
333
+ if (object_transform && (this.object_rotation !== 0 || this.object_scale_x !== 1 || this.object_scale_y !== 1)) {
334
+ if (!res) {
335
+ this.context.save();
336
+ res = true;
337
+ this.context.translate(x, y);
338
+ }
339
+ if (this.object_rotation !== 0) {
340
+ this.context.rotate(-this.object_rotation / 180 * Math.PI);
341
+ }
342
+ if (this.object_scale_x !== 1 || this.object_scale_y !== 1) {
343
+ this.context.scale(this.object_scale_x, this.object_scale_y);
344
+ }
345
+ }
346
+ return res;
347
+ }
348
+ closeDrawOp() {
349
+ this.context.restore();
350
+ }
351
+ /**
352
+ * Check mouse cursor visibility
353
+ * Auto-hides cursor after 4 seconds of inactivity
354
+ */
355
+ checkMouseCursor() {
356
+ if (Date.now() > this.last_mouse_move + 4e3 && this.cursor_visibility === "auto") {
357
+ if (this.cursor !== "none") {
358
+ this.cursor = "none";
359
+ this.canvas.style.cursor = "none";
360
+ }
361
+ }
362
+ }
363
+ /**
364
+ * Set cursor visibility
365
+ */
366
+ setCursorVisible(visible) {
367
+ this.cursor_visibility = visible ? "default" : "none";
368
+ if (visible) {
369
+ this.cursor = "default";
370
+ this.canvas.style.cursor = "default";
371
+ } else {
372
+ this.cursor = "none";
373
+ this.canvas.style.cursor = "none";
374
+ }
375
+ }
376
+ getCanvas() {
377
+ return this.canvas;
378
+ }
379
+ getContext() {
380
+ return this.context;
381
+ }
382
+ resize(width, height) {
383
+ if (width && height) {
384
+ if (width <= 0 || height <= 0 || !isFinite(width) || !isFinite(height)) {
385
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7002, {
386
+ width,
387
+ height
388
+ });
389
+ return;
390
+ }
391
+ this.canvas.width = width;
392
+ this.canvas.height = height;
393
+ this.initContext();
394
+ this.zBuffer.resize(width, height);
395
+ this.updateInterface();
396
+ }
397
+ }
398
+ };
399
+
400
+ // src/drawing/primitives-screen.ts
401
+ var PrimitiveScreen = class extends BaseScreen {
402
+ static {
403
+ __name(this, "PrimitiveScreen");
404
+ }
405
+ fillRect(x, y, w, h, color) {
406
+ if (!this.context) {
407
+ reportRuntimeError2(this.runtime?.listener, APIErrorCode2.E7092, {});
408
+ return;
409
+ }
410
+ if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h) || w <= 0 || h <= 0) {
411
+ reportRuntimeError2(this.runtime?.listener, APIErrorCode2.E7093, {
412
+ error: `Invalid parameters: x=${x}, y=${y}, w=${w}, h=${h}`
413
+ });
414
+ return;
415
+ }
416
+ if (color) this.setColor(color);
417
+ this.context.globalAlpha = this.alpha;
418
+ if (this.initDrawOp(x, -y)) {
419
+ this.context.fillRect(-w / 2 - this.anchor_x * w / 2, -h / 2 + this.anchor_y * h / 2, w, h);
420
+ this.closeDrawOp();
421
+ } else {
422
+ this.context.fillRect(x - w / 2 - this.anchor_x * w / 2, -y - h / 2 + this.anchor_y * h / 2, w, h);
423
+ }
424
+ }
425
+ fillRoundRect(x, y, w, h, round = 10, color) {
426
+ if (color) this.setColor(color);
427
+ this.context.globalAlpha = this.alpha;
428
+ const transform = this.initDrawOp(x, -y);
429
+ const rx = (transform ? -w / 2 : x - w / 2) - this.anchor_x * w / 2;
430
+ const ry = (transform ? -h / 2 : -y - h / 2) + this.anchor_y * h / 2;
431
+ this.context.beginPath();
432
+ if (this.context.roundRect) {
433
+ this.context.roundRect(rx, ry, w, h, round);
434
+ } else {
435
+ const r = Math.min(round, w / 2, h / 2);
436
+ this.context.moveTo(rx + r, ry);
437
+ this.context.lineTo(rx + w - r, ry);
438
+ this.context.quadraticCurveTo(rx + w, ry, rx + w, ry + r);
439
+ this.context.lineTo(rx + w, ry + h - r);
440
+ this.context.quadraticCurveTo(rx + w, ry + h, rx + w - r, ry + h);
441
+ this.context.lineTo(rx + r, ry + h);
442
+ this.context.quadraticCurveTo(rx, ry + h, rx, ry + h - r);
443
+ this.context.lineTo(rx, ry + r);
444
+ this.context.quadraticCurveTo(rx, ry, rx + r, ry);
445
+ this.context.closePath();
446
+ }
447
+ this.context.fill();
448
+ if (transform) this.closeDrawOp();
449
+ }
450
+ fillRound(x, y, w, h, color) {
451
+ if (color) this.setColor(color);
452
+ this.context.globalAlpha = this.alpha;
453
+ w = Math.abs(w);
454
+ h = Math.abs(h);
455
+ if (this.initDrawOp(x, -y)) {
456
+ this.context.beginPath();
457
+ this.context.ellipse(-this.anchor_x * w / 2, this.anchor_y * h / 2, w / 2, h / 2, 0, 0, Math.PI * 2, false);
458
+ this.context.fill();
459
+ this.closeDrawOp();
460
+ } else {
461
+ this.context.beginPath();
462
+ this.context.ellipse(x - this.anchor_x * w / 2, -y + this.anchor_y * h / 2, w / 2, h / 2, 0, 0, Math.PI * 2, false);
463
+ this.context.fill();
464
+ }
465
+ }
466
+ drawRect(x, y, w, h, color) {
467
+ if (color) this.setColor(color);
468
+ this.context.globalAlpha = this.alpha;
469
+ this.context.lineWidth = this.line_width;
470
+ if (this.initDrawOp(x, -y)) {
471
+ this.context.strokeRect(-w / 2 - this.anchor_x * w / 2, -h / 2 + this.anchor_y * h / 2, w, h);
472
+ this.closeDrawOp();
473
+ } else {
474
+ this.context.strokeRect(x - w / 2 - this.anchor_x * w / 2, -y - h / 2 + this.anchor_y * h / 2, w, h);
475
+ }
476
+ }
477
+ drawRoundRect(x, y, w, h, round = 10, color) {
478
+ if (color) this.setColor(color);
479
+ this.context.globalAlpha = this.alpha;
480
+ this.context.lineWidth = this.line_width;
481
+ const transform = this.initDrawOp(x, -y);
482
+ const rx = (transform ? -w / 2 : x - w / 2) - this.anchor_x * w / 2;
483
+ const ry = (transform ? -h / 2 : -y - h / 2) + this.anchor_y * h / 2;
484
+ this.context.beginPath();
485
+ if (this.context.roundRect) {
486
+ this.context.roundRect(rx, ry, w, h, round);
487
+ } else {
488
+ const r = Math.min(round, w / 2, h / 2);
489
+ this.context.moveTo(rx + r, ry);
490
+ this.context.lineTo(rx + w - r, ry);
491
+ this.context.quadraticCurveTo(rx + w, ry, rx + w, ry + r);
492
+ this.context.lineTo(rx + w, ry + h - r);
493
+ this.context.quadraticCurveTo(rx + w, ry + h, rx + w - r, ry + h);
494
+ this.context.lineTo(rx + r, ry + h);
495
+ this.context.quadraticCurveTo(rx, ry + h, rx, ry + h - r);
496
+ this.context.lineTo(rx, ry + r);
497
+ this.context.quadraticCurveTo(rx, ry, rx + r, ry);
498
+ this.context.closePath();
499
+ }
500
+ this.context.stroke();
501
+ if (transform) this.closeDrawOp();
502
+ }
503
+ drawRound(x, y, w, h, color) {
504
+ if (color) this.setColor(color);
505
+ this.context.globalAlpha = this.alpha;
506
+ this.context.lineWidth = this.line_width;
507
+ w = Math.abs(w);
508
+ h = Math.abs(h);
509
+ if (this.initDrawOp(x, -y)) {
510
+ this.context.beginPath();
511
+ this.context.ellipse(-this.anchor_x * w / 2, this.anchor_y * h / 2, w / 2, h / 2, 0, 0, Math.PI * 2, false);
512
+ this.context.stroke();
513
+ this.closeDrawOp();
514
+ } else {
515
+ this.context.beginPath();
516
+ this.context.ellipse(x - this.anchor_x * w / 2, -y + this.anchor_y * h / 2, w / 2, h / 2, 0, 0, Math.PI * 2, false);
517
+ this.context.stroke();
518
+ }
519
+ }
520
+ drawLine(x1, y1, x2, y2, color) {
521
+ if (color) this.setColor(color);
522
+ this.context.globalAlpha = this.alpha;
523
+ this.context.lineWidth = this.line_width;
524
+ const transform = this.initDrawOp(0, 0, false);
525
+ this.context.beginPath();
526
+ this.context.moveTo(x1, -y1);
527
+ this.context.lineTo(x2, -y2);
528
+ this.context.stroke();
529
+ if (transform) this.closeDrawOp();
530
+ }
531
+ drawPolygon(args) {
532
+ const { color, points } = this.extractPoints(args);
533
+ if (!points || points.length < 4) return;
534
+ if (color) this.setColor(color);
535
+ this.context.globalAlpha = this.alpha;
536
+ this.context.lineWidth = this.line_width;
537
+ const len = Math.floor(points.length / 2);
538
+ const transform = this.initDrawOp(0, 0, false);
539
+ this.context.beginPath();
540
+ this.context.moveTo(points[0], -points[1]);
541
+ for (let i = 1; i < len; i++) {
542
+ this.context.lineTo(points[i * 2], -points[i * 2 + 1]);
543
+ }
544
+ this.context.closePath();
545
+ this.context.stroke();
546
+ if (transform) this.closeDrawOp();
547
+ }
548
+ drawPolyline(args) {
549
+ const { color, points } = this.extractPoints(args);
550
+ if (!points || points.length < 4) return;
551
+ if (color) this.setColor(color);
552
+ this.context.globalAlpha = this.alpha;
553
+ this.context.lineWidth = this.line_width;
554
+ const len = Math.floor(points.length / 2);
555
+ const transform = this.initDrawOp(0, 0, false);
556
+ this.context.beginPath();
557
+ this.context.moveTo(points[0], -points[1]);
558
+ for (let i = 1; i < len; i++) {
559
+ this.context.lineTo(points[i * 2], -points[i * 2 + 1]);
560
+ }
561
+ this.context.stroke();
562
+ if (transform) this.closeDrawOp();
563
+ }
564
+ fillPolygon(args) {
565
+ const { color, points } = this.extractPoints(args);
566
+ if (!points || points.length < 4) return;
567
+ if (color) this.setColor(color);
568
+ this.context.globalAlpha = this.alpha;
569
+ const len = Math.floor(points.length / 2);
570
+ const transform = this.initDrawOp(0, 0, false);
571
+ this.context.beginPath();
572
+ this.context.moveTo(points[0], -points[1]);
573
+ for (let i = 1; i < len; i++) {
574
+ this.context.lineTo(points[i * 2], -points[i * 2 + 1]);
575
+ }
576
+ this.context.fill();
577
+ if (transform) this.closeDrawOp();
578
+ }
579
+ drawQuadCurve(args) {
580
+ const { color, points } = this.extractPoints(args);
581
+ if (!points || points.length < 4) return;
582
+ if (color) this.setColor(color);
583
+ this.context.globalAlpha = this.alpha;
584
+ this.context.lineWidth = this.line_width;
585
+ const transform = this.initDrawOp(0, 0, false);
586
+ this.context.beginPath();
587
+ this.context.moveTo(points[0], -points[1]);
588
+ let index = 2;
589
+ while (index <= points.length - 4) {
590
+ this.context.quadraticCurveTo(points[index], -points[index + 1], points[index + 2], -points[index + 3]);
591
+ index += 4;
592
+ }
593
+ this.context.stroke();
594
+ if (transform) this.closeDrawOp();
595
+ }
596
+ drawBezierCurve(args) {
597
+ const { color, points } = this.extractPoints(args);
598
+ if (!points || points.length < 4) return;
599
+ if (color) this.setColor(color);
600
+ this.context.globalAlpha = this.alpha;
601
+ this.context.lineWidth = this.line_width;
602
+ const transform = this.initDrawOp(0, 0, false);
603
+ this.context.beginPath();
604
+ this.context.moveTo(points[0], -points[1]);
605
+ let index = 2;
606
+ while (index <= points.length - 6) {
607
+ this.context.bezierCurveTo(points[index], -points[index + 1], points[index + 2], -points[index + 3], points[index + 4], -points[index + 5]);
608
+ index += 6;
609
+ }
610
+ this.context.stroke();
611
+ if (transform) this.closeDrawOp();
612
+ }
613
+ drawArc(x, y, radius, angle1, angle2, ccw, color) {
614
+ if (color) this.setColor(color);
615
+ this.context.globalAlpha = this.alpha;
616
+ this.context.lineWidth = this.line_width;
617
+ if (this.initDrawOp(x, -y)) {
618
+ this.context.beginPath();
619
+ this.context.arc(0, 0, radius, -angle1 / 180 * Math.PI, -angle2 / 180 * Math.PI, ccw);
620
+ this.context.stroke();
621
+ this.closeDrawOp();
622
+ } else {
623
+ this.context.beginPath();
624
+ this.context.arc(x, -y, radius, -angle1 / 180 * Math.PI, -angle2 / 180 * Math.PI, ccw);
625
+ this.context.stroke();
626
+ }
627
+ }
628
+ fillArc(x, y, radius, angle1, angle2, ccw, color) {
629
+ if (color) this.setColor(color);
630
+ this.context.globalAlpha = this.alpha;
631
+ if (this.initDrawOp(x, -y)) {
632
+ this.context.beginPath();
633
+ this.context.arc(0, 0, radius, -angle1 / 180 * Math.PI, -angle2 / 180 * Math.PI, ccw);
634
+ this.context.fill();
635
+ this.closeDrawOp();
636
+ } else {
637
+ this.context.beginPath();
638
+ this.context.arc(x, -y, radius, -angle1 / 180 * Math.PI, -angle2 / 180 * Math.PI, ccw);
639
+ this.context.fill();
640
+ }
641
+ }
642
+ extractPoints(args) {
643
+ let color;
644
+ let points;
645
+ if (args.length > 0 && args.length % 2 === 1 && typeof args[args.length - 1] === "string") {
646
+ color = args[args.length - 1];
647
+ points = args.slice(0, -1);
648
+ } else if (Array.isArray(args[0])) {
649
+ if (args[1] && typeof args[1] === "string") {
650
+ color = args[1];
651
+ }
652
+ points = args[0];
653
+ } else {
654
+ points = args;
655
+ }
656
+ return {
657
+ color,
658
+ points
659
+ };
660
+ }
661
+ };
662
+
663
+ // src/drawing/sprite-screen.ts
664
+ var SpriteScreen = class extends PrimitiveScreen {
665
+ static {
666
+ __name(this, "SpriteScreen");
667
+ }
668
+ // Cache imageSmoothingEnabled — only set when pixelated flag changes
669
+ _lastImageSmoothing = true;
670
+ // Cache frame time once per draw frame instead of per-sprite
671
+ _frameTime = 0;
672
+ /**
673
+ * Initialize draw state (called before each draw frame)
674
+ */
675
+ initDraw() {
676
+ super.initDraw();
677
+ this._frameTime = performance.now();
678
+ }
679
+ /**
680
+ * Set imageSmoothingEnabled only when it actually changes
681
+ */
682
+ setImageSmoothing() {
683
+ const smooth = !this.pixelated;
684
+ if (smooth !== this._lastImageSmoothing) {
685
+ this.context.imageSmoothingEnabled = smooth;
686
+ this._lastImageSmoothing = smooth;
687
+ }
688
+ }
689
+ /**
690
+ * Get the canvas for the current sprite frame
691
+ */
692
+ getSpriteFrame(sprite) {
693
+ let frame = null;
694
+ if (typeof sprite === "string") {
695
+ const spriteName = sprite;
696
+ let spriteObj2 = null;
697
+ if (this.runtime && this.runtime.sprites) {
698
+ spriteObj2 = this.runtime.sprites[sprite];
699
+ }
700
+ if (!spriteObj2) {
701
+ const parts = sprite.split(".");
702
+ if (parts.length > 1 && this.runtime && this.runtime.sprites) {
703
+ spriteObj2 = this.runtime.sprites[parts[0]];
704
+ frame = Number.parseInt(parts[1]) || 0;
705
+ }
706
+ }
707
+ if (!spriteObj2) {
708
+ reportRuntimeError3(this.runtime?.listener, APIErrorCode3.E7004, {
709
+ spriteName
710
+ });
711
+ return null;
712
+ }
713
+ sprite = spriteObj2;
714
+ } else if (sprite && typeof sprite === "object" && sprite.canvas && !sprite.frames) {
715
+ return sprite.canvas || sprite.image || null;
716
+ }
717
+ if (!sprite || !sprite.ready) {
718
+ const spriteName = typeof sprite === "string" ? sprite : "unknown";
719
+ reportRuntimeError3(this.runtime?.listener, APIErrorCode3.E7005, {
720
+ spriteName
721
+ });
722
+ return null;
723
+ }
724
+ const spriteObj = sprite;
725
+ if (spriteObj.frames && spriteObj.frames.length > 1) {
726
+ if (frame === null) {
727
+ if (spriteObj.animation_start === 0) {
728
+ spriteObj.animation_start = this._frameTime;
729
+ }
730
+ const dt = 1e3 / spriteObj.fps;
731
+ frame = Math.floor((this._frameTime - spriteObj.animation_start) / dt) % spriteObj.frames.length;
732
+ }
733
+ if (frame >= 0 && frame < spriteObj.frames.length) {
734
+ return spriteObj.frames[frame].canvas;
735
+ }
736
+ return spriteObj.frames[0].canvas;
737
+ } else if (spriteObj.frames && spriteObj.frames[0]) {
738
+ return spriteObj.frames[0].canvas;
739
+ }
740
+ return null;
741
+ }
742
+ /**
743
+ * Draw a sprite
744
+ */
745
+ drawSprite(sprite, x, y, w, h) {
746
+ const canvas = this.getSpriteFrame(sprite);
747
+ if (!canvas) return;
748
+ if (w == null) {
749
+ w = canvas.width;
750
+ }
751
+ if (!h) {
752
+ h = w / canvas.width * canvas.height;
753
+ }
754
+ if (!this.screen_transform && this.object_rotation === 0 && this.object_scale_x === 1 && this.object_scale_y === 1) {
755
+ const drawX = x - w / 2 - this.anchor_x * w / 2;
756
+ const drawY = -y - h / 2 + this.anchor_y * h / 2;
757
+ const halfW = this.width / 2;
758
+ const halfH = this.height / 2;
759
+ if (drawX > halfW || drawX + w < -halfW || drawY > halfH || drawY + h < -halfH) {
760
+ return;
761
+ }
762
+ }
763
+ this.context.globalAlpha = this.alpha;
764
+ this.setImageSmoothing();
765
+ if (this.initDrawOp(x, -y)) {
766
+ this.context.drawImage(canvas, -w / 2 - this.anchor_x * w / 2, -h / 2 + this.anchor_y * h / 2, w, h);
767
+ this.closeDrawOp();
768
+ } else {
769
+ this.context.drawImage(canvas, x - w / 2 - this.anchor_x * w / 2, -y - h / 2 + this.anchor_y * h / 2, w, h);
770
+ }
771
+ }
772
+ /**
773
+ * Draw a portion of a sprite
774
+ */
775
+ drawSpritePart(sprite, sx, sy, sw, sh, x, y, w, h) {
776
+ const canvas = this.getSpriteFrame(sprite);
777
+ if (!canvas) return;
778
+ if (w == null) {
779
+ w = sw;
780
+ }
781
+ if (!h) {
782
+ h = w / sw * sh;
783
+ }
784
+ if (!this.screen_transform && this.object_rotation === 0 && this.object_scale_x === 1 && this.object_scale_y === 1) {
785
+ const drawX = x - w / 2 - this.anchor_x * w / 2;
786
+ const drawY = -y - h / 2 + this.anchor_y * h / 2;
787
+ const halfW = this.width / 2;
788
+ const halfH = this.height / 2;
789
+ if (drawX > halfW || drawX + w < -halfW || drawY > halfH || drawY + h < -halfH) {
790
+ return;
791
+ }
792
+ }
793
+ this.context.globalAlpha = this.alpha;
794
+ this.setImageSmoothing();
795
+ if (this.initDrawOp(x, -y)) {
796
+ this.context.drawImage(canvas, sx, sy, sw, sh, -w / 2 - this.anchor_x * w / 2, -h / 2 + this.anchor_y * h / 2, w, h);
797
+ this.closeDrawOp();
798
+ } else {
799
+ this.context.drawImage(canvas, sx, sy, sw, sh, x - w / 2 - this.anchor_x * w / 2, -y - h / 2 + this.anchor_y * h / 2, w, h);
800
+ }
801
+ }
802
+ /**
803
+ * Draw a map
804
+ */
805
+ drawMap(map, x, y, w, h) {
806
+ let mapObj = null;
807
+ if (typeof map === "string") {
808
+ if (this.runtime && this.runtime.maps) {
809
+ mapObj = this.runtime.maps[map];
810
+ }
811
+ } else {
812
+ mapObj = map;
813
+ }
814
+ if (!(mapObj && mapObj.ready)) {
815
+ return;
816
+ }
817
+ this.context.globalAlpha = this.alpha;
818
+ this.setImageSmoothing();
819
+ if (this.initDrawOp(x, -y)) {
820
+ mapObj.draw(this.context, -w / 2 - this.anchor_x * w / 2, -h / 2 + this.anchor_y * h / 2, w, h);
821
+ this.closeDrawOp();
822
+ } else {
823
+ mapObj.draw(this.context, x - w / 2 - this.anchor_x * w / 2, -y - h / 2 + this.anchor_y * h / 2, w, h);
824
+ }
825
+ }
826
+ };
827
+
828
+ // src/drawing/text-screen.ts
829
+ var TextScreen = class extends SpriteScreen {
830
+ static {
831
+ __name(this, "TextScreen");
832
+ }
833
+ // Font string cache — avoid rebuilding template string every draw call
834
+ _cachedFontSize = -1;
835
+ _cachedFontName = "";
836
+ _cachedFontString = "";
837
+ getFontString(size) {
838
+ if (size !== this._cachedFontSize || this.font !== this._cachedFontName) {
839
+ this._cachedFontSize = size;
840
+ this._cachedFontName = this.font;
841
+ this._cachedFontString = `${size}pt ${this.font}`;
842
+ }
843
+ return this._cachedFontString;
844
+ }
845
+ textWidth(text, size) {
846
+ this.context.font = this.getFontString(size);
847
+ return this.context.measureText(text).width;
848
+ }
849
+ drawText(text, x, y, size, color) {
850
+ if (color) this.setColor(color);
851
+ this.context.globalAlpha = this.alpha;
852
+ this.context.font = this.getFontString(size);
853
+ this.context.textAlign = "center";
854
+ this.context.textBaseline = "middle";
855
+ const w = this.context.measureText(text).width;
856
+ const h = size;
857
+ if (this.initDrawOp(x, -y)) {
858
+ this.context.fillText(text, 0 - this.anchor_x * w / 2, 0 + this.anchor_y * h / 2);
859
+ this.closeDrawOp();
860
+ } else {
861
+ this.context.fillText(text, x - this.anchor_x * w / 2, -y + this.anchor_y * h / 2);
862
+ }
863
+ }
864
+ drawTextOutline(text, x, y, size, color) {
865
+ if (color) this.setColor(color);
866
+ this.context.globalAlpha = this.alpha;
867
+ this.context.font = this.getFontString(size);
868
+ this.context.lineWidth = this.line_width;
869
+ this.context.textAlign = "center";
870
+ this.context.textBaseline = "middle";
871
+ const w = this.context.measureText(text).width;
872
+ const h = size;
873
+ if (this.initDrawOp(x, -y)) {
874
+ this.context.strokeText(text, 0 - this.anchor_x * w / 2, 0 + this.anchor_y * h / 2);
875
+ this.closeDrawOp();
876
+ } else {
877
+ this.context.strokeText(text, x - this.anchor_x * w / 2, -y + this.anchor_y * h / 2);
878
+ }
879
+ }
880
+ };
881
+ export {
882
+ TextScreen
883
+ };
884
+ //# sourceMappingURL=text-screen.mjs.map