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