Implement stroking ourselves, and rename `PathSegment` to `PathCommand`.
This makes Pathfinder no longer use the FreeType stroker.
This commit is contained in:
parent
22d0eb7b24
commit
8de1970682
|
@ -35,9 +35,9 @@ use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey};
|
|||
use pathfinder_partitioner::mesh_library::MeshLibrary;
|
||||
use pathfinder_partitioner::partitioner::Partitioner;
|
||||
use pathfinder_path_utils::cubic::CubicCurve;
|
||||
use pathfinder_path_utils::monotonic::MonotonicPathSegmentStream;
|
||||
use pathfinder_path_utils::stroke;
|
||||
use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathSegment, Transform2DPathStream};
|
||||
use pathfinder_path_utils::monotonic::MonotonicPathCommandStream;
|
||||
use pathfinder_path_utils::stroke::Stroke;
|
||||
use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathCommand, Transform2DPathStream};
|
||||
use rocket::http::{ContentType, Header, Status};
|
||||
use rocket::request::Request;
|
||||
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
||||
|
@ -144,7 +144,7 @@ enum PartitionFontError {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
enum PartitionSvgPathsError {
|
||||
UnknownSvgPathSegmentType,
|
||||
UnknownSvgPathCommandType,
|
||||
Unimplemented,
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ struct PartitionSvgPathsRequest {
|
|||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct PartitionSvgPath {
|
||||
segments: Vec<PartitionSvgPathSegment>,
|
||||
segments: Vec<PartitionSvgPathCommand>,
|
||||
kind: PartitionSvgPathKind,
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ enum PartitionSvgPathKind {
|
|||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
struct PartitionSvgPathSegment {
|
||||
struct PartitionSvgPathCommand {
|
||||
#[serde(rename = "type")]
|
||||
kind: char,
|
||||
values: Vec<f64>,
|
||||
|
@ -296,7 +296,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
|
|||
// This might fail; if so, just leave it blank.
|
||||
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance_key, &glyph_key) {
|
||||
let stream = Transform2DPathStream::new(glyph_outline, &glyph.transform);
|
||||
let stream = MonotonicPathSegmentStream::new(stream);
|
||||
let stream = MonotonicPathCommandStream::new(stream);
|
||||
path_buffer.add_stream(stream)
|
||||
}
|
||||
|
||||
|
@ -350,14 +350,13 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
|||
match segment.kind {
|
||||
'M' => {
|
||||
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32);
|
||||
stream.push(PathSegment::MoveTo(last_point))
|
||||
stream.push(PathCommand::MoveTo(last_point))
|
||||
}
|
||||
'L' => {
|
||||
last_point = Point2D::new(segment.values[0] as f32, segment.values[1] as f32);
|
||||
stream.push(PathSegment::LineTo(last_point))
|
||||
stream.push(PathCommand::LineTo(last_point))
|
||||
}
|
||||
'C' => {
|
||||
// FIXME(pcwalton): Do real cubic-to-quadratic conversion.
|
||||
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,
|
||||
|
@ -372,21 +371,21 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
|||
stream.extend(cubic.approximate_curve(CUBIC_ERROR_TOLERANCE)
|
||||
.map(|curve| curve.to_path_segment()));
|
||||
}
|
||||
'Z' => stream.push(PathSegment::ClosePath),
|
||||
_ => return Err(PartitionSvgPathsError::UnknownSvgPathSegmentType),
|
||||
'Z' => stream.push(PathCommand::ClosePath),
|
||||
_ => return Err(PartitionSvgPathsError::UnknownSvgPathCommandType),
|
||||
}
|
||||
}
|
||||
|
||||
match path.kind {
|
||||
PartitionSvgPathKind::Fill => {
|
||||
path_buffer.add_stream(MonotonicPathSegmentStream::new(stream.into_iter()))
|
||||
path_buffer.add_stream(MonotonicPathCommandStream::new(stream.into_iter()))
|
||||
}
|
||||
PartitionSvgPathKind::Stroke(stroke_width) => {
|
||||
let mut temp_path_buffer = PathBuffer::new();
|
||||
stroke::stroke(&mut temp_path_buffer, stream.into_iter(), stroke_width);
|
||||
Stroke::new(stroke_width).apply(&mut temp_path_buffer, stream.into_iter());
|
||||
|
||||
let stream = PathBufferStream::new(&temp_path_buffer);
|
||||
let stream = MonotonicPathSegmentStream::new(stream);
|
||||
let stream = MonotonicPathCommandStream::new(stream);
|
||||
path_buffer.add_stream(stream)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_O
|
|||
use freetype_sys::{FT_GlyphSlot, FT_Init_FreeType, FT_Int32, FT_LOAD_NO_HINTING, FT_Library};
|
||||
use freetype_sys::{FT_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
|
||||
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
|
||||
use pathfinder_path_utils::PathSegment;
|
||||
use pathfinder_path_utils::PathCommand;
|
||||
use pathfinder_path_utils::freetype::OutlineStream;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::btree_map::Entry;
|
||||
|
@ -179,8 +179,8 @@ pub struct GlyphOutline<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for GlyphOutline<'a> {
|
||||
type Item = PathSegment;
|
||||
fn next(&mut self) -> Option<PathSegment> {
|
||||
type Item = PathCommand;
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
self.stream.next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use euclid::approxeq::ApproxEq;
|
|||
use euclid::Point2D;
|
||||
use std::f32;
|
||||
|
||||
use PathSegment;
|
||||
use PathCommand;
|
||||
use intersection::Intersect;
|
||||
use line::Line;
|
||||
|
||||
|
@ -58,8 +58,8 @@ impl Curve {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_path_segment(&self) -> PathSegment {
|
||||
PathSegment::CurveTo(self.control_point, self.endpoints[1])
|
||||
pub fn to_path_segment(&self) -> PathCommand {
|
||||
PathCommand::CurveTo(self.control_point, self.endpoints[1])
|
||||
}
|
||||
|
||||
pub fn inflection_points(&self) -> (Option<f32>, Option<f32>) {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
use euclid::Point2D;
|
||||
use freetype_sys::{FT_Fixed, FT_Outline, FT_Pos, FT_Vector};
|
||||
|
||||
use PathSegment;
|
||||
use PathCommand;
|
||||
|
||||
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
|
||||
|
||||
|
@ -49,9 +49,9 @@ impl<'a> OutlineStream<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for OutlineStream<'a> {
|
||||
type Item = PathSegment;
|
||||
type Item = PathCommand;
|
||||
|
||||
fn next(&mut self) -> Option<PathSegment> {
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
unsafe {
|
||||
let mut control_point_position: Option<Point2D<f32>> = None;
|
||||
loop {
|
||||
|
@ -63,13 +63,13 @@ 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(PathSegment::CurveTo(control_point_position,
|
||||
return Some(PathCommand::CurveTo(control_point_position,
|
||||
self.first_position_of_subpath))
|
||||
}
|
||||
|
||||
self.contour_index += 1;
|
||||
self.first_point_index_of_contour = true;
|
||||
return Some(PathSegment::ClosePath)
|
||||
return Some(PathCommand::ClosePath)
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): Approximate cubic curves with quadratics.
|
||||
|
@ -80,18 +80,18 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
self.first_point_index_of_contour = false;
|
||||
self.first_position_of_subpath = position;
|
||||
self.point_index += 1;
|
||||
return Some(PathSegment::MoveTo(position));
|
||||
return Some(PathCommand::MoveTo(position));
|
||||
}
|
||||
|
||||
match (control_point_position, point_on_curve) {
|
||||
(Some(control_point_position), false) => {
|
||||
let on_curve_position = control_point_position.lerp(position, 0.5);
|
||||
return Some(PathSegment::CurveTo(control_point_position,
|
||||
return Some(PathCommand::CurveTo(control_point_position,
|
||||
on_curve_position))
|
||||
}
|
||||
(Some(control_point_position), true) => {
|
||||
self.point_index += 1;
|
||||
return Some(PathSegment::CurveTo(control_point_position, position))
|
||||
return Some(PathCommand::CurveTo(control_point_position, position))
|
||||
}
|
||||
(None, false) => {
|
||||
self.point_index += 1;
|
||||
|
@ -99,7 +99,7 @@ impl<'a> Iterator for OutlineStream<'a> {
|
|||
}
|
||||
(None, true) => {
|
||||
self.point_index += 1;
|
||||
return Some(PathSegment::LineTo(position))
|
||||
return Some(PathCommand::LineTo(position))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ extern crate freetype_sys;
|
|||
extern crate serde_derive;
|
||||
|
||||
use euclid::{Point2D, Transform2D};
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::u32;
|
||||
|
||||
pub mod cubic;
|
||||
|
@ -27,9 +29,10 @@ pub mod stroke;
|
|||
pub mod svg;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PathSegment {
|
||||
pub enum PathCommand {
|
||||
MoveTo(Point2D<f32>),
|
||||
LineTo(Point2D<f32>),
|
||||
/// Control point and endpoint, respectively.
|
||||
CurveTo(Point2D<f32>, Point2D<f32>),
|
||||
ClosePath,
|
||||
}
|
||||
|
@ -51,13 +54,13 @@ impl PathBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_stream<I>(&mut self, stream: I) where I: Iterator<Item = PathSegment> {
|
||||
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 {
|
||||
PathSegment::ClosePath => self.close_subpath(&mut first_subpath_endpoint_index),
|
||||
PathCommand::ClosePath => self.close_subpath(&mut first_subpath_endpoint_index),
|
||||
|
||||
PathSegment::MoveTo(position) => {
|
||||
PathCommand::MoveTo(position) => {
|
||||
self.close_subpath(&mut first_subpath_endpoint_index);
|
||||
self.endpoints.push(Endpoint {
|
||||
position: position,
|
||||
|
@ -66,7 +69,7 @@ impl PathBuffer {
|
|||
})
|
||||
}
|
||||
|
||||
PathSegment::LineTo(position) => {
|
||||
PathCommand::LineTo(position) => {
|
||||
self.endpoints.push(Endpoint {
|
||||
position: position,
|
||||
control_point_index: u32::MAX,
|
||||
|
@ -74,7 +77,7 @@ impl PathBuffer {
|
|||
})
|
||||
}
|
||||
|
||||
PathSegment::CurveTo(control_point_position, endpoint_position) => {
|
||||
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 {
|
||||
|
@ -100,6 +103,23 @@ impl PathBuffer {
|
|||
|
||||
*first_subpath_endpoint_index = last_subpath_endpoint_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;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathBufferStream<'a> {
|
||||
|
@ -119,9 +139,9 @@ impl<'a> PathBufferStream<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for PathBufferStream<'a> {
|
||||
type Item = PathSegment;
|
||||
type Item = PathCommand;
|
||||
|
||||
fn next(&mut self) -> Option<PathSegment> {
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
if self.subpath_index as usize == self.path_buffer.subpaths.len() {
|
||||
return None
|
||||
}
|
||||
|
@ -129,7 +149,7 @@ impl<'a> Iterator for PathBufferStream<'a> {
|
|||
let subpath = &self.path_buffer.subpaths[self.subpath_index as usize];
|
||||
if self.endpoint_index == subpath.last_endpoint_index {
|
||||
self.subpath_index += 1;
|
||||
return Some(PathSegment::ClosePath)
|
||||
return Some(PathCommand::ClosePath)
|
||||
}
|
||||
|
||||
let endpoint_index = self.endpoint_index;
|
||||
|
@ -138,16 +158,16 @@ impl<'a> Iterator for PathBufferStream<'a> {
|
|||
let endpoint = &self.path_buffer.endpoints[endpoint_index as usize];
|
||||
|
||||
if endpoint_index == subpath.first_endpoint_index {
|
||||
return Some(PathSegment::MoveTo(endpoint.position))
|
||||
return Some(PathCommand::MoveTo(endpoint.position))
|
||||
}
|
||||
|
||||
if endpoint.control_point_index == u32::MAX {
|
||||
return Some(PathSegment::LineTo(endpoint.position))
|
||||
return Some(PathCommand::LineTo(endpoint.position))
|
||||
}
|
||||
|
||||
let control_point = &self.path_buffer
|
||||
.control_points[endpoint.control_point_index as usize];
|
||||
Some(PathSegment::CurveTo(*control_point, endpoint.position))
|
||||
Some(PathCommand::CurveTo(*control_point, endpoint.position))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,12 +187,78 @@ pub struct Subpath {
|
|||
pub last_endpoint_index: u32,
|
||||
}
|
||||
|
||||
impl Subpath {
|
||||
#[inline]
|
||||
pub fn range(self) -> Range<usize> {
|
||||
(self.first_endpoint_index as usize)..(self.last_endpoint_index as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PathSegment {
|
||||
/// First endpoint and second endpoint, respectively.
|
||||
Line(Point2D<f32>, Point2D<f32>),
|
||||
/// First endpoint, control point, and second endpoint, in that order.
|
||||
Curve(Point2D<f32>, Point2D<f32>, Point2D<f32>),
|
||||
}
|
||||
|
||||
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> {
|
||||
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)) => {
|
||||
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)) => {
|
||||
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 start_point = mem::replace(&mut self.current_point,
|
||||
self.current_subpath_start_point);
|
||||
return Some((PathSegment::Line(start_point, self.current_subpath_start_point),
|
||||
self.current_subpath_index))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Transform2DPathStream<I> {
|
||||
inner: I,
|
||||
transform: Transform2D<f32>,
|
||||
}
|
||||
|
||||
impl<I> Transform2DPathStream<I> where I: Iterator<Item = PathSegment> {
|
||||
impl<I> Transform2DPathStream<I> where I: Iterator<Item = PathCommand> {
|
||||
pub fn new(inner: I, transform: &Transform2D<f32>) -> Transform2DPathStream<I> {
|
||||
Transform2DPathStream {
|
||||
inner: inner,
|
||||
|
@ -181,23 +267,23 @@ impl<I> Transform2DPathStream<I> where I: Iterator<Item = PathSegment> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Transform2DPathStream<I> where I: Iterator<Item = PathSegment> {
|
||||
type Item = PathSegment;
|
||||
impl<I> Iterator for Transform2DPathStream<I> where I: Iterator<Item = PathCommand> {
|
||||
type Item = PathCommand;
|
||||
|
||||
fn next(&mut self) -> Option<PathSegment> {
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
match self.inner.next() {
|
||||
None => None,
|
||||
Some(PathSegment::MoveTo(position)) => {
|
||||
Some(PathSegment::MoveTo(self.transform.transform_point(&position)))
|
||||
Some(PathCommand::MoveTo(position)) => {
|
||||
Some(PathCommand::MoveTo(self.transform.transform_point(&position)))
|
||||
}
|
||||
Some(PathSegment::LineTo(position)) => {
|
||||
Some(PathSegment::LineTo(self.transform.transform_point(&position)))
|
||||
Some(PathCommand::LineTo(position)) => {
|
||||
Some(PathCommand::LineTo(self.transform.transform_point(&position)))
|
||||
}
|
||||
Some(PathSegment::CurveTo(control_point_position, endpoint_position)) => {
|
||||
Some(PathSegment::CurveTo(self.transform.transform_point(&control_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(PathSegment::ClosePath) => Some(PathSegment::ClosePath),
|
||||
Some(PathCommand::ClosePath) => Some(PathCommand::ClosePath),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
//! Geometry utilities for straight line segments.
|
||||
|
||||
use euclid::approxeq::ApproxEq;
|
||||
use euclid::{Point2D, Vector2D};
|
||||
use euclid::{Point2D, Vector2D, Vector3D};
|
||||
|
||||
use intersection::Intersect;
|
||||
|
||||
|
@ -102,4 +102,31 @@ impl Line {
|
|||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@ use arrayvec::ArrayVec;
|
|||
use euclid::Point2D;
|
||||
use std::mem;
|
||||
|
||||
use PathSegment;
|
||||
use PathCommand;
|
||||
use curve::Curve;
|
||||
|
||||
pub struct MonotonicPathSegmentStream<I> {
|
||||
pub struct MonotonicPathCommandStream<I> {
|
||||
inner: I,
|
||||
queue: ArrayVec<[PathSegment; 2]>,
|
||||
queue: ArrayVec<[PathCommand; 2]>,
|
||||
prev_point: Point2D<f32>,
|
||||
}
|
||||
|
||||
impl<I> MonotonicPathSegmentStream<I> where I: Iterator<Item = PathSegment> {
|
||||
pub fn new(inner: I) -> MonotonicPathSegmentStream<I> {
|
||||
MonotonicPathSegmentStream {
|
||||
impl<I> MonotonicPathCommandStream<I> where I: Iterator<Item = PathCommand> {
|
||||
pub fn new(inner: I) -> MonotonicPathCommandStream<I> {
|
||||
MonotonicPathCommandStream {
|
||||
inner: inner,
|
||||
queue: ArrayVec::new(),
|
||||
prev_point: Point2D::zero(),
|
||||
|
@ -31,30 +31,30 @@ impl<I> MonotonicPathSegmentStream<I> where I: Iterator<Item = PathSegment> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for MonotonicPathSegmentStream<I> where I: Iterator<Item = PathSegment> {
|
||||
type Item = PathSegment;
|
||||
impl<I> Iterator for MonotonicPathCommandStream<I> where I: Iterator<Item = PathCommand> {
|
||||
type Item = PathCommand;
|
||||
|
||||
fn next(&mut self) -> Option<PathSegment> {
|
||||
fn next(&mut self) -> Option<PathCommand> {
|
||||
if !self.queue.is_empty() {
|
||||
return Some(self.queue.remove(0))
|
||||
}
|
||||
|
||||
match self.inner.next() {
|
||||
None => None,
|
||||
Some(PathSegment::ClosePath) => Some(PathSegment::ClosePath),
|
||||
Some(PathSegment::MoveTo(point)) => {
|
||||
Some(PathCommand::ClosePath) => Some(PathCommand::ClosePath),
|
||||
Some(PathCommand::MoveTo(point)) => {
|
||||
self.prev_point = point;
|
||||
Some(PathSegment::MoveTo(point))
|
||||
Some(PathCommand::MoveTo(point))
|
||||
}
|
||||
Some(PathSegment::LineTo(point)) => {
|
||||
Some(PathCommand::LineTo(point)) => {
|
||||
self.prev_point = point;
|
||||
Some(PathSegment::LineTo(point))
|
||||
Some(PathCommand::LineTo(point))
|
||||
}
|
||||
Some(PathSegment::CurveTo(control_point, endpoint)) => {
|
||||
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(PathSegment::CurveTo(control_point, endpoint)),
|
||||
(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_segment());
|
||||
|
|
|
@ -8,110 +8,113 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use freetype_sys::{FT_Init_FreeType, FT_Library, FT_Outline, FT_STROKER_LINECAP_BUTT, FT_Stroker};
|
||||
use freetype_sys::{FT_STROKER_LINEJOIN_ROUND, FT_Stroker_BeginSubPath, FT_Stroker_ConicTo};
|
||||
use freetype_sys::{FT_Stroker_Done, FT_Stroker_EndSubPath, FT_Stroker_Export};
|
||||
use freetype_sys::{FT_Stroker_GetCounts, FT_Stroker_LineTo, FT_Stroker_New, FT_Stroker_Set};
|
||||
use freetype_sys::{FT_UInt, FT_Vector};
|
||||
use std::i16;
|
||||
use std::u32;
|
||||
|
||||
use freetype::{self, OutlineStream};
|
||||
use {PathBuffer, PathSegment};
|
||||
use {Endpoint, PathBuffer, PathCommand, Subpath};
|
||||
use line::Line;
|
||||
|
||||
const EPSILON_POSITION_OFFSET: i64 = 8;
|
||||
|
||||
thread_local! {
|
||||
pub static FREETYPE_LIBRARY: FT_Library = unsafe {
|
||||
let mut library = 0 as FT_Library;
|
||||
assert!(FT_Init_FreeType(&mut library) == 0);
|
||||
library
|
||||
};
|
||||
pub struct Stroke {
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
pub fn stroke<I>(output: &mut PathBuffer, stream: I, stroke_width: f32)
|
||||
where I: Iterator<Item = PathSegment> {
|
||||
unsafe {
|
||||
let mut stroker = 0 as FT_Stroker;
|
||||
FREETYPE_LIBRARY.with(|&library| {
|
||||
assert!(FT_Stroker_New(library, &mut stroker) == 0);
|
||||
});
|
||||
impl Stroke {
|
||||
#[inline]
|
||||
pub fn new(width: f32) -> Stroke {
|
||||
Stroke {
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Make line caps and line join customizable.
|
||||
let stroke_width = freetype::f32_to_26_6_ft_fixed(stroke_width);
|
||||
FT_Stroker_Set(stroker,
|
||||
stroke_width,
|
||||
FT_STROKER_LINECAP_BUTT,
|
||||
FT_STROKER_LINEJOIN_ROUND,
|
||||
0);
|
||||
pub fn apply<I>(&self, output: &mut PathBuffer, stream: I)
|
||||
where I: Iterator<Item = PathCommand> {
|
||||
let mut input = PathBuffer::new();
|
||||
input.add_stream(stream);
|
||||
|
||||
let mut first_position_in_subpath = None;
|
||||
for segment in stream {
|
||||
match segment {
|
||||
PathSegment::MoveTo(position) => {
|
||||
if first_position_in_subpath.is_some() {
|
||||
assert!(FT_Stroker_EndSubPath(stroker) == 0);
|
||||
}
|
||||
let mut position = freetype::f32_to_ft_vector(&position);
|
||||
first_position_in_subpath = Some(position);
|
||||
assert!(FT_Stroker_BeginSubPath(stroker, &mut position, 1) == 0);
|
||||
for subpath_index in 0..(input.subpaths.len() as u32) {
|
||||
let first_endpoint_index = output.endpoints.len() as u32;
|
||||
|
||||
// FIXME(pcwalton): This is a really bad hack to guard against segfaults in
|
||||
// FreeType when paths are empty (e.g. moveto plus closepath).
|
||||
let mut epsilon_position = FT_Vector {
|
||||
x: position.x + EPSILON_POSITION_OFFSET,
|
||||
y: position.y,
|
||||
};
|
||||
assert!(FT_Stroker_LineTo(stroker, &mut epsilon_position) == 0);
|
||||
}
|
||||
PathSegment::LineTo(position) => {
|
||||
let mut position = freetype::f32_to_ft_vector(&position);
|
||||
assert!(FT_Stroker_LineTo(stroker, &mut position) == 0);
|
||||
}
|
||||
PathSegment::CurveTo(control_point_position, endpoint_position) => {
|
||||
let mut control_point_position =
|
||||
freetype::f32_to_ft_vector(&control_point_position);
|
||||
let mut endpoint_position = freetype::f32_to_ft_vector(&endpoint_position);
|
||||
assert!(FT_Stroker_ConicTo(stroker,
|
||||
&mut control_point_position,
|
||||
&mut endpoint_position) == 0);
|
||||
}
|
||||
PathSegment::ClosePath => {
|
||||
if let Some(mut first_position_in_subpath) = first_position_in_subpath {
|
||||
assert!(FT_Stroker_LineTo(stroker, &mut first_position_in_subpath) == 0);
|
||||
assert!(FT_Stroker_EndSubPath(stroker) == 0);
|
||||
}
|
||||
first_position_in_subpath = None;
|
||||
// Compute offset curves.
|
||||
//
|
||||
// TODO(pcwalton): Support line caps.
|
||||
self.offset_subpath(output, &input, subpath_index);
|
||||
input.reverse_subpath(subpath_index);
|
||||
self.offset_subpath(output, &input, subpath_index);
|
||||
|
||||
// Close the path.
|
||||
if !output.endpoints.is_empty() {
|
||||
let first_endpoint = output.endpoints[first_endpoint_index as usize];
|
||||
output.endpoints.push(first_endpoint);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO(pcwalton): Miter and round joins.
|
||||
fn offset_subpath(&self, output: &mut PathBuffer, input: &PathBuffer, subpath_index: u32) {
|
||||
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(self.width);
|
||||
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(self.width);
|
||||
let offset_line_1 =
|
||||
Line::new(control_point_position, position).offset(self.width);
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
if first_position_in_subpath.is_some() {
|
||||
assert!(FT_Stroker_EndSubPath(stroker) == 0)
|
||||
}
|
||||
|
||||
let (mut anum_points, mut anum_contours) = (0, 0);
|
||||
assert!(FT_Stroker_GetCounts(stroker, &mut anum_points, &mut anum_contours) == 0);
|
||||
assert!(anum_points <= i16::MAX as FT_UInt && anum_contours <= i16::MAX as FT_UInt);
|
||||
|
||||
let mut outline_points = vec![FT_Vector { x: 0, y: 0 }; anum_points as usize];
|
||||
let mut outline_tags = vec![0; anum_points as usize];
|
||||
let mut outline_contours = vec![0; anum_contours as usize];
|
||||
|
||||
let mut outline = FT_Outline {
|
||||
n_contours: 0,
|
||||
n_points: 0,
|
||||
|
||||
points: outline_points.as_mut_ptr(),
|
||||
tags: outline_tags.as_mut_ptr(),
|
||||
contours: outline_contours.as_mut_ptr(),
|
||||
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
FT_Stroker_Export(stroker, &mut outline);
|
||||
|
||||
FT_Stroker_Done(stroker);
|
||||
|
||||
output.add_stream(OutlineStream::new(&outline, 1.0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,20 +12,20 @@
|
|||
|
||||
use std::io::{self, Write};
|
||||
|
||||
use PathSegment;
|
||||
use PathCommand;
|
||||
|
||||
pub fn to_svg_description<W, S>(output: &mut W, stream: S) -> io::Result<()>
|
||||
where W: Write, S: Iterator<Item = PathSegment> {
|
||||
where W: Write, S: Iterator<Item = PathCommand> {
|
||||
for segment in stream {
|
||||
match segment {
|
||||
PathSegment::MoveTo(point) => try!(write!(output, "M{},{} ", point.x, point.y)),
|
||||
PathSegment::LineTo(point) => try!(write!(output, "L{},{} ", point.x, point.y)),
|
||||
PathSegment::CurveTo(control_point, endpoint) => {
|
||||
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))
|
||||
}
|
||||
PathSegment::ClosePath => try!(output.write_all(b"z")),
|
||||
PathCommand::ClosePath => try!(output.write_all(b"z")),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in New Issue