Place: PDF pages and SVGs keep their exact size (#511)

file.place sized a placed PDF page (and SVG) from its size rounded to
whole points, while the export draws the page into that size: a page of
91.774 x 613.276 pt became 92 x 613 and was drawn 0.25 % wider and
0.3 % shorter, so pieces laid edge to edge left gaps of up to 1.9 pt.
A 100.4 x 50.4 pt page placed 100.4 pt wide came out 50.2 pt tall:
`Rect { x0: 0.0, y0: 0.0, x1: 100.4, y1: 50.2 }`.

The graphic's natural size is now the exact page (crop box) or SVG size;
the rounded pixel size stays for display and preflight. place.load (the
place cursor) gets the same exact size.

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
japbcoelhoandClaude Opus 5.5 authored and GitHub committed 2026-10-11 17:39:40 -06:00
1 parent ca343e8985
commit 3dc0a1563a
2 files changed
+32 -3

No files matched your search

+23 -3
View File
@@ -313,8 +313,9 @@ fn file_place(s: &mut Session, p: &Value) -> Result<Value> {
.map(|(w, h)| (w.round().max(1.0) as u32, h.round().max(1.0) as u32))
.ok_or_else(|| bad("file.place", "can't read that PDF page"))?,
};
// 72 ppi by default unless the file says otherwise; scale so it fits the page when huge.
let (nw, nh) = eps_size.unwrap_or((pw as f64, ph as f64));
// 72 ppi by default unless the file says otherwise; scale so it fits the page when huge. PDF pages
// and SVGs keep their exact size: rounding it to whole points would stretch them unevenly.
let (nw, nh) = eps_size.or_else(|| designcraft_render::natural_size(&bytes, pdf_page as usize)).unwrap_or((pw as f64, ph as f64));
// Image Import Options › Crop to: the frame shows that box of the PDF page.
let crop_box = match str_param(p, "pdfCrop") {
Some(k) if designcraft_render::is_pdf(&bytes) => {
@@ -440,6 +441,7 @@ pub(crate) fn read_source(p: &Value) -> Result<(Vec<u8>, String, Option<String>)
fn place_load(s: &mut Session, p: &Value) -> Result<Value> {
let (bytes, name, link) = read_source(p)?;
let (pw, ph) = designcraft_render::image_size(&bytes).ok_or_else(|| bad("place.load", "unsupported or corrupt image"))?;
let natural = designcraft_render::natural_size(&bytes, 0).unwrap_or((pw as f64, ph as f64));
let aid = s.edit(|d, _| {
let aid = AssetId(d.alloc());
let mime = designcraft_render::image_mime(&bytes).to_string();
@@ -449,7 +451,7 @@ fn place_load(s: &mut Session, p: &Value) -> Result<Value> {
);
Ok(aid)
})?;
s.loaded = Some((aid, (pw as f64, ph as f64)));
s.loaded = Some((aid, natural));
s.set_tool("placeGun");
Ok(json!({"asset": aid.0, "name": name, "width": pw, "height": ph}))
}
@@ -689,6 +691,24 @@ mod pdf_crop_tests {
assert!((trim.width() - 612.0).abs() < 1e-3 && (trim.height() - 792.0).abs() < 1e-3, "{trim:?}");
assert!(t.execute("file.place", &json!({"base64": b64, "name": "a.pdf", "pdfCrop": "nope"})).is_err());
}
#[test]
fn placed_pdf_keeps_its_exact_page_size() {
// A 100.4 x 50.4 pt page placed 100.4 pt wide is 50.4 pt tall, not stretched to whole points.
let mut s = Session::new();
s.execute("file.new", &json!({"width": 100.4, "height": 50.4, "pages": 1})).unwrap();
let b64 = s.execute("file.exportPdf", &json!({})).unwrap()["base64"].as_str().unwrap().to_string();
let mut t = Session::new();
t.execute("file.new", &json!({})).unwrap();
let r = t.execute("file.place", &json!({"base64": b64, "name": "a.pdf", "width": 100.4, "x": 0, "y": 0})).unwrap();
let id = designcraft_doc::ItemId(r["id"].as_u64().unwrap());
let d = &t.doc().unwrap().doc;
let it = d.item(id).unwrap();
let b = it.bounds();
assert!((b.width() - 100.4).abs() < 1e-3 && (b.height() - 50.4).abs() < 1e-3, "{b:?}");
let designcraft_doc::Content::Graphic(g) = &it.content else { panic!("not a graphic") };
assert!((g.size.0 - 100.4).abs() < 1e-3 && (g.size.1 - 50.4).abs() < 1e-3, "{:?}", g.size);
}
}
#[cfg(test)]
+9
View File
@@ -1022,6 +1022,15 @@ pub fn image_size(bytes: &[u8]) -> Option<(u32, u32)> {
designcraft_images::pixel_size(bytes)
}
/// Natural size of a placed graphic, unrounded: a PDF page (crop box) and an SVG in points, a raster
/// in pixels. Placed graphics are drawn at this size; `image_size` rounds it to whole pixels for display.
pub fn natural_size(bytes: &[u8], page: usize) -> Option<(f64, f64)> {
if is_pdf(bytes) {
return pdf_page_size(bytes, page).map(|(w, h)| (w.max(1e-3), h.max(1e-3)));
}
designcraft_images::natural_size(bytes)
}
/// MIME type guess for encoded image bytes.
/// Is this a PDF file (placed PDFs are graphics)?
pub fn is_pdf(bytes: &[u8]) -> bool {