Skip to main content

Point

Struct Point 

Source
pub struct Point {
    pub x: f64,
    pub y: f64,
}
Expand description

A 2D point.

This type represents a point in 2D space. It has the same layout as Vec2, but its meaning is different: Vec2 represents a change in location (for example velocity).

In general, kurbo overloads math operators where it makes sense, for example implementing Affine * Point as the point under the affine transformation. However Point + Point and f64 * Point are not implemented, because the operations do not make geometric sense. If you need to apply these operations, then 1) check what you’re doing makes geometric sense, then 2) use Point::to_vec2 to convert the point to a Vec2.

Fields§

§x: f64

The x coordinate.

§y: f64

The y coordinate.

Implementations§

Source§

impl Point

Source

pub const ZERO: Point

The point (0, 0).

Source

pub const ORIGIN: Point

The point at the origin; (0, 0).

Source

pub const fn new(x: f64, y: f64) -> Point

Create a new Point with the provided x and y coordinates.

Examples found in repository?
examples/shapes_demo.rs (lines 27-30)
24fn cell_center(idx: usize) -> Point {
25    let col = idx % GRID_COLS;
26    let row = idx / GRID_COLS;
27    Point::new(
28        GRID_LEFT + CELL_W * (col as f64 + 0.5),
29        GRID_TOP + CELL_H * (row as f64 + 0.5),
30    )
31}
32
33fn draw_shape_centered(
34    scene: &mut impl SceneBuilder,
35    shape: &Shape,
36    center: Point,
37    size: f64,
38    brush: &Brush,
39    stroke_world_width: f64,
40) {
41    let xform = Affine::translate(center.to_vec2()) * Affine::scale(size);
42    let (paths, style) = match shape.kind() {
43        ShapeKind::Paths { paths, style } => (paths, style),
44        ShapeKind::Glyph { .. } => return,
45    };
46    match style {
47        ShapeStyle::Fill => {
48            for sub in paths {
49                scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
50            }
51        }
52        ShapeStyle::Stroke => {
53            let stroke = Stroke::new(stroke_world_width / size)
54                .with_caps(Cap::Round)
55                .with_join(Join::Round);
56            for sub in paths {
57                scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
58            }
59        }
60    }
61}
62
63fn draw_shape_attached(
64    scene: &mut impl SceneBuilder,
65    shape: &Shape,
66    placement: Point,
67    direction: Vec2,
68    size: f64,
69    brush: &Brush,
70    stroke_world_width: f64,
71) {
72    let perp = Vec2::new(-direction.y, direction.x);
73    let a = shape.anchor();
74    let anchor_world = direction * (a.x * size) + perp * (a.y * size);
75    let origin = placement - anchor_world;
76    let xform = Affine::translate(origin.to_vec2())
77        * Affine::rotate(direction.atan2())
78        * Affine::scale(size);
79    let (paths, style) = match shape.kind() {
80        ShapeKind::Paths { paths, style } => (paths, style),
81        ShapeKind::Glyph { .. } => return,
82    };
83    match style {
84        ShapeStyle::Fill => {
85            for sub in paths {
86                scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
87            }
88        }
89        ShapeStyle::Stroke => {
90            let stroke = Stroke::new(stroke_world_width / size)
91                .with_caps(Cap::Round)
92                .with_join(Join::Round);
93            for sub in paths {
94                scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
95            }
96        }
97    }
98}
99
100fn main() {
101    let mut renderer = VelloRenderer::new().expect("vello renderer init");
102    let registry = ShapeRegistry::with_builtins();
103
104    let glyph_brush: Brush = rgb8(60, 130, 220).into();
105    let chrome_brush: Brush = rgb8(80, 84, 96).into();
106    let line_brush: Brush = rgb8(190, 195, 205).into();
107
108    let glyph_world_stroke = 2.0;
109    let line_stroke = Stroke::new(2.5).with_caps(Cap::Butt);
110    let cell_stroke = Stroke::new(1.0);
111
112    {
113        let scene = renderer.scene();
114
115        for (i, name) in builtin::NAMES.iter().enumerate() {
116            let center = cell_center(i);
117            let cell = Rect::new(
118                center.x - CELL_W * 0.5 + 2.0,
119                center.y - CELL_H * 0.5 + 2.0,
120                center.x + CELL_W * 0.5 - 2.0,
121                center.y + CELL_H * 0.5 - 2.0,
122            )
123            .to_path(0.1);
124            scene.stroke(
125                &cell_stroke,
126                Affine::IDENTITY,
127                &chrome_brush,
128                None,
129                &cell,
130                PickId::Skip,
131            );
132
133            let shape = registry.get(name).expect("registered");
134            draw_shape_centered(scene, shape, center, 28.0, &glyph_brush, glyph_world_stroke);
135        }
136
137        let demos: &[(&str, f64)] = &[
138            ("arrow-closed", 18.0),
139            ("arrow-stealth", 22.0),
140            ("arrow-open", 18.0),
141            ("arrow-feather", 28.0),
142            ("arrow-dot", 14.0),
143        ];
144        let demo_top = GRID_TOP + (GRID_ROWS as f64) * CELL_H + 25.0;
145        let demo_spacing = 28.0;
146        let x_start = 60.0;
147        let x_end = (W as f64) - 60.0;
148
149        for (i, &(name, size)) in demos.iter().enumerate() {
150            let y = demo_top + (i as f64) * demo_spacing;
151            let start = Point::new(x_start, y);
152            let end = Point::new(x_end, y);
153
154            let direction = (end - start).normalize();
155            let shape = registry.get(name).expect("registered");
156
157            let mut line = hephaestus::Path::new();
158            line.move_to(start);
159            line.line_to(end);
160            scene.stroke(
161                &line_stroke,
162                Affine::IDENTITY,
163                &line_brush,
164                None,
165                &line,
166                PickId::Skip,
167            );
168
169            draw_shape_attached(
170                scene,
171                shape,
172                end,
173                direction,
174                size,
175                &glyph_brush,
176                glyph_world_stroke,
177            );
178        }
179    }
180
181    let mut pixels = vec![0u8; (W * H * 4) as usize];
182    let bg: Color = rgb8(20, 22, 28);
183    renderer
184        .render_to_buffer(W, H, bg, &mut pixels)
185        .expect("render");
186
187    let path = std::env::current_dir()
188        .unwrap()
189        .join("examples/shapes_demo.png");
190    hephaestus::image::write_png(&path, W, H, &pixels).expect("write png");
191    println!("wrote {}", path.display());
192}
More examples
Hide additional examples
examples/hello.rs (line 35)
14fn main() {
15    let mut renderer = VelloRenderer::new().expect("vello renderer init");
16    let (w, h) = (512u32, 512u32);
17
18    {
19        let scene = renderer.scene();
20
21        // 1. Filled rectangle (solid brush)
22        let rect = Rect::new(40.0, 40.0, 240.0, 200.0).to_path(0.1);
23        let solid: Brush = rgb8(60, 120, 200).into();
24        scene.fill(
25            FillRule::NonZero,
26            Affine::IDENTITY,
27            &solid,
28            None,
29            &rect,
30            PickId::Skip,
31        );
32
33        // 2. Stroked open polyline with round caps/joins
34        let mut poly = Path::new();
35        poly.move_to(Point::new(60.0, 300.0));
36        poly.line_to(Point::new(140.0, 360.0));
37        poly.line_to(Point::new(200.0, 280.0));
38        poly.line_to(Point::new(260.0, 380.0));
39        let stroke = Stroke::new(8.0)
40            .with_caps(Cap::Round)
41            .with_join(Join::Round);
42        let line_brush: Brush = rgb8(220, 220, 220).into();
43        scene.stroke(
44            &stroke,
45            Affine::IDENTITY,
46            &line_brush,
47            None,
48            &poly,
49            PickId::Skip,
50        );
51
52        // 3. Gradient-filled circle
53        let circle = kurbo::Circle::new(Point::new(380.0, 150.0), 90.0).to_path(0.1);
54        let gradient = Gradient::new_linear(Point::new(290.0, 60.0), Point::new(470.0, 240.0))
55            .with_stops([rgb(0.95, 0.55, 0.15), rgb(0.15, 0.05, 0.45)].as_slice());
56        let grad_brush: Brush = gradient.into();
57        scene.fill(
58            FillRule::NonZero,
59            Affine::IDENTITY,
60            &grad_brush,
61            None,
62            &circle,
63            PickId::Skip,
64        );
65
66        // 4. Multiply layer with a translucent square overlapping the rect
67        let layer_clip = Rect::new(0.0, 0.0, w as f64, h as f64).to_path(0.1);
68        scene.push_layer(
69            BlendMode::new(Mix::Multiply, Compose::SrcOver),
70            1.0,
71            Affine::IDENTITY,
72            &layer_clip,
73        );
74        let overlap = Rect::new(160.0, 120.0, 360.0, 320.0).to_path(0.1);
75        let overlap_brush: Brush = rgba(1.0, 0.85, 0.2, 0.7).into();
76        scene.fill(
77            FillRule::NonZero,
78            Affine::IDENTITY,
79            &overlap_brush,
80            None,
81            &overlap,
82            PickId::Skip,
83        );
84        scene.pop_layer();
85    }
86
87    let mut pixels = vec![0u8; (w * h * 4) as usize];
88    let bg: Color = rgb8(20, 22, 28);
89    renderer
90        .render_to_buffer(w, h, bg, &mut pixels)
91        .expect("render");
92
93    let path = std::env::current_dir().unwrap().join("examples/hello.png");
94    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
95    println!("wrote {}", path.display());
96}
examples/hello_hybrid.rs (line 37)
16fn main() {
17    let mut renderer = HybridRenderer::new().expect("hybrid renderer init");
18    let (w, h) = (512u32, 512u32);
19
20    {
21        let scene = renderer.scene();
22
23        // 1. Filled rectangle (solid brush)
24        let rect = Rect::new(40.0, 40.0, 240.0, 200.0).to_path(0.1);
25        let solid: Brush = rgb8(60, 120, 200).into();
26        scene.fill(
27            FillRule::NonZero,
28            Affine::IDENTITY,
29            &solid,
30            None,
31            &rect,
32            PickId::Skip,
33        );
34
35        // 2. Stroked open polyline with round caps/joins
36        let mut poly = Path::new();
37        poly.move_to(Point::new(60.0, 300.0));
38        poly.line_to(Point::new(140.0, 360.0));
39        poly.line_to(Point::new(200.0, 280.0));
40        poly.line_to(Point::new(260.0, 380.0));
41        let stroke = Stroke::new(8.0)
42            .with_caps(Cap::Round)
43            .with_join(Join::Round);
44        let line_brush: Brush = rgb8(220, 220, 220).into();
45        scene.stroke(
46            &stroke,
47            Affine::IDENTITY,
48            &line_brush,
49            None,
50            &poly,
51            PickId::Skip,
52        );
53
54        // 3. Gradient-filled circle
55        let circle = kurbo::Circle::new(Point::new(380.0, 150.0), 90.0).to_path(0.1);
56        let gradient = Gradient::new_linear(Point::new(290.0, 60.0), Point::new(470.0, 240.0))
57            .with_stops([rgb(0.95, 0.55, 0.15), rgb(0.15, 0.05, 0.45)].as_slice());
58        let grad_brush: Brush = gradient.into();
59        scene.fill(
60            FillRule::NonZero,
61            Affine::IDENTITY,
62            &grad_brush,
63            None,
64            &circle,
65            PickId::Skip,
66        );
67
68        // 4. Multiply layer with a translucent square overlapping the rect
69        let layer_clip = Rect::new(0.0, 0.0, w as f64, h as f64).to_path(0.1);
70        scene.push_layer(
71            BlendMode::new(Mix::Multiply, Compose::SrcOver),
72            1.0,
73            Affine::IDENTITY,
74            &layer_clip,
75        );
76        let overlap = Rect::new(160.0, 120.0, 360.0, 320.0).to_path(0.1);
77        let overlap_brush: Brush = rgba(1.0, 0.85, 0.2, 0.7).into();
78        scene.fill(
79            FillRule::NonZero,
80            Affine::IDENTITY,
81            &overlap_brush,
82            None,
83            &overlap,
84            PickId::Skip,
85        );
86        scene.pop_layer();
87    }
88
89    let mut pixels = vec![0u8; (w * h * 4) as usize];
90    let bg: Color = rgb8(20, 22, 28);
91    renderer
92        .render_to_buffer(w, h, bg, &mut pixels)
93        .expect("render");
94
95    let path = std::env::current_dir()
96        .unwrap()
97        .join("examples/hello_hybrid.png");
98    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
99    println!("wrote {}", path.display());
100}
examples/primitives_demo.rs (line 33)
22fn main() {
23    let mut renderer = VelloRenderer::new().expect("vello renderer init");
24    let (w, h) = (640u32, 800u32);
25
26    {
27        let scene = renderer.scene();
28        let reference_brush: Brush = rgb8(80, 80, 80).into();
29        let dotted = Stroke::new(1.0).with_dashes(0.0, vec![3.0, 4.0]);
30
31        // ---------- strip 1: end-clipped, corner-rounded polyline ----------
32
33        let node_a_center = Point::new(120.0, 140.0);
34        let node_a_radius = 40.0;
35        let node_b = Rect::new(440.0, 100.0, 580.0, 180.0);
36
37        let node_brush: Brush = rgb8(60, 90, 140).into();
38        scene.fill(
39            FillRule::NonZero,
40            Affine::IDENTITY,
41            &node_brush,
42            None,
43            &kurbo::Circle::new(node_a_center, node_a_radius).to_path(0.1),
44            PickId::Skip,
45        );
46        scene.fill(
47            FillRule::NonZero,
48            Affine::IDENTITY,
49            &node_brush,
50            None,
51            &node_b.to_path(0.1),
52            PickId::Skip,
53        );
54
55        let raw = [
56            node_a_center,
57            Point::new(220.0, 60.0),
58            Point::new(340.0, 220.0),
59            Point::new(420.0, 80.0),
60            Point::new(node_b.center().x, node_b.center().y),
61        ];
62        let clipped = clip_polyline(
63            &raw,
64            Some(EndClip::Circle {
65                center: node_a_center,
66                radius: node_a_radius,
67            }),
68            Some(EndClip::Rect(node_b)),
69        );
70        let connector = round_corners(
71            &clipped,
72            false,
73            CornerRounding {
74                max_cut: 30.0,
75                ..Default::default()
76            },
77        );
78        let line_stroke = Stroke::new(4.0)
79            .with_caps(Cap::Round)
80            .with_join(Join::Round);
81        let line_brush: Brush = rgb8(230, 200, 120).into();
82        scene.stroke(
83            &line_stroke,
84            Affine::IDENTITY,
85            &line_brush,
86            None,
87            &connector,
88            PickId::Skip,
89        );
90        scene.stroke(
91            &dotted,
92            Affine::IDENTITY,
93            &reference_brush,
94            None,
95            &mk_polyline(&raw),
96            PickId::Skip,
97        );
98
99        // ---------- strip 2: polygon-with-hole, offset + rounded ----------
100
101        let outer = [
102            Point::new(120.0, 320.0),
103            Point::new(520.0, 320.0),
104            Point::new(520.0, 500.0),
105            Point::new(120.0, 500.0),
106        ];
107        let hole = [
108            Point::new(240.0, 370.0),
109            Point::new(400.0, 370.0),
110            Point::new(400.0, 450.0),
111            Point::new(240.0, 450.0),
112        ];
113        let rings: [&[Point]; 2] = [&outer, &hole];
114
115        let plain = polygon(&rings, PolygonOptions::default());
116        scene.stroke(
117            &dotted,
118            Affine::IDENTITY,
119            &reference_brush,
120            None,
121            &plain,
122            PickId::Skip,
123        );
124
125        let inflated_rings = offset_polygon(&rings, 15.0, 4.0);
126        let mut inflated = Path::new();
127        for r in &inflated_rings {
128            let sub = round_corners(r, true, CornerRounding::default());
129            for el in sub.iter() {
130                inflated.push(el);
131            }
132        }
133        let fill_brush: Brush = rgb8(180, 70, 90).into();
134        scene.fill(
135            FillRule::NonZero,
136            Affine::IDENTITY,
137            &fill_brush,
138            None,
139            &inflated,
140            PickId::Skip,
141        );
142
143        // ---------- strip 3: wedge & annular wedge with curve-aware rounding ----------
144
145        // Left: wedge with rounded line-to-arc corners.
146        let w_center = Point::new(170.0, 680.0);
147        let w_radius = 90.0;
148        let plain_wedge = wedge(w_center, w_radius, -PI / 2.0 - PI / 5.0, PI * 2.0 / 5.0);
149        scene.stroke(
150            &dotted,
151            Affine::IDENTITY,
152            &reference_brush,
153            None,
154            &plain_wedge,
155            PickId::Skip,
156        );
157        let rounded_wedge = round_path_corners(
158            &plain_wedge,
159            CornerRounding {
160                max_cut: 18.0,
161                ..Default::default()
162            },
163        );
164        let wedge_brush: Brush = rgb8(110, 160, 90).into();
165        scene.fill(
166            FillRule::NonZero,
167            Affine::IDENTITY,
168            &wedge_brush,
169            None,
170            &rounded_wedge,
171            PickId::Skip,
172        );
173
174        // Right: annular wedge with all four corners rounded.
175        let a_center = Point::new(450.0, 680.0);
176        let plain_aw = annular_wedge(a_center, 35.0, 100.0, -PI / 2.0 - PI / 4.0, PI / 2.0);
177        scene.stroke(
178            &dotted,
179            Affine::IDENTITY,
180            &reference_brush,
181            None,
182            &plain_aw,
183            PickId::Skip,
184        );
185        let rounded_aw = round_path_corners(
186            &plain_aw,
187            CornerRounding {
188                max_cut: 14.0,
189                ..Default::default()
190            },
191        );
192        let aw_brush: Brush = rgb8(180, 130, 90).into();
193        scene.fill(
194            FillRule::NonZero,
195            Affine::IDENTITY,
196            &aw_brush,
197            None,
198            &rounded_aw,
199            PickId::Skip,
200        );
201    }
202
203    let mut pixels = vec![0u8; (w * h * 4) as usize];
204    let bg: Color = rgb8(20, 22, 28);
205    renderer
206        .render_to_buffer(w, h, bg, &mut pixels)
207        .expect("render");
208
209    let path = std::env::current_dir()
210        .unwrap()
211        .join("examples/primitives_demo.png");
212    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
213    println!("wrote {}", path.display());
214}
examples/ribbon_gradient.rs (line 51)
31fn main() {
32    let dpi = 96.0;
33    let bg: Color = rgb8(248, 248, 252);
34    let mut renderer = VelloRenderer::new().expect("vello renderer init");
35
36    // ── Render 1: colour gradient along the line ───────────────────
37    {
38        let (w, h) = (1200u32, 300u32);
39        let n = 80;
40        let xs: Vec<f64> = (0..n)
41            .map(|i| 80.0 + i as f64 / (n - 1) as f64 * 1040.0)
42            .collect();
43        let ys: Vec<f64> = xs
44            .iter()
45            .enumerate()
46            .map(|(i, _)| 150.0 + 60.0 * (i as f64 * 0.18).sin())
47            .collect();
48        let points: Vec<Point> = xs
49            .iter()
50            .zip(ys.iter())
51            .map(|(x, y)| Point::new(*x, *y))
52            .collect();
53        // 5-stop gradient: cool blues to warm reds.
54        let stops = [
55            rgb8(60, 80, 200),
56            rgb8(80, 160, 200),
57            rgb8(220, 220, 220),
58            rgb8(220, 130, 80),
59            rgb8(200, 50, 60),
60        ];
61        let colors: Vec<Color> = (0..n)
62            .map(|i| interpolate_stops(&stops, i as f64 / (n - 1) as f64))
63            .collect();
64        let opts = RibbonOptions {
65            half_width: pt_to_px(12.0, dpi) * 0.5,
66            cap: Cap::Round,
67            join: Join::Round,
68            miter_limit: 4.0,
69        };
70        let mesh = polyline_gradient(&points, &colors, &opts);
71        render_mesh(
72            &mut renderer,
73            &mesh,
74            w,
75            h,
76            dpi,
77            bg,
78            "examples/ribbon_1_gradient_along.png",
79        );
80    }
81
82    // ── Render 2: variable width along the line ────────────────────
83    {
84        let (w, h) = (1200u32, 300u32);
85        let n = 80;
86        let xs: Vec<f64> = (0..n)
87            .map(|i| 80.0 + i as f64 / (n - 1) as f64 * 1040.0)
88            .collect();
89        let ys: Vec<f64> = (0..n)
90            .map(|i| 150.0 + 60.0 * (i as f64 * 0.18).sin())
91            .collect();
92        let points: Vec<Point> = xs
93            .iter()
94            .zip(ys.iter())
95            .map(|(x, y)| Point::new(*x, *y))
96            .collect();
97        // Width ramps from 2pt to 20pt linearly.
98        let half_widths: Vec<f64> = (0..n)
99            .map(|i| {
100                let t = i as f64 / (n - 1) as f64;
101                pt_to_px(2.0 + (20.0 - 2.0) * t, dpi) * 0.5
102            })
103            .collect();
104        let opts = RibbonOptions {
105            half_width: 1.0,
106            cap: Cap::Round,
107            join: Join::Round,
108            miter_limit: 4.0,
109        };
110        // `polyline_ribbon_full` with no colours uses black; pass a
111        // same-colour slice for the desired stroke colour.
112        let stroke = rgb8(40, 90, 180);
113        let colors = vec![stroke; n];
114        let mesh = polyline_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
115        render_mesh(
116            &mut renderer,
117            &mesh,
118            w,
119            h,
120            dpi,
121            bg,
122            "examples/ribbon_2_variable_width.png",
123        );
124    }
125
126    // ── Render 3: gradient + variable width on a self-intersecting
127    // figure-8 (Lissajous 1:2). The polyline crosses itself once at
128    // the centre — each crossing arm renders at a different width
129    // and colour, and SrcOver compositing layers them in source order
130    // (later vertices draw on top of earlier ones).
131    {
132        let (w, h) = (1200u32, 400u32);
133        let n = 240;
134        let cx = 600.0;
135        let cy = 200.0;
136        let amp_x = 500.0;
137        let amp_y = 120.0;
138        let xs: Vec<f64> = (0..n)
139            .map(|i| {
140                let t = i as f64 / (n - 1) as f64 * std::f64::consts::TAU;
141                cx + amp_x * t.sin()
142            })
143            .collect();
144        let ys: Vec<f64> = (0..n)
145            .map(|i| {
146                let t = i as f64 / (n - 1) as f64 * std::f64::consts::TAU;
147                cy + amp_y * (2.0 * t).sin()
148            })
149            .collect();
150        let points: Vec<Point> = xs
151            .iter()
152            .zip(ys.iter())
153            .map(|(x, y)| Point::new(*x, *y))
154            .collect();
155        let stops = [
156            rgb8(60, 80, 200),
157            rgb8(80, 160, 200),
158            rgb8(220, 220, 220),
159            rgb8(220, 130, 80),
160            rgb8(200, 50, 60),
161        ];
162        let colors: Vec<Color> = (0..n)
163            .map(|i| interpolate_stops(&stops, i as f64 / (n - 1) as f64))
164            .collect();
165        let half_widths: Vec<f64> = (0..n)
166            .map(|i| {
167                let t = i as f64 / (n - 1) as f64;
168                pt_to_px(2.0 + (24.0 - 2.0) * t, dpi) * 0.5
169            })
170            .collect();
171        let opts = RibbonOptions {
172            half_width: 1.0,
173            cap: Cap::Round,
174            join: Join::Round,
175            miter_limit: 4.0,
176        };
177        let mesh = polyline_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
178        render_mesh(
179            &mut renderer,
180            &mesh,
181            w,
182            h,
183            dpi,
184            bg,
185            "examples/ribbon_3_full.png",
186        );
187    }
188
189    // ── Render 4: 3×3 cap/join grid ────────────────────────────────
190    {
191        let (w, h) = (900u32, 700u32);
192        let caps = [
193            ("butt", Cap::Butt),
194            ("square", Cap::Square),
195            ("round", Cap::Round),
196        ];
197        let joins = [
198            ("miter", Join::Miter),
199            ("bevel", Join::Bevel),
200            ("round", Join::Round),
201        ];
202        // Build 9 ribbons in one mesh (or one render): for each
203        // (row, col), zigzag polyline showing the cap and join.
204        let mut all_meshes: Vec<Mesh> = Vec::new();
205        let cell_w = w as f64 / 3.0;
206        let cell_h = h as f64 / 3.0;
207        for (r_idx, (_cap_name, cap)) in caps.iter().enumerate() {
208            for (c_idx, (_join_name, join)) in joins.iter().enumerate() {
209                let cx = c_idx as f64 * cell_w;
210                let cy = r_idx as f64 * cell_h;
211                // Zigzag polyline filling the cell.
212                let pad = 30.0;
213                let pts = vec![
214                    Point::new(cx + pad, cy + cell_h - pad),
215                    Point::new(cx + cell_w * 0.5, cy + pad),
216                    Point::new(cx + cell_w - pad, cy + cell_h - pad),
217                ];
218                let opts = RibbonOptions {
219                    half_width: pt_to_px(18.0, dpi) * 0.5,
220                    cap: *cap,
221                    join: *join,
222                    miter_limit: 4.0,
223                };
224                let stroke = rgb8(40, 90, 180);
225                all_meshes.push(polyline_ribbon(&pts, stroke, &opts));
226            }
227        }
228        render_meshes(
229            &mut renderer,
230            &all_meshes,
231            w,
232            h,
233            dpi,
234            bg,
235            "examples/ribbon_4_caps_joins.png",
236        );
237    }
238
239    // ── Render 5: closed-loop rainbow ──────────────────────────────
240    // Regular dodecagon vertices, one full hue cycle around the
241    // loop. The wrap segment interpolates colors[n-1] → colors[0]
242    // (≈ magenta → red, a short hop on the colour wheel), which
243    // should be visually indistinguishable from the other segment
244    // transitions — i.e. no seam at the close.
245    {
246        let (w, h) = (700u32, 700u32);
247        let cx = 350.0;
248        let cy = 350.0;
249        let radius = 260.0;
250        let n = 12;
251        let points: Vec<Point> = (0..n)
252            .map(|i| {
253                let theta = i as f64 / n as f64 * std::f64::consts::TAU;
254                Point::new(cx + radius * theta.cos(), cy + radius * theta.sin())
255            })
256            .collect();
257        let colors: Vec<Color> = (0..n)
258            .map(|i| hsv_to_color(i as f64 / n as f64, 0.85, 0.95))
259            .collect();
260        let opts = RibbonOptions {
261            half_width: pt_to_px(28.0, dpi) * 0.5,
262            cap: Cap::Butt,
263            join: Join::Round,
264            miter_limit: 4.0,
265        };
266        let mesh = polygon_gradient(&points, &colors, &opts);
267        render_mesh(
268            &mut renderer,
269            &mesh,
270            w,
271            h,
272            dpi,
273            bg,
274            "examples/ribbon_5_closed_rainbow.png",
275        );
276    }
277
278    // ── Render 6: closed loop with gradient + variable width ───────
279    // Five-lobed polar curve r(θ) = R + a·sin(5θ) sampled at 240
280    // points. Colours cycle the full hue wheel once; widths pulse
281    // at twice the lobe frequency so the ribbon visibly fattens and
282    // narrows around the loop. Both pieces close seamlessly because
283    // `polygon_*` treats the n samples as one continuous ring.
284    {
285        let (w, h) = (900u32, 900u32);
286        let cx = 450.0;
287        let cy = 450.0;
288        let base_r = 280.0;
289        let amp_r = 70.0;
290        let lobes = 5.0;
291        let n = 240;
292        let points: Vec<Point> = (0..n)
293            .map(|i| {
294                let theta = i as f64 / n as f64 * std::f64::consts::TAU;
295                let r = base_r + amp_r * (lobes * theta).sin();
296                Point::new(cx + r * theta.cos(), cy + r * theta.sin())
297            })
298            .collect();
299        let colors: Vec<Color> = (0..n)
300            .map(|i| hsv_to_color(i as f64 / n as f64, 0.85, 0.95))
301            .collect();
302        let half_widths: Vec<f64> = (0..n)
303            .map(|i| {
304                let theta = i as f64 / n as f64 * std::f64::consts::TAU;
305                // Width pulses between ~4 pt and ~22 pt.
306                let pt = 13.0 + 9.0 * (2.0 * lobes * theta).sin();
307                pt_to_px(pt, dpi) * 0.5
308            })
309            .collect();
310        let opts = RibbonOptions {
311            half_width: 1.0,
312            cap: Cap::Butt, // ignored by polygon_*
313            join: Join::Round,
314            miter_limit: 4.0,
315        };
316        let mesh = polygon_ribbon_full(&points, Some(&colors), Some(&half_widths), &opts);
317        render_mesh(
318            &mut renderer,
319            &mesh,
320            w,
321            h,
322            dpi,
323            bg,
324            "examples/ribbon_6_closed_full.png",
325        );
326    }
327}
Source

