Partially refactor the path APIs to be streaming, like Lyon

This commit is contained in:
Patrick Walton 2017-09-08 13:09:00 -07:00
parent 57374e9f30
commit 3e5b53f13c
14 changed files with 378 additions and 159 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
/font-renderer/target
/partitioner/target
/path-utils/target
/demo/client/target
/demo/client/*.html
/demo/client/*.js

View File

@ -4,7 +4,6 @@ version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
app_units = "0.5"
base64 = "0.6"
bincode = "0.8"
env_logger = "0.3"
@ -25,3 +24,6 @@ path = "../../font-renderer"
[dependencies.pathfinder_partitioner]
path = "../../partitioner"
[dependencies.pathfinder_path_utils]
path = "../../path-utils"

View File

@ -13,10 +13,10 @@ extern crate euclid;
extern crate fontsan;
extern crate pathfinder_font_renderer;
extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils;
extern crate rocket;
extern crate rocket_contrib;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
@ -24,10 +24,9 @@ extern crate serde_derive;
use app_units::Au;
use bincode::Infinite;
use euclid::{Point2D, Size2D, Transform2D};
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey};
use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer};
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey};
use pathfinder_partitioner::partitioner::Partitioner;
use pathfinder_partitioner::{Endpoint, Subpath};
use pathfinder_path_utils::{Endpoint, PathBuffer, Subpath, Transform2DPathStream};
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{NamedFile, Redirect, Responder, Response};
@ -394,19 +393,18 @@ fn partition_font(request: Json<PartitionFontRequest>)
}
// Read glyph info.
let mut outline_buffer = GlyphOutlineBuffer::new();
let mut path_buffer = PathBuffer::new();
let subpath_indices: Vec<_> = request.glyphs.iter().map(|glyph| {
let glyph_key = GlyphKey::new(glyph.id);
let first_subpath_index = outline_buffer.subpaths.len();
let first_subpath_index = path_buffer.subpaths.len();
// This might fail; if so, just leave it blank.
drop(font_context.push_glyph_outline(&font_instance_key,
&glyph_key,
&mut outline_buffer,
&glyph.transform));
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance_key, &glyph_key) {
path_buffer.add_stream(Transform2DPathStream::new(glyph_outline, &glyph.transform))
}
let last_subpath_index = outline_buffer.subpaths.len();
let last_subpath_index = path_buffer.subpaths.len();
SubpathRange {
start: first_subpath_index as u32,
@ -416,9 +414,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
// Partition the decoded glyph outlines.
let mut partitioner = Partitioner::new();
partitioner.init(&outline_buffer.endpoints,
&outline_buffer.control_points,
&outline_buffer.subpaths);
partitioner.init_with_path_buffer(&path_buffer);
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices);
// Package up other miscellaneous glyph info.
@ -458,34 +454,34 @@ 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 endpoints, mut control_points, mut subpaths) = (vec![], vec![], vec![]);
let mut path_buffer = PathBuffer::new();
let mut paths = vec![];
for path in &request.paths {
let first_subpath_index = subpaths.len() as u32;
let first_subpath_index = path_buffer.subpaths.len() as u32;
let mut first_endpoint_index_in_subpath = endpoints.len();
let mut first_endpoint_index_in_subpath = path_buffer.endpoints.len();
for segment in &path.segments {
match segment.kind {
'M' => {
if first_endpoint_index_in_subpath < endpoints.len() {
subpaths.push(Subpath {
if first_endpoint_index_in_subpath < path_buffer.endpoints.len() {
path_buffer.subpaths.push(Subpath {
first_endpoint_index: first_endpoint_index_in_subpath as u32,
last_endpoint_index: endpoints.len() as u32,
last_endpoint_index: path_buffer.endpoints.len() as u32,
});
first_endpoint_index_in_subpath = endpoints.len();
first_endpoint_index_in_subpath = path_buffer.endpoints.len();
}
endpoints.push(Endpoint {
path_buffer.endpoints.push(Endpoint {
position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32),
control_point_index: u32::MAX,
subpath_index: subpaths.len() as u32,
subpath_index: path_buffer.subpaths.len() as u32,
})
}
'L' => {
endpoints.push(Endpoint {
path_buffer.endpoints.push(Endpoint {
position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32),
control_point_index: u32::MAX,
subpath_index: subpaths.len() as u32,
subpath_index: path_buffer.subpaths.len() as u32,
})
}
'C' => {
@ -495,25 +491,25 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
let control_point_1 = Point2D::new(segment.values[2] as f32,
segment.values[3] as f32);
let control_point = control_point_0.lerp(control_point_1, 0.5);
endpoints.push(Endpoint {
path_buffer.endpoints.push(Endpoint {
position: Point2D::new(segment.values[4] as f32, segment.values[5] as f32),
control_point_index: control_points.len() as u32,
subpath_index: subpaths.len() as u32,
control_point_index: path_buffer.control_points.len() as u32,
subpath_index: path_buffer.subpaths.len() as u32,
});
control_points.push(control_point);
path_buffer.control_points.push(control_point);
}
'Z' => {
subpaths.push(Subpath {
path_buffer.subpaths.push(Subpath {
first_endpoint_index: first_endpoint_index_in_subpath as u32,
last_endpoint_index: endpoints.len() as u32,
last_endpoint_index: path_buffer.endpoints.len() as u32,
});
first_endpoint_index_in_subpath = endpoints.len();
first_endpoint_index_in_subpath = path_buffer.endpoints.len();
}
_ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)),
}
}
let last_subpath_index = subpaths.len() as u32;
let last_subpath_index = path_buffer.subpaths.len() as u32;
paths.push(SubpathRange {
start: first_subpath_index,
end: last_subpath_index,
@ -522,7 +518,7 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
// Partition the paths.
let mut partitioner = Partitioner::new();
partitioner.init(&endpoints, &control_points, &subpaths);
partitioner.init_with_path_buffer(&path_buffer);
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &paths);
// Return the response.

View File

@ -10,10 +10,11 @@ log = "0.3"
[dependencies.freetype-sys]
git = "https://github.com/pcwalton/freetype-sys.git"
branch = "outline-get-cbox"
branch = "stroker"
rev = "11c67b2a215ec5d1aefd465a9b0560b9784ca672"
[dependencies.pathfinder_partitioner]
path = "../partitioner"
[dependencies.pathfinder_path_utils]
path = "../path-utils"
[dev-dependencies]
env_logger = "0.4"

View File

@ -3,7 +3,7 @@
extern crate app_units;
extern crate euclid;
extern crate freetype_sys;
extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils;
#[allow(unused_imports)]
#[macro_use]
@ -13,14 +13,16 @@ extern crate log;
extern crate env_logger;
use app_units::Au;
use euclid::{Point2D, Size2D, Transform2D};
use euclid::{Point2D, Size2D};
use freetype_sys::{FT_BBox, FT_Done_Face, FT_F26Dot6, FT_Face, FT_GLYPH_FORMAT_OUTLINE};
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_partitioner::{Endpoint, Subpath};
use pathfinder_path_utils::PathSegment;
use pathfinder_path_utils::freetype::OutlineStream;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
@ -33,8 +35,6 @@ mod tests;
// TODO(pcwalton): Make this configurable.
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
const DPI: u32 = 72;
pub struct FontContext {
@ -92,20 +92,33 @@ impl FontContext {
})
}
pub fn glyph_outline<'a>(&'a mut self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> 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,
}
}
})
}
/*
pub fn push_glyph_outline(&self,
font_instance: &FontInstanceKey,
glyph_key: &GlyphKey,
glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>)
path_buffer: &mut PathBuffer)
-> Result<(), ()> {
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
self.push_glyph_outline_from_glyph_slot(font_instance,
glyph_key,
glyph_slot,
glyph_outline_buffer,
transform)
})
unsafe {
let outline = &(*glyph_slot).outline;
for contour_index in 0..outline.n_contours as u16 {
path_buffer.add_subpath_from_stream(OutlineIter::new(outline, contour_index))
}
}
})
}*/
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
-> Option<FT_GlyphSlot> {
@ -174,73 +187,17 @@ impl FontContext {
bounding_box
}
}
fn push_glyph_outline_from_glyph_slot(&self,
_: &FontInstanceKey,
_: &GlyphKey,
glyph_slot: FT_GlyphSlot,
glyph_outline_buffer: &mut GlyphOutlineBuffer,
transform: &Transform2D<f32>) {
unsafe {
let outline = &(*glyph_slot).outline;
let mut first_point_index = 0 as u32;
let mut first_endpoint_index = glyph_outline_buffer.endpoints.len() as u32;
for contour_index in 0..outline.n_contours as usize {
let current_subpath_index = glyph_outline_buffer.subpaths.len() as u32;
let mut current_control_point_index = None;
let last_point_index = *outline.contours.offset(contour_index as isize) as u32 + 1;
for point_index in first_point_index..last_point_index {
// TODO(pcwalton): Approximate cubic Béziers with quadratics.
let point = *outline.points.offset(point_index as isize);
let point_position = Point2D::new(f32::from_ft_f26dot6(point.x as FT_F26Dot6),
f32::from_ft_f26dot6(point.y as FT_F26Dot6));
let point_position = point_position * (DPI as f32);
let point_position = transform.transform_point(&point_position);
if (*outline.tags.offset(point_index as isize) & FREETYPE_POINT_ON_CURVE) != 0 {
glyph_outline_buffer.endpoints.push(Endpoint {
position: point_position,
control_point_index: current_control_point_index.take().unwrap_or(!0),
subpath_index: current_subpath_index,
});
continue
}
pub struct GlyphOutline<'a> {
stream: OutlineStream<'static>,
phantom: PhantomData<&'a ()>,
}
// Add an implied endpoint if necessary.
let mut control_points = &mut glyph_outline_buffer.control_points;
if let Some(prev_control_point_index) = current_control_point_index.take() {
let prev_control_point_position =
control_points[prev_control_point_index as usize];
glyph_outline_buffer.endpoints.push(Endpoint {
position: prev_control_point_position.lerp(point_position, 0.5),
control_point_index: prev_control_point_index,
subpath_index: current_subpath_index,
})
}
current_control_point_index = Some(control_points.len() as u32);
control_points.push(point_position)
}
if let Some(last_control_point_index) = current_control_point_index.take() {
let first_endpoint = glyph_outline_buffer.endpoints[first_endpoint_index as
usize];
glyph_outline_buffer.endpoints.push(Endpoint {
position: first_endpoint.position,
control_point_index: last_control_point_index,
subpath_index: current_subpath_index,
})
}
let last_endpoint_index = glyph_outline_buffer.endpoints.len() as u32;
glyph_outline_buffer.subpaths.push(Subpath {
first_endpoint_index: first_endpoint_index,
last_endpoint_index: last_endpoint_index,
});
first_endpoint_index = last_endpoint_index;
first_point_index = last_point_index;
}
}
impl<'a> Iterator for GlyphOutline<'a> {
type Item = PathSegment;
fn next(&mut self) -> Option<PathSegment> {
self.stream.next()
}
}
@ -297,23 +254,6 @@ pub struct GlyphDimensions {
pub advance: f32,
}
pub struct GlyphOutlineBuffer {
pub endpoints: Vec<Endpoint>,
pub control_points: Vec<Point2D<f32>>,
pub subpaths: Vec<Subpath>,
}
impl GlyphOutlineBuffer {
#[inline]
pub fn new() -> GlyphOutlineBuffer {
GlyphOutlineBuffer {
endpoints: vec![],
control_points: vec![],
subpaths: vec![],
}
}
}
struct Face {
face: FT_Face,
bytes: Vec<u8>,

View File

@ -15,3 +15,6 @@ half = "1.0"
log = "0.3"
serde = "1.0"
serde_derive = "1.0"
[dependencies.pathfinder_path_utils]
path = "../path-utils"

View File

@ -157,8 +157,7 @@ pub unsafe extern fn pf_partitioner_init<'a>(partitioner: *mut Partitioner<'a>,
control_point_count: u32,
subpaths: *const Subpath,
subpath_count: u32) {
// FIXME(pcwalton): This is unsafe! `Point2D<f32>` and `Point2DF32` may have different layouts!
(*partitioner).init(slice::from_raw_parts(endpoints, endpoint_count as usize),
(*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))

View File

@ -8,8 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use euclid::Point2D;
use euclid::approxeq::ApproxEq;
use euclid::{Point2D, Vector2D};
use std::cmp::Ordering;
pub(crate) trait ApproxOrdered {

View File

@ -18,11 +18,13 @@ extern crate euclid;
extern crate half;
#[macro_use]
extern crate log;
extern crate pathfinder_path_utils;
extern crate serde;
#[macro_use]
extern crate serde_derive;
use euclid::Point2D;
use pathfinder_path_utils::{Endpoint, Subpath};
use std::u32;
pub mod capi;
@ -79,22 +81,6 @@ impl BQuad {
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Endpoint {
pub position: Point2D<f32>,
/// `u32::MAX` if not present.
pub control_point_index: u32,
pub subpath_index: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Subpath {
pub first_endpoint_index: u32,
pub last_endpoint_index: u32,
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(u8)]
pub enum AntialiasingMode {

View File

@ -12,6 +12,7 @@ use bit_vec::BitVec;
use euclid::Point2D;
use geometry::{self, SubdividedQuadraticBezier};
use log::LogLevel;
use pathfinder_path_utils::PathBuffer;
use std::collections::BinaryHeap;
use std::cmp::Ordering;
use std::f32;
@ -59,7 +60,7 @@ impl<'a> Partitioner<'a> {
}
}
pub fn init(&mut self,
pub fn init_with_raw_data(&mut self,
new_endpoints: &'a [Endpoint],
new_control_points: &'a [Point2D<f32>],
new_subpaths: &'a [Subpath]) {
@ -72,6 +73,12 @@ impl<'a> Partitioner<'a> {
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)
}
pub fn partition(&mut self, path_id: u16, first_subpath_index: u32, last_subpath_index: u32) {
self.b_quads.clear();
self.b_vertex_loop_blinn_data.clear();

13
path-utils/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "pathfinder_path_utils"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
euclid = "0.15"
serde = "1.0"
serde_derive = "1.0"
[dependencies.freetype-sys]
git = "https://github.com/pcwalton/freetype-sys.git"
branch = "stroker"

113
path-utils/src/freetype.rs Normal file
View File

@ -0,0 +1,113 @@
// pathfinder/path-utils/src/freetype.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.
use euclid::Point2D;
use freetype_sys::{FT_Outline, FT_Vector};
use PathSegment;
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
const DPI: f32 = 72.0;
pub struct OutlineStream<'a> {
outline: &'a FT_Outline,
point_index: u16,
contour_index: u16,
first_position_of_subpath: Point2D<f32>,
first_point_index_of_contour: bool,
}
impl<'a> OutlineStream<'a> {
#[inline]
pub unsafe fn new(outline: &FT_Outline) -> OutlineStream {
OutlineStream {
outline: outline,
point_index: 0,
contour_index: 0,
first_position_of_subpath: Point2D::zero(),
first_point_index_of_contour: true,
}
}
#[inline]
fn current_position_and_tag(&self) -> (Point2D<f32>, i8) {
unsafe {
let point_offset = self.point_index as isize;
let position = ft_vector_to_f32(*self.outline.points.offset(point_offset));
let tag = *self.outline.tags.offset(point_offset);
(position * DPI, tag)
}
}
}
impl<'a> Iterator for OutlineStream<'a> {
type Item = PathSegment;
fn next(&mut self) -> Option<PathSegment> {
unsafe {
let mut control_point_position: Option<Point2D<f32>> = None;
loop {
if self.contour_index == self.outline.n_contours as u16 {
return None
}
let last_point_index_in_current_contour =
*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,
self.first_position_of_subpath))
}
self.contour_index += 1;
self.first_point_index_of_contour = true;
return Some(PathSegment::ClosePath)
}
// FIXME(pcwalton): Approximate cubic curves with quadratics.
let (position, tag) = self.current_position_and_tag();
let point_on_curve = (tag & FREETYPE_POINT_ON_CURVE) != 0;
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(PathSegment::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,
on_curve_position))
}
(Some(control_point_position), true) => {
self.point_index += 1;
return Some(PathSegment::CurveTo(control_point_position, position))
}
(None, false) => {
self.point_index += 1;
control_point_position = Some(position);
}
(None, true) => {
self.point_index += 1;
return Some(PathSegment::LineTo(position))
}
}
}
}
}
}
#[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)
}

