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('mouseup', event => this.onMouseUp(event), false);
|
||||
this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
|
||||
unwrapNull(this.canvas.classList).add('pf-draggable');
|
||||
if (this.canvas.classList != null)
|
||||
this.canvas.classList.add('pf-draggable');
|
||||
} else {
|
||||
unwrapNull(this.canvas.classList).remove('pf-draggable');
|
||||
if (this.canvas.classList != null)
|
||||
this.canvas.classList.remove('pf-draggable');
|
||||
}
|
||||
|
||||
this.onPan = null;
|
||||
|
|
|
@ -12,11 +12,13 @@ app_units = "0.5"
|
|||
base64 = "0.6"
|
||||
bincode = "0.8"
|
||||
env_logger = "0.4"
|
||||
euclid = "0.15"
|
||||
euclid = "0.16"
|
||||
image = "0.17"
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
lru-cache = "0.1"
|
||||
lyon_geom = "0.9"
|
||||
lyon_path = "0.9"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -18,6 +18,8 @@ extern crate euclid;
|
|||
extern crate fontsan;
|
||||
extern crate image;
|
||||
extern crate lru_cache;
|
||||
extern crate lyon_geom;
|
||||
extern crate lyon_path;
|
||||
extern crate pathfinder_font_renderer;
|
||||
extern crate pathfinder_partitioner;
|
||||
extern crate pathfinder_path_utils;
|
||||
|
@ -38,23 +40,23 @@ use app_units::Au;
|
|||
use euclid::{Point2D, Transform2D};
|
||||
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageRgba8};
|
||||
use lru_cache::LruCache;
|
||||
use lyon_path::PathEvent;
|
||||
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
||||
use lyon_path::iterator::PathIter;
|
||||
use pathfinder_font_renderer::{FontContext, FontInstance, FontKey, GlyphImage};
|
||||
use pathfinder_font_renderer::{GlyphKey, SubpixelOffset};
|
||||
use pathfinder_partitioner::FillRule;
|
||||
use pathfinder_partitioner::mesh_library::MeshLibrary;
|
||||
use pathfinder_partitioner::partitioner::Partitioner;
|
||||
use pathfinder_path_utils::cubic::CubicCurve;
|
||||
use pathfinder_path_utils::monotonic::MonotonicPathCommandStream;
|
||||
use pathfinder_path_utils::stroke::Stroke;
|
||||
use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathCommand, Transform2DPathStream};
|
||||
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
|
||||
use pathfinder_path_utils::transform::Transform2DPathIter;
|
||||
use rocket::http::{ContentType, Header, Status};
|
||||
use rocket::request::Request;
|
||||
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
||||
use rocket_contrib::json::Json;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{self, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::u32;
|
||||
|
@ -71,8 +73,6 @@ use rsvg::{Handle, HandleExt};
|
|||
|
||||
const SUGGESTED_JSON_SIZE_LIMIT: u64 = 32 * 1024 * 1024;
|
||||
|
||||
const CUBIC_ERROR_TOLERANCE: f32 = 0.1;
|
||||
|
||||
const MESH_LIBRARY_CACHE_SIZE: usize = 16;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -246,7 +246,7 @@ impl PartitionSvgFillRule {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct PathDescriptor {
|
||||
subpath_indices: Range<u32>,
|
||||
path_index: usize,
|
||||
fill_rule: FillRule,
|
||||
}
|
||||
|
||||
|
@ -263,15 +263,17 @@ struct PathPartitioningResult {
|
|||
}
|
||||
|
||||
impl PathPartitioningResult {
|
||||
fn compute(partitioner: &mut Partitioner, path_descriptors: &[PathDescriptor])
|
||||
fn compute(partitioner: &mut Partitioner,
|
||||
path_descriptors: &[PathDescriptor],
|
||||
paths: &[Vec<PathEvent>])
|
||||
-> PathPartitioningResult {
|
||||
let timestamp_before = Instant::now();
|
||||
|
||||
for (path_index, path_descriptor) in path_descriptors.iter().enumerate() {
|
||||
partitioner.set_fill_rule(path_descriptor.fill_rule);
|
||||
partitioner.partition((path_index + 1) as u16,
|
||||
path_descriptor.subpath_indices.start,
|
||||
path_descriptor.subpath_indices.end);
|
||||
for (path, path_descriptor) in paths.iter().zip(path_descriptors.iter()) {
|
||||
path.iter().for_each(|event| partitioner.builder_mut().path_event(*event));
|
||||
partitioner.partition((path_descriptor.path_index + 1) as u16,
|
||||
path_descriptor.fill_rule);
|
||||
partitioner.builder_mut().build_and_reset();
|
||||
}
|
||||
|
||||
partitioner.library_mut().optimize();
|
||||
|
@ -444,42 +446,40 @@ fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionRespon
|
|||
};
|
||||
|
||||
// Read glyph info.
|
||||
let mut path_buffer = PathBuffer::new();
|
||||
let path_descriptors: Vec<_> = request.glyphs.iter().map(|glyph| {
|
||||
let mut paths: Vec<Vec<PathEvent>> = vec![];
|
||||
let mut path_descriptors = vec![];
|
||||
|
||||
for (glyph_index, glyph) in request.glyphs.iter().enumerate() {
|
||||
let glyph_key = GlyphKey::new(glyph.id, SubpixelOffset(0));
|
||||
|
||||
let first_subpath_index = path_buffer.subpaths.len();
|
||||
|
||||
// This might fail; if so, just leave it blank.
|
||||
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) {
|
||||
let stream = Transform2DPathStream::new(glyph_outline.into_iter(), &glyph.transform);
|
||||
let stream = MonotonicPathCommandStream::new(stream);
|
||||
path_buffer.add_stream(stream)
|
||||
match font_context.glyph_outline(&font_instance, &glyph_key) {
|
||||
Ok(glyph_outline) => {
|
||||
paths.push(Transform2DPathIter::new(glyph_outline.iter(),
|
||||
&glyph.transform).collect())
|
||||
}
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let last_subpath_index = path_buffer.subpaths.len();
|
||||
|
||||
PathDescriptor {
|
||||
subpath_indices: (first_subpath_index as u32)..(last_subpath_index as u32),
|
||||
path_descriptors.push(PathDescriptor {
|
||||
path_index: glyph_index,
|
||||
fill_rule: FillRule::Winding,
|
||||
})
|
||||
}
|
||||
}).collect();
|
||||
|
||||
// Partition the decoded glyph outlines.
|
||||
let mut library = MeshLibrary::new();
|
||||
for (path_index, path_descriptor) in path_descriptors.iter().enumerate() {
|
||||
let stream = PathBufferStream::subpath_range(&path_buffer,
|
||||
path_descriptor.subpath_indices.clone());
|
||||
library.push_segments((path_index + 1) as u16, stream);
|
||||
let stream = PathBufferStream::subpath_range(&path_buffer,
|
||||
path_descriptor.subpath_indices.clone());
|
||||
library.push_normals(stream);
|
||||
for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() {
|
||||
library.push_segments((path_descriptor.path_index + 1) as u16,
|
||||
PathIter::new(paths[stored_path_index].iter().cloned()));
|
||||
library.push_normals((path_descriptor.path_index + 1) as u16,
|
||||
PathIter::new(paths[stored_path_index].iter().cloned()));
|
||||
}
|
||||
|
||||
let mut partitioner = Partitioner::new(library);
|
||||
partitioner.init_with_path_buffer(&path_buffer);
|
||||
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
|
||||
&path_descriptors);
|
||||
&path_descriptors,
|
||||
&paths);
|
||||
|
||||
// Build the response.
|
||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
||||
|
@ -504,79 +504,64 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
|||
//
|
||||
// The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z`
|
||||
// commands.
|
||||
let mut path_buffer = PathBuffer::new();
|
||||
let mut paths = vec![];
|
||||
let mut last_point = Point2D::zero();
|
||||
let mut library = MeshLibrary::new();
|
||||
let mut path_descriptors = vec![];
|
||||
let mut partitioner = Partitioner::new(MeshLibrary::new());
|
||||
let mut path_index = 0;
|
||||
|
||||
for (path_index, path) in request.paths.iter().enumerate() {
|
||||
for path in &request.paths {
|
||||
let mut stream = vec![];
|
||||
|
||||
let first_subpath_index = path_buffer.subpaths.len() as u32;
|
||||
|
||||
for segment in &path.segments {
|
||||
match segment.kind {
|
||||
'M' => {
|
||||
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32);
|
||||
stream.push(PathCommand::MoveTo(last_point))
|
||||
stream.push(PathEvent::MoveTo(Point2D::new(segment.values[0] as f32,
|
||||
segment.values[1] as f32)))
|
||||
}
|
||||
'L' => {
|
||||
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32);
|
||||
stream.push(PathCommand::LineTo(last_point))
|
||||
stream.push(PathEvent::LineTo(Point2D::new(segment.values[0] as f32,
|
||||
segment.values[1] as f32)))
|
||||
}
|
||||
'C' => {
|
||||
let control_point_0 = Point2D::new(segment.values[0] as f32,
|
||||
segment.values[1] as f32);
|
||||
let control_point_1 = Point2D::new(segment.values[2] as f32,
|
||||
segment.values[3] as f32);
|
||||
let endpoint_1 = Point2D::new(segment.values[4] as f32,
|
||||
segment.values[5] as f32);
|
||||
let cubic = CubicCurve::new(&last_point,
|
||||
&control_point_0,
|
||||
&control_point_1,
|
||||
&endpoint_1);
|
||||
last_point = endpoint_1;
|
||||
stream.extend(cubic.approx_curve(CUBIC_ERROR_TOLERANCE)
|
||||
.map(|curve| curve.to_path_command()));
|
||||
stream.push(PathEvent::CubicTo(Point2D::new(segment.values[0] as f32,
|
||||
segment.values[1] as f32),
|
||||
Point2D::new(segment.values[2] as f32,
|
||||
segment.values[3] as f32),
|
||||
Point2D::new(segment.values[4] as f32,
|
||||
segment.values[5] as f32)))
|
||||
}
|
||||
'Z' => stream.push(PathCommand::ClosePath),
|
||||
'Z' => stream.push(PathEvent::Close),
|
||||
_ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType),
|
||||
}
|
||||
}
|
||||
|
||||
let fill_rule = match path.kind {
|
||||
PartitionSvgPathKind::Fill(fill_rule) => {
|
||||
let stream = MonotonicPathCommandStream::new(stream.into_iter());
|
||||
library.push_segments((path_index + 1) as u16, stream.clone());
|
||||
path_buffer.add_stream(stream);
|
||||
|
||||
fill_rule.to_fill_rule()
|
||||
}
|
||||
PartitionSvgPathKind::Stroke(stroke_width) => {
|
||||
let mut temp_path_buffer = PathBuffer::new();
|
||||
Stroke::new(stroke_width).apply(&mut temp_path_buffer, stream.into_iter());
|
||||
|
||||
let stream = PathBufferStream::new(&temp_path_buffer);
|
||||
let stream = MonotonicPathCommandStream::new(stream);
|
||||
library.push_segments((path_index + 1) as u16, stream.clone());
|
||||
path_buffer.add_stream(stream);
|
||||
|
||||
FillRule::Winding
|
||||
}
|
||||
PartitionSvgPathKind::Fill(fill_rule) => fill_rule.to_fill_rule(),
|
||||
PartitionSvgPathKind::Stroke(_) => FillRule::Winding,
|
||||
};
|
||||
|
||||
let last_subpath_index = path_buffer.subpaths.len() as u32;
|
||||
|
||||
paths.push(PathDescriptor {
|
||||
subpath_indices: first_subpath_index..last_subpath_index,
|
||||
path_descriptors.push(PathDescriptor {
|
||||
path_index: path_index,
|
||||
fill_rule: fill_rule,
|
||||
})
|
||||
});
|
||||
|
||||
match path.kind {
|
||||
PartitionSvgPathKind::Fill(_) => paths.push(stream),
|
||||
PartitionSvgPathKind::Stroke(stroke_width) => {
|
||||
let iterator = PathIter::new(stream.into_iter());
|
||||
let stroke_style = StrokeStyle::new(stroke_width);
|
||||
let path: Vec<_> = StrokeToFillIter::new(iterator, stroke_style).collect();
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
path_index += 1;
|
||||
}
|
||||
|
||||
// Partition the paths.
|
||||
let mut partitioner = Partitioner::new(library);
|
||||
partitioner.init_with_path_buffer(&path_buffer);
|
||||
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths);
|
||||
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
|
||||
&path_descriptors,
|
||||
&paths);
|
||||
|
||||
// Return the response.
|
||||
let elapsed_ms = path_partitioning_result.elapsed_ms();
|
||||
|
@ -710,69 +695,69 @@ fn static_doc_api_index() -> Redirect {
|
|||
}
|
||||
#[get("/doc/api/<file..>")]
|
||||
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..>")]
|
||||
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>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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>")]
|
||||
fn static_otf_demo(font_name: String) -> Option<NamedFile> {
|
||||
BUILTIN_FONTS.iter()
|
||||
.filter(|& &(name, _)| name == font_name)
|
||||
.next()
|
||||
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok())
|
||||
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
|
||||
}
|
||||
#[get("/svg/demo/<svg_name>")]
|
||||
fn static_svg_demo(svg_name: String) -> Option<NamedFile> {
|
||||
BUILTIN_SVGS.iter()
|
||||
.filter(|& &(name, _)| name == svg_name)
|
||||
.next()
|
||||
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok())
|
||||
.and_then(|&(_, path)| NamedFile::open(path::Path::new(path)).ok())
|
||||
}
|
||||
#[get("/data/<file..>")]
|
||||
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..>")]
|
||||
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..>")]
|
||||
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 {
|
||||
|
|
|
@ -9,9 +9,11 @@ freetype = ["freetype-sys"]
|
|||
|
||||
[dependencies]
|
||||
app_units = "0.5"
|
||||
euclid = "0.15"
|
||||
euclid = "0.16"
|
||||
libc = "0.2"
|
||||
log = "0.3"
|
||||
lyon_geom = "0.9"
|
||||
lyon_path = "0.9"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
|
@ -19,9 +21,6 @@ serde_derive = "1.0"
|
|||
version = "0.6"
|
||||
optional = true
|
||||
|
||||
[dependencies.pathfinder_path_utils]
|
||||
path = "../path-utils"
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
freetype-sys = "0.6"
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@ use core_graphics_sys::path::CGPathElementType;
|
|||
use core_text::font::CTFont;
|
||||
use core_text;
|
||||
use euclid::{Point2D, Rect, Size2D, Vector2D};
|
||||
use pathfinder_path_utils::cubic::{CubicPathCommand, CubicPathCommandApproxStream};
|
||||
use pathfinder_path_utils::PathCommand;
|
||||
use lyon_path::PathEvent;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::iter::Cloned;
|
||||
use std::slice::Iter;
|
||||
use std::sync::Arc;
|
||||
use {FontInstance, FontKey, GlyphDimensions, GlyphImage, GlyphKey};
|
||||
|
||||
|
@ -36,8 +37,6 @@ const CG_ZERO_RECT: CGRect = CGRect {
|
|||
},
|
||||
};
|
||||
|
||||
const CURVE_APPROX_ERROR_BOUND: f32 = 0.1;
|
||||
|
||||
// A conservative overestimate of the amount of font dilation that Core Graphics performs, as a
|
||||
// fraction of ppem.
|
||||
//
|
||||
|
@ -45,9 +44,6 @@ const CURVE_APPROX_ERROR_BOUND: f32 = 0.1;
|
|||
// direction.
|
||||
const FONT_DILATION_AMOUNT: f32 = 0.02;
|
||||
|
||||
/// A list of path commands.
|
||||
pub type GlyphOutline = Vec<PathCommand>;
|
||||
|
||||
/// An object that loads and renders fonts using macOS Core Graphics/Quartz.
|
||||
pub struct FontContext {
|
||||
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,
|
||||
&CG_AFFINE_TRANSFORM_IDENTITY));
|
||||
|
||||
let mut commands = vec![];
|
||||
let mut builder = vec![];
|
||||
path.apply(&|element| {
|
||||
let points = element.points();
|
||||
commands.push(match element.element_type {
|
||||
match element.element_type {
|
||||
CGPathElementType::MoveToPoint => {
|
||||
CubicPathCommand::MoveTo(convert_point(&points[0]))
|
||||
builder.push(PathEvent::MoveTo(convert_point(&points[0])))
|
||||
}
|
||||
CGPathElementType::AddLineToPoint => {
|
||||
CubicPathCommand::LineTo(convert_point(&points[0]))
|
||||
builder.push(PathEvent::LineTo(convert_point(&points[0])))
|
||||
}
|
||||
CGPathElementType::AddQuadCurveToPoint => {
|
||||
CubicPathCommand::QuadCurveTo(convert_point(&points[0]),
|
||||
convert_point(&points[1]))
|
||||
builder.push(PathEvent::QuadraticTo(convert_point(&points[0]),
|
||||
convert_point(&points[1])))
|
||||
}
|
||||
CGPathElementType::AddCurveToPoint => {
|
||||
CubicPathCommand::CubicCurveTo(convert_point(&points[0]),
|
||||
builder.push(PathEvent::CubicTo(convert_point(&points[0]),
|
||||
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(),
|
||||
CURVE_APPROX_ERROR_BOUND);
|
||||
|
||||
let approx_commands: Vec<_> = approx_stream.collect();
|
||||
return Ok(approx_commands);
|
||||
return Ok(GlyphOutline {
|
||||
events: builder,
|
||||
});
|
||||
|
||||
fn convert_point(core_graphics_point: &CGPoint) -> Point2D<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()))
|
||||
}
|
||||
}
|
||||
|
||||
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_Outline_Translate, FT_PIXEL_MODE_LCD, FT_RENDER_MODE_LCD, FT_Render_Glyph};
|
||||
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
|
||||
use pathfinder_path_utils::PathCommand;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
|
||||
use self::fixed::{FromFtF26Dot6, ToFtF26Dot6};
|
||||
use self::outline::OutlineStream;
|
||||
use self::outline::Outline;
|
||||
use {FontKey, FontInstance, GlyphDimensions, GlyphImage, GlyphKey};
|
||||
|
||||
mod fixed;
|
||||
mod outline;
|
||||
|
||||
pub type GlyphOutline<'a> = Outline<'a>;
|
||||
|
||||
// Default to no hinting.
|
||||
//
|
||||
// TODO(pcwalton): Make this configurable.
|
||||
|
@ -125,10 +125,7 @@ impl FontContext {
|
|||
-> Result<GlyphOutline<'a>, ()> {
|
||||
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
|
||||
unsafe {
|
||||
GlyphOutline {
|
||||
stream: OutlineStream::new(&(*glyph_slot).outline),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
GlyphOutline::new(&(*glyph_slot).outline)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -295,19 +292,6 @@ impl FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// A list of path commands.
|
||||
pub struct GlyphOutline<'a> {
|
||||
stream: OutlineStream<'static>,
|
||||
phantom: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GlyphOutline<'a> {
|
||||
type Item = PathCommand;
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
self.stream.next()
|
||||
}
|
||||
}
|
||||
|
||||
struct Face {
|
||||
face: FT_Face,
|
||||
bytes: Arc<Vec<u8>>,
|
||||
|
|
|
@ -10,15 +10,36 @@
|
|||
|
||||
use euclid::Point2D;
|
||||
use freetype_sys::{FT_Outline, FT_Vector};
|
||||
use pathfinder_path_utils::PathCommand;
|
||||
use lyon_path::iterator::PathIterator;
|
||||
use lyon_path::{PathEvent, PathState};
|
||||
|
||||
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
|
||||
|
||||
pub struct Outline<'a> {
|
||||
outline: &'a FT_Outline,
|
||||
}
|
||||
|
||||
impl<'a> Outline<'a> {
|
||||
#[inline]
|
||||
pub unsafe fn new(outline: &FT_Outline) -> Outline {
|
||||
Outline {
|
||||
outline: outline,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter(&self) -> OutlineStream {
|
||||
unsafe {
|
||||
OutlineStream::new(self.outline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutlineStream<'a> {
|
||||
outline: &'a FT_Outline,
|
||||
point_index: u16,
|
||||
contour_index: u16,
|
||||
first_position_of_subpath: Point2D<f32>,
|
||||
state: PathState,
|
||||
first_point_index_of_contour: bool,
|
||||
}
|
||||
|
||||
|
@ -29,7 +50,7 @@ impl<'a> OutlineStream<'a> {
|
|||
outline: outline,
|
||||
point_index: 0,
|
||||
contour_index: 0,
|
||||
first_position_of_subpath: Point2D::zero(),
|
||||
state: PathState::new(),
|
||||
first_point_index_of_contour: true,
|
||||
}
|
||||
}
|
||||
|
@ -46,9 +67,9 @@ impl<'a> OutlineStream<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for OutlineStream<'a> {
|
||||
type Item = PathCommand;
|
||||
type Item = PathEvent;
|
||||
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
fn next(&mut self) -> Option<PathEvent> {
|
||||
unsafe {
|
||||
let mut control_point_position: Option<Point2D<f32>> = None;
|
||||
loop {
|
||||
|
@ -60,13 +81,16 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
*self.outline.contours.offset(self.contour_index as isize) as u16;
|
||||
if self.point_index == last_point_index_in_current_contour + 1 {
|
||||
if let Some(control_point_position) = control_point_position {
|
||||
return Some(PathCommand::CurveTo(control_point_position,
|
||||
self.first_position_of_subpath))
|
||||
let event = PathEvent::QuadraticTo(control_point_position,
|
||||
self.state.first);
|
||||
self.state.path_event(event);
|
||||
return Some(event)
|
||||
}
|
||||
|
||||
self.contour_index += 1;
|
||||
self.first_point_index_of_contour = true;
|
||||
return Some(PathCommand::ClosePath)
|
||||
self.state.close();
|
||||
return Some(PathEvent::Close)
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): Approximate cubic curves with quadratics.
|
||||
|
@ -75,20 +99,24 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
|
||||
if self.first_point_index_of_contour {
|
||||
self.first_point_index_of_contour = false;
|
||||
self.first_position_of_subpath = position;
|
||||
self.point_index += 1;
|
||||
return Some(PathCommand::MoveTo(position));
|
||||
self.state.move_to(position);
|
||||
return Some(PathEvent::MoveTo(position));
|
||||
}
|
||||
|
||||
match (control_point_position, point_on_curve) {
|
||||
(Some(control_point_position), false) => {
|
||||
let on_curve_position = control_point_position.lerp(position, 0.5);
|
||||
return Some(PathCommand::CurveTo(control_point_position,
|
||||
on_curve_position))
|
||||
let event = PathEvent::QuadraticTo(control_point_position,
|
||||
on_curve_position);
|
||||
self.state.path_event(event);
|
||||
return Some(event)
|
||||
}
|
||||
(Some(control_point_position), true) => {
|
||||
self.point_index += 1;
|
||||
return Some(PathCommand::CurveTo(control_point_position, position))
|
||||
let event = PathEvent::QuadraticTo(control_point_position, position);
|
||||
self.state.path_event(event);
|
||||
return Some(event)
|
||||
}
|
||||
(None, false) => {
|
||||
self.point_index += 1;
|
||||
|
@ -96,7 +124,8 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
}
|
||||
(None, true) => {
|
||||
self.point_index += 1;
|
||||
return Some(PathCommand::LineTo(position))
|
||||
self.state.line_to(position);
|
||||
return Some(PathEvent::LineTo(position))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +133,13 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> PathIterator for OutlineStream<'a> {
|
||||
#[inline]
|
||||
fn get_state(&self) -> &PathState {
|
||||
&self.state
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ft_vector_to_f32(ft_vector: FT_Vector) -> Point2D<f32> {
|
||||
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 euclid;
|
||||
extern crate libc;
|
||||
extern crate pathfinder_path_utils;
|
||||
extern crate lyon_geom;
|
||||
extern crate lyon_path;
|
||||
extern crate serde;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
|
|
|
@ -7,13 +7,16 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
|||
name = "pathfinder_partitioner"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.4"
|
||||
bincode = "0.8"
|
||||
bit-vec = "0.4"
|
||||
byteorder = "1.2"
|
||||
env_logger = "0.4"
|
||||
euclid = "0.15"
|
||||
euclid = "0.16"
|
||||
half = "1.0"
|
||||
log = "0.3"
|
||||
lyon_geom = "0.9"
|
||||
lyon_path = "0.9"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
|
|
|
@ -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
|
||||
//! also be generated dynamically and rendered on the fly.
|
||||
|
||||
extern crate arrayvec;
|
||||
extern crate bincode;
|
||||
extern crate bit_vec;
|
||||
extern crate byteorder;
|
||||
extern crate env_logger;
|
||||
extern crate euclid;
|
||||
extern crate lyon_geom;
|
||||
extern crate lyon_path;
|
||||
extern crate pathfinder_path_utils;
|
||||
extern crate serde;
|
||||
|
||||
|
@ -35,15 +38,12 @@ extern crate log;
|
|||
extern crate serde_derive;
|
||||
|
||||
use euclid::Point2D;
|
||||
use pathfinder_path_utils::{Endpoint, Subpath};
|
||||
use std::u32;
|
||||
|
||||
pub mod capi;
|
||||
pub mod builder;
|
||||
pub mod mesh_library;
|
||||
pub mod partitioner;
|
||||
|
||||
mod normal;
|
||||
|
||||
/// The fill rule.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// pathfinder/partitioner/src/mesh_library.rs
|
||||
//
|
||||
// Copyright © 2017 The Pathfinder Project Developers.
|
||||
// Copyright © 2018 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
|
@ -10,14 +10,17 @@
|
|||
|
||||
use bincode::{self, Infinite};
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use euclid::Point2D;
|
||||
use pathfinder_path_utils::{PathCommand, PathSegment, PathSegmentStream};
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use lyon_path::PathEvent;
|
||||
use lyon_path::iterator::PathIterator;
|
||||
use pathfinder_path_utils::normals::PathNormals;
|
||||
use pathfinder_path_utils::segments::{self, SegmentIter};
|
||||
use serde::Serialize;
|
||||
use std::f32;
|
||||
use std::io::{self, ErrorKind, Seek, SeekFrom, Write};
|
||||
use std::ops::Range;
|
||||
use std::u32;
|
||||
|
||||
use normal;
|
||||
use {BQuad, BQuadVertexPositions, BVertexLoopBlinnData};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -174,25 +177,29 @@ impl MeshLibrary {
|
|||
}
|
||||
|
||||
pub fn push_segments<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_curve_index = self.segments.curves.len() as u32;
|
||||
|
||||
let stream = PathSegmentStream::new(stream);
|
||||
for (segment, _) in stream {
|
||||
let segment_iter = SegmentIter::new(stream);
|
||||
for segment in segment_iter {
|
||||
match segment {
|
||||
PathSegment::Line(endpoint_0, endpoint_1) => {
|
||||
segments::Segment::Line(line_segment) => {
|
||||
self.segments.lines.push(LineSegment {
|
||||
endpoint_0: endpoint_0,
|
||||
endpoint_1: endpoint_1,
|
||||
});
|
||||
endpoint_0: line_segment.from,
|
||||
endpoint_1: line_segment.to,
|
||||
})
|
||||
}
|
||||
PathSegment::Curve(endpoint_0, control_point, endpoint_1) => {
|
||||
segments::Segment::Quadratic(curve_segment) => {
|
||||
self.segments.curves.push(CurveSegment {
|
||||
endpoint_0: endpoint_0,
|
||||
control_point: control_point,
|
||||
endpoint_1: endpoint_1,
|
||||
});
|
||||
endpoint_0: curve_segment.from,
|
||||
control_point: curve_segment.ctrl,
|
||||
endpoint_1: curve_segment.to,
|
||||
})
|
||||
}
|
||||
segments::Segment::EndSubpath(..) => {}
|
||||
segments::Segment::Cubic(..) => {
|
||||
panic!("push_segments(): Convert cubics to quadratics first!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,8 +213,56 @@ impl MeshLibrary {
|
|||
}
|
||||
|
||||
/// Computes vertex normals necessary for emboldening and/or stem darkening.
|
||||
pub fn push_normals<I>(&mut self, stream: I) where I: Iterator<Item = PathCommand> {
|
||||
normal::push_normals(self, stream)
|
||||
pub fn push_normals<I>(&mut self, _path_id: u16, stream: I) where I: PathIterator {
|
||||
let path_events: Vec<_> = stream.collect();
|
||||
|
||||
let mut normals = PathNormals::new();
|
||||
normals.add_path(path_events.iter().cloned());
|
||||
let normals = normals.normals();
|
||||
|
||||
let mut current_point_normal_index = 0;
|
||||
let mut next_normal_index = 0;
|
||||
let mut first_normal_index_of_subpath = 0;
|
||||
|
||||
for event in path_events {
|
||||
match event {
|
||||
PathEvent::MoveTo(..) => {
|
||||
first_normal_index_of_subpath = next_normal_index;
|
||||
current_point_normal_index = next_normal_index;
|
||||
next_normal_index += 1;
|
||||
}
|
||||
PathEvent::LineTo(..) => {
|
||||
self.segment_normals.line_normals.push(LineSegmentNormals {
|
||||
endpoint_0: normal_angle(&normals[current_point_normal_index]),
|
||||
endpoint_1: normal_angle(&normals[next_normal_index]),
|
||||
});
|
||||
current_point_normal_index = next_normal_index;
|
||||
next_normal_index += 1;
|
||||
}
|
||||
PathEvent::QuadraticTo(..) => {
|
||||
self.segment_normals.curve_normals.push(CurveSegmentNormals {
|
||||
endpoint_0: normal_angle(&normals[current_point_normal_index]),
|
||||
control_point: normal_angle(&normals[next_normal_index + 0]),
|
||||
endpoint_1: normal_angle(&normals[next_normal_index + 1]),
|
||||
});
|
||||
current_point_normal_index = next_normal_index + 1;
|
||||
next_normal_index += 2;
|
||||
}
|
||||
PathEvent::Close => {
|
||||
self.segment_normals.line_normals.push(LineSegmentNormals {
|
||||
endpoint_0: normal_angle(&normals[current_point_normal_index]),
|
||||
endpoint_1: normal_angle(&normals[first_normal_index_of_subpath]),
|
||||
});
|
||||
}
|
||||
PathEvent::CubicTo(..) | PathEvent::Arc(..) => {
|
||||
panic!("push_normals(): Convert cubics and arcs to quadratics first!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_angle(vector: &Vector2D<f32>) -> f32 {
|
||||
Vector2D::new(vector.x, -vector.y).angle_from_x_axis().get()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
//
|
||||
// Copyright © 2017 The Pathfinder Project Developers.
|
||||
// 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
|
||||
|
@ -12,9 +12,7 @@ use bit_vec::BitVec;
|
|||
use euclid::approxeq::ApproxEq;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use log::LogLevel;
|
||||
use pathfinder_path_utils::PathBuffer;
|
||||
use pathfinder_path_utils::curve::Curve;
|
||||
use pathfinder_path_utils::line::Line;
|
||||
use lyon_geom::{LineSegment, QuadraticBezierSegment};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::cmp::Ordering;
|
||||
use std::f32;
|
||||
|
@ -22,15 +20,17 @@ use std::iter;
|
|||
use std::ops::{Add, AddAssign};
|
||||
use std::u32;
|
||||
|
||||
use builder::Builder;
|
||||
use mesh_library::MeshLibrary;
|
||||
use {BQuad, BVertexLoopBlinnData, BVertexKind, Endpoint, FillRule, Subpath};
|
||||
use {BQuad, BVertexLoopBlinnData, BVertexKind, FillRule};
|
||||
|
||||
const MAX_B_QUAD_SUBDIVISIONS: u8 = 8;
|
||||
|
||||
pub struct Partitioner<'a> {
|
||||
endpoints: &'a [Endpoint],
|
||||
control_points: &'a [Point2D<f32>],
|
||||
subpaths: &'a [Subpath],
|
||||
const INTERSECTION_TOLERANCE: f32 = 0.001;
|
||||
|
||||
pub struct Partitioner {
|
||||
path: Builder,
|
||||
path_id: u16,
|
||||
|
||||
library: MeshLibrary,
|
||||
|
||||
|
@ -40,17 +40,14 @@ pub struct Partitioner<'a> {
|
|||
visited_points: BitVec,
|
||||
active_edges: Vec<ActiveEdge>,
|
||||
vertex_normals: Vec<VertexNormal>,
|
||||
path_id: u16,
|
||||
}
|
||||
|
||||
impl<'a> Partitioner<'a> {
|
||||
impl Partitioner {
|
||||
#[inline]
|
||||
pub fn new<'b>(library: MeshLibrary) -> Partitioner<'b> {
|
||||
pub fn new(library: MeshLibrary) -> Partitioner {
|
||||
Partitioner {
|
||||
endpoints: &[],
|
||||
control_points: &[],
|
||||
subpaths: &[],
|
||||
|
||||
path: Builder::new(),
|
||||
path_id: 0,
|
||||
fill_rule: FillRule::Winding,
|
||||
|
||||
library: library,
|
||||
|
@ -59,7 +56,6 @@ impl<'a> Partitioner<'a> {
|
|||
visited_points: BitVec::new(),
|
||||
active_edges: vec![],
|
||||
vertex_normals: vec![],
|
||||
path_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,39 +74,31 @@ impl<'a> Partitioner<'a> {
|
|||
self.library
|
||||
}
|
||||
|
||||
pub fn init_with_raw_data(&mut self,
|
||||
new_endpoints: &'a [Endpoint],
|
||||
new_control_points: &'a [Point2D<f32>],
|
||||
new_subpaths: &'a [Subpath]) {
|
||||
self.endpoints = new_endpoints;
|
||||
self.control_points = new_control_points;
|
||||
self.subpaths = new_subpaths;
|
||||
|
||||
// FIXME(pcwalton): Move this initialization to `partition` below. Right now, this bit
|
||||
// vector uses too much memory.
|
||||
self.visited_points = BitVec::from_elem(self.endpoints.len(), false);
|
||||
}
|
||||
|
||||
pub fn init_with_path_buffer(&mut self, path_buffer: &'a PathBuffer) {
|
||||
self.init_with_raw_data(&path_buffer.endpoints,
|
||||
&path_buffer.control_points,
|
||||
&path_buffer.subpaths)
|
||||
#[inline]
|
||||
pub fn builder(&self) -> &Builder {
|
||||
&self.path
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fill_rule(&mut self, new_fill_rule: FillRule) {
|
||||
self.fill_rule = new_fill_rule
|
||||
pub fn builder_mut(&mut self) -> &mut Builder {
|
||||
&mut self.path
|
||||
}
|
||||
|
||||
pub fn partition(&mut self, path_id: u16, first_subpath_index: u32, last_subpath_index: u32) {
|
||||
pub fn partition(&mut self, path_id: u16, fill_rule: FillRule) {
|
||||
self.path.end_subpath();
|
||||
|
||||
self.heap.clear();
|
||||
self.active_edges.clear();
|
||||
|
||||
self.path_id = path_id;
|
||||
self.fill_rule = fill_rule;
|
||||
|
||||
// FIXME(pcwalton): Right now, this bit vector uses too much memory.
|
||||
self.visited_points = BitVec::from_elem(self.path.endpoints.len(), false);
|
||||
|
||||
let start_lengths = self.library.snapshot_lengths();
|
||||
|
||||
self.path_id = path_id;
|
||||
|
||||
self.init_heap(first_subpath_index, last_subpath_index);
|
||||
self.init_heap();
|
||||
|
||||
while self.process_next_point() {}
|
||||
|
||||
|
@ -135,7 +123,7 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
if log_enabled!(LogLevel::Debug) {
|
||||
let position = self.endpoints[point.endpoint_index as usize].position;
|
||||
let position = self.path.endpoints[point.endpoint_index as usize].to;
|
||||
debug!("processing point {}: {:?}", point.endpoint_index, position);
|
||||
debug!("... active edges at {}:", position.x);
|
||||
for (active_edge_index, active_edge) in self.active_edges.iter().enumerate() {
|
||||
|
@ -171,8 +159,8 @@ impl<'a> Partitioner<'a> {
|
|||
|
||||
let next_active_edge_index = self.find_point_between_active_edges(endpoint_index);
|
||||
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x);
|
||||
let endpoint = self.path.endpoints[endpoint_index as usize];
|
||||
self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.to.x);
|
||||
|
||||
self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index);
|
||||
|
||||
|
@ -189,8 +177,8 @@ impl<'a> Partitioner<'a> {
|
|||
fn process_regular_endpoint(&mut self, endpoint_index: u32, active_edge_index: u32) {
|
||||
debug!("... REGULAR point: active edge {}", active_edge_index);
|
||||
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.position.x) ==
|
||||
let endpoint = self.path.endpoints[endpoint_index as usize];
|
||||
let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.to.x) ==
|
||||
BQuadEmissionResult::BQuadEmittedAbove;
|
||||
|
||||
let prev_endpoint_index = self.prev_endpoint_of(endpoint_index);
|
||||
|
@ -198,8 +186,9 @@ impl<'a> Partitioner<'a> {
|
|||
|
||||
{
|
||||
let active_edge = &mut self.active_edges[active_edge_index as usize];
|
||||
let endpoint_position = self.endpoints[active_edge.right_endpoint_index as usize]
|
||||
.position;
|
||||
let endpoint_position = self.path
|
||||
.endpoints[active_edge.right_endpoint_index as usize]
|
||||
.to;
|
||||
|
||||
// If we already made a B-vertex point for this endpoint, reuse it instead of making a
|
||||
// new one.
|
||||
|
@ -230,23 +219,22 @@ impl<'a> Partitioner<'a> {
|
|||
let new_point = self.create_point_from_endpoint(right_endpoint_index);
|
||||
*self.heap.peek_mut().unwrap() = new_point;
|
||||
|
||||
let control_point_index = if self.active_edges[active_edge_index as usize].left_to_right {
|
||||
self.control_point_index_before_endpoint(next_endpoint_index)
|
||||
let control_point = if self.active_edges[active_edge_index as usize].left_to_right {
|
||||
self.control_point_before_endpoint(next_endpoint_index)
|
||||
} else {
|
||||
self.control_point_index_after_endpoint(prev_endpoint_index)
|
||||
self.control_point_after_endpoint(prev_endpoint_index)
|
||||
};
|
||||
|
||||
match control_point_index {
|
||||
u32::MAX => {
|
||||
match control_point {
|
||||
None => {
|
||||
self.active_edges[active_edge_index as usize].control_point_vertex_index = u32::MAX
|
||||
}
|
||||
control_point_index => {
|
||||
Some(ref control_point_position) => {
|
||||
self.active_edges[active_edge_index as usize].control_point_vertex_index =
|
||||
self.library.b_vertex_loop_blinn_data.len() as u32;
|
||||
|
||||
let left_vertex_index = self.active_edges[active_edge_index as usize]
|
||||
.left_vertex_index;
|
||||
let control_point_position = &self.control_points[control_point_index as usize];
|
||||
let control_point_b_vertex_loop_blinn_data = BVertexLoopBlinnData::control_point(
|
||||
&self.library.b_vertex_positions[left_vertex_index as usize],
|
||||
&control_point_position,
|
||||
|
@ -266,12 +254,12 @@ impl<'a> Partitioner<'a> {
|
|||
debug_assert!(active_edge_indices[0] < active_edge_indices[1],
|
||||
"Matching active edge indices in wrong order when processing MAX point");
|
||||
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let endpoint = self.path.endpoints[endpoint_index as usize];
|
||||
|
||||
// TODO(pcwalton): Collapse the two duplicate endpoints that this will create together if
|
||||
// possible (i.e. if they have the same parity).
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x);
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x);
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.to.x);
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.to.x);
|
||||
|
||||
// Add supporting interior triangles if necessary.
|
||||
self.heap.pop();
|
||||
|
@ -282,7 +270,7 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
fn sort_active_edge_list_and_emit_self_intersections(&mut self, endpoint_index: u32) {
|
||||
let x = self.endpoints[endpoint_index as usize].position.x;
|
||||
let x = self.path.endpoints[endpoint_index as usize].to.x;
|
||||
loop {
|
||||
let mut swapped = false;
|
||||
for lower_active_edge_index in 1..(self.active_edges.len() as u32) {
|
||||
|
@ -335,51 +323,47 @@ impl<'a> Partitioner<'a> {
|
|||
new_active_edges[1].left_vertex_index = left_vertex_index;
|
||||
|
||||
// FIXME(pcwalton): Normal
|
||||
let position = self.endpoints[endpoint_index as usize].position;
|
||||
let position = self.path.endpoints[endpoint_index as usize].to;
|
||||
self.library.add_b_vertex(&position,
|
||||
&BVertexLoopBlinnData::new(BVertexKind::Endpoint0));
|
||||
|
||||
new_active_edges[0].toggle_parity();
|
||||
new_active_edges[1].toggle_parity();
|
||||
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let prev_endpoint = &self.endpoints[prev_endpoint_index as usize];
|
||||
let next_endpoint = &self.endpoints[next_endpoint_index as usize];
|
||||
let endpoint = &self.path.endpoints[endpoint_index as usize];
|
||||
let prev_endpoint = &self.path.endpoints[prev_endpoint_index as usize];
|
||||
let next_endpoint = &self.path.endpoints[next_endpoint_index as usize];
|
||||
|
||||
let prev_vector = prev_endpoint.position - endpoint.position;
|
||||
let next_vector = next_endpoint.position - endpoint.position;
|
||||
let prev_vector = prev_endpoint.to - endpoint.to;
|
||||
let next_vector = next_endpoint.to - endpoint.to;
|
||||
|
||||
let (upper_control_point_index, lower_control_point_index);
|
||||
let (upper_control_point, lower_control_point);
|
||||
if prev_vector.cross(next_vector) >= 0.0 {
|
||||
new_active_edges[0].right_endpoint_index = prev_endpoint_index;
|
||||
new_active_edges[1].right_endpoint_index = next_endpoint_index;
|
||||
new_active_edges[0].left_to_right = false;
|
||||
new_active_edges[1].left_to_right = true;
|
||||
|
||||
upper_control_point_index = self.endpoints[endpoint_index as usize].control_point_index;
|
||||
lower_control_point_index = self.endpoints[next_endpoint_index as usize]
|
||||
.control_point_index;
|
||||
upper_control_point = self.path.endpoints[endpoint_index as usize].ctrl;
|
||||
lower_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl;
|
||||
} else {
|
||||
new_active_edges[0].right_endpoint_index = next_endpoint_index;
|
||||
new_active_edges[1].right_endpoint_index = prev_endpoint_index;
|
||||
new_active_edges[0].left_to_right = true;
|
||||
new_active_edges[1].left_to_right = false;
|
||||
|
||||
upper_control_point_index = self.endpoints[next_endpoint_index as usize]
|
||||
.control_point_index;
|
||||
lower_control_point_index = self.endpoints[endpoint_index as usize].control_point_index;
|
||||
upper_control_point = self.path.endpoints[next_endpoint_index as usize].ctrl;
|
||||
lower_control_point = self.path.endpoints[endpoint_index as usize].ctrl;
|
||||
}
|
||||
|
||||
match upper_control_point_index {
|
||||
u32::MAX => new_active_edges[0].control_point_vertex_index = u32::MAX,
|
||||
upper_control_point_index => {
|
||||
match upper_control_point {
|
||||
None => new_active_edges[0].control_point_vertex_index = u32::MAX,
|
||||
Some(control_point_position) => {
|
||||
new_active_edges[0].control_point_vertex_index =
|
||||
self.library.b_vertex_loop_blinn_data.len() as u32;
|
||||
|
||||
let control_point_position =
|
||||
self.control_points[upper_control_point_index as usize];
|
||||
let right_vertex_position =
|
||||
self.endpoints[new_active_edges[0].right_endpoint_index as usize].position;
|
||||
self.path.endpoints[new_active_edges[0].right_endpoint_index as usize].to;
|
||||
let control_point_b_vertex_loop_blinn_data =
|
||||
BVertexLoopBlinnData::control_point(&position,
|
||||
&control_point_position,
|
||||
|
@ -392,16 +376,14 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
match lower_control_point_index {
|
||||
u32::MAX => new_active_edges[1].control_point_vertex_index = u32::MAX,
|
||||
lower_control_point_index => {
|
||||
match lower_control_point {
|
||||
None => new_active_edges[1].control_point_vertex_index = u32::MAX,
|
||||
Some(control_point_position) => {
|
||||
new_active_edges[1].control_point_vertex_index =
|
||||
self.library.b_vertex_loop_blinn_data.len() as u32;
|
||||
|
||||
let control_point_position =
|
||||
self.control_points[lower_control_point_index as usize];
|
||||
let right_vertex_position =
|
||||
self.endpoints[new_active_edges[1].right_endpoint_index as usize].position;
|
||||
self.path.endpoints[new_active_edges[1].right_endpoint_index as usize].to;
|
||||
let control_point_b_vertex_loop_blinn_data =
|
||||
BVertexLoopBlinnData::control_point(&position,
|
||||
&control_point_position,
|
||||
|
@ -437,18 +419,12 @@ impl<'a> Partitioner<'a> {
|
|||
prev_active_edge_y <= next_active_edge_y
|
||||
}
|
||||
|
||||
fn init_heap(&mut self, first_subpath_index: u32, last_subpath_index: u32) {
|
||||
for subpath in &self.subpaths[(first_subpath_index as usize)..
|
||||
(last_subpath_index as usize)] {
|
||||
for endpoint_index in subpath.first_endpoint_index..subpath.last_endpoint_index {
|
||||
match self.classify_endpoint(endpoint_index) {
|
||||
EndpointClass::Min => {
|
||||
fn init_heap(&mut self) {
|
||||
for endpoint_index in 0..(self.path.endpoints.len() as u32) {
|
||||
if let EndpointClass::Min = self.classify_endpoint(endpoint_index) {
|
||||
let new_point = self.create_point_from_endpoint(endpoint_index);
|
||||
self.heap.push(new_point)
|
||||
}
|
||||
EndpointClass::Regular | EndpointClass::Max => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,7 +533,9 @@ impl<'a> Partitioner<'a> {
|
|||
lower_subdivision.to_curve(&self.library.b_vertex_positions)) {
|
||||
// TODO(pcwalton): Handle concave-concave convex hull intersections.
|
||||
if upper_shape == Shape::Concave &&
|
||||
lower_curve.baseline().side(&upper_curve.control_point) >
|
||||
lower_curve.baseline()
|
||||
.to_line()
|
||||
.signed_distance_to_point(&upper_curve.ctrl) >
|
||||
f32::approx_epsilon() {
|
||||
let (upper_left_subsubdivision, upper_right_subsubdivision) =
|
||||
self.subdivide_active_edge_again_at_t(&upper_subdivision,
|
||||
|
@ -585,7 +563,9 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
if lower_shape == Shape::Concave &&
|
||||
upper_curve.baseline().side(&lower_curve.control_point) <
|
||||
upper_curve.baseline()
|
||||
.to_line()
|
||||
.signed_distance_to_point(&lower_curve.ctrl) <
|
||||
-f32::approx_epsilon() {
|
||||
let (lower_left_subsubdivision, lower_right_subsubdivision) =
|
||||
self.subdivide_active_edge_again_at_t(&lower_subdivision,
|
||||
|
@ -652,28 +632,28 @@ impl<'a> Partitioner<'a> {
|
|||
-> (SubdividedActiveEdge, SubdividedActiveEdge) {
|
||||
let curve = subdivision.to_curve(&self.library.b_vertex_positions)
|
||||
.expect("subdivide_active_edge_again_at_t(): not a curve!");
|
||||
let (left_curve, right_curve) = curve.subdivide(t);
|
||||
let (left_curve, right_curve) = curve.assume_monotonic().split(t);
|
||||
|
||||
let left_control_point_index = self.library.b_vertex_positions.len() as u32;
|
||||
let midpoint_index = left_control_point_index + 1;
|
||||
let right_control_point_index = midpoint_index + 1;
|
||||
self.library.b_vertex_positions.extend([
|
||||
left_curve.control_point,
|
||||
left_curve.endpoints[1],
|
||||
right_curve.control_point,
|
||||
left_curve.segment().ctrl,
|
||||
left_curve.segment().to,
|
||||
right_curve.segment().ctrl,
|
||||
].into_iter());
|
||||
|
||||
// Initially, assume that the parity is false. We will modify the Loop-Blinn data later if
|
||||
// that is incorrect.
|
||||
self.library.b_vertex_loop_blinn_data.extend([
|
||||
BVertexLoopBlinnData::control_point(&left_curve.endpoints[0],
|
||||
&left_curve.control_point,
|
||||
&left_curve.endpoints[1],
|
||||
BVertexLoopBlinnData::control_point(&left_curve.segment().from,
|
||||
&left_curve.segment().ctrl,
|
||||
&left_curve.segment().to,
|
||||
bottom),
|
||||
BVertexLoopBlinnData::new(BVertexKind::Endpoint0),
|
||||
BVertexLoopBlinnData::control_point(&right_curve.endpoints[0],
|
||||
&right_curve.control_point,
|
||||
&right_curve.endpoints[1],
|
||||
BVertexLoopBlinnData::control_point(&right_curve.segment().from,
|
||||
&right_curve.segment().ctrl,
|
||||
&right_curve.segment().to,
|
||||
bottom),
|
||||
].into_iter());
|
||||
|
||||
|
@ -697,7 +677,7 @@ impl<'a> Partitioner<'a> {
|
|||
-> (SubdividedActiveEdge, SubdividedActiveEdge) {
|
||||
let curve = subdivision.to_curve(&self.library.b_vertex_positions)
|
||||
.expect("subdivide_active_edge_again_at_x(): not a curve!");
|
||||
let t = curve.solve_t_for_x(x);
|
||||
let t = curve.assume_monotonic().solve_t_for_x(x);
|
||||
self.subdivide_active_edge_again_at_t(subdivision, t, bottom)
|
||||
}
|
||||
|
||||
|
@ -750,9 +730,9 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
fn find_point_between_active_edges(&self, endpoint_index: u32) -> u32 {
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let endpoint = &self.path.endpoints[endpoint_index as usize];
|
||||
match self.active_edges.iter().position(|active_edge| {
|
||||
self.solve_active_edge_y_for_x(endpoint.position.x, active_edge) > endpoint.position.y
|
||||
self.solve_active_edge_y_for_x(endpoint.to.x, active_edge) > endpoint.to.y
|
||||
}) {
|
||||
Some(active_edge_index) => active_edge_index as u32,
|
||||
None => self.active_edges.len() as u32,
|
||||
|
@ -762,16 +742,24 @@ impl<'a> Partitioner<'a> {
|
|||
fn solve_active_edge_t_for_x(&self, x: f32, active_edge: &ActiveEdge) -> f32 {
|
||||
let left_vertex_position =
|
||||
&self.library.b_vertex_positions[active_edge.left_vertex_index as usize];
|
||||
let right_endpoint_position = &self.endpoints[active_edge.right_endpoint_index as usize]
|
||||
.position;
|
||||
let right_endpoint_position =
|
||||
&self.path.endpoints[active_edge.right_endpoint_index as usize].to;
|
||||
match active_edge.control_point_vertex_index {
|
||||
u32::MAX => Line::new(left_vertex_position, right_endpoint_position).solve_t_for_x(x),
|
||||
u32::MAX => {
|
||||
LineSegment {
|
||||
from: *left_vertex_position,
|
||||
to: *right_endpoint_position,
|
||||
}.solve_t_for_x(x)
|
||||
}
|
||||
control_point_vertex_index => {
|
||||
let control_point = &self.library
|
||||
.b_vertex_positions[control_point_vertex_index as usize];
|
||||
Curve::new(left_vertex_position,
|
||||
control_point,
|
||||
right_endpoint_position).solve_t_for_x(x)
|
||||
let segment = QuadraticBezierSegment {
|
||||
from: *left_vertex_position,
|
||||
ctrl: *control_point,
|
||||
to: *right_endpoint_position,
|
||||
};
|
||||
segment.assume_monotonic().solve_t_for_x(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -784,7 +772,7 @@ impl<'a> Partitioner<'a> {
|
|||
let left_vertex_position =
|
||||
&self.library.b_vertex_positions[active_edge.left_vertex_index as usize];
|
||||
let right_endpoint_position =
|
||||
&self.endpoints[active_edge.right_endpoint_index as usize].position;
|
||||
&self.path.endpoints[active_edge.right_endpoint_index as usize].to;
|
||||
match active_edge.control_point_vertex_index {
|
||||
u32::MAX => {
|
||||
left_vertex_position.to_vector()
|
||||
|
@ -794,7 +782,11 @@ impl<'a> Partitioner<'a> {
|
|||
control_point_vertex_index => {
|
||||
let control_point = &self.library
|
||||
.b_vertex_positions[control_point_vertex_index as usize];
|
||||
Curve::new(left_vertex_position, control_point, right_endpoint_position).sample(t)
|
||||
QuadraticBezierSegment {
|
||||
from: *left_vertex_position,
|
||||
ctrl: *control_point,
|
||||
to: *right_endpoint_position,
|
||||
}.sample(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -813,48 +805,54 @@ impl<'a> Partitioner<'a> {
|
|||
let upper_left_vertex_position =
|
||||
&self.library.b_vertex_positions[upper_active_edge.left_vertex_index as usize];
|
||||
let upper_right_endpoint_position =
|
||||
&self.endpoints[upper_active_edge.right_endpoint_index as usize].position;
|
||||
&self.path.endpoints[upper_active_edge.right_endpoint_index as usize].to;
|
||||
let lower_left_vertex_position =
|
||||
&self.library.b_vertex_positions[lower_active_edge.left_vertex_index as usize];
|
||||
let lower_right_endpoint_position =
|
||||
&self.endpoints[lower_active_edge.right_endpoint_index as usize].position;
|
||||
&self.path.endpoints[lower_active_edge.right_endpoint_index as usize].to;
|
||||
|
||||
match (upper_active_edge.control_point_vertex_index,
|
||||
lower_active_edge.control_point_vertex_index) {
|
||||
(u32::MAX, u32::MAX) => {
|
||||
let (upper_line, _) =
|
||||
Line::new(upper_left_vertex_position,
|
||||
upper_right_endpoint_position).subdivide_at_x(max_x);
|
||||
let (lower_line, _) =
|
||||
Line::new(lower_left_vertex_position,
|
||||
lower_right_endpoint_position).subdivide_at_x(max_x);
|
||||
upper_line.intersect(&lower_line)
|
||||
let (upper_line, _) = LineSegment {
|
||||
from: *upper_left_vertex_position,
|
||||
to: *upper_right_endpoint_position,
|
||||
}.split_at_x(max_x);
|
||||
let (lower_line, _) = LineSegment {
|
||||
from: *lower_left_vertex_position,
|
||||
to: *lower_right_endpoint_position,
|
||||
}.split_at_x(max_x);
|
||||
upper_line.intersection(&lower_line)
|
||||
}
|
||||
|
||||
(upper_control_point_vertex_index, u32::MAX) => {
|
||||
let upper_control_point =
|
||||
&self.library.b_vertex_positions[upper_control_point_vertex_index as usize];
|
||||
let (upper_curve, _) =
|
||||
Curve::new(&upper_left_vertex_position,
|
||||
&upper_control_point,
|
||||
&upper_right_endpoint_position).subdivide_at_x(max_x);
|
||||
let (lower_line, _) =
|
||||
Line::new(lower_left_vertex_position,
|
||||
lower_right_endpoint_position).subdivide_at_x(max_x);
|
||||
upper_curve.intersect(&lower_line)
|
||||
let (upper_curve, _) = QuadraticBezierSegment {
|
||||
from: *upper_left_vertex_position,
|
||||
ctrl: *upper_control_point,
|
||||
to: *upper_right_endpoint_position,
|
||||
}.assume_monotonic().split_at_x(max_x);
|
||||
let (lower_line, _) = LineSegment {
|
||||
from: *lower_left_vertex_position,
|
||||
to: *lower_right_endpoint_position,
|
||||
}.split_at_x(max_x);
|
||||
upper_curve.segment().line_segment_intersections(&lower_line).pop()
|
||||
}
|
||||
|
||||
(u32::MAX, lower_control_point_vertex_index) => {
|
||||
let lower_control_point =
|
||||
&self.library.b_vertex_positions[lower_control_point_vertex_index as usize];
|
||||
let (lower_curve, _) =
|
||||
Curve::new(&lower_left_vertex_position,
|
||||
&lower_control_point,
|
||||
&lower_right_endpoint_position).subdivide_at_x(max_x);
|
||||
let (upper_line, _) =
|
||||
Line::new(upper_left_vertex_position,
|
||||
upper_right_endpoint_position).subdivide_at_x(max_x);
|
||||
lower_curve.intersect(&upper_line)
|
||||
let (lower_curve, _) = QuadraticBezierSegment {
|
||||
from: *lower_left_vertex_position,
|
||||
ctrl: *lower_control_point,
|
||||
to: *lower_right_endpoint_position,
|
||||
}.assume_monotonic().split_at_x(max_x);
|
||||
let (upper_line, _) = LineSegment {
|
||||
from: *upper_left_vertex_position,
|
||||
to: *upper_right_endpoint_position,
|
||||
}.split_at_x(max_x);
|
||||
lower_curve.segment().line_segment_intersections(&upper_line).pop()
|
||||
}
|
||||
|
||||
(upper_control_point_vertex_index, lower_control_point_vertex_index) => {
|
||||
|
@ -862,15 +860,20 @@ impl<'a> Partitioner<'a> {
|
|||
&self.library.b_vertex_positions[upper_control_point_vertex_index as usize];
|
||||
let lower_control_point =
|
||||
&self.library.b_vertex_positions[lower_control_point_vertex_index as usize];
|
||||
let (upper_curve, _) =
|
||||
Curve::new(&upper_left_vertex_position,
|
||||
&upper_control_point,
|
||||
&upper_right_endpoint_position).subdivide_at_x(max_x);
|
||||
let (lower_curve, _) =
|
||||
Curve::new(&lower_left_vertex_position,
|
||||
&lower_control_point,
|
||||
&lower_right_endpoint_position).subdivide_at_x(max_x);
|
||||
upper_curve.intersect(&lower_curve)
|
||||
let (upper_curve, _) = QuadraticBezierSegment {
|
||||
from: *upper_left_vertex_position,
|
||||
ctrl: *upper_control_point,
|
||||
to: *upper_right_endpoint_position,
|
||||
}.assume_monotonic().split_at_x(max_x);
|
||||
let (lower_curve, _) = QuadraticBezierSegment {
|
||||
from: *lower_left_vertex_position,
|
||||
ctrl: *lower_control_point,
|
||||
to: *lower_right_endpoint_position,
|
||||
}.assume_monotonic().split_at_x(max_x);
|
||||
upper_curve.first_intersection(0.0..1.0,
|
||||
&lower_curve,
|
||||
0.0..1.0,
|
||||
INTERSECTION_TOLERANCE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -898,8 +901,8 @@ impl<'a> Partitioner<'a> {
|
|||
let left_curve_control_point_vertex_index;
|
||||
match active_edge.control_point_vertex_index {
|
||||
u32::MAX => {
|
||||
let right_point = self.endpoints[active_edge.right_endpoint_index as usize]
|
||||
.position;
|
||||
let right_point =
|
||||
self.path.endpoints[active_edge.right_endpoint_index as usize].to;
|
||||
let middle_point = left_point_position.to_vector()
|
||||
.lerp(right_point.to_vector(), t);
|
||||
|
||||
|
@ -917,11 +920,12 @@ impl<'a> Partitioner<'a> {
|
|||
self.library
|
||||
.b_vertex_positions[active_edge.control_point_vertex_index as usize];
|
||||
let right_endpoint_position =
|
||||
self.endpoints[active_edge.right_endpoint_index as usize].position;
|
||||
let original_curve = Curve::new(&left_endpoint_position,
|
||||
&control_point_position,
|
||||
&right_endpoint_position);
|
||||
let (left_curve, right_curve) = original_curve.subdivide(t);
|
||||
self.path.endpoints[active_edge.right_endpoint_index as usize].to;
|
||||
let (left_curve, right_curve) = QuadraticBezierSegment {
|
||||
from: left_endpoint_position,
|
||||
ctrl: control_point_position,
|
||||
to: right_endpoint_position,
|
||||
}.split(t);
|
||||
|
||||
left_curve_control_point_vertex_index =
|
||||
self.library.b_vertex_loop_blinn_data.len() as u32;
|
||||
|
@ -930,18 +934,18 @@ impl<'a> Partitioner<'a> {
|
|||
|
||||
// FIXME(pcwalton): Normals
|
||||
self.library
|
||||
.add_b_vertex(&left_curve.control_point,
|
||||
&BVertexLoopBlinnData::control_point(&left_curve.endpoints[0],
|
||||
&left_curve.control_point,
|
||||
&left_curve.endpoints[1],
|
||||
.add_b_vertex(&left_curve.ctrl,
|
||||
&BVertexLoopBlinnData::control_point(&left_curve.from,
|
||||
&left_curve.ctrl,
|
||||
&left_curve.to,
|
||||
bottom));
|
||||
self.library.add_b_vertex(&left_curve.endpoints[1],
|
||||
self.library.add_b_vertex(&left_curve.to,
|
||||
&BVertexLoopBlinnData::new(active_edge.endpoint_kind()));
|
||||
self.library
|
||||
.add_b_vertex(&right_curve.control_point,
|
||||
&BVertexLoopBlinnData::control_point(&right_curve.endpoints[0],
|
||||
&right_curve.control_point,
|
||||
&right_curve.endpoints[1],
|
||||
.add_b_vertex(&right_curve.ctrl,
|
||||
&BVertexLoopBlinnData::control_point(&right_curve.from,
|
||||
&right_curve.ctrl,
|
||||
&right_curve.to,
|
||||
bottom));
|
||||
}
|
||||
}
|
||||
|
@ -1006,38 +1010,38 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 {
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let subpath = &self.subpaths[endpoint.subpath_index as usize];
|
||||
if endpoint_index > subpath.first_endpoint_index {
|
||||
let endpoint = &self.path.endpoints[endpoint_index as usize];
|
||||
let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize];
|
||||
if endpoint_index > subpath.start {
|
||||
endpoint_index - 1
|
||||
} else {
|
||||
subpath.last_endpoint_index - 1
|
||||
subpath.end - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn next_endpoint_of(&self, endpoint_index: u32) -> u32 {
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let subpath = &self.subpaths[endpoint.subpath_index as usize];
|
||||
if endpoint_index + 1 < subpath.last_endpoint_index {
|
||||
let endpoint = &self.path.endpoints[endpoint_index as usize];
|
||||
let subpath = &self.path.subpath_ranges[endpoint.subpath_index as usize];
|
||||
if endpoint_index + 1 < subpath.end {
|
||||
endpoint_index + 1
|
||||
} else {
|
||||
subpath.first_endpoint_index
|
||||
subpath.start
|
||||
}
|
||||
}
|
||||
|
||||
fn create_point_from_endpoint(&self, endpoint_index: u32) -> Point {
|
||||
Point {
|
||||
position: self.endpoints[endpoint_index as usize].position,
|
||||
position: self.path.endpoints[endpoint_index as usize].to,
|
||||
endpoint_index: endpoint_index,
|
||||
}
|
||||
}
|
||||
|
||||
fn control_point_index_before_endpoint(&self, endpoint_index: u32) -> u32 {
|
||||
self.endpoints[endpoint_index as usize].control_point_index
|
||||
fn control_point_before_endpoint(&self, endpoint_index: u32) -> Option<Point2D<f32>> {
|
||||
self.path.endpoints[endpoint_index as usize].ctrl
|
||||
}
|
||||
|
||||
fn control_point_index_after_endpoint(&self, endpoint_index: u32) -> u32 {
|
||||
self.control_point_index_before_endpoint(self.next_endpoint_of(endpoint_index))
|
||||
fn control_point_after_endpoint(&self, endpoint_index: u32) -> Option<Point2D<f32>> {
|
||||
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 {
|
||||
None
|
||||
} else {
|
||||
Some(Curve::new(&b_vertex_positions[self.left_curve_left as usize],
|
||||
&b_vertex_positions[self.left_curve_control_point as usize],
|
||||
&b_vertex_positions[self.middle_point as usize]))
|
||||
Some(QuadraticBezierSegment {
|
||||
from: b_vertex_positions[self.left_curve_left as usize],
|
||||
ctrl: b_vertex_positions[self.left_curve_control_point as usize],
|
||||
to: b_vertex_positions[self.middle_point as usize],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
|||
|
||||
[dependencies]
|
||||
arrayvec = "0.4"
|
||||
euclid = "0.15"
|
||||
euclid = "0.16"
|
||||
lyon_geom = "0.9"
|
||||
lyon_path = "0.9"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
|
|
@ -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
|
||||
//
|
||||
// Copyright © 2017 The Pathfinder Project Developers.
|
||||
// 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
|
||||
|
@ -10,399 +10,15 @@
|
|||
|
||||
//! Various utilities for manipulating Bézier curves.
|
||||
//!
|
||||
//! On its own, the partitioner can only generate meshes for fill operations on quadratic Bézier
|
||||
//! curves. Frequently, however, other vector drawing operations are desired: for example,
|
||||
//! rendering cubic Béziers or stroking paths. These utilities can convert those complex operations
|
||||
//! into simpler sequences of quadratic Béziers that the partitioner can handle.
|
||||
//! Most of these should go upstream to Lyon at some point.
|
||||
|
||||
extern crate arrayvec;
|
||||
extern crate euclid;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate lyon_geom;
|
||||
extern crate lyon_path;
|
||||
|
||||
use euclid::{Point2D, Transform2D};
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::u32;
|
||||
|
||||
pub mod cubic;
|
||||
pub mod curve;
|
||||
pub mod intersection;
|
||||
pub mod line;
|
||||
pub mod monotonic;
|
||||
pub mod cubic_to_quadratic;
|
||||
pub mod normals;
|
||||
pub mod segments;
|
||||
pub mod stroke;
|
||||
pub mod svg;
|
||||
|
||||
/// A series of commands that define quadratic Bézier paths.
|
||||
///
|
||||
/// For cubics, see the `cubic` module.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PathCommand {
|
||||
/// Moves the pen to the given point.
|
||||
MoveTo(Point2D<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
|
||||
}
|
||||
}
|
||||
pub mod transform;
|
||||
|
|
|
@ -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
|
||||
//
|
||||
// Copyright © 2017 The Pathfinder Project Developers.
|
||||
// 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
|
||||
|
@ -10,132 +10,138 @@
|
|||
|
||||
//! Utilities for converting path strokes to fills.
|
||||
|
||||
use std::u32;
|
||||
use lyon_path::PathEvent;
|
||||
use lyon_path::iterator::PathIterator;
|
||||
|
||||
use {Endpoint, PathBuffer, PathCommand, Subpath};
|
||||
use line::Line;
|
||||
use segments::{Segment, SegmentIter};
|
||||
|
||||
/// Represents the style of a stroke.
|
||||
pub struct Stroke {
|
||||
/// The stroke diameter.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StrokeStyle {
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
/// Constructs a new stroke style with the given diameter.
|
||||
impl StrokeStyle {
|
||||
#[inline]
|
||||
pub fn new(width: f32) -> Stroke {
|
||||
Stroke {
|
||||
pub fn new(width: f32) -> StrokeStyle {
|
||||
StrokeStyle {
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a path that represents the result of stroking `stream` with this stroke style into
|
||||
/// `output`.
|
||||
pub fn apply<I>(&self, output: &mut PathBuffer, stream: I)
|
||||
where I: Iterator<Item = PathCommand> {
|
||||
let mut input = PathBuffer::new();
|
||||
input.add_stream(stream);
|
||||
pub struct StrokeToFillIter<I> where I: PathIterator {
|
||||
inner: SegmentIter<I>,
|
||||
subpath: Vec<Segment>,
|
||||
stack: Vec<PathEvent>,
|
||||
state: StrokeToFillState,
|
||||
style: StrokeStyle,
|
||||
first_point_in_subpath: bool,
|
||||
}
|
||||
|
||||
for subpath_index in 0..(input.subpaths.len() as u32) {
|
||||
let closed = input.subpaths[subpath_index as usize].closed;
|
||||
impl<I> StrokeToFillIter<I> where I: PathIterator {
|
||||
#[inline]
|
||||
pub fn new(inner: I, style: StrokeStyle) -> StrokeToFillIter<I> {
|
||||
StrokeToFillIter {
|
||||
inner: SegmentIter::new(inner),
|
||||
subpath: vec![],
|
||||
stack: vec![],
|
||||
state: StrokeToFillState::Forward,
|
||||
style: style,
|
||||
first_point_in_subpath: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut first_endpoint_index = output.endpoints.len() as u32;
|
||||
impl<I> Iterator for StrokeToFillIter<I> where I: PathIterator {
|
||||
type Item = PathEvent;
|
||||
|
||||
// Compute the first offset curve.
|
||||
//
|
||||
// TODO(pcwalton): Support line caps.
|
||||
self.offset_subpath(output, &input, subpath_index);
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
self.stack.reverse();
|
||||
return self.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
#[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 winding = gl_FrontFacing ? -1.0 : 1.0;
|
||||
float alpha = float(side == winding);
|
||||
//float alpha = mod(gl_FragCoord.x, 2.0) < 1.0 ? 1.0 : 0.0;
|
||||
gl_FragColor = alpha * vColor;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
|||
app_units = "0.5"
|
||||
clap = "2.27"
|
||||
freetype-sys = "0.6"
|
||||
lyon_geom = "0.9"
|
||||
lyon_path = "0.9"
|
||||
|
||||
[dependencies.pathfinder_font_renderer]
|
||||
path = "../../font-renderer"
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
extern crate app_units;
|
||||
extern crate clap;
|
||||
extern crate freetype_sys;
|
||||
extern crate lyon_geom;
|
||||
extern crate lyon_path;
|
||||
extern crate pathfinder_font_renderer;
|
||||
extern crate pathfinder_partitioner;
|
||||
extern crate pathfinder_path_utils;
|
||||
|
@ -33,11 +35,12 @@ extern crate pathfinder_path_utils;
|
|||
use app_units::Au;
|
||||
use clap::{App, Arg};
|
||||
use freetype_sys::{FT_Init_FreeType, FT_New_Face};
|
||||
use lyon_path::PathEvent;
|
||||
use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
||||
use pathfinder_font_renderer::{FontContext, FontKey, FontInstance, GlyphKey, SubpixelOffset};
|
||||
use pathfinder_partitioner::FillRule;
|
||||
use pathfinder_partitioner::mesh_library::MeshLibrary;
|
||||
use pathfinder_partitioner::partitioner::Partitioner;
|
||||
use pathfinder_path_utils::monotonic::MonotonicPathCommandStream;
|
||||
use pathfinder_path_utils::{PathBuffer, PathBufferStream};
|
||||
use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
@ -83,30 +86,28 @@ fn convert_font(font_path: &Path, output_path: &Path) -> Result<(), ()> {
|
|||
size: Au::from_f64_px(FONT_SIZE),
|
||||
};
|
||||
|
||||
let mut path_buffer = PathBuffer::new();
|
||||
let mut paths: Vec<(u16, Vec<PathEvent>)> = vec![];
|
||||
let mut partitioner = Partitioner::new(MeshLibrary::new());
|
||||
let subpath_ranges: Vec<_> = (0..glyph_count).map(|glyph_index| {
|
||||
|
||||
for glyph_index in 0..glyph_count {
|
||||
let glyph_key = GlyphKey::new(glyph_index, SubpixelOffset(0));
|
||||
|
||||
let subpath_start = path_buffer.subpaths.len() as u32;
|
||||
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance, &glyph_key) {
|
||||
path_buffer.add_stream(MonotonicPathCommandStream::new(glyph_outline.into_iter()))
|
||||
}
|
||||
let subpath_end = path_buffer.subpaths.len() as u32;
|
||||
let path = match font_context.glyph_outline(&font_instance, &glyph_key) {
|
||||
Ok(path) => path,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let path_index = (glyph_index + 1) as u16;
|
||||
let stream = PathBufferStream::subpath_range(&path_buffer, subpath_start..subpath_end);
|
||||
partitioner.library_mut().push_segments(path_index, stream);
|
||||
let stream = PathBufferStream::subpath_range(&path_buffer, subpath_start..subpath_end);
|
||||
partitioner.library_mut().push_normals(stream);
|
||||
partitioner.library_mut().push_segments(path_index, path.iter());
|
||||
partitioner.library_mut().push_normals(path_index, path.iter());
|
||||
|
||||
subpath_start..subpath_end
|
||||
}).collect();
|
||||
paths.push((path_index, path.iter().collect()));
|
||||
}
|
||||
|
||||
partitioner.init_with_path_buffer(&path_buffer);
|
||||
|
||||
for (glyph_index, subpath) in subpath_ranges.iter().cloned().enumerate() {
|
||||
partitioner.partition((glyph_index + 1) as u16, subpath.start, subpath.end);
|
||||
for (glyph_index, path) in paths {
|
||||
path.into_iter().for_each(|event| partitioner.builder_mut().path_event(event));
|
||||
partitioner.partition(glyph_index, FillRule::Winding);
|
||||
partitioner.builder_mut().build_and_reset();
|
||||
}
|
||||
|
||||
partitioner.library_mut().optimize();
|
||||
|
|
Loading…
Reference in New Issue