kei-lisp-plugin-graphics 3.0.1 → 4.0.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Cons, InterpretedSymbol } from "kei-lisp";
1
+ import { Cons, EvalError, InterpretedSymbol, Numeric } from "kei-lisp";
2
2
  //#region src/GraphicsPlugin/index.ts
3
3
  const LINE_CAPS = /* @__PURE__ */ new Set([
4
4
  "butt",
@@ -99,8 +99,15 @@ const COMPOSITE_OPERATIONS = /* @__PURE__ */ new Set([
99
99
  * @class
100
100
  * @classdesc Canvas2D drawing plugin for the kei-lisp interpreter. Implements
101
101
  * the `KeiLispPlugin` contract (`name` / `has` / `apply`) and
102
- * exposes 75 `g…` Lisp functions (plus two deprecated aliases)
103
- * that proxy to a 2D rendering context.
102
+ * exposes 75 `g…` Lisp functions that proxy to a 2D rendering
103
+ * context. Failures (wrong arity,
104
+ * type mismatch, closed canvas, canvas-level errors) signal an
105
+ * `EvalError` that Lisp callers can intercept with
106
+ * `(handler-case … (eval-error (e) …))`. Two exceptions still go
107
+ * to `process.stderr` as diagnostics: asynchronous work (image
108
+ * loading, OffscreenCanvas file writes), which fails after the
109
+ * call has returned, and `selectColor`'s best-effort color
110
+ * parsing, which falls back to black instead of failing.
104
111
  * @author Keisuke Ikeda
105
112
  */
106
113
  var GraphicsPlugin = class GraphicsPlugin {
@@ -163,8 +170,6 @@ var GraphicsPlugin = class GraphicsPlugin {
163
170
  aTable.set(InterpretedSymbol.of("gtext-direction"), "gTextDirection");
164
171
  aTable.set(InterpretedSymbol.of("gtext-font"), "gTextFont");
165
172
  aTable.set(InterpretedSymbol.of("gtext-baseline"), "gTextBaseline");
166
- aTable.set(InterpretedSymbol.of("gtext-dire"), "gTextDirection");
167
- aTable.set(InterpretedSymbol.of("gtext-line"), "gTextBaseline");
168
173
  aTable.set(InterpretedSymbol.of("gtranslate"), "gTranslate");
169
174
  aTable.set(InterpretedSymbol.of("grect"), "gRect");
170
175
  aTable.set(InterpretedSymbol.of("grotate"), "gRotate");
@@ -228,11 +233,13 @@ var GraphicsPlugin = class GraphicsPlugin {
228
233
  /**
229
234
  * Writes a diagnostic line to `process.stderr`, matching the convention
230
235
  * used by kei-lisp itself (`Applier.format` writes to `process.stdout`).
231
- * In a Node runtime this hits the real stderr; in a browser kei-lisp host
232
- * (e.g. kei-lisp-web) the host typically swaps `process.stderr.write` for
233
- * a sink that routes to the REPL output panel. In a plain browser with no
234
- * `process` shim at all, the line falls back to `console.error` instead of
235
- * throwing.
236
+ * Used only where an `EvalError` cannot reach the caller: asynchronous
237
+ * work (image loading), best-effort color parsing, and the informational
238
+ * line printed by `gopen`. In a Node runtime this hits the real stderr; in
239
+ * a browser kei-lisp host (e.g. kei-lisp-web) the host typically swaps
240
+ * `process.stderr.write` for a sink that routes to the REPL output panel.
241
+ * In a plain browser with no `process` shim at all, the line falls back to
242
+ * `console.error` instead of throwing.
236
243
  * @param line - the line to write
237
244
  */
238
245
  #print(line) {
@@ -246,7 +253,9 @@ var GraphicsPlugin = class GraphicsPlugin {
246
253
  /**
247
254
  * Resolves an image for the given source and runs `draw` with it —
248
255
  * synchronously when the image is already loaded, on its `load` event
249
- * otherwise. A load failure prints a diagnostic once.
256
+ * otherwise. A load failure prints a diagnostic once (the failure happens
257
+ * asynchronously, after the calling `g…` function has already returned, so
258
+ * it cannot signal an `EvalError`).
250
259
  * @param source - the image URL / data URI
251
260
  * @param draw - the drawing action to run once the image is available
252
261
  */
@@ -269,26 +278,37 @@ var GraphicsPlugin = class GraphicsPlugin {
269
278
  });
270
279
  }
271
280
  /**
272
- * Shared guard-and-dispatch skeleton for the newer `g…` methods: checks the
273
- * context and the open flag, runs `body`, and converts both a `null` return
274
- * and a thrown exception into `failureMessage` + `Cons.nil`.
275
- * @param failureMessage - the diagnostic printed when `body` fails
281
+ * Returns the 2D context, signaling an `EvalError` when the canvas exposes
282
+ * no usable context or has not been opened with `gopen`.
283
+ * @return the 2D rendering context
284
+ */
285
+ #requireOpenContext() {
286
+ if (this.ctx === null) throw new EvalError("Unable to initialize canvas. The browser or machine may not support it.");
287
+ if (!this.isOpen) throw new EvalError("The canvas is closed and cannot be executed.");
288
+ return this.ctx;
289
+ }
290
+ /**
291
+ * Shared guard-and-dispatch skeleton for the `g…` methods: requires an
292
+ * open canvas context, runs `body`, and signals an `EvalError` carrying
293
+ * `failureMessage` when `body` returns `null` (bad arguments) or throws a
294
+ * canvas-level error. The error propagates through the evaluator, where
295
+ * Lisp callers can intercept it with
296
+ * `(handler-case … (eval-error (e) …))`.
297
+ * @param failureMessage - the message of the signaled `EvalError`
276
298
  * @param body - the drawing action; returns the Lisp result, or `null` on
277
299
  * bad arguments
278
- * @return the body's result, or `Cons.nil` on failure
300
+ * @return the body's result
279
301
  */
280
302
  #execute(failureMessage, body) {
281
- if (!this.checkSupport()) return Cons.nil;
282
- if (!this.isOpen) {
283
- this.#print("The canvas is closed and cannot be executed.");
284
- return Cons.nil;
285
- }
303
+ const context = this.#requireOpenContext();
304
+ let result;
286
305
  try {
287
- const result = body(this.ctx);
288
- if (result !== null) return result;
289
- } catch {}
290
- this.#print(failureMessage);
291
- return Cons.nil;
306
+ result = body(context);
307
+ } catch (error) {
308
+ throw error instanceof EvalError ? error : new EvalError(failureMessage);
309
+ }
310
+ if (result === null) throw new EvalError(failureMessage);
311
+ return result;
292
312
  }
293
313
  /**
294
314
  * Reads a single string argument and validates it against an allowlist.
@@ -302,7 +322,9 @@ var GraphicsPlugin = class GraphicsPlugin {
302
322
  return allowed.has(arguments_.car) ? arguments_.car : null;
303
323
  }
304
324
  /**
305
- * Reads exactly `count` numbers from the argument list.
325
+ * Reads exactly `count` numbers from the argument list. kei-lisp v3
326
+ * evaluates integers to `bigint` and exact division to `Rational`, so each
327
+ * value is converted to a JS float for the Canvas 2D API.
306
328
  * @param arguments_ - the evaluated argument list
307
329
  * @param count - the expected argument count
308
330
  * @return the numbers, or `null` on wrong arity or a non-number argument
@@ -313,7 +335,7 @@ var GraphicsPlugin = class GraphicsPlugin {
313
335
  let rest = arguments_;
314
336
  for (let index = 0; index < count; index++) {
315
337
  if (!Cons.isNumber(rest.car)) return null;
316
- values.push(rest.car);
338
+ values.push(Numeric.toFloat(rest.car));
317
339
  rest = rest.cdr;
318
340
  }
319
341
  return values;
@@ -358,7 +380,7 @@ var GraphicsPlugin = class GraphicsPlugin {
358
380
  const offset = stops[index];
359
381
  const color = stops[index + 1];
360
382
  if (!Cons.isNumber(offset) || !Cons.isString(color)) return false;
361
- gradient.addColorStop(offset, color);
383
+ gradient.addColorStop(Numeric.toFloat(offset), color);
362
384
  }
363
385
  return true;
364
386
  }
@@ -383,13 +405,10 @@ var GraphicsPlugin = class GraphicsPlugin {
383
405
  * no `document` — those callers must pass a file path instead.
384
406
  * @param mimeType - the image MIME type to encode
385
407
  * @param label - the format name used in diagnostics ("jpeg" / "png")
386
- * @return `t` on success, `Cons.nil` otherwise
408
+ * @return `t` on success
387
409
  */
388
410
  #downloadCanvas(mimeType, label) {
389
- if (typeof document === "undefined" || !("toDataURL" in this.canvas)) {
390
- this.#print(`Can not save ${label}. Browser download needs a DOM and an HTMLCanvasElement; pass a file path to save on Node.js.`);
391
- return Cons.nil;
392
- }
411
+ if (typeof document === "undefined" || !("toDataURL" in this.canvas)) throw new EvalError(`Can not save ${label}. Browser download needs a DOM and an HTMLCanvasElement; pass a file path to save on Node.js.`);
393
412
  try {
394
413
  const link = document.createElement("a");
395
414
  link.href = this.canvas.toDataURL(mimeType);
@@ -399,8 +418,7 @@ var GraphicsPlugin = class GraphicsPlugin {
399
418
  link.remove();
400
419
  return InterpretedSymbol.of("t");
401
420
  } catch {
402
- this.#print(`Can not save ${label}. If you are using an image in the canvas, you can't save ${label}.`);
403
- return Cons.nil;
421
+ throw new EvalError(`Can not save ${label}. If you are using an image in the canvas, you can't save ${label}.`);
404
422
  }
405
423
  }
406
424
  /**
@@ -413,14 +431,12 @@ var GraphicsPlugin = class GraphicsPlugin {
413
431
  * @param path - the destination file path
414
432
  * @param mimeType - the image MIME type to encode
415
433
  * @param label - the format name used in diagnostics ("jpeg" / "png")
416
- * @return `t` on success, `Cons.nil` otherwise
434
+ * @return `t` on success
417
435
  */
418
436
  #writeCanvasToFile(path, mimeType, label) {
419
- const fs = typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("node:fs") : void 0;
420
- if (fs === void 0) {
421
- this.#print(`Can not save ${label}. Saving to a file path requires Node.js.`);
422
- return Cons.nil;
423
- }
437
+ const getBuiltinModule = globalThis.process?.getBuiltinModule;
438
+ const fs = typeof getBuiltinModule === "function" ? getBuiltinModule("node:fs") : void 0;
439
+ if (fs === void 0) throw new EvalError(`Can not save ${label}. Saving to a file path requires Node.js.`);
424
440
  try {
425
441
  if ("toDataURL" in this.canvas) {
426
442
  const dataUrl = this.canvas.toDataURL(mimeType);
@@ -430,11 +446,12 @@ var GraphicsPlugin = class GraphicsPlugin {
430
446
  }
431
447
  this.canvas.convertToBlob({ type: mimeType }).then(async (blob) => {
432
448
  fs.writeFileSync(path, new Uint8Array(await blob.arrayBuffer()));
449
+ }).catch(() => {
450
+ this.#print(`Can not save ${label}.`);
433
451
  });
434
452
  return InterpretedSymbol.of("t");
435
453
  } catch {
436
- this.#print(`Can not save ${label}.`);
437
- return Cons.nil;
454
+ throw new EvalError(`Can not save ${label}.`);
438
455
  }
439
456
  }
440
457
  /**
@@ -450,127 +467,45 @@ var GraphicsPlugin = class GraphicsPlugin {
450
467
  * @param aSymbol - the call symbol
451
468
  * @param arguments_ - the evaluated argument list
452
469
  * @param _context - the interpreter context (unused by this plugin)
453
- * @return the method's result, or `Cons.nil` if dispatch fails
470
+ * @return the method's result
454
471
  */
455
472
  apply(aSymbol, arguments_, _context) {
456
473
  const methodName = GraphicsPlugin.#builtInFunctions.get(aSymbol);
457
- if (methodName === void 0) {
458
- this.#print(`I could find no procedure description for ${String(aSymbol)}`);
459
- return Cons.nil;
460
- }
474
+ if (methodName === void 0) throw new EvalError(`I could find no procedure description for ${String(aSymbol)}`);
461
475
  return this[methodName].call(this, arguments_);
462
476
  }
463
- /**
464
- * Checks whether the canvas exposes a usable 2D context, and narrows
465
- * `this.ctx` to non-null for the caller when it returns true.
466
- * @return type predicate — true when context is non-null
467
- */
468
- checkSupport() {
469
- if (this.ctx === null) {
470
- this.#print("Unable to initialize canvas. The browser or machine may not support it.");
471
- return false;
472
- }
473
- return true;
474
- }
475
477
  gAlpha(arguments_) {
476
- if (!this.checkSupport()) return Cons.nil;
477
- if (this.isOpen) try {
478
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
479
- const aNumber = arguments_.car <= 0 ? 0 : arguments_.car >= 1 ? 1 : arguments_.car;
480
- this.ctx.globalAlpha = aNumber;
481
- return InterpretedSymbol.of("t");
482
- }
483
- this.#print("Can not set alpha.");
484
- return Cons.nil;
485
- } catch {
486
- this.#print("Can not set alpha.");
487
- return Cons.nil;
488
- }
489
- this.#print("The canvas is closed and cannot be executed.");
490
- return Cons.nil;
478
+ return this.#execute("Can not set alpha.", (context) => {
479
+ const a = this.#numbers(arguments_, 1);
480
+ if (a === null) return null;
481
+ context.globalAlpha = a[0] <= 0 ? 0 : a[0] >= 1 ? 1 : a[0];
482
+ return InterpretedSymbol.of("t");
483
+ });
491
484
  }
