Fit Frame to Content puts a text frame's bottom on its last baseline

The height search stopped up to 0.011 pt short of it (a line is set within 0.01 pt), so a fitted
frame scaled up with its text came out overset. InDesign fits 70 pt Kanji_PA to 18.521 mm, its
ascent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeer
2026-10-15 10:34:50 +02:00
co-authored by Claude Opus 5.5
parent f56273f72e
commit 183feade3b
+21 -4
View File
@@ -2066,7 +2066,7 @@ fn fitted_text_frame(d: &Document, id: ItemId, height: bool, width: bool) -> Res
// First fit the height at the existing width, then measure the complete text's width.
// Recheck height after fitting width, since a small width change can change a line break.
if height {
out = fit_text_height(out, &mut compose)?;
out = fit_text_height(out, id, tf.options.inset[2], &mut compose)?;
}
if width && tf.options.columns <= 1 {
let cs = compose(out);
@@ -2077,13 +2077,18 @@ fn fitted_text_frame(d: &Document, id: ItemId, height: bool, width: bool) -> Res
}
}
if height {
out = fit_text_height(out, &mut compose)?;
out = fit_text_height(out, id, tf.options.inset[2], &mut compose)?;
}
}
Ok(Some(out))
}
fn fit_text_height(mut rect: Rect, compose: &mut impl FnMut(Rect) -> Arc<designcraft_compose::ComposedStory>) -> Result<Rect> {
fn fit_text_height(
mut rect: Rect,
id: ItemId,
inset_bottom: f64,
compose: &mut impl FnMut(Rect) -> Arc<designcraft_compose::ComposedStory>,
) -> Result<Rect> {
let mut low = 1.0;
let limit = 20_000.0;
let mut high = rect.height().clamp(1.0, limit);
@@ -2110,6 +2115,13 @@ fn fit_text_height(mut rect: Rect, compose: &mut impl FnMut(Rect) -> Arc<designc
}
}
rect.y1 = rect.y0 + high;
// A line is set when its baseline is within 0.01 pt of the bottom, so the search stops just
// short of the lowest baseline. The bottom goes onto it (plus the inset), as InDesign's does:
// the frame then fits its text exactly and still does when it is scaled up.
let lowest = compose(rect).frame(id).and_then(|ft| ft.lines.iter().map(|l| l.baseline).reduce(f64::max));
if let Some(b) = lowest.map(|b| b + inset_bottom).filter(|b| b.is_finite() && *b > rect.y1 && *b - rect.y1 <= 0.02) {
rect.y1 = b;
}
Ok(rect)
}
@@ -3604,7 +3616,12 @@ mod fit_text_frame_tests {
assert!(b.x0 == 100.0 && b.width() > 100.0 && b.width() < 250.0, "{b:?}");
let d = s.doc().unwrap().doc.clone();
let sid = d.item(id).unwrap().text_frame().unwrap().story;
assert!(!s.cache.get(&d, sid, None).is_overset(), "still fits");
let cs = s.cache.get(&d, sid, None);
assert!(!cs.is_overset(), "still fits");
// The bottom is on the baseline (Dansky's Exclude Overlap tutorial, 1:56: 70 pt Kanji_PA
// fits to 18.521 mm, its ascent), so a frame scaled up with its text still fits.
let baseline = cs.frame(id).unwrap().lines[0].baseline;
assert!((b.y1 - baseline).abs() < 1e-9, "{b:?} {baseline}");
}
#[test]