Merge pull request #742 from jmverges/pdf-import-slanted-type
PDF import: slanted type keeps its slant
This commit is contained in:
commit
257c8ec435
|
|
@ -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:?}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue