xbintsc 0.5.21 → 0.5.23

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 (98) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +15 -5
  3. package/README.zh-CN.md +11 -4
  4. package/dist/src/cli/hints.js +11 -0
  5. package/dist/src/cli/hints.js.map +1 -1
  6. package/dist/src/cli/main.js +1 -1
  7. package/dist/src/driver/compiler.js +1 -0
  8. package/dist/src/driver/compiler.js.map +1 -1
  9. package/dist/src/driver/version.d.ts +1 -1
  10. package/dist/src/driver/version.js +1 -1
  11. package/dist/src/extensions/catalog.js +2 -1
  12. package/dist/src/extensions/catalog.js.map +1 -1
  13. package/dist/src/extensions/media/index.d.ts +27 -0
  14. package/dist/src/extensions/media/index.js +60 -0
  15. package/dist/src/extensions/media/index.js.map +1 -0
  16. package/dist/src/extensions/registry.d.ts +12 -0
  17. package/dist/src/extensions/registry.js +16 -0
  18. package/dist/src/extensions/registry.js.map +1 -1
  19. package/dist/src/index.d.ts +1 -0
  20. package/dist/src/index.js +1 -0
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/tests/cli/hints.test.js +15 -0
  23. package/dist/tests/cli/hints.test.js.map +1 -1
  24. package/dist/tests/cli/process.test.js +12 -0
  25. package/dist/tests/cli/process.test.js.map +1 -1
  26. package/dist/tests/e2e/gui-canvas.test.js +159 -0
  27. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  28. package/dist/tests/e2e/gui-example-canvas.test.d.ts +11 -0
  29. package/dist/tests/e2e/gui-example-canvas.test.js +72 -0
  30. package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -0
  31. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  32. package/dist/tests/e2e/gui-helpers.js +2 -0
  33. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  34. package/dist/tests/e2e/gui-media-disabled.test.d.ts +9 -0
  35. package/dist/tests/e2e/gui-media-disabled.test.js +42 -0
  36. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-media.test.d.ts +6 -4
  38. package/dist/tests/e2e/gui-media.test.js +15 -12
  39. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  40. package/dist/tests/e2e/unicode-output.test.d.ts +16 -0
  41. package/dist/tests/e2e/unicode-output.test.js +65 -0
  42. package/dist/tests/e2e/unicode-output.test.js.map +1 -0
  43. package/dist/tests/extensions/media.test.d.ts +1 -0
  44. package/dist/tests/extensions/media.test.js +58 -0
  45. package/dist/tests/extensions/media.test.js.map +1 -0
  46. package/dist/tests/extensions/registry.test.js +8 -0
  47. package/dist/tests/extensions/registry.test.js.map +1 -1
  48. package/dist/tests/lexer/identifiers.test.js +12 -0
  49. package/dist/tests/lexer/identifiers.test.js.map +1 -1
  50. package/dist/tests/lexer/scanner.test.js +8 -0
  51. package/dist/tests/lexer/scanner.test.js.map +1 -1
  52. package/dist/tests/lexer/strings.test.js +30 -0
  53. package/dist/tests/lexer/strings.test.js.map +1 -1
  54. package/doc/ai/cli.md +1 -1
  55. package/doc/ai/extensions.md +35 -22
  56. package/doc/ai/troubleshooting.md +9 -0
  57. package/doc/ai/zh-CN/cli.md +1 -1
  58. package/doc/ai/zh-CN/extensions.md +22 -9
  59. package/doc/ai/zh-CN/troubleshooting.md +8 -0
  60. package/doc/gui/build.md +37 -21
  61. package/doc/gui/canvas.md +47 -21
  62. package/doc/gui/known-issues.md +7 -6
  63. package/doc/gui/media.md +26 -11
  64. package/doc/gui.md +50 -34
  65. package/doc/zh-CN/gui/build.md +28 -16
  66. package/doc/zh-CN/gui/canvas.md +36 -17
  67. package/doc/zh-CN/gui/known-issues.md +6 -5
  68. package/doc/zh-CN/gui/media.md +21 -10
  69. package/doc/zh-CN/gui.md +43 -34
  70. package/package.json +2 -1
  71. package/runtime/ext_gui/canvas.cpp +114 -199
  72. package/runtime/ext_gui/canvas.h +101 -2
  73. package/runtime/ext_gui/canvas_gradient.cpp +112 -0
  74. package/runtime/ext_gui/canvas_internal.h +6 -6
  75. package/runtime/ext_gui/canvas_raster.cpp +27 -5
  76. package/runtime/ext_gui/canvas_stroke.cpp +252 -0
  77. package/runtime/ext_gui/canvas_style.cpp +240 -0
  78. package/runtime/ext_gui/dom_api.cpp +1 -0
  79. package/runtime/ext_gui/dom_api_canvas.cpp +38 -4
  80. package/runtime/ext_gui/dom_api_canvas_extra.cpp +232 -0
  81. package/runtime/ext_gui/dom_api_internal.h +31 -0
  82. package/runtime/ext_gui/dom_api_proto.cpp +24 -0
  83. package/runtime/ext_gui/gui_media.cpp +69 -2
  84. package/runtime/ext_gui/media.cpp +34 -488
  85. package/runtime/ext_gui/media.h +54 -19
  86. package/runtime/ext_gui/media_api.h +65 -0
  87. package/runtime/ext_media/media.cpp +507 -0
  88. package/runtime/ext_media/media_bridge.c +35 -0
  89. package/scripts/build-gui.ts +6 -72
  90. package/scripts/build-media.ts +245 -0
  91. package/src/cli/hints.ts +11 -0
  92. package/src/cli/main.ts +1 -1
  93. package/src/driver/compiler.ts +1 -0
  94. package/src/driver/version.ts +1 -1
  95. package/src/extensions/catalog.ts +2 -1
  96. package/src/extensions/media/index.ts +67 -0
  97. package/src/extensions/registry.ts +24 -0
  98. package/src/index.ts +1 -0
