Send mesh libraries to the client in raw binary instead of Base64 and JSON

This commit is contained in:
Patrick Walton 2017-10-02 16:31:54 -07:00
parent 0bbcf8f1a0
commit 7bbd02ed85
5 changed files with 65 additions and 47 deletions

View File

@ -346,3 +346,11 @@ function toFourCC(buffer: ArrayBuffer, position: number): string {
result += String.fromCharCode(byte); result += String.fromCharCode(byte);
return result; return result;
} }
export function parseServerTiming(headers: Headers): number {
if (!headers.has('Server-Timing'))
return 0.0;
const timing = headers.get('Server-Timing')!;
const matches = /^Partitioning\s*=\s*([0-9.]+)$/.exec(timing);
return matches != null ? parseFloat(matches[1]) / 1000.0 : 0.0;
}

View File

@ -11,7 +11,8 @@
import * as glmatrix from 'gl-matrix'; import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy, SubpixelAAType } from "./aa-strategy"; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {SubpixelAAType} from "./aa-strategy";
import {DemoAppController} from './app-controller'; import {DemoAppController} from './app-controller';
import PathfinderBufferTexture from "./buffer-texture"; import PathfinderBufferTexture from "./buffer-texture";
import {OrthographicCamera} from "./camera"; import {OrthographicCamera} from "./camera";

View File

@ -13,7 +13,7 @@ import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash'; import * as _ from 'lodash';
import 'path-data-polyfill.js'; import 'path-data-polyfill.js';
import {PathfinderMeshData} from "./meshes"; import {parseServerTiming, PathfinderMeshData} from "./meshes";
import {panic, unwrapNull} from "./utils"; import {panic, unwrapNull} from "./utils";
export const BUILTIN_SVG_URI: string = "/svg/demo"; export const BUILTIN_SVG_URI: string = "/svg/demo";
@ -70,17 +70,15 @@ export class SVGLoader {
partition(pathIndex?: number | undefined): Promise<PathfinderMeshData> { partition(pathIndex?: number | undefined): Promise<PathfinderMeshData> {
// Make the request. // Make the request.
const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]]; const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]];
let time = 0;
return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, { return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
body: JSON.stringify({ paths: paths }), body: JSON.stringify({ paths: paths }),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
method: 'POST', method: 'POST',
}).then(response => response.text()).then(responseText => { }).then(response => {
const response = JSON.parse(responseText); time = parseServerTiming(response.headers);
if (!('Ok' in response)) return response.arrayBuffer();
panic("Failed to partition the font!"); }).then(buffer => new PathfinderMeshData(buffer));
const meshes = base64js.toByteArray(response.Ok.pathData);
return new PathfinderMeshData(meshes.buffer as ArrayBuffer);
});
} }
private attachSVG(svgElement: SVGSVGElement) { private attachSVG(svgElement: SVGSVGElement) {

View File

@ -14,7 +14,7 @@ import * as _ from 'lodash';
import * as opentype from "opentype.js"; import * as opentype from "opentype.js";
import {Metrics} from 'opentype.js'; import {Metrics} from 'opentype.js';
import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes"; import {B_QUAD_SIZE, parseServerTiming, PathfinderMeshData} from "./meshes";
import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils"; import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
export const BUILTIN_FONT_URI: string = "/otf/demo"; export const BUILTIN_FONT_URI: string = "/otf/demo";
@ -227,18 +227,18 @@ export class GlyphStore {
}; };
// Make the request. // Make the request.
let time = 0;
return window.fetch(PARTITION_FONT_ENDPOINT_URI, { return window.fetch(PARTITION_FONT_ENDPOINT_URI, {
body: JSON.stringify(request), body: JSON.stringify(request),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
method: 'POST', method: 'POST',
}).then(response => response.text()).then(responseText => { }).then(response => {
const response = JSON.parse(responseText); time = parseServerTiming(response.headers);
if (!('Ok' in response)) return response.arrayBuffer();
panic(`Failed to partition the font: ${response.Err}`); }).then(buffer => {
const meshes = base64js.toByteArray(response.Ok.pathData);
return { return {
meshes: new PathfinderMeshData(meshes.buffer as ArrayBuffer), meshes: new PathfinderMeshData(buffer),
time: response.Ok.time, time: time,
}; };
}); });
} }

