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
@@ -91,15 +91,41 @@ class CanvasGradient {
91
91
  std::vector<Stop> stops_;
92
92
  };
93
93
 
94
- /** What a fill or stroke paints with: a solid colour, or a gradient sampled
95
- * through the inverse of the current transform. `color` points into the
96
- * canvas state and stays valid for the duration of one draw call. */
94
+ class CanvasGradient;
95
+ class CanvasPattern;
96
+
97
+ /** What a fill or stroke paints with: a solid colour, a gradient sampled through
98
+ * the inverse of the current transform, or a repeating image pattern. `color`
99
+ * points into the canvas state and stays valid for one draw call. */
97
100
  struct CanvasPaint {
98
101
  const float *color = nullptr;
99
102
  const CanvasGradient *gradient = nullptr;
103
+ const CanvasPattern *pattern = nullptr;
100
104
  CanvasTransform inverse;
101
105
  };
102
106
 
107
+ /**
108
+ * A `CanvasPattern`: an image tiled in user space with one of the four
109
+ * repetition modes. Owned by the canvas that created it, like a gradient, so a
110
+ * stale handle resolves to no pattern instead of a dangling pointer.
111
+ */
112
+ class CanvasPattern {
113
+ public:
114
+ enum class Repeat { Both, X, Y, None };
115
+
116
+ CanvasPattern(uint64_t id, const Image &image, Repeat repeat);
117
+ uint64_t id() const { return id_; }
118
+ Repeat repeat() const { return repeat_; }
119
+ /** Pattern colour at a user-space point; false when the point falls outside a
120
+ * non-repeating pattern (the caller then paints nothing). */
121
+ bool color_at(float x, float y, float out[4]) const;
122
+
123
+ private:
124
+ uint64_t id_ = 0;
125
+ Image image_;
126
+ Repeat repeat_ = Repeat::Both;
127
+ };
128
+
103
129
  class Canvas2D {
104
130
  public:
105
131
  Canvas2D(int width, int height);
@@ -114,6 +140,10 @@ class Canvas2D {
114
140
  /** Resize the bitmap, discarding its contents (the DOM `canvas.width = …`
115
141
  * behaviour). */
116
142
  void resize(int width, int height);
143
+ /** `getContextAttributes().alpha`: an opaque canvas clears to opaque black
144
+ * instead of transparent, as the WebGL/2D context attribute specifies. */
145
+ void set_alpha(bool alpha);
146
+ bool alpha() const { return alpha_; }
117
147
 
118
148
  /* -- state ------------------------------------------------------------- */
119
149
  void save();
@@ -138,6 +168,11 @@ class Canvas2D {
138
168
  void set_stroke_gradient(uint64_t id);
139
169
  uint64_t fill_gradient_id() const { return state_.fill_gradient; }
140
170
  uint64_t stroke_gradient_id() const { return state_.stroke_gradient; }
171
+ /** Use a pattern created by this canvas (`0` restores the solid colour). */
172
+ void set_fill_pattern(uint64_t id);
173
+ void set_stroke_pattern(uint64_t id);
174
+ uint64_t fill_pattern_id() const { return state_.fill_pattern; }
175
+ uint64_t stroke_pattern_id() const { return state_.stroke_pattern; }
141
176
  void set_line_width(float width) { state_.line_width = width > 0.0f ? width : 0.0f; }
142
177
  float line_width() const { return state_.line_width; }
143
178
  void set_line_cap(const std::string &value);
@@ -165,20 +200,32 @@ class Canvas2D {
165
200
  float r1);
166
201
  CanvasGradient *find_gradient(uint64_t id) const;
167
202
 
203
+ /* -- patterns ---------------------------------------------------------- */
204
+ /** Create a tiled pattern from `image`; the canvas owns it. */
205
+ CanvasPattern *create_pattern(const Image &image, CanvasPattern::Repeat repeat);
206
+ CanvasPattern *find_pattern(uint64_t id) const;
207
+
168
208
  /* -- paths ------------------------------------------------------------- */
169
209
  void begin_path();
170
210
  void close_path();
171
211
  void move_to(float x, float y);
172
212
  void line_to(float x, float y);
173
213
  void rect(float x, float y, float width, float height);
174
- void round_rect(float x, float y, float width, float height, float radius);
214
+ /** Four corner radii, clockwise from the top-left (CSS/`roundRect` order). */
215
+ void round_rect(float x, float y, float width, float height, float radius_tl, float radius_tr,
216
+ float radius_br, float radius_bl);
175
217
  void arc(float cx, float cy, float radius, float start, float end, bool counterclockwise);
176
218
  void ellipse(float cx, float cy, float rx, float ry, float rotation, float start, float end,
177
219
  bool counterclockwise);
178
220
  void quadratic_curve_to(float cpx, float cpy, float x, float y);
179
221
  void bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y);
180
- void fill();
222
+ /** Fill the current path (`even_odd` selects the even-odd rule). */
223
+ void fill(bool even_odd = false);
181
224
  void stroke();
225
+ /** Fill/stroke a path supplied by the caller (`Path2D`); the points are user
226
+ * space and are transformed with the current state, like the current path. */
227
+ void fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd);
228
+ void stroke_paths(const std::vector<CanvasSubpath> &paths);
182
229
 
183
230
  /* -- rectangles -------------------------------------------------------- */
184
231
  void clear_rect(float x, float y, float width, float height);
@@ -187,8 +234,11 @@ class Canvas2D {
187
234
 
188
235
  /* -- text -------------------------------------------------------------- */
189
236
  float measure_text(const std::string &text) const;
190
- void fill_text(const std::string &text, float x, float y);
191
- void stroke_text(const std::string &text, float x, float y);
237
+ /** Draw text; a positive `max_width` condenses it horizontally so the run is
238
+ * at most that wide, as the canvas `fillText`/`strokeText` fourth argument
239
+ * requires. */
240
+ void fill_text(const std::string &text, float x, float y, float max_width = 0.0f);
241
+ void stroke_text(const std::string &text, float x, float y, float max_width = 0.0f);
192
242
 
193
243
  /* -- images ------------------------------------------------------------ */
194
244
  /** Draw `source` scaled into the destination rectangle, sampling the source
@@ -196,6 +246,15 @@ class Canvas2D {
196
246
  void draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx, float sy,
197
247
  float sw, float sh);
198
248
 
249
+ /* -- pixel writes and export ------------------------------------------- */
250
+ /** `putImageData`: copy RGBA bytes straight into the bitmap (no transform,
251
+ * blending or globalAlpha), clipped to the canvas and the optional dirty
252
+ * rectangle. */
253
+ void set_image_data(const std::vector<unsigned char> &rgba, int data_width, int data_height,
254
+ int dx, int dy, int dirty_x, int dirty_y, int dirty_width, int dirty_height);
255
+ /** Encode the bitmap as a PNG (used by `toDataURL`/`toBlob`). */
256
+ bool encode_png(std::vector<unsigned char> *out) const;
257
+
199
258
  /* -- test hook --------------------------------------------------------- */
200
259
  /** Copy an `(x, y, w, h)` region to `out` (RGBA8, row-major). Out-of-bounds
201
260
  * pixels are transparent. Returns false when the region is empty. */
@@ -223,6 +282,8 @@ class Canvas2D {
223
282
  float stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
224
283
  uint64_t fill_gradient = 0;
225
284
  uint64_t stroke_gradient = 0;
285
+ uint64_t fill_pattern = 0;
286
+ uint64_t stroke_pattern = 0;
226
287
  std::string line_cap_string = "butt";
227
288
  std::string line_join_string = "miter";
228
289
  };
@@ -231,6 +292,8 @@ class Canvas2D {
231
292
  void to_device(float x, float y, float *outX, float *outY) const;
232
293
  /** The current path transformed into device space. */
233
294
  std::vector<CanvasSubpath> device_subpaths() const;
295
+ /** Any user-space path transformed into device space. */
296
+ std::vector<CanvasSubpath> device_subpaths_of(const std::vector<CanvasSubpath> &paths) const;
234
297
  /** Add one device-space polygon loop to `out`. */
235
298
  void flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
236
299
  CanvasSubpath *out) const;
@@ -240,7 +303,7 @@ class Canvas2D {
240
303
  /** Paint descriptors for the current state. */
241
304
  CanvasPaint fill_paint() const;
242
305
  CanvasPaint stroke_paint() const;
243
- CanvasPaint make_paint(uint64_t gradient_id, const float color[4]) const;
306
+ CanvasPaint make_paint(uint64_t gradient_id, uint64_t pattern_id, const float color[4]) const;
244
307
  void stroke_subpath(const CanvasSubpath &points, bool closed, const CanvasPaint &paint);
245
308
  /** Stroke one polyline with caps and joins; `half` is half the device-space
246
309
  * line width. Used by `stroke_subpath` for both solid and dashed paths. */
@@ -250,15 +313,17 @@ class Canvas2D {
250
313
  void dash_polyline(const CanvasSubpath &points, bool closed,
251
314
  std::vector<CanvasSubpath> *out) const;
252
315
  void paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
253
- bool stroke);
316
+ bool stroke, float max_width);
254
317
 
255
318
  Image image_;
319
+ bool alpha_ = true;
256
320
  uint64_t id_ = 0;
257
321
  uint64_t generation_ = 0;
258
322
  State state_;
259
323
  std::vector<State> stack_;
260
324
  std::vector<CanvasSubpath> path_;
261
325
  std::unordered_map<uint64_t, std::unique_ptr<CanvasGradient>> gradients_;
326
+ std::unordered_map<uint64_t, std::unique_ptr<CanvasPattern>> patterns_;
262
327
  };
263
328
 
264
329
  /** The canvas with the given process-wide id, or null. Used by the renderer. */
@@ -0,0 +1,143 @@
1
+ /*
2
+ * xbintsc GUI engine — `putImageData` and PNG export for `<canvas>`.
3
+ *
4
+ * `putImageData` writes RGBA bytes straight into the bitmap: it ignores the
5
+ * transform, `globalAlpha` and compositing, exactly as the canvas API requires.
6
+ * PNG export writes the bitmap back out for `toDataURL`/`toBlob`; it uses a
7
+ * zlib "stored" stream (no compression) so the engine needs no deflate
8
+ * dependency, which is enough for the small UI snapshots pages take.
9
+ */
10
+
11
+ #include "canvas.h"
12
+
13
+ #include <algorithm>
14
+ #include <cstdint>
15
+ #include <cstring>
16
+ #include <vector>
17
+
18
+ namespace xtgui {
19
+
20
+ void Canvas2D::set_image_data(const std::vector<unsigned char> &rgba, int data_width,
21
+ int data_height, int dx, int dy, int dirty_x, int dirty_y,
22
+ int dirty_width, int dirty_height) {
23
+ if (data_width <= 0 || data_height <= 0) return;
24
+ if (rgba.size() < (size_t)data_width * (size_t)data_height * 4) return;
25
+ int source_x0 = 0;
26
+ int source_y0 = 0;
27
+ int source_x1 = data_width;
28
+ int source_y1 = data_height;
29
+ /* The optional dirty rectangle is in image coordinates. */
30
+ if (dirty_width > 0 && dirty_height > 0) {
31
+ source_x0 = std::max(0, dirty_x);
32
+ source_y0 = std::max(0, dirty_y);
33
+ source_x1 = std::min(data_width, dirty_x + dirty_width);
34
+ source_y1 = std::min(data_height, dirty_y + dirty_height);
35
+ }
36
+ if (source_x1 <= source_x0 || source_y1 <= source_y0) return;
37
+ int dest_x0 = std::max(0, dx + source_x0);
38
+ int dest_y0 = std::max(0, dy + source_y0);
39
+ int dest_x1 = std::min(image_.width, dx + source_x1);
40
+ int dest_y1 = std::min(image_.height, dy + source_y1);
41
+ for (int py = dest_y0; py < dest_y1; py++) {
42
+ int source_row = py - dy;
43
+ const unsigned char *src =
44
+ rgba.data() + ((size_t)source_row * (size_t)data_width + (size_t)(dest_x0 - dx)) * 4;
45
+ unsigned char *dst =
46
+ image_.pixels.data() + ((size_t)py * (size_t)image_.width + (size_t)dest_x0) * 4;
47
+ memcpy(dst, src, (size_t)(dest_x1 - dest_x0) * 4);
48
+ }
49
+ generation_++;
50
+ }
51
+
52
+ namespace {
53
+
54
+ uint32_t crc32_update(uint32_t crc, const unsigned char *data, size_t length) {
55
+ crc = ~crc;
56
+ for (size_t i = 0; i < length; i++) {
57
+ crc ^= data[i];
58
+ for (int bit = 0; bit < 8; bit++) {
59
+ crc = (crc >> 1) ^ (0xEDB88320u & (uint32_t)(-(int32_t)(crc & 1u)));
60
+ }
61
+ }
62
+ return ~crc;
63
+ }
64
+
65
+ void append_u32(std::vector<unsigned char> *out, uint32_t value) {
66
+ out->push_back((unsigned char)(value >> 24));
67
+ out->push_back((unsigned char)(value >> 16));
68
+ out->push_back((unsigned char)(value >> 8));
69
+ out->push_back((unsigned char)value);
70
+ }
71
+
72
+ void append_chunk(std::vector<unsigned char> *out, const char type[4],
73
+ const std::vector<unsigned char> &data) {
74
+ append_u32(out, (uint32_t)data.size());
75
+ size_t crc_start = out->size();
76
+ out->insert(out->end(), type, type + 4);
77
+ out->insert(out->end(), data.begin(), data.end());
78
+ uint32_t crc = crc32_update(0, out->data() + crc_start, out->size() - crc_start);
79
+ append_u32(out, crc);
80
+ }
81
+
82
+ uint32_t adler32(const std::vector<unsigned char> &data) {
83
+ uint32_t a = 1;
84
+ uint32_t b = 0;
85
+ for (unsigned char byte : data) {
86
+ a = (a + byte) % 65521u;
87
+ b = (b + a) % 65521u;
88
+ }
89
+ return (b << 16) | a;
90
+ }
91
+
92
+ /** zlib stream with uncompressed (stored) deflate blocks. */
93
+ std::vector<unsigned char> zlib_store(const std::vector<unsigned char> &data) {
94
+ std::vector<unsigned char> out;
95
+ out.push_back(0x78);
96
+ out.push_back(0x01);
97
+ size_t offset = 0;
98
+ do {
99
+ size_t chunk = std::min<size_t>(65535, data.size() - offset);
100
+ bool last = offset + chunk >= data.size();
101
+ out.push_back(last ? 1 : 0);
102
+ out.push_back((unsigned char)(chunk & 0xFF));
103
+ out.push_back((unsigned char)(chunk >> 8));
104
+ out.push_back((unsigned char)(~chunk & 0xFF));
105
+ out.push_back((unsigned char)((~chunk >> 8) & 0xFF));
106
+ out.insert(out.end(), data.begin() + offset, data.begin() + offset + chunk);
107
+ offset += chunk;
108
+ } while (offset < data.size());
109
+ append_u32(&out, adler32(data));
110
+ return out;
111
+ }
112
+
113
+ } // namespace
114
+
115
+ bool Canvas2D::encode_png(std::vector<unsigned char> *out) const {
116
+ if (out == nullptr || image_.width <= 0 || image_.height <= 0) return false;
117
+ out->clear();
118
+ static const unsigned char signature[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
119
+ out->insert(out->end(), signature, signature + 8);
120
+
121
+ std::vector<unsigned char> header;
122
+ append_u32(&header, (uint32_t)image_.width);
123
+ append_u32(&header, (uint32_t)image_.height);
124
+ header.push_back(8); // bit depth
125
+ header.push_back(6); // colour type: RGBA
126
+ header.push_back(0); // compression
127
+ header.push_back(0); // filter
128
+ header.push_back(0); // interlace
129
+ append_chunk(out, "IHDR", header);
130
+
131
+ std::vector<unsigned char> raw;
132
+ raw.reserve(((size_t)image_.width * 4 + 1) * (size_t)image_.height);
133
+ for (int y = 0; y < image_.height; y++) {
134
+ raw.push_back(0); // filter type: none
135
+ const unsigned char *row = image_.pixels.data() + (size_t)y * (size_t)image_.width * 4;
136
+ raw.insert(raw.end(), row, row + (size_t)image_.width * 4);
137
+ }
138
+ append_chunk(out, "IDAT", zlib_store(raw));
139
+ append_chunk(out, "IEND", std::vector<unsigned char>());
140
+ return true;
141
+ }
142
+
143
+ } // namespace xtgui
@@ -24,13 +24,14 @@ void blend_pixel(unsigned char *pixel, const float color[4], float coverage, flo
24
24
  /** Fill one closed polygon loop (already in device space) with `paint`,
25
25
  * anti-aliased, blending source-over. */
26
26
  void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
27
- const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha);
27
+ const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha,
28
+ bool even_odd = false);
28
29
 
29
30
  /** Fill several loops as one path with the nonzero winding rule (so a contour
30
- * inside another punches a hole). */
31
+ * inside another punches a hole), or the even-odd rule. */
31
32
  void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
