Photon 1.0.0
Loading...
Searching...
No Matches
canvas_ity.hpp
Go to the documentation of this file.
1// canvas_ity v1.00 -- ISC license
2// Copyright (c) 2022 Andrew Kensler
3//
4// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16// ======== ABOUT ========
17//
18// This is a tiny, single-header C++ library for rasterizing immediate-mode
19// 2D vector graphics, closely modeled on the basic W3C (not WHATWG) HTML5 2D
20// canvas specification (https://www.w3.org/TR/2015/REC-2dcontext-20151119/).
21//
22// The priorities for this library are high-quality rendering, ease of use,
23// and compact size. Speed is important too, but secondary to the other
24// priorities. Notably, this library takes an opinionated approach and
25// does not provide options for trading off quality for speed.
26//
27// Despite its small size, it supports nearly everything listed in the W3C
28// HTML5 2D canvas specification, except for hit regions and getting certain
29// properties. The main differences lie in the surface-level API to make this
30// easier for C++ use, while the underlying implementation is carefully based
31// on the specification. In particular, stroke, fill, gradient, pattern,
32// image, and font styles are specified slightly differently (avoiding strings
33// and auxiliary classes). Nonetheless, the goal is that this library could
34// produce a conforming HTML5 2D canvas implementation if wrapped in a thin
35// layer of JavaScript bindings. See the accompanying C++ automated test
36// suite and its HTML5 port for a mapping between the APIs and a comparison
37// of this library's rendering output against browser canvas implementations.
38
39// ======== FEATURES ========
40//
41// High-quality rendering:
42//
43// - Trapezoidal area antialiasing provides very smooth antialiasing, even
44// when lines are nearly horizontal or vertical.
45// - Gamma-correct blending, interpolation, and resampling are used
46// throughout. It linearizes all colors and premultiplies alpha on
47// input and converts back to unpremultiplied sRGB on output. This
48// reduces muddiness on many gradients (e.g., red to green), makes line
49// thicknesses more perceptually uniform, and avoids dark fringes when
50// interpolating opacity.
51// - Bicubic convolution resampling is used whenever it needs to resample a
52// pattern or image. This smoothly interpolates with less blockiness when
53// magnifying, and antialiases well when minifying. It can simultaneously
54// magnify and minify along different axes.
55// - Ordered dithering is used on output. This reduces banding on subtle
56// gradients while still being compression-friendly.
57// - High curvature is handled carefully in line joins. Thick lines are
58// drawn correctly as though tracing with a wide pen nib, even where
59// the lines curve sharply. (Simpler curve offsetting approaches tend
60// to show bite-like artifacts in these cases.)
61//
62// Ease of use:
63//
64// - Provided as a single-header library with no dependencies beside the
65// standard C++ library. There is nothing to link, and it even includes
66// built-in binary parsing for TrueType font (TTF) files. It is pure CPU
67// code and does not require a GPU or GPU context.
68// - Has extensive Doxygen-style documentation comments for the public API.
69// - Compiles cleanly at moderately high warning levels on most compilers.
70// - Shares no internal pointers, nor holds any external pointers. Newcomers
71// to C++ can have fun drawing with this library without worrying so much
72// about resource lifetimes or mutability.
73// - Uses no static or global variables. Threads may safely work with
74// different canvas instances concurrently without locking.
75// - Allocates no dynamic memory after reaching the high-water mark. Except
76// for the pixel buffer, flat std::vector instances embedded in the canvas
77// instance handle all dynamic memory. This reduces fragmentation and
78// makes it easy to change the code to reserve memory up front or even
79// to use statically allocated vectors.
80// - Works with exceptions and RTTI disabled.
81// - Intentionally uses a plain C++03 style to make it as widely portable
82// as possible, easier to understand, and (with indexing preferred over
83// pointer arithmetic) easier to port natively to other languages. The
84// accompanying test suite may also help with porting.
85//
86// Compact size:
87//
88// - The source code for the entire library consists of a bit over 2300 lines
89// (not counting comments or blanks), each no longer than 78 columns.
90// Alternately measured, it has fewer than 1300 semicolons.
91// - The object code for the library can be less than 36 KiB on x86-64 with
92// appropriate compiler settings for size.
93// - Due to the library's small size, the accompanying automated test suite
94// achieves 100% line coverage of the library in gcov and llvm-cov.
95
96// ======== LIMITATIONS ========
97//
98// - Trapezoidal antialiasing overestimates coverage where paths self-
99// intersect within a single pixel. Where inner joins are visible, this
100// can lead to a "grittier" appearance due to the extra windings used.
101// - Clipping uses an antialiased sparse pixel mask rather than geometrically
102// intersecting paths. Therefore, it is not subpixel-accurate.
103// - Text rendering is extremely basic and mainly for convenience. It only
104// supports left-to-right text, and does not do any hinting, kerning,
105// ligatures, text shaping, or text layout. If you require any of those,
106// consider using another library to provide those and feed the results
107// to this library as either placed glyphs or raw paths.
108// - TRUETYPE FONT PARSING IS NOT SECURE! It does some basic validity
109// checking, but should only be used with known-good or sanitized fonts.
110// - Parameter checking does not test for non-finite floating-point values.
111// - Rendering is single-threaded, not explicitly vectorized, and not GPU-
112// accelerated. It also copies data to avoid ownership issues. If you
113// need the speed, you are better off using a more fully-featured library.
114// - The library does no input or output on its own. Instead, you must
115// provide it with buffers to copy into or out of.
116
117// ======== USAGE ========
118//
119// This is a single-header library. You may freely include it in any of
120// your source files to declare the canvas_ity namespace and its members.
121// However, to get the implementation, you must
122// #define CANVAS_ITY_IMPLEMENTATION
123// in exactly one C++ file before including this header.
124//
125// Then, construct an instance of the canvas_ity::canvas class with the pixel
126// dimensions that you want and draw into it using any of the various drawing
127// functions. You can then use the get_image_data() function to retrieve the
128// currently drawn image at any time.
129//
130// See each of the public member function and data member (i.e., method
131// and field) declarations for the full API documentation. Also see the
132// accompanying C++ automated test suite for examples of the usage of each
133// public member, and the test suite's HTML5 port for how these map to the
134// HTML5 canvas API.
135
136#ifndef CANVAS_ITY_HPP
137#define CANVAS_ITY_HPP
138
139#include <cstddef>
140#include <optional>
141#include <vector>
142
143namespace canvas_ity
144{
145
146 // Public API enums
148 {
154 // confusing name, 'lighter' should be called 'plus'
155 // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus
156 // RED_out = ALPHA_src * RED_src + ALPHA_dst * RED_dst
157 // ALPHA_out = ALPHA_src + ALPHA_dst
158 // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
159 // "Where both shapes overlap, the color is determined by adding color values."
166 };
208
209 // Implementation details
210 struct xy
211 {
212 float x, y;
213 xy();
214 xy(float, float);
215 };
216 struct rgba
217 {
218 float r, g, b, a;
220 rgba(float, float, float, float);
221 };
223 {
224 float a, b, c, d, e, f;
225 };
227 {
237 std::vector<rgba> colors;
238 std::vector<float> stops;
239 std::vector<std::optional<float>> hints;
243 float angle;
246 };
248 {
249 std::vector<unsigned char> data;
251 float scale;
252 };
254 {
255 size_t count;
256 bool closed;
257 };
259 {
260 std::vector<xy> points;
261 std::vector<subpath_data> subpaths;
262 };
264 {
265 std::vector<xy> points;
266 std::vector<subpath_data> subpaths;
267 };
269 {
270 unsigned short x, y;
271 float delta;
272 };
273 typedef std::vector<pixel_run> pixel_runs;
274
275 class canvas
276 {
277 public:
278 // ======== LIFECYCLE ========
279
291 canvas(int width, int height);
292
294
295 canvas() : canvas(0, 0)
296 {
297 }
298
302
303 // ======== TRANSFORMS ========
304
317 void scale(float x, float y);
318
328 void rotate(float angle);
329
339 void translate(float x, float y);
340
358 void transform(float a, float b, float c, float d, float e, float f);
359
380 void set_transform(float a, float b, float c, float d, float e, float f);
381
382 // ======== COMPOSITING ========
383
393 void set_global_alpha(float alpha);
394
414
415 // ======== SHADOWS ========
416
433 void set_shadow_color(float red, float green, float blue, float alpha);
434
441
448
458 void set_shadow_blur(float level);
459
460 // ======== LINE STYLES ========
461
471
482
493
506 void set_miter_limit(float limit);
507
517
536 void set_line_dash(float const* segments, int count);
537
538 // ======== FILL AND STROKE STYLES ========
539
552 void set_color(brush_type type, float red, float green, float blue, float alpha);
553
555 {
556 set_color(type, c.r, c.g, c.b, c.a);
557 }
558
574 void set_linear_gradient(brush_type type, float start_x, float start_y, float end_x, float end_y);
575
596 float start_x,
597 float start_y,
598 float start_radius,
599 float end_x,
600 float end_y,
601 float end_radius);
602
603 void set_css_radial_gradient(brush_type type, float x, float y, float radius_x, float radius_y);
604
605 void set_conic_gradient(brush_type type, float x, float y, float angle);
606
632 float offset,
633 float red,
634 float green,
635 float blue,
636 float alpha,
637 std::optional<float> hint = {});
638
665 unsigned char const* image,
666 int width,
667 int height,
668 int stride,
669 repetition_style repetition);
670
671 // ======== BUILDING PATHS ========
672
679
688 void move_to(float x, float y);
689
699
711 void line_to(float x, float y);
712
729 void quadratic_curve_to(float control_x, float control_y, float x, float y);
730
751 float control_1_x, float control_1_y, float control_2_x, float control_2_y, float x, float y);
752
779 void arc_to(float vertex_x, float vertex_y, float x, float y, float radius);
780
802 void arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise = false);
803
818 void rectangle(float x, float y, float width, float height);
819
820 void polygon(std::vector<xy> points);
821
822 // ======== DRAWING PATHS ========
823
838 void fill();
839
858 void stroke();
859
873 void clip();
874
888 bool is_point_in_path(float x, float y);
889
890 // ======== DRAWING RECTANGLES ========
891
905 void clear_rectangle(float x, float y, float width, float height);
906
921 void fill_rectangle(float x, float y, float width, float height);
922
939 void stroke_rectangle(float x, float y, float width, float height);
940
941 // ======== DRAWING TEXT ========
942
956
969
986 bool set_font(unsigned char const* font, int bytes, float size);
987
988 void get_font_metrics(int& ascent, int& descent, int& height, int& x_height);
989
1010 void fill_text(char const* text, float x, float y, float maximum_width = 1.0e30f);
1011
1032 void stroke_text(char const* text, float x, float y, float maximum_width = 1.0e30f);
1033
1047 float measure_text(char const* text);
1048
1049 // ======== DRAWING IMAGES ========
1050
1078 void draw_image(unsigned char const* image,
1079 int width,
1080 int height,
1081 int stride,
1082 float x,
1083 float y,
1084 float to_width,
1085 float to_height);
1086
1087 // ======== PIXEL MANIPULATION ========
1088
1112 void get_image_data(unsigned char* image, int width, int height, int stride, int x, int y);
1113
1137 void put_image_data(unsigned char const* image, int width, int height, int stride, int x, int y);
1138
1139 int width()
1140 {
1141 return size_x;
1142 }
1144 {
1145 return size_y;
1146 }
1147
1148 // ======== CANVAS STATE ========
1149
1158 void save();
1159
1165 void restore();
1166
1167 private:
1175 std::vector<float> shadow;
1178 std::vector<float> line_dash;
1192 void add_tessellation(xy, xy, xy, xy, float, int);
1193 void add_bezier(xy, xy, xy, xy, float);
1194 void path_to_lines(bool);
1195 void add_glyph(int, float);
1196 int character_to_glyph(char const*, int&);
1197 void text_to_lines(char const*, xy, float, bool);
1199 void add_half_stroke(size_t, size_t, bool);
1202 void lines_to_runs(xy, int);
1206 };
1207
1208} // namespace canvas_ity
1209
1210#endif // CANVAS_ITY_HPP
1211
1212// ======== IMPLEMENTATION ========
1213//
1214// The general internal data flow (albeit not control flow!) for rendering
1215// a shape onto the canvas is as follows:
1216//
1217// 1. Construct a set of polybeziers representing the current path via the
1218// public path building API (move_to, line_to, bezier_curve_to, etc.).
1219// 2. Adaptively tessellate the polybeziers into polylines (path_to_lines).
1220// 3. Maybe break the polylines into shorter polylines according to the dash
1221// pattern (dash_lines).
1222// 4. Maybe stroke expand the polylines into new polylines that can be filled
1223// to show the lines with width, joins, and caps (stroke_lines).
1224// 5. Scan-convert the polylines into a sparse representation of fractional
1225// pixel coverage (lines_to_runs).
1226// 6. Maybe paint the covered pixel span alphas "offscreen", blur, color,
1227// and blend them onto the canvas where not clipped (render_shadow).
1228// 7. Paint the covered pixel spans and blend them onto the canvas where not
1229// clipped (render_main).
1230
1231#ifdef CANVAS_ITY_IMPLEMENTATION
1232#define LINEARIZE_RGB 2
1233
1234#include <algorithm>
1235#include <cmath>
1236#include <numeric>
1237
1238namespace canvas_ity
1239{
1240
1241 // 2D vector math operations
1242 const float pi = 3.14159265f;
1243 xy::xy() : x(0.0f), y(0.0f)
1244 {
1245 }
1246 xy::xy(float new_x, float new_y) : x(new_x), y(new_y)
1247 {
1248 }
1249 static xy& operator+=(xy& left, xy right)
1250 {
1251 left.x += right.x;
1252 left.y += right.y;
1253 return left;
1254 }
1255 static xy& operator-=(xy& left, xy right)
1256 {
1257 left.x -= right.x;
1258 left.y -= right.y;
1259 return left;
1260 }
1261 static xy& operator*=(xy& left, float right)
1262 {
1263 left.x *= right;
1264 left.y *= right;
1265 return left;
1266 }
1267 static xy const operator+(xy left, xy right)
1268 {
1269 return left += right;
1270 }
1271 static xy const operator-(xy left, xy right)
1272 {
1273 return left -= right;
1274 }
1275 static xy const operator*(float left, xy right)
1276 {
1277 return right *= left;
1278 }
1279 static xy const operator*(affine_matrix const& left, xy right)
1280 {
1281 return xy(left.a * right.x + left.c * right.y + left.e, left.b * right.x + left.d * right.y + left.f);
1282 }
1283 static float dot(xy left, xy right)
1284 {
1285 return left.x * right.x + left.y * right.y;
1286 }
1287 static float length(xy that)
1288 {
1289 return sqrtf(dot(that, that));
1290 }
1291 static float direction(xy that)
1292 {
1293 return atan2f(that.y, that.x);
1294 }
1295 static xy const normalized(xy that)
1296 {
1297 return 1.0f / std::max(1.0e-6f, length(that)) * that;
1298 }
1299 static xy const perpendicular(xy that)
1300 {
1301 return xy(-that.y, that.x);
1302 }
1303 static xy const lerp(xy from, xy to, float ratio)
1304 {
1305 return from + ratio * (to - from);
1306 }
1307 // ensure 0 <= angle < 360
1308 float normalize_angle(float angle)
1309 {
1310 return fmodf(angle, 360) + (angle < 0 ? 360 : 0);
1311 }
1312
1313 // RGBA color operations
1314 rgba::rgba() : r(0.0f), g(0.0f), b(0.0f), a(0.0f)
1315 {
1316 }
1317 rgba::rgba(float new_r, float new_g, float new_b, float new_a) : r(new_r), g(new_g), b(new_b), a(new_a)
1318 {
1319 }
1320 static rgba& operator+=(rgba& left, rgba right)
1321 {
1322 left.r += right.r;
1323 left.g += right.g;
1324 left.b += right.b;
1325 left.a += right.a;
1326 return left;
1327 }
1328 // static rgba &operator-=( rgba &left, rgba right ) {
1329 // left.r -= right.r; left.g -= right.g; left.b -= right.b;
1330 // left.a -= right.a; return left; }
1331 static rgba& operator*=(rgba& left, float right)
1332 {
1333 left.r *= right;
1334 left.g *= right;
1335 left.b *= right;
1336 left.a *= right;
1337 return left;
1338 }
1339 static rgba const operator+(rgba left, rgba right)
1340 {
1341 return left += right;
1342 }
1343 // static rgba const operator-( rgba left, rgba right ) {
1344 // return left -= right; }
1345 static rgba const operator*(float left, rgba right)
1346 {
1347 return right *= left;
1348 }
1349#if (CANVAS_ITY_IMPLEMENTATION + 0) & LINEARIZE_RGB
1350 static float linearized(float value)
1351 {
1352 return value < 0.04045f ? value / 12.92f : powf((value + 0.055f) / 1.055f, 2.4f);
1353 }
1354 static float delinearized(float value)
1355 {
1356 return value < 0.0031308f ? 12.92f * value : 1.055f * powf(value, 1.0f / 2.4f) - 0.055f;
1357 }
1358#else
1359 static float linearized(float value)
1360 {
1361 return value;
1362 }
1363 static float delinearized(float value)
1364 {
1365 return value;
1366 }
1367#endif
1368 static rgba const linearized(rgba that)
1369 {
1370 return rgba(linearized(that.r), linearized(that.g), linearized(that.b), that.a);
1371 }
1372 static rgba const premultiplied(rgba that)
1373 {
1374 return rgba(that.r * that.a, that.g * that.a, that.b * that.a, that.a);
1375 }
1376 static rgba const delinearized(rgba that)
1377 {
1378 return rgba(delinearized(that.r), delinearized(that.g), delinearized(that.b), that.a);
1379 }
1380 static rgba const unpremultiplied(rgba that)
1381 {
1382 static float const threshold = 1.0f / 8160.0f;
1383 return that.a < threshold
1384 ? rgba(0.0f, 0.0f, 0.0f, 0.0f)
1385 : rgba(1.0f / that.a * that.r, 1.0f / that.a * that.g, 1.0f / that.a * that.b, that.a);
1386 }
1387 static rgba const clamped(rgba that)
1388 {
1389 return rgba(std::min(std::max(that.r, 0.0f), 1.0f), std::min(std::max(that.g, 0.0f), 1.0f),
1390 std::min(std::max(that.b, 0.0f), 1.0f), std::min(std::max(that.a, 0.0f), 1.0f));
1391 }
1392
1393 // Helpers for TTF file parsing
1394 static int unsigned_8(std::vector<unsigned char>& data, int index)
1395 {
1396 return data[static_cast<size_t>(index)];
1397 }
1398 static int signed_8(std::vector<unsigned char>& data, int index)
1399 {
1400 size_t place = static_cast<size_t>(index);
1401 return static_cast<signed char>(data[place]);
1402 }
1403 static int unsigned_16(std::vector<unsigned char>& data, int index)
1404 {
1405 size_t place = static_cast<size_t>(index);
1406 return data[place] << 8 | data[place + 1];
1407 }
1408 static int signed_16(std::vector<unsigned char>& data, int index)
1409 {
1410 size_t place = static_cast<size_t>(index);
1411 return static_cast<short>(data[place] << 8 | data[place + 1]);
1412 }
1413 static int signed_32(std::vector<unsigned char>& data, int index)
1414 {
1415 size_t place = static_cast<size_t>(index);
1416 return (data[place + 0] << 24 | data[place + 1] << 16 | data[place + 2] << 8 | data[place + 3] << 0);
1417 }
1418
1419 // Tessellate (at low-level) a cubic Bezier curve and add it to the polyline
1420 // data. This recursively splits the curve until two criteria are met
1421 // (subject to a hard recursion depth limit). First, the control points
1422 // must not be farther from the line between the endpoints than the tolerance.
1423 // By the Bezier convex hull property, this ensures that the distance between
1424 // the true curve and the polyline approximation will be no more than the
1425 // tolerance. Secondly, it takes the cosine of an angular turn limit; the
1426 // curve will be split until it turns less than this amount. This is used
1427 // for stroking, with the angular limit chosen such that the sagita of an arc
1428 // with that angle and a half-stroke radius will be equal to the tolerance.
1429 // This keeps expanded strokes approximately within tolerance. Note that
1430 // in the base case, it adds the control points as well as the end points.
1431 // This way, stroke expansion infers the correct tangents from the ends of
1432 // the polylines.
1433 //
1434 void canvas::add_tessellation(xy point_1, xy control_1, xy control_2, xy point_2, float angular, int limit)
1435 {
1436 static float const tolerance = 0.125f;
1437 float flatness = tolerance * tolerance;
1438 xy edge_1 = control_1 - point_1;
1439 xy edge_2 = control_2 - control_1;
1440 xy edge_3 = point_2 - control_2;
1441 xy segment = point_2 - point_1;
1442 float squared_1 = dot(edge_1, edge_1);
1443 float squared_2 = dot(edge_2, edge_2);
1444 float squared_3 = dot(edge_3, edge_3);
1445 static float const epsilon = 1.0e-4f;
1446 float length_squared = std::max(epsilon, dot(segment, segment));
1447 float projection_1 = dot(edge_1, segment) / length_squared;
1448 float projection_2 = dot(edge_3, segment) / length_squared;
1449 float clamped_1 = std::min(std::max(projection_1, 0.0f), 1.0f);
1450 float clamped_2 = std::min(std::max(projection_2, 0.0f), 1.0f);
1451 xy to_line_1 = point_1 + clamped_1 * segment - control_1;
1452 xy to_line_2 = point_2 - clamped_2 * segment - control_2;
1453 float cosine = 1.0f;
1454 if (angular > -1.0f)
1455 {
1456 if (squared_1 * squared_3 != 0.0f)
1457 cosine = dot(edge_1, edge_3) / sqrtf(squared_1 * squared_3);
1458 else if (squared_1 * squared_2 != 0.0f)
1459 cosine = dot(edge_1, edge_2) / sqrtf(squared_1 * squared_2);
1460 else if (squared_2 * squared_3 != 0.0f)
1461 cosine = dot(edge_2, edge_3) / sqrtf(squared_2 * squared_3);
1462 }
1463 if ((dot(to_line_1, to_line_1) <= flatness && dot(to_line_2, to_line_2) <= flatness && cosine >= angular) ||
1464 !limit)
1465 {
1466 if (angular > -1.0f && squared_1 != 0.0f)
1467 lines.points.push_back(control_1);
1468 if (angular > -1.0f && squared_2 != 0.0f)
1469 lines.points.push_back(control_2);
1470 if (angular == -1.0f || squared_3 != 0.0f)
1471 lines.points.push_back(point_2);
1472 return;
1473 }
1474 xy left_1 = lerp(point_1, control_1, 0.5f);
1475 xy middle = lerp(control_1, control_2, 0.5f);
1476 xy right_2 = lerp(control_2, point_2, 0.5f);
1477 xy left_2 = lerp(left_1, middle, 0.5f);
1478 xy right_1 = lerp(middle, right_2, 0.5f);
1479 xy split = lerp(left_2, right_1, 0.5f);
1480 add_tessellation(point_1, left_1, left_2, split, angular, limit - 1);
1481 add_tessellation(split, right_1, right_2, point_2, angular, limit - 1);
1482 }
1483
1484 // Tessellate (at high-level) a cubic Bezier curve and add it to the polyline
1485 // data. This solves both for the extreme in curvature and for the horizontal
1486 // and vertical extrema. It then splits the curve into segments at these
1487 // points and passes them off to the lower-level recursive tessellation.
1488 // This preconditioning means the polyline exactly includes any cusps or
1489 // ends of tight loops without the flatness test needing to locate it via
1490 // bisection, and the angular limit test can use simple dot products without
1491 // fear of curves turning more than 90 degrees.
1492 //
1493 void canvas::add_bezier(xy point_1, xy control_1, xy control_2, xy point_2, float angular)
1494 {
1495 xy edge_1 = control_1 - point_1;
1496 xy edge_2 = control_2 - control_1;
1497 xy edge_3 = point_2 - control_2;
1498 if (dot(edge_1, edge_1) == 0.0f && dot(edge_3, edge_3) == 0.0f)
1499 {
1500 lines.points.push_back(point_2);
1501 return;
1502 }
1503 float at[7] = {0.0f, 1.0f};
1504 int cuts = 2;
1505 xy extrema_a = -9.0f * edge_2 + 3.0f * (point_2 - point_1);
1506 xy extrema_b = 6.0f * (point_1 + control_2) - 12.0f * control_1;
1507 xy extrema_c = 3.0f * edge_1;
1508 static float const epsilon = 1.0e-4f;
1509 if (fabsf(extrema_a.x) > epsilon)
1510 {
1511 float discriminant = extrema_b.x * extrema_b.x - 4.0f * extrema_a.x * extrema_c.x;
1512 if (discriminant >= 0.0f)
1513 {
1514 float sign = extrema_b.x > 0.0f ? 1.0f : -1.0f;
1515 float term = -extrema_b.x - sign * sqrtf(discriminant);
1516 float extremum_1 = term / (2.0f * extrema_a.x);
1517 at[cuts++] = extremum_1;
1518 at[cuts++] = extrema_c.x / (extrema_a.x * extremum_1);
1519 }
1520 }
1521 else if (fabsf(extrema_b.x) > epsilon)
1522 at[cuts++] = -extrema_c.x / extrema_b.x;
1523 if (fabsf(extrema_a.y) > epsilon)
1524 {
1525 float discriminant = extrema_b.y * extrema_b.y - 4.0f * extrema_a.y * extrema_c.y;
1526 if (discriminant >= 0.0f)
1527 {
1528 float sign = extrema_b.y > 0.0f ? 1.0f : -1.0f;
1529 float term = -extrema_b.y - sign * sqrtf(discriminant);
1530 float extremum_1 = term / (2.0f * extrema_a.y);
1531 at[cuts++] = extremum_1;
1532 at[cuts++] = extrema_c.y / (extrema_a.y * extremum_1);
1533 }
1534 }
1535 else if (fabsf(extrema_b.y) > epsilon)
1536 at[cuts++] = -extrema_c.y / extrema_b.y;
1537 float determinant_1 = dot(perpendicular(edge_1), edge_2);
1538 float determinant_2 = dot(perpendicular(edge_1), edge_3);
1539 float determinant_3 = dot(perpendicular(edge_2), edge_3);
1540 float curve_a = determinant_1 - determinant_2 + determinant_3;
1541 float curve_b = -2.0f * determinant_1 + determinant_2;
1542 if (fabsf(curve_a) > epsilon && fabsf(curve_b) > epsilon)
1543 at[cuts++] = -0.5f * curve_b / curve_a;
1544 for (int index = 1; index < cuts; ++index)
1545 {
1546 float value = at[index];
1547 int sorted = index - 1;
1548 for (; 0 <= sorted && value < at[sorted]; --sorted)
1549 at[sorted + 1] = at[sorted];
1550 at[sorted + 1] = value;
1551 }
1552 xy split_point_1 = point_1;
1553 for (int index = 0; index < cuts - 1; ++index)
1554 {
1555 if (!(0.0f <= at[index] && at[index + 1] <= 1.0f && at[index] != at[index + 1]))
1556 continue;
1557 float ratio = at[index] / at[index + 1];
1558 xy partial_1 = lerp(point_1, control_1, at[index + 1]);
1559 xy partial_2 = lerp(control_1, control_2, at[index + 1]);
1560 xy partial_3 = lerp(control_2, point_2, at[index + 1]);
1561 xy partial_4 = lerp(partial_1, partial_2, at[index + 1]);
1562 xy partial_5 = lerp(partial_2, partial_3, at[index + 1]);
1563 xy partial_6 = lerp(partial_1, partial_4, ratio);
1564 xy split_point_2 = lerp(partial_4, partial_5, at[index + 1]);
1565 xy split_control_2 = lerp(partial_4, split_point_2, ratio);
1566 xy split_control_1 = lerp(partial_6, split_control_2, ratio);
1567 add_tessellation(split_point_1, split_control_1, split_control_2, split_point_2, angular, 20);
1568 split_point_1 = split_point_2;
1569 }
1570 }
1571
1572 // Convert the current path to a set of polylines. This walks over the
1573 // complete set of subpaths in the current path (stored as sets of cubic
1574 // Beziers) and converts each Bezier curve segment to a polyline while
1575 // preserving information about where subpaths begin and end and whether
1576 // they are closed or open. This replaces the previous polyline data.
1577 //
1578 void canvas::path_to_lines(bool stroking)
1579 {
1580 static float const tolerance = 0.125f;
1581 float ratio = tolerance / std::max(0.5f * line_width, tolerance);
1582 float angular = stroking ? (ratio - 2.0f) * ratio * 2.0f + 1.0f : -1.0f;
1583 lines.points.clear();
1584 lines.subpaths.clear();
1585 size_t index = 0;
1586 size_t ending = 0;
1587 for (size_t subpath = 0; subpath < path.subpaths.size(); ++subpath)
1588 {
1589 ending += path.subpaths[subpath].count;
1590 size_t first = lines.points.size();
1591 xy point_1 = path.points[index++];
1592 lines.points.push_back(point_1);
1593 for (; index < ending; index += 3)
1594 {
1595 xy control_1 = path.points[index + 0];
1596 xy control_2 = path.points[index + 1];
1597 xy point_2 = path.points[index + 2];
1598 add_bezier(point_1, control_1, control_2, point_2, angular);
1599 point_1 = point_2;
1600 }
1601 subpath_data entry = {lines.points.size() - first, path.subpaths[subpath].closed};
1602 lines.subpaths.push_back(entry);
1603 }
1604 }
1605
1606 // Add a text glyph directly to the polylines. Given a glyph index, this
1607 // parses the data for that glyph directly from the TTF glyph data table and
1608 // immediately tessellates it to a set of a polyline subpaths which it adds
1609 // to any subpaths already present. It uses the current transform matrix to
1610 // transform from the glyph's vertices in font units to the proper size and
1611 // position on the canvas.
1612 //
1613 void canvas::add_glyph(int glyph, float angular)
1614 {
1615 int loc_format = unsigned_16(face.data, face.head + 50);
1616 int offset = face.glyf + (loc_format ? signed_32(face.data, face.loca + glyph * 4)
1617 : unsigned_16(face.data, face.loca + glyph * 2) * 2);
1618 int next = face.glyf + (loc_format ? signed_32(face.data, face.loca + glyph * 4 + 4)
1619 : unsigned_16(face.data, face.loca + glyph * 2 + 2) * 2);
1620 if (offset == next)
1621 return;
1622 int contours = signed_16(face.data, offset);
1623 if (contours < 0)
1624 {
1625 offset += 10;
1626 for (;;)
1627 {
1628 int flags = unsigned_16(face.data, offset);
1629 int component = unsigned_16(face.data, offset + 2);
1630 if (!(flags & 2))
1631 return; // Matching points are not supported
1632 float e =
1633 static_cast<float>(flags & 1 ? signed_16(face.data, offset + 4) : signed_8(face.data, offset + 4));
1634 float f =
1635 static_cast<float>(flags & 1 ? signed_16(face.data, offset + 6) : signed_8(face.data, offset + 5));
1636 offset += flags & 1 ? 8 : 6;
1637 float a = flags & 200 ? static_cast<float>(signed_16(face.data, offset)) / 16384.0f : 1.0f;
1638 float b = flags & 128 ? static_cast<float>(signed_16(face.data, offset + 2)) / 16384.0f : 0.0f;
1639 float c = flags & 128 ? static_cast<float>(signed_16(face.data, offset + 4)) / 16384.0f : 0.0f;
1640 float d = flags & 8 ? a
1641 : flags & 64 ? static_cast<float>(signed_16(face.data, offset + 2)) / 16384.0f
1642 : flags & 128 ? static_cast<float>(signed_16(face.data, offset + 6)) / 16384.0f
1643 : 1.0f;
1644 offset += flags & 8 ? 2 : flags & 64 ? 4 : flags & 128 ? 8 : 0;
1645 affine_matrix saved_forward = forward;
1646 affine_matrix saved_inverse = inverse;
1647 transform(a, b, c, d, e, f);
1648 add_glyph(component, angular);
1649 forward = saved_forward;
1650 inverse = saved_inverse;
1651 if (!(flags & 32))
1652 return;
1653 }
1654 }
1655 int hmetrics = unsigned_16(face.data, face.hhea + 34);
1656 int left_side_bearing = glyph < hmetrics ? signed_16(face.data, face.hmtx + glyph * 4 + 2)
1657 : signed_16(face.data, face.hmtx + hmetrics * 2 + glyph * 2);
1658 int x_min = signed_16(face.data, offset + 2);
1659 int points = unsigned_16(face.data, offset + 8 + contours * 2) + 1;
1660 int instructions = unsigned_16(face.data, offset + 10 + contours * 2);
1661 int flags_array = offset + 12 + contours * 2 + instructions;
1662 int flags_size = 0;
1663 int x_size = 0;
1664 for (int index = 0; index < points;)
1665 {
1666 int flags = unsigned_8(face.data, flags_array + flags_size++);
1667 int repeated = flags & 8 ? unsigned_8(face.data, flags_array + flags_size++) + 1 : 1;
1668 x_size += repeated * (flags & 2 ? 1 : flags & 16 ? 0 : 2);
1669 index += repeated;
1670 }
1671 int x_array = flags_array + flags_size;
1672 int y_array = x_array + x_size;
1673 int x = left_side_bearing - x_min;
1674 int y = 0;
1675 int flags = 0;
1676 int repeated = 0;
1677 int index = 0;
1678 for (int contour = 0; contour < contours; ++contour)
1679 {
1680 int beginning = index;
1681 int ending = unsigned_16(face.data, offset + 10 + contour * 2);
1682 xy begin_point = xy(0.0f, 0.0f);
1683 bool begin_on = false;
1684 xy end_point = xy(0.0f, 0.0f);
1685 bool end_on = false;
1686 size_t first = lines.points.size();
1687 for (; index <= ending; ++index)
1688 {
1689 if (repeated)
1690 --repeated;
1691 else
1692 {
1693 flags = unsigned_8(face.data, flags_array++);
1694 if (flags & 8)
1695 repeated = unsigned_8(face.data, flags_array++);
1696 }
1697 if (flags & 2)
1698 x += (unsigned_8(face.data, x_array) * (flags & 16 ? 1 : -1));
1699 else if (!(flags & 16))
1700 x += signed_16(face.data, x_array);
1701 if (flags & 4)
1702 y += (unsigned_8(face.data, y_array) * (flags & 32 ? 1 : -1));
1703 else if (!(flags & 32))
1704 y += signed_16(face.data, y_array);
1705 x_array += flags & 2 ? 1 : flags & 16 ? 0 : 2;
1706 y_array += flags & 4 ? 1 : flags & 32 ? 0 : 2;
1707 xy point = forward * xy(static_cast<float>(x), static_cast<float>(y));
1708 int on_curve = flags & 1;
1709 if (index == beginning)
1710 {
1711 begin_point = point;
1712 begin_on = on_curve;
1713 if (on_curve)
1714 lines.points.push_back(point);
1715 }
1716 else
1717 {
1718 xy point_2 = on_curve ? point : lerp(end_point, point, 0.5f);
1719 if (lines.points.size() == first || (end_on && on_curve))
1720 lines.points.push_back(point_2);
1721 else if (!end_on || on_curve)
1722 {
1723 xy point_1 = lines.points.back();
1724 xy control_1 = lerp(point_1, end_point, 2.0f / 3.0f);
1725 xy control_2 = lerp(point_2, end_point, 2.0f / 3.0f);
1726 add_bezier(point_1, control_1, control_2, point_2, angular);
1727 }
1728 }
1729 end_point = point;
1730 end_on = on_curve;
1731 }
1732 if (begin_on ^ end_on)
1733 {
1734 xy point_1 = lines.points.back();
1735 xy point_2 = lines.points[first];
1736 xy control = end_on ? begin_point : end_point;
1737 xy control_1 = lerp(point_1, control, 2.0f / 3.0f);
1738 xy control_2 = lerp(point_2, control, 2.0f / 3.0f);
1739 add_bezier(point_1, control_1, control_2, point_2, angular);
1740 }
1741 else if (!begin_on && !end_on)
1742 {
1743 xy point_1 = lines.points.back();
1744 xy split = lerp(begin_point, end_point, 0.5f);
1745 xy point_2 = lines.points[first];
1746 xy left_1 = lerp(point_1, end_point, 2.0f / 3.0f);
1747 xy left_2 = lerp(split, end_point, 2.0f / 3.0f);
1748 xy right_1 = lerp(split, begin_point, 2.0f / 3.0f);
1749 xy right_2 = lerp(point_2, begin_point, 2.0f / 3.0f);
1750 add_bezier(point_1, left_1, left_2, split, angular);
1751 add_bezier(split, right_1, right_2, point_2, angular);
1752 }
1753 lines.points.push_back(lines.points[first]);
1754 subpath_data entry = {lines.points.size() - first, true};
1755 lines.subpaths.push_back(entry);
1756 }
1757 }
1758
1759 // Decode the next codepoint from a null-terminated UTF-8 string to its glyph
1760 // index within the font. The index to the next codepoint in the string
1761 // is advanced accordingly. It checks for valid UTF-8 encoding, but not
1762 // for valid unicode codepoints. Where it finds an invalid encoding, it
1763 // decodes it as the Unicode replacement character (U+FFFD) and advances to
1764 // the invalid byte, per Unicode recommendation. It also replaces low-ASCII
1765 // whitespace characters with regular spaces. After decoding the codepoint,
1766 // it looks up the corresponding glyph index from the current font's character
1767 // map table, returning a glyph index of 0 for the .notdef character (i.e.,
1768 // "tofu") if the font lacks a glyph for that codepoint.
1769 //
1770 int canvas::character_to_glyph(char const* text, int& index)
1771 {
1772 int bytes = ((text[index] & 0x80) == 0x00 ? 1
1773 : (text[index] & 0xe0) == 0xc0 ? 2
1774 : (text[index] & 0xf0) == 0xe0 ? 3
1775 : (text[index] & 0xf8) == 0xf0 ? 4
1776 : 0);
1777 int const masks[] = {0x0, 0x7f, 0x1f, 0x0f, 0x07};
1778 int codepoint = bytes ? text[index] & masks[bytes] : 0xfffd;
1779 ++index;
1780 while (--bytes > 0)
1781 if ((text[index] & 0xc0) == 0x80)
1782 codepoint = codepoint << 6 | (text[index++] & 0x3f);
1783 else
1784 {
1785 codepoint = 0xfffd;
1786 break;
1787 }
1788 if (codepoint == '\t' || codepoint == '\v' || codepoint == '\f' || codepoint == '\r' || codepoint == '\n')
1789 codepoint = ' ';
1790 int tables = unsigned_16(face.data, face.cmap + 2);
1791 int format_12 = 0;
1792 int format_4 = 0;
1793 int format_0 = 0;
1794 for (int table = 0; table < tables; ++table)
1795 {
1796 int platform = unsigned_16(face.data, face.cmap + table * 8 + 4);
1797 int encoding = unsigned_16(face.data, face.cmap + table * 8 + 6);
1798 int offset = signed_32(face.data, face.cmap + table * 8 + 8);
1799 int format = unsigned_16(face.data, face.cmap + offset);
1800 if (platform == 3 && encoding == 10 && format == 12)
1801 format_12 = face.cmap + offset;
1802 else if (platform == 3 && encoding == 1 && format == 4)
1803 format_4 = face.cmap + offset;
1804 else if (format == 0)
1805 format_0 = face.cmap + offset;
1806 }
1807 if (format_12)
1808 {
1809 int groups = signed_32(face.data, format_12 + 12);
1810 for (int group = 0; group < groups; ++group)
1811 {
1812 int start = signed_32(face.data, format_12 + 16 + group * 12);
1813 int end = signed_32(face.data, format_12 + 20 + group * 12);
1814 int glyph = signed_32(face.data, format_12 + 24 + group * 12);
1815 if (start <= codepoint && codepoint <= end)
1816 return codepoint - start + glyph;
1817 }
1818 }
1819 else if (format_4)
1820 {
1821 int segments = unsigned_16(face.data, format_4 + 6);
1822 int end_array = format_4 + 14;
1823 int start_array = end_array + 2 + segments;
1824 int delta_array = start_array + segments;
1825 int range_array = delta_array + segments;
1826 for (int segment = 0; segment < segments; segment += 2)
1827 {
1828 int start = unsigned_16(face.data, start_array + segment);
1829 int end = unsigned_16(face.data, end_array + segment);
1830 int delta = signed_16(face.data, delta_array + segment);
1831 int range = unsigned_16(face.data, range_array + segment);
1832 if (start <= codepoint && codepoint <= end)
1833 return range ? unsigned_16(face.data, range_array + segment + (codepoint - start) * 2 + range)
1834 : (codepoint + delta) & 0xffff;
1835 }
1836 }
1837 else if (format_0 && 0 <= codepoint && codepoint < 256)
1838 return unsigned_8(face.data, format_0 + 6 + codepoint);
1839 return 0;
1840 }
1841
1842 void canvas::get_font_metrics(int& ascent, int& descent, int& height, int& x_height)
1843 {
1844 if (face.data.empty())
1845 return;
1846
1847 // https://www.w3.org/TR/css-inline/#ascent-descent
1848 // "It is recommended that implementations that use OpenType or TrueType fonts use the metrics sTypoAscender and
1849 // sTypoDescender from the font’s OS/2 table"
1850 float sTypoAscender = (float)signed_16(face.data, face.os_2 + 68);
1851 float sTypoDescender = (float)signed_16(face.data, face.os_2 + 70);
1852 // Some fonts, eg. Inconsolata, don't have the sxHeight field (it is defined in the second version of OS/2
1853 // table). If it is absent (yMax - yMin) for 'x' from glyf table can be used.
1854 int os2ver = unsigned_16(face.data, face.os_2);
1855 float sxHeight = os2ver >= 2 ? (float)signed_16(face.data, face.os_2 + 86) : 0;
1856
1857 ascent = (int)ceil(sTypoAscender * face.scale);
1858 descent = (int)ceil(-sTypoDescender * face.scale);
1859
1860 // https://www.w3.org/TR/css-inline/#font-line-gap
1861 // https://www.w3.org/TR/css-inline/#inline-height
1862 // In several fonts I examined, including Inconsolata and csstest-ascii.ttf from w3.org,
1863 // both OS/2 sTypoLineGap and hhea lineGap are either 0 or very small (I used https://fontdrop.info/ viewer).
1864 // cairo container sets height to ascent + descent, litehtml uses this value as a normal line height and for
1865 // baseline calculations.
1866 height = (int)ceil((sTypoAscender - sTypoDescender) * face.scale);
1867
1868 x_height = (int)ceil(sxHeight * face.scale);
1869 }
1870
1871 // Convert a text string to a set of polylines. This works out the placement
1872 // of the text string relative to the anchor position. Then it walks through
1873 // the string, sizing and placing each character by temporarily changing the
1874 // current transform matrix to map from font units to canvas pixel coordinates
1875 // before adding the glyph to the polylines. This replaces the previous
1876 // polyline data.
1877 //
1878 void canvas::text_to_lines(char const* text, xy position, float maximum_width, bool stroking)
1879 {
1880 static float const tolerance = 0.125f;
1881 float ratio = tolerance / std::max(0.5f * line_width, tolerance);
1882 float angular = stroking ? (ratio - 2.0f) * ratio * 2.0f + 1.0f : -1.0f;
1883 lines.points.clear();
1884 lines.subpaths.clear();
1885 if (face.data.empty() || !text || maximum_width <= 0.0f)
1886 return;
1887 float width = maximum_width == 1.0e30f && text_align == leftward ? 0.0f : measure_text(text);
1888 float reduction = maximum_width / std::max(maximum_width, width);
1889 if (text_align == rightward)
1890 position.x -= width * reduction;
1891 else if (text_align == center)
1892 position.x -= 0.5f * width * reduction;
1893 xy scaling = face.scale * xy(reduction, 1.0f);
1894 float units_per_em = static_cast<float>(unsigned_16(face.data, face.head + 18));
1895 float ascender = static_cast<float>(signed_16(face.data, face.os_2 + 68));
1896 float descender = static_cast<float>(signed_16(face.data, face.os_2 + 70));
1897 if (text_baseline == top)
1898 position.y += ascender * face.scale;
1899 else if (text_baseline == middle)
1900 position.y += (ascender - descender) * 0.5f * face.scale;
1901 else if (text_baseline == bottom)
1902 position.y += descender * face.scale;
1903 else if (text_baseline == hanging)
1904 position.y += 0.6f * face.scale * units_per_em;
1905 affine_matrix saved_forward = forward;
1906 affine_matrix saved_inverse = inverse;
1907 int hmetrics = unsigned_16(face.data, face.hhea + 34);
1908 int place = 0;
1909 for (int index = 0; text[index];)
1910 {
1911 int glyph = character_to_glyph(text, index);
1912 forward = saved_forward;
1913 transform(scaling.x, 0.0f, 0.0f, -scaling.y, position.x + static_cast<float>(place) * scaling.x,
1914 position.y);
1915 add_glyph(glyph, angular);
1916 int entry = std::min(glyph, hmetrics - 1);
1917 place += unsigned_16(face.data, face.hmtx + entry * 4);
1918 }
1919 forward = saved_forward;
1920 inverse = saved_inverse;
1921 }
1922
1923 // Break the polylines into smaller pieces according to the dash settings.
1924 // This walks along the polyline subpaths and dash pattern together, emitting
1925 // new points via lerping where dash segments begin and end. Each dash
1926 // segment becomes a new open subpath in the polyline. Some care is to
1927 // taken to handle two special cases of closed subpaths. First, those that
1928 // are completely within the first dash segment should be emitted as-is and
1929 // remain closed. Secondly, those that start and end within a dash should
1930 // have the two dashes merged together so that the lines join. This replaces
1931 // the previous polyline data.
1932 //
1933 void canvas::dash_lines()
1934 {
1935 if (line_dash.empty())
1936 return;
1937 lines.points.swap(scratch.points);
1938 lines.points.clear();
1940 lines.subpaths.clear();
1941 float total = std::accumulate(line_dash.begin(), line_dash.end(), 0.0f);
1942 float offset = fmodf(line_dash_offset, total);
1943 if (offset < 0.0f)
1944 offset += total;
1945 size_t start = 0;
1946 while (offset >= line_dash[start])
1947 {
1949 start = start + 1 < line_dash.size() ? start + 1 : 0;
1950 }
1951 size_t ending = 0;
1952 for (size_t subpath = 0; subpath < scratch.subpaths.size(); ++subpath)
1953 {
1954 size_t index = ending;
1955 ending += scratch.subpaths[subpath].count;
1956 size_t first = lines.points.size();
1957 size_t segment = start;
1958 bool emit = ~start & 1;
1959 size_t merge_point = lines.points.size();
1960 size_t merge_subpath = lines.subpaths.size();
1961 bool merge_emit = emit;
1962 float next = line_dash[start] - offset;
1963 for (; index + 1 < ending; ++index)
1964 {
1965 xy from = scratch.points[index];
1966 xy to = scratch.points[index + 1];
1967 if (emit)
1968 lines.points.push_back(from);
1969 float line = length(inverse * to - inverse * from);
1970 while (next < line)
1971 {
1972 lines.points.push_back(lerp(from, to, next / line));
1973 if (emit)
1974 {
1975 subpath_data entry = {lines.points.size() - first, false};
1976 lines.subpaths.push_back(entry);
1977 first = lines.points.size();
1978 }
1979 segment = segment + 1 < line_dash.size() ? segment + 1 : 0;
1980 emit = !emit;
1981 next += line_dash[segment];
1982 }
1983 next -= line;
1984 }
1985 if (emit)
1986 {
1987 lines.points.push_back(scratch.points[index]);
1988 subpath_data entry = {lines.points.size() - first, false};
1989 lines.subpaths.push_back(entry);
1990 if (scratch.subpaths[subpath].closed && merge_emit)
1991 {
1992 if (lines.subpaths.size() == merge_subpath + 1)
1993 lines.subpaths.back().closed = true;
1994 else
1995 {
1996 size_t count = lines.subpaths.back().count;
1997 std::rotate((lines.points.begin() + static_cast<ptrdiff_t>(merge_point)),
1998 (lines.points.end() - static_cast<ptrdiff_t>(count)), lines.points.end());
1999 lines.subpaths[merge_subpath].count += count;
2000 lines.subpaths.pop_back();
2001 }
2002 }
2003 }
2004 }
2005 }
2006
2007 // Trace along a series of points from a subpath in the scratch polylines
2008 // and add new points to the main polylines with the stroke expansion on
2009 // one side. Calling this again with the ends reversed adds the other
2010 // half of the stroke. If the original subpath was closed, each pass
2011 // adds a complete closed loop, with one adding the inside and one adding
2012 // the outside. The two will wind in opposite directions. If the original
2013 // subpath was open, each pass ends with one of the line caps and the two
2014 // passes together form a single closed loop. In either case, this handles
2015 // adding line joins, including inner joins. Care is taken to fill tight
2016 // inside turns correctly by adding additional windings. See Figure 10 of
2017 // "Converting Stroked Primitives to Filled Primitives" by Diego Nehab, for
2018 // the inspiration for these extra windings.
2019 //
2020 void canvas::add_half_stroke(size_t beginning, size_t ending, bool closed)
2021 {
2022 float half = line_width * 0.5f;
2023 float ratio = miter_limit * miter_limit * half * half;
2024 xy in_direction = xy(0.0f, 0.0f);
2025 float in_length = 0.0f;
2026 xy point = inverse * scratch.points[beginning];
2027 size_t finish = beginning;
2028 size_t index = beginning;
2029 do
2030 {
2031 xy next = inverse * scratch.points[index];
2032 xy out_direction = normalized(next - point);
2033 float out_length = length(next - point);
2034 static float const epsilon = 1.0e-4f;
2035 if (in_length != 0.0f && out_length >= epsilon)
2036 {
2037 if (closed && finish == beginning)
2038 finish = index;
2039 xy side_in = point + half * perpendicular(in_direction);
2040 xy side_out = point + half * perpendicular(out_direction);
2041 float turn = dot(perpendicular(in_direction), out_direction);
2042 if (fabsf(turn) < epsilon)
2043 turn = 0.0f;
2044 xy offset = turn == 0.0f ? xy(0.0f, 0.0f) : half / turn * (out_direction - in_direction);
2045 bool tight = (dot(offset, in_direction) < -in_length && dot(offset, out_direction) > out_length);
2046 if (turn > 0.0f && tight)
2047 {
2048 std::swap(side_in, side_out);
2049 std::swap(in_direction, out_direction);
2050 lines.points.push_back(forward * side_out);
2051 lines.points.push_back(forward * point);
2052 lines.points.push_back(forward * side_in);
2053 }
2054 if ((turn > 0.0f && !tight) || (turn != 0.0f && line_join == miter && dot(offset, offset) <= ratio))
2055 lines.points.push_back(forward * (point + offset));
2056 else if (line_join == rounded)
2057 {
2058 float cosine = dot(in_direction, out_direction);
2059 float angle = acosf(std::min(std::max(cosine, -1.0f), 1.0f));
2060 float alpha = 4.0f / 3.0f * tanf(0.25f * angle);
2061 lines.points.push_back(forward * side_in);
2062 add_bezier(forward * side_in, forward * (side_in + alpha * half * in_direction),
2063 forward * (side_out - alpha * half * out_direction), forward * side_out, -1.0f);
2064 }
2065 else
2066 {
2067 lines.points.push_back(forward * side_in);
2068 lines.points.push_back(forward * side_out);
2069 }
2070 if (turn > 0.0f && tight)
2071 {
2072 lines.points.push_back(forward * side_out);
2073 lines.points.push_back(forward * point);
2074 lines.points.push_back(forward * side_in);
2075 std::swap(in_direction, out_direction);
2076 }
2077 }
2078 if (out_length >= epsilon)
2079 {
2080 in_direction = out_direction;
2081 in_length = out_length;
2082 point = next;
2083 }
2084 index = (index == ending ? beginning : ending > beginning ? index + 1 : index - 1);
2085 } while (index != finish);
2086 if (closed || in_length == 0.0f)
2087 return;
2088 xy ahead = half * in_direction;
2089 xy side = perpendicular(ahead);
2090 if (line_cap == butt)
2091 {
2092 lines.points.push_back(forward * (point + side));
2093 lines.points.push_back(forward * (point - side));
2094 }
2095 else if (line_cap == square)
2096 {
2097 lines.points.push_back(forward * (point + ahead + side));
2098 lines.points.push_back(forward * (point + ahead - side));
2099 }
2100 else if (line_cap == circle)
2101 {
2102 static float const alpha = 0.55228475f; // 4/3*tan(pi/8)
2103 lines.points.push_back(forward * (point + side));
2104 add_bezier(forward * (point + side), forward * (point + side + alpha * ahead),
2105 forward * (point + ahead + alpha * side), forward * (point + ahead), -1.0f);
2106 add_bezier(forward * (point + ahead), forward * (point + ahead - alpha * side),
2107 forward * (point - side + alpha * ahead), forward * (point - side), -1.0f);
2108 }
2109 }
2110
2111 // Perform stroke expansion on the polylines. After first breaking them up
2112 // according to the dash pattern (if any), it then moves the polyline data to
2113 // the scratch space. Each subpath is then traced both forwards and backwards
2114 // to add the points for a half stroke, which together create the points for
2115 // one (if the original subpath was open) or two (if it was closed) new closed
2116 // subpaths which, when filled, will draw the stroked lines. While the lower
2117 // level code this calls only adds the points of the half strokes, this
2118 // adds subpath information for the strokes. This replaces the previous
2119 // polyline data.
2120 //
2122 {
2123 if (forward.a * forward.d - forward.b * forward.c == 0.0f)
2124 return;
2125 dash_lines();
2126 lines.points.swap(scratch.points);
2127 lines.points.clear();
2129 lines.subpaths.clear();
2130 size_t ending = 0;
2131 for (size_t subpath = 0; subpath < scratch.subpaths.size(); ++subpath)
2132 {
2133 size_t beginning = ending;
2134 ending += scratch.subpaths[subpath].count;
2135 if (ending - beginning < 2)
2136 continue;
2137 size_t first = lines.points.size();
2138 add_half_stroke(beginning, ending - 1, scratch.subpaths[subpath].closed);
2139 if (scratch.subpaths[subpath].closed)
2140 {
2141 subpath_data entry = {lines.points.size() - first, true};
2142 lines.subpaths.push_back(entry);
2143 first = lines.points.size();
2144 }
2145 add_half_stroke(ending - 1, beginning, scratch.subpaths[subpath].closed);
2146 subpath_data entry = {lines.points.size() - first, true};
2147 lines.subpaths.push_back(entry);
2148 }
2149 }
2150
2151 // Scan-convert a single polyline segment. This walks along the pixels that
2152 // the segment touches in left-to-right order, using signed trapezoidal area
2153 // to accumulate a list of changes in signed coverage at each visible pixel
2154 // when processing them from left to right. Each run of horizontal pixels
2155 // ends with one final update to the right of the last pixel to bring the
2156 // total up to date. Note that this does not clip to the screen boundary.
2157 //
2158 void canvas::add_runs(xy from, xy to)
2159 {
2160 static float const epsilon = 2.0e-5f;
2161 if (fabsf(to.y - from.y) < epsilon)
2162 return;
2163 float sign = to.y > from.y ? 1.0f : -1.0f;
2164 if (from.x > to.x)
2165 std::swap(from, to);
2166 xy now = from;
2167 xy pixel = xy(floorf(now.x), floorf(now.y));
2168 xy corner = pixel + xy(1.0f, to.y > from.y ? 1.0f : 0.0f);
2169 xy slope = xy((to.x - from.x) / (to.y - from.y), (to.y - from.y) / (to.x - from.x));
2170 xy next_x = (to.x - from.x < epsilon) ? to : xy(corner.x, now.y + (corner.x - now.x) * slope.y);
2171 xy next_y = xy(now.x + (corner.y - now.y) * slope.x, corner.y);
2172 if ((from.y < to.y && to.y < next_y.y) || (from.y > to.y && to.y > next_y.y))
2173 next_y = to;
2174 float y_step = to.y > from.y ? 1.0f : -1.0f;
2175 do
2176 {
2177 float carry = 0.0f;
2178 while (next_x.x < next_y.x)
2179 {
2180 float strip = std::min(std::max((next_x.y - now.y) * y_step, 0.0f), 1.0f);
2181 float mid = (next_x.x + now.x) * 0.5f;
2182 float area = (mid - pixel.x) * strip;
2183 pixel_run piece = {static_cast<unsigned short>(pixel.x), static_cast<unsigned short>(pixel.y),
2184 (carry + strip - area) * sign};
2185 runs.push_back(piece);
2186 carry = area;
2187 now = next_x;
2188 next_x.x += 1.0f;
2189 next_x.y = (next_x.x - from.x) * slope.y + from.y;
2190 pixel.x += 1.0f;
2191 }
2192 float strip = std::min(std::max((next_y.y - now.y) * y_step, 0.0f), 1.0f);
2193 float mid = (next_y.x + now.x) * 0.5f;
2194 float area = (mid - pixel.x) * strip;
2195 pixel_run piece_1 = {static_cast<unsigned short>(pixel.x), static_cast<unsigned short>(pixel.y),
2196 (carry + strip - area) * sign};
2197 pixel_run piece_2 = {static_cast<unsigned short>(pixel.x + 1.0f), static_cast<unsigned short>(pixel.y),
2198 area * sign};
2199 runs.push_back(piece_1);
2200 runs.push_back(piece_2);
2201 now = next_y;
2202 next_y.y += y_step;
2203 next_y.x = (next_y.y - from.y) * slope.x + from.x;
2204 pixel.y += y_step;
2205 if ((from.y < to.y && to.y < next_y.y) || (from.y > to.y && to.y > next_y.y))
2206 next_y = to;
2207 } while (now.y != to.y);
2208 }
2209
2210 static bool operator<(pixel_run left, pixel_run right)
2211 {
2212 return (left.y < right.y ? true
2213 : left.y > right.y ? false
2214 : left.x < right.x ? true
2215 : left.x > right.x ? false
2216 : fabsf(left.delta) < fabsf(right.delta));
2217 }
2218
2219 // Scan-convert the polylines to prepare them for antialiased rendering.
2220 // For each of the polyline loops, it first clips them to the screen.
2221 // See "Reentrant Polygon Clipping" by Sutherland and Hodgman for details.
2222 // Then it walks the polyline loop and scan-converts each line segment to
2223 // produce a list of changes in signed pixel coverage when processed in
2224 // left-to-right, top-to-bottom order. The list of changes is then sorted
2225 // into that order, and multiple changes to the same pixel are coalesced
2226 // by summation. The result is a sparse, run-length encoded description
2227 // of the coverage of each pixel to be drawn.
2228 //
2229 void canvas::lines_to_runs(xy offset, int padding)
2230 {
2231 runs.clear();
2232 float width = static_cast<float>(size_x + padding);
2233 float height = static_cast<float>(size_y + padding);
2234 size_t ending = 0;
2235 for (size_t subpath = 0; subpath < lines.subpaths.size(); ++subpath)
2236 {
2237 size_t beginning = ending;
2238 ending += lines.subpaths[subpath].count;
2239 scratch.points.clear();
2240 for (size_t index = beginning; index < ending; ++index)
2241 scratch.points.push_back(offset + lines.points[index]);
2242 for (int edge = 0; edge < 4; ++edge)
2243 {
2244 xy normal = xy(edge == 0 ? 1.0f
2245 : edge == 2 ? -1.0f
2246 : 0.0f,
2247 edge == 1 ? 1.0f
2248 : edge == 3 ? -1.0f
2249 : 0.0f);
2250 float place = edge == 2 ? width : edge == 3 ? height : 0.0f;
2251 size_t first = scratch.points.size();
2252 for (size_t index = 0; index < first; ++index)
2253 {
2254 xy from = scratch.points[(index ? index : first) - 1];
2255 xy to = scratch.points[index];
2256 float from_side = dot(from, normal) + place;
2257 float to_side = dot(to, normal) + place;
2258 if (from_side * to_side < 0.0f)
2259 scratch.points.push_back(lerp(from, to, from_side / (from_side - to_side)));
2260 if (to_side >= 0.0f)
2261 scratch.points.push_back(to);
2262 }
2263 scratch.points.erase(scratch.points.begin(), scratch.points.begin() + static_cast<ptrdiff_t>(first));
2264 }
2265 size_t last = scratch.points.size();
2266 for (size_t index = 0; index < last; ++index)
2267 {
2268 xy from = scratch.points[(index ? index : last) - 1];
2269 xy to = scratch.points[index];
2270 add_runs(xy(std::min(std::max(from.x, 0.0f), width), std::min(std::max(from.y, 0.0f), height)),
2271 xy(std::min(std::max(to.x, 0.0f), width), std::min(std::max(to.y, 0.0f), height)));
2272 }
2273 }
2274 if (runs.empty())
2275 return;
2276 std::sort(runs.begin(), runs.end());
2277 size_t to = 0;
2278 for (size_t from = 1; from < runs.size(); ++from)
2279 if (runs[from].x == runs[to].x && runs[from].y == runs[to].y)
2280 runs[to].delta += runs[from].delta;
2281 else if (runs[from].delta != 0.0f)
2282 runs[++to] = runs[from];
2283 runs.erase(runs.begin() + static_cast<ptrdiff_t>(to) + 1, runs.end());
2284 }
2285
2286 // Paint a pixel according to its point location and a paint style to produce
2287 // a premultiplied, linearized RGBA color. This handles all supported paint
2288 // styles: solid colors, linear gradients, radial gradients, and patterns.
2289 // For gradients and patterns, it takes into account the current transform.
2290 // Patterns are resampled using a separable bicubic convolution filter,
2291 // with edges handled according to the wrap mode. See "Cubic Convolution
2292 // Interpolation for Digital Image Processing" by Keys. This filter is best
2293 // known for magnification, but also works well for antialiased minification,
2294 // since it's actually a Catmull-Rom spline approximation of Lanczos-2.
2295 //
2296 rgba canvas::paint_pixel(xy point, paint_brush const& brush)
2297 {
2298 if (brush.colors.empty())
2299 return rgba(0.0f, 0.0f, 0.0f, 0.0f);
2300 if (brush.type == paint_brush::color)
2301 return brush.colors.front();
2302 point = inverse * point;
2303 if (brush.type == paint_brush::pattern)
2304 {
2305 float width = static_cast<float>(brush.width);
2306 float height = static_cast<float>(brush.height);
2307 if (((brush.repetition & 2) && (point.x < 0.0f || width <= point.x)) ||
2308 ((brush.repetition & 1) && (point.y < 0.0f || height <= point.y)))
2309 return rgba(0.0f, 0.0f, 0.0f, 0.0f);
2310 float scale_x = fabsf(inverse.a) + fabsf(inverse.c);
2311 float scale_y = fabsf(inverse.b) + fabsf(inverse.d);
2312 scale_x = std::max(1.0f, std::min(scale_x, width * 0.25f));
2313 scale_y = std::max(1.0f, std::min(scale_y, height * 0.25f));
2314 float reciprocal_x = 1.0f / scale_x;
2315 float reciprocal_y = 1.0f / scale_y;
2316 point -= xy(0.5f, 0.5f);
2317 int left = static_cast<int>(ceilf(point.x - scale_x * 2.0f));
2318 int top = static_cast<int>(ceilf(point.y - scale_y * 2.0f));
2319 int right = static_cast<int>(ceilf(point.x + scale_x * 2.0f));
2320 int bottom = static_cast<int>(ceilf(point.y + scale_y * 2.0f));
2321 rgba total_color = rgba(0.0f, 0.0f, 0.0f, 0.0f);
2322 float total_weight = 0.0f;
2323 for (int pattern_y = top; pattern_y < bottom; ++pattern_y)
2324 {
2325 float y = fabsf(reciprocal_y * (static_cast<float>(pattern_y) - point.y));
2326 float weight_y =
2327 (y < 1.0f ? (1.5f * y - 2.5f) * y * y + 1.0f : ((-0.5f * y + 2.5f) * y - 4.0f) * y + 2.0f);
2328 int wrapped_y = pattern_y % brush.height;
2329 if (wrapped_y < 0)
2330 wrapped_y += brush.height;
2331 if (&brush == &image_brush)
2332 wrapped_y = std::min(std::max(pattern_y, 0), brush.height - 1);
2333 for (int pattern_x = left; pattern_x < right; ++pattern_x)
2334 {
2335 float x = fabsf(reciprocal_x * (static_cast<float>(pattern_x) - point.x));
2336 float weight_x =
2337 (x < 1.0f ? (1.5f * x - 2.5f) * x * x + 1.0f : ((-0.5f * x + 2.5f) * x - 4.0f) * x + 2.0f);
2338 int wrapped_x = pattern_x % brush.width;
2339 if (wrapped_x < 0)
2340 wrapped_x += brush.width;
2341 if (&brush == &image_brush)
2342 wrapped_x = std::min(std::max(pattern_x, 0), brush.width - 1);
2343 float weight = weight_x * weight_y;
2344 size_t index = static_cast<size_t>(wrapped_y * brush.width + wrapped_x);
2345 total_color += weight * brush.colors[index];
2346 total_weight += weight;
2347 }
2348 }
2349 return (1.0f / total_weight) * total_color;
2350 }
2351 float offset = 0;
2352 xy relative = point - brush.start;
2353 xy line = brush.end - brush.start;
2354 float gradient = dot(relative, line);
2355 float span = dot(line, line);
2356 if (brush.type == paint_brush::linear)
2357 {
2358 if (span == 0.0f)
2359 return rgba(0.0f, 0.0f, 0.0f, 0.0f);
2360 offset = gradient / span;
2361 }
2362 else if (brush.type == paint_brush::radial)
2363 {
2364 float initial = brush.start_radius;
2365 float change = brush.end_radius - initial;
2366 float a = span - change * change;
2367 float b = -2.0f * (gradient + initial * change);
2368 float c = dot(relative, relative) - initial * initial;
2369 float discriminant = b * b - 4.0f * a * c;
2370 if (discriminant < 0.0f || (span == 0.0f && change == 0.0f))
2371 return rgba(0.0f, 0.0f, 0.0f, 0.0f);
2372 float root = sqrtf(discriminant);
2373 float reciprocal = 1.0f / (2.0f * a);
2374 float offset_1 = (-b - root) * reciprocal;
2375 float offset_2 = (-b + root) * reciprocal;
2376 float radius_1 = initial + change * offset_1;
2377 float radius_2 = initial + change * offset_2;
2378 if (radius_2 >= 0.0f)
2379 offset = offset_2;
2380 else if (radius_1 >= 0.0f)
2381 offset = offset_1;
2382 else
2383 return rgba(0.0f, 0.0f, 0.0f, 0.0f);
2384 }
2385 else if (brush.type == paint_brush::css_radial)
2386 {
2387 if (brush.css_radius.x == 0 || brush.css_radius.y == 0)
2388 offset = 1;
2389 else
2390 {
2391 xy rel = {relative.x / brush.css_radius.x, relative.y / brush.css_radius.y};
2392 offset = length(rel);
2393 }
2394 }
2395 else if (brush.type == paint_brush::conic)
2396 {
2397 float angle = 90 + direction(relative) * 180 / pi;
2398 offset = normalize_angle(angle - brush.angle) / 360;
2399 }
2400 size_t index =
2401 static_cast<size_t>(std::upper_bound(brush.stops.begin(), brush.stops.end(), offset) - brush.stops.begin());
2402 if (index == 0)
2403 return premultiplied(brush.colors.front());
2404 if (index == brush.stops.size())
2405 return premultiplied(brush.colors.back());
2406 struct
2407 {
2408 rgba color;
2409 float stop;
2410 std::optional<float> hint;
2411 } A = {brush.colors[index - 1], brush.stops[index - 1], brush.hints[index - 1]},
2412 B = {brush.colors[index], brush.stops[index], {}};
2413 // https://drafts.csswg.org/css-images-4/#coloring-gradient-line
2414 // 1. Determine the location of the transition hint as a percentage of the distance between the two color stops,
2415 // denoted as a number between 0 and 1
2416 float H = !A.hint ? .5f : (*A.hint - A.stop) / (B.stop - A.stop);
2417 // 2. For any given point between the two color stops, determine the point’s location as a percentage of the
2418 // distance between the two color stops, in the same way as the previous step.
2419 float P = (offset - A.stop) / (B.stop - A.stop);
2420 // 3. Let C, the color weighting at that point, be equal to P^logH(.5).
2421 float C = pow(P, log(.5f) / log(H));
2422 // 4. The color at that point is then a linear blend between the colors of the two color stops, blending (1 - C)
2423 // of the first stop and C of the second stop.
2424 return premultiplied((1 - C) * A.color + C * B.color);
2425 }
2426
2427 // Render the shadow of the polylines into the pixel buffer if needed. After
2428 // computing the border as the maximum distance that one pixel can affect
2429 // another via the blur, it scan-converts the lines to runs with the shadow
2430 // offset and that extra amount of border padding. Then it bounds the scan
2431 // converted shape, pads this with border, clips that to the extended canvas
2432 // size, and rasterizes to fill a working area with the alpha. Next, a fast
2433 // approximation of a Gaussian blur is done using three passes of box blurs
2434 // each in the rows and columns. Note that these box blurs have a small extra
2435 // weight on the tails to allow for fractional widths. See "Theoretical
2436 // Foundations of Gaussian Convolution by Extended Box Filtering" by Gwosdek
2437 // et al. for details. Finally, it colors the blurred alpha image with
2438 // the shadow color and blends this into the pixel buffer according to the
2439 // compositing settings and clip mask. Note that it does not bother clearing
2440 // outside the area of the alpha image when the compositing settings require
2441 // clearing; that will be done on the subsequent main rendering pass.
2442 //
2443 void canvas::render_shadow(paint_brush const& brush)
2444 {
2445 if (shadow_color.a == 0.0f || (shadow_blur == 0.0f && shadow_offset_x == 0.0f && shadow_offset_y == 0.0f))
2446 return;
2447 float sigma_squared = 0.25f * shadow_blur * shadow_blur;
2448 size_t radius = static_cast<size_t>(0.5f * sqrtf(4.0f * sigma_squared + 1.0f) - 0.5f);
2449 int border = 3 * (static_cast<int>(radius) + 1);
2450 xy offset = xy(static_cast<float>(border) + shadow_offset_x, static_cast<float>(border) + shadow_offset_y);
2451 lines_to_runs(offset, 2 * border);
2452 int left = size_x + 2 * border;
2453 int right = 0;
2454 int top = size_y + 2 * border;
2455 int bottom = 0;
2456 for (size_t index = 0; index < runs.size(); ++index)
2457 {
2458 left = std::min(left, static_cast<int>(runs[index].x));
2459 right = std::max(right, static_cast<int>(runs[index].x));
2460 top = std::min(top, static_cast<int>(runs[index].y));
2461 bottom = std::max(bottom, static_cast<int>(runs[index].y));
2462 }
2463 left = std::max(left - border, 0);
2464 right = std::min(right + border, size_x + 2 * border) + 1;
2465 top = std::max(top - border, 0);
2466 bottom = std::min(bottom + border, size_y + 2 * border);
2467 size_t width = static_cast<size_t>(std::max(right - left, 0));
2468 size_t height = static_cast<size_t>(std::max(bottom - top, 0));
2469 size_t working = width * height;
2470 shadow.clear();
2471 shadow.resize(working + std::max(width, height));
2472 static float const threshold = 1.0f / 8160.0f;
2473 {
2474 int x = -1;
2475 int y = -1;
2476 float sum = 0.0f;
2477 for (size_t index = 0; index < runs.size(); ++index)
2478 {
2479 pixel_run next = runs[index];
2480 float coverage = std::min(fabsf(sum), 1.0f);
2481 int to = next.y == y ? next.x : x + 1;
2482 if (coverage >= threshold)
2483 for (; x < to; ++x)
2484 shadow[static_cast<size_t>(y - top) * width + static_cast<size_t>(x - left)] =
2485 coverage *
2486 paint_pixel(xy(static_cast<float>(x) + 0.5f, static_cast<float>(y) + 0.5f) - offset, brush)
2487 .a;
2488 if (next.y != y)
2489 sum = 0.0f;
2490 x = next.x;
2491 y = next.y;
2492 sum += next.delta;
2493 }
2494 }
2495 float alpha = static_cast<float>(2 * radius + 1) * (static_cast<float>(radius * (radius + 1)) - sigma_squared) /
2496 (2.0f * sigma_squared - static_cast<float>(6 * (radius + 1) * (radius + 1)));
2497 float divisor = 2.0f * (alpha + static_cast<float>(radius)) + 1.0f;
2498 float weight_1 = alpha / divisor;
2499 float weight_2 = (1.0f - alpha) / divisor;
2500 for (size_t y = 0; y < height; ++y)
2501 for (int pass = 0; pass < 3; ++pass)
2502 {
2503 for (size_t x = 0; x < width; ++x)
2504 shadow[working + x] = shadow[y * width + x];
2505 float running = weight_1 * shadow[working + radius + 1];
2506 for (size_t x = 0; x <= radius; ++x)
2507 running += (weight_1 + weight_2) * shadow[working + x];
2508 shadow[y * width] = running;
2509 for (size_t x = 1; x < width; ++x)
2510 {
2511 if (x >= radius + 1)
2512 running -= weight_2 * shadow[working + x - radius - 1];
2513 if (x >= radius + 2)
2514 running -= weight_1 * shadow[working + x - radius - 2];
2515 if (x + radius < width)
2516 running += weight_2 * shadow[working + x + radius];
2517 if (x + radius + 1 < width)
2518 running += weight_1 * shadow[working + x + radius + 1];
2519 shadow[y * width + x] = running;
2520 }
2521 }
2522 for (size_t x = 0; x < width; ++x)
2523 for (int pass = 0; pass < 3; ++pass)
2524 {
2525 for (size_t y = 0; y < height; ++y)
2526 shadow[working + y] = shadow[y * width + x];
2527 float running = weight_1 * shadow[working + radius + 1];
2528 for (size_t y = 0; y <= radius; ++y)
2529 running += (weight_1 + weight_2) * shadow[working + y];
2530 shadow[x] = running;
2531 for (size_t y = 1; y < height; ++y)
2532 {
2533 if (y >= radius + 1)
2534 running -= weight_2 * shadow[working + y - radius - 1];
2535 if (y >= radius + 2)
2536 running -= weight_1 * shadow[working + y - radius - 2];
2537 if (y + radius < height)
2538 running += weight_2 * shadow[working + y + radius];
2539 if (y + radius + 1 < height)
2540 running += weight_1 * shadow[working + y + radius + 1];
2541 shadow[y * width + x] = running;
2542 }
2543 }
2544 int operation = global_composite_operation;
2545 int x = -1;
2546 int y = -1;
2547 float sum = 0.0f;
2548 for (size_t index = 0; index < mask.size(); ++index)
2549 {
2550 pixel_run next = mask[index];
2551 float visibility = std::min(fabsf(sum), 1.0f);
2552 int to = std::min(next.y == y ? next.x : x + 1, right - border);
2553 if (visibility >= threshold && top <= y + border && y + border < bottom)
2554 for (; x < to; ++x)
2555 {
2556 rgba& back = bitmap[y * size_x + x];
2557 rgba fore =
2558 global_alpha *
2559 shadow[static_cast<size_t>(y + border - top) * width + static_cast<size_t>(x + border - left)] *
2561 float mix_fore = operation & 1 ? back.a : 0.0f;
2562 if (operation & 2)
2563 mix_fore = 1.0f - mix_fore;
2564 float mix_back = operation & 4 ? fore.a : 0.0f;
2565 if (operation & 8)
2566 mix_back = 1.0f - mix_back;
2567 rgba blend = mix_fore * fore + mix_back * back;
2568 blend.a = std::min(blend.a, 1.0f);
2569 back = visibility * blend + (1.0f - visibility) * back;
2570 }
2571 if (next.y != y)
2572 sum = 0.0f;
2573 x = std::max(static_cast<int>(next.x), left - border);
2574 y = next.y;
2575 sum += next.delta;
2576 }
2577 }
2578
2579 // Render the polylines into the pixel buffer. It scan-converts the lines
2580 // to runs which represent changes to the signed fractional coverage when
2581 // read from left-to-right, top-to-bottom. It scans through these to
2582 // determine spans of pixels that need to be drawn, paints those pixels
2583 // according to the brush, and then blends them into the buffer according
2584 // to the current compositing settings. This is slightly more complicated
2585 // because it interleaves this with a simultaneous scan through a similar
2586 // set of runs representing the current clip mask to determine which pixels
2587 // it can composite into. Note that shadows are always drawn first.
2588 //
2589 void canvas::render_main(paint_brush const& brush)
2590 {
2591 if (forward.a * forward.d - forward.b * forward.c == 0.0f)
2592 return;
2593 render_shadow(brush);
2594 lines_to_runs(xy(0.0f, 0.0f), 0);
2595 int operation = global_composite_operation;
2596 int x = -1;
2597 int y = -1;
2598 float path_sum = 0.0f;
2599 float clip_sum = 0.0f;
2600 size_t path_index = 0;
2601 size_t clip_index = 0;
2602 while (clip_index < mask.size())
2603 {
2604 bool which = (path_index < runs.size() && runs[path_index] < mask[clip_index]);
2605 pixel_run next = which ? runs[path_index] : mask[clip_index];
2606 float coverage = std::min(fabsf(path_sum), 1.0f);
2607 float visibility = std::min(fabsf(clip_sum), 1.0f);
2608 int to = next.y == y ? next.x : x + 1;
2609 static float const threshold = 1.0f / 8160.0f;
2610 if ((coverage >= threshold || ~operation & 8) && visibility >= threshold)
2611 for (; x < to; ++x)
2612 {
2613 rgba& back = bitmap[y * size_x + x];
2614 rgba fore = coverage * global_alpha *
2615 paint_pixel(xy(static_cast<float>(x) + 0.5f, static_cast<float>(y) + 0.5f), brush);
2616 float mix_fore = operation & 1 ? back.a : 0.0f;
2617 if (operation & 2)
2618 mix_fore = 1.0f - mix_fore;
2619 float mix_back = operation & 4 ? fore.a : 0.0f;
2620 if (operation & 8)
2621 mix_back = 1.0f - mix_back;
2622 rgba blend = mix_fore * fore + mix_back * back;
2623 blend.a = std::min(blend.a, 1.0f);
2624 back = visibility * blend + (1.0f - visibility) * back;
2625 }
2626 x = next.x;
2627 if (next.y != y)
2628 {
2629 y = next.y;
2630 path_sum = 0.0f;
2631 clip_sum = 0.0f;
2632 }
2633 if (which)
2634 path_sum += runs[path_index++].delta;
2635 else
2636 clip_sum += mask[clip_index++].delta;
2637 }
2638 }
2639
2640 canvas::canvas(int width, int height)
2641 : global_composite_operation(source_over), shadow_offset_x(0.0f), shadow_offset_y(0.0f), line_cap(butt),
2642 line_join(miter), line_dash_offset(0.0f), text_align(start), text_baseline(alphabetic), size_x(width),
2643 size_y(height), global_alpha(1.0f), shadow_blur(0.0f), line_width(1.0f), miter_limit(10.0f), fill_brush(),
2644 stroke_brush(), image_brush(), face(), bitmap(new rgba[width * height]), saves(0)
2645 {
2646 affine_matrix identity = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
2647 forward = identity;
2648 inverse = identity;
2649 set_color(fill_style, 0.0f, 0.0f, 0.0f, 1.0f);
2650 set_color(stroke_style, 0.0f, 0.0f, 0.0f, 1.0f);
2651 for (unsigned short y = 0; y < size_y; ++y)
2652 {
2653 pixel_run piece_1 = {0, y, 1.0f};
2654 pixel_run piece_2 = {static_cast<unsigned short>(size_x), y, -1.0f};
2655 mask.push_back(piece_1);
2656 mask.push_back(piece_2);
2657 }
2658 }
2659
2660 canvas::canvas(int width, int height, rgba c) : canvas(width, height)
2661 {
2662 save();
2663 set_color(fill_style, c.r, c.g, c.b, c.a);
2664 fill_rectangle(0, 0, (float)width, (float)height);
2665 restore();
2666 }
2667
2669 {
2670 delete[] bitmap;
2671 while (canvas* head = saves)
2672 {
2673 saves = head->saves;
2674 head->saves = 0;
2675 delete head;
2676 }
2677 }
2678
2679 void canvas::scale(float x, float y)
2680 {
2681 transform(x, 0.0f, 0.0f, y, 0.0f, 0.0f);
2682 }
2683
2684 void canvas::rotate(float angle)
2685 {
2686 float cosine = cosf(angle);
2687 float sine = sinf(angle);
2688 transform(cosine, sine, -sine, cosine, 0.0f, 0.0f);
2689 }
2690
2691 void canvas::translate(float x, float y)
2692 {
2693 transform(1.0f, 0.0f, 0.0f, 1.0f, x, y);
2694 }
2695
2696 void canvas::transform(float a, float b, float c, float d, float e, float f)
2697 {
2698 set_transform(forward.a * a + forward.c * b, forward.b * a + forward.d * b, forward.a * c + forward.c * d,
2699 forward.b * c + forward.d * d, forward.a * e + forward.c * f + forward.e,
2700 forward.b * e + forward.d * f + forward.f);
2701 }
2702
2703 void canvas::set_transform(float a, float b, float c, float d, float e, float f)
2704 {
2705 float determinant = a * d - b * c;
2706 float scaling = determinant != 0.0f ? 1.0f / determinant : 0.0f;
2707 affine_matrix new_forward = {a, b, c, d, e, f};
2708 affine_matrix new_inverse = {
2709 scaling * d, scaling * -b, scaling * -c, scaling * a, scaling * (c * f - d * e), scaling * (b * e - a * f)};
2710 forward = new_forward;
2711 inverse = new_inverse;
2712 }
2713
2714 void canvas::set_global_alpha(float alpha)
2715 {
2716 if (0.0f <= alpha && alpha <= 1.0f)
2717 global_alpha = alpha;
2718 }
2719
2720 void canvas::set_shadow_color(float red, float green, float blue, float alpha)
2721 {
2722 shadow_color = premultiplied(linearized(clamped(rgba(red, green, blue, alpha))));
2723 }
2724
2725 void canvas::set_shadow_blur(float level)
2726 {
2727 if (0.0f <= level)
2728 shadow_blur = level;
2729 }
2730
2731 void canvas::set_line_width(float width)
2732 {
2733 if (0.0f < width)
2734 line_width = width;
2735 }
2736
2737 void canvas::set_miter_limit(float limit)
2738 {
2739 if (0.0f < limit)
2740 miter_limit = limit;
2741 }
2742
2743 void canvas::set_line_dash(float const* segments, int count)
2744 {
2745 for (int index = 0; index < count; ++index)
2746 if (segments && segments[index] < 0.0f)
2747 return;
2748 line_dash.clear();
2749 if (!segments)
2750 return;
2751 for (int index = 0; index < count; ++index)
2752 line_dash.push_back(segments[index]);
2753 if (count & 1)
2754 for (int index = 0; index < count; ++index)
2755 line_dash.push_back(segments[index]);
2756 }
2757
2758 void canvas::set_color(brush_type type, float red, float green, float blue, float alpha)
2759 {
2760 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2761 brush.type = paint_brush::color;
2762 brush.colors.clear();
2763 brush.colors.push_back(premultiplied(linearized(clamped(rgba(red, green, blue, alpha)))));
2764 }
2765
2766 void canvas::set_linear_gradient(brush_type type, float start_x, float start_y, float end_x, float end_y)
2767 {
2768 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2769 brush.type = paint_brush::linear;
2770 brush.colors.clear();
2771 brush.stops.clear();
2772 brush.hints.clear();
2773 brush.start = xy(start_x, start_y);
2774 brush.end = xy(end_x, end_y);
2775 }
2776
2778 brush_type type, float start_x, float start_y, float start_radius, float end_x, float end_y, float end_radius)
2779 {
2780 if (start_radius < 0.0f || end_radius < 0.0f)
2781 return;
2782 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2783 brush.type = paint_brush::radial;
2784 brush.colors.clear();
2785 brush.stops.clear();
2786 brush.hints.clear();
2787 brush.start = xy(start_x, start_y);
2788 brush.end = xy(end_x, end_y);
2789 brush.start_radius = start_radius;
2790 brush.end_radius = end_radius;
2791 }
2792
2793 void canvas::set_css_radial_gradient(brush_type type, float x, float y, float radius_x, float radius_y)
2794 {
2795 if (radius_x < 0.0f || radius_y < 0.0f)
2796 return;
2797 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2799 brush.colors.clear();
2800 brush.stops.clear();
2801 brush.hints.clear();
2802 brush.start = {x, y};
2803 brush.css_radius = {radius_x, radius_y};
2804 }
2805
2806 void canvas::set_conic_gradient(brush_type type, float x, float y, float angle)
2807 {
2808 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2809 brush.type = paint_brush::conic;
2810 brush.colors = {};
2811 brush.stops = {};
2812 brush.hints = {};
2813 brush.start = {x, y};
2814 brush.angle = angle;
2815 }
2816
2818 brush_type type, float offset, float red, float green, float blue, float alpha, std::optional<float> hint)
2819 {
2820 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2821 if ((brush.type != paint_brush::linear && brush.type != paint_brush::radial &&
2822 brush.type != paint_brush::css_radial && brush.type != paint_brush::conic) ||
2823 offset < 0.0f || 1.0f < offset)
2824 return;
2825 ptrdiff_t index = std::upper_bound(brush.stops.begin(), brush.stops.end(), offset) - brush.stops.begin();
2826 rgba color = linearized(clamped(rgba(red, green, blue, alpha)));
2827 brush.colors.insert(brush.colors.begin() + index, color);
2828 brush.stops.insert(brush.stops.begin() + index, offset);
2829 brush.hints.insert(brush.hints.begin() + index, hint);
2830 }
2831
2833 brush_type type, unsigned char const* image, int width, int height, int stride, repetition_style repetition)
2834 {
2835 if (!image || width <= 0 || height <= 0)
2836 return;
2837 paint_brush& brush = type == fill_style ? fill_brush : stroke_brush;
2838 brush.type = paint_brush::pattern;
2839 brush.colors.clear();
2840 for (int y = 0; y < height; ++y)
2841 for (int x = 0; x < width; ++x)
2842 {
2843 int index = y * stride + x * 4;
2844 rgba color = rgba(image[index + 0] / 255.0f, image[index + 1] / 255.0f, image[index + 2] / 255.0f,
2845 image[index + 3] / 255.0f);
2846 brush.colors.push_back(premultiplied(linearized(color)));
2847 }
2848 brush.width = width;
2849 brush.height = height;
2850 brush.repetition = repetition;
2851 }
2852
2853 void canvas::begin_path()
2854 {
2855 path.points.clear();
2856 path.subpaths.clear();
2857 }
2858
2859 void canvas::move_to(float x, float y)
2860 {
2861 if (!path.subpaths.empty() && path.subpaths.back().count == 1)
2862 {
2863 path.points.back() = forward * xy(x, y);
2864 return;
2865 }
2866 subpath_data subpath = {1, false};
2867 path.points.push_back(forward * xy(x, y));
2868 path.subpaths.push_back(subpath);
2869 }
2870
2871 void canvas::close_path()
2872 {
2873 if (path.subpaths.empty())
2874 return;
2875 xy first = path.points[path.points.size() - path.subpaths.back().count];
2876 affine_matrix saved_forward = forward;
2877 affine_matrix identity = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
2878 forward = identity;
2879 line_to(first.x, first.y);
2880 path.subpaths.back().closed = true;
2881 move_to(first.x, first.y);
2882 forward = saved_forward;
2883 }
2884
2885 void canvas::line_to(float x, float y)
2886 {
2887 if (path.subpaths.empty())
2888 {
2889 move_to(x, y);
2890 return;
2891 }
2892 xy point_1 = path.points.back();
2893 xy point_2 = forward * xy(x, y);
2894 if (dot(point_2 - point_1, point_2 - point_1) == 0.0f)
2895 return;
2896 path.points.push_back(point_1);
2897 path.points.push_back(point_2);
2898 path.points.push_back(point_2);
2899 path.subpaths.back().count += 3;
2900 }
2901
2902 void canvas::quadratic_curve_to(float control_x, float control_y, float x, float y)
2903 {
2904 if (path.subpaths.empty())
2905 move_to(control_x, control_y);
2906 xy point_1 = path.points.back();
2907 xy control = forward * xy(control_x, control_y);
2908 xy point_2 = forward * xy(x, y);
2909 xy control_1 = lerp(point_1, control, 2.0f / 3.0f);
2910 xy control_2 = lerp(point_2, control, 2.0f / 3.0f);
2911 path.points.push_back(control_1);
2912 path.points.push_back(control_2);
2913 path.points.push_back(point_2);
2914 path.subpaths.back().count += 3;
2915 }
2916
2918 float control_1_x, float control_1_y, float control_2_x, float control_2_y, float x, float y)
2919 {
2920 if (path.subpaths.empty())
2921 move_to(control_1_x, control_1_y);
2922 xy control_1 = forward * xy(control_1_x, control_1_y);
2923 xy control_2 = forward * xy(control_2_x, control_2_y);
2924 xy point_2 = forward * xy(x, y);
2925 path.points.push_back(control_1);
2926 path.points.push_back(control_2);
2927 path.points.push_back(point_2);
2928 path.subpaths.back().count += 3;
2929 }
2930
2931 void canvas::arc_to(float vertex_x, float vertex_y, float x, float y, float radius)
2932 {
2933 if (radius < 0.0f || forward.a * forward.d - forward.b * forward.c == 0.0f)
2934 return;
2935 if (path.subpaths.empty())
2936 move_to(vertex_x, vertex_y);
2937 xy point_1 = inverse * path.points.back();
2938 xy vertex = xy(vertex_x, vertex_y);
2939 xy point_2 = xy(x, y);
2940 xy edge_1 = normalized(point_1 - vertex);
2941 xy edge_2 = normalized(point_2 - vertex);
2942 float sine = fabsf(dot(perpendicular(edge_1), edge_2));
2943 static float const epsilon = 1.0e-4f;
2944 if (sine < epsilon)
2945 {
2946 line_to(vertex_x, vertex_y);
2947 return;
2948 }
2949 xy offset = radius / sine * (edge_1 + edge_2);
2950 xy center = vertex + offset;
2951 float angle_1 = direction(dot(offset, edge_1) * edge_1 - offset);
2952 float angle_2 = direction(dot(offset, edge_2) * edge_2 - offset);
2953 bool reverse = static_cast<int>(floorf((angle_2 - angle_1) / pi)) & 1;
2954 arc(center.x, center.y, radius, angle_1, angle_2, reverse);
2955 }
2956
2957 void canvas::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
2958 {
2959 if (radius < 0.0f)
2960 return;
2961 static float const tau = 2 * pi;
2962 float winding = counter_clockwise ? -1.0f : 1.0f;
2963 float from = fmodf(start_angle, tau);
2964 float span = fmodf(end_angle, tau) - from;
2965 if ((end_angle - start_angle) * winding >= tau)
2966 span = tau * winding;
2967 else if (span * winding < 0.0f)
2968 span += tau * winding;
2969 xy centered_1 = radius * xy(cosf(from), sinf(from));
2970 line_to(x + centered_1.x, y + centered_1.y);
2971 if (span == 0.0f)
2972 return;
2973 int steps = static_cast<int>(std::max(1.0f, roundf(16.0f / tau * span * winding)));
2974 float segment = span / static_cast<float>(steps);
2975 float alpha = 4.0f / 3.0f * tanf(0.25f * segment);
2976 for (int step = 0; step < steps; ++step)
2977 {
2978 float angle = from + static_cast<float>(step + 1) * segment;
2979 xy centered_2 = radius * xy(cosf(angle), sinf(angle));
2980 xy point_1 = xy(x, y) + centered_1;
2981 xy point_2 = xy(x, y) + centered_2;
2982 xy control_1 = point_1 + alpha * perpendicular(centered_1);
2983 xy control_2 = point_2 - alpha * perpendicular(centered_2);
2984 bezier_curve_to(control_1.x, control_1.y, control_2.x, control_2.y, point_2.x, point_2.y);
2985 centered_1 = centered_2;
2986 }
2987 }
2988
2989 void canvas::rectangle(float x, float y, float width, float height)
2990 {
2991 move_to(x, y);
2992 line_to(x + width, y);
2993 line_to(x + width, y + height);
2994 line_to(x, y + height);
2995 close_path();
2996 }
2997
2998 void canvas::polygon(std::vector<xy> points)
2999 {
3000 move_to(points[0].x, points[0].y);
3001 for (auto pt : points)
3002 line_to(pt.x, pt.y);
3003 close_path();
3004 }
3005
3006 void canvas::fill()
3007 {
3008 path_to_lines(false);
3010 }
3011
3012 void canvas::stroke()
3013 {
3014 path_to_lines(true);
3015 stroke_lines();
3017 }
3018
3019 void canvas::clip()
3020 {
3021 path_to_lines(false);
3022 lines_to_runs(xy(0.0f, 0.0f), 0);
3023 size_t part = runs.size();
3024 runs.insert(runs.end(), mask.begin(), mask.end());
3025 mask.clear();
3026 int y = -1;
3027 float last = 0.0f;
3028 float sum_1 = 0.0f;
3029 float sum_2 = 0.0f;
3030 size_t index_1 = 0;
3031 size_t index_2 = part;
3032 while (index_1 < part && index_2 < runs.size())
3033 {
3034 bool which = runs[index_1] < runs[index_2];
3035 pixel_run next = which ? runs[index_1] : runs[index_2];
3036 if (next.y != y)
3037 {
3038 y = next.y;
3039 last = 0.0f;
3040 sum_1 = 0.0f;
3041 sum_2 = 0.0f;
3042 }
3043 if (which)
3044 sum_1 += runs[index_1++].delta;
3045 else
3046 sum_2 += runs[index_2++].delta;
3047 float visibility = (std::min(fabsf(sum_1), 1.0f) * std::min(fabsf(sum_2), 1.0f));
3048 if (visibility == last)
3049 continue;
3050 if (!mask.empty() && mask.back().x == next.x && mask.back().y == next.y)
3051 mask.back().delta += visibility - last;
3052 else
3053 {
3054 pixel_run piece = {next.x, next.y, visibility - last};
3055 mask.push_back(piece);
3056 }
3057 last = visibility;
3058 }
3059 }
3060
3061 bool canvas::is_point_in_path(float x, float y)
3062 {
3063 path_to_lines(false);
3064 int winding = 0;
3065 size_t subpath = 0;
3066 size_t beginning = 0;
3067 size_t ending = 0;
3068 for (size_t index = 0; index < lines.points.size(); ++index)
3069 {
3070 while (index >= ending)
3071 {
3072 beginning = ending;
3073 ending += lines.subpaths[subpath++].count;
3074 }
3075 xy from = lines.points[index];
3076 xy to = lines.points[index + 1 < ending ? index + 1 : beginning];
3077 if ((from.y < y && y <= to.y) || (to.y < y && y <= from.y))
3078 {
3079 float side = dot(perpendicular(to - from), xy(x, y) - from);
3080 if (side == 0.0f)
3081 return true;
3082 winding += side > 0.0f ? 1 : -1;
3083 }
3084 else if (from.y == y && y == to.y && ((from.x <= x && x <= to.x) || (to.x <= x && x <= from.x)))
3085 return true;
3086 }
3087 return winding;
3088 }
3089
3090 void canvas::clear_rectangle(float x, float y, float width, float height)
3091 {
3093 float saved_global_alpha = global_alpha;
3094 float saved_alpha = shadow_color.a;
3095 paint_brush::types saved_type = fill_brush.type;
3097 global_alpha = 1.0f;
3098 shadow_color.a = 0.0f;
3100 fill_rectangle(x, y, width, height);
3101 fill_brush.type = saved_type;
3102 shadow_color.a = saved_alpha;
3103 global_alpha = saved_global_alpha;
3104 global_composite_operation = saved_operation;
3105 }
3106
3107 void canvas::fill_rectangle(float x, float y, float width, float height)
3108 {
3109 if (width == 0.0f || height == 0.0f)
3110 return;
3111 lines.points.clear();
3112 lines.subpaths.clear();
3113 lines.points.push_back(forward * xy(x, y));
3114 lines.points.push_back(forward * xy(x + width, y));
3115 lines.points.push_back(forward * xy(x + width, y + height));
3116 lines.points.push_back(forward * xy(x, y + height));
3117 subpath_data entry = {4, true};
3118 lines.subpaths.push_back(entry);
3120 }
3121
3122 void canvas::stroke_rectangle(float x, float y, float width, float height)
3123 {
3124 if (width == 0.0f && height == 0.0f)
3125 return;
3126 lines.points.clear();
3127 lines.subpaths.clear();
3128 if (width == 0.0f || height == 0.0f)
3129 {
3130 lines.points.push_back(forward * xy(x, y));
3131 lines.points.push_back(forward * xy(x + width, y + height));
3132 subpath_data entry = {2, false};
3133 lines.subpaths.push_back(entry);
3134 }
3135 else
3136 {
3137 lines.points.push_back(forward * xy(x, y));
3138 lines.points.push_back(forward * xy(x + width, y));
3139 lines.points.push_back(forward * xy(x + width, y + height));
3140 lines.points.push_back(forward * xy(x, y + height));
3141 lines.points.push_back(forward * xy(x, y));
3142 subpath_data entry = {5, true};
3143 lines.subpaths.push_back(entry);
3144 }
3145 stroke_lines();
3147 }
3148
3149 bool canvas::set_font(unsigned char const* font, int bytes, float size)
3150 {
3151 if (font && bytes)
3152 {
3153 face.data.clear();
3154 face.cmap = 0;
3155 face.glyf = 0;
3156 face.head = 0;
3157 face.hhea = 0;
3158 face.hmtx = 0;
3159 face.loca = 0;
3160 face.maxp = 0;
3161 face.os_2 = 0;
3162 if (bytes < 6)
3163 return false;
3164 int version = (font[0] << 24 | font[1] << 16 | font[2] << 8 | font[3] << 0);
3165 int tables = font[4] << 8 | font[5];
3166 if ((version != 0x00010000 && version != 0x74727565) || bytes < tables * 16 + 12)
3167 return false;
3168 face.data.insert(face.data.end(), font, font + tables * 16 + 12);
3169 for (int index = 0; index < tables; ++index)
3170 {
3171 int tag = signed_32(face.data, index * 16 + 12);
3172 int offset = signed_32(face.data, index * 16 + 20);
3173 int span = signed_32(face.data, index * 16 + 24);
3174 if (bytes < offset + span)
3175 {
3176 face.data.clear();
3177 return false;
3178 }
3179 int place = static_cast<int>(face.data.size());
3180 if (tag == 0x636d6170)
3181 face.cmap = place;
3182 else if (tag == 0x676c7966)
3183 face.glyf = place;
3184 else if (tag == 0x68656164)
3185 face.head = place;
3186 else if (tag == 0x68686561)
3187 face.hhea = place;
3188 else if (tag == 0x686d7478)
3189 face.hmtx = place;
3190 else if (tag == 0x6c6f6361)
3191 face.loca = place;
3192 else if (tag == 0x6d617870)
3193 face.maxp = place;
3194 else if (tag == 0x4f532f32)
3195 face.os_2 = place;
3196 else
3197 continue;
3198 face.data.insert(face.data.end(), font + offset, font + offset + span);
3199 }
3200 if (!face.cmap || !face.glyf || !face.head || !face.hhea || !face.hmtx || !face.loca || !face.maxp ||
3201 !face.os_2)
3202 {
3203 face.data.clear();
3204 return false;
3205 }
3206 }
3207 if (face.data.empty())
3208 return false;
3209 int units_per_em = unsigned_16(face.data, face.head + 18);
3210 face.scale = size / static_cast<float>(units_per_em);
3211 return true;
3212 }
3213
3214 void canvas::fill_text(char const* text, float x, float y, float maximum_width)
3215 {
3216 text_to_lines(text, xy(x, y), maximum_width, false);
3218 }
3219
3220 void canvas::stroke_text(char const* text, float x, float y, float maximum_width)
3221 {
3222 text_to_lines(text, xy(x, y), maximum_width, true);
3223 stroke_lines();
3225 }
3226
3227 float canvas::measure_text(char const* text)
3228 {
3229 if (face.data.empty() || !text)
3230 return 0.0f;
3231 int hmetrics = unsigned_16(face.data, face.hhea + 34);
3232 int width = 0;
3233 for (int index = 0; text[index];)
3234 {
3235 int glyph = character_to_glyph(text, index);
3236 int entry = std::min(glyph, hmetrics - 1);
3237 width += unsigned_16(face.data, face.hmtx + entry * 4);
3238 }
3239 return static_cast<float>(width) * face.scale;
3240 }
3241
3242 void canvas::draw_image(unsigned char const* image,
3243 int width,
3244 int height,
3245 int stride,
3246 float x,
3247 float y,
3248 float to_width,
3249 float to_height)
3250 {
3251 if (!image || width <= 0 || height <= 0 || to_width == 0.0f || to_height == 0.0f)
3252 return;
3253 std::swap(fill_brush, image_brush);
3255 std::swap(fill_brush, image_brush);
3256 lines.points.clear();
3257 lines.subpaths.clear();
3258 lines.points.push_back(forward * xy(x, y));
3259 lines.points.push_back(forward * xy(x + to_width, y));
3260 lines.points.push_back(forward * xy(x + to_width, y + to_height));
3261 lines.points.push_back(forward * xy(x, y + to_height));
3262 subpath_data entry = {4, true};
3263 lines.subpaths.push_back(entry);
3264 affine_matrix saved_forward = forward;
3265 affine_matrix saved_inverse = inverse;
3266 translate(x + std::min(0.0f, to_width), y + std::min(0.0f, to_height));
3267 scale(fabsf(to_width) / static_cast<float>(width), fabsf(to_height) / static_cast<float>(height));
3269 forward = saved_forward;
3270 inverse = saved_inverse;
3271 }
3272
3273 void canvas::get_image_data(unsigned char* image, int width, int height, int stride, int x, int y)
3274 {
3275 if (!image)
3276 return;
3277 static float const bayer[][4] = {{0.03125f, 0.53125f, 0.15625f, 0.65625f},
3278 {0.78125f, 0.28125f, 0.90625f, 0.40625f},
3279 {0.21875f, 0.71875f, 0.09375f, 0.59375f},
3280 {0.96875f, 0.46875f, 0.84375f, 0.34375f}};
3281 for (int image_y = 0; image_y < height; ++image_y)
3282 for (int image_x = 0; image_x < width; ++image_x)
3283 {
3284 int index = image_y * stride + image_x * 4;
3285 int canvas_x = x + image_x;
3286 int canvas_y = y + image_y;
3287 rgba color = rgba(0.0f, 0.0f, 0.0f, 0.0f);
3288 if (0 <= canvas_x && canvas_x < size_x && 0 <= canvas_y && canvas_y < size_y)
3289 color = bitmap[canvas_y * size_x + canvas_x];
3290 float threshold = bayer[canvas_y & 3][canvas_x & 3];
3291 color = rgba(threshold, threshold, threshold, threshold) +
3292 255.0f * delinearized(clamped(unpremultiplied(color)));
3293 image[index + 0] = static_cast<unsigned char>(color.r);
3294 image[index + 1] = static_cast<unsigned char>(color.g);
3295 image[index + 2] = static_cast<unsigned char>(color.b);
3296 image[index + 3] = static_cast<unsigned char>(color.a);
3297 }
3298 }
3299
3300 void canvas::put_image_data(unsigned char const* image, int width, int height, int stride, int x, int y)
3301 {
3302 if (!image)
3303 return;
3304 for (int image_y = 0; image_y < height; ++image_y)
3305 for (int image_x = 0; image_x < width; ++image_x)
3306 {
3307 int index = image_y * stride + image_x * 4;
3308 int canvas_x = x + image_x;
3309 int canvas_y = y + image_y;
3310 if (canvas_x < 0 || size_x <= canvas_x || canvas_y < 0 || size_y <= canvas_y)
3311 continue;
3312 rgba color = rgba(image[index + 0] / 255.0f, image[index + 1] / 255.0f, image[index + 2] / 255.0f,
3313 image[index + 3] / 255.0f);
3314 bitmap[canvas_y * size_x + canvas_x] = premultiplied(linearized(color));
3315 }
3316 }
3317
3318 void canvas::save()
3319 {
3320 canvas* state = new canvas(0, 0);
3321 state->global_composite_operation = global_composite_operation;
3322 state->shadow_offset_x = shadow_offset_x;
3323 state->shadow_offset_y = shadow_offset_y;
3324 state->line_cap = line_cap;
3325 state->line_join = line_join;
3326 state->line_dash_offset = line_dash_offset;
3327 state->text_align = text_align;
3328 state->text_baseline = text_baseline;
3329 state->forward = forward;
3330 state->inverse = inverse;
3331 state->global_alpha = global_alpha;
3332 state->shadow_color = shadow_color;
3333 state->shadow_blur = shadow_blur;
3334 state->line_width = line_width;
3335 state->miter_limit = miter_limit;
3336 state->line_dash = line_dash;
3337 state->fill_brush = fill_brush;
3338 state->stroke_brush = stroke_brush;
3339 state->mask = mask;
3340 state->face = face;
3341 state->saves = saves;
3342 saves = state;
3343 }
3344
3345 void canvas::restore()
3346 {
3347 if (!saves)
3348 return;
3349 canvas* state = saves;
3351 shadow_offset_x = state->shadow_offset_x;
3352 shadow_offset_y = state->shadow_offset_y;
3353 line_cap = state->line_cap;
3354 line_join = state->line_join;
3355 line_dash_offset = state->line_dash_offset;
3356 text_align = state->text_align;
3357 text_baseline = state->text_baseline;
3358 forward = state->forward;
3359 inverse = state->inverse;
3360 global_alpha = state->global_alpha;
3361 shadow_color = state->shadow_color;
3362 shadow_blur = state->shadow_blur;
3363 line_width = state->line_width;
3364 miter_limit = state->miter_limit;
3365 line_dash = state->line_dash;
3366 fill_brush = state->fill_brush;
3367 stroke_brush = state->stroke_brush;
3368 mask = state->mask;
3369 face = state->face;
3370 saves = state->saves;
3371 state->saves = 0;
3372 delete state;
3373 }
3374
3375} // namespace canvas_ity
3376
3377#endif // CANVAS_ITY_IMPLEMENTATION
Definition format.h:4610
Definition canvas_ity.hpp:276
void rectangle(float x, float y, float width, float height)
Add a closed subpath in the shape of a rectangle.
void set_global_alpha(float alpha)
Set the degree of opacity applied to all drawing operations.
void put_image_data(unsigned char const *image, int width, int height, int stride, int x, int y)
Replace a rectangle of pixels on the canvas with an image.
void stroke_text(char const *text, float x, float y, float maximum_width=1.0e30f)
Draw a line of text by stroking its outline.
void render_shadow(paint_brush const &)
paint_brush image_brush
Definition canvas_ity.hpp:1181
void get_image_data(unsigned char *image, int width, int height, int stride, int x, int y)
Fetch a rectangle of pixels from the canvas to an image.
void set_pattern(brush_type type, unsigned char const *image, int width, int height, int stride, repetition_style repetition)
Set filling or stroking to draw with an image pattern.
bool set_font(unsigned char const *font, int bytes, float size)
Set the font to use for text drawing.
canvas * saves
Definition canvas_ity.hpp:1189
void stroke_rectangle(float x, float y, float width, float height)
Stroke a rectangular area.
canvas()
Definition canvas_ity.hpp:295
void set_shadow_blur(float level)
Set the level of Gaussian blurring on the shadow.
void move_to(float x, float y)
Create a new subpath.
float miter_limit
Definition canvas_ity.hpp:1177
cap_style line_cap
Cap style for the ends of open subpaths and dash segments.
Definition canvas_ity.hpp:481
void set_miter_limit(float limit)
Set the limit on maximum pointiness allowed for miter joins.
std::vector< float > line_dash
Definition canvas_ity.hpp:1178
void clip()
Restrict the clip region by the current path.
baseline_style text_baseline
Vertical position of the text relative to the anchor point.
Definition canvas_ity.hpp:968
rgba paint_pixel(xy, paint_brush const &)
void rotate(float angle)
Rotate the current transform.
void set_line_width(float width)
Set the width of the lines when stroking.
void set_css_radial_gradient(brush_type type, float x, float y, float radius_x, float radius_y)
void polygon(std::vector< xy > points)
void draw_image(unsigned char const *image, int width, int height, int stride, float x, float y, float to_width, float to_height)
Draw an image onto the canvas.
void set_shadow_color(float red, float green, float blue, float alpha)
Set the color and opacity of the shadow.
void text_to_lines(char const *, xy, float, bool)
void get_font_metrics(int &ascent, int &descent, int &height, int &x_height)
pixel_runs runs
Definition canvas_ity.hpp:1185
void save()
Save the current state as though to a stack.
void add_bezier(xy, xy, xy, xy, float)
void add_runs(xy, xy)
void bezier_curve_to(float control_1_x, float control_1_y, float control_2_x, float control_2_y, float x, float y)
Extend the current subpath with a cubic Bezier curve.
float global_alpha
Definition canvas_ity.hpp:1172
void scale(float x, float y)
Scale the current transform.
void arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise=false)
Extend the current subpath with an arc between two angles.
align_style text_align
Horizontal position of the text relative to the anchor point.
Definition canvas_ity.hpp:955
void restore()
Restore a previously saved state as though from a stack.
void add_glyph(int, float)
void set_line_dash(float const *segments, int count)
Set or clear the line dash pattern.
void set_conic_gradient(brush_type type, float x, float y, float angle)
void render_main(paint_brush const &)
canvas(int width, int height, rgba color)
paint_brush fill_brush
Definition canvas_ity.hpp:1179
float shadow_blur
Definition canvas_ity.hpp:1174
~canvas()
Destroy the canvas and release all associated memory.
void lines_to_runs(xy, int)
paint_brush stroke_brush
Definition canvas_ity.hpp:1180
float line_width
Definition canvas_ity.hpp:1176
void add_color_stop(brush_type type, float offset, float red, float green, float blue, float alpha, std::optional< float > hint={})
Add a color stop to a linear or radial gradient.
join_style line_join
Join style for connecting lines within the paths.
Definition canvas_ity.hpp:492
void transform(float a, float b, float c, float d, float e, float f)
Add an arbitrary transform to the current transform.
void fill_text(char const *text, float x, float y, float maximum_width=1.0e30f)
Draw a line of text by filling its outline.
void begin_path()
Reset the current path.
float line_dash_offset
Offset where each subpath starts the dash pattern.
Definition canvas_ity.hpp:516
void fill_rectangle(float x, float y, float width, float height)
Fill a rectangular area.
int size_x
Definition canvas_ity.hpp:1168
void add_half_stroke(size_t, size_t, bool)
void close_path()
Close the current subpath.
std::vector< float > shadow
Definition canvas_ity.hpp:1175
void translate(float x, float y)
Translate the current transform.
void add_tessellation(xy, xy, xy, xy, float, int)
void set_color(brush_type type, rgba c)
Definition canvas_ity.hpp:554
void set_color(brush_type type, float red, float green, float blue, float alpha)
Set filling or stroking to use a constant color and opacity.
affine_matrix forward
Definition canvas_ity.hpp:1170
int character_to_glyph(char const *, int &)
bool is_point_in_path(float x, float y)
Tests whether a point is in or on the current path.
void line_to(float x, float y)
Extend the current subpath with a straight line.
int height()
Definition canvas_ity.hpp:1143
float measure_text(char const *text)
Measure the width in pixels of a line of text.
rgba shadow_color
Definition canvas_ity.hpp:1173
int width()
Definition canvas_ity.hpp:1139
void set_radial_gradient(brush_type type, float start_x, float start_y, float start_radius, float end_x, float end_y, float end_radius)
Set filling or stroking to use a radial gradient.
canvas(canvas const &)
void set_linear_gradient(brush_type type, float start_x, float start_y, float end_x, float end_y)
Set filling or stroking to use a linear gradient.
void arc_to(float vertex_x, float vertex_y, float x, float y, float radius)
Extend the current subpath with an arc tangent to two lines.
affine_matrix inverse
Definition canvas_ity.hpp:1171
int size_y
Definition canvas_ity.hpp:1169
font_face face
Definition canvas_ity.hpp:1187
void path_to_lines(bool)
void set_transform(float a, float b, float c, float d, float e, float f)
Replace the current transform.
void quadratic_curve_to(float control_x, float control_y, float x, float y)
Extend the current subpath with a quadratic Bezier curve.
canvas & operator=(canvas const &)
bezier_path path
Definition canvas_ity.hpp:1182
float shadow_offset_y
Vertical offset of the shadow in pixels.
Definition canvas_ity.hpp:447
composite_operation global_composite_operation
Compositing operation for blending new drawing and old pixels.
Definition canvas_ity.hpp:413
void stroke()
Draw the edges of the current path using the stroke style.
void fill()
Draw the interior of the current path using the fill style.
canvas(int width, int height)
Construct a new canvas.
pixel_runs mask
Definition canvas_ity.hpp:1186
line_path scratch
Definition canvas_ity.hpp:1184
rgba * bitmap
Definition canvas_ity.hpp:1188
line_path lines
Definition canvas_ity.hpp:1183
float shadow_offset_x
Horizontal offset of the shadow in pixels.
Definition canvas_ity.hpp:440
void clear_rectangle(float x, float y, float width, float height)
Clear a rectangular area back to transparent black.
Definition core.h:1598
std::basic_string< Char > format(const text_style &ts, const S &format_str, const Args &... args)
Definition color.h:646
constexpr auto count() -> size_t
Definition core.h:1538
type
Definition core.h:681
#define offset(member)
Definition css_properties.cpp:5
FMT_CONSTEXPR fp operator*(fp x, fp y)
Definition format.h:1766
Definition Bitmap.h:5
brush_type
Definition canvas_ity.hpp:180
@ fill_style
Definition canvas_ity.hpp:181
@ stroke_style
Definition canvas_ity.hpp:182
baseline_style
Definition canvas_ity.hpp:200
@ middle
Definition canvas_ity.hpp:203
@ alphabetic
Definition canvas_ity.hpp:201
@ hanging
Definition canvas_ity.hpp:205
@ ideographic
Definition canvas_ity.hpp:206
@ top
Definition canvas_ity.hpp:202
@ bottom
Definition canvas_ity.hpp:204
align_style
Definition canvas_ity.hpp:192
@ leftward
Definition canvas_ity.hpp:193
@ rightward
Definition canvas_ity.hpp:194
@ center
Definition canvas_ity.hpp:195
@ start
Definition canvas_ity.hpp:196
@ ending
Definition canvas_ity.hpp:197
composite_operation
Definition canvas_ity.hpp:148
@ exclusive_or
Definition canvas_ity.hpp:165
@ source_out
Definition canvas_ity.hpp:151
@ lighter
Definition canvas_ity.hpp:160
@ source_over
Definition canvas_ity.hpp:164
@ destination_out
Definition canvas_ity.hpp:162
@ source_atop
Definition canvas_ity.hpp:163
@ source_copy
Definition canvas_ity.hpp:150
@ destination_atop
Definition canvas_ity.hpp:153
@ source_in
Definition canvas_ity.hpp:149
@ destination_over
Definition canvas_ity.hpp:161
@ destination_in
Definition canvas_ity.hpp:152
cap_style
Definition canvas_ity.hpp:168
@ square
Definition canvas_ity.hpp:170
@ circle
Definition canvas_ity.hpp:171
@ butt
Definition canvas_ity.hpp:169
join_style
Definition canvas_ity.hpp:174
@ miter
Definition canvas_ity.hpp:175
@ bevel
Definition canvas_ity.hpp:176
@ rounded
Definition canvas_ity.hpp:177
repetition_style
Definition canvas_ity.hpp:185
@ repeat_y
Definition canvas_ity.hpp:188
@ repeat
Definition canvas_ity.hpp:186
@ repeat_x
Definition canvas_ity.hpp:187
@ no_repeat
Definition canvas_ity.hpp:189
std::vector< pixel_run > pixel_runs
Definition canvas_ity.hpp:273
uint32_t divisor
Definition format-inl.h:268
const T & first(const T &value, const Tail &...)
Definition compile.h:179
const float pi
Definition gradient.cpp:350
bool operator<(const css_selector &v1, const css_selector &v2)
Definition css_selector.h:237
const T & at(const vector< T > &vec, int index)
Definition html.h:50
encoding
Definition encodings.h:9
vector< T > & operator+=(vector< T > &vec, const vector< T > &x)
Definition html.h:88
visibility
Definition types.h:624
bool end(const css_token_vector &tokens, int index)
Definition gradient.cpp:78
text_align
Definition types.h:572
Definition core.h:2689
SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT
Definition os-inl.h:76
void log(source_loc source, level::level_enum lvl, format_string_t< Args... > fmt, Args &&... args)
Definition spdlog.h:145
Definition canvas_ity.hpp:223
float a
Definition canvas_ity.hpp:224
float b
Definition canvas_ity.hpp:224
float e
Definition canvas_ity.hpp:224
float f
Definition canvas_ity.hpp:224
float d
Definition canvas_ity.hpp:224
float c
Definition canvas_ity.hpp:224
Definition canvas_ity.hpp:259
std::vector< xy > points
Definition canvas_ity.hpp:260
std::vector< subpath_data > subpaths
Definition canvas_ity.hpp:261
Definition canvas_ity.hpp:248
int maxp
Definition canvas_ity.hpp:250
std::vector< unsigned char > data
Definition canvas_ity.hpp:249
int hhea
Definition canvas_ity.hpp:250
int loca
Definition canvas_ity.hpp:250
int os_2
Definition canvas_ity.hpp:250
int hmtx
Definition canvas_ity.hpp:250
int cmap
Definition canvas_ity.hpp:250
int glyf
Definition canvas_ity.hpp:250
int head
Definition canvas_ity.hpp:250
float scale
Definition canvas_ity.hpp:251
Definition canvas_ity.hpp:264
std::vector< xy > points
Definition canvas_ity.hpp:265
std::vector< subpath_data > subpaths
Definition canvas_ity.hpp:266
Definition canvas_ity.hpp:227
xy end
Definition canvas_ity.hpp:240
xy start
Definition canvas_ity.hpp:240
int height
Definition canvas_ity.hpp:244
xy css_radius
Definition canvas_ity.hpp:242
float angle
Definition canvas_ity.hpp:243
float end_radius
Definition canvas_ity.hpp:241
int width
Definition canvas_ity.hpp:244
float start_radius
Definition canvas_ity.hpp:241
repetition_style repetition
Definition canvas_ity.hpp:245
std::vector< float > stops
Definition canvas_ity.hpp:238
std::vector< rgba > colors
Definition canvas_ity.hpp:237
types
Definition canvas_ity.hpp:229
@ radial
Definition canvas_ity.hpp:233
@ pattern
Definition canvas_ity.hpp:231
@ conic
Definition canvas_ity.hpp:235
@ linear
Definition canvas_ity.hpp:232
@ css_radial
Definition canvas_ity.hpp:234
@ color
Definition canvas_ity.hpp:230
std::vector< std::optional< float > > hints
Definition canvas_ity.hpp:239
enum canvas_ity::paint_brush::types type
Definition canvas_ity.hpp:269
float delta
Definition canvas_ity.hpp:271
unsigned short x
Definition canvas_ity.hpp:270
unsigned short y
Definition canvas_ity.hpp:270
Definition canvas_ity.hpp:217
float r
Definition canvas_ity.hpp:218
rgba(float, float, float, float)
float a
Definition canvas_ity.hpp:218
float g
Definition canvas_ity.hpp:218
float b
Definition canvas_ity.hpp:218
Definition canvas_ity.hpp:254
bool closed
Definition canvas_ity.hpp:256
size_t count
Definition canvas_ity.hpp:255
Definition canvas_ity.hpp:211
xy(float, float)
float x
Definition canvas_ity.hpp:212
float y
Definition canvas_ity.hpp:212
Definition Bitmap.h:10
byte a
Definition Bitmap.h:11
byte b
Definition Bitmap.h:11
byte r
Definition Bitmap.h:11
byte g
Definition Bitmap.h:11
Definition format.h:1901
b
Definition tag_strings.h:61
a
Definition tag_strings.h:43
image
Definition tag_strings.h:74
area
Definition tag_strings.h:87
head
Definition tag_strings.h:5
span
Definition tag_strings.h:69
annotation font
Definition tag_strings.h:148
canvas
Definition tag_strings.h:85
annotation table
Definition tag_strings.h:100