// pathfinder/utils/tile-svg/main.rs // // Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. #[macro_use] extern crate bitflags; #[cfg(test)] extern crate quickcheck; #[cfg(test)] extern crate rand; use euclid::{Point2D, Rect, Transform2D, Vector2D}; use lyon_algorithms::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment}; use quick_xml::Reader; use quick_xml::events::Event; use std::cmp::Ordering; use std::env; use std::mem; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Instant; use svgtypes::{Color as SvgColor, PathParser, PathSegment as SvgPathSegment, TransformListParser}; use svgtypes::{TransformListToken}; #[derive(Default)] struct GroupStyle { fill_color: Option, stroke_width: Option, stroke_color: Option, transform: Option>, } #[derive(Debug)] struct ComputedStyle { fill_color: Option, stroke_width: f32, stroke_color: Option, transform: Transform2D, } impl ComputedStyle { fn new() -> ComputedStyle { ComputedStyle { fill_color: None, stroke_width: 1.0, stroke_color: None, transform: Transform2D::identity(), } } } fn main() { let path = PathBuf::from(env::args().skip(1).next().unwrap()); let scene = Scene::from_path(&path); const RUNS: u32 = 1000; let start_time = Instant::now(); for _ in 0..RUNS { scene.generate_tiles(); } let elapsed_time = Instant::now() - start_time; let elapsed_ms = elapsed_time.as_secs() as f64 * 1000.0 + elapsed_time.subsec_micros() as f64 / 1000.0; println!("{}ms elapsed", elapsed_ms / RUNS as f64); } #[derive(Debug)] struct Scene { objects: Vec, styles: Vec, } #[derive(Debug)] struct PathObject { outline: Outline, style: StyleId, } #[derive(Clone, Copy, PartialEq, Debug)] struct StyleId(u32); impl Scene { fn new() -> Scene { Scene { objects: vec![], styles: vec![], } } fn from_path(path: &Path) -> Scene { let mut reader = Reader::from_file(&path).unwrap(); let mut xml_buffer = vec![]; let mut group_styles = vec![]; let mut style = None; let mut scene = Scene::new(); loop { match reader.read_event(&mut xml_buffer) { Ok(Event::Start(ref event)) | Ok(Event::Empty(ref event)) if event.name() == b"path" => { let attributes = event.attributes(); for attribute in attributes { let attribute = attribute.unwrap(); if attribute.key != b"d" { continue } let value = reader.decode(&attribute.value); let style = scene.ensure_style(&mut style, &mut group_styles); let path_parser = PathParser::from(&*value); let outline = Outline::from_svg_path_segments(path_parser, scene.get_style(style)); scene.objects.push(PathObject::new(outline, style)); } } Ok(Event::Start(ref event)) if event.name() == b"g" => { let mut group_style = GroupStyle::default(); let attributes = event.attributes(); for attribute in attributes { let attribute = attribute.unwrap(); match attribute.key { b"fill" => { let value = reader.decode(&attribute.value); if let Ok(color) = SvgColor::from_str(&value) { group_style.fill_color = Some(color) } } b"stroke" => { let value = reader.decode(&attribute.value); if let Ok(color) = SvgColor::from_str(&value) { group_style.stroke_color = Some(color) } } b"transform" => { let value = reader.decode(&attribute.value); let mut current_transform = Transform2D::identity(); let transform_list_parser = TransformListParser::from(&*value); for transform in transform_list_parser { match transform { Ok(TransformListToken::Matrix { a, b, c, d, e, f }) => { let transform: Transform2D = Transform2D::row_major(a, b, c, d, e, f).cast(); current_transform = current_transform.pre_mul(&transform) } _ => {} } } group_style.transform = Some(current_transform); } b"stroke-width" => { if let Ok(width) = reader.decode(&attribute.value).parse() { group_style.stroke_width = Some(width) } } _ => {} } } group_styles.push(group_style); style = None; } Ok(Event::Eof) | Err(_) => break, Ok(_) => {} } xml_buffer.clear(); } return scene; } fn ensure_style(&mut self, current_style: &mut Option, group_styles: &[GroupStyle]) -> StyleId { if let Some(current_style) = *current_style { return current_style } let mut computed_style = ComputedStyle::new(); for group_style in group_styles { if let Some(fill_color) = group_style.fill_color { computed_style.fill_color = Some(fill_color) } if let Some(stroke_width) = group_style.stroke_width { computed_style.stroke_width = stroke_width } if let Some(stroke_color) = group_style.stroke_color { computed_style.stroke_color = Some(stroke_color) } if let Some(transform) = group_style.transform { computed_style.transform = computed_style.transform.pre_mul(&transform) } } let id = StyleId(self.styles.len() as u32); self.styles.push(computed_style); id } fn get_style(&self, style: StyleId) -> &ComputedStyle { &self.styles[style.0 as usize] } fn generate_tiles(&self) { let mut strips = vec![]; for object in &self.objects { let mut tiler = Tiler::from_outline(&object.outline); tiler.generate_tiles(&mut strips); // TODO(pcwalton) } } } impl PathObject { fn new(outline: Outline, style: StyleId) -> PathObject { PathObject { outline, style, } } } // Outlines #[derive(Debug)] struct Outline { contours: Vec, bounds: Rect, } #[derive(Debug)] struct Contour { points: Vec>, flags: Vec, } bitflags! { struct PointFlags: u8 { const CONTROL_POINT_0 = 0x01; const CONTROL_POINT_1 = 0x02; } } impl Outline { fn new() -> Outline { Outline { contours: vec![], bounds: Rect::zero(), } } fn from_svg_path_segments(segments: I, style: &ComputedStyle) -> Outline where I: Iterator { let mut outline = Outline::new(); let mut current_contour = Contour::new(); let mut bounding_points = None; let (mut first_point_in_path, mut last_ctrl_point, mut last_point) = (None, None, None); for segment in segments { match segment { SvgPathSegment::MoveTo { abs, x, y } => { if !current_contour.is_empty() { outline.contours.push(mem::replace(&mut current_contour, Contour::new())) } let to = compute_point(x, y, abs, &last_point); first_point_in_path = Some(to); last_point = Some(to); last_ctrl_point = None; current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::LineTo { abs, x, y } => { let to = compute_point(x, y, abs, &last_point); last_point = Some(to); last_ctrl_point = None; current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::HorizontalLineTo { abs, x } => { let to = Point2D::new(compute_point(x, 0.0, abs, &last_point).x, last_point.unwrap_or(Point2D::zero()).y); last_point = Some(to); last_ctrl_point = None; current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::VerticalLineTo { abs, y } => { let to = Point2D::new(last_point.unwrap_or(Point2D::zero()).x, compute_point(0.0, y, abs, &last_point).y); last_point = Some(to); last_ctrl_point = None; current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::Quadratic { abs, x1, y1, x, y } => { let ctrl = compute_point(x1, y1, abs, &last_point); last_ctrl_point = Some(ctrl); let to = compute_point(x, y, abs, &last_point); last_point = Some(to); current_contour.push_transformed_point(&ctrl, PointFlags::CONTROL_POINT_0, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::SmoothQuadratic { abs, x, y } => { let ctrl = last_point.unwrap_or(Point2D::zero()) + (last_point.unwrap_or(Point2D::zero()) - last_ctrl_point.unwrap_or(Point2D::zero())); last_ctrl_point = Some(ctrl); let to = compute_point(x, y, abs, &last_point); last_point = Some(to); current_contour.push_transformed_point(&ctrl, PointFlags::CONTROL_POINT_0, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => { let ctrl0 = compute_point(x1, y1, abs, &last_point); let ctrl1 = compute_point(x2, y2, abs, &last_point); last_ctrl_point = Some(ctrl1); let to = compute_point(x, y, abs, &last_point); last_point = Some(to); current_contour.push_transformed_point(&ctrl0, PointFlags::CONTROL_POINT_0, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&ctrl1, PointFlags::CONTROL_POINT_1, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::SmoothCurveTo { abs, x2, y2, x, y } => { let ctrl0 = last_point.unwrap_or(Point2D::zero()) + (last_point.unwrap_or(Point2D::zero()) - last_ctrl_point.unwrap_or(Point2D::zero())); let ctrl1 = compute_point(x2, y2, abs, &last_point); last_ctrl_point = Some(ctrl1); let to = compute_point(x, y, abs, &last_point); last_point = Some(to); current_contour.push_transformed_point(&ctrl0, PointFlags::CONTROL_POINT_0, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&ctrl1, PointFlags::CONTROL_POINT_1, &style.transform, &mut bounding_points); current_contour.push_transformed_point(&to, PointFlags::empty(), &style.transform, &mut bounding_points); } SvgPathSegment::ClosePath { abs: _ } => { if !current_contour.is_empty() { outline.contours.push(mem::replace(&mut current_contour, Contour::new())); last_point = first_point_in_path; last_ctrl_point = None; } } SvgPathSegment::EllipticalArc { .. } => unimplemented!("arcs"), } } if !current_contour.is_empty() { outline.contours.push(current_contour) } if let Some((upper_left, lower_right)) = bounding_points { outline.bounds = Rect::from_points([upper_left, lower_right].into_iter()) } return outline; fn compute_point(x: f64, y: f64, abs: bool, last_point: &Option>) -> Point2D { let point = Point2D::new(x, y).to_f32(); match *last_point { Some(last_point) if !abs => last_point + point.to_vector(), _ => point, } } } fn segment_after(&self, endpoint_index: PointIndex) -> Segment { self.contours[endpoint_index.contour_index].segment_after(endpoint_index.point_index) } } impl Contour { fn new() -> Contour { Contour { points: vec![], flags: vec![], } } fn is_empty(&self) -> bool { self.points.is_empty() } fn push_transformed_point(&mut self, point: &Point2D, flags: PointFlags, transform: &Transform2D, bounding_points: &mut Option<(Point2D, Point2D)>) { let point = transform.transform_point(point); self.points.push(point); self.flags.push(flags); match *bounding_points { Some((ref mut upper_left, ref mut lower_right)) => { *upper_left = upper_left.min(point); *lower_right = lower_right.max(point); } None => *bounding_points = Some((point, point)), } } fn segment_after(&self, point_index: usize) -> Segment { debug_assert!(self.point_is_endpoint(point_index)); let point1_index = self.add_to_point_index(point_index, 1); if self.point_is_endpoint(point1_index) { return Segment::Line(LineSegment { from: self.points[point_index], to: self.points[point1_index], }) } let point2_index = self.add_to_point_index(point_index, 2); if self.point_is_endpoint(point2_index) { return Segment::Quadratic(QuadraticBezierSegment { from: self.points[point_index], ctrl: self.points[point1_index], to: self.points[point2_index], }) } let point3_index = self.add_to_point_index(point_index, 3); Segment::Cubic(CubicBezierSegment { from: self.points[point_index], ctrl1: self.points[point1_index], ctrl2: self.points[point2_index], to: self.points[point3_index], }) } fn point_is_endpoint(&self, point_index: usize) -> bool { self.flags[point_index].intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1) } fn add_to_point_index(&self, point_index: usize, addend: usize) -> usize { (point_index + addend) % self.points.len() } } #[derive(Clone, Copy, Debug)] struct PointIndex { contour_index: usize, point_index: usize, } #[derive(Clone, Copy, Debug, PartialEq)] enum Segment { None, Line(LineSegment), Quadratic(QuadraticBezierSegment), Cubic(CubicBezierSegment), } impl Segment { fn is_none(&self) -> bool { match *self { Segment::None => true, _ => false, } } fn endpoints(&self) -> (Point2D, Point2D) { match *self { Segment::Line(ref line) => (line.from, line.to), Segment::Quadratic(ref curve) => (curve.from, curve.to), Segment::Cubic(ref curve) => (curve.from, curve.to), Segment::None => unreachable!(), } } // Note: If we convert these to monotonic then we can optimize this method. // TODO(pcwalton): Consider changing the representation of `Segment` to remove the code // duplication in the branches here? fn min_y(&self) -> f32 { match *self { Segment::Line(ref line) => f32::min(line.from.y, line.to.y), Segment::Quadratic(ref curve) => { f32::min(f32::min(curve.from.y, curve.ctrl.y), curve.to.y) } Segment::Cubic(ref curve) => { f32::min(f32::min(f32::min(curve.from.y, curve.ctrl1.y), curve.ctrl2.y), curve.to.y) } Segment::None => unreachable!(), } } fn clip_y(&self, y: f32) -> ClippedSegments { let (from, to) = self.endpoints(); if from.y < y && to.y < y { return ClippedSegments { min: Some(*self), max: None } } if from.y > y && to.y > y { return ClippedSegments { min: None, max: Some(*self) } } let (prev, next) = match *self { Segment::Line(ref line) => { let (prev, next) = line.split(line.solve_t_for_y(y)); (Segment::Line(prev), Segment::Line(next)) } Segment::Quadratic(ref curve) => { let (prev, next) = curve.split(curve.assume_monotonic().solve_t_for_y(y)); (Segment::Quadratic(prev), Segment::Quadratic(next)) } Segment::Cubic(ref curve) => { let swapped_curve = CubicBezierSegment { from: curve.from.yx(), ctrl1: curve.ctrl1.yx(), ctrl2: curve.ctrl2.yx(), to: curve.to.yx(), }; let (prev, next) = curve.split( swapped_curve.assume_monotonic().solve_t_for_x(y, 0.0..1.0, TOLERANCE)); (Segment::Cubic(prev), Segment::Cubic(next)) } Segment::None => unreachable!(), }; if from.y <= to.y { return ClippedSegments { min: Some(prev), max: Some(next) }; } else { return ClippedSegments { min: Some(next), max: Some(prev) }; } const TOLERANCE: f32 = 0.01; } fn translate(&self, by: &Vector2D) -> Segment { match *self { Segment::Line(ref line) => { Segment::Line(LineSegment { from: line.from + *by, to: line.to + *by, }) } Segment::Quadratic(ref curve) => { Segment::Quadratic(QuadraticBezierSegment { from: curve.from + *by, ctrl: curve.ctrl + *by, to: curve.to + *by, }) } Segment::Cubic(ref curve) => { Segment::Cubic(CubicBezierSegment { from: curve.from + *by, ctrl1: curve.ctrl1 + *by, ctrl2: curve.ctrl2 + *by, to: curve.to + *by, }) } Segment::None => unreachable!(), } } } struct ClippedSegments { min: Option, max: Option, } // Tiling const TILE_WIDTH: f32 = 4.0; const TILE_HEIGHT: f32 = 4.0; struct Tiler<'a> { outline: &'a Outline, sorted_edge_indices: Vec, active_intervals: Intervals, active_edges: Vec, } impl<'a> Tiler<'a> { fn from_outline(outline: &Outline) -> Tiler { Tiler { outline, sorted_edge_indices: vec![], active_intervals: Intervals::new(0.0), active_edges: vec![], } } fn generate_tiles(&mut self, strips: &mut Vec) { // Sort all edge indices. self.sorted_edge_indices.clear(); for contour_index in 0..self.outline.contours.len() { let contour = &self.outline.contours[contour_index]; for point_index in 0..contour.points.len() { if contour.point_is_endpoint(point_index) { self.sorted_edge_indices.push(PointIndex { contour_index, point_index }) } } } { let outline = &self.outline; self.sorted_edge_indices.sort_by(|edge_index_a, edge_index_b| { let segment_a = outline.segment_after(*edge_index_a); let segment_b = outline.segment_after(*edge_index_b); segment_a.min_y().partial_cmp(&segment_b.min_y()).unwrap_or(Ordering::Equal) }); } let bounds = self.outline.bounds; let (max_x, max_y) = (bounds.max_x(), bounds.max_y()); self.active_intervals.reset(max_x); self.active_edges.clear(); let mut next_edge_index_index = 0; let mut tile_top = bounds.origin.y - bounds.origin.y % TILE_HEIGHT; while tile_top < max_y { let mut strip = Strip::new(tile_top); // TODO(pcwalton): Populate tile strip with active intervals. for active_edge in &mut self.active_edges { process_active_edge(active_edge, &mut strip, &mut self.active_intervals) } self.active_edges.retain(|edge| !edge.is_none()); while next_edge_index_index < self.sorted_edge_indices.len() { let mut segment = self.outline.segment_after(self.sorted_edge_indices[next_edge_index_index]); if segment.min_y() > strip.tile_bottom() { break } process_active_edge(&mut segment, &mut strip, &mut self.active_intervals); if !segment.is_none() { self.active_edges.push(segment); } next_edge_index_index += 1; } tile_top = strip.tile_bottom(); strips.push(strip); } } } fn process_active_edge(active_edge: &mut Segment, strip: &mut Strip, active_intervals: &mut Intervals) { let clipped = active_edge.clip_y(strip.tile_bottom()); if let Some(upper_segment) = clipped.min { strip.push_segment(upper_segment); // FIXME(pcwalton): Assumes x-monotonicity! // FIXME(pcwalton): The min call below is a hack! let (from, to) = upper_segment.endpoints(); let from_x = f32::max(0.0, f32::min(active_intervals.extent(), from.x)); let to_x = f32::max(0.0, f32::min(active_intervals.extent(), to.x)); if from_x < to_x { active_intervals.add(IntervalRange::new(from_x, to_x, -1.0)) } else { active_intervals.add(IntervalRange::new(to_x, from_x, 1.0)) } } match clipped.max { Some(lower_segment) => *active_edge = lower_segment, None => *active_edge = Segment::None, } } // Strips struct Strip { segments: Vec, tile_top: f32, } impl Strip { fn new(tile_top: f32) -> Strip { Strip { segments: vec![], tile_top, } } fn push_segment(&mut self, segment: Segment) { self.segments.push(segment.translate(&Vector2D::new(0.0, -self.tile_top))) } fn tile_bottom(&self) -> f32 { self.tile_top + TILE_HEIGHT } } // Intervals #[derive(Debug)] struct Intervals { ranges: Vec, } #[derive(Clone, Copy, Debug)] struct IntervalRange { start: f32, end: f32, winding: f32, } impl Intervals { fn new(end: f32) -> Intervals { Intervals { ranges: vec![IntervalRange::new(0.0, end, 0.0)], } } fn add(&mut self, range: IntervalRange) { self.split_at(range.start); self.split_at(range.end); // Find bracketing range. let mut start_index = 0; while range.start < self.ranges[start_index].start { start_index += 1 } let mut end_index = start_index; while range.end < self.ranges[end_index].end { end_index += 1 } // Adjust winding numbers. for existing_range in &mut self.ranges[start_index..(end_index + 1)] { existing_range.winding += range.winding } self.merge_adjacent(); } fn reset(&mut self, end: f32) { self.ranges.truncate(1); self.ranges[0] = IntervalRange::new(0.0, end, 0.0); } fn extent(&self) -> f32 { self.ranges.last().unwrap().end } fn split_at(&mut self, value: f32) { let mut range_index = 0; while range_index < self.ranges.len() { let IntervalRange { start: old_start, end: old_end, winding, } = self.ranges[range_index]; if value < old_start || value > old_end { range_index += 1; continue } self.ranges[range_index] = IntervalRange::new(old_start, value, winding); self.ranges.insert(range_index + 1, IntervalRange::new(value, old_end, winding)); return } } fn merge_adjacent(&mut self) { let mut dest_range_index = 0; let mut current_range = self.ranges[0]; for src_range_index in 1..self.ranges.len() { if self.ranges[src_range_index].winding == current_range.winding { current_range.end = self.ranges[src_range_index].end } else { self.ranges[dest_range_index] = current_range; dest_range_index += 1; current_range = self.ranges[src_range_index]; } } self.ranges[dest_range_index] = current_range; dest_range_index += 1; self.ranges.truncate(dest_range_index); } } impl IntervalRange { fn new(start: f32, end: f32, winding: f32) -> IntervalRange { IntervalRange { start, end, winding, } } fn contains(&self, value: f32) -> bool { value >= self.start && value < self.end } } #[cfg(test)] mod test { use crate::{IntervalRange, Intervals}; use quickcheck::{self, Arbitrary, Gen}; use rand::Rng; #[test] fn test_intervals() { quickcheck::quickcheck(prop_intervals as fn(Spec) -> bool); fn prop_intervals(spec: Spec) -> bool { let mut intervals = Intervals::new(spec.end); for range in spec.ranges { intervals.add(range); } assert!(intervals.ranges.len() > 0); assert_eq!(intervals.ranges[0].start, 0.0); assert_eq!(intervals.ranges.last().unwrap().end, spec.end); for prev_index in 0..(intervals.ranges.len() - 1) { let next_index = prev_index + 1; assert_eq!(intervals.ranges[prev_index].end, intervals.ranges[next_index].start); assert_ne!(intervals.ranges[prev_index].winding, intervals.ranges[next_index].winding); } true } #[derive(Clone, Debug)] struct Spec { end: f32, ranges: Vec, } impl Arbitrary for Spec { fn arbitrary(g: &mut G) -> Spec where G: Gen { const EPSILON: f32 = 0.0001; let size = g.size(); let end = g.gen_range(EPSILON, size as f32); let mut ranges = vec![]; let range_count = g.gen_range(0, size); for _ in 0..range_count { let (a, b) = (g.gen_range(0.0, end), g.gen_range(0.0, end)); let winding = g.gen_range(-(size as i32), size as i32) as f32; ranges.push(IntervalRange::new(f32::min(a, b), f32::max(a, b), winding)); } Spec { end, ranges, } } } } }