492
485
  gArc(arguments_) {
493
- if (!this.checkSupport()) return Cons.nil;
494
- if (this.isOpen) try {
495
- if (arguments_.length() === 6) {
496
- const a0 = arguments_.car;
497
- const cdr1 = arguments_.cdr;
498
- const a1 = cdr1.car;
499
- const cdr2 = cdr1.cdr;
500
- const a2 = cdr2.car;
501
- const cdr3 = cdr2.cdr;
502
- const a3 = cdr3.car;
503
- const cdr4 = cdr3.cdr;
504
- const a4 = cdr4.car;
505
- const a5 = cdr4.cdr.car;
506
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
507
- const isAFlag = a5 >= 0;
508
- this.ctx.arc(a0, a1, a2, Math.PI / 180 * a3, Math.PI / 180 * a4, isAFlag);
509
- return InterpretedSymbol.of("t");
510
- }
511
- }
512
- this.#print("Can not draw arc.");
513
- return Cons.nil;
514
- } catch {
515
- this.#print("Can not draw arc.");
516
- return Cons.nil;
517
- }
518
- this.#print("The canvas is closed and cannot be executed.");
519
- return Cons.nil;
486
+ return this.#execute("Can not draw arc.", (context) => {
487
+ const a = this.#numbers(arguments_, 6);
488
+ if (a === null) return null;
489
+ const isAFlag = a[5] >= 0;
490
+ context.arc(a[0], a[1], a[2], Math.PI / 180 * a[3], Math.PI / 180 * a[4], isAFlag);
491
+ return InterpretedSymbol.of("t");
492
+ });
520
493
  }
