xbintsc 0.3.69 → 0.3.79

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 (99) hide show
  1. package/README.md +59 -4
  2. package/README.zh-CN.md +52 -2
  3. package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
  4. package/dist/tests/e2e/gui-advanced.test.js +261 -0
  5. package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
  6. package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
  7. package/dist/tests/e2e/gui-canvas.test.js +233 -0
  8. package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
  9. package/dist/tests/e2e/gui-example.test.d.ts +1 -1
  10. package/dist/tests/e2e/gui-example.test.js +2 -2
  11. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  12. package/dist/tests/e2e/gui-helpers.js +5 -0
  13. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  14. package/dist/tests/e2e/gui-layout.test.js +73 -0
  15. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  16. package/dist/tests/e2e/gui-media.test.d.ts +8 -0
  17. package/dist/tests/e2e/gui-media.test.js +119 -0
  18. package/dist/tests/e2e/gui-media.test.js.map +1 -0
  19. package/dist/tests/e2e/gui-paint.test.js +40 -0
  20. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  21. package/doc/ai/extensions.md +16 -3
  22. package/doc/ai/zh-CN/extensions.md +10 -3
  23. package/doc/gui/animation.md +46 -0
  24. package/doc/gui/api.md +92 -0
  25. package/doc/gui/build.md +91 -0
  26. package/doc/gui/canvas.md +98 -0
  27. package/doc/gui/dom.md +123 -0
  28. package/doc/gui/input.md +22 -0
  29. package/doc/gui/known-issues.md +66 -0
  30. package/doc/gui/layout.md +72 -0
  31. package/doc/gui/media.md +87 -0
  32. package/doc/gui/rendering.md +98 -0
  33. package/doc/gui/runtime.md +88 -0
  34. package/doc/gui-scripts.md +7 -3
  35. package/doc/gui.md +80 -612
  36. package/doc/implemented.md +1 -0
  37. package/doc/zh-CN/gui/animation.md +46 -0
  38. package/doc/zh-CN/gui/api.md +88 -0
  39. package/doc/zh-CN/gui/build.md +84 -0
  40. package/doc/zh-CN/gui/canvas.md +80 -0
  41. package/doc/zh-CN/gui/dom.md +114 -0
  42. package/doc/zh-CN/gui/input.md +21 -0
  43. package/doc/zh-CN/gui/known-issues.md +50 -0
  44. package/doc/zh-CN/gui/layout.md +60 -0
  45. package/doc/zh-CN/gui/media.md +71 -0
  46. package/doc/zh-CN/gui/rendering.md +81 -0
  47. package/doc/zh-CN/gui/runtime.md +76 -0
  48. package/doc/zh-CN/gui-scripts.md +6 -2
  49. package/doc/zh-CN/gui.md +64 -532
  50. package/doc/zh-CN/implemented.md +1 -0
  51. package/llms.txt +15 -2
  52. package/package.json +1 -1
  53. package/runtime/ext_gui/canvas.cpp +580 -0
  54. package/runtime/ext_gui/canvas.h +170 -0
  55. package/runtime/ext_gui/canvas_internal.h +59 -0
  56. package/runtime/ext_gui/canvas_raster.cpp +254 -0
  57. package/runtime/ext_gui/document.cpp +24 -1
  58. package/runtime/ext_gui/document.h +11 -0
  59. package/runtime/ext_gui/dom_api.cpp +3 -0
  60. package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
  61. package/runtime/ext_gui/dom_api_internal.h +73 -0
  62. package/runtime/ext_gui/dom_api_media.cpp +274 -0
  63. package/runtime/ext_gui/dom_api_proto.cpp +80 -0
  64. package/runtime/ext_gui/gui.cpp +6 -0
  65. package/runtime/ext_gui/gui_canvas.cpp +95 -0
  66. package/runtime/ext_gui/gui_engine.h +49 -0
  67. package/runtime/ext_gui/gui_events.cpp +21 -5
  68. package/runtime/ext_gui/gui_media.cpp +193 -0
  69. package/runtime/ext_gui/image.cpp +31 -12
  70. package/runtime/ext_gui/image.h +20 -7
  71. package/runtime/ext_gui/layout.cpp +52 -5
  72. package/runtime/ext_gui/layout.h +33 -1
  73. package/runtime/ext_gui/layout_flex.cpp +263 -99
  74. package/runtime/ext_gui/layout_flow.cpp +85 -9
  75. package/runtime/ext_gui/layout_flow_internal.h +84 -5
  76. package/runtime/ext_gui/layout_inline.cpp +54 -37
  77. package/runtime/ext_gui/media.cpp +514 -0
  78. package/runtime/ext_gui/media.h +104 -0
  79. package/runtime/ext_gui/nanosvg.h +3278 -0
  80. package/runtime/ext_gui/nanosvgrast.h +1472 -0
  81. package/runtime/ext_gui/paint.cpp +123 -38
  82. package/runtime/ext_gui/paint.h +36 -6
  83. package/runtime/ext_gui/renderer.cpp +113 -10
  84. package/runtime/ext_gui/renderer.h +49 -9
  85. package/runtime/ext_gui/renderer_image.cpp +203 -77
  86. package/runtime/ext_gui/renderer_internal.h +6 -8
  87. package/runtime/ext_gui/renderer_text.cpp +11 -26
  88. package/runtime/ext_gui/style.h +11 -0
  89. package/runtime/ext_gui/style_print.cpp +11 -0
  90. package/runtime/ext_gui/style_ua.cpp +7 -0
  91. package/runtime/ext_gui/style_values.cpp +40 -1
  92. package/runtime/ext_gui/svg.cpp +182 -0
  93. package/runtime/ext_gui/svg.h +46 -0
  94. package/runtime/ext_gui/text.cpp +156 -25
  95. package/runtime/ext_gui/text.h +11 -2
  96. package/runtime/ext_gui/window.cpp +4 -1
  97. package/scripts/build-gui.ts +148 -6
  98. package/scripts/check-file-length.ts +5 -1
  99. package/scripts/install-gui-deps.sh +28 -0
