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]