521
494
  gArcTo(arguments_) {
522
- if (!this.checkSupport()) return Cons.nil;
523
- if (this.isOpen) try {
524
- if (arguments_.length() === 5) {
525
- const a0 = arguments_.car;
526
- const cdr1 = arguments_.cdr;
527
- const a1 = cdr1.car;
528
- const cdr2 = cdr1.cdr;
529
- const a2 = cdr2.car;
530
- const cdr3 = cdr2.cdr;
531
- const a3 = cdr3.car;
532
- const a4 = cdr3.cdr.car;
533
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4)) {
534
- this.ctx.arcTo(a0, a1, a2, a3, a4);
535
- return InterpretedSymbol.of("t");
536
- }
537
- }
538
- this.#print("Can not draw arc to.");
539
- return Cons.nil;
540
- } catch {
541
- this.#print("Can not draw arc to.");
542
- return Cons.nil;
543
- }
544
- this.#print("The canvas is closed and cannot be executed.");
545
- return Cons.nil;
495
+ return this.#execute("Can not draw arc to.", (context) => {
496
+ const a = this.#numbers(arguments_, 5);
497
+ if (a === null) return null;
498
+ context.arcTo(a[0], a[1], a[2], a[3], a[4]);
499
+ return InterpretedSymbol.of("t");
500
+ });
546
501
  }
547
502
  gBezCurveTo(arguments_) {
548
- if (!this.checkSupport()) return Cons.nil;
549
- if (this.isOpen) try {
550
- if (arguments_.length() === 6) {
551
- const a0 = arguments_.car;
552
- const cdr1 = arguments_.cdr;
553
- const a1 = cdr1.car;
554
- const cdr2 = cdr1.cdr;
555
- const a2 = cdr2.car;
556
- const cdr3 = cdr2.cdr;
557
- const a3 = cdr3.car;
558
- const cdr4 = cdr3.cdr;
559
- const a4 = cdr4.car;
560
- const a5 = cdr4.cdr.car;
561
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
562
- this.ctx.bezierCurveTo(a0, a1, a2, a3, a4, a5);
563
- return InterpretedSymbol.of("t");
564
- }
565
- }
566
- this.#print("Can not draw bezier curve.");
567
- return Cons.nil;
568
- } catch {
569
- this.#print("Can not draw bezier curve.");
570
- return Cons.nil;
571
- }
572
- this.#print("The canvas is closed and cannot be executed.");
573
- return Cons.nil;
503
+ return this.#execute("Can not draw bezier curve.", (context) => {
504
+ const a = this.#numbers(arguments_, 6);
505
+ if (a === null) return null;
506
+ context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]);
507
+ return InterpretedSymbol.of("t");
508
+ });
574
509
  }
575
510
  gClear(arguments_) {
576
511
  return this.#execute("Can not clear.", (context) => {
@@ -582,89 +517,48 @@ var GraphicsPlugin = class GraphicsPlugin {
582
517
  return InterpretedSymbol.of("t");
583
518
  });
584
519
  }
585
- gClose() {
586
- if (!this.checkSupport()) return Cons.nil;
587
- if (this.isOpen) try {
520
+ gClose(arguments_ = Cons.nil) {
521
+ if (this.ctx === null) throw new EvalError("Unable to initialize canvas. The browser or machine may not support it.");
522
+ if (arguments_.length() !== 0) throw new EvalError("Can not close.");
523
+ if (!this.isOpen) throw new EvalError("The canvas has already been closed.");
524
+ try {
588
525
  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
589
- this.isOpen = false;
590
- return InterpretedSymbol.of("t");
591
526
  } catch {
592
- this.#print("Can not close.");
593
- return Cons.nil;
527
+ throw new EvalError("Can not close.");
594
528
  }
595
- this.#print("The canvas has already been closed.");
596
- return Cons.nil;
529
+ this.isOpen = false;
530
+ return InterpretedSymbol.of("t");
597
531
  }
598
532
  gColor(arguments_) {
599
- if (!this.checkSupport()) return Cons.nil;
600
- if (this.isOpen) try {
601
- if (arguments_.length() >= 1) {
602
- const aColor = this.selectColor(arguments_);
603
- this.ctx.fillStyle = aColor;
604
- this.ctx.strokeStyle = aColor;
605
- return InterpretedSymbol.of("t");
606
- }
607
- this.#print("Can not set color.");
608
- return Cons.nil;
609
- } catch {
610
- this.#print("Can not set color.");
611
- return Cons.nil;
612
- }
613
- this.#print("The canvas is closed and cannot be executed.");
614
- return Cons.nil;
533
+ return this.#execute("Can not set color.", (context) => {
534
+ if (arguments_.length() < 1) return null;
535
+ const aColor = this.selectColor(arguments_);
536
+ context.fillStyle = aColor;
537
+ context.strokeStyle = aColor;
538
+ return InterpretedSymbol.of("t");
539
+ });
615
540
  }
616
- gFill() {
617
- if (!this.checkSupport()) return Cons.nil;
618
- if (this.isOpen) try {
619
- this.ctx.fill();
541
+ gFill(arguments_ = Cons.nil) {
542
+ return this.#execute("Can not fill.", (context) => {
543
+ if (arguments_.length() !== 0) return null;
544
+ context.fill();
620
545
  return InterpretedSymbol.of("t");
621
- } catch {
622
- this.#print("Can not fill.");
623
- return Cons.nil;
624
- }
625
- this.#print("The canvas is closed and cannot be executed.");
626
- return Cons.nil;
546
+ });
627
547
  }
628
548
  gFillColor(arguments_) {
629
- if (!this.checkSupport()) return Cons.nil;
630
- if (this.isOpen) try {
631
- if (arguments_.length() >= 1) {
632
- const aColor = this.selectColor(arguments_);
633
- this.ctx.fillStyle = aColor;
634
- return InterpretedSymbol.of("t");
635
- }
636
- this.#print("Can not set fill color.");
637
- return Cons.nil;
638
- } catch {
639
- this.#print("Can not set fill color.");
640
- return Cons.nil;
641
- }
642
- this.#print("The canvas is closed and cannot be executed.");
643
- return Cons.nil;
549
+ return this.#execute("Can not set fill color.", (context) => {
550
+ if (arguments_.length() < 1) return null;
551
+ context.fillStyle = this.selectColor(arguments_);
552
+ return InterpretedSymbol.of("t");
553
+ });
644
554
  }
645
555
  gFillRect(arguments_) {
646
- if (!this.checkSupport()) return Cons.nil;
647
- if (this.isOpen) try {
648
- if (arguments_.length() === 4) {
649
- const a0 = arguments_.car;
650
- const cdr1 = arguments_.cdr;
651
- const a1 = cdr1.car;
652
- const cdr2 = cdr1.cdr;
653
- const a2 = cdr2.car;
654
- const a3 = cdr2.cdr.car;
655
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
656
- this.ctx.fillRect(a0, a1, a2, a3);
657
- return InterpretedSymbol.of("t");
658
- }
659
- }
660
- this.#print("Can not draw fill rectangle.");
661
- return Cons.nil;
662
- } catch {
663
- this.#print("Can not draw fill rectangle.");
664
- return Cons.nil;
665
- }
666
- this.#print("The canvas is closed and cannot be executed.");
667
- return Cons.nil;
556
+ return this.#execute("Can not draw fill rectangle.", (context) => {
557
+ const a = this.#numbers(arguments_, 4);
558
+ if (a === null) return null;
559
+ context.fillRect(a[0], a[1], a[2], a[3]);
560
+ return InterpretedSymbol.of("t");
561
+ });
668
562
  }
