2018-12-11 19:38:06 -05:00
|
|
|
// pathfinder/utils/tile-svg/main.rs
|
|
|
|
//
|
|
|
|
// Copyright © 2018 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate bitflags;
|
|
|
|
|
2018-12-12 17:55:53 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
extern crate quickcheck;
|
|
|
|
#[cfg(test)]
|
|
|
|
extern crate rand;
|
|
|
|
|
2018-12-13 17:04:17 -05:00
|
|
|
use euclid::{Point2D, Rect, Transform2D, Vector2D};
|
|
|
|
use lyon_algorithms::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment};
|
2018-12-11 19:38:06 -05:00
|
|
|
use quick_xml::Reader;
|
|
|
|
use quick_xml::events::Event;
|
2018-12-13 17:04:17 -05:00
|
|
|
use std::cmp::Ordering;
|
2018-12-11 19:38:06 -05:00
|
|
|
use std::env;
|
|
|
|
use std::mem;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::str::FromStr;
|
2018-12-13 17:04:17 -05:00
|
|
|
use std::time::Instant;
|
2018-12-11 19:38:06 -05:00
|
|
|
use svgtypes::{Color as SvgColor, PathParser, PathSegment as SvgPathSegment, TransformListParser};
|
|
|
|
use svgtypes::{TransformListToken};
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct GroupStyle {
|
|
|
|
fill_color: Option<SvgColor>,
|
|
|
|
stroke_width: Option<f32>,
|
|
|
|
stroke_color: Option<SvgColor>,
|
|
|
|
transform: Option<Transform2D<f32>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct ComputedStyle {
|
|
|
|
fill_color: Option<SvgColor>,
|
|
|
|
stroke_width: f32,
|
|
|
|
stroke_color: Option<SvgColor>,
|
|
|
|
transform: Transform2D<f32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
let start_time = Instant::now();
|
|
|
|
scene.generate_tiles();
|
|
|
|
let elapsed_time = Instant::now() - start_time;
|
|
|
|
println!("{}ms elapsed",
|
|
|
|
elapsed_time.as_secs() as f64 * 1000.0 +
|
|
|
|
elapsed_time.subsec_micros() as f64 / 1000.0);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Scene {
|
|
|
|
objects: Vec<PathObject>,
|
|
|
|
styles: Vec<ComputedStyle>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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);
|
2018-12-11 19:53:57 -05:00
|
|
|
let outline =
|
|
|
|
Outline::from_svg_path_segments(path_parser, scene.get_style(style));
|
2018-12-11 19:38:06 -05:00
|
|
|
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<f32> =
|
|
|
|
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<StyleId>, 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
|
|
|
|
}
|
2018-12-11 19:53:57 -05:00
|
|
|
|
|
|
|
fn get_style(&self, style: StyleId) -> &ComputedStyle {
|
|
|
|
&self.styles[style.0 as usize]
|
|
|
|
}
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
fn generate_tiles(&self) {
|
|
|
|
for object in &self.objects {
|
|
|
|
let mut tiler = Tiler::from_outline(&object.outline);
|
|
|
|
tiler.generate_tiles();
|
|
|
|
// TODO(pcwalton)
|
|
|
|
}
|
|
|
|
}
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PathObject {
|
|
|
|
fn new(outline: Outline, style: StyleId) -> PathObject {
|
|
|
|
PathObject {
|
|
|
|
outline,
|
|
|
|
style,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Outlines
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Outline {
|
|
|
|
contours: Vec<Contour>,
|
2018-12-13 17:04:17 -05:00
|
|
|
bounds: Rect<f32>,
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Contour {
|
|
|
|
points: Vec<Point2D<f32>>,
|
|
|
|
flags: Vec<PointFlags>,
|
|
|
|
}
|
|
|
|
|
|
|
|
bitflags! {
|
|
|
|
struct PointFlags: u8 {
|
|
|
|
const CONTROL_POINT_0 = 0x01;
|
|
|
|
const CONTROL_POINT_1 = 0x02;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Outline {
|
|
|
|
fn new() -> Outline {
|
|
|
|
Outline {
|
|
|
|
contours: vec![],
|
2018-12-13 17:04:17 -05:00
|
|
|
bounds: Rect::zero(),
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-11 19:53:57 -05:00
|
|
|
fn from_svg_path_segments<I>(segments: I, style: &ComputedStyle) -> Outline
|
|
|
|
where I: Iterator<Item = SvgPathSegment> {
|
2018-12-11 19:38:06 -05:00
|
|
|
let mut outline = Outline::new();
|
|
|
|
let mut current_contour = Contour::new();
|
2018-12-13 17:04:17 -05:00
|
|
|
let mut bounding_points = None;
|
2018-12-11 19:38:06 -05:00
|
|
|
let (mut first_point_in_path, mut last_ctrl_point, mut last_point) = (None, None, None);
|
2018-12-13 17:04:17 -05:00
|
|
|
|
2018-12-11 19:38:06 -05:00
|
|
|
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;
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
SvgPathSegment::LineTo { abs, x, y } => {
|
|
|
|
let to = compute_point(x, y, abs, &last_point);
|
|
|
|
last_point = Some(to);
|
|
|
|
last_ctrl_point = None;
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
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;
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
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;
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
SvgPathSegment::Quadratic { abs, x1, y1, x, y } => {
|
|
|
|
let ctrl = compute_point(x1, y1, abs, &last_point);
|
|
|
|
last_ctrl_point = Some(ctrl);
|
2018-12-11 19:53:57 -05:00
|
|
|
let to = compute_point(x, y, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_point = Some(to);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl,
|
|
|
|
PointFlags::CONTROL_POINT_0,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
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);
|
2018-12-11 19:53:57 -05:00
|
|
|
let to = compute_point(x, y, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_point = Some(to);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl,
|
|
|
|
PointFlags::CONTROL_POINT_0,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
SvgPathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => {
|
|
|
|
let ctrl0 = compute_point(x1, y1, abs, &last_point);
|
2018-12-11 19:53:57 -05:00
|
|
|
let ctrl1 = compute_point(x2, y2, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_ctrl_point = Some(ctrl1);
|
2018-12-11 19:53:57 -05:00
|
|
|
let to = compute_point(x, y, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_point = Some(to);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl0,
|
|
|
|
PointFlags::CONTROL_POINT_0,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl1,
|
|
|
|
PointFlags::CONTROL_POINT_1,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
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()));
|
2018-12-11 19:53:57 -05:00
|
|
|
let ctrl1 = compute_point(x2, y2, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_ctrl_point = Some(ctrl1);
|
2018-12-11 19:53:57 -05:00
|
|
|
let to = compute_point(x, y, abs, &last_point);
|
2018-12-11 19:38:06 -05:00
|
|
|
last_point = Some(to);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl0,
|
|
|
|
PointFlags::CONTROL_POINT_0,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&ctrl1,
|
|
|
|
PointFlags::CONTROL_POINT_1,
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:53:57 -05:00
|
|
|
current_contour.push_transformed_point(&to,
|
|
|
|
PointFlags::empty(),
|
2018-12-13 17:04:17 -05:00
|
|
|
&style.transform,
|
|
|
|
&mut bounding_points);
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
if let Some((upper_left, lower_right)) = bounding_points {
|
|
|
|
outline.bounds = Rect::from_points([upper_left, lower_right].into_iter())
|
|
|
|
}
|
|
|
|
|
2018-12-11 19:38:06 -05:00
|
|
|
return outline;
|
|
|
|
|
|
|
|
fn compute_point(x: f64, y: f64, abs: bool, last_point: &Option<Point2D<f32>>)
|
|
|
|
-> Point2D<f32> {
|
|
|
|
let point = Point2D::new(x, y).to_f32();
|
|
|
|
match *last_point {
|
|
|
|
Some(last_point) if !abs => last_point + point.to_vector(),
|
|
|
|
_ => point,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
fn segment_after(&self, endpoint_index: PointIndex) -> Segment {
|
|
|
|
self.contours[endpoint_index.contour_index].segment_after(endpoint_index.point_index)
|
|
|
|
}
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Contour {
|
|
|
|
fn new() -> Contour {
|
|
|
|
Contour {
|
|
|
|
points: vec![],
|
|
|
|
flags: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
self.points.is_empty()
|
|
|
|
}
|
2018-12-11 19:53:57 -05:00
|
|
|
|
|
|
|
fn push_transformed_point(&mut self,
|
|
|
|
point: &Point2D<f32>,
|
|
|
|
flags: PointFlags,
|
2018-12-13 17:04:17 -05:00
|
|
|
transform: &Transform2D<f32>,
|
|
|
|
bounding_points: &mut Option<(Point2D<f32>, Point2D<f32>)>) {
|
|
|
|
let point = transform.transform_point(point);
|
|
|
|
self.points.push(point);
|
2018-12-11 19:53:57 -05:00
|
|
|
self.flags.push(flags);
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
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 {
|
|
|
|
Line(LineSegment<f32>),
|
|
|
|
Quadratic(QuadraticBezierSegment<f32>),
|
|
|
|
Cubic(CubicBezierSegment<f32>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Segment {
|
|
|
|
fn endpoints(&self) -> (Point2D<f32>, Point2D<f32>) {
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2018-12-11 19:53:57 -05:00
|
|
|
}
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
fn translate(&self, by: &Vector2D<f32>) -> 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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ClippedSegments {
|
|
|
|
min: Option<Segment>,
|
|
|
|
max: Option<Segment>,
|
2018-12-11 19:38:06 -05:00
|
|
|
}
|
2018-12-12 17:55:53 -05:00
|
|
|
|
|
|
|
// Tiling
|
|
|
|
|
2018-12-13 17:04:17 -05:00
|
|
|
const TILE_WIDTH: f32 = 4.0;
|
|
|
|
const TILE_HEIGHT: f32 = 4.0;
|
|
|
|
|
|
|
|
struct Tiler<'a> {
|
|
|
|
outline: &'a Outline,
|
2018-12-12 17:55:53 -05:00
|
|
|
}
|
|
|
|
|
2018-12-13 17:04:17 -05:00
|
|
|
impl<'a> Tiler<'a> {
|
|
|
|
fn from_outline(outline: &Outline) -> Tiler {
|
2018-12-12 17:55:53 -05:00
|
|
|
Tiler {
|
|
|
|
outline,
|
|
|
|
}
|
|
|
|
}
|
2018-12-13 17:04:17 -05:00
|
|
|
|
|
|
|
fn generate_tiles(&mut self) {
|
|
|
|
// Sort all edge indices.
|
|
|
|
let mut sorted_edge_indices = vec![];
|
|
|
|
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) {
|
|
|
|
sorted_edge_indices.push(PointIndex { contour_index, point_index })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sorted_edge_indices.sort_by(|edge_index_a, edge_index_b| {
|
|
|
|
let segment_a = self.outline.segment_after(*edge_index_a);
|
|
|
|
let segment_b = self.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());
|
|
|
|
|
|
|
|
let mut active_intervals = Intervals::new(max_x);
|
|
|
|
let mut active_edges = vec![];
|
|
|
|
let mut next_edge_index_index = 0;
|
|
|
|
let mut tile_top = bounds.origin.y - bounds.origin.y % TILE_HEIGHT;
|
|
|
|
let mut strips = vec![];
|
|
|
|
|
|
|
|
while tile_top < max_y {
|
|
|
|
let mut strip = Strip::new(tile_top);
|
|
|
|
|
|
|
|
// TODO(pcwalton): Populate tile strip with active intervals.
|
|
|
|
|
|
|
|
for active_edge in mem::replace(&mut active_edges, vec![]) {
|
|
|
|
self.process_edge(active_edge,
|
|
|
|
&mut strip,
|
|
|
|
&mut active_edges,
|
|
|
|
&mut active_intervals);
|
|
|
|
}
|
|
|
|
|
|
|
|
while next_edge_index_index < sorted_edge_indices.len() {
|
|
|
|
let segment =
|
|
|
|
self.outline.segment_after(sorted_edge_indices[next_edge_index_index]);
|
|
|
|
if segment.min_y() > strip.tile_bottom() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
self.process_edge(segment,
|
|
|
|
&mut strip,
|
|
|
|
&mut active_edges,
|
|
|
|
&mut active_intervals);
|
|
|
|
next_edge_index_index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile_top = strip.tile_bottom();
|
|
|
|
strips.push(strip);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_edge(&mut self,
|
|
|
|
edge: Segment,
|
|
|
|
strip: &mut Strip,
|
|
|
|
active_edges: &mut Vec<Segment>,
|
|
|
|
intervals: &mut Intervals) {
|
|
|
|
let clipped = 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(intervals.extent(), from.x));
|
|
|
|
let to_x = f32::max(0.0, f32::min(intervals.extent(), to.x));
|
|
|
|
if from_x < to_x {
|
|
|
|
intervals.add(IntervalRange::new(from_x, to_x, -1.0))
|
|
|
|
} else {
|
|
|
|
intervals.add(IntervalRange::new(to_x, from_x, 1.0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(lower_segment) = clipped.max {
|
|
|
|
active_edges.push(lower_segment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strips
|
|
|
|
|
|
|
|
struct Strip {
|
|
|
|
segments: Vec<Segment>,
|
|
|
|
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
|
|
|
|
}
|
2018-12-12 17:55:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Intervals
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Intervals {
|
|
|
|
ranges: Vec<IntervalRange>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 clear(&mut self) {
|
|
|
|
let end = self.ranges.last().unwrap().end;
|
|
|
|
self.ranges.truncate(1);
|
|
|
|
self.ranges[0] = IntervalRange::new(0.0, end, 0.0);
|
|
|
|
}
|
|
|
|
|
2018-12-13 17:04:17 -05:00
|
|
|
fn extent(&self) -> f32 {
|
|
|
|
self.ranges.last().unwrap().end
|
|
|
|
}
|
|
|
|
|
2018-12-12 17:55:53 -05:00
|
|
|
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<IntervalRange>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Arbitrary for Spec {
|
|
|
|
fn arbitrary<G>(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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|