32
33
  const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
33
- float alpha);
34
+ float alpha, bool even_odd = false);
34
35
 
35
36
  /** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
36
37
  * `fillRect`/`clearRect` fast path when the transform preserves axis
@@ -58,10 +59,12 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
58
59
  float sy, float sw, float sh, const CanvasTransform &transform,
59
60
  float alpha);
60
61
 
61
- /** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`. */
62
+ /** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`.
63
+ * `scale_x` < 1 condenses the bitmap horizontally (`fillText`'s `maxWidth`). */
62
64
  void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
63
65
  const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
64
- int top, float x, float baselineY, const CanvasPaint &paint, float alpha);
66
+ int top, float x, float baselineY, float scale_x, const CanvasPaint &paint,
67
+ float alpha);
65
68
 
66
69
  } // namespace canvas_detail
67
70
  } // namespace xtgui
@@ -0,0 +1,90 @@
1
+ /*
2
+ * xbintsc GUI engine — `CanvasPattern`.
3
+ *
4
+ * A pattern is an image tiled in user space with the four repetition modes.
5
+ * Like a gradient it is owned by the canvas that created it (and addressed by
6
+ * id), so a stale context handle resolves to "no pattern" instead of a dangling
7
+ * pointer. The rasteriser samples it through the inverse transform, which keeps
8
+ * the tiles attached to the shape when it is translated, scaled or rotated.
9
+ */
10
+
11
+ #include "canvas.h"
12
+
13
+ #include <atomic>
14
+ #include <cmath>
15
+ #include <memory>
16
+
17
+ #include "canvas_internal.h"
18
+
19
+ namespace xtgui {
20
+
21
+ namespace {
22
+
23
+ std::atomic<uint64_t> g_next_pattern_id{1};
24
+
25
+ } // namespace
26
+
27
+ CanvasPattern::CanvasPattern(uint64_t id, const Image &image, Repeat repeat)
28
+ : id_(id), image_(image), repeat_(repeat) {}
29
+
30
+ bool CanvasPattern::color_at(float x, float y, float out[4]) const {
31
+ const int width = image_.width;
32
+ const int height = image_.height;
33
+ if (width <= 0 || height <= 0) return false;
34
+ /* Tile in user space: wrap the coordinate into one tile for the enabled
35
+ * axes, and reject points outside the tile for the disabled ones. */
36
+ bool repeatX = repeat_ == Repeat::Both || repeat_ == Repeat::X;
37
+ bool repeatY = repeat_ == Repeat::Both || repeat_ == Repeat::Y;
38
+ float u = x;
39
+ float v = y;
40
+ if (repeatX) {
41
+ u = x - std::floor(x / (float)width) * (float)width;
42
+ if (u < 0.0f) u += (float)width;
43
+ } else if (x < 0.0f || x >= (float)width) {
44
+ return false;
45
+ }
46
+ if (repeatY) {
47
+ v = y - std::floor(y / (float)height) * (float)height;
48
+ if (v < 0.0f) v += (float)height;
49
+ } else if (y < 0.0f || y >= (float)height) {
50
+ return false;
51
+ }
52
+ int px = (int)u;
53
+ int py = (int)v;
54
+ if (px < 0) px = 0;
55
+ if (py < 0) py = 0;
56
+ if (px >= width) px = width - 1;
57
+ if (py >= height) py = height - 1;
58
+ const unsigned char *texel =
59
+ image_.pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
60
+ out[0] = (float)texel[0] / 255.0f;
61
+ out[1] = (float)texel[1] / 255.0f;
62
+ out[2] = (float)texel[2] / 255.0f;
63
+ out[3] = (float)texel[3] / 255.0f;
64
+ return true;
65
+ }
66
+
67
+ CanvasPattern *Canvas2D::create_pattern(const Image &image, CanvasPattern::Repeat repeat) {
68
+ auto pattern =
69
+ std::make_unique<CanvasPattern>(g_next_pattern_id.fetch_add(1), image, repeat);
70
+ CanvasPattern *raw = pattern.get();
71
+ patterns_[raw->id()] = std::move(pattern);
72
+ return raw;
73
+ }
74
+
75
+ CanvasPattern *Canvas2D::find_pattern(uint64_t id) const {
76
+ auto found = patterns_.find(id);
77
+ return found == patterns_.end() ? nullptr : found->second.get();
78
+ }
79
+
80
+ void Canvas2D::set_fill_pattern(uint64_t id) {
81
+ state_.fill_pattern = find_pattern(id) != nullptr ? id : 0;
82
+ if (state_.fill_pattern != 0) state_.fill_gradient = 0;
83
+ }
84
+
85
+ void Canvas2D::set_stroke_pattern(uint64_t id) {
86
+ state_.stroke_pattern = find_pattern(id) != nullptr ? id : 0;
87
+ if (state_.stroke_pattern != 0) state_.stroke_gradient = 0;
88
+ }
89
+
90
+ } // namespace xtgui
@@ -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