669
563
  gFillText(arguments_) {
670
564
  return this.#execute("Can not draw fill text.", (context) => {
@@ -674,54 +568,29 @@ var GraphicsPlugin = class GraphicsPlugin {
674
568
  if (!Cons.isString(a0) || !Cons.isNumber(a1) || !Cons.isNumber(a2)) return null;
675
569
  if (length_ === 4) {
676
570
  if (!Cons.isNumber(a3)) return null;
677
- context.fillText(a0, a1, a2, a3);
678
- } else context.fillText(a0, a1, a2);
571
+ context.fillText(a0, Numeric.toFloat(a1), Numeric.toFloat(a2), Numeric.toFloat(a3));
572
+ } else context.fillText(a0, Numeric.toFloat(a1), Numeric.toFloat(a2));
679
573
  return InterpretedSymbol.of("t");
680
574
  });
681
575
  }
682
576
  gFillTri(arguments_) {
683
- if (!this.checkSupport()) return Cons.nil;
684
- if (this.isOpen) try {
685
- if (arguments_.length() === 6) {
686
- const a0 = arguments_.car;
687
- const cdr1 = arguments_.cdr;
688
- const a1 = cdr1.car;
689
- const cdr2 = cdr1.cdr;
690
- const a2 = cdr2.car;
691
- const cdr3 = cdr2.cdr;
692
- const a3 = cdr3.car;
693
- const cdr4 = cdr3.cdr;
694
- const a4 = cdr4.car;
695
- const a5 = cdr4.cdr.car;
696
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
697
- this.ctx.beginPath();
698
- this.ctx.moveTo(a0, a1);
699
- this.ctx.lineTo(a2, a3);
700
- this.ctx.lineTo(a4, a5);
701
- this.ctx.fill();
702
- return InterpretedSymbol.of("t");
703
- }
704
- }
705
- this.#print("Can not draw fill triangle.");
706
- return Cons.nil;
707
- } catch {
708
- this.#print("Can not draw fill triangle.");
709
- return Cons.nil;
710
- }
711
- this.#print("The canvas is closed and cannot be executed.");
712
- return Cons.nil;
577
+ return this.#execute("Can not draw fill triangle.", (context) => {
578
+ const a = this.#numbers(arguments_, 6);
579
+ if (a === null) return null;
580
+ context.beginPath();
581
+ context.moveTo(a[0], a[1]);
582
+ context.lineTo(a[2], a[3]);
583
+ context.lineTo(a[4], a[5]);
584
+ context.fill();
585
+ return InterpretedSymbol.of("t");
586
+ });
713
587
  }
714
- gFinishPath() {
715
- if (!this.checkSupport()) return Cons.nil;
716
- if (this.isOpen) try {
717
- this.ctx.closePath();
588
+ gFinishPath(arguments_ = Cons.nil) {
589
+ return this.#execute("Can not finish path.", (context) => {
590
+ if (arguments_.length() !== 0) return null;
591
+ context.closePath();
718
592
  return InterpretedSymbol.of("t");
719
- } catch {
720
- this.#print("Can not finish path.");
721
- return Cons.nil;
722
- }
723
- this.#print("The canvas is closed and cannot be executed.");
724
- return Cons.nil;
593
+ });
725
594
  }
726
595
  gImage(arguments_) {
727
596
  return this.#execute("Can not draw Image.", (context) => {
@@ -732,33 +601,21 @@ var GraphicsPlugin = class GraphicsPlugin {
732
601
  if (length_ === 5) {
733
602
  if (!Cons.isNumber(a3) || !Cons.isNumber(a4)) return null;
734
603
  this.#withImage(a0, (image) => {
735
- context.drawImage(image, a1, a2, a3, a4);
604
+ context.drawImage(image, Numeric.toFloat(a1), Numeric.toFloat(a2), Numeric.toFloat(a3), Numeric.toFloat(a4));
736
605
  });
737
606
  } else this.#withImage(a0, (image) => {
738
- context.drawImage(image, a1, a2);
607
+ context.drawImage(image, Numeric.toFloat(a1), Numeric.toFloat(a2));
739
608
  });
740
609
  return InterpretedSymbol.of("t");
741
610
  });
742
611
  }
743
612
  gLineTo(arguments_) {
744
- if (!this.checkSupport()) return Cons.nil;
745
- if (this.isOpen) try {
746
- if (arguments_.length() === 2) {
747
- const a0 = arguments_.car;
748
- const a1 = arguments_.cdr.car;
749
- if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
750
- this.ctx.lineTo(a0, a1);
751
- return InterpretedSymbol.of("t");
752
- }
753
- }
754
- this.#print("Can not draw line to");
755
- return Cons.nil;
756
- } catch {
757
- this.#print("Can not draw line to");
758
- return Cons.nil;
759
- }
760
- this.#print("The canvas is closed and cannot be executed.");
761
- return Cons.nil;
613
+ return this.#execute("Can not draw line to", (context) => {
614
+ const a = this.#numbers(arguments_, 2);
615
+ if (a === null) return null;
616
+ context.lineTo(a[0], a[1]);
617
+ return InterpretedSymbol.of("t");
618
+ });
762
619
  }
763
620
  gLineCap(arguments_) {
764
621
  return this.#execute("Can not set line cap. Expected \"butt\" / \"round\" / \"square\".", (context) => {
@@ -777,57 +634,35 @@ var GraphicsPlugin = class GraphicsPlugin {
777
634
  });
778
635
  }
779
636
  gLineWidth(arguments_) {
780
- if (!this.checkSupport()) return Cons.nil;
781
- if (this.isOpen) try {
782
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
783
- const aNumber = arguments_.car <= 0 ? 1 : arguments_.car;
784
- this.ctx.lineWidth = aNumber;
785
- return InterpretedSymbol.of("t");
786
- }
787
- this.#print("Can not set line width.");
788
- return Cons.nil;
789
- } catch {
790
- this.#print("Can not set line width.");
791
- return Cons.nil;
792
- }
793
- this.#print("The canvas is closed and cannot be executed.");
794
- return Cons.nil;
637
+ return this.#execute("Can not set line width.", (context) => {
638
+ const a = this.#numbers(arguments_, 1);
639
+ if (a === null) return null;
640
+ context.lineWidth = a[0] <= 0 ? 1 : a[0];
641
+ return InterpretedSymbol.of("t");
642
+ });
795
643
  }
796
644
  gMoveTo(arguments_) {
797
- if (!this.checkSupport()) return Cons.nil;
798
- if (this.isOpen) try {
799
- if (arguments_.length() === 2) {
800
- const a0 = arguments_.car;
801
- const a1 = arguments_.cdr.car;
802
- if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
803
- this.ctx.moveTo(a0, a1);
804
- return InterpretedSymbol.of("t");
805
- }
806
- }
807
- this.#print("Can not move");
808
- return Cons.nil;
809
- } catch {
810
- this.#print("Can not move");
811
- return Cons.nil;
812
- }
813
- this.#print("The canvas is closed and cannot be executed.");
814
- return Cons.nil;
645
+ return this.#execute("Can not move", (context) => {
646
+ const a = this.#numbers(arguments_, 2);
647
+ if (a === null) return null;
648
+ context.moveTo(a[0], a[1]);
649
+ return InterpretedSymbol.of("t");
650
+ });
815
651
  }
