Send mesh libraries to the client in raw binary instead of Base64 and JSON
This commit is contained in:
parent
0bbcf8f1a0
commit
7bbd02ed85
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue