diff --git a/demo/client/css/pathfinder.css b/demo/client/css/pathfinder.css index 5d61d489..8eee3714 100644 --- a/demo/client/css/pathfinder.css +++ b/demo/client/css/pathfinder.css @@ -15,6 +15,7 @@ body { } #pf-canvas { display: block; + position: absolute; touch-action: none; } #pf-fps-label { diff --git a/demo/client/package.json b/demo/client/package.json index 6f9bfbb0..33bdda20 100644 --- a/demo/client/package.json +++ b/demo/client/package.json @@ -19,6 +19,7 @@ "gl-matrix": "^2.4.0", "lodash": "^4.17.4", "opentype.js": "^0.7.3", + "path-data-polyfill.js": "^1.0.2", "ts-loader": "^2.3.2", "typescript": "^2.4.2", "webpack": "^3.4.1" diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index 665f2b13..2c9ce8b2 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -9,6 +9,7 @@ // except according to those terms. import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader'; +import {expectNotNull} from './utils'; export default abstract class AppController { constructor() {} @@ -24,10 +25,27 @@ export default abstract class AppController { }); } + protected loadFile() { + const file = expectNotNull(this.loadFileButton.files, "No file selected!")[0]; + const reader = new FileReader; + reader.addEventListener('loadend', () => { + this.fileData = reader.result; + this.fileLoaded(); + }, false); + reader.readAsArrayBuffer(file); + } + + protected abstract fileLoaded(): void; + protected abstract createView(canvas: HTMLCanvasElement, commonShaderSource: string, shaderSources: ShaderMap): View; view: Promise; + + protected fileData: ArrayBuffer; + + protected canvas: HTMLCanvasElement; + protected loadFileButton: HTMLInputElement; } diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index 7200bde7..20193ca4 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -45,11 +45,8 @@ export interface Meshes { } export class PathfinderMeshData implements Meshes { - constructor(encodedResponse: string) { - const response = JSON.parse(encodedResponse); - if (!('Ok' in response)) - throw new PathfinderError("Failed to partition the font!"); - const meshes = response.Ok; + constructor(meshes: any) { + console.log(meshes); for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer as ArrayBuffer; diff --git a/demo/client/src/svg.ts b/demo/client/src/svg.ts index f7222cb1..c7997121 100644 --- a/demo/client/src/svg.ts +++ b/demo/client/src/svg.ts @@ -8,33 +8,113 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +import * as glmatrix from 'gl-matrix'; +import 'path-data-polyfill.js'; + import {ShaderMap, ShaderProgramSource} from './shader-loader'; import {PathfinderView} from './view'; import {panic} from './utils'; import AppController from './app-controller'; +const SVG_NS: string = "http://www.w3.org/2000/svg"; + +const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; + +declare class SVGPathSegment { + type: string; + values: number[]; +} + +declare class SVGPathElement { + getPathData(settings: any): SVGPathSegment[]; +} + class SVGDemoController extends AppController { + start() { + super.start(); + + this.svg = document.getElementById('pf-svg') as Element as SVGSVGElement; + + this.pathElements = []; + + this.loadFileButton = document.getElementById('pf-load-svg-button') as HTMLInputElement; + this.loadFileButton.addEventListener('change', () => this.loadFile(), false); + } + + protected fileLoaded() { + const decoder = new (window as any).TextDecoder('utf-8'); + const fileStringData = decoder.decode(new DataView(this.fileData)); + const svgDocument = (new DOMParser).parseFromString(fileStringData, 'image/svg+xml'); + const svgElement = svgDocument.documentElement as Element as SVGSVGElement; + this.attachSVG(svgElement); + } + protected createView(canvas: HTMLCanvasElement, commonShaderSource: string, shaderSources: ShaderMap) { - const svg = document.getElementById('pf-svg') as Element as SVGSVGElement; - return new SVGDemoView(this, svg, canvas, commonShaderSource, shaderSources); + return new SVGDemoView(this, canvas, commonShaderSource, shaderSources); } + + private attachSVG(svgElement: SVGSVGElement) { + // Clear out the current document. + let kid; + while ((kid = this.svg.firstChild) != null) + this.svg.removeChild(kid); + + // Add all kids of the incoming SVG document. + while ((kid = svgElement.firstChild) != null) + this.svg.appendChild(kid); + + // Scan for geometry elements. + this.pathElements.length = 0; + const queue: Array = [this.svg]; + let element; + while ((element = queue.pop()) != null) { + for (const kid of element.childNodes) { + if (kid instanceof Element) + queue.push(kid); + } + if (element instanceof SVGPathElement) + this.pathElements.push(element); + } + + // Extract and normalize the path data. + let pathData = this.pathElements.map(element => element.getPathData({normalize: true})); + + // Build the partitioning request to the server. + const request = {paths: pathData.map(segments => ({segments: segments}))}; + console.log(JSON.stringify(request)); + + // Make the request. + window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(request), + }).then(response => response.text()).then(responseText => { + console.log(JSON.parse(responseText)); + }); + } + + private svg: SVGSVGElement; + private pathElements: Array; } class SVGDemoView extends PathfinderView { constructor(appController: SVGDemoController, - svg: SVGSVGElement, canvas: HTMLCanvasElement, commonShaderSource: string, shaderSources: ShaderMap) { super(canvas, commonShaderSource, shaderSources); - this.svg = svg; + this.appController = appController; + + this.resized(false); } - get destAllocatedSize() { - return panic("TODO"); + protected resized(initialSize: boolean) {} + + get destAllocatedSize(): glmatrix.vec2 { + return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); } get destDepthTexture() { @@ -45,8 +125,8 @@ class SVGDemoView extends PathfinderView { return panic("TODO"); } - get destUsedSize() { - return panic("TODO"); + get destUsedSize(): glmatrix.vec2 { + return this.destAllocatedSize; } setTransformAndTexScaleUniformsForDest() { @@ -57,5 +137,12 @@ class SVGDemoView extends PathfinderView { panic("TODO"); } - private svg: SVGSVGElement; + private appController: SVGDemoController; } + +function main() { + const controller = new SVGDemoController; + window.addEventListener('load', () => controller.start(), false); +} + +main(); diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index e3e7f38a..f734e425 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -20,7 +20,7 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from import {UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader'; -import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull} from './utils'; +import { PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic } from './utils'; import {MonochromePathfinderView} from './view'; import AppController from './app-controller'; import PathfinderBufferTexture from './buffer-texture'; @@ -140,9 +140,8 @@ class TextDemoController extends AppController { this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label')); - const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement; - this.loadFontButton = document.getElementById('pf-load-font-button') as HTMLInputElement; - this.loadFontButton.addEventListener('change', () => this.loadFont(), false); + this.loadFileButton = document.getElementById('pf-load-font-button') as HTMLInputElement; + this.loadFileButton.addEventListener('change', () => this.loadFile(), false); this.aaLevelSelect = document.getElementById('pf-aa-level-select') as HTMLSelectElement; this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false); @@ -155,16 +154,6 @@ class TextDemoController extends AppController { return new TextDemoView(this, canvas, commonShaderSource, shaderSources); } - private loadFont() { - const file = expectNotNull(this.loadFontButton.files, "No file selected!")[0]; - const reader = new FileReader; - reader.addEventListener('loadend', () => { - this.fontData = reader.result; - this.fontLoaded(); - }, false); - reader.readAsArrayBuffer(file); - } - private updateAALevel() { const selectedOption = this.aaLevelSelect.selectedOptions[0]; const aaType = unwrapUndef(selectedOption.dataset.pfType) as @@ -173,8 +162,8 @@ class TextDemoController extends AppController { this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel)); } - private fontLoaded() { - this.font = opentype.parse(this.fontData); + protected fileLoaded() { + this.font = opentype.parse(this.fileData); if (!this.font.isSupported()) throw new PathfinderError("The font type is unsupported."); @@ -191,7 +180,7 @@ class TextDemoController extends AppController { // Build the partitioning request to the server. const request = { - otf: base64js.fromByteArray(new Uint8Array(this.fontData)), + otf: base64js.fromByteArray(new Uint8Array(this.fileData)), fontIndex: 0, glyphs: this.uniqueGlyphs.map(glyph => { const metrics = glyph.metrics; @@ -203,12 +192,17 @@ class TextDemoController extends AppController { pointSize: this.font.unitsPerEm, }; + // Make the request. window.fetch(PARTITION_FONT_ENDPOINT_URL, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: {'Content-Type': 'application/json'}, body: JSON.stringify(request), - }).then(response => response.text()).then(encodedMeshes => { - this.meshes = new PathfinderMeshData(encodedMeshes); + }).then(response => response.text()).then(responseText => { + const response = JSON.parse(responseText); + if (!('Ok' in response)) + panic("Failed to partition the font!"); + const meshes = response.Ok.pathData; + this.meshes = new PathfinderMeshData(meshes); this.meshesReceived(); }); } @@ -239,11 +233,9 @@ class TextDemoController extends AppController { return this._atlas; } - private loadFontButton: HTMLInputElement; private aaLevelSelect: HTMLSelectElement; private fpsLabel: HTMLElement; - private fontData: ArrayBuffer; font: opentype.Font; lineGlyphs: TextGlyph[][]; textGlyphs: TextGlyph[]; @@ -272,9 +264,7 @@ class TextDemoView extends MonochromePathfinderView { this.canvas.addEventListener('wheel', event => this.onWheel(event), false); this.antialiasingStrategy = new NoAAStrategy(0); - - window.addEventListener('resize', () => this.resizeToFit(), false); - this.resizeToFit(); + this.antialiasingStrategy.init(this); } setAntialiasingOptions(aaType: keyof AntialiasingStrategyTable, aaLevel: number) { @@ -540,22 +530,9 @@ class TextDemoView extends MonochromePathfinderView { this.rebuildAtlasIfNecessary(); } - private resizeToFit() { - const width = window.innerWidth; - const height = window.scrollY + window.innerHeight - - this.canvas.getBoundingClientRect().top; - const devicePixelRatio = window.devicePixelRatio; - - const canvasSize = new Float32Array([width, height]) as glmatrix.vec2; - glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio); - - this.canvas.style.width = width + 'px'; - this.canvas.style.height = height + 'px'; - this.canvas.width = canvasSize[0]; - this.canvas.height = canvasSize[1]; - - this.antialiasingStrategy.init(this); - + protected resized(initialSize: boolean) { + if (!initialSize) + this.antialiasingStrategy.init(this); this.setDirty(); } diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index a2bac5d0..6a310243 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -44,8 +44,30 @@ export abstract class PathfinderView { this.atlasTransformBuffer = new PathfinderBufferTexture(this.gl, 'uPathTransform'); this.pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors'); + + window.addEventListener('resize', () => this.resizeToFit(false), false); + this.resizeToFit(true); } + private resizeToFit(initialSize: boolean) { + const width = window.innerWidth; + const height = window.scrollY + window.innerHeight - + this.canvas.getBoundingClientRect().top; + const devicePixelRatio = window.devicePixelRatio; + + const canvasSize = new Float32Array([width, height]) as glmatrix.vec2; + glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio); + + this.canvas.style.width = width + 'px'; + this.canvas.style.height = height + 'px'; + this.canvas.width = canvasSize[0]; + this.canvas.height = canvasSize[1]; + + this.resized(initialSize); + } + + protected abstract resized(initialSize: boolean): void; + protected initContext() { // Initialize the OpenGL context. this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }), diff --git a/demo/resources/tests/Ghostscript_Tiger.svg b/demo/resources/tests/Ghostscript_Tiger.svg new file mode 100644 index 00000000..67ba6369 --- /dev/null +++ b/demo/resources/tests/Ghostscript_Tiger.svg @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index edf95bd3..c53b0490 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -26,6 +26,7 @@ use euclid::{Point2D, Size2D, Transform2D}; use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey}; use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer}; use pathfinder_partitioner::partitioner::Partitioner; +use pathfinder_partitioner::{Endpoint, Subpath}; use rocket::http::{ContentType, Status}; use rocket::request::Request; use rocket::response::{NamedFile, Responder, Response}; @@ -35,6 +36,7 @@ use std::fs::File; use std::io; use std::mem; use std::path::{Path, PathBuf}; +use std::u32; static STATIC_TEXT_DEMO_PATH: &'static str = "../client/html/text.html"; static STATIC_SVG_DEMO_PATH: &'static str = "../client/html/svg.html"; @@ -45,6 +47,12 @@ static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist static STATIC_JS_PATHFINDER_PATH: &'static str = "../client"; static STATIC_GLSL_PATH: &'static str = "../../shaders"; +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct SubpathRange { + start: u32, + end: u32, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct IndexRange { start: usize, @@ -52,13 +60,6 @@ struct IndexRange { } impl IndexRange { - fn new(start: usize, end: usize) -> IndexRange { - IndexRange { - start: start, - end: end, - } - } - fn from_data(dest: &mut Vec, src: &[T]) -> Result where T: Serialize { let byte_len_before = dest.len(); for src_value in src { @@ -95,52 +96,84 @@ struct PartitionGlyphDimensions { advance: f32, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -struct DecodedOutlineIndices { - endpoint_indices: IndexRange, - control_point_indices: IndexRange, - subpath_indices: IndexRange, +impl PartitionGlyphDimensions { + fn dummy() -> PartitionGlyphDimensions { + PartitionGlyphDimensions { + origin: Point2D::zero(), + size: Size2D::zero(), + advance: 0.0, + } + } } -#[allow(non_snake_case)] #[derive(Clone, Copy, Serialize, Deserialize)] struct PartitionGlyphInfo { id: u32, dimensions: PartitionGlyphDimensions, - bQuadIndices: IndexRange, - bVertexIndices: IndexRange, - coverInteriorIndices: IndexRange, - coverCurveIndices: IndexRange, - edgeUpperLineIndices: IndexRange, - edgeUpperCurveIndices: IndexRange, - edgeLowerLineIndices: IndexRange, - edgeLowerCurveIndices: IndexRange, + #[serde(rename = "pathIndices")] + path_indices: PartitionPathIndices, } -#[allow(non_snake_case)] #[derive(Clone, Serialize, Deserialize)] struct PartitionFontResponse { - glyphInfo: Vec, + #[serde(rename = "glyphInfo")] + glyph_info: Vec, + #[serde(rename = "pathData")] + path_data: PartitionEncodedPathData, +} + +#[derive(Clone, Copy, Serialize, Deserialize)] +struct PartitionPathIndices { + #[serde(rename = "bQuadIndices")] + b_quad_indices: IndexRange, + #[serde(rename = "bVertexIndices")] + b_vertex_indices: IndexRange, + #[serde(rename = "coverInteriorIndices")] + cover_interior_indices: IndexRange, + #[serde(rename = "coverCurveIndices")] + cover_curve_indices: IndexRange, + #[serde(rename = "coverUpperLineIndices")] + edge_upper_line_indices: IndexRange, + #[serde(rename = "coverUpperCurveIndices")] + edge_upper_curve_indices: IndexRange, + #[serde(rename = "coverLowerLineIndices")] + edge_lower_line_indices: IndexRange, + #[serde(rename = "coverLowerCurveIndices")] + edge_lower_curve_indices: IndexRange, +} + +#[derive(Clone, Serialize, Deserialize)] +struct PartitionEncodedPathData { // Base64-encoded `bincode`-encoded `BQuad`s. - bQuads: String, + #[serde(rename = "bQuads")] + b_quads: String, // Base64-encoded `bincode`-encoded `Point2D`s. - bVertexPositions: String, + #[serde(rename = "bVertexPositions")] + b_vertex_positions: String, // Base64-encoded `bincode`-encoded `u16`s. - bVertexPathIDs: String, + #[serde(rename = "bVertexPathIDs")] + b_vertex_path_ids: String, // Base64-encoded `bincode`-encoded `BVertexLoopBlinnData`s. - bVertexLoopBlinnData: String, + #[serde(rename = "bVertexLoopBlinnData")] + b_vertex_loop_blinn_data: String, // Base64-encoded `u32`s. - coverInteriorIndices: String, + #[serde(rename = "coverInteriorIndices")] + cover_interior_indices: String, // Base64-encoded `u32`s. - coverCurveIndices: String, + #[serde(rename = "coverCurveIndices")] + cover_curve_indices: String, // Base64-encoded `bincode`-encoded `LineIndices` instances. - edgeUpperLineIndices: String, + #[serde(rename = "edgeUpperLineIndices")] + edge_upper_line_indices: String, // Base64-encoded `bincode`-encoded `CurveIndices` instances. - edgeUpperCurveIndices: String, + #[serde(rename = "edgeUpperCurveIndices")] + edge_upper_curve_indices: String, // Base64-encoded `bincode`-encoded `LineIndices` instances. - edgeLowerLineIndices: String, + #[serde(rename = "edgeLowerLineIndices")] + edge_lower_line_indices: String, // Base64-encoded `bincode`-encoded `CurveIndices` instances. - edgeLowerCurveIndices: String, + #[serde(rename = "edgeLowerCurveIndices")] + edge_lower_curve_indices: String, } #[derive(Clone, Copy, Serialize, Deserialize)] @@ -148,99 +181,52 @@ 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)), - }; +#[derive(Clone, Copy, Serialize, Deserialize)] +enum PartitionSvgPathsError { + UnknownSvgPathSegmentType, + Unimplemented, +} - // Sanitize. - let otf_data = match fontsan::process(&unsafe_otf_data) { - Ok(otf_data) => otf_data, - Err(_) => return Json(Err(PartitionFontError::FontSanitizationFailed)), - }; +#[derive(Clone, Serialize, Deserialize)] +struct PartitionSvgPathsRequest { + paths: Vec, +} - // Parse glyph data. - let font_key = FontKey::new(); - let font_instance_key = FontInstanceKey { - font_key: font_key, - size: Au::from_f64_px(request.pointSize), - }; - let mut font_context = FontContext::new(); - if font_context.add_font_from_memory(&font_key, otf_data, request.fontIndex).is_err() { - return Json(Err(PartitionFontError::FontLoadingFailed)) - } +#[derive(Clone, Serialize, Deserialize)] +struct PartitionSvgPath { + segments: Vec, +} - // Read glyph info. - let mut outline_buffer = GlyphOutlineBuffer::new(); - let decoded_outline_indices: Vec<_> = request.glyphs.iter().map(|glyph| { - let glyph_key = GlyphKey::new(glyph.id); +#[derive(Clone, Serialize, Deserialize)] +struct PartitionSvgPathSegment { + #[serde(rename = "type")] + kind: char, + values: Vec, +} - 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(); +#[derive(Clone, Serialize, Deserialize)] +struct PartitionSvgPathsResponse { + #[serde(rename = "pathIndices")] + path_indices: Vec, + #[serde(rename = "pathData")] + path_data: PartitionEncodedPathData, +} - // 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)); - - 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(); +fn partition_paths(partitioner: &mut Partitioner, subpath_indices: &[SubpathRange]) + -> (PartitionEncodedPathData, Vec) { let (mut b_quads, mut b_vertex_positions) = (vec![], vec![]); let (mut b_vertex_path_ids, mut b_vertex_loop_blinn_data) = (vec![], vec![]); let (mut cover_interior_indices, mut cover_curve_indices) = (vec![], vec![]); let (mut edge_upper_line_indices, mut edge_upper_curve_indices) = (vec![], vec![]); let (mut edge_lower_line_indices, mut edge_lower_curve_indices) = (vec![], vec![]); - partitioner.init(&outline_buffer.endpoints, - &outline_buffer.control_points, - &outline_buffer.subpaths); + let mut path_indices = vec![]; - let mut glyph_info = vec![]; - for (path_index, (&glyph, decoded_outline_indices)) in - request.glyphs.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 + 1) as u16, - decoded_outline_indices.subpath_indices.start as u32, - decoded_outline_indices.subpath_indices.end as u32); + for (path_index, subpath_range) in subpath_indices.iter().enumerate() { + partitioner.partition((path_index + 1) as u16, subpath_range.start, subpath_range.end); let path_b_vertex_positions = partitioner.b_vertex_positions(); let path_b_vertex_path_ids = partitioner.b_vertex_path_ids(); @@ -282,40 +268,205 @@ fn partition_font(request: Json) path_edge_lower_curve_indices.offset(positions_start); } + path_indices.push(PartitionPathIndices { + b_quad_indices: IndexRange::from_data(&mut b_quads, &path_b_quads).unwrap(), + b_vertex_indices: IndexRange::from_data(&mut b_vertex_loop_blinn_data, + path_b_vertex_loop_blinn_data).unwrap(), + cover_interior_indices: IndexRange::from_data(&mut cover_interior_indices, + &path_cover_interior_indices).unwrap(), + cover_curve_indices: IndexRange::from_data(&mut cover_curve_indices, + &path_cover_curve_indices).unwrap(), + edge_upper_line_indices: IndexRange::from_data(&mut edge_upper_line_indices, + &path_edge_upper_line_indices).unwrap(), + edge_upper_curve_indices: + IndexRange::from_data(&mut edge_upper_curve_indices, + &path_edge_upper_curve_indices).unwrap(), + edge_lower_line_indices: IndexRange::from_data(&mut edge_lower_line_indices, + &path_edge_lower_line_indices).unwrap(), + edge_lower_curve_indices: + IndexRange::from_data(&mut edge_lower_curve_indices, + &path_edge_lower_curve_indices).unwrap(), + }) + } + + let encoded_path_data = PartitionEncodedPathData { + b_quads: base64::encode(&b_quads), + b_vertex_positions: base64::encode(&b_vertex_positions), + b_vertex_path_ids: base64::encode(&b_vertex_path_ids), + b_vertex_loop_blinn_data: base64::encode(&b_vertex_loop_blinn_data), + cover_interior_indices: base64::encode(&cover_interior_indices), + cover_curve_indices: base64::encode(&cover_curve_indices), + edge_upper_line_indices: base64::encode(&edge_upper_line_indices), + edge_upper_curve_indices: base64::encode(&edge_upper_curve_indices), + edge_lower_line_indices: base64::encode(&edge_lower_line_indices), + edge_lower_curve_indices: base64::encode(&edge_lower_curve_indices), + }; + + (encoded_path_data, path_indices) +} + +#[post("/partition-font", format = "application/json", data = "")] +fn partition_font(request: Json) + -> Json> { + // Decode Base64-encoded OTF data. + 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.pointSize), + }; + let mut font_context = FontContext::new(); + if font_context.add_font_from_memory(&font_key, otf_data, request.fontIndex).is_err() { + return Json(Err(PartitionFontError::FontLoadingFailed)) + } + + // Read glyph info. + let mut outline_buffer = GlyphOutlineBuffer::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(); + + // 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)); + + let last_subpath_index = outline_buffer.subpaths.len(); + + SubpathRange { + start: first_subpath_index as u32, + end: last_subpath_index as u32, + } + }).collect(); + + // Partition the decoded glyph outlines. + let mut partitioner = Partitioner::new(); + partitioner.init(&outline_buffer.endpoints, + &outline_buffer.control_points, + &outline_buffer.subpaths); + let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices); + + // Package up other miscellaneous glyph info. + let mut glyph_info = vec![]; + for (glyph, glyph_path_indices) in request.glyphs.iter().zip(path_indices.iter()) { + 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::dummy(), + }; + glyph_info.push(PartitionGlyphInfo { id: glyph.id, dimensions: dimensions, - bQuadIndices: IndexRange::from_data(&mut b_quads, &path_b_quads).unwrap(), - bVertexIndices: IndexRange::from_data(&mut b_vertex_loop_blinn_data, - path_b_vertex_loop_blinn_data).unwrap(), - coverInteriorIndices: IndexRange::from_data(&mut cover_interior_indices, - &path_cover_interior_indices).unwrap(), - coverCurveIndices: IndexRange::from_data(&mut cover_curve_indices, - &path_cover_curve_indices).unwrap(), - edgeUpperLineIndices: IndexRange::from_data(&mut edge_upper_line_indices, - &path_edge_upper_line_indices).unwrap(), - edgeUpperCurveIndices: IndexRange::from_data(&mut edge_upper_curve_indices, - &path_edge_upper_curve_indices).unwrap(), - edgeLowerLineIndices: IndexRange::from_data(&mut edge_lower_line_indices, - &path_edge_lower_line_indices).unwrap(), - edgeLowerCurveIndices: IndexRange::from_data(&mut edge_lower_curve_indices, - &path_edge_lower_curve_indices).unwrap(), + path_indices: *glyph_path_indices, }) } // Return the response. Json(Ok(PartitionFontResponse { - glyphInfo: glyph_info, - bQuads: base64::encode(&b_quads), - bVertexPositions: base64::encode(&b_vertex_positions), - bVertexPathIDs: base64::encode(&b_vertex_path_ids), - bVertexLoopBlinnData: base64::encode(&b_vertex_loop_blinn_data), - coverInteriorIndices: base64::encode(&cover_interior_indices), - coverCurveIndices: base64::encode(&cover_curve_indices), - edgeUpperLineIndices: base64::encode(&edge_upper_line_indices), - edgeUpperCurveIndices: base64::encode(&edge_upper_curve_indices), - edgeLowerLineIndices: base64::encode(&edge_lower_line_indices), - edgeLowerCurveIndices: base64::encode(&edge_lower_curve_indices), + glyph_info: glyph_info, + path_data: encoded_path_data, + })) +} + +#[post("/partition-svg-paths", format = "application/json", data = "")] +fn partition_svg_paths(request: Json) + -> Json> { + // Parse the SVG path. + // + // 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 paths = vec![]; + for path in &request.paths { + let first_subpath_index = subpaths.len() as u32; + + let mut first_endpoint_index_in_subpath = endpoints.len(); + for segment in &path.segments { + match segment.kind { + 'M' => { + if first_endpoint_index_in_subpath < endpoints.len() { + subpaths.push(Subpath { + first_endpoint_index: first_endpoint_index_in_subpath as u32, + last_endpoint_index: endpoints.len() as u32, + }); + first_endpoint_index_in_subpath = endpoints.len(); + } + + 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, + }) + } + 'L' => { + 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, + }) + } + 'C' => { + // FIXME(pcwalton): Do real cubic-to-quadratic conversion. + let control_point_0 = Point2D::new(segment.values[0] as f32, + segment.values[1] as f32); + let control_point_1 = Point2D::new(segment.values[2] as f32, + segment.values[3] as f32); + let control_point = control_point_0.lerp(control_point_1, 0.5); + 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_points.push(control_point); + } + 'Z' => { + subpaths.push(Subpath { + first_endpoint_index: first_endpoint_index_in_subpath as u32, + last_endpoint_index: endpoints.len() as u32, + }); + first_endpoint_index_in_subpath = endpoints.len(); + } + _ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)), + } + } + + let last_subpath_index = subpaths.len() as u32; + paths.push(SubpathRange { + start: first_subpath_index, + end: last_subpath_index, + }) + } + + // Partition the paths. + let mut partitioner = Partitioner::new(); + partitioner.init(&endpoints, &control_points, &subpaths); + let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &paths); + + // Return the response. + Json(Ok(PartitionSvgPathsResponse { + path_indices: path_indices, + path_data: encoded_path_data, })) } @@ -374,6 +525,7 @@ impl<'a> Responder<'a> for Shader { fn main() { rocket::ignite().mount("/", routes![ partition_font, + partition_svg_paths, static_text_demo, static_svg_demo, static_css_bootstrap,