pub const fn to_vec2(self) -> Vec2

Convert this point into a Vec2.

Examples found in repository?
examples/shapes_demo.rs (line 41)
33fn draw_shape_centered(
34    scene: &mut impl SceneBuilder,
35    shape: &Shape,
36    center: Point,
37    size: f64,
38    brush: &Brush,
39    stroke_world_width: f64,
40) {
41    let xform = Affine::translate(center.to_vec2()) * Affine::scale(size);
42    let (paths, style) = match shape.kind() {
43        ShapeKind::Paths { paths, style } => (paths, style),
44        ShapeKind::Glyph { .. } => return,
45    };
46    match style {
47        ShapeStyle::Fill => {
48            for sub in paths {
49                scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
50            }
51        }
52        ShapeStyle::Stroke => {
53            let stroke = Stroke::new(stroke_world_width / size)
54                .with_caps(Cap::Round)
55                .with_join(Join::Round);
56            for sub in paths {
57                scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
58            }
59        }
60    }
61}
62
63fn draw_shape_attached(
64    scene: &mut impl SceneBuilder,
65    shape: &Shape,
66    placement: Point,
67    direction: Vec2,
68    size: f64,
69    brush: &Brush,
70    stroke_world_width: f64,
71) {
72    let perp = Vec2::new(-direction.y, direction.x);
73    let a = shape.anchor();
74    let anchor_world = direction * (a.x * size) + perp * (a.y * size);
75    let origin = placement - anchor_world;
76    let xform = Affine::translate(origin.to_vec2())
77        * Affine::rotate(direction.atan2())
78        * Affine::scale(size);
79    let (paths, style) = match shape.kind() {
80        ShapeKind::Paths { paths, style } => (paths, style),
81        ShapeKind::Glyph { .. } => return,
82    };
83    match style {
84        ShapeStyle::Fill => {
85            for sub in paths {
86                scene.fill(FillRule::NonZero, xform, brush, None, sub, PickId::Skip);
87            }
88        }
89        ShapeStyle::Stroke => {
90            let stroke = Stroke::new(stroke_world_width / size)
91                .with_caps(Cap::Round)
92                .with_join(Join::Round);
93            for sub in paths {
94                scene.stroke(&stroke, xform, brush, None, sub, PickId::Skip);
95            }
96        }
97    }
98}
Source