147
path-utils/src/lib.rs Normal file
View File

@ -0,0 +1,147 @@
// pathfinder/path-utils/src/lib.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.
extern crate euclid;
extern crate freetype_sys;
#[macro_use]
extern crate serde_derive;
use euclid::{Point2D, Transform2D};
use std::u32;
pub mod freetype;
pub mod stroke;
#[derive(Clone, Copy, Debug)]
pub enum PathSegment {
MoveTo(Point2D<f32>),
LineTo(Point2D<f32>),
CurveTo(Point2D<f32>, Point2D<f32>),
ClosePath,
}
#[derive(Clone, Debug)]
pub struct PathBuffer {
pub endpoints: Vec<Endpoint>,
pub control_points: Vec<Point2D<f32>>,
pub subpaths: Vec<Subpath>,
}
impl PathBuffer {
#[inline]
pub fn new() -> PathBuffer {
PathBuffer {
endpoints: vec![],
control_points: vec![],
subpaths: vec![],
}
}
pub fn add_stream<I>(&mut self, stream: I) where I: Iterator<Item = PathSegment> {
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),
PathSegment::MoveTo(position) => {
self.close_subpath(&mut first_subpath_endpoint_index);
self.endpoints.push(Endpoint {
position: position,
control_point_index: u32::MAX,
subpath_index: self.subpaths.len() as u32,
})
}
PathSegment::LineTo(position) => {
self.endpoints.push(Endpoint {
position: position,
control_point_index: u32::MAX,
subpath_index: self.subpaths.len() as u32,
})
}
PathSegment::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.close_subpath(&mut first_subpath_endpoint_index)
}
fn close_subpath(&mut self, first_subpath_endpoint_index: &mut u32) {
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,
})
}
*first_subpath_endpoint_index = last_subpath_endpoint_index;
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Endpoint {
pub position: Point2D<f32>,
/// `u32::MAX` if not present.
pub control_point_index: u32,
pub subpath_index: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Subpath {
pub first_endpoint_index: u32,
pub last_endpoint_index: u32,
}
pub struct Transform2DPathStream<I> {
inner: I,
transform: Transform2D<f32>,
}
impl<I> Transform2DPathStream<I> where I: Iterator<Item = PathSegment> {
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 = PathSegment> {
type Item = PathSegment;
fn next(&mut self) -> Option<PathSegment> {
match self.inner.next() {
None => None,
Some(PathSegment::MoveTo(position)) => {
Some(PathSegment::MoveTo(self.transform.transform_point(&position)))
}
Some(PathSegment::LineTo(position)) => {
Some(PathSegment::LineTo(self.transform.transform_point(&position)))
}
Some(PathSegment::CurveTo(control_point_position, endpoint_position)) => {
Some(PathSegment::CurveTo(self.transform.transform_point(&control_point_position),
self.transform.transform_point(&endpoint_position)))
}
Some(PathSegment::ClosePath) => Some(PathSegment::ClosePath),
}
}
}

11
path-utils/src/stroke.rs Normal file
View File

@ -0,0 +1,11 @@
// pathfinder/path-utils/src/stroke.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.
// TODO(pcwalton)