xbintsc 0.5.21 → 0.5.22
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/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/tests/e2e/gui-canvas.test.js +159 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-canvas.test.d.ts +11 -0
- package/dist/tests/e2e/gui-example-canvas.test.js +70 -0
- package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -0
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/gui/canvas.md +47 -21
- package/doc/gui.md +7 -1
- package/doc/zh-CN/gui/canvas.md +36 -17
- package/doc/zh-CN/gui.md +6 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +114 -199
- package/runtime/ext_gui/canvas.h +101 -2
- package/runtime/ext_gui/canvas_gradient.cpp +112 -0
- package/runtime/ext_gui/canvas_internal.h +6 -6
- package/runtime/ext_gui/canvas_raster.cpp +27 -5
- package/runtime/ext_gui/canvas_stroke.cpp +252 -0
- package/runtime/ext_gui/canvas_style.cpp +240 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +38 -4
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +232 -0
- package/runtime/ext_gui/dom_api_internal.h +31 -0
- package/runtime/ext_gui/dom_api_proto.cpp +24 -0
- package/src/driver/version.ts +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CanvasGradient objects.
|
|
3
|
+
*
|
|
4
|
+
* A gradient is owned by the canvas that created it (`Canvas2D::gradients_`),
|
|
5
|
+
* so a handle that outlives the document resolves to no gradient instead of a
|
|
6
|
+
* dangling pointer. Colour stops stay live: a later `addColorStop` is visible
|
|
7
|
+
* to every fill that already uses the gradient, which is the canvas semantics
|
|
8
|
+
* the DOM layer exposes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <algorithm>
|
|
12
|
+
#include <atomic>
|
|
13
|
+
#include <cmath>
|
|
14
|
+
|
|
15
|
+
#include "canvas.h"
|
|
16
|
+
|
|
17
|
+
namespace xtgui {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
std::atomic<uint64_t> g_next_gradient_id{1};
|
|
22
|
+
|
|
23
|
+
float clamp_unit(float value) {
|
|
24
|
+
if (value < 0.0f) return 0.0f;
|
|
25
|
+
if (value > 1.0f) return 1.0f;
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
} // namespace
|
|
30
|
+
|
|
31
|
+
CanvasGradient::CanvasGradient(uint64_t id, Type type, float x0, float y0, float x1, float y1,
|
|
32
|
+
float r0, float r1)
|
|
33
|
+
: id_(id), type_(type), x0_(x0), y0_(y0), x1_(x1), y1_(y1), r0_(r0), r1_(r1) {}
|
|
34
|
+
|
|
35
|
+
void CanvasGradient::add_color_stop(float offset, const float color[4]) {
|
|
36
|
+
Stop stop;
|
|
37
|
+
stop.offset = clamp_unit(offset);
|
|
38
|
+
for (int i = 0; i < 4; i++) stop.color[i] = color[i];
|
|
39
|
+
auto position = std::upper_bound(stops_.begin(), stops_.end(), stop,
|
|
40
|
+
[](const Stop &a, const Stop &b) { return a.offset < b.offset; });
|
|
41
|
+
stops_.insert(position, stop);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
bool CanvasGradient::color_at(float x, float y, float out[4]) const {
|
|
45
|
+
if (stops_.empty()) {
|
|
46
|
+
out[0] = out[1] = out[2] = out[3] = 0.0f;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
float t = 0.0f;
|
|
50
|
+
if (type_ == Type::Linear) {
|
|
51
|
+
const float dx = x1_ - x0_;
|
|
52
|
+
const float dy = y1_ - y0_;
|
|
53
|
+
const float lengthSquared = dx * dx + dy * dy;
|
|
54
|
+
if (lengthSquared > 1e-9f) {
|
|
55
|
+
t = ((x - x0_) * dx + (y - y0_) * dy) / lengthSquared;
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
const float dx = x - x1_;
|
|
59
|
+
const float dy = y - y1_;
|
|
60
|
+
const float distance = std::sqrt(dx * dx + dy * dy);
|
|
61
|
+
const float radiusDelta = r1_ - r0_;
|
|
62
|
+
if (std::fabs(radiusDelta) > 1e-6f) t = (distance - r0_) / radiusDelta;
|
|
63
|
+
}
|
|
64
|
+
t = clamp_unit(t);
|
|
65
|
+
if (t <= stops_.front().offset) {
|
|
66
|
+
for (int i = 0; i < 4; i++) out[i] = stops_.front().color[i];
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (t >= stops_.back().offset) {
|
|
70
|
+
for (int i = 0; i < 4; i++) out[i] = stops_.back().color[i];
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
for (size_t i = 1; i < stops_.size(); i++) {
|
|
74
|
+
const Stop &a = stops_[i - 1];
|
|
75
|
+
const Stop &b = stops_[i];
|
|
76
|
+
if (t > b.offset) continue;
|
|
77
|
+
const float span = b.offset - a.offset;
|
|
78
|
+
const float u = span > 1e-9f ? (t - a.offset) / span : 1.0f;
|
|
79
|
+
for (int channel = 0; channel < 4; channel++) {
|
|
80
|
+
out[channel] = a.color[channel] + (b.color[channel] - a.color[channel]) * u;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
for (int i = 0; i < 4; i++) out[i] = stops_.back().color[i];
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
CanvasGradient *Canvas2D::create_linear_gradient(float x0, float y0, float x1, float y1) {
|
|
89
|
+
auto gradient = std::make_unique<CanvasGradient>(g_next_gradient_id.fetch_add(1),
|
|
90
|
+
CanvasGradient::Type::Linear, x0, y0, x1, y1,
|
|
91
|
+
0.0f, 1.0f);
|
|
92
|
+
CanvasGradient *raw = gradient.get();
|
|
93
|
+
gradients_[raw->id()] = std::move(gradient);
|
|
94
|
+
return raw;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
CanvasGradient *Canvas2D::create_radial_gradient(float x0, float y0, float r0, float x1, float y1,
|
|
98
|
+
float r1) {
|
|
99
|
+
auto gradient = std::make_unique<CanvasGradient>(g_next_gradient_id.fetch_add(1),
|
|
100
|
+
CanvasGradient::Type::Radial, x0, y0, x1, y1, r0,
|
|
101
|
+
r1);
|
|
102
|
+
CanvasGradient *raw = gradient.get();
|
|
103
|
+
gradients_[raw->id()] = std::move(gradient);
|
|
104
|
+
return raw;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
CanvasGradient *Canvas2D::find_gradient(uint64_t id) const {
|
|
108
|
+
auto found = gradients_.find(id);
|
|
109
|
+
return found == gradients_.end() ? nullptr : found->second.get();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace xtgui
|
|
@@ -21,22 +21,22 @@ namespace canvas_detail {
|
|
|
21
21
|
* pixel. `alpha` folds in `globalAlpha`. */
|
|
22
22
|
void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha);
|
|
23
23
|
|
|
24
|
-
/** Fill one closed polygon loop (already in device space) with `
|
|
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
|
|
27
|
+
const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha);
|
|
28
28
|
|
|
29
29
|
/** Fill several loops as one path with the nonzero winding rule (so a contour
|
|
30
30
|
* inside another punches a hole). */
|
|
31
31
|
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
32
|
-
const std::vector<std::vector<CanvasPoint>> &loops, const
|
|
32
|
+
const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
|
|
33
33
|
float alpha);
|
|
34
34
|
|
|
35
35
|
/** Fill a device-space axis-aligned rectangle, anti-aliased. Used by the
|
|
36
36
|
* `fillRect`/`clearRect` fast path when the transform preserves axis
|
|
37
37
|
* alignment. */
|
|
38
38
|
void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
39
|
-
float x1, float y1, const
|
|
39
|
+
float x1, float y1, const CanvasPaint &paint, float alpha);
|
|
40
40
|
|
|
41
41
|
/** Clear a device-space axis-aligned rectangle to transparent. */
|
|
42
42
|
void clear_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
@@ -48,10 +48,10 @@ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const
|
|
|
48
48
|
float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh,
|
|
49
49
|
float alpha);
|
|
50
50
|
|
|
51
|
-
/** Composite one grayscale glyph bitmap at `(x, baselineY)` with `
|
|
51
|
+
/** Composite one grayscale glyph bitmap at `(x, baselineY)` with `paint`. */
|
|
52
52
|
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
53
53
|
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
54
|
-
int top, float x, float baselineY, const
|
|
54
|
+
int top, float x, float baselineY, const CanvasPaint &paint, float alpha);
|
|
55
55
|
|
|
56
56
|
} // namespace canvas_detail
|
|
57
57
|
} // namespace xtgui
|
|
@@ -34,6 +34,22 @@ struct Crossing {
|
|
|
34
34
|
int winding;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
/** Resolve `paint` at a device-space pixel centre. Gradients are defined in
|
|
38
|
+
* user space, so the pixel is mapped back through the inverse transform. */
|
|
39
|
+
void paint_color_at(const CanvasPaint &paint, float x, float y, float out[4]) {
|
|
40
|
+
if (paint.gradient != nullptr) {
|
|
41
|
+
const CanvasTransform &inverse = paint.inverse;
|
|
42
|
+
float userX = inverse.a * x + inverse.c * y + inverse.e;
|
|
43
|
+
float userY = inverse.b * x + inverse.d * y + inverse.f;
|
|
44
|
+
if (paint.gradient->color_at(userX, userY, out)) return;
|
|
45
|
+
}
|
|
46
|
+
if (paint.color != nullptr) {
|
|
47
|
+
for (int i = 0; i < 4; i++) out[i] = paint.color[i];
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
out[0] = out[1] = out[2] = out[3] = 0.0f;
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
} // namespace
|
|
38
54
|
|
|
39
55
|
void blend_pixel(unsigned char *pixel, const float color[4], float coverage, float alpha) {
|
|
@@ -74,7 +90,7 @@ static void add_span(float *row, int rowOriginX, float xa, float xb, float weigh
|
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
77
|
-
const std::vector<std::vector<CanvasPoint>> &loops, const
|
|
93
|
+
const std::vector<std::vector<CanvasPoint>> &loops, const CanvasPaint &paint,
|
|
78
94
|
float alpha) {
|
|
79
95
|
if (width <= 0 || height <= 0 || loops.empty()) return;
|
|
80
96
|
bool havePoint = false;
|
|
@@ -146,18 +162,20 @@ void fill_loops(std::vector<unsigned char> &pixels, int width, int height,
|
|
|
146
162
|
float c = row[px];
|
|
147
163
|
if (c <= 0.0f) continue;
|
|
148
164
|
if (c > 1.0f) c = 1.0f;
|
|
165
|
+
float color[4];
|
|
166
|
+
paint_color_at(paint, (float)(x0 + px) + 0.5f, (float)py + 0.5f, color);
|
|
149
167
|
blend_pixel(dst + (size_t)px * 4, color, c, alpha);
|
|
150
168
|
}
|
|
151
169
|
}
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
void fill_loop(std::vector<unsigned char> &pixels, int width, int height,
|
|
155
|
-
const std::vector<CanvasPoint> &loop, const
|
|
156
|
-
fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop},
|
|
173
|
+
const std::vector<CanvasPoint> &loop, const CanvasPaint &paint, float alpha) {
|
|
174
|
+
fill_loops(pixels, width, height, std::vector<std::vector<CanvasPoint>>{loop}, paint, alpha);
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float x0, float y0,
|
|
160
|
-
float x1, float y1, const
|
|
178
|
+
float x1, float y1, const CanvasPaint &paint, float alpha) {
|
|
161
179
|
if (x1 < x0) std::swap(x0, x1);
|
|
162
180
|
if (y1 < y0) std::swap(y0, y1);
|
|
163
181
|
int ix0 = std::max(0, (int)std::floor(x0));
|
|
@@ -171,6 +189,8 @@ void fill_rect(std::vector<unsigned char> &pixels, int width, int height, float
|
|
|
171
189
|
for (int px = ix0; px < ix1; px++) {
|
|
172
190
|
float cx = std::min(x1, (float)(px + 1)) - std::max(x0, (float)px);
|
|
173
191
|
if (cx <= 0.0f) continue;
|
|
192
|
+
float color[4];
|
|
193
|
+
paint_color_at(paint, (float)px + 0.5f, (float)py + 0.5f, color);
|
|
174
194
|
blend_pixel(dst + (size_t)(px - ix0) * 4, color, cx * cy, alpha);
|
|
175
195
|
}
|
|
176
196
|
}
|
|
@@ -233,7 +253,7 @@ void draw_image(std::vector<unsigned char> &pixels, int width, int height, const
|
|
|
233
253
|
|
|
234
254
|
void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
235
255
|
const std::vector<unsigned char> &glyph, int glyphWidth, int glyphHeight, int left,
|
|
236
|
-
int top, float x, float baselineY, const
|
|
256
|
+
int top, float x, float baselineY, const CanvasPaint &paint, float alpha) {
|
|
237
257
|
int originX = (int)std::lround(x) + left;
|
|
238
258
|
int originY = (int)std::lround(baselineY) - top;
|
|
239
259
|
for (int gy = 0; gy < glyphHeight; gy++) {
|
|
@@ -244,6 +264,8 @@ void blit_glyph(std::vector<unsigned char> &pixels, int width, int height,
|
|
|
244
264
|
if (px < 0 || px >= width) continue;
|
|
245
265
|
float coverage = glyph[(size_t)gy * (size_t)glyphWidth + (size_t)gx] / 255.0f;
|
|
246
266
|
if (coverage <= 0.0f) continue;
|
|
267
|
+
float color[4];
|
|
268
|
+
paint_color_at(paint, (float)px + 0.5f, (float)py + 0.5f, color);
|
|
247
269
|
blend_pixel(pixels.data() + ((size_t)py * (size_t)width + (size_t)px) * 4, color, coverage,
|
|
248
270
|
alpha);
|
|
249
271
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — Canvas2D stroking.
|
|
3
|
+
*
|
|
4
|
+
* A stroked path is flattened into segments; each segment becomes a quad and
|
|
5
|
+
* every joint/cap is filled with the shape the current `lineJoin`/`lineCap`
|
|
6
|
+
* asks for. Dash patterns split a polyline into the "on" pieces first, so a
|
|
7
|
+
* dashed stroke is just a set of open polylines with the same cap rules.
|
|
8
|
+
*
|
|
9
|
+
* Everything here works in device space: `canvas.cpp` flattens the path and
|
|
10
|
+
* passes the paint (solid colour or gradient) through.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
#include <cmath>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
#include "canvas_internal.h"
|
|
18
|
+
|
|
19
|
+
namespace xtgui {
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
constexpr float kPi = 3.14159265358979323846f;
|
|
24
|
+
|
|
25
|
+
CanvasPoint lerp_point(const CanvasPoint &a, const CanvasPoint &b, float t) {
|
|
26
|
+
return CanvasPoint{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
bool same_point(const CanvasPoint &a, const CanvasPoint &b) {
|
|
30
|
+
return std::fabs(a.x - b.x) < 1e-6f && std::fabs(a.y - b.y) < 1e-6f;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
void Canvas2D::stroke_subpath(const CanvasSubpath &points, bool closed, const CanvasPaint &paint) {
|
|
36
|
+
if (points.size() < 2) return;
|
|
37
|
+
float scale = state_.transform.scale();
|
|
38
|
+
if (!(scale > 0.0f)) scale = 1.0f;
|
|
39
|
+
float half = state_.line_width * scale * 0.5f;
|
|
40
|
+
if (half <= 0.0f) half = 0.5f;
|
|
41
|
+
if (!state_.line_dash.empty()) {
|
|
42
|
+
std::vector<CanvasSubpath> pieces;
|
|
43
|
+
dash_polyline(points, closed, &pieces);
|
|
44
|
+
for (const CanvasSubpath &piece : pieces) stroke_polyline(piece, false, half, paint);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
stroke_polyline(points, closed, half, paint);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void Canvas2D::stroke_polyline(const CanvasSubpath &points, bool closed, float half,
|
|
51
|
+
const CanvasPaint &paint) {
|
|
52
|
+
CanvasSubpath pts = points;
|
|
53
|
+
if (closed && !same_point(pts.front(), pts.back())) pts.push_back(pts.front());
|
|
54
|
+
const size_t n = pts.size();
|
|
55
|
+
if (n < 2) return;
|
|
56
|
+
|
|
57
|
+
auto fill_loop = [&](const CanvasSubpath &loop) {
|
|
58
|
+
canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, paint,
|
|
59
|
+
state_.global_alpha);
|
|
60
|
+
};
|
|
61
|
+
auto fill_disc = [&](const CanvasPoint &p) {
|
|
62
|
+
CanvasSubpath disc;
|
|
63
|
+
const int segments = 24;
|
|
64
|
+
disc.reserve((size_t)segments);
|
|
65
|
+
for (int i = 0; i < segments; i++) {
|
|
66
|
+
float angle = 2.0f * kPi * ((float)i / (float)segments);
|
|
67
|
+
disc.push_back(CanvasPoint{p.x + half * std::cos(angle), p.y + half * std::sin(angle)});
|
|
68
|
+
}
|
|
69
|
+
fill_loop(disc);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/* Segments. A square cap extends the first/last quad along the line by half
|
|
73
|
+
* the width; round caps add a disc at the endpoints below. */
|
|
74
|
+
for (size_t i = 0; i + 1 < n; i++) {
|
|
75
|
+
CanvasPoint p = pts[i];
|
|
76
|
+
CanvasPoint q = pts[i + 1];
|
|
77
|
+
float dx = q.x - p.x;
|
|
78
|
+
float dy = q.y - p.y;
|
|
79
|
+
float length = std::sqrt(dx * dx + dy * dy);
|
|
80
|
+
if (length <= 1e-6f) continue;
|
|
81
|
+
float ux = dx / length;
|
|
82
|
+
float uy = dy / length;
|
|
83
|
+
float nx = -uy * half;
|
|
84
|
+
float ny = ux * half;
|
|
85
|
+
if (!closed && i == 0 && state_.line_cap == 2) {
|
|
86
|
+
p.x -= ux * half;
|
|
87
|
+
p.y -= uy * half;
|
|
88
|
+
}
|
|
89
|
+
if (!closed && i + 2 == n && state_.line_cap == 2) {
|
|
90
|
+
q.x += ux * half;
|
|
91
|
+
q.y += uy * half;
|
|
92
|
+
}
|
|
93
|
+
CanvasSubpath quad{CanvasPoint{p.x + nx, p.y + ny}, CanvasPoint{q.x + nx, q.y + ny},
|
|
94
|
+
CanvasPoint{q.x - nx, q.y - ny}, CanvasPoint{p.x - nx, p.y - ny}};
|
|
95
|
+
fill_loop(quad);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Joins: round adds a disc, bevel a triangle, miter the intersect of the two
|
|
99
|
+
* outer offset lines (clamped by `miterLimit`). */
|
|
100
|
+
auto add_join = [&](const CanvasPoint &previous, const CanvasPoint &p, const CanvasPoint &next) {
|
|
101
|
+
float v1x = p.x - previous.x;
|
|
102
|
+
float v1y = p.y - previous.y;
|
|
103
|
+
float v2x = next.x - p.x;
|
|
104
|
+
float v2y = next.y - p.y;
|
|
105
|
+
float len1 = std::sqrt(v1x * v1x + v1y * v1y);
|
|
106
|
+
float len2 = std::sqrt(v2x * v2x + v2y * v2y);
|
|
107
|
+
if (len1 <= 1e-6f || len2 <= 1e-6f) return;
|
|
108
|
+
v1x /= len1;
|
|
109
|
+
v1y /= len1;
|
|
110
|
+
v2x /= len2;
|
|
111
|
+
v2y /= len2;
|
|
112
|
+
float cross = v1x * v2y - v1y * v2x;
|
|
113
|
+
if (std::fabs(cross) < 1e-6f) return;
|
|
114
|
+
if (state_.line_join == 1) {
|
|
115
|
+
fill_disc(p);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
/* The outer side is the opposite side of the turn. */
|
|
119
|
+
float side = cross > 0.0f ? -1.0f : 1.0f;
|
|
120
|
+
CanvasPoint a{p.x - v1y * half * side, p.y + v1x * half * side};
|
|
121
|
+
CanvasPoint b{p.x - v2y * half * side, p.y + v2x * half * side};
|
|
122
|
+
if (state_.line_join == 2) {
|
|
123
|
+
fill_loop(CanvasSubpath{a, b, p});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
float t = ((b.x - a.x) * v2y - (b.y - a.y) * v2x) / cross;
|
|
127
|
+
CanvasPoint miter{a.x + v1x * t, a.y + v1y * t};
|
|
128
|
+
float miterLength = std::sqrt((miter.x - p.x) * (miter.x - p.x) +
|
|
129
|
+
(miter.y - p.y) * (miter.y - p.y));
|
|
130
|
+
if (std::isfinite(miterLength) && miterLength <= half * state_.miter_limit) {
|
|
131
|
+
fill_loop(CanvasSubpath{a, miter, b, p});
|
|
132
|
+
} else {
|
|
133
|
+
fill_loop(CanvasSubpath{a, b, p});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (closed) {
|
|
138
|
+
const size_t unique = n - 1;
|
|
139
|
+
for (size_t i = 0; i < unique; i++) {
|
|
140
|
+
const CanvasPoint &previous = pts[(i + unique - 1) % unique];
|
|
141
|
+
const CanvasPoint &next = pts[(i + 1) % unique];
|
|
142
|
+
add_join(previous, pts[i], next);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
for (size_t i = 1; i + 1 < n; i++) add_join(pts[i - 1], pts[i], pts[i + 1]);
|
|
146
|
+
if (state_.line_cap == 1) {
|
|
147
|
+
fill_disc(pts.front());
|
|
148
|
+
fill_disc(pts.back());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void Canvas2D::dash_polyline(const CanvasSubpath &points, bool closed,
|
|
154
|
+
std::vector<CanvasSubpath> *out) const {
|
|
155
|
+
if (points.size() < 2) return;
|
|
156
|
+
float scale = state_.transform.scale();
|
|
157
|
+
if (!(scale > 0.0f)) scale = 1.0f;
|
|
158
|
+
std::vector<float> pattern;
|
|
159
|
+
pattern.reserve(state_.line_dash.size());
|
|
160
|
+
float total = 0.0f;
|
|
161
|
+
for (float entry : state_.line_dash) {
|
|
162
|
+
pattern.push_back(entry * scale);
|
|
163
|
+
total += entry * scale;
|
|
164
|
+
}
|
|
165
|
+
if (pattern.empty() || total <= 1e-6f) {
|
|
166
|
+
out->push_back(points);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
CanvasSubpath pts = points;
|
|
171
|
+
if (closed && !same_point(pts.front(), pts.back())) pts.push_back(pts.front());
|
|
172
|
+
const size_t n = pts.size();
|
|
173
|
+
|
|
174
|
+
/* Consume `lineDashOffset` from the repeating pattern before drawing. */
|
|
175
|
+
float dashOffset = state_.line_dash_offset;
|
|
176
|
+
if (!std::isfinite(dashOffset)) dashOffset = 0.0f;
|
|
177
|
+
float offset = std::fmod(dashOffset * scale, total);
|
|
178
|
+
if (offset < 0.0f) offset += total;
|
|
179
|
+
size_t index = 0;
|
|
180
|
+
float remain = pattern[0];
|
|
181
|
+
int guard = 0;
|
|
182
|
+
while (offset > 1e-6f && guard++ < 100000) {
|
|
183
|
+
if (offset >= remain) {
|
|
184
|
+
offset -= remain;
|
|
185
|
+
index = (index + 1) % pattern.size();
|
|
186
|
+
remain = pattern[index];
|
|
187
|
+
} else {
|
|
188
|
+
remain -= offset;
|
|
189
|
+
offset = 0.0f;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
auto skip_empty = [&]() {
|
|
193
|
+
int skipped = 0;
|
|
194
|
+
while (remain <= 1e-6f && skipped++ < 100000) {
|
|
195
|
+
index = (index + 1) % pattern.size();
|
|
196
|
+
remain = pattern[index];
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
skip_empty();
|
|
200
|
+
bool on = (index % 2 == 0);
|
|
201
|
+
|
|
202
|
+
CanvasSubpath current;
|
|
203
|
+
bool have_current = false;
|
|
204
|
+
auto finish_dash = [&]() {
|
|
205
|
+
if (have_current) {
|
|
206
|
+
out->push_back(current);
|
|
207
|
+
current.clear();
|
|
208
|
+
have_current = false;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
for (size_t i = 0; i + 1 < n; i++) {
|
|
213
|
+
const CanvasPoint &p = pts[i];
|
|
214
|
+
const CanvasPoint &q = pts[i + 1];
|
|
215
|
+
float dx = q.x - p.x;
|
|
216
|
+
float dy = q.y - p.y;
|
|
217
|
+
float length = std::sqrt(dx * dx + dy * dy);
|
|
218
|
+
if (length <= 1e-6f) continue;
|
|
219
|
+
float position = 0.0f;
|
|
220
|
+
while (position < length - 1e-6f) {
|
|
221
|
+
if (remain <= 1e-6f) {
|
|
222
|
+
skip_empty();
|
|
223
|
+
on = (index % 2 == 0);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
float step = std::min(remain, length - position);
|
|
227
|
+
if (on) {
|
|
228
|
+
if (!have_current) {
|
|
229
|
+
current.clear();
|
|
230
|
+
current.push_back(lerp_point(p, q, position / length));
|
|
231
|
+
have_current = true;
|
|
232
|
+
}
|
|
233
|
+
position += step;
|
|
234
|
+
current.push_back(lerp_point(p, q, position / length));
|
|
235
|
+
} else {
|
|
236
|
+
position += step;
|
|
237
|
+
}
|
|
238
|
+
remain -= step;
|
|
239
|
+
if (remain <= 1e-6f) {
|
|
240
|
+
if (on) finish_dash();
|
|
241
|
+
index = (index + 1) % pattern.size();
|
|
242
|
+
remain = pattern[index];
|
|
243
|
+
on = (index % 2 == 0);
|
|
244
|
+
skip_empty();
|
|
245
|
+
on = (index % 2 == 0);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
finish_dash();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
} // namespace xtgui
|