mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-29 04:36:47 -06:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<_>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user