Layout: a picture after a content control's start stays on its line (regression from #392) (#1102)

Since content controls are kept (#392), a picture content control puts a
zero-width start marker before its picture. Line breaking keeps a line's
first item even when it doesn't fit, but counted the marker as that item, so
a picture a little wider than its line wrapped onto a second line, leaving a
marker-only line above it; in a fixed-height text box the picture's line was
then cut off. A logo in a header text box (which 0.5.0 drew) disappeared.

Markers before an item no longer count, as a display equation's check already
does.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-14 01:54:07 -04:00
1 parent 0837c8b7e0
commit 927a305943
2 files changed
+21 -1

No files matched your search

+3 -1
View File
@@ -1728,7 +1728,9 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u16, level: Optio
});
let shrink = if shrink_spaces && !has_tab { space_w * MAX_SPACE_SHRINK } else { 0.0 };
let fits = absorbs || x + c.adv <= right_edge + shrink + 0.01 || c.kind == ClKind::Space || c.kind == ClKind::Marker;
if !fits && j > c0 {
// A line's first item stays on it even when it doesn't fit; markers before it
// (a content control's start, a bookmark) take no room and don't count (Word).
if !fits && (c0..j).any(|k| pl.clusters.get(k).is_some_and(|c| c.kind != ClKind::Marker)) {
// Wrap: back up to the last break opportunity on this line.
end = LineEnd::Wrap;
// Hyphenate only when the plain break leaves more than the hyphenation zone empty.
+18
View File
@@ -3102,6 +3102,24 @@ fn inline_picture_wider_than_the_text_keeps_its_size() {
assert!((r.w - 1000.0).abs() < 0.01 && (r.h - 100.0).abs() < 0.01, "{r:?}");
}
#[test]
fn a_picture_too_wide_after_a_content_control_start_stays_on_the_first_line() {
// The picture opening a picture content control (a logo in a text box a little too
// narrow for it): the control's start marker before it takes no room, so the picture is
// still the line's first item and stays there instead of leaving an empty line above it.
let mut d = Document::from_text("");
if let Some(wordcraft_doc::Block::Para(p)) = d.body.get_mut(0).map(std::sync::Arc::make_mut) {
p.insert_object(0, picture(500.0, 100.0, Default::default()), &Default::default()).unwrap();
let start = InlineObject::ControlStart { control: Box::default() };
p.insert_object(0, start, &Default::default()).unwrap();
}
let l = lay(&d);
let Some(Placed::Lines { para, .. }) = l.pages[0].items.iter().find(|i| matches!(i, Placed::Lines { .. })) else { panic!() };
assert_eq!(para.lines.len(), 1, "{:?}", para.lines);
let line = &para.lines[0];
assert!((line.baseline - line.top - 100.0).abs() < 0.01, "the picture is on the first line: {line:?}");
}
/// The paragraph at `path` on page 0: its first line's x, its y and its line count.
fn para_at(l: &DocLayout, path: &[u32]) -> Vec<(f32, f32, usize)> {
l.pages[0]