mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
PDF import: slanted type keeps its slant
A text matrix that shears the glyphs (type turned and slanted as a whole) came in as point type turned by the baseline's angle only: the glyphs stood square on the baseline, so the type drew somewhere else (60 pt type leaning by 0.25 came back about 10 pt off its outlines). The placement now keeps the slant (the em's lean along the baseline per unit of its height, the height measured across the baseline), lines only join glyphs with the same slant, and the type object's transform shears by it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
133ad87541
commit
ddd1c8fade
3 files changed
+37
-6
No files matched your search
@@ -24,6 +24,9 @@ pub(crate) struct Placement {
|
||||
pub size: f64,
|
||||
/// Width of the em ÷ its height, in percent.
|
||||
pub h_scale: f64,
|
||||
/// How far the em leans along the baseline per unit of its height (a text matrix with a
|
||||
/// shear: faux italic, or type slanted as a whole); 0 when it stands square on the baseline.
|
||||
pub slant: f64,
|
||||
}
|
||||
|
||||
impl Placement {
|
||||
@@ -33,14 +36,23 @@ impl Placement {
|
||||
let origin = m * Point::ORIGIN;
|
||||
let ex = m * Point::new(1000.0, 0.0) - origin;
|
||||
let ey = m * Point::new(0.0, 1000.0) - origin;
|
||||
let (w, size) = (ex.hypot(), ey.hypot());
|
||||
let w = ex.hypot();
|
||||
// Upright in y-down document space: x to the right of up.
|
||||
let upright = ex.cross(ey) < 0.0;
|
||||
(origin.is_finite() && w.is_finite() && size.is_finite() && w > 0.01 && size > 0.01 && size < 1e5 && upright).then(|| Self {
|
||||
if !(w.is_finite() && w > 0.01) {
|
||||
return None;
|
||||
}
|
||||
let dir = ex / w;
|
||||
// The em's height is across the baseline; what's left of `ey` along it is a slant.
|
||||
let up = Vec2::new(dir.y, -dir.x);
|
||||
let size = ey.dot(up);
|
||||
let slant = ey.dot(dir) / size;
|
||||
(origin.is_finite() && size.is_finite() && slant.is_finite() && size > 0.01 && size < 1e5 && upright).then(|| Self {
|
||||
origin,
|
||||
dir: ex / w,
|
||||
dir,
|
||||
size,
|
||||
h_scale: w / size * 100.0,
|
||||
slant,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,6 +223,7 @@ impl TextLine {
|
||||
let gap = (at.origin - self.next).dot(self.last_dir);
|
||||
let on_line = opacity == self.opacity
|
||||
&& at.dir.dot(self.at.dir) > 0.9995
|
||||
&& (at.slant - self.at.slant).abs() < 1e-3
|
||||
&& (at.origin - self.at.origin).dot(self.at.up()).abs() < size * 0.15
|
||||
&& gap > -size * 0.3
|
||||
&& gap < size * 3.0;
|
||||
@@ -218,6 +231,7 @@ impl TextLine {
|
||||
// that one ends.
|
||||
let on_curve = !on_line
|
||||
&& opacity == self.opacity
|
||||
&& (at.slant - self.at.slant).abs() < 1e-3
|
||||
&& at.dir.dot(self.last_dir) > CURVE_TURN_COS
|
||||
&& at.dir.dot(self.last_dir) < 0.99999
|
||||
&& (at.origin - self.next).hypot() < size * 0.6;
|
||||
@@ -309,7 +323,8 @@ impl TextLine {
|
||||
to_logical(&mut t);
|
||||
}
|
||||
let angle = self.at.dir.atan2();
|
||||
t.xf = Affine::translate(self.at.origin.to_vec2()) * Affine::rotate(angle);
|
||||
// The glyphs' tops lean along the baseline (+x), up being -y in the type's own space.
|
||||
t.xf = Affine::translate(self.at.origin.to_vec2()) * Affine::rotate(angle) * Affine::skew(-self.at.slant, 0.0);
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
if let Some(path) = path {
|
||||
t.kind = TextKind::OnPath { path: vectorcraft_geom::PathData::from_bezpath(&path), start: 0.0, end: None };
|
||||
|
||||
@@ -265,7 +265,7 @@ fn a_single_upright_glyph_comes_in_as_vertical_type_and_takes_no_horizontal_glyp
|
||||
fill: Some(vectorcraft_color::Paint::solid(vectorcraft_color::Color::rgb(0.0, 0.0, 0.0))),
|
||||
stroke: None,
|
||||
};
|
||||
let at = |x: f64, y: f64| Placement { origin: Point::new(x, y), dir: Vec2::new(1.0, 0.0), size: 20.0, h_scale: 100.0 };
|
||||
let at = |x: f64, y: f64| Placement { origin: Point::new(x, y), dir: Vec2::new(1.0, 0.0), size: 20.0, h_scale: 100.0, slant: 0.0 };
|
||||
let mut line = TextLine::new(at(100.0, 100.0), 1.0);
|
||||
assert!(line.push_upright(&look, at(100.0, 100.0), 1.0, Upright { top: Point::new(110.0, 82.0) }, "§"));
|
||||
assert!(!line.push(&look, at(130.0, 100.0), 1.0, 10.0, "a"), "a horizontal glyph starts another line");
|
||||
@@ -288,7 +288,7 @@ fn letter_spaced_vertical_type_keeps_its_characters_together() {
|
||||
stroke: None,
|
||||
};
|
||||
// Glyphs set down a column 1.5 em apart (tracking 500): no spaces come in between them.
|
||||
let at = |y: f64| Placement { origin: Point::new(100.0, y), dir: Vec2::new(1.0, 0.0), size: 20.0, h_scale: 100.0 };
|
||||
let at = |y: f64| Placement { origin: Point::new(100.0, y), dir: Vec2::new(1.0, 0.0), size: 20.0, h_scale: 100.0, slant: 0.0 };
|
||||
let mut line = TextLine::new(at(100.0), 1.0);
|
||||
for (i, c) in ["§", "§", "§"].iter().enumerate() {
|
||||
let y = 100.0 + 30.0 * i as f64;
|
||||
|
||||
@@ -274,3 +274,19 @@ fn an_embedded_fonts_own_cmap_names_its_glyphs() {
|
||||
}
|
||||
assert!(crate::import::font_chars(b"not a font").is_none());
|
||||
}
|
||||
|
||||
/// Type whose text matrix shears its glyphs (slanted as a whole, as Illustrator writes type
|
||||
/// turned and skewed) reopens as type that still leans: drawn, it covers what its outlines do.
|
||||
#[test]
|
||||
fn slanted_type_reopens_as_type_that_still_leans() {
|
||||
for (turn, lean) in [(0.0, 0.25), (-0.2, 0.25), (0.3, -0.15)] {
|
||||
let mut t = TextObject::point(Point::new(80.0, 150.0), "LEAN", style(60.0));
|
||||
t.xf *= Affine::rotate(turn) * Affine::skew(lean, 0.0);
|
||||
let d = doc(vec![t]);
|
||||
let reopened = import_as(&pdf(&d, false).bytes, TextAs::Text);
|
||||
assert_eq!(texts(&reopened), ["LEAN"], "{turn} {lean}: reopened as type");
|
||||
let (a, b) = (ink(&import_as(&pdf(&reopened, true).bytes, TextAs::Outlines)), ink(&import_as(&pdf(&d, true).bytes, TextAs::Outlines)));
|
||||
let off = [a.x0 - b.x0, a.y0 - b.y0, a.x1 - b.x1, a.y1 - b.y1].iter().fold(0.0f64, |m, v| m.max(v.abs()));
|
||||
assert!(off < 0.5, "{turn} {lean}: {a:?} vs {b:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user