From 3e5b53f13c6618138d83f68d6aafdf226939c173 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 8 Sep 2017 13:09:00 -0700 Subject: [PATCH] Partially refactor the path APIs to be streaming, like Lyon --- .gitignore | 1 + demo/server/Cargo.toml | 4 +- demo/server/src/main.rs | 64 +++++++------- font-renderer/Cargo.toml | 7 +- font-renderer/src/lib.rs | 130 ++++++++--------------------- partitioner/Cargo.toml | 3 + partitioner/src/capi.rs | 9 +- partitioner/src/geometry.rs | 2 +- partitioner/src/lib.rs | 18 +--- partitioner/src/partitioner.rs | 15 +++- path-utils/Cargo.toml | 13 +++ path-utils/src/freetype.rs | 113 +++++++++++++++++++++++++ path-utils/src/lib.rs | 147 +++++++++++++++++++++++++++++++++ path-utils/src/stroke.rs | 11 +++ 14 files changed, 378 insertions(+), 159 deletions(-) create mode 100644 path-utils/Cargo.toml create mode 100644 path-utils/src/freetype.rs create mode 100644 path-utils/src/lib.rs create mode 100644 path-utils/src/stroke.rs diff --git a/.gitignore b/.gitignore index ee3c68e1..1275a135 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /font-renderer/target /partitioner/target +/path-utils/target /demo/client/target /demo/client/*.html /demo/client/*.js diff --git a/demo/server/Cargo.toml b/demo/server/Cargo.toml index 9237f709..30849f77 100644 --- a/demo/server/Cargo.toml +++ b/demo/server/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" authors = ["Patrick Walton "] [dependencies] -app_units = "0.5" base64 = "0.6" bincode = "0.8" env_logger = "0.3" @@ -25,3 +24,6 @@ path = "../../font-renderer" [dependencies.pathfinder_partitioner] path = "../../partitioner" + +[dependencies.pathfinder_path_utils] +path = "../../path-utils" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 6c425468..3d2e78b0 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -13,10 +13,10 @@ extern crate euclid; extern crate fontsan; extern crate pathfinder_font_renderer; extern crate pathfinder_partitioner; +extern crate pathfinder_path_utils; extern crate rocket; extern crate rocket_contrib; extern crate serde; -extern crate serde_json; #[macro_use] extern crate serde_derive; @@ -24,10 +24,9 @@ extern crate serde_derive; use app_units::Au; use bincode::Infinite; use euclid::{Point2D, Size2D, Transform2D}; -use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey}; -use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer}; +use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey}; use pathfinder_partitioner::partitioner::Partitioner; -use pathfinder_partitioner::{Endpoint, Subpath}; +use pathfinder_path_utils::{Endpoint, PathBuffer, Subpath, Transform2DPathStream}; use rocket::http::{ContentType, Status}; use rocket::request::Request; use rocket::response::{NamedFile, Redirect, Responder, Response}; @@ -394,19 +393,18 @@ fn partition_font(request: Json) } // Read glyph info. - let mut outline_buffer = GlyphOutlineBuffer::new(); + let mut path_buffer = PathBuffer::new(); let subpath_indices: Vec<_> = request.glyphs.iter().map(|glyph| { let glyph_key = GlyphKey::new(glyph.id); - let first_subpath_index = outline_buffer.subpaths.len(); + let first_subpath_index = path_buffer.subpaths.len(); // This might fail; if so, just leave it blank. - drop(font_context.push_glyph_outline(&font_instance_key, - &glyph_key, - &mut outline_buffer, - &glyph.transform)); + if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance_key, &glyph_key) { + path_buffer.add_stream(Transform2DPathStream::new(glyph_outline, &glyph.transform)) + } - let last_subpath_index = outline_buffer.subpaths.len(); + let last_subpath_index = path_buffer.subpaths.len(); SubpathRange { start: first_subpath_index as u32, @@ -416,9 +414,7 @@ fn partition_font(request: Json) // Partition the decoded glyph outlines. let mut partitioner = Partitioner::new(); - partitioner.init(&outline_buffer.endpoints, - &outline_buffer.control_points, - &outline_buffer.subpaths); + partitioner.init_with_path_buffer(&path_buffer); let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices); // Package up other miscellaneous glyph info. @@ -458,34 +454,34 @@ fn partition_svg_paths(request: Json) // // The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z` // commands. - let (mut endpoints, mut control_points, mut subpaths) = (vec![], vec![], vec![]); + let mut path_buffer = PathBuffer::new(); let mut paths = vec![]; for path in &request.paths { - let first_subpath_index = subpaths.len() as u32; + let first_subpath_index = path_buffer.subpaths.len() as u32; - let mut first_endpoint_index_in_subpath = endpoints.len(); + let mut first_endpoint_index_in_subpath = path_buffer.endpoints.len(); for segment in &path.segments { match segment.kind { 'M' => { - if first_endpoint_index_in_subpath < endpoints.len() { - subpaths.push(Subpath { + if first_endpoint_index_in_subpath < path_buffer.endpoints.len() { + path_buffer.subpaths.push(Subpath { first_endpoint_index: first_endpoint_index_in_subpath as u32, - last_endpoint_index: endpoints.len() as u32, + last_endpoint_index: path_buffer.endpoints.len() as u32, }); - first_endpoint_index_in_subpath = endpoints.len(); + first_endpoint_index_in_subpath = path_buffer.endpoints.len(); } - endpoints.push(Endpoint { + path_buffer.endpoints.push(Endpoint { position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32), control_point_index: u32::MAX, - subpath_index: subpaths.len() as u32, + subpath_index: path_buffer.subpaths.len() as u32, }) } 'L' => { - endpoints.push(Endpoint { + path_buffer.endpoints.push(Endpoint { position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32), control_point_index: u32::MAX, - subpath_index: subpaths.len() as u32, + subpath_index: path_buffer.subpaths.len() as u32, }) } 'C' => { @@ -495,25 +491,25 @@ fn partition_svg_paths(request: Json) let control_point_1 = Point2D::new(segment.values[2] as f32, segment.values[3] as f32); let control_point = control_point_0.lerp(control_point_1, 0.5); - endpoints.push(Endpoint { + path_buffer.endpoints.push(Endpoint { position: Point2D::new(segment.values[4] as f32, segment.values[5] as f32), - control_point_index: control_points.len() as u32, - subpath_index: subpaths.len() as u32, + control_point_index: path_buffer.control_points.len() as u32, + subpath_index: path_buffer.subpaths.len() as u32, }); - control_points.push(control_point); + path_buffer.control_points.push(control_point); } 'Z' => { - subpaths.push(Subpath { + path_buffer.subpaths.push(Subpath { first_endpoint_index: first_endpoint_index_in_subpath as u32, - last_endpoint_index: endpoints.len() as u32, + last_endpoint_index: path_buffer.endpoints.len() as u32, }); - first_endpoint_index_in_subpath = endpoints.len(); + first_endpoint_index_in_subpath = path_buffer.endpoints.len(); } _ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)), } } - let last_subpath_index = subpaths.len() as u32; + let last_subpath_index = path_buffer.subpaths.len() as u32; paths.push(SubpathRange { start: first_subpath_index, end: last_subpath_index, @@ -522,7 +518,7 @@ fn partition_svg_paths(request: Json) // Partition the paths. let mut partitioner = Partitioner::new(); - partitioner.init(&endpoints, &control_points, &subpaths); + partitioner.init_with_path_buffer(&path_buffer); let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &paths); // Return the response. diff --git a/font-renderer/Cargo.toml b/font-renderer/Cargo.toml index b8f37586..b3486faa 100644 --- a/font-renderer/Cargo.toml +++ b/font-renderer/Cargo.toml @@ -10,10 +10,11 @@ log = "0.3" [dependencies.freetype-sys] git = "https://github.com/pcwalton/freetype-sys.git" -branch = "outline-get-cbox" +branch = "stroker" +rev = "11c67b2a215ec5d1aefd465a9b0560b9784ca672" -[dependencies.pathfinder_partitioner] -path = "../partitioner" +[dependencies.pathfinder_path_utils] +path = "../path-utils" [dev-dependencies] env_logger = "0.4" diff --git a/font-renderer/src/lib.rs b/font-renderer/src/lib.rs index dc61a9c2..0b1d1d9d 100644 --- a/font-renderer/src/lib.rs +++ b/font-renderer/src/lib.rs @@ -3,7 +3,7 @@ extern crate app_units; extern crate euclid; extern crate freetype_sys; -extern crate pathfinder_partitioner; +extern crate pathfinder_path_utils; #[allow(unused_imports)] #[macro_use] @@ -13,14 +13,16 @@ extern crate log; extern crate env_logger; use app_units::Au; -use euclid::{Point2D, Size2D, Transform2D}; +use euclid::{Point2D, Size2D}; use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE}; use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_NO_HINTING, FT_Library}; use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox}; use freetype_sys::{FT_Set_Char_Size, FT_UInt}; -use pathfinder_partitioner::{Endpoint, Subpath}; +use pathfinder_path_utils::PathSegment; +use pathfinder_path_utils::freetype::OutlineStream; use std::collections::BTreeMap; use std::collections::btree_map::Entry; +use std::marker::PhantomData; use std::mem; use std::ptr; use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering}; @@ -33,8 +35,6 @@ mod tests; // TODO(pcwalton): Make this configurable. const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING; -const FREETYPE_POINT_ON_CURVE: i8 = 0x01; - const DPI: u32 = 72; pub struct FontContext { @@ -92,20 +92,33 @@ impl FontContext { }) } + pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) + -> Result, ()> { + self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { + unsafe { + GlyphOutline { + stream: OutlineStream::new(&(*glyph_slot).outline), + phantom: PhantomData, + } + } + }) + } + +/* pub fn push_glyph_outline(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey, - glyph_outline_buffer: &mut GlyphOutlineBuffer, - transform: &Transform2D) + path_buffer: &mut PathBuffer) -> Result<(), ()> { self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { - self.push_glyph_outline_from_glyph_slot(font_instance, - glyph_key, - glyph_slot, - glyph_outline_buffer, - transform) + unsafe { + let outline = &(*glyph_slot).outline; + for contour_index in 0..outline.n_contours as u16 { + path_buffer.add_subpath_from_stream(OutlineIter::new(outline, contour_index)) + } + } }) - } + }*/ fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey) -> Option { @@ -174,73 +187,17 @@ impl FontContext { bounding_box } +} - fn push_glyph_outline_from_glyph_slot(&self, - _: &FontInstanceKey, - _: &GlyphKey, - glyph_slot: FT_GlyphSlot, - glyph_outline_buffer: &mut GlyphOutlineBuffer, - transform: &Transform2D) { - unsafe { - let outline = &(*glyph_slot).outline; - let mut first_point_index = 0 as u32; - let mut first_endpoint_index = glyph_outline_buffer.endpoints.len() as u32; - for contour_index in 0..outline.n_contours as usize { - let current_subpath_index = glyph_outline_buffer.subpaths.len() as u32; - let mut current_control_point_index = None; - let last_point_index = *outline.contours.offset(contour_index as isize) as u32 + 1; - for point_index in first_point_index..last_point_index { - // TODO(pcwalton): Approximate cubic Béziers with quadratics. - let point = *outline.points.offset(point_index as isize); - let point_position = Point2D::new(f32::from_ft_f26dot6(point.x as FT_F26Dot6), - f32::from_ft_f26dot6(point.y as FT_F26Dot6)); - let point_position = point_position * (DPI as f32); - let point_position = transform.transform_point(&point_position); - if (*outline.tags.offset(point_index as isize) & FREETYPE_POINT_ON_CURVE) != 0 { - glyph_outline_buffer.endpoints.push(Endpoint { - position: point_position, - control_point_index: current_control_point_index.take().unwrap_or(!0), - subpath_index: current_subpath_index, - }); - continue - } +pub struct GlyphOutline<'a> { + stream: OutlineStream<'static>, + phantom: PhantomData<&'a ()>, +} - // Add an implied endpoint if necessary. - let mut control_points = &mut glyph_outline_buffer.control_points; - if let Some(prev_control_point_index) = current_control_point_index.take() { - let prev_control_point_position = - control_points[prev_control_point_index as usize]; - glyph_outline_buffer.endpoints.push(Endpoint { - position: prev_control_point_position.lerp(point_position, 0.5), - control_point_index: prev_control_point_index, - subpath_index: current_subpath_index, - }) - } - - current_control_point_index = Some(control_points.len() as u32); - control_points.push(point_position) - } - - if let Some(last_control_point_index) = current_control_point_index.take() { - let first_endpoint = glyph_outline_buffer.endpoints[first_endpoint_index as - usize]; - glyph_outline_buffer.endpoints.push(Endpoint { - position: first_endpoint.position, - control_point_index: last_control_point_index, - subpath_index: current_subpath_index, - }) - } - - let last_endpoint_index = glyph_outline_buffer.endpoints.len() as u32; - glyph_outline_buffer.subpaths.push(Subpath { - first_endpoint_index: first_endpoint_index, - last_endpoint_index: last_endpoint_index, - }); - - first_endpoint_index = last_endpoint_index; - first_point_index = last_point_index; - } - } +impl<'a> Iterator for GlyphOutline<'a> { + type Item = PathSegment; + fn next(&mut self) -> Option { + self.stream.next() } } @@ -297,23 +254,6 @@ pub struct GlyphDimensions { pub advance: f32, } -pub struct GlyphOutlineBuffer { - pub endpoints: Vec, - pub control_points: Vec>, - pub subpaths: Vec, -} - -impl GlyphOutlineBuffer { - #[inline] - pub fn new() -> GlyphOutlineBuffer { - GlyphOutlineBuffer { - endpoints: vec![], - control_points: vec![], - subpaths: vec![], - } - } -} - struct Face { face: FT_Face, bytes: Vec, diff --git a/partitioner/Cargo.toml b/partitioner/Cargo.toml index 7671c436..386786a2 100644 --- a/partitioner/Cargo.toml +++ b/partitioner/Cargo.toml @@ -15,3 +15,6 @@ half = "1.0" log = "0.3" serde = "1.0" serde_derive = "1.0" + +[dependencies.pathfinder_path_utils] +path = "../path-utils" diff --git a/partitioner/src/capi.rs b/partitioner/src/capi.rs index 06c18f81..0033519e 100644 --- a/partitioner/src/capi.rs +++ b/partitioner/src/capi.rs @@ -157,11 +157,10 @@ pub unsafe extern fn pf_partitioner_init<'a>(partitioner: *mut Partitioner<'a>, control_point_count: u32, subpaths: *const Subpath, subpath_count: u32) { - // FIXME(pcwalton): This is unsafe! `Point2D` and `Point2DF32` may have different layouts! - (*partitioner).init(slice::from_raw_parts(endpoints, endpoint_count as usize), - slice::from_raw_parts(control_points as *const Point2D, - control_point_count as usize), - slice::from_raw_parts(subpaths, subpath_count as usize)) + (*partitioner).init_with_raw_data(slice::from_raw_parts(endpoints, endpoint_count as usize), + slice::from_raw_parts(control_points as *const Point2D, + control_point_count as usize), + slice::from_raw_parts(subpaths, subpath_count as usize)) } #[no_mangle] diff --git a/partitioner/src/geometry.rs b/partitioner/src/geometry.rs index 2c2fe998..759937f1 100644 --- a/partitioner/src/geometry.rs +++ b/partitioner/src/geometry.rs @@ -8,8 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use euclid::Point2D; use euclid::approxeq::ApproxEq; -use euclid::{Point2D, Vector2D}; use std::cmp::Ordering; pub(crate) trait ApproxOrdered { diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index 016b477d..aff8a737 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -18,11 +18,13 @@ extern crate euclid; extern crate half; #[macro_use] extern crate log; +extern crate pathfinder_path_utils; extern crate serde; #[macro_use] extern crate serde_derive; use euclid::Point2D; +use pathfinder_path_utils::{Endpoint, Subpath}; use std::u32; pub mod capi; @@ -79,22 +81,6 @@ impl BQuad { } } -#[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Endpoint { - pub position: Point2D, - /// `u32::MAX` if not present. - pub control_point_index: u32, - pub subpath_index: u32, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Subpath { - pub first_endpoint_index: u32, - pub last_endpoint_index: u32, -} - #[derive(Clone, Copy, PartialEq, Debug)] #[repr(u8)] pub enum AntialiasingMode { diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index 3578d93a..1e09a89e 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -12,6 +12,7 @@ use bit_vec::BitVec; use euclid::Point2D; use geometry::{self, SubdividedQuadraticBezier}; use log::LogLevel; +use pathfinder_path_utils::PathBuffer; use std::collections::BinaryHeap; use std::cmp::Ordering; use std::f32; @@ -59,10 +60,10 @@ impl<'a> Partitioner<'a> { } } - pub fn init(&mut self, - new_endpoints: &'a [Endpoint], - new_control_points: &'a [Point2D], - new_subpaths: &'a [Subpath]) { + pub fn init_with_raw_data(&mut self, + new_endpoints: &'a [Endpoint], + new_control_points: &'a [Point2D], + new_subpaths: &'a [Subpath]) { self.endpoints = new_endpoints; self.control_points = new_control_points; self.subpaths = new_subpaths; @@ -72,6 +73,12 @@ impl<'a> Partitioner<'a> { self.visited_points = BitVec::from_elem(self.endpoints.len(), false); } + pub fn init_with_path_buffer(&mut self, path_buffer: &'a PathBuffer) { + self.init_with_raw_data(&path_buffer.endpoints, + &path_buffer.control_points, + &path_buffer.subpaths) + } + pub fn partition(&mut self, path_id: u16, first_subpath_index: u32, last_subpath_index: u32) { self.b_quads.clear(); self.b_vertex_loop_blinn_data.clear(); diff --git a/path-utils/Cargo.toml b/path-utils/Cargo.toml new file mode 100644 index 00000000..4420dd1c --- /dev/null +++ b/path-utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pathfinder_path_utils" +version = "0.1.0" +authors = ["Patrick Walton "] + +[dependencies] +euclid = "0.15" +serde = "1.0" +serde_derive = "1.0" + +[dependencies.freetype-sys] +git = "https://github.com/pcwalton/freetype-sys.git" +branch = "stroker" diff --git a/path-utils/src/freetype.rs b/path-utils/src/freetype.rs new file mode 100644 index 00000000..dee9e66a --- /dev/null +++ b/path-utils/src/freetype.rs @@ -0,0 +1,113 @@ +// pathfinder/path-utils/src/freetype.rs +// +// Copyright © 2017 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. + +use euclid::Point2D; +use freetype_sys::{FT_Outline, FT_Vector}; + +use PathSegment; + +const FREETYPE_POINT_ON_CURVE: i8 = 0x01; + +const DPI: f32 = 72.0; + +pub struct OutlineStream<'a> { + outline: &'a FT_Outline, + point_index: u16, + contour_index: u16, + first_position_of_subpath: Point2D, + first_point_index_of_contour: bool, +} + +impl<'a> OutlineStream<'a> { + #[inline] + pub unsafe fn new(outline: &FT_Outline) -> OutlineStream { + OutlineStream { + outline: outline, + point_index: 0, + contour_index: 0, + first_position_of_subpath: Point2D::zero(), + first_point_index_of_contour: true, + } + } + + #[inline] + fn current_position_and_tag(&self) -> (Point2D, i8) { + unsafe { + let point_offset = self.point_index as isize; + let position = ft_vector_to_f32(*self.outline.points.offset(point_offset)); + let tag = *self.outline.tags.offset(point_offset); + (position * DPI, tag) + } + } +} + +impl<'a> Iterator for OutlineStream<'a> { + type Item = PathSegment; + + fn next(&mut self) -> Option { + unsafe { + let mut control_point_position: Option> = None; + loop { + if self.contour_index == self.outline.n_contours as u16 { + return None + } + + let last_point_index_in_current_contour = + *self.outline.contours.offset(self.contour_index as isize) as u16; + if self.point_index == last_point_index_in_current_contour + 1 { + if let Some(control_point_position) = control_point_position { + return Some(PathSegment::CurveTo(control_point_position, + self.first_position_of_subpath)) + } + + self.contour_index += 1; + self.first_point_index_of_contour = true; + return Some(PathSegment::ClosePath) + } + + // FIXME(pcwalton): Approximate cubic curves with quadratics. + let (position, tag) = self.current_position_and_tag(); + let point_on_curve = (tag & FREETYPE_POINT_ON_CURVE) != 0; + + if self.first_point_index_of_contour { + self.first_point_index_of_contour = false; + self.first_position_of_subpath = position; + self.point_index += 1; + return Some(PathSegment::MoveTo(position)); + } + + match (control_point_position, point_on_curve) { + (Some(control_point_position), false) => { + let on_curve_position = control_point_position.lerp(position, 0.5); + return Some(PathSegment::CurveTo(control_point_position, + on_curve_position)) + } + (Some(control_point_position), true) => { + self.point_index += 1; + return Some(PathSegment::CurveTo(control_point_position, position)) + } + (None, false) => { + self.point_index += 1; + control_point_position = Some(position); + } + (None, true) => { + self.point_index += 1; + return Some(PathSegment::LineTo(position)) + } + } + } + } + } +} + +#[inline] +fn ft_vector_to_f32(ft_vector: FT_Vector) -> Point2D { + Point2D::new(ft_vector.x as f32 / 64.0, ft_vector.y as f32 / 64.0) +} diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs new file mode 100644 index 00000000..7e3bfba3 --- /dev/null +++ b/path-utils/src/lib.rs @@ -0,0 +1,147 @@ +// pathfinder/path-utils/src/lib.rs +// +// Copyright © 2017 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. + +extern crate euclid; +extern crate freetype_sys; +#[macro_use] +extern crate serde_derive; + +use euclid::{Point2D, Transform2D}; +use std::u32; + +pub mod freetype; +pub mod stroke; + +#[derive(Clone, Copy, Debug)] +pub enum PathSegment { + MoveTo(Point2D), + LineTo(Point2D), + CurveTo(Point2D, Point2D), + ClosePath, +} + +#[derive(Clone, Debug)] +pub struct PathBuffer { + pub endpoints: Vec, + pub control_points: Vec>, + pub subpaths: Vec, +} + +impl PathBuffer { + #[inline] + pub fn new() -> PathBuffer { + PathBuffer { + endpoints: vec![], + control_points: vec![], + subpaths: vec![], + } + } + + pub fn add_stream(&mut self, stream: I) where I: Iterator { + let mut first_subpath_endpoint_index = self.endpoints.len() as u32; + for segment in stream { + match segment { + PathSegment::ClosePath => self.close_subpath(&mut first_subpath_endpoint_index), + + PathSegment::MoveTo(position) => { + self.close_subpath(&mut first_subpath_endpoint_index); + self.endpoints.push(Endpoint { + position: position, + control_point_index: u32::MAX, + subpath_index: self.subpaths.len() as u32, + }) + } + + PathSegment::LineTo(position) => { + self.endpoints.push(Endpoint { + position: position, + control_point_index: u32::MAX, + subpath_index: self.subpaths.len() as u32, + }) + } + + PathSegment::CurveTo(control_point_position, endpoint_position) => { + let control_point_index = self.control_points.len() as u32; + self.control_points.push(control_point_position); + self.endpoints.push(Endpoint { + position: endpoint_position, + control_point_index: control_point_index, + subpath_index: self.subpaths.len() as u32, + }) + } + } + } + + self.close_subpath(&mut first_subpath_endpoint_index) + } + + fn close_subpath(&mut self, first_subpath_endpoint_index: &mut u32) { + let last_subpath_endpoint_index = self.endpoints.len() as u32; + if *first_subpath_endpoint_index != last_subpath_endpoint_index { + self.subpaths.push(Subpath { + first_endpoint_index: *first_subpath_endpoint_index, + last_endpoint_index: last_subpath_endpoint_index, + }) + } + + *first_subpath_endpoint_index = last_subpath_endpoint_index; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Endpoint { + pub position: Point2D, + /// `u32::MAX` if not present. + pub control_point_index: u32, + pub subpath_index: u32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Subpath { + pub first_endpoint_index: u32, + pub last_endpoint_index: u32, +} + +pub struct Transform2DPathStream { + inner: I, + transform: Transform2D, +} + +impl Transform2DPathStream where I: Iterator { + pub fn new(inner: I, transform: &Transform2D) -> Transform2DPathStream { + Transform2DPathStream { + inner: inner, + transform: *transform, + } + } +} + +impl Iterator for Transform2DPathStream where I: Iterator { + type Item = PathSegment; + + fn next(&mut self) -> Option { + match self.inner.next() { + None => None, + Some(PathSegment::MoveTo(position)) => { + Some(PathSegment::MoveTo(self.transform.transform_point(&position))) + } + Some(PathSegment::LineTo(position)) => { + Some(PathSegment::LineTo(self.transform.transform_point(&position))) + } + Some(PathSegment::CurveTo(control_point_position, endpoint_position)) => { + Some(PathSegment::CurveTo(self.transform.transform_point(&control_point_position), + self.transform.transform_point(&endpoint_position))) + } + Some(PathSegment::ClosePath) => Some(PathSegment::ClosePath), + } + } +} diff --git a/path-utils/src/stroke.rs b/path-utils/src/stroke.rs new file mode 100644 index 00000000..a22c8123 --- /dev/null +++ b/path-utils/src/stroke.rs @@ -0,0 +1,11 @@ +// pathfinder/path-utils/src/stroke.rs +// +// Copyright © 2017 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. + +// TODO(pcwalton)