xbintsc 0.5.36 → 0.5.38

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 (124) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/state.js +1 -0
  3. package/dist/src/codegen/generator/state.js.map +1 -1
  4. package/dist/src/codegen/generator/tables.js +1 -0
  5. package/dist/src/codegen/generator/tables.js.map +1 -1
  6. package/dist/src/driver/bundler/deferred.d.ts +63 -0
  7. package/dist/src/driver/bundler/deferred.js +279 -0
  8. package/dist/src/driver/bundler/deferred.js.map +1 -0
  9. package/dist/src/driver/bundler/merge.js +214 -2
  10. package/dist/src/driver/bundler/merge.js.map +1 -1
  11. package/dist/src/driver/runtime-objects.d.ts +1 -1
  12. package/dist/src/driver/runtime-objects.js +1 -0
  13. package/dist/src/driver/runtime-objects.js.map +1 -1
  14. package/dist/src/driver/version.d.ts +1 -1
  15. package/dist/src/driver/version.js +1 -1
  16. package/dist/src/extensions/gui/html.js +89 -13
  17. package/dist/src/extensions/gui/html.js.map +1 -1
  18. package/dist/src/extensions/gui/index.js +13 -0
  19. package/dist/src/extensions/gui/index.js.map +1 -1
  20. package/dist/src/extensions/gui/three.d.ts +4 -3
  21. package/dist/src/extensions/gui/three.js +4 -3
  22. package/dist/src/extensions/gui/three.js.map +1 -1
  23. package/dist/tests/e2e/gui-browser.test.js +65 -0
  24. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  25. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  26. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  27. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  28. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-dom.test.js +215 -0
  30. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  31. package/dist/tests/e2e/gui-events.test.js +112 -0
  32. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  33. package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
  34. package/dist/tests/e2e/gui-script-import-order.test.js +161 -0
  35. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
  36. package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
  37. package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
  38. package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
  39. package/dist/tests/e2e/url.test.d.ts +6 -0
  40. package/dist/tests/e2e/url.test.js +114 -0
  41. package/dist/tests/e2e/url.test.js.map +1 -0
  42. package/dist/tests/extensions/gui.test.js +22 -10
  43. package/dist/tests/extensions/gui.test.js.map +1 -1
  44. package/doc/gui/api.md +38 -9
  45. package/doc/gui/canvas.md +49 -18
  46. package/doc/gui/dom.md +51 -4
  47. package/doc/gui/webgl.md +20 -10
  48. package/doc/gui-scripts.md +19 -19
  49. package/doc/gui.md +10 -8
  50. package/doc/implemented/fetch.md +5 -0
  51. package/doc/implemented/modules.md +13 -0
  52. package/doc/implemented/stdlib.md +17 -0
  53. package/doc/unimplemented/deviations.md +1 -0
  54. package/doc/zh-CN/gui/api.md +33 -4
  55. package/doc/zh-CN/gui/canvas.md +44 -19
  56. package/doc/zh-CN/gui/dom.md +45 -3
  57. package/doc/zh-CN/gui/webgl.md +3 -3
  58. package/doc/zh-CN/gui-scripts.md +14 -13
  59. package/doc/zh-CN/gui.md +7 -6
  60. package/doc/zh-CN/implemented/fetch.md +4 -0
  61. package/doc/zh-CN/implemented/modules.md +10 -0
  62. package/doc/zh-CN/implemented/stdlib.md +15 -0
  63. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  64. package/package.json +7 -7
  65. package/runtime/ext_gui/browser.cpp +34 -49
  66. package/runtime/ext_gui/browser_abort.cpp +249 -0
  67. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  68. package/runtime/ext_gui/browser_media.cpp +176 -0
  69. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  70. package/runtime/ext_gui/canvas.cpp +88 -30
  71. package/runtime/ext_gui/canvas.h +74 -9
  72. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  73. package/runtime/ext_gui/canvas_internal.h +8 -5
  74. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  75. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  76. package/runtime/ext_gui/canvas_style.cpp +4 -2
  77. package/runtime/ext_gui/dom.h +4 -0
  78. package/runtime/ext_gui/dom_api.cpp +1 -0
  79. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  80. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  81. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  82. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  83. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  84. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  85. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  86. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  87. package/runtime/ext_gui/dom_api_internal.h +55 -2
  88. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  89. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  90. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  91. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  92. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  93. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  94. package/runtime/ext_gui/dom_api_webgl.cpp +10 -0
  95. package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
  96. package/runtime/ext_gui/gui.cpp +59 -5
  97. package/runtime/ext_gui/gui_engine.h +49 -0
  98. package/runtime/ext_gui/gui_events.cpp +80 -9
  99. package/runtime/ext_gui/listener_options.h +43 -0
  100. package/runtime/ext_gui/paint.cpp +28 -0
  101. package/runtime/ext_gui/paint.h +4 -0
  102. package/runtime/ext_gui/script.cpp +31 -0
  103. package/runtime/ext_gui/webgl_glsl.h +3 -0
  104. package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
  105. package/runtime/ext_gui/webgl_msl.cpp +4 -0
  106. package/runtime/ext_gui/webgl_shader.cpp +12 -6
  107. package/runtime/ext_gui/window.cpp +14 -65
  108. package/runtime/ext_gui/window_events.cpp +125 -0
  109. package/runtime/rt.h +29 -0
  110. package/runtime/rt_internal.h +4 -0
  111. package/runtime/xt_builtins.c +0 -21
  112. package/runtime/xt_containers/access.inc +94 -2
  113. package/runtime/xt_containers/objects.inc +29 -31
  114. package/runtime/xt_fetch/async.inc +60 -0
  115. package/runtime/xt_url_object.c +463 -0
  116. package/src/codegen/generator/state.ts +1 -0
  117. package/src/codegen/generator/tables.ts +1 -0
  118. package/src/driver/bundler/deferred.ts +351 -0
  119. package/src/driver/bundler/merge.ts +219 -2
  120. package/src/driver/runtime-objects.ts +1 -0
  121. package/src/driver/version.ts +1 -1
  122. package/src/extensions/gui/html.ts +91 -15
  123. package/src/extensions/gui/index.ts +13 -0
  124. package/src/extensions/gui/three.ts +4 -3
