Coverage Report

Created: 2025-11-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/plotters-backend-0.3.7/src/rasterizer/path.rs
Line
Count
Source
1
use crate::BackendCoord;
2
3
// Compute the tanginal and normal vectors of the given straight line.
4
0
fn get_dir_vector(from: BackendCoord, to: BackendCoord, flag: bool) -> ((f64, f64), (f64, f64)) {
5
0
    let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1));
6
0
    let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt();
7
8
0
    let v = (v.0 as f64 / l, v.1 as f64 / l);
9
10
0
    if flag {
11
0
        (v, (v.1, -v.0))
12
    } else {
13
0
        (v, (-v.1, v.0))
14
    }
15
0
}
16
17
// Compute the polygonized vertex of the given angle
18
// d is the distance between the polygon edge and the actual line.
19
// d can be negative, this will emit a vertex on the other side of the line.
20
0
fn compute_polygon_vertex(triple: &[BackendCoord; 3], d: f64, buf: &mut Vec<BackendCoord>) {
21
0
    buf.clear();
22
23
    // Compute the tanginal and normal vectors of the given straight line.
24
0
    let (a_t, a_n) = get_dir_vector(triple[0], triple[1], false);
25
0
    let (b_t, b_n) = get_dir_vector(triple[2], triple[1], true);
26
27
    // Compute a point that is d away from the line for line a and line b.
28
0
    let a_p = (
29
0
        f64::from(triple[1].0) + d * a_n.0,
30
0
        f64::from(triple[1].1) + d * a_n.1,
31
0
    );
32
0
    let b_p = (
33
0
        f64::from(triple[1].0) + d * b_n.0,
34
0
        f64::from(triple[1].1) + d * b_n.1,
35
0
    );
36
37
    // Check if 3 points are colinear, up to precision. If so, just emit the point.
38
0
    if (a_t.1 * b_t.0 - a_t.0 * b_t.1).abs() <= f64::EPSILON {
39
0
        buf.push((a_p.0 as i32, a_p.1 as i32));
40
0
        return;
41
0
    }
42
43
    // So we are actually computing the intersection of two lines:
44
    // a_p + u * a_t and b_p + v * b_t.
45
    // We can solve the following vector equation:
46
    // u * a_t + a_p = v * b_t + b_p
47
    //
48
    // which is actually a equation system:
49
    // u * a_t.0 - v * b_t.0 = b_p.0 - a_p.0
50
    // u * a_t.1 - v * b_t.1 = b_p.1 - a_p.1
51
52
    // The following vars are coefficients of the linear equation system.
53
    // a0*u + b0*v = c0
54
    // a1*u + b1*v = c1
55
    // in which x and y are the coordinates that two polygon edges intersect.
56
57
0
    let a0 = a_t.0;
58
0
    let b0 = -b_t.0;
59
0
    let c0 = b_p.0 - a_p.0;
60
0
    let a1 = a_t.1;
61
0
    let b1 = -b_t.1;
62
0
    let c1 = b_p.1 - a_p.1;
63
64
    // Since the points are not collinear, the determinant is not 0, and we can get a intersection point.
65
0
    let u = (c0 * b1 - c1 * b0) / (a0 * b1 - a1 * b0);
66
0
    let x = a_p.0 + u * a_t.0;
67
0
    let y = a_p.1 + u * a_t.1;
68
69
0
    let cross_product = a_t.0 * b_t.1 - a_t.1 * b_t.0;
70
0
    if (cross_product < 0.0 && d < 0.0) || (cross_product > 0.0 && d > 0.0) {
71
        // Then we are at the outer side of the angle, so we need to consider a cap.
72
0
        let dist_square = (x - triple[1].0 as f64).powi(2) + (y - triple[1].1 as f64).powi(2);
73
        // If the point is too far away from the line, we need to cap it.
74
0
        if dist_square > d * d * 16.0 {
75
0
            buf.push((a_p.0.round() as i32, a_p.1.round() as i32));
76
0
            buf.push((b_p.0.round() as i32, b_p.1.round() as i32));
77
0
            return;
78
0
        }
79
0
    }
80
81
0
    buf.push((x.round() as i32, y.round() as i32));
82
0
}
83
84
0
fn traverse_vertices<'a>(
85
0
    mut vertices: impl Iterator<Item = &'a BackendCoord>,
