kei-lisp-plugin-graphics 1.0.1 → 2.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
@@ -4,7 +4,7 @@ import { Cons, InterpretedSymbol } from "kei-lisp";
4
4
  * @class
5
5
  * @classdesc Canvas2D drawing plugin for the kei-lisp interpreter. Implements
6
6
  * the `KeiLispPlugin` contract (`name` / `has` / `apply`) and
7
- * exposes 43 `g…` Lisp functions that proxy to a 2D rendering
7
+ * exposes 45 `g…` Lisp functions that proxy to a 2D rendering
8
8
  * context.
9
9
  * @author Keisuke Ikeda
10
10
  * @this {GraphicsPlugin}
@@ -16,6 +16,59 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
16
16
  */
17
17
  static buildInFunctions = GraphicsPlugin.setup();
18
18
  /**
19
+ * Builds the dispatch table.
20
+ * @return the dispatch table
21
+ */
22
+ static setup() {
23
+ const aTable = /* @__PURE__ */ new Map();
24
+ aTable.set(InterpretedSymbol.of("galpha"), "gAlpha");
25
+ aTable.set(InterpretedSymbol.of("garc"), "gArc");
26
+ aTable.set(InterpretedSymbol.of("garc-to"), "gArcTo");
27
+ aTable.set(InterpretedSymbol.of("gbezcurve-to"), "gBezCurveTo");
28
+ aTable.set(InterpretedSymbol.of("gclear"), "gClear");
29
+ aTable.set(InterpretedSymbol.of("gclose"), "gClose");
30
+ aTable.set(InterpretedSymbol.of("gcolor"), "gColor");
31
+ aTable.set(InterpretedSymbol.of("gfill"), "gFill");
32
+ aTable.set(InterpretedSymbol.of("gfill-color"), "gFillColor");
33
+ aTable.set(InterpretedSymbol.of("gfill-rect"), "gFillRect");
34
+ aTable.set(InterpretedSymbol.of("gfill-text"), "gFillText");
35
+ aTable.set(InterpretedSymbol.of("gfill-tri"), "gFillTri");
36
+ aTable.set(InterpretedSymbol.of("gfinish-path"), "gFinishPath");
37
+ aTable.set(InterpretedSymbol.of("gimage"), "gImage");
38
+ aTable.set(InterpretedSymbol.of("gmove-to"), "gMoveTo");
39
+ aTable.set(InterpretedSymbol.of("gline-to"), "gLineTo");
40
+ aTable.set(InterpretedSymbol.of("gline-cap"), "gLineCap");
41
+ aTable.set(InterpretedSymbol.of("gline-join"), "gLineJoin");
42
+ aTable.set(InterpretedSymbol.of("gline-width"), "gLineWidth");
43
+ aTable.set(InterpretedSymbol.of("gopen"), "gOpen");
44
+ aTable.set(InterpretedSymbol.of("gpattern"), "gPattern");
45
+ aTable.set(InterpretedSymbol.of("gquadcurve-to"), "gQuadCurveTo");
46
+ aTable.set(InterpretedSymbol.of("gsave-jpeg"), "gSaveJpeg");
47
+ aTable.set(InterpretedSymbol.of("gsave-png"), "gSavePng");
48
+ aTable.set(InterpretedSymbol.of("gscale"), "gScale");
49
+ aTable.set(InterpretedSymbol.of("gshadow-blur"), "gShadowBlur");
50
+ aTable.set(InterpretedSymbol.of("gshadow-color"), "gShadowColor");
51
+ aTable.set(InterpretedSymbol.of("gshadow-offsetx"), "gShadowOffsetX");
52
+ aTable.set(InterpretedSymbol.of("gshadow-offsety"), "gShadowOffsetY");
53
+ aTable.set(InterpretedSymbol.of("gsleep"), "gSleep");
54
+ aTable.set(InterpretedSymbol.of("gstart-path"), "gStartPath");
55
+ aTable.set(InterpretedSymbol.of("gstroke"), "gStroke");
56
+ aTable.set(InterpretedSymbol.of("gstroke-color"), "gStrokeColor");
57
+ aTable.set(InterpretedSymbol.of("gstroke-rect"), "gStrokeRect");
58
+ aTable.set(InterpretedSymbol.of("gstroke-text"), "gStrokeText");
59
+ aTable.set(InterpretedSymbol.of("gstroke-tri"), "gStrokeTri");
60
+ aTable.set(InterpretedSymbol.of("gtext-align"), "gTextAlign");
61
+ aTable.set(InterpretedSymbol.of("gtext-dire"), "gTextDirection");
62
+ aTable.set(InterpretedSymbol.of("gtext-font"), "gTextFont");
63
+ aTable.set(InterpretedSymbol.of("gtext-line"), "gTextBaseline");
64
+ aTable.set(InterpretedSymbol.of("gtranslate"), "gTranslate");
65
+ aTable.set(InterpretedSymbol.of("grect"), "gRect");
66
+ aTable.set(InterpretedSymbol.of("grotate"), "gRotate");
67
+ aTable.set(InterpretedSymbol.of("gsave"), "gSave");
68
+ aTable.set(InterpretedSymbol.of("grestore"), "gRestore");
69
+ return aTable;
70
+ }
71
+ /**
19
72
  * Plugin identifier, used for diagnostics.
20
73
  */
21
74
  name = "graphics";
@@ -34,6 +87,79 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
34
87
  this.isOpen = false;
35
88
  }
36
89
  /**
90
+ * Writes a diagnostic line directly to `process.stderr`, matching the
91
+ * convention used by kei-lisp itself (`Applier.format` writes to
92
+ * `process.stdout`). In a Node runtime this hits the real stderr; in a
93
+ * browser kei-lisp host (e.g. kei-lisp-web) the host typically swaps
94
+ * `process.stderr.write` for a sink that routes to the REPL output panel,
95
+ * so the same call reaches the user via the host's normal output channel.
96
+ * @param line - the line to write
97
+ */
98
+ #print(line) {
99
+ process.stderr.write(line + "\n");
100
+ }
101
+ /**
102
+ * Encodes the canvas via `toDataURL` and triggers a browser download through
103
+ * a temporary `<a download>` element. Requires a DOM (`document`) and an
104
+ * `HTMLCanvasElement`; `OffscreenCanvas` has no `toDataURL`, and Node.js has
105
+ * no `document` — those callers must pass a file path instead.
106
+ * @param mimeType - the image MIME type to encode
107
+ * @param label - the format name used in diagnostics ("jpeg" / "png")
108
+ * @return `t` on success, `Cons.nil` otherwise
109
+ */
110
+ #downloadCanvas(mimeType, label) {
111
+ if (typeof document === "undefined" || !("toDataURL" in this.canvas)) {
112
+ this.#print(`Can not save ${label}. Browser download needs a DOM and an HTMLCanvasElement; pass a file path to save on Node.js.`);
113
+ return Cons.nil;
114
+ }
115
+ try {
116
+ const link = document.createElement("a");
117
+ link.href = this.canvas.toDataURL(mimeType);
118
+ link.download = "canvas";
119
+ document.body.append(link);
120
+ link.click();
121
+ link.remove();
122
+ return InterpretedSymbol.of("t");
123
+ } catch {
124
+ this.#print(`Can not save ${label}. If you are using an image in the canvas, you can't save ${label}.`);
125
+ return Cons.nil;
126
+ }
127
+ }
128
+ /**
129
+ * Writes the encoded canvas image to a file path on Node.js, using
130
+ * `process.getBuiltinModule('node:fs')` so browser bundles never see a
131
+ * `node:fs` import. `HTMLCanvasElement` (and node-canvas) encode
132
+ * synchronously via `toDataURL`; `OffscreenCanvas` only offers the async
133
+ * `convertToBlob`, so its file is written after this returns — the same
134
+ * fire-and-forget contract as `gImage`.
135
+ * @param path - the destination file path
136
+ * @param mimeType - the image MIME type to encode
137
+ * @param label - the format name used in diagnostics ("jpeg" / "png")
138
+ * @return `t` on success, `Cons.nil` otherwise
139
+ */
140
+ #writeCanvasToFile(path, mimeType, label) {
141
+ const fs = typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("node:fs") : void 0;
142
+ if (fs === void 0) {
143
+ this.#print(`Can not save ${label}. Saving to a file path requires Node.js.`);
144
+ return Cons.nil;
145
+ }
146
+ try {
147
+ if ("toDataURL" in this.canvas) {
148
+ const dataUrl = this.canvas.toDataURL(mimeType);
149
+ const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
150
+ fs.writeFileSync(path, Buffer.from(base64, "base64"));
151
+ return InterpretedSymbol.of("t");
152
+ }
153
+ this.canvas.convertToBlob({ type: mimeType }).then(async (blob) => {
154
+ fs.writeFileSync(path, new Uint8Array(await blob.arrayBuffer()));
155
+ });
156
+ return InterpretedSymbol.of("t");
157
+ } catch {
158
+ this.#print(`Can not save ${label}.`);
159
+ return Cons.nil;
160
+ }
161
+ }
162
+ /**
37
163
  * Returns true if this plugin handles the given symbol.
38
164
  * @param aSymbol - the call symbol
39
165
  * @return true if `apply` should be called
@@ -44,22 +170,22 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
44
170
  /**
45
171
  * Dispatches the given symbol to the matching `g…` method.
46
172
  * @param aSymbol - the call symbol
47
- * @param args - the evaluated argument list
48
- * @param _ctx - the interpreter context (unused by this plugin)
173
+ * @param arguments_ - the evaluated argument list
174
+ * @param _context - the interpreter context (unused by this plugin)
49
175
  * @return the method's result, or `Cons.nil` if dispatch fails
50
176
  */
