xbintsc 0.5.20 → 0.5.21

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 (100) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  22. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  23. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  24. package/doc/ai/contributing.md +2 -1
  25. package/doc/ai/extensions.md +4 -3
  26. package/doc/ai/language-support.md +2 -1
  27. package/doc/ai/zh-CN/contributing.md +1 -1
  28. package/doc/ai/zh-CN/extensions.md +4 -3
  29. package/doc/ai/zh-CN/language-support.md +2 -1
  30. package/doc/gui/api.md +8 -0
  31. package/doc/gui/known-issues.md +10 -9
  32. package/doc/gui/media.md +32 -3
  33. package/doc/gui.md +24 -2
  34. package/doc/implemented/async.md +16 -3
  35. package/doc/implemented/fetch.md +38 -6
  36. package/doc/implemented/node/fs-promises.md +19 -8
  37. package/doc/implemented/node.md +1 -1
  38. package/doc/implemented.md +1 -1
  39. package/doc/unimplemented/deviations.md +1 -1
  40. package/doc/unimplemented/node/async.md +6 -3
  41. package/doc/unimplemented/node/fs-promises.md +10 -6
  42. package/doc/unimplemented/node/fs.md +7 -8
  43. package/doc/unimplemented/node.md +2 -2
  44. package/doc/unimplemented/toolchain.md +1 -1
  45. package/doc/zh-CN/gui/api.md +7 -0
  46. package/doc/zh-CN/gui/known-issues.md +8 -7
  47. package/doc/zh-CN/gui/media.md +26 -2
  48. package/doc/zh-CN/gui.md +18 -1
  49. package/doc/zh-CN/implemented/async.md +13 -2
  50. package/doc/zh-CN/implemented/fetch.md +23 -4
  51. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  52. package/doc/zh-CN/implemented/node.md +1 -1
  53. package/doc/zh-CN/implemented.md +1 -1
  54. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  55. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  56. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  57. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  58. package/doc/zh-CN/unimplemented/node.md +2 -2
  59. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  60. package/package.json +1 -1
  61. package/runtime/ext_gui/inline_svg.cpp +219 -0
  62. package/runtime/ext_gui/inline_svg.h +41 -0
  63. package/runtime/ext_gui/layout.cpp +18 -0
  64. package/runtime/ext_gui/layout.h +7 -0
  65. package/runtime/ext_gui/layout_flow.cpp +4 -3
  66. package/runtime/ext_gui/paint.cpp +14 -0
  67. package/runtime/ext_gui/paint.h +5 -1
  68. package/runtime/ext_gui/renderer_image.cpp +53 -25
  69. package/runtime/ext_gui/style_ua.cpp +3 -0
  70. package/runtime/ext_gui/svg.cpp +122 -39
  71. package/runtime/ext_gui/svg.h +14 -3
  72. package/runtime/ext_gui/window.cpp +28 -0
  73. package/runtime/ext_node/fs/async_api.c +314 -0
  74. package/runtime/ext_node/fs/async_complete.c +99 -0
  75. package/runtime/ext_node/fs/async_fh.c +206 -0
  76. package/runtime/ext_node/fs/async_job.c +81 -0
  77. package/runtime/ext_node/fs/async_job.h +132 -0
  78. package/runtime/ext_node/fs/async_worker.c +534 -0
  79. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  80. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  81. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  82. package/runtime/ext_node/fs/dir.c +60 -15
  83. package/runtime/ext_node/fs/fs_common.h +9 -0
  84. package/runtime/ext_node/fs/fs_ops.c +18 -29
  85. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  86. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  87. package/runtime/ext_node/fs/promises.c +44 -234
  88. package/runtime/rt_internal.h +2 -0
  89. package/runtime/xt_async_io.c +333 -0
  90. package/runtime/xt_async_io.h +47 -0
  91. package/runtime/xt_fetch/async.inc +295 -0
  92. package/runtime/xt_fetch/request.inc +6 -183
  93. package/runtime/xt_fetch/socket.inc +5 -4
  94. package/runtime/xt_fetch.c +6 -4
  95. package/runtime/xt_loop.c +44 -3
  96. package/src/driver/compiler.ts +1 -1
  97. package/src/driver/runtime-objects.ts +1 -0
  98. package/src/driver/version.ts +1 -1
  99. package/src/extensions/node/fs/index.ts +12 -3
  100. package/src/extensions/node/fs-promises/index.ts +15 -6
