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
|
/font-renderer/target
|
||||||
/partitioner/target
|
/partitioner/target
|
||||||
|
/path-utils/target
|
||||||
/demo/client/target
|
/demo/client/target
|
||||||
/demo/client/*.html
|
/demo/client/*.html
|
||||||
/demo/client/*.js
|
/demo/client/*.js
|
||||||
|
|
|
@ -4,7 +4,6 @@ version = "0.1.0"
|
||||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
app_units = "0.5"
|
|
||||||
base64 = "0.6"
|
base64 = "0.6"
|
||||||
bincode = "0.8"
|
bincode = "0.8"
|
||||||
env_logger = "0.3"
|
env_logger = "0.3"
|
||||||
|
@ -25,3 +24,6 @@ path = "../../font-renderer"
|
||||||
|
|
||||||
[dependencies.pathfinder_partitioner]
|
[dependencies.pathfinder_partitioner]
|
||||||
path = "../../partitioner"
|
path = "../../partitioner"
|
||||||
|
|
||||||
|
[dependencies.pathfinder_path_utils]
|
||||||
|
path = "../../path-utils"
|
||||||
|
|
|
@ -13,10 +13,10 @@ extern crate euclid;
|
||||||
extern crate fontsan;
|
extern crate fontsan;
|
||||||
extern crate pathfinder_font_renderer;
|
extern crate pathfinder_font_renderer;
|
||||||
extern crate pathfinder_partitioner;
|
extern crate pathfinder_partitioner;
|
||||||
|
extern crate pathfinder_path_utils;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
@ -24,10 +24,9 @@ extern crate serde_derive;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use bincode::Infinite;
|
use bincode::Infinite;
|
||||||
use euclid::{Point2D, Size2D, Transform2D};
|
use euclid::{Point2D, Size2D, Transform2D};
|
||||||
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey};
|
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey};
|
||||||
use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer};
|
|
||||||
use pathfinder_partitioner::partitioner::Partitioner;
|
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::http::{ContentType, Status};
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
use rocket::response::{NamedFile, Redirect, Responder, Response};
|
||||||
|
@ -394,19 +393,18 @@ fn partition_font(request: Json<PartitionFontRequest>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read glyph info.
|
// 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 subpath_indices: Vec<_> = request.glyphs.iter().map(|glyph| {
|
||||||
let glyph_key = GlyphKey::new(glyph.id);
|
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.
|
// This might fail; if so, just leave it blank.
|
||||||
drop(font_context.push_glyph_outline(&font_instance_key,
|
if let Ok(glyph_outline) = font_context.glyph_outline(&font_instance_key, &glyph_key) {
|
||||||
&glyph_key,
|
path_buffer.add_stream(Transform2DPathStream::new(glyph_outline, &glyph.transform))
|
||||||
&mut outline_buffer,
|
}
|
||||||
&glyph.transform));
|
|
||||||
|
|
||||||
let last_subpath_index = outline_buffer.subpaths.len();
|
let last_subpath_index = path_buffer.subpaths.len();
|
||||||
|
|
||||||
SubpathRange {
|
SubpathRange {
|
||||||
start: first_subpath_index as u32,
|
start: first_subpath_index as u32,
|
||||||
|
@ -416,9 +414,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
|
||||||
|
|
||||||
// Partition the decoded glyph outlines.
|
// Partition the decoded glyph outlines.
|
||||||
let mut partitioner = Partitioner::new();
|
let mut partitioner = Partitioner::new();
|
||||||
partitioner.init(&outline_buffer.endpoints,
|
partitioner.init_with_path_buffer(&path_buffer);
|
||||||
&outline_buffer.control_points,
|
|
||||||
&outline_buffer.subpaths);
|
|
||||||
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices);
|
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices);
|
||||||
|
|
||||||
// Package up other miscellaneous glyph info.
|
// 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`
|
// The client has already normalized it, so we only have to handle `M`, `L`, `C`, and `Z`
|
||||||
// commands.
|
// commands.
|
||||||
let (mut endpoints, mut control_points, mut subpaths) = (vec![], vec![], vec![]);
|
let mut path_buffer = PathBuffer::new();
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
for path in &request.paths {
|
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 {
|
for segment in &path.segments {
|
||||||
match segment.kind {
|
match segment.kind {
|
||||||
'M' => {
|
'M' => {
|
||||||
if first_endpoint_index_in_subpath < endpoints.len() {
|
if first_endpoint_index_in_subpath < path_buffer.endpoints.len() {
|
||||||
subpaths.push(Subpath {
|
path_buffer.subpaths.push(Subpath {
|
||||||
first_endpoint_index: first_endpoint_index_in_subpath as u32,
|
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),
|
position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32),
|
||||||
control_point_index: u32::MAX,
|
control_point_index: u32::MAX,
|
||||||
subpath_index: subpaths.len() as u32,
|
subpath_index: path_buffer.subpaths.len() as u32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
'L' => {
|
'L' => {
|
||||||
endpoints.push(Endpoint {
|
path_buffer.endpoints.push(Endpoint {
|
||||||
position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32),
|
position: Point2D::new(segment.values[0] as f32, segment.values[1] as f32),
|
||||||
control_point_index: u32::MAX,
|
control_point_index: u32::MAX,
|
||||||
subpath_index: subpaths.len() as u32,
|
subpath_index: path_buffer.subpaths.len() as u32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
'C' => {
|
'C' => {
|
||||||
|
@ -495,25 +491,25 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
||||||
let control_point_1 = Point2D::new(segment.values[2] as f32,
|
let control_point_1 = Point2D::new(segment.values[2] as f32,
|
||||||
segment.values[3] as f32);
|
segment.values[3] as f32);
|
||||||
let control_point = control_point_0.lerp(control_point_1, 0.5);
|
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),
|
position: Point2D::new(segment.values[4] as f32, segment.values[5] as f32),
|
||||||
control_point_index: control_points.len() as u32,
|
control_point_index: path_buffer.control_points.len() as u32,
|
||||||
subpath_index: subpaths.len() as u32,
|
subpath_index: path_buffer.subpaths.len() as u32,
|
||||||
});
|
});
|
||||||
control_points.push(control_point);
|
path_buffer.control_points.push(control_point);
|
||||||
}
|
}
|
||||||
'Z' => {
|
'Z' => {
|
||||||
subpaths.push(Subpath {
|
path_buffer.subpaths.push(Subpath {
|
||||||
first_endpoint_index: first_endpoint_index_in_subpath as u32,
|
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)),
|
_ => 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 {
|
paths.push(SubpathRange {
|
||||||
start: first_subpath_index,
|
start: first_subpath_index,
|
||||||
end: last_subpath_index,
|
end: last_subpath_index,
|
||||||
|
@ -522,7 +518,7 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
|
||||||
|
|
||||||
// Partition the paths.
|
// Partition the paths.
|
||||||
let mut partitioner = Partitioner::new();
|
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);
|
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &paths);
|
||||||
|
|
||||||
// Return the response.
|
// Return the response.
|
||||||
|
|
|
@ -10,10 +10,11 @@ log = "0.3"
|
||||||
|
|
||||||
[dependencies.freetype-sys]
|
[dependencies.freetype-sys]
|
||||||
git = "https://github.com/pcwalton/freetype-sys.git"
|
git = "https://github.com/pcwalton/freetype-sys.git"
|
||||||
branch = "outline-get-cbox"
|
branch = "stroker"
|
||||||
|
rev = "11c67b2a215ec5d1aefd465a9b0560b9784ca672"
|
||||||
|
|
||||||
[dependencies.pathfinder_partitioner]
|
[dependencies.pathfinder_path_utils]
|
||||||
path = "../partitioner"
|
path = "../path-utils"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.4"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
extern crate app_units;
|
extern crate app_units;
|
||||||
extern crate euclid;
|
extern crate euclid;
|
||||||
extern crate freetype_sys;
|
extern crate freetype_sys;
|
||||||
extern crate pathfinder_partitioner;
|
extern crate pathfinder_path_utils;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -13,14 +13,16 @@ extern crate log;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
|
||||||
use app_units::Au;
|
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_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_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_Load_Glyph, FT_Long, FT_New_Memory_Face, FT_Outline_Get_CBox};
|
||||||
use freetype_sys::{FT_Set_Char_Size, FT_UInt};
|
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::BTreeMap;
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
|
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
|
||||||
|
@ -33,8 +35,6 @@ mod tests;
|
||||||
// TODO(pcwalton): Make this configurable.
|
// TODO(pcwalton): Make this configurable.
|
||||||
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
|
const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_NO_HINTING;
|
||||||
|
|
||||||
const FREETYPE_POINT_ON_CURVE: i8 = 0x01;
|
|
||||||
|
|
||||||
const DPI: u32 = 72;
|
const DPI: u32 = 72;
|
||||||
|
|
||||||
pub struct FontContext {
|
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,
|
pub fn push_glyph_outline(&self,
|
||||||
font_instance: &FontInstanceKey,
|
font_instance: &FontInstanceKey,
|
||||||
glyph_key: &GlyphKey,
|
glyph_key: &GlyphKey,
|
||||||
glyph_outline_buffer: &mut GlyphOutlineBuffer,
|
path_buffer: &mut PathBuffer)
|
||||||
transform: &Transform2D<f32>)
|
|
||||||
-> Result<(), ()> {
|
-> Result<(), ()> {
|
||||||
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
|
self.load_glyph(font_instance, glyph_key).ok_or(()).map(|glyph_slot| {
|
||||||
self.push_glyph_outline_from_glyph_slot(font_instance,
|
unsafe {
|
||||||
glyph_key,
|
let outline = &(*glyph_slot).outline;
|
||||||
glyph_slot,
|
for contour_index in 0..outline.n_contours as u16 {
|
||||||
glyph_outline_buffer,
|
path_buffer.add_subpath_from_stream(OutlineIter::new(outline, contour_index))
|
||||||
transform)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}*/
|
||||||
|
|
||||||
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
|
fn load_glyph(&self, font_instance: &FontInstanceKey, glyph_key: &GlyphKey)
|
||||||
-> Option<FT_GlyphSlot> {
|
-> Option<FT_GlyphSlot> {
|
||||||
|
@ -174,73 +187,17 @@ impl FontContext {
|
||||||
|
|
||||||
bounding_box
|
bounding_box
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn push_glyph_outline_from_glyph_slot(&self,
|
pub struct GlyphOutline<'a> {
|
||||||
_: &FontInstanceKey,
|
stream: OutlineStream<'static>,
|
||||||
_: &GlyphKey,
|
phantom: PhantomData<&'a ()>,
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an implied endpoint if necessary.
|
impl<'a> Iterator for GlyphOutline<'a> {
|
||||||
let mut control_points = &mut glyph_outline_buffer.control_points;
|
type Item = PathSegment;
|
||||||
if let Some(prev_control_point_index) = current_control_point_index.take() {
|
fn next(&mut self) -> Option<PathSegment> {
|
||||||
let prev_control_point_position =
|
self.stream.next()
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,23 +254,6 @@ pub struct GlyphDimensions {
|
||||||
pub advance: f32,
|
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 {
|
struct Face {
|
||||||
face: FT_Face,
|
face: FT_Face,
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
|
|
|
@ -15,3 +15,6 @@ half = "1.0"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "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,
|
control_point_count: u32,
|
||||||
subpaths: *const Subpath,
|
subpaths: *const Subpath,
|
||||||
subpath_count: u32) {
|
subpath_count: u32) {
|
||||||
// FIXME(pcwalton): This is unsafe! `Point2D<f32>` and `Point2DF32` may have different layouts!
|
(*partitioner).init_with_raw_data(slice::from_raw_parts(endpoints, endpoint_count as usize),
|
||||||
(*partitioner).init(slice::from_raw_parts(endpoints, endpoint_count as usize),
|
|
||||||
slice::from_raw_parts(control_points as *const Point2D<f32>,
|
slice::from_raw_parts(control_points as *const Point2D<f32>,
|
||||||
control_point_count as usize),
|
control_point_count as usize),
|
||||||
slice::from_raw_parts(subpaths, subpath_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
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
|
use euclid::Point2D;
|
||||||
use euclid::approxeq::ApproxEq;
|
use euclid::approxeq::ApproxEq;
|
||||||
use euclid::{Point2D, Vector2D};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
pub(crate) trait ApproxOrdered {
|
pub(crate) trait ApproxOrdered {
|
||||||
|
|
|
@ -18,11 +18,13 @@ extern crate euclid;
|
||||||
extern crate half;
|
extern crate half;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
extern crate pathfinder_path_utils;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
use euclid::Point2D;
|
use euclid::Point2D;
|
||||||
|
use pathfinder_path_utils::{Endpoint, Subpath};
|
||||||
use std::u32;
|
use std::u32;
|
||||||
|
|
||||||
pub mod capi;
|
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)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum AntialiasingMode {
|
pub enum AntialiasingMode {
|
||||||
|
|
|
@ -12,6 +12,7 @@ use bit_vec::BitVec;
|
||||||
use euclid::Point2D;
|
use euclid::Point2D;
|
||||||
use geometry::{self, SubdividedQuadraticBezier};
|
use geometry::{self, SubdividedQuadraticBezier};
|
||||||
use log::LogLevel;
|
use log::LogLevel;
|
||||||
|
use pathfinder_path_utils::PathBuffer;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::f32;
|
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_endpoints: &'a [Endpoint],
|
||||||
new_control_points: &'a [Point2D<f32>],
|
new_control_points: &'a [Point2D<f32>],
|
||||||
new_subpaths: &'a [Subpath]) {
|
new_subpaths: &'a [Subpath]) {
|
||||||
|
@ -72,6 +73,12 @@ impl<'a> Partitioner<'a> {
|
||||||
self.visited_points = BitVec::from_elem(self.endpoints.len(), false);
|
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) {
|
pub fn partition(&mut self, path_id: u16, first_subpath_index: u32, last_subpath_index: u32) {
|
||||||
self.b_quads.clear();
|
self.b_quads.clear();
|
||||||
self.b_vertex_loop_blinn_data.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