mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 19:57:05 -06:00
Type Object Selection by Path Only: every baseline, Type tool and Eyedropper (review of #475)
- Type is picked on the baseline of any of its lines (point type and area type, besides area type's frame), as in Illustrator, not only the first one: the layout's baselines are cached with its bounds (`TextObject::cached_baselines`, from `TextLayout::baselines`), with the first one across the bounds as the fallback. Vertical type's runs down each column's centre line. - Type in perspective is picked where it is drawn (through its projection). - The bounds rule type out before its path is built, so hovering stays free of allocations away from type. - The Type tools still edit type clicked among its characters (they made new type there with the preference on), and the Eyedropper still samples it there, as it does inside fills with Object Selection by Path Only. - Tests for all of it; docs/mcp.md and ROADMAP say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
beea693ee1
commit
afb438367d
@@ -158,6 +158,18 @@ impl TextLayout {
|
||||
pub fn physical_point(&self, p: Point) -> Point {
|
||||
if self.vertical { self.line_xf * p } else { p }
|
||||
}
|
||||
/// Each line's baseline in text space, start to end (a vertical column's centre line), for
|
||||
/// the lines that hold characters; none for type on a path, which its path stands for.
|
||||
pub fn baselines(&self) -> Vec<(Point, Point)> {
|
||||
if self.on_path {
|
||||
return vec![];
|
||||
}
|
||||
let line = |l: &LineInfo| {
|
||||
let y = if self.vertical { l.baseline + (l.descent - l.ascent) / 2.0 } else { l.baseline };
|
||||
(self.physical_point(Point::new(l.x0, y)), self.physical_point(Point::new(l.x1, y)))
|
||||
};
|
||||
self.lines.iter().filter(|l| l.x1 > l.x0).map(line).collect()
|
||||
}
|
||||
/// All glyph outlines combined (e.g. for Create Outlines).
|
||||
pub fn to_bezpath(&self) -> BezPath {
|
||||
let mut p = BezPath::new();
|
||||
|
||||
@@ -73,6 +73,41 @@ fn area_type_starts_at_the_right_edge_and_wraps_to_the_left() {
|
||||
assert!(l.glyphs.iter().all(|g| g.outline.bounding_box().y1 <= 105.0 + 1e-6), "inside the frame");
|
||||
}
|
||||
|
||||
/// `baselines`: one per line that holds characters, along it for horizontal type and down the
|
||||
/// column's centre line for vertical type (where point type's anchor is); none on a path.
|
||||
#[test]
|
||||
fn baselines_run_along_lines_and_down_column_centres() {
|
||||
let h = layout(
|
||||
FontDb::global(),
|
||||
&TextObject::point(
|
||||
Point::ZERO,
|
||||
"§§
|
||||
|
||||
§",
|
||||
CharStyle { size: 20.0, ..CharStyle::default() },
|
||||
),
|
||||
);
|
||||
let hb = h.baselines();
|
||||
assert_eq!(hb.len(), 2, "the empty line has none: {hb:?}");
|
||||
assert!(hb[0].0 == Point::ZERO && hb[0].1.y == 0.0 && hb[0].1.x > 15.0, "the first baseline runs from the anchor: {hb:?}");
|
||||
assert!(hb[1].0.y > 40.0 && hb[1].0.y == hb[1].1.y && hb[1].1.x > hb[1].0.x, "the third line's, lower: {hb:?}");
|
||||
let v = layout(
|
||||
FontDb::global(),
|
||||
&vertical(
|
||||
"§§
|
||||
§",
|
||||
),
|
||||
);
|
||||
let vb = v.baselines();
|
||||
let (a, c) = (ink(&v, 0), ink(&v, 2));
|
||||
assert_eq!(vb.len(), 2, "{vb:?}");
|
||||
assert!(vb[0].0.x.abs() < 4.0 && vb[0].0.x == vb[0].1.x && vb[0].1.y > vb[0].0.y + 30.0, "down the first column's centre: {vb:?}");
|
||||
assert!((vb[0].0.x - a.center().x).abs() < 4.0 && (vb[1].0.x - c.center().x).abs() < 4.0, "through the glyphs: {vb:?} {a:?} {c:?}");
|
||||
let mut on_path = vertical("ab");
|
||||
on_path.kind = TextKind::OnPath { path: PathData::from_bezpath(&kurbo::Line::new((0.0, 0.0), (100.0, 0.0)).to_path(0.1)), start: 0.0, end: None };
|
||||
assert!(layout(FontDb::global(), &on_path).baselines().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_on_a_path_stays_horizontal() {
|
||||
let mut t = vertical("ab");
|
||||
|
||||
Reference in New Issue
Block a user