mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge PR #651 from NikitkaRa: Text: shrink on overflow gives up at once when even 10% overflows
# Conflicts: # crates/text/src/lib.rs # crates/text/src/tests.rs
This commit is contained in:
+19
-2
@@ -1536,6 +1536,12 @@ fn tab_advance(x: f64, stops: &[Tab], default_tab: f64, seg: (f64, f64)) -> f64
|
||||
(next - x).max(1.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// How many times `layout_scaled` ran on this thread (tests count autofit's passes).
|
||||
pub(crate) static LAYOUT_PASSES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// Lay out a shape's text body.
|
||||
pub fn layout(ctx: &Ctx, shape: &Shape, body: &TextBody, opts: &Opts) -> TextLayout {
|
||||
let bp = resolve::body(ctx, shape);
|
||||
@@ -1558,9 +1564,16 @@ pub fn layout(ctx: &Ctx, shape: &Shape, body: &TextBody, opts: &Opts) -> TextLay
|
||||
&& l.overflow
|
||||
&& !saved
|
||||
{
|
||||
// Text that overflows even at the smallest scale gets that scale: every larger one
|
||||
// overflows too, so searching would only lay it out a dozen more times (seconds for a
|
||||
// body of tens of thousands of paragraphs, on every redraw of the slide).
|
||||
let smallest = layout_scaled(ctx, shape, body, &bp, opts, 0.1, 0.2);
|
||||
if smallest.overflow {
|
||||
return smallest;
|
||||
}
|
||||
// Binary search the largest scale that fits (steps like 90%, 80%… with line reduction).
|
||||
let (mut lo, mut hi) = (0.1_f64, scale0);
|
||||
let mut best = None;
|
||||
let mut best = Some(smallest);
|
||||
for _ in 0..12 {
|
||||
let mid = (lo + hi) / 2.0;
|
||||
let red: f64 = ((1.0 - mid) * 0.8_f64).min(0.2).max(reduce0);
|
||||
@@ -1572,13 +1585,17 @@ pub fn layout(ctx: &Ctx, shape: &Shape, body: &TextBody, opts: &Opts) -> TextLay
|
||||
best = Some(t);
|
||||
}
|
||||
}
|
||||
l = best.unwrap_or_else(|| layout_scaled(ctx, shape, body, &bp, opts, 0.1, 0.2));
|
||||
if let Some(b) = best {
|
||||
l = b;
|
||||
}
|
||||
}
|
||||
l
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_scaled(ctx: &Ctx, shape: &Shape, body: &TextBody, bp: &BodyProps, opts: &Opts, scale: f64, reduce: f64) -> TextLayout {
|
||||
#[cfg(test)]
|
||||
LAYOUT_PASSES.with(|n| n.set(n.get() + 1));
|
||||
let r = opts.rect;
|
||||
let vert = bp.vert.unwrap_or(TextDir::Horizontal);
|
||||
// East Asian vertical text: CJK stands upright in text turned a quarter turn; stacked text
|
||||
|
||||
@@ -264,6 +264,22 @@ fn line_spacing_reduction_comes_off_the_percentage() {
|
||||
assert!((gap(&full) - 10.0).abs() < 1e-6 && (gap(&reduced) - 10.0).abs() < 1e-6, "{} {}", gap(&full), gap(&reduced));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_gives_up_at_once_when_even_the_smallest_scale_overflows() {
|
||||
// Found by fuzzing: a tiny box with 20,000 paragraphs and shrink on overflow took 15 s to
|
||||
// open, laying out the whole body 13 times in a search that could never fit.
|
||||
let text = (0..400).map(|i| format!("Line number {i}")).collect::<Vec<_>>().join("\n");
|
||||
let (p, sh) = setup(&text, 100.0, 20.0);
|
||||
crate::LAYOUT_PASSES.with(|n| n.set(0));
|
||||
let l = lay(&p, &sh, 100.0, 20.0);
|
||||
assert!(l.overflow && l.font_scale <= 0.11, "scale {}", l.font_scale);
|
||||
assert!(crate::LAYOUT_PASSES.with(|n| n.get()) <= 2, "{} passes", crate::LAYOUT_PASSES.with(|n| n.get()));
|
||||
// Text that fits somewhere in the range still gets the search.
|
||||
let (p, sh) = setup(&(0..12).map(|i| format!("Line number {i}")).collect::<Vec<_>>().join("\n"), 600.0, 200.0);
|
||||
let l = lay(&p, &sh, 600.0, 200.0);
|
||||
assert!(l.font_scale > 0.11 && l.font_scale < 1.0 && !l.overflow, "scale {} overflow {}", l.font_scale, l.overflow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caret_and_hit_roundtrip() {
|
||||
let (p, sh) = setup("Hello world", 600.0, 100.0);
|
||||
|
||||
Reference in New Issue
Block a user