xbintsc 0.5.37 → 0.5.41
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/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +23 -7
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +1 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +1 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/deferred.d.ts +63 -0
- package/dist/src/driver/bundler/deferred.js +279 -0
- package/dist/src/driver/bundler/deferred.js.map +1 -0
- package/dist/src/driver/bundler/merge.js +214 -2
- package/dist/src/driver/bundler/merge.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/gui/html.js +98 -15
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +1 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/gui/three.d.ts +4 -3
- package/dist/src/extensions/gui/three.js +4 -3
- package/dist/src/extensions/gui/three.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +7 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +31 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-state.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas-state.test.js +297 -0
- package/dist/tests/e2e/gui-canvas-state.test.js.map +1 -0
- package/dist/tests/e2e/gui-draw-perf.test.d.ts +22 -0
- package/dist/tests/e2e/gui-draw-perf.test.js +405 -0
- package/dist/tests/e2e/gui-draw-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-perf.test.d.ts +23 -0
- package/dist/tests/e2e/gui-perf.test.js +283 -0
- package/dist/tests/e2e/gui-perf.test.js.map +1 -0
- package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +169 -0
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl.test.js +4 -4
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js +336 -0
- package/dist/tests/e2e/gui-webgl2-shadow-sampler.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
- package/dist/tests/e2e/url.test.d.ts +6 -0
- package/dist/tests/e2e/url.test.js +114 -0
- package/dist/tests/e2e/url.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +45 -10
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/api.md +28 -2
- package/doc/gui/canvas.md +110 -27
- package/doc/gui/known-issues.md +32 -10
- package/doc/gui/rendering.md +77 -0
- package/doc/gui/runtime.md +18 -3
- package/doc/gui/webgl.md +58 -29
- package/doc/gui-scripts.md +19 -19
- package/doc/gui.md +43 -9
- package/doc/implemented/fetch.md +5 -0
- package/doc/implemented/modules.md +13 -0
- package/doc/implemented/stdlib.md +17 -0
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/api.md +25 -2
- package/doc/zh-CN/gui/canvas.md +95 -26
- package/doc/zh-CN/gui/known-issues.md +21 -4
- package/doc/zh-CN/gui/rendering.md +59 -0
- package/doc/zh-CN/gui/runtime.md +13 -3
- package/doc/zh-CN/gui/webgl.md +30 -8
- package/doc/zh-CN/gui-scripts.md +14 -13
- package/doc/zh-CN/gui.md +32 -8
- package/doc/zh-CN/implemented/fetch.md +4 -0
- package/doc/zh-CN/implemented/modules.md +10 -0
- package/doc/zh-CN/implemented/stdlib.md +15 -0
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +3 -1
- package/runtime/ext_gui/canvas.cpp +135 -89
- package/runtime/ext_gui/canvas.h +116 -0
- package/runtime/ext_gui/canvas_clip.cpp +143 -0
- package/runtime/ext_gui/canvas_composite.cpp +241 -0
- package/runtime/ext_gui/canvas_coverage.cpp +241 -0
- package/runtime/ext_gui/canvas_gradient.cpp +41 -4
- package/runtime/ext_gui/canvas_image_data.cpp +4 -0
- package/runtime/ext_gui/canvas_internal.h +34 -9
- package/runtime/ext_gui/canvas_raster.cpp +207 -178
- package/runtime/ext_gui/canvas_shadow.cpp +161 -0
- package/runtime/ext_gui/canvas_stroke.cpp +37 -9
- package/runtime/ext_gui/canvas_style.cpp +59 -0
- package/runtime/ext_gui/canvas_text.cpp +140 -0
- package/runtime/ext_gui/css.h +7 -0
- package/runtime/ext_gui/css_sheet.cpp +22 -0
- package/runtime/ext_gui/document.cpp +25 -1
- package/runtime/ext_gui/document.h +8 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +16 -1
- package/runtime/ext_gui/dom_api_canvas_image.cpp +9 -0
- package/runtime/ext_gui/dom_api_canvas_state.cpp +217 -0
- package/runtime/ext_gui/dom_api_internal.h +36 -37
- package/runtime/ext_gui/dom_api_offscreen.cpp +213 -0
- package/runtime/ext_gui/dom_api_proto.cpp +18 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +29 -9
- package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
- package/runtime/ext_gui/dom_api_webgl_internal.h +58 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +18 -0
- package/runtime/ext_gui/gradient_values.cpp +12 -1
- package/runtime/ext_gui/gui.cpp +5 -0
- package/runtime/ext_gui/gui_canvas.cpp +4 -0
- package/runtime/ext_gui/gui_engine.h +32 -0
- package/runtime/ext_gui/gui_events.cpp +182 -58
- package/runtime/ext_gui/gui_media.cpp +3 -0
- package/runtime/ext_gui/image.cpp +69 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +11 -0
- package/runtime/ext_gui/layout.cpp +4 -0
- package/runtime/ext_gui/paint.cpp +19 -0
- package/runtime/ext_gui/paint_background.cpp +18 -1
- package/runtime/ext_gui/perf.cpp +171 -0
- package/runtime/ext_gui/perf.h +127 -0
- package/runtime/ext_gui/renderer.cpp +109 -41
- package/runtime/ext_gui/renderer.h +33 -9
- package/runtime/ext_gui/renderer_image.cpp +135 -65
- package/runtime/ext_gui/renderer_internal.h +15 -3
- package/runtime/ext_gui/renderer_text.cpp +24 -12
- package/runtime/ext_gui/script.cpp +31 -0
- package/runtime/ext_gui/style.cpp +101 -30
- package/runtime/ext_gui/style.h +6 -3
- package/runtime/ext_gui/style_print.cpp +7 -1
- package/runtime/ext_gui/text.cpp +23 -4
- package/runtime/ext_gui/text.h +29 -0
- package/runtime/ext_gui/text_cache.cpp +118 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +100 -0
- package/runtime/ext_gui/webgl.h +48 -146
- package/runtime/ext_gui/webgl_blit.cpp +12 -1
- package/runtime/ext_gui/webgl_blit_depth.cpp +270 -0
- package/runtime/ext_gui/webgl_buffer.cpp +59 -1
- package/runtime/ext_gui/webgl_buffer_objects.cpp +106 -9
- package/runtime/ext_gui/webgl_clear.cpp +3 -0
- package/runtime/ext_gui/webgl_context.cpp +29 -3
- package/runtime/ext_gui/webgl_draw.cpp +75 -270
- package/runtime/ext_gui/webgl_draw_buffers.cpp +1 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +321 -0
- package/runtime/ext_gui/webgl_draw_state.h +130 -0
- package/runtime/ext_gui/webgl_glsl.h +3 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +3 -8
- package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
- package/runtime/ext_gui/webgl_msl.cpp +4 -0
- package/runtime/ext_gui/webgl_objects.h +148 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +8 -8
- package/runtime/ext_gui/webgl_program_internal.h +35 -0
- package/runtime/ext_gui/webgl_shader.cpp +34 -36
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/runtime/rt.h +3 -0
- package/runtime/xt_fetch/async.inc +60 -0
- package/runtime/xt_url_object.c +463 -0
- package/src/codegen/generator/module.ts +22 -7
- package/src/codegen/generator/state.ts +1 -0
- package/src/codegen/generator/tables.ts +1 -0
- package/src/driver/bundler/deferred.ts +351 -0
- package/src/driver/bundler/merge.ts +219 -2
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +100 -17
- package/src/extensions/gui/index.ts +1 -0
- package/src/extensions/gui/three.ts +4 -3
|
@@ -30,6 +30,27 @@ bool same_point(const CanvasPoint &a, const CanvasPoint &b) {
|
|
|
30
30
|
return std::fabs(a.x - b.x) < 1e-6f && std::fabs(a.y - b.y) < 1e-6f;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Twice the signed area of a closed polygon (shoelace). */
|
|
34
|
+
float signed_area(const CanvasSubpath &loop) {
|
|
35
|
+
float sum = 0.0f;
|
|
36
|
+
const size_t count = loop.size();
|
|
37
|
+
for (size_t i = 0; i < count; i++) {
|
|
38
|
+
const CanvasPoint &a = loop[i];
|
|
39
|
+
const CanvasPoint &b = loop[(i + 1) % count];
|
|
40
|
+
sum += a.x * b.y - b.x * a.y;
|
|
41
|
+
}
|
|
42
|
+
return sum;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Append one stroke piece to the batch, normalising its winding: a stroke is
|
|
46
|
+
* filled as one nonzero path, so a join that happened to be wound the other
|
|
47
|
+
* way would cancel the quads it overlaps instead of covering them. */
|
|
48
|
+
void append_piece(std::vector<CanvasSubpath> *loops, CanvasSubpath piece) {
|
|
49
|
+
if (piece.size() < 3) return;
|
|
50
|
+
if (signed_area(piece) < 0.0f) std::reverse(piece.begin(), piece.end());
|
|
51
|
+
loops->push_back(std::move(piece));
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
} // namespace
|
|
34
55
|
|
|
35
56
|
void Canvas2D::stroke_subpath(const CanvasSubpath &points, bool closed, const CanvasPaint &paint) {
|
|
@@ -54,10 +75,12 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
54
75
|
const size_t n = pts.size();
|
|
55
76
|
if (n < 2) return;
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
/* Every segment, join and cap becomes one polygon; the whole polyline is
|
|
79
|
+
* filled in a single coverage pass instead of one pass per piece (which was
|
|
80
|
+
* the dominant cost of a many-segment stroke). */
|
|
81
|
+
std::vector<CanvasSubpath> loops;
|
|
82
|
+
loops.reserve(n * 2 + 2);
|
|
83
|
+
|
|
61
84
|
auto fill_disc = [&](const CanvasPoint &p) {
|
|
62
85
|
CanvasSubpath disc;
|
|
63
86
|
const int segments = 24;
|
|
@@ -66,7 +89,7 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
66
89
|
float angle = 2.0f * kPi * ((float)i / (float)segments);
|
|
67
90
|
disc.push_back(CanvasPoint{p.x + half * std::cos(angle), p.y + half * std::sin(angle)});
|
|
68
91
|
}
|
|
69
|
-
|
|
92
|
+
append_piece(&loops, std::move(disc));
|
|
70
93
|
};
|
|
71
94
|
|
|
72
95
|
/* Segments. A square cap extends the first/last quad along the line by half
|
|
@@ -92,7 +115,7 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
92
115
|
}
|
|
93
116
|
CanvasSubpath quad{CanvasPoint{p.x + nx, p.y + ny}, CanvasPoint{q.x + nx, q.y + ny},
|
|
94
117
|
CanvasPoint{q.x - nx, q.y - ny}, CanvasPoint{p.x - nx, p.y - ny}};
|
|
95
|
-
|
|
118
|
+
append_piece(&loops, std::move(quad));
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
/* Joins: round adds a disc, bevel a triangle, miter the intersect of the two
|
|
@@ -120,7 +143,7 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
120
143
|
CanvasPoint a{p.x - v1y * half * side, p.y + v1x * half * side};
|
|
121
144
|
CanvasPoint b{p.x - v2y * half * side, p.y + v2x * half * side};
|
|
122
145
|
if (state_.line_join == 2) {
|
|
123
|
-
|
|
146
|
+
append_piece(&loops, CanvasSubpath{a, b, p});
|
|
124
147
|
return;
|
|
125
148
|
}
|
|
126
149
|
float t = ((b.x - a.x) * v2y - (b.y - a.y) * v2x) / cross;
|
|
@@ -128,9 +151,9 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
128
151
|
float miterLength = std::sqrt((miter.x - p.x) * (miter.x - p.x) +
|
|
129
152
|
(miter.y - p.y) * (miter.y - p.y));
|
|
130
153
|
if (std::isfinite(miterLength) && miterLength <= half * state_.miter_limit) {
|
|
131
|
-
|
|
154
|
+
append_piece(&loops, CanvasSubpath{a, miter, b, p});
|
|
132
155
|
} else {
|
|
133
|
-
|
|
156
|
+
append_piece(&loops, CanvasSubpath{a, b, p});
|
|
134
157
|
}
|
|
135
158
|
};
|
|
136
159
|
|
|
@@ -148,6 +171,11 @@ void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float h
|
|
|
148
171
|
fill_disc(pts.back());
|
|
149
172
|
}
|
|
150
173
|
}
|
|
174
|
+
|
|
175
|
+
if (!loops.empty()) {
|
|
176
|
+
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, paint,
|
|
177
|
+
state_.global_alpha, false, clip_mask());
|
|
178
|
+
}
|
|
151
179
|
}
|
|
152
180
|
|
|
153
181
|
void Canvas2D::dash_polyline(const CanvasSubpath &points, bool closed,
|
|
@@ -144,6 +144,65 @@ void Canvas2D::set_global_alpha(float alpha) {
|
|
|
144
144
|
state_.global_alpha = alpha;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
void Canvas2D::set_global_composite_operation(const std::string &value) {
|
|
148
|
+
std::string lower = lower_ascii(value);
|
|
149
|
+
struct Entry {
|
|
150
|
+
const char *name;
|
|
151
|
+
int value;
|
|
152
|
+
};
|
|
153
|
+
static const Entry entries[] = {
|
|
154
|
+
{"source-over", kCanvasCompositeSourceOver},
|
|
155
|
+
{"source-in", kCanvasCompositeSourceIn},
|
|
156
|
+
{"source-out", kCanvasCompositeSourceOut},
|
|
157
|
+
{"source-atop", kCanvasCompositeSourceAtop},
|
|
158
|
+
{"destination-over", kCanvasCompositeDestinationOver},
|
|
159
|
+
{"destination-in", kCanvasCompositeDestinationIn},
|
|
160
|
+
{"destination-out", kCanvasCompositeDestinationOut},
|
|
161
|
+
{"destination-atop", kCanvasCompositeDestinationAtop},
|
|
162
|
+
{"lighter", kCanvasCompositeLighter},
|
|
163
|
+
{"copy", kCanvasCompositeCopy},
|
|
164
|
+
{"xor", kCanvasCompositeXor},
|
|
165
|
+
{"multiply", kCanvasCompositeMultiply},
|
|
166
|
+
{"screen", kCanvasCompositeScreen},
|
|
167
|
+
{"overlay", kCanvasCompositeOverlay},
|
|
168
|
+
{"darken", kCanvasCompositeDarken},
|
|
169
|
+
{"lighten", kCanvasCompositeLighten},
|
|
170
|
+
{"color-dodge", kCanvasCompositeColorDodge},
|
|
171
|
+
{"color-burn", kCanvasCompositeColorBurn},
|
|
172
|
+
{"hard-light", kCanvasCompositeHardLight},
|
|
173
|
+
{"soft-light", kCanvasCompositeSoftLight},
|
|
174
|
+
{"difference", kCanvasCompositeDifference},
|
|
175
|
+
{"exclusion", kCanvasCompositeExclusion},
|
|
176
|
+
{"hue", kCanvasCompositeHue},
|
|
177
|
+
{"saturation", kCanvasCompositeSaturation},
|
|
178
|
+
{"color", kCanvasCompositeColor},
|
|
179
|
+
{"luminosity", kCanvasCompositeLuminosity},
|
|
180
|
+
};
|
|
181
|
+
for (const Entry &entry : entries) {
|
|
182
|
+
if (lower == entry.name) {
|
|
183
|
+
state_.global_composite = entry.value;
|
|
184
|
+
state_.global_composite_string = entry.name;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
void Canvas2D::set_shadow_color(const std::string &css) {
|
|
191
|
+
parse_color_into(css, state_.shadow_color, &state_.shadow_color_string);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
void Canvas2D::set_shadow_blur(float blur) {
|
|
195
|
+
if (!positive_finite(blur)) return;
|
|
196
|
+
state_.shadow_blur = blur;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
void Canvas2D::set_image_smoothing_quality(const std::string &value) {
|
|
200
|
+
std::string lower = lower_ascii(value);
|
|
201
|
+
if (lower == "low" || lower == "medium" || lower == "high") {
|
|
202
|
+
state_.image_smoothing_quality = lower;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
147
206
|
void Canvas2D::set_font(const std::string &font) {
|
|
148
207
|
state_.font = parse_font_string(font);
|
|
149
208
|
state_.font_string = font;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — Canvas2D text drawing.
|
|
3
|
+
*
|
|
4
|
+
* `fillText`/`strokeText` shape a run with HarfBuzz and blit FreeType glyph
|
|
5
|
+
* coverage through the rasteriser. The state and path live in `canvas.h`; this
|
|
6
|
+
* unit only owns the text entry points and the optional `maxWidth` condense.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "canvas.h"
|
|
10
|
+
|
|
11
|
+
#include <cmath>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
#include "canvas_internal.h"
|
|
16
|
+
#include "perf.h"
|
|
17
|
+
|
|
18
|
+
namespace xtgui {
|
|
19
|
+
|
|
20
|
+
float Canvas2D::measure_text(const std::string &text) const {
|
|
21
|
+
return xt_text_measure_width(text, state_.font);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
25
|
+
bool stroke, float max_width) {
|
|
26
|
+
Font *font = xt_text_resolve(state_.font);
|
|
27
|
+
if (font == nullptr) return;
|
|
28
|
+
/* Layout and paint share the bounded shaping cache: a label redrawn every
|
|
29
|
+
* frame is shaped once, not once per frame. `maxWidth` only condenses the
|
|
30
|
+
* drawn run, so it does not affect the shaped result. */
|
|
31
|
+
const ShapedRun *run = xt_text_shape_cached(text, font);
|
|
32
|
+
if (run == nullptr) return;
|
|
33
|
+
const std::vector<ShapedGlyph> &glyphs = run->glyphs;
|
|
34
|
+
float width = run->width;
|
|
35
|
+
float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
|
|
36
|
+
xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
|
|
37
|
+
|
|
38
|
+
/* The optional `maxWidth` condenses the run horizontally instead of clipping
|
|
39
|
+
* it: advances and glyph bitmaps are squeezed by the same factor. */
|
|
40
|
+
float horizontal = 1.0f;
|
|
41
|
+
if (max_width > 0.0f && width > max_width && width > 0.0001f) {
|
|
42
|
+
horizontal = max_width / width;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
float originX = x;
|
|
46
|
+
if (state_.text_align == 1) {
|
|
47
|
+
originX -= width * horizontal * 0.5f;
|
|
48
|
+
} else if (state_.text_align == 2) {
|
|
49
|
+
originX -= width * horizontal;
|
|
50
|
+
}
|
|
51
|
+
float originY = y;
|
|
52
|
+
switch (state_.text_baseline) {
|
|
53
|
+
case 1:
|
|
54
|
+
originY += ascent;
|
|
55
|
+
break;
|
|
56
|
+
case 2:
|
|
57
|
+
originY += (ascent - descent) * 0.5f;
|
|
58
|
+
break;
|
|
59
|
+
case 3:
|
|
60
|
+
originY -= descent;
|
|
61
|
+
break;
|
|
62
|
+
case 4:
|
|
63
|
+
originY += ascent * 0.8f;
|
|
64
|
+
break;
|
|
65
|
+
case 5:
|
|
66
|
+
originY -= descent;
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
float scale = state_.transform.scale();
|
|
73
|
+
if (scale <= 0.0001f) scale = 1.0f;
|
|
74
|
+
bool scaled = std::fabs(scale - 1.0f) > 0.01f;
|
|
75
|
+
|
|
76
|
+
/* Scaling a face is a cached lookup, but it was still one path key + hash
|
|
77
|
+
* per glyph. There are at most a handful of faces in a run, so remember the
|
|
78
|
+
* sized face for the ones this call touches. */
|
|
79
|
+
Font *scaled_from[8] = {nullptr};
|
|
80
|
+
Font *scaled_to[8] = {nullptr};
|
|
81
|
+
int scaled_count = 0;
|
|
82
|
+
auto sized_face = [&](Font *face) -> Font * {
|
|
83
|
+
for (int i = 0; i < scaled_count; i++) {
|
|
84
|
+
if (scaled_from[i] == face) return scaled_to[i];
|
|
85
|
+
}
|
|
86
|
+
Font *sized = xt_text_scale(face, state_.font.pixel_size * scale);
|
|
87
|
+
if (sized == nullptr) sized = face;
|
|
88
|
+
if (scaled_count < 8) {
|
|
89
|
+
scaled_from[scaled_count] = face;
|
|
90
|
+
scaled_to[scaled_count] = sized;
|
|
91
|
+
scaled_count += 1;
|
|
92
|
+
}
|
|
93
|
+
return sized;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
float penX = 0.0f;
|
|
97
|
+
for (const ShapedGlyph &glyph : glyphs) {
|
|
98
|
+
float userX = originX + (penX + glyph.x_offset) * horizontal;
|
|
99
|
+
float userY = originY + glyph.y_offset;
|
|
100
|
+
float deviceX = 0.0f, deviceY = 0.0f;
|
|
101
|
+
to_device(userX, userY, &deviceX, &deviceY);
|
|
102
|
+
Font *face = glyph.font != nullptr ? glyph.font : font;
|
|
103
|
+
if (scaled) face = sized_face(face);
|
|
104
|
+
const GlyphImage *image = xt_text_glyph_cached(face, glyph.glyph);
|
|
105
|
+
if (image != nullptr && image->width > 0 && image->height > 0) {
|
|
106
|
+
const float left = deviceX + (float)image->left * horizontal;
|
|
107
|
+
const float top = deviceY - (float)image->top;
|
|
108
|
+
mark_dirty(left, top, left + (float)image->width * horizontal,
|
|
109
|
+
top + (float)image->height);
|
|
110
|
+
canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image->pixels,
|
|
111
|
+
image->width, image->height, image->left, image->top, deviceX,
|
|
112
|
+
deviceY, horizontal, paint, state_.global_alpha, clip_mask());
|
|
113
|
+
}
|
|
114
|
+
penX += glyph.x_advance;
|
|
115
|
+
}
|
|
116
|
+
(void)stroke;
|
|
117
|
+
generation_++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void Canvas2D::fill_text(const std::string &text, float x, float y, float max_width) {
|
|
121
|
+
if (begin_shadow_layer()) {
|
|
122
|
+
fill_text(text, x, y, max_width);
|
|
123
|
+
end_shadow_layer();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
g_perf_frame.canvasDraws += 1;
|
|
127
|
+
paint_text(text, x, y, fill_paint(), false, max_width);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
void Canvas2D::stroke_text(const std::string &text, float x, float y, float max_width) {
|
|
131
|
+
if (begin_shadow_layer()) {
|
|
132
|
+
stroke_text(text, x, y, max_width);
|
|
133
|
+
end_shadow_layer();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
g_perf_frame.canvasDraws += 1;
|
|
137
|
+
paint_text(text, x, y, stroke_paint(), true, max_width);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} // namespace xtgui
|
package/runtime/ext_gui/css.h
CHANGED
|
@@ -81,6 +81,13 @@ struct Rule {
|
|
|
81
81
|
|
|
82
82
|
struct StyleSheet {
|
|
83
83
|
std::vector<Rule> rules;
|
|
84
|
+
/* Facts the cascade can act on once per sheet instead of once per element (a
|
|
85
|
+
* `@media` block, a `::before` rule or a `transition` declaration used to be
|
|
86
|
+
* discovered by scanning every rule for every node). */
|
|
87
|
+
bool has_pseudo_elements = false;
|
|
88
|
+
bool has_hover = false;
|
|
89
|
+
bool has_focus = false;
|
|
90
|
+
bool has_transitions = false;
|
|
84
91
|
};
|
|
85
92
|
|
|
86
93
|
/** Dynamic element state consulted by `:hover` / `:focus` while matching. */
|
|
@@ -140,6 +140,27 @@ void parseRules(const std::string &text, const std::string &media, int *order,
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/** Record the sheet-wide facts the cascade consults per run (see `StyleSheet`).
|
|
144
|
+
* `:hover`/`:focus` are matched by substring so a nested `:not(:hover)` rule is
|
|
145
|
+
* still recognised — a false positive only costs the restyle that is already
|
|
146
|
+
* being skipped. */
|
|
147
|
+
void computeSheetFlags(StyleSheet *sheet) {
|
|
148
|
+
for (const Rule &rule : sheet->rules) {
|
|
149
|
+
for (const ComplexSelector &selector : rule.selectors) {
|
|
150
|
+
for (const CompoundSelector &compound : selector.compounds) {
|
|
151
|
+
if (!compound.pseudo_element.empty()) sheet->has_pseudo_elements = true;
|
|
152
|
+
for (const std::string &pseudo : compound.pseudoClasses) {
|
|
153
|
+
if (pseudo.find("hover") != std::string::npos) sheet->has_hover = true;
|
|
154
|
+
if (pseudo.find("focus") != std::string::npos) sheet->has_focus = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const Declaration &declaration : rule.declarations) {
|
|
159
|
+
if (declaration.property.compare(0, 10, "transition") == 0) sheet->has_transitions = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
143
164
|
} // namespace
|
|
144
165
|
|
|
145
166
|
std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
|
|
@@ -167,6 +188,7 @@ StyleSheet xt_css_parse_stylesheet(const std::string &text) {
|
|
|
167
188
|
std::string clean = stripComments(text);
|
|
168
189
|
int order = 0;
|
|
169
190
|
parseRules(clean, "", &order, &sheet);
|
|
191
|
+
computeSheetFlags(&sheet);
|
|
170
192
|
return sheet;
|
|
171
193
|
}
|
|
172
194
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
#include "document.h"
|
|
6
6
|
|
|
7
|
+
#include "perf.h"
|
|
8
|
+
|
|
7
9
|
namespace xtgui {
|
|
8
10
|
|
|
9
11
|
void XtDocument::collectStyleText(const Node *node, std::string *out) const {
|
|
@@ -43,11 +45,15 @@ void XtDocument::load(const std::string &html, float viewportWidth, float viewpo
|
|
|
43
45
|
void XtDocument::restyle(float viewportWidth, float viewportHeight) {
|
|
44
46
|
viewportWidth_ = viewportWidth;
|
|
45
47
|
viewportHeight_ = viewportHeight;
|
|
48
|
+
g_perf_frame.restyles += 1;
|
|
46
49
|
if (root_ != nullptr) {
|
|
47
50
|
MatchState state;
|
|
48
51
|
state.hover = hover_;
|
|
49
52
|
state.focus = focus_;
|
|
50
|
-
|
|
53
|
+
bool hasTransitions = sheet_.has_transitions;
|
|
54
|
+
xt_style_compute(root_.get(), sheet_, styles_, viewportWidth, viewportHeight, state, &pseudos_,
|
|
55
|
+
&hasTransitions);
|
|
56
|
+
has_transitions_ = hasTransitions;
|
|
51
57
|
startTransitions();
|
|
52
58
|
updateDisplay();
|
|
53
59
|
}
|
|
@@ -119,6 +125,10 @@ void applyNumber(XtStyle *style, const std::string &property, float value) {
|
|
|
119
125
|
|
|
120
126
|
void XtDocument::startTransitions() {
|
|
121
127
|
if (display_.empty()) return; // first style computation: nothing to animate from
|
|
128
|
+
/* Without a `transition` declaration anywhere in the sheet (or in an inline
|
|
129
|
+
* style) no transition can start, so skip the O(nodes) scan `restyle` would
|
|
130
|
+
* otherwise do every time. */
|
|
131
|
+
if (!has_transitions_) return;
|
|
122
132
|
static const char *const kProperties[] = {"background-color", "color", "border-color",
|
|
123
133
|
"border-radius", "transform"};
|
|
124
134
|
for (auto &entry : styles_) {
|
|
@@ -186,6 +196,17 @@ void XtDocument::startTransitions() {
|
|
|
186
196
|
}
|
|
187
197
|
|
|
188
198
|
void XtDocument::updateDisplay() {
|
|
199
|
+
/* Without a `transition` declaration in the sheet (or an inline style) no
|
|
200
|
+
* transition can ever start, so the displayed style *is* the cascade result:
|
|
201
|
+
* skip the per-node deep copy of the whole style map (and keep `display_`
|
|
202
|
+
* empty, which `styleOf` already falls back from). */
|
|
203
|
+
if (!has_transitions_) {
|
|
204
|
+
if (!transitions_.empty()) transitions_.clear();
|
|
205
|
+
if (!display_.empty()) display_.clear();
|
|
206
|
+
layout_.compute(root_.get(), styles_, viewportWidth_, viewportHeight_, &mediaSizes_,
|
|
207
|
+
&canvasInfo_);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
189
210
|
display_ = styles_;
|
|
190
211
|
for (size_t i = 0; i < transitions_.size();) {
|
|
191
212
|
ActiveTransition &transition = transitions_[i];
|
|
@@ -232,6 +253,8 @@ bool XtDocument::advance(double delta_ms) {
|
|
|
232
253
|
bool XtDocument::setHover(const Node *node) {
|
|
233
254
|
if (hover_ == node) return false;
|
|
234
255
|
hover_ = node;
|
|
256
|
+
/* No `:hover` rule anywhere: the state changed but the paint cannot. */
|
|
257
|
+
if (!sheet_.has_hover) return false;
|
|
235
258
|
restyle(viewportWidth_, viewportHeight_);
|
|
236
259
|
return true;
|
|
237
260
|
}
|
|
@@ -239,6 +262,7 @@ bool XtDocument::setHover(const Node *node) {
|
|
|
239
262
|
bool XtDocument::setFocus(const Node *node) {
|
|
240
263
|
if (focus_ == node) return false;
|
|
241
264
|
focus_ = node;
|
|
265
|
+
if (!sheet_.has_focus) return false;
|
|
242
266
|
restyle(viewportWidth_, viewportHeight_);
|
|
243
267
|
return true;
|
|
244
268
|
}
|
|
@@ -93,6 +93,10 @@ class XtDocument {
|
|
|
93
93
|
|
|
94
94
|
/** Mark the document as needing a restyle/relayout before the next paint. */
|
|
95
95
|
void invalidate() { dirty_ = true; }
|
|
96
|
+
/** Whether a restyle is pending (peek; `takeDirty` consumes it). The frame
|
|
97
|
+
* loop uses this to skip reconciling media/canvas tables when nothing in the
|
|
98
|
+
* document changed. */
|
|
99
|
+
bool dirty() const { return dirty_; }
|
|
96
100
|
bool takeDirty() {
|
|
97
101
|
bool was = dirty_;
|
|
98
102
|
dirty_ = false;
|
|
@@ -127,6 +131,10 @@ class XtDocument {
|
|
|
127
131
|
MediaSizeMap mediaSizes_; // `<video>` intrinsic sizes
|
|
128
132
|
CanvasInfoMap canvasInfo_; // `<canvas>` sizes + ids
|
|
129
133
|
std::vector<ActiveTransition> transitions_;
|
|
134
|
+
/** Whether any computed style declares a `transition` (from the sheet or an
|
|
135
|
+
* inline `style="…"`): when false the document skips the transition snapshot
|
|
136
|
+
* and `display_` stays empty. */
|
|
137
|
+
bool has_transitions_ = false;
|
|
130
138
|
double clock_ms_ = 0.0;
|
|
131
139
|
LayoutTree layout_;
|
|
132
140
|
const Node *hover_ = nullptr;
|
|
@@ -80,6 +80,9 @@ std::string canvas_string_arg(int32_t argc, xt_value *argv, int32_t index) {
|
|
|
80
80
|
METHOD(node_get_context) {
|
|
81
81
|
(void)env;
|
|
82
82
|
std::string mode = string_arg(argc, argv, 0);
|
|
83
|
+
/* After `transferControlToOffscreen` the element has no usable context; the
|
|
84
|
+
* OffscreenCanvas owns it. */
|
|
85
|
+
if (value_truthy(xt_object_get_cstr(self, "__xt_gui_offscreen"))) return XT_NULL;
|
|
83
86
|
XtGuiWindow *win = nullptr;
|
|
84
87
|
xtgui::Node *node = resolve_node(self, &win);
|
|
85
88
|
if (mode == "webgl" || mode == "experimental-webgl" || mode == "webgl2") {
|
|
@@ -466,8 +469,20 @@ METHOD(canvas_draw_image) {
|
|
|
466
469
|
xtgui::Canvas2D *canvas = context_canvas(self);
|
|
467
470
|
if (canvas == nullptr || argc < 3) return XT_UNDEFINED;
|
|
468
471
|
XtGuiWindow *win = nullptr;
|
|
469
|
-
|
|
472
|
+
xt_value source_value = xt_arg(argc, argv, 0);
|
|
470
473
|
const xtgui::Image *image = nullptr;
|
|
474
|
+
/* An `OffscreenCanvas` (or a transferred one) has no DOM node; its bitmap is
|
|
475
|
+
* reachable through the process-wide canvas id. */
|
|
476
|
+
if (XT_IS_OBJECT(source_value)) {
|
|
477
|
+
xt_value canvas_id = xt_object_get_cstr(source_value, "__xt_gui_canvas");
|
|
478
|
+
if (XT_IS_NUMBER(canvas_id)) {
|
|
479
|
+
xtgui::Canvas2D *offscreen = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvas_id));
|
|
480
|
+
if (offscreen != nullptr) {
|
|
481
|
+
image = &offscreen->image();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
xtgui::Node *source = resolve_node(source_value, &win);
|
|
471
486
|
if (source != nullptr && source->isElement() && source->isTag("img")) {
|
|
472
487
|
const std::string *src = source->attr("src");
|
|
473
488
|
if (src != nullptr) image = xtgui::xt_image_load(*src);
|
|
@@ -54,6 +54,15 @@ xt_value make_image_data(int width, int height, const std::vector<unsigned char>
|
|
|
54
54
|
|
|
55
55
|
/** Decode a `createPattern` source: an `<img>` or another `<canvas>`. */
|
|
56
56
|
bool source_image(XtGuiWindow *win, xt_value value, xtgui::Image *out) {
|
|
57
|
+
if (XT_IS_OBJECT(value)) {
|
|
58
|
+
xt_value canvas_id = xt_object_get_cstr(value, "__xt_gui_canvas");
|
|
59
|
+
if (XT_IS_NUMBER(canvas_id)) {
|
|
60
|
+
xtgui::Canvas2D *offscreen = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvas_id));
|
|
61
|
+
if (offscreen == nullptr) return false;
|
|
62
|
+
*out = offscreen->image();
|
|
63
|
+
return out->width > 0 && out->height > 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
57
66
|
xtgui::Node *node = resolve_node(value, &win);
|
|
58
67
|
if (node == nullptr || !node->isElement()) return false;
|
|
59
68
|
if (node->isTag("img")) {
|