51
- apply(aSymbol, args, _ctx) {
52
- return this.selectProcedure(aSymbol, args);
177
+ apply(aSymbol, arguments_, _context) {
178
+ return this.selectProcedure(aSymbol, arguments_);
53
179
  }
54
180
  /**
55
181
  * Resolves the procedure name and invokes the matching method.
56
182
  * @param procedure - the Lisp symbol
57
- * @param args - the evaluated argument list
183
+ * @param arguments_ - the evaluated argument list
58
184
  * @return the method's result, or `Cons.nil` if not registered
59
185
  */
60
- selectProcedure(procedure, args) {
61
- if (GraphicsPlugin.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, args);
62
- this._print(`I could find no procedure description for ${String(procedure)}`);
186
+ selectProcedure(procedure, arguments_) {
187
+ if (GraphicsPlugin.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, arguments_);
188
+ this.#print(`I could find no procedure description for ${String(procedure)}`);
63
189
  return Cons.nil;
64
190
  }
65
191
  /**
@@ -69,14 +195,14 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
69
195
  * Ramda `R.invoker(1, methodName)(args, this)` call would surface the same
70
196
  * error by attempting to call `undefined`.
71
197
  * @param procedure - the Lisp symbol
72
- * @param args - the evaluated argument list
198
+ * @param arguments_ - the evaluated argument list
73
199
  * @return the method's result
74
200
  */
75
- buildInFunction(procedure, args) {
201
+ buildInFunction(procedure, arguments_) {
76
202
  const methodName = GraphicsPlugin.buildInFunctions.get(procedure);
77
203
  const method = this[methodName];
78
204
  if (typeof method !== "function") throw new TypeError(`${this.constructor.name} does not have a method named "${methodName}"`);
79
- return method.call(this, args);
205
+ return method.call(this, arguments_);
80
206
  }
81
207
  /**
82
208
  * Checks whether the canvas exposes a usable 2D context, and narrows
@@ -85,47 +211,34 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
85
211
  */
86
212
  checkSupport() {
87
213
  if (this.ctx === null) {
88
- this._print("Unable to initialize canvas. The browser or machine may not support it.");
214
+ this.#print("Unable to initialize canvas. The browser or machine may not support it.");
89
215
  return false;
90
216
  }
91
217
  return true;
92
218
  }
93
- /**
94
- * Writes a diagnostic line directly to `process.stderr`, matching the
95
- * convention used by kei-lisp itself (`Applier.format` writes to
96
- * `process.stdout`). In a Node runtime this hits the real stderr; in a
97
- * browser kei-lisp host (e.g. kei-lisp-web) the host typically swaps
98
- * `process.stderr.write` for a sink that routes to the REPL output panel,
99
- * so the same call reaches the user via the host's normal output channel.
100
- * @param line - the line to write
101
- */
102
- _print(line) {
103
- process.stderr.write(line + "\n");
104
- }
105
- gAlpha(args) {
219
+ gAlpha(arguments_) {
106
220
  if (!this.checkSupport()) return Cons.nil;
107
221
  if (this.isOpen) try {
108
- if (args.length() === 1 && Cons.isNumber(args.car)) {
109
- const aNumber = args.car <= 0 ? 0 : args.car >= 1 ? 1 : args.car;
222
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
223
+ const aNumber = arguments_.car <= 0 ? 0 : arguments_.car >= 1 ? 1 : arguments_.car;
110
224
  this.ctx.globalAlpha = aNumber;
111
- this.ctx.save();
112
225
  return InterpretedSymbol.of("t");
113
226
  }
114
- this._print("Can not set alpha.");
227
+ this.#print("Can not set alpha.");
115
228
  return Cons.nil;
116
229
  } catch {
117
- this._print("Can not set alpha.");
230
+ this.#print("Can not set alpha.");
118
231
  return Cons.nil;
119
232
  }
120
- this._print("The canvas is closed and cannot be executed.");
233
+ this.#print("The canvas is closed and cannot be executed.");
121
234
  return Cons.nil;
122
235
  }
123
- gArc(args) {
236
+ gArc(arguments_) {
124
237
  if (!this.checkSupport()) return Cons.nil;
125
238
  if (this.isOpen) try {
126
- if (args.length() === 6) {
127
- const a0 = args.car;
128
- const cdr1 = args.cdr;
239
+ if (arguments_.length() === 6) {
240
+ const a0 = arguments_.car;
241
+ const cdr1 = arguments_.cdr;
129
242
  const a1 = cdr1.car;
130
243
  const cdr2 = cdr1.cdr;
131
244
  const a2 = cdr2.car;
@@ -135,27 +248,26 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
135
248
  const a4 = cdr4.car;
136
249
  const a5 = cdr4.cdr.car;
137
250
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
138
- const aFlag = a5 >= 0;
139
- this.ctx.arc(a0, a1, a2, Math.PI / 180 * a3, Math.PI / 180 * a4, aFlag);
140
- this.ctx.save();
251
+ const isAFlag = a5 >= 0;
252
+ this.ctx.arc(a0, a1, a2, Math.PI / 180 * a3, Math.PI / 180 * a4, isAFlag);
141
253
  return InterpretedSymbol.of("t");
142
254
  }
143
255
  }
144
- this._print("Can not draw arc.");
256
+ this.#print("Can not draw arc.");
145
257
  return Cons.nil;
146
258
  } catch {
147
- this._print("Can not draw arc.");
259
+ this.#print("Can not draw arc.");
148
260
  return Cons.nil;
149
261
  }
150
- this._print("The canvas is closed and cannot be executed.");
262
+ this.#print("The canvas is closed and cannot be executed.");
151
263
  return Cons.nil;
152
264
  }
153
- gArcTo(args) {
265
+ gArcTo(arguments_) {
154
266
  if (!this.checkSupport()) return Cons.nil;
155
267
  if (this.isOpen) try {
156
- if (args.length() === 5) {
157
- const a0 = args.car;
158
- const cdr1 = args.cdr;
268
+ if (arguments_.length() === 5) {
269
+ const a0 = arguments_.car;
270
+ const cdr1 = arguments_.cdr;
159
271
  const a1 = cdr1.car;
160
272
  const cdr2 = cdr1.cdr;
161
273
  const a2 = cdr2.car;
@@ -164,25 +276,24 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
164
276
  const a4 = cdr3.cdr.car;
165
277
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4)) {
166
278
  this.ctx.arcTo(a0, a1, a2, a3, a4);
167
- this.ctx.save();
168
279
  return InterpretedSymbol.of("t");
169
280
  }
170
281
  }
171
- this._print("Can not draw arc to.");
282
+ this.#print("Can not draw arc to.");
172
283
  return Cons.nil;
173
284
  } catch {
174
- this._print("Can not draw arc to.");
285
+ this.#print("Can not draw arc to.");
175
286
  return Cons.nil;
176
287
  }
177
- this._print("The canvas is closed and cannot be executed.");
288
+ this.#print("The canvas is closed and cannot be executed.");
178
289
  return Cons.nil;
179
290
  }
180
- gBezCurveTo(args) {
291
+ gBezCurveTo(arguments_) {
181
292
  if (!this.checkSupport()) return Cons.nil;
182
293
  if (this.isOpen) try {
183
- if (args.length() === 6) {
184
- const a0 = args.car;
185
- const cdr1 = args.cdr;
294
+ if (arguments_.length() === 6) {
295
+ const a0 = arguments_.car;
296
+ const cdr1 = arguments_.cdr;
186
297
  const a1 = cdr1.car;
187
298
  const cdr2 = cdr1.cdr;
188
299
  const a2 = cdr2.car;
@@ -193,17 +304,16 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
193
304
  const a5 = cdr4.cdr.car;
194
305
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4) && Cons.isNumber(a5)) {
195
306
  this.ctx.bezierCurveTo(a0, a1, a2, a3, a4, a5);
196
- this.ctx.save();
197
307
  return InterpretedSymbol.of("t");
198
308
  }
199
309
  }
200
- this._print("Can not draw bezier curve.");
310
+ this.#print("Can not draw bezier curve.");
201
311
  return Cons.nil;
202
312
  } catch {
203
- this._print("Can not draw bezier curve.");
313
+ this.#print("Can not draw bezier curve.");
204
314
  return Cons.nil;
205
315
  }
206
- this._print("The canvas is closed and cannot be executed.");
316
+ this.#print("The canvas is closed and cannot be executed.");
207
317
  return Cons.nil;
208
318
  }
209
319
  gClear() {
@@ -212,13 +322,12 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
212
322
  this.ctx.fillStyle = "#ffffff";
213
323
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
214
324
  this.ctx.fillStyle = "#000000";
215
- this.ctx.save();
216
325
  return InterpretedSymbol.of("t");
217
326
  } catch {
218
- this._print("Can not clear.");
327
+ this.#print("Can not clear.");
219
328
  return Cons.nil;
220
329
  }
221
- this._print("The canvas is closed and cannot be executed.");
330
+ this.#print("The canvas is closed and cannot be executed.");
222
331
  return Cons.nil;
223
332
  }
224
333
  gClose() {
@@ -228,116 +337,111 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
228
337
  this.isOpen = false;
229
338
  return InterpretedSymbol.of("t");
230
339
  } catch {
231
- this._print("Can not close.");
340
+ this.#print("Can not close.");
232
341
  return Cons.nil;
233
342
  }
234
- this._print("The canvas has already been closed.");
343
+ this.#print("The canvas has already been closed.");
235
344
  return Cons.nil;
236
345
  }
237
- gColor(args) {
346
+ gColor(arguments_) {
238
347
  if (!this.checkSupport()) return Cons.nil;
239
348
  if (this.isOpen) try {
240
- if (args.length() >= 1) {
241
- const aColor = this.selectColor(args);
349
+ if (arguments_.length() >= 1) {
350
+ const aColor = this.selectColor(arguments_);
242
351
  this.ctx.fillStyle = aColor;
243
352
  this.ctx.strokeStyle = aColor;
244
- this.ctx.save();
245
353
  return InterpretedSymbol.of("t");
246
354
  }
247
- this._print("Can not set color.");
355
+ this.#print("Can not set color.");
248
356
  return Cons.nil;
249
357
  } catch {
250
- this._print("Can not set color.");
358
+ this.#print("Can not set color.");
251
359
  return Cons.nil;
252
360
  }
253
- this._print("The canvas is closed and cannot be executed.");
361
+ this.#print("The canvas is closed and cannot be executed.");
254
362
  return Cons.nil;
255
363
  }
256
364
  gFill() {
257
365
  if (!this.checkSupport()) return Cons.nil;
258
366
  if (this.isOpen) try {
259
367
  this.ctx.fill();
260
- this.ctx.save();
261
368
  return InterpretedSymbol.of("t");
262
369
  } catch {
263
- this._print("Can not fill.");
370
+ this.#print("Can not fill.");
264
371
  return Cons.nil;
265
372
  }
266
- this._print("The canvas is closed and cannot be executed.");
373
+ this.#print("The canvas is closed and cannot be executed.");
267
374
  return Cons.nil;
268
375
  }
269
- gFillColor(args) {
376
+ gFillColor(arguments_) {
270
377
  if (!this.checkSupport()) return Cons.nil;
271
378
  if (this.isOpen) try {
272
- if (args.length() >= 1) {
273
- const aColor = this.selectColor(args);
379
+ if (arguments_.length() >= 1) {
380
+ const aColor = this.selectColor(arguments_);
274
381
  this.ctx.fillStyle = aColor;
275
- this.ctx.save();
276
382
  return InterpretedSymbol.of("t");
277
383
  }
278
- this._print("Can not set fill color.");
384
+ this.#print("Can not set fill color.");
279
385
  return Cons.nil;
280
386
  } catch {
281
- this._print("Can not set fill color.");
387
+ this.#print("Can not set fill color.");
282
388
  return Cons.nil;
283
389
  }
284
- this._print("The canvas is closed and cannot be executed.");
390
+ this.#print("The canvas is closed and cannot be executed.");
285
391
  return Cons.nil;
286
392
  }
287
- gFillRect(args) {
393
+ gFillRect(arguments_) {
288
394
  if (!this.checkSupport()) return Cons.nil;
289
395
  if (this.isOpen) try {
290
- if (args.length() === 4) {
291
- const a0 = args.car;
292
- const cdr1 = args.cdr;
396
+ if (arguments_.length() === 4) {
397
+ const a0 = arguments_.car;
398
+ const cdr1 = arguments_.cdr;
293
399
  const a1 = cdr1.car;
294
400
  const cdr2 = cdr1.cdr;
295
401
  const a2 = cdr2.car;
296
402
  const a3 = cdr2.cdr.car;
297
403
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
298
404
  this.ctx.fillRect(a0, a1, a2, a3);
299
- this.ctx.save();
300
405
  return InterpretedSymbol.of("t");
301
406
  }
302
407
  }
303
- this._print("Can not draw fill rectangle.");
408
+ this.#print("Can not draw fill rectangle.");
304
409
  return Cons.nil;
305
410
  } catch {
306
- this._print("Can not draw fill rectangle.");
411
+ this.#print("Can not draw fill rectangle.");
307
412
  return Cons.nil;
308
413
  }
309
- this._print("The canvas is closed and cannot be executed.");
414
+ this.#print("The canvas is closed and cannot be executed.");
310
415
  return Cons.nil;
311
416
  }
312
- gFillText(args) {
417
+ gFillText(arguments_) {
313
418
  if (!this.checkSupport()) return Cons.nil;
314
419
  if (this.isOpen) try {
315
- if (args.length() === 3) {
316
- const a0 = args.car;
317
- const cdr1 = args.cdr;
420
+ if (arguments_.length() === 3) {
421
+ const a0 = arguments_.car;
422
+ const cdr1 = arguments_.cdr;
318
423
  const a1 = cdr1.car;
319
424
  const a2 = cdr1.cdr.car;
320
425
  if (Cons.isString(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) {
321
426
  this.ctx.fillText(a0, a1, a2);
322
- this.ctx.save();
323
427
  return InterpretedSymbol.of("t");
324
428
  }
325
429
  }
326
- this._print("Can not draw fill text.");
430
+ this.#print("Can not draw fill text.");
327
431
  return Cons.nil;
328
432
  } catch {
329
- this._print("Can not draw fill text.");
433
+ this.#print("Can not draw fill text.");
330
434
  return Cons.nil;
331
435
  }
332
- this._print("The canvas is closed and cannot be executed.");
436
+ this.#print("The canvas is closed and cannot be executed.");
333
437
  return Cons.nil;
334
438
  }
335
- gFillTri(args) {
439
+ gFillTri(arguments_) {
336
440
  if (!this.checkSupport()) return Cons.nil;
337
441
  if (this.isOpen) try {
338
- if (args.length() === 6) {
339
- const a0 = args.car;
340
- const cdr1 = args.cdr;
442
+ if (arguments_.length() === 6) {
443
+ const a0 = arguments_.car;
444
+ const cdr1 = arguments_.cdr;
341
445
  const a1 = cdr1.car;
342
446
  const cdr2 = cdr1.cdr;
343
447
  const a2 = cdr2.car;
@@ -352,54 +456,51 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
352
456
  this.ctx.lineTo(a2, a3);
353
457
  this.ctx.lineTo(a4, a5);
354
458
  this.ctx.fill();
355
- this.ctx.save();
356
459
  return InterpretedSymbol.of("t");
357
460
  }
358
461
  }
359
- this._print("Can not draw fill triangle.");
462
+ this.#print("Can not draw fill triangle.");
360
463
  return Cons.nil;
361
464
  } catch {
362
- this._print("Can not draw fill triangle.");
465
+ this.#print("Can not draw fill triangle.");
363
466
  return Cons.nil;
364
467
  }
365
- this._print("The canvas is closed and cannot be executed.");
468
+ this.#print("The canvas is closed and cannot be executed.");
366
469
  return Cons.nil;
367
470
  }
368
471
  gFinishPath() {
369
472
  if (!this.checkSupport()) return Cons.nil;
370
473
  if (this.isOpen) try {
371
474
  this.ctx.closePath();
372
- this.ctx.save();
373
475
  return InterpretedSymbol.of("t");
374
476
  } catch {
375
- this._print("Can not finish path.");
477
+ this.#print("Can not finish path.");
376
478
  return Cons.nil;
377
479
  }
378
- this._print("The canvas is closed and cannot be executed.");
480
+ this.#print("The canvas is closed and cannot be executed.");
379
481
  return Cons.nil;
380
482
  }
381
- gImage(args) {
483
+ gImage(arguments_) {
382
484
  if (!this.checkSupport()) return Cons.nil;
383
485
  if (this.isOpen) try {
384
- const len = args.length();
385
- if (len === 3) {
386
- const a0 = args.car;
387
- const cdr1 = args.cdr;
486
+ const length_ = arguments_.length();
487
+ if (length_ === 3) {
488
+ const a0 = arguments_.car;
489
+ const cdr1 = arguments_.cdr;
388
490
  const a1 = cdr1.car;
389
491
  const a2 = cdr1.cdr.car;
390
492
  if (Cons.isString(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) {
391
- const ctx = this.ctx;
493
+ const context = this.ctx;
392
494
  const anImage = new Image();
393
495
  anImage.src = a0;
394
496
  anImage.onload = () => {
395
- ctx.drawImage(anImage, a1, a2);
497
+ context.drawImage(anImage, a1, a2);
396
498
  };
397
- ctx.save();
398
499
  return InterpretedSymbol.of("t");
399
500
  }
400
- } else if (len === 5) {
401
- const a0 = args.car;
402
- const cdr1 = args.cdr;
501
+ } else if (length_ === 5) {
502
+ const a0 = arguments_.car;
503
+ const cdr1 = arguments_.cdr;
403
504
  const a1 = cdr1.car;
404
505
  const cdr2 = cdr1.cdr;
405
506
  const a2 = cdr2.car;
@@ -407,119 +508,111 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
407
508
  const a3 = cdr3.car;
408
509
  const a4 = cdr3.cdr.car;
409
510
  if (Cons.isString(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3) && Cons.isNumber(a4)) {
410
- const ctx = this.ctx;
511
+ const context = this.ctx;
411
512
  const anImage = new Image();
412
513
  anImage.src = a0;
413
514
  anImage.onload = () => {
414
- ctx.drawImage(anImage, a1, a2, a3, a4);
515
+ context.drawImage(anImage, a1, a2, a3, a4);
415
516
  };
416
- ctx.save();
417
517
  return InterpretedSymbol.of("t");
418
518
  }
419
519
  }
420
- this._print("Can not draw Image.");
520
+ this.#print("Can not draw Image.");
421
521
  return Cons.nil;
422
522
  } catch {
423
- this._print("Can not draw Image.");
523
+ this.#print("Can not draw Image.");
424
524
  return Cons.nil;
425
525
  }
426
- this._print("The canvas is closed and cannot be executed.");
526
+ this.#print("The canvas is closed and cannot be executed.");
427
527
  return Cons.nil;
428
528
  }
429
- gLineTo(args) {
529
+ gLineTo(arguments_) {
430
530
  if (!this.checkSupport()) return Cons.nil;
431
531
  if (this.isOpen) try {
432
- if (args.length() === 2) {
433
- const a0 = args.car;
434
- const a1 = args.cdr.car;
532
+ if (arguments_.length() === 2) {
533
+ const a0 = arguments_.car;
534
+ const a1 = arguments_.cdr.car;
435
535
  if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
436
536
  this.ctx.lineTo(a0, a1);
437
- this.ctx.save();
438
537
  return InterpretedSymbol.of("t");
439
538
  }
440
539
  }
441
- this._print("Can not draw line to");
540
+ this.#print("Can not draw line to");
442
541
  return Cons.nil;
443
542
  } catch {
444
- this._print("Can not draw line to");
543
+ this.#print("Can not draw line to");
445
544
  return Cons.nil;
446
545
  }
447
- this._print("The canvas is closed and cannot be executed.");
546
+ this.#print("The canvas is closed and cannot be executed.");
448
547
  return Cons.nil;
449
548
  }
450
- gLineCap(args) {
549
+ gLineCap(arguments_) {
451
550
  if (!this.checkSupport()) return Cons.nil;
452
551
  if (this.isOpen) try {
453
- if (args.length() === 1 && Cons.isNumber(args.car)) {
454
- const aString = args.car === 0 ? "butt" : args.car > 0 ? "round" : "square";
455
- this.ctx.lineCap = aString;
456
- this.ctx.save();
552
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
553
+ this.ctx.lineCap = arguments_.car;
457
554
  return InterpretedSymbol.of("t");
458
555
  }
459
- this._print("Can not set line cap.");
556
+ this.#print("Can not set line cap.");
460
557
  return Cons.nil;
461
558
  } catch {
462
- this._print("Can not set line cap.");
559
+ this.#print("Can not set line cap.");
463
560
  return Cons.nil;
464
561
  }
465
- this._print("The canvas is closed and cannot be executed.");
562
+ this.#print("The canvas is closed and cannot be executed.");
466
563
  return Cons.nil;
467
564
  }
468
- gLineJoin(args) {
565
+ gLineJoin(arguments_) {
469
566
  if (!this.checkSupport()) return Cons.nil;
470
567
  if (this.isOpen) try {
471
- if (args.length() === 1 && Cons.isNumber(args.car)) {
472
- const aString = args.car === 0 ? "miter" : args.car > 0 ? "round" : "bevel";
473
- this.ctx.lineJoin = aString;
474
- this.ctx.save();
568
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
569
+ this.ctx.lineJoin = arguments_.car;
475
570
  return InterpretedSymbol.of("t");
476
571
  }
477
- this._print("Can not set line join.");
572
+ this.#print("Can not set line join.");
478
573
  return Cons.nil;
479
574
  } catch {
480
- this._print("Can not set line join.");
575
+ this.#print("Can not set line join.");
481
576
  return Cons.nil;
482
577
  }
483
- this._print("The canvas is closed and cannot be executed.");
578
+ this.#print("The canvas is closed and cannot be executed.");
484
579
  return Cons.nil;
485
580
  }
486
- gLineWidth(args) {
581
+ gLineWidth(arguments_) {
487
582
  if (!this.checkSupport()) return Cons.nil;
488
583
  if (this.isOpen) try {
489
- if (args.length() === 1 && Cons.isNumber(args.car)) {
490
- const aNumber = args.car <= 0 ? 1 : args.car;
584
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
585
+ const aNumber = arguments_.car <= 0 ? 1 : arguments_.car;
491
586
  this.ctx.lineWidth = aNumber;
492
- this.ctx.save();
493
587
  return InterpretedSymbol.of("t");
494
588
  }
495
- this._print("Can not set line width.");
589
+ this.#print("Can not set line width.");
496
590
  return Cons.nil;
497
591
  } catch {
498
- this._print("Can not set line width.");
592
+ this.#print("Can not set line width.");
499
593
  return Cons.nil;
500
594
  }
501
- this._print("The canvas is closed and cannot be executed.");
595
+ this.#print("The canvas is closed and cannot be executed.");
502
596
  return Cons.nil;
503
597
  }
504
- gMoveTo(args) {
598
+ gMoveTo(arguments_) {
505
599
  if (!this.checkSupport()) return Cons.nil;
506
600
  if (this.isOpen) try {
507
- if (args.length() === 2) {
508
- const a0 = args.car;
509
- const a1 = args.cdr.car;
601
+ if (arguments_.length() === 2) {
602
+ const a0 = arguments_.car;
603
+ const a1 = arguments_.cdr.car;
510
604
  if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
511
605
  this.ctx.moveTo(a0, a1);
512
- this.ctx.save();
513
606
  return InterpretedSymbol.of("t");
514
607
  }
515
608
  }
516
- this._print("Can not move");
609
+ this.#print("Can not move");
517
610
  return Cons.nil;
518
611
  } catch {
519
- this._print("Can not move");
612
+ this.#print("Can not move");
520
613
  return Cons.nil;
521
614
  }
522
- this._print("The canvas is closed and cannot be executed.");
615
+ this.#print("The canvas is closed and cannot be executed.");
523
616
  return Cons.nil;
524
617
  }
525
618
  gOpen() {
@@ -529,304 +622,278 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
529
622
  this.ctx.fillStyle = "#ffffff";
530
623
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
531
624
  this.ctx.fillStyle = "#000000";
532
- this.ctx.save();
533
- this._print("canvas size, width : 600 height : 300");
625
+ this.#print(`canvas size, width : ${this.canvas.width} height : ${this.canvas.height}`);
534
626
  return InterpretedSymbol.of("t");
535
627
  } catch {
536
- this._print("Can not open.");
628
+ this.#print("Can not open.");
537
629
  return Cons.nil;
538
630
  }
539
- this._print("The canvas has already been opened.");
631
+ this.#print("The canvas has already been opened.");
540
632
  return Cons.nil;
541
633
  }
542
- gPattern(args) {
634
+ gPattern(arguments_) {
543
635
  if (!this.checkSupport()) return Cons.nil;
544
636
  if (this.isOpen) try {
545
- if (args.length() === 2) {
546
- const a0 = args.car;
547
- const a1 = args.cdr.car;
637
+ if (arguments_.length() === 2) {
638
+ const a0 = arguments_.car;
639
+ const a1 = arguments_.cdr.car;
548
640
  if (Cons.isString(a0) && Cons.isNumber(a1)) {
549
641
  const aString = a1 === 0 ? "repeat" : a1 > 0 ? "repeat-x" : "repeat-y";
550
- const ctx = this.ctx;
642
+ const context = this.ctx;
551
643
  const anImage = new Image();
552
644
  anImage.src = a0;
553
645
  anImage.onload = () => {
554
- ctx.fillStyle = ctx.createPattern(anImage, aString);
646
+ context.fillStyle = context.createPattern(anImage, aString);
555
647
  };
556
- ctx.save();
557
648
  return InterpretedSymbol.of("t");
558
649
  }
559
650
  }
560
- this._print("Can not set pattern.");
651
+ this.#print("Can not set pattern.");
561
652
  return Cons.nil;
562
653
  } catch {
563
- this._print("Can not set pattern.");
654
+ this.#print("Can not set pattern.");
564
655
  return Cons.nil;
565
656
  }
566
- this._print("The canvas is closed and cannot be executed.");
657
+ this.#print("The canvas is closed and cannot be executed.");
567
658
  return Cons.nil;
568
659
  }
569
- gQuadCurveTo(args) {
660
+ gQuadCurveTo(arguments_) {
570
661
  if (!this.checkSupport()) return Cons.nil;
571
662
  if (this.isOpen) try {
572
- if (args.length() === 4) {
573
- const a0 = args.car;
574
- const cdr1 = args.cdr;
663
+ if (arguments_.length() === 4) {
664
+ const a0 = arguments_.car;
665
+ const cdr1 = arguments_.cdr;
575
666
  const a1 = cdr1.car;
576
667
  const cdr2 = cdr1.cdr;
577
668
  const a2 = cdr2.car;
578
669
  const a3 = cdr2.cdr.car;
579
670
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
580
671
  this.ctx.quadraticCurveTo(a0, a1, a2, a3);
581
- this.ctx.save();
582
672
  return InterpretedSymbol.of("t");
583
673
  }
584
674
  }
585
- this._print("Can not draw quadratic curve.");
675
+ this.#print("Can not draw quadratic curve.");
586
676
  return Cons.nil;
587
677
  } catch {
588
- this._print("Can not draw quadratic curve.");
678
+ this.#print("Can not draw quadratic curve.");
589
679
  return Cons.nil;
590
680
  }
591
- this._print("The canvas is closed and cannot be executed.");
681
+ this.#print("The canvas is closed and cannot be executed.");
592
682
  return Cons.nil;
593
683
  }
594
- gSaveJpeg() {
595
- if (!this.checkSupport()) return Cons.nil;
596
- if (this.isOpen) try {
597
- const anImage = new Image();
598
- anImage.crossOrigin = "Anonymous";
599
- anImage.src = this.canvas.toDataURL("image/jpeg");
600
- const link = document.createElement("a");
601
- link.href = anImage.src;
602
- link.download = "canvas";
603
- document.body.append(link);
604
- link.click();
605
- link.remove();
606
- return InterpretedSymbol.of("t");
607
- } catch {
608
- this._print("Can not save jpeg. If you are using an image in the canvas, you can't save jpeg.");
609
- return Cons.nil;
610
- }
611
- this._print("The canvas is closed and cannot be executed.");
612
- return Cons.nil;
684
+ gSaveJpeg(arguments_) {
685
+ return this.saveCanvas(arguments_, "image/jpeg", "jpeg");
686
+ }
687
+ gSavePng(arguments_) {
688
+ return this.saveCanvas(arguments_, "image/png", "png");
613
689
  }
614
- gSavePng() {
690
+ /**
691
+ * Shared implementation of `gsave-jpeg` / `gsave-png`. With no argument it
692
+ * triggers a browser download; with a single string argument it writes the
693
+ * encoded image to that file path on Node.js.
694
+ * @param arguments_ - the evaluated argument list (empty, or one path string)
695
+ * @param mimeType - the image MIME type to encode
696
+ * @param label - the format name used in diagnostics ("jpeg" / "png")
697
+ * @return `t` on success, `Cons.nil` otherwise
698
+ */
699
+ saveCanvas(arguments_, mimeType, label) {
615
700
  if (!this.checkSupport()) return Cons.nil;
616
- if (this.isOpen) try {
617
- const anImage = new Image();
618
- anImage.crossOrigin = "Anonymous";
619
- anImage.src = this.canvas.toDataURL("image/png");
620
- const link = document.createElement("a");
621
- link.href = anImage.src;
622
- link.download = "canvas";
623
- document.body.append(link);
624
- link.click();
625
- link.remove();
626
- return InterpretedSymbol.of("t");
627
- } catch {
628
- this._print("Can not save png. If you are using an image in the canvas, you can't save png.");
701
+ if (this.isOpen) {
702
+ const length_ = arguments_.length();
703
+ if (length_ === 0) return this.#downloadCanvas(mimeType, label);
704
+ if (length_ === 1 && Cons.isString(arguments_.car)) return this.#writeCanvasToFile(arguments_.car, mimeType, label);
705
+ this.#print(`Can not save ${label}.`);
629
706
  return Cons.nil;
630
707
  }
631
- this._print("The canvas is closed and cannot be executed.");
708
+ this.#print("The canvas is closed and cannot be executed.");
632
709
  return Cons.nil;
633
710
  }
634
- gScale(args) {
711
+ gScale(arguments_) {
635
712
  if (!this.checkSupport()) return Cons.nil;
636
713
  if (this.isOpen) try {
637
- if (args.length() === 2) {
638
- const a0 = args.car;
639
- const a1 = args.cdr.car;
714
+ if (arguments_.length() === 2) {
715
+ const a0 = arguments_.car;
716
+ const a1 = arguments_.cdr.car;
640
717
  if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
641
718
  this.ctx.scale(a0, a1);
642
- this.ctx.save();
643
719
  return InterpretedSymbol.of("t");
644
720
  }
645
721
  }
646
- this._print("Can not scale.");
722
+ this.#print("Can not scale.");
647
723
  return Cons.nil;
648
724
  } catch {
649
- this._print("Can not scale.");
725
+ this.#print("Can not scale.");
650
726
  return Cons.nil;
651
727
  }
652
- this._print("The canvas is closed and cannot be executed.");
728
+ this.#print("The canvas is closed and cannot be executed.");
653
729
  return Cons.nil;
654
730
  }
655
- gShadowBlur(args) {
731
+ gShadowBlur(arguments_) {
656
732
  if (!this.checkSupport()) return Cons.nil;
657
733
  if (this.isOpen) {
658
- if (args.length() === 1 && Cons.isNumber(args.car)) {
659
- this.ctx.Blur = args.car;
660
- this.ctx.save();
734
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
735
+ this.ctx.shadowBlur = arguments_.car;
661
736
  return InterpretedSymbol.of("t");
662
737
  }
663
- this._print("Can not set shadow blur.");
738
+ this.#print("Can not set shadow blur.");
664
739
  return Cons.nil;
665
740
  }
666
- this._print("The canvas is closed and cannot be executed.");
741
+ this.#print("The canvas is closed and cannot be executed.");
667
742
  return Cons.nil;
668
743
  }
669
- gShadowColor(args) {
744
+ gShadowColor(arguments_) {
670
745
  if (!this.checkSupport()) return Cons.nil;
671
746
  if (this.isOpen) try {
672
- if (args.length() === 1) {
673
- const aColor = this.selectColor(args);
747
+ if (arguments_.length() === 1) {
748
+ const aColor = this.selectColor(arguments_);
674
749
  this.ctx.shadowColor = aColor;
675
- this.ctx.save();
676
750
  return InterpretedSymbol.of("t");
677
751
  }
678
- this._print("Can not set shadow color.");
752
+ this.#print("Can not set shadow color.");
679
753
  return Cons.nil;
680
754
  } catch {
681
- this._print("Can not set shadow color.");
755
+ this.#print("Can not set shadow color.");
682
756
  return Cons.nil;
683
757
  }
684
- this._print("The canvas is closed and cannot be executed.");
758
+ this.#print("The canvas is closed and cannot be executed.");
685
759
  return Cons.nil;
686
760
  }
687
- gShadowOffsetX(args) {
761
+ gShadowOffsetX(arguments_) {
688
762
  if (!this.checkSupport()) return Cons.nil;
689
763
  if (this.isOpen) {
690
- if (args.length() === 1 && Cons.isNumber(args.car)) {
691
- this.ctx.shadowOffsetX = args.car;
692
- this.ctx.save();
764
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
765
+ this.ctx.shadowOffsetX = arguments_.car;
693
766
  return InterpretedSymbol.of("t");
694
767
  }
695
- this._print("Can not set shadow offsetX.");
768
+ this.#print("Can not set shadow offsetX.");
696
769
  return Cons.nil;
697
770
  }
698
- this._print("The canvas is closed and cannot be executed.");
771
+ this.#print("The canvas is closed and cannot be executed.");
699
772
  return Cons.nil;
700
773
  }
701
- gShadowOffsetY(args) {
774
+ gShadowOffsetY(arguments_) {
702
775
  if (!this.checkSupport()) return Cons.nil;
703
776
  if (this.isOpen) {
704
- if (args.length() === 1 && Cons.isNumber(args.car)) {
705
- this.ctx.shadowOffsetY = args.car;
706
- this.ctx.save();
777
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
778
+ this.ctx.shadowOffsetY = arguments_.car;
707
779
  return InterpretedSymbol.of("t");
708
780
  }
709
- this._print("Can not set shadow offsetY.");
781
+ this.#print("Can not set shadow offsetY.");
710
782
  return Cons.nil;
711
783
  }
712
- this._print("The canvas is closed and cannot be executed.");
784
+ this.#print("The canvas is closed and cannot be executed.");
713
785
  return Cons.nil;
714
786
  }
715
- gSleep(args) {
787
+ gSleep(arguments_) {
716
788
  if (!this.checkSupport()) return Cons.nil;
717
789
  if (this.isOpen) {
718
790
  const sleep = (ms) => {
719
791
  const time = Date.now() + ms;
720
792
  while (Date.now() < time);
721
793
  };
722
- if (args.length() === 1 && Cons.isNumber(args.car)) {
723
- sleep(args.car);
794
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
795
+ sleep(arguments_.car);
724
796
  return InterpretedSymbol.of("t");
725
797
  }
726
- this._print("Can not sleep");
798
+ this.#print("Can not sleep");
727
799
  return Cons.nil;
728
800
  }
729
- this._print("The canvas is closed and cannot be executed.");
801
+ this.#print("The canvas is closed and cannot be executed.");
730
802
  return Cons.nil;
731
803
  }
732
804
  gStartPath() {
733
805
  if (!this.checkSupport()) return Cons.nil;
734
806
  if (this.isOpen) try {
735
807
  this.ctx.beginPath();
736
- this.ctx.save();
737
808
  return InterpretedSymbol.of("t");
738
809
  } catch {
739
- this._print("Can not start path.");
810
+ this.#print("Can not start path.");
740
811
  return Cons.nil;
741
812
  }
742
- this._print("The canvas is closed and cannot be executed.");
813
+ this.#print("The canvas is closed and cannot be executed.");
743
814
  return Cons.nil;
744
815
  }
745
816
  gStroke() {
746
817
  if (!this.checkSupport()) return Cons.nil;
747
818
  if (this.isOpen) try {
748
819
  this.ctx.stroke();
749
- this.ctx.save();
750
820
  return InterpretedSymbol.of("t");
751
821
  } catch {
752
- this._print("Can not stroke.");
822
+ this.#print("Can not stroke.");
753
823
  return Cons.nil;
754
824
  }
755
- this._print("The canvas is closed and cannot be executed.");
825
+ this.#print("The canvas is closed and cannot be executed.");
756
826
  return Cons.nil;
757
827
  }
758
- gStrokeColor(args) {
828
+ gStrokeColor(arguments_) {
759
829
  if (!this.checkSupport()) return Cons.nil;
760
830
  if (this.isOpen) try {
761
- if (args.length() >= 1) {
762
- const aColor = this.selectColor(args);
831
+ if (arguments_.length() >= 1) {
832
+ const aColor = this.selectColor(arguments_);
763
833
  this.ctx.strokeStyle = aColor;
764
- this.ctx.save();
765
834
  return InterpretedSymbol.of("t");
766
835
  }
767
- this._print("Can not set stroke color");
836
+ this.#print("Can not set stroke color");
768
837
  return Cons.nil;
769
838
  } catch {
770
- this._print("Can not set stroke color");
839
+ this.#print("Can not set stroke color");
771
840
  return Cons.nil;
772
841
  }
773
- this._print("The canvas is closed and cannot be executed.");
842
+ this.#print("The canvas is closed and cannot be executed.");
774
843
  return Cons.nil;
775
844
  }
776
- gStrokeRect(args) {
845
+ gStrokeRect(arguments_) {
777
846
  if (!this.checkSupport()) return Cons.nil;
778
847
  if (this.isOpen) try {
779
- if (args.length() === 4) {
780
- const a0 = args.car;
781
- const cdr1 = args.cdr;
848
+ if (arguments_.length() === 4) {
849
+ const a0 = arguments_.car;
850
+ const cdr1 = arguments_.cdr;
782
851
  const a1 = cdr1.car;
783
852
  const cdr2 = cdr1.cdr;
784
853
  const a2 = cdr2.car;
785
854
  const a3 = cdr2.cdr.car;
786
855
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
787
856
  this.ctx.strokeRect(a0, a1, a2, a3);
788
- this.ctx.save();
789
857
  return InterpretedSymbol.of("t");
790
858
  }
791
859
  }
792
- this._print("Can not draw stroke rectangle.");
860
+ this.#print("Can not draw stroke rectangle.");
793
861
  return Cons.nil;
794
862
  } catch {
795
- this._print("Can not draw stroke rectangle.");
863
+ this.#print("Can not draw stroke rectangle.");
796
864
  return Cons.nil;
797
865
  }
798
- this._print("The canvas is closed and cannot be executed.");
866
+ this.#print("The canvas is closed and cannot be executed.");
799
867
  return Cons.nil;
800
868
  }
801
- gStrokeText(args) {
869
+ gStrokeText(arguments_) {
802
870
  if (!this.checkSupport()) return Cons.nil;
803
871
  if (this.isOpen) try {
804
- if (args.length() === 3) {
805
- const a0 = args.car;
806
- const cdr1 = args.cdr;
872
+ if (arguments_.length() === 3) {
873
+ const a0 = arguments_.car;
874
+ const cdr1 = arguments_.cdr;
807
875
  const a1 = cdr1.car;
808
876
  const a2 = cdr1.cdr.car;
809
877
  if (Cons.isString(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) {
810
878
  this.ctx.strokeText(a0, a1, a2);
811
- this.ctx.save();
812
879
  return InterpretedSymbol.of("t");
813
880
  }
814
881
  }
815
- this._print("Can not draw fill text.");
882
+ this.#print("Can not draw stroke text.");
816
883
  return Cons.nil;
817
884
  } catch {
818
- this._print("Can not draw fill text.");
885
+ this.#print("Can not draw stroke text.");
819
886
  return Cons.nil;
820
887
  }
821
- this._print("The canvas is closed and cannot be executed.");
888
+ this.#print("The canvas is closed and cannot be executed.");
822
889
  return Cons.nil;
823
890
  }
824
- gStrokeTri(args) {
891
+ gStrokeTri(arguments_) {
825
892
  if (!this.checkSupport()) return Cons.nil;
826
893
  if (this.isOpen) try {
827
- if (args.length() === 6) {
828
- const a0 = args.car;
829
- const cdr1 = args.cdr;
894
+ if (arguments_.length() === 6) {
895
+ const a0 = arguments_.car;
896
+ const cdr1 = arguments_.cdr;
830
897
  const a1 = cdr1.car;
831
898
  const cdr2 = cdr1.cdr;
832
899
  const a2 = cdr2.car;
@@ -842,231 +909,208 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
842
909
  this.ctx.lineTo(a4, a5);
843
910
  this.ctx.closePath();
844
911
  this.ctx.stroke();
845
- this.ctx.save();
846
912
  return InterpretedSymbol.of("t");
847
913
  }
848
914
  }
849
- this._print("Can not draw stroke triangle.");
915
+ this.#print("Can not draw stroke triangle.");
850
916
  return Cons.nil;
851
917
  } catch {
852
- this._print("Can not draw stroke triangle.");
918
+ this.#print("Can not draw stroke triangle.");
853
919
  return Cons.nil;
854
920
  }
855
- this._print("The canvas is closed and cannot be executed.");
921
+ this.#print("The canvas is closed and cannot be executed.");
856
922
  return Cons.nil;
857
923
  }
858
- gTextAlign(args) {
924
+ gTextAlign(arguments_) {
859
925
  if (!this.checkSupport()) return Cons.nil;
860
926
  if (this.isOpen) try {
861
- if (args.length() === 1 && Cons.isString(args.car)) {
862
- this.ctx.textAlign = args.car;
863
- this.ctx.save();
927
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
928
+ this.ctx.textAlign = arguments_.car;
864
929
  return InterpretedSymbol.of("t");
865
930
  }
866
- this._print("Can not set text align.");
931
+ this.#print("Can not set text align.");
867
932
  return Cons.nil;
868
933
  } catch {
869
- this._print("Can not set text align.");
934
+ this.#print("Can not set text align.");
870
935
  return Cons.nil;
871
936
  }
872
- this._print("The canvas is closed and cannot be executed.");
937
+ this.#print("The canvas is closed and cannot be executed.");
873
938
  return Cons.nil;
874
939
  }
875
- gTextBaseline(args) {
940
+ gTextBaseline(arguments_) {
876
941
  if (!this.checkSupport()) return Cons.nil;
877
942
  if (this.isOpen) try {
878
- if (args.length() === 1 && Cons.isString(args.car)) {
879
- this.ctx.textBaseline = args.car;
880
- this.ctx.save();
943
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
944
+ this.ctx.textBaseline = arguments_.car;
881
945
  return InterpretedSymbol.of("t");
882
946
  }
883
- this._print("Can not set text baseline.");
947
+ this.#print("Can not set text baseline.");
884
948
  return Cons.nil;
885
949
  } catch {
886
- this._print("Can not set text baseline.");
950
+ this.#print("Can not set text baseline.");
887
951
  return Cons.nil;
888
952
  }
889
- this._print("The canvas is closed and cannot be executed.");
953
+ this.#print("The canvas is closed and cannot be executed.");
890
954
  return Cons.nil;
891
955
  }
892
- gTextDirection(args) {
956
+ gTextDirection(arguments_) {
893
957
  if (!this.checkSupport()) return Cons.nil;
894
958
  if (this.isOpen) try {
895
- if (args.length() === 1 && Cons.isNumber(args.car)) {
896
- const aString = args.car === 0 ? "inherit" : args.car > 0 ? "rtl" : "ltr";
897
- this.ctx.direction = aString;
898
- this.ctx.save();
959
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
960
+ this.ctx.direction = arguments_.car;
899
961
  return InterpretedSymbol.of("t");
900
962
  }
901
- this._print("Can not set text direction.");
963
+ this.#print("Can not set text direction.");
902
964
  return Cons.nil;
903
965
  } catch {
904
- this._print("Can not set text direction.");
966
+ this.#print("Can not set text direction.");
905
967
  return Cons.nil;
906
968
  }
907
- this._print("The canvas is closed and cannot be executed.");
969
+ this.#print("The canvas is closed and cannot be executed.");
908
970
  return Cons.nil;
909
971
  }
910
- gTextFont(args) {
972
+ gTextFont(arguments_) {
911
973
  if (!this.checkSupport()) return Cons.nil;
912
974
  if (this.isOpen) try {
913
- if (args.length() === 1 && Cons.isString(args.car)) {
914
- this.ctx.font = args.car;
915
- this.ctx.save();
975
+ if (arguments_.length() === 1 && Cons.isString(arguments_.car)) {
976
+ this.ctx.font = arguments_.car;
916
977
  return InterpretedSymbol.of("t");
917
978
  }
918
- this._print("Can not set text font.");
979
+ this.#print("Can not set text font.");
919
980
  return Cons.nil;
920
981
  } catch {
921
- this._print("Can not set text font.");
982
+ this.#print("Can not set text font.");
922
983
  return Cons.nil;
923
984
  }
924
- this._print("The canvas is closed and cannot be executed.");
985
+ this.#print("The canvas is closed and cannot be executed.");
925
986
  return Cons.nil;
926
987
  }
927
- gTranslate(args) {
988
+ gTranslate(arguments_) {
928
989
  if (!this.checkSupport()) return Cons.nil;
929
990
  if (this.isOpen) try {
930
- if (args.length() === 2) {
931
- const a0 = args.car;
932
- const a1 = args.cdr.car;
991
+ if (arguments_.length() === 2) {
992
+ const a0 = arguments_.car;
993
+ const a1 = arguments_.cdr.car;
933
994
  if (Cons.isNumber(a0) && Cons.isNumber(a1)) {
934
995
  this.ctx.translate(a0, a1);
935
- this.ctx.save();
936
996
  return InterpretedSymbol.of("t");
937
997
  }
938
998
  }
939
- this._print("Can not translate.");
999
+ this.#print("Can not translate.");
940
1000
  return Cons.nil;
941
1001
  } catch {
942
- this._print("Can not translate.");
1002
+ this.#print("Can not translate.");
943
1003
  return Cons.nil;
944
1004
  }
945
- this._print("The canvas is closed and cannot be executed.");
1005
+ this.#print("The canvas is closed and cannot be executed.");
946
1006
  return Cons.nil;
947
1007
  }
948
- gRect(args) {
1008
+ gRect(arguments_) {
949
1009
  if (!this.checkSupport()) return Cons.nil;
950
1010
  if (this.isOpen) try {
951
- if (args.length() === 4) {
952
- const a0 = args.car;
953
- const cdr1 = args.cdr;
1011
+ if (arguments_.length() === 4) {
1012
+ const a0 = arguments_.car;
1013
+ const cdr1 = arguments_.cdr;
954
1014
  const a1 = cdr1.car;
955
1015
  const cdr2 = cdr1.cdr;
956
1016
  const a2 = cdr2.car;
957
1017
  const a3 = cdr2.cdr.car;
958
1018
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) {
959
1019
  this.ctx.rect(a0, a1, a2, a3);
960
- this.ctx.save();
961
1020
  return InterpretedSymbol.of("t");
962
1021
  }
963
1022
  }
964
- this._print("Can not draw rectangle.");
1023
+ this.#print("Can not draw rectangle.");
965
1024
  return Cons.nil;
966
1025
  } catch {
967
- this._print("Can not draw rectangle.");
1026
+ this.#print("Can not draw rectangle.");
968
1027
  return Cons.nil;
969
1028
  }
970
- this._print("The canvas is closed and cannot be executed.");
1029
+ this.#print("The canvas is closed and cannot be executed.");
971
1030
  return Cons.nil;
972
1031
  }
973
- gRotate(args) {
1032
+ gRotate(arguments_) {
974
1033
  if (!this.checkSupport()) return Cons.nil;
975
1034
  if (this.isOpen) try {
976
- if (args.length() === 1 && Cons.isNumber(args.car)) {
977
- this.ctx.rotate(Math.PI / 180 * args.car);
978
- this.ctx.save();
1035
+ if (arguments_.length() === 1 && Cons.isNumber(arguments_.car)) {
1036
+ this.ctx.rotate(Math.PI / 180 * arguments_.car);
979
1037
  return InterpretedSymbol.of("t");
980
1038
  }
981
- this._print("Can not rotate.");
1039
+ this.#print("Can not rotate.");
982
1040
  return Cons.nil;
983
1041
  } catch {
984
- this._print("Can not rotate.");
1042
+ this.#print("Can not rotate.");
985
1043
  return Cons.nil;
986
1044
  }
987
- this._print("The canvas is closed and cannot be executed.");
1045
+ this.#print("The canvas is closed and cannot be executed.");
1046
+ return Cons.nil;
1047
+ }
1048
+ /**
1049
+ * Pushes the current drawing state (styles, transform, clip) onto the
1050
+ * context's state stack. Pairs with `gRestore` to let Lisp callers manage
1051
+ * state explicitly. Replaces the legacy per-method `ctx.save()` calls, which
1052
+ * pushed state on every draw with no matching `restore()` and grew the stack
1053
+ * unbounded.
1054
+ * @return `t` on success, `Cons.nil` otherwise
1055
+ */
1056
+ gSave() {
1057
+ if (!this.checkSupport()) return Cons.nil;
1058
+ if (this.isOpen) try {
1059
+ this.ctx.save();
1060
+ return InterpretedSymbol.of("t");
1061
+ } catch {
1062
+ this.#print("Can not save.");
1063
+ return Cons.nil;
1064
+ }
1065
+ this.#print("The canvas is closed and cannot be executed.");
1066
+ return Cons.nil;
1067
+ }
1068
+ /**
1069
+ * Pops the most recently saved drawing state off the context's state stack.
1070
+ * Pairs with `gSave`. Popping an empty stack is a no-op per the Canvas spec.
1071
+ * @return `t` on success, `Cons.nil` otherwise
1072
+ */
1073
+ gRestore() {
1074
+ if (!this.checkSupport()) return Cons.nil;
1075
+ if (this.isOpen) try {
1076
+ this.ctx.restore();
1077
+ return InterpretedSymbol.of("t");
1078
+ } catch {
1079
+ this.#print("Can not restore.");
1080
+ return Cons.nil;
1081
+ }
1082
+ this.#print("The canvas is closed and cannot be executed.");
988
1083
  return Cons.nil;
989
1084
  }
990
1085
  /**
991
1086
  * Parses a color spec from the head of the argument Cons. Accepts
992
1087
  * (1 string), (3 numbers — rgb), or (4 numbers — rgba); falls back to
993
1088
  * `'black'` on anything else (legacy behavior).
994
- * @param args - the argument Cons to parse
1089
+ * @param arguments_ - the argument Cons to parse
995
1090
  * @return CSS color string
996
1091
  */
997
- selectColor(args) {
1092
+ selectColor(arguments_) {
998
1093
  let aColor = "black";
999
- const len = args.length();
1000
- const a0 = args.car;
1001
- if (len === 1 && Cons.isString(a0)) aColor = a0;
1002
- else if (len === 3) {
1003
- const cdr1 = args.cdr;
1094
+ const length_ = arguments_.length();
1095
+ const a0 = arguments_.car;
1096
+ if (length_ === 1 && Cons.isString(a0)) aColor = a0;
1097
+ else if (length_ === 3) {
1098
+ const cdr1 = arguments_.cdr;
1004
1099
  const a1 = cdr1.car;
1005
1100
  const a2 = cdr1.cdr.car;
1006
1101
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2)) aColor = `rgb(${a0}, ${a1}, ${a2})`;
1007
- else this._print("Can not set color. set color \"black\".");
1008
- } else if (len === 4) {
1009
- const cdr1 = args.cdr;
1102
+ else this.#print("Can not set color. set color \"black\".");
1103
+ } else if (length_ === 4) {
1104
+ const cdr1 = arguments_.cdr;
1010
1105
  const a1 = cdr1.car;
1011
1106
  const cdr2 = cdr1.cdr;
1012
1107
  const a2 = cdr2.car;
1013
1108
  const a3 = cdr2.cdr.car;
1014
1109
  if (Cons.isNumber(a0) && Cons.isNumber(a1) && Cons.isNumber(a2) && Cons.isNumber(a3)) aColor = `rgba(${a0}, ${a1}, ${a2}, ${a3})`;
1015
- else this._print("Can not set color. set color \"black\".");
1016
- } else this._print("Can not set color. set color \"black\".");
1110
+ else this.#print("Can not set color. set color \"black\".");
1111
+ } else this.#print("Can not set color. set color \"black\".");
1017
1112
  return aColor;
1018
1113
  }
1019
- /**
1020
- * Builds the dispatch table.
1021
- * @return the dispatch table
1022
- */
1023
- static setup() {
1024
- const aTable = /* @__PURE__ */ new Map();
1025
- aTable.set(InterpretedSymbol.of("galpha"), "gAlpha");
1026
- aTable.set(InterpretedSymbol.of("garc"), "gArc");
1027
- aTable.set(InterpretedSymbol.of("garc-to"), "gArcTo");
1028
- aTable.set(InterpretedSymbol.of("gbezcurve-to"), "gBezCurveTo");
1029
- aTable.set(InterpretedSymbol.of("gclear"), "gClear");
1030
- aTable.set(InterpretedSymbol.of("gclose"), "gClose");
1031
- aTable.set(InterpretedSymbol.of("gcolor"), "gColor");
1032
- aTable.set(InterpretedSymbol.of("gfill"), "gFill");
1033
- aTable.set(InterpretedSymbol.of("gfill-color"), "gFillColor");
1034
- aTable.set(InterpretedSymbol.of("gfill-rect"), "gFillRect");
1035
- aTable.set(InterpretedSymbol.of("gfill-text"), "gFillText");
1036
- aTable.set(InterpretedSymbol.of("gfill-tri"), "gFillTri");
1037
- aTable.set(InterpretedSymbol.of("gfinish-path"), "gFinishPath");
1038
- aTable.set(InterpretedSymbol.of("gimage"), "gImage");
1039
- aTable.set(InterpretedSymbol.of("gmove-to"), "gMoveTo");
1040
- aTable.set(InterpretedSymbol.of("gline-to"), "gLineTo");
1041
- aTable.set(InterpretedSymbol.of("gline-cap"), "gLineCap");
1042
- aTable.set(InterpretedSymbol.of("gline-join"), "gLineJoin");
1043
- aTable.set(InterpretedSymbol.of("gline-width"), "gLineWidth");
1044
- aTable.set(InterpretedSymbol.of("gopen"), "gOpen");
1045
- aTable.set(InterpretedSymbol.of("gpattern"), "gPattern");
1046
- aTable.set(InterpretedSymbol.of("gquadcurve-to"), "gQuadCurveTo");
1047
- aTable.set(InterpretedSymbol.of("gsave-jpeg"), "gSaveJpeg");
1048
- aTable.set(InterpretedSymbol.of("gsave-png"), "gSavePng");
1049
- aTable.set(InterpretedSymbol.of("gscale"), "gScale");
1050
- aTable.set(InterpretedSymbol.of("gshadow-blur"), "gShadowBlur");
1051
- aTable.set(InterpretedSymbol.of("gshadow-color"), "gShadowColor");
1052
- aTable.set(InterpretedSymbol.of("gshadow-offsetx"), "gShadowOffsetX");
1053
- aTable.set(InterpretedSymbol.of("gshadow-offsety"), "gShadowOffsetY");
1054
- aTable.set(InterpretedSymbol.of("gsleep"), "gSleep");
1055
- aTable.set(InterpretedSymbol.of("gstart-path"), "gStartPath");
1056
- aTable.set(InterpretedSymbol.of("gstroke"), "gStroke");
1057
- aTable.set(InterpretedSymbol.of("gstroke-color"), "gStrokeColor");
1058
- aTable.set(InterpretedSymbol.of("gstroke-rect"), "gStrokeRect");
1059
- aTable.set(InterpretedSymbol.of("gstroke-text"), "gStrokeText");
1060
- aTable.set(InterpretedSymbol.of("gstroke-tri"), "gStrokeTri");
1061
- aTable.set(InterpretedSymbol.of("gtext-align"), "gTextAlign");
1062
- aTable.set(InterpretedSymbol.of("gtext-dire"), "gTextDirection");
1063
- aTable.set(InterpretedSymbol.of("gtext-font"), "gTextFont");
1064
- aTable.set(InterpretedSymbol.of("gtext-line"), "gTextBaseline");
1065
- aTable.set(InterpretedSymbol.of("gtranslate"), "gTranslate");
1066
- aTable.set(InterpretedSymbol.of("grect"), "gRect");
1067
- aTable.set(InterpretedSymbol.of("grotate"), "gRotate");
1068
- return aTable;
1069
- }
1070
1114
  };
1071
1115
  //#endregion
1072
1116
  //#region src/index.ts