Merge pull request #1193 from MarekSpodBiedry/issue-1178

This commit is contained in:
Brandon Thomas
2026-10-23 17:10:30 -04:00
2 changed files with 23 additions and 2 deletions
+12 -2
View File
@@ -66,7 +66,7 @@ pub enum PageLayout {
#[serde(default, rename_all = "camelCase")]
pub struct PdfOptions {
pub layout: PageLayout,
/// Raster resolution of slide artwork (dots per inch).
/// Raster resolution of slide artwork (dots per inch), lowered for a slide whose raster would pass 64M pixels.
pub dpi: f64,
/// Slide indices to export (0-based); `None` = all.
pub slides: Option<Vec<usize>>,
@@ -423,6 +423,16 @@ fn backdrop(mut img: deckcraft_render::Image) -> Image {
Image::from_rgba8(img.pixels, img.width, img.height)
}
/// Most pixels one slide raster may have; it is RGBA, and the backdrop and the content are two rasters.
const MAX_RASTER_PIXELS: f64 = 64_000_000.0;
/// Render scale for a slide of `slide` units drawn `box_width` points wide at `dpi`, with the total pixel count capped.
fn raster_scale(box_width: f64, slide: (f64, f64), dpi: f64) -> f64 {
let scale = (box_width / 72.0 * dpi.clamp(36.0, 600.0)) / slide.0;
let pixels = slide.0 * scale * slide.1 * scale;
if pixels > MAX_RASTER_PIXELS { scale * (MAX_RASTER_PIXELS / pixels).sqrt() } else { scale }
}
impl Exporter<'_> {
fn font(&mut self, face: &deckcraft_fonts::FontFace) -> Result<Font> {
if let Some(font) = self.fonts.get(&face.id()) {
@@ -439,7 +449,7 @@ impl Exporter<'_> {
let (sw, sh) = (pres.slide_size.width.max(1.0), pres.slide_size.height.max(1.0));
let k = b.width() / sw;
// Raster at the requested dpi relative to the box size on paper.
let scale = (b.width() / 72.0 * self.opts.dpi.clamp(36.0, 600.0)) / sw;
let scale = raster_scale(b.width(), (sw, sh), self.opts.dpi);
let ropts = RenderOpts { scale, threads: 4, ..Default::default() };
// Two rasters per slide: the backdrop (background, master and layout graphics) is the same
// on every slide that shares its layout, so the writer stores it once, and the slide's own
+11
View File
@@ -530,3 +530,14 @@ fn glyph_ranges_follow_shaping_clusters() {
// A run starting mid-paragraph, multi-byte characters in visual (right-to-left) order.
assert_eq!(glyph_text_ranges("αβγ", (3, 6), &[(5, 'γ'), (4, 'β'), (3, 'α')], 3), vec![4..6, 2..4, 0..2]);
}
#[test]
fn raster_scale_caps_total_pixels_not_each_side() {
// A huge square slide at 600 dpi would be 16,000 x 16,000 px (~1 GB RGBA) without the cap.
let s = raster_scale(20_000.0, (20_000.0, 20_000.0), 600.0);
let px = 20_000.0 * s * 20_000.0 * s;
assert!(px <= MAX_RASTER_PIXELS * 1.0001, "{px}");
// A normal slide keeps the requested dpi.
let s = raster_scale(720.0, (960.0, 540.0), 200.0);
assert!((s - 720.0 / 72.0 * 200.0 / 960.0).abs() < 1e-12);
}