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:
parent
2ec5dbfa42
commit
5bd68dec65
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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 => builder.push(PathEvent::Close),
|
||||||
}
|
}
|
||||||
CGPathElementType::CloseSubpath => CubicPathCommand::ClosePath,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>>,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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,18 +419,12 @@ 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 {
|
|
||||||
match self.classify_endpoint(endpoint_index) {
|
|
||||||
EndpointClass::Min => {
|
|
||||||
let new_point = self.create_point_from_endpoint(endpoint_index);
|
let new_point = self.create_point_from_endpoint(endpoint_index);
|
||||||
self.heap.push(new_point)
|
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],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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]))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
@ -10,399 +10,15 @@
|
||||||
|
|
||||||
//! 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -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(¢er),
|
||||||
|
self.transform.transform_vector(&radius),
|
||||||
|
start,
|
||||||
|
end))
|
||||||
|
}
|
||||||
|
event => event,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue