Skip to content

Commit 0e145f3

Browse files
committed
fix(text): make the shipped fonts win over installed ones of the same name
Linux: an installed face with a shipped family name (Inter is common) won the fontdb query, so the render depended on the machine again. Drop every system face that shares a shipped family name before building the FontSystem; the other system families stay for script fallback. macOS had the same exposure through name lookup (and a duplicate process-scope registration is refused), so shipped families are now created from descriptors of their own files, with no registration. Tests on both assert the selected face's source FILE against a colliding same-named copy loaded first. Also delete process.resourcesPath in the fonts test teardown when it did not exist before.
1 parent 5fe5083 commit 0e145f3

4 files changed

Lines changed: 261 additions & 99 deletions

File tree

‎crates/compositor/src/text_fonts.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
//!
88
//! Les fichiers vivent dans `public/fonts`. L'app pose `OPENSCREEN_FONTS_DIR` sur leur dossier
99
//! avant de charger l'addon (`compositorViewService.ts`, même contrat que `ORT_DYLIB_PATH`), et
10-
//! chaque backend les enregistre pour ce processus seulement, sans rien installer : collection
11-
//! privée pour DirectWrite, portée processus pour CoreText, base `fontdb` pour cosmic-text.
12-
//! Sur macOS et Linux, une police installée qui porte le même nom peut passer devant la nôtre :
13-
//! c'est alors la même famille dans une autre version, jamais un repli.
10+
//! chaque backend les charge pour ce processus seulement, sans rien installer. Et nos fichiers
11+
//! gagnent toujours contre une police installée du même nom, sinon le rendu dépendrait encore
12+
//! de la machine : collection privée pour DirectWrite, descripteurs tirés des fichiers pour
13+
//! CoreText, faces homonymes du système retirées de la base `fontdb` pour cosmic-text.
1414
//!
1515
//! Sans la variable (POC, addon chargé hors de l'app), la liste est vide et le texte se dessine
1616
//! avec les polices du système, comme avant.

‎crates/compositor/src/text_linux.rs‎

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,21 +111,51 @@ pub struct TextRasterizer {
111111
swash_cache: RefCell<SwashCache>,
112112
}
113113

