mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:33:54 +00:00
Merge pull request #5721 from dotCooCoo/fix/glyph-fallback-cluster
Glyphs: a fallback face's consonant glyph is reported, not its vowel sign's
This commit is contained in:
@@ -1203,8 +1203,8 @@ 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). Also the first glyph drawn for the character, which can be in a fallback
|
||||
/// face.
|
||||
/// (overflowing text). Also the glyph drawn for the character in whatever face draws it
|
||||
/// ([`drawn_glyph`]).
|
||||
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();
|
||||
@@ -1219,8 +1219,27 @@ 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);
|
||||
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)
|
||||
let (alone, at) = (single.plain_text(), 0..bytes.len());
|
||||
(own_glyph(&alone, &lay, ch, &at, face).flatten(), drawn_glyph(&alone, &lay, ch, &at, face))
|
||||
}
|
||||
|
||||
/// The glyph `lay` draws for the character `ch` at `bytes` of `plain` in whatever face draws it:
|
||||
/// the glyph of `face` ([`own_glyph`]); else, when a fallback face draws the character, that face's
|
||||
/// glyph for it by the same rule (its vowel sign drawn first is not reported); else the first glyph
|
||||
/// drawn for the character. `None` when the character is not laid out.
|
||||
fn drawn_glyph(
|
||||
plain: &str,
|
||||
lay: &vectorcraft_text::TextLayout,
|
||||
ch: char,
|
||||
bytes: &std::ops::Range<usize>,
|
||||
face: &vectorcraft_text::FontFace,
|
||||
) -> Option<u32> {
|
||||
if let Some(own) = own_glyph(plain, lay, ch, bytes, face).flatten() {
|
||||
return Some(own);
|
||||
}
|
||||
let first = lay.glyphs.iter().find(|g| g.byte <= bytes.start && bytes.start < g.byte.saturating_add(g.len))?;
|
||||
let fallback = (first.font_id != face.id()).then(|| vectorcraft_text::FontDb::global().face_by_id(first.font_id)).flatten();
|
||||
fallback.and_then(|f| own_glyph(plain, lay, ch, bytes, &f).flatten()).or(Some(first.gid))
|
||||
}
|
||||
|
||||
fn set_glyph_alternate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -1304,18 +1323,10 @@ fn glyph_alternates(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (ch, bytes, face) = glyph_target(&t, p, C)?;
|
||||
let default = face.glyph_for(ch);
|
||||
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; 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 => {
|
||||
let (own, first) = shaped_alone(&t, ch, &bytes, &face);
|
||||
own.or(first)
|
||||
}
|
||||
}
|
||||
.unwrap_or(default);
|
||||
let chosen = drawn_glyph(&t.plain_text(), &lay, ch, &bytes, &face).or_else(|| shaped_alone(&t, ch, &bytes, &face).1).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();
|
||||
Ok(json!({"char": ch.to_string(), "font": face.family, "style": face.style, "default": default, "chosen": chosen, "alternates": alternates}))
|
||||
}
|
||||
|
||||
@@ -2800,3 +2800,37 @@ fn glyph_alternates_query_reports_the_fallback_glyph_that_is_drawn() {
|
||||
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");
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[test]
|
||||
fn the_fallback_glyph_of_a_consonant_after_its_vowel_sign_is_reported() {
|
||||
use vectorcraft_text::test_fonts::{MATRA_FAMILY, alternates_family, alternates_font, matra_font};
|
||||
let _fonts = crate::adding_fonts();
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
db.add_font(alternates_font(true).expect("the test font builds"));
|
||||
db.add_font(matra_font().expect("the test font builds"));
|
||||
let requested = db.face(alternates_family(true), "Regular").expect("the test face");
|
||||
let text = "क\u{93F}";
|
||||
assert_eq!(requested.glyph_for('क'), 0);
|
||||
let mut s = session();
|
||||
let create = |s: &mut Session, height: f64| {
|
||||
let spec = json!({"x": 0, "y": 0, "text": text, "font": alternates_family(true), "area": {"width": 200, "height": height}});
|
||||
NodeId(s.execute("text.create", &spec).unwrap()["id"].as_u64().unwrap())
|
||||
};
|
||||
let open = create(&mut s, 200.0);
|
||||
let glyphs = vectorcraft_text::layout(db, &obj(&s, open)).glyphs;
|
||||
let fallback = db.face_by_id(glyphs[0].font_id).expect("the fallback face");
|
||||
assert_ne!(fallback.id(), requested.id());
|
||||
let (ka, sign) = (fallback.glyph_for('क'), fallback.glyph_for('\u{93F}'));
|
||||
let drawn: Vec<_> = glyphs.iter().map(|g| (g.gid, g.byte)).collect();
|
||||
assert_eq!(drawn, [(sign, 0), (ka, 0)], "the vowel sign is drawn first, in Ka's cluster ({} or {MATRA_FAMILY})", fallback.family);
|
||||
let query = s.execute("text.glyphAlternates", &json!({"id": open.0, "start": 0})).unwrap();
|
||||
assert_eq!(query["chosen"], json!(ka), "the fallback face's glyph for Ka");
|
||||
|
||||
let boxed = create(&mut s, 2.0);
|
||||
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!(ka), "an overflowing Ka reports the fallback face's glyph for Ka");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user