diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index 3bfb5593..9fda6980 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -533,6 +533,15 @@ pub fn caret_maps(ctx: &EvalCtx, layer: &Layer, chars: usize) -> Vec { out } +/// A paragraph's alignment: 0 left, 1 centre, 2 right (a justified paragraph by its last line). +fn align_index(j: Justify) -> u8 { + match j { + Justify::Center | Justify::JustifyLastCenter => 1, + Justify::Right | Justify::JustifyLastRight => 2, + _ => 0, + } +} + /// Lay out and animate a text layer. pub fn text_geom(ctx: &EvalCtx, layer: &Layer) -> Option { let doc = source_text(ctx, layer)?; @@ -578,7 +587,9 @@ pub fn text_geom(ctx: &EvalCtx, layer: &Layer) -> Option { }) .collect(); let advance = |gi: usize| lay.glyphs[gi].advance + vadv[gi]; - // Tracking (before / after each character) and Line Anchor, per line. + // Tracking (before / after each character) and Line Anchor, per line. Without a Line Anchor + // a line grows from where its paragraph's alignment pins it (left, centre or right). + let paras = doc.paras(); let mut shift = vec![0.0f64; n]; let mut li_start = 0; while li_start < n { @@ -588,7 +599,8 @@ pub fn text_geom(ctx: &EvalCtx, layer: &Layer) -> Option { li_end += 1; } let mut pen = 0.0; - let (mut la, mut la_k) = (0.0, 0.0); + let justify = lay.layout.lines.get(line).and_then(|l| paras.get(l.para)).map_or(doc.justify, |p| p.justify); + let (mut la, mut la_k) = (50.0 * f64::from(align_index(justify)), 0.0); for gi in li_start..li_end { let x = &xfs[gi]; let em = lay.styles.get(lay.glyphs[gi].run).map_or(doc.size, |s| s.size) / 1000.0; @@ -639,11 +651,7 @@ pub fn text_geom(ctx: &EvalCtx, layer: &Layer) -> Option { // Path Options. let on_path = text_path(ctx, layer, text); - let align = match doc.justify { - Justify::Center | Justify::JustifyLastCenter => 1, - Justify::Right | Justify::JustifyLastRight => 2, - _ => 0, - }; + let align = align_index(doc.justify); let first_baseline = lay.line_boxes.first().map(|b| b[1]).unwrap_or(0.0); let mut arc = vec![0.0f64; n]; if let Some((pm, _, force, fm, lm)) = &on_path { diff --git a/crates/render/src/text/tests.rs b/crates/render/src/text/tests.rs index 93913aa8..c50999b0 100644 --- a/crates/render/src/text/tests.rs +++ b/crates/render/src/text/tests.rs @@ -207,6 +207,39 @@ fn tracking_line_anchor_and_character_offset() { assert_eq!(substitute('a', &CharXf { char_value: Some((66.0, 1.0)), ..Default::default() }), Some('B')); } +/// Without a Line Anchor, tracking grows each line from where its paragraph's alignment pins it: +/// left text grows right, centred text around its centre, right text to the left (#146). +#[test] +fn tracking_grows_from_each_paragraphs_alignment() { + use effectcraft_keyframe::{Justify, ParaStyle}; + let tracked = |d: TextDoc, amount: f64| { + let (mut p, cid, comp) = setup(600, 300); + let mut l = text_layer(&mut p, &comp, d); + animator(&mut p, &mut l, &["tracking"], &["range"]); + set(&mut l, "text/animators/#1/properties/tracking", Value::Scalar(amount)); + p.comp_mut(cid).unwrap().layers.push(l); + glyph_paths(&ctx(&p, cid, 0.0), &p.comp(cid).unwrap().layers[0]) + }; + // 4 chars × 500/1000 em × 40 px: 20 px of tracking each, half before and half after. + for (justify, x0, centre, x1) in [(Justify::Left, 10.0, 40.0, 70.0), (Justify::Center, -30.0, 0.0, 30.0), (Justify::Right, -70.0, -40.0, -10.0)] { + let d = TextDoc { justify, ..doc("ABCD", 40.0) }; + let (w0, w1) = (bounds_of(&tracked(d.clone(), 0.0)), bounds_of(&tracked(d, 500.0))); + let got = [w1.x0 - w0.x0, w1.center().x - w0.center().x, w1.x1 - w0.x1]; + assert!(got.iter().zip([x0, centre, x1]).all(|(g, w)| (g - w).abs() < 1.0), "{justify:?}: moved {got:?}, want {:?}", [x0, centre, x1]); + } + // Each paragraph by its own alignment: a left paragraph above a right-aligned one. + let mut d = doc("ABCD\nABCD", 40.0); + let left = d.base_para(); + d.set_paras(vec![left.clone(), ParaStyle { justify: Justify::Right, ..left }]); + let lines = |paths: Vec<(BezPath, CharXf)>| -> [kurbo::Rect; 2] { + let (a, b): (Vec<_>, Vec<_>) = paths.into_iter().partition(|g| bounds_of(std::slice::from_ref(g)).center().y < 10.0); + [bounds_of(&a), bounds_of(&b)] + }; + let ([a0, b0], [a1, b1]) = (lines(tracked(d.clone(), 0.0)), lines(tracked(d, 500.0))); + assert!((a1.x0 - a0.x0 - 10.0).abs() < 1.0, "the left paragraph grows right: {} {}", a0.x0, a1.x0); + assert!((b1.x1 - b0.x1 + 10.0).abs() < 1.0, "the right paragraph grows left: {} {}", b0.x1, b1.x1); +} + #[test] fn anchor_grouping_line_rotates_around_line_centre() { let (mut p, cid, comp) = setup(600, 300);