diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 13fb822d..88ac34a3 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -113,8 +113,8 @@ class ThreeDController extends DemoAppController { 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)); } diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index fc7390d6..0360b3fa 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -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 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 diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts index a400588b..d8cb672f 100644 --- a/demo/client/src/benchmark.ts +++ b/demo/client/src/benchmark.ts @@ -94,8 +94,8 @@ class BenchmarkAppController extends DemoAppController { 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); diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts index f7eabdbe..d94f636e 100644 --- a/demo/client/src/mesh-debugger.ts +++ b/demo/client/src/mesh-debugger.ts @@ -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; diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index 0053ca8e..db15f213 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -184,8 +184,8 @@ class TextDemoController extends DemoAppController { 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); } diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 3ea3dc8f..1f08c49d 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -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 { // 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, diff --git a/demo/server/Cargo.toml b/demo/server/Cargo.toml index e9c3bd48..db525b3c 100644 --- a/demo/server/Cargo.toml +++ b/demo/server/Cargo.toml @@ -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" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index f0ec2d9a..2a3b824d 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -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> = { + 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, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct SubpathRange { start: u32, @@ -154,7 +173,7 @@ struct PartitionSvgPathSegment { } struct PathPartitioningResult { - encoded_data: Vec, + encoded_data: Arc>, 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, + data: Arc>, 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>`… + 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 = "")] fn partition_font(request: Json) -> Result { + 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) 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 = "")] diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index b4d9a850..86992165 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -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;