Cache font mesh libraries on the server

This commit is contained in:
Patrick Walton 2017-10-02 19:58:38 -07:00
parent 7bbd02ed85
commit b631fec80f
9 changed files with 84 additions and 29 deletions

View File

@ -113,8 +113,8 @@ class ThreeDController extends DemoAppController<ThreeDView> {
this.loadInitialFile(this.builtinFileURI);
}
protected fileLoaded(fileData: ArrayBuffer): void {
const font = new PathfinderFont(fileData);
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
const font = new PathfinderFont(fileData, builtinName);
this.monumentPromise.then(monument => this.layoutMonument(font, fileData, monument));
}

View File

@ -37,10 +37,10 @@ export abstract class AppController {
protected fetchFile(file: string, builtinFileURI: string) {
window.fetch(`${builtinFileURI}/${file}`)
.then(response => response.arrayBuffer())
.then(data => this.fileLoaded(data));
.then(data => this.fileLoaded(data, file));
}
protected abstract fileLoaded(data: ArrayBuffer): void;
protected abstract fileLoaded(data: ArrayBuffer, builtinName: string | null): void;
protected abstract get defaultFile(): string;
}
@ -116,7 +116,7 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
this.filePickerView = FilePickerView.create();
if (this.filePickerView != null) {
this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData);
this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData, null);
}
const selectFileElement = document.getElementById('pf-select-file') as

View File

@ -94,8 +94,8 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
this.loadInitialFile(this.builtinFileURI);
}
protected fileLoaded(fileData: ArrayBuffer): void {
const font = new PathfinderFont(fileData);
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
const font = new PathfinderFont(fileData, builtinName);
this.font = font;
const textRun = new TextRun(STRING, [0, 0], font);

View File

@ -73,7 +73,7 @@ class MeshDebuggerAppController extends AppController {
this.view = new MeshDebuggerView(this);
this.filePicker = unwrapNull(FilePickerView.create());
this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData);
this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData, null);
this.openModal = unwrapNull(document.getElementById('pf-open-modal'));
this.fontPathSelectGroup =
@ -94,14 +94,14 @@ class MeshDebuggerAppController extends AppController {
this.loadInitialFile(BUILTIN_FONT_URI);
}
protected fileLoaded(fileData: ArrayBuffer): void {
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
while (this.fontPathSelect.lastChild != null)
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
this.fontPathSelectGroup.classList.remove('pf-display-none');
if (this.fileType === 'font')
this.fontLoaded(fileData);
this.fontLoaded(fileData, builtinName);
else if (this.fileType === 'svg')
this.svgLoaded(fileData);
}
@ -151,8 +151,8 @@ class MeshDebuggerAppController extends AppController {
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
}
private fontLoaded(fileData: ArrayBuffer): void {
this.file = new PathfinderFont(fileData);
private fontLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
this.file = new PathfinderFont(fileData, builtinName);
this.fileData = fileData;
const glyphCount = this.file.opentypeFont.numGlyphs;

View File

@ -184,8 +184,8 @@ class TextDemoController extends DemoAppController<TextDemoView> {
unwrapNull(this.shaderSources));
}
protected fileLoaded(fileData: ArrayBuffer) {
const font = new PathfinderFont(fileData);
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null) {
const font = new PathfinderFont(fileData, builtinName);
this.recreateLayout(font);
}

View File

