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.
@@ -12,12 +12,10 @@
12
12
  #include <algorithm>
13
13
  #include <atomic>
14
14
  #include <cmath>
15
- #include <cstdlib>
16
15
  #include <cstring>
17
16
  #include <unordered_map>
18
17
 
19
18
  #include "canvas_internal.h"
20
- #include "css.h"
21
19
 
22
20
  namespace xtgui {
23
21
 
@@ -32,99 +30,12 @@ std::unordered_map<uint64_t, Canvas2D *> &canvas_registry() {
32
30
  return registry;
33
31
  }
34
32
 
35
- std::string lower_ascii(const std::string &value) {
36
- std::string out = value;
37
- for (char &c : out) {
38
- if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
39
- }
40
- return out;
41
- }
42
-
43
- bool parse_font_size_token(const std::string &token, float fallback, float *out) {
44
- std::string lower = lower_ascii(token);
45
- struct Suffix {
46
- const char *text;
47
- float factor;
48
- };
49
- static const Suffix suffixes[] = {{"px", 1.0f}, {"pt", 96.0f / 72.0f}, {"pc", 16.0f},
50
- {"in", 96.0f}, {"cm", 96.0f / 2.54f}, {"mm", 96.0f / 25.4f},
51
- {"q", 96.0f / 101.6f}, {"em", 0.0f}, {"rem", 0.0f}};
52
- for (const Suffix &suffix : suffixes) {
53
- size_t length = std::strlen(suffix.text);
54
- if (lower.size() <= length || lower.compare(lower.size() - length, length, suffix.text) != 0)
55
- continue;
56
- char *end = nullptr;
57
- float value = std::strtof(lower.c_str(), &end);
58
- if (end == lower.c_str()) return false;
59
- if (suffix.factor == 0.0f) {
60
- *out = value * fallback;
61
- } else {
62
- *out = value * suffix.factor;
63
- }
64
- return true;
65
- }
66
- return false;
67
- }
68
-
69
- /** Parse the CSS `font` shorthand into a `FontSpec` (the pieces Canvas2D
70
- * understands; unknown tokens are ignored). */
71
- FontSpec parse_font_string(const std::string &text) {
72
- FontSpec spec;
73
- spec.family = "sans-serif";
74
- spec.pixel_size = 10.0f;
75
- spec.weight = 400;
76
- spec.italic = false;
77
- std::vector<std::string> tokens = xt_css_split_whitespace(text);
78
- std::vector<std::string> family;
79
- float size = 10.0f;
80
- bool have_size = false;
81
- for (const std::string &token : tokens) {
82
- std::string lower = lower_ascii(token);
83
- float parsed = 0.0f;
84
- if (!have_size && parse_font_size_token(lower, 16.0f, &parsed)) {
85
- size = parsed;
86
- have_size = true;
87
- continue;
88
- }
89
- if (lower == "italic" || lower == "oblique") {
90
- spec.italic = true;
91
- continue;
92
- }
93
- if (lower == "bold") {
94
- spec.weight = 700;
95
- continue;
96
- }
97
- if (lower == "bolder") {
98
- spec.weight = 700;
99
- continue;
100
- }
101
- if (lower == "lighter") {
102
- spec.weight = 300;
103
- continue;
104
- }
105
- if (lower == "normal" || lower == "small-caps") continue;
106
- if (!lower.empty() && lower.back() == ',') lower.pop_back();
107
- if (!lower.empty()) family.push_back(lower);
108
- }
109
- spec.pixel_size = size;
110
- if (!family.empty()) spec.family = family[0];
111
- return spec;
112
- }
113
-
114
- void parse_color_into(const std::string &css, float color[4], std::string *stored) {
115
- Color parsed = xt_css_parse_color(css);
116
- if (!parsed.valid) return;
117
- color[0] = parsed.r;
118
- color[1] = parsed.g;
119
- color[2] = parsed.b;
120
- color[3] = parsed.a;
121
- *stored = css;
122
- }
123
-
124
33
  CanvasPoint lerp(const CanvasPoint &a, const CanvasPoint &b, float t) {
125
34
  return CanvasPoint{a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t};
126
35
  }
127
36
 
37
+ constexpr float kPi = 3.14159265358979323846f;
38
+
128
39
  } // namespace
129
40
 
