Merge pull request #2884 from nrq6993-a11y/stream/vc-issue-2576-font-poll

Fix #2576: poll for fonts that font managers activate while the app is in front
This commit is contained in:
BFlat
2026-10-18 19:19:49 +07:00
committed by GitHub
3 changed files with 56 additions and 19 deletions
+6
View File
@@ -1611,6 +1611,12 @@ impl FontDb {
Self::new(FontPaths::Fixed(font_dirs))
}
/// Same scan paths as [`FontDb::global`] (platform lister and font folders), for tests.
#[cfg(test)]
pub(crate) fn for_system_scan_test() -> Self {
Self::new(FontPaths::System)
}
fn new(font_paths: FontPaths) -> Self {
let mut faces = Vec::new();
// Bundled fonts first, then explicitly supplied craft-fonts faces: Japanese (Mincho
+19 -7
View File
@@ -332,18 +332,30 @@ fn service_font() -> PathBuf {
}
/// The fonts a font service (Adobe Fonts on Windows, through DirectWrite) loads in place are
/// cataloged: named by the platform's lister, read whatever their file is called (#579).
/// cataloged: named by the platform's lister, read whatever their file is called (#579), and one it
/// activates after a scan is picked up by the next check (#2576). One test: the lister is set once
/// per process, so two tests changing what it lists would race.
#[test]
fn fonts_the_platform_lists_outside_the_font_folders_are_cataloged() {
use std::sync::Mutex;
static FILES: Mutex<Vec<String>> = Mutex::new(Vec::new());
let list = |files: Vec<String>| *FILES.lock().unwrap_or_else(|e| e.into_inner()) = files;
set_platform_font_files(|| FILES.lock().unwrap_or_else(|e| e.into_inner()).clone());
let file = service_font();
std::fs::create_dir_all(file.parent().unwrap()).unwrap();
let _ = std::fs::remove_file(&file);
// Nothing listed at the first scan; then a font service activates a font.
let db = FontDb::for_system_scan_test();
assert!(!db.installed_fonts_changed());
db.load_system_fonts();
assert!(!db.has_family("Service Sans3"));
std::fs::write(&file, renamed_to("SourceSans3-Regular.ttf", "Service Sans3")).unwrap();
// The lister's files outside the font folders are scanned with them, each once (a collection
// lists its file once per face).
fn lister() -> Vec<String> {
vec![service_font().to_string_lossy().into_owned(); 2]
}
set_platform_font_files(lister);
// A collection lists its file once per face.
list(vec![file.to_string_lossy().into_owned(); 2]);
assert!(db.installed_fonts_changed(), "a font a font service activated since the scan");
db.load_system_fonts();
assert!(db.has_family("Service Sans3"));
// The lister's files outside the font folders are scanned with them, each once.
let dirs = system_font_dirs();
assert_eq!(dirs.iter().filter(|d| **d == file).count(), 1, "{dirs:?}");
// A file named by itself is read without a font's extension, one in a folder isn't.
+31 -12
View File
@@ -452,9 +452,12 @@ pub struct VectorcraftApp {
pub(crate) picks: picks::Picks,
/// The screen color sampler shown and not answered yet, and where its color goes.
pub(crate) screen_sampler: Option<screen_color::Waiting>,
/// The look for fonts installed or removed while the app was in the background, running
/// ([`Self::refresh_installed_fonts`]): whether they were.
font_check: Option<std::sync::mpsc::Receiver<bool>>,
/// The look for fonts installed or removed meanwhile, running
/// ([`Self::refresh_installed_fonts`]): whether they were, and whether to wake the app for
/// the answer.
font_check: Option<(std::sync::mpsc::Receiver<bool>, bool)>,
/// When installed fonts were last checked in line (app time, s).
font_install_check_at: f64,
/// Keyboard pastes of something other than text (see [`shortcuts::PasteChord`]).
pub(crate) paste_chord: shortcuts::PasteChord,
/// Saves and exports running in the background (Preferences → File Handling).
@@ -500,6 +503,8 @@ struct PendingScreenshot {
/// Seconds between two looks at the system clipboard for [`VectorcraftApp::system_paste`].
const SYSTEM_CLIPBOARD_POLL: f64 = 0.25;
/// Seconds between looks for fonts a font manager activated while the app stays in front (#2576).
const FONT_INSTALL_POLL: f64 = 2.0;
impl VectorcraftApp {
pub fn new(mut session: Session, services: Services) -> Self {
@@ -562,6 +567,7 @@ impl VectorcraftApp {
picks: picks::Picks::default(),
screen_sampler: None,
font_check: None,
font_install_check_at: f64::NEG_INFINITY,
paste_chord: Default::default(),
background: Default::default(),
opening: Default::default(),
@@ -1198,6 +1204,12 @@ impl VectorcraftApp {
self.system_paste_at = now;
self.system_paste = wanted && self.system_clipboard_pasteable();
}
#[cfg(not(target_arch = "wasm32"))]
if !(0.0..FONT_INSTALL_POLL).contains(&(now - self.font_install_check_at)) {
self.font_install_check_at = now;
// Read on the next frame the app draws anyway: an idle app isn't woken for it.
self.refresh_installed_fonts(false);
}
self.poll_font_check(ctx);
picks::poll(self, ctx);
self.write_formats_before_text_input(ctx);
@@ -1292,12 +1304,15 @@ impl VectorcraftApp {
place::drop_files(self, files);
}
/// Fonts installed or removed while the app was in the background are listed when it comes
/// back (Refresh Font List by itself): a look at the font folders, a scan only when they changed.
/// Fonts installed or removed while the app runs are listed without Refresh Font List: a look
/// at the font folders and the platform's font list on a timer and when the window comes back,
/// a scan only when they changed.
/// The look runs on another thread (asking DirectWrite for the fonts font services loaded
/// meanwhile takes tens of milliseconds on Windows, #579), [`Self::poll_font_check`] scans.
fn refresh_installed_fonts(&mut self) {
if self.font_check.is_some() {
/// meanwhile takes tens of milliseconds on Windows, #579), [`Self::poll_font_check`] scans;
/// `wake` wakes the app for the answer.
fn refresh_installed_fonts(&mut self, wake: bool) {
if let Some((_, w)) = &mut self.font_check {
*w |= wake;
return;
}
let (tx, rx) = std::sync::mpsc::channel();
@@ -1310,12 +1325,12 @@ impl VectorcraftApp {
if spawned.is_none() {
look();
}
self.font_check = Some(rx);
self.font_check = Some((rx, wake));
}
/// Rescan the fonts once [`Self::refresh_installed_fonts`]'s look says they changed.
fn poll_font_check(&mut self, ctx: &egui::Context) {
let Some(check) = &self.font_check else { return };
let Some((check, wake)) = &self.font_check else { return };
match check.try_recv() {
Ok(changed) => {
self.font_check = None;
@@ -1324,7 +1339,11 @@ impl VectorcraftApp {
let _ = self.run("text.rescanFonts", json!({}));
}
}
Err(std::sync::mpsc::TryRecvError::Empty) => ctx.request_repaint_after(std::time::Duration::from_millis(50)),
Err(std::sync::mpsc::TryRecvError::Empty) => {
if *wake {
ctx.request_repaint_after(std::time::Duration::from_millis(50));
}
}
Err(std::sync::mpsc::TryRecvError::Disconnected) => self.font_check = None,
}
}
@@ -1346,7 +1365,7 @@ impl VectorcraftApp {
match e {
egui::Event::ModifiersChanged(m) => self.host_modifiers = *m,
egui::Event::WindowFocused(false) => self.host_modifiers = egui::Modifiers::NONE,
egui::Event::WindowFocused(true) => self.refresh_installed_fonts(),
egui::Event::WindowFocused(true) => self.refresh_installed_fonts(true),
_ => {}
}
}