@@ -0,0 +1,170 @@
1
+ /*
2
+ * xbintsc GUI engine — `<canvas>` 2D drawing.
3
+ *
4
+ * A `Canvas2D` owns one RGBA8 bitmap and the 2D drawing state (fill/stroke
5
+ * style, line width, transform, text settings). It is the engine-side object
6
+ * behind `canvas.getContext("2d")`; the DOM bindings live in
7
+ * `dom_api_canvas.cpp` and the pixels reach the screen through the same GPU
8
+ * texture path as images and video (`renderer_image.cpp`).
9
+ *
10
+ * The rasteriser is deliberately small and self-written (no Skia/Cairo): it
11
+ * fills rectangles, polygons and flattened arcs with sub-scanline coverage
12
+ * anti-aliasing, and blits FreeType glyph bitmaps for `fillText`. The pieces
13
+ * live in `canvas_raster.cpp`; this header only exposes the object model.
14
+ *
15
+ * Canvas pixels are *not* touched by the garbage collector: a canvas is owned
16
+ * by the window (keyed by its DOM node) and released with the document.
17
+ */
18
+ #ifndef XT_GUI_CANVAS_H
19
+ #define XT_GUI_CANVAS_H
20
+
21
+ #include <cstdint>
22
+ #include <string>
23
+ #include <vector>
24
+
25
+ #include "image.h"
26
+ #include "text.h"
27
+
28
+ namespace xtgui {
29
+
30
+ /** A 2D affine transform, laid out like the DOM `DOMMatrix` (a b c d e f). */
31
+ struct CanvasTransform {
32
+ float a = 1.0f, b = 0.0f, c = 0.0f, d = 1.0f, e = 0.0f, f = 0.0f;
33
+
34
+ void apply(float x, float y, float *outX, float *outY) const {
35
+ *outX = a * x + c * y + e;
36
+ *outY = b * x + d * y + f;
37
+ }
38
+ /** True when the transform keeps axis-aligned rectangles axis aligned (no
39
+ * rotation or skew), which lets the rasteriser use its fast rectangle path. */
40
+ bool axis_aligned() const { return b == 0.0f && c == 0.0f; }
41
+ /** Approximate uniform scale, used to size stroked lines and text. */
42
+ float scale() const;
43
+ };
44
+
45
+ /** A point in device (canvas) space. */
46
+ struct CanvasPoint {
47
+ float x = 0.0f;
48
+ float y = 0.0f;
49
+ };
50
+
51
+ /** One subpath of the current path (device space). */
52
+ using CanvasSubpath = std::vector<CanvasPoint>;
53
+
54
+ class Canvas2D {
55
+ public:
56
+ Canvas2D(int width, int height);
57
+ ~Canvas2D();
58
+
59
+ int width() const { return image_.width; }
60
+ int height() const { return image_.height; }
61
+ uint64_t id() const { return id_; }
62
+ /** Bumped by every draw; the renderer re-uploads when it changes. */
63
+ uint64_t generation() const { return generation_; }
64
+ const Image &image() const { return image_; }
65
+ /** Resize the bitmap, discarding its contents (the DOM `canvas.width = …`
66
+ * behaviour). */
67
+ void resize(int width, int height);
68
+
69
+ /* -- state ------------------------------------------------------------- */
70
+ void save();
71
+ void restore();
72
+
73
+ /* -- transform --------------------------------------------------------- */
74
+ void translate(float x, float y);
75
+ void rotate(float radians);
76
+ void scale(float sx, float sy);
77
+ void transform(float a, float b, float c, float d, float e, float f);
78
+ void set_transform(float a, float b, float c, float d, float e, float f);
79
+ void reset_transform() { state_.transform = CanvasTransform(); }
80
+
81
+ /* -- style ------------------------------------------------------------- */
82
+ void set_fill_style(const std::string &css);
83
+ const std::string &fill_style() const { return state_.fill_style; }
84
+ void set_stroke_style(const std::string &css);
85
+ const std::string &stroke_style() const { return state_.stroke_style; }
86
+ void set_line_width(float width) { state_.line_width = width > 0.0f ? width : 0.0f; }
87
+ float line_width() const { return state_.line_width; }
88
+ void set_global_alpha(float alpha);
89
+ float global_alpha() const { return state_.global_alpha; }
90
+ void set_font(const std::string &font);
91
+ const std::string &font() const { return state_.font_string; }
92
+ void set_text_align(const std::string &value);
93
+ const std::string &text_align() const { return state_.text_align_string; }
94
+ void set_text_baseline(const std::string &value);
95
+ const std::string &text_baseline() const { return state_.text_baseline_string; }
96
+
97
+ /* -- paths ------------------------------------------------------------- */
98
+ void begin_path();
99
+ void close_path();
100
+ void move_to(float x, float y);
101
+ void line_to(float x, float y);
102
+ void rect(float x, float y, float width, float height);
103
+ void arc(float cx, float cy, float radius, float start, float end, bool counterclockwise);
104
+ void quadratic_curve_to(float cpx, float cpy, float x, float y);
105
+ void bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y);
106
+ void fill();
107
+ void stroke();
108
+
109
+ /* -- rectangles -------------------------------------------------------- */
110
+ void clear_rect(float x, float y, float width, float height);
111
+ void fill_rect(float x, float y, float width, float height);
112
+ void stroke_rect(float x, float y, float width, float height);
113
+
114
+ /* -- text -------------------------------------------------------------- */
115
+ float measure_text(const std::string &text) const;
116
+ void fill_text(const std::string &text, float x, float y);
117
+ void stroke_text(const std::string &text, float x, float y);
118
+
119
+ /* -- images ------------------------------------------------------------ */
120
+ /** Draw `source` scaled into the destination rectangle, sampling the source
121
+ * rectangle `(sx, sy, sw, sh)`. `source` is RGBA8. */
122
+ void draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx, float sy,
123
+ float sw, float sh);
124
+
125
+ /* -- test hook --------------------------------------------------------- */
126
+ /** Copy an `(x, y, w, h)` region to `out` (RGBA8, row-major). Out-of-bounds
127
+ * pixels are transparent. Returns false when the region is empty. */
128
+ bool get_image_data(int x, int y, int width, int height, std::vector<unsigned char> *out) const;
129
+
130
+ private:
131
+ struct State {
132
+ CanvasTransform transform;
133
+ float line_width = 1.0f;
134
+ float global_alpha = 1.0f;
135
+ FontSpec font;
136
+ std::string font_string = "10px sans-serif";
137
+ int text_align = 0; // 0 start/left, 1 center, 2 right/end
138
+ int text_baseline = 0; // 0 alphabetic, 1 top, 2 middle, 3 bottom, 4 hanging, 5 ideographic
139
+ std::string text_align_string = "start";
140
+ std::string text_baseline_string = "alphabetic";
141
+ std::string fill_style = "#000000";
142
+ std::string stroke_style = "#000000";
143
+ float fill_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
144
+ float stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
145
+ };
146
+
147
+ /** Transform a user-space point into device space. */
148
+ void to_device(float x, float y, float *outX, float *outY) const;
149
+ /** The current path transformed into device space. */
150
+ std::vector<CanvasSubpath> device_subpaths() const;
151
+ /** Add one device-space polygon loop to `out`. */
152
+ void flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
153
+ CanvasSubpath *out) const;
154
+ void stroke_subpath(const CanvasSubpath &points, bool closed, const float color[4]);
155
+ void paint_text(const std::string &text, float x, float y, const float color[4], bool stroke);
156
+
157
+ Image image_;
158
+ uint64_t id_ = 0;
159
+ uint64_t generation_ = 0;
160
+ State state_;
161
+ std::vector<State> stack_;
162
+ std::vector<CanvasSubpath> path_;
163
+ };
164
+
165
+ /** The canvas with the given process-wide id, or null. Used by the renderer. */
166
+ Canvas2D *xt_canvas_by_id(uint64_t id);
167
+
168
+ } // namespace xtgui
169
+
170
+ #endif /* XT_GUI_CANVAS_H */
@@ -0,0 +1,59 @@
1
+ /*
2
+ * xbintsc GUI engine — canvas rasteriser internals.
3
+ *
4
+ * The public object model lives in `canvas.h`; these functions are the
5
+ * software rasteriser `canvas.cpp` calls. They are deliberately small,
6
+ * self-contained and free of SDL so they can be unit-tested by reasoning about
7
+ * pixels alone.
8
+ */
9
+ #ifndef XT_GUI_CANVAS_INTERNAL_H
10
+ #define XT_GUI_CANVAS_INTERNAL_H
11
+
12
+ #include <cstdint>
13
+ #include <vector>
14
+
15
+ #include "canvas.h"
16
+
17
+ namespace xtgui {
18
+ namespace canvas_detail {
19
+
20
+ /** Source-over blend `color` (0..1) with `coverage` (0..1) into one RGBA8
21
+ * pixel. `alpha` folds in `globalAlpha`. */
22
+ void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha);
23
+
24
+ /** Fill one closed polygon loop (already in device space) with `color`,
25
+ * anti-aliased, blending source-over. */
26
+ void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
27
+ const std::vector<CanvasPoint> &loop, const float color[4], float alpha);
28
+
29
+ /** Fill several loops as one path with the nonzero winding rule (so a contour
30
+ * inside another punches a hole). */
31
+ void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
32
+ const std::vector<std::vector<CanvasPoint>> &loops, const float color[4],
33
+ float alpha);
34
+
35
+ /** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
36
+ * `fillRect`/`clearRect` fast path when the transform preserves axis
37
+ * alignment. */
38
+ void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
39
+ float x1, float y1, const float color[4], float alpha);
40
+
41
+ /** Clear a device-space axis-aligned rectangle to transparent. */
42
+ void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
43
+ float x1, float y1);
44
+
45
+ /** Blit an RGBA8 source image into the destination rectangle using nearest
46
+ * sampling, source-over. Runs in device space (no transform). */
47
+ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
48
+ float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
49
+ float alpha);
50
+
51
+ /** Composite one grayscale glyph bitmap at `(x, baselineY)` with `color`. */
52
+ void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
53
+ const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
54
+ int top, float x, float baselineY, const float color[4], float alpha);
55
+
56
+ } // namespace canvas_detail
57
+ } // namespace xtgui
58
+
59
+ #endif /* XT_GUI_CANVAS_INTERNAL_H */
@@ -0,0 +1,254 @@
1
+ /*
2
+ * xbintsc GUI engine — the software rasteriser behind `<canvas>`.
3
+ *
4
+ * Everything here is CPU-side and backend-free. Polygons are filled with the
5
+ * nonzero winding rule and vertical sub-scanline sampling (4 samples per pixel
6
+ * row) plus analytic horizontal coverage, which gives cheap anti-aliasing
7
+ * without a full analytic rasteriser. Rectangles take a faster axis-aligned
8
+ * path, and glyphs are composited from their FreeType coverage masks.
9
+ */
10
+
11
+ #include "canvas_internal.h"
12
+
13
+ #include <algorithm>
14
+ #include <cmath>
15
+ #include <cstring>
16
+
17
+ namespace xtgui {
18
+ namespace canvas_detail {
19
+
20
+ namespace {
21
+
22
+ constexpr int kSubSamples = 4;
23
+
24
+ int clamp_byte(float value) {
25
+ int result = (int)(value + 0.5f);
26
+ if (result < 0) result = 0;
27
+ if (result > 255) result = 255;
28
+ return result;
29
+ }
30
+
31
+ /** One edge crossing on the current sub-scanline. */
32
+ struct Crossing {
33
+ float x;
34
+ int winding;
35
+ };
36
+
37
+ } // namespace
38
+
39
+ void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha) {
40
+ float sa = color[3] * coverage * alpha;
41
+ if (sa <= 0.0f) return;
42
+ if (sa > 1.0f) sa = 1.0f;
43
+ float da = pixel[3] / 255.0f;
44
+ float out_a = sa + da * (1.0f - sa);
45
+ if (out_a <= 0.0f) {
46
+ pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
47
+ return;
48
+ }
49
+ const float inv = 1.0f / out_a;
50
+ pixel[0] = (unsigned char)clamp_byte((color[0] * sa + (pixel[0] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
51
+ pixel[1] = (unsigned char)clamp_byte((color[1] * sa + (pixel[1] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
52
+ pixel[2] = (unsigned char)clamp_byte((color[2] * sa + (pixel[2] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
53
+ pixel[3] = (unsigned char)clamp_byte(out_a * 255.0f);
54
+ }
55
+
56
+ /** Add the horizontal span `[xa, xb)` into a bbox-relative coverage row. */
57
+ static void add_span(float *row, int rowOriginX, float xa, float xb, float weight, int clipX0,
58
+ int clipX1) {
59
+ if (xb <= xa) return;
60
+ if (xa < (float)clipX0) xa = (float)clipX0;
61
+ if (xb > (float)clipX1) xb = (float)clipX1;
62
+ if (xb <= xa) return;
63
+ int ia = (int)std::floor(xa);
64
+ int ib = (int)std::ceil(xb) - 1;
65
+ if (ib < ia) return;
66
+ if (ia == ib) {
67
+ row[ia - rowOriginX] += (xb - xa) * weight;
68
+ return;
69
+ }
70
+ row[ia - rowOriginX] += ((float)(ia + 1) - xa) * weight;
71
+ for (int x = ia + 1; x < ib; x++) row[x - rowOriginX] += weight;
72
+ float last = xb - (float)ib;
73
+ if (last > 0.0f) row[ib - rowOriginX] += last * weight;
74
+ }
75
+
76
+ void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
77
+ const std::vector<std::vector<CanvasPoint>> &loops, const float color[4],
78
+ float alpha) {
79
+ if (width <= 0 || height <= 0 || loops.empty()) return;
80
+ bool havePoint = false;
81
+ float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
82
+ for (const std::vector<CanvasPoint> &loop : loops) {
83
+ for (const CanvasPoint &point : loop) {
84
+ if (!havePoint) {
85
+ minX = maxX = point.x;
86
+ minY = maxY = point.y;
87
+ havePoint = true;
88
+ } else {
89
+ minX = std::min(minX, point.x);
90
+ maxX = std::max(maxX, point.x);
91
+ minY = std::min(minY, point.y);
92
+ maxY = std::max(maxY, point.y);
93
+ }
94
+ }
95
+ }
96
+ if (!havePoint) return;
97
+ int x0 = std::max(0, (int)std::floor(minX));
98
+ int x1 = std::min(width, (int)std::ceil(maxX));
99
+ int y0 = std::max(0, (int)std::floor(minY));
100
+ int y1 = std::min(height, (int)std::ceil(maxY));
101
+ if (x1 <= x0 || y1 <= y0) return;
102
+
103
+ int boxWidth = x1 - x0;
104
+ std::vector<float> coverage((size_t)boxWidth * (size_t)(y1 - y0), 0.0f);
105
+ const float weight = 1.0f / (float)kSubSamples;
106
+
107
+ std::vector<Crossing> crossings;
108
+ for (int py = y0; py < y1; py++) {
109
+ float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
110
+ for (int sample = 0; sample < kSubSamples; sample++) {
111
+ float sy = (float)py + ((float)sample + 0.5f) / (float)kSubSamples;
112
+ crossings.clear();
113
+ for (const std::vector<CanvasPoint> &loop : loops) {
114
+ if (loop.size() < 3) continue;
115
+ for (size_t i = 0; i < loop.size(); i++) {
116
+ const CanvasPoint &p0 = loop[i];
117
+ const CanvasPoint &p1 = loop[(i + 1) % loop.size()];
118
+ if (p0.y == p1.y) continue;
119
+ float top = std::min(p0.y, p1.y);
120
+ float bottom = std::max(p0.y, p1.y);
121
+ if (sy < top || sy >= bottom) continue;
122
+ float t = (sy - p0.y) / (p1.y - p0.y);
123
+ Crossing crossing;
124
+ crossing.x = p0.x + t * (p1.x - p0.x);
125
+ crossing.winding = p1.y > p0.y ? 1 : -1;
126
+ crossings.push_back(crossing);
127
+ }
128
+ }
129
+ if (crossings.empty()) continue;
130
+ std::sort(crossings.begin(), crossings.end(),
131
+ [](const Crossing &a, const Crossing &b) { return a.x < b.x; });
132
+ int winding = 0;
133
+ float spanStart = 0.0f;
134
+ for (const Crossing &crossing : crossings) {
135
+ if (winding == 0) spanStart = crossing.x;
136
+ winding += crossing.winding;
137
+ if (winding == 0) add_span(row, x0, spanStart, crossing.x, weight, x0, x1);
138
+ }
139
+ }
140
+ }
141
+
142
+ for (int py = y0; py < y1; py++) {
143
+ const float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
144
+ unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)x0) * 4;
145
+ for (int px = 0; px < boxWidth; px++) {
146
+ float c = row[px];
147
+ if (c <= 0.0f) continue;
148
+ if (c > 1.0f) c = 1.0f;
149
+ blend_pixel(dst + (size_t)px * 4, color, c, alpha);
150
+ }
151
+ }
152
+ }
153
+
154
+ void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
155
+ const std::vector<CanvasPoint> &loop, const float color[4], float alpha) {
156
+ fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, color, alpha);
157
+ }
158
+
159
+ void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
160
+ float x1, float y1, const float color[4], float alpha) {
161
+ if (x1 < x0) std::swap(x0, x1);
162
+ if (y1 < y0) std::swap(y0, y1);
163
+ int ix0 = std::max(0, (int)std::floor(x0));
164
+ int ix1 = std::min(width, (int)std::ceil(x1));
165
+ int iy0 = std::max(0, (int)std::floor(y0));
166
+ int iy1 = std::min(height, (int)std::ceil(y1));
167
+ for (int py = iy0; py < iy1; py++) {
168
+ float cy = std::min(y1, (float)(py + 1)) - std::max(y0, (float)py);
169
+ if (cy <= 0.0f) continue;
170
+ unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
171
+ for (int px = ix0; px < ix1; px++) {
172
+ float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
173
+ if (cx <= 0.0f) continue;
174
+ blend_pixel(dst + (size_t)(px - ix0) * 4, color, cx * cy, alpha);
175
+ }
176
+ }
177
+ }
178
+
179
+ void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
180
+ float x1, float y1) {
181
+ if (x1 < x0) std::swap(x0, x1);
182
+ if (y1 < y0) std::swap(y0, y1);
183
+ int ix0 = std::max(0, (int)std::floor(x0));
184
+ int ix1 = std::min(width, (int)std::ceil(x1));
185
+ int iy0 = std::max(0, (int)std::floor(y0));
186
+ int iy1 = std::min(height, (int)std::ceil(y1));
187
+ for (int py = iy0; py < iy1; py++) {
188
+ float cy = std::min(y1, (float)(py + 1)) - std::max(y0, (float)py);
189
+ if (cy <= 0.0f) continue;
190
+ unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
191
+ for (int px = ix0; px < ix1; px++) {
192
+ float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
193
+ if (cx <= 0.0f) continue;
194
+ unsigned char *pixel = dst + (size_t)(px - ix0) * 4;
195
+ float keep = 1.0f - cx * cy;
196
+ if (keep <= 0.0f) {
197
+ pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
198
+ } else {
199
+ pixel[3] = (unsigned char)clamp_byte(pixel[3] * keep);
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
206
+ float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
207
+ float alpha) {
208
+ if (source.width <= 0 || source.height <= 0 || dw <= 0.0f || dh <= 0.0f || sw <= 0.0f ||
209
+ sh <= 0.0f)
210
+ return;
211
+ int ix0 = std::max(0, (int)std::floor(dx));
212
+ int ix1 = std::min(width, (int)std::ceil(dx + dw));
213
+ int iy0 = std::max(0, (int)std::floor(dy));
214
+ int iy1 = std::min(height, (int)std::ceil(dy + dh));
215
+ for (int py = iy0; py < iy1; py++) {
216
+ for (int px = ix0; px < ix1; px++) {
217
+ float u = ((float)px + 0.5f - dx) / dw;
218
+ float v = ((float)py + 0.5f - dy) / dh;
219
+ if (u < 0.0f || u >= 1.0f || v < 0.0f || v >= 1.0f) continue;
220
+ int sxi = (int)(sx + u * sw);
221
+ int syi = (int)(sy + v * sh);
222
+ if (sxi < 0) sxi = 0;
223
+ if (syi < 0) syi = 0;
224
+ if (sxi >= source.width) sxi = source.width - 1;
225
+ if (syi >= source.height) syi = source.height - 1;
226
+ const unsigned char *texel =
227
+ source.pixels.data() + ((size_t)syi * (size_t)source.width + (size_t)sxi) * 4;
228
+ float color[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f, texel[3] / 255.0f};
229
+ blend_pixel(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color, 1.0f, alpha);
230
+ }
231
+ }
232
+ }
233
+
234
+ void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
235
+ const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
236
+ int top, float x, float baselineY, const float color[4], float alpha) {
237
+ int originX = (int)std::lround(x) + left;
238
+ int originY = (int)std::lround(baselineY) - top;
239
+ for (int gy = 0; gy < glyphHeight; gy++) {
240
+ int py = originY + gy;
241
+ if (py < 0 || py >= height) continue;
242
+ for (int gx = 0; gx < glyphWidth; gx++) {
243
+ int px = originX + gx;
244
+ if (px < 0 || px >= width) continue;
245
+ float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)gx] / 255.0f;
246
+ if (coverage <= 0.0f) continue;
247
+ blend_pixel(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color, coverage,
248
+ alpha);
249
+ }
250
+ }
251
+ }
252
+
253
+ } // namespace canvas_detail
254
+ } // namespace xtgui
@@ -31,6 +31,8 @@ void XtDocument::load(const std::string &html, float viewportWidth, float viewpo
31
31
  styles_.clear();
32
32
  display_.clear();
33
33
  transitions_.clear();
34
+ mediaSizes_.clear();
35
+ canvasInfo_.clear();
34
36
  clock_ms_ = 0.0;
35
37
  std::string css;
36
38
  collectStyleText(root_.get(), &css);
@@ -195,7 +197,8 @@ void XtDocument::updateDisplay() {
195
197
  i++;
196
198
  }
197
199
  }
198
- layout_.compute(root_.get(), display_, viewportWidth_, viewportHeight_);
200
+ layout_.compute(root_.get(), display_, viewportWidth_, viewportHeight_, &mediaSizes_,
201
+ &canvasInfo_);
199
202
  }
