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:
parent
0e3ffc0991
commit
f4e9ef6af9
10 changed files with 162 additions and 135 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -870,6 +870,7 @@ dependencies = [
|
|||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"shared",
|
||||
"simple_logger",
|
||||
]
|
||||
|
@ -1463,6 +1464,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"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]]
|
||||
|
|
|
@ -63,3 +63,7 @@ default_features = false
|
|||
|
||||
[dependencies.proc_macros]
|
||||
path = "./crates/proc-macros"
|
||||
|
||||
[dependencies.sha2]
|
||||
version = "0.10"
|
||||
features = ["asm"]
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use super::templates::HyperlegibleTemplate;
|
||||
use askama::Template;
|
||||
use rocket::http::Status;
|
||||
use rust_embed::RustEmbed;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
|
@ -8,11 +11,11 @@ struct Assets;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Files {
|
||||
pub style: TextFile,
|
||||
pub hyperlegible: TextFile,
|
||||
pub atkinson_latin_woff2: BinaryFile,
|
||||
pub atkinson_latin_ext_woff2: BinaryFile,
|
||||
pub atkinson_all_woff: BinaryFile,
|
||||
pub style: TextFile,
|
||||
pub hyperlegible: TextFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -42,6 +45,44 @@ pub struct TextFile {
|
|||
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 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> {
|
||||
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,
|
||||
}
|
||||
fn get_sha256_hash(string: &String) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(string);
|
||||
let result = hasher.finalize();
|
||||
hex::encode(result)
|
||||
}
|
||||
|
||||
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> {
|
||||
let file = get_binary_file(filename, extension)?;
|
||||
match std::str::from_utf8(&file.data) {
|
||||
Ok(string) => Some(TextFile {
|
||||
text: string.into(),
|
||||
metadata: file.metadata,
|
||||
}),
|
||||
Err(_) => None,
|
||||
fn get_hyperlegible(
|
||||
latin_woff2_filename: String,
|
||||
latin_ext_woff2_filename: String,
|
||||
all_woff_filename: String,
|
||||
) -> TextFile {
|
||||
let hyperlegible_template = HyperlegibleTemplate {
|
||||
atkinson_latin_woff2_filename: latin_woff2_filename,
|
||||
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() {
|
||||
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 {
|
||||
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2")
|
||||
.unwrap(),
|
||||
atkinson_latin_ext_woff2: get_binary_file(
|
||||
"atkinson-hyperlegible-latin-ext-400-normal",
|
||||
"woff2",
|
||||
)
|
||||
.unwrap(),
|
||||
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(),
|
||||
style: get_text_file("style", "css").unwrap(),
|
||||
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
|
||||
style: TextFile::get("style", "css").unwrap(),
|
||||
hyperlegible: get_hyperlegible(
|
||||
atkinson_latin_woff2.metadata.get_hash_filename(),
|
||||
atkinson_latin_ext_woff2.metadata.get_hash_filename(),
|
||||
atkinson_all_woff.metadata.get_hash_filename(),
|
||||
),
|
||||
atkinson_latin_woff2,
|
||||
atkinson_latin_ext_woff2,
|
||||
atkinson_all_woff,
|
||||
};
|
||||
FILES.set(files).unwrap();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod files;
|
||||
mod responders;
|
||||
mod routes;
|
||||
pub mod templates;
|
||||
|
||||
|
|
44
src/assets/responders.rs
Normal file
44
src/assets/responders.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -1,47 +1,6 @@
|
|||
use super::{files::get_file_wrapper, templates::ErrorTemplate};
|
||||
use rocket::{
|
||||
http::{Header, Status},
|
||||
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 }
|
||||
}
|
||||
}
|
||||
use super::files::get_file_wrapper;
|
||||
use crate::assets::responders::{CachedResponse, RawWoff2Font, RawWoffFont};
|
||||
use rocket::{http::Status, response::content::RawCss};
|
||||
|
||||
#[get("/css/<style>")]
|
||||
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {
|
||||
|
|
|
@ -3,7 +3,12 @@ use proc_macros::uses_base_template;
|
|||
use shared::names::Site;
|
||||
|
||||
pub struct BaseTemplate {
|
||||
pub hyperlegible_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_ext_woff2_filename: String,
|
||||
pub atkinson_all_woff_filename: String,
|
||||
|
|
|
@ -20,17 +20,12 @@ fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
|
|||
fn get_base_template() -> Result<BaseTemplate, Status> {
|
||||
let files = get_file_wrapper()?;
|
||||
|
||||
let hyperlegible_filename = get_hash_filename(&files.hyperlegible.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 {
|
||||
hyperlegible_filename,
|
||||
style_filename,
|
||||
atkinson_latin_woff2_filename,
|
||||
atkinson_latin_ext_woff2_filename,
|
||||
atkinson_all_woff_filename,
|
||||
};
|
||||
|
||||
Ok(template)
|
||||
|
|
|
@ -5,51 +5,7 @@
|
|||
{% block head %}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<style>
|
||||
@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.hyperlegible_filename }}" />
|
||||
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
|
||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||
src: url("/public/woff2/{{ atkinson_latin_ext_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0100-024F,
|
||||
U+0259,
|
||||
U+1E00-1EFF,
|
||||
|
@ -21,8 +21,8 @@
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"),
|
||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||
src: url("/public/woff2/{{ atkinson_latin_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0000-00FF,
|
||||
U+0131,
|
||||
U+0152-0153,
|
Loading…
Reference in a new issue