PDF/.ai import: evenly tracked type on a path keeps its length

Straight type takes the tracking that keeps its length as the file set
it; type on a path took none, so letter-spaced type on a curve came back
closed up to the font's advances (an "OFFICIAL ISSUED" badge sat to one
side of its band, a condensed title ran off the end of its path). Type on
a path now takes the same tracking, its length measured along the path
from the first glyph to the end of the last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hiroshi Inukai 2026-10-12 00:07:28 +09:00 committed by bflatastic
parent 8d2d838884
commit 02da9bbdc9
2 changed files with 33 additions and 0 deletions

View File

@ -350,6 +350,20 @@ impl TextLine {
t.runs = tracked_each(std::mem::take(&mut t.runs), own, self.at.size); t.runs = tracked_each(std::mem::take(&mut t.runs), own, self.at.size);
} }
if let Some(path) = path { if let Some(path) = path {
// Evenly spaced type keeps its length along the path: the path runs through the
// glyphs' origins, one curve each, to the end of the last glyph, and tracking makes up
// the difference from the advances.
if own.is_none() {
let length: f64 = path.segments().take(self.baseline.len()).map(|seg| kurbo::ParamCurveArclen::arclen(&seg, 1e-3)).sum();
let laid = vectorcraft_text::layout(db, &t);
let natural: f64 = laid.glyphs.iter().map(|g| g.advance).sum();
let chars: usize = t.runs.iter().map(|r| r.text.chars().count()).sum();
let size = self.at.size;
if chars > 1 && length.is_finite() && (length - natural).abs() > size * 0.01 {
let (tracking, spaces) = spacing(&laid, &[], length - natural, chars, size);
t.runs = tracked(std::mem::take(&mut t.runs), tracking, &spaces);
}
}
t.kind = TextKind::OnPath { path: vectorcraft_geom::PathData::from_bezpath(&path), start: 0.0, end: None }; t.kind = TextKind::OnPath { path: vectorcraft_geom::PathData::from_bezpath(&path), start: 0.0, end: None };
t.xf = Affine::IDENTITY; t.xf = Affine::IDENTITY;
t.cached_bounds = Some(vectorcraft_text::layout(db, &t).bounds); t.cached_bounds = Some(vectorcraft_text::layout(db, &t).bounds);

View File

@ -424,3 +424,22 @@ fn glyphs_spaced_each_their_own_way_keep_their_place() {
} }
}); });
} }
/// Evenly tracked type on a path comes back with its tracking: each glyph where it was along the
/// curve, not closed up to the font's advances.
#[test]
fn evenly_tracked_type_on_a_path_keeps_its_length() {
let mut arc = BezPath::new();
arc.move_to((40.0, 200.0));
arc.curve_to((100.0, 80.0), (260.0, 80.0), (320.0, 200.0));
let mut t = TextObject::point(Point::ORIGIN, "ARCHING TYPE", CharStyle { tracking: 80.0, ..style(24.0) });
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&arc), start: 0.0, end: None };
let d = doc(vec![t]);
let want = origins(&d);
let back = import_as(&pdf(&d, false).bytes, TextAs::default());
let got = origins(&back);
assert_eq!(got.len(), want.len(), "{:?}", texts(&back));
for (g, w) in got.iter().zip(&want) {
assert!(g.distance(*w) < 0.3, "{got:?}\nvs {want:?}");
}
}