Text: a line that exactly fills the measure fits

A line whose natural width overruns its measure by less than 0.01 pt
(rounded frame widths and advances) fits, in the paragraph and the
single-line composer. A one-word line that exactly filled its frame was
hyphenated and lost its last letters.
This commit is contained in:
aiongg
2026-10-10 05:35:48 +00:00
parent 4fbf8938bf
commit 88eebe48e8
2 changed files with 36 additions and 3 deletions
+11 -3
View File
@@ -166,6 +166,11 @@ impl Item {
const INF: f64 = 10000.0;
/// A line whose natural width exceeds its measure by no more than this (points) still fits: frame
/// widths and glyph advances are rounded, so a line that exactly fills the measure can come out a
/// hair too wide.
const FIT_EPS: f64 = 0.01;
/// Does the glyph force a line end after it?
/// A line may break between `a` and `b` in CJK text (any break next to an ideograph, kana or
/// hangul), except where kinsoku forbids it: no line starts with closing punctuation, small
@@ -454,7 +459,10 @@ fn kp_pass(
y[t] = sy[b][t] - n.ty[t];
z[t] = sz[b][t] - n.tz[t];
}
let (r, feasible) = tiered_ratio(target - l, y, z);
// A line that overruns its measure by a rounding error fits exactly.
let d = target - l;
let d = if d < 0.0 && d > -FIT_EPS { 0.0 } else { d };
let (r, feasible) = tiered_ratio(d, y, z);
if feasible && !forced {
keep.push(a);
} else {
@@ -664,7 +672,7 @@ pub fn greedy(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing, width: &dyn F
continue;
}
let hang_r = right_hang(g, sp);
if x + g.adv - hang_r > w + shrink
if x + g.adv - hang_r > w + shrink + FIT_EPS
&& i > start
&& (last_ok.is_some() || glyphs.get(i - 1).is_none_or(|p| p.break_after != Some(false) && !p.no_break))
{
@@ -690,7 +698,7 @@ pub fn greedy(glyphs: &[Glyph], hyph_after: &[bool], sp: &Spacing, width: &dyn F
last_ok = Some((i + 1, false));
} else if hyph_after[i] && may_hyphenate(hyphens) {
let hy = hyphen_width(g, &mut hy_cache) * if sp.optical { 1.0 - hang('-').1 } else { 1.0 };
if x + hy <= w + shrink {
if x + hy <= w + shrink + FIT_EPS {
last_ok = Some((i + 1, true));
}
}
+25
View File
@@ -1889,3 +1889,28 @@ fn vertical_lines_fit_the_em_box() {
assert!((l.baseline - col.y0 - ascent).abs() < 1e-9, "{family}: {} {}", l.baseline, col.y0);
}
}
/// Lines of `text` set in a frame `width` wide, with `tracking` on every character.
fn tracked_lines(text: &str, width: f64, para: ParaAttrs, tracking: f64) -> Vec<Line> {
let (mut d, sid, _) = doc_with(text, Rect::new(0.0, 0.0, width, 4000.0), para);
d.story_mut(sid).unwrap().format_chars(0..text.len(), |f| f.over.tracking = Some(tracking));
let cs = compose_story(&d, sid, &ComposeOptions::default());
assert!(!cs.is_overset(), "overset at width {width}");
all_lines(&cs).into_iter().cloned().collect()
}
#[test]
fn a_line_that_exactly_fills_the_measure_fits() {
use designcraft_doc::Composer;
let text = "#knowyourplastic";
let one = &tracked_lines(text, 1e5, ParaAttrs::default(), 0.0)[0];
// The measure a rounding error short of the natural width.
let width = one.end_x - one.x0 - 0.005;
for composer in [Composer::Paragraph, Composer::SingleLine] {
for align in [Align::Left, Align::LeftJustified] {
let para = ParaAttrs { composer: Some(composer), align: Some(align), ..Default::default() };
let lines = tracked_lines(text, width, para, 0.0);
assert_eq!(lines.len(), 1, "{composer:?} {align:?}: {:?}", lines.iter().map(|l| l.range.clone()).collect::<Vec<_>>());
}
}
}