Type: whitespace the primary font doesn't map takes the fallback face too (#1844)

An ideographic space in a face without U+3000 was shaped with that face
and could set at zero width, removing an indent. Only what the layout
handles itself (tab, line and paragraph breaks) stays with the primary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
imachus
2026-10-16 06:16:35 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent a14942de9c
commit 4265c2f301
2 changed files with 24 additions and 1 deletions
+4 -1
View File
@@ -374,7 +374,10 @@ pub(crate) fn shape_range(
// Digits are drawn in the style's numeral system: the font must cover that digit.
let c = st.digits.map(c);
let level = level_at(i);
let covered = c.is_whitespace() || c.is_control() || pmap.as_ref().is_none_or(|m| m.map(c).is_some());
// Whitespace the primary font doesn't map goes to the fallback face like any other
// character (#1844): an ideographic space set at zero width would remove an indent.
// What the layout handles itself (tab, line and paragraph breaks) stays.
let covered = c.is_control() || pmap.as_ref().is_none_or(|m| m.map(c).is_some());
let face = if covered {
primary.clone()
} else if let Some((_, f)) = cache.iter().find(|(k, _)| *k == c) {
+20
View File
@@ -405,6 +405,26 @@ fn fallback_font_per_character() {
assert_eq!(l.glyphs[2].font_id, primary.id());
}
/// A space the primary font doesn't map goes to the fallback face like any other character
/// (#1844): an ideographic space in a face without U+3000 keeps its em, so an indent made with it
/// isn't lost. A tab, which the layout handles itself, stays with the primary face. Needs a bundled
/// face with U+3000 and あ (craft-fonts; skipped without it).
#[test]
fn whitespace_the_primary_font_lacks_takes_the_fallback_face() {
let primary = db().face("Source Sans 3", "Regular").unwrap();
assert!(!primary.covers('\u{3000}'));
if ['\u{3000}', 'あ'].iter().any(|&c| db().fallback_for(c, primary.id()).is_none()) {
eprintln!("skipped: built without craft-fonts (set CRAFT_FONTS_DIR to a craft-fonts checkout to run it)");
return;
}
let l = layout(db(), &point("あ\u{3000}あ", style(20.0)));
let width: f64 = l.glyphs.iter().map(|g| g.advance).sum();
assert!((width - 60.0).abs() < 0.01, "あ あ sets at 3 em: {:?}", l.glyphs.iter().map(|g| g.advance).collect::<Vec<_>>());
assert_ne!(l.glyphs[1].font_id, primary.id());
let l = layout(db(), &point("a\tb", style(20.0)));
assert!(l.glyphs.iter().all(|g| g.font_id == primary.id()));
}
#[test]
fn multiple_runs_and_styles() {
let mut t = point("", style(10.0));