Merge pull request #5620 from dotCooCoo/fix/upright-vertical-baseline-shift

Type: baseline shift moves an upright glyph across the column in vertical type
This commit is contained in:
BFlat
2026-11-04 22:23:49 +07:00
committed by GitHub
2 changed files with 41 additions and 2 deletions
+6 -2
View File
@@ -327,10 +327,14 @@ impl Ctx<'_> {
} else if self.vertical && stands_upright(g) {
let next = self.out.glyphs.len();
let after_base = self.out.glyphs.last().is_some_and(|p| p.byte == g.byte && p.line == line && p.len > 0);
// Baseline shift moves it across the column at any Character Rotation: the shift is
// taken off before the turn and put back after it.
let shift = pre * Point::new(0.0, -g.bshift) - pre * Point::ZERO;
let (shift, unshift) = (Affine::translate(shift), Affine::translate(-shift));
if let Some((_, _, turn, after)) = cluster.filter(|c| c.0 == g.byte && c.1 == next && after_base && g.len > 0) {
// A later glyph of an upright glyph's cluster: set after it as the shaper placed
// it, and turned with it.
m = turn * Affine::translate((after - origin.x, 0.0)) * m;
m = shift * turn * Affine::translate((after - origin.x, 0.0)) * unshift * m;
self.upright_cluster = Some((g.byte, next + 1, turn, after));
} else {
// Turned about the centre of its cell, which the column's centre line runs through:
@@ -342,7 +346,7 @@ impl Ctx<'_> {
// turning point instead would move it across the column too).
let turn = Affine::translate((-g.lead, 0.0))
* Affine::rotate_about(-std::f64::consts::FRAC_PI_2, Point::new(origin.x + cell * 0.5, origin.y - upright_centre(g, cell, em)));
m = turn * m;
m = shift * turn * unshift * m;
// Its own advance across, from where the cell centres it: where the shaper set the
// next glyph of its cluster.
let across = g.face.advance(g.gid) * g.sx;
+35
View File
@@ -343,6 +343,41 @@ fn tracking_spaces_upright_marks_without_moving_them_off_the_centre_line() {
assert!((step - 24.0).abs() < 1e-6, "one em plus 200/1000 em of tracking: {step}");
}
/// Baseline shift moves an upright glyph across the column, as it moves a letter lying on its
/// side, at any Character Rotation, and leaves its neighbors where they are. The glyph turns about
/// its shifted pen position (`xf * pivot`), and `xf * unturned` places the shifted glyph without
/// its rotation.
#[test]
fn baseline_shift_moves_an_upright_glyph_across_the_column() {
let at = |byte: usize, rotation: f64, shift: f64| {
let mut t = vertical("§l§");
edit::style_range(&mut t.runs, byte, byte + "§l§"[byte..].chars().next().map_or(1, char::len_utf8), |s| {
s.rotation = rotation;
s.baseline_shift = shift;
});
layout(FontDb::global(), &t)
};
let centre = |l: &TextLayout, byte: usize| l.glyphs.iter().find(|g| g.byte == byte).map(|g| g.outline.bounding_box().center()).unwrap();
// The letter lying on its side moves across the column by the shift.
let across = centre(&at(2, 0.0, 10.0), 2) - centre(&at(2, 0.0, 0.0), 2);
assert!((across.x.abs() - 10.0).abs() < 1e-6 && across.y.abs() < 1e-6, "{across:?}");
for rotation in [0.0, 30.0, 90.0, -90.0] {
let (plain, shifted) = (at(3, rotation, 0.0), at(3, rotation, 10.0));
let moved = centre(&shifted, 3) - centre(&plain, 3);
assert!((moved - across).hypot() < 1e-6, "{rotation}°: {moved:?}, across {across:?}");
for byte in [0, 2] {
assert!((centre(&shifted, byte) - centre(&plain, byte)).hypot() < 1e-6, "{rotation}°: byte {byte} moved");
}
let (p, s) = (plain.glyphs.iter().find(|g| g.byte == 3).unwrap(), shifted.glyphs.iter().find(|g| g.byte == 3).unwrap());
let turn_at = s.xf * s.pivot - p.xf * p.pivot;
assert!((turn_at - across).hypot() < 1e-6, "{rotation}°: turns about a point moved by {turn_at:?}");
let unrotated = at(3, 0.0, 10.0);
let u = unrotated.glyphs.iter().find(|g| g.byte == 3).unwrap();
let (a, b) = ((s.xf * s.unturned).as_coeffs(), u.xf.as_coeffs());
assert!(a.iter().zip(b).all(|(a, b)| (a - b).abs() < 1e-6), "{rotation}°: {a:?} vs {b:?}");
}
}
/// Character panel menu › Tate-chu-yoko: a marked run stands across one em of the column, including
/// a number longer than three digits and Latin letters. Unmarked text keeps the automatic rule
/// (two or three digits only). Horizontal type stores the mark and does not move the letters.