130
41
  Canvas2D* xt_canvas_by_id(uint64_t id) {
@@ -137,6 +48,20 @@ float CanvasTransform::scale() const {
137
48
  return std::sqrt(determinant);
138
49
  }
139
50
 
51
+ CanvasTransform CanvasTransform::inverted() const {
52
+ const float determinant = a * d - b * c;
53
+ if (std::fabs(determinant) < 1e-9f) return CanvasTransform();
54
+ const float inv = 1.0f / determinant;
55
+ CanvasTransform result;
56
+ result.a = d * inv;
57
+ result.b = -b * inv;
58
+ result.c = -c * inv;
59
+ result.d = a * inv;
60
+ result.e = (c * f - d * e) * inv;
61
+ result.f = (b * e - a * f) * inv;
62
+ return result;
63
+ }
64
+
140
65
  Canvas2D::Canvas2D(int width, int height) : id_(g_next_canvas_id.fetch_add(1)) {
141
66
  resize(width, height);
142
67
  canvas_registry()[id_] = this;
@@ -178,14 +103,17 @@ void Canvas2D::rotate(float radians) {
178
103
  void Canvas2D::scale(float sx, float sy) { transform(sx, 0.0f, 0.0f, sy, 0.0f, 0.0f); }
179
104
 
180
105
  void Canvas2D::transform(float a, float b, float c, float d, float e, float f) {
106
+ /* Post-multiply: the new matrix applies *after* the current one, so
107
+ * `translate(x, y); rotate(a)` rotates around the translated origin (the
108
+ * canvas rules) instead of rotating the translation itself. */
181
109
  const CanvasTransform &m = state_.transform;
182
110
  CanvasTransform next;
183
- next.a = a * m.a + c * m.b;
184
- next.b = b * m.a + d * m.b;
185
- next.c = a * m.c + c * m.d;
186
- next.d = b * m.c + d * m.d;
187
- next.e = a * m.e + c * m.f + e;
188
- next.f = b * m.e + d * m.f + f;
111
+ next.a = m.a * a + m.c * b;
112
+ next.b = m.b * a + m.d * b;
113
+ next.c = m.a * c + m.c * d;
114
+ next.d = m.b * c + m.d * d;
115
+ next.e = m.a * e + m.c * f + m.e;
116
+ next.f = m.b * e + m.d * f + m.f;
189
117
  state_.transform = next;
190
118
  }
191
119
 
@@ -193,59 +121,6 @@ void Canvas2D::set_transform(float a, float b, float c, float d, float e, float
193
121
  state_.transform = CanvasTransform{a, b, c, d, e, f};
194
122
  }
195
123
 
196
- void Canvas2D::set_fill_style(const std::string &css) {
197
- parse_color_into(css, state_.fill_color, &state_.fill_style);
198
- }
199
-
200
- void Canvas2D::set_stroke_style(const std::string &css) {
201
- parse_color_into(css, state_.stroke_color, &state_.stroke_style);
202
- }
203
-
204
- void Canvas2D::set_global_alpha(float alpha) {
205
- if (alpha < 0.0f) alpha = 0.0f;
206
- if (alpha > 1.0f) alpha = 1.0f;
207
- state_.global_alpha = alpha;
208
- }
209
-
210
- void Canvas2D::set_font(const std::string &font) {
211
- state_.font = parse_font_string(font);
212
- state_.font_string = font;
213
- }
214
-
215
- void Canvas2D::set_text_align(const std::string &value) {
216
- std::string lower = lower_ascii(value);
217
- if (lower == "start" || lower == "left") {
218
- state_.text_align = 0;
219
- } else if (lower == "center") {
220
- state_.text_align = 1;
221
- } else if (lower == "right" || lower == "end") {
222
- state_.text_align = 2;
223
- } else {
224
- return;
225
- }
226
- state_.text_align_string = lower;
227
- }
228
-
229
- void Canvas2D::set_text_baseline(const std::string &value) {
230
- std::string lower = lower_ascii(value);
231
- if (lower == "alphabetic") {
232
- state_.text_baseline = 0;
233
- } else if (lower == "top") {
234
- state_.text_baseline = 1;
235
- } else if (lower == "middle") {
236
- state_.text_baseline = 2;
237
- } else if (lower == "bottom") {
238
- state_.text_baseline = 3;
239
- } else if (lower == "hanging") {
240
- state_.text_baseline = 4;
241
- } else if (lower == "ideographic") {
242
- state_.text_baseline = 5;
243
- } else {
244
- return;
245
- }
246
- state_.text_baseline_string = lower;
247
- }
248
-
249
124
  void Canvas2D::begin_path() { path_.clear(); }
250
125
 
251
126
  void Canvas2D::move_to(float x, float y) { path_.push_back(CanvasSubpath{CanvasPoint{x, y}}); }
@@ -287,19 +162,74 @@ void Canvas2D::flatten_arc(float cx, float cy, float radius, float start, float
287
162
  }
288
163
  }
289
164
 
290
- void Canvas2D::arc(float cx, float cy, float radius, float start, float end, bool ccw) {
291
- CanvasSubpath arcPoints;
292
- flatten_arc(cx, cy, radius, start, end, ccw, &arcPoints);
293
- if (arcPoints.empty()) return;
165
+ void Canvas2D::append_path_points(const CanvasSubpath &points) {
166
+ if (points.empty()) return;
294
167
  if (path_.empty() || path_.back().empty()) {
295
- path_.push_back(CanvasSubpath{arcPoints.front()});
168
+ path_.push_back(CanvasSubpath{points.front()});
296
169
  } else {
297
170
  CanvasPoint last = path_.back().back();
298
- if (last.x != arcPoints.front().x || last.y != arcPoints.front().y) {
299
- path_.back().push_back(arcPoints.front());
171
+ if (last.x != points.front().x || last.y != points.front().y) {
172
+ path_.back().push_back(points.front());
300
173
  }
301
174
  }
302
- for (size_t i = 1; i < arcPoints.size(); i++) path_.back().push_back(arcPoints[i]);
175
+ for (size_t i = 1; i < points.size(); i++) path_.back().push_back(points[i]);
176
+ }
177
+
178
+ void Canvas2D::arc(float cx, float cy, float radius, float start, float end, bool ccw) {
179
+ CanvasSubpath arcPoints;
180
+ flatten_arc(cx, cy, radius, start, end, ccw, &arcPoints);
181
+ append_path_points(arcPoints);
182
+ }
183
+
184
+ void Canvas2D::ellipse(float cx, float cy, float rx, float ry, float rotation, float start,
185
+ float end, bool counterclockwise) {
186
+ float sweep = end - start;
187
+ if (counterclockwise) {
188
+ while (sweep > 0.0f) sweep -= 2.0f * kPi;
189
+ while (sweep < -2.0f * kPi) sweep += 2.0f * kPi;
190
+ } else {
191
+ while (sweep < 0.0f) sweep += 2.0f * kPi;
192
+ while (sweep > 2.0f * kPi) sweep -= 2.0f * kPi;
193
+ }
194
+ int segments = (int)std::ceil(std::fabs(sweep) / (kPi / 32.0f));
195
+ if (segments < 1) segments = 1;
196
+ if (segments > 2048) segments = 2048;
197
+ const float cosRotation = std::cos(rotation);
198
+ const float sinRotation = std::sin(rotation);
199
+ CanvasSubpath points;
200
+ points.reserve((size_t)segments + 1);
201
+ for (int i = 0; i <= segments; i++) {
202
+ float angle = start + sweep * ((float)i / (float)segments);
203
+ float px = rx * std::cos(angle);
204
+ float py = ry * std::sin(angle);
205
+ points.push_back(CanvasPoint{cx + px * cosRotation - py * sinRotation,
206
+ cy + px * sinRotation + py * cosRotation});
207
+ }
208
+ append_path_points(points);
209
+ }
210
+
211
+ void Canvas2D::round_rect(float x, float y, float width, float height, float radius) {
212
+ if (width < 0.0f) {
213
+ x += width;
214
+ width = -width;
215
+ }
216
+ if (height < 0.0f) {
217
+ y += height;
218
+ height = -height;
219
+ }
220
+ float maxRadius = std::min(width, height) * 0.5f;
221
+ if (radius < 0.0f) radius = 0.0f;
222
+ if (radius > maxRadius) radius = maxRadius;
223
+ move_to(x + radius, y);
224
+ line_to(x + width - radius, y);
225
+ quadratic_curve_to(x + width, y, x + width, y + radius);
226
+ line_to(x + width, y + height - radius);
227
+ quadratic_curve_to(x + width, y + height, x + width - radius, y + height);
228
+ line_to(x + radius, y + height);
229
+ quadratic_curve_to(x, y + height, x, y + height - radius);
230
+ line_to(x, y + radius);
231
+ quadratic_curve_to(x, y, x + radius, y);
232
+ close_path();
303
233
  }
304
234
 
305
235
  void Canvas2D::quadratic_curve_to(float cpx, float cpy, float x, float y) {
@@ -353,6 +283,22 @@ std::vector<CanvasSubpath> Canvas2D::device_subpaths() const {
353
283
  return result;
354
284
  }
355
285
 
286
+ CanvasPaint Canvas2D::make_paint(uint64_t gradient_id, const float color[4]) const {
287
+ CanvasPaint paint;
288
+ paint.color = color;
289
+ paint.gradient = gradient_id == 0 ? nullptr : find_gradient(gradient_id);
290
+ paint.inverse = state_.transform.inverted();
291
+ return paint;
292
+ }
293
+
294
+ CanvasPaint Canvas2D::fill_paint() const {
295
+ return make_paint(state_.fill_gradient, state_.fill_color);
296
+ }
297
+
298
+ CanvasPaint Canvas2D::stroke_paint() const {
299
+ return make_paint(state_.stroke_gradient, state_.stroke_color);
300
+ }
301
+
356
302
  void Canvas2D::fill() {
357
303
  std::vector<CanvasSubpath> subpaths = device_subpaths();
358
304
  std::vector<CanvasSubpath> loops;
@@ -361,50 +307,19 @@ void Canvas2D::fill() {
361
307
  loops.push_back(std::move(subpath));
362
308
  }
363
309
  if (loops.empty()) return;
364
- canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, state_.fill_color,
310
+ canvas_detail::fill_loops(image_.pixels, image_.width, image_.height, loops, fill_paint(),
365
311
  state_.global_alpha);
366
312
  generation_++;
367
313
  }
368
314
 
369
- void Canvas2D::stroke_subpath(const CanvasSubpath &points, bool closed, const float color[4]) {
370
- if (points.size() < 2) return;
371
- float half = state_.line_width * state_.transform.scale() * 0.5f;
372
- if (half <= 0.0f) half = 0.5f;
373
- size_t segmentCount = points.size() - 1;
374
- for (size_t i = 0; i < segmentCount; i++) {
375
- const CanvasPoint &p = points[i];
376
- const CanvasPoint &q = points[i + 1];
377
- float dx = q.x - p.x;
378
- float dy = q.y - p.y;
379
- float length = std::sqrt(dx * dx + dy * dy);
380
- if (length <= 0.0001f) continue;
381
- float nx = -dy / length * half;
382
- float ny = dx / length * half;
383
- CanvasSubpath quad{CanvasPoint{p.x + nx, p.y + ny}, CanvasPoint{q.x + nx, q.y + ny},
384
- CanvasPoint{q.x - nx, q.y - ny}, CanvasPoint{p.x - nx, p.y - ny}};
385
- canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, quad, color,
386
- state_.global_alpha);
387
- }
388
- (void)closed;
389
- for (const CanvasPoint &p : points) {
390
- CanvasSubpath disc;
391
- const int segments = 16;
392
- for (int i = 0; i < segments; i++) {
393
- float angle = 2.0f * 3.14159265358979323846f * ((float)i / (float)segments);
394
- disc.push_back(CanvasPoint{p.x + half * std::cos(angle), p.y + half * std::sin(angle)});
395
- }
396
- canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, disc, color,
397
- state_.global_alpha);
398
- }
399
- }
400
-
401
315
  void Canvas2D::stroke() {
402
316
  std::vector<CanvasSubpath> subpaths = device_subpaths();
317
+ const CanvasPaint paint = stroke_paint();
403
318
  for (const CanvasSubpath &subpath : subpaths) {
404
319
  if (subpath.size() < 2) continue;
405
320
  bool closed = subpath.size() >= 3 && subpath.front().x == subpath.back().x &&
406
321
  subpath.front().y == subpath.back().y;
407
- stroke_subpath(subpath, closed, state_.stroke_color);
322
+ stroke_subpath(subpath, closed, paint);
408
323
  }
409
324
  generation_++;
410
325
  }
@@ -423,7 +338,7 @@ void Canvas2D::fill_rect(float x, float y, float width, float height) {
423
338
  to_device(x, y, &x0, &y0);
424
339
  to_device(x + width, y + height, &x1, &y1);
425
340
  canvas_detail::fill_rect(image_.pixels, image_.width, image_.height, x0, y0, x1, y1,
426
- state_.fill_color, state_.global_alpha);
341
+ fill_paint(), state_.global_alpha);
427
342
  } else {
428
343
  CanvasSubpath loop;
429
344
  const float corners[4][2] = {
@@ -433,7 +348,7 @@ void Canvas2D::fill_rect(float x, float y, float width, float height) {
433
348
  to_device(corner[0], corner[1], &point.x, &point.y);
434
349
  loop.push_back(point);
435
350
  }
436
- canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, state_.fill_color,
351
+ canvas_detail::fill_loop(image_.pixels, image_.width, image_.height, loop, fill_paint(),
437
352
  state_.global_alpha);
438
353
  }
439
354
  generation_++;
@@ -448,7 +363,7 @@ void Canvas2D::stroke_rect(float x, float y, float width, float height) {
448
363
  to_device(corner[0], corner[1], &point.x, &point.y);
449
364
  loop.push_back(point);
450
365
  }
451
- stroke_subpath(loop, true, state_.stroke_color);
366
+ stroke_subpath(loop, true, stroke_paint());
452
367
  generation_++;
453
368
  }
454
369
 
@@ -456,7 +371,7 @@ float Canvas2D::measure_text(const std::string &text) const {
456
371
  return xt_text_measure_width(text, state_.font);
457
372
  }
458
373
 
459
- void Canvas2D::paint_text(const std::string &text, float x, float y, const float color[4],
374
+ void Canvas2D::paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
460
375
  bool stroke) {
461
376
  Font *font = xt_text_resolve(state_.font);
462
377
  if (font == nullptr) return;
@@ -510,7 +425,7 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const float
510
425
  GlyphImage image;
511
426
  if (xt_text_rasterize(face, glyph.glyph, image) && image.width > 0 && image.height > 0) {
512
427
  canvas_detail::blit_glyph(image_.pixels, image_.width, image_.height, image.pixels, image.width,
513
- image.height, image.left, image.top, deviceX, deviceY, color,
428
+ image.height, image.left, image.top, deviceX, deviceY, paint,
514
429
  state_.global_alpha);
515
430
  }
516
431
  penX += glyph.x_advance;
@@ -520,11 +435,11 @@ void Canvas2D::paint_text(const std::string &text, float x, float y, const float
520
435
  }
521
436
 
522
437
  void Canvas2D::fill_text(const std::string &text, float x, float y) {
523
- paint_text(text, x, y, state_.fill_color, false);
438
+ paint_text(text, x, y, fill_paint(), false);
524
439
  }
525
440
 
526
441
  void Canvas2D::stroke_text(const std::string &text, float x, float y) {
527
- paint_text(text, x, y, state_.stroke_color, true);
442
+ paint_text(text, x, y, stroke_paint(), true);
528
443
  }
529
444
 
530
445
  void Canvas2D::draw_image(const Image &source, float dx, float dy, float dw, float dh, float sx,
@@ -19,7 +19,9 @@
19
19
  #define XT_GUI_CANVAS_H
20
20
 
21
21
  #include <cstdint>
22
+ #include <memory>
22
23
  #include <string>
24
+ #include <unordered_map>
23
25
  #include <vector>
24
26
 
25
27
  #include "image.h"
@@ -40,6 +42,8 @@ struct CanvasTransform {
40
42
  bool axis_aligned() const { return b == 0.0f && c == 0.0f; }
41
43
  /** Approximate uniform scale, used to size stroked lines and text. */
42
44
  float scale() const;
45
+ /** The inverse transform (device -> user space); identity when singular. */
46
+ CanvasTransform inverted() const;
43
47
  };
44
48
 
45
49
  /** A point in device (canvas) space. */
@@ -51,6 +55,51 @@ struct CanvasPoint {
51
55
  /** One subpath of the current path (device space). */
52
56
  using CanvasSubpath = std::vector<CanvasPoint>;
53
57
 
58
+ /**
59
+ * A `CanvasGradient` handle: the colour stops plus the geometric definition of
60
+ * a linear or radial gradient. Gradients are owned by the canvas that created
61
+ * them, so a stale context handle resolves to no gradient instead of a dangling
62
+ * pointer. The DOM layer copies only the id around; the stops stay live, which
63
+ * is what makes `addColorStop` after `fillStyle = gradient` affect later fills.
64
+ */
65
+ class CanvasGradient {
66
+ public:
67
+ enum class Type { Linear, Radial };
68
+
69
+ CanvasGradient(uint64_t id, Type type, float x0, float y0, float x1, float y1, float r0,
70
+ float r1);
71
+
72
+ uint64_t id() const { return id_; }
73
+ Type type() const { return type_; }
74
+
75
+ /** Add a colour stop (offset clamped to 0..1, stops kept sorted by offset). */
76
+ void add_color_stop(float offset, const float color[4]);
77
+ /** Sample the gradient at a user-space point. A gradient with no stops is
78
+ * transparent black, so this always fills `out`. */
79
+ bool color_at(float x, float y, float out[4]) const;
80
+
81
+ private:
82
+ struct Stop {
83
+ float offset = 0.0f;
84
+ float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
85
+ };
86
+
87
+ uint64_t id_ = 0;
88
+ Type type_ = Type::Linear;
89
+ float x0_ = 0.0f, y0_ = 0.0f, x1_ = 0.0f, y1_ = 0.0f;
90
+ float r0_ = 0.0f, r1_ = 0.0f;
91
+ std::vector<Stop> stops_;
92
+ };
93
+
94
+ /** What a fill or stroke paints with: a solid colour, or a gradient sampled
95
+ * through the inverse of the current transform. `color` points into the
96
+ * canvas state and stays valid for the duration of one draw call. */
97
+ struct CanvasPaint {
98
+ const float *color = nullptr;
99
+ const CanvasGradient *gradient = nullptr;
100
+ CanvasTransform inverse;
101
+ };
102
+
54
103
  class Canvas2D {
55
104
  public:
56
105
  Canvas2D(int width, int height);
@@ -77,14 +126,30 @@ class Canvas2D {
77
126
  void transform(float a, float b, float c, float d, float e, float f);
78
127
  void set_transform(float a, float b, float c, float d, float e, float f);
79
128
  void reset_transform() { state_.transform = CanvasTransform(); }
129
+ const CanvasTransform &current_transform() const { return state_.transform; }
80
130
 
81
131
  /* -- style ------------------------------------------------------------- */
82
132
  void set_fill_style(const std::string &css);
83
133
  const std::string &fill_style() const { return state_.fill_style; }
84
134
  void set_stroke_style(const std::string &css);
85
135
  const std::string &stroke_style() const { return state_.stroke_style; }
136
+ /** Use a gradient created by this canvas (`0` restores the solid colour). */
137
+ void set_fill_gradient(uint64_t id);
138
+ void set_stroke_gradient(uint64_t id);
139
+ uint64_t fill_gradient_id() const { return state_.fill_gradient; }
140
+ uint64_t stroke_gradient_id() const { return state_.stroke_gradient; }
86
141
  void set_line_width(float width) { state_.line_width = width > 0.0f ? width : 0.0f; }
87
142
  float line_width() const { return state_.line_width; }
143
+ void set_line_cap(const std::string &value);
144
+ const std::string &line_cap() const { return state_.line_cap_string; }
145
+ void set_line_join(const std::string &value);
146
+ const std::string &line_join() const { return state_.line_join_string; }
147
+ void set_miter_limit(float limit);
148
+ float miter_limit() const { return state_.miter_limit; }
149
+ void set_line_dash(const std::vector<float> &segments);
150
+ const std::vector<float> &line_dash() const { return state_.line_dash; }
151
+ void set_line_dash_offset(float offset) { state_.line_dash_offset = offset; }
152
+ float line_dash_offset() const { return state_.line_dash_offset; }
88
153
  void set_global_alpha(float alpha);
89
154
  float global_alpha() const { return state_.global_alpha; }
90
155
  void set_font(const std::string &font);
@@ -94,13 +159,22 @@ class Canvas2D {
94
159
  void set_text_baseline(const std::string &value);
95
160
  const std::string &text_baseline() const { return state_.text_baseline_string; }
96
161
 
162
+ /* -- gradients --------------------------------------------------------- */
163
+ CanvasGradient *create_linear_gradient(float x0, float y0, float x1, float y1);
164
+ CanvasGradient *create_radial_gradient(float x0, float y0, float r0, float x1, float y1,
165
+ float r1);
166
+ CanvasGradient *find_gradient(uint64_t id) const;
167
+
97
168
  /* -- paths ------------------------------------------------------------- */
98
169
  void begin_path();
99
170
  void close_path();
100
171
  void move_to(float x, float y);
101
172
  void line_to(float x, float y);
102
173
  void rect(float x, float y, float width, float height);
174
+ void round_rect(float x, float y, float width, float height, float radius);
103
175
  void arc(float cx, float cy, float radius, float start, float end, bool counterclockwise);
176
+ void ellipse(float cx, float cy, float rx, float ry, float rotation, float start, float end,
177
+ bool counterclockwise);
104
178
  void quadratic_curve_to(float cpx, float cpy, float x, float y);
105
179
  void bezier_curve_to(float c1x, float c1y, float c2x, float c2y, float x, float y);
106
180
  void fill();
@@ -132,6 +206,11 @@ class Canvas2D {
132
206
  CanvasTransform transform;
133
207
  float line_width = 1.0f;
134
208
  float global_alpha = 1.0f;
209
+ int line_cap = 0; // 0 butt, 1 round, 2 square
210
+ int line_join = 0; // 0 miter, 1 round, 2 bevel
211
+ float miter_limit = 10.0f;
212
+ std::vector<float> line_dash;
213
+ float line_dash_offset = 0.0f;
135
214
  FontSpec font;
136
215
  std::string font_string = "10px sans-serif";
137
216
  int text_align = 0; // 0 start/left, 1 center, 2 right/end
@@ -142,6 +221,10 @@ class Canvas2D {
142
221
  std::string stroke_style = "#000000";
143
222
  float fill_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
144
223
  float stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
224
+ uint64_t fill_gradient = 0;
225
+ uint64_t stroke_gradient = 0;
226
+ std::string line_cap_string = "butt";
227
+ std::string line_join_string = "miter";
145
228
  };
146
229
 
147
230
  /** Transform a user-space point into device space. */
@@ -151,8 +234,23 @@ class Canvas2D {
151
234
  /** Add one device-space polygon loop to `out`. */
152
235
  void flatten_arc(float cx, float cy, float radius, float start, float end, bool ccw,
153
236
  CanvasSubpath *out) const;
154
- void stroke_subpath(const CanvasSubpath &points, bool closed, const float color[4]);
155
- void paint_text(const std::string &text, float x, float y, const float color[4], bool stroke);
237
+ /** Append a flattened arc to the current subpath, moving/line-to its start as
238
+ * the canvas path rules require. Shared by `arc` and `ellipse`. */
239
+ void append_path_points(const CanvasSubpath &points);
240
+ /** Paint descriptors for the current state. */
241
+ CanvasPaint fill_paint() const;
242
+ CanvasPaint stroke_paint() const;
243
+ CanvasPaint make_paint(uint64_t gradient_id, const float color[4]) const;
244
+ void stroke_subpath(const CanvasSubpath &points, bool closed, const CanvasPaint &paint);
245
+ /** Stroke one polyline with caps and joins; `half` is half the device-space
246
+ * line width. Used by `stroke_subpath` for both solid and dashed paths. */
247
+ void stroke_polyline(const CanvasSubpath &points, bool closed, float half,
248
+ const CanvasPaint &paint);
249
+ /** Split `points` into the "on" pieces of the current dash pattern. */
250
+ void dash_polyline(const CanvasSubpath &points, bool closed,
251
+ std::vector<CanvasSubpath> *out) const;
252
+ void paint_text(const std::string &text, float x, float y, const CanvasPaint &paint,
253
+ bool stroke);
156
254
 
157
255
  Image image_;
158
256
  uint64_t id_ = 0;
@@ -160,6 +258,7 @@ class Canvas2D {
160
258
  State state_;
161
259
  std::vector<State> stack_;
162
260
  std::vector<CanvasSubpath> path_;
261
+ std::unordered_map<uint64_t, std::unique_ptr<CanvasGradient>> gradients_;
163
262
  };
164
263
 
165
264
  /** The canvas with the given process-wide id, or null. Used by the renderer. */