M7: inline art follows vertically aligned lines

align_vertically moved lines and glyphs but not TextLayout::inlines, so inline
symbols in a centred or bottom-aligned area stayed where top alignment put them.
The reflow path for non-rectangular frames also kept the previous pass's
inlines, drawing each symbol twice.
This commit is contained in:
haven 2026-10-08 20:12:23 -07:00
parent 2dcac5b48b
commit 56c975465c
2 changed files with 46 additions and 2 deletions

View File

@ -595,6 +595,15 @@ fn align_vertically(cx: &mut Ctx<'_>, paras: &[Range<usize>], t: &TextObject, re
g.origin.y += d;
}
}
// Inline art moves with its glyph's line.
for i in &mut cx.out.inlines {
let d = cx.out.glyphs.get(i.glyph).and_then(|g| offs.get(g.line)).copied().unwrap_or(0.0);
if d != 0.0 {
let m = Affine::translate((0.0, d));
i.xf = m * i.xf;
i.bounds = m.transform_rect_bbox(i.bounds);
}
}
return;
}
let laid_out = |out: &TextLayout| out.lines.last().map_or(0, |l| l.end);
@ -624,12 +633,17 @@ fn align_vertically(cx: &mut Ctx<'_>, paras: &[Range<usize>], t: &TextObject, re
return;
}
let before = laid_out(&cx.out);
let prev = (std::mem::take(&mut cx.out.glyphs), std::mem::take(&mut cx.out.lines), std::mem::replace(&mut cx.out.overflow, false));
let prev = (
std::mem::take(&mut cx.out.glyphs),
std::mem::take(&mut cx.out.lines),
std::mem::take(&mut cx.out.inlines),
std::mem::replace(&mut cx.out.overflow, false),
);
flow(cx, paras, t, Some(regions));
if laid_out(&cx.out) < before {
// Text no longer fits (lines got narrower, or flow around a wrap object): undo the
// pass and try a smaller step.
(cx.out.glyphs, cx.out.lines, cx.out.overflow) = prev;
(cx.out.glyphs, cx.out.lines, cx.out.inlines, cx.out.overflow) = prev;
for (r, (shift, gap)) in regions.iter_mut().zip(saved) {
r.shift = shift;
r.gap = gap;

View File

@ -296,3 +296,33 @@ fn top_to_top_leading_spaces_inline_art_by_its_run() {
let (tall0, tall1) = lines(3.0);
assert!((small0 - tall0).abs() < 1e-6 && (small1 - tall1).abs() < 1e-6, "{small0} {small1} / {tall0} {tall1}");
}
#[test]
fn inline_art_moves_with_vertically_aligned_lines() {
use vectorcraft_doc::text::{AreaOptions, VerticalAlign};
// A rectangle (lines shift) and a triangle (text flows again lower down).
let rect = Rect::new(0.0, 0.0, 400.0, 400.0).to_path(0.1);
let mut tri = kurbo::BezPath::new();
tri.move_to((0.0, 0.0));
tri.line_to((800.0, 0.0));
tri.line_to((400.0, 400.0));
tri.close_path();
for frame in [rect, tri] {
for align in [VerticalAlign::Center, VerticalAlign::Bottom] {
let mut t = text("A", art(1.0), "B", 20.0);
t.kind = TextKind::Area { frame: PathData::from_bezpath(&frame) };
t.area = AreaOptions { vertical_align: align, ..AreaOptions::default() };
let l = layout(db(), &t);
assert_eq!(l.inlines.len(), 1, "{align:?}");
let g = inline_glyph(&l);
let line = &l.lines[g.line];
assert!(line.baseline > 100.0, "{align:?}: not aligned, baseline {}", line.baseline);
// Still centred on the cap height above its (moved) baseline.
let face = db().face("Source Sans 3", "Regular").unwrap();
let cap = face.cap_height * 20.0 / face.upem;
let b = l.inlines[0].bounds;
assert!((b.center().y - (line.baseline - cap * 0.5)).abs() < 1e-6, "{align:?}: {b:?} baseline {}", line.baseline);
assert!((b.x0 - g.origin.x).abs() < 1e-6, "{align:?}: {b:?}");
}
}
}