feat(webui): embed latin fonts and serve via /fonts route

This commit is contained in:
xiaoxixi 2026-07-24 09:06:48 +08:00
parent 83d1d792bb
commit 3118083707
9 changed files with 61 additions and 0 deletions

View File

@ -69,6 +69,7 @@ fn build_webui(out_dir: &Path) {
"webui/package-lock.json",
"webui/svelte.config.js",
"webui/vite.config.js",
"webui/public",
] {
println!("cargo:rerun-if-changed={path}");
}

View File

@ -78,6 +78,41 @@ pub async fn webui_styles() -> Response {
)
}
const EMBEDDED_FONTS: &[(&str, &[u8])] = &[
(
"space-grotesk-500.woff2",
include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/space-grotesk-500.woff2")),
),
(
"space-grotesk-700.woff2",
include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/space-grotesk-700.woff2")),
),
(
"jetbrains-mono-400.woff2",
include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/jetbrains-mono-400.woff2")),
),
(
"jetbrains-mono-700.woff2",
include_bytes!(concat!(env!("OUT_DIR"), "/webui/fonts/jetbrains-mono-700.woff2")),
),
];
pub async fn webui_font(Path(name): Path<String>) -> Response {
let bytes = EMBEDDED_FONTS
.iter()
.find(|(font_name, _)| *font_name == name)
.map(|(_, bytes)| *bytes);
let Some(bytes) = bytes else {
return StatusCode::NOT_FOUND.into_response();
};
Response::builder()
.header(header::CONTENT_TYPE, "font/woff2")
.header(header::CACHE_CONTROL, "public, max-age=31536000, immutable")
.header("X-Content-Type-Options", "nosniff")
.body(Body::from(bytes))
.expect("valid font response")
}
#[derive(Debug)]
pub struct ApiError {
status: StatusCode,

View File

@ -593,6 +593,7 @@ fn build_router(state: Arc<GatewayState>) -> Router {
.route("/", routing::get(http::webui_index))
.route("/app.js", routing::get(http::webui_script))
.route("/styles.css", routing::get(http::webui_styles))
.route("/fonts/{name}", routing::get(http::webui_font))
.route("/health", routing::get(http::health))
.route("/api/auth/status", routing::get(auth::status))
.route("/api/auth/pair", routing::post(auth::pair))

View File

@ -14,6 +14,8 @@
"svelte": "^5.0.0"
},
"devDependencies": {
"@fontsource/jetbrains-mono": "^5.3.0",
"@fontsource/space-grotesk": "^5.3.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^24.0.0",
"svelte-check": "^4.0.0",
@ -490,6 +492,26 @@
"resolved": "https://mirrors.cloud.tencent.com/npm/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="
},
"node_modules/@fontsource/jetbrains-mono": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/@fontsource/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
"integrity": "sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==",
"dev": true,
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/space-grotesk": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/@fontsource/space-grotesk/-/space-grotesk-5.3.0.tgz",
"integrity": "sha512-ksnGizDPXIDuvqcTYTSrmZ+evx9sDlS8rp7+42BQ7wU+spt3twEoXfbJz672C+5CLg6VeUQwRy5RXshWb67LcQ==",
"dev": true,
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@internationalized/date": {
"version": "3.12.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/@internationalized/date/-/date-3.12.2.tgz",

View File

@ -18,6 +18,8 @@
"svelte": "^5.0.0"
},
"devDependencies": {
"@fontsource/jetbrains-mono": "^5.3.0",
"@fontsource/space-grotesk": "^5.3.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@types/node": "^24.0.0",
"svelte-check": "^4.0.0",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.