@@ -0,0 +1,219 @@
1
+ /*
2
+ * xbintsc GUI engine — inline `<svg>` serialization. See `inline_svg.h`.
3
+ */
4
+
5
+ #include "inline_svg.h"
6
+
7
+ #include <cctype>
8
+ #include <string>
9
+ #include <unordered_map>
10
+
11
+ namespace xtgui {
12
+ namespace {
13
+
14
+ /** SVG element names that are case-sensitive and contain an upper-case letter.
15
+ * The HTML parser lowercases tag names, so the serializer maps them back;
16
+ * everything not listed is already lower case in SVG (`path`, `rect`, `g`,
17
+ * `defs`, `stop`, …). */
18
+ const std::unordered_map<std::string, const char *> &canonicalTags() {
19
+ static const std::unordered_map<std::string, const char *> tags = {
20
+ {"altglyph", "altGlyph"},
21
+ {"altglyphdef", "altGlyphDef"},
22
+ {"altglyphitem", "altGlyphItem"},
23
+ {"animatecolor", "animateColor"},
24
+ {"animatemotion", "animateMotion"},
25
+ {"animatetransform", "animateTransform"},
26
+ {"clippath", "clipPath"},
27
+ {"feblend", "feBlend"},
28
+ {"fecolormatrix", "feColorMatrix"},
29
+ {"fecomponenttransfer", "feComponentTransfer"},
30
+ {"fecomposite", "feComposite"},
31
+ {"feconvolvematrix", "feConvolveMatrix"},
32
+ {"fediffuselighting", "feDiffuseLighting"},
33
+ {"fedisplacementmap", "feDisplacementMap"},
34
+ {"fedistantlight", "feDistantLight"},
35
+ {"fedropshadow", "feDropShadow"},
36
+ {"feflood", "feFlood"},
37
+ {"fefunca", "feFuncA"},
38
+ {"fefuncb", "feFuncB"},
39
+ {"fefuncg", "feFuncG"},
40
+ {"fefuncr", "feFuncR"},
41
+ {"fegaussianblur", "feGaussianBlur"},
42
+ {"feimage", "feImage"},
43
+ {"femerge", "feMerge"},
44
+ {"femergenode", "feMergeNode"},
45
+ {"femorphology", "feMorphology"},
46
+ {"feoffset", "feOffset"},
47
+ {"fepointlight", "fePointLight"},
48
+ {"fespecularlighting", "feSpecularLighting"},
49
+ {"fespotlight", "feSpotLight"},
50
+ {"fetile", "feTile"},
51
+ {"feturbulence", "feTurbulence"},
52
+ {"foreignobject", "foreignObject"},
53
+ {"glyphref", "glyphRef"},
54
+ {"lineargradient", "linearGradient"},
55
+ {"radialgradient", "radialGradient"},
56
+ {"textpath", "textPath"},
57
+ };
58
+ return tags;
59
+ }
60
+
61
+ /** The same for attributes: NanoSVG compares these names exactly, and the HTML
62
+ * parser delivers them in lower case. The table also covers the common SVG
63
+ * attributes NanoSVG does not read, so the markup it is handed stays faithful
64
+ * to what a browser would parse. */
65
+ const std::unordered_map<std::string, const char *> &canonicalAttrs() {
66
+ static const std::unordered_map<std::string, const char *> attrs = {
67
+ {"attributename", "attributeName"},
68
+ {"attributetype", "attributeType"},
69
+ {"basefrequency", "baseFrequency"},
70
+ {"baseprofile", "baseProfile"},
71
+ {"calcmode", "calcMode"},
72
+ {"clippathunits", "clipPathUnits"},
73
+ {"contentscripttype", "contentScriptType"},
74
+ {"contentstyletype", "contentStyleType"},
75
+ {"diffuseconstant", "diffuseConstant"},
76
+ {"edgemode", "edgeMode"},
77
+ {"filterunits", "filterUnits"},
78
+ {"gradienttransform", "gradientTransform"},
79
+ {"gradientunits", "gradientUnits"},
80
+ {"kernelmatrix", "kernelMatrix"},
81
+ {"kernelunitlength", "kernelUnitLength"},
82
+ {"keypoints", "keyPoints"},
83
+ {"keysplines", "keySplines"},
84
+ {"keytimes", "keyTimes"},
85
+ {"lengthadjust", "lengthAdjust"},
86
+ {"limitingconeangle", "limitingConeAngle"},
87
+ {"markerheight", "markerHeight"},
88
+ {"markerunits", "markerUnits"},
89
+ {"markerwidth", "markerWidth"},
90
+ {"maskcontentunits", "maskContentUnits"},
91
+ {"maskunits", "maskUnits"},
92
+ {"numoctaves", "numOctaves"},
93
+ {"pathlength", "pathLength"},
94
+ {"patterncontentunits", "patternContentUnits"},
95
+ {"patterntransform", "patternTransform"},
96
+ {"patternunits", "patternUnits"},
97
+ {"pointsatx", "pointsAtX"},
98
+ {"pointsaty", "pointsAtY"},
99
+ {"pointsatz", "pointsAtZ"},
100
+ {"preserveaspectratio", "preserveAspectRatio"},
101
+ {"primitiveunits", "primitiveUnits"},
102
+ {"refx", "refX"},
103
+ {"refy", "refY"},
104
+ {"repeatcount", "repeatCount"},
105
+ {"repeatdur", "repeatDur"},
106
+ {"requiredextensions", "requiredExtensions"},
107
+ {"requiredfeatures", "requiredFeatures"},
108
+ {"specularconstant", "specularConstant"},
109
+ {"specularexponent", "specularExponent"},
110
+ {"spreadmethod", "spreadMethod"},
111
+ {"startoffset", "startOffset"},
112
+ {"stddeviation", "stdDeviation"},
113
+ {"stitchtiles", "stitchTiles"},
114
+ {"surfacescale", "surfaceScale"},
115
+ {"systemlanguage", "systemLanguage"},
116
+ {"tablevalues", "tableValues"},
117
+ {"targetx", "targetX"},
118
+ {"targety", "targetY"},
119
+ {"textlength", "textLength"},
120
+ {"transformorigin", "transformOrigin"},
121
+ {"viewbox", "viewBox"},
122
+ {"viewtarget", "viewTarget"},
123
+ {"xchannelselector", "xChannelSelector"},
124
+ {"ychannelselector", "yChannelSelector"},
125
+ {"zoomandpan", "zoomAndPan"},
126
+ };
127
+ return attrs;
128
+ }
129
+
130
+ void escapeAttribute(const std::string &value, std::string *out) {
131
+ for (char c : value) {
132
+ switch (c) {
133
+ case '&': out->append("&amp;"); break;
134
+ case '<': out->append("&lt;"); break;
135
+ case '>': out->append("&gt;"); break;
136
+ case '"': out->append("&quot;"); break;
137
+ default: out->push_back(c); break;
138
+ }
139
+ }
140
+ }
141
+
142
+ std::string canonicalTag(const std::string &tag) {
143
+ const auto &tags = canonicalTags();
144
+ auto found = tags.find(tag);
145
+ return found == tags.end() ? tag : std::string(found->second);
146
+ }
147
+
148
+ const char *canonicalAttr(const std::string &name) {
149
+ const auto &attrs = canonicalAttrs();
150
+ auto found = attrs.find(name);
151
+ return found == attrs.end() ? nullptr : found->second;
152
+ }
153
+
154
+ bool isNamespaceAttr(const std::string &name) {
155
+ return name == "xmlns" || name.compare(0, 6, "xmlns:") == 0;
156
+ }
157
+
158
+ /** HTML-only attributes that must not reach the SVG reader (inline `style` and
159
+ * `class` are legitimate on SVG elements and stay). */
160
+ bool isHtmlAttr(const std::string &name) {
161
+ return name == "tabindex" || name == "role" || name == "contenteditable";
162
+ }
163
+
164
+ void serializeNode(const Node *node, std::string *out);
165
+
166
+ void serializeStyleText(const Node *node, std::string *out) {
167
+ for (const std::unique_ptr<Node> &child : node->children) {
168
+ if (child->isText()) {
169
+ out->append(child->text);
170
+ } else if (child->isElement()) {
171
+ serializeNode(child.get(), out);
172
+ }
173
+ }
174
+ }
175
+
176
+ void serializeNode(const Node *node, std::string *out) {
177
+ if (node == nullptr || !node->isElement()) return;
178
+ std::string tag = canonicalTag(node->tag);
179
+ out->push_back('<');
180
+ out->append(tag);
181
+ if (node->isTag("svg")) out->append(" xmlns=\"http://www.w3.org/2000/svg\"");
182
+ for (const Attr &attr : node->attrs) {
183
+ if (isNamespaceAttr(attr.name) || isHtmlAttr(attr.name)) continue;
184
+ /* The root's xmlns is emitted above, so a second copy would be an XML
185
+ * duplicate-attribute error. */
186
+ const char *name = canonicalAttr(attr.name);
187
+ out->push_back(' ');
188
+ out->append(name != nullptr ? name : attr.name);
189
+ out->append("=\"");
190
+ escapeAttribute(attr.value, out);
191
+ out->push_back('"');
192
+ }
193
+ out->push_back('>');
194
+ if (node->isTag("style")) {
195
+ serializeStyleText(node, out);
196
+ } else {
197
+ for (const std::unique_ptr<Node> &child : node->children) {
198
+ if (child->isElement()) serializeNode(child.get(), out);
199
+ }
200
+ }
201
+ out->append("</");
202
+ out->append(tag);
203
+ out->push_back('>');
204
+ }
205
+
206
+ } // namespace
207
+
208
+ bool xt_inline_svg_is_root(const Node *node) {
209
+ return node != nullptr && node->isElement() && node->isTag("svg");
210
+ }
211
+
212
+ std::string xt_inline_svg_serialize(const Node *node) {
213
+ if (!xt_inline_svg_is_root(node)) return std::string();
214
+ std::string out;
215
+ serializeNode(node, &out);
216
+ return out;
217
+ }
218
+
219
+ } // namespace xtgui
@@ -0,0 +1,41 @@
1
+ /*
2
+ * xbintsc GUI engine — inline `<svg>` elements.
3
+ *
4
+ * An SVG that arrives as a file is decoded by `svg.h` (`<img src="logo.svg">`).
5
+ * An inline `<svg>` tree is already part of the document, so this module turns
6
+ * the subtree back into a standalone SVG document and hands it to the same
7
+ * NanoSVG parser/rasteriser. Layout then treats `<svg>` as a replaced element
8
+ * (its intrinsic size comes from the parsed document) and paint emits an image
9
+ * op carrying the markup, so the renderer rasterises it at the drawn device
10
+ * pixel size — vector art stays crisp exactly like an SVG file.
11
+ *
12
+ * The HTML parser lowercases element and attribute names (so `viewBox` arrives
13
+ * as `viewbox`), while NanoSVG matches SVG's case-sensitive spellings. The
14
+ * serializer therefore restores the canonical names of the elements and
15
+ * attributes NanoSVG understands, and drops the HTML-only ones that would
16
+ * confuse the XML reader.
17
+ */
18
+ #ifndef XT_GUI_INLINE_SVG_H
19
+ #define XT_GUI_INLINE_SVG_H
20
+
21
+ #include <string>
22
+
23
+ #include "dom.h"
24
+ #include "svg.h"
25
+
26
+ namespace xtgui {
27
+
28
+ /** True when `node` is an `<svg>` element (an inline SVG root). */
29
+ bool xt_inline_svg_is_root(const Node *node);
30
+
31
+ /** Serialize an `<svg>` DOM subtree into a standalone SVG document: the root
32
+ * element, its descendants (including `<defs>`, gradients and `<style>` text)
33
+ * and the canonical `width`/`height`/`viewBox`/`gradient*` spellings NanoSVG
34
+ * expects. Text outside `<style>` is dropped (NanoSVG renders shapes, not
35
+ * `<text>`), and HTML-only attributes are skipped. Returns an empty string when
36
+ * `node` is not an `<svg>` element. */
37
+ std::string xt_inline_svg_serialize(const Node *node);
38
+
39
+ } // namespace xtgui
40
+
41
+ #endif /* XT_GUI_INLINE_SVG_H */
@@ -19,6 +19,7 @@
19
19
  #include <cstring>
