Files
vectorcraft/crates/text/src/tests/ligature_carets.rs
T
dotCooCoo 3487e1ad80 Type: vertical-only settings keep horizontal ligatures whole
Shaping split a cluster wherever two characters' styles differed, so
setting Standard Vertical Roman Alignment or a tate-chu-yoko offset on the
f of "fi" in horizontal type broke the ligature. Horizontal type does not
read those settings, so they no longer split a cluster there. Vertical
type still shapes such characters apart.
2026-11-10 15:17:59 -08:00

849 lines
45 KiB
Rust

//! Caret stops inside ligatures: the caret, a click, arrow keys and the selection reach each
//! character a ligature glyph stands for, at the font's ligature carets (`GDEF`) or, without them,
//! at the glyph's advance divided equally.
use kurbo::{BezPath, Point, Vec2};
use vectorcraft_doc::{CharStyle, TextKind, TextObject};
use vectorcraft_geom::PathData;
use crate::test_fonts::{
LIGATURE_CARET_FAMILY, LIGATURE_CARET_GLYPH, VARIABLE_LIGATURE_CARET_FAMILY, VERTICAL_LIGATURE_CARET_FAMILY, lam_alef_family, lam_alef_font,
ligature_caret_font, placed_ligature_caret_font, proportional_vertical_ligature_caret_font, source_sans_gid, variable_ligature_caret_font,
vertical_ligature_caret_font,
};
use crate::*;
/// A database of the bundled fonts and `font`.
fn db_with(font: Option<Vec<u8>>) -> FontDb {
let db = FontDb::with_font_dirs(vec![]);
assert_eq!(db.add_font(font.expect("the test font builds")), 1);
db
}
/// Put a contour-point caret on the existing test ligature without changing its outline.
fn contour_caret_font(point_index: u16) -> Vec<u8> {
use write_fonts::FontBuilder;
use write_fonts::tables::gdef::{CaretValue, Gdef, LigCaretList, LigGlyph};
use write_fonts::tables::layout::CoverageFormat1 as WriteCoverage;
use write_fonts::types::{GlyphId16, Tag};
let base = ligature_caret_font(None).expect("test font builds");
let font = skrifa::FontRef::new(&base).expect("test font parses");
let gid = source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph");
let mut builder = FontBuilder::new();
let list = LigCaretList::new(WriteCoverage::new(vec![GlyphId16::new(gid)]).into(), vec![LigGlyph::new(vec![CaretValue::format_2(point_index)])]);
builder.add_table(&Gdef::new(None, None, Some(list), None)).expect("GDEF builds");
for record in font.table_directory.table_records() {
let tag = Tag::new(&record.tag().to_be_bytes());
if !builder.contains(tag) {
builder.add_raw(tag, font.table_data(record.tag()).expect("base table reads").as_bytes());
}
}
builder.build()
}
#[test]
fn a_contour_point_caret_uses_its_unhinted_x_and_y() {
use skrifa::raw::TableProvider;
use skrifa::raw::tables::glyf::Glyph;
let base = ligature_caret_font(None).expect("test font builds");
let font = skrifa::FontRef::new(&base).expect("test font parses");
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let outline = font.loca(None).expect("loca reads").get(skrifa::GlyphId::new(gid), &font.glyf().expect("glyf reads")).expect("glyph reads");
let Glyph::Simple(simple) = outline.into_glyph().expect("glyph has an outline") else { panic!("W is simple") };
let (point_index, point) =
simple.points().enumerate().find(|(_, p)| p.x > 0 && p.x < 500 && p.y > -500 && p.y < 1000).expect("W has an interior contour point");
let db = db_with(Some(contour_caret_font(u16::try_from(point_index).unwrap())));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").unwrap();
assert_eq!(face.ligature_carets(gid, 1), Some(vec![f64::from(point.x)]));
assert_eq!(face.vertical_ligature_carets(gid, 1, 1000.0, 1500.0), Some(vec![1000.0 - f64::from(point.y)]));
let bad = db_with(Some(contour_caret_font(u16::MAX)));
assert_eq!(bad.face(LIGATURE_CARET_FAMILY, "Regular").unwrap().ligature_carets(gid, 1), None);
}
/// `count` glyph ids of [`contour_caret_font`] other than the ligature's.
fn spare_gids(count: usize) -> Vec<u16> {
let ligature = source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph");
(1..).filter(|g| *g != ligature).take(count).collect()
}
/// [`contour_caret_font`], whose caret is the ligature's point 0, with its `glyf` and `loca`
/// replaced: each `(gid, data)` in `glyphs` gives that glyph's `glyf` data, and every other glyph
/// is empty.
fn glyf_caret_font(glyphs: &[(u16, Vec<u8>)]) -> Vec<u8> {
use skrifa::raw::TableProvider;
use write_fonts::FontBuilder;
use write_fonts::types::Tag;
let base = contour_caret_font(0);
let font = skrifa::FontRef::new(&base).expect("test font parses");
let num_glyphs = font.maxp().expect("maxp reads").num_glyphs();
let short_loca = font.head().expect("head reads").index_to_loc_format() == 0;
let mut glyf = Vec::new();
let mut offsets = vec![0usize];
for gid in 0..num_glyphs {
if let Some((_, data)) = glyphs.iter().find(|(g, _)| *g == gid) {
glyf.extend(data);
// Keep every offset even for the short `loca` format.
if glyf.len() % 2 == 1 {
glyf.push(0);
}
}
offsets.push(glyf.len());
}
let loca: Vec<u8> = if short_loca {
offsets.iter().flat_map(|o| u16::try_from(o / 2).expect("short offset").to_be_bytes()).collect()
} else {
offsets.iter().flat_map(|o| u32::try_from(*o).expect("long offset").to_be_bytes()).collect()
};
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"glyf"), glyf);
builder.add_raw(Tag::new(b"loca"), loca);
for record in font.table_directory.table_records() {
let tag = Tag::new(&record.tag().to_be_bytes());
if !builder.contains(tag) {
builder.add_raw(tag, font.table_data(record.tag()).expect("base table reads").as_bytes());
}
}
builder.build()
}
/// [`glyf_caret_font`] whose ligature is a composite of eight copies of a glyph that is itself
/// eight copies of the next, 15 levels down to a glyph with no contours.
fn composite_fan_caret_font() -> Vec<u8> {
const FAN: usize = 8;
let ligature = source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph");
let levels = spare_gids(15);
let composite = |child: u16| {
let mut data = vec![0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0];
for i in 0..FAN {
// ARGS_ARE_XY_VALUES, plus MORE_COMPONENTS on all but the last; byte offsets of 0.
let flags: u16 = if i + 1 < FAN { 0x0022 } else { 0x0002 };
data.extend(flags.to_be_bytes());
data.extend(child.to_be_bytes());
data.extend([0, 0]);
}
data
};
let mut glyphs = vec![(ligature, composite(levels[0]))];
for (level, gid) in levels.iter().enumerate() {
match levels.get(level + 1) {
Some(child) => glyphs.push((*gid, composite(*child))),
// No contours, a zero bounding box and no instructions.
None => glyphs.push((*gid, vec![0; 12])),
}
}
glyf_caret_font(&glyphs)
}
/// A contour-point caret on a composite's point uses the component offset the outline loader
/// uses. The ligature is one component, a glyph with a single point at (50, 0), turned a quarter
/// turn and scaled by 1.5 with a two-by-two matrix and placed at (100, 0). With
/// SCALED_COMPONENT_OFFSET the offset's x scales by the length of the x basis, 1.5, so the point
/// lands at x = 150; with UNSCALED_COMPONENT_OFFSET as well the offset stays (100, 0).
#[test]
fn a_contour_point_caret_on_a_composite_takes_its_scaled_offset() {
let gid = source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph");
let [leaf] = spare_gids(1)[..] else { panic!("one spare glyph") };
// One on-curve point at (50, 0) with word coordinates, and no instructions.
let mut point = vec![0, 1, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0x01];
point.extend(50i16.to_be_bytes());
point.extend(0i16.to_be_bytes());
let composite = |offset_flags: u16| {
// ARG_1_AND_2_ARE_WORDS, ARGS_ARE_XY_VALUES and WE_HAVE_A_TWO_BY_TWO.
let flags = 0x0083 | offset_flags;
let mut data = vec![0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0];
data.extend(flags.to_be_bytes());
data.extend(leaf.to_be_bytes());
data.extend(100i16.to_be_bytes());
data.extend(0i16.to_be_bytes());
// xx = 0, yx = 1.5, xy = -1.5, yy = 0 in F2Dot14.
for f2dot14 in [0x0000u16, 0x6000, 0xa000, 0x0000] {
data.extend(f2dot14.to_be_bytes());
}
data
};
let caret = |offset_flags: u16| {
let db = db_with(Some(glyf_caret_font(&[(gid, composite(offset_flags)), (leaf, point.clone())])));
db.face(LIGATURE_CARET_FAMILY, "Regular").unwrap().ligature_carets(u32::from(gid), 1)
};
// SCALED_COMPONENT_OFFSET, then with UNSCALED_COMPONENT_OFFSET too.
assert_eq!(caret(0x0800), Some(vec![150.0]));
assert_eq!(caret(0x1800), Some(vec![100.0]));
}
/// A contour-point caret on a composite that expands to billions of glyphs with no points gives
/// no caret, and quickly, rather than reading every one of them.
#[test]
fn a_contour_point_caret_on_a_huge_empty_composite_gives_up() {
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let (send, receive) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let db = db_with(Some(composite_fan_caret_font()));
let carets = db.face(LIGATURE_CARET_FAMILY, "Regular").map(|face| face.ligature_carets(gid, 1));
let _ = send.send(carets);
});
let carets = receive.recv_timeout(std::time::Duration::from_secs(30)).expect("the caret lookup finishes");
assert_eq!(carets, Some(None));
}
fn point(text: &str, family: &str) -> TextObject {
TextObject::point(Point::ZERO, text, CharStyle { font_family: family.into(), size: 100.0, ..CharStyle::default() })
}
/// The caret's x for byte `b`.
fn caret_x(l: &TextLayout, b: usize) -> f64 {
caret_position(l, b).0.x
}
/// The x of a click at `at` (0 to 1) along glyph `g`'s advance from its left, on its baseline.
fn along(g: &PositionedGlyph, at: f64) -> Point {
Point::new(g.origin.x + g.advance * at, g.origin.y)
}
/// A font whose caret list for a ligature holds a number of carets other than the one asked for
/// gives none.
#[test]
fn a_caret_list_of_another_length_gives_no_carets() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
assert_eq!(face.ligature_carets(gid, 1), Some(vec![150.0]));
assert_eq!(face.ligature_carets(gid, 2), None);
assert_eq!(face.ligature_carets(gid, 0), None);
}
/// A variable font's ligature caret with a variation index moves with the face's axis settings: by
/// none at the default weight, by the whole delta at the weight its region peaks at.
#[test]
fn a_variable_ligature_caret_takes_its_delta() {
let db = FontDb::with_font_dirs(vec![]);
assert_eq!(db.add_font(variable_ligature_caret_font(150, 100).expect("the test font builds")), 2, "Regular and Bold");
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let regular = db.face(VARIABLE_LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let bold = db.face(VARIABLE_LIGATURE_CARET_FAMILY, "Bold").expect("the test font is added");
assert_eq!(regular.ligature_carets(gid, 1), Some(vec![150.0]));
assert_eq!(bold.ligature_carets(gid, 1), Some(vec![250.0]));
}
/// The font's ligature caret is where the caret, a click, arrow keys and the selection stop inside
/// the ligature, not the middle of the glyph.
#[test]
fn the_font_ligature_caret_places_the_inner_stop() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let l = layout(&db, &point("fi", LIGATURE_CARET_FAMILY));
assert_eq!(l.glyphs.len(), 1, "f + i is one glyph: {:?}", l.glyphs);
let g = &l.glyphs[0];
assert_eq!((g.gid, g.len), (gid, 2));
let at = 150.0 / face.advance(gid);
assert!(at < 0.4, "the caret is well left of the middle: {at}");
assert_eq!(g.carets.len(), 1);
assert_eq!(g.carets[0].0, 1);
assert!((g.carets[0].1 - at).abs() < 1e-9, "{:?} against {at}", g.carets);
assert!((caret_x(&l, 1) - g.advance * at).abs() < 1e-6);
// A click left or right of the caret goes to it rather than to either end.
assert_eq!(hit_byte(&l, along(g, at * 0.4)), 0);
assert_eq!(hit_byte(&l, along(g, at * 1.4)), 1);
assert_eq!(hit_byte(&l, along(g, 0.9)), 2);
// Arrow keys stop at it both ways, and the f alone is selected up to it.
assert_eq!(caret_horizontal(&l, 0, true), 1);
assert_eq!(caret_horizontal(&l, 1, true), 2);
assert_eq!(caret_horizontal(&l, 2, false), 1);
let q = selection_quads(&l, 0, 1);
assert_eq!(q.len(), 1);
assert!((q[0][1].x - q[0][0].x - g.advance * at).abs() < 1e-6, "{q:?}");
}
/// The font's placement of a ligature (`GPOS` x placement) moves its caret with the glyph.
#[test]
fn a_placed_ligature_keeps_its_caret_on_the_glyph() {
let at = |font: Option<Vec<u8>>| {
let db = db_with(font);
let l = layout(&db, &point("fi", LIGATURE_CARET_FAMILY));
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph").clone();
(caret_x(&l, 1) - g.origin.x, g.outline.bounding_box().x0 - g.origin.x, g.width_start, hit_byte(&l, Point::new(caret_x(&l, 1) + 1.0, 0.0)))
};
let (caret, ink, start, _) = at(ligature_caret_font(Some(150)));
let (placed_caret, placed_ink, placed_start, hit) = at(placed_ligature_caret_font(150, 100));
// 100 units at 100 pt in a 1000-unit em.
assert!(start.abs() < 1e-9 && (placed_start - 10.0).abs() < 1e-9, "{start} {placed_start}");
assert!((placed_ink - ink - 10.0).abs() < 1e-6, "the glyph moves right: {placed_ink} against {ink}");
assert!((placed_caret - caret - 10.0).abs() < 1e-6, "the caret moves with it: {placed_caret} against {caret}");
assert_eq!(hit, 1);
}
/// A ligature whose font lists no carets for it divides its advance equally among its characters.
#[test]
fn a_ligature_without_font_carets_is_divided_equally() {
let db = db_with(ligature_caret_font(None));
let l = layout(&db, &point("fi", LIGATURE_CARET_FAMILY));
assert_eq!(l.glyphs.len(), 1);
let g = &l.glyphs[0];
assert_eq!(g.carets, vec![(1, 0.5)]);
assert!((caret_x(&l, 1) - g.advance * 0.5).abs() < 1e-6);
assert_eq!(hit_byte(&l, along(g, 0.4)), 1);
assert_eq!(hit_byte(&l, along(g, 0.2)), 0);
// A caret value outside the glyph is not used either.
let db = db_with(ligature_caret_font(Some(-20)));
let l = layout(&db, &point("fi", LIGATURE_CARET_FAMILY));
assert_eq!(l.glyphs[0].carets, vec![(1, 0.5)]);
}
/// Tracking adds space after a ligature's own width: the font's caret stays where the font puts it
/// in the glyph, and the caret, a click and the selection use that place, left to right and right
/// to left.
#[test]
fn tracking_does_not_move_a_ligature_caret() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let mut t = point("fi", LIGATURE_CARET_FAMILY);
t.runs[0].style.tracking = 50.0;
let l = layout(&db, &t);
assert_eq!(l.glyphs.len(), 1, "f + i stays one glyph at this tracking: {:?}", l.glyphs);
let g = &l.glyphs[0];
let k = 100.0 / face.units_per_em();
let width = face.advance(g.gid) * k;
assert!((g.width - width).abs() < 1e-9, "{} against {width}", g.width);
assert!((g.advance - width - 5.0).abs() < 1e-9, "the advance takes in 5 pt of tracking: {}", g.advance);
let x = g.origin.x + 150.0 * k;
assert!((caret_x(&l, 1) - x).abs() < 1e-6, "{} against {x}", caret_x(&l, 1));
assert_eq!(hit_byte(&l, Point::new(g.origin.x + 150.0 * k * 0.45, -10.0)), 0);
assert_eq!(hit_byte(&l, Point::new(x + 0.3, -10.0)), 1);
let q = selection_quads(&l, 0, 1);
assert_eq!(q.len(), 1);
assert!((q[0][1].x - x).abs() < 1e-6, "{q:?}");
// Right to left the tracking lies to the glyph's right, before its first character: the stop
// between lam and alef stays in the middle of the glyph's own width.
let db = db_with(lam_alef_font(b"rlig"));
let mut t = point("\u{0644}\u{0627}", lam_alef_family(b"rlig"));
t.runs[0].style.tracking = 50.0;
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 4).expect("lam + alef is one glyph");
assert!(g.rtl && g.advance > g.width + 4.0, "{} {}", g.advance, g.width);
let mid = g.origin.x + g.width * 0.5;
assert!((caret_x(&l, 2) - mid).abs() < 1e-6, "{} against {mid}", caret_x(&l, 2));
let q = selection_quads(&l, 2, 4);
assert_eq!(q.len(), 1, "{q:?}");
let (x0, x1) = (q[0][0].x.min(q[0][1].x), q[0][0].x.max(q[0][1].x));
assert!((x0 - g.origin.x).abs() < 1e-6 && (x1 - mid).abs() < 1e-6, "{q:?}");
}
/// Negative kerning can end a ligature's advance before its font caret: the caret still stands at
/// the font's place, a click at the advance's end goes to the end of the ligature, and the last
/// character's selection runs between the two.
#[test]
fn negative_kerning_keeps_a_ligature_caret_past_the_advance() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let k = 100.0 / face.units_per_em();
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let width = face.advance(gid) * k;
// Kerning (thousandths of an em at 100 pt, a tenth of a point each) that leaves an advance of
// half the distance to the font caret.
let kern = (150.0 * k * 0.5 - width) * 10.0;
let mut t = point("fi", LIGATURE_CARET_FAMILY);
t.runs[0].style.kerning = Some(kern);
let l = layout(&db, &t);
assert_eq!(l.glyphs.len(), 1, "f + i stays one glyph with this kerning: {:?}", l.glyphs);
let g = &l.glyphs[0];
assert!((g.width - width).abs() < 1e-9, "{} against {width}", g.width);
assert!((g.advance - 150.0 * k * 0.5).abs() < 1e-6, "the advance ends before the caret: {}", g.advance);
let x = g.origin.x + 150.0 * k;
let end = g.origin.x + g.advance;
assert!((caret_x(&l, 1) - x).abs() < 1e-6, "{} against {x}", caret_x(&l, 1));
assert!((caret_x(&l, 2) - end).abs() < 1e-6, "{} against {end}", caret_x(&l, 2));
assert_eq!(hit_byte(&l, Point::new(end, -10.0)), 2);
assert_eq!(hit_byte(&l, Point::new(x + 0.3, -10.0)), 1);
let q = selection_quads(&l, 1, 2);
assert_eq!(q.len(), 1, "{q:?}");
let (x0, x1) = (q[0][0].x.min(q[0][1].x), q[0][0].x.max(q[0][1].x));
assert!((x0 - end).abs() < 1e-6 && (x1 - x).abs() < 1e-6, "{q:?}");
// On a path, a click at the advance's end goes to the end of the ligature as well.
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph");
let d = Vec2::new(g.angle.cos(), g.angle.sin());
assert_eq!(hit_byte(&l, g.origin + d * g.advance), 2);
assert_eq!(hit_byte(&l, g.origin + d * (g.width * 150.0 / face.advance(gid) + 0.3)), 1);
}
/// On a path, a ligature whose negative kerning ends its advance before its font caret is followed
/// by an i that covers the caret: a click at the caret goes to the caret, not to the i.
#[test]
fn a_click_at_a_ligature_caret_past_the_advance_on_a_path_reaches_it() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let k = 100.0 / face.units_per_em();
let gid = u32::from(source_sans_gid(LIGATURE_CARET_GLYPH).expect("Source Sans 3 has the glyph"));
let width = face.advance(gid) * k;
let kern = (150.0 * k * 0.5 - width) * 10.0;
let mut t = point("fii", LIGATURE_CARET_FAMILY);
crate::edit::style_range(&mut t.runs, 0, 2, |st| st.kerning = Some(kern));
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph");
let i = l.glyphs.iter().find(|h| h.byte == 2).expect("the second i");
let d = Vec2::new(g.angle.cos(), g.angle.sin());
let caret = g.origin + d * (g.width * 150.0 / face.advance(gid));
let mid = |h: &PositionedGlyph| h.origin + d * (h.advance * 0.5);
assert!((caret - mid(i)).hypot() < (caret - mid(g)).hypot(), "the i's middle is nearer the caret than the ligature's");
assert_eq!(hit_byte(&l, caret + d * 0.3), 1);
}
/// Tracking of -1000 (one em to the left) leaves Source Serif 4's fi ligature, with standard
/// ligatures turned on explicitly, an advance of zero or less: the caret, a click and the selection
/// still reach the i at its place in the glyph's own width, across and on a path.
#[test]
fn a_ligature_without_advance_keeps_its_inner_stop() {
let db = FontDb::with_font_dirs(vec![]);
let mut t = point("fi", "Source Serif 4");
t.runs[0].style.tracking = -1000.0;
t.runs[0].style.features = vec!["liga".into()];
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph");
assert!(g.advance <= 0.0 && g.width > 0.0, "{} {}", g.advance, g.width);
let &[(1, f)] = g.carets.as_slice() else { panic!("one inner stop: {:?}", g.carets) };
let x = g.origin.x + g.width * f;
let end = g.origin.x + g.advance;
assert!((caret_x(&l, 1) - x).abs() < 1e-6, "{} against {x}", caret_x(&l, 1));
assert!((caret_x(&l, 2) - end).abs() < 1e-6, "{} against {end}", caret_x(&l, 2));
assert_eq!(hit_byte(&l, Point::new(x + 0.3, -10.0)), 1);
assert_eq!(hit_byte(&l, Point::new(g.origin.x + 0.3, -10.0)), 0);
let q = selection_quads(&l, 0, 1);
assert_eq!(q.len(), 1, "{q:?}");
let (x0, x1) = (q[0][0].x.min(q[0][1].x), q[0][0].x.max(q[0][1].x));
assert!((x0 - g.origin.x).abs() < 1e-6 && (x1 - x).abs() < 1e-6, "{q:?} against {} {x}", g.origin.x);
assert_eq!(caret_horizontal(&l, 0, true), 1);
// On a path, a click past the inner stop goes to it, and the f alone is selected up to it.
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph");
assert!(g.advance <= 0.0, "{}", g.advance);
let d = Vec2::new(g.angle.cos(), g.angle.sin());
assert_eq!(hit_byte(&l, g.origin + d * (g.width * f + 0.3)), 1);
assert_eq!(hit_byte(&l, g.origin + d * 0.3), 0);
let q = selection_quads(&l, 0, 1);
assert_eq!(q.len(), 1, "{q:?}");
assert!(((q[0][1] - q[0][0]).hypot() - g.width * f).abs() < 1e-6, "{q:?}");
}
/// Bundled Source Serif 4's fi ligature has a caret stop that a click and the arrow keys reach.
#[test]
fn source_serif_fi_and_ffi_have_inner_stops() {
let db = FontDb::with_font_dirs(vec![]);
for (s, stops) in [("fi", vec![1]), ("ffi", vec![1, 2])] {
let l = layout(&db, &point(s, "Source Serif 4"));
let lone: Vec<&PositionedGlyph> = l.glyphs.iter().filter(|g| g.len == s.len()).collect();
if lone.is_empty() {
// The font has no ligature for these letters (ffi in some versions).
assert_ne!(s, "fi", "{:?}", l.glyphs);
continue;
}
let g = lone[0];
assert_eq!(g.carets.iter().map(|c| c.0).collect::<Vec<_>>(), stops, "{s}");
for &(b, f) in &g.carets {
let x = g.origin.x + g.advance * f;
assert_eq!(hit_byte(&l, Point::new(x + 0.01, -10.0)), b, "{s} byte {b}");
assert!((caret_x(&l, b) - x).abs() < 1e-6);
}
let mut b = 0;
let mut seen = vec![];
while b < s.len() {
b = caret_horizontal(&l, b, true);
seen.push(b);
}
assert_eq!(seen[..stops.len()], stops[..], "{s}");
}
}
/// A right-to-left ligature (lam + alef, two bytes each) has its stop between the two characters,
/// never inside a character, and its first character is on its right.
#[test]
fn a_right_to_left_ligature_stops_between_its_characters() {
let db = db_with(lam_alef_font(b"rlig"));
let l = layout(&db, &point("\u{0644}\u{0627}", lam_alef_family(b"rlig")));
let g = l.glyphs.iter().find(|g| g.len == 4).expect("lam + alef is one glyph");
assert!(g.rtl);
assert_eq!(g.carets, vec![(2, 0.5)]);
// Right of the middle is the lam, left of it the alef.
assert_eq!(hit_byte(&l, along(g, 0.9)), 0);
assert_eq!(hit_byte(&l, along(g, 0.6)), 2);
assert_eq!(hit_byte(&l, along(g, 0.1)), 4);
assert!((caret_x(&l, 2) - (g.origin.x + g.advance * 0.5)).abs() < 1e-6);
assert!(caret_x(&l, 0) > caret_x(&l, 2) && caret_x(&l, 2) > caret_x(&l, 4));
// The lam alone is selected on the right half.
let q = selection_quads(&l, 0, 2);
assert_eq!(q.len(), 1, "{q:?}");
let (x0, x1) = (q[0][0].x.min(q[0][1].x), q[0][0].x.max(q[0][1].x));
assert!((x0 - (g.origin.x + g.advance * 0.5)).abs() < 1e-6 && (x1 - (g.origin.x + g.advance)).abs() < 1e-6, "{q:?}");
// Arrow keys (visual stops) take the inner stop: right to left, left goes forward.
assert_eq!(caret_horizontal_cluster(&l, "\u{0644}\u{0627}", 0, false), 2);
assert_eq!(caret_horizontal_cluster(&l, "\u{0644}\u{0627}", 2, false), 4);
}
/// Right to left, manual kerning moves a ligature right and puts its space on the left: the inner
/// stop moves with the glyph, and the click, the caret and the selection of each character follow.
#[test]
fn manual_kerning_moves_a_right_to_left_ligature_stop_with_the_glyph() {
let db = db_with(lam_alef_font(b"rlig"));
let at = |kern: f64| {
let mut t = point("\u{0644}\u{0627}", lam_alef_family(b"rlig"));
t.runs[0].style.kerning = Some(kern);
let l = layout(&db, &t);
let g = l.glyphs.iter().find(|g| g.len == 4).expect("lam + alef is one glyph").clone();
assert!(g.rtl);
(l, g)
};
let (plain, p) = at(0.0);
let (kerned, k) = at(100.0);
// 100/1000 em at 100 pt.
assert!((k.width_start - 10.0).abs() < 1e-9 && (k.advance - p.advance - 10.0).abs() < 1e-9, "{} {} {}", k.width_start, k.advance, p.advance);
let ink = |g: &PositionedGlyph| g.outline.bounding_box().x0;
assert!((ink(&k) - k.origin.x - (ink(&p) - p.origin.x) - 10.0).abs() < 1e-6, "the glyph moves right");
let mid = k.origin.x + 10.0 + k.width * 0.5;
assert!((caret_x(&kerned, 2) - ink(&k) - (caret_x(&plain, 2) - ink(&p))).abs() < 1e-6, "the stop moves with the glyph");
assert!((caret_x(&kerned, 2) - mid).abs() < 1e-6, "{} against {mid}", caret_x(&kerned, 2));
assert_eq!(hit_byte(&kerned, Point::new(mid + 1.0, k.origin.y)), 2);
assert_eq!(hit_byte(&kerned, Point::new(mid + 4.0, k.origin.y)), 2);
assert_eq!(hit_byte(&kerned, Point::new(k.origin.x + k.advance - 1.0, k.origin.y)), 0);
let q = selection_quads(&kerned, 0, 2);
assert_eq!(q.len(), 1, "{q:?}");
let x0 = q[0][0].x.min(q[0][1].x);
assert!((x0 - mid).abs() < 1e-6, "the lam alone is selected right of the stop: {q:?}");
}
/// A letter with a combining mark is one character: a glyph for it has no inner stop.
#[test]
fn a_letter_and_its_mark_has_no_inner_stop() {
let db = FontDb::with_font_dirs(vec![]);
let l = layout(&db, &point("e\u{0301}", "Source Sans 3"));
assert!(l.glyphs.iter().all(|g| g.carets.is_empty()), "{:?}", l.glyphs);
}
/// On a path, a click on either half of the ligature goes to its nearer stop, and the selection of
/// one character covers its part of the glyph.
#[test]
fn a_ligature_on_a_path_has_its_inner_stop() {
let db = db_with(ligature_caret_font(None));
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
let mut t = point("fi", LIGATURE_CARET_FAMILY);
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
let l = layout(&db, &t);
assert!(l.on_path);
let g = l.glyphs.iter().find(|g| g.len == 2).expect("f + i is one glyph");
assert_eq!(g.carets, vec![(1, 0.5)]);
let at = |f: f64| g.origin + Vec2::new(g.angle.cos(), g.angle.sin()) * (g.advance * f);
assert_eq!(hit_byte(&l, at(0.1)), 0);
assert_eq!(hit_byte(&l, at(0.4)), 1);
assert_eq!(hit_byte(&l, at(0.95)), 2);
let q = selection_quads(&l, 1, 2);
assert_eq!(q.len(), 1);
assert!(((q[0][1] - q[0][0]).hypot() - g.advance * 0.5).abs() < 1e-6, "{q:?}");
}
/// In vertical type, the inner stop of a turned Latin ligature lies down the column between its
/// ends.
#[test]
fn a_ligature_in_vertical_type_has_its_inner_stop() {
let db = db_with(ligature_caret_font(None));
let mut t = point("fi", LIGATURE_CARET_FAMILY);
t.vertical = true;
let l = layout(&db, &t);
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)]);
let y = |b| caret_position(&l, b).0.y;
assert!(y(0) < y(1) && y(1) < y(2), "{} {} {}", y(0), y(1), y(2));
let mid = caret_position(&l, 1);
assert_eq!(hit_byte(&l, mid.0.midpoint(mid.1) + Vec2::new(0.0, 0.01)), 1);
}
/// In vertical type, tracking adds space after a turned ligature's own width as it does across:
/// the font's caret stays the same distance down the column from the ligature's start, and the
/// last character's selection runs from it to the end of the advance.
#[test]
fn tracking_does_not_move_a_ligature_caret_in_vertical_type() {
let db = db_with(ligature_caret_font(Some(150)));
let face = db.face(LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let k = 100.0 / face.units_per_em();
let at = |tracking: f64| {
let mut t = point("fi", LIGATURE_CARET_FAMILY);
t.vertical = true;
t.runs[0].style.tracking = tracking;
let l = layout(&db, &t);
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
let width = face.advance(g.gid) * k;
assert!((g.width - width).abs() < 1e-9, "{} against {width}", g.width);
let y = |b| caret_position(&l, b).0.y;
let q = selection_quads(&l, 1, 2);
assert_eq!(q.len(), 1, "{q:?}");
let ys = q[0].map(|p| p.y);
let (y0, y1) = (ys.iter().copied().fold(f64::INFINITY, f64::min), ys.iter().copied().fold(f64::NEG_INFINITY, f64::max));
assert!((y0 - y(1)).abs() < 1e-6 && (y1 - y(2)).abs() < 1e-6, "{q:?} {} {}", y(1), y(2));
(y(1) - y(0), y(2) - y(0), g.advance)
};
let (plain, plain_end, _) = at(0.0);
let (tracked, tracked_end, advance) = at(50.0);
assert!((plain - 150.0 * k).abs() < 1e-6, "{plain} against {}", 150.0 * k);
assert!((tracked - plain).abs() < 1e-6, "{tracked} against {plain}");
assert!((tracked_end - advance).abs() < 1e-6 && (tracked_end - plain_end - 5.0).abs() < 1e-6, "{tracked_end} {plain_end} {advance}");
}
/// A ligature standing upright in vertical type (Standard Vertical Roman Alignment) takes its font
/// caret as a y coordinate: the stop lies that far below the top of its cell, which runs from its
/// vertical origin (0.88 em) one em down the column. A caret above the cell is not used.
#[test]
fn an_upright_ligature_takes_its_caret_down_its_cell() {
let at = |caret: i16| {
let db = db_with(vertical_ligature_caret_font(caret));
let mut t = point("fi", VERTICAL_LIGATURE_CARET_FAMILY);
t.vertical = true;
t.runs[0].style.standard_vertical_roman = true;
let l = layout(&db, &t);
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
let face = db.face(VERTICAL_LIGATURE_CARET_FAMILY, "Regular").expect("the test font is added");
let cell = 1000.0 * 100.0 / face.units_per_em();
assert!((g.width - cell).abs() < 1e-9, "the glyph stands upright, one em down the column: {} against {cell}", g.width);
let &[(1, f)] = g.carets.as_slice() else { panic!("one inner stop: {:?}", g.carets) };
let y = |b| caret_position(&l, b).0.y;
assert!(((y(1) - y(0)) - g.width * f).abs() < 1e-6, "{} {} against {f}", y(0), y(1));
f
};
// 0.88 em at the top: y = 280 is 600 units down, y = -50 is 930 units down.
assert!((at(280) - 0.6).abs() < 1e-9, "{}", at(280));
assert!((at(-50) - 0.93).abs() < 1e-9, "{}", at(-50));
assert!((at(900) - 0.5).abs() < 1e-9, "{}", at(900));
}
/// Horizontal type does not read Standard Vertical Roman Alignment or the tate-chu-yoko offsets, so
/// setting one of them on the f of "fi" keeps the ligature. Vertical type reads them per glyph, so
/// there the f is shaped apart from the i.
#[test]
fn vertical_only_settings_keep_a_horizontal_ligature_whole() {
let db = db_with(vertical_ligature_caret_font(280));
let marks: [fn(&mut CharStyle); 3] = [|s| s.standard_vertical_roman = true, |s| s.tcy_up_down = 10.0, |s| s.tcy_left_right = 10.0];
for (i, mark) in marks.into_iter().enumerate() {
for vertical in [false, true] {
let mut t = point("fi", VERTICAL_LIGATURE_CARET_FAMILY);
t.vertical = vertical;
let plain = t.runs[0].style.clone();
let mut marked = plain.clone();
mark(&mut marked);
t.runs = vec![
vectorcraft_doc::TextRun { text: "f".into(), style: marked, inline: None },
vectorcraft_doc::TextRun { text: "i".into(), style: plain, inline: None },
];
let l = layout(&db, &t);
let joined = l.glyphs.iter().any(|g| g.len == 2);
assert_eq!(joined, !vertical, "setting {i}, vertical {vertical}: {:?}", l.glyphs);
}
}
}
/// On a path, an upright ligature in vertical type stands with its top toward the path's start, and
/// 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}");
}
}
/// On a path, an upright ligature in vertical type reads along the path in order: the top of its
/// cell comes first, and its font caret (600 units down the cell) is drawn where the caret, a click
/// and the selection put the stop, on a path running either way with any path effect. A glyph lying
/// on its side keeps its top toward the path's end.
#[test]
fn an_upright_ligature_on_a_path_reads_along_it_in_order() {
use vectorcraft_doc::PathEffect;
let db = db_with(vertical_ligature_caret_font(280));
let mut t = point("fi", VERTICAL_LIGATURE_CARET_FAMILY);
t.vertical = true;
t.runs[0].style.standard_vertical_roman = true;
for (to, effect) in [
(1000.0, PathEffect::Rainbow),
(-1000.0, PathEffect::Rainbow),
(-1000.0, PathEffect::Skew),
(-1000.0, PathEffect::Ribbon3d),
(-1000.0, PathEffect::StairStep),
(-1000.0, PathEffect::Gravity),
] {
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((to, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
t.path_effect = effect;
let l = layout(&db, &t);
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
let &[(1, f)] = g.carets.as_slice() else { panic!("one inner stop: {:?}", g.carets) };
assert!((f - 0.6).abs() < 1e-9, "{f}");
let tangent = Vec2::new(g.angle.cos(), g.angle.sin());
let along = |p: Point| (p - g.origin).dot(tangent);
// Font units, y down: the cell's top is 880 units above the baseline and its foot 120 below.
let (top, foot) = (along(g.xf * Point::new(0.0, -880.0)), along(g.xf * Point::new(0.0, 120.0)));
assert!(top < foot, "{to} {effect:?}: the top comes first along the path: {top} {foot}");
let (start, width) = (g.width_start, g.width);
assert!((top - start).abs() < 1e-6 && (foot - top - width).abs() < 1e-6, "{to} {effect:?}: {top} {foot} {start} {width}");
let drawn = along(g.xf * Point::new(0.0, -280.0));
assert!((drawn - (top + (foot - top) * 0.6)).abs() < 1e-6, "{to} {effect:?}: {drawn} against {top} {foot}");
let (a, b) = caret_position(&l, 1);
let caret = along(a.midpoint(b));
assert!((caret - drawn).abs() < 1e-6, "{to} {effect:?}: the caret stands at the drawn stop: {caret} against {drawn}");
assert_eq!(hit_byte(&l, g.origin + tangent * (drawn + 0.3)), 1, "{to} {effect:?}");
assert_eq!(hit_byte(&l, g.origin + tangent * (drawn - 0.3)), 1, "{to} {effect:?}");
assert_eq!(hit_byte(&l, g.origin + tangent * 0.3), 0, "{to} {effect:?}");
let q = selection_quads(&l, 0, 1);
assert_eq!(q.len(), 1, "{q:?}");
let (q0, q1) = (along(q[0][0]), along(q[0][1]));
assert!((q0.min(q1) - drawn.min(0.0)).abs() < 1e-6 && (q0.max(q1) - drawn.max(0.0)).abs() < 1e-6, "{to} {effect:?}: {q:?} against {drawn}");
}
// Without Standard Vertical Roman Alignment the letter lies on its side: its top still faces
// the path's end and its advance runs across the path, to its right.
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
t.path_effect = PathEffect::Rainbow;
t.runs[0].style.standard_vertical_roman = false;
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 right = Vec2::new(-tangent.y, tangent.x);
let (pen, up, ahead) = (s.xf * Point::ZERO, s.xf * Point::new(0.0, -500.0), s.xf * Point::new(500.0, 0.0));
assert!((up - pen).dot(tangent) > 1.0 && (ahead - pen).dot(right) > 1.0, "{pen:?} {up:?} {ahead:?}");
}
/// On a path, an upright ligature whose cell Proportional Metrics shortens (`vpal`, from 1000 units
/// to 800) keeps its font caret (600 units down the cell) where it is drawn: the inner stop lies at
/// 600 / 800 of its width, and the caret stands at the drawn stop.
#[test]
fn an_upright_ligature_on_a_path_keeps_its_font_caret_on_a_proportional_cell() {
use vectorcraft_doc::PathEffect;
let db = db_with(proportional_vertical_ligature_caret_font(280, -200));
let mut t = point("fi", VERTICAL_LIGATURE_CARET_FAMILY);
t.vertical = true;
t.runs[0].style.standard_vertical_roman = true;
t.runs[0].style.proportional_metrics = true;
let mut line = BezPath::new();
line.move_to((0.0, 0.0));
line.line_to((1000.0, 0.0));
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&line), start: 0.0, end: None };
t.path_effect = PathEffect::Rainbow;
let l = layout(&db, &t);
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
assert!((g.width - 80.0).abs() < 1e-6, "the cell is 800 units at 100 pt: {}", g.width);
let &[(1, f)] = g.carets.as_slice() else { panic!("one inner stop: {:?}", g.carets) };
assert!((f - 0.75).abs() < 1e-9, "{f}");
let tangent = Vec2::new(g.angle.cos(), g.angle.sin());
let along = |p: Point| (p - g.origin).dot(tangent);
let (top, drawn) = (along(g.xf * Point::new(0.0, -880.0)), along(g.xf * Point::new(0.0, -280.0)));
assert!((top - g.width_start).abs() < 1e-6 && (drawn - top - 60.0).abs() < 1e-6, "{top} {drawn} {}", g.width_start);
let (a, b) = caret_position(&l, 1);
let caret = along(a.midpoint(b));
assert!((caret - drawn).abs() < 1e-6, "the caret stands at the drawn stop: {caret} against {drawn}");
}
/// 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.
#[test]
fn an_upright_ligature_with_a_negative_advance_keeps_its_cell() {
let at = |font: Option<Vec<u8>>, family: &str, tracking: f64| {
let db = db_with(font);
let mut t = point("fi", family);
t.vertical = true;
t.runs[0].style.standard_vertical_roman = true;
t.runs[0].style.tracking = tracking;
// Ligatures turned on by name stay on at any tracking.
t.runs[0].style.features = vec!["+liga".into()];
let l = layout(&db, &t);
let Some(g) = l.glyphs.iter().find(|g| g.len == 2) else { panic!("f + i is one glyph: {:?}", l.glyphs) };
let face = db.face(family, "Regular").expect("the test font is added");
assert!(tracking == 0.0 || g.advance <= 0.0, "the tracking makes the advance negative: {}", g.advance);
let cell = 1000.0 * 100.0 / face.units_per_em();
assert!((g.width - cell).abs() < 1e-9, "the glyph stands upright, one em down the column: {} against {cell}", g.width);
let y = |b| caret_position(&l, b).0.y;
let &[(1, f)] = g.carets.as_slice() else { panic!("one inner stop: {:?}", g.carets) };
assert!(((y(1) - y(0)) - g.width * f).abs() < 1e-6, "{} {} against {f}", y(0), y(1));
(f, g.outline.bounding_box().y0 - y(0))
};
let vertical = |tracking| at(vertical_ligature_caret_font(280), VERTICAL_LIGATURE_CARET_FAMILY, tracking);
let (f, top) = vertical(0.0);
assert!((f - 0.6).abs() < 1e-9, "{f}");
let (tracked, tracked_top) = vertical(-1500.0);
assert!((tracked - f).abs() < 1e-9, "the caret is read down the cell: {tracked} against {f}");
assert!((tracked_top - top).abs() < 1e-6, "{tracked_top} against {top}");
let plain = |tracking| at(ligature_caret_font(None), LIGATURE_CARET_FAMILY, tracking);
let (f, top) = plain(0.0);
assert!((f - 0.5).abs() < 1e-9, "{f}");
let (tracked, tracked_top) = plain(-1500.0);
assert!((tracked - f).abs() < 1e-9, "{tracked} against {f}");
assert!((tracked_top - top).abs() < 1e-6, "the cell starts where it does without tracking: {tracked_top} against {top}");
}