@ -49,11 +49,13 @@ opentype.Font.prototype.lineHeight = function() {
export class PathfinderFont {
readonly opentypeFont: opentype.Font;
readonly data: ArrayBuffer;
readonly builtinFontName: string | null;
private metricsCache: Metrics[];
constructor(data: ArrayBuffer) {
constructor(data: ArrayBuffer, builtinFontName: string | null) {
this.data = data;
this.builtinFontName = builtinFontName != null ? builtinFontName : null;
this.opentypeFont = opentype.parse(data);
if (!this.opentypeFont.isSupported())
@ -215,12 +217,14 @@ export class GlyphStore {
partition(): Promise<PartitionResult> {
// Build the partitioning request to the server.
//
// FIXME(pcwalton): If this is a builtin font, don't resend it to the server!
let fontFace;
if (this.font.builtinFontName != null)
fontFace = { Builtin: this.font.builtinFontName };
else
fontFace = { Custom: base64js.fromByteArray(new Uint8Array(this.font.data)) };
const request = {
face: {
Custom: base64js.fromByteArray(new Uint8Array(this.font.data)),
},
face: fontFace,
fontIndex: 0,
glyphs: this.glyphIDs.map(id => ({ id: id, transform: [1, 0, 0, 1, 0, 0] })),
pointSize: this.font.opentypeFont.unitsPerEm,

View File

@ -9,7 +9,9 @@ base64 = "0.6"
bincode = "0.8"
env_logger = "0.3"
euclid = "0.15"
lazy_static = "0.2"
log = "0.3"
lru-cache = "0.1"
rocket = "0.3"
rocket_codegen = "0.3"
rocket_contrib = "0.3"

View File

@ -16,17 +16,21 @@ extern crate base64;
extern crate env_logger;
extern crate euclid;
extern crate fontsan;
extern crate lru_cache;
extern crate pathfinder_font_renderer;
extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
use app_units::Au;
use euclid::{Point2D, Transform2D};
use lru_cache::LruCache;
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey};
use pathfinder_partitioner::mesh_library::MeshLibrary;
use pathfinder_partitioner::partitioner::Partitioner;
@ -41,11 +45,20 @@ use rocket_contrib::json::Json;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::u32;
const CUBIC_ERROR_TOLERANCE: f32 = 0.1;
const MESH_LIBRARY_CACHE_SIZE: usize = 16;
lazy_static! {
static ref MESH_LIBRARY_CACHE: Mutex<LruCache<MeshLibraryCacheKey, PartitionResponder>> = {
Mutex::new(LruCache::new(MESH_LIBRARY_CACHE_SIZE))
};
}
static STATIC_INDEX_PATH: &'static str = "../client/index.html";
static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html";
static STATIC_SVG_DEMO_PATH: &'static str = "../client/svg-demo.html";
@ -78,6 +91,12 @@ static BUILTIN_SVGS: [(&'static str, &'static str); 1] = [
("tiger", "../../resources/svg/Ghostscript_Tiger.svg"),
];
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct MeshLibraryCacheKey {
builtin_font_name: String,
glyph_ids: Vec<u32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct SubpathRange {
start: u32,
@ -154,7 +173,7 @@ struct PartitionSvgPathSegment {
}
struct PathPartitioningResult {
encoded_data: Vec<u8>,
encoded_data: Arc<Vec<u8>>,
time: Duration,
}
@ -177,7 +196,7 @@ impl PathPartitioningResult {
drop(partitioner.library().serialize_into(&mut data_buffer));
PathPartitioningResult {
encoded_data: data_buffer.into_inner(),
encoded_data: Arc::new(data_buffer.into_inner()),
time: time_elapsed,
}
}
@ -187,8 +206,9 @@ impl PathPartitioningResult {
}
}
#[derive(Clone)]
struct PartitionResponder {
data: Vec<u8>,
data: Arc<Vec<u8>>,
time: f64,
}
@ -197,7 +217,9 @@ impl<'r> Responder<'r> for PartitionResponder {
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));
// FIXME(pcwalton): Don't clone! Requires a `Cursor` implementation for `Arc<Vec<u8>>`…
builder.sized_body(Cursor::new((*self.data).clone()));
builder.ok()
}
}
@ -205,6 +227,24 @@ impl<'r> Responder<'r> for PartitionResponder {
#[post("/partition-font", format = "application/json", data = "<request>")]
fn partition_font(request: Json<PartitionFontRequest>)
-> Result<PartitionResponder, PartitionFontError> {
let cache_key = match request.face {
PartitionFontRequestFace::Builtin(ref builtin_font_name) => {
Some(MeshLibraryCacheKey {
builtin_font_name: (*builtin_font_name).clone(),
glyph_ids: request.glyphs.iter().map(|glyph| glyph.id).collect(),
})
}
_ => None,
};
if let Some(ref cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_LIBRARY_CACHE.lock() {
if let Some(cache_entry) = mesh_library_cache.get_mut(cache_key) {
return Ok((*cache_entry).clone())
}
}
}
// Fetch the OTF data.
let otf_data = match request.face {
PartitionFontRequestFace::Builtin(ref builtin_font_name) => {
@ -274,12 +314,20 @@ fn partition_font(request: Json<PartitionFontRequest>)
let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
&subpath_indices);
// Return the response.
// Build the response.
let elapsed_ms = path_partitioning_result.elapsed_ms();
Ok(PartitionResponder {
let responder = PartitionResponder {
data: path_partitioning_result.encoded_data,
time: elapsed_ms,
})
};
if let Some(cache_key) = cache_key {
if let Ok(mut mesh_library_cache) = MESH_LIBRARY_CACHE.lock() {
mesh_library_cache.insert(cache_key, responder.clone());
}
}
Ok(responder)
}
#[post("/partition-svg-paths", format = "application/json", data = "<request>")]

View File

@ -17,10 +17,11 @@ extern crate bit_vec;
extern crate byteorder;
extern crate env_logger;
extern crate euclid;
#[macro_use]
extern crate log;
extern crate pathfinder_path_utils;
extern crate serde;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;