xbintsc 0.3.79 → 0.3.87
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/README.md +13 -5
- package/README.zh-CN.md +13 -6
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +5 -1
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +5 -2
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -0
- package/dist/src/codegen/generator/module.js +76 -6
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +5 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +7 -0
- package/dist/src/codegen/generator/tables.js +25 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +22 -3
- package/dist/src/driver/compiler.js +49 -58
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/extensions/gui/index.js +10 -2
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
- package/dist/tests/e2e/gui-example-svg.test.js +105 -0
- package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
- package/dist/tests/e2e/gui-transform.test.js +198 -0
- package/dist/tests/e2e/gui-transform.test.js.map +1 -0
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +7 -0
- package/doc/ai/contributing.md +7 -1
- package/doc/ai/extensions.md +5 -1
- package/doc/ai/language-support.md +4 -4
- package/doc/ai/troubleshooting.md +39 -2
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +6 -2
- package/doc/ai/zh-CN/extensions.md +5 -1
- package/doc/ai/zh-CN/language-support.md +4 -4
- package/doc/ai/zh-CN/troubleshooting.md +35 -2
- package/doc/gui/animation.md +53 -2
- package/doc/gui/api.md +1 -1
- package/doc/gui/dom.md +6 -1
- package/doc/gui/input.md +4 -1
- package/doc/gui/known-issues.md +16 -4
- package/doc/gui/layout.md +36 -0
- package/doc/gui/rendering.md +15 -0
- package/doc/gui.md +32 -3
- package/doc/implemented.md +4 -4
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/gui/animation.md +49 -2
- package/doc/zh-CN/gui/api.md +1 -1
- package/doc/zh-CN/gui/dom.md +6 -1
- package/doc/zh-CN/gui/input.md +2 -0
- package/doc/zh-CN/gui/known-issues.md +12 -2
- package/doc/zh-CN/gui/layout.md +31 -0
- package/doc/zh-CN/gui/rendering.md +11 -0
- package/doc/zh-CN/gui.md +28 -3
- package/doc/zh-CN/implemented.md +4 -4
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +10 -9
- package/package.json +3 -1
- package/runtime/ext_gui/document.cpp +21 -3
- package/runtime/ext_gui/document.h +4 -1
- package/runtime/ext_gui/dom_api_node.cpp +6 -4
- package/runtime/ext_gui/layout.cpp +78 -1
- package/runtime/ext_gui/layout.h +21 -0
- package/runtime/ext_gui/paint.cpp +48 -15
- package/runtime/ext_gui/paint.h +8 -2
- package/runtime/ext_gui/renderer.cpp +4 -2
- package/runtime/ext_gui/renderer_image.cpp +2 -1
- package/runtime/ext_gui/renderer_internal.h +6 -1
- package/runtime/ext_gui/renderer_text.cpp +37 -15
- package/runtime/ext_gui/style.h +10 -0
- package/runtime/ext_gui/style_print.cpp +6 -0
- package/runtime/ext_gui/style_values.cpp +43 -0
- package/runtime/ext_gui/transform.cpp +412 -0
- package/runtime/ext_gui/transform.h +112 -0
- package/runtime/ext_gui/window.cpp +6 -4
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +17 -1
- package/runtime/rt_internal.h +47 -4
- package/runtime/xt_alloc.c +14 -2
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +136 -14
- package/runtime/xt_containers/access.inc +17 -16
- package/runtime/xt_containers/objects.inc +4 -3
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +15 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +2 -2
- package/runtime/xt_stdlib/strings.inc +33 -21
- package/runtime/xt_stdlib2/array-statics.inc +2 -1
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +22 -6
- package/runtime/xt_stdlib2/json.inc +3 -2
- package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
- package/runtime/xt_stdlib2/regexp-match.inc +48 -33
- package/runtime/xt_stdlib2/strings.inc +23 -14
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +10 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +78 -4
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/invocation.ts +5 -0
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +5 -2
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +78 -6
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +22 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -1
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +26 -0
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +68 -56
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/extensions/gui/index.ts +10 -2
- package/src/parser/expressions.ts +20 -1
|
@@ -441,6 +441,49 @@ void xt_style_apply_declaration(XtStyle *style, const std::string &property,
|
|
|
441
441
|
return;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
if (property == "transform") {
|
|
445
|
+
std::vector<TransformOp> ops;
|
|
446
|
+
if (xt_transform_parse(value, &ops)) style->transform = ops;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (property == "transform-origin") {
|
|
450
|
+
/* One or two values plus an optional z offset (ignored: the engine is 2D).
|
|
451
|
+
* The keywords carry an axis, so `top` alone means `center top` and
|
|
452
|
+
* `top center` is the same as `center top`. */
|
|
453
|
+
std::vector<std::string> parts = xt_css_split_whitespace(value);
|
|
454
|
+
Length values[2] = {Length{50.0f, Unit::Percent}, Length{50.0f, Unit::Percent}};
|
|
455
|
+
bool vertical[2] = {false, false};
|
|
456
|
+
size_t used = 0;
|
|
457
|
+
for (const std::string &part : parts) {
|
|
458
|
+
if (used >= 2) break;
|
|
459
|
+
std::string keyword = xt_css_lower(part);
|
|
460
|
+
Length length = xt_css_parse_length(keyword);
|
|
461
|
+
if (length.unit == Unit::Invalid || length.unit == Unit::Auto || length.unit == Unit::None) {
|
|
462
|
+
if (keyword == "left" || keyword == "top") length = {0.0f, Unit::Percent};
|
|
463
|
+
else if (keyword == "center") length = {50.0f, Unit::Percent};
|
|
464
|
+
else if (keyword == "right" || keyword == "bottom") length = {100.0f, Unit::Percent};
|
|
465
|
+
else return;
|
|
466
|
+
vertical[used] = keyword == "top" || keyword == "bottom";
|
|
467
|
+
}
|
|
468
|
+
values[used++] = length;
|
|
469
|
+
}
|
|
470
|
+
if (used == 0) return;
|
|
471
|
+
Length x = values[0];
|
|
472
|
+
Length y = used > 1 ? values[1] : Length{50.0f, Unit::Percent};
|
|
473
|
+
if (used > 1) {
|
|
474
|
+
if (vertical[0] && !vertical[1]) {
|
|
475
|
+
x = values[1];
|
|
476
|
+
y = values[0];
|
|
477
|
+
}
|
|
478
|
+
} else if (vertical[0]) {
|
|
479
|
+
/* `top`/`bottom` on their own keep x centred. */
|
|
480
|
+
x = Length{50.0f, Unit::Percent};
|
|
481
|
+
y = values[0];
|
|
482
|
+
}
|
|
483
|
+
style->transform_origin_x = x;
|
|
484
|
+
style->transform_origin_y = y;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
444
487
|
if (property == "opacity") {
|
|
445
488
|
float number = 0;
|
|
446
489
|
if (xt_css_parse_number(value, &number)) style->opacity = number < 0 ? 0 : (number > 1 ? 1 : number);
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS `transform`/`transform-origin` parsing, composition
|
|
3
|
+
* and interpolation.
|
|
4
|
+
*
|
|
5
|
+
* `style.cpp`/`style_values.cpp` call `xt_transform_parse` to turn a declaration
|
|
6
|
+
* value into a list of `TransformOp`s; `layout.cpp` asks `xt_transform_matrix`
|
|
7
|
+
* for the composition once it knows the box size; `document.cpp` asks
|
|
8
|
+
* `xt_transform_interpolate` while a transition runs; `style_print.cpp` asks
|
|
9
|
+
* `xt_transform_to_string` for `getComputedStyle`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include "transform.h"
|
|
13
|
+
|
|
14
|
+
#include <cctype>
|
|
15
|
+
#include <cmath>
|
|
16
|
+
#include <cstdlib>
|
|
17
|
+
|
|
18
|
+
#include "css_internal.h"
|
|
19
|
+
#include "style_internal.h"
|
|
20
|
+
|
|
21
|
+
namespace xtgui {
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
constexpr float kPi = 3.14159265358979323846f;
|
|
26
|
+
/** `rem` units inside a transform resolve against the initial font size; the
|
|
27
|
+
* computed style carries `font-size` in pixels, so the root size is not
|
|
28
|
+
* available here (layout only has the element's own style). */
|
|
29
|
+
constexpr float kRootFontSize = 16.0f;
|
|
30
|
+
|
|
31
|
+
bool isSpace(char c) { return xt_is_css_space(c); }
|
|
32
|
+
|
|
33
|
+
std::string trim(const std::string &text) { return xt_css_trim(text); }
|
|
34
|
+
|
|
35
|
+
/** Split `a, b` / `a b` / `a,b` into its arguments. */
|
|
36
|
+
std::vector<std::string> splitArgs(const std::string &text) {
|
|
37
|
+
std::vector<std::string> parts;
|
|
38
|
+
size_t index = 0;
|
|
39
|
+
while (index < text.size()) {
|
|
40
|
+
while (index < text.size() && (isSpace(text[index]) || text[index] == ',')) index++;
|
|
41
|
+
size_t start = index;
|
|
42
|
+
while (index < text.size() && !isSpace(text[index]) && text[index] != ',') index++;
|
|
43
|
+
if (index > start) parts.push_back(text.substr(start, index - start));
|
|
44
|
+
}
|
|
45
|
+
return parts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
bool parseLengthArg(const std::string &token, Length *out) {
|
|
49
|
+
Length length = xt_css_parse_length(token);
|
|
50
|
+
if (length.unit == Unit::Invalid || length.unit == Unit::Auto || length.unit == Unit::None) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
*out = length;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A unitless number, optionally a percentage (which is divided by 100). */
|
|
58
|
+
bool parseFactorArg(const std::string &raw, float *out) {
|
|
59
|
+
std::string token = trim(raw);
|
|
60
|
+
if (token.empty()) return false;
|
|
61
|
+
bool percent = token.back() == '%';
|
|
62
|
+
if (percent) token.pop_back();
|
|
63
|
+
float number = 0;
|
|
64
|
+
if (!xt_css_parse_number(token, &number)) return false;
|
|
65
|
+
*out = percent ? number / 100.0f : number;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
bool parseAngleArg(const std::string &raw, float *out) {
|
|
70
|
+
std::string token = xt_css_lower(trim(raw));
|
|
71
|
+
if (token.empty()) return false;
|
|
72
|
+
char *end = nullptr;
|
|
73
|
+
double number = std::strtod(token.c_str(), &end);
|
|
74
|
+
if (end == token.c_str()) return false;
|
|
75
|
+
std::string unit = trim(std::string(end));
|
|
76
|
+
float radians = 0;
|
|
77
|
+
if (unit.empty() || unit == "deg") radians = (float)number * kPi / 180.0f;
|
|
78
|
+
else if (unit == "rad") radians = (float)number;
|
|
79
|
+
else if (unit == "grad") radians = (float)number * kPi / 200.0f;
|
|
80
|
+
else if (unit == "turn") radians = (float)number * 2.0f * kPi;
|
|
81
|
+
else return false;
|
|
82
|
+
*out = radians;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Translate one parsed function call into a `TransformOp`. */
|
|
87
|
+
bool applyFunction(const std::string &name, const std::vector<std::string> &args,
|
|
88
|
+
TransformOp *op) {
|
|
89
|
+
if (name == "translate" || name == "translate3d") {
|
|
90
|
+
/* The 3d spelling takes a `z` we parse and drop: the engine is 2D. */
|
|
91
|
+
size_t most = name == "translate3d" ? 3 : 2;
|
|
92
|
+
if (args.empty() || args.size() > most) return false;
|
|
93
|
+
op->kind = TransformKind::Translate;
|
|
94
|
+
if (!parseLengthArg(args[0], &op->x)) return false;
|
|
95
|
+
op->y = Length{0.0f, Unit::Px};
|
|
96
|
+
if (args.size() >= 2 && !parseLengthArg(args[1], &op->y)) return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (name == "translatex") {
|
|
100
|
+
if (args.size() != 1) return false;
|
|
101
|
+
op->kind = TransformKind::TranslateX;
|
|
102
|
+
op->y = Length{0.0f, Unit::Px};
|
|
103
|
+
return parseLengthArg(args[0], &op->x);
|
|
104
|
+
}
|
|
105
|
+
if (name == "translatey") {
|
|
106
|
+
if (args.size() != 1) return false;
|
|
107
|
+
op->kind = TransformKind::TranslateY;
|
|
108
|
+
op->x = Length{0.0f, Unit::Px};
|
|
109
|
+
return parseLengthArg(args[0], &op->y);
|
|
110
|
+
}
|
|
111
|
+
if (name == "scale" || name == "scale3d") {
|
|
112
|
+
size_t most = name == "scale3d" ? 3 : 2;
|
|
113
|
+
if (args.empty() || args.size() > most) return false;
|
|
114
|
+
op->kind = TransformKind::Scale;
|
|
115
|
+
if (!parseFactorArg(args[0], &op->scale_x)) return false;
|
|
116
|
+
op->scale_y = op->scale_x;
|
|
117
|
+
if (args.size() >= 2 && !parseFactorArg(args[1], &op->scale_y)) return false;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (name == "scalex") {
|
|
121
|
+
if (args.size() != 1) return false;
|
|
122
|
+
op->kind = TransformKind::ScaleX;
|
|
123
|
+
op->scale_y = 1.0f;
|
|
124
|
+
return parseFactorArg(args[0], &op->scale_x);
|
|
125
|
+
}
|
|
126
|
+
if (name == "scaley") {
|
|
127
|
+
if (args.size() != 1) return false;
|
|
128
|
+
op->kind = TransformKind::ScaleY;
|
|
129
|
+
op->scale_x = 1.0f;
|
|
130
|
+
return parseFactorArg(args[0], &op->scale_y);
|
|
131
|
+
}
|
|
132
|
+
if (name == "rotate" || name == "rotatez") {
|
|
133
|
+
if (args.size() != 1) return false;
|
|
134
|
+
op->kind = TransformKind::Rotate;
|
|
135
|
+
return parseAngleArg(args[0], &op->angle);
|
|
136
|
+
}
|
|
137
|
+
if (name == "matrix") {
|
|
138
|
+
if (args.size() != 6) return false;
|
|
139
|
+
op->kind = TransformKind::Matrix;
|
|
140
|
+
for (size_t i = 0; i < 6; i++) {
|
|
141
|
+
if (!xt_css_parse_number(args[i], &op->matrix[i])) return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false; // skew/perspective/… are not supported yet
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
float resolveLength(const Length &length, float base, float em, float vw, float vh) {
|
|
149
|
+
return xt_style_resolve_absolute(length, base, em, kRootFontSize, vw, vh);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bool sameLength(const Length &left, const Length &right) {
|
|
153
|
+
return left.unit == right.unit && left.value == right.value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
bool sameOp(const TransformOp &left, const TransformOp &right) {
|
|
157
|
+
if (left.kind != right.kind) return false;
|
|
158
|
+
switch (left.kind) {
|
|
159
|
+
case TransformKind::Translate:
|
|
160
|
+
return sameLength(left.x, right.x) && sameLength(left.y, right.y);
|
|
161
|
+
case TransformKind::TranslateX:
|
|
162
|
+
return sameLength(left.x, right.x);
|
|
163
|
+
case TransformKind::TranslateY:
|
|
164
|
+
return sameLength(left.y, right.y);
|
|
165
|
+
case TransformKind::Scale:
|
|
166
|
+
return left.scale_x == right.scale_x && left.scale_y == right.scale_y;
|
|
167
|
+
case TransformKind::ScaleX:
|
|
168
|
+
return left.scale_x == right.scale_x;
|
|
169
|
+
case TransformKind::ScaleY:
|
|
170
|
+
return left.scale_y == right.scale_y;
|
|
171
|
+
case TransformKind::Rotate:
|
|
172
|
+
return left.angle == right.angle;
|
|
173
|
+
case TransformKind::Matrix:
|
|
174
|
+
for (int i = 0; i < 6; i++) {
|
|
175
|
+
if (left.matrix[i] != right.matrix[i]) return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
bool mixLength(const Length &from, const Length &to, float t, Length *out) {
|
|
183
|
+
if (from.unit != to.unit) return false;
|
|
184
|
+
out->unit = from.unit;
|
|
185
|
+
out->value = from.value + (to.value - from.value) * t;
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
} // namespace
|
|
190
|
+
|
|
191
|
+
Matrix2D Matrix2D::rotate(float radians) {
|
|
192
|
+
Matrix2D out;
|
|
193
|
+
float cosine = std::cos(radians);
|
|
194
|
+
float sine = std::sin(radians);
|
|
195
|
+
out.a = cosine;
|
|
196
|
+
out.b = sine;
|
|
197
|
+
out.c = -sine;
|
|
198
|
+
out.d = cosine;
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Matrix2D Matrix2D::multiply(const Matrix2D &other) const {
|
|
203
|
+
Matrix2D out;
|
|
204
|
+
out.a = a * other.a + c * other.b;
|
|
205
|
+
out.b = b * other.a + d * other.b;
|
|
206
|
+
out.c = a * other.c + c * other.d;
|
|
207
|
+
out.d = b * other.c + d * other.d;
|
|
208
|
+
out.e = a * other.e + c * other.f + e;
|
|
209
|
+
out.f = b * other.e + d * other.f + f;
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
bool Matrix2D::invert(Matrix2D *out) const {
|
|
214
|
+
float determinant = a * d - b * c;
|
|
215
|
+
if (std::fabs(determinant) < 1e-9f) return false;
|
|
216
|
+
float inverse = 1.0f / determinant;
|
|
217
|
+
Matrix2D result;
|
|
218
|
+
result.a = d * inverse;
|
|
219
|
+
result.b = -b * inverse;
|
|
220
|
+
result.c = -c * inverse;
|
|
221
|
+
result.d = a * inverse;
|
|
222
|
+
result.e = -(result.a * e + result.c * f);
|
|
223
|
+
result.f = -(result.b * e + result.d * f);
|
|
224
|
+
*out = result;
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
float Matrix2D::magnification() const {
|
|
229
|
+
float determinant = std::fabs(a * d - b * c);
|
|
230
|
+
return std::sqrt(determinant);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bool xt_transform_parse(const std::string &value, std::vector<TransformOp> *out) {
|
|
234
|
+
out->clear();
|
|
235
|
+
std::string text = trim(value);
|
|
236
|
+
std::string lowered = xt_css_lower(text);
|
|
237
|
+
if (lowered.empty() || lowered == "none") return true;
|
|
238
|
+
|
|
239
|
+
size_t pos = 0;
|
|
240
|
+
while (pos < text.size()) {
|
|
241
|
+
while (pos < text.size() && isSpace(text[pos])) pos++;
|
|
242
|
+
if (pos >= text.size()) break;
|
|
243
|
+
size_t name_start = pos;
|
|
244
|
+
/* A function name may carry digits: `translate3d`, `scale3d`, `matrix3d`. */
|
|
245
|
+
while (pos < text.size() && (std::isalnum((unsigned char)text[pos]) || text[pos] == '-')) pos++;
|
|
246
|
+
if (pos == name_start) return false;
|
|
247
|
+
std::string name = xt_css_lower(text.substr(name_start, pos - name_start));
|
|
248
|
+
while (pos < text.size() && isSpace(text[pos])) pos++;
|
|
249
|
+
if (pos >= text.size() || text[pos] != '(') return false;
|
|
250
|
+
pos++;
|
|
251
|
+
size_t args_start = pos;
|
|
252
|
+
int depth = 1;
|
|
253
|
+
while (pos < text.size() && depth > 0) {
|
|
254
|
+
if (text[pos] == '(') depth++;
|
|
255
|
+
else if (text[pos] == ')') depth--;
|
|
256
|
+
pos++;
|
|
257
|
+
}
|
|
258
|
+
if (depth != 0) return false;
|
|
259
|
+
std::string inner = text.substr(args_start, pos - 1 - args_start);
|
|
260
|
+
TransformOp op;
|
|
261
|
+
if (!applyFunction(name, splitArgs(inner), &op)) return false;
|
|
262
|
+
out->push_back(op);
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
bool xt_transform_equal(const std::vector<TransformOp> &left,
|
|
268
|
+
const std::vector<TransformOp> &right) {
|
|
269
|
+
if (left.size() != right.size()) return false;
|
|
270
|
+
for (size_t i = 0; i < left.size(); i++) {
|
|
271
|
+
if (!sameOp(left[i], right[i])) return false;
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
bool xt_transform_interpolate(const std::vector<TransformOp> &from,
|
|
277
|
+
const std::vector<TransformOp> &to, float t,
|
|
278
|
+
std::vector<TransformOp> *out) {
|
|
279
|
+
if (from.size() != to.size()) return false;
|
|
280
|
+
std::vector<TransformOp> mixed(from.size());
|
|
281
|
+
for (size_t i = 0; i < from.size(); i++) {
|
|
282
|
+
const TransformOp &left = from[i];
|
|
283
|
+
const TransformOp &right = to[i];
|
|
284
|
+
if (left.kind != right.kind) return false;
|
|
285
|
+
TransformOp op;
|
|
286
|
+
op.kind = left.kind;
|
|
287
|
+
switch (left.kind) {
|
|
288
|
+
case TransformKind::Translate:
|
|
289
|
+
if (!mixLength(left.x, right.x, t, &op.x)) return false;
|
|
290
|
+
if (!mixLength(left.y, right.y, t, &op.y)) return false;
|
|
291
|
+
break;
|
|
292
|
+
case TransformKind::TranslateX:
|
|
293
|
+
if (!mixLength(left.x, right.x, t, &op.x)) return false;
|
|
294
|
+
break;
|
|
295
|
+
case TransformKind::TranslateY:
|
|
296
|
+
if (!mixLength(left.y, right.y, t, &op.y)) return false;
|
|
297
|
+
break;
|
|
298
|
+
case TransformKind::Scale:
|
|
299
|
+
op.scale_x = left.scale_x + (right.scale_x - left.scale_x) * t;
|
|
300
|
+
op.scale_y = left.scale_y + (right.scale_y - left.scale_y) * t;
|
|
301
|
+
break;
|
|
302
|
+
case TransformKind::ScaleX:
|
|
303
|
+
op.scale_x = left.scale_x + (right.scale_x - left.scale_x) * t;
|
|
304
|
+
break;
|
|
305
|
+
case TransformKind::ScaleY:
|
|
306
|
+
op.scale_y = left.scale_y + (right.scale_y - left.scale_y) * t;
|
|
307
|
+
break;
|
|
308
|
+
case TransformKind::Rotate:
|
|
309
|
+
op.angle = left.angle + (right.angle - left.angle) * t;
|
|
310
|
+
break;
|
|
311
|
+
case TransformKind::Matrix:
|
|
312
|
+
for (int entry = 0; entry < 6; entry++) {
|
|
313
|
+
op.matrix[entry] = left.matrix[entry] + (right.matrix[entry] - left.matrix[entry]) * t;
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
mixed[i] = op;
|
|
318
|
+
}
|
|
319
|
+
*out = mixed;
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
Matrix2D xt_transform_matrix(const std::vector<TransformOp> &ops, float percent_width,
|
|
324
|
+
float percent_height, float em, float viewport_width,
|
|
325
|
+
float viewport_height) {
|
|
326
|
+
Matrix2D result;
|
|
327
|
+
for (const TransformOp &op : ops) {
|
|
328
|
+
Matrix2D step;
|
|
329
|
+
switch (op.kind) {
|
|
330
|
+
case TransformKind::Translate:
|
|
331
|
+
step.e = resolveLength(op.x, percent_width, em, viewport_width, viewport_height);
|
|
332
|
+
step.f = resolveLength(op.y, percent_height, em, viewport_width, viewport_height);
|
|
333
|
+
break;
|
|
334
|
+
case TransformKind::TranslateX:
|
|
335
|
+
step.e = resolveLength(op.x, percent_width, em, viewport_width, viewport_height);
|
|
336
|
+
break;
|
|
337
|
+
case TransformKind::TranslateY:
|
|
338
|
+
step.f = resolveLength(op.y, percent_height, em, viewport_width, viewport_height);
|
|
339
|
+
break;
|
|
340
|
+
case TransformKind::Scale:
|
|
341
|
+
step = Matrix2D::scale(op.scale_x, op.scale_y);
|
|
342
|
+
break;
|
|
343
|
+
case TransformKind::ScaleX:
|
|
344
|
+
step = Matrix2D::scale(op.scale_x, 1.0f);
|
|
345
|
+
break;
|
|
346
|
+
case TransformKind::ScaleY:
|
|
347
|
+
step = Matrix2D::scale(1.0f, op.scale_y);
|
|
348
|
+
break;
|
|
349
|
+
case TransformKind::Rotate:
|
|
350
|
+
step = Matrix2D::rotate(op.angle);
|
|
351
|
+
break;
|
|
352
|
+
case TransformKind::Matrix:
|
|
353
|
+
step.a = op.matrix[0];
|
|
354
|
+
step.b = op.matrix[1];
|
|
355
|
+
step.c = op.matrix[2];
|
|
356
|
+
step.d = op.matrix[3];
|
|
357
|
+
step.e = op.matrix[4];
|
|
358
|
+
step.f = op.matrix[5];
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
result = result.multiply(step);
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
std::string xt_transform_to_string(const std::vector<TransformOp> &ops) {
|
|
367
|
+
if (ops.empty()) return "none";
|
|
368
|
+
std::string text;
|
|
369
|
+
for (size_t i = 0; i < ops.size(); i++) {
|
|
370
|
+
if (i > 0) text += " ";
|
|
371
|
+
const TransformOp &op = ops[i];
|
|
372
|
+
switch (op.kind) {
|
|
373
|
+
case TransformKind::Translate:
|
|
374
|
+
text += "translate(" + xt_style_length_to_string(op.x) + ", " +
|
|
375
|
+
xt_style_length_to_string(op.y) + ")";
|
|
376
|
+
break;
|
|
377
|
+
case TransformKind::TranslateX:
|
|
378
|
+
text += "translateX(" + xt_style_length_to_string(op.x) + ")";
|
|
379
|
+
break;
|
|
380
|
+
case TransformKind::TranslateY:
|
|
381
|
+
text += "translateY(" + xt_style_length_to_string(op.y) + ")";
|
|
382
|
+
break;
|
|
383
|
+
case TransformKind::Scale:
|
|
384
|
+
text += "scale(" + xt_style_length_to_string(Length{op.scale_x, Unit::Number}) + ", " +
|
|
385
|
+
xt_style_length_to_string(Length{op.scale_y, Unit::Number}) + ")";
|
|
386
|
+
break;
|
|
387
|
+
case TransformKind::ScaleX:
|
|
388
|
+
text += "scaleX(" + xt_style_length_to_string(Length{op.scale_x, Unit::Number}) + ")";
|
|
389
|
+
break;
|
|
390
|
+
case TransformKind::ScaleY:
|
|
391
|
+
text += "scaleY(" + xt_style_length_to_string(Length{op.scale_y, Unit::Number}) + ")";
|
|
392
|
+
break;
|
|
393
|
+
case TransformKind::Rotate: {
|
|
394
|
+
char buffer[64];
|
|
395
|
+
std::snprintf(buffer, sizeof(buffer), "rotate(%.4gdeg)", (double)(op.angle * 180.0f / kPi));
|
|
396
|
+
text += buffer;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case TransformKind::Matrix: {
|
|
400
|
+
char buffer[160];
|
|
401
|
+
std::snprintf(buffer, sizeof(buffer), "matrix(%.6g, %.6g, %.6g, %.6g, %.6g, %.6g)",
|
|
402
|
+
(double)op.matrix[0], (double)op.matrix[1], (double)op.matrix[2],
|
|
403
|
+
(double)op.matrix[3], (double)op.matrix[4], (double)op.matrix[5]);
|
|
404
|
+
text += buffer;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return text;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
} // namespace xtgui
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS `transform` and `transform-origin`.
|
|
3
|
+
*
|
|
4
|
+
* A `transform` declaration is parsed into a list of `TransformOp`s (the
|
|
5
|
+
* familiar `translate(...) rotate(...) scale(...)` chain, plus `matrix(...)`).
|
|
6
|
+
* Lengths stay unresolved in the list, because percentages and `em`/`vw`
|
|
7
|
+
* units need the box that carries them — which only layout knows. Layout
|
|
8
|
+
* therefore resolves the list into a `Matrix2D`, and paint, hit testing and
|
|
9
|
+
* `getBoundingClientRect` all consume that one matrix.
|
|
10
|
+
*
|
|
11
|
+
* Transforms are a *paint-time* effect: they never change layout geometry, so
|
|
12
|
+
* a transform cannot move a sibling, and it must be composed down the subtree
|
|
13
|
+
* (a child of a transformed box is transformed too).
|
|
14
|
+
*/
|
|
15
|
+
#ifndef XT_GUI_TRANSFORM_H
|
|
16
|
+
#define XT_GUI_TRANSFORM_H
|
|
17
|
+
|
|
18
|
+
#include <string>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
#include "css.h"
|
|
22
|
+
|
|
23
|
+
namespace xtgui {
|
|
24
|
+
|
|
25
|
+
/** A 2D affine transform laid out like the DOM `DOMMatrix` (a b c d e f):
|
|
26
|
+
* `x' = a*x + c*y + e`, `y' = b*x + d*y + f`. */
|
|
27
|
+
struct Matrix2D {
|
|
28
|
+
float a = 1.0f, b = 0.0f, c = 0.0f, d = 1.0f, e = 0.0f, f = 0.0f;
|
|
29
|
+
|
|
30
|
+
bool identity() const {
|
|
31
|
+
return a == 1.0f && b == 0.0f && c == 0.0f && d == 1.0f && e == 0.0f && f == 0.0f;
|
|
32
|
+
}
|
|
33
|
+
/** True when the transform keeps axis-aligned rectangles axis aligned (no
|
|
34
|
+
* rotation or skew). */
|
|
35
|
+
bool axis_aligned() const { return b == 0.0f && c == 0.0f; }
|
|
36
|
+
void apply(float x, float y, float *out_x, float *out_y) const {
|
|
37
|
+
*out_x = a * x + c * y + e;
|
|
38
|
+
*out_y = b * x + d * y + f;
|
|
39
|
+
}
|
|
40
|
+
/** `this * other`: the point is put through `other` first, then `this`. */
|
|
41
|
+
Matrix2D multiply(const Matrix2D &other) const;
|
|
42
|
+
/** The inverse transform, or false for a singular matrix (scale 0). */
|
|
43
|
+
bool invert(Matrix2D *out) const;
|
|
44
|
+
/** Approximate uniform magnification (geometric mean of the axis scales). */
|
|
45
|
+
float magnification() const;
|
|
46
|
+
|
|
47
|
+
static Matrix2D translate(float x, float y) {
|
|
48
|
+
Matrix2D out;
|
|
49
|
+
out.e = x;
|
|
50
|
+
out.f = y;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
static Matrix2D scale(float x, float y) {
|
|
54
|
+
Matrix2D out;
|
|
55
|
+
out.a = x;
|
|
56
|
+
out.d = y;
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
static Matrix2D rotate(float radians);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
enum class TransformKind {
|
|
63
|
+
Translate,
|
|
64
|
+
TranslateX,
|
|
65
|
+
TranslateY,
|
|
66
|
+
Scale,
|
|
67
|
+
ScaleX,
|
|
68
|
+
ScaleY,
|
|
69
|
+
Rotate,
|
|
70
|
+
Matrix,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** One function in a parsed `transform` list. Only the fields its `kind` uses
|
|
74
|
+
* carry a value; the rest stay at their neutral defaults. */
|
|
75
|
+
struct TransformOp {
|
|
76
|
+
TransformKind kind = TransformKind::Translate;
|
|
77
|
+
Length x; // translate x; `%` resolves against the box width
|
|
78
|
+
Length y; // translate y; `%` resolves against the box height
|
|
79
|
+
float scale_x = 1.0f; // scale x (a unitless factor)
|
|
80
|
+
float scale_y = 1.0f; // scale y
|
|
81
|
+
float angle = 0.0f; // rotate, in radians
|
|
82
|
+
float matrix[6] = {1, 0, 0, 1, 0, 0}; // a b c d e f
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Parse a `transform` value. `none` (or an empty list) yields an empty vector.
|
|
86
|
+
* An unsupported function or a malformed argument returns false, which makes
|
|
87
|
+
* the whole declaration invalid — exactly what CSS does. */
|
|
88
|
+
bool xt_transform_parse(const std::string &value, std::vector<TransformOp> *out);
|
|
89
|
+
|
|
90
|
+
/** Structural equality (used to decide whether a transition is needed). */
|
|
91
|
+
bool xt_transform_equal(const std::vector<TransformOp> &left,
|
|
92
|
+
const std::vector<TransformOp> &right);
|
|
93
|
+
|
|
94
|
+
/** Interpolate two lists of the same shape (same kinds, compatible units) at
|
|
95
|
+
* progress `t` in [0, 1]. Returns false when the lists cannot be mixed, in
|
|
96
|
+
* which case the caller should snap to the target. */
|
|
97
|
+
bool xt_transform_interpolate(const std::vector<TransformOp> &from,
|
|
98
|
+
const std::vector<TransformOp> &to, float t,
|
|
99
|
+
std::vector<TransformOp> *out);
|
|
100
|
+
|
|
101
|
+
/** Compose a `transform` list into one matrix. `percent_width`/`percent_height`
|
|
102
|
+
* are the element's border-box size (the base for `%`), `em` its font size. */
|
|
103
|
+
Matrix2D xt_transform_matrix(const std::vector<TransformOp> &ops, float percent_width,
|
|
104
|
+
float percent_height, float em, float viewport_width,
|
|
105
|
+
float viewport_height);
|
|
106
|
+
|
|
107
|
+
/** Canonical function-list spelling, for `getComputedStyle(el, "transform")`. */
|
|
108
|
+
std::string xt_transform_to_string(const std::vector<TransformOp> &ops);
|
|
109
|
+
|
|
110
|
+
} // namespace xtgui
|
|
111
|
+
|
|
112
|
+
#endif /* XT_GUI_TRANSFORM_H */
|
|
@@ -147,11 +147,13 @@ static xt_value win_get_bounding_rect(xt_value self, xt_value env, int32_t argc,
|
|
|
147
147
|
if (node == nullptr) return XT_UNDEFINED;
|
|
148
148
|
const xtgui::LayoutBox *box = win->document->boxOf(node);
|
|
149
149
|
if (box == nullptr) return XT_UNDEFINED;
|
|
150
|
+
/* Like the DOM: the rectangle of the *transformed* border box. */
|
|
151
|
+
xtgui::LayoutRect rect = xtgui::xt_layout_transformed_bounds(box);
|
|
150
152
|
xt_value object = xt_object_new();
|
|
151
|
-
xt_object_set(object, xt_string_from_cstr("x"), xt_number(
|
|
152
|
-
xt_object_set(object, xt_string_from_cstr("y"), xt_number(
|
|
153
|
-
xt_object_set(object, xt_string_from_cstr("width"), xt_number(
|
|
154
|
-
xt_object_set(object, xt_string_from_cstr("height"), xt_number(
|
|
153
|
+
xt_object_set(object, xt_string_from_cstr("x"), xt_number(rect.x));
|
|
154
|
+
xt_object_set(object, xt_string_from_cstr("y"), xt_number(rect.y));
|
|
155
|
+
xt_object_set(object, xt_string_from_cstr("width"), xt_number(rect.width));
|
|
156
|
+
xt_object_set(object, xt_string_from_cstr("height"), xt_number(rect.height));
|
|
155
157
|
return object;
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -25,7 +25,7 @@ static xt_value xt_buffer_from(xt_value value, const char *encoding) {
|
|
|
25
25
|
xt_value text = xt_to_string(value);
|
|
26
26
|
xt_string *string = xt_as_string(text);
|
|
27
27
|
size_t decoded = 0;
|
|
28
|
-
unsigned char *bytes = xt_node_decode(string
|
|
28
|
+
unsigned char *bytes = xt_node_decode(xt_string_bytes(string), string->length, encoding, &decoded);
|
|
29
29
|
if (!bytes) return xt_buffer_new(0, NULL);
|
|
30
30
|
xt_value result = xt_buffer_from_bytes(bytes, decoded);
|
|
31
31
|
free(bytes);
|
|
@@ -49,7 +49,7 @@ static xt_value xt_buffer_alloc(uint32_t size, xt_value fill, const char *encodi
|
|
|
49
49
|
xt_value text = xt_to_string(fill);
|
|
50
50
|
xt_string *string = xt_as_string(text);
|
|
51
51
|
size_t decoded = 0;
|
|
52
|
-
unsigned char *bytes = xt_node_decode(string
|
|
52
|
+
unsigned char *bytes = xt_node_decode(xt_string_bytes(string), string->length, encoding, &decoded);
|
|
53
53
|
if (!bytes || decoded == 0) {
|
|
54
54
|
free(bytes);
|
|
55
55
|
return buffer;
|
|
@@ -93,7 +93,7 @@ static xt_value xt_buffer_method_write(xt_value thisValue, xt_value env, int32_t
|
|
|
93
93
|
uint32_t offset = argc > 1 ? (uint32_t)xt_to_number(argv[1]) : 0;
|
|
94
94
|
const char *encoding = argc > 3 ? xt_node_encoding(argv[3]) : NULL;
|
|
95
95
|
size_t decoded = 0;
|
|
96
|
-
unsigned char *bytes = xt_node_decode(string
|
|
96
|
+
unsigned char *bytes = xt_node_decode(xt_string_bytes(string), string->length, encoding, &decoded);
|
|
97
97
|
uint32_t available = xt_buffer_length(thisValue);
|
|
98
98
|
uint32_t written = 0;
|
|
99
99
|
for (size_t i = 0; i < decoded && offset + written < available; i++) {
|
|
@@ -19,7 +19,7 @@ static const unsigned char *xt_buffer_search_needle(xt_value value, const char *
|
|
|
19
19
|
xt_value text = xt_to_string(value);
|
|
20
20
|
xt_string *string = xt_as_string(text);
|
|
21
21
|
size_t decoded = 0;
|
|
22
|
-
unsigned char *bytes = xt_node_decode(string
|
|
22
|
+
unsigned char *bytes = xt_node_decode(xt_string_bytes(string), string->length, encoding, &decoded);
|
|
23
23
|
*length = decoded;
|
|
24
24
|
return bytes;
|
|
25
25
|
}
|
|
@@ -82,6 +82,27 @@ static char *read_temp(FILE *file) {
|
|
|
82
82
|
return buffer;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/*
|
|
86
|
+
* MSVC's `_spawnvp` builds the child command line from argv without quoting
|
|
87
|
+
* argv[0]. When the executable path contains a space — the default install
|
|
88
|
+
* location is "C:\Program Files\…" — the child sees the path split into several
|
|
89
|
+
* arguments ("Files\Microsoft", "Visual", …). Quote argv[0] ourselves. Returns
|
|
90
|
+
* NULL when no quoting is needed (the caller keeps the original pointer).
|
|
91
|
+
*/
|
|
92
|
+
static char *quote_argv0(char **child_argv) {
|
|
93
|
+
const char *arg = child_argv[0];
|
|
94
|
+
if (arg == NULL || strchr(arg, ' ') == NULL) return NULL;
|
|
95
|
+
size_t length = strlen(arg);
|
|
96
|
+
char *quoted = (char *)malloc(length + 3);
|
|
97
|
+
if (!quoted) return NULL;
|
|
98
|
+
quoted[0] = '"';
|
|
99
|
+
memcpy(quoted + 1, arg, length);
|
|
100
|
+
quoted[length + 1] = '"';
|
|
101
|
+
quoted[length + 2] = 0;
|
|
102
|
+
child_argv[0] = quoted;
|
|
103
|
+
return quoted;
|
|
104
|
+
}
|
|
105
|
+
|
|
85
106
|
xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
86
107
|
const char *command = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
|
|
87
108
|
if (!command) command = "";
|
|
@@ -91,6 +112,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
91
112
|
int count = 0;
|
|
92
113
|
char **child_argv = (char **)build_argv(command, args, &count);
|
|
93
114
|
if (!child_argv) return make_result(-1, "", "out of memory");
|
|
115
|
+
char *quoted_command = quote_argv0(child_argv);
|
|
94
116
|
|
|
95
117
|
char previous[4096];
|
|
96
118
|
int had_cwd = cwd && _getcwd(previous, sizeof(previous)) != NULL;
|
|
@@ -99,6 +121,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
99
121
|
if (cwd) _chdir(cwd);
|
|
100
122
|
intptr_t inherit_rc = _spawnvp(_P_WAIT, command, (const char *const *)child_argv);
|
|
101
123
|
if (had_cwd) _chdir(previous);
|
|
124
|
+
free(quoted_command);
|
|
102
125
|
free(child_argv);
|
|
103
126
|
return make_result(inherit_rc < 0 ? -1 : (double)inherit_rc, "", "");
|
|
104
127
|
}
|
|
@@ -106,6 +129,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
106
129
|
FILE *out_file = tmpfile();
|
|
107
130
|
FILE *err_file = tmpfile();
|
|
108
131
|
if (!out_file || !err_file) {
|
|
132
|
+
free(quoted_command);
|
|
109
133
|
free(child_argv);
|
|
110
134
|
if (out_file) fclose(out_file);
|
|
111
135
|
if (err_file) fclose(err_file);
|
|
@@ -136,6 +160,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
136
160
|
char *err = read_temp(err_file);
|
|
137
161
|
fclose(out_file);
|
|
138
162
|
fclose(err_file);
|
|
163
|
+
free(quoted_command);
|
|
139
164
|
free(child_argv);
|
|
140
165
|
|
|
141
166
|
xt_value result = make_result(rc < 0 ? -1 : (double)rc, out, err);
|