816
- gOpen() {
817
- if (!this.checkSupport()) return Cons.nil;
818
- if (!this.isOpen) try {
819
- this.isOpen = true;
652
+ gOpen(arguments_ = Cons.nil) {
653
+ if (this.ctx === null) throw new EvalError("Unable to initialize canvas. The browser or machine may not support it.");
654
+ if (arguments_.length() !== 0) throw new EvalError("Can not open.");
655
+ if (this.isOpen) throw new EvalError("The canvas has already been opened.");
656
+ try {
820
657
  this.ctx.fillStyle = "#ffffff";
821
658
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
822
659
  this.ctx.fillStyle = "#000000";
823
- this.#print(`canvas size, width : ${this.canvas.width} height : ${this.canvas.height}`);
824
- return InterpretedSymbol.of("t");
825
660
  } catch {
826
- this.#print("Can not open.");
827
- return Cons.nil;
661
+ throw new EvalError("Can not open.");
828
662
  }
829
- this.#print("The canvas has already been opened.");
830
- return Cons.nil;
663
+ this.isOpen = true;
664
+ this.#print(`canvas size, width : ${this.canvas.width} height : ${this.canvas.height}`);
665
+ return InterpretedSymbol.of("t");
831
666
  }
832
667
  gPattern(arguments_) {
833
668
  return this.#execute("Can not set pattern. Expected an image source and a repetition (\"repeat\" / \"repeat-x\" / \"repeat-y\" / \"no-repeat\").", (context) => {
@@ -846,28 +681,12 @@ var GraphicsPlugin = class GraphicsPlugin {
846
681
  });
847
682
  }
848
683
  gQuadCurveTo(arguments_) {
849
- if (!this.checkSupport()) return Cons.nil;
850
- if (this.isOpen) try {
851
- if (arguments_.length() === 4) {
852
- const a0 = arguments_.car;
853
- const cdr1 = arguments_.cdr;
854
- const a1 = cdr1.car;
855
- const cdr2 = cdr1.cdr;
856
- const a2 = cdr2.car;
857
- const a3 = cdr2.cdr.car;
858
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
859
- this.ctx.quadraticCurveTo(a0, a1, a2, a3);
860
- return InterpretedSymbol.of("t");
861
- }
862
- }
863
- this.#print("Can not draw quadratic curve.");
864
- return Cons.nil;
865
- } catch {
866
- this.#print("Can not draw quadratic curve.");
867
- return Cons.nil;
868
- }
869
- this.#print("The canvas is closed and cannot be executed.");
870
- return Cons.nil;
684
+ return this.#execute("Can not draw quadratic curve.", (context) => {
685
+ const a = this.#numbers(arguments_, 4);
686
+ if (a === null) return null;
687
+ context.quadraticCurveTo(a[0], a[1], a[2], a[3]);
688
+ return InterpretedSymbol.of("t");
689
+ });
871
690
  }
872
691
  gSaveJpeg(arguments_) {
873
692
  return this.saveCanvas(arguments_, "image/jpeg", "jpeg");
@@ -882,177 +701,91 @@ var GraphicsPlugin = class GraphicsPlugin {
882
701
  * @param arguments_ - the evaluated argument list (empty, or one path string)
883
702
  * @param mimeType - the image MIME type to encode
884
703
  * @param label - the format name used in diagnostics ("jpeg" / "png")
885
- * @return `t` on success, `Cons.nil` otherwise
704
+ * @return `t` on success
886
705
  */
887
706
  saveCanvas(arguments_, mimeType, label) {
888
- if (!this.checkSupport()) return Cons.nil;
889
- if (this.isOpen) {
890
- const length_ = arguments_.length();
891
- if (length_ === 0) return this.#downloadCanvas(mimeType, label);
892
- if (length_ === 1 && Cons.isString(arguments_.car)) return this.#writeCanvasToFile(arguments_.car, mimeType, label);
893
- this.#print(`Can not save ${label}.`);
894
- return Cons.nil;
895
- }
896
- this.#print("The canvas is closed and cannot be executed.");
897
- return Cons.nil;
707
+ this.#requireOpenContext();
708
+ const length_ = arguments_.length();
709
+ if (length_ === 0) return this.#downloadCanvas(mimeType, label);
710
+ if (length_ === 1 && Cons.isString(arguments_.car)) return this.#writeCanvasToFile(arguments_.car, mimeType, label);
711
+ throw new EvalError(`Can not save ${label}.`);
898
712
  }
899
713
  gScale(arguments_) {
900
- if (!this.checkSupport()) return Cons.nil;
901
- if (this.isOpen) try {
902
- if (arguments_.length() === 2) {
903
- const a0 = arguments_.car;
904
- const a1 = arguments_.cdr.car;
905
- if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
906
- this.ctx.scale(a0, a1);
907
- return InterpretedSymbol.of("t");
908
- }
909
- }
910
- this.#print("Can not scale.");
911
- return Cons.nil;
912
- } catch {
913
- this.#print("Can not scale.");
914
- return Cons.nil;
915
- }
916
- this.#print("The canvas is closed and cannot be executed.");
917
- return Cons.nil;
714
+ return this.#execute("Can not scale.", (context) => {
715
+ const a = this.#numbers(arguments_, 2);
716
+ if (a === null) return null;
717
+ context.scale(a[0], a[1]);
718
+ return InterpretedSymbol.of("t");
719
+ });
918
720
  }
919
721
  gShadowBlur(arguments_) {
920
- if (!this.checkSupport()) return Cons.nil;
921
- if (this.isOpen) {
922
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
923
- this.ctx.shadowBlur = arguments_.car;
924
- return InterpretedSymbol.of("t");
925
- }
926
- this.#print("Can not set shadow blur.");
927
- return Cons.nil;
928
- }
929
- this.#print("The canvas is closed and cannot be executed.");
930
- return Cons.nil;
722
+ return this.#execute("Can not set shadow blur.", (context) => {
723
+ const a = this.#numbers(arguments_, 1);
724
+ if (a === null) return null;
725
+ context.shadowBlur = a[0];
726
+ return InterpretedSymbol.of("t");
727
+ });
931
728
  }
932
729
  gShadowColor(arguments_) {
933
- if (!this.checkSupport()) return Cons.nil;
934
- if (this.isOpen) try {
935
- if (arguments_.length() === 1) {
936
- const aColor = this.selectColor(arguments_);
937
- this.ctx.shadowColor = aColor;
938
- return InterpretedSymbol.of("t");
939
- }
940
- this.#print("Can not set shadow color.");
941
- return Cons.nil;
942
- } catch {
943
- this.#print("Can not set shadow color.");
944
- return Cons.nil;
945
- }
946
- this.#print("The canvas is closed and cannot be executed.");
947
- return Cons.nil;
730
+ return this.#execute("Can not set shadow color.", (context) => {
731
+ if (arguments_.length() < 1) return null;
732
+ context.shadowColor = this.selectColor(arguments_);
733
+ return InterpretedSymbol.of("t");
734
+ });
948
735
  }
949
736
  gShadowOffsetX(arguments_) {
950
- if (!this.checkSupport()) return Cons.nil;
951
- if (this.isOpen) {
952
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
953
- this.ctx.shadowOffsetX = arguments_.car;
954
- return InterpretedSymbol.of("t");
955
- }
956
- this.#print("Can not set shadow offsetX.");
957
- return Cons.nil;
958
- }
959
- this.#print("The canvas is closed and cannot be executed.");
960
- return Cons.nil;
737
+ return this.#execute("Can not set shadow offsetX.", (context) => {
738
+ const a = this.#numbers(arguments_, 1);
739
+ if (a === null) return null;
740
+ context.shadowOffsetX = a[0];
741
+ return InterpretedSymbol.of("t");
742
+ });
961
743
  }