pub fn lerp(self, other: Point, t: f64) -> Point

Linearly interpolate between two points.

Source

pub const fn midpoint(self, other: Point) -> Point

Determine the midpoint of two points.

Source

pub fn distance(self, other: Point) -> f64

Euclidean distance.

See Vec2::hypot for the same operation on Vec2.

Source

pub fn distance_squared(self, other: Point) -> f64

Squared Euclidean distance.

See Vec2::hypot2 for the same operation on Vec2.

Source

pub fn round(self) -> Point

Returns a new Point, with x and y rounded to the nearest integer.

§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).round();
let b = Point::new(3.0, -3.1).round();
assert_eq!(a.x, 3.0);
assert_eq!(a.y, 4.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -3.0);
Source

pub fn ceil(self) -> Point

Returns a new Point, with x and y rounded up to the nearest integer, unless they are already an integer.

§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).ceil();
let b = Point::new(3.0, -3.1).ceil();
assert_eq!(a.x, 4.0);
assert_eq!(a.y, 4.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -3.0);
Source

pub fn floor(self) -> Point

Returns a new Point, with x and y rounded down to the nearest integer, unless they are already an integer.

§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).floor();
let b = Point::new(3.0, -3.1).floor();
assert_eq!(a.x, 3.0);
assert_eq!(a.y, 3.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -4.0);
Source

