xbintsc 0.5.36 → 0.5.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/src/extensions/gui/index.js +12 -0
  5. package/dist/src/extensions/gui/index.js.map +1 -1
  6. package/dist/tests/e2e/gui-browser.test.js +65 -0
  7. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  8. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  9. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  10. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  11. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-dom.test.js +215 -0
  13. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  14. package/dist/tests/e2e/gui-events.test.js +112 -0
  15. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  16. package/doc/gui/api.md +38 -9
  17. package/doc/gui/canvas.md +49 -18
  18. package/doc/gui/dom.md +51 -4
  19. package/doc/zh-CN/gui/api.md +33 -4
  20. package/doc/zh-CN/gui/canvas.md +44 -19
  21. package/doc/zh-CN/gui/dom.md +45 -3
  22. package/package.json +7 -7
  23. package/runtime/ext_gui/browser.cpp +34 -49
  24. package/runtime/ext_gui/browser_abort.cpp +249 -0
  25. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  26. package/runtime/ext_gui/browser_media.cpp +176 -0
  27. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  28. package/runtime/ext_gui/canvas.cpp +88 -30
  29. package/runtime/ext_gui/canvas.h +74 -9
  30. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  31. package/runtime/ext_gui/canvas_internal.h +8 -5
  32. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  33. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  34. package/runtime/ext_gui/canvas_style.cpp +4 -2
  35. package/runtime/ext_gui/dom.h +4 -0
  36. package/runtime/ext_gui/dom_api.cpp +1 -0
  37. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  38. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  39. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  40. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  41. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  42. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  43. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  44. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  45. package/runtime/ext_gui/dom_api_internal.h +55 -2
  46. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  47. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  48. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  49. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  50. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  51. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  52. package/runtime/ext_gui/gui.cpp +59 -5
  53. package/runtime/ext_gui/gui_engine.h +47 -0
  54. package/runtime/ext_gui/gui_events.cpp +80 -9
  55. package/runtime/ext_gui/listener_options.h +43 -0
  56. package/runtime/ext_gui/paint.cpp +28 -0
  57. package/runtime/ext_gui/paint.h +4 -0
  58. package/runtime/ext_gui/window.cpp +14 -65
  59. package/runtime/ext_gui/window_events.cpp +125 -0
  60. package/runtime/rt.h +26 -0
  61. package/runtime/rt_internal.h +4 -0
  62. package/runtime/xt_builtins.c +0 -21
  63. package/runtime/xt_containers/access.inc +94 -2
  64. package/runtime/xt_containers/objects.inc +29 -31
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/index.ts +12 -0
@@ -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,
@@ -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