962
744
  gShadowOffsetY(arguments_) {
963
- if (!this.checkSupport()) return Cons.nil;
964
- if (this.isOpen) {
965
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
966
- this.ctx.shadowOffsetY = arguments_.car;
967
- return InterpretedSymbol.of("t");
968
- }
969
- this.#print("Can not set shadow offsetY.");
970
- return Cons.nil;
971
- }
972
- this.#print("The canvas is closed and cannot be executed.");
973
- return Cons.nil;
745
+ return this.#execute("Can not set shadow offsetY.", (context) => {
746
+ const a = this.#numbers(arguments_, 1);
747
+ if (a === null) return null;
748
+ context.shadowOffsetY = a[0];
749
+ return InterpretedSymbol.of("t");
750
+ });
974
751
  }
975
752
  gSleep(arguments_) {
976
- if (!this.checkSupport()) return Cons.nil;
977
- if (this.isOpen) {
978
- const sleep = (ms) => {
979
- const time = Date.now() + ms;
980
- while (Date.now() < time);
981
- };
982
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
983
- sleep(arguments_.car);
984
- return InterpretedSymbol.of("t");
985
- }
986
- this.#print("Can not sleep");
987
- return Cons.nil;
988
- }
989
- this.#print("The canvas is closed and cannot be executed.");
990
- return Cons.nil;
753
+ return this.#execute("Can not sleep", () => {
754
+ const a = this.#numbers(arguments_, 1);
755
+ if (a === null) return null;
756
+ const time = Date.now() + a[0];
757
+ while (Date.now() < time);
758
+ return InterpretedSymbol.of("t");
759
+ });
991
760
  }
992
- gStartPath() {
993
- if (!this.checkSupport()) return Cons.nil;
994
- if (this.isOpen) try {
995
- this.ctx.beginPath();
761
+ gStartPath(arguments_ = Cons.nil) {
762
+ return this.#execute("Can not start path.", (context) => {
763
+ if (arguments_.length() !== 0) return null;
764
+ context.beginPath();
996
765
  return InterpretedSymbol.of("t");
997
- } catch {
998
- this.#print("Can not start path.");
999
- return Cons.nil;
1000
- }
1001
- this.#print("The canvas is closed and cannot be executed.");
1002
- return Cons.nil;
766
+ });
1003
767
  }
1004
- gStroke() {
1005
- if (!this.checkSupport()) return Cons.nil;
1006
- if (this.isOpen) try {
1007
- this.ctx.stroke();
768
+ gStroke(arguments_ = Cons.nil) {
769
+ return this.#execute("Can not stroke.", (context) => {
770
+ if (arguments_.length() !== 0) return null;
771
+ context.stroke();
1008
772
  return InterpretedSymbol.of("t");
1009
- } catch {
1010
- this.#print("Can not stroke.");
1011
- return Cons.nil;
1012
- }
1013
- this.#print("The canvas is closed and cannot be executed.");
1014
- return Cons.nil;
773
+ });
1015
774
  }
1016
775
  gStrokeColor(arguments_) {
1017
- if (!this.checkSupport()) return Cons.nil;
1018
- if (this.isOpen) try {
1019
- if (arguments_.length() >= 1) {
1020
- const aColor = this.selectColor(arguments_);
1021
- this.ctx.strokeStyle = aColor;
1022
- return InterpretedSymbol.of("t");
1023
- }
1024
- this.#print("Can not set stroke color");
1025
- return Cons.nil;
1026
- } catch {
1027
- this.#print("Can not set stroke color");
1028
- return Cons.nil;
1029
- }
1030
- this.#print("The canvas is closed and cannot be executed.");
1031
- return Cons.nil;
776
+ return this.#execute("Can not set stroke color", (context) => {
777
+ if (arguments_.length() < 1) return null;
778
+ context.strokeStyle = this.selectColor(arguments_);
779
+ return InterpretedSymbol.of("t");
780
+ });
1032
781
  }
1033
782
  gStrokeRect(arguments_) {
1034
- if (!this.checkSupport()) return Cons.nil;
1035
- if (this.isOpen) try {
1036
- if (arguments_.length() === 4) {
1037
- const a0 = arguments_.car;
1038
- const cdr1 = arguments_.cdr;
1039
- const a1 = cdr1.car;
1040
- const cdr2 = cdr1.cdr;
1041
- const a2 = cdr2.car;
1042
- const a3 = cdr2.cdr.car;
1043
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
1044
- this.ctx.strokeRect(a0, a1, a2, a3);
1045
- return InterpretedSymbol.of("t");
1046
- }
1047
- }
1048
- this.#print("Can not draw stroke rectangle.");
1049
- return Cons.nil;
1050
- } catch {
1051
- this.#print("Can not draw stroke rectangle.");
1052
- return Cons.nil;
1053
- }
1054
- this.#print("The canvas is closed and cannot be executed.");
1055
- return Cons.nil;
783
+ return this.#execute("Can not draw stroke rectangle.", (context) => {
784
+ const a = this.#numbers(arguments_, 4);
785
+ if (a === null) return null;
786
+ context.strokeRect(a[0], a[1], a[2], a[3]);
787
+ return InterpretedSymbol.of("t");
788
+ });
1056
789
  }
1057
790
  gStrokeText(arguments_) {
1058
791
  return this.#execute("Can not draw stroke text.", (context) => {
@@ -1062,43 +795,23 @@ var GraphicsPlugin = class GraphicsPlugin {
1062
795
  if (!Cons.isString(a0) || !Cons.isNumber(a1) || !Cons.isNumber(a2)) return null;
1063
796
  if (length_ === 4) {
1064
797
  if (!Cons.isNumber(a3)) return null;
1065
- context.strokeText(a0, a1, a2, a3);
1066
- } else context.strokeText(a0, a1, a2);
798
+ context.strokeText(a0, Numeric.toFloat(a1), Numeric.toFloat(a2), Numeric.toFloat(a3));
799
+ } else context.strokeText(a0, Numeric.toFloat(a1), Numeric.toFloat(a2));
1067
800
  return InterpretedSymbol.of("t");
1068
801
  });
1069
802
  }
1070
803
  gStrokeTri(arguments_) {
1071
- if (!this.checkSupport()) return Cons.nil;
1072
- if (this.isOpen) try {
1073
- if (arguments_.length() === 6) {
1074
- const a0 = arguments_.car;
1075
- const cdr1 = arguments_.cdr;
1076
- const a1 = cdr1.car;
1077
- const cdr2 = cdr1.cdr;
1078
- const a2 = cdr2.car;
1079
- const cdr3 = cdr2.cdr;
1080
- const a3 = cdr3.car;
1081
- const cdr4 = cdr3.cdr;
1082
- const a4 = cdr4.car;
1083
- const a5 = cdr4.cdr.car;
1084
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
1085
- this.ctx.beginPath();
1086
- this.ctx.moveTo(a0, a1);
1087
- this.ctx.lineTo(a2, a3);
1088
- this.ctx.lineTo(a4, a5);
1089
- this.ctx.closePath();
1090
- this.ctx.stroke();
1091
- return InterpretedSymbol.of("t");
1092
- }
1093
- }
1094
- this.#print("Can not draw stroke triangle.");
1095
- return Cons.nil;
1096
- } catch {
1097
- this.#print("Can not draw stroke triangle.");
1098
- return Cons.nil;
1099
- }
1100
- this.#print("The canvas is closed and cannot be executed.");
1101
- return Cons.nil;
804
+ return this.#execute("Can not draw stroke triangle.", (context) => {
805
+ const a = this.#numbers(arguments_, 6);
806
+ if (a === null) return null;
807
+ context.beginPath();
808
+ context.moveTo(a[0], a[1]);
809
+ context.lineTo(a[2], a[3]);
810
+ context.lineTo(a[4], a[5]);
811
+ context.closePath();
812
+ context.stroke();
813
+ return InterpretedSymbol.of("t");
814
+ });
1102
815
  }