20
20
 
21
21
  #include "image.h"
22
+ #include "inline_svg.h"
22
23
  #include "style_internal.h"
23
24
 
24
25
  namespace xtgui {
@@ -104,7 +105,24 @@ std::unique_ptr<LayoutBox> buildBoxTree(const Node *node,
104
105
  }
105
106
  }
106
107
  }
108
+ } else if (node->isElement() && node->isTag("svg")) {
109
+ /* An inline `<svg>` is a replaced element: the subtree is vector art, not
110
+ * HTML boxes. Serialize it once, take the intrinsic size from the parsed
111
+ * document (width/height attributes, else the viewBox, else 300x150), and
112
+ * let paint/rasterisation handle the children — the normal box walk must
113
+ * not descend into them. */
114
+ box->is_inline_svg = true;
115
+ box->svg_markup = xt_inline_svg_serialize(node);
116
+ int width = 0;
117
+ int height = 0;
118
+ if (xt_svg_markup_size(box->svg_markup, &width, &height)) {
119
+ box->intrinsic_width = (float)width;
120
+ box->intrinsic_height = (float)height;
121
+ }
122
+ if (box->intrinsic_width <= 0.0f) box->intrinsic_width = 300.0f;
123
+ if (box->intrinsic_height <= 0.0f) box->intrinsic_height = 150.0f;
107
124
  }
