diff --git a/crates/engine/src/cmd/layout.rs b/crates/engine/src/cmd/layout.rs index cb6ac0fdf..3629a7541 100644 --- a/crates/engine/src/cmd/layout.rs +++ b/crates/engine/src/cmd/layout.rs @@ -1701,11 +1701,14 @@ fn publication(s: &mut Session, p: &Value) -> CmdResult { let (ix, iy, iw, ih) = (r.0 + pad, r.1 + pad, lw - 2.0 * pad, lh - 2.0 * pad); let bar_fit = if bar.is_some() { bar_h.min(ih) } else { 0.0 }; let rows_h = (ih - bar_fit - if bar.is_some() { bar_part - bar_h } else { 0.0 }).max(0.0); - if ch > 0.0 && (cw > iw || ch > rows_h) { + // A box sized to its content, less the padding again, can come out a rounding error + // short of it: that still fits. + const FIT_MM: f64 = 1e-6; + if ch > 0.0 && (cw > iw + FIT_MM || ch > rows_h + FIT_MM) { warnings.push("the legend lists more than fits its corner: its last rows or the ends of long labels are left out".into()); } // In a column the colorbar takes the column's width, as its rows do. - if bar.is_some() && ((bar_w > iw && !legend_in_column) || bar_h > ih) { + if bar.is_some() && ((bar_w > iw + FIT_MM && !legend_in_column) || bar_h > ih + FIT_MM) { warnings.push("the legend's box is smaller than the colorbar: it is drawn narrower or shorter".into()); } if ch > 0.0 && rows_h >= 1.0 { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index feb515119..d8e9f95cb 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3183,6 +3183,29 @@ fn publication_surrounds_keep_off_the_data() { let _ = std::fs::remove_dir_all(&dir); } +/// A publication legend sized to its rows fits them: its box, less the padding, may come out a +/// rounding error short of the rows' width, which isn't "more than fits" (macOS CI, whose fonts +/// gave one such width). Layer names of many widths, so some land on a rounding error with any font. +#[test] +fn a_publication_legend_sized_to_its_rows_fits_them() { + let dir = std::env::temp_dir().join(format!("rustgis-legend-fit-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let names: Vec = (0..300).map(|i| format!("{}{i}", &"WmilQxkaRb"[i % 10..])).collect(); + for name in &names { + let path = dir.join(format!("{name}.geojson")); + std::fs::write( + &path, + r#"{"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[[4,4],[6,4],[6,6],[4,6],[4,4]]]}}]}"#, + ) + .unwrap(); + let mut s = Session::new(Project::default()); + s.run("layer.addData", &json!({"path": path.display().to_string()})).unwrap(); + let r = s.run("layout.publication", &json!({"extent": [0, 0, 10, 10], "author": ""})).unwrap(); + assert!(!r["warnings"].to_string().contains("more than fits"), "{name}: {r}"); + } + let _ = std::fs::remove_dir_all(&dir); +} + /// A publication layout keys a raster theme with a colorbar: the topmost visible raster drawn on a /// ramp gets one at the bottom of the legend's box; a map of vectors only gets none. #[test]