114-
/// Ajoute `files` (`crate::text_fonts`) a la base `fontdb` de ce rasteriseur, sans rien
115-
/// installer sur la machine. Un fichier illisible ne coute que sa police : le texte retombe
116-
/// sur celles du systeme.
117-
fn load_fonts(font_system: &mut FontSystem, files: &[std::path::PathBuf]) {
114+
/// Ajoute `files` (`crate::text_fonts`) a `db`, sans rien installer sur la machine, et
115+
/// retire de `db` toute autre face qui porte le nom d'une famille embarquee.
116+
///
117+
/// Le retrait est ce qui fait gagner nos fichiers. Une police installee du meme nom (Inter
118+
/// est courante sous Linux) passait devant la notre : `fontdb` ne documente aucune priorite
119+
/// entre deux faces egales, et en pratique la premiere chargee, celle du systeme, l'emportait.
120+
/// Le rendu dependait alors a nouveau de la machine. Sans face concurrente, il n'y a plus
121+
/// d'ordre dont dependre. Les autres familles du systeme restent, pour le repli des
122+
/// ecritures que nos polices ne couvrent pas.
123+
///
124+
/// Un fichier illisible ne coute que sa police : sa famille retombe sur celles du systeme.
125+
fn with_embedded_fonts(
126+
mut db: cosmic_text::fontdb::Database,
127+
files: &[std::path::PathBuf],
128+
) -> cosmic_text::fontdb::Database {
129+
let before: std::collections::HashSet<_> = db.faces().map(|f| f.id).collect();
118130
for path in files {
119-
if let Err(e) = font_system.db_mut().load_font_file(path) {
131+
if let Err(e) = db.load_font_file(path) {
120132
eprintln!("[text] police non chargee ({e}) : {}", path.display());
121133
}
122134
}
135+
let shipped: std::collections::HashSet<String> = db
136+
.faces()
137+
.filter(|f| !before.contains(&f.id))
138+
.flat_map(|f| f.families.iter().map(|(name, _)| name.to_lowercase()))
139+
.collect();
140+
let shadowed: Vec<_> = db
141+
.faces()
142+
.filter(|f| before.contains(&f.id))
143+
.filter(|f| f.families.iter().any(|(name, _)| shipped.contains(&name.to_lowercase())))
144+
.map(|f| f.id)
145+
.collect();
146+
for id in shadowed {
147+
db.remove_face(id);
148+
}
149+
db
123150
}
124151

125152
impl TextRasterizer {
126153
pub fn new() -> Result<TextRasterizer> {
127-
let mut font_system = FontSystem::new();
128-
load_fonts(&mut font_system, &crate::text_fonts::embedded_font_files());
154+
// Reconstruit le `FontSystem` sur la base modifiee, plutot que de la retoucher en
155+
// place : il indexe ses faces (monospace, repli) a la construction.
156+
let (locale, db) = FontSystem::new().into_locale_and_db();
157+
let db = with_embedded_fonts(db, &crate::text_fonts::embedded_font_files());
158+
let font_system = FontSystem::new_with_locale_and_db(locale, db);
129159
Ok(TextRasterizer {
130160
font_system: RefCell::new(font_system),
131161
swash_cache: RefCell::new(SwashCache::new()),
@@ -453,10 +483,29 @@ mod tests {
453483
/// qu'un controle peut produire, et deux familles ne se confondent jamais a l'ecran.
454484
#[test]
455485
fn every_shipped_family_draws_from_its_own_files() {
456-
use cosmic_text::fontdb::{Family, Query, Stretch, Style, Weight};
457-
let mut font_system = FontSystem::new();
458-
let files = crate::text_fonts::font_files_in(&crate::text_fonts::repo_fonts_dir());
459-
load_fonts(&mut font_system, &files);
486+
use cosmic_text::fontdb::{Database, Family, Query, Source, Stretch, Style, Weight};
487+
let repo = crate::text_fonts::repo_fonts_dir();
488+
let files = crate::text_fonts::font_files_in(&repo);
489+
490+
// La collision : une police « installee » qui porte EXACTEMENT les memes familles,
491+
// graisses et styles, chargee AVANT les notres comme `FontSystem::new` le fait des
492+
// polices du systeme. Ce sont nos fichiers, copies ailleurs : seul le chemin distingue
493+
// le gagnant.
494+
let system =
495+
std::env::temp_dir().join(format!("openscreen-sysfonts-{}", std::process::id()));
496+
std::fs::create_dir_all(&system).unwrap();
497+
let mut db = Database::new();
498+
db.load_system_fonts();
499+
for file in &files {
500+
let copy = system.join(file.file_name().unwrap());
501+
std::fs::copy(file, &copy).unwrap();
502+
db.load_font_file(&copy).unwrap();
503+
}
504+
let db = with_embedded_fonts(db, &files);
505+
let font_system = FontSystem::new_with_locale_and_db("en-US".into(), db);
506+
std::fs::remove_dir_all(&system).ok();
507+
508+
let repo = std::fs::canonicalize(&repo).unwrap();
460509
for family in crate::text_fonts::SHIPPED_FAMILIES {
461510
for weight in [Weight::NORMAL, Weight::BOLD] {
462511
let query = Query {
@@ -469,8 +518,17 @@ mod tests {
469518
.db()
470519
.query(&query)
471520
.unwrap_or_else(|| panic!("{family} introuvable"));
521+
let face = font_system.db().face(id).unwrap();
472522
// Un vrai fichier de cette graisse, pas le regulier epaissi ou non.
473-
assert_eq!(font_system.db().face(id).unwrap().weight, weight, "{family}");
523+
assert_eq!(face.weight, weight, "{family}");
524+
// Et c'est NOTRE fichier, pas la face du systeme qui porte le meme nom.
525+
let path = match &face.source {
526+
Source::File(p) | Source::SharedFile(p, _) => p.clone(),
527+
Source::Binary(_) => panic!("{family} : face sans fichier"),
528+
};
529+
let path = std::fs::canonicalize(&path).unwrap_or(path);
530+
let from = path.display();
531+
assert!(path.starts_with(&repo), "{family} {weight:?} tire de {from}");
474532
}
475533
}
476534

0 commit comments

Comments
 (0)