pub fn expand(self) -> Point

Returns a new Point, with x and y rounded away from zero to the nearest integer, unless they are already an integer.

§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).expand();
let b = Point::new(3.0, -3.1).expand();
assert_eq!(a.x, 4.0);
assert_eq!(a.y, 4.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -4.0);
Source

pub fn trunc(self) -> Point

Returns a new Point, with x and y rounded towards zero to the nearest integer, unless they are already an integer.

§Examples
use kurbo::Point;
let a = Point::new(3.3, 3.6).trunc();
let b = Point::new(3.0, -3.1).trunc();
assert_eq!(a.x, 3.0);
assert_eq!(a.y, 3.0);
assert_eq!(b.x, 3.0);
assert_eq!(b.y, -3.0);
Source

pub const fn is_finite(self) -> bool

Is this point finite?

Source

pub const fn is_nan(self) -> bool

Is this point NaN?

Source

pub const fn get_coord(self, axis: Axis) -> f64

Get the member matching the given axis.

Source

pub const fn get_coord_mut(&mut self, axis: Axis) -> &mut f64

Get a mutable reference to the member matching the given axis.

Source

pub const fn set_coord(&mut self, axis: Axis, value: f64)

Set the member matching the given axis to the given value.

Trait Implementations§

Source§

impl Add<(f64, f64)> for Point

