1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
use crate::path_builder::*;
use crate::Point;
use lyon_geom::LineSegment;
#[derive(Clone, Copy)]
struct DashState {
index: usize, // index into the dash array
on: bool, // whether the dash is on or off
remaining_length: f32, // how much of the dash remains
}
pub fn dash_path(path: &Path, dash_array: &[f32], mut dash_offset: f32) -> Path {
let mut dashed = PathBuilder::new();
let mut cur_pt = None;
let mut start_point = None;
let mut total_dash_length = 0.;
for dash in dash_array {
total_dash_length += *dash;
}
if dash_array.len() % 2 == 1 {
// if the number of items in the dash_array is odd then we need it takes two periods
// to return to the beginning.
total_dash_length *= 2.;
}
// The dash length must be more than zero.
if !(total_dash_length > 0.) {
return dashed.finish();
}
// Handle large positive and negative offsets so that we don't loop for a high number of
// iterations below in extreme cases
dash_offset = dash_offset % total_dash_length;
if dash_offset < 0. {
dash_offset += total_dash_length;
}
// To handle closed paths we need a bunch of extra state so that we properly
// join the first segment. Unfortunately, this makes the code sort of hairy.
// We need to store all of the points in the initial segment so that we can
// join the end of the path with it.
let mut is_first_segment = true;
let mut first_dash = true;
let mut initial_segment : Vec<Point> = Vec::new();
let mut state = DashState {
on: true,
remaining_length: dash_array[0],
index: 0,
};
// adjust our position in the dash array by the dash offset
while dash_offset > state.remaining_length {
dash_offset -= state.remaining_length;
state.index += 1;
state.remaining_length = dash_array[state.index % dash_array.len()];
state.on = !state.on;
}
state.remaining_length -= dash_offset;
// Save a copy of the initial state so that we can restore it for each subpath
let initial = state;
for op in &path.ops {
match *op {
PathOp::MoveTo(pt) => {
cur_pt = Some(pt);
start_point = Some(pt);
dashed.move_to(pt.x, pt.y);
// flush the previous initial segment
if initial_segment.len() > 0 {
dashed.move_to(initial_segment[0].x, initial_segment[0].y);
for i in 1..initial_segment.len() {
dashed.line_to(initial_segment[i].x, initial_segment[i].y);
}
}
is_first_segment = true;
initial_segment = Vec::new();
first_dash = true;
// reset the dash state
state = initial;
}
PathOp::LineTo(pt) => {
if let Some(cur_pt) = cur_pt {
let mut start = cur_pt;
let line = LineSegment {
from: start,
to: pt,
};
let mut len = line.length();
let lv = line.to_vector().normalize();
while len > state.remaining_length {
let seg = start + lv * state.remaining_length;
if state.on {
if is_first_segment {
initial_segment.push(start);
initial_segment.push(seg);
} else {
dashed.line_to(seg.x, seg.y);
}
} else {
first_dash = false;
dashed.move_to(seg.x, seg.y);
}
is_first_segment = false;
state.on = !state.on;
state.index += 1;
len -= state.remaining_length;
state.remaining_length = dash_array[state.index % dash_array.len()];
start = seg;
}
if state.on {
if is_first_segment {
initial_segment.push(start);
initial_segment.push(pt);
} else {
dashed.line_to(pt.x, pt.y);
}
} else {
first_dash = false;
dashed.move_to(pt.x, pt.y);
}
state.remaining_length -= len;
}
cur_pt = Some(pt);
}
PathOp::Close => {
if let (Some(current), Some(start_point)) = (cur_pt, start_point) {
let mut start = current;
let line = LineSegment {
from: start,
to: start_point,
};
let mut len = line.length();
let lv = line.to_vector().normalize();
while len > state.remaining_length {
let seg = start + lv * state.remaining_length;
if state.on {
if is_first_segment {
initial_segment.push(start);
initial_segment.push(seg);
} else {
dashed.line_to(seg.x, seg.y);
}
} else {
first_dash = false;
dashed.move_to(seg.x, seg.y);
}
state.on = !state.on;
state.index += 1;
len -= state.remaining_length;
state.remaining_length = dash_array[state.index % dash_array.len()];
start = seg;
}
if state.on {
if first_dash {
// If we're still on the first dash we can just close
dashed.close();
} else {
if initial_segment.len() > 0 {
// If have an initial segment we'll need to connect with it
for pt in initial_segment {
dashed.line_to(pt.x, pt.y);
}
} else {
dashed.line_to(start_point.x, start_point.y);
}
}
} else {
if initial_segment.len() > 0 {
dashed.move_to(initial_segment[0].x, initial_segment[0].y);
for i in 1..initial_segment.len() {
dashed.line_to(initial_segment[i].x, initial_segment[i].y);
}
}
}
initial_segment = Vec::new();
cur_pt = Some(start_point);
// reset the dash state
state = initial;
} else {
cur_pt = None;
}
}
PathOp::QuadTo(..) => panic!("Only flat paths handled"),
PathOp::CubicTo(..) => panic!("Only flat paths handled"),
}
}
// We still have an initial segment that we need to emit
if !initial_segment.is_empty() {
dashed.move_to(initial_segment[0].x, initial_segment[0].y);
for i in 1..initial_segment.len() {
dashed.line_to(initial_segment[i].x, initial_segment[i].y);
}
}
dashed.finish()
}