| 1 | // Copyright 2011 Google Inc. |
| 2 | // Copyright 2020 Yevhenii Reizner |
| 3 | // |
| 4 | // Use of this source code is governed by a BSD-style license that can be |
| 5 | // found in the LICENSE file. |
| 6 | |
| 7 | use alloc::vec::Vec; |
| 8 | |
| 9 | use tiny_skia_path::PathVerb; |
| 10 | |
| 11 | use crate::{Path, Point}; |
| 12 | |
| 13 | use crate::edge::{CubicEdge, Edge, LineEdge, QuadraticEdge}; |
| 14 | use crate::edge_clipper::EdgeClipperIter; |
| 15 | use crate::geom::ScreenIntRect; |
| 16 | use crate::path_geometry; |
| 17 | |
| 18 | #[derive (Copy, Clone, PartialEq, Debug)] |
| 19 | enum Combine { |
| 20 | No, |
| 21 | Partial, |
| 22 | Total, |
| 23 | } |
| 24 | |
| 25 | #[derive (Copy, Clone, Debug)] |
| 26 | pub struct ShiftedIntRect { |
| 27 | shifted: ScreenIntRect, |
| 28 | shift: i32, |
| 29 | } |
| 30 | |
| 31 | impl ShiftedIntRect { |
| 32 | pub fn new(rect: &ScreenIntRect, shift: i32) -> Option<Self> { |
| 33 | let shifted = ScreenIntRect::from_xywh( |
| 34 | rect.x() << shift, |
| 35 | rect.y() << shift, |
| 36 | rect.width() << shift, |
| 37 | rect.height() << shift, |
| 38 | )?; |
| 39 | Some(ShiftedIntRect { shifted, shift }) |
| 40 | } |
| 41 | |
| 42 | pub fn shifted(&self) -> &ScreenIntRect { |
| 43 | &self.shifted |
| 44 | } |
| 45 | |
| 46 | pub fn recover(&self) -> ScreenIntRect { |
| 47 | ScreenIntRect::from_xywh( |
| 48 | self.shifted.x() >> self.shift, |
| 49 | self.shifted.y() >> self.shift, |
| 50 | self.shifted.width() >> self.shift, |
| 51 | self.shifted.height() >> self.shift, |
| 52 | ) |
| 53 | .unwrap() // cannot fail, because the original rect was valid |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | pub struct BasicEdgeBuilder { |
| 58 | edges: Vec<Edge>, |
| 59 | clip_shift: i32, |
| 60 | } |
| 61 | |
| 62 | impl BasicEdgeBuilder { |
| 63 | pub fn new(clip_shift: i32) -> Self { |
| 64 | BasicEdgeBuilder { |
| 65 | edges: Vec::with_capacity(64), // TODO: stack array + fallback |
| 66 | clip_shift, |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // Skia returns a linked list here, but it's a nightmare to use in Rust, |
| 71 | // so we're mimicking it with Vec. |
| 72 | pub fn build_edges( |
| 73 | path: &Path, |
| 74 | clip: Option<&ShiftedIntRect>, |
| 75 | clip_shift: i32, |
| 76 | ) -> Option<Vec<Edge>> { |
| 77 | // If we're convex, then we need both edges, even if the right edge is past the clip. |
| 78 | // let can_cull_to_the_right = !path.isConvex(); |
| 79 | let can_cull_to_the_right = false; // TODO: this |
| 80 | |
| 81 | let mut builder = BasicEdgeBuilder::new(clip_shift); |
| 82 | if !builder.build(path, clip, can_cull_to_the_right) { |
| 83 | log::warn!("infinite or NaN segments detected during edges building" ); |
| 84 | return None; |
| 85 | } |
| 86 | |
| 87 | if builder.edges.len() < 2 { |
| 88 | return None; |
| 89 | } |
| 90 | |
| 91 | Some(builder.edges) |
| 92 | } |
| 93 | |
| 94 | // TODO: build_poly |
| 95 | pub fn build( |
| 96 | &mut self, |
| 97 | path: &Path, |
| 98 | clip: Option<&ShiftedIntRect>, |
| 99 | can_cull_to_the_right: bool, |
| 100 | ) -> bool { |
| 101 | if let Some(clip) = clip { |
| 102 | let clip = clip.recover().to_rect(); |
| 103 | for edges in EdgeClipperIter::new(path, clip, can_cull_to_the_right) { |
| 104 | for edge in edges { |
| 105 | match edge { |
| 106 | PathEdge::LineTo(p0, p1) => { |
| 107 | if !p0.is_finite() || !p1.is_finite() { |
| 108 | return false; |
| 109 | } |
| 110 | |
| 111 | self.push_line(&[p0, p1]) |
| 112 | } |
| 113 | PathEdge::QuadTo(p0, p1, p2) => { |
| 114 | if !p0.is_finite() || !p1.is_finite() || !p2.is_finite() { |
| 115 | return false; |
| 116 | } |
| 117 | |
| 118 | self.push_quad(&[p0, p1, p2]) |
| 119 | } |
| 120 | PathEdge::CubicTo(p0, p1, p2, p3) => { |
| 121 | if !p0.is_finite() |
| 122 | || !p1.is_finite() |
| 123 | || !p2.is_finite() |
| 124 | || !p3.is_finite() |
| 125 | { |
| 126 | return false; |
| 127 | } |
| 128 | |
| 129 | self.push_cubic(&[p0, p1, p2, p3]) |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | } else { |
| 135 | for edge in edge_iter(path) { |
| 136 | match edge { |
| 137 | PathEdge::LineTo(p0, p1) => { |
| 138 | self.push_line(&[p0, p1]); |
| 139 | } |
| 140 | PathEdge::QuadTo(p0, p1, p2) => { |
| 141 | let points = [p0, p1, p2]; |
| 142 | let mut mono_x = [Point::zero(); 5]; |
| 143 | let n = path_geometry::chop_quad_at_y_extrema(&points, &mut mono_x); |
| 144 | for i in 0..=n { |
| 145 | self.push_quad(&mono_x[i * 2..]); |
| 146 | } |
| 147 | } |
| 148 | PathEdge::CubicTo(p0, p1, p2, p3) => { |
| 149 | let points = [p0, p1, p2, p3]; |
| 150 | let mut mono_y = [Point::zero(); 10]; |
| 151 | let n = path_geometry::chop_cubic_at_y_extrema(&points, &mut mono_y); |
| 152 | for i in 0..=n { |
| 153 | self.push_cubic(&mono_y[i * 3..]); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | true |
| 161 | } |
| 162 | |
| 163 | fn push_line(&mut self, points: &[Point; 2]) { |
| 164 | if let Some(edge) = LineEdge::new(points[0], points[1], self.clip_shift) { |
| 165 | let combine = if edge.is_vertical() && !self.edges.is_empty() { |
| 166 | if let Some(Edge::Line(last)) = self.edges.last_mut() { |
| 167 | combine_vertical(&edge, last) |
| 168 | } else { |
| 169 | Combine::No |
| 170 | } |
| 171 | } else { |
| 172 | Combine::No |
| 173 | }; |
| 174 | |
| 175 | match combine { |
| 176 | Combine::Total => { |
| 177 | self.edges.pop(); |
| 178 | } |
| 179 | Combine::Partial => {} |
| 180 | Combine::No => self.edges.push(Edge::Line(edge)), |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | fn push_quad(&mut self, points: &[Point]) { |
| 186 | if let Some(edge) = QuadraticEdge::new(points, self.clip_shift) { |
| 187 | self.edges.push(Edge::Quadratic(edge)); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | fn push_cubic(&mut self, points: &[Point]) { |
| 192 | if let Some(edge) = CubicEdge::new(points, self.clip_shift) { |
| 193 | self.edges.push(Edge::Cubic(edge)); |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | fn combine_vertical(edge: &LineEdge, last: &mut LineEdge) -> Combine { |
| 199 | if last.dx != 0 || edge.x != last.x { |
| 200 | return Combine::No; |
| 201 | } |
| 202 | |
| 203 | if edge.winding == last.winding { |
| 204 | return if edge.last_y + 1 == last.first_y { |
| 205 | last.first_y = edge.first_y; |
| 206 | Combine::Partial |
| 207 | } else if edge.first_y == last.last_y + 1 { |
| 208 | last.last_y = edge.last_y; |
| 209 | Combine::Partial |
| 210 | } else { |
| 211 | Combine::No |
| 212 | }; |
| 213 | } |
| 214 | |
| 215 | if edge.first_y == last.first_y { |
| 216 | return if edge.last_y == last.last_y { |
| 217 | Combine::Total |
| 218 | } else if edge.last_y < last.last_y { |
| 219 | last.first_y = edge.last_y + 1; |
| 220 | Combine::Partial |
| 221 | } else { |
| 222 | last.first_y = last.last_y + 1; |
| 223 | last.last_y = edge.last_y; |
| 224 | last.winding = edge.winding; |
| 225 | Combine::Partial |
| 226 | }; |
| 227 | } |
| 228 | |
| 229 | if edge.last_y == last.last_y { |
| 230 | if edge.first_y > last.first_y { |
| 231 | last.last_y = edge.first_y - 1; |
| 232 | } else { |
| 233 | last.last_y = last.first_y - 1; |
| 234 | last.first_y = edge.first_y; |
| 235 | last.winding = edge.winding; |
| 236 | } |
| 237 | |
| 238 | return Combine::Partial; |
| 239 | } |
| 240 | |
| 241 | Combine::No |
| 242 | } |
| 243 | |
| 244 | pub fn edge_iter(path: &Path) -> PathEdgeIter { |
| 245 | PathEdgeIter { |
| 246 | path, |
| 247 | verb_index: 0, |
| 248 | points_index: 0, |
| 249 | move_to: Point::zero(), |
| 250 | needs_close_line: false, |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | #[derive (Copy, Clone, PartialEq, Debug)] |
| 255 | pub enum PathEdge { |
| 256 | LineTo(Point, Point), |
| 257 | QuadTo(Point, Point, Point), |
| 258 | CubicTo(Point, Point, Point, Point), |
| 259 | } |
| 260 | |
| 261 | /// Lightweight variant of PathIter that only returns segments (e.g. lines/quads). |
| 262 | /// |
| 263 | /// Does not return Move or Close. Always "auto-closes" each contour. |
| 264 | pub struct PathEdgeIter<'a> { |
| 265 | path: &'a Path, |
| 266 | verb_index: usize, |
| 267 | points_index: usize, |
| 268 | move_to: Point, |
| 269 | needs_close_line: bool, |
| 270 | } |
| 271 | |
| 272 | impl<'a> PathEdgeIter<'a> { |
| 273 | fn close_line(&mut self) -> Option<PathEdge> { |
| 274 | self.needs_close_line = false; |
| 275 | |
| 276 | let edge: PathEdge = PathEdge::LineTo(self.path.points()[self.points_index - 1], self.move_to); |
| 277 | Some(edge) |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | impl<'a> Iterator for PathEdgeIter<'a> { |
| 282 | type Item = PathEdge; |
| 283 | |
| 284 | fn next(&mut self) -> Option<Self::Item> { |
| 285 | if self.verb_index < self.path.verbs().len() { |
| 286 | let verb = self.path.verbs()[self.verb_index]; |
| 287 | self.verb_index += 1; |
| 288 | |
| 289 | match verb { |
| 290 | PathVerb::Move => { |
| 291 | if self.needs_close_line { |
| 292 | let res = self.close_line(); |
| 293 | self.move_to = self.path.points()[self.points_index]; |
| 294 | self.points_index += 1; |
| 295 | return res; |
| 296 | } |
| 297 | |
| 298 | self.move_to = self.path.points()[self.points_index]; |
| 299 | self.points_index += 1; |
| 300 | self.next() |
| 301 | } |
| 302 | PathVerb::Close => { |
| 303 | if self.needs_close_line { |
| 304 | return self.close_line(); |
| 305 | } |
| 306 | |
| 307 | self.next() |
| 308 | } |
| 309 | _ => { |
| 310 | // Actual edge. |
| 311 | self.needs_close_line = true; |
| 312 | |
| 313 | let edge; |
| 314 | match verb { |
| 315 | PathVerb::Line => { |
| 316 | edge = PathEdge::LineTo( |
| 317 | self.path.points()[self.points_index - 1], |
| 318 | self.path.points()[self.points_index + 0], |
| 319 | ); |
| 320 | self.points_index += 1; |
| 321 | } |
| 322 | PathVerb::Quad => { |
| 323 | edge = PathEdge::QuadTo( |
| 324 | self.path.points()[self.points_index - 1], |
| 325 | self.path.points()[self.points_index + 0], |
| 326 | self.path.points()[self.points_index + 1], |
| 327 | ); |
| 328 | self.points_index += 2; |
| 329 | } |
| 330 | PathVerb::Cubic => { |
| 331 | edge = PathEdge::CubicTo( |
| 332 | self.path.points()[self.points_index - 1], |
| 333 | self.path.points()[self.points_index + 0], |
| 334 | self.path.points()[self.points_index + 1], |
| 335 | self.path.points()[self.points_index + 2], |
| 336 | ); |
| 337 | self.points_index += 3; |
| 338 | } |
| 339 | _ => unreachable!(), |
| 340 | }; |
| 341 | |
| 342 | Some(edge) |
| 343 | } |
| 344 | } |
| 345 | } else if self.needs_close_line { |
| 346 | self.close_line() |
| 347 | } else { |
| 348 | None |
| 349 | } |
| 350 | } |
| 351 | } |
| 352 | |