Automatically load some built-in fonts and SVGs

This commit is contained in:
Patrick Walton 2017-08-30 19:48:18 -07:00
parent 58b558de64
commit d6767219ff
8 changed files with 114 additions and 28 deletions

View File

@ -7,7 +7,7 @@
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
opacity: 1.0; opacity: 1.0;
transition: opacity 300ms, transform 300ms; transition: opacity 300ms, transform 300ms, visibility 300ms;
transform: translateY(0); transform: translateY(0);
} }
#pf-settings-button:not(:hover) { #pf-settings-button:not(:hover) {
@ -20,6 +20,7 @@
#pf-settings.pf-invisible { #pf-settings.pf-invisible {
opacity: 0.0; opacity: 0.0;
transform: translateY(1em); transform: translateY(1em);
visibility: hidden;
} }
button > svg { button > svg {
width: 1.25em; width: 1.25em;

View File

@ -17,7 +17,6 @@
aria-label="Close"> aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h4 class="card-title">Settings</h4>
<form> <form>
<div class="form-group"> <div class="form-group">
<label for="pf-aa-level-select">Antialiasing</label> <label for="pf-aa-level-select">Antialiasing</label>

View File

@ -18,7 +18,6 @@
aria-label="Close"> aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h4 class="card-title">Settings</h4>
<form> <form>
<div class="form-group"> <div class="form-group">
<label for="pf-select-file">SVG document</label> <label for="pf-select-file">SVG document</label>

View File

@ -17,7 +17,6 @@
aria-label="Close"> aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h4 class="card-title">Settings</h4>
<form> <form>
<div class="form-group"> <div class="form-group">
<label for="pf-select-file">Font</label> <label for="pf-select-file">Font</label>

View File

@ -50,6 +50,10 @@ export default abstract class AppController<View extends PathfinderView> {
this.updateAALevel(); this.updateAALevel();
} }
protected loadInitialFile() {
this.fileSelectionChanged();
}
private updateAALevel() { private updateAALevel() {
const selectedOption = this.aaLevelSelect.selectedOptions[0]; const selectedOption = this.aaLevelSelect.selectedOptions[0];
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value)); const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
@ -83,13 +87,24 @@ export default abstract class AppController<View extends PathfinderView> {
return; return;
} }
// Remove the "Custom…" placeholder if it exists.
const placeholder = document.getElementById('pf-custom-option-placeholder'); const placeholder = document.getElementById('pf-custom-option-placeholder');
if (placeholder != null) if (placeholder != null)
this.selectFileElement.removeChild(placeholder); this.selectFileElement.removeChild(placeholder);
// Fetch the file.
window.fetch(`${this.builtinFileURI}/${selectedOption.value}`)
.then(response => response.arrayBuffer())
.then(data => {
this.fileData = data;
this.fileLoaded();
});
} }
protected abstract fileLoaded(): void; protected abstract fileLoaded(): void;
protected abstract get builtinFileURI(): string;
protected abstract createView(canvas: HTMLCanvasElement, protected abstract createView(canvas: HTMLCanvasElement,
commonShaderSource: string, commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>): View; shaderSources: ShaderMap<ShaderProgramSource>): View;

View File

