fix(text): stroke aliases txt/monotxt must ignore a same-named TTF

Face::resolve routes a name to a system TrueType face when installed_alias
reports that an alias names a real TTF face (the #1764 escape hatch, added so
an installed ISOCPEUR draws that face instead of its LFF stand-in).

That hatch also caught TXT/MONOTXT: on a machine with a font family matching
"txt" (e.g. a "Txt Regular"), the DXF stroke text font resolved to that
TrueType face instead of the embedded stroke substitute, contradicting the
documented contract that a missing .shx draws with the stroke substitute and
never with a similarly-named TrueType face.

TXT/MONOTXT are stroke aliases, so exclude them from the hatch. ISOCPEUR keeps
its #1764 behaviour.

Eight tests failed on such a machine and now pass: the four font-face/LFF
assertions (shx_name_stays_lff, lff_falls_back_to_ttf_for_uncovered_glyph,
big_font_pairing_routes_ideographs_and_leaves_latin_alone,
lff_tessellation_golden) and the four searchable-PDF assertions that route
through the same Face::resolve. Adds a regression test for the contract.
This commit is contained in:
Karim Jerbi committed 2026-10-17 05:56:14 +01:00
1 parent 0e4c4c311d
commit ddf348af22
2 files changed
+35 -2

No files matched your search

+24
View File
@@ -279,6 +279,30 @@ mod tests {
assert!(matches!(Face::resolve("romans.shx"), Face::Lff(_)));
}
// `txt`/`monotxt` are the DXF *stroke* text fonts. An installed TrueType
// face that happens to share the name (e.g. "Txt Regular") is not that
// font and must never stand in for it, so the stroke substitute is drawn
// on every machine. The #1764 escape hatch (an installed copy of a real
// TrueType alias such as ISOCPEUR draws that face) stays for aliases that
// genuinely name one.
#[test]
fn stroke_aliases_ignore_a_same_named_true_type_face() {
for name in &["txt", "TXT", "monotxt", "MONOTXT"] {
assert!(
matches!(Face::resolve(name), Face::Lff(_)),
"{name} must resolve to the embedded stroke font"
);
}
// A stroke face still supplies its own Latin glyphs.
assert!(Face::resolve("txt").glyph('A').is_some());
// ISOCPEUR keeps the #1764 behaviour: Lff stand-in, or the installed
// TrueType face when one is present.
assert!(matches!(
Face::resolve("isocpeur"),
Face::Lff(_) | Face::Ttf { .. }
));
}
#[test]
fn lff_falls_back_to_ttf_for_uncovered_glyph() {
// A CJK ideograph is in no stroke font; with a stroke style selected it
+11 -2
View File
@@ -96,6 +96,13 @@ const ALIASES: &[(&str, &str)] = &[
("EXTFONT", "unicode"),
];
/// Aliases that always mean the reference's *stroke* font and so never defer
/// to an installed TrueType face of the same name: `txt`/`monotxt` are the DXF
/// stroke text fonts, and a coincidental `Txt Regular` is not that font. The
/// `installed_alias` escape hatch stays for aliases that genuinely name a
/// TrueType face (ISOCPEUR, #1764).
const STROKE_ALIASES: &[&str] = &["TXT", "MONOTXT"];
/// The space advance of the reference's stroke fonts, in glyph units (cap
/// height 9), measured from their text extents: the SHX names these LFF
/// fonts stand in for keep the reference's word gaps.
@@ -529,11 +536,13 @@ pub fn is_builtin(name: &str) -> bool {
/// draws with the stroke substitute, not the installed `.ttf` (measured).
fn installed_alias(stem: &str, name: &str) -> bool {
use crate::scene::text::sysfont;
let stem = stem.trim().to_ascii_uppercase();
!is_shx_name(name)
&& !STROKE_ALIASES.contains(&stem.as_str())
&& ALIASES.iter().any(|(alias, _)| *alias == stem)
&& (sysfont::family_for_file(name).is_some()
|| sysfont::canonical_family_name(stem)
.is_some_and(|family| family.eq_ignore_ascii_case(stem)))
|| sysfont::canonical_family_name(&stem)
.is_some_and(|family| family.eq_ignore_ascii_case(&stem)))
}
/// Whether `name` is written as an `.shx` file name.