diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index 98e8809b..60c2e96d 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -595,6 +595,15 @@ fn align_vertically(cx: &mut Ctx<'_>, paras: &[Range], 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], 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; diff --git a/crates/text/src/tests_inline.rs b/crates/text/src/tests_inline.rs index 6a435ade..49555538 100644 --- a/crates/text/src/tests_inline.rs +++ b/crates/text/src/tests_inline.rs @@ -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:?}"); + } + } +}