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.
- package/AGENTS.md +5 -3
- package/dist/src/driver/compiler.js +1 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/node/fs/index.d.ts +4 -3
- package/dist/src/extensions/node/fs/index.js +12 -3
- package/dist/src/extensions/node/fs/index.js.map +1 -1
- package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
- package/dist/src/extensions/node/fs-promises/index.js +15 -6
- package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +1 -1
- package/dist/tests/e2e/async-fs.test.d.ts +9 -0
- package/dist/tests/e2e/async-fs.test.js +119 -0
- package/dist/tests/e2e/async-fs.test.js.map +1 -0
- package/dist/tests/e2e/fetch.test.js +24 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
- package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
- package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
- package/doc/ai/contributing.md +2 -1
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/language-support.md +2 -1
- package/doc/ai/zh-CN/contributing.md +1 -1
- package/doc/ai/zh-CN/extensions.md +4 -3
- package/doc/ai/zh-CN/language-support.md +2 -1
- package/doc/gui/api.md +8 -0
- package/doc/gui/known-issues.md +10 -9
- package/doc/gui/media.md +32 -3
- package/doc/gui.md +24 -2
- package/doc/implemented/async.md +16 -3
- package/doc/implemented/fetch.md +38 -6
- package/doc/implemented/node/fs-promises.md +19 -8
- package/doc/implemented/node.md +1 -1
- package/doc/implemented.md +1 -1
- package/doc/unimplemented/deviations.md +1 -1
- package/doc/unimplemented/node/async.md +6 -3
- package/doc/unimplemented/node/fs-promises.md +10 -6
- package/doc/unimplemented/node/fs.md +7 -8
- package/doc/unimplemented/node.md +2 -2
- package/doc/unimplemented/toolchain.md +1 -1
- package/doc/zh-CN/gui/api.md +7 -0
- package/doc/zh-CN/gui/known-issues.md +8 -7
- package/doc/zh-CN/gui/media.md +26 -2
- package/doc/zh-CN/gui.md +18 -1
- package/doc/zh-CN/implemented/async.md +13 -2
- package/doc/zh-CN/implemented/fetch.md +23 -4
- package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
- package/doc/zh-CN/implemented/node.md +1 -1
- package/doc/zh-CN/implemented.md +1 -1
- package/doc/zh-CN/unimplemented/deviations.md +1 -1
- package/doc/zh-CN/unimplemented/node/async.md +6 -3
- package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
- package/doc/zh-CN/unimplemented/node/fs.md +2 -2
- package/doc/zh-CN/unimplemented/node.md +2 -2
- package/doc/zh-CN/unimplemented/toolchain.md +1 -1
- package/package.json +1 -1
- package/runtime/ext_gui/inline_svg.cpp +219 -0
- package/runtime/ext_gui/inline_svg.h +41 -0
- package/runtime/ext_gui/layout.cpp +18 -0
- package/runtime/ext_gui/layout.h +7 -0
- package/runtime/ext_gui/layout_flow.cpp +4 -3
- package/runtime/ext_gui/paint.cpp +14 -0
- package/runtime/ext_gui/paint.h +5 -1
- package/runtime/ext_gui/renderer_image.cpp +53 -25
- package/runtime/ext_gui/style_ua.cpp +3 -0
- package/runtime/ext_gui/svg.cpp +122 -39
- package/runtime/ext_gui/svg.h +14 -3
- package/runtime/ext_gui/window.cpp +28 -0
- package/runtime/ext_node/fs/async_api.c +314 -0
- package/runtime/ext_node/fs/async_complete.c +99 -0
- package/runtime/ext_node/fs/async_fh.c +206 -0
- package/runtime/ext_node/fs/async_job.c +81 -0
- package/runtime/ext_node/fs/async_job.h +132 -0
- package/runtime/ext_node/fs/async_worker.c +534 -0
- package/runtime/ext_node/fs/async_worker_dir.c +439 -0
- package/runtime/ext_node/fs/async_worker_dir.h +33 -0
- package/runtime/ext_node/fs/async_worker_fd.c +74 -0
- package/runtime/ext_node/fs/dir.c +60 -15
- package/runtime/ext_node/fs/fs_common.h +9 -0
- package/runtime/ext_node/fs/fs_ops.c +18 -29
- package/runtime/ext_node/fs/parts/builtins.inc +34 -0
- package/runtime/ext_node/fs/parts/platform.inc +66 -0
- package/runtime/ext_node/fs/promises.c +44 -234
- package/runtime/rt_internal.h +2 -0
- package/runtime/xt_async_io.c +333 -0
- package/runtime/xt_async_io.h +47 -0
- package/runtime/xt_fetch/async.inc +295 -0
- package/runtime/xt_fetch/request.inc +6 -183
- package/runtime/xt_fetch/socket.inc +5 -4
- package/runtime/xt_fetch.c +6 -4
- package/runtime/xt_loop.c +44 -3
- package/src/driver/compiler.ts +1 -1
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/node/fs/index.ts +12 -3
- 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("&"); break;
|
|
134
|
+
case '<': out->append("<"); break;
|
|
135
|
+
case '>': out->append(">"); break;
|
|
136
|
+
case '"': out->append("""); 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);
|
package/runtime/ext_gui/layout.h
CHANGED
|
@@ -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)
|
|
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 &&
|
|
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,
|
package/runtime/ext_gui/paint.h
CHANGED
|
@@ -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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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. */
|