xbintsc 0.3.69 → 0.3.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -4
- package/README.zh-CN.md +52 -2
- package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
- package/dist/tests/e2e/gui-advanced.test.js +261 -0
- package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas.test.js +233 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +1 -1
- package/dist/tests/e2e/gui-example.test.js +2 -2
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +5 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +73 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +8 -0
- package/dist/tests/e2e/gui-media.test.js +119 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.js +40 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/ai/extensions.md +16 -3
- package/doc/ai/zh-CN/extensions.md +10 -3
- package/doc/gui/animation.md +46 -0
- package/doc/gui/api.md +92 -0
- package/doc/gui/build.md +91 -0
- package/doc/gui/canvas.md +98 -0
- package/doc/gui/dom.md +123 -0
- package/doc/gui/input.md +22 -0
- package/doc/gui/known-issues.md +66 -0
- package/doc/gui/layout.md +72 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +98 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +80 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +46 -0
- package/doc/zh-CN/gui/api.md +88 -0
- package/doc/zh-CN/gui/build.md +84 -0
- package/doc/zh-CN/gui/canvas.md +80 -0
- package/doc/zh-CN/gui/dom.md +114 -0
- package/doc/zh-CN/gui/input.md +21 -0
- package/doc/zh-CN/gui/known-issues.md +50 -0
- package/doc/zh-CN/gui/layout.md +60 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +81 -0
- package/doc/zh-CN/gui/runtime.md +76 -0
- package/doc/zh-CN/gui-scripts.md +6 -2
- package/doc/zh-CN/gui.md +64 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/llms.txt +15 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +580 -0
- package/runtime/ext_gui/canvas.h +170 -0
- package/runtime/ext_gui/canvas_internal.h +59 -0
- package/runtime/ext_gui/canvas_raster.cpp +254 -0
- package/runtime/ext_gui/document.cpp +24 -1
- package/runtime/ext_gui/document.h +11 -0
- package/runtime/ext_gui/dom_api.cpp +3 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
- package/runtime/ext_gui/dom_api_internal.h +73 -0
- package/runtime/ext_gui/dom_api_media.cpp +274 -0
- package/runtime/ext_gui/dom_api_proto.cpp +80 -0
- package/runtime/ext_gui/gui.cpp +6 -0
- package/runtime/ext_gui/gui_canvas.cpp +95 -0
- package/runtime/ext_gui/gui_engine.h +49 -0
- package/runtime/ext_gui/gui_events.cpp +21 -5
- package/runtime/ext_gui/gui_media.cpp +193 -0
- package/runtime/ext_gui/image.cpp +31 -12
- package/runtime/ext_gui/image.h +20 -7
- package/runtime/ext_gui/layout.cpp +52 -5
- package/runtime/ext_gui/layout.h +33 -1
- package/runtime/ext_gui/layout_flex.cpp +263 -99
- package/runtime/ext_gui/layout_flow.cpp +85 -9
- package/runtime/ext_gui/layout_flow_internal.h +84 -5
- package/runtime/ext_gui/layout_inline.cpp +54 -37
- package/runtime/ext_gui/media.cpp +514 -0
- package/runtime/ext_gui/media.h +104 -0
- package/runtime/ext_gui/nanosvg.h +3278 -0
- package/runtime/ext_gui/nanosvgrast.h +1472 -0
- package/runtime/ext_gui/paint.cpp +123 -38
- package/runtime/ext_gui/paint.h +36 -6
- package/runtime/ext_gui/renderer.cpp +113 -10
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +203 -77
- package/runtime/ext_gui/renderer_internal.h +6 -8
- package/runtime/ext_gui/renderer_text.cpp +11 -26
- package/runtime/ext_gui/style.h +11 -0
- package/runtime/ext_gui/style_print.cpp +11 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +40 -1
- package/runtime/ext_gui/svg.cpp +182 -0
- package/runtime/ext_gui/svg.h +46 -0
- package/runtime/ext_gui/text.cpp +156 -25
- package/runtime/ext_gui/text.h +11 -2
- package/runtime/ext_gui/window.cpp +4 -1
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- package/scripts/install-gui-deps.sh +28 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `<canvas>` 2D drawing.
|
|
3
|
+
*
|
|
4
|
+
* A `Canvas2D` owns one RGBA8 bitmap and the 2D drawing state (fill/stroke
|
|
5
|
+
* style, line width, transform, text settings). It is the engine-side object
|
|
6
|
+
* behind `canvas.getContext("2d")`; the DOM bindings live in
|
|
7
|
+
* `dom_api_canvas.cpp` and the pixels reach the screen through the same GPU
|
|
8
|
+
* texture path as images and video (`renderer_image.cpp`).
|
|
9
|
+
*
|
|
10
|
+
* The rasteriser is deliberately small and self-written (no Skia/Cairo): it
|
|
11
|
+
* fills rectangles, polygons and flattened arcs with sub-scanline coverage
|
|
12
|
+
* anti-aliasing, and blits FreeType glyph bitmaps for `fillText`. The pieces
|
|
13
|
+
* live in `canvas_raster.cpp`; this header only exposes the object model.
|
|
14
|
+
*
|
|
15
|
+
* Canvas pixels are *not* touched by the garbage collector: a canvas is owned
|
|
16
|
+
* by the window (keyed by its DOM node) and released with the document.
|
|
17
|
+
*/
|
|
18
|
+
#ifndef XT_GUI_CANVAS_H
|
|
19
|
+
#define XT_GUI_CANVAS_H
|
|
20
|
+
|
|
21
|
+
#include <cstdint>
|
|
22
|
+
#include <string>
|
|
23
|
+
#include <vector>
|
|
24
|
+
|
|
25
|
+
#include "image.h"
|
|
26
|
+
#include "text.h"
|
|
27
|
+
|
|
28
|
+
namespace xtgui {
|
|
29
|
+
|
|
30
|
+
/** A 2D affine transform, laid out like the DOM `DOMMatrix` (a b c d e f). */
|
|
31
|
+
struct CanvasTransform {
|
|
32
|
+
float a = 1.0f, b = 0.0f, c = 0.0f, d = 1.0f, e = 0.0f, f = 0.0f;
|
|
33
|
+
|
|
34
|
+
void apply(float x, float y, float *outX, float *outY) const {
|
|
35
|
+
*outX = a * x + c * y + e;
|
|
36
|
+
*outY = b * x + d * y + f;
|
|
37
|
+
}
|
|
38
|
+
/** True when the transform keeps axis-aligned rectangles axis aligned (no
|
|
39
|
+
* rotation or skew), which lets the rasteriser use its fast rectangle path. */
|
|
40
|
+
bool axis_aligned() const { return b == 0.0f && c == 0.0f; }
|
|
41
|
+
/** Approximate uniform scale, used to size stroked lines and text. */
|
|
42
|
+
float scale() const;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** A point in device (canvas) space. */
|
|
46
|
+
struct CanvasPoint {
|
|
47
|
+
float x = 0.0f;
|
|
48
|
+
float y = 0.0f;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** One subpath of the current path (device space). */
|
|
52
|
+
using CanvasSubpath = std::vector<CanvasPoint>;
|
|
53
|
+
|
|
54
|
+
class Canvas2D {
|
|
55
|
+
public:
|
|
56
|
+
Canvas2D(int width, int height);
|
|
57
|
+
~Canvas2D();
|
|
58
|
+
|
|
59
|
+
int width() const { return image_.width; }
|
|
60
|
+
int height() const { return image_.height; }
|
|
61
|
+
uint64_t id() const { return id_; }
|
|
62
|
+
/** Bumped by every draw; the renderer re-uploads when it changes. */
|
|
63
|
+
uint64_t generation() const { return generation_; }
|
|
64
|
+
const Image &image() const { return image_; }
|
|
65
|
+
/** Resize the bitmap, discarding its contents (the DOM `canvas.width = …`
|
|
66
|
+
* behaviour). */
|
|
67
|
+
void resize(int width, int height);
|
|
68
|
+
|
|
69
|
+
/* -- state ------------------------------------------------------------- */
|
|
70
|
+
void save();
|
|
71
|
+
void restore();
|
|
72
|
+
|
|
73
|
+
/* -- transform --------------------------------------------------------- */
|
|
74
|
+
void translate(float x, float y);
|
|
75
|
+
void rotate(float radians);
|
|
76
|
+
void scale(float sx, float sy);
|
|
77
|
+
void transform(float a, float b, float c, float d, float e, float f);
|
|
78
|
+
void set_transform(float a, float b, float c, float d, float e, float f);
|
|
79
|
+
void reset_transform() { state_.transform = CanvasTransform(); }
|
|
80
|
+
|
|
81
|
+
/* -- style ------------------------------------------------------------- */
|
|
82
|
+
void set_fill_style(const std::string &css);
|
|
83
|
+
const std::string &fill_style() const { return state_.fill_style; }
|
|
84
|
+
void set_stroke_style(const std::string &css);
|
|
85
|
+
const std::string &stroke_style() const { return state_.stroke_style; }
|
|
86
|
+
void set_line_width(float width) { state_.line_width = width > 0.0f ? width : 0.0f; }
|
|
87
|
+
float line_width() const { return state_.line_width; }
|
|
88
|
+
void set_global_alpha(float alpha);
|
|
89
|
+
float global_alpha() const { return state_.global_alpha; }
|
|
90
|
+
void set_font(const std::string &font);
|
|
91
|
+
const std::string &font() const { return state_.font_string; }
|
|
92
|
+
void set_text_align(const std::string &value);
|
|
93
|
+
const std::string &text_align() const { return state_.text_align_string; }
|
|
94
|
+
void set_text_baseline(const std::string &value);
|
|
95
|
+
const std::string &text_baseline() const { return state_.text_baseline_string; }
|
|
96
|
+
|
|
97
|
+
/* -- paths ------------------------------------------------------------- */
|
|
98
|
+
void begin_path();
|
|
99
|
+
void close_path();
|
|
100
|
+
void move_to(float x, float y);
|
|
101
|
+
void line_to(float x, float y);
|
|
102
|
+
void rect(float x, float y, float width, float height);
|
|
103
|
+
void arc(float cx, float cy, float radius, float start, float end, bool counterclockwise);
|
|
104
|
+
void quadratic_curve_to(float cpx, float cpy, float x, float y);
|
|
105
|
+
void bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y);
|
|
106
|
+
void fill();
|
|
107
|
+
void stroke();
|
|
108
|
+
|
|
109
|
+
/* -- rectangles -------------------------------------------------------- */
|
|
110
|
+
void clear_rect(float x, float y, float width, float height);
|
|
111
|
+
void fill_rect(float x, float y, float width, float height);
|
|
112
|
+
void stroke_rect(float x, float y, float width, float height);
|
|
113
|
+
|
|
114
|
+
/* -- text -------------------------------------------------------------- */
|
|
115
|
+
float measure_text(const std::string &text) const;
|
|
116
|
+
void fill_text(const std::string &text, float x, float y);
|
|
117
|
+
void stroke_text(const std::string &text, float x, float y);
|
|
118
|
+
|
|
119
|
+
/* -- images ------------------------------------------------------------ */
|
|
120
|
+
/** Draw `source` scaled into the destination rectangle, sampling the source
|
|
121
|
+
* rectangle `(sx, sy, sw, sh)`. `source` is RGBA8. */
|
|
122
|
+
void draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx, float sy,
|
|
123
|
+
float sw, float sh);
|
|
124
|
+
|
|
125
|
+
/* -- test hook --------------------------------------------------------- */
|
|
126
|
+
/** Copy an `(x, y, w, h)` region to `out` (RGBA8, row-major). Out-of-bounds
|
|
127
|
+
* pixels are transparent. Returns false when the region is empty. */
|
|
128
|
+
bool get_image_data(int x, int y, int width, int height, std::vector<unsigned char> *out) const;
|
|
129
|
+
|
|
130
|
+
private:
|
|
131
|
+
struct State {
|
|
132
|
+
CanvasTransform transform;
|
|
133
|
+
float line_width = 1.0f;
|
|
134
|
+
float global_alpha = 1.0f;
|
|
135
|
+
FontSpec font;
|
|
136
|
+
std::string font_string = "10px sans-serif";
|
|
137
|
+
int text_align = 0; // 0 start/left, 1 center, 2 right/end
|
|
138
|
+
int text_baseline = 0; // 0 alphabetic, 1 top, 2 middle, 3 bottom, 4 hanging, 5 ideographic
|
|
139
|
+
std::string text_align_string = "start";
|
|
140
|
+
std::string text_baseline_string = "alphabetic";
|
|
141
|
+
std::string fill_style = "#000000";
|
|
142
|
+
std::string stroke_style = "#000000";
|
|
143
|
+
float fill_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
144
|
+
float stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Transform a user-space point into device space. */
|
|
148
|
+
void to_device(float x, float y, float *outX, float *outY) const;
|
|
149
|
+
/** The current path transformed into device space. */
|
|
150
|
+
std::vector<CanvasSubpath> device_subpaths() const;
|
|
151
|
+
/** Add one device-space polygon loop to `out`. */
|
|
152
|
+
void flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
|
|
153
|
+
CanvasSubpath *out) const;
|
|
154
|
+
void stroke_subpath(const CanvasSubpath &points, bool closed, const float color[4]);
|
|
155
|
+
void paint_text(const std::string &text, float x, float y, const float color[4], bool stroke);
|
|
156
|
+
|
|
157
|
+
Image image_;
|
|
158
|
+
uint64_t id_ = 0;
|
|
159
|
+
uint64_t generation_ = 0;
|
|
160
|
+
State state_;
|
|
161
|
+
std::vector<State> stack_;
|
|
162
|
+
std::vector<CanvasSubpath> path_;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** The canvas with the given process-wide id, or null. Used by the renderer. */
|
|
166
|
+
Canvas2D *xt_canvas_by_id(uint64_t id);
|
|
167
|
+
|
|
168
|
+
} // namespace xtgui
|
|
169
|
+
|
|
170
|
+
#endif /* XT_GUI_CANVAS_H */
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — canvas rasteriser internals.
|
|
3
|
+
*
|
|
4
|
+
* The public object model lives in `canvas.h`; these functions are the
|
|
5
|
+
* software rasteriser `canvas.cpp` calls. They are deliberately small,
|
|
6
|
+
* self-contained and free of SDL so they can be unit-tested by reasoning about
|
|
7
|
+
* pixels alone.
|
|
8
|
+
*/
|
|
9
|
+
#ifndef XT_GUI_CANVAS_INTERNAL_H
|
|
10
|
+
#define XT_GUI_CANVAS_INTERNAL_H
|
|
11
|
+
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
#include "canvas.h"
|
|
16
|
+
|
|
17
|
+
namespace xtgui {
|
|
18
|
+
namespace canvas_detail {
|
|
19
|
+
|
|
20
|
+
/** Source-over blend `color` (0..1) with `coverage` (0..1) into one RGBA8
|
|
21
|
+
* pixel. `alpha` folds in `globalAlpha`. */
|
|
22
|
+
void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha);
|
|
23
|
+
|
|
24
|
+
/** Fill one closed polygon loop (already in device space) with `color`,
|
|
25
|
+
* anti-aliased, blending source-over. */
|
|
26
|
+
void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
|
|
27
|
+
const std::vector<CanvasPoint> &loop, const float color[4], float alpha);
|
|
28
|
+
|
|
29
|
+
/** Fill several loops as one path with the nonzero winding rule (so a contour
|
|
30
|
+
* inside another punches a hole). */
|
|
31
|
+
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
32
|
+
const std::vector<std::vector<CanvasPoint>> &loops, const float color[4],
|
|
33
|
+
float alpha);
|
|
34
|
+
|
|
35
|
+
/** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
|
|
36
|
+
* `fillRect`/`clearRect` fast path when the transform preserves axis
|
|
37
|
+
* alignment. */
|
|
38
|
+
void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
39
|
+
float x1, float y1, const float color[4], float alpha);
|
|
40
|
+
|
|
41
|
+
/** Clear a device-space axis-aligned rectangle to transparent. */
|
|
42
|
+
void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
43
|
+
float x1, float y1);
|
|
44
|
+
|
|
45
|
+
/** Blit an RGBA8 source image into the destination rectangle using nearest
|
|
46
|
+
* sampling, source-over. Runs in device space (no transform). */
|
|
47
|
+
void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
|
|
48
|
+
float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
|
|
49
|
+
float alpha);
|
|
50
|
+
|
|
51
|
+
/** Composite one grayscale glyph bitmap at `(x, baselineY)` with `color`. */
|
|
52
|
+
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
53
|
+
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
54
|
+
int top, float x, float baselineY, const float color[4], float alpha);
|
|
55
|
+
|
|
56
|
+
} // namespace canvas_detail
|
|
57
|
+
} // namespace xtgui
|
|
58
|
+
|
|
59
|
+
#endif /* XT_GUI_CANVAS_INTERNAL_H */
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — the software rasteriser behind `<canvas>`.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is CPU-side and backend-free. Polygons are filled with the
|
|
5
|
+
* nonzero winding rule and vertical sub-scanline sampling (4 samples per pixel
|
|
6
|
+
* row) plus analytic horizontal coverage, which gives cheap anti-aliasing
|
|
7
|
+
* without a full analytic rasteriser. Rectangles take a faster axis-aligned
|
|
8
|
+
* path, and glyphs are composited from their FreeType coverage masks.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "canvas_internal.h"
|
|
12
|
+
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
#include <cmath>
|
|
15
|
+
#include <cstring>
|
|
16
|
+
|
|
17
|
+
namespace xtgui {
|
|
18
|
+
namespace canvas_detail {
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
constexpr int kSubSamples = 4;
|
|
23
|
+
|
|
24
|
+
int clamp_byte(float value) {
|
|
25
|
+
int result = (int)(value + 0.5f);
|
|
26
|
+
if (result < 0) result = 0;
|
|
27
|
+
if (result > 255) result = 255;
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One edge crossing on the current sub-scanline. */
|
|
32
|
+
struct Crossing {
|
|
33
|
+
float x;
|
|
34
|
+
int winding;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
} // namespace
|
|
38
|
+
|
|
39
|
+
void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha) {
|
|
40
|
+
float sa = color[3] * coverage * alpha;
|
|
41
|
+
if (sa <= 0.0f) return;
|
|
42
|
+
if (sa > 1.0f) sa = 1.0f;
|
|
43
|
+
float da = pixel[3] / 255.0f;
|
|
44
|
+
float out_a = sa + da * (1.0f - sa);
|
|
45
|
+
if (out_a <= 0.0f) {
|
|
46
|
+
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const float inv = 1.0f / out_a;
|
|
50
|
+
pixel[0] = (unsigned char)clamp_byte((color[0] * sa + (pixel[0] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
51
|
+
pixel[1] = (unsigned char)clamp_byte((color[1] * sa + (pixel[1] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
52
|
+
pixel[2] = (unsigned char)clamp_byte((color[2] * sa + (pixel[2] / 255.0f) * da * (1.0f - sa)) * inv * 255.0f);
|
|
53
|
+
pixel[3] = (unsigned char)clamp_byte(out_a * 255.0f);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Add the horizontal span `[xa, xb)` into a bbox-relative coverage row. */
|
|
57
|
+
static void add_span(float *row, int rowOriginX, float xa, float xb, float weight, int clipX0,
|
|
58
|
+
int clipX1) {
|
|
59
|
+
if (xb <= xa) return;
|
|
60
|
+
if (xa < (float)clipX0) xa = (float)clipX0;
|
|
61
|
+
if (xb > (float)clipX1) xb = (float)clipX1;
|
|
62
|
+
if (xb <= xa) return;
|
|
63
|
+
int ia = (int)std::floor(xa);
|
|
64
|
+
int ib = (int)std::ceil(xb) - 1;
|
|
65
|
+
if (ib < ia) return;
|
|
66
|
+
if (ia == ib) {
|
|
67
|
+
row[ia - rowOriginX] += (xb - xa) * weight;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
row[ia - rowOriginX] += ((float)(ia + 1) - xa) * weight;
|
|
71
|
+
for (int x = ia + 1; x < ib; x++) row[x - rowOriginX] += weight;
|
|
72
|
+
float last = xb - (float)ib;
|
|
73
|
+
if (last > 0.0f) row[ib - rowOriginX] += last * weight;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
77
|
+
const std::vector<std::vector<CanvasPoint>> &loops, const float color[4],
|
|
78
|
+
float alpha) {
|
|
79
|
+
if (width <= 0 || height <= 0 || loops.empty()) return;
|
|
80
|
+
bool havePoint = false;
|
|
81
|
+
float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
|
|
82
|
+
for (const std::vector<CanvasPoint> &loop : loops) {
|
|
83
|
+
for (const CanvasPoint &point : loop) {
|
|
84
|
+
if (!havePoint) {
|
|
85
|
+
minX = maxX = point.x;
|
|
86
|
+
minY = maxY = point.y;
|
|
87
|
+
havePoint = true;
|
|
88
|
+
} else {
|
|
89
|
+
minX = std::min(minX, point.x);
|
|
90
|
+
maxX = std::max(maxX, point.x);
|
|
91
|
+
minY = std::min(minY, point.y);
|
|
92
|
+
maxY = std::max(maxY, point.y);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!havePoint) return;
|
|
97
|
+
int x0 = std::max(0, (int)std::floor(minX));
|
|
98
|
+
int x1 = std::min(width, (int)std::ceil(maxX));
|
|
99
|
+
int y0 = std::max(0, (int)std::floor(minY));
|
|
100
|
+
int y1 = std::min(height, (int)std::ceil(maxY));
|
|
101
|
+
if (x1 <= x0 || y1 <= y0) return;
|
|
102
|
+
|
|
103
|
+
int boxWidth = x1 - x0;
|
|
104
|
+
std::vector<float> coverage((size_t)boxWidth * (size_t)(y1 - y0), 0.0f);
|
|
105
|
+
const float weight = 1.0f / (float)kSubSamples;
|
|
106
|
+
|
|
107
|
+
std::vector<Crossing> crossings;
|
|
108
|
+
for (int py = y0; py < y1; py++) {
|
|
109
|
+
float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
|
|
110
|
+
for (int sample = 0; sample < kSubSamples; sample++) {
|
|
111
|
+
float sy = (float)py + ((float)sample + 0.5f) / (float)kSubSamples;
|
|
112
|
+
crossings.clear();
|
|
113
|
+
for (const std::vector<CanvasPoint> &loop : loops) {
|
|
114
|
+
if (loop.size() < 3) continue;
|
|
115
|
+
for (size_t i = 0; i < loop.size(); i++) {
|
|
116
|
+
const CanvasPoint &p0 = loop[i];
|
|
117
|
+
const CanvasPoint &p1 = loop[(i + 1) % loop.size()];
|
|
118
|
+
if (p0.y == p1.y) continue;
|
|
119
|
+
float top = std::min(p0.y, p1.y);
|
|
120
|
+
float bottom = std::max(p0.y, p1.y);
|
|
121
|
+
if (sy < top || sy >= bottom) continue;
|
|
122
|
+
float t = (sy - p0.y) / (p1.y - p0.y);
|
|
123
|
+
Crossing crossing;
|
|
124
|
+
crossing.x = p0.x + t * (p1.x - p0.x);
|
|
125
|
+
crossing.winding = p1.y > p0.y ? 1 : -1;
|
|
126
|
+
crossings.push_back(crossing);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (crossings.empty()) continue;
|
|
130
|
+
std::sort(crossings.begin(), crossings.end(),
|
|
131
|
+
[](const Crossing &a, const Crossing &b) { return a.x < b.x; });
|
|
132
|
+
int winding = 0;
|
|
133
|
+
float spanStart = 0.0f;
|
|
134
|
+
for (const Crossing &crossing : crossings) {
|
|
135
|
+
if (winding == 0) spanStart = crossing.x;
|
|
136
|
+
winding += crossing.winding;
|
|
137
|
+
if (winding == 0) add_span(row, x0, spanStart, crossing.x, weight, x0, x1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (int py = y0; py < y1; py++) {
|
|
143
|
+
const float *row = coverage.data() + (size_t)(py - y0) * (size_t)boxWidth;
|
|
144
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)x0) * 4;
|
|
145
|
+
for (int px = 0; px < boxWidth; px++) {
|
|
146
|
+
float c = row[px];
|
|
147
|
+
if (c <= 0.0f) continue;
|
|
148
|
+
if (c > 1.0f) c = 1.0f;
|
|
149
|
+
blend_pixel(dst + (size_t)px * 4, color, c, alpha);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
|
|
155
|
+
const std::vector<CanvasPoint> &loop, const float color[4], float alpha) {
|
|
156
|
+
fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, color, alpha);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
160
|
+
float x1, float y1, const float color[4], float alpha) {
|
|
161
|
+
if (x1 < x0) std::swap(x0, x1);
|
|
162
|
+
if (y1 < y0) std::swap(y0, y1);
|
|
163
|
+
int ix0 = std::max(0, (int)std::floor(x0));
|
|
164
|
+
int ix1 = std::min(width, (int)std::ceil(x1));
|
|
165
|
+
int iy0 = std::max(0, (int)std::floor(y0));
|
|
166
|
+
int iy1 = std::min(height, (int)std::ceil(y1));
|
|
167
|
+
for (int py = iy0; py < iy1; py++) {
|
|
168
|
+
float cy = std::min(y1, (float)(py + 1)) - std::max(y0, (float)py);
|
|
169
|
+
if (cy <= 0.0f) continue;
|
|
170
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
|
|
171
|
+
for (int px = ix0; px < ix1; px++) {
|
|
172
|
+
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
173
|
+
if (cx <= 0.0f) continue;
|
|
174
|
+
blend_pixel(dst + (size_t)(px - ix0) * 4, color, cx * cy, alpha);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
180
|
+
float x1, float y1) {
|
|
181
|
+
if (x1 < x0) std::swap(x0, x1);
|
|
182
|
+
if (y1 < y0) std::swap(y0, y1);
|
|
183
|
+
int ix0 = std::max(0, (int)std::floor(x0));
|
|
184
|
+
int ix1 = std::min(width, (int)std::ceil(x1));
|
|
185
|
+
int iy0 = std::max(0, (int)std::floor(y0));
|
|
186
|
+
int iy1 = std::min(height, (int)std::ceil(y1));
|
|
187
|
+
for (int py = iy0; py < iy1; py++) {
|
|
188
|
+
float cy = std::min(y1, (float)(py + 1)) - std::max(y0, (float)py);
|
|
189
|
+
if (cy <= 0.0f) continue;
|
|
190
|
+
unsigned char *dst = pixels.data() + ((size_t)py * (size_t)width + (size_t)ix0) * 4;
|
|
191
|
+
for (int px = ix0; px < ix1; px++) {
|
|
192
|
+
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
193
|
+
if (cx <= 0.0f) continue;
|
|
194
|
+
unsigned char *pixel = dst + (size_t)(px - ix0) * 4;
|
|
195
|
+
float keep = 1.0f - cx * cy;
|
|
196
|
+
if (keep <= 0.0f) {
|
|
197
|
+
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
|
|
198
|
+
} else {
|
|
199
|
+
pixel[3] = (unsigned char)clamp_byte(pixel[3] * keep);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
void draw_image(std::vector<unsigned char> &pixels, int width, int height, const Image &source,
|
|
206
|
+
float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
|
|
207
|
+
float alpha) {
|
|
208
|
+
if (source.width <= 0 || source.height <= 0 || dw <= 0.0f || dh <= 0.0f || sw <= 0.0f ||
|
|
209
|
+
sh <= 0.0f)
|
|
210
|
+
return;
|
|
211
|
+
int ix0 = std::max(0, (int)std::floor(dx));
|
|
212
|
+
int ix1 = std::min(width, (int)std::ceil(dx + dw));
|
|
213
|
+
int iy0 = std::max(0, (int)std::floor(dy));
|
|
214
|
+
int iy1 = std::min(height, (int)std::ceil(dy + dh));
|
|
215
|
+
for (int py = iy0; py < iy1; py++) {
|
|
216
|
+
for (int px = ix0; px < ix1; px++) {
|
|
217
|
+
float u = ((float)px + 0.5f - dx) / dw;
|
|
218
|
+
float v = ((float)py + 0.5f - dy) / dh;
|
|
219
|
+
if (u < 0.0f || u >= 1.0f || v < 0.0f || v >= 1.0f) continue;
|
|
220
|
+
int sxi = (int)(sx + u * sw);
|
|
221
|
+
int syi = (int)(sy + v * sh);
|
|
222
|
+
if (sxi < 0) sxi = 0;
|
|
223
|
+
if (syi < 0) syi = 0;
|
|
224
|
+
if (sxi >= source.width) sxi = source.width - 1;
|
|
225
|
+
if (syi >= source.height) syi = source.height - 1;
|
|
226
|
+
const unsigned char *texel =
|
|
227
|
+
source.pixels.data() + ((size_t)syi * (size_t)source.width + (size_t)sxi) * 4;
|
|
228
|
+
float color[4] = {texel[0] / 255.0f, texel[1] / 255.0f, texel[2] / 255.0f, texel[3] / 255.0f};
|
|
229
|
+
blend_pixel(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color, 1.0f, alpha);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
235
|
+
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
236
|
+
int top, float x, float baselineY, const float color[4], float alpha) {
|
|
237
|
+
int originX = (int)std::lround(x) + left;
|
|
238
|
+
int originY = (int)std::lround(baselineY) - top;
|
|
239
|
+
for (int gy = 0; gy < glyphHeight; gy++) {
|
|
240
|
+
int py = originY + gy;
|
|
241
|
+
if (py < 0 || py >= height) continue;
|
|
242
|
+
for (int gx = 0; gx < glyphWidth; gx++) {
|
|
243
|
+
int px = originX + gx;
|
|
244
|
+
if (px < 0 || px >= width) continue;
|
|
245
|
+
float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)gx] / 255.0f;
|
|
246
|
+
if (coverage <= 0.0f) continue;
|
|
247
|
+
blend_pixel(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color, coverage,
|
|
248
|
+
alpha);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
} // namespace canvas_detail
|
|
254
|
+
} // namespace xtgui
|
|
@@ -31,6 +31,8 @@ void XtDocument::load(const std::string &html, float viewportWidth, float viewpo
|
|
|
31
31
|
styles_.clear();
|
|
32
32
|
display_.clear();
|
|
33
33
|
transitions_.clear();
|
|
34
|
+
mediaSizes_.clear();
|
|
35
|
+
canvasInfo_.clear();
|
|
34
36
|
clock_ms_ = 0.0;
|
|
35
37
|
std::string css;
|
|
36
38
|
collectStyleText(root_.get(), &css);
|
|
@@ -195,7 +197,8 @@ void XtDocument::updateDisplay() {
|
|
|
195
197
|
i++;
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
|
-
layout_.compute(root_.get(), display_, viewportWidth_, viewportHeight_
|
|
200
|
+
layout_.compute(root_.get(), display_, viewportWidth_, viewportHeight_, &mediaSizes_,
|
|
201
|
+
&canvasInfo_);
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
bool XtDocument::advance(double delta_ms) {
|
|
@@ -228,6 +231,26 @@ const XtStyle *XtDocument::styleOf(const Node *node) const {
|
|
|
228
231
|
|
|
229
232
|
const LayoutBox *XtDocument::boxOf(const Node *node) const { return layout_.find(node); }
|
|
230
233
|
|
|
234
|
+
bool XtDocument::setMediaSize(const Node *node, float width, float height, uint64_t id) {
|
|
235
|
+
MediaSize &entry = mediaSizes_[node];
|
|
236
|
+
if (entry.width == width && entry.height == height && entry.id == id) return false;
|
|
237
|
+
entry.width = width;
|
|
238
|
+
entry.height = height;
|
|
239
|
+
entry.id = id;
|
|
240
|
+
dirty_ = true;
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bool XtDocument::setCanvasInfo(const Node *node, float width, float height, uint64_t id) {
|
|
245
|
+
CanvasInfo &entry = canvasInfo_[node];
|
|
246
|
+
if (entry.width == width && entry.height == height && entry.id == id) return false;
|
|
247
|
+
entry.width = width;
|
|
248
|
+
entry.height = height;
|
|
249
|
+
entry.id = id;
|
|
250
|
+
dirty_ = true;
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
231
254
|
std::vector<const Node *> XtDocument::querySelectorAll(const std::string &selectorText) const {
|
|
232
255
|
std::vector<const Node *> matches;
|
|
233
256
|
if (root_ == nullptr) return matches;
|
|
@@ -95,6 +95,15 @@ class XtDocument {
|
|
|
95
95
|
dirty_ = false;
|
|
96
96
|
return was;
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/* -- media (`<video>`) metadata --------------------------------------- */
|
|
100
|
+
/** Record (or update) the decoded size and decoder id of one `<video>` node.
|
|
101
|
+
* Returns true when the stored value changed, so the caller can invalidate a
|
|
102
|
+
* layout that was computed with the old value. */
|
|
103
|
+
bool setMediaSize(const Node *node, float width, float height, uint64_t id);
|
|
104
|
+
/** Record (or update) the pixel size and render id of one `<canvas>` node.
|
|
105
|
+
* Returns true when the stored value changed. */
|
|
106
|
+
bool setCanvasInfo(const Node *node, float width, float height, uint64_t id);
|
|
98
107
|
/** Serialized DOM, for diagnostics. */
|
|
99
108
|
std::string toDebugString() const;
|
|
100
109
|
|
|
@@ -110,6 +119,8 @@ class XtDocument {
|
|
|
110
119
|
StyleSheet sheet_;
|
|
111
120
|
std::unordered_map<const Node *, XtStyle> styles_; // target (cascade)
|
|
112
121
|
std::unordered_map<const Node *, XtStyle> display_; // after transitions
|
|
122
|
+
MediaSizeMap mediaSizes_; // `<video>` intrinsic sizes
|
|
123
|
+
CanvasInfoMap canvasInfo_; // `<canvas>` sizes + ids
|
|
113
124
|
std::vector<ActiveTransition> transitions_;
|
|
114
125
|
double clock_ms_ = 0.0;
|
|
115
126
|
LayoutTree layout_;
|
|
@@ -32,6 +32,7 @@ xt_value g_document_proto = XT_UNDEFINED;
|
|
|
32
32
|
xt_value g_event_proto = XT_UNDEFINED;
|
|
33
33
|
xt_value g_class_list_proto = XT_UNDEFINED;
|
|
34
34
|
xt_value g_style_proto = XT_UNDEFINED;
|
|
35
|
+
xt_value g_canvas_proto = XT_UNDEFINED;
|
|
35
36
|
|
|
36
37
|
/* -- small helpers -------------------------------------------------------- */
|
|
37
38
|
|
|
@@ -173,6 +174,8 @@ void xt_gui_node_handle_forget(XtGuiWindow *win, const xtgui::Node *node) {
|
|
|
173
174
|
win->node_index.erase(index);
|
|
174
175
|
}
|
|
175
176
|
win->node_handles.erase(node);
|
|
177
|
+
/* A destroyed canvas node must not keep its bitmap or GPU texture alive. */
|
|
178
|
+
xt_gui_canvas_forget(win, node);
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
/** Forget the handles of `node` and every descendant. Runs before the subtree
|