1103
816
  gTextAlign(arguments_) {
1104
817
  return this.#execute("Can not set text align. Expected \"left\" / \"right\" / \"center\" / \"start\" / \"end\".", (context) => {
@@ -1125,80 +838,35 @@ var GraphicsPlugin = class GraphicsPlugin {
1125
838
  });
1126
839
  }
1127
840
  gTextFont(arguments_) {
1128
- if (!this.checkSupport()) return Cons.nil;
1129
- if (this.isOpen) try {
1130
- if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
1131
- this.ctx.font = arguments_.car;
1132
- return InterpretedSymbol.of("t");
1133
- }
1134
- this.#print("Can not set text font.");
1135
- return Cons.nil;
1136
- } catch {
1137
- this.#print("Can not set text font.");
1138
- return Cons.nil;
1139
- }
1140
- this.#print("The canvas is closed and cannot be executed.");
1141
- return Cons.nil;
841
+ return this.#execute("Can not set text font.", (context) => {
842
+ if (arguments_.length() !== 1 || !Cons.isString(arguments_.car)) return null;
843
+ context.font = arguments_.car;
844
+ return InterpretedSymbol.of("t");
845
+ });
1142
846
  }
1143
847
  gTranslate(arguments_) {
1144
- if (!this.checkSupport()) return Cons.nil;
1145
- if (this.isOpen) try {
1146
- if (arguments_.length() === 2) {
1147
- const a0 = arguments_.car;
1148
- const a1 = arguments_.cdr.car;
1149
- if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
1150
- this.ctx.translate(a0, a1);
1151
- return InterpretedSymbol.of("t");
1152
- }
1153
- }
1154
- this.#print("Can not translate.");
1155
- return Cons.nil;
1156
- } catch {
1157
- this.#print("Can not translate.");
1158
- return Cons.nil;
1159
- }
1160
- this.#print("The canvas is closed and cannot be executed.");
1161
- return Cons.nil;
848
+ return this.#execute("Can not translate.", (context) => {
849
+ const a = this.#numbers(arguments_, 2);
850
+ if (a === null) return null;
851
+ context.translate(a[0], a[1]);
852
+ return InterpretedSymbol.of("t");
853
+ });
1162
854
  }
1163
855
  gRect(arguments_) {
1164
- if (!this.checkSupport()) return Cons.nil;
1165
- if (this.isOpen) try {
1166
- if (arguments_.length() === 4) {
1167
- const a0 = arguments_.car;
1168
- const cdr1 = arguments_.cdr;
1169
- const a1 = cdr1.car;
1170
- const cdr2 = cdr1.cdr;
1171
- const a2 = cdr2.car;
1172
- const a3 = cdr2.cdr.car;
1173
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
1174
- this.ctx.rect(a0, a1, a2, a3);
1175
- return InterpretedSymbol.of("t");
1176
- }
1177
- }
1178
- this.#print("Can not draw rectangle.");
1179
- return Cons.nil;
1180
- } catch {
1181
- this.#print("Can not draw rectangle.");
1182
- return Cons.nil;
1183
- }
1184
- this.#print("The canvas is closed and cannot be executed.");
1185
- return Cons.nil;
856
+ return this.#execute("Can not draw rectangle.", (context) => {
857
+ const a = this.#numbers(arguments_, 4);
858
+ if (a === null) return null;
859
+ context.rect(a[0], a[1], a[2], a[3]);
860
+ return InterpretedSymbol.of("t");
861
+ });
1186
862
  }
1187
863
  gRotate(arguments_) {
1188
- if (!this.checkSupport()) return Cons.nil;
1189
- if (this.isOpen) try {
1190
- if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
1191
- this.ctx.rotate(Math.PI / 180 * arguments_.car);
1192
- return InterpretedSymbol.of("t");
1193
- }
1194
- this.#print("Can not rotate.");
1195
- return Cons.nil;
1196
- } catch {
1197
- this.#print("Can not rotate.");
1198
- return Cons.nil;
1199
- }
1200
- this.#print("The canvas is closed and cannot be executed.");
1201
- return Cons.nil;
864
+ return this.#execute("Can not rotate.", (context) => {
865
+ const a = this.#numbers(arguments_, 1);
866
+ if (a === null) return null;
867
+ context.rotate(Math.PI / 180 * a[0]);
868
+ return InterpretedSymbol.of("t");
869
+ });
1202
870
  }
1203
871
  /**
1204
872
  * Pushes the current drawing state (styles, transform, clip) onto the
@@ -1206,36 +874,26 @@ var GraphicsPlugin = class GraphicsPlugin {
1206
874
  * state explicitly. Replaces the legacy per-method `context.save()` calls, which
1207
875
  * pushed state on every draw with no matching `restore()` and grew the stack
1208
876
  * unbounded.
1209
- * @return `t` on success, `Cons.nil` otherwise
877
+ * @return `t` on success
1210
878
  */
1211
- gSave() {
1212
- if (!this.checkSupport()) return Cons.nil;
1213
- if (this.isOpen) try {
1214
- this.ctx.save();
879
+ gSave(arguments_ = Cons.nil) {
880
+ return this.#execute("Can not save.", (context) => {
881
+ if (arguments_.length() !== 0) return null;
882
+ context.save();
1215
883
  return InterpretedSymbol.of("t");
1216
- } catch {
1217
- this.#print("Can not save.");
1218
- return Cons.nil;
1219
- }
1220
- this.#print("The canvas is closed and cannot be executed.");
1221
- return Cons.nil;
884
+ });
1222
885
  }
1223
886
  /**
1224
887
  * Pops the most recently saved drawing state off the context's state stack.
1225
888
  * Pairs with `gSave`. Popping an empty stack is a no-op per the Canvas spec.
1226
- * @return `t` on success, `Cons.nil` otherwise
889
+ * @return `t` on success
1227
890
  */
1228
- gRestore() {
1229
- if (!this.checkSupport()) return Cons.nil;
1230
- if (this.isOpen) try {
1231
- this.ctx.restore();
891
+ gRestore(arguments_ = Cons.nil) {
892
+ return this.#execute("Can not restore.", (context) => {
893
+ if (arguments_.length() !== 0) return null;
894
+ context.restore();
1232
895
  return InterpretedSymbol.of("t");
1233
- } catch {
1234
- this.#print("Can not restore.");
1235
- return Cons.nil;
1236
- }
1237
- this.#print("The canvas is closed and cannot be executed.");
1238
- return Cons.nil;
896
+ });
1239
897
  }
1240
898
  gEllipse(arguments_) {
1241
899
  return this.#execute("Can not draw ellipse.", (context) => {
@@ -1257,6 +915,7 @@ var GraphicsPlugin = class GraphicsPlugin {
1257
915
  return this.#execute("Can not set line dash.", (context) => {
1258
916
  const segments = this.#numbers(arguments_, arguments_.length());
1259
917
  if (segments === null) return null;
918
+ if (segments.some((segment) => !Number.isFinite(segment) || segment < 0)) return null;
1260
919
  context.setLineDash(segments);
1261
920
  return InterpretedSymbol.of("t");
1262
921
  });
