Cache font mesh libraries on the server
This commit is contained in:
parent
7bbd02ed85
commit
b631fec80f
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>")]
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue