seperate the hyperlegible font-face to a template

this lets me make the base html code cleaner and it lets me keep using a
seperate file for font-face stuff so the style.css is cleaner. this is
implemented by using the askama template engine with the same syntax
that was used in the style tag in base.html, but i render the template
to text instead of directly to a rocket responder and i hash it with the
sha2 crate. this hashing should only run once, at startup, so it
shouldn't be much of a performance hit.
This commit is contained in:
Fries 2023-07-12 18:41:39 -07:00
parent 0e3ffc0991
commit f4e9ef6af9
10 changed files with 162 additions and 135 deletions

11
Cargo.lock generated
View file

@ -870,6 +870,7 @@ dependencies = [
"rust-embed", "rust-embed",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"shared", "shared",
"simple_logger", "simple_logger",
] ]
@ -1463,6 +1464,16 @@ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"digest", "digest",
"sha2-asm",
]
[[package]]
name = "sha2-asm"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf27176fb5d15398e3a479c652c20459d9dac830dedd1fa55b42a77dbcdbfcea"
dependencies = [
"cc",
] ]
[[package]] [[package]]

View file

@ -63,3 +63,7 @@ default_features = false
[dependencies.proc_macros] [dependencies.proc_macros]
path = "./crates/proc-macros" path = "./crates/proc-macros"
[dependencies.sha2]
version = "0.10"
features = ["asm"]

View file

@ -1,5 +1,8 @@
use super::templates::HyperlegibleTemplate;
use askama::Template;
use rocket::http::Status; use rocket::http::Status;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use sha2::{Digest, Sha256};
use std::{borrow::Cow, sync::OnceLock}; use std::{borrow::Cow, sync::OnceLock};
#[derive(RustEmbed)] #[derive(RustEmbed)]
@ -8,11 +11,11 @@ struct Assets;
#[derive(Debug)] #[derive(Debug)]
pub struct Files { pub struct Files {
pub style: TextFile,
pub hyperlegible: TextFile,
pub atkinson_latin_woff2: BinaryFile, pub atkinson_latin_woff2: BinaryFile,
pub atkinson_latin_ext_woff2: BinaryFile, pub atkinson_latin_ext_woff2: BinaryFile,
pub atkinson_all_woff: BinaryFile, pub atkinson_all_woff: BinaryFile,
pub style: TextFile,
pub hyperlegible: TextFile,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -42,6 +45,44 @@ pub struct TextFile {
pub metadata: FileMetadata, pub metadata: FileMetadata,
} }
trait GetFile {
fn get(filename: &str, extension: &str) -> Option<Self>
where
Self: Sized;
}
impl GetFile for BinaryFile {
fn get(filename: &str, extension: &str) -> Option<Self> {
match Assets::get(&format!("{}.{}", filename, extension)) {
Some(file) => {
let metadata = FileMetadata {
filename: filename.into(),
extension: extension.into(),
hash: hex::encode(file.metadata.sha256_hash()),
};
Some(BinaryFile {
data: file.data,
metadata: metadata,
})
}
None => None,
}
}
}
impl GetFile for TextFile {
fn get(filename: &str, extension: &str) -> Option<Self> {
let file = BinaryFile::get(filename, extension)?;
match std::str::from_utf8(&file.data) {
Ok(string) => Some(TextFile {
text: string.into(),
metadata: file.metadata,
}),
Err(_) => None,
}
}
}
pub static FILES: OnceLock<Files> = OnceLock::new(); pub static FILES: OnceLock<Files> = OnceLock::new();
pub fn get_file_wrapper() -> Result<&'static Files, Status> { pub fn get_file_wrapper() -> Result<&'static Files, Status> {
@ -51,46 +92,57 @@ pub fn get_file_wrapper() -> Result<&'static Files, Status> {
} }
} }
fn get_binary_file(filename: &str, extension: &str) -> Option<BinaryFile> { fn get_sha256_hash(string: &String) -> String {
match Assets::get(&format!("{}.{}", filename, extension)) { let mut hasher = Sha256::new();
Some(file) => { hasher.update(string);
let metadata = FileMetadata { let result = hasher.finalize();
filename: filename.into(), hex::encode(result)
extension: extension.into(),
hash: hex::encode(file.metadata.sha256_hash()),
};
Some(BinaryFile {
data: file.data,
metadata: metadata,
})
}
None => None,
}
} }
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> { fn get_hyperlegible(
let file = get_binary_file(filename, extension)?; latin_woff2_filename: String,
match std::str::from_utf8(&file.data) { latin_ext_woff2_filename: String,
Ok(string) => Some(TextFile { all_woff_filename: String,
text: string.into(), ) -> TextFile {
metadata: file.metadata, let hyperlegible_template = HyperlegibleTemplate {
}), atkinson_latin_woff2_filename: latin_woff2_filename,
Err(_) => None, atkinson_latin_ext_woff2_filename: latin_ext_woff2_filename,
atkinson_all_woff_filename: all_woff_filename,
};
let rendered_template = hyperlegible_template.render().unwrap();
let hash = get_sha256_hash(&rendered_template);
let metadata = FileMetadata {
filename: "hyperlegible".into(),
extension: "css".into(),
hash,
};
TextFile {
text: rendered_template,
metadata,
} }
} }
pub fn initialize_files() { pub fn initialize_files() {
let atkinson_latin_woff2 =
BinaryFile::get("atkinson-hyperlegible-latin-400-normal", "woff2").unwrap();
let atkinson_latin_ext_woff2 =
BinaryFile::get("atkinson-hyperlegible-latin-ext-400-normal", "woff2").unwrap();
let atkinson_all_woff =
BinaryFile::get("atkinson-hyperlegible-all-400-normal", "woff").unwrap();
let files = Files { let files = Files {
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2") style: TextFile::get("style", "css").unwrap(),
.unwrap(), hyperlegible: get_hyperlegible(
atkinson_latin_ext_woff2: get_binary_file( atkinson_latin_woff2.metadata.get_hash_filename(),
"atkinson-hyperlegible-latin-ext-400-normal", atkinson_latin_ext_woff2.metadata.get_hash_filename(),
"woff2", atkinson_all_woff.metadata.get_hash_filename(),
) ),
.unwrap(), atkinson_latin_woff2,
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(), atkinson_latin_ext_woff2,
style: get_text_file("style", "css").unwrap(), atkinson_all_woff,
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
}; };
FILES.set(files).unwrap(); FILES.set(files).unwrap();
} }

