xbintsc 0.5.36 → 0.5.37

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.
Files changed (66) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/src/extensions/gui/index.js +12 -0
  5. package/dist/src/extensions/gui/index.js.map +1 -1
  6. package/dist/tests/e2e/gui-browser.test.js +65 -0
  7. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  8. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  9. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  10. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  11. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-dom.test.js +215 -0
  13. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  14. package/dist/tests/e2e/gui-events.test.js +112 -0
  15. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  16. package/doc/gui/api.md +38 -9
  17. package/doc/gui/canvas.md +49 -18
  18. package/doc/gui/dom.md +51 -4
  19. package/doc/zh-CN/gui/api.md +33 -4
  20. package/doc/zh-CN/gui/canvas.md +44 -19
  21. package/doc/zh-CN/gui/dom.md +45 -3
  22. package/package.json +7 -7
  23. package/runtime/ext_gui/browser.cpp +34 -49
  24. package/runtime/ext_gui/browser_abort.cpp +249 -0
  25. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  26. package/runtime/ext_gui/browser_media.cpp +176 -0
  27. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  28. package/runtime/ext_gui/canvas.cpp +88 -30
  29. package/runtime/ext_gui/canvas.h +74 -9
  30. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  31. package/runtime/ext_gui/canvas_internal.h +8 -5
  32. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  33. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  34. package/runtime/ext_gui/canvas_style.cpp +4 -2
  35. package/runtime/ext_gui/dom.h +4 -0
  36. package/runtime/ext_gui/dom_api.cpp +1 -0
  37. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  38. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  39. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  40. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  41. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  42. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  43. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  44. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  45. package/runtime/ext_gui/dom_api_internal.h +55 -2
  46. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  47. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  48. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  49. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  50. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  51. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  52. package/runtime/ext_gui/gui.cpp +59 -5
  53. package/runtime/ext_gui/gui_engine.h +47 -0
  54. package/runtime/ext_gui/gui_events.cpp +80 -9
  55. package/runtime/ext_gui/listener_options.h +43 -0
  56. package/runtime/ext_gui/paint.cpp +28 -0
  57. package/runtime/ext_gui/paint.h +4 -0
  58. package/runtime/ext_gui/window.cpp +14 -65
  59. package/runtime/ext_gui/window_events.cpp +125 -0
  60. package/runtime/rt.h +26 -0
  61. package/runtime/rt_internal.h +4 -0
  62. package/runtime/xt_builtins.c +0 -21
  63. package/runtime/xt_containers/access.inc +94 -2
  64. package/runtime/xt_containers/objects.inc +29 -31
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/index.ts +12 -0
@@ -37,11 +37,15 @@ struct Crossing {
37
37
  /** Resolve `paint` at a device-space pixel centre. Gradients are defined in
38
38
  * user space, so the pixel is mapped back through the inverse transform. */
39
39
  void paint_color_at(const CanvasPaint &paint, float x, float y, float out[4]) {
40
- if (paint.gradient != nullptr) {
40
+ if (paint.pattern != nullptr || paint.gradient != nullptr) {
41
41
  const CanvasTransform &inverse = paint.inverse;
42
42
  float userX = inverse.a * x + inverse.c * y + inverse.e;
43
43
  float userY = inverse.b * x + inverse.d * y + inverse.f;
44
- if (paint.gradient->color_at(userX, userY, out)) return;
44
+ if (paint.pattern != nullptr) {
45
+ if (paint.pattern->color_at(userX, userY, out)) return;
46
+ } else if (paint.gradient->color_at(userX, userY, out)) {
47
+ return;
48
+ }
45
49
  }
46
50
  if (paint.color != nullptr) {
47
51
  for (int i = 0; i < 4; i++) out[i] = paint.color[i];
@@ -100,7 +104,7 @@ static void add_span(float *row, int rowOriginX, float xa, float xb, float weigh
100
104
 
101
105
  void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
102
106
  const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
103
- float alpha) {
107
+ float alpha, bool even_odd) {
104
108
  if (width <= 0 || height <= 0 || loops.empty()) return;
105
109
  bool havePoint = false;
106
110
  float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
@@ -154,6 +158,14 @@ void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
154
158
  if (crossings.empty()) continue;
155
159
  std::sort(crossings.begin(), crossings.end(),
156
160
  [](const Crossing &a, const Crossing &b) { return a.x < b.x; });
161
+ if (even_odd) {
162
+ /* Every second crossing toggles the covered span, regardless of the
163
+ * direction each contour is wound. */
164
+ for (size_t i = 0; i + 1 < crossings.size(); i += 2) {
165
+ add_span(row, x0, crossings[i].x, crossings[i + 1].x, weight, x0, x1);
166
+ }
167
+ continue;
168
+ }
157
169
  int winding = 0;
158
170
  float spanStart = 0.0f;
159
171
  for (const Crossing &crossing : crossings) {
@@ -179,8 +191,10 @@ void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
179
191
  }
180
192
 
181
193
  void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
182
- const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha) {
183
- fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, paint, alpha);
194
+ const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha,
195
+ bool even_odd) {
196
+ fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, paint, alpha,
197
+ even_odd);
184
198
  }
185
199
 
186
200
  void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
@@ -331,16 +345,24 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
331
345
 
332
346
  void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
333
347
  const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
334
- int top, float x, float baselineY, const CanvasPaint &paint, float alpha) {
335
- int originX = (int)std::lround(x) + left;
348
+ int top, float x, float baselineY, float scale_x, const CanvasPaint &paint,
349
+ float alpha) {
350
+ if (glyphWidth <= 0 || glyphHeight <= 0) return;
351
+ if (scale_x <= 0.0f) scale_x = 1.0f;
352
+ bool condense = scale_x < 0.999f;
353
+ int drawWidth = condense ? std::max(1, (int)std::lround((float)glyphWidth * scale_x))
354
+ : glyphWidth;
355
+ int originX = (int)std::lround(x) + (int)std::lround((float)left * scale_x);
336
356
  int originY = (int)std::lround(baselineY) - top;
337
357
  for (int gy = 0; gy < glyphHeight; gy++) {
338
358
  int py = originY + gy;
339
359
  if (py < 0 || py >= height) continue;
340
- for (int gx = 0; gx < glyphWidth; gx++) {
360
+ for (int gx = 0; gx < drawWidth; gx++) {
341
361
  int px = originX + gx;
342
362
  if (px < 0 || px >= width) continue;
343
- float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)gx] / 255.0f;
363
+ /* Nearest sample from the glyph coverage mask when condensing. */
364
+ int sourceX = condense ? std::min(glyphWidth - 1, (int)((float)gx / scale_x)) : gx;
365
+ float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)sourceX] / 255.0f;
344
366
  if (coverage <= 0.0f) continue;
345
367
  float color[4];
346
368
  paint_color_at(paint, (float)px + 0.5f, (float)py + 0.5f, color);
@@ -112,11 +112,13 @@ bool positive_finite(float value) { return std::isfinite(value) && value >= 0.0f
112
112
  void Canvas2D::set_fill_style(const std::string &css) {
113
113
  parse_color_into(css, state_.fill_color, &state_.fill_style);
114
114
  state_.fill_gradient = 0;
115
+ state_.fill_pattern = 0;
115
116
  }
116
117
 
117
118
  void Canvas2D::set_stroke_style(const std::string &css) {
118
119
  parse_color_into(css, state_.stroke_color, &state_.stroke_style);
119
120
  state_.stroke_gradient = 0;
121
+ state_.stroke_pattern = 0;
120
122
  }
121
123
 
122
124
  void Canvas2D::set_fill_gradient(uint64_t id) {
@@ -124,7 +126,7 @@ void Canvas2D::set_fill_gradient(uint64_t id) {
124
126
  state_.fill_gradient = 0;
125
127
  return;
126
128
  }
127
- if (find_gradient(id) != nullptr) state_.fill_gradient = id;
129
+ if (find_gradient(id) != nullptr) { state_.fill_gradient = id; state_.fill_pattern = 0; }
128
130
  }
129
131
 
130
132
  void Canvas2D::set_stroke_gradient(uint64_t id) {
@@ -132,7 +134,7 @@ void Canvas2D::set_stroke_gradient(uint64_t id) {
132
134
  state_.stroke_gradient = 0;
133
135
  return;
134
136
  }
135
- if (find_gradient(id) != nullptr) state_.stroke_gradient = id;
137
+ if (find_gradient(id) != nullptr) { state_.stroke_gradient = id; state_.stroke_pattern = 0; }
136
138
  }
137
139
 
138
140
  void Canvas2D::set_global_alpha(float alpha) {
@@ -31,6 +31,10 @@ struct Node {
31
31
  NodeType type = NodeType::Element;
32
32
  std::string tag; // lowercased element name (empty for document/text)
33
33
  std::string text; // text content (text nodes only)
34
+ /* `createElementNS` keeps the namespace and the qualified name's prefix.
35
+ * Ordinary HTML elements leave both empty and report the XHTML namespace. */
36
+ std::string ns;
37
+ std::string ns_prefix;
34
38
  std::vector<Attr> attrs;
35
39
  Node *parent = nullptr;
36
40
  std::vector<std::unique_ptr<Node>> children;
@@ -32,6 +32,7 @@ xt_value g_document_proto = XT_UNDEFINED;
32
32
  xt_value g_event_proto = XT_UNDEFINED;
33
33
  xt_value g_class_list_proto = XT_UNDEFINED;
34
34
  xt_value g_style_proto = XT_UNDEFINED;
35
+ xt_value g_computed_style_proto = XT_UNDEFINED;
35
36
  xt_value g_canvas_proto = XT_UNDEFINED;
36
37
  xt_value g_canvas_gradient_proto = XT_UNDEFINED;
37
38
 
@@ -41,10 +41,34 @@ std::string string_arg(int32_t argc, xt_value *argv, int32_t index) {
41
41
  return to_string_value(xt_arg(argc, argv, index));
42
42
  }
43
43
 
44
+ /* Defined with the path bindings below; the transform methods accept a matrix
45
+ * argument too. */
46
+ /** `transform(a, b, c, d, e, f)` or `transform(matrix)` where `matrix` is a
47
+ * `DOMMatrix`, `{ a, b, c, d, e, f }` or a six-element array. */
48
+ bool canvas_matrix_arg(int32_t argc, xt_value *argv, int32_t index, float values[6]) {
49
+ if (index >= argc) return false;
50
+ xt_value value = xt_arg(argc, argv, index);
51
+ if (XT_IS_ARRAY(value)) {
52
+ if (xt_to_number(xt_array_length(value)) < 6) return false;
53
+ for (int i = 0; i < 6; i++) values[i] = (float)xt_to_number(xt_array_get(value, xt_number(i)));
54
+ return true;
55
+ }
56
+ if (!XT_IS_OBJECT(value)) return false;
57
+ static const char *const names[6] = {"a", "b", "c", "d", "e", "f"};
58
+ for (int i = 0; i < 6; i++) {
59
+ xt_value member = xt_object_get_cstr(value, names[i]);
60
+ if (!XT_IS_NUMBER(member)) return false;
61
+ values[i] = (float)xt_to_number(member);
62
+ }
63
+ return true;
64
+ }
65
+
44
66
  } // namespace
45
67
 
46
68
  xtgui::Canvas2D *canvas_from_context(xt_value self) { return context_canvas(self); }
47
69
 
70
+ xt_value canvas_context_attributes(xt_value requested, bool alpha);
71
+
48
72
  double canvas_number_arg(int32_t argc, xt_value *argv, int32_t index, double fallback) {
49
73
  return number_arg(argc, argv, index, fallback);
50
74
  }
@@ -75,18 +99,70 @@ METHOD(node_get_context) {
75
99
  if (node == nullptr) return xt_canvas_pending_context(self, win);
76
100
  if (!node->isElement() || !node->isTag("canvas")) return XT_NULL;
77
101
  if (xt_gui_webgl_find(win, node) != nullptr) return XT_NULL; // already a WebGL canvas
78
- xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
79
- if (canvas == nullptr) return XT_NULL;
80
102
  xt_value cached = xt_object_get_cstr(self, "__xt_gui_ctx");
81
103
  if (XT_IS_OBJECT(cached)) return cached;
104
+ xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
105
+ if (canvas == nullptr) return XT_NULL;
106
+ /* The second argument only takes effect when the context is created; a later
107
+ * `getContext("2d", ...)` returns the existing context unchanged. `alpha` is
108
+ * the one attribute with an observable effect on the bitmap. */
109
+ xt_value attributes = xt_arg(argc, argv, 1);
110
+ bool alpha = true;
111
+ if (XT_IS_OBJECT(attributes)) {
112
+ xt_value value = xt_object_get_cstr(attributes, "alpha");
113
+ if (XT_IS_BOOL(value)) alpha = value_truthy(value);
114
+ }
115
+ canvas->set_alpha(alpha);
82
116
  xt_value ctx = xt_object_new_with_proto(canvas_proto());
83
117
  xt_object_set(ctx, xt_string_from_cstr("__xt_gui_canvas"), xt_number((double)canvas->id()));
84
118
  /* Keep the element handle alive (and reachable) from the context. */
85
119
  xt_object_set(ctx, xt_string_from_cstr("__xt_gui_node"), self);
120
+ xt_object_set(ctx, xt_string_from_cstr("__xt_gui_ctx_attributes"),
121
+ canvas_context_attributes(attributes, alpha));
86
122
  xt_object_set(self, xt_string_from_cstr("__xt_gui_ctx"), ctx);
87
123
  return ctx;
88
124
  }
89
125
 
126
+ /** The attributes object `getContextAttributes()` reports. Options the engine
127
+ * does not implement keep their specification default, so a feature test sees
128
+ * an honest answer instead of `undefined`. */
129
+ xt_value canvas_context_attributes(xt_value requested, bool alpha) {
130
+ xt_value result = xt_object_new();
131
+ struct Attribute {
132
+ const char *name;
133
+ bool fallback;
134
+ };
135
+ const Attribute defaults[] = {
136
+ {"alpha", alpha}, {"antialias", true},
137
+ {"depth", true}, {"stencil", false},
138
+ {"premultipliedAlpha", true}, {"preserveDrawingBuffer", false},
139
+ {"desynchronized", false}, {"willReadFrequently", false},
140
+ {"failIfMajorPerformanceCaveat", false},
141
+ };
142
+ for (const Attribute &attribute : defaults) {
143
+ xt_value value = XT_UNDEFINED;
144
+ if (XT_IS_OBJECT(requested)) value = xt_object_get_cstr(requested, attribute.name);
145
+ if (XT_IS_BOOL(value)) {
146
+ xt_object_set(result, xt_string_from_cstr(attribute.name), value);
147
+ } else {
148
+ xt_object_set(result, xt_string_from_cstr(attribute.name),
149
+ attribute.fallback ? XT_TRUE : XT_FALSE);
150
+ }
151
+ }
152
+ xt_object_set(result, xt_string_from_cstr("powerPreference"),
153
+ xt_string_from_cstr("default"));
154
+ xt_object_set(result, xt_string_from_cstr("colorSpace"), xt_string_from_cstr("srgb"));
155
+ return result;
156
+ }
157
+
158
+ METHOD(canvas_get_context_attributes) {
159
+ (void)env;
160
+ (void)argc;
161
+ (void)argv;
162
+ xt_value stored = xt_object_get_cstr(self, "__xt_gui_ctx_attributes");
163
+ return XT_IS_OBJECT(stored) ? stored : canvas_context_attributes(XT_UNDEFINED, true);
164
+ }
165
+
90
166
  METHOD(canvas_get_canvas) {
91
167
  (void)env;
92
168
  (void)argc;
@@ -103,6 +179,9 @@ METHOD(canvas_fill_style_get) {
103
179
  if (canvas->fill_gradient_id() != 0) {
104
180
  return canvas_gradient_handle(self, canvas, canvas->fill_gradient_id());
105
181
  }
182
+ if (canvas->fill_pattern_id() != 0) {
183
+ return canvas_pattern_handle(self, canvas, canvas->fill_pattern_id());
184
+ }
106
185
  return xt_string_from_cstr(canvas->fill_style().c_str());
107
186
  }
108
187
 
@@ -116,6 +195,11 @@ METHOD(canvas_fill_style_set) {
116
195
  canvas->set_fill_gradient(gradient);
117
196
  return XT_UNDEFINED;
118
197
  }
198
+ uint64_t pattern = canvas_pattern_id(xt_arg(argc, argv, 0), canvas);
199
+ if (pattern != 0) {
200
+ canvas->set_fill_pattern(pattern);
201
+ return XT_UNDEFINED;
202
+ }
119
203
  }
120
204
  canvas->set_fill_style(string_arg(argc, argv, 0));
121
205
  return XT_UNDEFINED;
@@ -130,6 +214,9 @@ METHOD(canvas_stroke_style_get) {
130
214
  if (canvas->stroke_gradient_id() != 0) {
131
215
  return canvas_gradient_handle(self, canvas, canvas->stroke_gradient_id());
132
216
  }
217
+ if (canvas->stroke_pattern_id() != 0) {
218
+ return canvas_pattern_handle(self, canvas, canvas->stroke_pattern_id());
219
+ }
133
220
  return xt_string_from_cstr(canvas->stroke_style().c_str());
134
221
  }
135
222
 
@@ -143,6 +230,11 @@ METHOD(canvas_stroke_style_set) {
143
230
  canvas->set_stroke_gradient(gradient);
144
231
  return XT_UNDEFINED;
145
232
  }
233
+ uint64_t pattern = canvas_pattern_id(xt_arg(argc, argv, 0), canvas);
234
+ if (pattern != 0) {
235
+ canvas->set_stroke_pattern(pattern);
236
+ return XT_UNDEFINED;
237
+ }
146
238
  }
147
239
  canvas->set_stroke_style(string_arg(argc, argv, 0));
148
240
  return XT_UNDEFINED;
@@ -269,23 +361,33 @@ METHOD(canvas_scale) {
269
361
  METHOD(canvas_transform) {
270
362
  (void)env;
271
363
  xtgui::Canvas2D *canvas = context_canvas(self);
272
- if (canvas != nullptr)
273
- canvas->transform((float)number_arg(argc, argv, 0, 1.0), (float)number_arg(argc, argv, 1, 0.0),
274
- (float)number_arg(argc, argv, 2, 0.0), (float)number_arg(argc, argv, 3, 1.0),
275
- (float)number_arg(argc, argv, 4, 0.0), (float)number_arg(argc, argv, 5, 0.0));
364
+ if (canvas == nullptr) return XT_UNDEFINED;
365
+ float values[6];
366
+ if (canvas_matrix_arg(argc, argv, 0, values)) {
367
+ canvas->transform(values[0], values[1], values[2], values[3], values[4], values[5]);
368
+ return XT_UNDEFINED;
369
+ }
370
+ canvas->transform((float)number_arg(argc, argv, 0, 1.0), (float)number_arg(argc, argv, 1, 0.0),
371
+ (float)number_arg(argc, argv, 2, 0.0), (float)number_arg(argc, argv, 3, 1.0),
372
+ (float)number_arg(argc, argv, 4, 0.0), (float)number_arg(argc, argv, 5, 0.0));
276
373
  return XT_UNDEFINED;
277
374
  }
278
375
 
279
376
  METHOD(canvas_set_transform) {
280
377
  (void)env;
281
378
  xtgui::Canvas2D *canvas = context_canvas(self);
282
- if (canvas != nullptr)
283
- canvas->set_transform((float)number_arg(argc, argv, 0, 1.0),
284
- (float)number_arg(argc, argv, 1, 0.0),
285
- (float)number_arg(argc, argv, 2, 0.0),
286
- (float)number_arg(argc, argv, 3, 1.0),
287
- (float)number_arg(argc, argv, 4, 0.0),
288
- (float)number_arg(argc, argv, 5, 0.0));
379
+ if (canvas == nullptr) return XT_UNDEFINED;
380
+ float values[6];
381
+ if (canvas_matrix_arg(argc, argv, 0, values)) {
382
+ canvas->set_transform(values[0], values[1], values[2], values[3], values[4], values[5]);
383
+ return XT_UNDEFINED;
384
+ }
385
+ canvas->set_transform((float)number_arg(argc, argv, 0, 1.0),
386
+ (float)number_arg(argc, argv, 1, 0.0),
387
+ (float)number_arg(argc, argv, 2, 0.0),
388
+ (float)number_arg(argc, argv, 3, 1.0),
389
+ (float)number_arg(argc, argv, 4, 0.0),
390
+ (float)number_arg(argc, argv, 5, 0.0));
289
391
  return XT_UNDEFINED;
290
392
  }
291
393
 
@@ -298,104 +400,6 @@ METHOD(canvas_reset_transform) {
298
400
  return XT_UNDEFINED;
299
401
  }
300
402
 
301
- /* -- paths ---------------------------------------------------------------- */
302
-
303
- METHOD(canvas_begin_path) {
304
- (void)env;
305
- (void)argc;
306
- (void)argv;
307
- xtgui::Canvas2D *canvas = context_canvas(self);
308
- if (canvas != nullptr) canvas->begin_path();
309
- return XT_UNDEFINED;
310
- }
311
-
312
- METHOD(canvas_close_path) {
313
- (void)env;
314
- (void)argc;
315
- (void)argv;
316
- xtgui::Canvas2D *canvas = context_canvas(self);
317
- if (canvas != nullptr) canvas->close_path();
318
- return XT_UNDEFINED;
319
- }
320
-
321
- METHOD(canvas_move_to) {
322
- (void)env;
323
- xtgui::Canvas2D *canvas = context_canvas(self);
324
- if (canvas != nullptr)
325
- canvas->move_to((float)number_arg(argc, argv, 0, 0.0), (float)number_arg(argc, argv, 1, 0.0));
326
- return XT_UNDEFINED;
327
- }
328
-
329
- METHOD(canvas_line_to) {
330
- (void)env;
331
- xtgui::Canvas2D *canvas = context_canvas(self);
332
- if (canvas != nullptr)
333
- canvas->line_to((float)number_arg(argc, argv, 0, 0.0), (float)number_arg(argc, argv, 1, 0.0));
334
- return XT_UNDEFINED;
335
- }
336
-
337
- METHOD(canvas_rect) {
338
- (void)env;
339
- xtgui::Canvas2D *canvas = context_canvas(self);
340
- if (canvas != nullptr)
341
- canvas->rect((float)number_arg(argc, argv, 0, 0.0), (float)number_arg(argc, argv, 1, 0.0),
342
- (float)number_arg(argc, argv, 2, 0.0), (float)number_arg(argc, argv, 3, 0.0));
343
- return XT_UNDEFINED;
344
- }
345
-
346
- METHOD(canvas_arc) {
347
- (void)env;
348
- xtgui::Canvas2D *canvas = context_canvas(self);
349
- if (canvas != nullptr) {
350
- canvas->arc((float)number_arg(argc, argv, 0, 0.0), (float)number_arg(argc, argv, 1, 0.0),
351
- (float)number_arg(argc, argv, 2, 0.0), (float)number_arg(argc, argv, 3, 0.0),
352
- (float)number_arg(argc, argv, 4, 0.0), value_truthy(xt_arg(argc, argv, 5)));
353
- }
354
- return XT_UNDEFINED;
355
- }
356
-
357
- METHOD(canvas_quadratic_curve_to) {
358
- (void)env;
359
- xtgui::Canvas2D *canvas = context_canvas(self);
360
- if (canvas != nullptr)
361
- canvas->quadratic_curve_to((float)number_arg(argc, argv, 0, 0.0),
362
- (float)number_arg(argc, argv, 1, 0.0),
363
- (float)number_arg(argc, argv, 2, 0.0),
364
- (float)number_arg(argc, argv, 3, 0.0));
365
- return XT_UNDEFINED;
366
- }
367
-
368
- METHOD(canvas_bezier_curve_to) {
369
- (void)env;
370
- xtgui::Canvas2D *canvas = context_canvas(self);
371
- if (canvas != nullptr)
372
- canvas->bezier_curve_to((float)number_arg(argc, argv, 0, 0.0),
373
- (float)number_arg(argc, argv, 1, 0.0),
374
- (float)number_arg(argc, argv, 2, 0.0),
375
- (float)number_arg(argc, argv, 3, 0.0),
376
- (float)number_arg(argc, argv, 4, 0.0),
377
- (float)number_arg(argc, argv, 5, 0.0));
378
- return XT_UNDEFINED;
379
- }
380
-
381
- METHOD(canvas_fill) {
382
- (void)env;
383
- (void)argc;
384
- (void)argv;
385
- xtgui::Canvas2D *canvas = context_canvas(self);
386
- if (canvas != nullptr) canvas->fill();
387
- return XT_UNDEFINED;
388
- }
389
-
390
- METHOD(canvas_stroke) {
391
- (void)env;
392
- (void)argc;
393
- (void)argv;
394
- xtgui::Canvas2D *canvas = context_canvas(self);
395
- if (canvas != nullptr) canvas->stroke();
396
- return XT_UNDEFINED;
397
- }
398
-
399
403
  METHOD(canvas_clear_rect) {
400
404
  (void)env;
401
405
  xtgui::Canvas2D *canvas = context_canvas(self);
@@ -440,7 +444,8 @@ METHOD(canvas_fill_text) {
440
444
  xtgui::Canvas2D *canvas = context_canvas(self);
441
445
  if (canvas != nullptr)
442
446
  canvas->fill_text(string_arg(argc, argv, 0), (float)number_arg(argc, argv, 1, 0.0),
443
- (float)number_arg(argc, argv, 2, 0.0));
447
+ (float)number_arg(argc, argv, 2, 0.0),
448
+ (float)number_arg(argc, argv, 3, 0.0));
444
449
  return XT_UNDEFINED;
445
450
  }
446
451
 
@@ -449,7 +454,8 @@ METHOD(canvas_stroke_text) {
449
454
  xtgui::Canvas2D *canvas = context_canvas(self);
450
455
  if (canvas != nullptr)
451
456
  canvas->stroke_text(string_arg(argc, argv, 0), (float)number_arg(argc, argv, 1, 0.0),
452
- (float)number_arg(argc, argv, 2, 0.0));
457
+ (float)number_arg(argc, argv, 2, 0.0),
458
+ (float)number_arg(argc, argv, 3, 0.0));
453
459
  return XT_UNDEFINED;
454
460
  }
455
461
 
@@ -516,4 +522,4 @@ METHOD(canvas_get_image_data) {
516
522
 
517
523
  } // namespace domapi
518
524
 
519
- #undef METHOD
525
+ #undef METHOD
@@ -100,15 +100,70 @@ METHOD(canvas_ellipse) {
100
100
  return XT_UNDEFINED;
101
101
  }
102
102
 
103
+ namespace {
104
+
105
+ /** One corner radius: a number, or a `{ x, y }` pair (the engine draws circular
106
+ * corners, so such a pair contributes its `x` radius). */
107
+ bool canvas_radius_value(xt_value value, float *out) {
108
+ if (XT_IS_NUMBER(value)) {
109
+ *out = (float)xt_to_number(value);
110
+ return true;
111
+ }
112
+ if (XT_IS_OBJECT(value)) {
113
+ xt_value x = xt_object_get_cstr(value, "x");
114
+ xt_value y = xt_object_get_cstr(value, "y");
115
+ if (XT_IS_NUMBER(x)) {
116
+ *out = (float)xt_to_number(x);
117
+ return true;
118
+ }
119
+ if (XT_IS_NUMBER(y)) {
120
+ *out = (float)xt_to_number(y);
121
+ return true;
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+
127
+ /** `roundRect`'s fifth argument: one radius or a list of 1–4 radii in CSS order
128
+ * (top-left, top-right, bottom-right, bottom-left). */
129
+ bool canvas_radii_arg(xt_value value, float radii[4]) {
130
+ radii[0] = radii[1] = radii[2] = radii[3] = 0.0f;
131
+ if (canvas_radius_value(value, &radii[0])) {
132
+ radii[1] = radii[2] = radii[3] = radii[0];
133
+ return true;
134
+ }
135
+ if (!XT_IS_ARRAY(value)) return false;
136
+ int32_t count = (int32_t)xt_to_number(xt_array_length(value));
137
+ if (count <= 0) return false;
138
+ if (count > 4) count = 4;
139
+ for (int32_t i = 0; i < count; i++) {
140
+ if (!canvas_radius_value(xt_array_get(value, xt_number((double)i)), &radii[i])) return false;
141
+ }
142
+ /* CSS expansion: [a] [a,b] [a,b,c] to four corners. */
143
+ if (count == 1) {
144
+ radii[1] = radii[2] = radii[3] = radii[0];
145
+ } else if (count == 2) {
146
+ radii[2] = radii[0];
147
+ radii[3] = radii[1];
148
+ } else if (count == 3) {
149
+ radii[3] = radii[1];
150
+ }
151
+ return true;
152
+ }
153
+
154
+ } // namespace
155
+
103
156
  METHOD(canvas_round_rect) {
104
157
  (void)env;
105
158
  xtgui::Canvas2D *canvas = canvas_from_context(self);
106
159
  if (canvas != nullptr) {
160
+ float radii[4];
161
+ canvas_radii_arg(xt_arg(argc, argv, 4), radii);
107
162
  canvas->round_rect((float)canvas_number_arg(argc, argv, 0, 0.0),
108
163
  (float)canvas_number_arg(argc, argv, 1, 0.0),
109
164
  (float)canvas_number_arg(argc, argv, 2, 0.0),
110
- (float)canvas_number_arg(argc, argv, 3, 0.0),
111
- (float)canvas_number_arg(argc, argv, 4, 0.0));
165
+ (float)canvas_number_arg(argc, argv, 3, 0.0), radii[0], radii[1],
166
+ radii[2], radii[3]);
112
167
  }
113
168
  return XT_UNDEFINED;
114
169
  }