xbintsc 0.5.36 → 0.5.37
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/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/index.js +12 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/tests/e2e/gui-browser.test.js +65 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
- package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
- package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-dom.test.js +215 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -1
- package/dist/tests/e2e/gui-events.test.js +112 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -1
- package/doc/gui/api.md +38 -9
- package/doc/gui/canvas.md +49 -18
- package/doc/gui/dom.md +51 -4
- package/doc/zh-CN/gui/api.md +33 -4
- package/doc/zh-CN/gui/canvas.md +44 -19
- package/doc/zh-CN/gui/dom.md +45 -3
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +34 -49
- package/runtime/ext_gui/browser_abort.cpp +249 -0
- package/runtime/ext_gui/browser_dialogs.cpp +76 -0
- package/runtime/ext_gui/browser_media.cpp +176 -0
- package/runtime/ext_gui/browser_navigation.cpp +130 -0
- package/runtime/ext_gui/canvas.cpp +88 -30
- package/runtime/ext_gui/canvas.h +74 -9
- package/runtime/ext_gui/canvas_image_data.cpp +143 -0
- package/runtime/ext_gui/canvas_internal.h +8 -5
- package/runtime/ext_gui/canvas_pattern.cpp +90 -0
- package/runtime/ext_gui/canvas_raster.cpp +31 -9
- package/runtime/ext_gui/canvas_style.cpp +4 -2
- package/runtime/ext_gui/dom.h +4 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
- package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
- package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
- package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
- package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
- package/runtime/ext_gui/dom_api_document.cpp +58 -5
- package/runtime/ext_gui/dom_api_events.cpp +93 -17
- package/runtime/ext_gui/dom_api_internal.h +55 -2
- package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
- package/runtime/ext_gui/dom_api_node.cpp +40 -19
- package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
- package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
- package/runtime/ext_gui/dom_api_proto.cpp +35 -0
- package/runtime/ext_gui/dom_api_style.cpp +223 -13
- package/runtime/ext_gui/gui.cpp +59 -5
- package/runtime/ext_gui/gui_engine.h +47 -0
- package/runtime/ext_gui/gui_events.cpp +80 -9
- package/runtime/ext_gui/listener_options.h +43 -0
- package/runtime/ext_gui/paint.cpp +28 -0
- package/runtime/ext_gui/paint.h +4 -0
- package/runtime/ext_gui/window.cpp +14 -65
- package/runtime/ext_gui/window_events.cpp +125 -0
- package/runtime/rt.h +26 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +0 -21
- package/runtime/xt_containers/access.inc +94 -2
- package/runtime/xt_containers/objects.inc +29 -31
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/index.ts +12 -0
|
@@ -85,9 +85,20 @@ void Canvas2D::resize(int width, int height) {
|
|
|
85
85
|
image_.width = width;
|
|
86
86
|
image_.height = height;
|
|
87
87
|
image_.pixels.assign((size_t)width * (size_t)height * 4, 0);
|
|
88
|
+
if (!alpha_) {
|
|
89
|
+
/* `{ alpha: false }`: the canvas has an opaque black backdrop, so cleared
|
|
90
|
+
* pixels are black rather than transparent. */
|
|
91
|
+
for (size_t i = 3; i < image_.pixels.size(); i += 4) image_.pixels[i] = 255;
|
|
92
|
+
}
|
|
88
93
|
generation_++;
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
void Canvas2D::set_alpha(bool alpha) {
|
|
97
|
+
if (alpha_ == alpha) return;
|
|
98
|
+
alpha_ = alpha;
|
|
99
|
+
resize(image_.width, image_.height);
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
void Canvas2D::save() { stack_.push_back(state_); }
|
|
92
103
|
|
|
93
104
|
void Canvas2D::restore() {
|
|
@@ -214,7 +225,8 @@ void Canvas2D::ellipse(float cx, float cy, float rx, float ry, float rotation, f
|
|
|
214
225
|
append_path_points(points);
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
void Canvas2D::round_rect(float x, float y, float width, float height, float
|
|
228
|
+
void Canvas2D::round_rect(float x, float y, float width, float height, float radius_tl,
|
|
229
|
+
float radius_tr, float radius_br, float radius_bl) {
|
|
218
230
|
if (width < 0.0f) {
|
|
219
231
|
x += width;
|
|
220
232
|
width = -width;
|
|
@@ -223,18 +235,25 @@ void Canvas2D::round_rect(float x, float y, float width, float height, float rad
|
|
|
223
235
|
y += height;
|
|
224
236
|
height = -height;
|
|
225
237
|
}
|
|
226
|
-
float maxRadius = std::min(width, height) * 0.5f;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
const float maxRadius = std::min(width, height) * 0.5f;
|
|
239
|
+
float radii[4] = {radius_tl, radius_tr, radius_br, radius_bl};
|
|
240
|
+
for (float &radius : radii) {
|
|
241
|
+
if (radius < 0.0f) radius = 0.0f;
|
|
242
|
+
if (radius > maxRadius) radius = maxRadius;
|
|
243
|
+
}
|
|
244
|
+
const float tl = radii[0];
|
|
245
|
+
const float tr = radii[1];
|
|
246
|
+
const float br = radii[2];
|
|
247
|
+
const float bl = radii[3];
|
|
248
|
+
move_to(x + tl, y);
|
|
249
|
+
line_to(x + width - tr, y);
|
|
250
|
+
quadratic_curve_to(x + width, y, x + width, y + tr);
|
|
251
|
+
line_to(x + width, y + height - br);
|
|
252
|
+
quadratic_curve_to(x + width, y + height, x + width - br, y + height);
|
|
253
|
+
line_to(x + bl, y + height);
|
|
254
|
+
quadratic_curve_to(x, y + height, x, y + height - bl);
|
|
255
|
+
line_to(x, y + tl);
|
|
256
|
+
quadratic_curve_to(x, y, x + tl, y);
|
|
238
257
|
close_path();
|
|
239
258
|
}
|
|
240
259
|
|
|
@@ -274,9 +293,14 @@ void Canvas2D::to_device(float x, float y, float *outX, float *outY) const {
|
|
|
274
293
|
}
|
|
275
294
|
|
|
276
295
|
std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
|
|
296
|
+
return device_subpaths_of(path_);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
std::vector<CanvasSubpath> Canvas2D::device_subpaths_of(
|
|
300
|
+
const std::vector<CanvasSubpath> &paths) const {
|
|
277
301
|
std::vector<CanvasSubpath> result;
|
|
278
|
-
result.reserve(
|
|
279
|
-
for (const CanvasSubpath &subpath :
|
|
302
|
+
result.reserve(paths.size());
|
|
303
|
+
for (const CanvasSubpath &subpath : paths) {
|
|
280
304
|
CanvasSubpath device;
|
|
281
305
|
device.reserve(subpath.size());
|
|
282
306
|
for (const CanvasPoint &point : subpath) {
|
|
@@ -289,23 +313,25 @@ std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
|
|
|
289
313
|
return result;
|
|
290
314
|
}
|
|
291
315
|
|
|
292
|
-
CanvasPaint Canvas2D::make_paint(uint64_t gradient_id,
|
|
316
|
+
CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, uint64_t pattern_id,
|
|
317
|
+
const float color[4]) const {
|
|
293
318
|
CanvasPaint paint;
|
|
294
319
|
paint.color = color;
|
|
295
320
|
paint.gradient = gradient_id == 0 ? nullptr : find_gradient(gradient_id);
|
|
321
|
+
paint.pattern = pattern_id == 0 ? nullptr : find_pattern(pattern_id);
|
|
296
322
|
paint.inverse = state_.transform.inverted();
|
|
297
323
|
return paint;
|
|
298
324
|
}
|
|
299
325
|
|
|
300
326
|
CanvasPaint Canvas2D::fill_paint() const {
|
|
301
|
-
return make_paint(state_.fill_gradient, state_.fill_color);
|
|
327
|
+
return make_paint(state_.fill_gradient, state_.fill_pattern, state_.fill_color);
|
|
302
328
|
}
|
|
303
329
|
|
|
304
330
|
CanvasPaint Canvas2D::stroke_paint() const {
|
|
305
|
-
return make_paint(state_.stroke_gradient, state_.stroke_color);
|
|
331
|
+
return make_paint(state_.stroke_gradient, state_.stroke_pattern, state_.stroke_color);
|
|
306
332
|
}
|
|
307
333
|
|
|
308
|
-
void Canvas2D::fill() {
|
|
334
|
+
void Canvas2D::fill(bool even_odd) {
|
|
309
335
|
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
310
336
|
std::vector<CanvasSubpath> loops;
|
|
311
337
|
for (CanvasSubpath &subpath : subpaths) {
|
|
@@ -314,7 +340,20 @@ void Canvas2D::fill() {
|
|
|
314
340
|
}
|
|
315
341
|
if (loops.empty()) return;
|
|
316
342
|
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
317
|
-
state_.global_alpha);
|
|
343
|
+
state_.global_alpha, even_odd);
|
|
344
|
+
generation_++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd) {
|
|
348
|
+
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
349
|
+
std::vector<CanvasSubpath> loops;
|
|
350
|
+
for (CanvasSubpath &subpath : subpaths) {
|
|
351
|
+
if (subpath.size() < 3) continue;
|
|
352
|
+
loops.push_back(std::move(subpath));
|
|
353
|
+
}
|
|
354
|
+
if (loops.empty()) return;
|
|
355
|
+
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
356
|
+
state_.global_alpha, even_odd);
|
|
318
357
|
generation_++;
|
|
319
358
|
}
|
|
320
359
|
|
|
@@ -330,6 +369,18 @@ void Canvas2D::stroke() {
|
|
|
330
369
|
generation_++;
|
|
331
370
|
}
|
|
332
371
|
|
|
372
|
+
void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
|
|
373
|
+
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
374
|
+
const CanvasPaint paint = stroke_paint();
|
|
375
|
+
for (const CanvasSubpath &subpath : subpaths) {
|
|
376
|
+
if (subpath.size() < 2) continue;
|
|
377
|
+
bool closed = subpath.size() >= 3 && subpath.front().x == subpath.back().x &&
|
|
378
|
+
subpath.front().y == subpath.back().y;
|
|
379
|
+
stroke_subpath(subpath, closed, paint);
|
|
380
|
+
}
|
|
381
|
+
generation_++;
|
|
382
|
+
}
|
|
383
|
+
|
|
333
384
|
void Canvas2D::clear_rect(float x, float y, float width, float height) {
|
|
334
385
|
float x0, y0, x1, y1;
|
|
335
386
|
to_device(x, y, &x0, &y0);
|
|
@@ -378,7 +429,7 @@ float Canvas2D::measure_text(const std::string &text) const {
|
|
|
378
429
|
}
|
|
379
430
|
|
|
380
431
|
void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
381
|
-
bool stroke) {
|
|
432
|
+
bool stroke, float max_width) {
|
|
382
433
|
Font *font = xt_text_resolve(state_.font);
|
|
383
434
|
if (font == nullptr) return;
|
|
384
435
|
std::vector<ShapedGlyph> glyphs;
|
|
@@ -386,11 +437,18 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
|
|
|
386
437
|
float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
|
|
387
438
|
xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
|
|
388
439
|
|
|
440
|
+
/* The optional `maxWidth` condenses the run horizontally instead of clipping
|
|
441
|
+
* it: advances and glyph bitmaps are squeezed by the same factor. */
|
|
442
|
+
float horizontal = 1.0f;
|
|
443
|
+
if (max_width > 0.0f && width > max_width && width > 0.0001f) {
|
|
444
|
+
horizontal = max_width / width;
|
|
445
|
+
}
|
|
446
|
+
|
|
389
447
|
float originX = x;
|
|
390
448
|
if (state_.text_align == 1) {
|
|
391
|
-
originX -= width * 0.5f;
|
|
449
|
+
originX -= width * horizontal * 0.5f;
|
|
392
450
|
} else if (state_.text_align == 2) {
|
|
393
|
-
originX -= width;
|
|
451
|
+
originX -= width * horizontal;
|
|
394
452
|
}
|
|
395
453
|
float originY = y;
|
|
396
454
|
switch (state_.text_baseline) {
|
|
@@ -419,7 +477,7 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
|
|
|
419
477
|
|
|
420
478
|
float penX = 0.0f;
|
|
421
479
|
for (const ShapedGlyph &glyph : glyphs) {
|
|
422
|
-
float userX = originX + penX + glyph.x_offset;
|
|
480
|
+
float userX = originX + (penX + glyph.x_offset) * horizontal;
|
|
423
481
|
float userY = originY + glyph.y_offset;
|
|
424
482
|
float deviceX = 0.0f, deviceY = 0.0f;
|
|
425
483
|
to_device(userX, userY, &deviceX, &deviceY);
|
|
@@ -431,8 +489,8 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
|
|
|
431
489
|
GlyphImage image;
|
|
432
490
|
if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
|
|
433
491
|
canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
|
|
434
|
-
image.height, image.left, image.top, deviceX, deviceY,
|
|
435
|
-
state_.global_alpha);
|
|
492
|
+
image.height, image.left, image.top, deviceX, deviceY, horizontal,
|
|
493
|
+
paint, state_.global_alpha);
|
|
436
494
|
}
|
|
437
495
|
penX += glyph.x_advance;
|
|
438
496
|
}
|
|
@@ -440,12 +498,12 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const Canva
|
|
|
440
498
|
generation_++;
|
|
441
499
|
}
|
|
442
500
|
|
|
443
|
-
void Canvas2D::fill_text(const std::string &text, float x, float y) {
|
|
444
|
-
paint_text(text, x, y, fill_paint(), false);
|
|
501
|
+
void Canvas2D::fill_text(const std::string &text, float x, float y, float max_width) {
|
|
502
|
+
paint_text(text, x, y, fill_paint(), false, max_width);
|
|
445
503
|
}
|
|
446
504
|
|
|
447
|
-
void Canvas2D::stroke_text(const std::string &text, float x, float y) {
|
|
448
|
-
paint_text(text, x, y, stroke_paint(), true);
|
|
505
|
+
void Canvas2D::stroke_text(const std::string &text, float x, float y, float max_width) {
|
|
506
|
+
paint_text(text, x, y, stroke_paint(), true, max_width);
|
|
449
507
|
}
|
|
450
508
|
|
|
451
509
|
void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,
|
package/runtime/ext_gui/canvas.h
CHANGED
|
@@ -91,15 +91,41 @@ class CanvasGradient {
|
|
|
91
91
|
std::vector<Stop> stops_;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
class CanvasGradient;
|
|
95
|
+
class CanvasPattern;
|
|
96
|
+
|
|
97
|
+
/** What a fill or stroke paints with: a solid colour, a gradient sampled through
|
|
98
|
+
* the inverse of the current transform, or a repeating image pattern. `color`
|
|
99
|
+
* points into the canvas state and stays valid for one draw call. */
|
|
97
100
|
struct CanvasPaint {
|
|
98
101
|
const float *color = nullptr;
|
|
99
102
|
const CanvasGradient *gradient = nullptr;
|
|
103
|
+
const CanvasPattern *pattern = nullptr;
|
|
100
104
|
CanvasTransform inverse;
|
|
101
105
|
};
|
|
102
106
|
|
|
107
|
+
/**
|
|
108
|
+
* A `CanvasPattern`: an image tiled in user space with one of the four
|
|
109
|
+
* repetition modes. Owned by the canvas that created it, like a gradient, so a
|
|
110
|
+
* stale handle resolves to no pattern instead of a dangling pointer.
|
|
111
|
+
*/
|
|
112
|
+
class CanvasPattern {
|
|
113
|
+
public:
|
|
114
|
+
enum class Repeat { Both, X, Y, None };
|
|
115
|
+
|
|
116
|
+
CanvasPattern(uint64_t id, const Image &image, Repeat repeat);
|
|
117
|
+
uint64_t id() const { return id_; }
|
|
118
|
+
Repeat repeat() const { return repeat_; }
|
|
119
|
+
/** Pattern colour at a user-space point; false when the point falls outside a
|
|
120
|
+
* non-repeating pattern (the caller then paints nothing). */
|
|
121
|
+
bool color_at(float x, float y, float out[4]) const;
|
|
122
|
+
|
|
123
|
+
private:
|
|
124
|
+
uint64_t id_ = 0;
|
|
125
|
+
Image image_;
|
|
126
|
+
Repeat repeat_ = Repeat::Both;
|
|
127
|
+
};
|
|
128
|
+
|
|
103
129
|
class Canvas2D {
|
|
104
130
|
public:
|
|
105
131
|
Canvas2D(int width, int height);
|
|
@@ -114,6 +140,10 @@ class Canvas2D {
|
|
|
114
140
|
/** Resize the bitmap, discarding its contents (the DOM `canvas.width = …`
|
|
115
141
|
* behaviour). */
|
|
116
142
|
void resize(int width, int height);
|
|
143
|
+
/** `getContextAttributes().alpha`: an opaque canvas clears to opaque black
|
|
144
|
+
* instead of transparent, as the WebGL/2D context attribute specifies. */
|
|
145
|
+
void set_alpha(bool alpha);
|
|
146
|
+
bool alpha() const { return alpha_; }
|
|
117
147
|
|
|
118
148
|
/* -- state ------------------------------------------------------------- */
|
|
119
149
|
void save();
|
|
@@ -138,6 +168,11 @@ class Canvas2D {
|
|
|
138
168
|
void set_stroke_gradient(uint64_t id);
|
|
139
169
|
uint64_t fill_gradient_id() const { return state_.fill_gradient; }
|
|
140
170
|
uint64_t stroke_gradient_id() const { return state_.stroke_gradient; }
|
|
171
|
+
/** Use a pattern created by this canvas (`0` restores the solid colour). */
|
|
172
|
+
void set_fill_pattern(uint64_t id);
|
|
173
|
+
void set_stroke_pattern(uint64_t id);
|
|
174
|
+
uint64_t fill_pattern_id() const { return state_.fill_pattern; }
|
|
175
|
+
uint64_t stroke_pattern_id() const { return state_.stroke_pattern; }
|
|
141
176
|
void set_line_width(float width) { state_.line_width = width > 0.0f ? width : 0.0f; }
|
|
142
177
|
float line_width() const { return state_.line_width; }
|
|
143
178
|
void set_line_cap(const std::string &value);
|
|
@@ -165,20 +200,32 @@ class Canvas2D {
|
|
|
165
200
|
float r1);
|
|
166
201
|
CanvasGradient *find_gradient(uint64_t id) const;
|
|
167
202
|
|
|
203
|
+
/* -- patterns ---------------------------------------------------------- */
|
|
204
|
+
/** Create a tiled pattern from `image`; the canvas owns it. */
|
|
205
|
+
CanvasPattern *create_pattern(const Image &image, CanvasPattern::Repeat repeat);
|
|
206
|
+
CanvasPattern *find_pattern(uint64_t id) const;
|
|
207
|
+
|
|
168
208
|
/* -- paths ------------------------------------------------------------- */
|
|
169
209
|
void begin_path();
|
|
170
210
|
void close_path();
|
|
171
211
|
void move_to(float x, float y);
|
|
172
212
|
void line_to(float x, float y);
|
|
173
213
|
void rect(float x, float y, float width, float height);
|
|
174
|
-
|
|
214
|
+
/** Four corner radii, clockwise from the top-left (CSS/`roundRect` order). */
|
|
215
|
+
void round_rect(float x, float y, float width, float height, float radius_tl, float radius_tr,
|
|
216
|
+
float radius_br, float radius_bl);
|
|
175
217
|
void arc(float cx, float cy, float radius, float start, float end, bool counterclockwise);
|
|
176
218
|
void ellipse(float cx, float cy, float rx, float ry, float rotation, float start, float end,
|
|
177
219
|
bool counterclockwise);
|
|
178
220
|
void quadratic_curve_to(float cpx, float cpy, float x, float y);
|
|
179
221
|
void bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y);
|
|
180
|
-
|
|
222
|
+
/** Fill the current path (`even_odd` selects the even-odd rule). */
|
|
223
|
+
void fill(bool even_odd = false);
|
|
181
224
|
void stroke();
|
|
225
|
+
/** Fill/stroke a path supplied by the caller (`Path2D`); the points are user
|
|
226
|
+
* space and are transformed with the current state, like the current path. */
|
|
227
|
+
void fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd);
|
|
228
|
+
void stroke_paths(const std::vector<CanvasSubpath> &paths);
|
|
182
229
|
|
|
183
230
|
/* -- rectangles -------------------------------------------------------- */
|
|
184
231
|
void clear_rect(float x, float y, float width, float height);
|
|
@@ -187,8 +234,11 @@ class Canvas2D {
|
|
|
187
234
|
|
|
188
235
|
/* -- text -------------------------------------------------------------- */
|
|
189
236
|
float measure_text(const std::string &text) const;
|
|
190
|
-
|
|
191
|
-
|
|
237
|
+
/** Draw text; a positive `max_width` condenses it horizontally so the run is
|
|
238
|
+
* at most that wide, as the canvas `fillText`/`strokeText` fourth argument
|
|
239
|
+
* requires. */
|
|
240
|
+
void fill_text(const std::string &text, float x, float y, float max_width = 0.0f);
|
|
241
|
+
void stroke_text(const std::string &text, float x, float y, float max_width = 0.0f);
|
|
192
242
|
|
|
193
243
|
/* -- images ------------------------------------------------------------ */
|
|
194
244
|
/** Draw `source` scaled into the destination rectangle, sampling the source
|
|
@@ -196,6 +246,15 @@ class Canvas2D {
|
|
|
196
246
|
void draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx, float sy,
|
|
197
247
|
float sw, float sh);
|
|
198
248
|
|
|
249
|
+
/* -- pixel writes and export ------------------------------------------- */
|
|
250
|
+
/** `putImageData`: copy RGBA bytes straight into the bitmap (no transform,
|
|
251
|
+
* blending or globalAlpha), clipped to the canvas and the optional dirty
|
|
252
|
+
* rectangle. */
|
|
253
|
+
void set_image_data(const std::vector<unsigned char> &rgba, int data_width, int data_height,
|
|
254
|
+
int dx, int dy, int dirty_x, int dirty_y, int dirty_width, int dirty_height);
|
|
255
|
+
/** Encode the bitmap as a PNG (used by `toDataURL`/`toBlob`). */
|
|
256
|
+
bool encode_png(std::vector<unsigned char> *out) const;
|
|
257
|
+
|
|
199
258
|
/* -- test hook --------------------------------------------------------- */
|
|
200
259
|
/** Copy an `(x, y, w, h)` region to `out` (RGBA8, row-major). Out-of-bounds
|
|
201
260
|
* pixels are transparent. Returns false when the region is empty. */
|
|
@@ -223,6 +282,8 @@ class Canvas2D {
|
|
|
223
282
|
float stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
224
283
|
uint64_t fill_gradient = 0;
|
|
225
284
|
uint64_t stroke_gradient = 0;
|
|
285
|
+
uint64_t fill_pattern = 0;
|
|
286
|
+
uint64_t stroke_pattern = 0;
|
|
226
287
|
std::string line_cap_string = "butt";
|
|
227
288
|
std::string line_join_string = "miter";
|
|
228
289
|
};
|
|
@@ -231,6 +292,8 @@ class Canvas2D {
|
|
|
231
292
|
void to_device(float x, float y, float *outX, float *outY) const;
|
|
232
293
|
/** The current path transformed into device space. */
|
|
233
294
|
std::vector<CanvasSubpath> device_subpaths() const;
|
|
295
|
+
/** Any user-space path transformed into device space. */
|
|
296
|
+
std::vector<CanvasSubpath> device_subpaths_of(const std::vector<CanvasSubpath> &paths) const;
|
|
234
297
|
/** Add one device-space polygon loop to `out`. */
|
|
235
298
|
void flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
|
|
236
299
|
CanvasSubpath *out) const;
|
|
@@ -240,7 +303,7 @@ class Canvas2D {
|
|
|
240
303
|
/** Paint descriptors for the current state. */
|
|
241
304
|
CanvasPaint fill_paint() const;
|
|
242
305
|
CanvasPaint stroke_paint() const;
|
|
243
|
-
CanvasPaint make_paint(uint64_t gradient_id, const float color[4]) const;
|
|
306
|
+
CanvasPaint make_paint(uint64_t gradient_id, uint64_t pattern_id, const float color[4]) const;
|
|
244
307
|
void stroke_subpath(const CanvasSubpath &points, bool closed, const CanvasPaint &paint);
|
|
245
308
|
/** Stroke one polyline with caps and joins; `half` is half the device-space
|
|
246
309
|
* line width. Used by `stroke_subpath` for both solid and dashed paths. */
|
|
@@ -250,15 +313,17 @@ class Canvas2D {
|
|
|
250
313
|
void dash_polyline(const CanvasSubpath &points, bool closed,
|
|
251
314
|
std::vector<CanvasSubpath> *out) const;
|
|
252
315
|
void paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
253
|
-
bool stroke);
|
|
316
|
+
bool stroke, float max_width);
|
|
254
317
|
|
|
255
318
|
Image image_;
|
|
319
|
+
bool alpha_ = true;
|
|
256
320
|
uint64_t id_ = 0;
|
|
257
321
|
uint64_t generation_ = 0;
|
|
258
322
|
State state_;
|
|
259
323
|
std::vector<State> stack_;
|
|
260
324
|
std::vector<CanvasSubpath> path_;
|
|
261
325
|
std::unordered_map<uint64_t, std::unique_ptr<CanvasGradient>> gradients_;
|
|
326
|
+
std::unordered_map<uint64_t, std::unique_ptr<CanvasPattern>> patterns_;
|
|
262
327
|
};
|
|
263
328
|
|
|
264
329
|
/** The canvas with the given process-wide id, or null. Used by the renderer. */
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `putImageData` and PNG export for `<canvas>`.
|
|
3
|
+
*
|
|
4
|
+
* `putImageData` writes RGBA bytes straight into the bitmap: it ignores the
|
|
5
|
+
* transform, `globalAlpha` and compositing, exactly as the canvas API requires.
|
|
6
|
+
* PNG export writes the bitmap back out for `toDataURL`/`toBlob`; it uses a
|
|
7
|
+
* zlib "stored" stream (no compression) so the engine needs no deflate
|
|
8
|
+
* dependency, which is enough for the small UI snapshots pages take.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "canvas.h"
|
|
12
|
+
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
#include <cstdint>
|
|
15
|
+
#include <cstring>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
namespace xtgui {
|
|
19
|
+
|
|
20
|
+
void Canvas2D::set_image_data(const std::vector<unsigned char> &rgba, int data_width,
|
|
21
|
+
int data_height, int dx, int dy, int dirty_x, int dirty_y,
|
|
22
|
+
int dirty_width, int dirty_height) {
|
|
23
|
+
if (data_width <= 0 || data_height <= 0) return;
|
|
24
|
+
if (rgba.size() < (size_t)data_width * (size_t)data_height * 4) return;
|
|
25
|
+
int source_x0 = 0;
|
|
26
|
+
int source_y0 = 0;
|
|
27
|
+
int source_x1 = data_width;
|
|
28
|
+
int source_y1 = data_height;
|
|
29
|
+
/* The optional dirty rectangle is in image coordinates. */
|
|
30
|
+
if (dirty_width > 0 && dirty_height > 0) {
|
|
31
|
+
source_x0 = std::max(0, dirty_x);
|
|
32
|
+
source_y0 = std::max(0, dirty_y);
|
|
33
|
+
source_x1 = std::min(data_width, dirty_x + dirty_width);
|
|
34
|
+
source_y1 = std::min(data_height, dirty_y + dirty_height);
|
|
35
|
+
}
|
|
36
|
+
if (source_x1 <= source_x0 || source_y1 <= source_y0) return;
|
|
37
|
+
int dest_x0 = std::max(0, dx + source_x0);
|
|
38
|
+
int dest_y0 = std::max(0, dy + source_y0);
|
|
39
|
+
int dest_x1 = std::min(image_.width, dx + source_x1);
|
|
40
|
+
int dest_y1 = std::min(image_.height, dy + source_y1);
|
|
41
|
+
for (int py = dest_y0; py < dest_y1; py++) {
|
|
42
|
+
int source_row = py - dy;
|
|
43
|
+
const unsigned char *src =
|
|
44
|
+
rgba.data() + ((size_t)source_row * (size_t)data_width + (size_t)(dest_x0 - dx)) * 4;
|
|
45
|
+
unsigned char *dst =
|
|
46
|
+
image_.pixels.data() + ((size_t)py * (size_t)image_.width + (size_t)dest_x0) * 4;
|
|
47
|
+
memcpy(dst, src, (size_t)(dest_x1 - dest_x0) * 4);
|
|
48
|
+
}
|
|
49
|
+
generation_++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
namespace {
|
|
53
|
+
|
|
54
|
+
uint32_t crc32_update(uint32_t crc, const unsigned char *data, size_t length) {
|
|
55
|
+
crc = ~crc;
|
|
56
|
+
for (size_t i = 0; i < length; i++) {
|
|
57
|
+
crc ^= data[i];
|
|
58
|
+
for (int bit = 0; bit < 8; bit++) {
|
|
59
|
+
crc = (crc >> 1) ^ (0xEDB88320u & (uint32_t)(-(int32_t)(crc & 1u)));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return ~crc;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void append_u32(std::vector<unsigned char> *out, uint32_t value) {
|
|
66
|
+
out->push_back((unsigned char)(value >> 24));
|
|
67
|
+
out->push_back((unsigned char)(value >> 16));
|
|
68
|
+
out->push_back((unsigned char)(value >> 8));
|
|
69
|
+
out->push_back((unsigned char)value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void append_chunk(std::vector<unsigned char> *out, const char type[4],
|
|
73
|
+
const std::vector<unsigned char> &data) {
|
|
74
|
+
append_u32(out, (uint32_t)data.size());
|
|
75
|
+
size_t crc_start = out->size();
|
|
76
|
+
out->insert(out->end(), type, type + 4);
|
|
77
|
+
out->insert(out->end(), data.begin(), data.end());
|
|
78
|
+
uint32_t crc = crc32_update(0, out->data() + crc_start, out->size() - crc_start);
|
|
79
|
+
append_u32(out, crc);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
uint32_t adler32(const std::vector<unsigned char> &data) {
|
|
83
|
+
uint32_t a = 1;
|
|
84
|
+
uint32_t b = 0;
|
|
85
|
+
for (unsigned char byte : data) {
|
|
86
|
+
a = (a + byte) % 65521u;
|
|
87
|
+
b = (b + a) % 65521u;
|
|
88
|
+
}
|
|
89
|
+
return (b << 16) | a;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** zlib stream with uncompressed (stored) deflate blocks. */
|
|
93
|
+
std::vector<unsigned char> zlib_store(const std::vector<unsigned char> &data) {
|
|
94
|
+
std::vector<unsigned char> out;
|
|
95
|
+
out.push_back(0x78);
|
|
96
|
+
out.push_back(0x01);
|
|
97
|
+
size_t offset = 0;
|
|
98
|
+
do {
|
|
99
|
+
size_t chunk = std::min<size_t>(65535, data.size() - offset);
|
|
100
|
+
bool last = offset + chunk >= data.size();
|
|
101
|
+
out.push_back(last ? 1 : 0);
|
|
102
|
+
out.push_back((unsigned char)(chunk & 0xFF));
|
|
103
|
+
out.push_back((unsigned char)(chunk >> 8));
|
|
104
|
+
out.push_back((unsigned char)(~chunk & 0xFF));
|
|
105
|
+
out.push_back((unsigned char)((~chunk >> 8) & 0xFF));
|
|
106
|
+
out.insert(out.end(), data.begin() + offset, data.begin() + offset + chunk);
|
|
107
|
+
offset += chunk;
|
|
108
|
+
} while (offset < data.size());
|
|
109
|
+
append_u32(&out, adler32(data));
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
} // namespace
|
|
114
|
+
|
|
115
|
+
bool Canvas2D::encode_png(std::vector<unsigned char> *out) const {
|
|
116
|
+
if (out == nullptr || image_.width <= 0 || image_.height <= 0) return false;
|
|
117
|
+
out->clear();
|
|
118
|
+
static const unsigned char signature[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
|
|
119
|
+
out->insert(out->end(), signature, signature + 8);
|
|
120
|
+
|
|
121
|
+
std::vector<unsigned char> header;
|
|
122
|
+
append_u32(&header, (uint32_t)image_.width);
|
|
123
|
+
append_u32(&header, (uint32_t)image_.height);
|
|
124
|
+
header.push_back(8); // bit depth
|
|
125
|
+
header.push_back(6); // colour type: RGBA
|
|
126
|
+
header.push_back(0); // compression
|
|
127
|
+
header.push_back(0); // filter
|
|
128
|
+
header.push_back(0); // interlace
|
|
129
|
+
append_chunk(out, "IHDR", header);
|
|
130
|
+
|
|
131
|
+
std::vector<unsigned char> raw;
|
|
132
|
+
raw.reserve(((size_t)image_.width * 4 + 1) * (size_t)image_.height);
|
|
133
|
+
for (int y = 0; y < image_.height; y++) {
|
|
134
|
+
raw.push_back(0); // filter type: none
|
|
135
|
+
const unsigned char *row = image_.pixels.data() + (size_t)y * (size_t)image_.width * 4;
|
|
136
|
+
raw.insert(raw.end(), row, row + (size_t)image_.width * 4);
|
|
137
|
+
}
|
|
138
|
+
append_chunk(out, "IDAT", zlib_store(raw));
|
|
139
|
+
append_chunk(out, "IEND", std::vector<unsigned char>());
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} // namespace xtgui
|
|
@@ -24,13 +24,14 @@ void blend_pixel(unsigned char *pixel, const float color[4], float coverage, flo
|
|
|
24
24
|
/** Fill one closed polygon loop (already in device space) with `paint`,
|
|
25
25
|
* anti-aliased, blending source-over. */
|
|
26
26
|
void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
|
|
27
|
-
const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha
|
|
27
|
+
const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha,
|
|
28
|
+
bool even_odd = false);
|
|
28
29
|
|
|
29
30
|
/** Fill several loops as one path with the nonzero winding rule (so a contour
|
|
30
|
-
* inside another punches a hole). */
|
|
31
|
+
* inside another punches a hole), or the even-odd rule. */
|
|
31
32
|
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
32
33
|
const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
|
|
33
|
-
float alpha);
|
|
34
|
+
float alpha, bool even_odd = false);
|
|
34
35
|
|
|
35
36
|
/** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
|
|
36
37
|
* `fillRect`/`clearRect` fast path when the transform preserves axis
|
|
@@ -58,10 +59,12 @@ void draw_image_affine(std::vector<unsigned char> &pixels, int width, int height
|
|
|
58
59
|
float sy, float sw, float sh, const CanvasTransform &transform,
|
|
59
60
|
float alpha);
|
|
60
61
|
|
|
61
|
-
/** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`.
|
|
62
|
+
/** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`.
|
|
63
|
+
* `scale_x` < 1 condenses the bitmap horizontally (`fillText`'s `maxWidth`). */
|
|
62
64
|
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
63
65
|
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
64
|
-
int top, float x, float baselineY, const CanvasPaint &paint,
|
|
66
|
+
int top, float x, float baselineY, float scale_x, const CanvasPaint &paint,
|
|
67
|
+
float alpha);
|
|
65
68
|
|
|
66
69
|
} // namespace canvas_detail
|
|
67
70
|
} // namespace xtgui
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `CanvasPattern`.
|
|
3
|
+
*
|
|
4
|
+
* A pattern is an image tiled in user space with the four repetition modes.
|
|
5
|
+
* Like a gradient it is owned by the canvas that created it (and addressed by
|
|
6
|
+
* id), so a stale context handle resolves to "no pattern" instead of a dangling
|
|
7
|
+
* pointer. The rasteriser samples it through the inverse transform, which keeps
|
|
8
|
+
* the tiles attached to the shape when it is translated, scaled or rotated.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "canvas.h"
|
|
12
|
+
|
|
13
|
+
#include <atomic>
|
|
14
|
+
#include <cmath>
|
|
15
|
+
#include <memory>
|
|
16
|
+
|
|
17
|
+
#include "canvas_internal.h"
|
|
18
|
+
|
|
19
|
+
namespace xtgui {
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
std::atomic<uint64_t> g_next_pattern_id{1};
|
|
24
|
+
|
|
25
|
+
} // namespace
|
|
26
|
+
|
|
27
|
+
CanvasPattern::CanvasPattern(uint64_t id, const Image &image, Repeat repeat)
|
|
28
|
+
: id_(id), image_(image), repeat_(repeat) {}
|
|
29
|
+
|
|
30
|
+
bool CanvasPattern::color_at(float x, float y, float out[4]) const {
|
|
31
|
+
const int width = image_.width;
|
|
32
|
+
const int height = image_.height;
|
|
33
|
+
if (width <= 0 || height <= 0) return false;
|
|
34
|
+
/* Tile in user space: wrap the coordinate into one tile for the enabled
|
|
35
|
+
* axes, and reject points outside the tile for the disabled ones. */
|
|
36
|
+
bool repeatX = repeat_ == Repeat::Both || repeat_ == Repeat::X;
|
|
37
|
+
bool repeatY = repeat_ == Repeat::Both || repeat_ == Repeat::Y;
|
|
38
|
+
float u = x;
|
|
39
|
+
float v = y;
|
|
40
|
+
if (repeatX) {
|
|
41
|
+
u = x - std::floor(x / (float)width) * (float)width;
|
|
42
|
+
if (u < 0.0f) u += (float)width;
|
|
43
|
+
} else if (x < 0.0f || x >= (float)width) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (repeatY) {
|
|
47
|
+
v = y - std::floor(y / (float)height) * (float)height;
|
|
48
|
+
if (v < 0.0f) v += (float)height;
|
|
49
|
+
} else if (y < 0.0f || y >= (float)height) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
int px = (int)u;
|
|
53
|
+
int py = (int)v;
|
|
54
|
+
if (px < 0) px = 0;
|
|
55
|
+
if (py < 0) py = 0;
|
|
56
|
+
if (px >= width) px = width - 1;
|
|
57
|
+
if (py >= height) py = height - 1;
|
|
58
|
+
const unsigned char *texel =
|
|
59
|
+
image_.pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4;
|
|
60
|
+
out[0] = (float)texel[0] / 255.0f;
|
|
61
|
+
out[1] = (float)texel[1] / 255.0f;
|
|
62
|
+
out[2] = (float)texel[2] / 255.0f;
|
|
63
|
+
out[3] = (float)texel[3] / 255.0f;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
CanvasPattern *Canvas2D::create_pattern(const Image &image, CanvasPattern::Repeat repeat) {
|
|
68
|
+
auto pattern =
|
|
69
|
+
std::make_unique<CanvasPattern>(g_next_pattern_id.fetch_add(1), image, repeat);
|
|
70
|
+
CanvasPattern *raw = pattern.get();
|
|
71
|
+
patterns_[raw->id()] = std::move(pattern);
|
|
72
|
+
return raw;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
CanvasPattern *Canvas2D::find_pattern(uint64_t id) const {
|
|
76
|
+
auto found = patterns_.find(id);
|
|
77
|
+
return found == patterns_.end() ? nullptr : found->second.get();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void Canvas2D::set_fill_pattern(uint64_t id) {
|
|
81
|
+
state_.fill_pattern = find_pattern(id) != nullptr ? id : 0;
|
|
82
|
+
if (state_.fill_pattern != 0) state_.fill_gradient = 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void Canvas2D::set_stroke_pattern(uint64_t id) {
|
|
86
|
+
state_.stroke_pattern = find_pattern(id) != nullptr ? id : 0;
|
|
87
|
+
if (state_.stroke_pattern != 0) state_.stroke_gradient = 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
} // namespace xtgui
|