diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index 51b7369d..5f616e99 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -233,7 +233,8 @@ impl Scene { if let Some(ref fill) = path.fill { let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint)); - let path = UsvgPathToPathEvents::new(path.segments.iter().cloned()); + let path = UsvgPathToSegments::new(path.segments.iter().cloned()); + let path = SegmentsToPathEvents::new(path); let path = PathTransformingIter::new(path, &transform); let path = MonotonicConversionIter::new(path); let outline = Outline::from_path_events(path); @@ -250,7 +251,8 @@ impl Scene { let stroke_width = f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH); - let path = UsvgPathToPathEvents::new(path.segments.iter().cloned()); + let path = UsvgPathToSegments::new(path.segments.iter().cloned()); + let path = SegmentsToPathEvents::new(path); let path = PathIter::new(path); let path = StrokeToFillIter::new(path, StrokeStyle::new(stroke_width)); let path = PathTransformingIter::new(path, &transform); @@ -655,6 +657,7 @@ struct Segment { baseline: LineSegmentF32, ctrl: LineSegmentF32, kind: SegmentKind, + flags: SegmentFlags, } impl Segment { @@ -663,14 +666,34 @@ impl Segment { baseline: LineSegmentF32::default(), ctrl: LineSegmentF32::default(), kind: SegmentKind::None, + flags: SegmentFlags::empty(), } } - fn from_line(line: &LineSegmentF32) -> Segment { + fn line(line: &LineSegmentF32) -> Segment { Segment { baseline: *line, ctrl: LineSegmentF32::default(), kind: SegmentKind::Line, + flags: SegmentFlags::empty(), + } + } + + fn quadratic(baseline: &LineSegmentF32, ctrl: &Point2DF32) -> Segment { + Segment { + baseline: *baseline, + ctrl: LineSegmentF32::new(ctrl, &Point2DF32::default()), + kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), + } + } + + fn cubic(baseline: &LineSegmentF32, ctrl: &LineSegmentF32) -> Segment { + Segment { + baseline: *baseline, + ctrl: *ctrl, + kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), } } @@ -708,6 +731,7 @@ impl Segment { baseline: self.baseline.reversed(), ctrl: if self.is_quadratic_segment() { self.ctrl } else { self.ctrl.reversed() }, kind: self.kind, + flags: self.flags, } } @@ -723,6 +747,7 @@ impl Segment { } #[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] enum SegmentKind { None, Line, @@ -730,6 +755,13 @@ enum SegmentKind { Cubic, } +bitflags! { + struct SegmentFlags: u8 { + const FIRST_IN_SUBPATH = 0x01; + const CLOSES_SUBPATH = 0x02; + } +} + #[derive(Clone, Copy, Debug)] struct CubicSegment<'s>(&'s Segment); @@ -799,14 +831,17 @@ impl<'s> CubicSegment<'s> { let baseline1 = assemble(&p0123, &p0p3, 0, 1); let ctrl1 = assemble(&p012p123, &p12p23, 1, 1); + // FIXME(pcwalton): Set flags appropriately! return (Segment { baseline: LineSegmentF32(baseline0), ctrl: LineSegmentF32(ctrl0), kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), }, Segment { baseline: LineSegmentF32(baseline1), ctrl: LineSegmentF32(ctrl1), kind: SegmentKind::Cubic, + flags: SegmentFlags::empty(), }) } @@ -1700,33 +1735,200 @@ fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 { transform.e as f32, transform.f as f32) } -struct UsvgPathToPathEvents where I: Iterator { +struct UsvgPathToSegments where I: Iterator { iter: I, + first_subpath_point: Point2DF32, + last_subpath_point: Point2DF32, + just_moved: bool, } -impl UsvgPathToPathEvents where I: Iterator { - fn new(iter: I) -> UsvgPathToPathEvents { - UsvgPathToPathEvents { iter } +impl UsvgPathToSegments where I: Iterator { + fn new(iter: I) -> UsvgPathToSegments { + UsvgPathToSegments { + iter, + first_subpath_point: Point2DF32::default(), + last_subpath_point: Point2DF32::default(), + just_moved: false, + } } } -impl Iterator for UsvgPathToPathEvents where I: Iterator { +impl Iterator for UsvgPathToSegments where I: Iterator { + type Item = Segment; + + fn next(&mut self) -> Option { + match self.iter.next()? { + UsvgPathSegment::MoveTo { x, y } => { + let to = Point2DF32::new(x as f32, y as f32); + self.first_subpath_point = to; + self.last_subpath_point = to; + self.just_moved = true; + self.next() + } + UsvgPathSegment::LineTo { x, y } => { + let to = Point2DF32::new(x as f32, y as f32); + let mut segment = + Segment::line(&LineSegmentF32::new(&self.last_subpath_point, &to)); + if self.just_moved { + segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH); + } + self.last_subpath_point = to; + self.just_moved = false; + Some(segment) + } + UsvgPathSegment::CurveTo { x1, y1, x2, y2, x, y } => { + let ctrl0 = Point2DF32::new(x1 as f32, y1 as f32); + let ctrl1 = Point2DF32::new(x2 as f32, y2 as f32); + let to = Point2DF32::new(x as f32, y as f32); + let mut segment = + Segment::cubic(&LineSegmentF32::new(&self.last_subpath_point, &to), + &LineSegmentF32::new(&ctrl0, &ctrl1)); + if self.just_moved { + segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH); + } + self.last_subpath_point = to; + self.just_moved = false; + Some(segment) + } + UsvgPathSegment::ClosePath => { + let mut segment = Segment::line(&LineSegmentF32::new(&self.last_subpath_point, + &self.first_subpath_point)); + segment.flags.insert(SegmentFlags::CLOSES_SUBPATH); + self.just_moved = false; + self.last_subpath_point = self.first_subpath_point; + Some(segment) + } + } + } +} + +// Euclid interoperability +// +// TODO(pcwalton): Remove this once we're fully on Pathfinder's native geometry. + +struct PathEventsToSegments where I: Iterator { + iter: I, + first_subpath_point: Point2DF32, + last_subpath_point: Point2DF32, + just_moved: bool, +} + +impl PathEventsToSegments where I: Iterator { + fn new(iter: I) -> PathEventsToSegments { + PathEventsToSegments { + iter, + first_subpath_point: Point2DF32::default(), + last_subpath_point: Point2DF32::default(), + just_moved: false, + } + } +} + +impl Iterator for PathEventsToSegments where I: Iterator { + type Item = Segment; + + fn next(&mut self) -> Option { + match self.iter.next()? { + PathEvent::MoveTo(to) => { + let to = Point2DF32::from_euclid(to); + self.first_subpath_point = to; + self.last_subpath_point = to; + self.just_moved = true; + self.next() + } + PathEvent::LineTo(to) => { + let to = Point2DF32::from_euclid(to); + let mut segment = + Segment::line(&LineSegmentF32::new(&self.last_subpath_point, &to)); + if self.just_moved { + segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH); + } + self.last_subpath_point = to; + self.just_moved = false; + Some(segment) + } + PathEvent::QuadraticTo(ctrl, to) => { + let (ctrl, to) = (Point2DF32::from_euclid(ctrl), Point2DF32::from_euclid(to)); + let mut segment = + Segment::quadratic(&LineSegmentF32::new(&self.last_subpath_point, &to), + &ctrl); + if self.just_moved { + segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH); + } + self.last_subpath_point = to; + self.just_moved = false; + Some(segment) + } + PathEvent::CubicTo(ctrl0, ctrl1, to) => { + let ctrl0 = Point2DF32::from_euclid(ctrl0); + let ctrl1 = Point2DF32::from_euclid(ctrl1); + let to = Point2DF32::from_euclid(to); + let mut segment = + Segment::cubic(&LineSegmentF32::new(&self.last_subpath_point, &to), + &LineSegmentF32::new(&ctrl0, &ctrl1)); + if self.just_moved { + segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH); + } + self.last_subpath_point = to; + self.just_moved = false; + Some(segment) + } + PathEvent::Close => { + let mut segment = Segment::line(&LineSegmentF32::new(&self.last_subpath_point, + &self.first_subpath_point)); + segment.flags.insert(SegmentFlags::CLOSES_SUBPATH); + self.just_moved = false; + self.last_subpath_point = self.first_subpath_point; + Some(segment) + } + PathEvent::Arc(..) => panic!("TODO: arcs"), + } + } +} + +struct SegmentsToPathEvents where I: Iterator { + iter: I, + buffer: Option, +} + +impl SegmentsToPathEvents where I: Iterator { + fn new(iter: I) -> SegmentsToPathEvents { + SegmentsToPathEvents { iter, buffer: None } + } +} + +impl Iterator for SegmentsToPathEvents where I: Iterator { type Item = PathEvent; fn next(&mut self) -> Option { - match self.iter.next()? { - UsvgPathSegment::MoveTo { x, y } => { - Some(PathEvent::MoveTo(Point2D::new(x, y).to_f32())) + if let Some(event) = self.buffer.take() { + return Some(event); + } + + let segment = self.iter.next()?; + if segment.flags.contains(SegmentFlags::CLOSES_SUBPATH) { + return Some(PathEvent::Close); + } + + let event = match segment.kind { + SegmentKind::None => return self.next(), + SegmentKind::Line => PathEvent::LineTo(segment.baseline.to().as_euclid()), + SegmentKind::Quadratic => { + PathEvent::QuadraticTo(segment.ctrl.from().as_euclid(), + segment.baseline.to().as_euclid()) } - UsvgPathSegment::LineTo { x, y } => { - Some(PathEvent::LineTo(Point2D::new(x, y).to_f32())) + SegmentKind::Cubic => { + PathEvent::CubicTo(segment.ctrl.from().as_euclid(), + segment.ctrl.to().as_euclid(), + segment.baseline.to().as_euclid()) } - UsvgPathSegment::CurveTo { x1, y1, x2, y2, x, y } => { - Some(PathEvent::CubicTo(Point2D::new(x1, y1).to_f32(), - Point2D::new(x2, y2).to_f32(), - Point2D::new(x, y) .to_f32())) - } - UsvgPathSegment::ClosePath => Some(PathEvent::Close), + }; + + if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) { + self.buffer = Some(event); + Some(PathEvent::MoveTo(segment.baseline.from().as_euclid())) + } else { + Some(event) } } } @@ -1977,7 +2179,7 @@ impl ActiveEdge { if segment.is_line_segment() { let line_segment = segment.as_line_segment(); self.segment = match self.process_line_segment(&line_segment, built_object, tile_y) { - Some(lower_part) => Segment::from_line(&lower_part), + Some(lower_part) => Segment::line(&lower_part), None => Segment::none(), }; return; @@ -2005,7 +2207,7 @@ impl ActiveEdge { self.segment = match self.process_line_segment(&line_segment, built_object, tile_y) { - Some(ref lower_part) => Segment::from_line(lower_part), + Some(ref lower_part) => Segment::line(lower_part), None => Segment::none(), }; return;