@@ -0,0 +1,176 @@
1
+ /*
2
+ * xbintsc GUI engine — live `matchMedia` lists.
3
+ *
4
+ * `window.matchMedia(query)` returns one cached `MediaQueryList` per query. The
5
+ * engine re-evaluates every live list on a window resize (the same evaluator
6
+ * `@media` rules use) and dispatches a `change` event when the match flips, so
7
+ * a page can react to a viewport change instead of polling `matches`.
8
+ */
9
+
10
+ #include "gui_engine.h"
11
+
12
+
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ #include "css.h"
17
+
18
+ namespace {
19
+
20
+ std::string string_value(xt_value value) {
21
+ const char *data = xt_string_data(xt_to_string(value));
22
+ return data != nullptr ? data : "";
23
+ }
24
+
25
+ xt_value listeners_of(xt_value self, bool create) {
26
+ xt_value listeners = xt_object_get_cstr(self, "__xt_gui_media_listeners");
27
+ if (XT_IS_ARRAY(listeners) || !create) return listeners;
28
+ listeners = xt_array_new(0, nullptr);
29
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_listeners"), listeners);
30
+ return listeners;
31
+ }
32
+
33
+ void add_listener(xt_value self, xt_value fn, bool once, xt_value signal) {
34
+ if (!XT_IS_FUNCTION(fn)) return;
35
+ xt_value entry = xt_object_new();
36
+ xt_object_set(entry, xt_string_from_cstr("fn"), fn);
37
+ xt_object_set(entry, xt_string_from_cstr("once"), once ? XT_TRUE : XT_FALSE);
38
+ xt_object_set(entry, xt_string_from_cstr("signal"), signal);
39
+ xt_array_push(listeners_of(self, true), entry);
40
+ }
41
+
42
+ void remove_listener(xt_value self, xt_value fn) {
43
+ xt_value listeners = listeners_of(self, false);
44
+ if (!XT_IS_ARRAY(listeners)) return;
45
+ xt_value kept = xt_array_new(0, nullptr);
46
+ int32_t count = (int32_t)xt_to_number(xt_array_length(listeners));
47
+ for (int32_t i = 0; i < count; i++) {
48
+ xt_value entry = xt_array_get(listeners, xt_number((double)i));
49
+ if (!xt_truthy(xt_seq(xt_object_get_cstr(entry, "fn"), fn))) xt_array_push(kept, entry);
50
+ }
51
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_listeners"), kept);
52
+ }
53
+
54
+ void call_change_listener(xt_value fn, xt_value list, xt_value event) {
55
+ if (!XT_IS_FUNCTION(fn)) return;
56
+ xt_value args[1] = {event};
57
+ xt_call_with_this(fn, list, 1, args);
58
+ }
59
+
60
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
61
+
62
+ METHOD(media_add_event_listener) {
63
+ (void)env;
64
+ if (argc < 2 || string_value(xt_arg(argc, argv, 0)) != "change") return XT_UNDEFINED;
65
+ bool once = false;
66
+ xt_value options = xt_arg(argc, argv, 2);
67
+ if (XT_IS_OBJECT(options)) once = xt_truthy(xt_object_get_cstr(options, "once")) != 0;
68
+ add_listener(self, xt_arg(argc, argv, 1), once, XT_UNDEFINED);
69
+ return XT_UNDEFINED;
70
+ }
71
+
72
+ METHOD(media_remove_event_listener) {
73
+ (void)env;
74
+ if (argc < 2 || string_value(xt_arg(argc, argv, 0)) != "change") return XT_UNDEFINED;
75
+ remove_listener(self, xt_arg(argc, argv, 1));
76
+ return XT_UNDEFINED;
77
+ }
78
+
79
+ /** Legacy `addListener(fn)` / `removeListener(fn)`. */
80
+ METHOD(media_add_listener) {
81
+ (void)env;
82
+ if (argc >= 1) add_listener(self, xt_arg(argc, argv, 0), false, XT_UNDEFINED);
83
+ return XT_UNDEFINED;
84
+ }
85
+
86
+ METHOD(media_remove_listener) {
87
+ (void)env;
88
+ if (argc >= 1) remove_listener(self, xt_arg(argc, argv, 0));
89
+ return XT_UNDEFINED;
90
+ }
91
+
92
+ METHOD(media_onchange_get) {
93
+ (void)env;
94
+ (void)argc;
95
+ (void)argv;
96
+ return xt_object_get_cstr(self, "__xt_gui_media_onchange");
97
+ }
98
+
99
+ METHOD(media_onchange_set) {
100
+ (void)env;
101
+ if (argc >= 1) {
102
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_onchange"), xt_arg(argc, argv, 0));
103
+ }
104
+ return XT_UNDEFINED;
105
+ }
106
+
107
+ #undef METHOD
108
+
109
+ xt_value build_list(const std::string &query, bool matches) {
110
+ xt_value list = xt_object_new();
111
+ xt_object_set(list, xt_string_from_cstr("media"), xt_string_from_cstr(query.c_str()));
112
+ xt_object_set(list, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
113
+ xt_object_set(list, xt_string_from_cstr("__xt_gui_media_query"),
114
+ xt_string_from_cstr(query.c_str()));
115
+ xt_object_set(list, xt_string_from_cstr("addEventListener"),
116
+ xt_closure_new((void *)media_add_event_listener, 0, nullptr));
117
+ xt_object_set(list, xt_string_from_cstr("removeEventListener"),
118
+ xt_closure_new((void *)media_remove_event_listener, 0, nullptr));
119
+ xt_object_set(list, xt_string_from_cstr("addListener"),
120
+ xt_closure_new((void *)media_add_listener, 0, nullptr));
121
+ xt_object_set(list, xt_string_from_cstr("removeListener"),
122
+ xt_closure_new((void *)media_remove_listener, 0, nullptr));
123
+ xt_object_define_getter(list, xt_string_from_cstr("onchange"),
124
+ xt_closure_new((void *)media_onchange_get, 0, nullptr));
125
+ xt_object_define_setter(list, xt_string_from_cstr("onchange"),
126
+ xt_closure_new((void *)media_onchange_set, 0, nullptr));
127
+ return list;
128
+ }
129
+
130
+ } // namespace
131
+
132
+ xt_value xt_browser_media_list(xt_value window, const std::string &query) {
133
+ XtGuiWindow *win = xt_gui_window_from_this(window);
134
+ if (win == nullptr) return build_list(query, false);
135
+ auto cached = win->media_lists_by_query.find(query);
136
+ if (cached != win->media_lists_by_query.end()) return cached->second;
137
+ float width = 0.0f;
138
+ float height = 0.0f;
139
+ xt_gui_window_viewport(win, &width, &height);
140
+ xt_value list = build_list(query, xtgui::xt_css_media_matches(query, width, height));
141
+ win->media_lists.push_back(list);
142
+ win->media_lists_by_query[query] = list;
143
+ return list;
144
+ }
145
+
146
+ void xt_gui_media_lists_notify(XtGuiWindow *win) {
147
+ if (win == nullptr) return;
148
+ float width = 0.0f;
149
+ float height = 0.0f;
150
+ xt_gui_window_viewport(win, &width, &height);
151
+ for (xt_value list : win->media_lists) {
152
+ if (!XT_IS_OBJECT(list)) continue;
153
+ std::string query = string_value(xt_object_get_cstr(list, "__xt_gui_media_query"));
154
+ bool matches = xtgui::xt_css_media_matches(query, width, height);
155
+ bool was = xt_truthy(xt_object_get_cstr(list, "matches"));
156
+ xt_object_set(list, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
157
+ if (was == matches) continue;
158
+ xt_value event = xt_object_new();
159
+ xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr("change"));
160
+ xt_object_set(event, xt_string_from_cstr("media"), xt_string_from_cstr(query.c_str()));
161
+ xt_object_set(event, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
162
+ xt_value onchange = xt_object_get_cstr(list, "__xt_gui_media_onchange");
163
+ call_change_listener(onchange, list, event);
164
+ xt_value listeners = listeners_of(list, false);
165
+ if (!XT_IS_ARRAY(listeners)) continue;
166
+ xt_value snapshot = xt_array_new(0, nullptr);
167
+ int32_t count = (int32_t)xt_to_number(xt_array_length(listeners));
168
+ for (int32_t i = 0; i < count; i++) {
169
+ xt_array_push(snapshot, xt_array_get(listeners, xt_number((double)i)));
170
+ }
171
+ for (int32_t i = 0; i < count; i++) {
172
+ xt_value entry = xt_array_get(snapshot, xt_number((double)i));
173
+ call_change_listener(xt_object_get_cstr(entry, "fn"), list, event);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,130 @@
1
+ /*
2
+ * xbintsc GUI engine — window navigation and focus.
3
+ *
4
+ * `scrollTo` / `scrollBy` move the page viewport (painting and hit testing
5
+ * subtract the offset), `scrollX`/`scrollY` (and the `page*Offset` aliases)
6
+ * report it, and `focus` / `blur` raise the OS window and toggle
7
+ * `document.hasFocus()` plus the corresponding window events.
8
+ */
9
+
10
+ #include "gui_engine.h"
11
+
12
+ #include <cmath>
13
+
14
+ namespace {
15
+
16
+ xt_value scroll_axis(xt_value options, const char *name) {
17
+ if (!XT_IS_OBJECT(options)) return XT_UNDEFINED;
18
+ return xt_object_get_cstr(options, name);
19
+ }
20
+
21
+ double coordinate(xt_value value, double fallback) {
22
+ return XT_IS_NUMBER(value) ? xt_to_number(value) : fallback;
23
+ }
24
+
25
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
26
+
27
+ /** `scrollTo(x, y)` / `scrollTo({ left, top })`. */
28
+ METHOD(win_scroll_to) {
29
+ (void)env;
30
+ XtGuiWindow *win = xt_gui_window_from_this(self);
31
+ if (win == nullptr || argc < 1) return XT_UNDEFINED;
32
+ xt_value options = xt_arg(argc, argv, 0);
33
+ double x = 0;
34
+ double y = 0;
35
+ if (XT_IS_OBJECT(options) && !XT_IS_ARRAY(options)) {
36
+ x = coordinate(scroll_axis(options, "left"), coordinate(scroll_axis(options, "x"), 0));
37
+ y = coordinate(scroll_axis(options, "top"), coordinate(scroll_axis(options, "y"), 0));
38
+ } else {
39
+ x = coordinate(options, 0);
40
+ if (argc >= 2) y = coordinate(xt_arg(argc, argv, 1), 0);
41
+ }
42
+ xt_gui_scroll_to(win, (float)x, (float)y);
43
+ return XT_UNDEFINED;
44
+ }
45
+
46
+ METHOD(win_scroll_by) {
47
+ (void)env;
48
+ XtGuiWindow *win = xt_gui_window_from_this(self);
49
+ if (win == nullptr || argc < 1) return XT_UNDEFINED;
50
+ xt_value options = xt_arg(argc, argv, 0);
51
+ double dx = 0;
52
+ double dy = 0;
53
+ if (XT_IS_OBJECT(options) && !XT_IS_ARRAY(options)) {
54
+ dx = coordinate(scroll_axis(options, "left"), coordinate(scroll_axis(options, "x"), 0));
55
+ dy = coordinate(scroll_axis(options, "top"), coordinate(scroll_axis(options, "y"), 0));
56
+ } else {
57
+ dx = coordinate(options, 0);
58
+ if (argc >= 2) dy = coordinate(xt_arg(argc, argv, 1), 0);
59
+ }
60
+ xt_gui_scroll_to(win, win->scroll_x + (float)dx, win->scroll_y + (float)dy);
61
+ return XT_UNDEFINED;
62
+ }
63
+
64
+ METHOD(win_scroll_x_get) {
65
+ (void)env;
66
+ (void)argc;
67
+ (void)argv;
68
+ XtGuiWindow *win = xt_gui_window_from_this(self);
69
+ return xt_number(win != nullptr ? (double)win->scroll_x : 0.0);
70
+ }
71
+
72
+ METHOD(win_scroll_y_get) {
73
+ (void)env;
74
+ (void)argc;
75
+ (void)argv;
76
+ XtGuiWindow *win = xt_gui_window_from_this(self);
77
+ return xt_number(win != nullptr ? (double)win->scroll_y : 0.0);
78
+ }
79
+
80
+ METHOD(win_focus) {
81
+ (void)env;
82
+ (void)argc;
83
+ (void)argv;
84
+ XtGuiWindow *win = xt_gui_window_from_this(self);
85
+ if (win == nullptr || !win->open) return XT_UNDEFINED;
86
+ if (win->window != nullptr) SDL_RaiseWindow(win->window);
87
+ if (!win->focused) {
88
+ win->focused = 1;
89
+ xt_gui_emit(win, "focus");
90
+ }
91
+ return XT_UNDEFINED;
92
+ }
93
+
94
+ METHOD(win_blur) {
95
+ (void)env;
96
+ (void)argc;
97
+ (void)argv;
98
+ XtGuiWindow *win = xt_gui_window_from_this(self);
99
+ if (win == nullptr || !win->open) return XT_UNDEFINED;
100
+ if (win->focused) {
101
+ win->focused = 0;
102
+ xt_gui_emit(win, "blur");
103
+ }
104
+ return XT_UNDEFINED;
105
+ }
106
+
107
+ #undef METHOD
108
+
109
+ } // namespace
110
+
111
+ void xt_browser_install_window_navigation(xt_value proto) {
112
+ xt_object_set(proto, xt_string_from_cstr("scrollTo"),
113
+ xt_closure_new((void *)win_scroll_to, 0, nullptr));
114
+ xt_object_set(proto, xt_string_from_cstr("scroll"),
115
+ xt_closure_new((void *)win_scroll_to, 0, nullptr));
116
+ xt_object_set(proto, xt_string_from_cstr("scrollBy"),
117
+ xt_closure_new((void *)win_scroll_by, 0, nullptr));
118
+ xt_object_set(proto, xt_string_from_cstr("focus"),
119
+ xt_closure_new((void *)win_focus, 0, nullptr));
120
+ xt_object_set(proto, xt_string_from_cstr("blur"),
121
+ xt_closure_new((void *)win_blur, 0, nullptr));
122
+ xt_object_define_getter(proto, xt_string_from_cstr("scrollX"),
123
+ xt_closure_new((void *)win_scroll_x_get, 0, nullptr));
124
+ xt_object_define_getter(proto, xt_string_from_cstr("scrollY"),
125
+ xt_closure_new((void *)win_scroll_y_get, 0, nullptr));
126
+ xt_object_define_getter(proto, xt_string_from_cstr("pageXOffset"),
127
+ xt_closure_new((void *)win_scroll_x_get, 0, nullptr));
128
+ xt_object_define_getter(proto, xt_string_from_cstr("pageYOffset"),
129
+ xt_closure_new((void *)win_scroll_y_get, 0, nullptr));
130
+ }
@@ -85,9 +85,20 @@ void Canvas2D::resize(int width, int height) {
85
85
  image_.width = width;
86
86
  image_.height = height;
87
87
  image_.pixels.assign((size_t)width * (size_t)height * 4, 0);
88
+ if (!alpha_) {
89
+ /* `{ alpha: false }`: the canvas has an opaque black backdrop, so cleared
90
+ * pixels are black rather than transparent. */
91
+ for (size_t i = 3; i < image_.pixels.size(); i += 4) image_.pixels[i] = 255;
92
+ }
88
93
  generation_++;
89
94
  }
90
95
 
96
+ void Canvas2D::set_alpha(bool alpha) {
97
+ if (alpha_ == alpha) return;
98
+ alpha_ = alpha;
99
+ resize(image_.width, image_.height);
100
+ }
101
+
91
102
  void Canvas2D::save() { stack_.push_back(state_); }
92
103
 
93
104
  void Canvas2D::restore() {
@@ -214,7 +225,8 @@ void Canvas2D::ellipse(float cx, float cy, float rx, float ry, float rotation, f
214
225
  append_path_points(points);
215
226
  }
216
227
 
217
- void Canvas2D::round_rect(float x, float y, float width, float height, float radius) {
228
+ void Canvas2D::round_rect(float x, float y, float width, float height, float radius_tl,
229
+ float radius_tr, float radius_br, float radius_bl) {
218
230
  if (width < 0.0f) {
219
231
  x += width;
220
232
  width = -width;
@@ -223,18 +235,25 @@ void Canvas2D::round_rect(float x, float y, float width, float height, float rad
223
235
  y += height;
224
236
  height = -height;
225
237
  }
226
- float maxRadius = std::min(width, height) * 0.5f;
227
- if (radius < 0.0f) radius = 0.0f;
228
- if (radius > maxRadius) radius = maxRadius;
229
- move_to(x + radius, y);
230
- line_to(x + width - radius, y);
231
- quadratic_curve_to(x + width, y, x + width, y + radius);
232
- line_to(x + width, y + height - radius);
233
- quadratic_curve_to(x + width, y + height, x + width - radius, y + height);
234
- line_to(x + radius, y + height);
235
- quadratic_curve_to(x, y + height, x, y + height - radius);
236
- line_to(x, y + radius);
237
- quadratic_curve_to(x, y, x + radius, y);
238
+ const float maxRadius = std::min(width, height) * 0.5f;
239
+ float radii[4] = {radius_tl, radius_tr, radius_br, radius_bl};
240
+ for (float &radius : radii) {
241
+ if (radius < 0.0f) radius = 0.0f;
242
+ if (radius > maxRadius) radius = maxRadius;
243
+ }
244
+ const float tl = radii[0];
245
+ const float tr = radii[1];
246
+ const float br = radii[2];
247
+ const float bl = radii[3];
248
+ move_to(x + tl, y);
249
+ line_to(x + width - tr, y);
250
+ quadratic_curve_to(x + width, y, x + width, y + tr);
251
+ line_to(x + width, y + height - br);
252
+ quadratic_curve_to(x + width, y + height, x + width - br, y + height);
253
+ line_to(x + bl, y + height);
254
+ quadratic_curve_to(x, y + height, x, y + height - bl);
255
+ line_to(x, y + tl);
256
+ quadratic_curve_to(x, y, x + tl, y);
238
257
  close_path();
239
258
  }
240
259
 
@@ -274,9 +293,14 @@ void Canvas2D::to_device(float x, float y, float *outX, float *outY) const {
274
293
  }
275
294
 
276
295
  std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
296
+ return device_subpaths_of(path_);
297
+ }
298
+
299
+ std::vector<CanvasSubpath> Canvas2D::device_subpaths_of(
300
+ const std::vector<CanvasSubpath> &paths) const {
277
301
  std::vector<CanvasSubpath> result;
278
- result.reserve(path_.size());
279
- for (const CanvasSubpath &subpath : path_) {
302
+ result.reserve(paths.size());
303
+ for (const CanvasSubpath &subpath : paths) {
280
304
  CanvasSubpath device;
281
305
  device.reserve(subpath.size());
282
306
  for (const CanvasPoint &point : subpath) {
@@ -289,23 +313,25 @@ std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
289
313
  return result;
290
314
  }
291
315
 
292
- CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, const float color[4]) const {
316
+ CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, uint64_t pattern_id,
317
+ const float color[4]) const {
293
318
  CanvasPaint paint;
294
319
  paint.color = color;
295
320
  paint.gradient = gradient_id == 0 ? nullptr : find_gradient(gradient_id);
321
+ paint.pattern = pattern_id == 0 ? nullptr : find_pattern(pattern_id);
296
322
  paint.inverse = state_.transform.inverted();
297
323
  return paint;
298
324
  }
299
325
 
300
326
  CanvasPaint Canvas2D::fill_paint() const {
301
- return make_paint(state_.fill_gradient, state_.fill_color);
327
+ return make_paint(state_.fill_gradient, state_.fill_pattern, state_.fill_color);
302
328
  }
303
329
 
304
330
  CanvasPaint Canvas2D::stroke_paint() const {
305
- return make_paint(state_.stroke_gradient, state_.stroke_color);
331
+ return make_paint(state_.stroke_gradient, state_.stroke_pattern, state_.stroke_color);
306
332
  }
307
333
 
308
- void Canvas2D::fill() {
334
+ void Canvas2D::fill(bool even_odd) {
309
335
  std::vector<CanvasSubpath> subpaths = device_subpaths();
310
336
  std::vector<CanvasSubpath> loops;
311
337
  for (CanvasSubpath &subpath : subpaths) {
@@ -314,7 +340,20 @@ void Canvas2D::fill() {
314
340
  }
315
341
  if (loops.empty()) return;
316
342
  canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
317
- state_.global_alpha);
343
+ state_.global_alpha, even_odd);
344
+ generation_++;
345
+ }
346
+
347
+ void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd) {
348
+ std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
349
+ std::vector<CanvasSubpath> loops;
350
+ for (CanvasSubpath &subpath : subpaths) {
351
+ if (subpath.size() < 3) continue;
352
+ loops.push_back(std::move(subpath));
353
+ }
354
+ if (loops.empty()) return;
355
+ canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
356
+ state_.global_alpha, even_odd);
318
357
  generation_++;
319
358
  }
320
359
 
@@ -330,6 +369,18 @@ void Canvas2D::stroke() {
330
369
  generation_++;
331
370
  }
332
371
 
372
+ void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
373
+ std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
374
+ const CanvasPaint paint = stroke_paint();
375
+ for (const CanvasSubpath &subpath : subpaths) {
376
+ if (subpath.size() < 2) continue;
377
+ bool closed = subpath.size() >= 3 && subpath.front().x == subpath.back().x &&
378
+ subpath.front().y == subpath.back().y;
379
+ stroke_subpath(subpath, closed, paint);
380
+ }
381
+ generation_++;
382
+ }
383
+
333
384
  void Canvas2D::clear_rect(float x, float y, float width, float height) {
334
385
  float x0, y0, x1, y1;
335
386
  to_device(x, y, &x0, &y0);
@@ -378,7 +429,7 @@ float Canvas2D::measure_text(const std::string &text) const {
378
429
  }
379
430
 
380
431
  void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
381
- bool stroke) {
432
+ bool stroke, float max_width) {
382
433
  Font *font = xt_text_resolve(state_.font);
383
434
  if (font == nullptr) return;
384
435
  std::vector<ShapedGlyph> glyphs;
@@ -386,11 +437,18 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
386
437
  float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
387
438
  xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
388
439
 
440
+ /* The optional `maxWidth` condenses the run horizontally instead of clipping
441
+ * it: advances and glyph bitmaps are squeezed by the same factor. */
442
+ float horizontal = 1.0f;
443
+ if (max_width > 0.0f && width > max_width && width > 0.0001f) {
444
+ horizontal = max_width / width;
445
+ }
446
+
389
447
  float originX = x;
390
448
  if (state_.text_align == 1) {
391
- originX -= width * 0.5f;
449
+ originX -= width * horizontal * 0.5f;
392
450
  } else if (state_.text_align == 2) {
393
- originX -= width;
451
+ originX -= width * horizontal;
394
452
  }
395
453
  float originY = y;
396
454
  switch (state_.text_baseline) {
@@ -419,7 +477,7 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
419
477
 
420
478
  float penX = 0.0f;
421
479
  for (const ShapedGlyph &glyph : glyphs) {
422
- float userX = originX + penX + glyph.x_offset;
480
+ float userX = originX + (penX + glyph.x_offset) * horizontal;
423
481
  float userY = originY + glyph.y_offset;
424
482
  float deviceX = 0.0f, deviceY = 0.0f;
425
483
  to_device(userX, userY, &deviceX, &deviceY);
@@ -431,8 +489,8 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
431
489
  GlyphImage image;
432
490
  if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
433
491
  canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
434
- image.height, image.left, image.top, deviceX, deviceY, paint,
435
- state_.global_alpha);
492
+ image.height, image.left, image.top, deviceX, deviceY, horizontal,
493
+ paint, state_.global_alpha);
436
494
  }
437
495
  penX += glyph.x_advance;
438
496
  }
@@ -440,12 +498,12 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
440
498
  generation_++;
441
499
  }
442
500
 
443
- void Canvas2D::fill_text(const std::string &text, float x, float y) {
444
- paint_text(text, x, y, fill_paint(), false);
501
+ void Canvas2D::fill_text(const std::string &text, float x, float y, float max_width) {
502
+ paint_text(text, x, y, fill_paint(), false, max_width);
445
503
  }
446
504
 
447
- void Canvas2D::stroke_text(const std::string &text, float x, float y) {
448
- paint_text(text, x, y, stroke_paint(), true);
505
+ void Canvas2D::stroke_text(const std::string &text, float x, float y, float max_width) {
506
+ paint_text(text, x, y, stroke_paint(), true, max_width);
449
507
  }
450
508
 
451
509
  void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,