@ -29,6 +29,8 @@ const SVG_NS: string = "http://www.w3.org/2000/svg";
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
const BUILTIN_SVG_URI: string = "/svg/demo";
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy, none: NoAAStrategy,
ssaa: SSAAStrategy, ssaa: SSAAStrategy,
@ -59,6 +61,8 @@ class SVGDemoController extends AppController<SVGDemoView> {
this.svg = document.getElementById('pf-svg') as Element as SVGSVGElement; this.svg = document.getElementById('pf-svg') as Element as SVGSVGElement;
this.pathElements = []; this.pathElements = [];
this.loadInitialFile();
} }
protected fileLoaded() { protected fileLoaded() {
@ -141,6 +145,10 @@ class SVGDemoController extends AppController<SVGDemoView> {
}); });
} }
protected get builtinFileURI(): string {
return BUILTIN_SVG_URI;
}
private meshesReceived() { private meshesReceived() {
this.view.then(view => { this.view.then(view => {
view.uploadPathData(this.pathElements); view.uploadPathData(this.pathElements);

View File

@ -68,6 +68,8 @@ const INITIAL_FONT_SIZE: number = 72.0;
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
const BUILTIN_FONT_URI: string = "/otf/demo";
const B_POSITION_SIZE: number = 8; const B_POSITION_SIZE: number = 8;
const B_PATH_INDEX_SIZE: number = 2; const B_PATH_INDEX_SIZE: number = 2;
@ -119,6 +121,8 @@ class TextDemoController extends AppController<TextDemoView> {
this.fontSize = INITIAL_FONT_SIZE; this.fontSize = INITIAL_FONT_SIZE;
this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label')); this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label'));
this.loadInitialFile();
} }
protected createView(canvas: HTMLCanvasElement, protected createView(canvas: HTMLCanvasElement,
@ -144,8 +148,12 @@ class TextDemoController extends AppController<TextDemoView> {
this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index); this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index);
// Build the partitioning request to the server. // Build the partitioning request to the server.
//
// FIXME(pcwalton): If this is a builtin font, don't resend it to the server!
const request = { const request = {
otf: base64js.fromByteArray(new Uint8Array(this.fileData)), face: {
Custom: base64js.fromByteArray(new Uint8Array(this.fileData)),
},
fontIndex: 0, fontIndex: 0,
glyphs: this.uniqueGlyphs.map(glyph => { glyphs: this.uniqueGlyphs.map(glyph => {
const metrics = glyph.metrics; const metrics = glyph.metrics;
@ -200,6 +208,10 @@ class TextDemoController extends AppController<TextDemoView> {
this.view.then(view => view.attachText()); this.view.then(view => view.attachText());
} }
protected get builtinFileURI(): string {
return BUILTIN_FONT_URI;
}
private fpsLabel: HTMLElement; private fpsLabel: HTMLElement;
font: opentype.Font; font: opentype.Font;

View File

@ -33,7 +33,7 @@ use rocket::response::{NamedFile, Responder, Response};
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use serde::Serialize; use serde::Serialize;
use std::fs::File; use std::fs::File;
use std::io; use std::io::{self, Read};
use std::mem; use std::mem;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::u32; use std::u32;
@ -51,6 +51,16 @@ static STATIC_JS_PATHFINDER_PATH: &'static str = "../client";
static STATIC_SVG_OCTICONS_PATH: &'static str = "../client/node_modules/octicons/build/svg"; static STATIC_SVG_OCTICONS_PATH: &'static str = "../client/node_modules/octicons/build/svg";
static STATIC_GLSL_PATH: &'static str = "../../shaders"; static STATIC_GLSL_PATH: &'static str = "../../shaders";
static BUILTIN_FONTS: [(&'static str, &'static str); 3] = [
("open-sans", "../../resources/fonts/open-sans/OpenSans-Regular.ttf"),
("nimbus-sans", "../../resources/fonts/nimbus-sans/NimbusSanL-Regu.ttf"),
("eb-garamond", "../../resources/fonts/eb-garamond/EBGaramond12-Regular.ttf"),
];
static BUILTIN_SVGS: [(&'static str, &'static str); 1] = [
("tiger", "../../resources/svg/Ghostscript_Tiger.svg"),
];
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct SubpathRange { struct SubpathRange {
start: u32, start: u32,
@ -77,14 +87,22 @@ impl IndexRange {
} }
} }
#[allow(non_snake_case)]
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
struct PartitionFontRequest { struct PartitionFontRequest {
// Base64 encoded. face: PartitionFontRequestFace,
otf: String, #[serde(rename = "fontIndex")]
fontIndex: u32, font_index: u32,
glyphs: Vec<PartitionGlyph>, glyphs: Vec<PartitionGlyph>,
pointSize: f64, #[serde(rename = "pointSize")]
point_size: f64,
}
#[derive(Clone, Serialize, Deserialize)]
enum PartitionFontRequestFace {
/// One of the builtin fonts in `BUILTIN_FONTS`.
Builtin(String),
/// Base64-encoded OTF data.
Custom(String),
} }
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize)]
@ -182,6 +200,7 @@ struct PartitionEncodedPathData {
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize)]
enum PartitionFontError { enum PartitionFontError {
UnknownBuiltinFont,
Base64DecodingFailed, Base64DecodingFailed,
FontSanitizationFailed, FontSanitizationFailed,
FontLoadingFailed, FontLoadingFailed,
@ -312,26 +331,44 @@ fn partition_paths(partitioner: &mut Partitioner, subpath_indices: &[SubpathRang
#[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>)
-> Json<Result<PartitionFontResponse, PartitionFontError>> { -> Json<Result<PartitionFontResponse, PartitionFontError>> {
// Decode Base64-encoded OTF data. // Fetch the OTF data.
let unsafe_otf_data = match base64::decode(&request.otf) { let otf_data = match request.face {
Ok(unsafe_otf_data) => unsafe_otf_data, PartitionFontRequestFace::Builtin(ref builtin_font_name) => {
Err(_) => return Json(Err(PartitionFontError::Base64DecodingFailed)), // Read in the builtin font.
}; match BUILTIN_FONTS.iter().filter(|& &(name, _)| name == builtin_font_name).next() {
Some(&(_, path)) => {
let mut data = vec![];
File::open(path).expect("Couldn't find builtin font!")
.read_to_end(&mut data)
.expect("Couldn't read builtin font!");
data
}
None => return Json(Err(PartitionFontError::UnknownBuiltinFont)),
}
}
PartitionFontRequestFace::Custom(ref encoded_data) => {
// Decode Base64-encoded OTF data.
let unsafe_otf_data = match base64::decode(encoded_data) {
Ok(unsafe_otf_data) => unsafe_otf_data,
Err(_) => return Json(Err(PartitionFontError::Base64DecodingFailed)),
};
// Sanitize. // Sanitize.
let otf_data = match fontsan::process(&unsafe_otf_data) { match fontsan::process(&unsafe_otf_data) {
Ok(otf_data) => otf_data, Ok(otf_data) => otf_data,
Err(_) => return Json(Err(PartitionFontError::FontSanitizationFailed)), Err(_) => return Json(Err(PartitionFontError::FontSanitizationFailed)),
}
}
}; };
// Parse glyph data. // Parse glyph data.
let font_key = FontKey::new(); let font_key = FontKey::new();
let font_instance_key = FontInstanceKey { let font_instance_key = FontInstanceKey {
font_key: font_key, font_key: font_key,
size: Au::from_f64_px(request.pointSize), size: Au::from_f64_px(request.point_size),
}; };
let mut font_context = FontContext::new(); let mut font_context = FontContext::new();
if font_context.add_font_from_memory(&font_key, otf_data, request.fontIndex).is_err() { if font_context.add_font_from_memory(&font_key, otf_data, request.font_index).is_err() {
return Json(Err(PartitionFontError::FontLoadingFailed)) return Json(Err(PartitionFontError::FontLoadingFailed))
} }
@ -476,15 +513,15 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
// Static files // Static files
#[get("/")] #[get("/")]
fn static_text_demo() -> io::Result<NamedFile> { fn static_demo_text() -> io::Result<NamedFile> {
NamedFile::open(STATIC_TEXT_DEMO_PATH) NamedFile::open(STATIC_TEXT_DEMO_PATH)
} }
#[get("/demo/svg")] #[get("/demo/svg")]
fn static_svg_demo() -> io::Result<NamedFile> { fn static_demo_svg() -> io::Result<NamedFile> {
NamedFile::open(STATIC_SVG_DEMO_PATH) NamedFile::open(STATIC_SVG_DEMO_PATH)
} }
#[get("/demo/3d")] #[get("/demo/3d")]
fn static_3d_demo() -> io::Result<NamedFile> { fn static_demo_3d() -> io::Result<NamedFile> {
NamedFile::open(STATIC_3D_DEMO_PATH) NamedFile::open(STATIC_3D_DEMO_PATH)
} }
#[get("/css/bootstrap/<file..>")] #[get("/css/bootstrap/<file..>")]
@ -523,6 +560,20 @@ fn static_svg_octicons(file: PathBuf) -> Option<NamedFile> {
fn static_glsl(file: PathBuf) -> Option<Shader> { fn static_glsl(file: PathBuf) -> Option<Shader> {
Shader::open(Path::new(STATIC_GLSL_PATH).join(file)).ok() Shader::open(Path::new(STATIC_GLSL_PATH).join(file)).ok()
} }
#[get("/otf/demo/<font_name>")]
fn static_otf_demo(font_name: String) -> Option<NamedFile> {
BUILTIN_FONTS.iter()
.filter(|& &(name, _)| name == font_name)
.next()
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok())
}
#[get("/svg/demo/<svg_name>")]
fn static_svg_demo(svg_name: String) -> Option<NamedFile> {
BUILTIN_SVGS.iter()
.filter(|& &(name, _)| name == svg_name)
.next()
.and_then(|&(_, path)| NamedFile::open(Path::new(path)).ok())
}
struct Shader { struct Shader {
file: File, file: File,
@ -546,9 +597,9 @@ fn main() {
rocket::ignite().mount("/", routes![ rocket::ignite().mount("/", routes![
partition_font, partition_font,
partition_svg_paths, partition_svg_paths,
static_text_demo, static_demo_text,
static_svg_demo, static_demo_svg,
static_3d_demo, static_demo_3d,
static_css_bootstrap, static_css_bootstrap,
static_css_octicons, static_css_octicons,
static_css_pathfinder_css, static_css_pathfinder_css,
@ -558,5 +609,7 @@ fn main() {
static_js_pathfinder, static_js_pathfinder,
static_svg_octicons, static_svg_octicons,
static_glsl, static_glsl,
static_otf_demo,
static_svg_demo,
]).launch(); ]).launch();
} }