xbintsc 0.5.38 → 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/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.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.js +9 -1
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -1
- 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/extensions/gui.test.js +23 -0
- 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 +38 -19
- package/doc/gui.md +33 -1
- 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 +27 -5
- package/doc/zh-CN/gui.md +25 -2
- 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 +19 -9
- 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 +30 -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/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_scan.cpp +3 -8
- 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 +22 -30
- package/runtime/ext_gui/webgl_webgl2.cpp +36 -0
- package/runtime/ext_gui/window.cpp +56 -1
- package/src/codegen/generator/module.ts +22 -7
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +9 -2
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#include <unordered_map>
|
|
17
17
|
|
|
18
18
|
#include "canvas_internal.h"
|
|
19
|
+
#include "perf.h"
|
|
19
20
|
|
|
20
21
|
namespace xtgui {
|
|
21
22
|
|
|
@@ -85,11 +86,16 @@ void Canvas2D::resize(int width, int height) {
|
|
|
85
86
|
image_.width = width;
|
|
86
87
|
image_.height = height;
|
|
87
88
|
image_.pixels.assign((size_t)width * (size_t)height * 4, 0);
|
|
89
|
+
/* The DOM resets the rendering context when the bitmap is resized; the clip
|
|
90
|
+
* region in particular has device-space dimensions and cannot survive. */
|
|
91
|
+
state_.clip.reset();
|
|
92
|
+
shadow_layer_.pixels.clear();
|
|
88
93
|
if (!alpha_) {
|
|
89
94
|
/* `{ alpha: false }`: the canvas has an opaque black backdrop, so cleared
|
|
90
95
|
* pixels are black rather than transparent. */
|
|
91
96
|
for (size_t i = 3; i < image_.pixels.size(); i += 4) image_.pixels[i] = 255;
|
|
92
97
|
}
|
|
98
|
+
mark_all_dirty();
|
|
93
99
|
generation_++;
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -292,6 +298,57 @@ void Canvas2D::to_device(float x, float y, float *outX, float *outY) const {
|
|
|
292
298
|
state_.transform.apply(x, y, outX, outY);
|
|
293
299
|
}
|
|
294
300
|
|
|
301
|
+
void Canvas2D::mark_dirty(float x0, float y0, float x1, float y1) {
|
|
302
|
+
if (x1 < x0) std::swap(x0, x1);
|
|
303
|
+
if (y1 < y0) std::swap(y0, y1);
|
|
304
|
+
const int ix0 = std::max(0, (int)std::floor(x0));
|
|
305
|
+
const int iy0 = std::max(0, (int)std::floor(y0));
|
|
306
|
+
const int ix1 = std::min(image_.width, (int)std::ceil(x1));
|
|
307
|
+
const int iy1 = std::min(image_.height, (int)std::ceil(y1));
|
|
308
|
+
if (ix1 <= ix0 || iy1 <= iy0) return;
|
|
309
|
+
if (dirty_.empty()) {
|
|
310
|
+
dirty_ = CanvasDirtyRect{ix0, iy0, ix1, iy1};
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
dirty_.x0 = std::min(dirty_.x0, ix0);
|
|
314
|
+
dirty_.y0 = std::min(dirty_.y0, iy0);
|
|
315
|
+
dirty_.x1 = std::max(dirty_.x1, ix1);
|
|
316
|
+
dirty_.y1 = std::max(dirty_.y1, iy1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
void Canvas2D::mark_path_dirty(const std::vector<CanvasSubpath> &subpaths, float expand) {
|
|
320
|
+
bool have = false;
|
|
321
|
+
float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
|
|
322
|
+
for (const CanvasSubpath &subpath : subpaths) {
|
|
323
|
+
for (const CanvasPoint &point : subpath) {
|
|
324
|
+
if (!have) {
|
|
325
|
+
minX = maxX = point.x;
|
|
326
|
+
minY = maxY = point.y;
|
|
327
|
+
have = true;
|
|
328
|
+
} else {
|
|
329
|
+
minX = std::min(minX, point.x);
|
|
330
|
+
maxX = std::max(maxX, point.x);
|
|
331
|
+
minY = std::min(minY, point.y);
|
|
332
|
+
maxY = std::max(maxY, point.y);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!have) return;
|
|
337
|
+
mark_dirty(minX - expand, minY - expand, maxX + expand, maxY + expand);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void Canvas2D::mark_all_dirty() {
|
|
341
|
+
dirty_ = CanvasDirtyRect{0, 0, image_.width, image_.height};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
float Canvas2D::stroke_extent() const {
|
|
345
|
+
float scale = state_.transform.scale();
|
|
346
|
+
if (!(scale > 0.0f)) scale = 1.0f;
|
|
347
|
+
float half = state_.line_width * scale * 0.5f;
|
|
348
|
+
if (!(half > 0.0f)) half = 0.5f;
|
|
349
|
+
return half * std::max(1.0f, state_.miter_limit) + 1.0f;
|
|
350
|
+
}
|
|
351
|
+
|
|
295
352
|
std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
|
|
296
353
|
return device_subpaths_of(path_);
|
|
297
354
|
}
|
|
@@ -320,6 +377,7 @@ CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, uint64_t pattern_id,
|
|
|
320
377
|
paint.gradient = gradient_id == 0 ? nullptr : find_gradient(gradient_id);
|
|
321
378
|
paint.pattern = pattern_id == 0 ? nullptr : find_pattern(pattern_id);
|
|
322
379
|
paint.inverse = state_.transform.inverted();
|
|
380
|
+
paint.composite = state_.global_composite;
|
|
323
381
|
return paint;
|
|
324
382
|
}
|
|
325
383
|
|
|
@@ -332,6 +390,12 @@ CanvasPaint Canvas2D::stroke_paint() const {
|
|
|
332
390
|
}
|
|
333
391
|
|
|
334
392
|
void Canvas2D::fill(bool even_odd) {
|
|
393
|
+
if (begin_shadow_layer()) {
|
|
394
|
+
fill(even_odd);
|
|
395
|
+
end_shadow_layer();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
g_perf_frame.canvasDraws += 1;
|
|
335
399
|
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
336
400
|
std::vector<CanvasSubpath> loops;
|
|
337
401
|
for (CanvasSubpath &subpath : subpaths) {
|
|
@@ -339,12 +403,19 @@ void Canvas2D::fill(bool even_odd) {
|
|
|
339
403
|
loops.push_back(std::move(subpath));
|
|
340
404
|
}
|
|
341
405
|
if (loops.empty()) return;
|
|
406
|
+
mark_path_dirty(loops, 0.0f);
|
|
342
407
|
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
343
|
-
state_.global_alpha, even_odd);
|
|
408
|
+
state_.global_alpha, even_odd, clip_mask());
|
|
344
409
|
generation_++;
|
|
345
410
|
}
|
|
346
411
|
|
|
347
412
|
void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd) {
|
|
413
|
+
if (begin_shadow_layer()) {
|
|
414
|
+
fill_paths(paths, even_odd);
|
|
415
|
+
end_shadow_layer();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
g_perf_frame.canvasDraws += 1;
|
|
348
419
|
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
349
420
|
std::vector<CanvasSubpath> loops;
|
|
350
421
|
for (CanvasSubpath &subpath : subpaths) {
|
|
@@ -352,13 +423,21 @@ void Canvas2D::fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd
|
|
|
352
423
|
loops.push_back(std::move(subpath));
|
|
353
424
|
}
|
|
354
425
|
if (loops.empty()) return;
|
|
426
|
+
mark_path_dirty(loops, 0.0f);
|
|
355
427
|
canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
|
|
356
|
-
state_.global_alpha, even_odd);
|
|
428
|
+
state_.global_alpha, even_odd, clip_mask());
|
|
357
429
|
generation_++;
|
|
358
430
|
}
|
|
359
431
|
|
|
360
432
|
void Canvas2D::stroke() {
|
|
433
|
+
if (begin_shadow_layer()) {
|
|
434
|
+
stroke();
|
|
435
|
+
end_shadow_layer();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
g_perf_frame.canvasDraws += 1;
|
|
361
439
|
std::vector<CanvasSubpath> subpaths = device_subpaths();
|
|
440
|
+
mark_path_dirty(subpaths, stroke_extent());
|
|
362
441
|
const CanvasPaint paint = stroke_paint();
|
|
363
442
|
for (const CanvasSubpath &subpath : subpaths) {
|
|
364
443
|
if (subpath.size() < 2) continue;
|
|
@@ -370,7 +449,14 @@ void Canvas2D::stroke() {
|
|
|
370
449
|
}
|
|
371
450
|
|
|
372
451
|
void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
|
|
452
|
+
if (begin_shadow_layer()) {
|
|
453
|
+
stroke_paths(paths);
|
|
454
|
+
end_shadow_layer();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
g_perf_frame.canvasDraws += 1;
|
|
373
458
|
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
459
|
+
mark_path_dirty(subpaths, stroke_extent());
|
|
374
460
|
const CanvasPaint paint = stroke_paint();
|
|
375
461
|
for (const CanvasSubpath &subpath : subpaths) {
|
|
376
462
|
if (subpath.size() < 2) continue;
|
|
@@ -382,20 +468,30 @@ void Canvas2D::stroke_paths(const std::vector<CanvasSubpath> &paths) {
|
|
|
382
468
|
}
|
|
383
469
|
|
|
384
470
|
void Canvas2D::clear_rect(float x, float y, float width, float height) {
|
|
471
|
+
g_perf_frame.canvasDraws += 1;
|
|
385
472
|
float x0, y0, x1, y1;
|
|
386
473
|
to_device(x, y, &x0, &y0);
|
|
387
474
|
to_device(x + width, y + height, &x1, &y1);
|
|
388
|
-
|
|
475
|
+
mark_dirty(x0, y0, x1, y1);
|
|
476
|
+
canvas_detail::clear_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
|
|
477
|
+
clip_mask());
|
|
389
478
|
generation_++;
|
|
390
479
|
}
|
|
391
480
|
|
|
392
481
|
void Canvas2D::fill_rect(float x, float y, float width, float height) {
|
|
482
|
+
if (begin_shadow_layer()) {
|
|
483
|
+
fill_rect(x, y, width, height);
|
|
484
|
+
end_shadow_layer();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
g_perf_frame.canvasDraws += 1;
|
|
393
488
|
if (state_.transform.axis_aligned()) {
|
|
394
489
|
float x0, y0, x1, y1;
|
|
395
490
|
to_device(x, y, &x0, &y0);
|
|
396
491
|
to_device(x + width, y + height, &x1, &y1);
|
|
492
|
+
mark_dirty(x0, y0, x1, y1);
|
|
397
493
|
canvas_detail::fill_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
|
|
398
|
-
fill_paint(), state_.global_alpha);
|
|
494
|
+
fill_paint(), state_.global_alpha, clip_mask());
|
|
399
495
|
} else {
|
|
400
496
|
CanvasSubpath loop;
|
|
401
497
|
const float corners[4][2] = {
|
|
@@ -405,13 +501,20 @@ void Canvas2D::fill_rect(float x, float y, float width, float height) {
|
|
|
405
501
|
to_device(corner[0], corner[1], &point.x, &point.y);
|
|
406
502
|
loop.push_back(point);
|
|
407
503
|
}
|
|
504
|
+
mark_path_dirty(std::vector<CanvasSubpath>{loop}, 0.0f);
|
|
408
505
|
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, fill_paint(),
|
|
409
|
-
state_.global_alpha);
|
|
506
|
+
state_.global_alpha, false, clip_mask());
|
|
410
507
|
}
|
|
411
508
|
generation_++;
|
|
412
509
|
}
|
|
413
510
|
|
|
414
511
|
void Canvas2D::stroke_rect(float x, float y, float width, float height) {
|
|
512
|
+
if (begin_shadow_layer()) {
|
|
513
|
+
stroke_rect(x, y, width, height);
|
|
514
|
+
end_shadow_layer();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
g_perf_frame.canvasDraws += 1;
|
|
415
518
|
CanvasSubpath loop;
|
|
416
519
|
const float corners[5][2] = {
|
|
417
520
|
{x, y}, {x + width, y}, {x + width, y + height}, {x, y + height}, {x, y}};
|
|
@@ -421,103 +524,46 @@ void Canvas2D::stroke_rect(float x, float y, float width, float height) {
|
|
|
421
524
|
loop.push_back(point);
|
|
422
525
|
}
|
|
423
526
|
stroke_subpath(loop, true, stroke_paint());
|
|
527
|
+
mark_path_dirty(std::vector<CanvasSubpath>{loop}, stroke_extent());
|
|
424
528
|
generation_++;
|
|
425
529
|
}
|
|
426
530
|
|
|
427
|
-
float Canvas2D::measure_text(const std::string &text) const {
|
|
428
|
-
return xt_text_measure_width(text, state_.font);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
432
|
-
bool stroke, float max_width) {
|
|
433
|
-
Font *font = xt_text_resolve(state_.font);
|
|
434
|
-
if (font == nullptr) return;
|
|
435
|
-
std::vector<ShapedGlyph> glyphs;
|
|
436
|
-
float width = xt_text_shape_run(text, font, glyphs);
|
|
437
|
-
float ascent = 0.0f, descent = 0.0f, lineHeight = 0.0f;
|
|
438
|
-
xt_text_metrics(state_.font, &ascent, &descent, &lineHeight);
|
|
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
|
-
|
|
447
|
-
float originX = x;
|
|
448
|
-
if (state_.text_align == 1) {
|
|
449
|
-
originX -= width * horizontal * 0.5f;
|
|
450
|
-
} else if (state_.text_align == 2) {
|
|
451
|
-
originX -= width * horizontal;
|
|
452
|
-
}
|
|
453
|
-
float originY = y;
|
|
454
|
-
switch (state_.text_baseline) {
|
|
455
|
-
case 1:
|
|
456
|
-
originY += ascent;
|
|
457
|
-
break;
|
|
458
|
-
case 2:
|
|
459
|
-
originY += (ascent - descent) * 0.5f;
|
|
460
|
-
break;
|
|
461
|
-
case 3:
|
|
462
|
-
originY -= descent;
|
|
463
|
-
break;
|
|
464
|
-
case 4:
|
|
465
|
-
originY += ascent * 0.8f;
|
|
466
|
-
break;
|
|
467
|
-
case 5:
|
|
468
|
-
originY -= descent;
|
|
469
|
-
break;
|
|
470
|
-
default:
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
float scale = state_.transform.scale();
|
|
475
|
-
if (scale <= 0.0001f) scale = 1.0f;
|
|
476
|
-
bool scaled = std::fabs(scale - 1.0f) > 0.01f;
|
|
477
|
-
|
|
478
|
-
float penX = 0.0f;
|
|
479
|
-
for (const ShapedGlyph &glyph : glyphs) {
|
|
480
|
-
float userX = originX + (penX + glyph.x_offset) * horizontal;
|
|
481
|
-
float userY = originY + glyph.y_offset;
|
|
482
|
-
float deviceX = 0.0f, deviceY = 0.0f;
|
|
483
|
-
to_device(userX, userY, &deviceX, &deviceY);
|
|
484
|
-
Font *face = glyph.font != nullptr ? glyph.font : font;
|
|
485
|
-
if (scaled) {
|
|
486
|
-
Font *sized = xt_text_scale(face, state_.font.pixel_size * scale);
|
|
487
|
-
if (sized != nullptr) face = sized;
|
|
488
|
-
}
|
|
489
|
-
GlyphImage image;
|
|
490
|
-
if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
|
|
491
|
-
canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
|
|
492
|
-
image.height, image.left, image.top, deviceX, deviceY, horizontal,
|
|
493
|
-
paint, state_.global_alpha);
|
|
494
|
-
}
|
|
495
|
-
penX += glyph.x_advance;
|
|
496
|
-
}
|
|
497
|
-
(void)stroke;
|
|
498
|
-
generation_++;
|
|
499
|
-
}
|
|
500
|
-
|
|
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);
|
|
503
|
-
}
|
|
504
|
-
|
|
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);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
531
|
void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,
|
|
510
532
|
float sy, float sw, float sh) {
|
|
533
|
+
if (begin_shadow_layer()) {
|
|
534
|
+
draw_image(source, dx, dy, dw, dh, sx, sy, sw, sh);
|
|
535
|
+
end_shadow_layer();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
g_perf_frame.canvasDraws += 1;
|
|
511
539
|
if (state_.transform.axis_aligned()) {
|
|
512
540
|
float x0, y0, x1, y1;
|
|
513
541
|
to_device(dx, dy, &x0, &y0);
|
|
514
542
|
to_device(dx + dw, dy + dh, &x1, &y1);
|
|
543
|
+
mark_dirty(x0, y0, x1, y1);
|
|
515
544
|
canvas_detail::draw_image(image_.pixels, image_.width, image_.height, source, x0, y0, x1 - x0,
|
|
516
|
-
y1 - y0, sx, sy, sw, sh, state_.global_alpha
|
|
545
|
+
y1 - y0, sx, sy, sw, sh, state_.global_alpha,
|
|
546
|
+
state_.image_smoothing, clip_mask());
|
|
517
547
|
} else {
|
|
548
|
+
float minX = 0.0f, minY = 0.0f, maxX = 0.0f, maxY = 0.0f;
|
|
549
|
+
const float corners[4][2] = {{dx, dy}, {dx + dw, dy}, {dx + dw, dy + dh}, {dx, dy + dh}};
|
|
550
|
+
for (int i = 0; i < 4; i++) {
|
|
551
|
+
float px = 0.0f, py = 0.0f;
|
|
552
|
+
to_device(corners[i][0], corners[i][1], &px, &py);
|
|
553
|
+
if (i == 0) {
|
|
554
|
+
minX = maxX = px;
|
|
555
|
+
minY = maxY = py;
|
|
556
|
+
} else {
|
|
557
|
+
minX = std::min(minX, px);
|
|
558
|
+
maxX = std::max(maxX, px);
|
|
559
|
+
minY = std::min(minY, py);
|
|
560
|
+
maxY = std::max(maxY, py);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
mark_dirty(minX, minY, maxX, maxY);
|
|
518
564
|
canvas_detail::draw_image_affine(image_.pixels, image_.width, image_.height, source, dx, dy,
|
|
519
565
|
dw, dh, sx, sy, sw, sh, state_.transform,
|
|
520
|
-
state_.global_alpha);
|
|
566
|
+
state_.global_alpha, state_.image_smoothing, clip_mask());
|
|
521
567
|
}
|
|
522
568
|
generation_++;
|
|
523
569
|
}
|
package/runtime/ext_gui/canvas.h
CHANGED
|
@@ -52,6 +52,18 @@ struct CanvasPoint {
|
|
|
52
52
|
float y = 0.0f;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
/** One clip region: per-pixel coverage (0 = fully clipped, 255 = kept). A clip
|
|
56
|
+
* is baked into device space when `clip()` runs, exactly like a browser. */
|
|
57
|
+
struct CanvasClipMask {
|
|
58
|
+
std::vector<uint8_t> data;
|
|
59
|
+
int width = 0;
|
|
60
|
+
int height = 0;
|
|
61
|
+
uint8_t at(int x, int y) const {
|
|
62
|
+
if (x < 0 || y < 0 || x >= width || y >= height) return 0;
|
|
63
|
+
return data[(size_t)y * (size_t)width + (size_t)x];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
55
67
|
/** One subpath of the current path (device space). */
|
|
56
68
|
using CanvasSubpath = std::vector<CanvasPoint>;
|
|
57
69
|
|
|
@@ -94,6 +106,36 @@ class CanvasGradient {
|
|
|
94
106
|
class CanvasGradient;
|
|
95
107
|
class CanvasPattern;
|
|
96
108
|
|
|
109
|
+
/** `globalCompositeOperation` values, in the order the DOM layer maps names. */
|
|
110
|
+
enum CanvasComposite {
|
|
111
|
+
kCanvasCompositeSourceOver = 0,
|
|
112
|
+
kCanvasCompositeSourceIn,
|
|
113
|
+
kCanvasCompositeSourceOut,
|
|
114
|
+
kCanvasCompositeSourceAtop,
|
|
115
|
+
kCanvasCompositeDestinationOver,
|
|
116
|
+
kCanvasCompositeDestinationIn,
|
|
117
|
+
kCanvasCompositeDestinationOut,
|
|
118
|
+
kCanvasCompositeDestinationAtop,
|
|
119
|
+
kCanvasCompositeLighter,
|
|
120
|
+
kCanvasCompositeCopy,
|
|
121
|
+
kCanvasCompositeXor,
|
|
122
|
+
kCanvasCompositeMultiply,
|
|
123
|
+
kCanvasCompositeScreen,
|
|
124
|
+
kCanvasCompositeOverlay,
|
|
125
|
+
kCanvasCompositeDarken,
|
|
126
|
+
kCanvasCompositeLighten,
|
|
127
|
+
kCanvasCompositeColorDodge,
|
|
128
|
+
kCanvasCompositeColorBurn,
|
|
129
|
+
kCanvasCompositeHardLight,
|
|
130
|
+
kCanvasCompositeSoftLight,
|
|
131
|
+
kCanvasCompositeDifference,
|
|
132
|
+
kCanvasCompositeExclusion,
|
|
133
|
+
kCanvasCompositeHue,
|
|
134
|
+
kCanvasCompositeSaturation,
|
|
135
|
+
kCanvasCompositeColor,
|
|
136
|
+
kCanvasCompositeLuminosity,
|
|
137
|
+
};
|
|
138
|
+
|
|
97
139
|
/** What a fill or stroke paints with: a solid colour, a gradient sampled through
|
|
98
140
|
* the inverse of the current transform, or a repeating image pattern. `color`
|
|
99
141
|
* points into the canvas state and stays valid for one draw call. */
|
|
@@ -102,6 +144,8 @@ struct CanvasPaint {
|
|
|
102
144
|
const CanvasGradient *gradient = nullptr;
|
|
103
145
|
const CanvasPattern *pattern = nullptr;
|
|
104
146
|
CanvasTransform inverse;
|
|
147
|
+
/** `globalCompositeOperation` as the `CanvasComposite` enum value. */
|
|
148
|
+
int composite = 0;
|
|
105
149
|
};
|
|
106
150
|
|
|
107
151
|
/**
|
|
@@ -136,6 +180,15 @@ class Canvas2D {
|
|
|
136
180
|
uint64_t id() const { return id_; }
|
|
137
181
|
/** Bumped by every draw; the renderer re-uploads when it changes. */
|
|
138
182
|
uint64_t generation() const { return generation_; }
|
|
183
|
+
/** Device-space bounds of the pixels changed since the renderer last uploaded
|
|
184
|
+
* them. Empty means nothing visible changed (a no-op or off-canvas draw), so
|
|
185
|
+
* the renderer can skip the upload entirely. */
|
|
186
|
+
struct CanvasDirtyRect {
|
|
187
|
+
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
188
|
+
bool empty() const { return x1 <= x0 || y1 <= y0; }
|
|
189
|
+
};
|
|
190
|
+
const CanvasDirtyRect &pending_dirty() const { return dirty_; }
|
|
191
|
+
void clear_dirty() { dirty_ = CanvasDirtyRect(); }
|
|
139
192
|
const Image &image() const { return image_; }
|
|
140
193
|
/** Resize the bitmap, discarding its contents (the DOM `canvas.width = …`
|
|
141
194
|
* behaviour). */
|
|
@@ -187,6 +240,24 @@ class Canvas2D {
|
|
|
187
240
|
float line_dash_offset() const { return state_.line_dash_offset; }
|
|
188
241
|
void set_global_alpha(float alpha);
|
|
189
242
|
float global_alpha() const { return state_.global_alpha; }
|
|
243
|
+
void set_global_composite_operation(const std::string &value);
|
|
244
|
+
const std::string &global_composite_operation() const {
|
|
245
|
+
return state_.global_composite_string;
|
|
246
|
+
}
|
|
247
|
+
void set_shadow_color(const std::string &css);
|
|
248
|
+
const std::string &shadow_color() const { return state_.shadow_color_string; }
|
|
249
|
+
void set_shadow_blur(float blur);
|
|
250
|
+
float shadow_blur() const { return state_.shadow_blur; }
|
|
251
|
+
void set_shadow_offset_x(float offset) { state_.shadow_offset_x = offset; }
|
|
252
|
+
float shadow_offset_x() const { return state_.shadow_offset_x; }
|
|
253
|
+
void set_shadow_offset_y(float offset) { state_.shadow_offset_y = offset; }
|
|
254
|
+
float shadow_offset_y() const { return state_.shadow_offset_y; }
|
|
255
|
+
void set_image_smoothing_enabled(bool enabled) { state_.image_smoothing = enabled; }
|
|
256
|
+
bool image_smoothing_enabled() const { return state_.image_smoothing; }
|
|
257
|
+
void set_image_smoothing_quality(const std::string &value);
|
|
258
|
+
const std::string &image_smoothing_quality() const {
|
|
259
|
+
return state_.image_smoothing_quality;
|
|
260
|
+
}
|
|
190
261
|
void set_font(const std::string &font);
|
|
191
262
|
const std::string &font() const { return state_.font_string; }
|
|
192
263
|
void set_text_align(const std::string &value);
|
|
@@ -226,6 +297,15 @@ class Canvas2D {
|
|
|
226
297
|
* space and are transformed with the current state, like the current path. */
|
|
227
298
|
void fill_paths(const std::vector<CanvasSubpath> &paths, bool even_odd);
|
|
228
299
|
void stroke_paths(const std::vector<CanvasSubpath> &paths);
|
|
300
|
+
/** Intersect the clip region with the current path (or a `Path2D`). */
|
|
301
|
+
void clip(bool even_odd = false);
|
|
302
|
+
void clip_paths(const std::vector<CanvasSubpath> &paths, bool even_odd);
|
|
303
|
+
/** Hit testing in the current user space. */
|
|
304
|
+
bool is_point_in_path(float x, float y, bool even_odd = false) const;
|
|
305
|
+
bool is_point_in_paths(const std::vector<CanvasSubpath> &paths, float x, float y,
|
|
306
|
+
bool even_odd) const;
|
|
307
|
+
bool is_point_in_stroke(float x, float y) const;
|
|
308
|
+
bool is_point_in_stroke_paths(const std::vector<CanvasSubpath> &paths, float x, float y) const;
|
|
229
309
|
|
|
230
310
|
/* -- rectangles -------------------------------------------------------- */
|
|
231
311
|
void clear_rect(float x, float y, float width, float height);
|
|
@@ -286,10 +366,31 @@ class Canvas2D {
|
|
|
286
366
|
uint64_t stroke_pattern = 0;
|
|
287
367
|
std::string line_cap_string = "butt";
|
|
288
368
|
std::string line_join_string = "miter";
|
|
369
|
+
int global_composite = kCanvasCompositeSourceOver;
|
|
370
|
+
std::string global_composite_string = "source-over";
|
|
371
|
+
float shadow_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
372
|
+
std::string shadow_color_string = "rgba(0, 0, 0, 0)";
|
|
373
|
+
float shadow_blur = 0.0f;
|
|
374
|
+
float shadow_offset_x = 0.0f;
|
|
375
|
+
float shadow_offset_y = 0.0f;
|
|
376
|
+
bool image_smoothing = true;
|
|
377
|
+
std::string image_smoothing_quality = "low";
|
|
378
|
+
/** Current clip region; `save`/`restore` share it (a later `clip` installs a
|
|
379
|
+
* new mask instead of mutating the saved one). */
|
|
380
|
+
std::shared_ptr<CanvasClipMask> clip;
|
|
289
381
|
};
|
|
290
382
|
|
|
291
383
|
/** Transform a user-space point into device space. */
|
|
292
384
|
void to_device(float x, float y, float *outX, float *outY) const;
|
|
385
|
+
/** Union a device-space rectangle into the pending upload region. */
|
|
386
|
+
void mark_dirty(float x0, float y0, float x1, float y1);
|
|
387
|
+
/** Union the bounds of a device-space path (grown by `expand`) into the
|
|
388
|
+
* pending upload region. */
|
|
389
|
+
void mark_path_dirty(const std::vector<CanvasSubpath> &subpaths, float expand);
|
|
390
|
+
void mark_all_dirty();
|
|
391
|
+
/** How far a stroke can reach outside its centre line (half width, miter
|
|
392
|
+
* limit and a pixel of anti-aliasing slack). */
|
|
393
|
+
float stroke_extent() const;
|
|
293
394
|
/** The current path transformed into device space. */
|
|
294
395
|
std::vector<CanvasSubpath> device_subpaths() const;
|
|
295
396
|
/** Any user-space path transformed into device space. */
|
|
@@ -314,11 +415,26 @@ class Canvas2D {
|
|
|
314
415
|
std::vector<CanvasSubpath> *out) const;
|
|
315
416
|
void paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
|
|
316
417
|
bool stroke, float max_width);
|
|
418
|
+
/** True when the current state paints a shadow for a drawing operation. */
|
|
419
|
+
bool shadow_active() const;
|
|
420
|
+
/** Redirect the next draw into a scratch layer so its alpha can be turned
|
|
421
|
+
* into a shadow. Returns false while already inside the shadow layer (or when
|
|
422
|
+
* no shadow is active), in which case the caller draws directly. */
|
|
423
|
+
bool begin_shadow_layer();
|
|
424
|
+
void end_shadow_layer();
|
|
425
|
+
/** The current clip coverage, or null when the canvas is unclipped. */
|
|
426
|
+
const CanvasClipMask *clip_mask() const;
|
|
427
|
+
void composite_layer(const Image &layer);
|
|
428
|
+
void composite_shadow_layer(const Image &layer);
|
|
317
429
|
|
|
318
430
|
Image image_;
|
|
431
|
+
/** Scratch bitmap a draw is rendered into while its shadow is built. */
|
|
432
|
+
Image shadow_layer_;
|
|
433
|
+
bool in_shadow_layer_ = false;
|
|
319
434
|
bool alpha_ = true;
|
|
320
435
|
uint64_t id_ = 0;
|
|
321
436
|
uint64_t generation_ = 0;
|
|
437
|
+
CanvasDirtyRect dirty_;
|
|
322
438
|
State state_;
|
|
323
439
|
std::vector<State> stack_;
|
|
324
440
|
std::vector<CanvasSubpath> path_;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — Canvas2D clipping and hit testing.
|
|
3
|
+
*
|
|
4
|
+
* `clip()` rasterises the current path into a device-space coverage mask and
|
|
5
|
+
* intersects it with the region already in effect. `save`/`restore` share the
|
|
6
|
+
* mask through a `shared_ptr`; a later `clip()` installs a new mask, so a saved
|
|
7
|
+
* state can never observe a mutated region.
|
|
8
|
+
*
|
|
9
|
+
* `isPointInPath`/`isPointInStroke` run against the same device-space geometry
|
|
10
|
+
* the rasteriser would use, which keeps them consistent with anti-aliasing and
|
|
11
|
+
* the current transform.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "canvas_internal.h"
|
|
15
|
+
|
|
16
|
+
#include <algorithm>
|
|
17
|
+
#include <cmath>
|
|
18
|
+
#include <memory>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
namespace xtgui {
|
|
22
|
+
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
bool point_in_loops(const std::vector<CanvasSubpath> &loops, float x, float y, bool even_odd) {
|
|
26
|
+
int winding = 0;
|
|
27
|
+
int crossings = 0;
|
|
28
|
+
for (const CanvasSubpath &loop : loops) {
|
|
29
|
+
if (loop.size() < 2) continue;
|
|
30
|
+
for (size_t i = 0; i < loop.size(); i++) {
|
|
31
|
+
const CanvasPoint &p0 = loop[i];
|
|
32
|
+
const CanvasPoint &p1 = loop[(i + 1) % loop.size()];
|
|
33
|
+
if ((p0.y > y) == (p1.y > y)) continue;
|
|
34
|
+
const float intersection = p0.x + (y - p0.y) * (p1.x - p0.x) / (p1.y - p0.y);
|
|
35
|
+
if (intersection <= x) continue;
|
|
36
|
+
if (p1.y > p0.y) winding++;
|
|
37
|
+
else winding--;
|
|
38
|
+
crossings++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return even_odd ? (crossings % 2) != 0 : winding != 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
float distance_to_segment(const CanvasPoint &p, const CanvasPoint &a, const CanvasPoint &b) {
|
|
45
|
+
const float dx = b.x - a.x;
|
|
46
|
+
const float dy = b.y - a.y;
|
|
47
|
+
const float lengthSquared = dx * dx + dy * dy;
|
|
48
|
+
if (lengthSquared <= 1e-9f) return std::hypot(p.x - a.x, p.y - a.y);
|
|
49
|
+
float t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSquared;
|
|
50
|
+
t = std::max(0.0f, std::min(1.0f, t));
|
|
51
|
+
const float cx = a.x + t * dx;
|
|
52
|
+
const float cy = a.y + t * dy;
|
|
53
|
+
return std::hypot(p.x - cx, p.y - cy);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bool point_in_stroke_loops(const std::vector<CanvasSubpath> &loops, float x, float y, float half) {
|
|
57
|
+
const CanvasPoint point{x, y};
|
|
58
|
+
for (const CanvasSubpath &loop : loops) {
|
|
59
|
+
if (loop.size() < 2) continue;
|
|
60
|
+
for (size_t i = 0; i + 1 < loop.size(); i++) {
|
|
61
|
+
if (distance_to_segment(point, loop[i], loop[i + 1]) <= half) return true;
|
|
62
|
+
}
|
|
63
|
+
if (loop.front().x != loop.back().x || loop.front().y != loop.back().y) {
|
|
64
|
+
if (distance_to_segment(point, loop.back(), loop.front()) <= half) return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} // namespace
|
|
71
|
+
|
|
72
|
+
void Canvas2D::clip(bool even_odd) { clip_paths(path_, even_odd); }
|
|
73
|
+
|
|
74
|
+
void Canvas2D::clip_paths(const std::vector<CanvasSubpath> &paths, bool even_odd) {
|
|
75
|
+
std::vector<CanvasSubpath> subpaths = device_subpaths_of(paths);
|
|
76
|
+
std::vector<CanvasSubpath> loops;
|
|
77
|
+
for (CanvasSubpath &subpath : subpaths) {
|
|
78
|
+
if (subpath.size() < 3) continue;
|
|
79
|
+
loops.push_back(std::move(subpath));
|
|
80
|
+
}
|
|
81
|
+
auto mask = std::make_shared<CanvasClipMask>();
|
|
82
|
+
mask->width = image_.width;
|
|
83
|
+
mask->height = image_.height;
|
|
84
|
+
mask->data.assign((size_t)image_.width * (size_t)image_.height, 0);
|
|
85
|
+
canvas_detail::CoverageGrid coverage;
|
|
86
|
+
canvas_detail::compute_coverage(image_.width, image_.height, loops, even_odd, &coverage);
|
|
87
|
+
if (!coverage.empty()) {
|
|
88
|
+
const int rows = coverage.y1 - coverage.y0;
|
|
89
|
+
for (int r = 0; r < rows; r++) {
|
|
90
|
+
const uint32_t begin = coverage.row_offset[(size_t)r];
|
|
91
|
+
const uint32_t end = coverage.row_offset[(size_t)r + 1];
|
|
92
|
+
if (begin >= end) continue;
|
|
93
|
+
const int y = coverage.y0 + r;
|
|
94
|
+
const int rx = coverage.row_x[(size_t)r];
|
|
95
|
+
const float *values = coverage.values.data() + begin;
|
|
96
|
+
uint8_t *out = mask->data.data() + (size_t)y * (size_t)image_.width + (size_t)rx;
|
|
97
|
+
for (uint32_t i = begin; i < end; i++) {
|
|
98
|
+
float value = std::max(0.0f, std::min(1.0f, values[i - begin]));
|
|
99
|
+
out[i - begin] = (uint8_t)(value * 255.0f + 0.5f);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (state_.clip) {
|
|
104
|
+
const CanvasClipMask &previous = *state_.clip;
|
|
105
|
+
if (previous.width == mask->width && previous.height == mask->height) {
|
|
106
|
+
for (size_t i = 0; i < mask->data.size(); i++) {
|
|
107
|
+
mask->data[i] = (uint8_t)((unsigned)mask->data[i] * (unsigned)previous.data[i] / 255u);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
state_.clip = std::move(mask);
|
|
112
|
+
/* A clip changes future draws only; it touches no pixels, so it must not
|
|
113
|
+
* schedule a texture upload. */
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
bool Canvas2D::is_point_in_path(float x, float y, bool even_odd) const {
|
|
117
|
+
return is_point_in_paths(path_, x, y, even_odd);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bool Canvas2D::is_point_in_paths(const std::vector<CanvasSubpath> &paths, float x, float y,
|
|
121
|
+
bool even_odd) const {
|
|
122
|
+
float deviceX = 0.0f;
|
|
123
|
+
float deviceY = 0.0f;
|
|
124
|
+
to_device(x, y, &deviceX, &deviceY);
|
|
125
|
+
return point_in_loops(device_subpaths_of(paths), deviceX, deviceY, even_odd);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
bool Canvas2D::is_point_in_stroke(float x, float y) const {
|
|
129
|
+
return is_point_in_stroke_paths(path_, x, y);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
bool Canvas2D::is_point_in_stroke_paths(const std::vector<CanvasSubpath> &paths, float x,
|
|
133
|
+
float y) const {
|
|
134
|
+
float scale = state_.transform.scale();
|
|
135
|
+
if (!(scale > 0.0f)) scale = 1.0f;
|
|
136
|
+
const float half = std::max(0.5f, state_.line_width * scale * 0.5f);
|
|
137
|
+
float deviceX = 0.0f;
|
|
138
|
+
float deviceY = 0.0f;
|
|
139
|
+
to_device(x, y, &deviceX, &deviceY);
|
|
140
|
+
return point_in_stroke_loops(device_subpaths_of(paths), deviceX, deviceY, half);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} // namespace xtgui
|