View File

@ -34,7 +34,7 @@ use pathfinder_path_utils::cubic::CubicCurve;
use pathfinder_path_utils::monotonic::MonotonicPathSegmentStream; use pathfinder_path_utils::monotonic::MonotonicPathSegmentStream;
use pathfinder_path_utils::stroke; use pathfinder_path_utils::stroke;
use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathSegment, Transform2DPathStream}; use pathfinder_path_utils::{PathBuffer, PathBufferStream, PathSegment, Transform2DPathStream};
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Header, Status};
use rocket::request::Request; use rocket::request::Request;
use rocket::response::{NamedFile, Redirect, Responder, Response}; use rocket::response::{NamedFile, Redirect, Responder, Response};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
@ -112,10 +112,9 @@ struct PartitionGlyph {
struct PartitionFontResponse { struct PartitionFontResponse {
#[serde(rename = "pathData")] #[serde(rename = "pathData")]
path_data: String, path_data: String,
time: f64,
} }
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum PartitionFontError { enum PartitionFontError {
UnknownBuiltinFont, UnknownBuiltinFont,
Base64DecodingFailed, Base64DecodingFailed,
@ -124,7 +123,7 @@ enum PartitionFontError {
Unimplemented, Unimplemented,
} }
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
enum PartitionSvgPathsError { enum PartitionSvgPathsError {
UnknownSvgPathSegmentType, UnknownSvgPathSegmentType,
Unimplemented, Unimplemented,
@ -154,14 +153,8 @@ struct PartitionSvgPathSegment {
values: Vec<f64>, values: Vec<f64>,
} }
#[derive(Clone, Serialize, Deserialize)]
struct PartitionSvgPathsResponse {
#[serde(rename = "pathData")]
path_data: String,
}
struct PathPartitioningResult { struct PathPartitioningResult {
encoded_data: String, encoded_data: Vec<u8>,
time: Duration, time: Duration,
} }
@ -182,18 +175,36 @@ impl PathPartitioningResult {
let mut data_buffer = Cursor::new(vec![]); let mut data_buffer = Cursor::new(vec![]);
drop(partitioner.library().serialize_into(&mut data_buffer)); drop(partitioner.library().serialize_into(&mut data_buffer));
let data_string = base64::encode(data_buffer.get_ref());
PathPartitioningResult { PathPartitioningResult {
encoded_data: data_string, encoded_data: data_buffer.into_inner(),
time: time_elapsed, time: time_elapsed,
} }
} }
fn elapsed_ms(&self) -> f64 {
self.time.as_secs() as f64 * 1000.0 + self.time.subsec_nanos() as f64 * 1e-6
}
}
struct PartitionResponder {
data: Vec<u8>,
time: f64,
}
impl<'r> Responder<'r> for PartitionResponder {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
let mut builder = Response::build();
builder.header(ContentType::new("application", "vnd.mozilla.pfml"));
builder.header(Header::new("Server-Timing", format!("Partitioning={}", self.time)));
builder.sized_body(Cursor::new(self.data));
builder.ok()
}
} }
#[post("/partition-font", format = "application/json", data = "<request>")] #[post("/partition-font", format = "application/json", data = "<request>")]
fn partition_font(request: Json<PartitionFontRequest>) fn partition_font(request: Json<PartitionFontRequest>)
-> Json<Result<PartitionFontResponse, PartitionFontError>> { -> Result<PartitionResponder, PartitionFontError> {
// Fetch the OTF data. // Fetch the OTF data.
let otf_data = match request.face { let otf_data = match request.face {
PartitionFontRequestFace::Builtin(ref builtin_font_name) => { PartitionFontRequestFace::Builtin(ref builtin_font_name) => {
@ -206,20 +217,20 @@ fn partition_font(request: Json<PartitionFontRequest>)
.expect("Couldn't read builtin font!"); .expect("Couldn't read builtin font!");
data data
} }
None => return Json(Err(PartitionFontError::UnknownBuiltinFont)), None => return Err(PartitionFontError::UnknownBuiltinFont),
} }
} }
PartitionFontRequestFace::Custom(ref encoded_data) => { PartitionFontRequestFace::Custom(ref encoded_data) => {
// Decode Base64-encoded OTF data. // Decode Base64-encoded OTF data.
let unsafe_otf_data = match base64::decode(encoded_data) { let unsafe_otf_data = match base64::decode(encoded_data) {
Ok(unsafe_otf_data) => unsafe_otf_data, Ok(unsafe_otf_data) => unsafe_otf_data,
Err(_) => return Json(Err(PartitionFontError::Base64DecodingFailed)), Err(_) => return Err(PartitionFontError::Base64DecodingFailed),
}; };
// Sanitize. // Sanitize.
match fontsan::process(&unsafe_otf_data) { match fontsan::process(&unsafe_otf_data) {
Ok(otf_data) => otf_data, Ok(otf_data) => otf_data,
Err(_) => return Json(Err(PartitionFontError::FontSanitizationFailed)), Err(_) => return Err(PartitionFontError::FontSanitizationFailed),
} }
} }
}; };
@ -232,7 +243,7 @@ fn partition_font(request: Json<PartitionFontRequest>)
}; };
let mut font_context = FontContext::new(); let mut font_context = FontContext::new();
if font_context.add_font_from_memory(&font_key, otf_data, request.font_index).is_err() { if font_context.add_font_from_memory(&font_key, otf_data, request.font_index).is_err() {
return Json(Err(PartitionFontError::FontLoadingFailed)) return Err(PartitionFontError::FontLoadingFailed)
} }
// Read glyph info. // Read glyph info.
@ -263,19 +274,17 @@ fn partition_font(request: Json<PartitionFontRequest>)
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
&subpath_indices); &subpath_indices);
let time = path_partitioning_result.time.as_secs() as f64 +
path_partitioning_result.time.subsec_nanos() as f64 * 1e-9;
// Return the response. // Return the response.
Json(Ok(PartitionFontResponse { let elapsed_ms = path_partitioning_result.elapsed_ms();
path_data: path_partitioning_result.encoded_data, Ok(PartitionResponder {
time: time, data: path_partitioning_result.encoded_data,
})) time: elapsed_ms,
})
} }
#[post("/partition-svg-paths", format = "application/json", data = "<request>")] #[post("/partition-svg-paths", format = "application/json", data = "<request>")]
fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>) fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
-> Json<Result<PartitionSvgPathsResponse, PartitionSvgPathsError>> { -> Result<PartitionResponder, PartitionSvgPathsError> {
// Parse the SVG path. // Parse the SVG path.
// //
// 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`
@ -316,7 +325,7 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
.map(|curve| curve.to_path_segment())); .map(|curve| curve.to_path_segment()));
} }
'Z' => stream.push(PathSegment::ClosePath), 'Z' => stream.push(PathSegment::ClosePath),
_ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)), _ => return Err(PartitionSvgPathsError::UnknownSvgPathSegmentType),
} }
} }
@ -348,9 +357,11 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths);
// Return the response. // Return the response.
Json(Ok(PartitionSvgPathsResponse { let elapsed_ms = path_partitioning_result.elapsed_ms();
path_data: path_partitioning_result.encoded_data, Ok(PartitionResponder {
})) data: path_partitioning_result.encoded_data,
time: elapsed_ms,
})
} }
// Static files // Static files