mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 14:33:34 +00:00
Type: the stop inside an upright ligature on a vertical path follows its drawn cell
On a path, vertical type turns an upright glyph about its pen, so its cell runs from 0.12 em before the pen to 0.88 em after it without the font's vertical metrics. The ligature caret, a click and the selection measured the cell from the pen instead. They now take the cell where the glyph's transform draws it, with its placement, baseline shift, Horizontal Scale and Skew.
This commit is contained in:
@@ -382,6 +382,21 @@ impl Ctx<'_> {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// On a path, an upright glyph turns about its pen: its cell runs along the path between
|
||||
// where the glyph's transform sets the cell's foot and its top, moved by the shaper's
|
||||
// placement and a baseline shift, and slanted by the path's effect.
|
||||
let (width, width_start) = match (upright && self.on_path, g.sy) {
|
||||
(true, sy) if sy != 0.0 => {
|
||||
// In font units, with the Proportional Metrics change to its length.
|
||||
let (cell, top) = upright_extent(g);
|
||||
let proportional = g.vpal.filter(|_| g.face.vertical_glyph(g.gid).is_some()).map_or(0.0, |a| a.advance) / sy;
|
||||
let tangent = Vec2::new(angle.cos(), angle.sin());
|
||||
let along = |y: f64| (m * Point::new(0.0, y) - origin).dot(tangent);
|
||||
let (start, end) = (along(cell + proportional - top), along(-top));
|
||||
if start.is_finite() && end > start { (end - start, start) } else { (width, width_start) }
|
||||
}
|
||||
_ => (width, width_start),
|
||||
};
|
||||
if let Some(art) = g.inline.as_ref().and_then(|ib| ib.art) {
|
||||
self.out.inlines.push(InlineGlyph { run: g.run, byte: g.byte, glyph: self.out.glyphs.len(), xf: m, bounds: m.transform_rect_bbox(art) });
|
||||
}
|
||||
|
||||
@@ -166,7 +166,9 @@ pub struct PositionedGlyph {
|
||||
/// in text space: the font's placement of the glyph (`GPOS` x placement), less a leading space
|
||||
/// taken off, plus the manual kerning of right-to-left text, which moves the glyph right so that
|
||||
/// the space it adds lies on the glyph's left. 0 for a glyph standing upright or set across the
|
||||
/// column in vertical type, and for an inline graphic.
|
||||
/// column in vertical type, and for an inline graphic. An upright glyph in vertical type on a
|
||||
/// path turns about its pen, and its cell starts one cell length before the cell's top: 0.12 em
|
||||
/// before the pen without the font's vertical metrics.
|
||||
pub width_start: f64,
|
||||
}
|
||||
|
||||
|
||||
@@ -459,6 +459,66 @@ fn an_upright_ligature_takes_its_caret_down_its_cell() {
|
||||
assert!((at(900) - 0.5).abs() < 1e-9, "{}", at(900));
|
||||
}
|
||||
|
||||
/// On a path, an upright ligature in vertical type turns about its pen, so its cell runs from
|
||||
/// 0.12 em before the pen to 0.88 em after it, with or without the font's vertical metrics. The
|
||||
/// inner stop lies at the middle of that cell, and the first character's selection ends there.
|
||||
#[test]
|
||||
fn an_upright_ligature_on_a_path_takes_its_stop_in_its_turned_cell() {
|
||||
for (font, family) in [(ligature_caret_font(None), LIGATURE_CARET_FAMILY), (vertical_ligature_caret_font(900), VERTICAL_LIGATURE_CARET_FAMILY)] {
|
||||
let db = db_with(font);
|
||||
let mut line = BezPath::new();
|
||||
line.move_to((0.0, 0.0));
|
||||
line.line_to((1000.0, 0.0));
|
||||
let mut t = point("fi", family);
|
||||
t.vertical = true;
|
||||
t.runs[0].style.standard_vertical_roman = true;
|
||||
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
|
||||
let l = layout(&db, &t);
|
||||
assert!(l.on_path);
|
||||
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
assert_eq!(g.carets, vec![(1, 0.5)], "{family}");
|
||||
assert!((g.width - 100.0).abs() < 1e-9 && (g.width_start + 12.0).abs() < 1e-9, "{family}: {} {}", g.width, g.width_start);
|
||||
let x = |b| caret_position(&l, b).0.x - g.origin.x;
|
||||
assert!((x(1) - 38.0).abs() < 1e-6, "{family}: {}", x(1));
|
||||
let q = selection_quads(&l, 0, 1);
|
||||
assert_eq!(q.len(), 1, "{q:?}");
|
||||
assert!(((q[0][1] - q[0][0]).hypot() - 38.0).abs() < 1e-6, "{family}: {q:?}");
|
||||
// A baseline shift moves the turned glyph along the path, and its stop with it.
|
||||
let ink = g.outline.bounding_box().x0;
|
||||
t.runs[0].style.baseline_shift = 10.0;
|
||||
let l = layout(&db, &t);
|
||||
let Some(s) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
let moved = s.outline.bounding_box().x0 - ink;
|
||||
assert!(moved.abs() > 1.0, "{family}: the shift moves the glyph along the path: {moved}");
|
||||
let stop = caret_position(&l, 1).0.x - s.origin.x;
|
||||
assert!((stop - 38.0 - moved).abs() < 1e-6, "{family}: {stop} against {}", 38.0 + moved);
|
||||
// Horizontal Scale widens the glyph across the path; its stop stays inside the glyph.
|
||||
t.runs[0].style.baseline_shift = 0.0;
|
||||
t.runs[0].style.h_scale = 200.0;
|
||||
let l = layout(&db, &t);
|
||||
let Some(s) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
let bounds = s.outline.bounding_box();
|
||||
let stop = caret_position(&l, 1).0.x - s.origin.x;
|
||||
assert!(bounds.x0 < stop && stop < bounds.x1 && stop > 0.0, "{family}: {stop} outside {bounds:?}");
|
||||
t.runs[0].style.h_scale = 100.0;
|
||||
// Skew on a slanted path: the stop stays inside the drawn glyph along the path.
|
||||
let mut slant = BezPath::new();
|
||||
slant.move_to((0.0, 0.0));
|
||||
slant.line_to((1000.0, 1000.0));
|
||||
t.runs[0].style.baseline_shift = 0.0;
|
||||
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&slant), start: 0.0, end: None };
|
||||
t.path_effect = vectorcraft_doc::PathEffect::Skew;
|
||||
let l = layout(&db, &t);
|
||||
let Some(s) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
|
||||
let tangent = Vec2::new(s.angle.cos(), s.angle.sin());
|
||||
let ink = s.outline.elements().iter().filter_map(|el| el.end_point()).map(|p| (p - s.origin).dot(tangent));
|
||||
let (lo, hi) = ink.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), d| (lo.min(d), hi.max(d)));
|
||||
let (a, b) = caret_position(&l, 1);
|
||||
let stop = (a.midpoint(b) - s.origin).dot(tangent);
|
||||
assert!(lo < stop && stop < hi, "{family}: {stop} outside {lo}..{hi}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracking that makes an upright ligature's advance negative leaves it upright: it keeps its
|
||||
/// caret down its cell, and its cell starts where it does without tracking, with or without the
|
||||
/// font's vertical metrics.
|
||||
|
||||
Reference in New Issue
Block a user