@@ -1277,8 +936,9 @@ var GraphicsPlugin = class GraphicsPlugin {
1277
936
  return InterpretedSymbol.of("t");
1278
937
  });
1279
938
  }
1280
- gClip() {
939
+ gClip(arguments_ = Cons.nil) {
1281
940
  return this.#execute("Can not clip.", (context) => {
941
+ if (arguments_.length() !== 0) return null;
1282
942
  context.clip();
1283
943
  return InterpretedSymbol.of("t");
1284
944
  });
@@ -1313,8 +973,9 @@ var GraphicsPlugin = class GraphicsPlugin {
1313
973
  return InterpretedSymbol.of("t");
1314
974
  });
1315
975
  }
1316
- gResetTransform() {
976
+ gResetTransform(arguments_ = Cons.nil) {
1317
977
  return this.#execute("Can not reset transform.", (context) => {
978
+ if (arguments_.length() !== 0) return null;
1318
979
  context.resetTransform();
1319
980
  return InterpretedSymbol.of("t");
1320
981
  });
@@ -1406,17 +1067,18 @@ var GraphicsPlugin = class GraphicsPlugin {
1406
1067
  return InterpretedSymbol.of("t");
1407
1068
  });
1408
1069
  }
1409
- gReset() {
1070
+ gReset(arguments_ = Cons.nil) {
1410
1071
  return this.#execute("Can not reset.", (context) => {
1072
+ if (arguments_.length() !== 0) return null;
1411
1073
  context.reset();
1412
1074
  return InterpretedSymbol.of("t");
1413
1075
  });
1414
1076
  }
1415
- gWidth() {
1416
- return this.#execute("Can not get width.", () => this.canvas.width);
1077
+ gWidth(arguments_ = Cons.nil) {
1078
+ return this.#execute("Can not get width.", () => arguments_.length() === 0 ? BigInt(this.canvas.width) : null);
1417
1079
  }
1418
- gHeight() {
1419
- return this.#execute("Can not get height.", () => this.canvas.height);
1080
+ gHeight(arguments_ = Cons.nil) {
1081
+ return this.#execute("Can not get height.", () => arguments_.length() === 0 ? BigInt(this.canvas.height) : null);
1420
1082
  }
1421
1083
  gPixel(arguments_) {
1422
1084
  return this.#execute("Can not read pixel.", (context) => {
@@ -1424,10 +1086,10 @@ var GraphicsPlugin = class GraphicsPlugin {
1424
1086
  if (a === null) return null;
1425
1087
  const data = context.getImageData(a[0], a[1], 1, 1).data;
1426
1088
  return this.#toList([
1427
- data[0],
1428
- data[1],
1429
- data[2],
1430
- data[3]
1089
+ BigInt(data[0]),
1090
+ BigInt(data[1]),
1091
+ BigInt(data[2]),
1092
+ BigInt(data[3])
1431
1093
  ]);
1432
1094
  });
1433
1095
  }
@@ -1448,27 +1110,28 @@ var GraphicsPlugin = class GraphicsPlugin {
1448
1110
  return this.#execute("Can not set linear gradient.", (context) => {
1449
1111
  const [x0, y0, x1, y1, ...stops] = this.#listValues(arguments_);
1450
1112
  if (stops.length < 2 || !Cons.isNumber(x0) || !Cons.isNumber(y0) || !Cons.isNumber(x1) || !Cons.isNumber(y1)) return null;
1451
- return this.#installGradient(context, context.createLinearGradient(x0, y0, x1, y1), stops);
1113
+ return this.#installGradient(context, context.createLinearGradient(Numeric.toFloat(x0), Numeric.toFloat(y0), Numeric.toFloat(x1), Numeric.toFloat(y1)), stops);
1452
1114
  });
1453
1115
  }
1454
1116
  gRadialGradient(arguments_) {
1455
1117
  return this.#execute("Can not set radial gradient.", (context) => {
1456
1118
  const [x0, y0, r0, x1, y1, r1, ...stops] = this.#listValues(arguments_);
1457
1119
  if (stops.length < 2 || !Cons.isNumber(x0) || !Cons.isNumber(y0) || !Cons.isNumber(r0) || !Cons.isNumber(x1) || !Cons.isNumber(y1) || !Cons.isNumber(r1)) return null;
1458
- return this.#installGradient(context, context.createRadialGradient(x0, y0, r0, x1, y1, r1), stops);
1120
+ return this.#installGradient(context, context.createRadialGradient(Numeric.toFloat(x0), Numeric.toFloat(y0), Numeric.toFloat(r0), Numeric.toFloat(x1), Numeric.toFloat(y1), Numeric.toFloat(r1)), stops);
1459
1121
  });
1460
1122
  }
1461
1123
  gConicGradient(arguments_) {
1462
1124
  return this.#execute("Can not set conic gradient.", (context) => {
1463
1125
  const [angle, x, y, ...stops] = this.#listValues(arguments_);
1464
1126
  if (stops.length < 2 || !Cons.isNumber(angle) || !Cons.isNumber(x) || !Cons.isNumber(y)) return null;
1465
- return this.#installGradient(context, context.createConicGradient(Math.PI / 180 * angle, x, y), stops);
1127
+ return this.#installGradient(context, context.createConicGradient(Math.PI / 180 * Numeric.toFloat(angle), Numeric.toFloat(x), Numeric.toFloat(y)), stops);
1466
1128
  });
1467
1129
  }
1468
1130
  /**
1469
1131
  * Parses a color spec from the head of the argument Cons. Accepts
1470
1132
  * (1 string), (3 numbers — rgb), or (4 numbers — rgba); falls back to
1471
- * `'black'` on anything else (legacy behavior).
1133
+ * `'black'` on anything else (legacy behavior — a best-effort color parse
1134
+ * that prints a diagnostic instead of signaling an error).
1472
1135
  * @param arguments_ - the argument Cons to parse
1473
1136
  * @return CSS color string
1474
1137
  */
@@ -1481,7 +1144,7 @@ var GraphicsPlugin = class GraphicsPlugin {
1481
1144
  const cdr1 = arguments_.cdr;
1482
1145
  const a1 = cdr1.car;
1483
1146
  const a2 = cdr1.cdr.car;
1484
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) aColor = `rgb(${a0}, ${a1}, ${a2})`;
1147
+ if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) aColor = `rgb(${Numeric.toFloat(a0)}, ${Numeric.toFloat(a1)}, ${Numeric.toFloat(a2)})`;
1485
1148
  else this.#print("Can not set color. set color \"black\".");
1486
1149
  } else if (length_ === 4) {
1487
1150
  const cdr1 = arguments_.cdr;
@@ -1489,7 +1152,7 @@ var GraphicsPlugin = class GraphicsPlugin {
1489
1152
  const cdr2 = cdr1.cdr;
1490
1153
  const a2 = cdr2.car;
1491
1154
  const a3 = cdr2.cdr.car;
1492
- if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) aColor = `rgba(${a0}, ${a1}, ${a2}, ${a3})`;
1155
+ if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) aColor = `rgba(${Numeric.toFloat(a0)}, ${Numeric.toFloat(a1)}, ${Numeric.toFloat(a2)}, ${Numeric.toFloat(a3)})`;
1493
1156
  else this.#print("Can not set color. set color \"black\".");
1494
1157
  } else this.#print("Can not set color. set color \"black\".");
1495
1158
  return aColor;