From a883874a7341231f1b61f1bd8a56640fb3e57bb7 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 4 Nov 2026 06:20:29 -0800 Subject: [PATCH] Type: Touch Type moves an upright character in vertical type down and across its column An upright glyph in vertical type has its frame's up axis along its column, parallel to the line, so Touch Type split no drag or arrow-key nudge into kerning and baseline shift and sent no movement. Such a glyph now takes the line's cross axis, right of its column, as the way baseline shift moves it. --- crates/tools/src/touchtype.rs | 10 ++++++++-- crates/tools/src/touchtype/tests.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/tools/src/touchtype.rs b/crates/tools/src/touchtype.rs index c1184d72a..172487c19 100644 --- a/crates/tools/src/touchtype.rs +++ b/crates/tools/src/touchtype.rs @@ -58,7 +58,8 @@ struct CharBox { pivot: Point, /// A unit vector along its line in reading order. along: Vec2, - /// A unit vector up from its line, without its rotation: the way baseline shift moves it. + /// A unit vector up from its line, without its rotation: the way baseline shift moves it. For + /// a glyph standing upright in vertical type, the line's cross axis (right of its column). up: Vec2, /// The glyph's own x axis and its up axis (with its rotation), unit vectors. ex: Vec2, @@ -174,6 +175,11 @@ fn char_box(t: &TextObject, lay: &TextLayout, gi: usize) -> Option { } let quad = [m * Point::new(x0, y0), m * Point::new(x1, y0), m * Point::new(x1, y1), m * Point::new(x0, y1)]; let d = Vec2::new(g.angle.cos(), g.angle.sin()); + // Up from the glyph's frame without its rotation. For a glyph standing upright in vertical + // type, that up runs along the column, so the glyph takes the line's cross axis instead: the + // way baseline shift moves the column's sideways glyphs. + let up = unit(linear(m * g.unturned, Vec2::new(0.0, -1.0))); + let up = if d.cross(up).abs() > 1e-6 { up } else { Vec2::new(d.y, -d.x) }; let style = edit::slice_runs(&t.runs, g.byte, g.byte + g.len).first().map(|r| r.style.clone())?; Some(CharBox { start: g.byte, @@ -181,7 +187,7 @@ fn char_box(t: &TextObject, lay: &TextLayout, gi: usize) -> Option { pen, pivot: m * g.pivot, along: if g.rtl { -d } else { d }, - up: unit(linear(m * g.unturned, Vec2::new(0.0, -1.0))), + up, ex, ey, kerned: lay.glyphs.iter().any(|h| h.line == g.line && h.len > 0 && h.byte + h.len == g.byte && h.rtl == g.rtl), diff --git a/crates/tools/src/touchtype/tests.rs b/crates/tools/src/touchtype/tests.rs index 99388178e..3b551950d 100644 --- a/crates/tools/src/touchtype/tests.rs +++ b/crates/tools/src/touchtype/tests.rs @@ -176,6 +176,35 @@ fn a_character_rotated_near_a_quarter_turn_moves_across_its_line() { } } +/// In vertical type, a character standing upright (§) still splits a drag into kerning down its +/// column and baseline shift across it: down 10 pt and right 4 pt is `dx` 10 and `dy` 4. Every +/// arrow key nudges it. +#[test] +fn an_upright_character_in_vertical_type_moves_down_and_across_its_column() { + let (mut d, id, s) = doc_with_type(); + if let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) { + **t = TextObject::point(Point::new(100.0, 200.0), "l§", CharStyle { size: 40.0, ..Default::default() }); + t.vertical = true; + } + let p = paint(); + let cx = cx(&d, &s, &p); + let mut t = TouchTypeTool { target: Some((id, 1)), ..Default::default() }; + let q = quad(&t, &cx); + let at = q[0].midpoint(q[2]); + let acts = drag(&mut t, &cx, at, at + Vec2::new(4.0, 10.0)); + let p1 = preview(&acts[1]); + assert!(close(p1["dx"].as_f64().unwrap_or(0.0), 10.0) && close(p1["dy"].as_f64().unwrap_or(0.0), 4.0), "{p1}"); + for key in [ToolKey::Up, ToolKey::Down, ToolKey::Left, ToolKey::Right] { + let acts = t.key(&cx, key, Mods::default()); + let moved = match acts.first() { + Some(Action::Exec(c, v)) if c == "text.touchType" => v, + other => panic!("{key:?}: {other:?}"), + }; + let parts = [&moved["dx"], &moved["dy"]].map(|v| v.as_f64().unwrap_or(0.0).abs()); + assert!(close(parts[0] + parts[1], 1.0) && (close(parts[0], 0.0) || close(parts[1], 0.0)), "{key:?}: {moved}"); + } +} + /// In type scaled unevenly, a drag's parts are measured in document points: right 10 pt and up 4 /// pt of a quarter-turned character is `dx` 10 and `dy` 4. #[test]