Source§

type Output = Point

The resulting type after applying the + operator.
Source§

fn add(self, _: (f64, f64)) -> Point

Performs the + operation. Read more
Source§

impl Add<Vec2> for Point

Source§

type Output = Point

The resulting type after applying the + operator.
Source§

fn add(self, other: Vec2) -> Point

Performs the + operation. Read more
Source§

impl AddAssign<(f64, f64)> for Point

Source§

fn add_assign(&mut self, _: (f64, f64))

Performs the += operation. Read more
Source§

impl AddAssign<Vec2> for Point

Source§

fn add_assign(&mut self, other: Vec2)

Performs the += operation. Read more
Source§

impl Clone for Point

Source§

fn clone(&self) -> Point

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Point

Source§

impl Debug for Point

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for Point

Source§

fn default() -> Point

Returns the “default value” for a type. Read more
Source§

impl Display for Point

Source§

fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl From<(f32, f32)> for Point

Source§

fn from(v: (f32, f32)) -> Point

Converts to this type from the input type.
Source§

impl From<(f64, f64)> for Point

Source§

fn from(v: (f64, f64)) -> Point

Converts to this type from the input type.
Source§

impl Mul<Point> for Affine

Source§

type Output = Point

The resulting type after applying the * operator.
Source§

fn mul(self, other: Point) -> Point

