Port Pathfinder to use Lyon for Bézier curve math.

This removes a whole lot of code from `pathfinder_path_utils`. Hopefully
the remaining code can go upstream.

These changes regress quality of stroke widths for cubic curves, because
they move fill-to-stroke conversion before cubic-to-quadratic
conversion. To fix that, we will need to recursively subdivide when
doing fill-to-stroke conversion.
This commit is contained in:
Patrick Walton 2018-01-24 12:08:39 -08:00
parent 2ec5dbfa42
commit 5bd68dec65
32 changed files with 1302 additions and 2077 deletions

View File

@ -135,9 +135,11 @@ export class OrthographicCamera extends Camera {
this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false); this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false); this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
this.canvas.addEventListener('mousemove', event => this.onMouseMove(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 { } else {
unwrapNull(this.canvas.classList).remove('pf-draggable'); if (this.canvas.classList != null)
this.canvas.classList.remove('pf-draggable');
} }
this.onPan = null; this.onPan = null;

View File

@ -12,11 +12,13 @@ app_units = "0.5"
base64 = "0.6" base64 = "0.6"
bincode = "0.8" bincode = "0.8"
env_logger = "0.4" env_logger = "0.4"
euclid = "0.15" euclid = "0.16"
image = "0.17" image = "0.17"
lazy_static = "0.2" lazy_static = "0.2"
log = "0.3" log = "0.3"
lru-cache = "0.1" lru-cache = "0.1"
lyon_geom = "0.9"
lyon_path = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"

View File

@ -18,6 +18,8 @@ extern crate euclid;
extern crate fontsan; extern crate fontsan;
extern crate image; extern crate image;
extern crate lru_cache; extern crate lru_cache;
extern crate lyon_geom;
extern crate lyon_path;
extern crate pathfinder_font_renderer; extern crate pathfinder_font_renderer;
extern crate pathfinder_partitioner; extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils; extern crate pathfinder_path_utils;
@ -38,23 +40,23 @@ use app_units::Au;
use euclid::{Point2D, Transform2D}; use euclid::{Point2D, Transform2D};
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8}; use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
use lru_cache::LruCache; 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::{FontContext, FontInstance, FontKey, GlyphImage};
use pathfinder_font_renderer::{GlyphKey, SubpixelOffset}; use pathfinder_font_renderer::{GlyphKey, SubpixelOffset};
use pathfinder_partitioner::FillRule; use pathfinder_partitioner::FillRule;
use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::mesh_library::MeshLibrary;
use pathfinder_partitioner::partitioner::Partitioner; use pathfinder_partitioner::partitioner::Partitioner;
use pathfinder_path_utils::cubic::CubicCurve; use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
use pathfinder_path_utils::monotonic::MonotonicPathCommandStream; use pathfinder_path_utils::transform::Transform2DPathIter;
use pathfinder_path_utils::stroke::Stroke;
use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathCommand, Transform2DPathStream};
use rocket::http::{ContentType, Header, Status}; use rocket::http::{ContentType, Header, Status};
use rocket::request::Request; use rocket::request::Request;
use rocket::response::{NamedFile, Redirect, Responder, Response}; use rocket::response::{NamedFile, Redirect, Responder, Response};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use std::fs::File; use std::fs::File;
use std::io::{self, Cursor, Read}; use std::io::{self, Cursor, Read};
use std::ops::Range; use std::path::{self, PathBuf};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::u32; use std::u32;
@ -71,8 +73,6 @@ use rsvg::{Handle, HandleExt};
const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024; const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024;
const CUBIC_ERROR_TOLERANCE: f32 = 0.1;
const MESH_LIBRARY_CACHE_SIZE: usize = 16; const MESH_LIBRARY_CACHE_SIZE: usize = 16;
lazy_static! { lazy_static! {
@ -246,7 +246,7 @@ impl PartitionSvgFillRule {
#[derive(Clone)] #[derive(Clone)]
struct PathDescriptor { struct PathDescriptor {
subpath_indices: Range<u32>, path_index: usize,
fill_rule: FillRule, fill_rule: FillRule,
} }
@ -263,15 +263,17 @@ struct PathPartitioningResult {
} }
impl PathPartitioningResult { impl PathPartitioningResult {
fn compute(partitioner: &mut Partitioner, path_descriptors: &[PathDescriptor]) fn compute(partitioner: &mut Partitioner,
path_descriptors: &[PathDescriptor],
paths: &[Vec<PathEvent>])
-> PathPartitioningResult { -> PathPartitioningResult {
let timestamp_before = Instant::now(); let timestamp_before = Instant::now();
for (path_index, path_descriptor) in path_descriptors.iter().enumerate() { for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
partitioner.set_fill_rule(path_descriptor.fill_rule); path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
partitioner.partition((path_index + 1) as u16, partitioner.partition((path_descriptor.path_index + 1) as u16,
path_descriptor.subpath_indices.start, path_descriptor.fill_rule);
path_descriptor.subpath_indices.end); partitioner.builder_mut().build_and_reset();
} }
partitioner.library_mut().optimize(); partitioner.library_mut().optimize();
@ -444,42 +446,40 @@ fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionRespon
}; };
// Read glyph info. // Read glyph info.
let mut path_buffer = PathBuffer::new(); let mut paths: Vec<Vec<PathEvent>> = vec![];
let path_descriptors: Vec<_> = request.glyphs.iter().map(|glyph| { let mut path_descriptors = vec![];
for (glyph_index, glyph) in request.glyphs.iter().enumerate() {
let glyph_key = GlyphKey::new(glyph.id, SubpixelOffset(0)); 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. // This might fail; if so, just leave it blank.
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) { match font_context.glyph_outline(&font_instance, &glyph_key) {
let stream = Transform2DPathStream::new(glyph_outline.into_iter(), &glyph.transform); Ok(glyph_outline) => {
let stream = MonotonicPathCommandStream::new(stream); paths.push(Transform2DPathIter::new(glyph_outline.iter(),
path_buffer.add_stream(stream) &glyph.transform).collect())
} }
Err(_) => continue,
};
let last_subpath_index = path_buffer.subpaths.len(); path_descriptors.push(PathDescriptor {
path_index: glyph_index,
PathDescriptor {
subpath_indices: (first_subpath_index as u32)..(last_subpath_index as u32),
fill_rule: FillRule::Winding, fill_rule: FillRule::Winding,
} })
}).collect(); }
// Partition the decoded glyph outlines. // Partition the decoded glyph outlines.
let mut library = MeshLibrary::new(); let mut library = MeshLibrary::new();
for (path_index, path_descriptor) in path_descriptors.iter().enumerate() { for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() {
let stream = PathBufferStream::subpath_range(&path_buffer, library.push_segments((path_descriptor.path_index + 1) as u16,
path_descriptor.subpath_indices.clone()); PathIter::new(paths[stored_path_index].iter().cloned()));
library.push_segments((path_index + 1) as u16, stream); library.push_normals((path_descriptor.path_index + 1) as u16,
let stream = PathBufferStream::subpath_range(&path_buffer, PathIter::new(paths[stored_path_index].iter().cloned()));
path_descriptor.subpath_indices.clone());
library.push_normals(stream);
} }
let mut partitioner = Partitioner::new(library); let mut partitioner = Partitioner::new(library);
partitioner.init_with_path_buffer(&path_buffer);
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
&path_descriptors); &path_descriptors,
&paths);
// Build the response. // Build the response.
let elapsed_ms = path_partitioning_result.elapsed_ms(); let elapsed_ms = path_partitioning_result.elapsed_ms();
@ -504,79 +504,64 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
// //
// The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z` // The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z`
// commands. // commands.
let mut path_buffer = PathBuffer::new();
let mut paths = vec![]; let mut paths = vec![];
let mut last_point = Point2D::zero(); let mut path_descriptors = vec![];
let mut library = MeshLibrary::new(); 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 mut stream = vec![];
let first_subpath_index = path_buffer.subpaths.len() as u32;
for segment in &path.segments { for segment in &path.segments {
match segment.kind { match segment.kind {
'M' => { 'M' => {
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); stream.push(PathEvent::MoveTo(Point2D::new(segment.values[0] as f32,
stream.push(PathCommand::MoveTo(last_point)) segment.values[1] as f32)))
} }
'L' => { 'L' => {
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32); stream.push(PathEvent::LineTo(Point2D::new(segment.values[0] as f32,
stream.push(PathCommand::LineTo(last_point)) segment.values[1] as f32)))
} }
'C' => { 'C' => {
let control_point_0 = Point2D::new(segment.values[0] as f32, stream.push(PathEvent::CubicTo(Point2D::new(segment.values[0] as f32,
segment.values[1] as f32); segment.values[1] as f32),
let control_point_1 = Point2D::new(segment.values[2] as f32, Point2D::new(segment.values[2] as f32,
segment.values[3] as f32); segment.values[3] as f32),
let endpoint_1 = Point2D::new(segment.values[4] as f32, Point2D::new(segment.values[4] as f32,
segment.values[5] 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()));
} }
'Z' => stream.push(PathCommand::ClosePath), 'Z' => stream.push(PathEvent::Close),
_ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType), _ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType),
} }
} }
let fill_rule = match path.kind { let fill_rule = match path.kind {
PartitionSvgPathKind::Fill(fill_rule) => { PartitionSvgPathKind::Fill(fill_rule) => fill_rule.to_fill_rule(),
let stream = MonotonicPathCommandStream::new(stream.into_iter()); PartitionSvgPathKind::Stroke(_) => FillRule::Winding,
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
}
}; };
let last_subpath_index = path_buffer.subpaths.len() as u32; path_descriptors.push(PathDescriptor {
path_index: path_index,
paths.push(PathDescriptor {
subpath_indices: first_subpath_index..last_subpath_index,
fill_rule: fill_rule, 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. // Partition the paths.
let mut partitioner = Partitioner::new(library); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
partitioner.init_with_path_buffer(&path_buffer); &path_descriptors,
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths); &paths);
// Return the response. // Return the response.
let elapsed_ms = path_partitioning_result.elapsed_ms(); let elapsed_ms = path_partitioning_result.elapsed_ms();
@ -710,69 +695,69 @@ fn static_doc_api_index() -> Redirect {
} }
#[get("/doc/api/<file..>")] #[get("/doc/api/<file..>")]
fn static_doc_api(file: PathBuf) -> Option<NamedFile> { fn static_doc_api(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/css/bootstrap/<file..>")]
fn static_css_bootstrap(file: PathBuf) -> Option<NamedFile> { fn static_css_bootstrap(file: PathBuf) -> Option<NamedFile> {
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/<file>")] #[get("/css/<file>")]
fn static_css(file: String) -> Option<NamedFile> { fn static_css(file: String) -> Option<NamedFile> {
NamedFile::open(Path::new(STATIC_CSS_PATH).join(file)).ok() NamedFile::open(path::Path::new(STATIC_CSS_PATH).join(file)).ok()
} }
#[get("/js/bootstrap/<file..>")] #[get("/js/bootstrap/<file..>")]
fn static_js_bootstrap(file: PathBuf) -> Option<NamedFile> { fn static_js_bootstrap(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/js/jquery/<file..>")]
fn static_js_jquery(file: PathBuf) -> Option<NamedFile> { fn static_js_jquery(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/js/popper.js/<file..>")]
fn static_js_popper_js(file: PathBuf) -> Option<NamedFile> { fn static_js_popper_js(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/js/pathfinder/<file..>")]
fn static_js_pathfinder(file: PathBuf) -> Option<NamedFile> { fn static_js_pathfinder(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/woff2/inter-ui/<file..>")]
fn static_woff2_inter_ui(file: PathBuf) -> Option<NamedFile> { fn static_woff2_inter_ui(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/woff2/material-icons/<file..>")]
fn static_woff2_material_icons(file: PathBuf) -> Option<NamedFile> { fn static_woff2_material_icons(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/glsl/<file..>")]
fn static_glsl(file: PathBuf) -> Option<Shader> { fn static_glsl(file: PathBuf) -> Option<Shader> {
Shader::open(Path::new(STATIC_GLSL_PATH).join(file)).ok() Shader::open(path::Path::new(STATIC_GLSL_PATH).join(file)).ok()
} }
#[get("/otf/demo/<font_name>")] #[get("/otf/demo/<font_name>")]
fn static_otf_demo(font_name: String) -> Option<NamedFile> { fn static_otf_demo(font_name: String) -> Option<NamedFile> {
BUILTIN_FONTS.iter() BUILTIN_FONTS.iter()
.filter(|& &(name, _)| name == font_name) .filter(|& &(name, _)| name == font_name)
.next() .next()
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok()) .and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
} }
#[get("/svg/demo/<svg_name>")] #[get("/svg/demo/<svg_name>")]
fn static_svg_demo(svg_name: String) -> Option<NamedFile> { fn static_svg_demo(svg_name: String) -> Option<NamedFile> {
BUILTIN_SVGS.iter() BUILTIN_SVGS.iter()
.filter(|& &(name, _)| name == svg_name) .filter(|& &(name, _)| name == svg_name)
.next() .next()
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok()) .and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
} }
#[get("/data/<file..>")] #[get("/data/<file..>")]
fn static_data(file: PathBuf) -> Option<NamedFile> { fn static_data(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new(STATIC_DATA_PATH).join(file)).ok() NamedFile::open(path::Path::new(STATIC_DATA_PATH).join(file)).ok()
} }
#[get("/test-data/<file..>")] #[get("/test-data/<file..>")]
fn static_test_data(file: PathBuf) -> Option<NamedFile> { fn static_test_data(file: PathBuf) -> Option<NamedFile> {
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/<file..>")] #[get("/textures/<file..>")]
fn static_textures(file: PathBuf) -> Option<NamedFile> { fn static_textures(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new(STATIC_TEXTURES_PATH).join(file)).ok() NamedFile::open(path::Path::new(STATIC_TEXTURES_PATH).join(file)).ok()
} }
struct Shader { struct Shader {

View File

@ -9,9 +9,11 @@ freetype = ["freetype-sys"]
[dependencies] [dependencies]
app_units = "0.5" app_units = "0.5"
euclid = "0.15" euclid = "0.16"
libc = "0.2" libc = "0.2"
log = "0.3" log = "0.3"
lyon_geom = "0.9"
lyon_path = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
@ -19,9 +21,6 @@ serde_derive = "1.0"
version = "0.6" version = "0.6"
optional = true optional = true
[dependencies.pathfinder_path_utils]
path = "../path-utils"
[target.'cfg(not(target_os = "macos"))'.dependencies] [target.'cfg(not(target_os = "macos"))'.dependencies]
freetype-sys = "0.6" freetype-sys = "0.6"

View File

@ -21,10 +21,11 @@ use core_graphics_sys::path::CGPathElementType;
use core_text::font::CTFont; use core_text::font::CTFont;
use core_text; use core_text;
use euclid::{Point2D, Rect, Size2D, Vector2D}; use euclid::{Point2D, Rect, Size2D, Vector2D};
use pathfinder_path_utils::cubic::{CubicPathCommand, CubicPathCommandApproxStream}; use lyon_path::PathEvent;
use pathfinder_path_utils::PathCommand;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::iter::Cloned;
use std::slice::Iter;
use std::sync::Arc; use std::sync::Arc;
use {FontInstance, FontKey, GlyphDimensions, GlyphImage, GlyphKey}; 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 // A conservative overestimate of the amount of font dilation that Core Graphics performs, as a
// fraction of ppem. // fraction of ppem.
// //
@ -45,9 +44,6 @@ const CURVE_APPROX_ERROR_BOUND: f32 = 0.1;
// direction. // direction.
const FONT_DILATION_AMOUNT: f32 = 0.02; const FONT_DILATION_AMOUNT: f32 = 0.02;
/// A list of path commands.
pub type GlyphOutline = Vec<PathCommand>;
/// An object that loads and renders fonts using macOS Core Graphics/Quartz. /// An object that loads and renders fonts using macOS Core Graphics/Quartz.
pub struct FontContext { pub struct FontContext {
core_graphics_fonts: BTreeMap<FontKey, CGFont>, core_graphics_fonts: BTreeMap<FontKey, CGFont>,
@ -184,34 +180,32 @@ impl FontContext {
let path = try!(core_text_font.create_path_for_glyph(glyph_key.glyph_index as CGGlyph, let path = try!(core_text_font.create_path_for_glyph(glyph_key.glyph_index as CGGlyph,
&CG_AFFINE_TRANSFORM_IDENTITY)); &CG_AFFINE_TRANSFORM_IDENTITY));
let mut commands = vec![]; let mut builder = vec![];
path.apply(&|element| { path.apply(&|element| {
let points = element.points(); let points = element.points();
commands.push(match element.element_type { match element.element_type {
CGPathElementType::MoveToPoint => { CGPathElementType::MoveToPoint => {
CubicPathCommand::MoveTo(convert_point(&points[0])) builder.push(PathEvent::MoveTo(convert_point(&points[0])))
} }
CGPathElementType::AddLineToPoint => { CGPathElementType::AddLineToPoint => {
CubicPathCommand::LineTo(convert_point(&points[0])) builder.push(PathEvent::LineTo(convert_point(&points[0])))
} }
CGPathElementType::AddQuadCurveToPoint => { CGPathElementType::AddQuadCurveToPoint => {
CubicPathCommand::QuadCurveTo(convert_point(&points[0]), builder.push(PathEvent::QuadraticTo(convert_point(&points[0]),
convert_point(&points[1])) convert_point(&points[1])))
} }
CGPathElementType::AddCurveToPoint => { CGPathElementType::AddCurveToPoint => {
CubicPathCommand::CubicCurveTo(convert_point(&points[0]), builder.push(PathEvent::CubicTo(convert_point(&points[0]),
convert_point(&points[1]), convert_point(&points[1]),
convert_point(&points[2])) convert_point(&points[2])))
} }
CGPathElementType::CloseSubpath => CubicPathCommand::ClosePath, CGPathElementType::CloseSubpath => builder.push(PathEvent::Close),
}); }
}); });
let approx_stream = CubicPathCommandApproxStream::new(commands.into_iter(), return Ok(GlyphOutline {
CURVE_APPROX_ERROR_BOUND); events: builder,
});
let approx_commands: Vec<_> = approx_stream.collect();
return Ok(approx_commands);
fn convert_point(core_graphics_point: &CGPoint) -> Point2D<f32> { fn convert_point(core_graphics_point: &CGPoint) -> Point2D<f32> {
Point2D::new(core_graphics_point.x as f32, core_graphics_point.y as f32) 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())) Ok(core_text::font::new_from_CGFont(core_graphics_font, self.size.to_f64_px()))
} }
} }
pub struct GlyphOutline {
events: Vec<PathEvent>,
}
impl GlyphOutline {
#[inline]
pub fn iter(&self) -> Cloned<Iter<PathEvent>> {
self.events.iter().cloned()
}
}

View File

@ -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_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_Outline_Translate, FT_PIXEL_MODE_LCD, FT_RENDER_MODE_LCD, FT_Render_Glyph};
use freetype_sys::{FT_Set_Char_Size, FT_UInt}; use freetype_sys::{FT_Set_Char_Size, FT_UInt};
use pathfinder_path_utils::PathCommand;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::marker::PhantomData;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use std::sync::Arc; use std::sync::Arc;
use self::fixed::{FromFtF26Dot6, ToFtF26Dot6}; use self::fixed::{FromFtF26Dot6, ToFtF26Dot6};
use self::outline::OutlineStream; use self::outline::Outline;
use {FontKey, FontInstance, GlyphDimensions, GlyphImage, GlyphKey}; use {FontKey, FontInstance, GlyphDimensions, GlyphImage, GlyphKey};
mod fixed; mod fixed;
mod outline; mod outline;
pub type GlyphOutline<'a> = Outline<'a>;
// Default to no hinting. // Default to no hinting.
// //
// TODO(pcwalton): Make this configurable. // TODO(pcwalton): Make this configurable.
@ -125,10 +125,7 @@ impl FontContext {
-> Result<GlyphOutline<'a>, ()> { -> Result<GlyphOutline<'a>, ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| { self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
unsafe { unsafe {
GlyphOutline { GlyphOutline::new(&(*glyph_slot).outline)
stream: OutlineStream::new(&(*glyph_slot).outline),
phantom: PhantomData,
}
} }
}) })
} }
@ -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<PathCommand> {
self.stream.next()
}
}
struct Face { struct Face {
face: FT_Face, face: FT_Face,
bytes: Arc<Vec<u8>>, bytes: Arc<Vec<u8>>,

View File

@ -10,15 +10,36 @@
use euclid::Point2D; use euclid::Point2D;
use freetype_sys::{FT_Outline, FT_Vector}; 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; 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> { pub struct OutlineStream<'a> {
outline: &'a FT_Outline, outline: &'a FT_Outline,
point_index: u16, point_index: u16,
contour_index: u16, contour_index: u16,
first_position_of_subpath: Point2D<f32>, state: PathState,
first_point_index_of_contour: bool, first_point_index_of_contour: bool,
} }
@ -29,7 +50,7 @@ impl<'a> OutlineStream<'a> {
outline: outline, outline: outline,
point_index: 0, point_index: 0,
contour_index: 0, contour_index: 0,
first_position_of_subpath: Point2D::zero(), state: PathState::new(),
first_point_index_of_contour: true, first_point_index_of_contour: true,
} }
} }
@ -46,9 +67,9 @@ impl<'a> OutlineStream<'a> {
} }
impl<'a> Iterator for OutlineStream<'a> { impl<'a> Iterator for OutlineStream<'a> {
type Item = PathCommand; type Item = PathEvent;
fn next(&mut self) -> Option<PathCommand> { fn next(&mut self) -> Option<PathEvent> {
unsafe { unsafe {
let mut control_point_position: Option<Point2D<f32>> = None; let mut control_point_position: Option<Point2D<f32>> = None;
loop { loop {
@ -60,13 +81,16 @@ impl<'a> Iterator for OutlineStream<'a> {
*self.outline.contours.offset(self.contour_index as isize) as u16; *self.outline.contours.offset(self.contour_index as isize) as u16;
if self.point_index == last_point_index_in_current_contour + 1 { if self.point_index == last_point_index_in_current_contour + 1 {
if let Some(control_point_position) = control_point_position { if let Some(control_point_position) = control_point_position {
return Some(PathCommand::CurveTo(control_point_position, let event = PathEvent::QuadraticTo(control_point_position,
self.first_position_of_subpath)) self.state.first);
self.state.path_event(event);
return Some(event)
} }
self.contour_index += 1; self.contour_index += 1;
self.first_point_index_of_contour = true; 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. // FIXME(pcwalton): Approximate cubic curves with quadratics.
@ -75,20 +99,24 @@ impl<'a> Iterator for OutlineStream<'a> {
if self.first_point_index_of_contour { if self.first_point_index_of_contour {
self.first_point_index_of_contour = false; self.first_point_index_of_contour = false;
self.first_position_of_subpath = position;
self.point_index += 1; 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) { match (control_point_position, point_on_curve) {
(Some(control_point_position), false) => { (Some(control_point_position), false) => {
let on_curve_position = control_point_position.lerp(position, 0.5); let on_curve_position = control_point_position.lerp(position, 0.5);
return Some(PathCommand::CurveTo(control_point_position, let event = PathEvent::QuadraticTo(control_point_position,
on_curve_position)) on_curve_position);
self.state.path_event(event);
return Some(event)
} }
(Some(control_point_position), true) => { (Some(control_point_position), true) => {
self.point_index += 1; 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) => { (None, false) => {
self.point_index += 1; self.point_index += 1;
@ -96,7 +124,8 @@ impl<'a> Iterator for OutlineStream<'a> {
} }
(None, true) => { (None, true) => {
self.point_index += 1; 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] #[inline]
fn ft_vector_to_f32(ft_vector: FT_Vector) -> Point2D<f32> { fn ft_vector_to_f32(ft_vector: FT_Vector) -> Point2D<f32> {
Point2D::new(ft_vector.x as f32 / 64.0, ft_vector.y as f32 / 64.0) Point2D::new(ft_vector.x as f32 / 64.0, ft_vector.y as f32 / 64.0)

View File

@ -20,7 +20,8 @@
extern crate app_units; extern crate app_units;
extern crate euclid; extern crate euclid;
extern crate libc; extern crate libc;
extern crate pathfinder_path_utils; extern crate lyon_geom;
extern crate lyon_path;
extern crate serde; extern crate serde;
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@ -7,13 +7,16 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
name = "pathfinder_partitioner" name = "pathfinder_partitioner"
[dependencies] [dependencies]
arrayvec = "0.4"
bincode = "0.8" bincode = "0.8"
bit-vec = "0.4" bit-vec = "0.4"
byteorder = "1.2" byteorder = "1.2"
env_logger = "0.4" env_logger = "0.4"
euclid = "0.15" euclid = "0.16"
half = "1.0" half = "1.0"
log = "0.3" log = "0.3"
lyon_geom = "0.9"
lyon_path = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

187
partitioner/src/builder.rs Normal file
View File

@ -0,0 +1,187 @@
// pathfinder/partitioner/src/builder.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
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<Endpoint>,
pub subpath_ranges: Vec<Range<u32>>,
}
impl Builder {
#[inline]
pub fn new() -> Builder {
Builder {
endpoints: vec![],
subpath_ranges: vec![],
}
}
#[inline]
fn current_subpath_index(&self) -> Option<u32> {
if self.subpath_ranges.is_empty() {
None
} else {
Some(self.subpath_ranges.len() as u32 - 1)
}
}
fn add_endpoint(&mut self, ctrl: Option<Point2D<f32>>, to: Point2D<f32>) {
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<Point2D<f32>> {
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<f32> {
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<f32>) {
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<f32>) {
self.add_endpoint(None, to);
}
}
impl PathBuilder for Builder {
fn quadratic_bezier_to(&mut self, ctrl: Point2D<f32>, to: Point2D<f32>) {
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<f32>; 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<f32>, ctrl2: Point2D<f32>, to: Point2D<f32>) {
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<f32>,
_radii: Vector2D<f32>,
_angle: Angle<f32>,
_x_rotation: Angle<f32>) {
panic!("TODO: Support arcs in the Pathfinder builder!")
}
}
#[derive(Clone, Copy, Debug)]
pub struct Endpoint {
pub to: Point2D<f32>,
pub ctrl: Option<Point2D<f32>>,
pub subpath_index: u32,
}

View File

@ -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<f32> {
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>>(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<f32>,
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<f32> {
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<f32>
}
#[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
}

View File

@ -21,11 +21,14 @@
//! use case, mesh libraries can be serialized into a simple binary format. Of course, meshes can //! 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. //! also be generated dynamically and rendered on the fly.
extern crate arrayvec;
extern crate bincode; extern crate bincode;
extern crate bit_vec; extern crate bit_vec;
extern crate byteorder; extern crate byteorder;
extern crate env_logger; extern crate env_logger;
extern crate euclid; extern crate euclid;
extern crate lyon_geom;
extern crate lyon_path;
extern crate pathfinder_path_utils; extern crate pathfinder_path_utils;
extern crate serde; extern crate serde;
@ -35,15 +38,12 @@ extern crate log;
extern crate serde_derive; extern crate serde_derive;
use euclid::Point2D; use euclid::Point2D;
use pathfinder_path_utils::{Endpoint, Subpath};
use std::u32; use std::u32;
pub mod capi; pub mod builder;
pub mod mesh_library; pub mod mesh_library;
pub mod partitioner; pub mod partitioner;
mod normal;
/// The fill rule. /// The fill rule.
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View File

@ -1,6 +1,6 @@
// pathfinder/partitioner/src/mesh_library.rs // 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 <LICENSE-APACHE or // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -10,14 +10,17 @@
use bincode::{self, Infinite}; use bincode::{self, Infinite};
use byteorder::{LittleEndian, WriteBytesExt}; use byteorder::{LittleEndian, WriteBytesExt};
use euclid::Point2D; use euclid::{Point2D, Vector2D};
use pathfinder_path_utils::{PathCommand, PathSegment, PathSegmentStream}; 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 serde::Serialize;
use std::f32;
use std::io::{self, ErrorKind, Seek, SeekFrom, Write}; use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
use std::ops::Range; use std::ops::Range;
use std::u32; use std::u32;
use normal;
use {BQuad, BQuadVertexPositions, BVertexLoopBlinnData}; use {BQuad, BQuadVertexPositions, BVertexLoopBlinnData};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -174,25 +177,29 @@ impl MeshLibrary {
} }
pub fn push_segments<I>(&mut self, path_id: u16, stream: I) pub fn push_segments<I>(&mut self, path_id: u16, stream: I)
where I: Iterator<Item = PathCommand> { where I: Iterator<Item = PathEvent> {
let first_line_index = self.segments.lines.len() as u32; let first_line_index = self.segments.lines.len() as u32;
let first_curve_index = self.segments.curves.len() as u32; let first_curve_index = self.segments.curves.len() as u32;
let stream = PathSegmentStream::new(stream); let segment_iter = SegmentIter::new(stream);
for (segment, _) in stream { for segment in segment_iter {
match segment { match segment {
PathSegment::Line(endpoint_0, endpoint_1) => { segments::Segment::Line(line_segment) => {
self.segments.lines.push(LineSegment { self.segments.lines.push(LineSegment {
endpoint_0: endpoint_0, endpoint_0: line_segment.from,
endpoint_1: endpoint_1, endpoint_1: line_segment.to,
}); })
} }
PathSegment::Curve(endpoint_0, control_point, endpoint_1) => { segments::Segment::Quadratic(curve_segment) => {
self.segments.curves.push(CurveSegment { self.segments.curves.push(CurveSegment {
endpoint_0: endpoint_0, endpoint_0: curve_segment.from,
control_point: control_point, control_point: curve_segment.ctrl,
endpoint_1: endpoint_1, 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. /// Computes vertex normals necessary for emboldening and/or stem darkening.
pub fn push_normals<I>(&mut self, stream: I) where I: Iterator<Item = PathCommand> { pub fn push_normals<I>(&mut self, _path_id: u16, stream: I) where I: PathIterator {
normal::push_normals(self, stream) 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>) -> f32 {
Vector2D::new(vector.x, -vector.y).angle_from_x_axis().get()
}
} }
/// Writes this mesh library to a RIFF file. /// Writes this mesh library to a RIFF file.

View File

@ -1,142 +0,0 @@
// pathfinder/partitioner/src/normal.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<I>(library: &mut MeshLibrary, stream: I)
where I: Iterator<Item = PathCommand> {
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<f32>,
vertex_position: &Point2D<f32>,
next_position: &Point2D<f32>)
-> Vector2D<f32> {
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>) -> f32 {
(-normal.y).atan2(normal.x)
}
#[derive(Clone, Copy, Debug)]
enum SegmentIndex {
Line(usize),
Curve(usize),
}

View File

@ -1,180 +0,0 @@
// pathfinder/partitioner/partitioner.h
#ifndef PATHFINDER_PARTITIONER_H
#define PATHFINDER_PARTITIONER_H
#include <limits.h>
#include <stdint.h>
#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

View File

@ -1,6 +1,6 @@
// pathfinder/partitioner/src/partitioner.rs // 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 <LICENSE-APACHE or // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -12,9 +12,7 @@ use bit_vec::BitVec;
use euclid::approxeq::ApproxEq; use euclid::approxeq::ApproxEq;
use euclid::{Point2D, Vector2D}; use euclid::{Point2D, Vector2D};
use log::LogLevel; use log::LogLevel;
use pathfinder_path_utils::PathBuffer; use lyon_geom::{LineSegment, QuadraticBezierSegment};
use pathfinder_path_utils::curve::Curve;
use pathfinder_path_utils::line::Line;
use std::collections::BinaryHeap; use std::collections::BinaryHeap;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::f32; use std::f32;
@ -22,15 +20,17 @@ use std::iter;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::u32; use std::u32;
use builder::Builder;
use mesh_library::MeshLibrary; use mesh_library::MeshLibrary;
use {BQuad, BVertexLoopBlinnData, BVertexKind, Endpoint, FillRule, Subpath}; use {BQuad, BVertexLoopBlinnData, BVertexKind, FillRule};
const MAX_B_QUAD_SUBDIVISIONS: u8 = 8; const MAX_B_QUAD_SUBDIVISIONS: u8 = 8;
pub struct Partitioner<'a> { const INTERSECTION_TOLERANCE: f32 = 0.001;
endpoints: &'a [Endpoint],
control_points: &'a [Point2D<f32>], pub struct Partitioner {
subpaths: &'a [Subpath], path: Builder,
path_id: u16,
library: MeshLibrary, library: MeshLibrary,
@ -40,17 +40,14 @@ pub struct Partitioner<'a> {
visited_points: BitVec, visited_points: BitVec,
active_edges: Vec<ActiveEdge>, active_edges: Vec<ActiveEdge>,
vertex_normals: Vec<VertexNormal>, vertex_normals: Vec<VertexNormal>,
path_id: u16,
} }
impl<'a> Partitioner<'a> { impl Partitioner {
#[inline] #[inline]
pub fn new<'b>(library: MeshLibrary) -> Partitioner<'b> { pub fn new(library: MeshLibrary) -> Partitioner {
Partitioner { Partitioner {
endpoints: &[], path: Builder::new(),
control_points: &[], path_id: 0,
subpaths: &[],
fill_rule: FillRule::Winding, fill_rule: FillRule::Winding,
library: library, library: library,
@ -59,7 +56,6 @@ impl<'a> Partitioner<'a> {
visited_points: BitVec::new(), visited_points: BitVec::new(),
active_edges: vec![], active_edges: vec![],
vertex_normals: vec![], vertex_normals: vec![],
path_id: 0,
} }
} }
@ -78,39 +74,31 @@ impl<'a> Partitioner<'a> {
self.library self.library
} }
pub fn init_with_raw_data(&mut self, #[inline]
new_endpoints: &'a [Endpoint], pub fn builder(&self) -> &Builder {
new_control_points: &'a [Point2D<f32>], &self.path
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] #[inline]
pub fn set_fill_rule(&mut self, new_fill_rule: FillRule) { pub fn builder_mut(&mut self) -> &mut Builder {
self.fill_rule = new_fill_rule &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.heap.clear();
self.active_edges.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(); let start_lengths = self.library.snapshot_lengths();
self.path_id = path_id; self.init_heap();
self.init_heap(first_subpath_index, last_subpath_index);
while self.process_next_point() {} while self.process_next_point() {}
@ -135,7 +123,7 @@ impl<'a> Partitioner<'a> {
} }
if log_enabled!(LogLevel::Debug) { 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!("processing point {}: {:?}", point.endpoint_index, position);
debug!("... active edges at {}:", position.x); debug!("... active edges at {}:", position.x);
for (active_edge_index, active_edge) in self.active_edges.iter().enumerate() { 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 next_active_edge_index = self.find_point_between_active_edges(endpoint_index);
let endpoint = &self.endpoints[endpoint_index as usize]; let endpoint = self.path.endpoints[endpoint_index as usize];
self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x); 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); 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) { fn process_regular_endpoint(&mut self, endpoint_index: u32, active_edge_index: u32) {
debug!("... REGULAR point: active edge {}", active_edge_index); debug!("... REGULAR point: active edge {}", active_edge_index);
let endpoint = &self.endpoints[endpoint_index as usize]; let endpoint = self.path.endpoints[endpoint_index as usize];
let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.position.x) == let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.to.x) ==
BQuadEmissionResult::BQuadEmittedAbove; BQuadEmissionResult::BQuadEmittedAbove;
let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); 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 active_edge = &mut self.active_edges[active_edge_index as usize];
let endpoint_position = self.endpoints[active_edge.right_endpoint_index as usize] let endpoint_position = self.path
.position; .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 // If we already made a B-vertex point for this endpoint, reuse it instead of making a
// new one. // new one.
@ -230,23 +219,22 @@ impl<'a> Partitioner<'a> {
let new_point = self.create_point_from_endpoint(right_endpoint_index); let new_point = self.create_point_from_endpoint(right_endpoint_index);
*self.heap.peek_mut().unwrap() = new_point; *self.heap.peek_mut().unwrap() = new_point;
let control_point_index = if self.active_edges[active_edge_index as usize].left_to_right { let control_point = if self.active_edges[active_edge_index as usize].left_to_right {
self.control_point_index_before_endpoint(next_endpoint_index) self.control_point_before_endpoint(next_endpoint_index)
} else { } else {
self.control_point_index_after_endpoint(prev_endpoint_index) self.control_point_after_endpoint(prev_endpoint_index)
}; };
match control_point_index { match control_point {
u32::MAX => { None => {
self.active_edges[active_edge_index as usize].control_point_vertex_index = u32::MAX 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.active_edges[active_edge_index as usize].control_point_vertex_index =
self.library.b_vertex_loop_blinn_data.len() as u32; self.library.b_vertex_loop_blinn_data.len() as u32;
let left_vertex_index = self.active_edges[active_edge_index as usize] let left_vertex_index = self.active_edges[active_edge_index as usize]
.left_vertex_index; .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( let control_point_b_vertex_loop_blinn_data = BVertexLoopBlinnData::control_point(
&self.library.b_vertex_positions[left_vertex_index as usize], &self.library.b_vertex_positions[left_vertex_index as usize],
&control_point_position, &control_point_position,
@ -266,12 +254,12 @@ impl<'a> Partitioner<'a> {
debug_assert!(active_edge_indices[0] < active_edge_indices[1], debug_assert!(active_edge_indices[0] < active_edge_indices[1],
"Matching active edge indices in wrong order when processing MAX point"); "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 // TODO(pcwalton): Collapse the two duplicate endpoints that this will create together if
// possible (i.e. if they have the same parity). // 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[0], endpoint.to.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[1], endpoint.to.x);
// Add supporting interior triangles if necessary. // Add supporting interior triangles if necessary.
self.heap.pop(); 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) { 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 { loop {
let mut swapped = false; let mut swapped = false;
for lower_active_edge_index in 1..(self.active_edges.len() as u32) { 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; new_active_edges[1].left_vertex_index = left_vertex_index;
// FIXME(pcwalton): Normal // 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, self.library.add_b_vertex(&position,
&BVertexLoopBlinnData::new(BVertexKind::Endpoint0)); &BVertexLoopBlinnData::new(BVertexKind::Endpoint0));
new_active_edges[0].toggle_parity(); new_active_edges[0].toggle_parity();
new_active_edges[1].toggle_parity(); new_active_edges[1].toggle_parity();
let endpoint = &self.endpoints[endpoint_index as usize]; let endpoint = &self.path.endpoints[endpoint_index as usize];
let prev_endpoint = &self.endpoints[prev_endpoint_index as usize]; let prev_endpoint = &self.path.endpoints[prev_endpoint_index as usize];
let next_endpoint = &self.endpoints[next_endpoint_index as usize]; let next_endpoint = &self.path.endpoints[next_endpoint_index as usize];
let prev_vector = prev_endpoint.position - endpoint.position; let prev_vector = prev_endpoint.to - endpoint.to;
let next_vector = next_endpoint.position - endpoint.position; 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 { if prev_vector.cross(next_vector) >= 0.0 {
new_active_edges[0].right_endpoint_index = prev_endpoint_index; new_active_edges[0].right_endpoint_index = prev_endpoint_index;
new_active_edges[1].right_endpoint_index = next_endpoint_index; new_active_edges[1].right_endpoint_index = next_endpoint_index;
new_active_edges[0].left_to_right = false; new_active_edges[0].left_to_right = false;
new_active_edges[1].left_to_right = true; new_active_edges[1].left_to_right = true;
upper_control_point_index = self.endpoints[endpoint_index as usize].control_point_index; upper_control_point = self.path.endpoints[endpoint_index as usize].ctrl;
lower_control_point_index = self.endpoints[next_endpoint_index as usize] lower_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl;
.control_point_index;
} else { } else {
new_active_edges[0].right_endpoint_index = next_endpoint_index; new_active_edges[0].right_endpoint_index = next_endpoint_index;
new_active_edges[1].right_endpoint_index = prev_endpoint_index; new_active_edges[1].right_endpoint_index = prev_endpoint_index;
new_active_edges[0].left_to_right = true; new_active_edges[0].left_to_right = true;
new_active_edges[1].left_to_right = false; new_active_edges[1].left_to_right = false;
upper_control_point_index = self.endpoints[next_endpoint_index as usize] upper_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl;
.control_point_index; lower_control_point = self.path.endpoints[endpoint_index as usize].ctrl;
lower_control_point_index = self.endpoints[endpoint_index as usize].control_point_index;
} }
match upper_control_point_index { match upper_control_point {
u32::MAX => new_active_edges[0].control_point_vertex_index = u32::MAX, None => new_active_edges[0].control_point_vertex_index = u32::MAX,
upper_control_point_index => { Some(control_point_position) => {
new_active_edges[0].control_point_vertex_index = new_active_edges[0].control_point_vertex_index =
self.library.b_vertex_loop_blinn_data.len() as u32; 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 = 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 = let control_point_b_vertex_loop_blinn_data =
BVertexLoopBlinnData::control_point(&position, BVertexLoopBlinnData::control_point(&position,
&control_point_position, &control_point_position,
@ -392,16 +376,14 @@ impl<'a> Partitioner<'a> {
} }
} }
match lower_control_point_index { match lower_control_point {
u32::MAX => new_active_edges[1].control_point_vertex_index = u32::MAX, None => new_active_edges[1].control_point_vertex_index = u32::MAX,
lower_control_point_index => { Some(control_point_position) => {
new_active_edges[1].control_point_vertex_index = new_active_edges[1].control_point_vertex_index =
self.library.b_vertex_loop_blinn_data.len() as u32; 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 = 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 = let control_point_b_vertex_loop_blinn_data =
BVertexLoopBlinnData::control_point(&position, BVertexLoopBlinnData::control_point(&position,
&control_point_position, &control_point_position,
@ -437,17 +419,11 @@ impl<'a> Partitioner<'a> {
prev_active_edge_y <= next_active_edge_y prev_active_edge_y <= next_active_edge_y
} }
fn init_heap(&mut self, first_subpath_index: u32, last_subpath_index: u32) { fn init_heap(&mut self) {
for subpath in &self.subpaths[(first_subpath_index as usize).. for endpoint_index in 0..(self.path.endpoints.len() as u32) {
(last_subpath_index as usize)] { if let EndpointClass::Min = self.classify_endpoint(endpoint_index) {
for endpoint_index in subpath.first_endpoint_index..subpath.last_endpoint_index { let new_point = self.create_point_from_endpoint(endpoint_index);
match self.classify_endpoint(endpoint_index) { self.heap.push(new_point)
EndpointClass::Min => {
let new_point = self.create_point_from_endpoint(endpoint_index);
self.heap.push(new_point)
}
EndpointClass::Regular | EndpointClass::Max => {}
}
} }
} }
} }
@ -557,7 +533,9 @@ impl<'a> Partitioner<'a> {
lower_subdivision.to_curve(&self.library.b_vertex_positions)) { lower_subdivision.to_curve(&self.library.b_vertex_positions)) {
// TODO(pcwalton): Handle concave-concave convex hull intersections. // TODO(pcwalton): Handle concave-concave convex hull intersections.
if upper_shape == Shape::Concave && 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() { f32::approx_epsilon() {
let (upper_left_subsubdivision, upper_right_subsubdivision) = let (upper_left_subsubdivision, upper_right_subsubdivision) =
self.subdivide_active_edge_again_at_t(&upper_subdivision, self.subdivide_active_edge_again_at_t(&upper_subdivision,
@ -585,7 +563,9 @@ impl<'a> Partitioner<'a> {
} }
if lower_shape == Shape::Concave && 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() { -f32::approx_epsilon() {
let (lower_left_subsubdivision, lower_right_subsubdivision) = let (lower_left_subsubdivision, lower_right_subsubdivision) =
self.subdivide_active_edge_again_at_t(&lower_subdivision, self.subdivide_active_edge_again_at_t(&lower_subdivision,
@ -652,28 +632,28 @@ impl<'a> Partitioner<'a> {
-> (SubdividedActiveEdge, SubdividedActiveEdge) { -> (SubdividedActiveEdge, SubdividedActiveEdge) {
let curve = subdivision.to_curve(&self.library.b_vertex_positions) let curve = subdivision.to_curve(&self.library.b_vertex_positions)
.expect("subdivide_active_edge_again_at_t(): not a curve!"); .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 left_control_point_index = self.library.b_vertex_positions.len() as u32;
let midpoint_index = left_control_point_index + 1; let midpoint_index = left_control_point_index + 1;
let right_control_point_index = midpoint_index + 1; let right_control_point_index = midpoint_index + 1;
self.library.b_vertex_positions.extend([ self.library.b_vertex_positions.extend([
left_curve.control_point, left_curve.segment().ctrl,
left_curve.endpoints[1], left_curve.segment().to,
right_curve.control_point, right_curve.segment().ctrl,
].into_iter()); ].into_iter());
// Initially, assume that the parity is false. We will modify the Loop-Blinn data later if // Initially, assume that the parity is false. We will modify the Loop-Blinn data later if
// that is incorrect. // that is incorrect.
self.library.b_vertex_loop_blinn_data.extend([ self.library.b_vertex_loop_blinn_data.extend([
BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], BVertexLoopBlinnData::control_point(&left_curve.segment().from,
&left_curve.control_point, &left_curve.segment().ctrl,
&left_curve.endpoints[1], &left_curve.segment().to,
bottom), bottom),
BVertexLoopBlinnData::new(BVertexKind::Endpoint0), BVertexLoopBlinnData::new(BVertexKind::Endpoint0),
BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], BVertexLoopBlinnData::control_point(&right_curve.segment().from,
&right_curve.control_point, &right_curve.segment().ctrl,
&right_curve.endpoints[1], &right_curve.segment().to,
bottom), bottom),
].into_iter()); ].into_iter());
@ -697,7 +677,7 @@ impl<'a> Partitioner<'a> {
-> (SubdividedActiveEdge, SubdividedActiveEdge) { -> (SubdividedActiveEdge, SubdividedActiveEdge) {
let curve = subdivision.to_curve(&self.library.b_vertex_positions) let curve = subdivision.to_curve(&self.library.b_vertex_positions)
.expect("subdivide_active_edge_again_at_x(): not a curve!"); .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) 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 { 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| { 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, Some(active_edge_index) => active_edge_index as u32,
None => self.active_edges.len() 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 { fn solve_active_edge_t_for_x(&self, x: f32, active_edge: &ActiveEdge) -> f32 {
let left_vertex_position = let left_vertex_position =
&self.library.b_vertex_positions[active_edge.left_vertex_index as usize]; &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] let right_endpoint_position =
.position; &self.path.endpoints[active_edge.right_endpoint_index as usize].to;
match active_edge.control_point_vertex_index { 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 => { control_point_vertex_index => {
let control_point = &self.library let control_point = &self.library
.b_vertex_positions[control_point_vertex_index as usize]; .b_vertex_positions[control_point_vertex_index as usize];
Curve::new(left_vertex_position, let segment = QuadraticBezierSegment {
control_point, from: *left_vertex_position,
right_endpoint_position).solve_t_for_x(x) 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 = let left_vertex_position =
&self.library.b_vertex_positions[active_edge.left_vertex_index as usize]; &self.library.b_vertex_positions[active_edge.left_vertex_index as usize];
let right_endpoint_position = 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 { match active_edge.control_point_vertex_index {
u32::MAX => { u32::MAX => {
left_vertex_position.to_vector() left_vertex_position.to_vector()
@ -794,7 +782,11 @@ impl<'a> Partitioner<'a> {
control_point_vertex_index => { control_point_vertex_index => {
let control_point = &self.library let control_point = &self.library
.b_vertex_positions[control_point_vertex_index as usize]; .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 = let upper_left_vertex_position =
&self.library.b_vertex_positions[upper_active_edge.left_vertex_index as usize]; &self.library.b_vertex_positions[upper_active_edge.left_vertex_index as usize];
let upper_right_endpoint_position = 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 = let lower_left_vertex_position =
&self.library.b_vertex_positions[lower_active_edge.left_vertex_index as usize]; &self.library.b_vertex_positions[lower_active_edge.left_vertex_index as usize];
let lower_right_endpoint_position = 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, match (upper_active_edge.control_point_vertex_index,
lower_active_edge.control_point_vertex_index) { lower_active_edge.control_point_vertex_index) {
(u32::MAX, u32::MAX) => { (u32::MAX, u32::MAX) => {
let (upper_line, _) = let (upper_line, _) = LineSegment {
Line::new(upper_left_vertex_position, from: *upper_left_vertex_position,
upper_right_endpoint_position).subdivide_at_x(max_x); to: *upper_right_endpoint_position,
let (lower_line, _) = }.split_at_x(max_x);
Line::new(lower_left_vertex_position, let (lower_line, _) = LineSegment {
lower_right_endpoint_position).subdivide_at_x(max_x); from: *lower_left_vertex_position,
upper_line.intersect(&lower_line) to: *lower_right_endpoint_position,
}.split_at_x(max_x);
upper_line.intersection(&lower_line)
} }
(upper_control_point_vertex_index, u32::MAX) => { (upper_control_point_vertex_index, u32::MAX) => {
let upper_control_point = let upper_control_point =
&self.library.b_vertex_positions[upper_control_point_vertex_index as usize]; &self.library.b_vertex_positions[upper_control_point_vertex_index as usize];
let (upper_curve, _) = let (upper_curve, _) = QuadraticBezierSegment {
Curve::new(&upper_left_vertex_position, from: *upper_left_vertex_position,
&upper_control_point, ctrl: *upper_control_point,
&upper_right_endpoint_position).subdivide_at_x(max_x); to: *upper_right_endpoint_position,
let (lower_line, _) = }.assume_monotonic().split_at_x(max_x);
Line::new(lower_left_vertex_position, let (lower_line, _) = LineSegment {
lower_right_endpoint_position).subdivide_at_x(max_x); from: *lower_left_vertex_position,
upper_curve.intersect(&lower_line) 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) => { (u32::MAX, lower_control_point_vertex_index) => {
let lower_control_point = let lower_control_point =
&self.library.b_vertex_positions[lower_control_point_vertex_index as usize]; &self.library.b_vertex_positions[lower_control_point_vertex_index as usize];
let (lower_curve, _) = let (lower_curve, _) = QuadraticBezierSegment {
Curve::new(&lower_left_vertex_position, from: *lower_left_vertex_position,
&lower_control_point, ctrl: *lower_control_point,
&lower_right_endpoint_position).subdivide_at_x(max_x); to: *lower_right_endpoint_position,
let (upper_line, _) = }.assume_monotonic().split_at_x(max_x);
Line::new(upper_left_vertex_position, let (upper_line, _) = LineSegment {
upper_right_endpoint_position).subdivide_at_x(max_x); from: *upper_left_vertex_position,
lower_curve.intersect(&upper_line) 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) => { (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]; &self.library.b_vertex_positions[upper_control_point_vertex_index as usize];
let lower_control_point = let lower_control_point =
&self.library.b_vertex_positions[lower_control_point_vertex_index as usize]; &self.library.b_vertex_positions[lower_control_point_vertex_index as usize];
let (upper_curve, _) = let (upper_curve, _) = QuadraticBezierSegment {
Curve::new(&upper_left_vertex_position, from: *upper_left_vertex_position,
&upper_control_point, ctrl: *upper_control_point,
&upper_right_endpoint_position).subdivide_at_x(max_x); to: *upper_right_endpoint_position,
let (lower_curve, _) = }.assume_monotonic().split_at_x(max_x);
Curve::new(&lower_left_vertex_position, let (lower_curve, _) = QuadraticBezierSegment {
&lower_control_point, from: *lower_left_vertex_position,
&lower_right_endpoint_position).subdivide_at_x(max_x); ctrl: *lower_control_point,
upper_curve.intersect(&lower_curve) 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; let left_curve_control_point_vertex_index;
match active_edge.control_point_vertex_index { match active_edge.control_point_vertex_index {
u32::MAX => { u32::MAX => {
let right_point = self.endpoints[active_edge.right_endpoint_index as usize] let right_point =
.position; self.path.endpoints[active_edge.right_endpoint_index as usize].to;
let middle_point = left_point_position.to_vector() let middle_point = left_point_position.to_vector()
.lerp(right_point.to_vector(), t); .lerp(right_point.to_vector(), t);
@ -917,11 +920,12 @@ impl<'a> Partitioner<'a> {
self.library self.library
.b_vertex_positions[active_edge.control_point_vertex_index as usize]; .b_vertex_positions[active_edge.control_point_vertex_index as usize];
let right_endpoint_position = 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;
let original_curve = Curve::new(&left_endpoint_position, let (left_curve, right_curve) = QuadraticBezierSegment {
&control_point_position, from: left_endpoint_position,
&right_endpoint_position); ctrl: control_point_position,
let (left_curve, right_curve) = original_curve.subdivide(t); to: right_endpoint_position,
}.split(t);
left_curve_control_point_vertex_index = left_curve_control_point_vertex_index =
self.library.b_vertex_loop_blinn_data.len() as u32; self.library.b_vertex_loop_blinn_data.len() as u32;
@ -930,18 +934,18 @@ impl<'a> Partitioner<'a> {
// FIXME(pcwalton): Normals // FIXME(pcwalton): Normals
self.library self.library
.add_b_vertex(&left_curve.control_point, .add_b_vertex(&left_curve.ctrl,
&BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], &BVertexLoopBlinnData::control_point(&left_curve.from,
&left_curve.control_point, &left_curve.ctrl,
&left_curve.endpoints[1], &left_curve.to,
bottom)); bottom));
self.library.add_b_vertex(&left_curve.endpoints[1], self.library.add_b_vertex(&left_curve.to,
&BVertexLoopBlinnData::new(active_edge.endpoint_kind())); &BVertexLoopBlinnData::new(active_edge.endpoint_kind()));
self.library self.library
.add_b_vertex(&right_curve.control_point, .add_b_vertex(&right_curve.ctrl,
&BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], &BVertexLoopBlinnData::control_point(&right_curve.from,
&right_curve.control_point, &right_curve.ctrl,
&right_curve.endpoints[1], &right_curve.to,
bottom)); bottom));
} }
} }
@ -1006,38 +1010,38 @@ impl<'a> Partitioner<'a> {
} }
fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 { fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 {
let endpoint = &self.endpoints[endpoint_index as usize]; let endpoint = &self.path.endpoints[endpoint_index as usize];
let subpath = &self.subpaths[endpoint.subpath_index as usize]; let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize];
if endpoint_index > subpath.first_endpoint_index { if endpoint_index > subpath.start {
endpoint_index - 1 endpoint_index - 1
} else { } else {
subpath.last_endpoint_index - 1 subpath.end - 1
} }
} }
fn next_endpoint_of(&self, endpoint_index: u32) -> u32 { fn next_endpoint_of(&self, endpoint_index: u32) -> u32 {
let endpoint = &self.endpoints[endpoint_index as usize]; let endpoint = &self.path.endpoints[endpoint_index as usize];
let subpath = &self.subpaths[endpoint.subpath_index as usize]; let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize];
if endpoint_index + 1 < subpath.last_endpoint_index { if endpoint_index + 1 < subpath.end {
endpoint_index + 1 endpoint_index + 1
} else { } else {
subpath.first_endpoint_index subpath.start
} }
} }
fn create_point_from_endpoint(&self, endpoint_index: u32) -> Point { fn create_point_from_endpoint(&self, endpoint_index: u32) -> Point {
Point { Point {
position: self.endpoints[endpoint_index as usize].position, position: self.path.endpoints[endpoint_index as usize].to,
endpoint_index: endpoint_index, endpoint_index: endpoint_index,
} }
} }
fn control_point_index_before_endpoint(&self, endpoint_index: u32) -> u32 { fn control_point_before_endpoint(&self, endpoint_index: u32) -> Option<Point2D<f32>> {
self.endpoints[endpoint_index as usize].control_point_index self.path.endpoints[endpoint_index as usize].ctrl
} }
fn control_point_index_after_endpoint(&self, endpoint_index: u32) -> u32 { fn control_point_after_endpoint(&self, endpoint_index: u32) -> Option<Point2D<f32>> {
self.control_point_index_before_endpoint(self.next_endpoint_of(endpoint_index)) 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<f32>]) -> Option<Curve> { fn to_curve(&self, b_vertex_positions: &[Point2D<f32>])
-> Option<QuadraticBezierSegment<f32>> {
if self.left_curve_control_point == u32::MAX { if self.left_curve_control_point == u32::MAX {
None None
} else { } else {
Some(Curve::new(&b_vertex_positions[self.left_curve_left as usize], Some(QuadraticBezierSegment {
&b_vertex_positions[self.left_curve_control_point as usize], from: b_vertex_positions[self.left_curve_left as usize],
&b_vertex_positions[self.middle_point as usize])) ctrl: b_vertex_positions[self.left_curve_control_point as usize],
to: b_vertex_positions[self.middle_point as usize],
})
} }
} }
} }

View File

@ -5,6 +5,8 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies] [dependencies]
arrayvec = "0.4" arrayvec = "0.4"
euclid = "0.15" euclid = "0.16"
lyon_geom = "0.9"
lyon_path = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -1,214 +0,0 @@
// pathfinder/path-utils/src/cubic.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<f32>; 2],
/// The control points of the curve.
pub control_points: [Point2D<f32>; 2],
}
impl CubicCurve {
/// Constructs a new cubic Bézier curve from the given points.
#[inline]
pub fn new(endpoint_0: &Point2D<f32>,
control_point_0: &Point2D<f32>,
control_point_1: &Point2D<f32>,
endpoint_1: &Point2D<f32>)
-> 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<f32> {
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<f32>),
/// Draws a line to the given point.
LineTo(Point2D<f32>),
/// Draws a quadratic curve with the control point to the endpoint, respectively.
QuadCurveTo(Point2D<f32>, Point2D<f32>),
/// Draws a cubic curve with the two control points to the endpoint, respectively.
CubicCurveTo(Point2D<f32>, Point2D<f32>, Point2D<f32>),
/// 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<I> {
inner: I,
error_bound: f32,
last_endpoint: Point2D<f32>,
approx_curve_iter: Option<ApproxCurveIter>,
}
impl<I> CubicPathCommandApproxStream<I> where I: Iterator<Item = CubicPathCommand> {
/// 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<I> {
CubicPathCommandApproxStream {
inner: inner,
error_bound: error_bound,
last_endpoint: Point2D::zero(),
approx_curve_iter: None,
}
}
}
impl<I> Iterator for CubicPathCommandApproxStream<I> where I: Iterator<Item = CubicPathCommand> {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
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<CubicCurve>,
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<Curve> {
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]))
}
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<CubicBezierSegment<f32>>,
error_bound: f32,
iteration: u8,
}
impl CubicToQuadraticSegmentIter {
pub fn new(cubic: &CubicBezierSegment<f32>, 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<f32>;
fn next(&mut self) -> Option<QuadraticBezierSegment<f32>> {
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,
})
}
}

View File

@ -1,146 +0,0 @@
// pathfinder/path-utils/src/curve.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<f32>; 2],
/// The control point of the curve.
pub control_point: Point2D<f32>,
}
impl Curve {
/// Creates a new quadratic Bézier curve from the given endpoints and control point.
#[inline]
pub fn new(endpoint_0: &Point2D<f32>, control_point: &Point2D<f32>, endpoint_1: &Point2D<f32>)
-> 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<f32> {
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<f32>, Option<f32>) {
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<f32> {
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<T>(&self, other: &T) -> Option<Point2D<f32>> where T: Intersect {
<Curve as Intersect>::intersect(self, other)
}
}

View File

@ -1,99 +0,0 @@
// pathfinder/path-utils/src/intersection.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<T>(&self, other: &T) -> Option<Point2D<f32>> 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)
}
}

View File

@ -1,6 +1,6 @@
// pathfinder/path-utils/src/lib.rs // 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 <LICENSE-APACHE or // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -9,400 +9,16 @@
// except according to those terms. // except according to those terms.
//! Various utilities for manipulating Bézier curves. //! Various utilities for manipulating Bézier curves.
//! //!
//! On its own, the partitioner can only generate meshes for fill operations on quadratic Bézier //! Most of these should go upstream to Lyon at some point.
//! 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.
extern crate arrayvec; extern crate arrayvec;
extern crate euclid; extern crate euclid;
#[macro_use] extern crate lyon_geom;
extern crate serde_derive; extern crate lyon_path;
use euclid::{Point2D, Transform2D}; pub mod cubic_to_quadratic;
use std::mem; pub mod normals;
use std::ops::Range; pub mod segments;
use std::u32;
pub mod cubic;
pub mod curve;
pub mod intersection;
pub mod line;
pub mod monotonic;
pub mod stroke; pub mod stroke;
pub mod svg; pub mod transform;
/// 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<f32>),
/// Draws a line to the given point.
LineTo(Point2D<f32>),
/// Draws a quadratic curve with the control point to the endpoint, respectively.
CurveTo(Point2D<f32>, Point2D<f32>),
/// 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<PathCommand>` 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<Endpoint>,
/// All control points of all subpaths.
pub control_points: Vec<Point2D<f32>>,
/// A series of ranges defining each subpath.
pub subpaths: Vec<Subpath>,
}
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<I>(&mut self, stream: I) where I: Iterator<Item = PathCommand> {
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<u32>)
-> 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<PathCommand> {
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<f32>,
/// 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<usize> {
(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<f32>, Point2D<f32>),
/// A quadratic Bézier curve with an endpoint, a control point, and another endpoint, in that
/// order.
Curve(Point2D<f32>, Point2D<f32>, Point2D<f32>),
}
/// 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<I> {
inner: I,
current_subpath_index: u32,
current_point: Point2D<f32>,
current_subpath_start_point: Point2D<f32>,
}
impl<I> PathSegmentStream<I> where I: Iterator<Item = PathCommand> {
/// Creates a new path segment stream that will yield path segments from the given collection
/// of path commands.
pub fn new(inner: I) -> PathSegmentStream<I> {
PathSegmentStream {
inner: inner,
current_subpath_index: u32::MAX,
current_point: Point2D::zero(),
current_subpath_start_point: Point2D::zero(),
}
}
}
impl<I> Iterator for PathSegmentStream<I> where I: Iterator<Item = PathCommand> {
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<f32>, point_b: &Point2D<f32>)
-> 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<I> {
inner: I,
transform: Transform2D<f32>,
}
impl<I> Transform2DPathStream<I> where I: Iterator<Item = PathCommand> {
/// Creates a new transformed path stream from a path stream.
pub fn new(inner: I, transform: &Transform2D<f32>) -> Transform2DPathStream<I> {
Transform2DPathStream {
inner: inner,
transform: *transform,
}
}
}
impl<I> Iterator for Transform2DPathStream<I> where I: Iterator<Item = PathCommand> {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
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
}
}

View File

@ -1,151 +0,0 @@
// pathfinder/path-utils/src/line.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<f32>; 2],
}
impl Line {
/// Creates a new line segment from the two endpoints.
#[inline]
pub fn new(endpoint_0: &Point2D<f32>, endpoint_1: &Point2D<f32>) -> 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<f32> {
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>) -> f32 {
self.to_vector().cross(*point - self.endpoints[0])
}
#[inline]
pub(crate) fn to_vector(&self) -> Vector2D<f32> {
self.endpoints[1] - self.endpoints[0]
}
/// Computes the point of intersection of this line with the given curve.
#[inline]
pub fn intersect<T>(&self, other: &T) -> Option<Point2D<f32>> where T: Intersect {
<Line as 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<Point2D<f32>> {
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<Point2D<f32>> {
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))
}
}

View File

@ -1,88 +0,0 @@
// pathfinder/path-utils/src/monotonic.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<I> {
inner: I,
queue: ArrayVec<[PathCommand; 2]>,
prev_point: Point2D<f32>,
}
impl<I> MonotonicPathCommandStream<I> where I: Iterator<Item = PathCommand> {
/// 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<I> {
MonotonicPathCommandStream {
inner: inner,
queue: ArrayVec::new(),
prev_point: Point2D::zero(),
}
}
}
impl<I> Iterator for MonotonicPathCommandStream<I> where I: Iterator<Item = PathCommand> {
type Item = PathCommand;
fn next(&mut self) -> Option<PathCommand> {
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())
}
}
}
}
}
}

110
path-utils/src/normals.rs Normal file
View File

@ -0,0 +1,110 @@
// pathfinder/path-utils/src/normals.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use euclid::Vector2D;
use euclid::approxeq::ApproxEq;
use lyon_path::PathEvent;
#[derive(Clone)]
pub struct PathNormals {
normals: Vec<Vector2D<f32>>,
}
impl PathNormals {
#[inline]
pub fn new() -> PathNormals {
PathNormals {
normals: vec![],
}
}
#[inline]
pub fn normals(&self) -> &[Vector2D<f32>] {
&self.normals
}
pub fn clear(&mut self) {
self.normals.clear()
}
pub fn add_path<I>(&mut self, path: I) where I: Iterator<Item = PathEvent> {
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<f32>) -> Vector2D<f32> {
Vector2D::new(-vector.y, vector.x)
}

265
path-utils/src/segments.rs Normal file
View File

@ -0,0 +1,265 @@
// pathfinder/path-utils/src/segments.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<I> where I: Iterator<Item = PathEvent> {
inner: PathIter<I>,
stack: Vec<Segment>,
was_just_closed: bool,
}
impl<I> SegmentIter<I> where I: Iterator<Item = PathEvent> {
#[inline]
pub fn new(inner: I) -> SegmentIter<I> {
SegmentIter {
inner: PathIter::new(inner),
stack: vec![],
was_just_closed: true,
}
}
}
impl<I> Iterator for SegmentIter<I> where I: Iterator<Item = PathEvent> {
type Item = Segment;
fn next(&mut self) -> Option<Segment> {
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<f32>),
Quadratic(QuadraticBezierSegment<f32>),
Cubic(CubicBezierSegment<f32>),
/// 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<F>(&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<f32>, distance: f32) -> LineSegment<f32> {
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<f32>,
mut line_segment_1: LineSegment<f32>,
distance: f32)
-> Option<(Point2D<f32>, Point2D<f32>, Point2D<f32>)> {
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<f32>, b: &Point2D<f32>) -> bool {
a.x.approx_eq(&b.x) && a.y.approx_eq(&b.y)
}

View File

@ -1,6 +1,6 @@
// pathfinder/path-utils/src/stroke.rs // 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 <LICENSE-APACHE or // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -10,132 +10,138 @@
//! Utilities for converting path strokes to fills. //! 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 segments::{Segment, SegmentIter};
use line::Line;
/// Represents the style of a stroke. #[derive(Clone, Copy, Debug)]
pub struct Stroke { pub struct StrokeStyle {
/// The stroke diameter.
pub width: f32, pub width: f32,
} }
impl Stroke { impl StrokeStyle {
/// Constructs a new stroke style with the given diameter.
#[inline] #[inline]
pub fn new(width: f32) -> Stroke { pub fn new(width: f32) -> StrokeStyle {
Stroke { StrokeStyle {
width: width, width: width,
} }
} }
}
/// Writes a path that represents the result of stroking `stream` with this stroke style into pub struct StrokeToFillIter<I> where I: PathIterator {
/// `output`. inner: SegmentIter<I>,
pub fn apply<I>(&self, output: &mut PathBuffer, stream: I) subpath: Vec<Segment>,
where I: Iterator<Item = PathCommand> { stack: Vec<PathEvent>,
let mut input = PathBuffer::new(); state: StrokeToFillState,
input.add_stream(stream); style: StrokeStyle,
first_point_in_subpath: bool,
}
for subpath_index in 0..(input.subpaths.len() as u32) { impl<I> StrokeToFillIter<I> where I: PathIterator {
let closed = input.subpaths[subpath_index as usize].closed; #[inline]
pub fn new(inner: I, style: StrokeStyle) -> StrokeToFillIter<I> {
let mut first_endpoint_index = output.endpoints.len() as u32; StrokeToFillIter {
inner: SegmentIter::new(inner),
// Compute the first offset curve. subpath: vec![],
// stack: vec![],
// TODO(pcwalton): Support line caps. state: StrokeToFillState::Forward,
self.offset_subpath(output, &input, subpath_index); style: style,
first_point_in_subpath: true,
// 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<I> Iterator for StrokeToFillIter<I> 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<PathEvent> {
// 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,
}

View File

@ -1,33 +0,0 @@
// pathfinder/path-utils/src/svg.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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<W, S>(output: &mut W, stream: S) -> io::Result<()>
where W: Write, S: Iterator<Item = PathCommand> {
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(())
}

View File

@ -0,0 +1,60 @@
// pathfinder/path-utils/src/transform.rs
//
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Applies a transform to paths.
use euclid::Transform2D;
use lyon_path::PathEvent;
pub struct Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
inner: I,
transform: Transform2D<f32>,
}
impl<I> Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
#[inline]
pub fn new(inner: I, transform: &Transform2D<f32>) -> Transform2DPathIter<I> {
Transform2DPathIter {
inner: inner,
transform: *transform,
}
}
}
impl<I> Iterator for Transform2DPathIter<I> where I: Iterator<Item = PathEvent> {
type Item = PathEvent;
fn next(&mut self) -> Option<PathEvent> {
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(&center),
self.transform.transform_vector(&radius),
start,
end))
}
event => event,
}
}
}

View File

@ -30,6 +30,5 @@ void main() {
float side = sign(vTexCoord.x * vTexCoord.x - vTexCoord.y); float side = sign(vTexCoord.x * vTexCoord.x - vTexCoord.y);
float winding = gl_FrontFacing ? -1.0 : 1.0; float winding = gl_FrontFacing ? -1.0 : 1.0;
float alpha = float(side == winding); float alpha = float(side == winding);
//float alpha = mod(gl_FragCoord.x, 2.0) < 1.0 ? 1.0 : 0.0;
gl_FragColor = alpha * vColor; gl_FragColor = alpha * vColor;
} }

View File

@ -7,6 +7,8 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
app_units = "0.5" app_units = "0.5"
clap = "2.27" clap = "2.27"
freetype-sys = "0.6" freetype-sys = "0.6"
lyon_geom = "0.9"
lyon_path = "0.9"
[dependencies.pathfinder_font_renderer] [dependencies.pathfinder_font_renderer]
path = "../../font-renderer" path = "../../font-renderer"

View File

@ -26,6 +26,8 @@
extern crate app_units; extern crate app_units;
extern crate clap; extern crate clap;
extern crate freetype_sys; extern crate freetype_sys;
extern crate lyon_geom;
extern crate lyon_path;
extern crate pathfinder_font_renderer; extern crate pathfinder_font_renderer;
extern crate pathfinder_partitioner; extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils; extern crate pathfinder_path_utils;
@ -33,11 +35,12 @@ extern crate pathfinder_path_utils;
use app_units::Au; use app_units::Au;
use clap::{App, Arg}; use clap::{App, Arg};
use freetype_sys::{FT_Init_FreeType, FT_New_Face}; 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_font_renderer::{FontContext, FontKey, FontInstance, GlyphKey, SubpixelOffset};
use pathfinder_partitioner::FillRule;
use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::mesh_library::MeshLibrary;
use pathfinder_partitioner::partitioner::Partitioner; use pathfinder_partitioner::partitioner::Partitioner;
use pathfinder_path_utils::monotonic::MonotonicPathCommandStream;
use pathfinder_path_utils::{PathBuffer, PathBufferStream};
use std::ffi::CString; use std::ffi::CString;
use std::fs::File; use std::fs::File;
use std::io::Read; 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), size: Au::from_f64_px(FONT_SIZE),
}; };
let mut path_buffer = PathBuffer::new(); let mut paths: Vec<(u16, Vec<PathEvent>)> = vec![];
let mut partitioner = Partitioner::new(MeshLibrary::new()); 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 glyph_key = GlyphKey::new(glyph_index, SubpixelOffset(0));
let subpath_start = path_buffer.subpaths.len() as u32; let path = match font_context.glyph_outline(&font_instance, &glyph_key) {
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) { Ok(path) => path,
path_buffer.add_stream(MonotonicPathCommandStream::new(glyph_outline.into_iter())) Err(_) => continue,
} };
let subpath_end = path_buffer.subpaths.len() as u32;
let path_index = (glyph_index + 1) as u16; 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, path.iter());
partitioner.library_mut().push_segments(path_index, stream); partitioner.library_mut().push_normals(path_index, path.iter());
let stream = PathBufferStream::subpath_range(&path_buffer, subpath_start..subpath_end);
partitioner.library_mut().push_normals(stream);
subpath_start..subpath_end paths.push((path_index, path.iter().collect()));
}).collect(); }
partitioner.init_with_path_buffer(&path_buffer); for (glyph_index, path) in paths {
path.into_iter().for_each(|event| partitioner.builder_mut().path_event(event));
for (glyph_index, subpath) in subpath_ranges.iter().cloned().enumerate() { partitioner.partition(glyph_index, FillRule::Winding);
partitioner.partition((glyph_index + 1) as u16, subpath.start, subpath.end); partitioner.builder_mut().build_and_reset();
} }
partitioner.library_mut().optimize(); partitioner.library_mut().optimize();