mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 20:01:37 +00:00
Fix #5483: a large font library no longer keeps the window from opening
The menu bar builds Type > Font in every frame, the first included, and its family list waited for the scan of every installed font. When that scan took longer than the 20 s first-frame watchdog (thousands of fonts on a slow disk, or each file checked by antivirus), the app took the graphics adapter for hung and started again without it, until none was left and no window ever showed. The menu now lists the fonts already loaded until the scan ends, then all of them (FontDb::menu_family_list_now, installed_fonts_ready).
This commit is contained in:
@@ -152,6 +152,32 @@ impl FontDb {
|
||||
list
|
||||
}
|
||||
|
||||
/// Whether the installed fonts are cataloged, so lookups by family name no longer wait for
|
||||
/// the scan. Always true on wasm, which has no installed fonts.
|
||||
pub fn installed_fonts_ready(&self) -> bool {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
self.cataloged.get().is_some()
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
true
|
||||
}
|
||||
|
||||
/// [`FontDb::menu_family_list`] without waiting for the installed fonts' scan: until it ends,
|
||||
/// the families already loaded (the bundled fonts and any loaded meanwhile), sorted, without
|
||||
/// the hidden ones. For what is built every frame, such as the menu bar's Type › Font, so the
|
||||
/// window's first frame doesn't wait for the scan of a large font library (#5483).
|
||||
/// [`FontDb::generation`] changes when the scan ends.
|
||||
pub fn menu_family_list_now(&self) -> Arc<[String]> {
|
||||
if self.installed_fonts_ready() {
|
||||
return self.menu_family_list();
|
||||
}
|
||||
let mut v: Vec<String> = self.read_faces().iter().map(|f| f.family.clone()).filter(|f| !f.starts_with('.')).collect();
|
||||
v.sort_by_key(|a| a.to_lowercase());
|
||||
v.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
|
||||
v.into()
|
||||
}
|
||||
|
||||
/// How a font menu labels `family`: the English name when `english` is on (Preferences › Type ›
|
||||
/// Show Font Names in English, the default), else the font's native-language name when it has
|
||||
/// one. The document and commands still use the English family ([`Self::canonical`]).
|
||||
|
||||
@@ -308,6 +308,36 @@ fn a_background_scan_serves_the_first_lookup() {
|
||||
assert_eq!(db.load_system_fonts(), 2, "a rescan catalogs the two faces again");
|
||||
}
|
||||
|
||||
/// The menu bar's Type › Font is built in the window's first frame, so its family list doesn't wait
|
||||
/// for the scan of the installed fonts: a large library would hold the first frame past the
|
||||
/// first-frame watchdog, and the app would never show its window (#5483). Once the scan ends, the
|
||||
/// list has the installed fonts.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn the_menu_family_list_now_does_not_wait_for_the_scan() {
|
||||
let dir = font_dir("held-scan");
|
||||
// A named pipe with a font's name: the scan opens it and waits there until something writes.
|
||||
let pipe = dir.join("Held.ttf");
|
||||
assert!(std::process::Command::new("mkfifo").arg(&pipe).status().unwrap().success());
|
||||
let db: &'static FontDb = Box::leak(Box::new(FontDb::with_font_dirs(vec![dir])));
|
||||
let generation = db.generation();
|
||||
db.scan_in_background();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let _ = tx.send((db.installed_fonts_ready(), db.menu_family_list_now()));
|
||||
});
|
||||
let listed = rx.recv_timeout(std::time::Duration::from_secs(10));
|
||||
// Let the scan go on whatever happened: the pipe is opened and closed with nothing written.
|
||||
drop(std::fs::OpenOptions::new().write(true).open(&pipe).unwrap());
|
||||
let (ready, listed) = listed.expect("the list comes while the scan is held");
|
||||
assert!(!ready && !has(&listed, FAMILY), "before the scan ends, only the fonts already loaded");
|
||||
// Waits for the scan.
|
||||
assert!(has(&db.menu_family_list(), FAMILY));
|
||||
assert!(db.installed_fonts_ready());
|
||||
assert_ne!(db.generation(), generation, "a menu built from the short list is built again");
|
||||
assert!(has(&db.menu_family_list_now(), FAMILY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_family_list_is_shared_until_fonts_change() {
|
||||
let db = FontDb::with_font_dirs(vec![]);
|
||||
|
||||
@@ -63,21 +63,27 @@ pub(super) fn insert_items(list: &[(&'static str, &'static str)]) -> Vec<Item> {
|
||||
/// Type → Font: one item per available family, the installed fonts included. Built again only when
|
||||
/// the fonts or Show Font Names in English change (the menu tree is built every frame); labels are
|
||||
/// interned once each, so rebuilding doesn't allocate forever. The command still sets the English
|
||||
/// family name ([`vectorcraft_text::FontDb::canonical`]).
|
||||
/// family name ([`vectorcraft_text::FontDb::canonical`]). Until the installed fonts are scanned it
|
||||
/// lists the fonts already loaded, by their English names, rather than wait for the scan: the menu
|
||||
/// bar is drawn in the window's first frame, which a large font library would otherwise hold up
|
||||
/// past the first-frame watchdog (#5483).
|
||||
pub(super) fn font_items(english_names: bool) -> Vec<Item> {
|
||||
static NAMES: std::sync::Mutex<std::collections::BTreeSet<&'static str>> = std::sync::Mutex::new(std::collections::BTreeSet::new());
|
||||
static ITEMS: std::sync::Mutex<(u64, bool, Vec<Item>)> = std::sync::Mutex::new((u64::MAX, true, Vec::new()));
|
||||
static ITEMS: std::sync::Mutex<(u64, bool, bool, Vec<Item>)> = std::sync::Mutex::new((u64::MAX, true, false, Vec::new()));
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
// Read first: fonts that load meanwhile make the next frame build the list again.
|
||||
// Read first: fonts that load meanwhile make the next frame build the list again. The scan
|
||||
// ends between changing the generation and counting as cataloged, so both are kept.
|
||||
let cataloged = db.installed_fonts_ready();
|
||||
let generation = db.generation();
|
||||
let fams = db.menu_family_list();
|
||||
let fams = db.menu_family_list_now();
|
||||
let (Ok(mut names), Ok(mut items)) = (NAMES.lock(), ITEMS.lock()) else { return vec![] };
|
||||
if items.0 != generation || items.1 != english_names {
|
||||
if items.0 != generation || items.1 != english_names || items.2 != cataloged {
|
||||
let list = fams
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let english = f.as_str();
|
||||
let shown = db.family_display_name(english, english_names);
|
||||
// A native name may be the catalog's, which isn't there to read yet.
|
||||
let shown = if cataloged { db.family_display_name(english, english_names) } else { english.to_string() };
|
||||
let label: &'static str = match names.get(shown.as_str()) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
@@ -97,9 +103,9 @@ pub(super) fn font_items(english_names: bool) -> Vec<Item> {
|
||||
cp(label, "text.setStyle", json!({ "font": font }))
|
||||
})
|
||||
.collect();
|
||||
*items = (generation, english_names, list);
|
||||
*items = (generation, english_names, cataloged, list);
|
||||
}
|
||||
items.2.clone()
|
||||
items.3.clone()
|
||||
}
|
||||
|
||||
/// The raster effects' submenus of the Effect menu (the Photoshop-style effects, below the
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# Gaps
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (new gradients follow document color mode; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (the menu bar no longer waits for the installed fonts' scan, so a large font library doesn't keep the window from opening; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -191,6 +191,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
|
||||
- **Unconfirmed (2026-10-14):** Tool cursors are painted into the window on Wayland (#1652; not run on Linux, #1645 stays open until confirmed). On Windows the custom title bar's window stays truly borderless, so the pointer lines up with what is drawn on systems where the hidden caption offset it (#1791, #1758; awaiting the reporter's confirmation).
|
||||
- **Done:** RPM required dependencies use 64-bit library capabilities and declare glibc 2.35, libm and libgcc_s (#1275); generated metadata and isolated synthetic-provider checks cover x86_64/aarch64. Real-distribution install/start-up QA is still missing.
|
||||
- **Done (2026-11-04):** a large font library no longer keeps the window from opening (#5483). The menu bar's Type › Font, drawn in the first frame, waited for the scan of every installed font; when that took longer than the 20 s first-frame watchdog (thousands of fonts on a slow disk, or with each file checked by antivirus) the app took the graphics adapter for hung and started again without it until none was left. The menu now lists the fonts already loaded until the scan ends, then all of them (`the_menu_family_list_now_does_not_wait_for_the_scan`; checked on Linux with 3,923 extra font files, each open delayed 8 ms: the window opened in 1.5 s, where it never did before). Estimate unchanged.
|
||||
- **Done (2026-10-29):** the Linux Flatpak on GNOME no longer reconnects to the compositor and logs a warning every 250 ms when arboard has neither data-control nor X11 (267k warnings in 18 h filled the 16 MB log in 3 h): a clipboard that can't be opened is retried with a wait growing to a minute and logged once. Copy and Paste of text with other apps work there through the core Wayland protocol.
|
||||
- **Missing:** pictures (`image/png`) and SVG as `image/svg+xml` on the clipboard of the Flatpak on GNOME, where only text travels (smithay-clipboard 0.7 carries text only); the app not starting or staying in the background on some Windows 11 machines (#858, #964 fixed
|
||||
2026-10-10, #620, #818), slow pointer on a Linux AppImage (#575), macOS paste from other apps (#954), a
|
||||
|
||||
Reference in New Issue
Block a user