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,193 @@
1
+ /*
2
+ * xbintsc GUI engine — `<video>` / `<audio>` lifecycle.
3
+ *
4
+ * This unit bridges three pieces:
5
+ * - the DOM (`Node`) and the per-window media table on `XtGuiWindow`;
6
+ * - the decoders (`media.cpp`, backed by FFmpeg + SDL audio);
7
+ * - the renderer (`renderer_image.cpp`), which owns the GPU textures.
8
+ *
9
+ * The frame loop calls `xt_gui_media_sync` (open/close decoders so the next
10
+ * layout sees the current sources and intrinsic sizes) and `xt_gui_media_tick`
11
+ * (decode for the elapsed wall clock and fire the resulting DOM events) once
12
+ * per frame. No `xt_value` is stored here, so there is nothing for the
13
+ * collector to trace.
14
+ */
15
+
16
+ #include "gui_internal.h"
17
+
18
+ #include <string>
19
+ #include <unordered_set>
20
+ #include <vector>
21
+
22
+ namespace {
23
+
24
+ bool isMediaElement(const xtgui::Node *node) {
25
+ return node != nullptr && node->isElement() && (node->isTag("video") || node->isTag("audio"));
26
+ }
27
+
28
+ /** The element's `src`, or the `src` of its first child `<source>` (which is
29
+ * how a `<video>` with fallback formats is written). */
30
+ std::string sourceOf(const xtgui::Node *node) {
31
+ if (!isMediaElement(node)) return "";
32
+ const std::string *src = node->attr("src");
33
+ if (src != nullptr && !src->empty()) return *src;
34
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
35
+ if (child->isElement() && child->isTag("source")) {
36
+ const std::string *child_src = child->attr("src");
37
+ if (child_src != nullptr && !child_src->empty()) return *child_src;
38
+ }
39
+ }
40
+ return "";
41
+ }
42
+
43
+ bool booleanAttr(const xtgui::Node *node, const char *name) {
44
+ return node->attr(name) != nullptr;
45
+ }
46
+
47
+ void collectMedia(const xtgui::Node *node, std::vector<const xtgui::Node *> *out) {
48
+ if (node == nullptr) return;
49
+ if (isMediaElement(node)) {
50
+ out->push_back(node);
51
+ return; // a media element's children are `<source>`/`<track>`, not media
52
+ }
53
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) collectMedia(child.get(), out);
54
+ }
55
+
56
+ void releasePlayerTexture(const xtgui::MediaPlayer *player) {
57
+ if (player != nullptr && guidev::g_device != nullptr) {
58
+ xt_gui_media_texture_release(guidev::g_device, player->id());
59
+ }
60
+ }
61
+
62
+ /** Open a decoder for `node`/`src`, apply its boolean attributes, record it and
63
+ * return it (or NULL, after firing `error`). */
64
+ xtgui::MediaPlayer *openEntry(XtGuiWindow *win, const xtgui::Node *node, const std::string &src) {
65
+ XtGuiMediaEntry entry;
66
+ entry.src = src;
67
+ std::string error;
68
+ entry.player = xtgui::MediaPlayer::open(src, &error);
69
+ if (!entry.player) {
70
+ entry.failed = true;
71
+ win->media_players[node] = std::move(entry);
72
+ xt_gui_dispatch_to_node(win, node, "error");
73
+ return nullptr;
74
+ }
75
+ entry.player->set_loop(booleanAttr(node, "loop"));
76
+ entry.player->set_muted(booleanAttr(node, "muted"));
77
+ entry.player->set_autoplay(booleanAttr(node, "autoplay"));
78
+ xtgui::MediaPlayer *raw = entry.player.get();
79
+ win->media_players[node] = std::move(entry);
80
+ return raw;
81
+ }
82
+
83
+ void emitMediaEvents(XtGuiWindow *win, const xtgui::Node *node, unsigned events) {
84
+ if (events == 0) return;
85
+ if (events & xtgui::kMediaEventLoadedMetadata) {
86
+ xt_gui_dispatch_to_node(win, node, "loadedmetadata");
87
+ }
88
+ if (events & xtgui::kMediaEventPlay) xt_gui_dispatch_to_node(win, node, "play");
89
+ if (events & xtgui::kMediaEventPause) xt_gui_dispatch_to_node(win, node, "pause");
90
+ if (events & xtgui::kMediaEventEnded) xt_gui_dispatch_to_node(win, node, "ended");
91
+ if (events & xtgui::kMediaEventTimeUpdate) xt_gui_dispatch_to_node(win, node, "timeupdate");
92
+ if (events & xtgui::kMediaEventError) xt_gui_dispatch_to_node(win, node, "error");
93
+ }
94
+
95
+ } // namespace
96
+
97
+ xtgui::MediaPlayer *xt_gui_media_player(XtGuiWindow *win, const xtgui::Node *node) {
98
+ if (win == nullptr || !isMediaElement(node)) return nullptr;
99
+ auto found = win->media_players.find(node);
100
+ if (found != win->media_players.end()) return found->second.player.get();
101
+ std::string src = sourceOf(node);
102
+ if (src.empty()) return nullptr;
103
+ return openEntry(win, node, src);
104
+ }
105
+
106
+ void xt_gui_media_forget(XtGuiWindow *win, const xtgui::Node *node) {
107
+ if (win == nullptr) return;
108
+ auto found = win->media_players.find(node);
109
+ if (found == win->media_players.end()) return;
110
+ releasePlayerTexture(found->second.player.get());
111
+ win->media_players.erase(found);
112
+ }
113
+
114
+ void xt_gui_media_sync(XtGuiWindow *win) {
115
+ if (win == nullptr || win->document == nullptr) return;
116
+ std::vector<const xtgui::Node *> nodes;
117
+ collectMedia(win->document->root(), &nodes);
118
+ std::unordered_set<const xtgui::Node *> live(nodes.begin(), nodes.end());
119
+
120
+ /* Drop decoders whose element left the tree. Comparing pointers only (never
121
+ * dereferencing) is safe for a node that was already destroyed. */
122
+ for (auto it = win->media_players.begin(); it != win->media_players.end();) {
123
+ if (live.count(it->first) == 0) {
124
+ releasePlayerTexture(it->second.player.get());
125
+ it = win->media_players.erase(it);
126
+ } else {
127
+ ++it;
128
+ }
129
+ }
130
+
131
+ /* Open decoders for new sources and reopen when an element's `src` changes. */
132
+ for (const xtgui::Node *node : nodes) {
133
+ const std::string src = sourceOf(node);
134
+ auto found = win->media_players.find(node);
135
+ if (src.empty()) {
136
+ if (found != win->media_players.end()) {
137
+ releasePlayerTexture(found->second.player.get());
138
+ win->media_players.erase(found);
139
+ }
140
+ continue;
141
+ }
142
+ if (found != win->media_players.end() && found->second.src == src) continue;
143
+ if (found != win->media_players.end()) {
144
+ releasePlayerTexture(found->second.player.get());
145
+ win->media_players.erase(found);
146
+ }
147
+ openEntry(win, node, src);
148
+ }
149
+
150
+ /* Publish the intrinsic size of every `<video>` so layout can use it. */
151
+ for (const xtgui::Node *node : nodes) {
152
+ if (!node->isTag("video")) continue;
153
+ auto found = win->media_players.find(node);
154
+ xtgui::MediaPlayer *player = found == win->media_players.end() ? nullptr : found->second.player.get();
155
+ float width = 0.0f;
156
+ float height = 0.0f;
157
+ uint64_t id = 0;
158
+ if (player != nullptr) {
159
+ width = (float)player->frame_width();
160
+ height = (float)player->frame_height();
161
+ id = player->id();
162
+ }
163
+ win->document->setMediaSize(node, width, height, id);
164
+ }
165
+ }
166
+
167
+ void xt_gui_media_tick(XtGuiWindow *win, double now_ms) {
168
+ if (win == nullptr) return;
169
+ /* Snapshot the keys: a handler may change an element's `src`, which erases and
170
+ * reinserts into the table while we iterate. */
171
+ std::vector<const xtgui::Node *> nodes;
172
+ nodes.reserve(win->media_players.size());
173
+ for (const auto &entry : win->media_players) nodes.push_back(entry.first);
174
+ for (const xtgui::Node *node : nodes) {
175
+ auto found = win->media_players.find(node);
176
+ if (found == win->media_players.end() || !found->second.player) continue;
177
+ unsigned events = found->second.player->tick(now_ms);
178
+ /* A freshly decoded frame must reach the image upload. The geometry pass
179
+ * rebuilds the image batches, so mark it dirty once per new generation. */
180
+ if (found->second.player->has_video() &&
181
+ found->second.player->frame_generation() != found->second.rendered_generation) {
182
+ found->second.rendered_generation = found->second.player->frame_generation();
183
+ win->geometry.dirty = 1;
184
+ }
185
+ emitMediaEvents(win, node, events);
186
+ }
187
+ }
188
+
189
+ void xt_gui_media_shutdown(XtGuiWindow *win) {
190
+ if (win == nullptr) return;
191
+ for (auto &entry : win->media_players) releasePlayerTexture(entry.second.player.get());
192
+ win->media_players.clear();
193
+ }
@@ -13,8 +13,11 @@
13
13
  #include "stb_image.h"
