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); this.loadInitialFile(this.builtinFileURI);
} }
protected fileLoaded(fileData: ArrayBuffer): void { protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
const font = new PathfinderFont(fileData); const font = new PathfinderFont(fileData, builtinName);
this.monumentPromise.then(monument => this.layoutMonument(font, fileData, monument)); 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) { protected fetchFile(file: string, builtinFileURI: string) {
window.fetch(`${builtinFileURI}/${file}`) window.fetch(`${builtinFileURI}/${file}`)
.then(response => response.arrayBuffer()) .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; protected abstract get defaultFile(): string;
} }
@ -116,7 +116,7 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
this.filePickerView = FilePickerView.create(); this.filePickerView = FilePickerView.create();
if (this.filePickerView != null) { 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 const selectFileElement = document.getElementById('pf-select-file') as

View File

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

View File

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

View File

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

View File

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

View File

@ -16,17 +16,21 @@ extern crate base64;
extern crate env_logger; extern crate env_logger;
extern crate euclid; extern crate euclid;
extern crate fontsan; extern crate fontsan;
extern crate lru_cache;
extern crate pathfinder_font_renderer; extern crate pathfinder_font_renderer;
extern crate pathfinder_partitioner; extern crate pathfinder_partitioner;
extern crate pathfinder_path_utils; extern crate pathfinder_path_utils;
extern crate rocket; extern crate rocket;
extern crate rocket_contrib; extern crate rocket_contrib;
#[macro_use]
extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
use app_units::Au; use app_units::Au;
use euclid::{Point2D, Transform2D}; use euclid::{Point2D, Transform2D};
use lru_cache::LruCache;
use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey}; use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey, GlyphKey};
use pathfinder_partitioner::mesh_library::MeshLibrary; use pathfinder_partitioner::mesh_library::MeshLibrary;
use pathfinder_partitioner::partitioner::Partitioner; use pathfinder_partitioner::partitioner::Partitioner;
@ -41,11 +45,20 @@ use rocket_contrib::json::Json;
use std::fs::File; use std::fs::File;
use std::io::{self, Cursor, Read}; use std::io::{self, Cursor, Read};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::u32; use std::u32;
const CUBIC_ERROR_TOLERANCE: f32 = 0.1; 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_INDEX_PATH: &'static str = "../client/index.html";
static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html"; static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html";
static STATIC_SVG_DEMO_PATH: &'static str = "../client/svg-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"), ("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)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct SubpathRange { struct SubpathRange {
start: u32, start: u32,
@ -154,7 +173,7 @@ struct PartitionSvgPathSegment {
} }
struct PathPartitioningResult { struct PathPartitioningResult {
encoded_data: Vec<u8>, encoded_data: Arc<Vec<u8>>,
time: Duration, time: Duration,
} }
@ -177,7 +196,7 @@ impl PathPartitioningResult {
drop(partitioner.library().serialize_into(&mut data_buffer)); drop(partitioner.library().serialize_into(&mut data_buffer));
PathPartitioningResult { PathPartitioningResult {
encoded_data: data_buffer.into_inner(), encoded_data: Arc::new(data_buffer.into_inner()),
time: time_elapsed, time: time_elapsed,
} }
} }
@ -187,8 +206,9 @@ impl PathPartitioningResult {
} }
} }
#[derive(Clone)]
struct PartitionResponder { struct PartitionResponder {
data: Vec<u8>, data: Arc<Vec<u8>>,
time: f64, time: f64,
} }
@ -197,7 +217,9 @@ impl<'r> Responder<'r> for PartitionResponder {
let mut builder = Response::build(); let mut builder = Response::build();
builder.header(ContentType::new("application", "vnd.mozilla.pfml")); builder.header(ContentType::new("application", "vnd.mozilla.pfml"));
builder.header(Header::new("Server-Timing", format!("Partitioning={}", self.time))); 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() builder.ok()
} }
} }
@ -205,6 +227,24 @@ impl<'r> Responder<'r> for PartitionResponder {
#[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>)
-> Result<PartitionResponder, PartitionFontError> { -> 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. // 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) => {
@ -274,12 +314,20 @@ 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);
// Return the response. // Build the response.
let elapsed_ms = path_partitioning_result.elapsed_ms(); let elapsed_ms = path_partitioning_result.elapsed_ms();
Ok(PartitionResponder { let responder = PartitionResponder {
data: path_partitioning_result.encoded_data, data: path_partitioning_result.encoded_data,
time: elapsed_ms, 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>")] #[post("/partition-svg-paths", format = "application/json", data = "<request>")]

View File

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