diff --git a/demo/client/src/camera.ts b/demo/client/src/camera.ts index 6e1d6116..03784c78 100644 --- a/demo/client/src/camera.ts +++ b/demo/client/src/camera.ts @@ -135,9 +135,11 @@ export class OrthographicCamera extends Camera { this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false); this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false); this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false); - unwrapNull(this.canvas.classList).add('pf-draggable'); + if (this.canvas.classList != null) + this.canvas.classList.add('pf-draggable'); } else { - unwrapNull(this.canvas.classList).remove('pf-draggable'); + if (this.canvas.classList != null) + this.canvas.classList.remove('pf-draggable'); } this.onPan = null; diff --git a/demo/server/Cargo.toml b/demo/server/Cargo.toml index 7358289e..8066e20b 100644 --- a/demo/server/Cargo.toml +++ b/demo/server/Cargo.toml @@ -12,11 +12,13 @@ app_units = "0.5" base64 = "0.6" bincode = "0.8" env_logger = "0.4" -euclid = "0.15" +euclid = "0.16" image = "0.17" lazy_static = "0.2" log = "0.3" lru-cache = "0.1" +lyon_geom = "0.9" +lyon_path = "0.9" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index b1effa52..7ac26997 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -18,6 +18,8 @@ extern crate euclid; extern crate fontsan; extern crate image; extern crate lru_cache; +extern crate lyon_geom; +extern crate lyon_path; extern crate pathfinder_font_renderer; extern crate pathfinder_partitioner; extern crate pathfinder_path_utils; @@ -38,23 +40,23 @@ use app_units::Au; use euclid::{Point2D, Transform2D}; use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8}; use lru_cache::LruCache; +use lyon_path::PathEvent; +use lyon_path::builder::{FlatPathBuilder, PathBuilder}; +use lyon_path::iterator::PathIter; use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphImage}; use pathfinder_font_renderer::{GlyphKey, SubpixelOffset}; use pathfinder_partitioner::FillRule; use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::partitioner::Partitioner; -use pathfinder_path_utils::cubic::CubicCurve; -use pathfinder_path_utils::monotonic::MonotonicPathCommandStream; -use pathfinder_path_utils::stroke::Stroke; -use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathCommand, Transform2DPathStream}; +use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter}; +use pathfinder_path_utils::transform::Transform2DPathIter; use rocket::http::{ContentType, Header, Status}; use rocket::request::Request; use rocket::response::{NamedFile, Redirect, Responder, Response}; use rocket_contrib::json::Json; use std::fs::File; use std::io::{self, Cursor, Read}; -use std::ops::Range; -use std::path::{Path, PathBuf}; +use std::path::{self, PathBuf}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use std::u32; @@ -71,8 +73,6 @@ use rsvg::{Handle, HandleExt}; const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024; -const CUBIC_ERROR_TOLERANCE: f32 = 0.1; - const MESH_LIBRARY_CACHE_SIZE: usize = 16; lazy_static! { @@ -246,7 +246,7 @@ impl PartitionSvgFillRule { #[derive(Clone)] struct PathDescriptor { - subpath_indices: Range, + path_index: usize, fill_rule: FillRule, } @@ -263,15 +263,17 @@ struct PathPartitioningResult { } impl PathPartitioningResult { - fn compute(partitioner: &mut Partitioner, path_descriptors: &[PathDescriptor]) + fn compute(partitioner: &mut Partitioner, + path_descriptors: &[PathDescriptor], + paths: &[Vec]) -> PathPartitioningResult { let timestamp_before = Instant::now(); - for (path_index, path_descriptor) in path_descriptors.iter().enumerate() { - partitioner.set_fill_rule(path_descriptor.fill_rule); - partitioner.partition((path_index + 1) as u16, - path_descriptor.subpath_indices.start, - path_descriptor.subpath_indices.end); + for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) { + path.iter().for_each(|event| partitioner.builder_mut().path_event(*event)); + partitioner.partition((path_descriptor.path_index + 1) as u16, + path_descriptor.fill_rule); + partitioner.builder_mut().build_and_reset(); } partitioner.library_mut().optimize(); @@ -444,42 +446,40 @@ fn partition_font(request: Json) -> Result = request.glyphs.iter().map(|glyph| { + let mut paths: Vec> = vec![]; + let mut path_descriptors = vec![]; + + for (glyph_index, glyph) in request.glyphs.iter().enumerate() { let glyph_key = GlyphKey::new(glyph.id, SubpixelOffset(0)); - let first_subpath_index = path_buffer.subpaths.len(); - // This might fail; if so, just leave it blank. - if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) { - let stream = Transform2DPathStream::new(glyph_outline.into_iter(), &glyph.transform); - let stream = MonotonicPathCommandStream::new(stream); - path_buffer.add_stream(stream) - } + match font_context.glyph_outline(&font_instance, &glyph_key) { + Ok(glyph_outline) => { + paths.push(Transform2DPathIter::new(glyph_outline.iter(), + &glyph.transform).collect()) + } + Err(_) => continue, + }; - let last_subpath_index = path_buffer.subpaths.len(); - - PathDescriptor { - subpath_indices: (first_subpath_index as u32)..(last_subpath_index as u32), + path_descriptors.push(PathDescriptor { + path_index: glyph_index, fill_rule: FillRule::Winding, - } - }).collect(); + }) + } // Partition the decoded glyph outlines. let mut library = MeshLibrary::new(); - for (path_index, path_descriptor) in path_descriptors.iter().enumerate() { - let stream = PathBufferStream::subpath_range(&path_buffer, - path_descriptor.subpath_indices.clone()); - library.push_segments((path_index + 1) as u16, stream); - let stream = PathBufferStream::subpath_range(&path_buffer, - path_descriptor.subpath_indices.clone()); - library.push_normals(stream); + for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() { + library.push_segments((path_descriptor.path_index + 1) as u16, + PathIter::new(paths[stored_path_index].iter().cloned())); + library.push_normals((path_descriptor.path_index + 1) as u16, + PathIter::new(paths[stored_path_index].iter().cloned())); } let mut partitioner = Partitioner::new(library); - partitioner.init_with_path_buffer(&path_buffer); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, - &path_descriptors); + &path_descriptors, + &paths); // Build the response. let elapsed_ms = path_partitioning_result.elapsed_ms(); @@ -504,79 +504,64 @@ 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 path_buffer = PathBuffer::new(); let mut paths = vec![]; - let mut last_point = Point2D::zero(); - let mut library = MeshLibrary::new(); + let mut path_descriptors = vec![]; + let mut partitioner = Partitioner::new(MeshLibrary::new()); + let mut path_index = 0; - for (path_index, path) in request.paths.iter().enumerate() { + for path in &request.paths { let mut stream = vec![]; - let first_subpath_index = path_buffer.subpaths.len() as u32; - for segment in &path.segments { match segment.kind { 'M' => { - last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); - stream.push(PathCommand::MoveTo(last_point)) + stream.push(PathEvent::MoveTo(Point2D::new(segment.values[0] as f32, + segment.values[1] as f32))) } 'L' => { - last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); - stream.push(PathCommand::LineTo(last_point)) + stream.push(PathEvent::LineTo(Point2D::new(segment.values[0] as f32, + segment.values[1] as f32))) } 'C' => { - let control_point_0 = Point2D::new(segment.values[0] as f32, - segment.values[1] as f32); - let control_point_1 = Point2D::new(segment.values[2] as f32, - segment.values[3] as f32); - let endpoint_1 = Point2D::new(segment.values[4] as f32, - segment.values[5] as f32); - let cubic = CubicCurve::new(&last_point, - &control_point_0, - &control_point_1, - &endpoint_1); - last_point = endpoint_1; - stream.extend(cubic.approx_curve(CUBIC_ERROR_TOLERANCE) - .map(|curve| curve.to_path_command())); + stream.push(PathEvent::CubicTo(Point2D::new(segment.values[0] as f32, + segment.values[1] as f32), + Point2D::new(segment.values[2] as f32, + segment.values[3] as f32), + Point2D::new(segment.values[4] as f32, + segment.values[5] as f32))) } - 'Z' => stream.push(PathCommand::ClosePath), + 'Z' => stream.push(PathEvent::Close), _ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType), } } let fill_rule = match path.kind { - PartitionSvgPathKind::Fill(fill_rule) => { - let stream = MonotonicPathCommandStream::new(stream.into_iter()); - library.push_segments((path_index + 1) as u16, stream.clone()); - path_buffer.add_stream(stream); - - fill_rule.to_fill_rule() - } - PartitionSvgPathKind::Stroke(stroke_width) => { - let mut temp_path_buffer = PathBuffer::new(); - Stroke::new(stroke_width).apply(&mut temp_path_buffer, stream.into_iter()); - - let stream = PathBufferStream::new(&temp_path_buffer); - let stream = MonotonicPathCommandStream::new(stream); - library.push_segments((path_index + 1) as u16, stream.clone()); - path_buffer.add_stream(stream); - - FillRule::Winding - } + PartitionSvgPathKind::Fill(fill_rule) => fill_rule.to_fill_rule(), + PartitionSvgPathKind::Stroke(_) => FillRule::Winding, }; - let last_subpath_index = path_buffer.subpaths.len() as u32; - - paths.push(PathDescriptor { - subpath_indices: first_subpath_index..last_subpath_index, + path_descriptors.push(PathDescriptor { + path_index: path_index, fill_rule: fill_rule, - }) + }); + + match path.kind { + PartitionSvgPathKind::Fill(_) => paths.push(stream), + PartitionSvgPathKind::Stroke(stroke_width) => { + let iterator = PathIter::new(stream.into_iter()); + let stroke_style = StrokeStyle::new(stroke_width); + let path: Vec<_> = StrokeToFillIter::new(iterator, stroke_style).collect(); + paths.push(path); + } + } + + path_index += 1; } // Partition the paths. - let mut partitioner = Partitioner::new(library); - partitioner.init_with_path_buffer(&path_buffer); - let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths); + let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, + &path_descriptors, + &paths); // Return the response. let elapsed_ms = path_partitioning_result.elapsed_ms(); @@ -710,69 +695,69 @@ fn static_doc_api_index() -> Redirect { } #[get("/doc/api/")] fn static_doc_api(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_DOC_API_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_DOC_API_PATH).join(file)).ok() } #[get("/css/bootstrap/")] fn static_css_bootstrap(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_CSS_BOOTSTRAP_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_CSS_BOOTSTRAP_PATH).join(file)).ok() } #[get("/css/")] fn static_css(file: String) -> Option { - NamedFile::open(Path::new(STATIC_CSS_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_CSS_PATH).join(file)).ok() } #[get("/js/bootstrap/")] fn static_js_bootstrap(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_JS_BOOTSTRAP_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_JS_BOOTSTRAP_PATH).join(file)).ok() } #[get("/js/jquery/")] fn static_js_jquery(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok() } #[get("/js/popper.js/")] fn static_js_popper_js(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_JS_POPPER_JS_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_JS_POPPER_JS_PATH).join(file)).ok() } #[get("/js/pathfinder/")] fn static_js_pathfinder(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_JS_PATHFINDER_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_JS_PATHFINDER_PATH).join(file)).ok() } #[get("/woff2/inter-ui/")] fn static_woff2_inter_ui(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_WOFF2_INTER_UI_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_WOFF2_INTER_UI_PATH).join(file)).ok() } #[get("/woff2/material-icons/")] fn static_woff2_material_icons(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_WOFF2_MATERIAL_ICONS_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_WOFF2_MATERIAL_ICONS_PATH).join(file)).ok() } #[get("/glsl/")] fn static_glsl(file: PathBuf) -> Option { - Shader::open(Path::new(STATIC_GLSL_PATH).join(file)).ok() + Shader::open(path::Path::new(STATIC_GLSL_PATH).join(file)).ok() } #[get("/otf/demo/")] fn static_otf_demo(font_name: String) -> Option { BUILTIN_FONTS.iter() .filter(|& &(name, _)| name == font_name) .next() - .and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok()) + .and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok()) } #[get("/svg/demo/")] fn static_svg_demo(svg_name: String) -> Option { BUILTIN_SVGS.iter() .filter(|& &(name, _)| name == svg_name) .next() - .and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok()) + .and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok()) } #[get("/data/")] fn static_data(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_DATA_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_DATA_PATH).join(file)).ok() } #[get("/test-data/")] fn static_test_data(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_TEST_DATA_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_TEST_DATA_PATH).join(file)).ok() } #[get("/textures/")] fn static_textures(file: PathBuf) -> Option { - NamedFile::open(Path::new(STATIC_TEXTURES_PATH).join(file)).ok() + NamedFile::open(path::Path::new(STATIC_TEXTURES_PATH).join(file)).ok() } struct Shader { diff --git a/font-renderer/Cargo.toml b/font-renderer/Cargo.toml index 5f410819..6e663933 100644 --- a/font-renderer/Cargo.toml +++ b/font-renderer/Cargo.toml @@ -9,9 +9,11 @@ freetype = ["freetype-sys"] [dependencies] app_units = "0.5" -euclid = "0.15" +euclid = "0.16" libc = "0.2" log = "0.3" +lyon_geom = "0.9" +lyon_path = "0.9" serde = "1.0" serde_derive = "1.0" @@ -19,9 +21,6 @@ serde_derive = "1.0" version = "0.6" optional = true -[dependencies.pathfinder_path_utils] -path = "../path-utils" - [target.'cfg(not(target_os = "macos"))'.dependencies] freetype-sys = "0.6" diff --git a/font-renderer/src/core_graphics.rs b/font-renderer/src/core_graphics.rs index cbf2848f..7ebcb689 100644 --- a/font-renderer/src/core_graphics.rs +++ b/font-renderer/src/core_graphics.rs @@ -21,10 +21,11 @@ use core_graphics_sys::path::CGPathElementType; use core_text::font::CTFont; use core_text; use euclid::{Point2D, Rect, Size2D, Vector2D}; -use pathfinder_path_utils::cubic::{CubicPathCommand, CubicPathCommandApproxStream}; -use pathfinder_path_utils::PathCommand; +use lyon_path::PathEvent; use std::collections::BTreeMap; use std::collections::btree_map::Entry; +use std::iter::Cloned; +use std::slice::Iter; use std::sync::Arc; use {FontInstance, FontKey, GlyphDimensions, GlyphImage, GlyphKey}; @@ -36,8 +37,6 @@ const CG_ZERO_RECT: CGRect = CGRect { }, }; -const CURVE_APPROX_ERROR_BOUND: f32 = 0.1; - // A conservative overestimate of the amount of font dilation that Core Graphics performs, as a // fraction of ppem. // @@ -45,9 +44,6 @@ const CURVE_APPROX_ERROR_BOUND: f32 = 0.1; // direction. const FONT_DILATION_AMOUNT: f32 = 0.02; -/// A list of path commands. -pub type GlyphOutline = Vec; - /// An object that loads and renders fonts using macOS Core Graphics/Quartz. pub struct FontContext { core_graphics_fonts: BTreeMap, @@ -184,34 +180,32 @@ impl FontContext { let path = try!(core_text_font.create_path_for_glyph(glyph_key.glyph_index as CGGlyph, &CG_AFFINE_TRANSFORM_IDENTITY)); - let mut commands = vec![]; + let mut builder = vec![]; path.apply(&|element| { let points = element.points(); - commands.push(match element.element_type { + match element.element_type { CGPathElementType::MoveToPoint => { - CubicPathCommand::MoveTo(convert_point(&points[0])) + builder.push(PathEvent::MoveTo(convert_point(&points[0]))) } CGPathElementType::AddLineToPoint => { - CubicPathCommand::LineTo(convert_point(&points[0])) + builder.push(PathEvent::LineTo(convert_point(&points[0]))) } CGPathElementType::AddQuadCurveToPoint => { - CubicPathCommand::QuadCurveTo(convert_point(&points[0]), - convert_point(&points[1])) + builder.push(PathEvent::QuadraticTo(convert_point(&points[0]), + convert_point(&points[1]))) } CGPathElementType::AddCurveToPoint => { - CubicPathCommand::CubicCurveTo(convert_point(&points[0]), - convert_point(&points[1]), - convert_point(&points[2])) + builder.push(PathEvent::CubicTo(convert_point(&points[0]), + convert_point(&points[1]), + convert_point(&points[2]))) } - CGPathElementType::CloseSubpath => CubicPathCommand::ClosePath, - }); + CGPathElementType::CloseSubpath => builder.push(PathEvent::Close), + } }); - let approx_stream = CubicPathCommandApproxStream::new(commands.into_iter(), - CURVE_APPROX_ERROR_BOUND); - - let approx_commands: Vec<_> = approx_stream.collect(); - return Ok(approx_commands); + return Ok(GlyphOutline { + events: builder, + }); fn convert_point(core_graphics_point: &CGPoint) -> Point2D { Point2D::new(core_graphics_point.x as f32, core_graphics_point.y as f32) @@ -316,3 +310,14 @@ impl FontInstance { Ok(core_text::font::new_from_CGFont(core_graphics_font, self.size.to_f64_px())) } } + +pub struct GlyphOutline { + events: Vec, +} + +impl GlyphOutline { + #[inline] + pub fn iter(&self) -> Cloned> { + self.events.iter().cloned() + } +} diff --git a/font-renderer/src/freetype/mod.rs b/font-renderer/src/freetype/mod.rs index 2482bbca..919a044b 100644 --- a/font-renderer/src/freetype/mod.rs +++ b/font-renderer/src/freetype/mod.rs @@ -17,22 +17,22 @@ use freetype_sys::{FT_LOAD_NO_HINTING, FT_Library, FT_Library_SetLcdFilter}; use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox}; use freetype_sys::{FT_Outline_Translate, FT_PIXEL_MODE_LCD, FT_RENDER_MODE_LCD, FT_Render_Glyph}; use freetype_sys::{FT_Set_Char_Size, FT_UInt}; -use pathfinder_path_utils::PathCommand; use std::collections::BTreeMap; use std::collections::btree_map::Entry; -use std::marker::PhantomData; use std::mem; use std::ptr; use std::slice; use std::sync::Arc; use self::fixed::{FromFtF26Dot6, ToFtF26Dot6}; -use self::outline::OutlineStream; +use self::outline::Outline; use {FontKey, FontInstance, GlyphDimensions, GlyphImage, GlyphKey}; mod fixed; mod outline; +pub type GlyphOutline<'a> = Outline<'a>; + // Default to no hinting. // // TODO(pcwalton): Make this configurable. @@ -125,10 +125,7 @@ impl FontContext { -> Result, ()> { self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { unsafe { - GlyphOutline { - stream: OutlineStream::new(&(*glyph_slot).outline), - phantom: PhantomData, - } + GlyphOutline::new(&(*glyph_slot).outline) } }) } @@ -295,19 +292,6 @@ impl FontContext { } } -/// A list of path commands. -pub struct GlyphOutline<'a> { - stream: OutlineStream<'static>, - phantom: PhantomData<&'a ()>, -} - -impl<'a> Iterator for GlyphOutline<'a> { - type Item = PathCommand; - fn next(&mut self) -> Option { - self.stream.next() - } -} - struct Face { face: FT_Face, bytes: Arc>, diff --git a/font-renderer/src/freetype/outline.rs b/font-renderer/src/freetype/outline.rs index c3bf614c..d9ff6842 100644 --- a/font-renderer/src/freetype/outline.rs +++ b/font-renderer/src/freetype/outline.rs @@ -10,15 +10,36 @@ use euclid::Point2D; use freetype_sys::{FT_Outline, FT_Vector}; -use pathfinder_path_utils::PathCommand; +use lyon_path::iterator::PathIterator; +use lyon_path::{PathEvent, PathState}; const FREETYPE_POINT_ON_CURVE: i8 = 0x01; +pub struct Outline<'a> { + outline: &'a FT_Outline, +} + +impl<'a> Outline<'a> { + #[inline] + pub unsafe fn new(outline: &FT_Outline) -> Outline { + Outline { + outline: outline, + } + } + + #[inline] + pub fn iter(&self) -> OutlineStream { + unsafe { + OutlineStream::new(self.outline) + } + } +} + pub struct OutlineStream<'a> { outline: &'a FT_Outline, point_index: u16, contour_index: u16, - first_position_of_subpath: Point2D, + state: PathState, first_point_index_of_contour: bool, } @@ -29,7 +50,7 @@ impl<'a> OutlineStream<'a> { outline: outline, point_index: 0, contour_index: 0, - first_position_of_subpath: Point2D::zero(), + state: PathState::new(), first_point_index_of_contour: true, } } @@ -46,9 +67,9 @@ impl<'a> OutlineStream<'a> { } impl<'a> Iterator for OutlineStream<'a> { - type Item = PathCommand; + type Item = PathEvent; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { unsafe { let mut control_point_position: Option> = None; loop { @@ -60,13 +81,16 @@ impl<'a> Iterator for OutlineStream<'a> { *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(PathCommand::CurveTo(control_point_position, - self.first_position_of_subpath)) + let event = PathEvent::QuadraticTo(control_point_position, + self.state.first); + self.state.path_event(event); + return Some(event) } self.contour_index += 1; self.first_point_index_of_contour = true; - return Some(PathCommand::ClosePath) + self.state.close(); + return Some(PathEvent::Close) } // FIXME(pcwalton): Approximate cubic curves with quadratics. @@ -75,20 +99,24 @@ impl<'a> Iterator for OutlineStream<'a> { 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(PathCommand::MoveTo(position)); + self.state.move_to(position); + return Some(PathEvent::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(PathCommand::CurveTo(control_point_position, - on_curve_position)) + let event = PathEvent::QuadraticTo(control_point_position, + on_curve_position); + self.state.path_event(event); + return Some(event) } (Some(control_point_position), true) => { self.point_index += 1; - return Some(PathCommand::CurveTo(control_point_position, position)) + let event = PathEvent::QuadraticTo(control_point_position, position); + self.state.path_event(event); + return Some(event) } (None, false) => { self.point_index += 1; @@ -96,7 +124,8 @@ impl<'a> Iterator for OutlineStream<'a> { } (None, true) => { self.point_index += 1; - return Some(PathCommand::LineTo(position)) + self.state.line_to(position); + return Some(PathEvent::LineTo(position)) } } } @@ -104,6 +133,13 @@ impl<'a> Iterator for OutlineStream<'a> { } } +impl<'a> PathIterator for OutlineStream<'a> { + #[inline] + fn get_state(&self) -> &PathState { + &self.state + } +} + #[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/font-renderer/src/lib.rs b/font-renderer/src/lib.rs index f4b10d71..3ea8929d 100644 --- a/font-renderer/src/lib.rs +++ b/font-renderer/src/lib.rs @@ -20,7 +20,8 @@ extern crate app_units; extern crate euclid; extern crate libc; -extern crate pathfinder_path_utils; +extern crate lyon_geom; +extern crate lyon_path; extern crate serde; #[allow(unused_imports)] diff --git a/partitioner/Cargo.toml b/partitioner/Cargo.toml index 2f5bd02e..89a7ff74 100644 --- a/partitioner/Cargo.toml +++ b/partitioner/Cargo.toml @@ -7,13 +7,16 @@ authors = ["Patrick Walton "] name = "pathfinder_partitioner" [dependencies] +arrayvec = "0.4" bincode = "0.8" bit-vec = "0.4" byteorder = "1.2" env_logger = "0.4" -euclid = "0.15" +euclid = "0.16" half = "1.0" log = "0.3" +lyon_geom = "0.9" +lyon_path = "0.9" serde = "1.0" serde_derive = "1.0" diff --git a/partitioner/src/builder.rs b/partitioner/src/builder.rs new file mode 100644 index 00000000..d5fd04ec --- /dev/null +++ b/partitioner/src/builder.rs @@ -0,0 +1,187 @@ +// pathfinder/partitioner/src/builder.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. + +use arrayvec::ArrayVec; +use euclid::{Angle, Point2D, Vector2D}; +use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment}; +use lyon_path::builder::{FlatPathBuilder, PathBuilder}; +use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticSegmentIter; +use std::ops::Range; + +const TANGENT_PARAMETER_TOLERANCE: f32 = 0.001; + +const CUBIC_APPROX_TOLERANCE: f32 = 0.001; + +// TODO(pcwalton): A better debug. +#[derive(Debug)] +pub struct Builder { + pub endpoints: Vec, + pub subpath_ranges: Vec>, +} + +impl Builder { + #[inline] + pub fn new() -> Builder { + Builder { + endpoints: vec![], + subpath_ranges: vec![], + } + } + + #[inline] + fn current_subpath_index(&self) -> Option { + if self.subpath_ranges.is_empty() { + None + } else { + Some(self.subpath_ranges.len() as u32 - 1) + } + } + + fn add_endpoint(&mut self, ctrl: Option>, to: Point2D) { + let current_subpath_index = match self.current_subpath_index() { + None => return, + Some(current_subpath_index) => current_subpath_index, + }; + + self.endpoints.push(Endpoint { + to: to, + ctrl: ctrl, + subpath_index: current_subpath_index, + }); + } + + #[inline] + pub fn end_subpath(&mut self) { + let last_endpoint_index = self.endpoints.len() as u32; + if let Some(current_subpath) = self.subpath_ranges.last_mut() { + current_subpath.end = last_endpoint_index + } + } + + #[inline] + fn first_position_of_subpath(&self) -> Option> { + self.subpath_ranges + .last() + .map(|subpath_range| self.endpoints[subpath_range.start as usize].to) + } +} + +impl FlatPathBuilder for Builder { + type PathType = (); + + #[inline] + fn build(self) {} + + #[inline] + fn build_and_reset(&mut self) { + self.endpoints.clear(); + self.subpath_ranges.clear(); + } + + #[inline] + fn current_position(&self) -> Point2D { + match self.endpoints.last() { + None => Point2D::zero(), + Some(endpoint) => endpoint.to, + } + } + + fn close(&mut self) { + let first_position_of_subpath = match self.first_position_of_subpath() { + None => return, + Some(first_position_of_subpath) => first_position_of_subpath, + }; + + if first_position_of_subpath == self.current_position() { + return + } + + self.add_endpoint(None, first_position_of_subpath); + self.end_subpath(); + } + + fn move_to(&mut self, to: Point2D) { + self.end_subpath(); + let last_endpoint_index = self.endpoints.len() as u32; + self.subpath_ranges.push(last_endpoint_index..last_endpoint_index); + self.add_endpoint(None, to); + } + + #[inline] + fn line_to(&mut self, to: Point2D) { + self.add_endpoint(None, to); + } +} + +impl PathBuilder for Builder { + fn quadratic_bezier_to(&mut self, ctrl: Point2D, to: Point2D) { + let segment = QuadraticBezierSegment { + from: self.current_position(), + ctrl: ctrl, + to: to, + }; + + //self.add_endpoint(Some(ctrl), to); + + // Split at X tangent. + let mut worklist: ArrayVec<[QuadraticBezierSegment; 2]> = ArrayVec::new(); + match segment.find_local_x_extremum() { + Some(t) if t > TANGENT_PARAMETER_TOLERANCE && + t < 1.0 - TANGENT_PARAMETER_TOLERANCE => { + let subsegments = segment.split(t); + worklist.push(subsegments.0); + worklist.push(subsegments.1); + } + _ => worklist.push(segment), + } + + // Split at Y tangent. + for segment in worklist { + match segment.find_local_y_extremum() { + Some(t) if t > TANGENT_PARAMETER_TOLERANCE && + t < 1.0 - TANGENT_PARAMETER_TOLERANCE => { + let subsegments = segment.split(t); + self.add_endpoint(Some(subsegments.0.ctrl), subsegments.0.to); + self.add_endpoint(Some(subsegments.1.ctrl), subsegments.1.to); + } + _ => self.add_endpoint(Some(segment.ctrl), segment.to), + } + } + } + + fn cubic_bezier_to(&mut self, ctrl1: Point2D, ctrl2: Point2D, to: Point2D) { + let cubic_segment = CubicBezierSegment { + from: self.current_position(), + ctrl1: ctrl1, + ctrl2: ctrl2, + to: to, + }; + + for quadratic_segment in CubicToQuadraticSegmentIter::new(&cubic_segment, + CUBIC_APPROX_TOLERANCE) { + self.quadratic_bezier_to(quadratic_segment.ctrl, quadratic_segment.to) + } + } + + fn arc(&mut self, + _center: Point2D, + _radii: Vector2D, + _angle: Angle, + _x_rotation: Angle) { + panic!("TODO: Support arcs in the Pathfinder builder!") + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Endpoint { + pub to: Point2D, + pub ctrl: Option>, + pub subpath_index: u32, +} diff --git a/partitioner/src/capi.rs b/partitioner/src/capi.rs deleted file mode 100644 index 43660e39..00000000 --- a/partitioner/src/capi.rs +++ /dev/null @@ -1,118 +0,0 @@ -// partitionfinder/capi.rs - -use env_logger; -use euclid::Point2D; -use std::mem; -use std::slice; - -use mesh_library::MeshLibrary; -use partitioner::Partitioner; -use {BQuad, BVertexLoopBlinnData, Endpoint, Subpath}; - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct Point2DF32 { - pub x: f32, - pub y: f32, -} - -impl Point2DF32 { - #[inline] - pub fn to_point2d(&self) -> Point2D { - Point2D::new(self.x, self.y) - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct Matrix2DF32 { - pub m00: f32, - pub m01: f32, - pub m02: f32, - pub m10: f32, - pub m11: f32, - pub m12: f32, -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct CoverIndices { - pub interior_indices: *const u32, - pub interior_indices_len: u32, - pub curve_indices: *const u32, - pub curve_indices_len: u32, -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_new() -> *mut Partitioner<'static> { - let mut partitioner = Box::new(Partitioner::new(MeshLibrary::new())); - let partitioner_ptr: *mut Partitioner<'static> = &mut *partitioner; - mem::forget(partitioner); - partitioner_ptr -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_destroy<'a>(partitioner: *mut Partitioner<'a>) { - drop(mem::transmute::<*mut Partitioner<'a>, Box>(partitioner)) -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_init<'a>(partitioner: *mut Partitioner<'a>, - endpoints: *const Endpoint, - endpoint_count: u32, - control_points: *const Point2DF32, - control_point_count: u32, - subpaths: *const Subpath, - subpath_count: u32) { - (*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] -pub unsafe extern fn pf_partitioner_partition<'a>(partitioner: *mut Partitioner<'a>, - path_id: u16, - first_subpath_index: u32, - last_subpath_index: u32) { - (*partitioner).partition(path_id, first_subpath_index, last_subpath_index); -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_b_quads<'a>(partitioner: *const Partitioner<'a>, - out_b_quad_count: *mut u32) - -> *const BQuad { - let b_quads = &(*partitioner).library().b_quads; - if !out_b_quad_count.is_null() { - *out_b_quad_count = b_quads.len() as u32 - } - b_quads.as_ptr() -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_b_vertex_positions<'a>(partitioner: *const Partitioner<'a>, - out_b_vertex_count: *mut u32) - -> *const Point2D { - let b_vertex_positions = &(*partitioner).library().b_vertex_positions; - if !out_b_vertex_count.is_null() { - *out_b_vertex_count = b_vertex_positions.len() as u32 - } - b_vertex_positions.as_ptr() as *const Point2D -} - -#[no_mangle] -pub unsafe extern fn pf_partitioner_b_vertex_loop_blinn_data<'a>( - partitioner: *const Partitioner<'a>, - out_b_vertex_count: *mut u32) - -> *const BVertexLoopBlinnData { - let b_vertex_loop_blinn_data = &(*partitioner).library().b_vertex_loop_blinn_data; - if !out_b_vertex_count.is_null() { - *out_b_vertex_count = b_vertex_loop_blinn_data.len() as u32 - } - b_vertex_loop_blinn_data.as_ptr() as *const BVertexLoopBlinnData -} - -#[no_mangle] -pub unsafe extern fn pf_init_env_logger() -> u32 { - env_logger::init().is_ok() as u32 -} diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index 16e6094c..2754385c 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -21,11 +21,14 @@ //! use case, mesh libraries can be serialized into a simple binary format. Of course, meshes can //! also be generated dynamically and rendered on the fly. +extern crate arrayvec; extern crate bincode; extern crate bit_vec; extern crate byteorder; extern crate env_logger; extern crate euclid; +extern crate lyon_geom; +extern crate lyon_path; extern crate pathfinder_path_utils; extern crate serde; @@ -35,15 +38,12 @@ extern crate log; extern crate serde_derive; use euclid::Point2D; -use pathfinder_path_utils::{Endpoint, Subpath}; use std::u32; -pub mod capi; +pub mod builder; pub mod mesh_library; pub mod partitioner; -mod normal; - /// The fill rule. #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/partitioner/src/mesh_library.rs b/partitioner/src/mesh_library.rs index 3a903680..4e65d4b7 100644 --- a/partitioner/src/mesh_library.rs +++ b/partitioner/src/mesh_library.rs @@ -1,6 +1,6 @@ // pathfinder/partitioner/src/mesh_library.rs // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -10,14 +10,17 @@ use bincode::{self, Infinite}; use byteorder::{LittleEndian, WriteBytesExt}; -use euclid::Point2D; -use pathfinder_path_utils::{PathCommand, PathSegment, PathSegmentStream}; +use euclid::{Point2D, Vector2D}; +use lyon_path::PathEvent; +use lyon_path::iterator::PathIterator; +use pathfinder_path_utils::normals::PathNormals; +use pathfinder_path_utils::segments::{self, SegmentIter}; use serde::Serialize; +use std::f32; use std::io::{self, ErrorKind, Seek, SeekFrom, Write}; use std::ops::Range; use std::u32; -use normal; use {BQuad, BQuadVertexPositions, BVertexLoopBlinnData}; #[derive(Debug, Clone)] @@ -174,25 +177,29 @@ impl MeshLibrary { } pub fn push_segments(&mut self, path_id: u16, stream: I) - where I: Iterator { + where I: Iterator { let first_line_index = self.segments.lines.len() as u32; let first_curve_index = self.segments.curves.len() as u32; - let stream = PathSegmentStream::new(stream); - for (segment, _) in stream { + let segment_iter = SegmentIter::new(stream); + for segment in segment_iter { match segment { - PathSegment::Line(endpoint_0, endpoint_1) => { + segments::Segment::Line(line_segment) => { self.segments.lines.push(LineSegment { - endpoint_0: endpoint_0, - endpoint_1: endpoint_1, - }); + endpoint_0: line_segment.from, + endpoint_1: line_segment.to, + }) } - PathSegment::Curve(endpoint_0, control_point, endpoint_1) => { + segments::Segment::Quadratic(curve_segment) => { self.segments.curves.push(CurveSegment { - endpoint_0: endpoint_0, - control_point: control_point, - endpoint_1: endpoint_1, - }); + endpoint_0: curve_segment.from, + control_point: curve_segment.ctrl, + endpoint_1: curve_segment.to, + }) + } + segments::Segment::EndSubpath(..) => {} + segments::Segment::Cubic(..) => { + panic!("push_segments(): Convert cubics to quadratics first!") } } } @@ -206,8 +213,56 @@ impl MeshLibrary { } /// Computes vertex normals necessary for emboldening and/or stem darkening. - pub fn push_normals(&mut self, stream: I) where I: Iterator { - normal::push_normals(self, stream) + pub fn push_normals(&mut self, _path_id: u16, stream: I) where I: PathIterator { + let path_events: Vec<_> = stream.collect(); + + let mut normals = PathNormals::new(); + normals.add_path(path_events.iter().cloned()); + let normals = normals.normals(); + + let mut current_point_normal_index = 0; + let mut next_normal_index = 0; + let mut first_normal_index_of_subpath = 0; + + for event in path_events { + match event { + PathEvent::MoveTo(..) => { + first_normal_index_of_subpath = next_normal_index; + current_point_normal_index = next_normal_index; + next_normal_index += 1; + } + PathEvent::LineTo(..) => { + self.segment_normals.line_normals.push(LineSegmentNormals { + endpoint_0: normal_angle(&normals[current_point_normal_index]), + endpoint_1: normal_angle(&normals[next_normal_index]), + }); + current_point_normal_index = next_normal_index; + next_normal_index += 1; + } + PathEvent::QuadraticTo(..) => { + self.segment_normals.curve_normals.push(CurveSegmentNormals { + endpoint_0: normal_angle(&normals[current_point_normal_index]), + control_point: normal_angle(&normals[next_normal_index + 0]), + endpoint_1: normal_angle(&normals[next_normal_index + 1]), + }); + current_point_normal_index = next_normal_index + 1; + next_normal_index += 2; + } + PathEvent::Close => { + self.segment_normals.line_normals.push(LineSegmentNormals { + endpoint_0: normal_angle(&normals[current_point_normal_index]), + endpoint_1: normal_angle(&normals[first_normal_index_of_subpath]), + }); + } + PathEvent::CubicTo(..) | PathEvent::Arc(..) => { + panic!("push_normals(): Convert cubics and arcs to quadratics first!") + } + } + } + + fn normal_angle(vector: &Vector2D) -> f32 { + Vector2D::new(vector.x, -vector.y).angle_from_x_axis().get() + } } /// Writes this mesh library to a RIFF file. diff --git a/partitioner/src/normal.rs b/partitioner/src/normal.rs deleted file mode 100644 index 5ab0ac4a..00000000 --- a/partitioner/src/normal.rs +++ /dev/null @@ -1,142 +0,0 @@ -// pathfinder/partitioner/src/normal.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. - -//! Utility functions for vertex normals. - -use euclid::{Point2D, Vector2D}; -use pathfinder_path_utils::{PathCommand, PathSegment, PathSegmentStream}; - -use mesh_library::{CurveSegmentNormals, LineSegmentNormals, MeshLibrary}; - -pub fn push_normals(library: &mut MeshLibrary, stream: I) - where I: Iterator { - let mut stream = PathSegmentStream::new(stream).peekable(); - - let mut first_segment_of_subpath = None; - let mut index_of_first_segment_of_subpath = None; - let mut index_of_prev_segment = None; - - while let Some((prev_segment, prev_subpath_index)) = stream.next() { - let is_first_segment = index_of_first_segment_of_subpath.is_none(); - let is_last_segment = match stream.peek() { - Some(&(_, next_subpath_index)) => prev_subpath_index != next_subpath_index, - _ => true, - }; - - let next_segment = if is_last_segment { - first_segment_of_subpath.unwrap_or(PathSegment::Line(Point2D::zero(), Point2D::zero())) - } else { - stream.peek().unwrap().0 - }; - - if is_first_segment { - first_segment_of_subpath = Some(prev_segment); - index_of_first_segment_of_subpath = match prev_segment { - PathSegment::Line(..) => { - Some(SegmentIndex::Line(library.segment_normals.line_normals.len())) - } - PathSegment::Curve(..) => { - Some(SegmentIndex::Curve(library.segment_normals.curve_normals.len())) - } - }; - } - - let next_vertex_normal = match (&prev_segment, &next_segment) { - (&PathSegment::Line(ref prev_endpoint, ref vertex_endpoint), - &PathSegment::Line(_, ref next_endpoint)) | - (&PathSegment::Curve(_, ref prev_endpoint, ref vertex_endpoint), - &PathSegment::Line(_, ref next_endpoint)) | - (&PathSegment::Line(ref prev_endpoint, ref vertex_endpoint), - &PathSegment::Curve(_, ref next_endpoint, _)) | - (&PathSegment::Curve(_, ref prev_endpoint, ref vertex_endpoint), - &PathSegment::Curve(_, ref next_endpoint, _)) => { - calculate_vertex_normal(prev_endpoint, vertex_endpoint, next_endpoint) - } - }; - - let next_vertex_angle = calculate_normal_angle(&next_vertex_normal); - - let prev_vertex_angle = if !is_first_segment { - match index_of_prev_segment.unwrap() { - SegmentIndex::Line(prev_line_index) => { - library.segment_normals.line_normals[prev_line_index].endpoint_1 - } - SegmentIndex::Curve(prev_curve_index) => { - library.segment_normals.curve_normals[prev_curve_index].endpoint_1 - } - } - } else { - // We'll patch this later. - 0.0 - }; - - match prev_segment { - PathSegment::Line(..) => { - index_of_prev_segment = - Some(SegmentIndex::Line(library.segment_normals.line_normals.len())); - library.segment_normals.line_normals.push(LineSegmentNormals { - endpoint_0: prev_vertex_angle, - endpoint_1: next_vertex_angle, - }); - } - PathSegment::Curve(endpoint_0, control_point, endpoint_1) => { - let control_point_vertex_normal = calculate_vertex_normal(&endpoint_0, - &control_point, - &endpoint_1); - let control_point_vertex_angle = - calculate_normal_angle(&control_point_vertex_normal); - - index_of_prev_segment = - Some(SegmentIndex::Curve(library.segment_normals.curve_normals.len())); - library.segment_normals.curve_normals.push(CurveSegmentNormals { - endpoint_0: prev_vertex_angle, - control_point: control_point_vertex_angle, - endpoint_1: next_vertex_angle, - }); - } - } - - // Patch that first segment if necessary. - if is_last_segment { - match index_of_first_segment_of_subpath.unwrap() { - SegmentIndex::Line(index) => { - library.segment_normals.line_normals[index].endpoint_0 = next_vertex_angle - } - SegmentIndex::Curve(index) => { - library.segment_normals.curve_normals[index].endpoint_0 = next_vertex_angle - } - } - index_of_first_segment_of_subpath = None; - } - } -} - -pub fn calculate_vertex_normal(prev_position: &Point2D, - vertex_position: &Point2D, - next_position: &Point2D) - -> Vector2D { - let prev_edge_vector = *vertex_position - *prev_position; - let next_edge_vector = *next_position - *vertex_position; - - let prev_edge_normal = Vector2D::new(-prev_edge_vector.y, prev_edge_vector.x).normalize(); - let next_edge_normal = Vector2D::new(-next_edge_vector.y, next_edge_vector.x).normalize(); - - (prev_edge_normal + next_edge_normal) * 0.5 -} - -pub fn calculate_normal_angle(normal: &Vector2D) -> f32 { - (-normal.y).atan2(normal.x) -} - -#[derive(Clone, Copy, Debug)] -enum SegmentIndex { - Line(usize), - Curve(usize), -} diff --git a/partitioner/src/partitioner.h b/partitioner/src/partitioner.h deleted file mode 100644 index f39e5e5e..00000000 --- a/partitioner/src/partitioner.h +++ /dev/null @@ -1,180 +0,0 @@ -// pathfinder/partitioner/partitioner.h - -#ifndef PATHFINDER_PARTITIONER_H -#define PATHFINDER_PARTITIONER_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef uint16_t pf_float16_t; - -struct pf_point2d_f32 { - float x, y; -}; - -typedef struct pf_point2d_f32 pf_point2d_f32_t; - -struct pf_matrix2d_f32 { - float m00, m01, m02; - float m10, m11, m12; -}; - -typedef struct pf_matrix2d_f32 pf_matrix2d_f32_t; - -struct pf_b_vertex_loop_blinn_data { - uint8_t tex_coord[2]; - int8_t sign; - uint8_t pad; -}; - -typedef struct pf_b_vertex_loop_blinn_data pf_b_vertex_loop_blinn_data_t; - -struct pf_cover_indices { - const uint32_t *interior_indices; - uint32_t interior_indices_len; - const uint32_t *curve_indices; - uint32_t curve_indices_len; -}; - -typedef struct pf_cover_indices pf_cover_indices_t; - -struct pf_line_indices { - uint32_t left_vertex_index; - uint32_t right_vertex_index; -}; - -typedef struct pf_line_indices pf_line_indices_t; - -struct pf_curve_indices { - uint32_t left_vertex_index; - uint32_t right_vertex_index; - uint32_t control_point_vertex_index; - uint32_t pad; -}; - -typedef struct pf_curve_indices pf_curve_indices_t; - -struct pf_edge_indices { - const pf_line_indices_t *top_line_indices; - uint32_t top_line_indices_len; - const pf_curve_indices_t *top_curve_indices; - uint32_t top_curve_indices_len; - const pf_line_indices_t *bottom_line_indices; - uint32_t bottom_line_indices_len; - const pf_curve_indices_t *bottom_curve_indices; - uint32_t bottom_curve_indices_len; -}; - -typedef struct pf_edge_indices pf_edge_indices_t; - -struct pf_b_quad { - uint32_t upper_left_vertex_index; - uint32_t upper_right_vertex_index; - uint32_t upper_control_point_vertex_index; - uint32_t pad0; - uint32_t lower_left_vertex_index; - uint32_t lower_right_vertex_index; - uint32_t lower_control_point_vertex_index; - uint32_t pad1; -}; - -typedef struct pf_b_quad pf_b_quad_t; - -struct pf_endpoint { - pf_point2d_f32_t position; - uint32_t control_point_index; - uint32_t subpath_index; -}; - -typedef struct pf_endpoint pf_endpoint_t; - -struct pf_subpath { - uint32_t first_endpoint_index; - uint32_t last_endpoint_index; -}; - -typedef struct pf_subpath pf_subpath_t; - -struct pf_legalizer; - -typedef struct pf_legalizer pf_legalizer_t; - -struct pf_partitioner; - -typedef struct pf_partitioner pf_partitioner_t; - -pf_legalizer_t *pf_legalizer_new(); - -void pf_legalizer_destroy(pf_legalizer_t *legalizer); - -const pf_endpoint_t *pf_legalizer_endpoints(const pf_legalizer_t *legalizer, - uint32_t *out_endpoint_count); - -const pf_point2d_f32_t *pf_legalizer_control_points(const pf_legalizer_t *legalizer, - uint32_t *out_control_point_count); - -const pf_subpath_t *pf_legalizer_subpaths(const pf_legalizer_t *legalizer, - uint32_t *out_subpaths_count); - -void pf_legalizer_move_to(pf_legalizer_t *legalizer, const pf_point2d_f32_t *position); - -void pf_legalizer_close_path(pf_legalizer_t *legalizer); - -void pf_legalizer_line_to(pf_legalizer_t *legalizer, const pf_point2d_f32_t *endpoint); - -void pf_legalizer_quadratic_curve_to(pf_legalizer_t *legalizer, - const pf_point2d_f32_t *control_point, - const pf_point2d_f32_t *endpoint); - -void pf_legalizer_bezier_curve_to(pf_legalizer_t *legalizer, - const pf_point2d_f32_t *point1, - const pf_point2d_f32_t *point2, - const pf_point2d_f32_t *endpoint); - -pf_partitioner_t *pf_partitioner_new(); - -void pf_partitioner_destroy(pf_partitioner_t *partitioner); - -void pf_partitioner_init(pf_partitioner_t *partitioner, - const pf_endpoint_t *endpoints, - uint32_t endpoint_count, - const pf_point2d_f32_t *control_points, - uint32_t control_point_count, - const pf_subpath_t *subpaths, - uint32_t subpath_count); - -void pf_partitioner_partition(pf_partitioner_t *partitioner, - uint16_t path_id, - uint32_t first_subpath_index, - uint32_t last_subpath_index); - -const pf_b_quad_t *pf_partitioner_b_quads(const pf_partitioner_t *partitioner, - uint32_t *out_b_quad_count); - -const pf_point2d_f32_t *pf_partitioner_b_vertex_positions(const pf_partitioner_t *partitioner, - uint32_t *out_b_vertex_count); - -const uint16_t *pf_partitioner_b_vertex_path_ids(const pf_partitioner_t *partitioner, - uint32_t *out_b_vertex_count); - -const pf_b_vertex_loop_blinn_data_t *pf_partitioner_b_vertex_loop_blinn_data( - const pf_partitioner_t *partitioner, - uint32_t *out_b_vertex_count); - -const void pf_partitioner_cover_indices(const pf_partitioner_t *partitioner, - pf_cover_indices_t *out_cover_indices); - -const void pf_partitioner_edge_indices(const pf_partitioner_t *partitioner, - pf_edge_indices_t *out_edge_indices); - -uint32_t pf_init_env_logger(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index e4206040..3b10cbac 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -1,6 +1,6 @@ // pathfinder/partitioner/src/partitioner.rs // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -12,9 +12,7 @@ use bit_vec::BitVec; use euclid::approxeq::ApproxEq; use euclid::{Point2D, Vector2D}; use log::LogLevel; -use pathfinder_path_utils::PathBuffer; -use pathfinder_path_utils::curve::Curve; -use pathfinder_path_utils::line::Line; +use lyon_geom::{LineSegment, QuadraticBezierSegment}; use std::collections::BinaryHeap; use std::cmp::Ordering; use std::f32; @@ -22,15 +20,17 @@ use std::iter; use std::ops::{Add, AddAssign}; use std::u32; +use builder::Builder; use mesh_library::MeshLibrary; -use {BQuad, BVertexLoopBlinnData, BVertexKind, Endpoint, FillRule, Subpath}; +use {BQuad, BVertexLoopBlinnData, BVertexKind, FillRule}; const MAX_B_QUAD_SUBDIVISIONS: u8 = 8; -pub struct Partitioner<'a> { - endpoints: &'a [Endpoint], - control_points: &'a [Point2D], - subpaths: &'a [Subpath], +const INTERSECTION_TOLERANCE: f32 = 0.001; + +pub struct Partitioner { + path: Builder, + path_id: u16, library: MeshLibrary, @@ -40,17 +40,14 @@ pub struct Partitioner<'a> { visited_points: BitVec, active_edges: Vec, vertex_normals: Vec, - path_id: u16, } -impl<'a> Partitioner<'a> { +impl Partitioner { #[inline] - pub fn new<'b>(library: MeshLibrary) -> Partitioner<'b> { + pub fn new(library: MeshLibrary) -> Partitioner { Partitioner { - endpoints: &[], - control_points: &[], - subpaths: &[], - + path: Builder::new(), + path_id: 0, fill_rule: FillRule::Winding, library: library, @@ -59,7 +56,6 @@ impl<'a> Partitioner<'a> { visited_points: BitVec::new(), active_edges: vec![], vertex_normals: vec![], - path_id: 0, } } @@ -78,39 +74,31 @@ impl<'a> Partitioner<'a> { self.library } - 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; - - // FIXME(pcwalton): Move this initialization to `partition` below. Right now, this bit - // vector uses too much memory. - 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) + #[inline] + pub fn builder(&self) -> &Builder { + &self.path } #[inline] - pub fn set_fill_rule(&mut self, new_fill_rule: FillRule) { - self.fill_rule = new_fill_rule + pub fn builder_mut(&mut self) -> &mut Builder { + &mut self.path } - pub fn partition(&mut self, path_id: u16, first_subpath_index: u32, last_subpath_index: u32) { + pub fn partition(&mut self, path_id: u16, fill_rule: FillRule) { + self.path.end_subpath(); + self.heap.clear(); self.active_edges.clear(); + self.path_id = path_id; + self.fill_rule = fill_rule; + + // FIXME(pcwalton): Right now, this bit vector uses too much memory. + self.visited_points = BitVec::from_elem(self.path.endpoints.len(), false); + let start_lengths = self.library.snapshot_lengths(); - self.path_id = path_id; - - self.init_heap(first_subpath_index, last_subpath_index); + self.init_heap(); while self.process_next_point() {} @@ -135,7 +123,7 @@ impl<'a> Partitioner<'a> { } if log_enabled!(LogLevel::Debug) { - let position = self.endpoints[point.endpoint_index as usize].position; + let position = self.path.endpoints[point.endpoint_index as usize].to; debug!("processing point {}: {:?}", point.endpoint_index, position); debug!("... active edges at {}:", position.x); for (active_edge_index, active_edge) in self.active_edges.iter().enumerate() { @@ -171,8 +159,8 @@ impl<'a> Partitioner<'a> { let next_active_edge_index = self.find_point_between_active_edges(endpoint_index); - let endpoint = &self.endpoints[endpoint_index as usize]; - self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x); + let endpoint = self.path.endpoints[endpoint_index as usize]; + self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.to.x); self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index); @@ -189,8 +177,8 @@ impl<'a> Partitioner<'a> { fn process_regular_endpoint(&mut self, endpoint_index: u32, active_edge_index: u32) { debug!("... REGULAR point: active edge {}", active_edge_index); - let endpoint = &self.endpoints[endpoint_index as usize]; - let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.position.x) == + let endpoint = self.path.endpoints[endpoint_index as usize]; + let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.to.x) == BQuadEmissionResult::BQuadEmittedAbove; let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); @@ -198,8 +186,9 @@ impl<'a> Partitioner<'a> { { let active_edge = &mut self.active_edges[active_edge_index as usize]; - let endpoint_position = self.endpoints[active_edge.right_endpoint_index as usize] - .position; + let endpoint_position = self.path + .endpoints[active_edge.right_endpoint_index as usize] + .to; // If we already made a B-vertex point for this endpoint, reuse it instead of making a // new one. @@ -230,23 +219,22 @@ impl<'a> Partitioner<'a> { let new_point = self.create_point_from_endpoint(right_endpoint_index); *self.heap.peek_mut().unwrap() = new_point; - let control_point_index = if self.active_edges[active_edge_index as usize].left_to_right { - self.control_point_index_before_endpoint(next_endpoint_index) + let control_point = if self.active_edges[active_edge_index as usize].left_to_right { + self.control_point_before_endpoint(next_endpoint_index) } else { - self.control_point_index_after_endpoint(prev_endpoint_index) + self.control_point_after_endpoint(prev_endpoint_index) }; - match control_point_index { - u32::MAX => { + match control_point { + None => { self.active_edges[active_edge_index as usize].control_point_vertex_index = u32::MAX } - control_point_index => { + Some(ref control_point_position) => { self.active_edges[active_edge_index as usize].control_point_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; let left_vertex_index = self.active_edges[active_edge_index as usize] .left_vertex_index; - let control_point_position = &self.control_points[control_point_index as usize]; let control_point_b_vertex_loop_blinn_data = BVertexLoopBlinnData::control_point( &self.library.b_vertex_positions[left_vertex_index as usize], &control_point_position, @@ -266,12 +254,12 @@ impl<'a> Partitioner<'a> { debug_assert!(active_edge_indices[0] < active_edge_indices[1], "Matching active edge indices in wrong order when processing MAX point"); - let endpoint = &self.endpoints[endpoint_index as usize]; + let endpoint = self.path.endpoints[endpoint_index as usize]; // TODO(pcwalton): Collapse the two duplicate endpoints that this will create together if // possible (i.e. if they have the same parity). - self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x); - self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x); + self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.to.x); + self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.to.x); // Add supporting interior triangles if necessary. self.heap.pop(); @@ -282,7 +270,7 @@ impl<'a> Partitioner<'a> { } fn sort_active_edge_list_and_emit_self_intersections(&mut self, endpoint_index: u32) { - let x = self.endpoints[endpoint_index as usize].position.x; + let x = self.path.endpoints[endpoint_index as usize].to.x; loop { let mut swapped = false; for lower_active_edge_index in 1..(self.active_edges.len() as u32) { @@ -335,51 +323,47 @@ impl<'a> Partitioner<'a> { new_active_edges[1].left_vertex_index = left_vertex_index; // FIXME(pcwalton): Normal - let position = self.endpoints[endpoint_index as usize].position; + let position = self.path.endpoints[endpoint_index as usize].to; self.library.add_b_vertex(&position, &BVertexLoopBlinnData::new(BVertexKind::Endpoint0)); new_active_edges[0].toggle_parity(); new_active_edges[1].toggle_parity(); - let endpoint = &self.endpoints[endpoint_index as usize]; - let prev_endpoint = &self.endpoints[prev_endpoint_index as usize]; - let next_endpoint = &self.endpoints[next_endpoint_index as usize]; + let endpoint = &self.path.endpoints[endpoint_index as usize]; + let prev_endpoint = &self.path.endpoints[prev_endpoint_index as usize]; + let next_endpoint = &self.path.endpoints[next_endpoint_index as usize]; - let prev_vector = prev_endpoint.position - endpoint.position; - let next_vector = next_endpoint.position - endpoint.position; + let prev_vector = prev_endpoint.to - endpoint.to; + let next_vector = next_endpoint.to - endpoint.to; - let (upper_control_point_index, lower_control_point_index); + let (upper_control_point, lower_control_point); if prev_vector.cross(next_vector) >= 0.0 { new_active_edges[0].right_endpoint_index = prev_endpoint_index; new_active_edges[1].right_endpoint_index = next_endpoint_index; new_active_edges[0].left_to_right = false; new_active_edges[1].left_to_right = true; - upper_control_point_index = self.endpoints[endpoint_index as usize].control_point_index; - lower_control_point_index = self.endpoints[next_endpoint_index as usize] - .control_point_index; + upper_control_point = self.path.endpoints[endpoint_index as usize].ctrl; + lower_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl; } else { new_active_edges[0].right_endpoint_index = next_endpoint_index; new_active_edges[1].right_endpoint_index = prev_endpoint_index; new_active_edges[0].left_to_right = true; new_active_edges[1].left_to_right = false; - upper_control_point_index = self.endpoints[next_endpoint_index as usize] - .control_point_index; - lower_control_point_index = self.endpoints[endpoint_index as usize].control_point_index; + upper_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl; + lower_control_point = self.path.endpoints[endpoint_index as usize].ctrl; } - match upper_control_point_index { - u32::MAX => new_active_edges[0].control_point_vertex_index = u32::MAX, - upper_control_point_index => { + match upper_control_point { + None => new_active_edges[0].control_point_vertex_index = u32::MAX, + Some(control_point_position) => { new_active_edges[0].control_point_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; - let control_point_position = - self.control_points[upper_control_point_index as usize]; let right_vertex_position = - self.endpoints[new_active_edges[0].right_endpoint_index as usize].position; + self.path.endpoints[new_active_edges[0].right_endpoint_index as usize].to; let control_point_b_vertex_loop_blinn_data = BVertexLoopBlinnData::control_point(&position, &control_point_position, @@ -392,16 +376,14 @@ impl<'a> Partitioner<'a> { } } - match lower_control_point_index { - u32::MAX => new_active_edges[1].control_point_vertex_index = u32::MAX, - lower_control_point_index => { + match lower_control_point { + None => new_active_edges[1].control_point_vertex_index = u32::MAX, + Some(control_point_position) => { new_active_edges[1].control_point_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; - let control_point_position = - self.control_points[lower_control_point_index as usize]; let right_vertex_position = - self.endpoints[new_active_edges[1].right_endpoint_index as usize].position; + self.path.endpoints[new_active_edges[1].right_endpoint_index as usize].to; let control_point_b_vertex_loop_blinn_data = BVertexLoopBlinnData::control_point(&position, &control_point_position, @@ -437,17 +419,11 @@ impl<'a> Partitioner<'a> { prev_active_edge_y <= next_active_edge_y } - fn init_heap(&mut self, first_subpath_index: u32, last_subpath_index: u32) { - for subpath in &self.subpaths[(first_subpath_index as usize).. - (last_subpath_index as usize)] { - for endpoint_index in subpath.first_endpoint_index..subpath.last_endpoint_index { - match self.classify_endpoint(endpoint_index) { - EndpointClass::Min => { - let new_point = self.create_point_from_endpoint(endpoint_index); - self.heap.push(new_point) - } - EndpointClass::Regular | EndpointClass::Max => {} - } + fn init_heap(&mut self) { + for endpoint_index in 0..(self.path.endpoints.len() as u32) { + if let EndpointClass::Min = self.classify_endpoint(endpoint_index) { + let new_point = self.create_point_from_endpoint(endpoint_index); + self.heap.push(new_point) } } } @@ -557,7 +533,9 @@ impl<'a> Partitioner<'a> { lower_subdivision.to_curve(&self.library.b_vertex_positions)) { // TODO(pcwalton): Handle concave-concave convex hull intersections. if upper_shape == Shape::Concave && - lower_curve.baseline().side(&upper_curve.control_point) > + lower_curve.baseline() + .to_line() + .signed_distance_to_point(&upper_curve.ctrl) > f32::approx_epsilon() { let (upper_left_subsubdivision, upper_right_subsubdivision) = self.subdivide_active_edge_again_at_t(&upper_subdivision, @@ -585,7 +563,9 @@ impl<'a> Partitioner<'a> { } if lower_shape == Shape::Concave && - upper_curve.baseline().side(&lower_curve.control_point) < + upper_curve.baseline() + .to_line() + .signed_distance_to_point(&lower_curve.ctrl) < -f32::approx_epsilon() { let (lower_left_subsubdivision, lower_right_subsubdivision) = self.subdivide_active_edge_again_at_t(&lower_subdivision, @@ -652,28 +632,28 @@ impl<'a> Partitioner<'a> { -> (SubdividedActiveEdge, SubdividedActiveEdge) { let curve = subdivision.to_curve(&self.library.b_vertex_positions) .expect("subdivide_active_edge_again_at_t(): not a curve!"); - let (left_curve, right_curve) = curve.subdivide(t); + let (left_curve, right_curve) = curve.assume_monotonic().split(t); let left_control_point_index = self.library.b_vertex_positions.len() as u32; let midpoint_index = left_control_point_index + 1; let right_control_point_index = midpoint_index + 1; self.library.b_vertex_positions.extend([ - left_curve.control_point, - left_curve.endpoints[1], - right_curve.control_point, + left_curve.segment().ctrl, + left_curve.segment().to, + right_curve.segment().ctrl, ].into_iter()); // Initially, assume that the parity is false. We will modify the Loop-Blinn data later if // that is incorrect. self.library.b_vertex_loop_blinn_data.extend([ - BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], - &left_curve.control_point, - &left_curve.endpoints[1], + BVertexLoopBlinnData::control_point(&left_curve.segment().from, + &left_curve.segment().ctrl, + &left_curve.segment().to, bottom), BVertexLoopBlinnData::new(BVertexKind::Endpoint0), - BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], - &right_curve.control_point, - &right_curve.endpoints[1], + BVertexLoopBlinnData::control_point(&right_curve.segment().from, + &right_curve.segment().ctrl, + &right_curve.segment().to, bottom), ].into_iter()); @@ -697,7 +677,7 @@ impl<'a> Partitioner<'a> { -> (SubdividedActiveEdge, SubdividedActiveEdge) { let curve = subdivision.to_curve(&self.library.b_vertex_positions) .expect("subdivide_active_edge_again_at_x(): not a curve!"); - let t = curve.solve_t_for_x(x); + let t = curve.assume_monotonic().solve_t_for_x(x); self.subdivide_active_edge_again_at_t(subdivision, t, bottom) } @@ -750,9 +730,9 @@ impl<'a> Partitioner<'a> { } fn find_point_between_active_edges(&self, endpoint_index: u32) -> u32 { - let endpoint = &self.endpoints[endpoint_index as usize]; + let endpoint = &self.path.endpoints[endpoint_index as usize]; match self.active_edges.iter().position(|active_edge| { - self.solve_active_edge_y_for_x(endpoint.position.x, active_edge) > endpoint.position.y + self.solve_active_edge_y_for_x(endpoint.to.x, active_edge) > endpoint.to.y }) { Some(active_edge_index) => active_edge_index as u32, None => self.active_edges.len() as u32, @@ -762,16 +742,24 @@ impl<'a> Partitioner<'a> { fn solve_active_edge_t_for_x(&self, x: f32, active_edge: &ActiveEdge) -> f32 { let left_vertex_position = &self.library.b_vertex_positions[active_edge.left_vertex_index as usize]; - let right_endpoint_position = &self.endpoints[active_edge.right_endpoint_index as usize] - .position; + let right_endpoint_position = + &self.path.endpoints[active_edge.right_endpoint_index as usize].to; match active_edge.control_point_vertex_index { - u32::MAX => Line::new(left_vertex_position, right_endpoint_position).solve_t_for_x(x), + u32::MAX => { + LineSegment { + from: *left_vertex_position, + to: *right_endpoint_position, + }.solve_t_for_x(x) + } control_point_vertex_index => { let control_point = &self.library .b_vertex_positions[control_point_vertex_index as usize]; - Curve::new(left_vertex_position, - control_point, - right_endpoint_position).solve_t_for_x(x) + let segment = QuadraticBezierSegment { + from: *left_vertex_position, + ctrl: *control_point, + to: *right_endpoint_position, + }; + segment.assume_monotonic().solve_t_for_x(x) } } } @@ -784,7 +772,7 @@ impl<'a> Partitioner<'a> { let left_vertex_position = &self.library.b_vertex_positions[active_edge.left_vertex_index as usize]; let right_endpoint_position = - &self.endpoints[active_edge.right_endpoint_index as usize].position; + &self.path.endpoints[active_edge.right_endpoint_index as usize].to; match active_edge.control_point_vertex_index { u32::MAX => { left_vertex_position.to_vector() @@ -794,7 +782,11 @@ impl<'a> Partitioner<'a> { control_point_vertex_index => { let control_point = &self.library .b_vertex_positions[control_point_vertex_index as usize]; - Curve::new(left_vertex_position, control_point, right_endpoint_position).sample(t) + QuadraticBezierSegment { + from: *left_vertex_position, + ctrl: *control_point, + to: *right_endpoint_position, + }.sample(t) } } } @@ -813,48 +805,54 @@ impl<'a> Partitioner<'a> { let upper_left_vertex_position = &self.library.b_vertex_positions[upper_active_edge.left_vertex_index as usize]; let upper_right_endpoint_position = - &self.endpoints[upper_active_edge.right_endpoint_index as usize].position; + &self.path.endpoints[upper_active_edge.right_endpoint_index as usize].to; let lower_left_vertex_position = &self.library.b_vertex_positions[lower_active_edge.left_vertex_index as usize]; let lower_right_endpoint_position = - &self.endpoints[lower_active_edge.right_endpoint_index as usize].position; + &self.path.endpoints[lower_active_edge.right_endpoint_index as usize].to; match (upper_active_edge.control_point_vertex_index, lower_active_edge.control_point_vertex_index) { (u32::MAX, u32::MAX) => { - let (upper_line, _) = - Line::new(upper_left_vertex_position, - upper_right_endpoint_position).subdivide_at_x(max_x); - let (lower_line, _) = - Line::new(lower_left_vertex_position, - lower_right_endpoint_position).subdivide_at_x(max_x); - upper_line.intersect(&lower_line) + let (upper_line, _) = LineSegment { + from: *upper_left_vertex_position, + to: *upper_right_endpoint_position, + }.split_at_x(max_x); + let (lower_line, _) = LineSegment { + from: *lower_left_vertex_position, + to: *lower_right_endpoint_position, + }.split_at_x(max_x); + upper_line.intersection(&lower_line) } (upper_control_point_vertex_index, u32::MAX) => { let upper_control_point = &self.library.b_vertex_positions[upper_control_point_vertex_index as usize]; - let (upper_curve, _) = - Curve::new(&upper_left_vertex_position, - &upper_control_point, - &upper_right_endpoint_position).subdivide_at_x(max_x); - let (lower_line, _) = - Line::new(lower_left_vertex_position, - lower_right_endpoint_position).subdivide_at_x(max_x); - upper_curve.intersect(&lower_line) + let (upper_curve, _) = QuadraticBezierSegment { + from: *upper_left_vertex_position, + ctrl: *upper_control_point, + to: *upper_right_endpoint_position, + }.assume_monotonic().split_at_x(max_x); + let (lower_line, _) = LineSegment { + from: *lower_left_vertex_position, + to: *lower_right_endpoint_position, + }.split_at_x(max_x); + upper_curve.segment().line_segment_intersections(&lower_line).pop() } (u32::MAX, lower_control_point_vertex_index) => { let lower_control_point = &self.library.b_vertex_positions[lower_control_point_vertex_index as usize]; - let (lower_curve, _) = - Curve::new(&lower_left_vertex_position, - &lower_control_point, - &lower_right_endpoint_position).subdivide_at_x(max_x); - let (upper_line, _) = - Line::new(upper_left_vertex_position, - upper_right_endpoint_position).subdivide_at_x(max_x); - lower_curve.intersect(&upper_line) + let (lower_curve, _) = QuadraticBezierSegment { + from: *lower_left_vertex_position, + ctrl: *lower_control_point, + to: *lower_right_endpoint_position, + }.assume_monotonic().split_at_x(max_x); + let (upper_line, _) = LineSegment { + from: *upper_left_vertex_position, + to: *upper_right_endpoint_position, + }.split_at_x(max_x); + lower_curve.segment().line_segment_intersections(&upper_line).pop() } (upper_control_point_vertex_index, lower_control_point_vertex_index) => { @@ -862,15 +860,20 @@ impl<'a> Partitioner<'a> { &self.library.b_vertex_positions[upper_control_point_vertex_index as usize]; let lower_control_point = &self.library.b_vertex_positions[lower_control_point_vertex_index as usize]; - let (upper_curve, _) = - Curve::new(&upper_left_vertex_position, - &upper_control_point, - &upper_right_endpoint_position).subdivide_at_x(max_x); - let (lower_curve, _) = - Curve::new(&lower_left_vertex_position, - &lower_control_point, - &lower_right_endpoint_position).subdivide_at_x(max_x); - upper_curve.intersect(&lower_curve) + let (upper_curve, _) = QuadraticBezierSegment { + from: *upper_left_vertex_position, + ctrl: *upper_control_point, + to: *upper_right_endpoint_position, + }.assume_monotonic().split_at_x(max_x); + let (lower_curve, _) = QuadraticBezierSegment { + from: *lower_left_vertex_position, + ctrl: *lower_control_point, + to: *lower_right_endpoint_position, + }.assume_monotonic().split_at_x(max_x); + upper_curve.first_intersection(0.0..1.0, + &lower_curve, + 0.0..1.0, + INTERSECTION_TOLERANCE) } } } @@ -898,8 +901,8 @@ impl<'a> Partitioner<'a> { let left_curve_control_point_vertex_index; match active_edge.control_point_vertex_index { u32::MAX => { - let right_point = self.endpoints[active_edge.right_endpoint_index as usize] - .position; + let right_point = + self.path.endpoints[active_edge.right_endpoint_index as usize].to; let middle_point = left_point_position.to_vector() .lerp(right_point.to_vector(), t); @@ -917,11 +920,12 @@ impl<'a> Partitioner<'a> { self.library .b_vertex_positions[active_edge.control_point_vertex_index as usize]; let right_endpoint_position = - self.endpoints[active_edge.right_endpoint_index as usize].position; - let original_curve = Curve::new(&left_endpoint_position, - &control_point_position, - &right_endpoint_position); - let (left_curve, right_curve) = original_curve.subdivide(t); + self.path.endpoints[active_edge.right_endpoint_index as usize].to; + let (left_curve, right_curve) = QuadraticBezierSegment { + from: left_endpoint_position, + ctrl: control_point_position, + to: right_endpoint_position, + }.split(t); left_curve_control_point_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; @@ -930,18 +934,18 @@ impl<'a> Partitioner<'a> { // FIXME(pcwalton): Normals self.library - .add_b_vertex(&left_curve.control_point, - &BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], - &left_curve.control_point, - &left_curve.endpoints[1], + .add_b_vertex(&left_curve.ctrl, + &BVertexLoopBlinnData::control_point(&left_curve.from, + &left_curve.ctrl, + &left_curve.to, bottom)); - self.library.add_b_vertex(&left_curve.endpoints[1], + self.library.add_b_vertex(&left_curve.to, &BVertexLoopBlinnData::new(active_edge.endpoint_kind())); self.library - .add_b_vertex(&right_curve.control_point, - &BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], - &right_curve.control_point, - &right_curve.endpoints[1], + .add_b_vertex(&right_curve.ctrl, + &BVertexLoopBlinnData::control_point(&right_curve.from, + &right_curve.ctrl, + &right_curve.to, bottom)); } } @@ -1006,38 +1010,38 @@ impl<'a> Partitioner<'a> { } fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 { - let endpoint = &self.endpoints[endpoint_index as usize]; - let subpath = &self.subpaths[endpoint.subpath_index as usize]; - if endpoint_index > subpath.first_endpoint_index { + let endpoint = &self.path.endpoints[endpoint_index as usize]; + let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize]; + if endpoint_index > subpath.start { endpoint_index - 1 } else { - subpath.last_endpoint_index - 1 + subpath.end - 1 } } fn next_endpoint_of(&self, endpoint_index: u32) -> u32 { - let endpoint = &self.endpoints[endpoint_index as usize]; - let subpath = &self.subpaths[endpoint.subpath_index as usize]; - if endpoint_index + 1 < subpath.last_endpoint_index { + let endpoint = &self.path.endpoints[endpoint_index as usize]; + let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize]; + if endpoint_index + 1 < subpath.end { endpoint_index + 1 } else { - subpath.first_endpoint_index + subpath.start } } fn create_point_from_endpoint(&self, endpoint_index: u32) -> Point { Point { - position: self.endpoints[endpoint_index as usize].position, + position: self.path.endpoints[endpoint_index as usize].to, endpoint_index: endpoint_index, } } - fn control_point_index_before_endpoint(&self, endpoint_index: u32) -> u32 { - self.endpoints[endpoint_index as usize].control_point_index + fn control_point_before_endpoint(&self, endpoint_index: u32) -> Option> { + self.path.endpoints[endpoint_index as usize].ctrl } - fn control_point_index_after_endpoint(&self, endpoint_index: u32) -> u32 { - self.control_point_index_before_endpoint(self.next_endpoint_of(endpoint_index)) + fn control_point_after_endpoint(&self, endpoint_index: u32) -> Option> { + self.control_point_before_endpoint(self.next_endpoint_of(endpoint_index)) } } @@ -1145,13 +1149,16 @@ impl SubdividedActiveEdge { } } - fn to_curve(&self, b_vertex_positions: &[Point2D]) -> Option { + fn to_curve(&self, b_vertex_positions: &[Point2D]) + -> Option> { if self.left_curve_control_point == u32::MAX { None } else { - Some(Curve::new(&b_vertex_positions[self.left_curve_left as usize], - &b_vertex_positions[self.left_curve_control_point as usize], - &b_vertex_positions[self.middle_point as usize])) + Some(QuadraticBezierSegment { + from: b_vertex_positions[self.left_curve_left as usize], + ctrl: b_vertex_positions[self.left_curve_control_point as usize], + to: b_vertex_positions[self.middle_point as usize], + }) } } } diff --git a/path-utils/Cargo.toml b/path-utils/Cargo.toml index fe1ee7fc..ded4f091 100644 --- a/path-utils/Cargo.toml +++ b/path-utils/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Patrick Walton "] [dependencies] arrayvec = "0.4" -euclid = "0.15" +euclid = "0.16" +lyon_geom = "0.9" +lyon_path = "0.9" serde = "1.0" serde_derive = "1.0" diff --git a/path-utils/src/cubic.rs b/path-utils/src/cubic.rs deleted file mode 100644 index 14d7624b..00000000 --- a/path-utils/src/cubic.rs +++ /dev/null @@ -1,214 +0,0 @@ -// pathfinder/path-utils/src/cubic.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. - -//! Utilities for cubic Bézier curves. - -use euclid::Point2D; - -use curve::Curve; -use PathCommand; - -const MAX_APPROXIMATION_ITERATIONS: u8 = 32; - -/// A cubic Bézier curve. -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct CubicCurve { - /// The endpoints of the curve. - pub endpoints: [Point2D; 2], - /// The control points of the curve. - pub control_points: [Point2D; 2], -} - -impl CubicCurve { - /// Constructs a new cubic Bézier curve from the given points. - #[inline] - pub fn new(endpoint_0: &Point2D, - control_point_0: &Point2D, - control_point_1: &Point2D, - endpoint_1: &Point2D) - -> CubicCurve { - CubicCurve { - endpoints: [*endpoint_0, *endpoint_1], - control_points: [*control_point_0, *control_point_1], - } - } - - /// Returns the curve point at the given t value (from 0.0 to 1.0). - pub fn sample(&self, t: f32) -> Point2D { - let (p0, p3) = (&self.endpoints[0], &self.endpoints[1]); - let (p1, p2) = (&self.control_points[0], &self.control_points[1]); - let (p0p1, p1p2, p2p3) = (p0.lerp(*p1, t), p1.lerp(*p2, t), p2.lerp(*p3, t)); - let (p0p1p2, p1p2p3) = (p0p1.lerp(p1p2, t), p1p2.lerp(p2p3, t)); - p0p1p2.lerp(p1p2p3, t) - } - - /// De Casteljau subdivides this curve into two at the given t value (from 0.0 to 1.0). - pub fn subdivide(&self, t: f32) -> (CubicCurve, CubicCurve) { - let (p0, p3) = (&self.endpoints[0], &self.endpoints[1]); - let (p1, p2) = (&self.control_points[0], &self.control_points[1]); - let (p0p1, p1p2, p2p3) = (p0.lerp(*p1, t), p1.lerp(*p2, t), p2.lerp(*p3, t)); - let (p0p1p2, p1p2p3) = (p0p1.lerp(p1p2, t), p1p2.lerp(p2p3, t)); - let p0p1p2p3 = p0p1p2.lerp(p1p2p3, t); - (CubicCurve::new(&p0, &p0p1, &p0p1p2, &p0p1p2p3), - CubicCurve::new(&p0p1p2p3, &p1p2p3, &p2p3, &p3)) - } - - /// Approximates this curve with a series of quadratic Bézier curves. - /// - /// The quadratic curves are guaranteed not to deviate from this cubic curve by more than - /// `error_bound`. - pub fn approx_curve(&self, error_bound: f32) -> ApproxCurveIter { - ApproxCurveIter::new(self, error_bound) - } -} - -/// A series of path commands that can contain cubic Bézier segments. -#[derive(Clone, Copy, Debug)] -pub enum CubicPathCommand { - /// Moves the pen to the given point. - MoveTo(Point2D), - /// Draws a line to the given point. - LineTo(Point2D), - /// Draws a quadratic curve with the control point to the endpoint, respectively. - QuadCurveTo(Point2D, Point2D), - /// Draws a cubic curve with the two control points to the endpoint, respectively. - CubicCurveTo(Point2D, Point2D, Point2D), - /// Closes the current subpath by drawing a line from the current point to the first point of - /// the subpath. - ClosePath, -} - -/// Converts a series of path commands that can contain cubic Bézier segments to a series of path -/// commands that contain only quadratic Bézier segments. -pub struct CubicPathCommandApproxStream { - inner: I, - error_bound: f32, - last_endpoint: Point2D, - approx_curve_iter: Option, -} - -impl CubicPathCommandApproxStream where I: Iterator { - /// Creates a stream that approximates the given path commands by converting all cubic Bézier - /// curves to quadratic Bézier curves. - /// - /// The resulting path command stream is guaranteed not to deviate more than a distance of - /// `error_bound` from the original path command stream. - #[inline] - pub fn new(inner: I, error_bound: f32) -> CubicPathCommandApproxStream { - CubicPathCommandApproxStream { - inner: inner, - error_bound: error_bound, - last_endpoint: Point2D::zero(), - approx_curve_iter: None, - } - } -} - -impl Iterator for CubicPathCommandApproxStream where I: Iterator { - type Item = PathCommand; - - fn next(&mut self) -> Option { - loop { - if let Some(ref mut approx_curve_iter) = self.approx_curve_iter { - if let Some(curve) = approx_curve_iter.next() { - return Some(curve.to_path_command()) - } - } - self.approx_curve_iter = None; - - let next_command = match self.inner.next() { - None => return None, - Some(next_command) => next_command, - }; - - match next_command { - CubicPathCommand::ClosePath => { - self.last_endpoint = Point2D::zero(); - return Some(PathCommand::ClosePath) - } - CubicPathCommand::MoveTo(endpoint) => { - self.last_endpoint = endpoint; - return Some(PathCommand::MoveTo(endpoint)) - } - CubicPathCommand::LineTo(endpoint) => { - self.last_endpoint = endpoint; - return Some(PathCommand::LineTo(endpoint)) - } - CubicPathCommand::QuadCurveTo(control_point, endpoint) => { - self.last_endpoint = endpoint; - return Some(PathCommand::CurveTo(control_point, endpoint)) - } - CubicPathCommand::CubicCurveTo(control_point_0, control_point_1, endpoint) => { - let curve = CubicCurve::new(&self.last_endpoint, - &control_point_0, - &control_point_1, - &endpoint); - self.last_endpoint = endpoint; - self.approx_curve_iter = Some(ApproxCurveIter::new(&curve, self.error_bound)); - } - } - } - } -} - -/// Approximates a single cubic Bézier curve with a series of quadratic Bézier curves. -pub struct ApproxCurveIter { - curves: Vec, - error_bound: f32, - iteration: u8, -} - -impl ApproxCurveIter { - fn new(cubic: &CubicCurve, error_bound: f32) -> ApproxCurveIter { - let (curve_a, curve_b) = cubic.subdivide(0.5); - ApproxCurveIter { - curves: vec![curve_b, curve_a], - error_bound: error_bound, - iteration: 0, - } - } -} - -impl Iterator for ApproxCurveIter { - type Item = Curve; - - fn next(&mut self) -> Option { - let mut cubic = match self.curves.pop() { - Some(cubic) => cubic, - None => return None, - }; - - while self.iteration < MAX_APPROXIMATION_ITERATIONS { - self.iteration += 1; - - // See Sederberg § 2.6, "Distance Between Two Bézier Curves". - let delta_control_point_0 = (cubic.endpoints[0] - cubic.control_points[0] * 3.0) + - (cubic.control_points[1] * 3.0 - cubic.endpoints[1]); - let delta_control_point_1 = (cubic.control_points[0] * 3.0 - cubic.endpoints[0]) + - (cubic.endpoints[1] - cubic.control_points[1] * 3.0); - let max_error = f32::max(delta_control_point_1.length(), - delta_control_point_0.length()) / 6.0; - if max_error < self.error_bound { - break - } - - let (cubic_a, cubic_b) = cubic.subdivide(0.5); - self.curves.push(cubic_b); - cubic = cubic_a - } - - let approx_control_point_0 = (cubic.control_points[0] * 3.0 - cubic.endpoints[0]) * 0.5; - let approx_control_point_1 = (cubic.control_points[1] * 3.0 - cubic.endpoints[1]) * 0.5; - - Some(Curve::new(&cubic.endpoints[0], - &approx_control_point_0.lerp(approx_control_point_1, 0.5).to_point(), - &cubic.endpoints[1])) - } -} diff --git a/path-utils/src/cubic_to_quadratic.rs b/path-utils/src/cubic_to_quadratic.rs new file mode 100644 index 00000000..79e1430c --- /dev/null +++ b/path-utils/src/cubic_to_quadratic.rs @@ -0,0 +1,69 @@ +// pathfinder/partitioner/src/cubic_to_quadratic.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. + +//! A version of Lyon's `cubic_to_quadratic` that is less sensitive to floating point error. + +use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment}; + +const MAX_APPROXIMATION_ITERATIONS: u8 = 32; + +/// Approximates a single cubic Bézier curve with a series of quadratic Bézier curves. +pub struct CubicToQuadraticSegmentIter { + cubic_curves: Vec>, + error_bound: f32, + iteration: u8, +} + +impl CubicToQuadraticSegmentIter { + pub fn new(cubic: &CubicBezierSegment, error_bound: f32) -> CubicToQuadraticSegmentIter { + let (curve_a, curve_b) = cubic.split(0.5); + CubicToQuadraticSegmentIter { + cubic_curves: vec![curve_b, curve_a], + error_bound: error_bound, + iteration: 0, + } + } +} + +impl Iterator for CubicToQuadraticSegmentIter { + type Item = QuadraticBezierSegment; + + fn next(&mut self) -> Option> { + let mut cubic = match self.cubic_curves.pop() { + Some(cubic) => cubic, + None => return None, + }; + + while self.iteration < MAX_APPROXIMATION_ITERATIONS { + self.iteration += 1; + + // See Sederberg § 2.6, "Distance Between Two Bézier Curves". + let delta_ctrl_0 = (cubic.from - cubic.ctrl1 * 3.0) + (cubic.ctrl2 * 3.0 - cubic.to); + let delta_ctrl_1 = (cubic.ctrl1 * 3.0 - cubic.from) + (cubic.to - cubic.ctrl2 * 3.0); + let max_error = f32::max(delta_ctrl_1.length(), delta_ctrl_0.length()) / 6.0; + if max_error < self.error_bound { + break + } + + let (cubic_a, cubic_b) = cubic.split(0.5); + self.cubic_curves.push(cubic_b); + cubic = cubic_a + } + + let approx_ctrl_0 = (cubic.ctrl1 * 3.0 - cubic.from) * 0.5; + let approx_ctrl_1 = (cubic.ctrl2 * 3.0 - cubic.to) * 0.5; + + Some(QuadraticBezierSegment { + from: cubic.from, + ctrl: approx_ctrl_0.lerp(approx_ctrl_1, 0.5).to_point(), + to: cubic.to, + }) + } +} diff --git a/path-utils/src/curve.rs b/path-utils/src/curve.rs deleted file mode 100644 index d14e4ab5..00000000 --- a/path-utils/src/curve.rs +++ /dev/null @@ -1,146 +0,0 @@ -// pathfinder/path-utils/src/curve.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. - -//! Geometry utilities for quadratic Bézier curves. - -use euclid::approxeq::ApproxEq; -use euclid::Point2D; -use std::f32; - -use PathCommand; -use intersection::Intersect; -use line::Line; - -/// A quadratic Bézier curve. -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct Curve { - /// The start and end points of the curve, respectively. - pub endpoints: [Point2D; 2], - /// The control point of the curve. - pub control_point: Point2D, -} - -impl Curve { - /// Creates a new quadratic Bézier curve from the given endpoints and control point. - #[inline] - pub fn new(endpoint_0: &Point2D, control_point: &Point2D, endpoint_1: &Point2D) - -> Curve { - Curve { - endpoints: [*endpoint_0, *endpoint_1], - control_point: *control_point, - } - } - - /// Returns the curve point at time `t` (0.0 to 1.0). - #[inline] - pub fn sample(&self, t: f32) -> Point2D { - let (p0, p1, p2) = (&self.endpoints[0], &self.control_point, &self.endpoints[1]); - Point2D::lerp(&p0.lerp(*p1, t), p1.lerp(*p2, t), t) - } - - /// De Casteljau subdivides this curve into two at time `t` (0.0 to 1.0). - /// - /// Returns the two resulting curves. - #[inline] - pub fn subdivide(&self, t: f32) -> (Curve, Curve) { - let (p0, p1, p2) = (&self.endpoints[0], &self.control_point, &self.endpoints[1]); - let (ap1, bp1) = (p0.lerp(*p1, t), p1.lerp(*p2, t)); - let ap2bp0 = ap1.lerp(bp1, t); - (Curve::new(p0, &ap1, &ap2bp0), Curve::new(&ap2bp0, &bp1, p2)) - } - - /// Divides this curve into two at the point with *x* coordinate equal to `x`. - /// - /// Results are undefined if there is not exactly one point on the curve with *x* coordinate - /// equal to `x`. - pub fn subdivide_at_x(&self, x: f32) -> (Curve, Curve) { - let (prev_part, next_part) = self.subdivide(self.solve_t_for_x(x)); - if self.endpoints[0].x <= self.endpoints[1].x { - (prev_part, next_part) - } else { - (next_part, prev_part) - } - } - - /// A convenience method that constructs a `CurveTo` path command from this curve's control - /// point and second endpoint. - #[inline] - pub fn to_path_command(&self) -> PathCommand { - PathCommand::CurveTo(self.control_point, self.endpoints[1]) - } - - /// Returns the times at which the derivative of the curve becomes 0 with respect to *x* and - /// *y* in that order. - pub fn inflection_points(&self) -> (Option, Option) { - let inflection_point_x = Curve::inflection_point_x(self.endpoints[0].x, - self.control_point.x, - self.endpoints[1].x); - let inflection_point_y = Curve::inflection_point_x(self.endpoints[0].y, - self.control_point.y, - self.endpoints[1].y); - (inflection_point_x, inflection_point_y) - } - - /// Returns the time of the single point on this curve with *x* coordinate equal to `x`. - /// - /// Internally, this method uses the [Citardauq Formula] to avoid precision problems. - /// - /// If there is not exactly one point with *x* coordinate equal to `x`, the results are - /// undefined. - /// - /// [Citardauq Formula]: https://math.stackexchange.com/a/311397 - pub fn solve_t_for_x(&self, x: f32) -> f32 { - let p0x = self.endpoints[0].x as f64; - let p1x = self.control_point.x as f64; - let p2x = self.endpoints[1].x as f64; - let x = x as f64; - - let a = p0x - 2.0 * p1x + p2x; - let b = -2.0 * p0x + 2.0 * p1x; - let c = p0x - x; - - let t = 2.0 * c / (-b - (b * b - 4.0 * a * c).sqrt()); - t.max(0.0).min(1.0) as f32 - } - - /// A convenience method that returns the *y* coordinate of the single point on this curve with - /// *x* coordinate equal to `x`. - /// - /// Results are undefined if there is not exactly one point with *x* coordinate equal to `x`. - #[inline] - pub fn solve_y_for_x(&self, x: f32) -> f32 { - self.sample(self.solve_t_for_x(x)).y - } - - /// Returns a line segment from the start endpoint of this curve to the end of this curve. - #[inline] - pub fn baseline(&self) -> Line { - Line::new(&self.endpoints[0], &self.endpoints[1]) - } - - #[inline] - fn inflection_point_x(endpoint_x_0: f32, control_point_x: f32, endpoint_x_1: f32) - -> Option { - let num = endpoint_x_0 - control_point_x; - let denom = endpoint_x_0 - 2.0 * control_point_x + endpoint_x_1; - let t = num / denom; - if t > f32::approx_epsilon() && t < 1.0 - f32::approx_epsilon() { - Some(t) - } else { - None - } - } - - /// Returns the point of intersection of this curve with the given curve. - #[inline] - pub fn intersect(&self, other: &T) -> Option> where T: Intersect { - ::intersect(self, other) - } -} diff --git a/path-utils/src/intersection.rs b/path-utils/src/intersection.rs deleted file mode 100644 index 313e9e23..00000000 --- a/path-utils/src/intersection.rs +++ /dev/null @@ -1,99 +0,0 @@ -// pathfinder/path-utils/src/intersection.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. - -//! Intersections of two curves. - -use euclid::approxeq::ApproxEq; -use euclid::Point2D; - -use curve::Curve; -use line::Line; -use {lerp, sign}; - -const MAX_ITERATIONS: u8 = 32; - -/// Represents a line or curve that intersections can be computed for. -pub trait Intersect { - /// The minimum *x* extent of this curve. - fn min_x(&self) -> f32; - /// The minimum *y* extent of this curve. - fn max_x(&self) -> f32; - /// Finds the *y* coordinate of the single point on this curve with the given *x* coordinate. - /// - /// If this curve does not have exactly one such point, the result is undefined. - fn solve_y_for_x(&self, x: f32) -> f32; - - /// Returns a point at which this curve intersects the other curve, if such a point exists. - /// - /// Requires that any curves be monotonically increasing or decreasing. (See the `monotonic` - /// module for utilities to convert curves to this form.) - /// - /// This algorithm used to be smarter (based on implicitization) but floating point error - /// forced the adoption of this simpler, but slower, technique. Improvements are welcome. - fn intersect(&self, other: &T) -> Option> where T: Intersect { - let mut min_x = f32::max(self.min_x(), other.min_x()); - let mut max_x = f32::min(self.max_x(), other.max_x()); - let mut iteration = 0; - - while iteration < MAX_ITERATIONS && max_x - min_x > f32::approx_epsilon() { - iteration += 1; - - let mid_x = lerp(min_x, max_x, 0.5); - let min_sign = sign(self.solve_y_for_x(min_x) - other.solve_y_for_x(min_x)); - let mid_sign = sign(self.solve_y_for_x(mid_x) - other.solve_y_for_x(mid_x)); - let max_sign = sign(self.solve_y_for_x(max_x) - other.solve_y_for_x(max_x)); - - if min_sign == mid_sign && mid_sign != max_sign { - min_x = mid_x - } else if min_sign != mid_sign && mid_sign == max_sign { - max_x = min_x - } else { - break - } - } - - let mid_x = lerp(min_x, max_x, 0.5); - Some(Point2D::new(mid_x, self.solve_y_for_x(mid_x))) - } -} - -impl Intersect for Line { - #[inline] - fn min_x(&self) -> f32 { - f32::min(self.endpoints[0].x, self.endpoints[1].x) - } - - #[inline] - fn max_x(&self) -> f32 { - f32::max(self.endpoints[0].x, self.endpoints[1].x) - } - - #[inline] - fn solve_y_for_x(&self, x: f32) -> f32 { - Line::solve_y_for_x(self, x) - } -} - -impl Intersect for Curve { - #[inline] - fn min_x(&self) -> f32 { - f32::min(self.endpoints[0].x, self.endpoints[1].x) - } - - #[inline] - fn max_x(&self) -> f32 { - f32::max(self.endpoints[0].x, self.endpoints[1].x) - } - - #[inline] - fn solve_y_for_x(&self, x: f32) -> f32 { - Curve::solve_y_for_x(self, x) - } -} diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs index c996ce3e..d0a90dd6 100644 --- a/path-utils/src/lib.rs +++ b/path-utils/src/lib.rs @@ -1,6 +1,6 @@ // pathfinder/path-utils/src/lib.rs // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -9,400 +9,16 @@ // except according to those terms. //! Various utilities for manipulating Bézier curves. -//! -//! On its own, the partitioner can only generate meshes for fill operations on quadratic Bézier -//! curves. Frequently, however, other vector drawing operations are desired: for example, -//! rendering cubic Béziers or stroking paths. These utilities can convert those complex operations -//! into simpler sequences of quadratic Béziers that the partitioner can handle. +//! +//! Most of these should go upstream to Lyon at some point. extern crate arrayvec; extern crate euclid; -#[macro_use] -extern crate serde_derive; +extern crate lyon_geom; +extern crate lyon_path; -use euclid::{Point2D, Transform2D}; -use std::mem; -use std::ops::Range; -use std::u32; - -pub mod cubic; -pub mod curve; -pub mod intersection; -pub mod line; -pub mod monotonic; +pub mod cubic_to_quadratic; +pub mod normals; +pub mod segments; pub mod stroke; -pub mod svg; - -/// A series of commands that define quadratic Bézier paths. -/// -/// For cubics, see the `cubic` module. -#[derive(Clone, Copy, Debug)] -pub enum PathCommand { - /// Moves the pen to the given point. - MoveTo(Point2D), - /// Draws a line to the given point. - LineTo(Point2D), - /// Draws a quadratic curve with the control point to the endpoint, respectively. - CurveTo(Point2D, Point2D), - /// Closes the current subpath by drawing a line from the current point to the first point of - /// the subpath. - ClosePath, -} - -/// Holds one or more paths in memory in an efficient form. -/// -/// This structure is generally preferable to `Vec` if you need to buffer paths in -/// memory. It is both smaller and offers random access to individual subpaths. -#[derive(Clone, Debug)] -pub struct PathBuffer { - /// All endpoints of all subpaths. - pub endpoints: Vec, - /// All control points of all subpaths. - pub control_points: Vec>, - /// A series of ranges defining each subpath. - pub subpaths: Vec, -} - -impl PathBuffer { - /// Creates a new, empty path buffer. - #[inline] - pub fn new() -> PathBuffer { - PathBuffer { - endpoints: vec![], - control_points: vec![], - subpaths: vec![], - } - } - - /// Appends a sequence of path commands to this path buffer. - 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 { - PathCommand::ClosePath => self.close_subpath(&mut first_subpath_endpoint_index), - - PathCommand::MoveTo(position) => { - self.end_subpath(&mut first_subpath_endpoint_index); - self.endpoints.push(Endpoint { - position: position, - control_point_index: u32::MAX, - subpath_index: self.subpaths.len() as u32, - }) - } - - PathCommand::LineTo(position) => { - self.endpoints.push(Endpoint { - position: position, - control_point_index: u32::MAX, - subpath_index: self.subpaths.len() as u32, - }) - } - - PathCommand::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.end_subpath(&mut first_subpath_endpoint_index) - } - - fn close_subpath(&mut self, first_subpath_endpoint_index: &mut u32) { - if self.endpoints.len() > *first_subpath_endpoint_index as usize { - let first_endpoint = self.endpoints[*first_subpath_endpoint_index as usize]; - self.endpoints.push(first_endpoint); - } - - self.do_end_subpath(first_subpath_endpoint_index, true) - } - - fn end_subpath(&mut self, first_subpath_endpoint_index: &mut u32) { - self.do_end_subpath(first_subpath_endpoint_index, false) - } - - fn do_end_subpath(&mut self, first_subpath_endpoint_index: &mut u32, closed: bool) { - 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, - closed: closed, - }) - } - - *first_subpath_endpoint_index = last_subpath_endpoint_index; - } - - /// Reverses the winding order of the subpath with the given index. - pub fn reverse_subpath(&mut self, subpath_index: u32) { - let subpath = &self.subpaths[subpath_index as usize]; - let endpoint_range = subpath.range(); - if endpoint_range.start == endpoint_range.end { - return - } - - self.endpoints[endpoint_range.clone()].reverse(); - - for endpoint_index in (endpoint_range.start..(endpoint_range.end - 1)).rev() { - let control_point_index = self.endpoints[endpoint_index].control_point_index; - self.endpoints[endpoint_index + 1].control_point_index = control_point_index; - } - - self.endpoints[endpoint_range.start].control_point_index = u32::MAX; - } -} - -/// Converts a path buffer back into a series of path commands. -#[derive(Clone)] -pub struct PathBufferStream<'a> { - path_buffer: &'a PathBuffer, - endpoint_index: u32, - subpath_index: u32, - last_subpath_index: u32, -} - -impl<'a> PathBufferStream<'a> { - /// Prepares a path buffer stream to stream all subpaths from the given path buffer. - #[inline] - pub fn new<'b>(path_buffer: &'b PathBuffer) -> PathBufferStream<'b> { - PathBufferStream { - path_buffer: path_buffer, - endpoint_index: 0, - subpath_index: 0, - last_subpath_index: path_buffer.subpaths.len() as u32, - } - } - - /// Prepares a path buffer stream to stream only a subset of subpaths from the given path - /// buffer. - #[inline] - pub fn subpath_range<'b>(path_buffer: &'b PathBuffer, subpath_range: Range) - -> PathBufferStream<'b> { - let first_endpoint_index = if subpath_range.start == subpath_range.end { - 0 - } else { - path_buffer.subpaths[subpath_range.start as usize].first_endpoint_index - }; - PathBufferStream { - path_buffer: path_buffer, - endpoint_index: first_endpoint_index, - subpath_index: subpath_range.start, - last_subpath_index: subpath_range.end, - } - } -} - -impl<'a> Iterator for PathBufferStream<'a> { - type Item = PathCommand; - - fn next(&mut self) -> Option { - if self.subpath_index == self.last_subpath_index { - return None - } - - let subpath = &self.path_buffer.subpaths[self.subpath_index as usize]; - if self.endpoint_index == subpath.last_endpoint_index { - self.subpath_index += 1; - if subpath.closed { - return Some(PathCommand::ClosePath) - } - return self.next() - } - - let endpoint_index = self.endpoint_index; - self.endpoint_index += 1; - - let endpoint = &self.path_buffer.endpoints[endpoint_index as usize]; - - if endpoint_index == subpath.first_endpoint_index { - return Some(PathCommand::MoveTo(endpoint.position)) - } - - if endpoint.control_point_index == u32::MAX { - return Some(PathCommand::LineTo(endpoint.position)) - } - - let control_point = &self.path_buffer - .control_points[endpoint.control_point_index as usize]; - Some(PathCommand::CurveTo(*control_point, endpoint.position)) - } -} - -/// Describes a path endpoint in a path buffer. -#[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Endpoint { - /// The 2D position of the endpoint. - pub position: Point2D, - /// The index of the control point *before* this endpoint in the `control_points` vector, or - /// `u32::MAX` if this endpoint is the end of a line segment. - pub control_point_index: u32, - /// The index of the subpath that this endpoint belongs to. - pub subpath_index: u32, -} - -/// Stores the endpoint indices of each subpath. -#[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Subpath { - /// The index of the first endpoint that makes up this subpath. - pub first_endpoint_index: u32, - /// One plus the index of the last endpoint that makes up this subpath. - pub last_endpoint_index: u32, - /// Whether the subpath is closed (i.e. fully connected). - pub closed: bool, -} - -impl Subpath { - /// Returns the endpoint indices as a `Range`. - #[inline] - pub fn range(self) -> Range { - (self.first_endpoint_index as usize)..(self.last_endpoint_index as usize) - } -} - -/// Represents a single path segment (i.e. a single side of a Béziergon). -#[derive(Debug, Clone, Copy)] -pub enum PathSegment { - /// A line segment with two endpoints. - Line(Point2D, Point2D), - /// A quadratic Bézier curve with an endpoint, a control point, and another endpoint, in that - /// order. - Curve(Point2D, Point2D, Point2D), -} - -/// Yields a set of `PathSegment`s corresponding to a list of `PathCommand`s. -/// -/// For example, the path commands `[MoveTo(A), LineTo(B), LineTo(C), ClosePath]` become -/// `[Line(A, B), Line(B, C), Line(C, A)]`. -/// -/// This representation can simplify the implementation of certain geometric algorithms, such as -/// offset paths (stroking). -pub struct PathSegmentStream { - inner: I, - current_subpath_index: u32, - current_point: Point2D, - current_subpath_start_point: Point2D, -} - -impl PathSegmentStream where I: Iterator { - /// Creates a new path segment stream that will yield path segments from the given collection - /// of path commands. - pub fn new(inner: I) -> PathSegmentStream { - PathSegmentStream { - inner: inner, - current_subpath_index: u32::MAX, - current_point: Point2D::zero(), - current_subpath_start_point: Point2D::zero(), - } - } -} - -impl Iterator for PathSegmentStream where I: Iterator { - type Item = (PathSegment, u32); - - fn next(&mut self) -> Option<(PathSegment, u32)> { - loop { - match self.inner.next() { - None => return None, - Some(PathCommand::MoveTo(point)) => { - self.current_subpath_index = self.current_subpath_index.wrapping_add(1); - self.current_point = point; - self.current_subpath_start_point = point; - } - Some(PathCommand::LineTo(endpoint)) => { - if points_are_sufficiently_far_apart(&self.current_point, &endpoint) { - let start_point = mem::replace(&mut self.current_point, endpoint); - return Some((PathSegment::Line(start_point, endpoint), - self.current_subpath_index)) - } - } - Some(PathCommand::CurveTo(control_point, endpoint)) => { - if points_are_sufficiently_far_apart(&self.current_point, &endpoint) { - let start_point = mem::replace(&mut self.current_point, endpoint); - return Some((PathSegment::Curve(start_point, control_point, endpoint), - self.current_subpath_index)) - } - } - Some(PathCommand::ClosePath) => { - let endpoint = self.current_subpath_start_point; - if points_are_sufficiently_far_apart(&self.current_point, &endpoint) { - let start_point = mem::replace(&mut self.current_point, endpoint); - return Some((PathSegment::Line(start_point, endpoint), - self.current_subpath_index)) - } - } - } - } - - fn points_are_sufficiently_far_apart(point_a: &Point2D, point_b: &Point2D) - -> bool { - (point_a.x - point_b.x).abs() > 0.001 || - (point_a.y - point_b.y).abs() > 0.001 - } - } -} - -/// Applies an affine transform to a path stream and yields the resulting path stream. -pub struct Transform2DPathStream { - inner: I, - transform: Transform2D, -} - -impl Transform2DPathStream where I: Iterator { - /// Creates a new transformed path stream from a path stream. - pub fn new(inner: I, transform: &Transform2D) -> Transform2DPathStream { - Transform2DPathStream { - inner: inner, - transform: *transform, - } - } -} - -impl Iterator for Transform2DPathStream where I: Iterator { - type Item = PathCommand; - - fn next(&mut self) -> Option { - match self.inner.next() { - None => None, - Some(PathCommand::MoveTo(position)) => { - Some(PathCommand::MoveTo(self.transform.transform_point(&position))) - } - Some(PathCommand::LineTo(position)) => { - Some(PathCommand::LineTo(self.transform.transform_point(&position))) - } - Some(PathCommand::CurveTo(control_point_position, endpoint_position)) => { - Some(PathCommand::CurveTo(self.transform.transform_point(&control_point_position), - self.transform.transform_point(&endpoint_position))) - } - Some(PathCommand::ClosePath) => Some(PathCommand::ClosePath), - } - } -} - -/// Linear interpolation: `lerp(a, b, t)` = `a + (b - a) * t`. -#[inline] -pub(crate) fn lerp(a: f32, b: f32, t: f32) -> f32 { - a + (b - a) * t -} - -/// Returns -1.0 if the value is negative, 1.0 if the value is positive, or 0.0 if the value is -/// zero. -/// -/// Returns NaN when given NaN. -#[inline] -pub(crate) fn sign(x: f32) -> f32 { - if x < 0.0 { - -1.0 - } else if x > 0.0 { - 1.0 - } else { - x - } -} +pub mod transform; diff --git a/path-utils/src/line.rs b/path-utils/src/line.rs deleted file mode 100644 index 5329944f..00000000 --- a/path-utils/src/line.rs +++ /dev/null @@ -1,151 +0,0 @@ -// pathfinder/path-utils/src/line.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. - -//! Geometry utilities for straight line segments. - -use euclid::approxeq::ApproxEq; -use euclid::{Point2D, Vector2D, Vector3D}; - -use intersection::Intersect; - -/// Represents a straight line segment. -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct Line { - /// The start and end points of the line, respectively. - pub endpoints: [Point2D; 2], -} - -impl Line { - /// Creates a new line segment from the two endpoints. - #[inline] - pub fn new(endpoint_0: &Point2D, endpoint_1: &Point2D) -> Line { - Line { - endpoints: [*endpoint_0, *endpoint_1], - } - } - - /// Returns the point at the given t value (from 0.0 to 1.0). - #[inline] - pub fn sample(&self, t: f32) -> Point2D { - self.endpoints[0].lerp(self.endpoints[1], t) - } - - /// Returns the time value (0.0 to 1.0) for the given X coordinate. - #[inline] - pub fn solve_t_for_x(&self, x: f32) -> f32 { - let x_span = self.endpoints[1].x - self.endpoints[0].x; - if x_span != 0.0 { - (x - self.endpoints[0].x) / x_span - } else { - 0.0 - } - } - - /// Returns the Y coordinate corresponding to the given X coordinate. - #[inline] - pub fn solve_y_for_x(&self, x: f32) -> f32 { - self.sample(self.solve_t_for_x(x)).y - } - - /// Divides the line segment into two at the given time value (0.0 to 1.0). - /// - /// Returns the two resulting line segments. - #[inline] - pub fn subdivide(&self, t: f32) -> (Line, Line) { - let midpoint = self.sample(t); - (Line::new(&self.endpoints[0], &midpoint), Line::new(&midpoint, &self.endpoints[1])) - } - - /// Divides the line segment into two at the given X value. - /// - /// Returns the two resulting line segments. - /// - /// The behavior is undefined if the X value lies outside the line segment span. - pub fn subdivide_at_x(&self, x: f32) -> (Line, Line) { - let (prev_part, next_part) = self.subdivide(self.solve_t_for_x(x)); - if self.endpoints[0].x <= self.endpoints[1].x { - (prev_part, next_part) - } else { - (next_part, prev_part) - } - } - - /// Returns a value whose sign can be tested to determine which side of the line the given - /// point is on. - /// - /// If the point is on the line, returns a value near zero. - #[inline] - pub fn side(&self, point: &Point2D) -> f32 { - self.to_vector().cross(*point - self.endpoints[0]) - } - - #[inline] - pub(crate) fn to_vector(&self) -> Vector2D { - self.endpoints[1] - self.endpoints[0] - } - - /// Computes the point of intersection of this line with the given curve. - #[inline] - pub fn intersect(&self, other: &T) -> Option> where T: Intersect { - ::intersect(self, other) - } - - /// A faster version of `intersect` for the special case of two lines. - /// - /// https://stackoverflow.com/a/565282 - pub fn intersect_with_line(&self, other: &Line) -> Option> { - let (p, r) = (self.endpoints[0], self.to_vector()); - let (q, s) = (self.endpoints[1], other.to_vector()); - - let rs = r.cross(s); - if rs.approx_eq(&0.0) { - return None - } - - let t = (q - p).cross(s) / rs; - if t < f32::approx_epsilon() || t > 1.0f32 - f32::approx_epsilon() { - return None - } - - let u = (q - p).cross(r) / rs; - if u < f32::approx_epsilon() || u > 1.0f32 - f32::approx_epsilon() { - return None - } - - Some(p + r * t) - } - - /// A version of `intersect` that accounts for intersection points at infinity. - /// - /// See Sederberg § 7.2.1. - pub fn intersect_at_infinity(&self, other: &Line) -> Option> { - let this_vector_0 = Vector3D::new(self.endpoints[0].x, self.endpoints[0].y, 1.0); - let this_vector_1 = Vector3D::new(self.endpoints[1].x, self.endpoints[1].y, 1.0); - let other_vector_0 = Vector3D::new(other.endpoints[0].x, other.endpoints[0].y, 1.0); - let other_vector_1 = Vector3D::new(other.endpoints[1].x, other.endpoints[1].y, 1.0); - - let this_vector = this_vector_0.cross(this_vector_1); - let other_vector = other_vector_0.cross(other_vector_1); - let intersection = this_vector.cross(other_vector); - - if intersection.z.approx_eq(&0.0) { - None - } else { - Some(Point2D::new(intersection.x / intersection.z, intersection.y / intersection.z)) - } - } - - // Translates this line in the perpendicular counterclockwise direction by the given length. - pub(crate) fn offset(&self, length: f32) -> Line { - let vector = self.to_vector(); - let normal = Vector2D::new(-vector.y, vector.x).normalize() * length; - Line::new(&(self.endpoints[0] + normal), &(self.endpoints[1] + normal)) - } -} diff --git a/path-utils/src/monotonic.rs b/path-utils/src/monotonic.rs deleted file mode 100644 index 781d89f2..00000000 --- a/path-utils/src/monotonic.rs +++ /dev/null @@ -1,88 +0,0 @@ -// pathfinder/path-utils/src/monotonic.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. - -//! Utilities for converting path commands into monotonically increasing/decreasing segments. - -use arrayvec::ArrayVec; -use euclid::Point2D; -use std::mem; - -use PathCommand; -use curve::Curve; - -/// Converts a stream of path commands into one in which all curves are monotonically increasing -/// or decreasing. -/// -/// Equivalently, all curves have X or Y inflection points only at endpoints, if at all. -#[derive(Clone)] -pub struct MonotonicPathCommandStream { - inner: I, - queue: ArrayVec<[PathCommand; 2]>, - prev_point: Point2D, -} - -impl MonotonicPathCommandStream where I: Iterator { - /// Creates a new monotonic path command stream, which converts a stream of path commands into - /// one in which each curve is monotonially increasing or decreasing. - pub fn new(inner: I) -> MonotonicPathCommandStream { - MonotonicPathCommandStream { - inner: inner, - queue: ArrayVec::new(), - prev_point: Point2D::zero(), - } - } -} - -impl Iterator for MonotonicPathCommandStream where I: Iterator { - type Item = PathCommand; - - fn next(&mut self) -> Option { - if !self.queue.is_empty() { - return Some(self.queue.remove(0)) - } - - match self.inner.next() { - None => None, - Some(PathCommand::ClosePath) => Some(PathCommand::ClosePath), - Some(PathCommand::MoveTo(point)) => { - self.prev_point = point; - Some(PathCommand::MoveTo(point)) - } - Some(PathCommand::LineTo(point)) => { - self.prev_point = point; - Some(PathCommand::LineTo(point)) - } - Some(PathCommand::CurveTo(control_point, endpoint)) => { - let curve = Curve::new(&self.prev_point, &control_point, &endpoint); - self.prev_point = endpoint; - match curve.inflection_points() { - (None, None) => Some(PathCommand::CurveTo(control_point, endpoint)), - (Some(t), None) | (None, Some(t)) => { - let (prev_curve, next_curve) = curve.subdivide(t); - self.queue.push(next_curve.to_path_command()); - Some(prev_curve.to_path_command()) - } - (Some(mut t0), Some(mut t1)) => { - if t1 < t0 { - mem::swap(&mut t0, &mut t1) - } - - let (curve_0, curve_12) = curve.subdivide(t0); - let (curve_1, curve_2) = curve_12.subdivide((t1 - t0) / (1.0 - t0)); - self.queue.push(curve_1.to_path_command()); - self.queue.push(curve_2.to_path_command()); - - Some(curve_0.to_path_command()) - } - } - } - } - } -} diff --git a/path-utils/src/normals.rs b/path-utils/src/normals.rs new file mode 100644 index 00000000..75a37de7 --- /dev/null +++ b/path-utils/src/normals.rs @@ -0,0 +1,110 @@ +// pathfinder/path-utils/src/normals.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. + +use euclid::Vector2D; +use euclid::approxeq::ApproxEq; +use lyon_path::PathEvent; + +#[derive(Clone)] +pub struct PathNormals { + normals: Vec>, +} + +impl PathNormals { + #[inline] + pub fn new() -> PathNormals { + PathNormals { + normals: vec![], + } + } + + #[inline] + pub fn normals(&self) -> &[Vector2D] { + &self.normals + } + + pub fn clear(&mut self) { + self.normals.clear() + } + + pub fn add_path(&mut self, path: I) where I: Iterator { + let mut path = path.peekable(); + while path.peek().is_some() { + let mut positions = vec![]; + loop { + match path.next() { + Some(PathEvent::MoveTo(to)) | Some(PathEvent::LineTo(to)) => { + positions.push(to); + } + Some(PathEvent::QuadraticTo(ctrl, to)) => { + positions.push(ctrl); + positions.push(to); + } + Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => { + positions.push(ctrl1); + positions.push(ctrl2); + positions.push(to); + } + Some(PathEvent::Arc(..)) => panic!("PathNormals: Arcs currently unsupported!"), + None | Some(PathEvent::Close) => break, + } + + if let Some(&PathEvent::MoveTo(..)) = path.peek() { + break + } + } + + self.normals.reserve(positions.len()); + + for (this_index, this_position) in positions.iter().enumerate() { + let mut prev_index = this_index; + let mut prev_vector; + loop { + if prev_index > 0 { + prev_index -= 1 + } else { + prev_index = positions.len() - 1 + } + prev_vector = *this_position - positions[prev_index]; + if !prev_vector.square_length().approx_eq(&0.0) { + break + } + } + + let mut next_index = this_index; + let mut next_vector; + loop { + if next_index + 1 < positions.len() { + next_index += 1 + } else { + next_index = 0 + } + next_vector = positions[next_index] - *this_position; + if !next_vector.square_length().approx_eq(&0.0) { + break + } + } + + let prev_normal = rotate(&prev_vector).normalize(); + let next_normal = rotate(&next_vector).normalize(); + let mut bisector = (prev_normal + next_normal) * 0.5; + if bisector.square_length().approx_eq(&0.0) { + bisector = Vector2D::new(next_vector.y, next_vector.x) + } + + self.normals.push(bisector.normalize()); + } + } + } +} + +fn rotate(vector: &Vector2D) -> Vector2D { + Vector2D::new(-vector.y, vector.x) +} diff --git a/path-utils/src/segments.rs b/path-utils/src/segments.rs new file mode 100644 index 00000000..8c5ba541 --- /dev/null +++ b/path-utils/src/segments.rs @@ -0,0 +1,265 @@ +// pathfinder/path-utils/src/segments.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. + +//! Returns each segment of a path. + +use euclid::approxeq::ApproxEq; +use euclid::{Point2D, Vector2D}; +use lyon_geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment}; +use lyon_path::iterator::{PathIter, PathIterator}; +use lyon_path::PathEvent; + +pub struct SegmentIter where I: Iterator { + inner: PathIter, + stack: Vec, + was_just_closed: bool, +} + +impl SegmentIter where I: Iterator { + #[inline] + pub fn new(inner: I) -> SegmentIter { + SegmentIter { + inner: PathIter::new(inner), + stack: vec![], + was_just_closed: true, + } + } +} + +impl Iterator for SegmentIter where I: Iterator { + type Item = Segment; + + fn next(&mut self) -> Option { + if let Some(segment) = self.stack.pop() { + return Some(segment) + } + + let current_point = self.inner.get_state().current; + + match self.inner.next() { + None => None, + Some(PathEvent::Close) => { + self.was_just_closed = true; + let state = self.inner.get_state(); + /*if state.first == current_point { + return Some(Segment::EndSubpath(true)) + }*/ + self.stack.push(Segment::EndSubpath(true)); + Some(Segment::Line(LineSegment { + from: state.current, + to: state.first, + })) + } + Some(PathEvent::MoveTo(_)) => { + if self.was_just_closed { + self.was_just_closed = false; + return self.next(); + } + Some(Segment::EndSubpath(false)) + } + Some(PathEvent::LineTo(to)) => { + Some(Segment::Line(LineSegment { + from: current_point, + to: to, + })) + } + Some(PathEvent::QuadraticTo(ctrl, to)) => { + Some(Segment::Quadratic(QuadraticBezierSegment { + from: current_point, + ctrl: ctrl, + to: to, + })) + } + Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => { + Some(Segment::Cubic(CubicBezierSegment { + from: current_point, + ctrl1: ctrl1, + ctrl2: ctrl2, + to: to, + })) + } + Some(PathEvent::Arc(..)) => { + panic!("SegmentIter doesn't support cubics and arcs yet!") + } + } + } +} + +#[derive(Clone, Copy)] +pub enum Segment { + Line(LineSegment), + Quadratic(QuadraticBezierSegment), + Cubic(CubicBezierSegment), + /// True if the subpath is closed. + EndSubpath(bool), +} + +impl Segment { + pub fn flip(&self) -> Segment { + match *self { + Segment::EndSubpath(closed) => Segment::EndSubpath(closed), + Segment::Line(line_segment) => Segment::Line(line_segment.flip()), + Segment::Quadratic(quadratic_segment) => Segment::Quadratic(quadratic_segment.flip()), + Segment::Cubic(cubic_segment) => Segment::Cubic(cubic_segment.flip()), + } + } + + pub fn offset(&self, distance: f32, mut sink: F) where F: FnMut(&Segment) { + match *self { + Segment::EndSubpath(_) => {} + Segment::Line(ref segment) => { + sink(&Segment::Line(offset_line_segment(segment, distance))) + } + + Segment::Quadratic(ref quadratic_segment) => { + // This is the Tiller & Hanson 1984 algorithm for approximate Bézier offset curves. + // We take the cage (i.e. convex hull) and push its edges out along their normals, + // then recompute the control point with a miter join. + let line_segments = (LineSegment { + from: quadratic_segment.from, + to: quadratic_segment.ctrl, + }, LineSegment { + from: quadratic_segment.ctrl, + to: quadratic_segment.to, + }); + + // Miter join. + let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0, + line_segments.1, + distance) { + None => return sink(self), + Some(intersection) => intersection, + }; + + sink(&Segment::Quadratic(QuadraticBezierSegment { + from: from, + ctrl: intersection, + to: to, + })) + } + + Segment::Cubic(ref cubic_segment) if points_overlap(&cubic_segment.from, + &cubic_segment.ctrl1) => { + // As above. + let line_segments = (LineSegment { + from: cubic_segment.from, + to: cubic_segment.ctrl2, + }, LineSegment { + from: cubic_segment.ctrl2, + to: cubic_segment.to, + }); + + // Miter join. + let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0, + line_segments.1, + distance) { + None => return sink(self), + Some(intersection) => intersection, + }; + + sink(&Segment::Cubic(CubicBezierSegment { + from: from, + ctrl1: from, + ctrl2: intersection, + to: to, + })) + } + + Segment::Cubic(ref cubic_segment) if points_overlap(&cubic_segment.ctrl2, + &cubic_segment.to) => { + // As above. + let line_segments = (LineSegment { + from: cubic_segment.from, + to: cubic_segment.ctrl1, + }, LineSegment { + from: cubic_segment.ctrl1, + to: cubic_segment.to, + }); + + // Miter join. + let (from, intersection, to) = match offset_and_join_line_segments(line_segments.0, + line_segments.1, + distance) { + None => return sink(self), + Some(intersection) => intersection, + }; + + sink(&Segment::Cubic(CubicBezierSegment { + from: from, + ctrl1: intersection, + ctrl2: to, + to: to, + })) + } + + Segment::Cubic(ref cubic_segment) => { + // As above. + let line_segments = (LineSegment { + from: cubic_segment.from, + to: cubic_segment.ctrl1, + }, LineSegment { + from: cubic_segment.ctrl1, + to: cubic_segment.ctrl2, + }, LineSegment { + from: cubic_segment.ctrl2, + to: cubic_segment.to, + }); + + let (from, intersection_0, _) = + match offset_and_join_line_segments(line_segments.0, + line_segments.1, + distance) { + None => return sink(self), + Some(intersection) => intersection, + }; + let (_, intersection_1, to) = match offset_and_join_line_segments(line_segments.1, + line_segments.2, + distance) { + None => return sink(self), + Some(intersection) => intersection, + }; + + sink(&Segment::Cubic(CubicBezierSegment { + from: from, + ctrl1: intersection_0, + ctrl2: intersection_1, + to: to, + })) + } + } + } +} + +fn offset_line_segment(segment: &LineSegment, distance: f32) -> LineSegment { + let mut segment = *segment; + let vector = segment.to_vector(); + if vector.square_length() < f32::approx_epsilon() { + return segment + } + let tangent = vector.normalize() * distance; + segment.translate(Vector2D::new(-tangent.y, tangent.x)) +} + +// Performs a miter join. +fn offset_and_join_line_segments(mut line_segment_0: LineSegment, + mut line_segment_1: LineSegment, + distance: f32) + -> Option<(Point2D, Point2D, Point2D)> { + line_segment_0 = offset_line_segment(&line_segment_0, distance); + line_segment_1 = offset_line_segment(&line_segment_1, distance); + match line_segment_0.to_line().intersection(&line_segment_1.to_line()) { + None => None, + Some(intersection) => Some((line_segment_0.from, intersection, line_segment_1.to)), + } +} + +fn points_overlap(a: &Point2D, b: &Point2D) -> bool { + a.x.approx_eq(&b.x) && a.y.approx_eq(&b.y) +} diff --git a/path-utils/src/stroke.rs b/path-utils/src/stroke.rs index 20f1f210..069646e3 100644 --- a/path-utils/src/stroke.rs +++ b/path-utils/src/stroke.rs @@ -1,6 +1,6 @@ // pathfinder/path-utils/src/stroke.rs // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -10,132 +10,138 @@ //! Utilities for converting path strokes to fills. -use std::u32; +use lyon_path::PathEvent; +use lyon_path::iterator::PathIterator; -use {Endpoint, PathBuffer, PathCommand, Subpath}; -use line::Line; +use segments::{Segment, SegmentIter}; -/// Represents the style of a stroke. -pub struct Stroke { - /// The stroke diameter. +#[derive(Clone, Copy, Debug)] +pub struct StrokeStyle { pub width: f32, } -impl Stroke { - /// Constructs a new stroke style with the given diameter. +impl StrokeStyle { #[inline] - pub fn new(width: f32) -> Stroke { - Stroke { + pub fn new(width: f32) -> StrokeStyle { + StrokeStyle { width: width, } } +} - /// Writes a path that represents the result of stroking `stream` with this stroke style into - /// `output`. - pub fn apply(&self, output: &mut PathBuffer, stream: I) - where I: Iterator { - let mut input = PathBuffer::new(); - input.add_stream(stream); +pub struct StrokeToFillIter where I: PathIterator { + inner: SegmentIter, + subpath: Vec, + stack: Vec, + state: StrokeToFillState, + style: StrokeStyle, + first_point_in_subpath: bool, +} - for subpath_index in 0..(input.subpaths.len() as u32) { - let closed = input.subpaths[subpath_index as usize].closed; - - let mut first_endpoint_index = output.endpoints.len() as u32; - - // Compute the first offset curve. - // - // TODO(pcwalton): Support line caps. - self.offset_subpath(output, &input, subpath_index); - - // Close the first subpath if necessary. - if closed && !output.endpoints.is_empty() { - let last_endpoint_index = output.endpoints.len() as u32; - output.subpaths.push(Subpath { - first_endpoint_index: first_endpoint_index, - last_endpoint_index: last_endpoint_index, - closed: true, - }); - - first_endpoint_index = last_endpoint_index; - } - - // Compute the second offset curve. - input.reverse_subpath(subpath_index); - self.offset_subpath(output, &input, subpath_index); - - // Close the path. - let last_endpoint_index = output.endpoints.len() as u32; - output.subpaths.push(Subpath { - first_endpoint_index: first_endpoint_index, - last_endpoint_index: last_endpoint_index, - closed: true, - }); - } - } - - /// TODO(pcwalton): Miter and round joins. - fn offset_subpath(&self, output: &mut PathBuffer, input: &PathBuffer, subpath_index: u32) { - let radius = self.width * 0.5; - - let subpath = &input.subpaths[subpath_index as usize]; - - let mut prev_position = None; - for endpoint_index in subpath.first_endpoint_index..subpath.last_endpoint_index { - let endpoint = &input.endpoints[endpoint_index as usize]; - let position = &endpoint.position; - - if let Some(ref prev_position) = prev_position { - if endpoint.control_point_index == u32::MAX { - let offset_line = Line::new(&prev_position, position).offset(radius); - output.endpoints.extend_from_slice(&[ - Endpoint { - position: offset_line.endpoints[0], - control_point_index: u32::MAX, - subpath_index: 0, - }, - Endpoint { - position: offset_line.endpoints[1], - control_point_index: u32::MAX, - subpath_index: 0, - }, - ]); - } else { - // This is the Tiller & Hanson 1984 algorithm for approximate Bézier offset - // curves. It's beautifully simple: just take the cage (i.e. convex hull) and - // push its edges out along their normals, then recompute the control point - // with a miter join. - - let control_point_position = - &input.control_points[endpoint.control_point_index as usize]; - let offset_line_0 = - Line::new(&prev_position, control_point_position).offset(radius); - let offset_line_1 = - Line::new(control_point_position, position).offset(radius); - - // FIXME(pcwalton): Can the `None` case ever happen? - let offset_control_point = - offset_line_0.intersect_at_infinity(&offset_line_1).unwrap_or_else(|| { - offset_line_0.endpoints[1].lerp(offset_line_1.endpoints[0], 0.5) - }); - - output.endpoints.extend_from_slice(&[ - Endpoint { - position: offset_line_0.endpoints[0], - control_point_index: u32::MAX, - subpath_index: 0, - }, - Endpoint { - position: offset_line_1.endpoints[1], - control_point_index: output.control_points.len() as u32, - subpath_index: 0, - }, - ]); - - output.control_points.push(offset_control_point); - } - } - - prev_position = Some(*position) +impl StrokeToFillIter where I: PathIterator { + #[inline] + pub fn new(inner: I, style: StrokeStyle) -> StrokeToFillIter { + StrokeToFillIter { + inner: SegmentIter::new(inner), + subpath: vec![], + stack: vec![], + state: StrokeToFillState::Forward, + style: style, + first_point_in_subpath: true, } } } + +impl Iterator for StrokeToFillIter where I: PathIterator { + type Item = PathEvent; + + // TODO(pcwalton): Support miter and round joins. This will probably require the inner iterator + // to be `Peekable`, I guess. + fn next(&mut self) -> Option { + // If we have path events queued, return the latest. + if let Some(path_event) = self.stack.pop() { + return Some(path_event) + } + + // Fetch the next segment. + let next_segment = match self.state { + StrokeToFillState::Forward => { + match self.inner.next() { + None | Some(Segment::EndSubpath(false)) => { + if self.subpath.is_empty() { + return None + } + self.state = StrokeToFillState::Backward; + return self.next() + } + Some(Segment::EndSubpath(true)) => { + if self.subpath.is_empty() { + return None + } + self.state = StrokeToFillState::Backward; + self.first_point_in_subpath = true; + return Some(PathEvent::Close) + } + Some(segment) => { + self.subpath.push(segment); + segment + } + } + } + StrokeToFillState::Backward => { + match self.subpath.pop() { + None | Some(Segment::EndSubpath(_)) => { + self.state = StrokeToFillState::Forward; + self.first_point_in_subpath = true; + return Some(PathEvent::Close) + } + Some(segment) => segment.flip(), + } + } + }; + + next_segment.offset(self.style.width * 0.5, |offset_segment| { + match *offset_segment { + Segment::EndSubpath(_) => unreachable!(), + Segment::Line(ref offset_segment) => { + if self.first_point_in_subpath { + self.first_point_in_subpath = false; + self.stack.push(PathEvent::MoveTo(offset_segment.from)) + } else if self.stack.is_empty() { + self.stack.push(PathEvent::LineTo(offset_segment.from)) + } + self.stack.push(PathEvent::LineTo(offset_segment.to)) + } + Segment::Quadratic(ref offset_segment) => { + if self.first_point_in_subpath { + self.first_point_in_subpath = false; + self.stack.push(PathEvent::MoveTo(offset_segment.from)) + } else if self.stack.is_empty() { + self.stack.push(PathEvent::LineTo(offset_segment.from)) + } + self.stack.push(PathEvent::QuadraticTo(offset_segment.ctrl, offset_segment.to)) + } + Segment::Cubic(ref offset_segment) => { + if self.first_point_in_subpath { + self.first_point_in_subpath = false; + self.stack.push(PathEvent::MoveTo(offset_segment.from)) + } else if self.stack.is_empty() { + self.stack.push(PathEvent::LineTo(offset_segment.from)) + } + self.stack.push(PathEvent::CubicTo(offset_segment.ctrl1, + offset_segment.ctrl2, + offset_segment.to)) + } + } + }); + + self.stack.reverse(); + return self.next() + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum StrokeToFillState { + Forward, + Backward, +} diff --git a/path-utils/src/svg.rs b/path-utils/src/svg.rs deleted file mode 100644 index 76fb9401..00000000 --- a/path-utils/src/svg.rs +++ /dev/null @@ -1,33 +0,0 @@ -// pathfinder/path-utils/src/svg.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. - -//! Utilities for converting paths to SVG representations. - -use std::io::{self, Write}; - -use PathCommand; - -/// Writes a textual representation of the path `stream` to the given `Writer` in SVG `path` form. -pub fn to_svg_description(output: &mut W, stream: S) -> io::Result<()> - where W: Write, S: Iterator { - for segment in stream { - match segment { - PathCommand::MoveTo(point) => try!(write!(output, "M{},{} ", point.x, point.y)), - PathCommand::LineTo(point) => try!(write!(output, "L{},{} ", point.x, point.y)), - PathCommand::CurveTo(control_point, endpoint) => { - try!(write!(output, "Q{},{} {},{} ", - control_point.x, control_point.y, - endpoint.x, endpoint.y)) - } - PathCommand::ClosePath => try!(output.write_all(b"z")), - } - } - Ok(()) -} diff --git a/path-utils/src/transform.rs b/path-utils/src/transform.rs new file mode 100644 index 00000000..004a03c1 --- /dev/null +++ b/path-utils/src/transform.rs @@ -0,0 +1,60 @@ +// pathfinder/path-utils/src/transform.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. + +//! Applies a transform to paths. + +use euclid::Transform2D; +use lyon_path::PathEvent; + +pub struct Transform2DPathIter where I: Iterator { + inner: I, + transform: Transform2D, +} + +impl Transform2DPathIter where I: Iterator { + #[inline] + pub fn new(inner: I, transform: &Transform2D) -> Transform2DPathIter { + Transform2DPathIter { + inner: inner, + transform: *transform, + } + } +} + +impl Iterator for Transform2DPathIter where I: Iterator { + type Item = PathEvent; + + fn next(&mut self) -> Option { + match self.inner.next() { + Some(PathEvent::MoveTo(to)) => { + Some(PathEvent::MoveTo(self.transform.transform_point(&to))) + } + Some(PathEvent::LineTo(to)) => { + Some(PathEvent::LineTo(self.transform.transform_point(&to))) + } + Some(PathEvent::QuadraticTo(ctrl, to)) => { + Some(PathEvent::QuadraticTo(self.transform.transform_point(&ctrl), + self.transform.transform_point(&to))) + } + Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => { + Some(PathEvent::CubicTo(self.transform.transform_point(&ctrl1), + self.transform.transform_point(&ctrl2), + self.transform.transform_point(&to))) + } + Some(PathEvent::Arc(center, radius, start, end)) => { + Some(PathEvent::Arc(self.transform.transform_point(¢er), + self.transform.transform_vector(&radius), + start, + end)) + } + event => event, + } + } +} diff --git a/shaders/gles2/direct-curve.fs.glsl b/shaders/gles2/direct-curve.fs.glsl index 4517930f..e1d9ed85 100644 --- a/shaders/gles2/direct-curve.fs.glsl +++ b/shaders/gles2/direct-curve.fs.glsl @@ -30,6 +30,5 @@ void main() { float side = sign(vTexCoord.x * vTexCoord.x - vTexCoord.y); float winding = gl_FrontFacing ? -1.0 : 1.0; float alpha = float(side == winding); - //float alpha = mod(gl_FragCoord.x, 2.0) < 1.0 ? 1.0 : 0.0; gl_FragColor = alpha * vColor; } diff --git a/utils/frontend/Cargo.toml b/utils/frontend/Cargo.toml index 2f7c851f..f75e04ff 100644 --- a/utils/frontend/Cargo.toml +++ b/utils/frontend/Cargo.toml @@ -7,6 +7,8 @@ authors = ["Patrick Walton "] app_units = "0.5" clap = "2.27" freetype-sys = "0.6" +lyon_geom = "0.9" +lyon_path = "0.9" [dependencies.pathfinder_font_renderer] path = "../../font-renderer" diff --git a/utils/frontend/src/main.rs b/utils/frontend/src/main.rs index 7b8c439b..70bcfbce 100644 --- a/utils/frontend/src/main.rs +++ b/utils/frontend/src/main.rs @@ -26,6 +26,8 @@ extern crate app_units; extern crate clap; extern crate freetype_sys; +extern crate lyon_geom; +extern crate lyon_path; extern crate pathfinder_font_renderer; extern crate pathfinder_partitioner; extern crate pathfinder_path_utils; @@ -33,11 +35,12 @@ extern crate pathfinder_path_utils; use app_units::Au; use clap::{App, Arg}; use freetype_sys::{FT_Init_FreeType, FT_New_Face}; +use lyon_path::PathEvent; +use lyon_path::builder::{FlatPathBuilder, PathBuilder}; use pathfinder_font_renderer::{FontContext, FontKey, FontInstance, GlyphKey, SubpixelOffset}; +use pathfinder_partitioner::FillRule; use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::partitioner::Partitioner; -use pathfinder_path_utils::monotonic::MonotonicPathCommandStream; -use pathfinder_path_utils::{PathBuffer, PathBufferStream}; use std::ffi::CString; use std::fs::File; use std::io::Read; @@ -83,30 +86,28 @@ fn convert_font(font_path: &Path, output_path: &Path) -> Result<(), ()> { size: Au::from_f64_px(FONT_SIZE), }; - let mut path_buffer = PathBuffer::new(); + let mut paths: Vec<(u16, Vec)> = vec![]; let mut partitioner = Partitioner::new(MeshLibrary::new()); - let subpath_ranges: Vec<_> = (0..glyph_count).map(|glyph_index| { + + for glyph_index in 0..glyph_count { let glyph_key = GlyphKey::new(glyph_index, SubpixelOffset(0)); - let subpath_start = path_buffer.subpaths.len() as u32; - if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) { - path_buffer.add_stream(MonotonicPathCommandStream::new(glyph_outline.into_iter())) - } - let subpath_end = path_buffer.subpaths.len() as u32; + let path = match font_context.glyph_outline(&font_instance, &glyph_key) { + Ok(path) => path, + Err(_) => continue, + }; let path_index = (glyph_index + 1) as u16; - let stream = PathBufferStream::subpath_range(&path_buffer, subpath_start..subpath_end); - partitioner.library_mut().push_segments(path_index, stream); - let stream = PathBufferStream::subpath_range(&path_buffer, subpath_start..subpath_end); - partitioner.library_mut().push_normals(stream); + partitioner.library_mut().push_segments(path_index, path.iter()); + partitioner.library_mut().push_normals(path_index, path.iter()); - subpath_start..subpath_end - }).collect(); + paths.push((path_index, path.iter().collect())); + } - partitioner.init_with_path_buffer(&path_buffer); - - for (glyph_index, subpath) in subpath_ranges.iter().cloned().enumerate() { - partitioner.partition((glyph_index + 1) as u16, subpath.start, subpath.end); + for (glyph_index, path) in paths { + path.into_iter().for_each(|event| partitioner.builder_mut().path_event(event)); + partitioner.partition(glyph_index, FillRule::Winding); + partitioner.builder_mut().build_and_reset(); } partitioner.library_mut().optimize();