@@ -0,0 +1,240 @@
1
+ /*
2
+ * xbintsc GUI engine — Canvas2D drawing-state parsing.
3
+ *
4
+ * The state object itself lives in `canvas.h`; this unit parses the strings the
5
+ * DOM setters receive (CSS colours, the `font` shorthand, `lineCap`/`lineJoin`
6
+ * keywords) and validates the dash list. Keeping the parsing out of
7
+ * `canvas.cpp` leaves that unit free to own the path and draw entry points.
8
+ */
9
+
10
+ #include <cmath>
11
+ #include <cstdlib>
12
+ #include <cstring>
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ #include "canvas.h"
17
+ #include "css.h"
18
+
19
+ namespace xtgui {
20
+
21
+ namespace {
22
+
23
+ std::string lower_ascii(const std::string &value) {
24
+ std::string out = value;
25
+ for (char &c : out) {
26
+ if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
27
+ }
28
+ return out;
29
+ }
30
+
31
+ bool parse_font_size_token(const std::string &token, float fallback, float *out) {
32
+ std::string lower = lower_ascii(token);
33
+ struct Suffix {
34
+ const char *text;
35
+ float factor;
36
+ };
37
+ static const Suffix suffixes[] = {{"px", 1.0f}, {"pt", 96.0f / 72.0f}, {"pc", 16.0f},
38
+ {"in", 96.0f}, {"cm", 96.0f / 2.54f}, {"mm", 96.0f / 25.4f},
39
+ {"q", 96.0f / 101.6f}, {"em", 0.0f}, {"rem", 0.0f}};
40
+ for (const Suffix &suffix : suffixes) {
41
+ size_t length = std::strlen(suffix.text);
42
+ if (lower.size() <= length || lower.compare(lower.size() - length, length, suffix.text) != 0)
43
+ continue;
44
+ char *end = nullptr;
45
+ float value = std::strtof(lower.c_str(), &end);
46
+ if (end == lower.c_str()) return false;
47
+ if (suffix.factor == 0.0f) {
48
+ *out = value * fallback;
49
+ } else {
50
+ *out = value * suffix.factor;
51
+ }
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+
57
+ /** Parse the CSS `font` shorthand into a `FontSpec` (the pieces Canvas2D
58
+ * understands; unknown tokens are ignored). */
59
+ FontSpec parse_font_string(const std::string &text) {
60
+ FontSpec spec;
61
+ spec.family = "sans-serif";
62
+ spec.pixel_size = 10.0f;
63
+ spec.weight = 400;
64
+ spec.italic = false;
65
+ std::vector<std::string> tokens = xt_css_split_whitespace(text);
66
+ std::vector<std::string> family;
67
+ float size = 10.0f;
68
+ bool have_size = false;
69
+ for (const std::string &token : tokens) {
70
+ std::string lower = lower_ascii(token);
71
+ float parsed = 0.0f;
72
+ if (!have_size && parse_font_size_token(lower, 16.0f, &parsed)) {
73
+ size = parsed;
74
+ have_size = true;
75
+ continue;
76
+ }
77
+ if (lower == "italic" || lower == "oblique") {
78
+ spec.italic = true;
79
+ continue;
80
+ }
81
+ if (lower == "bold" || lower == "bolder") {
82
+ spec.weight = 700;
83
+ continue;
84
+ }
85
+ if (lower == "lighter") {
86
+ spec.weight = 300;
87
+ continue;
88
+ }
89
+ if (lower == "normal" || lower == "small-caps") continue;
90
+ if (!lower.empty() && lower.back() == ',') lower.pop_back();
91
+ if (!lower.empty()) family.push_back(lower);
92
+ }
93
+ spec.pixel_size = size;
94
+ if (!family.empty()) spec.family = family[0];
95
+ return spec;
96
+ }
97
+
98
+ void parse_color_into(const std::string &css, float color[4], std::string *stored) {
99
+ Color parsed = xt_css_parse_color(css);
100
+ if (!parsed.valid) return;
101
+ color[0] = parsed.r;
102
+ color[1] = parsed.g;
103
+ color[2] = parsed.b;
104
+ color[3] = parsed.a;
105
+ *stored = css;
106
+ }
107
+
108
+ bool positive_finite(float value) { return std::isfinite(value) && value >= 0.0f; }
109
+
110
+ } // namespace
111
+
112
+ void Canvas2D::set_fill_style(const std::string &css) {
113
+ parse_color_into(css, state_.fill_color, &state_.fill_style);
114
+ state_.fill_gradient = 0;
115
+ }
116
+
117
+ void Canvas2D::set_stroke_style(const std::string &css) {
118
+ parse_color_into(css, state_.stroke_color, &state_.stroke_style);
119
+ state_.stroke_gradient = 0;
120
+ }
121
+
122
+ void Canvas2D::set_fill_gradient(uint64_t id) {
123
+ if (id == 0) {
124
+ state_.fill_gradient = 0;
125
+ return;
126
+ }
127
+ if (find_gradient(id) != nullptr) state_.fill_gradient = id;
128
+ }
129
+
130
+ void Canvas2D::set_stroke_gradient(uint64_t id) {
131
+ if (id == 0) {
132
+ state_.stroke_gradient = 0;
133
+ return;
134
+ }
135
+ if (find_gradient(id) != nullptr) state_.stroke_gradient = id;
136
+ }
137
+
138
+ void Canvas2D::set_global_alpha(float alpha) {
139
+ if (!std::isfinite(alpha)) return;
140
+ if (alpha < 0.0f) alpha = 0.0f;
141
+ if (alpha > 1.0f) alpha = 1.0f;
142
+ state_.global_alpha = alpha;
143
+ }
144
+
145
+ void Canvas2D::set_font(const std::string &font) {
146
+ state_.font = parse_font_string(font);
147
+ state_.font_string = font;
148
+ }
149
+
150
+ void Canvas2D::set_text_align(const std::string &value) {
151
+ std::string lower = lower_ascii(value);
152
+ if (lower == "start" || lower == "left") {
153
+ state_.text_align = 0;
154
+ } else if (lower == "center") {
155
+ state_.text_align = 1;
156
+ } else if (lower == "right" || lower == "end") {
157
+ state_.text_align = 2;
158
+ } else {
159
+ return;
160
+ }
161
+ state_.text_align_string = lower;
162
+ }
163
+
164
+ void Canvas2D::set_text_baseline(const std::string &value) {
165
+ std::string lower = lower_ascii(value);
166
+ if (lower == "alphabetic") {
167
+ state_.text_baseline = 0;
168
+ } else if (lower == "top") {
169
+ state_.text_baseline = 1;
170
+ } else if (lower == "middle") {
171
+ state_.text_baseline = 2;
172
+ } else if (lower == "bottom") {
173
+ state_.text_baseline = 3;
174
+ } else if (lower == "hanging") {
175
+ state_.text_baseline = 4;
176
+ } else if (lower == "ideographic") {
177
+ state_.text_baseline = 5;
178
+ } else {
179
+ return;
180
+ }
181
+ state_.text_baseline_string = lower;
182
+ }
183
+
184
+ void Canvas2D::set_line_cap(const std::string &value) {
185
+ std::string lower = lower_ascii(value);
186
+ if (lower == "butt") {
187
+ state_.line_cap = 0;
188
+ } else if (lower == "round") {
189
+ state_.line_cap = 1;
190
+ } else if (lower == "square") {
191
+ state_.line_cap = 2;
192
+ } else {
193
+ return;
194
+ }
195
+ state_.line_cap_string = lower;
196
+ }
197
+
198
+ void Canvas2D::set_line_join(const std::string &value) {
199
+ std::string lower = lower_ascii(value);
200
+ if (lower == "miter") {
201
+ state_.line_join = 0;
202
+ } else if (lower == "round") {
203
+ state_.line_join = 1;
204
+ } else if (lower == "bevel") {
205
+ state_.line_join = 2;
206
+ } else {
207
+ return;
208
+ }
209
+ state_.line_join_string = lower;
210
+ }
211
+
212
+ void Canvas2D::set_miter_limit(float limit) {
213
+ if (!std::isfinite(limit) || limit <= 0.0f) return;
214
+ state_.miter_limit = limit;
215
+ }
216
+
217
+ void Canvas2D::set_line_dash(const std::vector<float> &segments) {
218
+ if (segments.empty()) {
219
+ state_.line_dash.clear();
220
+ return;
221
+ }
222
+ for (float segment : segments) {
223
+ /* The spec ignores a list containing a negative or non-finite value. */
224
+ if (!positive_finite(segment)) return;
225
+ }
226
+ std::vector<float> pattern = segments;
227
+ if (pattern.size() % 2 == 1) {
228
+ const size_t count = pattern.size();
229
+ for (size_t i = 0; i < count; i++) pattern.push_back(pattern[i]);
230
+ }
231
+ float total = 0.0f;
232
+ for (float segment : pattern) total += segment;
233
+ if (total <= 0.0f) {
234
+ state_.line_dash.clear();
235
+ return;
236
+ }
237
+ state_.line_dash = std::move(pattern);
238
+ }
239
+
240
+ } // namespace xtgui
@@ -33,6 +33,7 @@ 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
35
  xt_value g_canvas_proto = XT_UNDEFINED;
36
+ xt_value g_canvas_gradient_proto = XT_UNDEFINED;
36
37
 
37
38
  /* -- small helpers -------------------------------------------------------- */
38
39
 
@@ -43,6 +43,16 @@ std::string string_arg(int32_t argc, xt_value *argv, int32_t index) {
43
43
 
44
44
  } // namespace
45
45
 
46
+ xtgui::Canvas2D *canvas_from_context(xt_value self) { return context_canvas(self); }
47
+
48
+ double canvas_number_arg(int32_t argc, xt_value *argv, int32_t index, double fallback) {
49
+ return number_arg(argc, argv, index, fallback);
50
+ }
51
+
52
+ std::string canvas_string_arg(int32_t argc, xt_value *argv, int32_t index) {
53
+ return string_arg(argc, argv, index);
54
+ }
55
+
46
56
  METHOD(node_get_context) {
47
57
  (void)env;
48
58
  std::string mode = string_arg(argc, argv, 0);
@@ -152,13 +162,25 @@ METHOD(canvas_fill_style_get) {
152
162
  (void)argc;
153
163
  (void)argv;
154
164
  xtgui::Canvas2D *canvas = context_canvas(self);
155
- return xt_string_from_cstr(canvas != nullptr ? canvas->fill_style().c_str() : "#000000");
165
+ if (canvas == nullptr) return xt_string_from_cstr("#000000");
166
+ if (canvas->fill_gradient_id() != 0) {
167
+ return canvas_gradient_handle(self, canvas, canvas->fill_gradient_id());
168
+ }
169
+ return xt_string_from_cstr(canvas->fill_style().c_str());
156
170
  }
157
171
 
158
172
  METHOD(canvas_fill_style_set) {
159
173
  (void)env;
160
174
  xtgui::Canvas2D *canvas = context_canvas(self);
161
- if (canvas != nullptr) canvas->set_fill_style(string_arg(argc, argv, 0));
175
+ if (canvas == nullptr) return XT_UNDEFINED;
176
+ if (argc > 0) {
177
+ uint64_t gradient = canvas_gradient_id(xt_arg(argc, argv, 0), canvas);
178
+ if (gradient != 0) {
179
+ canvas->set_fill_gradient(gradient);
180
+ return XT_UNDEFINED;
181
+ }
182
+ }
183
+ canvas->set_fill_style(string_arg(argc, argv, 0));
162
184
  return XT_UNDEFINED;
163
185
  }
164
186
 
@@ -167,13 +189,25 @@ METHOD(canvas_stroke_style_get) {
167
189
  (void)argc;
168
190
  (void)argv;
169
191
  xtgui::Canvas2D *canvas = context_canvas(self);
170
- return xt_string_from_cstr(canvas != nullptr ? canvas->stroke_style().c_str() : "#000000");
192
+ if (canvas == nullptr) return xt_string_from_cstr("#000000");
193
+ if (canvas->stroke_gradient_id() != 0) {
194
+ return canvas_gradient_handle(self, canvas, canvas->stroke_gradient_id());
195
+ }
196
+ return xt_string_from_cstr(canvas->stroke_style().c_str());
171
197
  }
172
198
 
173
199
  METHOD(canvas_stroke_style_set) {
174
200
  (void)env;
175
201
  xtgui::Canvas2D *canvas = context_canvas(self);
176
- if (canvas != nullptr) canvas->set_stroke_style(string_arg(argc, argv, 0));
202
+ if (canvas == nullptr) return XT_UNDEFINED;
203
+ if (argc > 0) {
204
+ uint64_t gradient = canvas_gradient_id(xt_arg(argc, argv, 0), canvas);
205
+ if (gradient != 0) {
206
+ canvas->set_stroke_gradient(gradient);
207
+ return XT_UNDEFINED;
208
+ }
209
+ }
210
+ canvas->set_stroke_style(string_arg(argc, argv, 0));
177
211
  return XT_UNDEFINED;
178
212
  }
179
213
 
@@ -0,0 +1,232 @@
1
+ /*
2
+ * xbintsc GUI engine — the newer half of the Canvas2D DOM surface.
3
+ *
4
+ * `dom_api_canvas.cpp` owns the original path/rect/text/image bindings and the
5
+ * shared context helpers; this unit adds gradients, ellipses, rounded
6
+ * rectangles, line caps/joins, dash patterns and `getTransform`. Keeping them
7
+ * separate keeps both translation units under the repository's file budget.
8
+ */
9
+
10
+ #include "dom_api_internal.h"
11
+
12
+ #include <cmath>
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ #include "canvas.h"
17
+ #include "css.h"
18
+
19
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
20
+
21
+ namespace domapi {
22
+
23
+ xt_value canvas_gradient_handle(xt_value context, const xtgui::Canvas2D *canvas, uint64_t id) {
24
+ const std::string key = "__xt_gui_grad_" + std::to_string(id);
25
+ xt_value cached = xt_object_get_cstr(context, key.c_str());
26
+ if (XT_IS_OBJECT(cached)) return cached;
27
+ xt_value handle = xt_object_new_with_proto(canvas_gradient_proto());
28
+ if (canvas != nullptr) {
29
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_canvas"), xt_number((double)canvas->id()));
30
+ }
31
+ xt_object_set(handle, xt_string_from_cstr("__xt_gui_gradient"), xt_number((double)id));
32
+ xt_object_set(context, xt_string_from_cstr(key.c_str()), handle);
33
+ return handle;
34
+ }
35
+
36
+ uint64_t canvas_gradient_id(xt_value handle, const xtgui::Canvas2D *canvas) {
37
+ if (canvas == nullptr || !XT_IS_OBJECT(handle)) return 0;
38
+ xt_value canvasId = xt_object_get_cstr(handle, "__xt_gui_canvas");
39
+ xt_value gradientId = xt_object_get_cstr(handle, "__xt_gui_gradient");
40
+ if (!XT_IS_NUMBER(canvasId) || !XT_IS_NUMBER(gradientId)) return 0;
41
+ if ((uint64_t)xt_to_number(canvasId) != canvas->id()) return 0;
42
+ uint64_t id = (uint64_t)xt_to_number(gradientId);
43
+ return canvas->find_gradient(id) == nullptr ? 0 : id;
44
+ }
45
+
46
+ /* -- gradients ------------------------------------------------------------ */
47
+
48
+ METHOD(canvas_create_linear_gradient) {
49
+ (void)env;
50
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
51
+ if (canvas == nullptr) return XT_NULL;
52
+ xtgui::CanvasGradient *gradient = canvas->create_linear_gradient(
53
+ (float)canvas_number_arg(argc, argv, 0, 0.0), (float)canvas_number_arg(argc, argv, 1, 0.0),
54
+ (float)canvas_number_arg(argc, argv, 2, 0.0), (float)canvas_number_arg(argc, argv, 3, 0.0));
55
+ return canvas_gradient_handle(self, canvas, gradient->id());
56
+ }
57
+
58
+ METHOD(canvas_create_radial_gradient) {
59
+ (void)env;
60
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
61
+ if (canvas == nullptr) return XT_NULL;
62
+ xtgui::CanvasGradient *gradient = canvas->create_radial_gradient(
63
+ (float)canvas_number_arg(argc, argv, 0, 0.0), (float)canvas_number_arg(argc, argv, 1, 0.0),
64
+ (float)canvas_number_arg(argc, argv, 2, 0.0), (float)canvas_number_arg(argc, argv, 3, 0.0),
65
+ (float)canvas_number_arg(argc, argv, 4, 0.0), (float)canvas_number_arg(argc, argv, 5, 0.0));
66
+ return canvas_gradient_handle(self, canvas, gradient->id());
67
+ }
68
+
69
+ METHOD(canvas_gradient_add_color_stop) {
70
+ (void)env;
71
+ xt_value canvasId = xt_object_get_cstr(self, "__xt_gui_canvas");
72
+ xt_value gradientId = xt_object_get_cstr(self, "__xt_gui_gradient");
73
+ if (!XT_IS_NUMBER(canvasId) || !XT_IS_NUMBER(gradientId)) return XT_UNDEFINED;
74
+ xtgui::Canvas2D *canvas = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvasId));
75
+ if (canvas == nullptr) return XT_UNDEFINED;
76
+ xtgui::CanvasGradient *gradient = canvas->find_gradient((uint64_t)xt_to_number(gradientId));
77
+ if (gradient == nullptr) return XT_UNDEFINED;
78
+ xtgui::Color color = xtgui::xt_css_parse_color(canvas_string_arg(argc, argv, 1));
79
+ if (!color.valid) return XT_UNDEFINED;
80
+ float rgba[4] = {color.r, color.g, color.b, color.a};
81
+ gradient->add_color_stop((float)canvas_number_arg(argc, argv, 0, 0.0), rgba);
82
+ return XT_UNDEFINED;
83
+ }
84
+
85
+ /* -- paths ---------------------------------------------------------------- */
86
+
87
+ METHOD(canvas_ellipse) {
88
+ (void)env;
89
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
90
+ if (canvas != nullptr) {
91
+ canvas->ellipse((float)canvas_number_arg(argc, argv, 0, 0.0),
92
+ (float)canvas_number_arg(argc, argv, 1, 0.0),
93
+ (float)canvas_number_arg(argc, argv, 2, 0.0),
94
+ (float)canvas_number_arg(argc, argv, 3, 0.0),
95
+ (float)canvas_number_arg(argc, argv, 4, 0.0),
96
+ (float)canvas_number_arg(argc, argv, 5, 0.0),
97
+ (float)canvas_number_arg(argc, argv, 6, 0.0),
98
+ value_truthy(xt_arg(argc, argv, 7)));
99
+ }
100
+ return XT_UNDEFINED;
101
+ }
102
+
103
+ METHOD(canvas_round_rect) {
104
+ (void)env;
105
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
106
+ if (canvas != nullptr) {
107
+ canvas->round_rect((float)canvas_number_arg(argc, argv, 0, 0.0),
108
+ (float)canvas_number_arg(argc, argv, 1, 0.0),
109
+ (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));
112
+ }
113
+ return XT_UNDEFINED;
114
+ }
115
+
116
+ /* -- line styles ---------------------------------------------------------- */
117
+
118
+ METHOD(canvas_line_cap_get) {
119
+ (void)env;
120
+ (void)argc;
121
+ (void)argv;
122
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
123
+ return xt_string_from_cstr(canvas != nullptr ? canvas->line_cap().c_str() : "butt");
124
+ }
125
+
126
+ METHOD(canvas_line_cap_set) {
127
+ (void)env;
128
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
129
+ if (canvas != nullptr) canvas->set_line_cap(canvas_string_arg(argc, argv, 0));
130
+ return XT_UNDEFINED;
131
+ }
132
+
133
+ METHOD(canvas_line_join_get) {
134
+ (void)env;
135
+ (void)argc;
136
+ (void)argv;
137
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
138
+ return xt_string_from_cstr(canvas != nullptr ? canvas->line_join().c_str() : "miter");
139
+ }
140
+
141
+ METHOD(canvas_line_join_set) {
142
+ (void)env;
143
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
144
+ if (canvas != nullptr) canvas->set_line_join(canvas_string_arg(argc, argv, 0));
145
+ return XT_UNDEFINED;
146
+ }
147
+
148
+ METHOD(canvas_miter_limit_get) {
149
+ (void)env;
150
+ (void)argc;
151
+ (void)argv;
152
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
153
+ return xt_number(canvas != nullptr ? canvas->miter_limit() : 10.0);
154
+ }
155
+
156
+ METHOD(canvas_miter_limit_set) {
157
+ (void)env;
158
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
159
+ if (canvas != nullptr) canvas->set_miter_limit((float)canvas_number_arg(argc, argv, 0, 10.0));
160
+ return XT_UNDEFINED;
161
+ }
162
+
163
+ METHOD(canvas_line_dash_get) {
164
+ (void)env;
165
+ (void)argc;
166
+ (void)argv;
167
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
168
+ xt_value result = xt_array_new(0, nullptr);
169
+ if (canvas != nullptr) {
170
+ for (float entry : canvas->line_dash()) xt_array_push(result, xt_number(entry));
171
+ }
172
+ return result;
173
+ }
174
+
175
+ METHOD(canvas_line_dash_set) {
176
+ (void)env;
177
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
178
+ if (canvas == nullptr) return XT_UNDEFINED;
179
+ std::vector<float> pattern;
180
+ if (argc > 0) {
181
+ xt_value value = xt_arg(argc, argv, 0);
182
+ if (XT_IS_ARRAY(value)) {
183
+ int32_t size = xt_array_size(value);
184
+ for (int32_t i = 0; i < size; i++) {
185
+ pattern.push_back((float)xt_to_number(xt_array_get(value, xt_number((double)i))));
186
+ }
187
+ }
188
+ }
189
+ canvas->set_line_dash(pattern);
190
+ return XT_UNDEFINED;
191
+ }
192
+
193
+ METHOD(canvas_line_dash_offset_get) {
194
+ (void)env;
195
+ (void)argc;
196
+ (void)argv;
197
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
198
+ return xt_number(canvas != nullptr ? canvas->line_dash_offset() : 0.0);
199
+ }
200
+
201
+ METHOD(canvas_line_dash_offset_set) {
202
+ (void)env;
203
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
204
+ if (canvas != nullptr) {
205
+ canvas->set_line_dash_offset((float)canvas_number_arg(argc, argv, 0, 0.0));
206
+ }
207
+ return XT_UNDEFINED;
208
+ }
209
+
210
+ /* -- transform ------------------------------------------------------------ */
211
+
212
+ METHOD(canvas_get_transform) {
213
+ (void)env;
214
+ (void)argc;
215
+ (void)argv;
216
+ xtgui::Canvas2D *canvas = canvas_from_context(self);
217
+ const xtgui::CanvasTransform transform =
218
+ canvas != nullptr ? canvas->current_transform() : xtgui::CanvasTransform();
219
+ xt_value result = xt_object_new();
220
+ xt_object_set(result, xt_string_from_cstr("a"), xt_number(transform.a));
221
+ xt_object_set(result, xt_string_from_cstr("b"), xt_number(transform.b));
222
+ xt_object_set(result, xt_string_from_cstr("c"), xt_number(transform.c));
223
+ xt_object_set(result, xt_string_from_cstr("d"), xt_number(transform.d));
224
+ xt_object_set(result, xt_string_from_cstr("e"), xt_number(transform.e));
225
+ xt_object_set(result, xt_string_from_cstr("f"), xt_number(transform.f));
226
+ xt_object_set(result, xt_string_from_cstr("is2D"), xt_bool(true));
227
+ return result;
228
+ }
229
+
230
+ } // namespace domapi
231
+
232
+ #undef METHOD
@@ -28,6 +28,7 @@ extern xt_value g_event_proto;
28
28
  extern xt_value g_class_list_proto;
29
29
  extern xt_value g_style_proto;
30
30
  extern xt_value g_canvas_proto;
31
+ extern xt_value g_canvas_gradient_proto;
31
32
 
32
33
  xt_value node_proto();
33
34
  xt_value document_proto();
@@ -35,6 +36,20 @@ xt_value event_proto();
35
36
  xt_value class_list_proto();
36
37
  xt_value style_proto();
37
38
  xt_value canvas_proto();
39
+ xt_value canvas_gradient_proto();
40
+
41
+ /* -- canvas helpers shared by `dom_api_canvas*.cpp` ----------------------- */
42
+
43
+ /** The engine canvas behind a 2D context handle, or null when stale/foreign. */
44
+ xtgui::Canvas2D *canvas_from_context(xt_value self);
45
+ double canvas_number_arg(int32_t argc, xt_value *argv, int32_t index, double fallback);
46
+ std::string canvas_string_arg(int32_t argc, xt_value *argv, int32_t index);
47
+ /** The cached `CanvasGradient` handle for `id` on `canvas`; cached on the
48
+ * context so `ctx.fillStyle === gradient` holds after `fillStyle = gradient`. */
49
+ xt_value canvas_gradient_handle(xt_value context, const xtgui::Canvas2D *canvas, uint64_t id);
50
+ /** The gradient id stored on a handle, or 0 when it is not a gradient for
51
+ * `canvas` (stale or created by another canvas). */
52
+ uint64_t canvas_gradient_id(xt_value handle, const xtgui::Canvas2D *canvas);
38
53
 
39
54
  /* -- cross-unit helpers --------------------------------------------------- */
40
55
 
@@ -195,6 +210,22 @@ METHOD(canvas_fill_text);
195
210
  METHOD(canvas_stroke_text);
196
211
  METHOD(canvas_draw_image);
197
212
  METHOD(canvas_get_image_data);
213
+ METHOD(canvas_ellipse);
214
+ METHOD(canvas_round_rect);
215
+ METHOD(canvas_line_cap_get);
216
+ METHOD(canvas_line_cap_set);
217
+ METHOD(canvas_line_join_get);
218
+ METHOD(canvas_line_join_set);
219
+ METHOD(canvas_miter_limit_get);
220
+ METHOD(canvas_miter_limit_set);
221
+ METHOD(canvas_line_dash_get);
222
+ METHOD(canvas_line_dash_set);
223
+ METHOD(canvas_line_dash_offset_get);
224
+ METHOD(canvas_line_dash_offset_set);
225
+ METHOD(canvas_get_transform);
226
+ METHOD(canvas_create_linear_gradient);
227
+ METHOD(canvas_create_radial_gradient);
228
+ METHOD(canvas_gradient_add_color_stop);
198
229
 
199
230
  /* classList methods */
200
231
  METHOD(class_list_add);
@@ -181,6 +181,14 @@ xt_value canvas_proto() {
181
181
  define_setter(proto, "textAlign", (void *)canvas_text_align_set);
182
182
  define_getter(proto, "textBaseline", (void *)canvas_text_baseline_get);
183
183
  define_setter(proto, "textBaseline", (void *)canvas_text_baseline_set);
184
+ define_getter(proto, "lineCap", (void *)canvas_line_cap_get);
185
+ define_setter(proto, "lineCap", (void *)canvas_line_cap_set);
186
+ define_getter(proto, "lineJoin", (void *)canvas_line_join_get);
187
+ define_setter(proto, "lineJoin", (void *)canvas_line_join_set);
188
+ define_getter(proto, "miterLimit", (void *)canvas_miter_limit_get);
189
+ define_setter(proto, "miterLimit", (void *)canvas_miter_limit_set);
190
+ define_getter(proto, "lineDashOffset", (void *)canvas_line_dash_offset_get);
191
+ define_setter(proto, "lineDashOffset", (void *)canvas_line_dash_offset_set);
184
192
  /* Methods. */
185
193
  define_method(proto, "save", (void *)canvas_save);
186
194
  define_method(proto, "restore", (void *)canvas_restore);
@@ -195,7 +203,9 @@ xt_value canvas_proto() {
195
203
  define_method(proto, "moveTo", (void *)canvas_move_to);
196
204
  define_method(proto, "lineTo", (void *)canvas_line_to);
197
205
  define_method(proto, "rect", (void *)canvas_rect);
206
+ define_method(proto, "roundRect", (void *)canvas_round_rect);
198
207
  define_method(proto, "arc", (void *)canvas_arc);
208
+ define_method(proto, "ellipse", (void *)canvas_ellipse);
199
209
  define_method(proto, "quadraticCurveTo", (void *)canvas_quadratic_curve_to);
200
210
  define_method(proto, "bezierCurveTo", (void *)canvas_bezier_curve_to);
201
211
  define_method(proto, "fill", (void *)canvas_fill);
@@ -208,8 +218,22 @@ xt_value canvas_proto() {
208
218
  define_method(proto, "strokeText", (void *)canvas_stroke_text);
209
219
  define_method(proto, "drawImage", (void *)canvas_draw_image);
210
220
  define_method(proto, "getImageData", (void *)canvas_get_image_data);
221
+ define_method(proto, "setLineDash", (void *)canvas_line_dash_set);
222
+ define_method(proto, "getLineDash", (void *)canvas_line_dash_get);
223
+ define_method(proto, "getTransform", (void *)canvas_get_transform);
224
+ define_method(proto, "createLinearGradient", (void *)canvas_create_linear_gradient);
225
+ define_method(proto, "createRadialGradient", (void *)canvas_create_radial_gradient);
211
226
  g_canvas_proto = proto;
212
227
  return proto;
213
228
  }
214
229
 
230
+ xt_value canvas_gradient_proto() {
231
+ xt_gc_add_root(&g_canvas_gradient_proto);
232
+ if (XT_IS_OBJECT(g_canvas_gradient_proto)) return g_canvas_gradient_proto;
233
+ xt_value proto = xt_object_new();
234
+ define_method(proto, "addColorStop", (void *)canvas_gradient_add_color_stop);
235
+ g_canvas_gradient_proto = proto;
236
+ return proto;
237
+ }
238
+
215
239
  } // namespace domapi