Implement stroking ourselves, and rename `PathSegment` to `PathCommand`.

This makes Pathfinder no longer use the FreeType stroker.
This commit is contained in:
Patrick Walton 2017-10-04 11:06:41 -07:00
parent 22d0eb7b24
commit 8de1970682
9 changed files with 286 additions and 171 deletions

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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>) {

View File

@ -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))
}
}
}

View File

@ -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),
}
}
}

View File

@ -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))
}
}

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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(())