summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-09-17 14:11:57 +0200
committerGitHub <noreply@github.com>2024-09-17 12:11:57 +0000
commit0abd46c3796e18e997e26f94837c76dc446036d0 (patch)
tree4ee75c6d84682176e014d05626c467685d17018d /crates
parentc145e05f01746a72a9455d7fad95f927f568e2fd (diff)
More robust ratio computation (#4976)
Diffstat (limited to 'crates')
-rw-r--r--crates/typst/src/layout/inline/linebreak.rs40
1 files changed, 27 insertions, 13 deletions
diff --git a/crates/typst/src/layout/inline/linebreak.rs b/crates/typst/src/layout/inline/linebreak.rs
index 2dc3edb5..b72507fd 100644
--- a/crates/typst/src/layout/inline/linebreak.rs
+++ b/crates/typst/src/layout/inline/linebreak.rs
@@ -490,7 +490,7 @@ fn linebreak_optimized_approximate(
// becomes useless and actively harmful (it could be lower than what
// optimal layout produces). Thus, we immediately bail with an infinite
// bound in this case.
- if ratio < metrics.min_ratio(false) {
+ if ratio < metrics.min_ratio {
return Cost::INFINITY;
}
@@ -536,6 +536,11 @@ fn ratio_and_cost(
}
/// 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(
p: &Preparation,
available_width: Abs,
@@ -548,30 +553,39 @@ fn raw_ratio(
// to make it the desired width.
let delta = available_width - line_width;
- // Determine how much stretch is permitted.
- let adjust = if delta >= Abs::zero() { stretchability } else { shrinkability };
+ // Determine how much stretch or shrink is natural.
+ let adjustability = if delta >= Abs::zero() { stretchability } else { shrinkability };
- // Ideally, the ratio should between -1.0 and 1.0.
- //
- // A ratio above 1.0 is possible for an underfull line, but a ratio below
- // -1.0 is forbidden because the line would overflow.
- let mut ratio = delta / adjust;
+ // Observations:
+ // - `delta` is negative for a line that needs shrinking and positive for a
+ // line that needs stretching.
+ // - `adjustability` must be non-negative to make sense.
+ // - `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
- // monospace fonts and CJK texts.
+ // The most likely cause of a NaN result is that `delta` was zero. This
+ // 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() {
ratio = 0.0;
}
+ // If the ratio exceeds 1, we should stretch above the natural
+ // stretchability using justifiables.
if ratio > 1.0 {
// We should stretch the line above its stretchability. Now
// 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.
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.
@@ -595,7 +609,7 @@ fn raw_cost(
// 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
// get to close to our maximum cost.
- 100.0 * ratio.abs().min(10.0).powi(3)
+ 100.0 * ratio.abs().powi(3)
} else {
// If the line shouldn't be justified and doesn't need shrink, we don't
// pay any cost.