From 3487e1ad80abad0d0662d953dd556d162926eb9b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 10 Nov 2026 15:17:59 -0800 Subject: [PATCH] Type: vertical-only settings keep horizontal ligatures whole Shaping split a cluster wherever two characters' styles differed, so setting Standard Vertical Roman Alignment or a tate-chu-yoko offset on the f of "fi" in horizontal type broke the ligature. Horizontal type does not read those settings, so they no longer split a cluster there. Vertical type still shapes such characters apart. --- crates/text/src/shape.rs | 22 +++++++++++++++++++-- crates/text/src/tests/ligature_carets.rs | 25 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/text/src/shape.rs b/crates/text/src/shape.rs index b913c7cc2..ae44ce433 100644 --- a/crates/text/src/shape.rs +++ b/crates/text/src/shape.rs @@ -796,6 +796,23 @@ fn source_run_at(runs: &[(Range, &CharStyle)], span: &Range, byte: source.get(i).filter(|(range, _)| range.contains(&byte)).map(|_| span.start + i) } +/// Can one glyph draw characters in styles `a` and `b`? Only when the styles are equal, except that +/// horizontal type (`vertical` false) does not read Standard Vertical Roman Alignment or the +/// tate-chu-yoko offsets, so styles that differ only in those are drawn alike there. +fn drawn_alike(a: &CharStyle, b: &CharStyle, vertical: bool) -> bool { + if a == b { + return true; + } + !vertical + && *a + == CharStyle { + standard_vertical_roman: a.standard_vertical_roman, + tcy_up_down: a.tcy_up_down, + tcy_left_right: a.tcy_left_right, + ..b.clone() + } +} + /// Do text runs in styles `a` and `b` shape alike, so that they are shaped as one piece of text? /// They do when they differ only in paint and stroke, underline and strikethrough, overprint, the /// character style's name, leading, baseline shift, rotation, Character Alignment, Character @@ -1275,9 +1292,10 @@ fn shape_segment(text: &str, context: &Range, seg: &Segment, feats: &OtFe cluster_starts.dedup(); // A glyph is drawn in one style: where a cluster would take in characters of runs in different // styles, the runs are shaped apart, as runs that do not shape alike are. - // Except at harakat styled apart only in paint ([`crate::harakat`]). + // Except at harakat styled apart only in paint ([`crate::harakat`]), and, in horizontal type, where + // the runs differ only in settings that vertical type alone reads. let inside_clusters: Vec = boundaries() - .filter(|&b| cluster_starts.binary_search(&b).is_err() && run_at(b - 1).1 != run_at(b).1) + .filter(|&b| cluster_starts.binary_search(&b).is_err() && !drawn_alike(run_at(b - 1).1, run_at(b).1, feats.vertical)) .filter(|b| mark_boundaries.binary_search(b).is_err()) .collect(); if !inside_clusters.is_empty() { diff --git a/crates/text/src/tests/ligature_carets.rs b/crates/text/src/tests/ligature_carets.rs index f669b07f4..a93d1a9cd 100644 --- a/crates/text/src/tests/ligature_carets.rs +++ b/crates/text/src/tests/ligature_carets.rs @@ -629,6 +629,31 @@ fn an_upright_ligature_takes_its_caret_down_its_cell() { assert!((at(900) - 0.5).abs() < 1e-9, "{}", at(900)); } +/// Horizontal type does not read Standard Vertical Roman Alignment or the tate-chu-yoko offsets, so +/// setting one of them on the f of "fi" keeps the ligature. Vertical type reads them per glyph, so +/// there the f is shaped apart from the i. +#[test] +fn vertical_only_settings_keep_a_horizontal_ligature_whole() { + let db = db_with(vertical_ligature_caret_font(280)); + let marks: [fn(&mut CharStyle); 3] = [|s| s.standard_vertical_roman = true, |s| s.tcy_up_down = 10.0, |s| s.tcy_left_right = 10.0]; + for (i, mark) in marks.into_iter().enumerate() { + for vertical in [false, true] { + let mut t = point("fi", VERTICAL_LIGATURE_CARET_FAMILY); + t.vertical = vertical; + let plain = t.runs[0].style.clone(); + let mut marked = plain.clone(); + mark(&mut marked); + t.runs = vec![ + vectorcraft_doc::TextRun { text: "f".into(), style: marked, inline: None }, + vectorcraft_doc::TextRun { text: "i".into(), style: plain, inline: None }, + ]; + let l = layout(&db, &t); + let joined = l.glyphs.iter().any(|g| g.len == 2); + assert_eq!(joined, !vertical, "setting {i}, vertical {vertical}: {:?}", l.glyphs); + } + } +} + /// On a path, an upright ligature in vertical type stands with its top toward the path's start, and /// its cell runs from 0.12 em before the pen to 0.88 em after it, with or without the font's vertical /// metrics. The inner stop lies at the middle of that cell, and the first character's selection ends