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);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>")]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue