diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index 55b119701..e9f532375 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -1088,7 +1088,7 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C if !f.exclusions.is_empty() { let mut tries = 0; loop { - let segs = free_slots(f, col, baseline - asc, baseline + desc, base_size, rtl); + let segs = free_slots(f, col, baseline - asc, baseline, base_size, rtl); let segs: Vec<(f64, f64)> = segs.into_iter().skip_while(|seg| !holds(seg)).collect(); if let Some((&first, rest)) = segs.split_first() { (x0, x1) = first; @@ -2244,7 +2244,8 @@ fn line_metrics( (asc, desc, lead) } -/// Free segments of `col` in the band `y0..y1`, in the order text fills them (left to right, or +/// Free segments of `col` in the band `y0..y1` (a line's top to its baseline: descenders may reach +/// into the wrap), in the order text fills them (left to right, or /// right to left for right-to-left paragraphs); empty if the band is blocked. A line beside an /// object that lets text through on both sides has a segment on each side. Segments narrower than /// 1.5 em of `size` are skipped (the model has no minimum segment width). @@ -2314,7 +2315,7 @@ fn estimate_slots(f: &FrameSpec, col: Rect, first: f64, lead: f64, size: f64, gl while v.len() < n + carry.len() { let mut tries = 0; let slots = loop { - let s = free_slots(f, col, b - size * 0.8, b + size * 0.25, size, rtl); + let s = free_slots(f, col, b - size * 0.8, b, size, rtl); if !s.is_empty() { break s; } diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index c5a48558b..c5d2149ee 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -173,7 +173,7 @@ fn wrap_on_both_sides_sets_text_left_then_right_of_the_object() { assert!(l.x1 < 150.0 && r.x0 > 150.0, "{} {}", l.x1, r.x0); assert_eq!(l.range.end, r.range.start); // No glyph inside the wrapped outline at its line. - let (a, b) = ex.contour.as_ref().unwrap().band_extent(l.baseline - l.ascent, l.baseline + l.descent).unwrap(); + let (a, b) = ex.contour.as_ref().unwrap().band_extent(l.baseline - l.ascent, l.baseline).unwrap(); let n = l.glyphs.len().saturating_sub(1); // The left segment's last glyph may be the space the line breaks at. for g in l.glyphs.iter().take(n).chain(&r.glyphs).filter(|g| g.visible) { @@ -222,7 +222,7 @@ fn words_stay_whole_beside_a_wrap_sliver() { assert!(g.x + g.adv <= l.x1 + 0.5, "{align:?} {hyphenate}: {line:?} overflows its {}..{} segment", l.x0, l.x1); } // A left segment beside the object whose sliver stays empty: the next word doesn't fit it. - let Some((_, b)) = ex.contour.as_ref().unwrap().band_extent(l.baseline - l.ascent, l.baseline + l.descent) else { continue }; + let Some((_, b)) = ex.contour.as_ref().unwrap().band_extent(l.baseline - l.ascent, l.baseline) else { continue }; let next = lines.get(i + 1); if l.x1 > b || 300.0 - b < 18.0 || next.is_some_and(|n| n.baseline == l.baseline) { continue; @@ -278,6 +278,32 @@ fn contour_wrap_follows_the_outline_band_by_band() { assert_eq!(ex.rect, Rect::new(106.0, 96.0, 194.0, 204.0)); } +/// A contour wrap with a slanted right edge, x = 100 + 2(y − 50) for y in 50..100, text to its right. +fn doc_with_slant(contour: designcraft_geom::path::PathData) -> (Document, StoryId) { + let (mut d, sid, _) = doc_with(&[LOREM; 2].join(" "), Rect::new(0.0, 0.0, 300.0, 800.0), ParaAttrs::default()); + let lid = d.default_layer(); + let mut it = designcraft_doc::Item::new(ItemId(d.alloc()), lid, designcraft_doc::Shape::Polygon, contour); + it.wrap.mode = WrapMode::Contour; + it.wrap.side = WrapSide::RightSide; + d.insert_item(SpreadRef::Doc(0), it, None).unwrap(); + (d, sid) +} + +#[test] +fn a_line_meets_the_wrap_from_its_top_to_its_baseline() { + let pts = [Point::new(0.0, 50.0), Point::new(100.0, 50.0), Point::new(200.0, 100.0), Point::new(0.0, 100.0)]; + let path = designcraft_geom::path::PathData::single(designcraft_geom::path::SubPath::polyline(&pts, true)); + let (d, sid) = doc_with_slant(path); + let cs = compose_story(&d, sid, &ComposeOptions::default()); + let beside: Vec<&Line> = cs.frames[0].lines.iter().filter(|l| l.baseline - l.ascent > 50.0 && l.baseline < 100.0).collect(); + assert!(beside.len() >= 2, "{} lines beside the slant", beside.len()); + for l in beside { + // The edge at the baseline, not at the bottom of the descenders (about 6 pt further out). + let edge = 100.0 + 2.0 * (l.baseline - 50.0); + assert!((l.x0 - edge).abs() < 0.5, "line at {} starts at {}, edge {edge}", l.baseline, l.x0); + } +} + #[test] fn paragraph_composer_is_no_worse_than_greedy() { // Sum of squared slack over lines (excluding last) should not exceed the greedy result.