86
0
    width: u32,
87
0
    mut op: impl FnMut(BackendCoord),
88
0
) {
89
0
    let mut a = vertices.next().unwrap();
90
0
    let mut b = vertices.next().unwrap();
91
92
0
    while a == b {
93
0
        a = b;
94
0
        if let Some(new_b) = vertices.next() {
95
0
            b = new_b;
96
0
        } else {
97
0
            return;
98
        }
99
    }
100
101
0
    let (_, n) = get_dir_vector(*a, *b, false);
102
103
0
    op((
104
0
        (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32,
105
0
        (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32,
106
0
    ));
107
108
0
    let mut recent = [(0, 0), *a, *b];
109
0
    let mut vertex_buf = Vec::with_capacity(3);
110
111
0
    for p in vertices {
112
0
        if *p == recent[2] {
113
0
            continue;
114
0
        }
115
0
        recent.swap(0, 1);
116
0
        recent.swap(1, 2);
117
0
        recent[2] = *p;
118
0
        compute_polygon_vertex(&recent, f64::from(width) / 2.0, &mut vertex_buf);
119
0
        vertex_buf.iter().cloned().for_each(&mut op);
120
    }
121
122
0
    let b = recent[1];
123
0
    let a = recent[2];
124
125
0
    let (_, n) = get_dir_vector(a, b, true);
126
127
0
    op((
128
0
        (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32,
129
0
        (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32,
130
0
    ));
131
0
}
Unexecuted instantiation: plotters_backend::rasterizer::path::traverse_vertices::<core::slice::iter::Iter<(i32, i32)>, plotters_backend::rasterizer::path::polygonize::{closure#0}>
Unexecuted instantiation: plotters_backend::rasterizer::path::traverse_vertices::<core::iter::adapters::rev::Rev<core::slice::iter::Iter<(i32, i32)>>, plotters_backend::rasterizer::path::polygonize::{closure#1}>
132
133
/// Covert a path with >1px stroke width into polygon.
134
0
pub fn polygonize(vertices: &[BackendCoord], stroke_width: u32) -> Vec<BackendCoord> {
135
0
    if vertices.len() < 2 {
136
0
        return vec![];
137
0
    }
138
139
0
    let mut ret = vec![];
140
141
0
    traverse_vertices(vertices.iter(), stroke_width, |v| ret.push(v));
142
0
    traverse_vertices(vertices.iter().rev(), stroke_width, |v| ret.push(v));
143
144
0
    ret
145
0
}
146
147
#[cfg(test)]
148
mod test {
149
    use super::*;
150
151
    /// Test for regression with respect to https://github.com/plotters-rs/plotters/issues/562
152
    #[test]
153
    fn test_no_inf_in_compute_polygon_vertex() {
154
        let path = [(335, 386), (338, 326), (340, 286)];
155
        let mut buf = Vec::new();
156
        compute_polygon_vertex(&path, 2.0, buf.as_mut());
157
        assert!(!buf.is_empty());
158
        let nani32 = f64::INFINITY as i32;
159
        assert!(!buf.iter().any(|&v| v.0 == nani32 || v.1 == nani32));
160
    }
161
162
    /// Correct 90 degree turn to the right
163
    #[test]
164
    fn standard_corner() {
165
        let path = [(10, 10), (20, 10), (20, 20)];
166
        let mut buf = Vec::new();
167
        compute_polygon_vertex(&path, 2.0, buf.as_mut());
168
        assert!(!buf.is_empty());
169
        let buf2 = vec![(18, 12)];
170
        assert_eq!(buf, buf2);
171
    }
172
}