diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 9a9d453..64fc83e 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -4,6 +4,21 @@ Firelink distributes separate executable tools. Firelink's MIT license does not Exact versions, target hashes, sources, and build descriptions are pinned in `engines.lock.json`. +## Bundled fonts + +Firelink bundles variable WOFF2 web fonts through Fontsource. Each font is +licensed under the SIL Open Font License, Version 1.1. The corresponding +license and author information are available from the linked project sources. + +- Inter: +- Outfit: +- Roboto: +- Vazirmatn: +- Noto Sans Hebrew: +- Noto Sans SC: + +Fontsource distribution: + ## aria2 - Project: diff --git a/package-lock.json b/package-lock.json index 5f65bf4..41536d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,11 @@ "license": "MIT", "dependencies": { "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/noto-sans-hebrew": "^5.3.0", + "@fontsource-variable/noto-sans-sc": "^5.3.0", "@fontsource-variable/outfit": "^5.3.0", + "@fontsource-variable/roboto": "^5.3.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@formkit/auto-animate": "^0.10.0", "@tailwindcss/vite": "^4.3.3", "@tauri-apps/api": "^2.11.1", @@ -92,6 +96,24 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/noto-sans-hebrew": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-hebrew/-/noto-sans-hebrew-5.3.0.tgz", + "integrity": "sha512-jGIZ2EOCTtT1ZhLthz6nEpPXGMniSPI/0tv5CDFDZ7MPYyoj6FskW7WJ0YTyesFADdzjx7F2Pg5W1PWD6y9UFw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/noto-sans-sc": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-sc/-/noto-sans-sc-5.3.0.tgz", + "integrity": "sha512-lNar1dF7Ik/lHNPo/7JWG0TolXY29LtsqYgMvEysooZ5bsO9uH4shJmRrwyJ3PjyTPljhpMJEK0jDuLSU4vJ1w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource-variable/outfit": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource-variable/outfit/-/outfit-5.3.0.tgz", @@ -101,6 +123,24 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/roboto": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/roboto/-/roboto-5.3.0.tgz", + "integrity": "sha512-BoaaZiQf8fgtxv07KXkTwx1bKz/EoqIlTXq2OOKKXINxp6qXWdnlChqV+hSaDANS49yyUASFl3OVBMMDTCIHFA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/vazirmatn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/vazirmatn/-/vazirmatn-5.3.0.tgz", + "integrity": "sha512-ZA68W8JHbMdNzzwjYHUWKB2PH22QpxNEnk0ukcXM+XiqYMV6ZWJZAKuNXcrMR2JjuIAwsfAU3KknCXPI6wXUvw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@formkit/auto-animate": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.10.0.tgz", diff --git a/package.json b/package.json index 7b80729..4c91e21 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,11 @@ }, "dependencies": { "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/noto-sans-hebrew": "^5.3.0", + "@fontsource-variable/noto-sans-sc": "^5.3.0", "@fontsource-variable/outfit": "^5.3.0", + "@fontsource-variable/roboto": "^5.3.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@formkit/auto-animate": "^0.10.0", "@tailwindcss/vite": "^4.3.3", "@tauri-apps/api": "^2.11.1", diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index dbdb1bd..6406830 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -215,12 +215,16 @@ pub enum Theme { } #[derive(Clone, Debug, Serialize, Deserialize, TS)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "kebab-case")] #[ts(export, export_to = "../../src/bindings/")] pub enum FontFamily { System, Inter, Outfit, + Vazirmatn, + NotoSansHebrew, + NotoSansSc, + Roboto, Serif, Monospace, } diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 995bff3..fcc5b0e 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -196,7 +196,17 @@ fn sanitize_persisted_setting_values(state: &mut Value) { sanitize_allowed_string( state, "fontFamily", - &["system", "inter", "outfit", "serif", "monospace"], + &[ + "system", + "inter", + "outfit", + "vazirmatn", + "noto-sans-hebrew", + "noto-sans-sc", + "roboto", + "serif", + "monospace", + ], ); sanitize_allowed_string( state, @@ -704,6 +714,20 @@ mod tests { assert!(matches!(settings.font_family, FontFamily::System)); } + #[test] + fn curated_font_families_are_preserved() { + for value in ["vazirmatn", "noto-sans-hebrew", "noto-sans-sc", "roboto"] { + let stored = json!({ + "state": {"fontFamily": value}, + "version": 5 + }); + + let settings = decode_stored_settings(&Value::String(stored.to_string())).unwrap(); + + assert_eq!(serde_json::to_value(settings.font_family).unwrap(), json!(value)); + } + } + #[test] fn invalid_window_control_style_uses_automatic_layout() { let stored = json!({ diff --git a/src/bindings/FontFamily.ts b/src/bindings/FontFamily.ts index 7023a86..5aa13eb 100644 --- a/src/bindings/FontFamily.ts +++ b/src/bindings/FontFamily.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type FontFamily = "system" | "inter" | "outfit" | "serif" | "monospace"; +export type FontFamily = "system" | "inter" | "outfit" | "vazirmatn" | "noto-sans-hebrew" | "noto-sans-sc" | "roboto" | "serif" | "monospace"; diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index a9a5576..9953f54 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -711,6 +711,10 @@ runEngineChecks(false); { value: 'system', label: t($ => $.settings.lookAndFeel.fontFamilySystem) }, { value: 'inter', label: t($ => $.settings.lookAndFeel.fontFamilyInter) }, { value: 'outfit', label: t($ => $.settings.lookAndFeel.fontFamilyOutfit) }, + { value: 'vazirmatn', label: t($ => $.settings.lookAndFeel.fontFamilyVazirmatn) }, + { value: 'noto-sans-hebrew', label: t($ => $.settings.lookAndFeel.fontFamilyNotoSansHebrew) }, + { value: 'noto-sans-sc', label: t($ => $.settings.lookAndFeel.fontFamilyNotoSansSc) }, + { value: 'roboto', label: t($ => $.settings.lookAndFeel.fontFamilyRoboto) }, { value: 'serif', label: t($ => $.settings.lookAndFeel.fontFamilySerif) }, { value: 'monospace', label: t($ => $.settings.lookAndFeel.fontFamilyMonospace) }, ]; diff --git a/src/i18n/catalogs/en.ts b/src/i18n/catalogs/en.ts index f92647a..6c0e756 100644 --- a/src/i18n/catalogs/en.ts +++ b/src/i18n/catalogs/en.ts @@ -623,6 +623,10 @@ const common = { fontFamilySystem: 'System UI (recommended)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn (Persian)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew', + fontFamilyNotoSansSc: 'Noto Sans SC (Simplified Chinese)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: 'Serif', fontFamilyMonospace: 'Monospace', fontSize: 'Font size', diff --git a/src/i18n/catalogs/fa.ts b/src/i18n/catalogs/fa.ts index 94b68c6..b394b28 100644 --- a/src/i18n/catalogs/fa.ts +++ b/src/i18n/catalogs/fa.ts @@ -623,6 +623,10 @@ const fa = { fontFamilySystem: 'رابط کاربری سیستم (پیشنهادشده)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn (فارسی)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew (عبری)', + fontFamilyNotoSansSc: 'Noto Sans SC (چینی ساده‌شده)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: 'سریف', fontFamilyMonospace: 'تک‌فاصله', fontSize: 'اندازه قلم', diff --git a/src/i18n/catalogs/he.ts b/src/i18n/catalogs/he.ts index cc2fc46..9fffbd0 100644 --- a/src/i18n/catalogs/he.ts +++ b/src/i18n/catalogs/he.ts @@ -623,6 +623,10 @@ const he = { fontFamilySystem: 'ממשק המערכת (מומלץ)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn (פרסית)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew (עברית)', + fontFamilyNotoSansSc: 'Noto Sans SC (סינית מפושטת)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: 'סריף', fontFamilyMonospace: 'רוחב קבוע', fontSize: 'גודל גופן', diff --git a/src/i18n/catalogs/ru.ts b/src/i18n/catalogs/ru.ts index 8407e33..ec524eb 100644 --- a/src/i18n/catalogs/ru.ts +++ b/src/i18n/catalogs/ru.ts @@ -623,6 +623,10 @@ const ru = { fontFamilySystem: 'Системный интерфейс (рекомендуется)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn (персидский)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew (иврит)', + fontFamilyNotoSansSc: 'Noto Sans SC (упрощённый китайский)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: 'С засечками', fontFamilyMonospace: 'Моноширинный', fontSize: 'Размер шрифта', diff --git a/src/i18n/catalogs/uk.ts b/src/i18n/catalogs/uk.ts index 0ba23ad..8f3299f 100644 --- a/src/i18n/catalogs/uk.ts +++ b/src/i18n/catalogs/uk.ts @@ -623,6 +623,10 @@ const uk = { fontFamilySystem: 'Системний інтерфейс (рекомендовано)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn (перська)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew (іврит)', + fontFamilyNotoSansSc: 'Noto Sans SC (спрощена китайська)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: 'Із зарубками', fontFamilyMonospace: 'Моноширинний', fontSize: 'Розмір шрифту', diff --git a/src/i18n/catalogs/zh-CN.ts b/src/i18n/catalogs/zh-CN.ts index 1d3dc2d..3a5a4bc 100644 --- a/src/i18n/catalogs/zh-CN.ts +++ b/src/i18n/catalogs/zh-CN.ts @@ -623,6 +623,10 @@ const zhCN = { fontFamilySystem: '系统界面字体(推荐)', fontFamilyInter: 'Inter', fontFamilyOutfit: 'Outfit', + fontFamilyVazirmatn: 'Vazirmatn(波斯语)', + fontFamilyNotoSansHebrew: 'Noto Sans Hebrew(希伯来语)', + fontFamilyNotoSansSc: 'Noto Sans SC(简体中文)', + fontFamilyRoboto: 'Roboto', fontFamilySerif: '衬线', fontFamilyMonospace: '等宽', fontSize: '字体大小', diff --git a/src/i18n/resources.test.ts b/src/i18n/resources.test.ts index 0cde90d..551ee62 100644 --- a/src/i18n/resources.test.ts +++ b/src/i18n/resources.test.ts @@ -96,6 +96,7 @@ describe('translation catalogs', () => { 'settings.lookAndFeel.nord', 'settings.lookAndFeel.fontFamilyInter', 'settings.lookAndFeel.fontFamilyOutfit', + 'settings.lookAndFeel.fontFamilyRoboto', 'settings.lookAndFeel.windowControlStyleWindows', 'settings.lookAndFeel.windowControlStyleGnome', 'settings.network.chromeWindows', diff --git a/src/index.css b/src/index.css index 42f6cf5..75d5b8f 100644 --- a/src/index.css +++ b/src/index.css @@ -232,21 +232,25 @@ --font-stack-system: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; --font-stack-inter: "Inter Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; --font-stack-outfit: "Outfit Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-stack-vazirmatn: "Vazirmatn Variable", var(--font-stack-system); + --font-stack-noto-sans-hebrew: "Noto Sans Hebrew Variable", var(--font-stack-system); + --font-stack-noto-sans-sc: "Noto Sans SC Variable", var(--font-stack-system); + --font-stack-roboto: "Roboto Variable", var(--font-stack-system); --font-stack-serif: Georgia, "Times New Roman", var(--font-locale-fallback), serif; --font-stack-monospace: ui-monospace, "SF Mono", Monaco, "Cascadia Code", "Fira Code", "JetBrains Mono", var(--font-locale-fallback), monospace; --font-sans: var(--font-stack-system); } html[lang="fa"] { - --font-locale-fallback: "Vazirmatn", "Noto Sans Arabic", "Segoe UI", Tahoma, Arial, sans-serif; + --font-locale-fallback: "Geeza Pro", "Noto Sans Arabic", "Segoe UI", Tahoma, Arial, sans-serif; } html[lang="he"] { - --font-locale-fallback: "Arial Hebrew", "Noto Sans Hebrew", "Segoe UI", Arial, sans-serif; + --font-locale-fallback: "Arial Hebrew", "Segoe UI", Arial, sans-serif; } html[lang="zh-CN"] { - --font-locale-fallback: "PingFang SC", "Microsoft YaHei UI", "Noto Sans CJK SC", "Noto Sans SC", sans-serif; + --font-locale-fallback: "PingFang SC", "Microsoft YaHei UI", "Noto Sans CJK SC", sans-serif; } html[lang="ru"], @@ -266,6 +270,22 @@ html[data-font-family="outfit"] { --font-sans: var(--font-stack-outfit); } +html[data-font-family="vazirmatn"] { + --font-sans: var(--font-stack-vazirmatn); +} + +html[data-font-family="noto-sans-hebrew"] { + --font-sans: var(--font-stack-noto-sans-hebrew); +} + +html[data-font-family="noto-sans-sc"] { + --font-sans: var(--font-stack-noto-sans-sc); +} + +html[data-font-family="roboto"] { + --font-sans: var(--font-stack-roboto); +} + html[data-font-family="serif"] { --font-sans: var(--font-stack-serif); } diff --git a/src/main.tsx b/src/main.tsx index 60bb629..b8ea1ad 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,11 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "@fontsource-variable/inter/wght.css"; +import "@fontsource-variable/noto-sans-hebrew/wght.css"; +import "@fontsource-variable/noto-sans-sc/wght.css"; import "@fontsource-variable/outfit/wght.css"; +import "@fontsource-variable/roboto/wght.css"; +import "@fontsource-variable/vazirmatn/wght.css"; import "./index.css"; import App from "./App"; import { i18nReady } from "./i18n"; diff --git a/src/store/useSettingsStore.ts b/src/store/useSettingsStore.ts index 0744f7a..8bcec3c 100644 --- a/src/store/useSettingsStore.ts +++ b/src/store/useSettingsStore.ts @@ -75,7 +75,17 @@ const notifySettingsPersistenceError = () => { }; const THEME_VALUES = ['system', 'light', 'dark', 'dracula', 'nord'] as const; -const FONT_FAMILY_VALUES = ['system', 'inter', 'outfit', 'serif', 'monospace'] as const; +const FONT_FAMILY_VALUES = [ + 'system', + 'inter', + 'outfit', + 'vazirmatn', + 'noto-sans-hebrew', + 'noto-sans-sc', + 'roboto', + 'serif', + 'monospace', +] as const; const WINDOW_CONTROL_STYLE_VALUES = ['auto', 'macos', 'windows', 'gnome', 'minimal'] as const; const APP_FONT_SIZE_VALUES = ['small', 'standard', 'large'] as const; const LIST_ROW_DENSITY_VALUES = ['compact', 'standard', 'relaxed'] as const;