125
+ if (box->is_inline_svg) return box;
108
126
  for (const std::unique_ptr<Node> &child : node->children) {
109
127
  std::unique_ptr<LayoutBox> childBox =
110
128
  buildBoxTree(child.get(), styles, style, media, canvases);
@@ -83,6 +83,13 @@ struct LayoutBox {
83
83
  bool is_canvas = false;
84
84
  uint64_t canvas_id = 0;
85
85
 
86
+ /* Inline `<svg>`: a replaced element whose pixels are the serialized subtree
87
+ * (`svg_markup`), rasterised like an SVG file at the drawn device size. The
88
+ * normal box walk does not descend into its children — they are vector art,
89
+ * not HTML boxes. */
90
+ bool is_inline_svg = false;
91
+ std::string svg_markup;
92
+
86
93
  Display display = Display::Inline;
87
94
 
88
95
  /* Resolved (px) box edges. */
@@ -44,7 +44,8 @@ float Layouter::verticalEdges(const LayoutBox *box) const {
44
44
  float Layouter::preferredContentWidth(const LayoutBox *box, float available) const {
45
45
  const XtStyle &style = *box->style;
46
46
  if (!isAuto(style.width)) return px(style.width, available, style.font_size, 0.0f);
47
- if ((box->is_image || box->is_canvas) && box->intrinsic_width > 0) return box->intrinsic_width;
47
+ if ((box->is_image || box->is_canvas || box->is_inline_svg) && box->intrinsic_width > 0)
48
+ return box->intrinsic_width;
48
49
  float best = 0;
49
50
  for (const std::unique_ptr<LayoutBox> &child : box->children) {
50
51
  if (child->display == Display::None) continue;
@@ -225,8 +226,8 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
225
226
  if (contentHeight < 0) contentHeight = 0;
226
227
  }
227
228
  /* Replaced elements keep their aspect ratio when only one axis is set. */
228
- if ((box->is_image || box->is_canvas) && forcedContentHeight < 0 && isAuto(style.height) &&
229
- box->intrinsic_width > 0 && box->intrinsic_height > 0) {
229
+ if ((box->is_image || box->is_canvas || box->is_inline_svg) && forcedContentHeight < 0 &&
230
+ isAuto(style.height) && box->intrinsic_width > 0 && box->intrinsic_height > 0) {
230
231
  contentHeight = contentWidth * box->intrinsic_height / box->intrinsic_width;
231
232
  }
232
233
  box->content_height = contentHeight;
@@ -187,6 +187,16 @@ void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
187
187
  image.media_id = box->media_id;
188
188
  image.transform = matrix;
189
189
  addImage(image, out, clip);
190
+ } else if (box->is_inline_svg && !box->svg_markup.empty() && box->content_width > 0.0f &&
191
+ box->content_height > 0.0f) {
192
+ PaintImage image;
193
+ image.x = box->content_x;
194
+ image.y = box->content_y;
195
+ image.width = box->content_width;
196
+ image.height = box->content_height;
197
+ image.svg = box->svg_markup;
198
+ image.transform = matrix;
199
+ addImage(image, out, clip);
190
200
  } else if (box->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
191
201
  box->content_height > 0.0f) {
192
202
  PaintImage image;
@@ -243,6 +253,10 @@ std::string DisplayList::dump() const {
243
253
  std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu",
244
254
  image.x, image.y, image.width, image.height,
245
255
  (unsigned long long)image.media_id);
256
+ } else if (!image.svg.empty()) {
257
+ std::snprintf(buffer, sizeof(buffer),
258
+ "image x=%.1f y=%.1f w=%.1f h=%.1f inline-svg=%zu", image.x, image.y,
259
+ image.width, image.height, image.svg.size());
246
260
  } else if (image.canvas_id != 0) {
247
261
  std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f canvas=%llu",
248
262
  image.x, image.y, image.width, image.height,
@@ -51,13 +51,17 @@ struct PaintText {
51
51
  /** An image to draw, scaled into the given rectangle (viewport-relative).
52
52
  * A non-zero `media_id` names a live `<video>` frame owned by the media
53
53
  * subsystem; a non-zero `canvas_id` names a live `<canvas>` bitmap owned by the
54
- * DOM; `src` is then empty and the renderer binds that subsystem's texture. */
54
+ * DOM; `src` is then empty and the renderer binds that subsystem's texture.
55
+ * A non-empty `svg` carries the serialized `<svg>` subtree of an inline SVG
56
+ * element, which the renderer rasterises at the drawn device size (there is no
57
+ * file to load). */
55
58
  struct PaintImage {
56
59
  float x = 0;
57
60
  float y = 0;
58
61
  float width = 0;
59
62
  float height = 0;
60
63
  std::string src;
64
+ std::string svg;
61
65
  uint64_t media_id = 0;
62
66
  uint64_t canvas_id = 0;
63
67
  Matrix2D transform;
@@ -12,6 +12,7 @@
12
12
  #include "canvas.h"
13
13
  #include "image.h"
14
14
  #include "media.h"
15
+ #include "svg.h"
15
16
 
16
17
  #include "renderer_shaders.h"
17
18
 
@@ -227,6 +228,32 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
227
228
  return pipeline;
228
229
  }
229
230
 
231
+ /** Create an RGBA texture holding `image` and upload its pixels. Returns null
232
+ * (after reporting the SDL error) when either step fails. */
233
+ SDL_GPUTexture *createImageTexture(SDL_GPUDevice *device, const xtgui::Image &image) {
234
+ SDL_GPUTextureCreateInfo info;
235
+ memset(&info, 0, sizeof(info));
236
+ info.type = SDL_GPU_TEXTURETYPE_2D;
237
+ info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
238
+ info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
239
+ info.width = (Uint32)image.width;
240
+ info.height = (Uint32)image.height;
241
+ info.layer_count_or_depth = 1;
242
+ info.num_levels = 1;
243
+ info.sample_count = SDL_GPU_SAMPLECOUNT_1;
244
+ SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &info);
245
+ if (texture == nullptr) {
246
+ fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
247
+ return nullptr;
248
+ }
249
+ uint32_t bytes = (uint32_t)(image.pixels.size());
250
+ if (!uploadPixels(device, texture, image.width, image.height, image.pixels.data(), bytes)) {
251
+ SDL_ReleaseGPUTexture(device, texture);
252
+ return nullptr;
253
+ }
254
+ return texture;
255
+ }
256
+
230
257
  /** Decode `src` (cached) and upload it as an RGBA texture. Vector sources are
231
258
  * rasterised at the drawn pixel size and cached by that size; bitmaps decode at
232
259
  * their intrinsic size and are cached by path. */
@@ -246,30 +273,29 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
246
273
  return nullptr;
247
274
  }
248
275
 
249
- SDL_GPUTextureCreateInfo info;
250
- memset(&info, 0, sizeof(info));
251
- info.type = SDL_GPU_TEXTURETYPE_2D;
252
- info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
253
- info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
254
- info.width = (Uint32)image->width;
255
- info.height = (Uint32)image->height;
256
- info.layer_count_or_depth = 1;
257
- info.num_levels = 1;
258
- info.sample_count = SDL_GPU_SAMPLECOUNT_1;
259
- SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &info);
260
- if (texture == nullptr) {
261
- fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
262
- g_image_textures.emplace(key, nullptr);
263
- return nullptr;
264
- }
276
+ SDL_GPUTexture *texture = createImageTexture(device, *image);
277
+ g_image_textures.emplace(key, texture);
278
+ return texture;
279
+ }
265
280
 
266
- uint32_t bytes = (uint32_t)(image->pixels.size());
267
- if (!uploadPixels(device, texture, image->width, image->height, image->pixels.data(), bytes)) {
268
- SDL_ReleaseGPUTexture(device, texture);
281
+ /** Rasterise the serialized subtree of an inline `<svg>` at the drawn pixel
282
+ * size and upload it. The markup is the cache key: a static document therefore
283
+ * rasterises once per drawn size, and an edited one gets a fresh texture. */
284
+ SDL_GPUTexture *ensureInlineSvgTexture(SDL_GPUDevice *device, const std::string &markup,
285
+ int pixel_width, int pixel_height) {
286
+ std::string key = "inline-svg@" + std::to_string(pixel_width) + "x" +
287
+ std::to_string(pixel_height) + ":" + markup;
288
+ auto found = g_image_textures.find(key);
289
+ if (found != g_image_textures.end()) return found->second;
290
+
291
+ xtgui::Image image;
292
+ if (!xtgui::xt_svg_markup_render(markup, pixel_width, pixel_height, &image)) {
293
+ fprintf(stderr, "xt_gui: could not rasterise inline SVG\n");
269
294
  g_image_textures.emplace(key, nullptr);
270
295
  return nullptr;
271
296
  }
272
297
 
298
+ SDL_GPUTexture *texture = createImageTexture(device, image);
273
299
  g_image_textures.emplace(key, texture);
274
300
  return texture;
275
301
  }
@@ -298,18 +324,20 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
298
324
  for (size_t index = 0; index < list.images.size(); index++) {
299
325
  const xtgui::PaintImage &image = list.images[index];
300
326
  if (image.width <= 0.0f || image.height <= 0.0f) continue;
327
+ /* Draw-time device pixels: an SVG (file or inline) is rasterised at this
328
+ * size so it stays crisp on HiDPI and when scaled. */
329
+ int pixel_width = (int)(image.width * scale + 0.5f);
330
+ int pixel_height = (int)(image.height * scale + 0.5f);
331
+ if (pixel_width < 1) pixel_width = 1;
332
+ if (pixel_height < 1) pixel_height = 1;
301
333
  SDL_GPUTexture *texture = nullptr;
302
334
  if (image.canvas_id != 0) {
303
335
  texture = ensureCanvasTexture(device, image.canvas_id);
304
336
  } else if (image.media_id != 0) {
305
337
  texture = ensureMediaTexture(device, image.media_id);
338
+ } else if (!image.svg.empty()) {
339
+ texture = ensureInlineSvgTexture(device, image.svg, pixel_width, pixel_height);
306
340
  } else {
307
- /* Draw-time device pixels: an SVG is rasterised at this size so it stays
308
- * crisp on HiDPI and when scaled. */
309
- int pixel_width = (int)(image.width * scale + 0.5f);
310
- int pixel_height = (int)(image.height * scale + 0.5f);
311
- if (pixel_width < 1) pixel_width = 1;
312
- if (pixel_height < 1) pixel_height = 1;
313
341
  texture = ensureImageTexture(device, image.src, pixel_width, pixel_height);
314
342
  }
315
343
  if (texture == nullptr) continue;
@@ -40,6 +40,9 @@ void xt_style_apply_user_agent(const Node *node, XtStyle *style) {
40
40
  style->display = Display::InlineBlock;
41
41
  }
42
42
  if (tag == "img") style->display = Display::InlineBlock;
43
+ /* An inline `<svg>` is a replaced inline-block too: its intrinsic size comes
44
+ * from the SVG document and its subtree is vector art, not HTML boxes. */
45
+ if (tag == "svg") style->display = Display::InlineBlock;
43
46
  /* `<video>` mirrors `<img>` (a replaced inline-block; 300x150 by default from
44
47
  * layout), while `<audio>` and its `<source>`/`<track>` children have no
45
48
  * rendered box of their own. */