Performs the * operation. Read more
Source§

impl PartialEq for Point

Source§

fn eq(&self, other: &Point) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Point

Source§

impl Sub for Point

Source§

type Output = Vec2

The resulting type after applying the - operator.
Source§

fn sub(self, other: Point) -> Vec2

Performs the - operation. Read more
Source§

impl Sub<(f64, f64)> for Point

Source§

type Output = Point

The resulting type after applying the - operator.
Source§

fn sub(self, _: (f64, f64)) -> Point

Performs the - operation. Read more
Source§

impl Sub<Vec2> for Point

Source§

type Output = Point

The resulting type after applying the - operator.
Source§

fn sub(self, other: Vec2) -> Point

Performs the - operation. Read more
Source§

impl SubAssign<(f64, f64)> for Point

Source§

fn sub_assign(&mut self, _: (f64, f64))

Performs the -= operation. Read more
Source§

impl SubAssign<Vec2> for Point

Source§

fn sub_assign(&mut self, other: Vec2)

Performs the -= operation. Read more

Auto Trait Implementations§

§

impl Freeze for Point

§

impl RefUnwindSafe for Point

§

impl Send for Point

§

impl Sync for Point

§

impl Unpin for Point

§

impl UnsafeUnpin for Point

§

impl UnwindSafe for Point

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Brush for T
where T: Clone + PartialEq + Default + Debug,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToSmolStr for T
where T: Display + ?Sized,

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<S, T> Upcast<T> for S
where T: UpcastFrom<S> + ?Sized, S: ?Sized,

Source§

fn upcast(&self) -> &T
where Self: ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider ref type within the Wasm bindgen generics type system. Read more
Source§

fn upcast_into(self) -> T
where Self: Sized + ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider type within the Wasm bindgen generics type system. Read more
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more