mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 18:54:55 +00:00
Glyphs: text.setGlyphAlternate with gid null reports the glyph drawn
With no glyph choice left on the character, text.setGlyphAlternate returned the font's default glyph for it: 0 for a character the font lacks, which a fallback font draws, and the lowercase glyph for a letter under All Caps. It now reports the glyph the character is drawn with, as text.glyphAlternates does.
This commit is contained in:
@@ -1276,19 +1276,25 @@ fn set_glyph_alternate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// out (overflowing text) is shaped alone in its writing direction, with the rest of its grapheme
|
||||
// in the grapheme's own styles.
|
||||
let plain = t.plain_text();
|
||||
let unchosen = holder.as_ref().filter(|_| !styled).and_then(|_| {
|
||||
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).0)
|
||||
});
|
||||
let mut probe = t.clone();
|
||||
edit::style_range(&mut probe.runs, bytes.start, bytes.end, |st| st.glyph_alternate = None);
|
||||
let probe_lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &probe);
|
||||
let unchosen = holder
|
||||
.as_ref()
|
||||
.filter(|_| !styled)
|
||||
.and_then(|_| own_glyph(&plain, &probe_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) {
|
||||
None => None,
|
||||
Some(g) if g == default || face.alternates(ch).iter().any(|a| a.gid == g) => Some(face.choice(ch, g)),
|
||||
Some(g) => return Err(bad(C, format!("glyph {g} is not an alternate of {ch} in {} {}", face.family, face.style))),
|
||||
};
|
||||
let drawn = choice.as_ref().map_or(unchosen.unwrap_or(default), |c| c.gid);
|
||||
// Without a choice, the glyph drawn in whatever face draws the character, as
|
||||
// `text.glyphAlternates` reports it (a fallback face's glyph, the capital of All Caps).
|
||||
let drawn = match &choice {
|
||||
Some(c) => c.gid,
|
||||
None => drawn_glyph(&plain, &probe_lay, ch, &bytes, &face).or_else(|| shaped_alone(&probe, ch, &bytes, &face).1).unwrap_or(default),
|
||||
};
|
||||
// The run holding the character. The request changes nothing when the choice is the one the
|
||||
// character's run holds and that run holds the character alone; otherwise the edit splits the
|
||||
// run, so that the following text does not take the character into a ligature.
|
||||
|
||||
@@ -3436,6 +3436,50 @@ fn glyph_alternates_query_reports_the_fallback_glyph_that_is_drawn() {
|
||||
assert_eq!(query["chosen"], json!(glyph.gid), "an overflowing character reports the fallback glyph it is drawn with");
|
||||
}
|
||||
|
||||
/// `text.setGlyphAlternate` with `gid: null` reports the glyph the character is drawn with, as
|
||||
/// `text.glyphAlternates` does: the fallback face's glyph for a character the font lacks, laid out
|
||||
/// or overflowing, and the capital that All Caps draws for an a.
|
||||
#[test]
|
||||
fn a_glyph_choice_taken_away_reports_the_glyph_drawn() {
|
||||
use vectorcraft_text::test_fonts::{alternates_family, alternates_font};
|
||||
let _fonts = crate::adding_fonts();
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
db.add_font(alternates_font(true).expect("the test font builds"));
|
||||
let face = db.face(alternates_family(true), "Regular").expect("the test face");
|
||||
let mut s = session();
|
||||
let reset = |s: &mut Session, id: NodeId, start: usize| {
|
||||
s.execute("text.setGlyphAlternate", &json!({"id": id.0, "start": start, "gid": null})).unwrap()["gid"].clone()
|
||||
};
|
||||
let chosen = |s: &mut Session, id: NodeId, start: usize| {
|
||||
s.execute("text.glyphAlternates", &json!({"id": id.0, "start": start})).unwrap()["chosen"].clone()
|
||||
};
|
||||
|
||||
let ch = 'Ƃ';
|
||||
assert_eq!(face.glyph_for(ch), 0);
|
||||
let point = json!({"x": 0, "y": 0, "text": ch.to_string(), "font": alternates_family(true)});
|
||||
let id = NodeId(s.execute("text.create", &point).unwrap()["id"].as_u64().unwrap());
|
||||
let drawn = vectorcraft_text::layout(db, &obj(&s, id)).glyphs[0].gid;
|
||||
assert_ne!(drawn, 0);
|
||||
assert_eq!(reset(&mut s, id, 0), json!(drawn), "the fallback glyph");
|
||||
assert_eq!(chosen(&mut s, id, 0), json!(drawn));
|
||||
|
||||
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");
|
||||
assert_eq!(reset(&mut s, boxed, 0), json!(drawn), "the overflowing character's fallback glyph");
|
||||
|
||||
let caps = NodeId(
|
||||
s.execute("text.create", &json!({"x": 100, "y": 100, "text": "xaay", "size": 20, "font": alternates_family(true)})).unwrap()["id"]
|
||||
.as_u64()
|
||||
.unwrap(),
|
||||
);
|
||||
s.execute("text.setRangeStyle", &json!({"id": caps.0, "start": 0, "end": 4, "allCaps": true})).unwrap();
|
||||
let drawn = vectorcraft_text::layout(db, &obj(&s, caps)).glyphs.iter().find(|g| g.byte == 1).expect("the a is laid out").gid;
|
||||
assert_ne!(drawn, face.glyph_for('a'), "All Caps draws the a as A");
|
||||
assert_eq!(reset(&mut s, caps, 1), json!(drawn), "the capital drawn");
|
||||
assert_eq!(chosen(&mut s, caps, 1), json!(drawn));
|
||||
}
|
||||
|
||||
/// A fallback face draws Ka with the vowel sign I in one cluster, the vowel sign first. The glyph
|
||||
/// reported for Ka is the fallback face's glyph for Ka, laid out or overflowing, not the vowel
|
||||
/// sign's.
|
||||
|
||||
Reference in New Issue
Block a user