Text: a missing font's substitute takes fallback fonts across a colour change

Text is shaped in pieces that differ only in paint, so each piece group,
and each piece shaped apart, takes its style's environment: a missing
font's substitute draws the characters it lacks from fallback fonts
whatever Draw Missing Glyphs from Fallback Fonts says.
This commit is contained in:
aiongg
2026-10-07 23:08:13 -04:00
parent c8d4ed8fa3
commit 3a10a749db
2 changed files with 31 additions and 2 deletions
+5 -2
View File
@@ -375,14 +375,17 @@ pub(crate) fn shape_para(
let range = group[0].range.start..group[j - i - 1].range.end;
let runs: Vec<(usize, u32)> = group.iter().map(|p| (p.range.start, p.style)).collect();
let start = glyphs.len();
shape_run(db, &story.text, range, &first.props, auto_leading, &StyleRuns(&runs), sub, &mut glyphs);
// A missing font's substitute stands in for the whole font: fallback fonts help it.
// Pieces shaped together share their font, so the first one's says for all.
shape_run(db, &story.text, range, &first.props, table.env_for(auto_leading, first.style), &StyleRuns(&runs), sub, &mut glyphs);
// A cluster (a ligature, a letter with its marks) that spans two styles would paint one
// of them in the other's colour: shape those pieces apart instead.
let spans_cut = |g: &Glyph| runs[1..].iter().any(|&(cut, _)| g.byte < cut && cut < g.byte + g.len);
if group.len() > 1 && glyphs[start..].iter().any(spans_cut) {
glyphs.truncate(start);
for p in group {
shape_run(db, &story.text, p.range.clone(), &p.props, auto_leading, &StyleRuns(&[(p.range.start, p.style)]), sub, &mut glyphs);
let env = table.env_for(auto_leading, p.style);
shape_run(db, &story.text, p.range.clone(), &p.props, env, &StyleRuns(&[(p.range.start, p.style)]), sub, &mut glyphs);
}
}
i = j;
+26
View File
@@ -1570,6 +1570,32 @@ fn missing_glyphs_are_the_fonts_box_unless_fallback_is_on() {
assert_ne!(gid, 0, "a fallback font draws it");
}
/// A missing font's substitute draws what it lacks from fallback fonts whatever the setting, also
/// in text shaped as one piece across a colour change.
#[test]
fn a_missing_fonts_substitute_takes_fallback_fonts() {
use designcraft_fonts::testing::font_with;
designcraft_fonts::FontDb::global().add_font(font_with("DC Test Missing Glyph Helper", &['語']).unwrap());
let text = "a語b語";
let (mut d, sid, _) = doc_with(text, Rect::new(36.0, 36.0, 300.0, 100.0), ParaAttrs::default());
{
let s = d.story_mut(sid).unwrap();
s.format_chars(0..text.len(), |f| {
f.over.language = Some("Japanese".into());
f.over.font_family = Some("DC Test No Such Family".into());
});
s.format_chars("a語".len()..text.len(), |f| f.over.fill = Some("Red".into()));
}
assert!(!d.settings.glyph_fallback);
let cs = compose_story(&d, sid, &ComposeOptions::default());
let glyphs: Vec<&PlacedGlyph> = all_lines(&cs).into_iter().flat_map(|l| l.glyphs.iter()).filter(|g| text[g.byte..].starts_with('語')).collect();
assert_eq!(glyphs.len(), 2);
for g in glyphs {
assert_ne!(g.face.family, designcraft_fonts::DEFAULT_FAMILY, "byte {}", g.byte);
assert_ne!(g.gid, 0, "byte {}: a fallback font draws it", g.byte);
}
}
#[test]
fn kenten_missing_from_the_font_are_its_box_unless_fallback_is_on() {
use designcraft_fonts::testing::font_with;