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