web: render plot preview in-page instead of a new tab

AutoCAD's plot preview renders the plotted result on screen instead of
producing a file. The web build now does the same: the plotted PDF bytes
are rasterised with the hayro pipeline already used for PDF underlays
(150 DPI, first 12 pages) and shown in an in-page overlay with
Download-PDF and Close actions.

The previous strategy opened a new tab via window.open from the async
plot task; browsers withhold popups without a live user gesture, and
embedded hosts ship no PDF viewer plugin at all — so the fallback
download fired and preview behaved like an export. Plain PNG <img>
elements need neither permission.
This commit is contained in:
Afonsop committed 2026-09-15 19:51:11 +01:00
1 parent e3c86eca29
commit 8722d84910
3 files changed
+277 -102

No files matched your search

+68 -9
View File
@@ -67,8 +67,30 @@ fn rasterize_uncached(path: &str, page: &str) -> Option<Arc<PdfPage>> {
let bytes = source_bytes(path)?;
let pdf = Pdf::new(bytes).ok()?;
let page_no = page.trim().parse::<usize>().unwrap_or(1).max(1);
let page = pdf.pages().get(page_no - 1)?;
let scale = RASTER_DPI / 72.0;
let pixmap = raster_page(&pdf, page_no - 1, RASTER_DPI)?;
let RasterizedPage {
pixels,
width,
height,
} = pixmap;
Some(Arc::new(PdfPage {
pixels,
width,
height,
dpi: RASTER_DPI,
}))
}
struct RasterizedPage {
pixels: Arc<Vec<u8>>,
width: u32,
height: u32,
}
/// Rasterise one 0-based page index at `dpi` onto a white background.
fn raster_page(pdf: &Pdf, index: usize, dpi: f32) -> Option<RasterizedPage> {
let page = pdf.pages().get(index)?;
let scale = dpi / 72.0;
let pixmap = hayro::render(
page,
&RenderCache::new(),
@@ -80,14 +102,32 @@ fn rasterize_uncached(path: &str, page: &str) -> Option<Arc<PdfPage>> {
..Default::default()
},
);
let width = u32::from(pixmap.width());
let height = u32::from(pixmap.height());
Some(Arc::new(PdfPage {
Some(RasterizedPage {
pixels: Arc::new(pixmap.data_as_u8_slice().to_vec()),
width,
height,
dpi: RASTER_DPI,
}))
width: u32::from(pixmap.width()),
height: u32::from(pixmap.height()),
})
}
/// One-shot rasterisation of freshly generated PDF bytes (plot preview).
/// Uncached: the bytes differ on every plot, so a cache would only grow.
/// Returns at most `max_pages` pages; larger plots preview their first pages.
pub fn rasterize_bytes_pages(bytes: Arc<Vec<u8>>, dpi: f32, max_pages: usize) -> Vec<Arc<PdfPage>> {
let Ok(pdf) = Pdf::new(bytes) else {
return Vec::new();
};
let count = pdf.pages().len().min(max_pages);
(0..count)
.filter_map(|index| {
let page = raster_page(&pdf, index, dpi)?;
Some(Arc::new(PdfPage {
pixels: page.pixels,
width: page.width,
height: page.height,
dpi,
}))
})
.collect()
}
fn source_bytes(path: &str) -> Option<Arc<Vec<u8>>> {
@@ -131,4 +171,23 @@ mod tests {
assert_eq!((page.width, page.height), (150, 150));
assert_eq!(page.pixels.len(), 150 * 150 * 4);
}
#[test]
fn bytes_pages_render_every_page_up_to_the_cap() {
let mut document = PdfDocument::new("multi-page");
for _ in 0..3 {
document
.pages
.push(OutputPage::new(Mm(50.8), Mm(25.4), Vec::new()));
}
let bytes = Arc::new(document.save(&PdfSaveOptions::default(), &mut Vec::new()));
let all = rasterize_bytes_pages(bytes.clone(), 72.0, 10);
assert_eq!(all.len(), 3);
assert_eq!((all[0].width, all[0].height), (144, 72));
assert_eq!(all[0].dpi, 72.0);
let capped = rasterize_bytes_pages(bytes, 72.0, 2);
assert_eq!(capped.len(), 2);
}
}