Partially refactor the path APIs to be streaming, like Lyon
This commit is contained in:
parent
57374e9f30
commit
3e5b53f13c
|
@ -1,5 +1,6 @@
|
|||
/font-renderer/target
|
||||
/partitioner/target
|
||||
/path-utils/target
|
||||
/demo/client/target
|
||||
/demo/client/*.html
|
||||
/demo/client/*.js
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -15,3 +15,6 @@ half = "1.0"
|
|||
log = "0.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[dependencies.pathfinder_path_utils]
|
||||
path = "../path-utils"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
Loading…
Reference in New Issue