View file

@ -1,4 +1,5 @@
pub mod files; pub mod files;
mod responders;
mod routes; mod routes;
pub mod templates; pub mod templates;

44
src/assets/responders.rs Normal file
View file

@ -0,0 +1,44 @@
use super::templates::ErrorTemplate;
use rocket::{
http::Header,
response::{self, Responder},
Response,
};
use std::borrow::Cow;
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff2")]
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff")]
pub struct RawWoffFont(pub Cow<'static, [u8]>);
#[derive(Responder)]
pub struct ErrorTemplateResponder<'a> {
template: ErrorTemplate<'a>,
}
pub struct CachedResponse<T> {
inner: T,
}
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
Response::build_from(self.inner.respond_to(request)?)
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
.ok()
}
}
impl<'r, T> From<T> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn from(value: T) -> Self {
CachedResponse { inner: value }
}
}

View file

@ -1,47 +1,6 @@
use super::{files::get_file_wrapper, templates::ErrorTemplate}; use super::files::get_file_wrapper;
use rocket::{ use crate::assets::responders::{CachedResponse, RawWoff2Font, RawWoffFont};
http::{Header, Status}, use rocket::{http::Status, response::content::RawCss};
response::{self, content::RawCss, Responder},
Response,
};
use std::borrow::Cow;
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff2")]
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
#[derive(Responder)]
#[response(status = 200, content_type = "font/woff")]
pub struct RawWoffFont(pub Cow<'static, [u8]>);
#[derive(Responder)]
pub struct ErrorTemplateResponder<'a> {
template: ErrorTemplate<'a>,
}
pub struct CachedResponse<T> {
inner: T,
}
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
Response::build_from(self.inner.respond_to(request)?)
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
.ok()
}
}
impl<'r, T> From<T> for CachedResponse<T>
where
T: Responder<'r, 'static>,
{
fn from(value: T) -> Self {
CachedResponse { inner: value }
}
}
#[get("/css/<style>")] #[get("/css/<style>")]
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> { pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {

View file

@ -3,7 +3,12 @@ use proc_macros::uses_base_template;
use shared::names::Site; use shared::names::Site;
pub struct BaseTemplate { pub struct BaseTemplate {
pub hyperlegible_filename: String,
pub style_filename: String, pub style_filename: String,
}
#[derive(Template)]
#[template(path = "hyperlegible.css", escape = "none")]
pub(super) struct HyperlegibleTemplate {
pub atkinson_latin_woff2_filename: String, pub atkinson_latin_woff2_filename: String,
pub atkinson_latin_ext_woff2_filename: String, pub atkinson_latin_ext_woff2_filename: String,
pub atkinson_all_woff_filename: String, pub atkinson_all_woff_filename: String,

View file

@ -20,17 +20,12 @@ fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
fn get_base_template() -> Result<BaseTemplate, Status> { fn get_base_template() -> Result<BaseTemplate, Status> {
let files = get_file_wrapper()?; let files = get_file_wrapper()?;
let hyperlegible_filename = get_hash_filename(&files.hyperlegible.metadata)?;
let style_filename = get_hash_filename(&files.style.metadata)?; let style_filename = get_hash_filename(&files.style.metadata)?;
let atkinson_latin_woff2_filename = get_hash_filename(&files.atkinson_latin_woff2.metadata)?;
let atkinson_latin_ext_woff2_filename =
get_hash_filename(&files.atkinson_latin_ext_woff2.metadata)?;
let atkinson_all_woff_filename = get_hash_filename(&files.atkinson_all_woff.metadata)?;
let template = BaseTemplate { let template = BaseTemplate {
hyperlegible_filename,
style_filename, style_filename,
atkinson_latin_woff2_filename,
atkinson_latin_ext_woff2_filename,
atkinson_all_woff_filename,
}; };
Ok(template) Ok(template)

View file

@ -5,51 +5,7 @@
{% block head %} {% block head %}
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<style> <link rel="stylesheet" href="/public/css/{{ base_template.hyperlegible_filename }}" />
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url("/public/woff2/{{ base_template.atkinson_latin_ext_woff2_filename }}") format("woff2"),
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0100-024F,
U+0259,
U+1E00-1EFF,
U+2020,
U+20A0-20AB,
U+20AD-20CF,
U+2113,
U+2C60-2C7F,
U+A720-A7FF
}
@font-face {
font-family: Atkinson Hyperlegible;
font-style: normal;
font-display: swap;
font-weight: 400;
src: url("/public/woff2/{{ base_template.atkinson_latin_woff2_filename }}") format("woff2"),
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0000-00FF,
U+0131,
U+0152-0153,
U+02BB-02BC,
U+02C6,
U+02DA,
U+02DC,
U+2000-206F,
U+2074,
U+20AC,
U+2122,
U+2191,
U+2193,
U+2212,
U+2215,
U+FEFF,
U+FFFD
}
</style>
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" /> <link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
{% endblock %} {% endblock %}
</head> </head>

View file

@ -3,8 +3,8 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"), src: url("/public/woff2/{{ atkinson_latin_ext_woff2_filename }}") format("woff2"),
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff"); url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0100-024F, unicode-range: U+0100-024F,
U+0259, U+0259,
U+1E00-1EFF, U+1E00-1EFF,
@ -21,8 +21,8 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
font-weight: 400; font-weight: 400;
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"), src: url("/public/woff2/{{ atkinson_latin_woff2_filename }}") format("woff2"),
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff"); url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
unicode-range: U+0000-00FF, unicode-range: U+0000-00FF,
U+0131, U+0131,
U+0152-0153, U+0152-0153,