diff --git a/.gitignore b/.gitignore index f260509b..dfae4132 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /font-renderer/target /partitioner/target +/demo/client/target +/demo/server/target Cargo.lock # Editors diff --git a/demo/client/index.ts b/demo/client/index.ts deleted file mode 100644 index 0d7d14d9..00000000 --- a/demo/client/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// pathfinder/demo/index.ts - -const liveServer = require('live-server'); - -const serverParams = { - open: false, - file: "index.html", - mount: [ - ["/css/bootstrap", "node_modules/bootstrap/dist/css"], - ["/js/bootstrap", "node_modules/bootstrap/dist/js"], - ["/js/jquery", "node_modules/jquery/dist"], - ["/js/pathfinder.js", "pathfinder.js"] - ] -}; - -liveServer.start(serverParams); diff --git a/demo/client/package.json b/demo/client/package.json index 32320821..a8b7a9c0 100644 --- a/demo/client/package.json +++ b/demo/client/package.json @@ -2,7 +2,6 @@ "name": "pathfinder-demo", "version": "0.1.0", "description": "Demo for Pathfinder 2", - "main": "index.js", "scripts": { "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" @@ -12,7 +11,6 @@ "dependencies": { "@types/node": "^8.0.19", "bootstrap": "^4.0.0-alpha.6", - "live-server": "^1.2.0", "opentype.js": "^0.7.3", "ts-loader": "^2.3.2", "typescript": "^2.4.2", diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json index b6f7167e..a92f59c9 100644 --- a/demo/client/tsconfig.json +++ b/demo/client/tsconfig.json @@ -11,7 +11,6 @@ "sourceMap": true }, "include": [ - "index.ts", "src" ], "exclude": [ diff --git a/demo/server/Cargo.toml b/demo/server/Cargo.toml index 9f3a4127..f354039d 100644 --- a/demo/server/Cargo.toml +++ b/demo/server/Cargo.toml @@ -4,3 +4,25 @@ version = "0.1.0" authors = ["Patrick Walton "] [dependencies] +app_units = "0.5" +base64 = "0.6" +bincode = "0.8" +euclid = "0.15" +rocket = "0.3" +rocket_codegen = "0.3" +rocket_contrib = "0.3" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" + +[dependencies.fontsan] +git = "https://github.com/servo/fontsan.git" + +[dependencies.opentype-sanitizer-sys] +git = "https://github.com/pcwalton/opentype-sanitizer-sys.git" + +[dependencies.pathfinder_font_renderer] +path = "../../font-renderer" + +[dependencies.pathfinder_partitioner] +path = "../../partitioner" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index e7a11a96..4d57c348 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -1,3 +1,252 @@ -fn main() { - println!("Hello, world!"); +// pathfinder/demo/server/main.rs +// +// Copyright © 2017 Mozilla Foundation + +#![feature(plugin)] +#![plugin(rocket_codegen)] + +extern crate app_units; +extern crate base64; +extern crate bincode; +extern crate euclid; +extern crate fontsan; +extern crate opentype_sanitizer_sys; +extern crate pathfinder_font_renderer; +extern crate pathfinder_partitioner; +extern crate rocket; +extern crate rocket_contrib; +extern crate serde_json; + +#[macro_use] +extern crate serde_derive; + +use app_units::Au; +use bincode::Infinite; +use euclid::{Point2D, Size2D}; +use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey}; +use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer}; +use pathfinder_partitioner::partitioner::Partitioner; +use rocket::response::NamedFile; +use rocket_contrib::json::Json; +use std::io; +use std::path::{Path, PathBuf}; + +static STATIC_ROOT_PATH: &'static str = "../client/index.html"; +static STATIC_CSS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/css"; +static STATIC_JS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/js"; +static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist"; +static STATIC_JS_PATHFINDER_JS_PATH: &'static str = "../client/pathfinder.js"; + +#[derive(Clone, Copy, Serialize, Deserialize)] +struct IndexRange { + start: usize, + end: usize, +} + +impl IndexRange { + fn new(start: usize, end: usize) -> IndexRange { + IndexRange { + start: start, + end: end, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +struct PartitionFontRequest { + // Base64 encoded. + otf: String, + font_index: u32, + glyph_ids: Vec, + point_size: f64, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +struct PartitionGlyphDimensions { + origin: Point2D, + size: Size2D, + advance: f32, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +struct DecodedOutlineIndices { + endpoint_indices: IndexRange, + control_point_indices: IndexRange, + subpath_indices: IndexRange, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +struct PartitionGlyphInfo { + id: u32, + dimensions: PartitionGlyphDimensions, + b_quad_indices: IndexRange, + b_vertex_indices: IndexRange, +} + +#[derive(Clone, Serialize, Deserialize)] +struct PartitionFontResponse { + glyph_info: Vec, + // Base64-encoded `bincode`-encoded `BQuad`s. + b_quads: String, + // Base64-encoded `bincode`-encoded `BVertex`es. + b_vertices: String, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +enum PartitionFontError { + Base64DecodingFailed, + FontSanitizationFailed, + FontLoadingFailed, + BincodeSerializationFailed, + Unimplemented, +} + +#[post("/partition-font", format = "application/json", data = "")] +fn partition_font(request: Json) + -> Json> { + let unsafe_otf_data = match base64::decode(&request.otf) { + Ok(unsafe_otf_data) => unsafe_otf_data, + Err(_) => return Json(Err(PartitionFontError::Base64DecodingFailed)), + }; + + // Sanitize. + let otf_data = match fontsan::process(&unsafe_otf_data) { + Ok(otf_data) => otf_data, + Err(_) => return Json(Err(PartitionFontError::FontSanitizationFailed)), + }; + + // Parse glyph data. + let font_key = FontKey::new(); + let font_instance_key = FontInstanceKey { + font_key: font_key, + size: Au::from_f64_px(request.point_size), + }; + let mut font_context = FontContext::new(); + if font_context.add_font_from_memory(&font_key, otf_data, request.font_index).is_err() { + return Json(Err(PartitionFontError::FontLoadingFailed)) + } + + // Read glyph info. + let mut outline_buffer = GlyphOutlineBuffer::new(); + let decoded_outline_indices: Vec<_> = request.glyph_ids.iter().map(|&glyph_id| { + let glyph_key = GlyphKey::new(glyph_id); + + let first_endpoint_index = outline_buffer.endpoints.len(); + let first_control_point_index = outline_buffer.control_points.len(); + let first_subpath_index = outline_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)); + + let last_endpoint_index = outline_buffer.endpoints.len(); + let last_control_point_index = outline_buffer.control_points.len(); + let last_subpath_index = outline_buffer.subpaths.len(); + + DecodedOutlineIndices { + endpoint_indices: IndexRange::new(first_endpoint_index, last_endpoint_index), + control_point_indices: IndexRange::new(first_control_point_index, + last_control_point_index), + subpath_indices: IndexRange::new(first_subpath_index, last_subpath_index), + } + }).collect(); + + // Partition the decoded glyph outlines. + let mut partitioner = Partitioner::new(); + let (mut b_quad_count, mut b_vertex_count) = (0, 0); + let (mut b_quads, mut b_vertices) = (vec![], vec![]); + partitioner.init(&outline_buffer.endpoints, + &outline_buffer.control_points, + &outline_buffer.subpaths); + let mut glyph_info = vec![]; + for (path_index, (&glyph_id, decoded_outline_indices)) in + request.glyph_ids.iter().zip(decoded_outline_indices.iter()).enumerate() { + let glyph_key = GlyphKey::new(glyph_id); + + let dimensions = match font_context.glyph_dimensions(&font_instance_key, &glyph_key) { + Some(dimensions) => { + PartitionGlyphDimensions { + origin: dimensions.origin, + size: dimensions.size, + advance: dimensions.advance, + } + } + None => { + PartitionGlyphDimensions { + origin: Point2D::zero(), + size: Size2D::zero(), + advance: 0.0, + } + } + }; + + partitioner.partition(path_index as u32, + decoded_outline_indices.subpath_indices.start as u32, + decoded_outline_indices.subpath_indices.end as u32); + + let (path_b_quads, path_b_vertices) = (partitioner.b_quads(), partitioner.b_vertices()); + let (first_b_quad_index, first_b_vertex_index) = (b_quad_count, b_vertex_count); + let last_b_quad_index = first_b_quad_index + path_b_quads.len(); + let last_b_vertex_index = first_b_vertex_index + path_b_vertices.len(); + + for b_quad in partitioner.b_quads() { + if bincode::serialize_into(&mut b_quads, b_quad, Infinite).is_err() { + return Json(Err(PartitionFontError::BincodeSerializationFailed)) + } + } + for b_vertex in partitioner.b_vertices() { + if bincode::serialize_into(&mut b_vertices, b_vertex, Infinite).is_err() { + return Json(Err(PartitionFontError::BincodeSerializationFailed)) + } + } + + b_quad_count = last_b_quad_index; + b_vertex_count = last_b_vertex_index; + + glyph_info.push(PartitionGlyphInfo { + id: glyph_id, + dimensions: dimensions, + b_quad_indices: IndexRange::new(first_b_quad_index, last_b_quad_index), + b_vertex_indices: IndexRange::new(first_b_vertex_index, last_b_vertex_index), + }) + } + + // Return the response. + Json(Ok(PartitionFontResponse { + glyph_info: glyph_info, + b_quads: base64::encode(&b_quads), + b_vertices: base64::encode(&b_vertices), + })) +} + +// Static files +#[get("/")] +fn static_index() -> io::Result { + NamedFile::open(STATIC_ROOT_PATH) +} +#[get("/js/pathfinder.js")] +fn static_js_pathfinder_js() -> io::Result { + NamedFile::open(STATIC_JS_PATHFINDER_JS_PATH) +} +#[get("/css/bootstrap/")] +fn static_css_bootstrap(file: PathBuf) -> Option { + NamedFile::open(Path::new(STATIC_CSS_BOOTSTRAP_PATH).join(file)).ok() +} +#[get("/js/bootstrap/")] +fn static_js_bootstrap(file: PathBuf) -> Option { + NamedFile::open(Path::new(STATIC_JS_BOOTSTRAP_PATH).join(file)).ok() +} +#[get("/js/jquery/")] +fn static_js_jquery(file: PathBuf) -> Option { + NamedFile::open(Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok() +} + +fn main() { + rocket::ignite().mount("/", routes![ + partition_font, + static_index, + static_js_pathfinder_js, + static_css_bootstrap, + static_js_bootstrap, + static_js_jquery, + ]).launch(); } diff --git a/partitioner/Cargo.toml b/partitioner/Cargo.toml index dc526f22..7671c436 100644 --- a/partitioner/Cargo.toml +++ b/partitioner/Cargo.toml @@ -13,3 +13,5 @@ env_logger = "0.4" euclid = "0.15" half = "1.0" log = "0.3" +serde = "1.0" +serde_derive = "1.0" diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index 2321e80b..14f22a74 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -10,6 +10,9 @@ extern crate euclid; extern crate half; #[macro_use] extern crate log; +extern crate serde; +#[macro_use] +extern crate serde_derive; use euclid::Point2D; use std::u32; @@ -20,7 +23,7 @@ pub mod legalizer; pub mod partitioner; #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct BQuad { pub upper_left_vertex_index: u32, pub upper_control_point_vertex_index: u32, @@ -53,7 +56,7 @@ impl BQuad { } #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct Endpoint { pub position: Point2D, /// `u32::MAX` if not present. @@ -62,7 +65,7 @@ pub struct Endpoint { } #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct Subpath { pub first_endpoint_index: u32, pub last_endpoint_index: u32, @@ -75,7 +78,7 @@ pub enum AntialiasingMode { Ecaa = 1, } -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] #[repr(u8)] pub enum BVertexKind { Endpoint0 = 0, @@ -84,7 +87,7 @@ pub enum BVertexKind { ConcaveControlPoint = 3, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[repr(C)] pub struct BVertex { pub position: Point2D, @@ -129,7 +132,7 @@ impl BVertex { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[repr(C)] pub struct LineIndices { pub left_vertex_index: u32, @@ -146,7 +149,7 @@ impl LineIndices { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[repr(C)] pub struct CurveIndices { pub left_vertex_index: u32,