Type: no justification gap inside a right-to-left warichu block

Justification gaps follow the drawn order, and right to left the bidi
order draws a block's first glyph after the others. The glyph drawn
second then did not continue the block, so Full Justify and CJK
justification opened a gap inside it. Both gap rules now also skip a
pair whose glyphs belong to the same block.
This commit is contained in:
dotCooCoo
2026-11-12 23:03:39 -08:00
parent 199a9dbce3
commit 06a283232e
2 changed files with 39 additions and 4 deletions
+16 -4
View File
@@ -753,12 +753,24 @@ fn gap_pair<'a>(glyph: usize, held: &[usize], after: &[Option<usize>], index: us
visible_after(index, glyph, after, sg).and_then(|next| sg.get(next)).map(|b| (a, b))
}
// Are `a` and `b` glyphs of the same warichu block? Right to left, the bidi order can draw a
// block's first glyph after the others, so the glyph drawn second need not continue the block.
fn same_warichu_block(a: &SGlyph, b: &SGlyph) -> bool {
a.wari.zip(b.wari).is_some_and(|(first, second)| first.start == second.start)
}
// Japanese (and Chinese) lines are justified between their characters (JLREQ 3.8): the
// gaps next to a CJK character, not inside a Latin word, a tate-chu-yoko or warichu block or a
// shaped cluster.
fn is_cjk_gap(glyph: usize, held: &[usize], after: &[Option<usize>], index: usize, sg: &[SGlyph]) -> bool {
pub(super) fn is_cjk_gap(glyph: usize, held: &[usize], after: &[Option<usize>], index: usize, sg: &[SGlyph]) -> bool {
gap_pair(glyph, held, after, index, sg).is_some_and(|(a, b)| {
a.byte != b.byte && !a.is_space() && !b.is_space() && !b.continues_block() && a.ch != '\t' && (is_cjk(a.ch) || is_cjk(b.ch))
a.byte != b.byte
&& !a.is_space()
&& !b.is_space()
&& !b.continues_block()
&& !same_warichu_block(a, b)
&& a.ch != '\t'
&& (is_cjk(a.ch) || is_cjk(b.ch))
}) && !in_pair(glyph, index, after, sg)
}
@@ -788,12 +800,12 @@ fn in_pair(glyph: usize, index: usize, after: &[Option<usize>], sg: &[SGlyph]) -
// warichu block (set as one piece), a shaped cluster, whose glyphs (a letter and its combining marks, a conjunct and its matra) stay
// together, or an inseparable pair. On a line that ends in a drawn hyphen, the gap before the
// hyphen counts too: it follows the line's last visible glyph.
fn is_letter_gap(glyph: usize, held: &[usize], after: &[Option<usize>], index: usize, sg: &[SGlyph], hyphen: bool) -> bool {
pub(super) fn is_letter_gap(glyph: usize, held: &[usize], after: &[Option<usize>], index: usize, sg: &[SGlyph], hyphen: bool) -> bool {
if held.contains(&glyph) || sg.get(glyph).is_none_or(|a| is_format_control(a.ch)) || in_pair(glyph, index, after, sg) {
return false;
}
match gap_pair(glyph, held, after, index, sg) {
Some((a, b)) => a.byte != b.byte && !b.continues_block(),
Some((a, b)) => a.byte != b.byte && !b.continues_block() && !same_warichu_block(a, b),
None => hyphen,
}
}
+23
View File
@@ -717,3 +717,26 @@ fn bunri_kinshi_progresses_with_an_overlong_pair_and_no_break() {
assert_eq!(line(text, &pairs(text, false), 0, 46.5, Kinsoku::Hard, PushInFirst), 5);
assert_eq!(line(text, &pairs(text, true), 0, 46.5, Kinsoku::Hard, PushInFirst), 3);
}
/// Justification adds no gap between two glyphs of one warichu block, whichever of them is drawn
/// first: right to left, the block's first glyph (the one that starts it) is drawn after the
/// others. Between glyphs of two blocks, the gap stays.
#[test]
fn justification_adds_no_gap_inside_a_warichu_block_drawn_right_to_left() {
let block = |g: &mut SGlyph, start: usize, pen: f64| {
g.wari = Some(crate::shape::Wari { start, pen, along: 0.0, across: 0.0, advance: g.adv, em: 10.0 });
};
// Glyph 1 is drawn first, then glyph 0, which starts the block.
let after = [None, Some(0)];
for (first, second) in [('א', 'ב'), ('漢', 'א')] {
let mut glyphs = synthetic_glyphs(&[(0, 2, 10.0, first), (2, 2, 0.0, second)]);
block(&mut glyphs[0], 0, 0.0);
block(&mut glyphs[1], 0, 10.0);
assert!(!flow::is_letter_gap(1, &[], &after, 0, &glyphs, false), "{first}{second}");
assert!(!flow::is_cjk_gap(1, &[], &after, 0, &glyphs), "{first}{second}");
// The same two glyphs as the first glyphs of two blocks.
block(&mut glyphs[1], 2, 0.0);
assert!(flow::is_letter_gap(1, &[], &after, 0, &glyphs, false), "{first}{second}");
assert_eq!(flow::is_cjk_gap(1, &[], &after, 0, &glyphs), first == '漢', "{first}{second}");
}
}