mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-10 20:41:55 +00:00
Text wrap: a line meets the wrap from its top to its baseline
This commit is contained in:
1 parent
7ddd99ee4c
commit
67ae415506
2 files changed
+32
-5
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user