More robust ratio computation (#4976)

This commit is contained in:
Laurenz 2024-09-17 14:11:57 +02:00 committed by GitHub
parent c145e05f01
commit 0abd46c379
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 13 deletions

View File

@ -490,7 +490,7 @@ fn linebreak_optimized_approximate(
// becomes useless and actively harmful (it could be lower than what // becomes useless and actively harmful (it could be lower than what
// optimal layout produces). Thus, we immediately bail with an infinite // optimal layout produces). Thus, we immediately bail with an infinite
// bound in this case. // bound in this case.
if ratio < metrics.min_ratio(false) { if ratio < metrics.min_ratio {
return Cost::INFINITY; return Cost::INFINITY;
} }
@ -536,6 +536,11 @@ fn ratio_and_cost(
} }
/// Determine the stretch ratio for a line given raw metrics. /// Determine the stretch ratio for a line given raw metrics.
///
/// - A ratio < min_ratio indicates an overfull line.
/// - A negative ratio indicates a line that needs shrinking.
/// - A ratio of zero indicates a perfect line.
/// - A positive ratio indicates a line that needs stretching.
fn raw_ratio( fn raw_ratio(
p: &Preparation, p: &Preparation,
available_width: Abs, available_width: Abs,
@ -548,30 +553,39 @@ fn raw_ratio(
// to make it the desired width. // to make it the desired width.
let delta = available_width - line_width; let delta = available_width - line_width;
// Determine how much stretch is permitted. // Determine how much stretch or shrink is natural.
let adjust = if delta >= Abs::zero() { stretchability } else { shrinkability }; let adjustability = if delta >= Abs::zero() { stretchability } else { shrinkability };
// Ideally, the ratio should between -1.0 and 1.0. // Observations:
// // - `delta` is negative for a line that needs shrinking and positive for a
// A ratio above 1.0 is possible for an underfull line, but a ratio below // line that needs stretching.
// -1.0 is forbidden because the line would overflow. // - `adjustability` must be non-negative to make sense.
let mut ratio = delta / adjust; // - `ratio` inherits the sign of `delta`.
let mut ratio = delta / adjustability.max(Abs::zero());
// The line is not stretchable, but it just fits. This often happens with // The most likely cause of a NaN result is that `delta` was zero. This
// monospace fonts and CJK texts. // often happens with monospace fonts and CJK texts. It means that the line
// already fits perfectly, so `ratio` should be zero then.
if ratio.is_nan() { if ratio.is_nan() {
ratio = 0.0; ratio = 0.0;
} }
// If the ratio exceeds 1, we should stretch above the natural
// stretchability using justifiables.
if ratio > 1.0 { if ratio > 1.0 {
// We should stretch the line above its stretchability. Now // We should stretch the line above its stretchability. Now
// calculate the extra amount. Also, don't divide by zero. // calculate the extra amount. Also, don't divide by zero.
let extra_stretch = (delta - adjust) / justifiables.max(1) as f64; let extra_stretch = (delta - adjustability) / justifiables.max(1) as f64;
// Normalize the amount by half the em size. // Normalize the amount by half the em size.
ratio = 1.0 + extra_stretch / (p.size / 2.0); ratio = 1.0 + extra_stretch / (p.size / 2.0);
} }
ratio // The min value must be < MIN_RATIO, but how much smaller doesn't matter
// since overfull lines have hard-coded huge costs anyway.
//
// The max value is clamped to 10 since it doesn't really matter whether a
// line is stretched 10x or 20x.
ratio.clamp(MIN_RATIO - 1.0, 10.0)
} }
/// Compute the cost of a line given raw metrics. /// Compute the cost of a line given raw metrics.
@ -595,7 +609,7 @@ fn raw_cost(
// If the line shall be justified or needs shrinking, it has normal // If the line shall be justified or needs shrinking, it has normal
// badness with cost 100|ratio|^3. We limit the ratio to 10 as to not // badness with cost 100|ratio|^3. We limit the ratio to 10 as to not
// get to close to our maximum cost. // get to close to our maximum cost.
100.0 * ratio.abs().min(10.0).powi(3) 100.0 * ratio.abs().powi(3)
} else { } else {
// If the line shouldn't be justified and doesn't need shrink, we don't // If the line shouldn't be justified and doesn't need shrink, we don't
// pay any cost. // pay any cost.

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

View File

@ -97,3 +97,7 @@ Lorem ipsum dolor #metadata(none) nonumy eirmod tempor.
--- issue-4278-par-trim-before-equation --- --- issue-4278-par-trim-before-equation ---
#set par(justify: true) #set par(justify: true)
#lorem(6) aa $a = c + b$ #lorem(6) aa $a = c + b$
--- issue-4938-par-bad-ratio ---
#set par(justify: true)
#box($k in NN_0$)