From 93eacf91a08b1032b53660a1d76ccaad2b5894c9 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 3 Feb 2025 00:41:30 +0100 Subject: [PATCH] feat: load system fonts list at runtime on desktop, closes #287 #237 (#290) --- .github/workflows/pull-request.yml | 2 +- .github/workflows/release.yml | 2 +- Cargo.lock | 69 +++++++++++++++++++ apps/readest-app/src-tauri/Cargo.toml | 1 + apps/readest-app/src-tauri/src/lib.rs | 15 +++- .../reader/components/settings/FontPanel.tsx | 29 +++++--- apps/readest-app/src/utils/font.ts | 31 +++++++++ 7 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 apps/readest-app/src/utils/font.ts diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 56acaf77..16cbc237 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,7 +22,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev + sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev - name: Format working-directory: apps/readest-app/src-tauri run: cargo fmt --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93e89b07..a6af6d5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,7 +121,7 @@ jobs: if: matrix.config.os == 'ubuntu-22.04' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - uses: tauri-apps/tauri-action@v0 env: diff --git a/Cargo.lock b/Cargo.lock index 359cbb1f..233b5d18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "Readest" version = "0.2.1" dependencies = [ "cocoa 0.25.0", + "font-enumeration", "futures-util", "log", "objc", @@ -1030,6 +1031,18 @@ dependencies = [ "libc", ] +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1388,6 +1401,20 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dwrote" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +dependencies = [ + "lazy_static", + "libc", + "serde", + "serde_derive", + "winapi", + "wio", +] + [[package]] name = "dyn-clone" version = "1.0.18" @@ -1575,6 +1602,28 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-enumeration" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378034ff1da05aa11a0a3882bfa929cda6d0273c30008240aea04cd3936e0902" +dependencies = [ + "core-text", + "dwrote", + "fontconfig", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "fontconfig" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f7d9b14055dddd3b3ab0e511ecfdf49f02e7e31b09f9111de3e424ea7bb4b" +dependencies = [ + "yeslogic-fontconfig-sys", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -6907,6 +6956,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" @@ -7023,6 +7081,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.7.5" diff --git a/apps/readest-app/src-tauri/Cargo.toml b/apps/readest-app/src-tauri/Cargo.toml index 31440ff5..623c411d 100644 --- a/apps/readest-app/src-tauri/Cargo.toml +++ b/apps/readest-app/src-tauri/Cargo.toml @@ -57,3 +57,4 @@ tauri-plugin-cli = "2" tauri-plugin-single-instance = "2.2.0" tauri-plugin-updater = "2" tauri-plugin-window-state = "2.2.0" +font-enumeration = "0.9.0" diff --git a/apps/readest-app/src-tauri/src/lib.rs b/apps/readest-app/src-tauri/src/lib.rs index 30551e3a..9f4d1aac 100644 --- a/apps/readest-app/src-tauri/src/lib.rs +++ b/apps/readest-app/src-tauri/src/lib.rs @@ -72,6 +72,17 @@ async fn start_server(window: Window) -> Result { .map_err(|err| err.to_string()) } +#[cfg(desktop)] +#[command] +async fn list_fonts() -> Result, String> { + let font_collection = font_enumeration::Collection::new().unwrap(); + let mut fonts = Vec::new(); + for font in font_collection.all() { + fonts.push(font.font_name.clone()); + } + Ok(fonts) +} + #[derive(Clone, serde::Serialize)] struct Payload { args: Vec, @@ -86,7 +97,9 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ start_server, download_file, - upload_file + upload_file, + #[cfg(desktop)] + list_fonts ]) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_opener::init()) diff --git a/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx b/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx index a3c11183..bfc4ae2c 100644 --- a/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx @@ -19,6 +19,7 @@ import { useTranslation } from '@/hooks/useTranslation'; import { useTheme } from '@/hooks/useTheme'; import { getStyles } from '@/utils/style'; import { getOSPlatform } from '@/utils/misc'; +import { FONT_ENUM_SUPPORTED_OS_PLATFORMS, getSysFontsList } from '@/utils/font'; interface FontFaceProps { className?: string; @@ -67,26 +68,27 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const { themeCode } = useTheme(); const osPlatform = getOSPlatform(); - let moreFonts: string[] = []; + let defaultSysFonts: string[] = []; switch (osPlatform) { case 'macos': - moreFonts = MACOS_FONTS; + defaultSysFonts = MACOS_FONTS; break; case 'windows': - moreFonts = WINDOWS_FONTS; + defaultSysFonts = WINDOWS_FONTS; break; case 'linux': - moreFonts = LINUX_FONTS; + defaultSysFonts = LINUX_FONTS; break; case 'ios': - moreFonts = IOS_FONTS; + defaultSysFonts = IOS_FONTS; break; case 'android': - moreFonts = ANDROID_FONTS; + defaultSysFonts = ANDROID_FONTS; break; default: break; } + const [sysFonts, setSysFonts] = useState(defaultSysFonts); const [defaultFontSize, setDefaultFontSize] = useState(viewSettings.defaultFontSize!); const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!); const [overrideFont, setOverrideFont] = useState(viewSettings.overrideFont!); @@ -95,6 +97,15 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!); const [monospaceFont, setMonospaceFont] = useState(viewSettings.monospaceFont!); + useEffect(() => { + if (FONT_ENUM_SUPPORTED_OS_PLATFORMS.includes(osPlatform)) { + getSysFontsList().then((fonts) => { + setSysFonts(fonts); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { viewSettings.defaultFont = defaultFont; setViewSettings(bookKey, viewSettings); @@ -247,7 +258,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { family='serif' label={_('Serif Font')} options={SERIF_FONTS} - moreOptions={moreFonts} + moreOptions={sysFonts} selected={serifFont} onSelect={setSerifFont} /> @@ -255,7 +266,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { family='sans-serif' label={_('Sans-Serif Font')} options={SANS_SERIF_FONTS} - moreOptions={moreFonts} + moreOptions={sysFonts} selected={sansSerifFont} onSelect={setSansSerifFont} /> @@ -264,7 +275,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { family='monospace' label={_('Monospace Font')} options={MONOSPACE_FONTS} - moreOptions={moreFonts} + moreOptions={sysFonts} selected={monospaceFont} onSelect={setMonospaceFont} /> diff --git a/apps/readest-app/src/utils/font.ts b/apps/readest-app/src/utils/font.ts new file mode 100644 index 00000000..5c12e379 --- /dev/null +++ b/apps/readest-app/src/utils/font.ts @@ -0,0 +1,31 @@ +import { invoke } from '@tauri-apps/api/core'; +import { getOSPlatform } from './misc'; + +let cachedSysFonts: string[] | null = null; + +export const FONT_ENUM_SUPPORTED_OS_PLATFORMS = ['macos', 'windows', 'linux']; + +const isSymbolicFontName = (font: string) => + /emoji|icons|symbol|dingbats|ornaments|webdings|wingdings/i.test(font); + +export const getSysFontsList = async (): Promise => { + if (cachedSysFonts) { + return cachedSysFonts; + } + + try { + const osPlatform = getOSPlatform(); + if (FONT_ENUM_SUPPORTED_OS_PLATFORMS.includes(osPlatform)) { + const fonts = await invoke('list_fonts'); + cachedSysFonts = fonts.filter((font) => !isSymbolicFontName(font)).sort(); + console.log('Fetched font list:', cachedSysFonts); + return cachedSysFonts; + } else { + console.warn(`Unsupported platform: ${osPlatform}`); + return []; + } + } catch (error) { + console.error('Error fetching font list:', error); + return []; + } +};