200
203
 
201
204
  bool XtDocument::advance(double delta_ms) {
@@ -228,6 +231,26 @@ const XtStyle *XtDocument::styleOf(const Node *node) const {
228
231
 
229
232
  const LayoutBox *XtDocument::boxOf(const Node *node) const { return layout_.find(node); }
230
233
 
234
+ bool XtDocument::setMediaSize(const Node *node, float width, float height, uint64_t id) {
235
+ MediaSize &entry = mediaSizes_[node];
236
+ if (entry.width == width && entry.height == height && entry.id == id) return false;
237
+ entry.width = width;
238
+ entry.height = height;
239
+ entry.id = id;
240
+ dirty_ = true;
241
+ return true;
242
+ }
243
+
244
+ bool XtDocument::setCanvasInfo(const Node *node, float width, float height, uint64_t id) {
245
+ CanvasInfo &entry = canvasInfo_[node];
246
+ if (entry.width == width && entry.height == height && entry.id == id) return false;
247
+ entry.width = width;
248
+ entry.height = height;
249
+ entry.id = id;
250
+ dirty_ = true;
251
+ return true;
252
+ }
253
+
231
254
  std::vector<const Node *> XtDocument::querySelectorAll(const std::string &selectorText) const {
232
255
  std::vector<const Node *> matches;
233
256
  if (root_ == nullptr) return matches;
@@ -95,6 +95,15 @@ class XtDocument {
95
95
  dirty_ = false;
96
96
  return was;
97
97
  }
98
+
99
+ /* -- media (`<video>`) metadata --------------------------------------- */
100
+ /** Record (or update) the decoded size and decoder id of one `<video>` node.
101
+ * Returns true when the stored value changed, so the caller can invalidate a
102
+ * layout that was computed with the old value. */
103
+ bool setMediaSize(const Node *node, float width, float height, uint64_t id);
104
+ /** Record (or update) the pixel size and render id of one `<canvas>` node.
105
+ * Returns true when the stored value changed. */
106
+ bool setCanvasInfo(const Node *node, float width, float height, uint64_t id);
98
107
  /** Serialized DOM, for diagnostics. */
99
108
  std::string toDebugString() const;
100
109
 
@@ -110,6 +119,8 @@ class XtDocument {
110
119
  StyleSheet sheet_;
111
120
  std::unordered_map<const Node *, XtStyle> styles_; // target (cascade)
112
121
  std::unordered_map<const Node *, XtStyle> display_; // after transitions
122
+ MediaSizeMap mediaSizes_; // `<video>` intrinsic sizes
123
+ CanvasInfoMap canvasInfo_; // `<canvas>` sizes + ids
113
124
  std::vector<ActiveTransition> transitions_;
114
125
  double clock_ms_ = 0.0;
115
126
  LayoutTree layout_;
@@ -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_canvas_proto = XT_UNDEFINED;
35
36
 
36
37
  /* -- small helpers -------------------------------------------------------- */
37
38
 
@@ -173,6 +174,8 @@ void xt_gui_node_handle_forget(XtGuiWindow *win, const xtgui::Node *node) {
173
174
  win->node_index.erase(index);
174
175
  }
175
176
  win->node_handles.erase(node);
177
+ /* A destroyed canvas node must not keep its bitmap or GPU texture alive. */
178
+ xt_gui_canvas_forget(win, node);
176
179
  }
177
180
 
178
181
  /** Forget the handles of `node` and every descendant. Runs before the subtree