Glyphs: the Glyphs query reports the fallback glyph of an overflowing character

When the requested font has no glyph for a character, the text draws it
from a fallback face, and text.glyphAlternates reports that glyph. For
a character that overflows its area, the query shaped the character
alone but kept only a glyph of the requested face, so it reported the
requested face's default glyph (0). It now takes the first glyph drawn
for the character when the requested face draws none, as it does for
laid-out text.
This commit is contained in:
dotCooCoo
2026-11-05 07:56:26 +07:00
committed by bflatastic
parent 1fc630e697
commit 8118b59d19
2 changed files with 18 additions and 6 deletions
+12 -6
View File
@@ -1203,8 +1203,9 @@ fn own_glyph(
/// The glyph of `face` drawn for the character `ch` at `bytes` of `t` when the character is shaped
/// alone in the writing direction of `t` and the direction its paragraph sets it in, with the rest
/// of its grapheme in the grapheme's own styles: the glyph of a character that is not laid out
/// (overflowing text).
fn shaped_alone(t: &TextObject, ch: char, bytes: &std::ops::Range<usize>, face: &vectorcraft_text::FontFace) -> Option<u32> {
/// (overflowing text). Also the first glyph drawn for the character, which can be in a fallback
/// face.
fn shaped_alone(t: &TextObject, ch: char, bytes: &std::ops::Range<usize>, face: &vectorcraft_text::FontFace) -> (Option<u32>, Option<u32>) {
use vectorcraft_doc::ParaDirection;
let plain = t.plain_text();
let end = edit::next_cluster(&plain, bytes.start).max(bytes.end);
@@ -1218,7 +1219,8 @@ fn shaped_alone(t: &TextObject, ch: char, bytes: &std::ops::Range<usize>, face:
let rtl = vectorcraft_text::char_is_rtl(plain.get(start..stop).unwrap_or_default(), t.para_at(paragraph).direction, bytes.start - start);
single.para.direction = Some(if rtl { ParaDirection::RightToLeft } else { ParaDirection::LeftToRight });
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &single);
own_glyph(&single.plain_text(), &lay, ch, &(0..bytes.len()), face).flatten()
let first = lay.glyphs.iter().find(|g| g.byte == 0 && g.len > 0).map(|g| g.gid);
(own_glyph(&single.plain_text(), &lay, ch, &(0..bytes.len()), face).flatten(), first)
}
fn set_glyph_alternate(s: &mut Session, p: &Value) -> Result<Value> {
@@ -1253,7 +1255,7 @@ fn set_glyph_alternate(s: &mut Session, p: &Value) -> Result<Value> {
let mut probe = t.clone();
edit::style_range(&mut probe.runs, bytes.start, bytes.end, |st| st.glyph_alternate = None);
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &probe);
own_glyph(&plain, &lay, ch, &bytes, &face).unwrap_or_else(|| shaped_alone(&probe, ch, &bytes, &face))
own_glyph(&plain, &lay, ch, &bytes, &face).unwrap_or_else(|| shaped_alone(&probe, ch, &bytes, &face).0)
});
let substituted = unchosen.is_some_and(|g| g != default);
let choice = match gid.filter(|g| *g != default || substituted) {
@@ -1304,10 +1306,14 @@ fn glyph_alternates(s: &mut Session, p: &Value) -> Result<Value> {
let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t);
let first = lay.glyphs.iter().find(|g| g.byte <= bytes.start && bytes.start < g.byte.saturating_add(g.len)).map(|g| g.gid);
// A character that is not laid out (overflowing text) reports the glyph it is drawn with when
// it is shaped alone, as `text.setGlyphAlternate` finds it.
// it is shaped alone, as `text.setGlyphAlternate` finds it; a fallback face's glyph when it is
// drawn in one.
let chosen = match own_glyph(&t.plain_text(), &lay, ch, &bytes, &face) {
Some(own) => own.or(first),
None => shaped_alone(&t, ch, &bytes, &face),
None => {
let (own, first) = shaped_alone(&t, ch, &bytes, &face);
own.or(first)
}
}
.unwrap_or(default);
let alternates: Vec<Value> = face.alternates(ch).iter().map(|a| json!({"gid": a.gid, "feature": String::from_utf8_lossy(&a.feature)})).collect();
+6
View File
@@ -2793,4 +2793,10 @@ fn glyph_alternates_query_reports_the_fallback_glyph_that_is_drawn() {
let query = s.execute("text.glyphAlternates", &json!({"id": id.0, "start": 0})).unwrap();
assert_eq!(query["chosen"], json!(glyph.gid), "the query reports the visible fallback glyph");
assert!(query["alternates"].as_array().unwrap().is_empty());
let area = json!({"x": 0, "y": 300, "text": ch.to_string(), "font": alternates_family(true), "area": {"width": 200, "height": 2}});
let boxed = NodeId(s.execute("text.create", &area).unwrap()["id"].as_u64().unwrap());
assert!(vectorcraft_text::layout(db, &obj(&s, boxed)).glyphs.is_empty(), "the text overflows");
let query = s.execute("text.glyphAlternates", &json!({"id": boxed.0, "start": 0})).unwrap();
assert_eq!(query["chosen"], json!(glyph.gid), "an overflowing character reports the fallback glyph it is drawn with");
}