14
14
 
15
15
  #include <cctype>
16
+ #include <string>
16
17
  #include <unordered_map>
17
18
 
19
+ #include "svg.h"
20
+
18
21
  namespace xtgui {
19
22
  namespace {
20
23
 
@@ -63,31 +66,44 @@ std::string normalizePath(const std::string &input) {
63
66
 
64
67
  } // namespace
65
68
 
66
- const Image *xt_image_load(const std::string &raw_path) {
69
+ const Image *xt_image_load(const std::string &raw_path, int target_width, int target_height) {
67
70
  std::string path = normalizePath(raw_path);
71
+ bool svg = xt_svg_is_svg(path);
72
+ /* Raster formats always decode at their intrinsic resolution, so their cache
73
+ * key is the path. SVG is rasterised at the drawn pixel size, which is part
74
+ * of its identity. */
75
+ std::string key = path;
76
+ if (svg && (target_width > 0 || target_height > 0)) {
77
+ key += "#" + std::to_string(target_width) + "x" + std::to_string(target_height);
78
+ }
68
79
  auto &cache = imageCache();
69
- auto found = cache.find(path);
80
+ auto found = cache.find(key);
70
81
  if (found != cache.end()) {
71
82
  return found->second.width > 0 ? &found->second : nullptr;
72
83
  }
73
84
 
74
85
  Image image;
75
- int width = 0;
76
- int height = 0;
77
- int channels = 0;
78
- unsigned char *data = stbi_load(path.c_str(), &width, &height, &channels, 4);
79
- if (data != nullptr) {
80
- image.width = width;
81
- image.height = height;
82
- image.pixels.assign(data, data + (size_t)width * (size_t)height * 4);
83
- stbi_image_free(data);
86
+ if (svg) {
87
+ xt_svg_render(path, target_width, target_height, &image);
88
+ } else {
89
+ int width = 0;
90
+ int height = 0;
91
+ int channels = 0;
92
+ unsigned char *data = stbi_load(path.c_str(), &width, &height, &channels, 4);
93
+ if (data != nullptr) {
94
+ image.width = width;
95
+ image.height = height;
96
+ image.pixels.assign(data, data + (size_t)width * (size_t)height * 4);
97
+ stbi_image_free(data);
98
+ }
84
99
  }
85
- auto inserted = cache.emplace(path, std::move(image));
100
+ auto inserted = cache.emplace(key, std::move(image));
86
101
  return inserted.first->second.width > 0 ? &inserted.first->second : nullptr;
87
102
  }
88
103
 
89
104
  bool xt_image_size(const std::string &raw_path, int *width, int *height) {
90
105
  std::string path = normalizePath(raw_path);
106
+ if (xt_svg_is_svg(path)) return xt_svg_size(path, width, height);
91
107
  auto &cache = sizeCache();
92
108
  auto found = cache.find(path);
93
109
  if (found == cache.end()) {
@@ -117,9 +133,12 @@ bool xt_image_decode(const unsigned char *data, size_t size, Image *out) {
117
133
  return true;
118
134
  }
119
135
 
136
+ bool xt_image_is_vector(const std::string &path) { return xt_svg_is_svg(normalizePath(path)); }
137
+
120
138
  void xt_image_shutdown() {
121
139
  imageCache().clear();
122
140
  sizeCache().clear();
141
+ xt_svg_shutdown();
123
142
  }
124
143
 
125
144
  } // namespace xtgui
@@ -23,18 +23,31 @@ struct Image {
23
23
  std::vector<unsigned char> pixels;
24
24
  };
25
25
 
26
- /** Decode an image file (PNG/JPEG/BMP/GIF/TGA) to RGBA8. Returns nullptr on
27
- * failure. Results (including failures) are cached by path. A `file://` prefix
28
- * and URL percent-encoding are handled. */
29
- const Image *xt_image_load(const std::string &path);
30
-
31
- /** Read just the intrinsic size from the file header, without decoding.
32
- * Returns false when the file cannot be read/parsed. Cached. */
26
+ /** Decode an image file (PNG/JPEG/BMP/GIF/TGA or SVG) to RGBA8. Returns
27
+ * nullptr on failure. Results (including failures) are cached by path. A
28
+ * `file://` prefix and URL percent-encoding are handled.
29
+ *
30
+ * `target_width`/`target_height` are the pixel size the image will be drawn at.
31
+ * Raster formats ignore them (they decode at their intrinsic resolution), but
32
+ * SVG is a vector format and is rasterised at (approximately) that size —
33
+ * preserving its aspect ratio — so a scaled `<img src="logo.svg">` stays crisp
34
+ * instead of being a magnified bitmap. Pass `<= 0` (the default) to use the intrinsic size. The cache is keyed by the
35
+ * resulting pixel size for SVG and by path for raster formats. */
36
+ const Image *xt_image_load(const std::string &path, int target_width = 0, int target_height = 0);
37
+
38
+ /** Read just the intrinsic size from the file header, without decoding. For an
39
+ * SVG this parses the document (the size comes from width/height or the
40
+ * viewBox). Returns false when the file cannot be read/parsed. Cached. */
33
41
  bool xt_image_size(const std::string &path, int *width, int *height);
34
42
 
35
43
  /** Decode an in-memory buffer to RGBA8 (e.g. a future `data:` URI). */
36
44
  bool xt_image_decode(const unsigned char *data, size_t size, Image *out);
37
45
 
46
+ /** True when `path` is a vector source (SVG) that is rasterised on demand
47
+ * rather than decoded as a bitmap. The renderer uses this to key vector
48
+ * textures by pixel size. */
49
+ bool xt_image_is_vector(const std::string &path);
50
+
38
51
  void xt_image_shutdown();
39
52
 
40
53
  } // namespace xtgui
@@ -25,7 +25,8 @@ namespace {
25
25
 
26
26
  std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
27
27
  const std::unordered_map<const Node *, XtStyle> &styles,
28
- const XtStyle *inherited) {
28
+ const XtStyle *inherited, const MediaSizeMap *media,
29
+ const CanvasInfoMap *canvases) {
29
30
  if (node->isText()) {
30
31
  auto box = std::make_unique<LayoutBox>();
31
32
  box->node = node;
@@ -55,9 +56,53 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
55
56
  box->intrinsic_height = (float)height;
56
57
  }
57
58
  }
59
+ } else if (node->isElement() && node->isTag("video")) {
60
+ /* Default the video box to the HTML 300x150 until the decoder reports the
61
+ * real pixel size (fetched as soon as the element is associated with a
62
+ * player, before the next layout pass). */
63
+ box->is_image = true;
64
+ box->is_media = true;
65
+ box->intrinsic_width = 300;
66
+ box->intrinsic_height = 150;
67
+ if (media != nullptr) {
68
+ auto found = media->find(node);
69
+ if (found != media->end()) {
70
+ box->media_id = found->second.id;
71
+ if (found->second.width > 0 && found->second.height > 0) {
72
+ box->intrinsic_width = found->second.width;
73
+ box->intrinsic_height = found->second.height;
74
+ }
75
+ }
76
+ }
77
+ } else if (node->isElement() && node->isTag("canvas")) {
78
+ /* A replaced inline-block whose pixels are a script-owned bitmap. The
79
+ * intrinsic size is the HTML 300x150 default, overridden by the
80
+ * `width`/`height` attributes (or a script assignment to `canvas.width`),
81
+ * published by the window as `CanvasInfo`. */
82
+ box->is_canvas = true;
83
+ box->intrinsic_width = 300;
84
+ box->intrinsic_height = 150;
85
+ float parsed = 0.0f;
86
+ const std::string *attr_width = node->attr("width");
87
+ const std::string *attr_height = node->attr("height");
88
+ if (attr_width != nullptr && xt_css_parse_number(*attr_width, &parsed) && parsed > 0.0f)
89
+ box->intrinsic_width = parsed;
90
+ if (attr_height != nullptr && xt_css_parse_number(*attr_height, &parsed) && parsed > 0.0f)
91
+ box->intrinsic_height = parsed;
92
+ if (canvases != nullptr) {
93
+ auto found = canvases->find(node);
94
+ if (found != canvases->end()) {
95
+ box->canvas_id = found->second.id;
96
+ if (found->second.width > 0 && found->second.height > 0) {
97
+ box->intrinsic_width = found->second.width;
98
+ box->intrinsic_height = found->second.height;
99
+ }
100
+ }
101
+ }
58
102
  }
59
103
  for (const std::unique_ptr<Node> &child : node->children) {
60
- std::unique_ptr<LayoutBox> childBox = buildBoxTree(child.get(), styles, style);
104
+ std::unique_ptr<LayoutBox> childBox =
105
+ buildBoxTree(child.get(), styles, style, media, canvases);
61
106
  if (childBox != nullptr) {
62
107
  childBox->parent = box.get();
63
108
  box->children.push_back(std::move(childBox));
@@ -86,7 +131,8 @@ std::string dumpBox(const LayoutBox *box, int depth) {
86
131
  } // namespace
87
132
 
88
133
  void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *, XtStyle> &styles,
89
- float viewportWidth, float viewportHeight) {
134
+ float viewportWidth, float viewportHeight, const MediaSizeMap *media,
135
+ const CanvasInfoMap *canvases) {
90
136
  root_.reset();
91
137
  index_.clear();
92
138
  root_style_ = xt_style_initial();
@@ -105,14 +151,15 @@ void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *
105
151
  if (root != nullptr) {
106
152
  if (root->type == NodeType::Document) {
107
153
  for (const std::unique_ptr<Node> &child : root->children) {
108
- std::unique_ptr<LayoutBox> box = buildBoxTree(child.get(), styles, &root_style_);
154
+ std::unique_ptr<LayoutBox> box =
155
+ buildBoxTree(child.get(), styles, &root_style_, media, canvases);
109
156
  if (box != nullptr) {
110
157
  box->parent = root_.get();
111
158
  root_->children.push_back(std::move(box));
112
159
  }
113
160
  }
114
161
  } else {
115
- std::unique_ptr<LayoutBox> box = buildBoxTree(root, styles, &root_style_);
162
+ std::unique_ptr<LayoutBox> box = buildBoxTree(root, styles, &root_style_, media, canvases);
116
163
  if (box != nullptr) {
117
164
  box->parent = root_.get();
118
165
  root_->children.push_back(std::move(box));
@@ -13,6 +13,7 @@
13
13
  #ifndef XT_GUI_LAYOUT_H
14
14
  #define XT_GUI_LAYOUT_H
15
15
 
16
+ #include <cstdint>
16
17
  #include <memory>
17
18
  #include <string>
18
19
  #include <unordered_map>
@@ -23,6 +24,25 @@
23
24
 
24
25
  namespace xtgui {
25
26
 
27
+ /** Intrinsic size and decoder id of one `<video>` element, resolved by the
28
+ * engine and keyed by node. `width`/`height` are the decoded pixel size, or
29
+ * the 300x150 default before metadata is available. */
30
+ struct MediaSize {
31
+ float width = 0;
32
+ float height = 0;
33
+ uint64_t id = 0;
34
+ };
35
+ using MediaSizeMap = std::unordered_map<const Node *, MediaSize>;
36
+
37
+ /** Intrinsic size and render id of one `<canvas>` element, resolved by the
38
+ * engine and keyed by node. `width`/`height` are the bitmap's pixel size. */
39
+ struct CanvasInfo {
40
+ float width = 0;
41
+ float height = 0;
42
+ uint64_t id = 0;
43
+ };
44
+ using CanvasInfoMap = std::unordered_map<const Node *, CanvasInfo>;
45
+
26
46
  /** A positioned text fragment (a wrapped text box can span several lines).
27
47
  * `text` holds the run this fragment covers (a word or a run of spaces). */
28
48
  struct TextFragment {
@@ -47,6 +67,17 @@ struct LayoutBox {
47
67
  float intrinsic_width = 0;
48
68
  float intrinsic_height = 0;
49
69
 
70
+ /* `<video>`: a replaced element whose pixels come from a live decoder
71
+ * (`media_id`) instead of a file. `is_image` is set too so the
72
+ * replaced-element sizing in `layout_flow.cpp` applies unchanged. */
73
+ bool is_media = false;
74
+ uint64_t media_id = 0;
75
+
76
+ /* `<canvas>`: a replaced element whose pixels are a script-owned `Canvas2D`
77
+ * (`canvas_id`). `is_image`-style intrinsic sizing applies. */
78
+ bool is_canvas = false;
79
+ uint64_t canvas_id = 0;
80
+
50
81
  Display display = Display::Inline;
51
82
 
52
83
  /* Resolved (px) box edges. */
@@ -70,7 +101,8 @@ struct LayoutBox {
70
101
  class LayoutTree {
71
102
  public:
72
103
  void compute(const Node *root, const std::unordered_map<const Node *, XtStyle> &styles,
73
- float viewportWidth, float viewportHeight);
104
+ float viewportWidth, float viewportHeight, const MediaSizeMap *media = nullptr,
105
+ const CanvasInfoMap *canvases = nullptr);
74
106
  void clear() {
75
107
  root_.reset();
76
108
  index_.clear();