kei-lisp-plugin-graphics 2.0.0 → 3.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/README.md +30 -12
- package/dist/index.cjs +617 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -32
- package/dist/index.d.ts +38 -32
- package/dist/index.js +617 -234
- package/dist/index.js.map +1 -1
- package/package.json +16 -6
package/dist/index.cjs
CHANGED
|
@@ -1,26 +1,128 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let kei_lisp = require("kei-lisp");
|
|
3
3
|
//#region src/GraphicsPlugin/index.ts
|
|
4
|
+
const LINE_CAPS = /* @__PURE__ */ new Set([
|
|
5
|
+
"butt",
|
|
6
|
+
"round",
|
|
7
|
+
"square"
|
|
8
|
+
]);
|
|
9
|
+
const LINE_JOINS = /* @__PURE__ */ new Set([
|
|
10
|
+
"miter",
|
|
11
|
+
"round",
|
|
12
|
+
"bevel"
|
|
13
|
+
]);
|
|
14
|
+
const TEXT_ALIGNS = /* @__PURE__ */ new Set([
|
|
15
|
+
"left",
|
|
16
|
+
"right",
|
|
17
|
+
"center",
|
|
18
|
+
"start",
|
|
19
|
+
"end"
|
|
20
|
+
]);
|
|
21
|
+
const TEXT_BASELINES = /* @__PURE__ */ new Set([
|
|
22
|
+
"top",
|
|
23
|
+
"hanging",
|
|
24
|
+
"middle",
|
|
25
|
+
"alphabetic",
|
|
26
|
+
"ideographic",
|
|
27
|
+
"bottom"
|
|
28
|
+
]);
|
|
29
|
+
const TEXT_DIRECTIONS = /* @__PURE__ */ new Set([
|
|
30
|
+
"ltr",
|
|
31
|
+
"rtl",
|
|
32
|
+
"inherit"
|
|
33
|
+
]);
|
|
34
|
+
const FONT_KERNINGS = /* @__PURE__ */ new Set([
|
|
35
|
+
"auto",
|
|
36
|
+
"normal",
|
|
37
|
+
"none"
|
|
38
|
+
]);
|
|
39
|
+
const FONT_STRETCHES = /* @__PURE__ */ new Set([
|
|
40
|
+
"ultra-condensed",
|
|
41
|
+
"extra-condensed",
|
|
42
|
+
"condensed",
|
|
43
|
+
"semi-condensed",
|
|
44
|
+
"normal",
|
|
45
|
+
"semi-expanded",
|
|
46
|
+
"expanded",
|
|
47
|
+
"extra-expanded",
|
|
48
|
+
"ultra-expanded"
|
|
49
|
+
]);
|
|
50
|
+
const FONT_VARIANTS = /* @__PURE__ */ new Set([
|
|
51
|
+
"normal",
|
|
52
|
+
"small-caps",
|
|
53
|
+
"all-small-caps",
|
|
54
|
+
"petite-caps",
|
|
55
|
+
"all-petite-caps",
|
|
56
|
+
"unicase",
|
|
57
|
+
"titling-caps"
|
|
58
|
+
]);
|
|
59
|
+
const TEXT_RENDERINGS = /* @__PURE__ */ new Set([
|
|
60
|
+
"auto",
|
|
61
|
+
"optimizeSpeed",
|
|
62
|
+
"optimizeLegibility",
|
|
63
|
+
"geometricPrecision"
|
|
64
|
+
]);
|
|
65
|
+
const PATTERN_REPETITIONS = /* @__PURE__ */ new Set([
|
|
66
|
+
"repeat",
|
|
67
|
+
"repeat-x",
|
|
68
|
+
"repeat-y",
|
|
69
|
+
"no-repeat"
|
|
70
|
+
]);
|
|
71
|
+
const COMPOSITE_OPERATIONS = /* @__PURE__ */ new Set([
|
|
72
|
+
"source-over",
|
|
73
|
+
"source-in",
|
|
74
|
+
"source-out",
|
|
75
|
+
"source-atop",
|
|
76
|
+
"destination-over",
|
|
77
|
+
"destination-in",
|
|
78
|
+
"destination-out",
|
|
79
|
+
"destination-atop",
|
|
80
|
+
"lighter",
|
|
81
|
+
"copy",
|
|
82
|
+
"xor",
|
|
83
|
+
"multiply",
|
|
84
|
+
"screen",
|
|
85
|
+
"overlay",
|
|
86
|
+
"darken",
|
|
87
|
+
"lighten",
|
|
88
|
+
"color-dodge",
|
|
89
|
+
"color-burn",
|
|
90
|
+
"hard-light",
|
|
91
|
+
"soft-light",
|
|
92
|
+
"difference",
|
|
93
|
+
"exclusion",
|
|
94
|
+
"hue",
|
|
95
|
+
"saturation",
|
|
96
|
+
"color",
|
|
97
|
+
"luminosity"
|
|
98
|
+
]);
|
|
4
99
|
/**
|
|
5
100
|
* @class
|
|
6
101
|
* @classdesc Canvas2D drawing plugin for the kei-lisp interpreter. Implements
|
|
7
102
|
* the `KeiLispPlugin` contract (`name` / `has` / `apply`) and
|
|
8
|
-
* exposes
|
|
103
|
+
* exposes 75 `g…` Lisp functions that proxy to a 2D rendering
|
|
9
104
|
* context.
|
|
10
105
|
* @author Keisuke Ikeda
|
|
11
|
-
* @this {GraphicsPlugin}
|
|
12
106
|
*/
|
|
13
|
-
var GraphicsPlugin = class GraphicsPlugin
|
|
107
|
+
var GraphicsPlugin = class GraphicsPlugin {
|
|
14
108
|
/**
|
|
15
109
|
* Dispatch map from a Lisp function name (InterpretedSymbol) to the name of
|
|
16
|
-
* the GraphicsPlugin method that implements it.
|
|
110
|
+
* the GraphicsPlugin method that implements it. Private so hosts cannot
|
|
111
|
+
* mutate the registered function set.
|
|
17
112
|
*/
|
|
18
|
-
static
|
|
113
|
+
static #builtInFunctions = GraphicsPlugin.#setup();
|
|
114
|
+
/**
|
|
115
|
+
* Lists every Lisp function name this plugin registers.
|
|
116
|
+
* @return the sorted `g…` function names
|
|
117
|
+
*/
|
|
118
|
+
static functionNames() {
|
|
119
|
+
return [...GraphicsPlugin.#builtInFunctions.keys()].map(String).sort((a, b) => a.localeCompare(b));
|
|
120
|
+
}
|
|
19
121
|
/**
|
|
20
122
|
* Builds the dispatch table.
|
|
21
123
|
* @return the dispatch table
|
|
22
124
|
*/
|
|
23
|
-
static setup() {
|
|
125
|
+
static #setup() {
|
|
24
126
|
const aTable = /* @__PURE__ */ new Map();
|
|
25
127
|
aTable.set(kei_lisp.InterpretedSymbol.of("galpha"), "gAlpha");
|
|
26
128
|
aTable.set(kei_lisp.InterpretedSymbol.of("garc"), "gArc");
|
|
@@ -59,17 +161,55 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
59
161
|
aTable.set(kei_lisp.InterpretedSymbol.of("gstroke-text"), "gStrokeText");
|
|
60
162
|
aTable.set(kei_lisp.InterpretedSymbol.of("gstroke-tri"), "gStrokeTri");
|
|
61
163
|
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-align"), "gTextAlign");
|
|
62
|
-
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-
|
|
164
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-direction"), "gTextDirection");
|
|
63
165
|
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-font"), "gTextFont");
|
|
166
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-baseline"), "gTextBaseline");
|
|
167
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-dire"), "gTextDirection");
|
|
64
168
|
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-line"), "gTextBaseline");
|
|
65
169
|
aTable.set(kei_lisp.InterpretedSymbol.of("gtranslate"), "gTranslate");
|
|
66
170
|
aTable.set(kei_lisp.InterpretedSymbol.of("grect"), "gRect");
|
|
67
171
|
aTable.set(kei_lisp.InterpretedSymbol.of("grotate"), "gRotate");
|
|
68
172
|
aTable.set(kei_lisp.InterpretedSymbol.of("gsave"), "gSave");
|
|
69
173
|
aTable.set(kei_lisp.InterpretedSymbol.of("grestore"), "gRestore");
|
|
174
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gellipse"), "gEllipse");
|
|
175
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("ground-rect"), "gRoundRect");
|
|
176
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gline-dash"), "gLineDash");
|
|
177
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gline-dash-offset"), "gLineDashOffset");
|
|
178
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gmiter-limit"), "gMiterLimit");
|
|
179
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gclip"), "gClip");
|
|
180
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gis-point-in-path"), "gIsPointInPath");
|
|
181
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gis-point-in-stroke"), "gIsPointInStroke");
|
|
182
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gtransform"), "gTransform");
|
|
183
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gset-transform"), "gSetTransform");
|
|
184
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("greset-transform"), "gResetTransform");
|
|
185
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gcomposite"), "gComposite");
|
|
186
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gfilter"), "gFilter");
|
|
187
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gimage-smoothing"), "gImageSmoothing");
|
|
188
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gmeasure-text"), "gMeasureText");
|
|
189
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gletter-spacing"), "gLetterSpacing");
|
|
190
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gword-spacing"), "gWordSpacing");
|
|
191
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gfont-kerning"), "gFontKerning");
|
|
192
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gfont-stretch"), "gFontStretch");
|
|
193
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gfont-variant"), "gFontVariant");
|
|
194
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gtext-rendering"), "gTextRendering");
|
|
195
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gclear-rect"), "gClearRect");
|
|
196
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("greset"), "gReset");
|
|
197
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gwidth"), "gWidth");
|
|
198
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gheight"), "gHeight");
|
|
199
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gpixel"), "gPixel");
|
|
200
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gset-pixel"), "gSetPixel");
|
|
201
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("glinear-gradient"), "gLinearGradient");
|
|
202
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gradial-gradient"), "gRadialGradient");
|
|
203
|
+
aTable.set(kei_lisp.InterpretedSymbol.of("gconic-gradient"), "gConicGradient");
|
|
70
204
|
return aTable;
|
|
71
205
|
}
|
|
72
206
|
/**
|
|
207
|
+
* Loaded-image cache shared by `gimage` and `gpattern`. Repeated draws of
|
|
208
|
+
* the same `src` reuse the loaded element and run synchronously, so they
|
|
209
|
+
* keep their place in the drawing order instead of racing the load.
|
|
210
|
+
*/
|
|
211
|
+
#imageCache = /* @__PURE__ */ new Map();
|
|
212
|
+
/**
|
|
73
213
|
* Plugin identifier, used for diagnostics.
|
|
74
214
|
*/
|
|
75
215
|
name = "graphics";
|
|
@@ -82,22 +222,160 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
82
222
|
* @param canvas - the canvas to draw to
|
|
83
223
|
*/
|
|
84
224
|
constructor(canvas) {
|
|
85
|
-
super();
|
|
86
225
|
this.canvas = canvas;
|
|
87
226
|
this.ctx = canvas.getContext("2d");
|
|
88
227
|
this.isOpen = false;
|
|
89
228
|
}
|
|
90
229
|
/**
|
|
91
|
-
* Writes a diagnostic line
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
230
|
+
* Writes a diagnostic line to `process.stderr`, matching the convention
|
|
231
|
+
* 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.
|
|
97
237
|
* @param line - the line to write
|
|
98
238
|
*/
|
|
99
239
|
#print(line) {
|
|
100
|
-
|
|
240
|
+
const stderr = globalThis.process?.stderr;
|
|
241
|
+
if (typeof stderr?.write === "function") {
|
|
242
|
+
stderr.write(line + "\n");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
console.error(line);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Resolves an image for the given source and runs `draw` with it —
|
|
249
|
+
* synchronously when the image is already loaded, on its `load` event
|
|
250
|
+
* otherwise. A load failure prints a diagnostic once.
|
|
251
|
+
* @param source - the image URL / data URI
|
|
252
|
+
* @param draw - the drawing action to run once the image is available
|
|
253
|
+
*/
|
|
254
|
+
#withImage(source, draw) {
|
|
255
|
+
const cached = this.#imageCache.get(source);
|
|
256
|
+
const image = cached ?? new Image();
|
|
257
|
+
if (cached === void 0) {
|
|
258
|
+
image.src = source;
|
|
259
|
+
this.#imageCache.set(source, image);
|
|
260
|
+
image.addEventListener("error", () => {
|
|
261
|
+
this.#print(`Can not load image: ${source}`);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (image.complete) {
|
|
265
|
+
draw(image);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
image.addEventListener("load", () => {
|
|
269
|
+
draw(image);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
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
|
|
277
|
+
* @param body - the drawing action; returns the Lisp result, or `null` on
|
|
278
|
+
* bad arguments
|
|
279
|
+
* @return the body's result, or `Cons.nil` on failure
|
|
280
|
+
*/
|
|
281
|
+
#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
|
+
}
|
|
287
|
+
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;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Reads a single string argument and validates it against an allowlist.
|
|
296
|
+
* @param arguments_ - the evaluated argument list
|
|
297
|
+
* @param allowed - the accepted values
|
|
298
|
+
* @return the validated string, or `null` on wrong arity, a non-string
|
|
299
|
+
* argument, or a value outside the allowlist
|
|
300
|
+
*/
|
|
301
|
+
#enumString(arguments_, allowed) {
|
|
302
|
+
if (arguments_.length() !== 1 || !kei_lisp.Cons.isString(arguments_.car)) return null;
|
|
303
|
+
return allowed.has(arguments_.car) ? arguments_.car : null;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Reads exactly `count` numbers from the argument list.
|
|
307
|
+
* @param arguments_ - the evaluated argument list
|
|
308
|
+
* @param count - the expected argument count
|
|
309
|
+
* @return the numbers, or `null` on wrong arity or a non-number argument
|
|
310
|
+
*/
|
|
311
|
+
#numbers(arguments_, count) {
|
|
312
|
+
if (arguments_.length() !== count) return null;
|
|
313
|
+
const values = [];
|
|
314
|
+
let rest = arguments_;
|
|
315
|
+
for (let index = 0; index < count; index++) {
|
|
316
|
+
if (!kei_lisp.Cons.isNumber(rest.car)) return null;
|
|
317
|
+
values.push(rest.car);
|
|
318
|
+
rest = rest.cdr;
|
|
319
|
+
}
|
|
320
|
+
return values;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Flattens the argument list into a JS array.
|
|
324
|
+
* @param arguments_ - the evaluated argument list
|
|
325
|
+
* @return the argument values in order
|
|
326
|
+
*/
|
|
327
|
+
#listValues(arguments_) {
|
|
328
|
+
const values = [];
|
|
329
|
+
let rest = arguments_;
|
|
330
|
+
for (let index = arguments_.length(); index > 0; index--) {
|
|
331
|
+
values.push(rest.car);
|
|
332
|
+
rest = rest.cdr;
|
|
333
|
+
}
|
|
334
|
+
return values;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Builds a Lisp list (Cons chain) from JS values.
|
|
338
|
+
* @param values - the values, at least one
|
|
339
|
+
* @return the list head
|
|
340
|
+
*/
|
|
341
|
+
#toList(values) {
|
|
342
|
+
const head = new kei_lisp.Cons(values[0]);
|
|
343
|
+
let tail = head;
|
|
344
|
+
for (let index = 1; index < values.length; index++) {
|
|
345
|
+
tail.cdr = new kei_lisp.Cons(values[index]);
|
|
346
|
+
tail = tail.cdr;
|
|
347
|
+
}
|
|
348
|
+
return head;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Applies `(offset color)` pairs to a gradient.
|
|
352
|
+
* @param gradient - the gradient to add stops to
|
|
353
|
+
* @param stops - alternating numeric offsets (0–1) and CSS color strings
|
|
354
|
+
* @return true when every pair was valid and at least one was applied
|
|
355
|
+
*/
|
|
356
|
+
#applyGradientStops(gradient, stops) {
|
|
357
|
+
if (stops.length === 0 || stops.length % 2 !== 0) return false;
|
|
358
|
+
for (let index = 0; index < stops.length; index += 2) {
|
|
359
|
+
const offset = stops[index];
|
|
360
|
+
const color = stops[index + 1];
|
|
361
|
+
if (!kei_lisp.Cons.isNumber(offset) || !kei_lisp.Cons.isString(color)) return false;
|
|
362
|
+
gradient.addColorStop(offset, color);
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Sets both `fillStyle` and `strokeStyle` to the given gradient after
|
|
368
|
+
* applying its stops, mirroring how `gcolor` sets both styles at once.
|
|
369
|
+
* @param context - the 2D context
|
|
370
|
+
* @param gradient - the gradient to install
|
|
371
|
+
* @param stops - alternating offsets and colors (see #applyGradientStops)
|
|
372
|
+
* @return `t` on success, `null` on invalid stops
|
|
373
|
+
*/
|
|
374
|
+
#installGradient(context, gradient, stops) {
|
|
375
|
+
if (!this.#applyGradientStops(gradient, stops)) return null;
|
|
376
|
+
context.fillStyle = gradient;
|
|
377
|
+
context.strokeStyle = gradient;
|
|
378
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
101
379
|
}
|
|
102
380
|
/**
|
|
103
381
|
* Encodes the canvas via `toDataURL` and triggers a browser download through
|
|
@@ -166,7 +444,7 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
166
444
|
* @return true if `apply` should be called
|
|
167
445
|
*/
|
|
168
446
|
has(aSymbol) {
|
|
169
|
-
return GraphicsPlugin.
|
|
447
|
+
return GraphicsPlugin.#builtInFunctions.has(aSymbol);
|
|
170
448
|
}
|
|
171
449
|
/**
|
|
172
450
|
* Dispatches the given symbol to the matching `g…` method.
|
|
@@ -176,39 +454,17 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
176
454
|
* @return the method's result, or `Cons.nil` if dispatch fails
|
|
177
455
|
*/
|
|
178
456
|
apply(aSymbol, arguments_, _context) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
* @return the method's result, or `Cons.nil` if not registered
|
|
186
|
-
*/
|
|
187
|
-
selectProcedure(procedure, arguments_) {
|
|
188
|
-
if (GraphicsPlugin.buildInFunctions.has(procedure)) return this.buildInFunction(procedure, arguments_);
|
|
189
|
-
this.#print(`I could find no procedure description for ${String(procedure)}`);
|
|
190
|
-
return kei_lisp.Cons.nil;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Looks up and invokes the JS method that implements the given Lisp symbol.
|
|
194
|
-
* Throws `TypeError` when the dispatch table points to a method that does
|
|
195
|
-
* not exist on the instance — this matches the legacy behavior, where the
|
|
196
|
-
* Ramda `R.invoker(1, methodName)(args, this)` call would surface the same
|
|
197
|
-
* error by attempting to call `undefined`.
|
|
198
|
-
* @param procedure - the Lisp symbol
|
|
199
|
-
* @param arguments_ - the evaluated argument list
|
|
200
|
-
* @return the method's result
|
|
201
|
-
*/
|
|
202
|
-
buildInFunction(procedure, arguments_) {
|
|
203
|
-
const methodName = GraphicsPlugin.buildInFunctions.get(procedure);
|
|
204
|
-
const method = this[methodName];
|
|
205
|
-
if (typeof method !== "function") throw new TypeError(`${this.constructor.name} does not have a method named "${methodName}"`);
|
|
206
|
-
return method.call(this, arguments_);
|
|
457
|
+
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
|
+
}
|
|
462
|
+
return this[methodName].call(this, arguments_);
|
|
207
463
|
}
|
|
208
464
|
/**
|
|
209
465
|
* Checks whether the canvas exposes a usable 2D context, and narrows
|
|
210
466
|
* `this.ctx` to non-null for the caller when it returns true.
|
|
211
|
-
* @return type predicate — true when
|
|
467
|
+
* @return type predicate — true when context is non-null
|
|
212
468
|
*/
|
|
213
469
|
checkSupport() {
|
|
214
470
|
if (this.ctx === null) {
|
|
@@ -317,19 +573,15 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
317
573
|
this.#print("The canvas is closed and cannot be executed.");
|
|
318
574
|
return kei_lisp.Cons.nil;
|
|
319
575
|
}
|
|
320
|
-
gClear() {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.
|
|
576
|
+
gClear(arguments_) {
|
|
577
|
+
return this.#execute("Can not clear.", (context) => {
|
|
578
|
+
const aColor = arguments_.length() === 0 ? "#ffffff" : this.selectColor(arguments_);
|
|
579
|
+
const previous = context.fillStyle;
|
|
580
|
+
context.fillStyle = aColor;
|
|
581
|
+
context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
582
|
+
context.fillStyle = previous;
|
|
326
583
|
return kei_lisp.InterpretedSymbol.of("t");
|
|
327
|
-
}
|
|
328
|
-
this.#print("Can not clear.");
|
|
329
|
-
return kei_lisp.Cons.nil;
|
|
330
|
-
}
|
|
331
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
332
|
-
return kei_lisp.Cons.nil;
|
|
584
|
+
});
|
|
333
585
|
}
|
|
334
586
|
gClose() {
|
|
335
587
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -416,26 +668,17 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
416
668
|
return kei_lisp.Cons.nil;
|
|
417
669
|
}
|
|
418
670
|
gFillText(arguments_) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
this.#print("Can not draw fill text.");
|
|
432
|
-
return kei_lisp.Cons.nil;
|
|
433
|
-
} catch {
|
|
434
|
-
this.#print("Can not draw fill text.");
|
|
435
|
-
return kei_lisp.Cons.nil;
|
|
436
|
-
}
|
|
437
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
438
|
-
return kei_lisp.Cons.nil;
|
|
671
|
+
return this.#execute("Can not draw fill text.", (context) => {
|
|
672
|
+
const length_ = arguments_.length();
|
|
673
|
+
if (length_ !== 3 && length_ !== 4) return null;
|
|
674
|
+
const [a0, a1, a2, a3] = this.#listValues(arguments_);
|
|
675
|
+
if (!kei_lisp.Cons.isString(a0) || !kei_lisp.Cons.isNumber(a1) || !kei_lisp.Cons.isNumber(a2)) return null;
|
|
676
|
+
if (length_ === 4) {
|
|
677
|
+
if (!kei_lisp.Cons.isNumber(a3)) return null;
|
|
678
|
+
context.fillText(a0, a1, a2, a3);
|
|
679
|
+
} else context.fillText(a0, a1, a2);
|
|
680
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
681
|
+
});
|
|
439
682
|
}
|
|
440
683
|
gFillTri(arguments_) {
|
|
441
684
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -482,50 +725,21 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
482
725
|
return kei_lisp.Cons.nil;
|
|
483
726
|
}
|
|
484
727
|
gImage(arguments_) {
|
|
485
|
-
|
|
486
|
-
if (this.isOpen) try {
|
|
728
|
+
return this.#execute("Can not draw Image.", (context) => {
|
|
487
729
|
const length_ = arguments_.length();
|
|
488
|
-
if (length_
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
} else if (length_ === 5) {
|
|
503
|
-
const a0 = arguments_.car;
|
|
504
|
-
const cdr1 = arguments_.cdr;
|
|
505
|
-
const a1 = cdr1.car;
|
|
506
|
-
const cdr2 = cdr1.cdr;
|
|
507
|
-
const a2 = cdr2.car;
|
|
508
|
-
const cdr3 = cdr2.cdr;
|
|
509
|
-
const a3 = cdr3.car;
|
|
510
|
-
const a4 = cdr3.cdr.car;
|
|
511
|
-
if (kei_lisp.Cons.isString(a0) && kei_lisp.Cons.isNumber(a1) && kei_lisp.Cons.isNumber(a2) && kei_lisp.Cons.isNumber(a3) && kei_lisp.Cons.isNumber(a4)) {
|
|
512
|
-
const context = this.ctx;
|
|
513
|
-
const anImage = new Image();
|
|
514
|
-
anImage.src = a0;
|
|
515
|
-
anImage.onload = () => {
|
|
516
|
-
context.drawImage(anImage, a1, a2, a3, a4);
|
|
517
|
-
};
|
|
518
|
-
return kei_lisp.InterpretedSymbol.of("t");
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
this.#print("Can not draw Image.");
|
|
522
|
-
return kei_lisp.Cons.nil;
|
|
523
|
-
} catch {
|
|
524
|
-
this.#print("Can not draw Image.");
|
|
525
|
-
return kei_lisp.Cons.nil;
|
|
526
|
-
}
|
|
527
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
528
|
-
return kei_lisp.Cons.nil;
|
|
730
|
+
if (length_ !== 3 && length_ !== 5) return null;
|
|
731
|
+
const [a0, a1, a2, a3, a4] = this.#listValues(arguments_);
|
|
732
|
+
if (!kei_lisp.Cons.isString(a0) || !kei_lisp.Cons.isNumber(a1) || !kei_lisp.Cons.isNumber(a2)) return null;
|
|
733
|
+
if (length_ === 5) {
|
|
734
|
+
if (!kei_lisp.Cons.isNumber(a3) || !kei_lisp.Cons.isNumber(a4)) return null;
|
|
735
|
+
this.#withImage(a0, (image) => {
|
|
736
|
+
context.drawImage(image, a1, a2, a3, a4);
|
|
737
|
+
});
|
|
738
|
+
} else this.#withImage(a0, (image) => {
|
|
739
|
+
context.drawImage(image, a1, a2);
|
|
740
|
+
});
|
|
741
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
742
|
+
});
|
|
529
743
|
}
|
|
530
744
|
gLineTo(arguments_) {
|
|
531
745
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -548,36 +762,20 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
548
762
|
return kei_lisp.Cons.nil;
|
|
549
763
|
}
|
|
550
764
|
gLineCap(arguments_) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
this.#print("Can not set line cap.");
|
|
558
|
-
return kei_lisp.Cons.nil;
|
|
559
|
-
} catch {
|
|
560
|
-
this.#print("Can not set line cap.");
|
|
561
|
-
return kei_lisp.Cons.nil;
|
|
562
|
-
}
|
|
563
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
564
|
-
return kei_lisp.Cons.nil;
|
|
765
|
+
return this.#execute("Can not set line cap. Expected \"butt\" / \"round\" / \"square\".", (context) => {
|
|
766
|
+
const value = this.#enumString(arguments_, LINE_CAPS);
|
|
767
|
+
if (value === null) return null;
|
|
768
|
+
context.lineCap = value;
|
|
769
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
770
|
+
});
|
|
565
771
|
}
|
|
566
772
|
gLineJoin(arguments_) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
this.#print("Can not set line join.");
|
|
574
|
-
return kei_lisp.Cons.nil;
|
|
575
|
-
} catch {
|
|
576
|
-
this.#print("Can not set line join.");
|
|
577
|
-
return kei_lisp.Cons.nil;
|
|
578
|
-
}
|
|
579
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
580
|
-
return kei_lisp.Cons.nil;
|
|
773
|
+
return this.#execute("Can not set line join. Expected \"miter\" / \"round\" / \"bevel\".", (context) => {
|
|
774
|
+
const value = this.#enumString(arguments_, LINE_JOINS);
|
|
775
|
+
if (value === null) return null;
|
|
776
|
+
context.lineJoin = value;
|
|
777
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
778
|
+
});
|
|
581
779
|
}
|
|
582
780
|
gLineWidth(arguments_) {
|
|
583
781
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -633,30 +831,20 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
633
831
|
return kei_lisp.Cons.nil;
|
|
634
832
|
}
|
|
635
833
|
gPattern(arguments_) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
anImage.src = a0;
|
|
646
|
-
anImage.onload = () => {
|
|
647
|
-
context.fillStyle = context.createPattern(anImage, aString);
|
|
648
|
-
};
|
|
649
|
-
return kei_lisp.InterpretedSymbol.of("t");
|
|
834
|
+
return this.#execute("Can not set pattern. Expected an image source and a repetition (\"repeat\" / \"repeat-x\" / \"repeat-y\" / \"no-repeat\").", (context) => {
|
|
835
|
+
if (arguments_.length() !== 2) return null;
|
|
836
|
+
const [a0, a1] = this.#listValues(arguments_);
|
|
837
|
+
if (!kei_lisp.Cons.isString(a0) || !kei_lisp.Cons.isString(a1) || !PATTERN_REPETITIONS.has(a1)) return null;
|
|
838
|
+
this.#withImage(a0, (image) => {
|
|
839
|
+
const pattern = context.createPattern(image, a1);
|
|
840
|
+
if (pattern === null) {
|
|
841
|
+
this.#print("Can not set pattern. The image could not be used as a pattern.");
|
|
842
|
+
return;
|
|
650
843
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
return kei_lisp.
|
|
654
|
-
}
|
|
655
|
-
this.#print("Can not set pattern.");
|
|
656
|
-
return kei_lisp.Cons.nil;
|
|
657
|
-
}
|
|
658
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
659
|
-
return kei_lisp.Cons.nil;
|
|
844
|
+
context.fillStyle = pattern;
|
|
845
|
+
});
|
|
846
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
847
|
+
});
|
|
660
848
|
}
|
|
661
849
|
gQuadCurveTo(arguments_) {
|
|
662
850
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -868,26 +1056,17 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
868
1056
|
return kei_lisp.Cons.nil;
|
|
869
1057
|
}
|
|
870
1058
|
gStrokeText(arguments_) {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
}
|
|
883
|
-
this.#print("Can not draw stroke text.");
|
|
884
|
-
return kei_lisp.Cons.nil;
|
|
885
|
-
} catch {
|
|
886
|
-
this.#print("Can not draw stroke text.");
|
|
887
|
-
return kei_lisp.Cons.nil;
|
|
888
|
-
}
|
|
889
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
890
|
-
return kei_lisp.Cons.nil;
|
|
1059
|
+
return this.#execute("Can not draw stroke text.", (context) => {
|
|
1060
|
+
const length_ = arguments_.length();
|
|
1061
|
+
if (length_ !== 3 && length_ !== 4) return null;
|
|
1062
|
+
const [a0, a1, a2, a3] = this.#listValues(arguments_);
|
|
1063
|
+
if (!kei_lisp.Cons.isString(a0) || !kei_lisp.Cons.isNumber(a1) || !kei_lisp.Cons.isNumber(a2)) return null;
|
|
1064
|
+
if (length_ === 4) {
|
|
1065
|
+
if (!kei_lisp.Cons.isNumber(a3)) return null;
|
|
1066
|
+
context.strokeText(a0, a1, a2, a3);
|
|
1067
|
+
} else context.strokeText(a0, a1, a2);
|
|
1068
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1069
|
+
});
|
|
891
1070
|
}
|
|
892
1071
|
gStrokeTri(arguments_) {
|
|
893
1072
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -923,52 +1102,28 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
923
1102
|
return kei_lisp.Cons.nil;
|
|
924
1103
|
}
|
|
925
1104
|
gTextAlign(arguments_) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
this.#print("Can not set text align.");
|
|
933
|
-
return kei_lisp.Cons.nil;
|
|
934
|
-
} catch {
|
|
935
|
-
this.#print("Can not set text align.");
|
|
936
|
-
return kei_lisp.Cons.nil;
|
|
937
|
-
}
|
|
938
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
939
|
-
return kei_lisp.Cons.nil;
|
|
1105
|
+
return this.#execute("Can not set text align. Expected \"left\" / \"right\" / \"center\" / \"start\" / \"end\".", (context) => {
|
|
1106
|
+
const value = this.#enumString(arguments_, TEXT_ALIGNS);
|
|
1107
|
+
if (value === null) return null;
|
|
1108
|
+
context.textAlign = value;
|
|
1109
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1110
|
+
});
|
|
940
1111
|
}
|
|
941
1112
|
gTextBaseline(arguments_) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
this.#print("Can not set text baseline.");
|
|
949
|
-
return kei_lisp.Cons.nil;
|
|
950
|
-
} catch {
|
|
951
|
-
this.#print("Can not set text baseline.");
|
|
952
|
-
return kei_lisp.Cons.nil;
|
|
953
|
-
}
|
|
954
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
955
|
-
return kei_lisp.Cons.nil;
|
|
1113
|
+
return this.#execute("Can not set text baseline. Expected \"top\" / \"hanging\" / \"middle\" / \"alphabetic\" / \"ideographic\" / \"bottom\".", (context) => {
|
|
1114
|
+
const value = this.#enumString(arguments_, TEXT_BASELINES);
|
|
1115
|
+
if (value === null) return null;
|
|
1116
|
+
context.textBaseline = value;
|
|
1117
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1118
|
+
});
|
|
956
1119
|
}
|
|
957
1120
|
gTextDirection(arguments_) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
if (
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
this.#print("Can not set text direction.");
|
|
965
|
-
return kei_lisp.Cons.nil;
|
|
966
|
-
} catch {
|
|
967
|
-
this.#print("Can not set text direction.");
|
|
968
|
-
return kei_lisp.Cons.nil;
|
|
969
|
-
}
|
|
970
|
-
this.#print("The canvas is closed and cannot be executed.");
|
|
971
|
-
return kei_lisp.Cons.nil;
|
|
1121
|
+
return this.#execute("Can not set text direction. Expected \"ltr\" / \"rtl\" / \"inherit\".", (context) => {
|
|
1122
|
+
const value = this.#enumString(arguments_, TEXT_DIRECTIONS);
|
|
1123
|
+
if (value === null) return null;
|
|
1124
|
+
context.direction = value;
|
|
1125
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1126
|
+
});
|
|
972
1127
|
}
|
|
973
1128
|
gTextFont(arguments_) {
|
|
974
1129
|
if (!this.checkSupport()) return kei_lisp.Cons.nil;
|
|
@@ -1049,7 +1204,7 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
1049
1204
|
/**
|
|
1050
1205
|
* Pushes the current drawing state (styles, transform, clip) onto the
|
|
1051
1206
|
* context's state stack. Pairs with `gRestore` to let Lisp callers manage
|
|
1052
|
-
* state explicitly. Replaces the legacy per-method `
|
|
1207
|
+
* state explicitly. Replaces the legacy per-method `context.save()` calls, which
|
|
1053
1208
|
* pushed state on every draw with no matching `restore()` and grew the stack
|
|
1054
1209
|
* unbounded.
|
|
1055
1210
|
* @return `t` on success, `Cons.nil` otherwise
|
|
@@ -1083,6 +1238,234 @@ var GraphicsPlugin = class GraphicsPlugin extends Object {
|
|
|
1083
1238
|
this.#print("The canvas is closed and cannot be executed.");
|
|
1084
1239
|
return kei_lisp.Cons.nil;
|
|
1085
1240
|
}
|
|
1241
|
+
gEllipse(arguments_) {
|
|
1242
|
+
return this.#execute("Can not draw ellipse.", (context) => {
|
|
1243
|
+
const a = this.#numbers(arguments_, 8);
|
|
1244
|
+
if (a === null) return null;
|
|
1245
|
+
context.ellipse(a[0], a[1], a[2], a[3], Math.PI / 180 * a[4], Math.PI / 180 * a[5], Math.PI / 180 * a[6], a[7] >= 0);
|
|
1246
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
gRoundRect(arguments_) {
|
|
1250
|
+
return this.#execute("Can not draw round rectangle.", (context) => {
|
|
1251
|
+
const a = this.#numbers(arguments_, 5);
|
|
1252
|
+
if (a === null) return null;
|
|
1253
|
+
context.roundRect(a[0], a[1], a[2], a[3], a[4]);
|
|
1254
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
gLineDash(arguments_) {
|
|
1258
|
+
return this.#execute("Can not set line dash.", (context) => {
|
|
1259
|
+
const segments = this.#numbers(arguments_, arguments_.length());
|
|
1260
|
+
if (segments === null) return null;
|
|
1261
|
+
context.setLineDash(segments);
|
|
1262
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
gLineDashOffset(arguments_) {
|
|
1266
|
+
return this.#execute("Can not set line dash offset.", (context) => {
|
|
1267
|
+
const a = this.#numbers(arguments_, 1);
|
|
1268
|
+
if (a === null) return null;
|
|
1269
|
+
context.lineDashOffset = a[0];
|
|
1270
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
gMiterLimit(arguments_) {
|
|
1274
|
+
return this.#execute("Can not set miter limit.", (context) => {
|
|
1275
|
+
const a = this.#numbers(arguments_, 1);
|
|
1276
|
+
if (a === null) return null;
|
|
1277
|
+
context.miterLimit = a[0];
|
|
1278
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
gClip() {
|
|
1282
|
+
return this.#execute("Can not clip.", (context) => {
|
|
1283
|
+
context.clip();
|
|
1284
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
gIsPointInPath(arguments_) {
|
|
1288
|
+
return this.#execute("Can not test point in path.", (context) => {
|
|
1289
|
+
const a = this.#numbers(arguments_, 2);
|
|
1290
|
+
if (a === null) return null;
|
|
1291
|
+
return context.isPointInPath(a[0], a[1]) ? kei_lisp.InterpretedSymbol.of("t") : kei_lisp.Cons.nil;
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
gIsPointInStroke(arguments_) {
|
|
1295
|
+
return this.#execute("Can not test point in stroke.", (context) => {
|
|
1296
|
+
const a = this.#numbers(arguments_, 2);
|
|
1297
|
+
if (a === null) return null;
|
|
1298
|
+
return context.isPointInStroke(a[0], a[1]) ? kei_lisp.InterpretedSymbol.of("t") : kei_lisp.Cons.nil;
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
gTransform(arguments_) {
|
|
1302
|
+
return this.#execute("Can not transform.", (context) => {
|
|
1303
|
+
const a = this.#numbers(arguments_, 6);
|
|
1304
|
+
if (a === null) return null;
|
|
1305
|
+
context.transform(a[0], a[1], a[2], a[3], a[4], a[5]);
|
|
1306
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
gSetTransform(arguments_) {
|
|
1310
|
+
return this.#execute("Can not set transform.", (context) => {
|
|
1311
|
+
const a = this.#numbers(arguments_, 6);
|
|
1312
|
+
if (a === null) return null;
|
|
1313
|
+
context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]);
|
|
1314
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
gResetTransform() {
|
|
1318
|
+
return this.#execute("Can not reset transform.", (context) => {
|
|
1319
|
+
context.resetTransform();
|
|
1320
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
gComposite(arguments_) {
|
|
1324
|
+
return this.#execute("Can not set composite operation. Expected a globalCompositeOperation keyword such as \"source-over\" / \"multiply\" / \"screen\".", (context) => {
|
|
1325
|
+
const value = this.#enumString(arguments_, COMPOSITE_OPERATIONS);
|
|
1326
|
+
if (value === null) return null;
|
|
1327
|
+
context.globalCompositeOperation = value;
|
|
1328
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
gFilter(arguments_) {
|
|
1332
|
+
return this.#execute("Can not set filter.", (context) => {
|
|
1333
|
+
if (arguments_.length() !== 1 || !kei_lisp.Cons.isString(arguments_.car)) return null;
|
|
1334
|
+
context.filter = arguments_.car;
|
|
1335
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
gImageSmoothing(arguments_) {
|
|
1339
|
+
return this.#execute("Can not set image smoothing.", (context) => {
|
|
1340
|
+
const quality = arguments_.length() === 1 ? arguments_.car : null;
|
|
1341
|
+
if (quality !== "off" && quality !== "low" && quality !== "medium" && quality !== "high") return null;
|
|
1342
|
+
if (quality === "off") context.imageSmoothingEnabled = false;
|
|
1343
|
+
else {
|
|
1344
|
+
context.imageSmoothingEnabled = true;
|
|
1345
|
+
context.imageSmoothingQuality = quality;
|
|
1346
|
+
}
|
|
1347
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
gMeasureText(arguments_) {
|
|
1351
|
+
return this.#execute("Can not measure text.", (context) => {
|
|
1352
|
+
if (arguments_.length() !== 1 || !kei_lisp.Cons.isString(arguments_.car)) return null;
|
|
1353
|
+
return context.measureText(arguments_.car).width;
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
gLetterSpacing(arguments_) {
|
|
1357
|
+
return this.#execute("Can not set letter spacing.", (context) => {
|
|
1358
|
+
if (arguments_.length() !== 1 || !kei_lisp.Cons.isString(arguments_.car)) return null;
|
|
1359
|
+
context.letterSpacing = arguments_.car;
|
|
1360
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
gWordSpacing(arguments_) {
|
|
1364
|
+
return this.#execute("Can not set word spacing.", (context) => {
|
|
1365
|
+
if (arguments_.length() !== 1 || !kei_lisp.Cons.isString(arguments_.car)) return null;
|
|
1366
|
+
context.wordSpacing = arguments_.car;
|
|
1367
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
gFontKerning(arguments_) {
|
|
1371
|
+
return this.#execute("Can not set font kerning. Expected \"auto\" / \"normal\" / \"none\".", (context) => {
|
|
1372
|
+
const value = this.#enumString(arguments_, FONT_KERNINGS);
|
|
1373
|
+
if (value === null) return null;
|
|
1374
|
+
context.fontKerning = value;
|
|
1375
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
gFontStretch(arguments_) {
|
|
1379
|
+
return this.#execute("Can not set font stretch. Expected a font-stretch keyword such as \"condensed\" / \"normal\" / \"expanded\".", (context) => {
|
|
1380
|
+
const value = this.#enumString(arguments_, FONT_STRETCHES);
|
|
1381
|
+
if (value === null) return null;
|
|
1382
|
+
context.fontStretch = value;
|
|
1383
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
gFontVariant(arguments_) {
|
|
1387
|
+
return this.#execute("Can not set font variant. Expected a font-variant-caps keyword such as \"normal\" / \"small-caps\".", (context) => {
|
|
1388
|
+
const value = this.#enumString(arguments_, FONT_VARIANTS);
|
|
1389
|
+
if (value === null) return null;
|
|
1390
|
+
context.fontVariantCaps = value;
|
|
1391
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
gTextRendering(arguments_) {
|
|
1395
|
+
return this.#execute("Can not set text rendering. Expected \"auto\" / \"optimizeSpeed\" / \"optimizeLegibility\" / \"geometricPrecision\".", (context) => {
|
|
1396
|
+
const value = this.#enumString(arguments_, TEXT_RENDERINGS);
|
|
1397
|
+
if (value === null) return null;
|
|
1398
|
+
context.textRendering = value;
|
|
1399
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
gClearRect(arguments_) {
|
|
1403
|
+
return this.#execute("Can not clear rectangle.", (context) => {
|
|
1404
|
+
const a = this.#numbers(arguments_, 4);
|
|
1405
|
+
if (a === null) return null;
|
|
1406
|
+
context.clearRect(a[0], a[1], a[2], a[3]);
|
|
1407
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
gReset() {
|
|
1411
|
+
return this.#execute("Can not reset.", (context) => {
|
|
1412
|
+
context.reset();
|
|
1413
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
gWidth() {
|
|
1417
|
+
return this.#execute("Can not get width.", () => this.canvas.width);
|
|
1418
|
+
}
|
|
1419
|
+
gHeight() {
|
|
1420
|
+
return this.#execute("Can not get height.", () => this.canvas.height);
|
|
1421
|
+
}
|
|
1422
|
+
gPixel(arguments_) {
|
|
1423
|
+
return this.#execute("Can not read pixel.", (context) => {
|
|
1424
|
+
const a = this.#numbers(arguments_, 2);
|
|
1425
|
+
if (a === null) return null;
|
|
1426
|
+
const data = context.getImageData(a[0], a[1], 1, 1).data;
|
|
1427
|
+
return this.#toList([
|
|
1428
|
+
data[0],
|
|
1429
|
+
data[1],
|
|
1430
|
+
data[2],
|
|
1431
|
+
data[3]
|
|
1432
|
+
]);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
gSetPixel(arguments_) {
|
|
1436
|
+
return this.#execute("Can not write pixel.", (context) => {
|
|
1437
|
+
const a = this.#numbers(arguments_, 6);
|
|
1438
|
+
if (a === null) return null;
|
|
1439
|
+
const imageData = context.createImageData(1, 1);
|
|
1440
|
+
imageData.data[0] = a[2];
|
|
1441
|
+
imageData.data[1] = a[3];
|
|
1442
|
+
imageData.data[2] = a[4];
|
|
1443
|
+
imageData.data[3] = a[5];
|
|
1444
|
+
context.putImageData(imageData, a[0], a[1]);
|
|
1445
|
+
return kei_lisp.InterpretedSymbol.of("t");
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
gLinearGradient(arguments_) {
|
|
1449
|
+
return this.#execute("Can not set linear gradient.", (context) => {
|
|
1450
|
+
const [x0, y0, x1, y1, ...stops] = this.#listValues(arguments_);
|
|
1451
|
+
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);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
gRadialGradient(arguments_) {
|
|
1456
|
+
return this.#execute("Can not set radial gradient.", (context) => {
|
|
1457
|
+
const [x0, y0, r0, x1, y1, r1, ...stops] = this.#listValues(arguments_);
|
|
1458
|
+
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);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
gConicGradient(arguments_) {
|
|
1463
|
+
return this.#execute("Can not set conic gradient.", (context) => {
|
|
1464
|
+
const [angle, x, y, ...stops] = this.#listValues(arguments_);
|
|
1465
|
+
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);
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1086
1469
|
/**
|
|
1087
1470
|
* Parses a color spec from the head of the argument Cons. Accepts
|
|
1088
1471
|
* (1 string), (3 numbers — rgb), or (4 numbers — rgba); falls back to
|