Canvas: selected point type's baseline marker runs under each line, centred and right-aligned type too (#2879)

The marker ran from the anchor to the right edge of the bounds, so centred type showed half a line and right-aligned type none, and rotated type a level line. It now draws each line's baseline in text space (TextObject::baselines, the lines a click selects the type on), turned with the type.
This commit is contained in:
bflatastic
2026-10-18 19:02:52 +07:00
parent eec304dde1
commit bb14031aa3
+43 -7
View File
@@ -2412,16 +2412,16 @@ fn selection_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf: &Xf, curve
{
anchor_square(p, xf.to_screen(b.center()), color, true, anchor(false));
}
// Point type: baseline marker (area type shows its frame instead, which may not be a
// rectangle, and type on a path its path).
// Point type: each line's baseline, where a click selects it (`hit`), whatever its
// alignment and rotation, and the anchor (area type shows its frame instead, which may
// not be a rectangle, and type on a path its path).
if let NodeKind::Text(tx) = &n.kind
&& matches!(tx.kind, vectorcraft_doc::TextKind::Point)
{
let o = xf.to_screen(tx.xf * Point::ZERO);
let b = n.geometric_bounds().unwrap_or_default();
let e = xf.to_screen(Point::new(b.x1, (tx.xf * Point::ZERO).y));
p.line_segment([o, e], Stroke::new(1.0, color));
p.circle_filled(o, 2.5, color);
for (a, b) in tx.baselines() {
p.line_segment([xf.to_screen(tx.xf * a), xf.to_screen(tx.xf * b)], Stroke::new(1.0, color));
}
p.circle_filled(xf.to_screen(tx.xf * Point::ZERO), 2.5, color);
}
}
if app.session.prefs.show_handles_multiple_anchors || with_handles <= 1 {
@@ -3537,6 +3537,42 @@ mod tests {
out.textures_delta.clear();
}
/// The baseline marker of selected point type runs under the line, wherever its alignment
/// puts the anchor: under centred and right-aligned type too, not from the anchor rightwards
/// (#2879).
#[test]
fn point_type_baselines_run_under_the_line_whatever_its_alignment() {
for justify in ["left", "center", "right"] {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 600, "height": 300})).unwrap();
app.ui.task_bar = false;
app.ui.view.rulers = false;
let made = app.run("text.create", json!({"x": 300, "y": 150, "text": "Baseline marker", "size": 24, "justify": justify})).unwrap();
let id = vectorcraft_doc::NodeId(made["id"].as_u64().unwrap());
app.run("select.set", json!({"ids": [id.0]})).unwrap();
let ctx = egui::Context::default();
frame(&mut app, &ctx, vec![]);
let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap());
let st = app.session.active().unwrap();
let b = st.doc.node(id).unwrap().geometric_bounds().unwrap();
let color = c32(st.doc.layer_color(id));
let y = xf.to_screen(Point::new(0.0, 150.0)).y;
let (left, right) = (xf.to_screen(Point::new(b.x0, 150.0)).x, xf.to_screen(Point::new(b.x1, 150.0)).x);
let on: Vec<f32> = shapes(&mut app, &ctx)
.into_iter()
.filter_map(|s| match s {
Shape::LineSegment { points, stroke } if stroke.color == color => Some(points),
_ => None,
})
.flatten()
.filter(|q| (q.y - y).abs() < 0.5)
.map(|q| q.x)
.collect();
let (lo, hi) = on.iter().fold((f32::MAX, f32::MIN), |(lo, hi), x| (lo.min(*x), hi.max(*x)));
assert!((lo - left).abs() < 3.0 && (hi - right).abs() < 3.0, "{justify}: the baseline runs {lo}..{hi}, the type {left}..{right}");
}
}
/// A click with the Selection tool inside an artboard makes it the active one, and Paste in
/// Place then pastes onto it, where the objects were on their own artboard (#693).
#[test]