Merge pull request #2211

This commit is contained in:
aiongg committed 2026-11-04 04:14:12 +00:00
commit b1f67ee4e4
3 files changed
+69 -1

No files matched your search

+10
View File
@@ -14,6 +14,10 @@ pub struct RecentPreview {
pub rgba: Vec<u8>,
}
/// The most pixels a recent file's preview may have to be shown (its PNG is bounded by
/// `designcraft_format::MAX_PREVIEW_BYTES`, but a small PNG can claim a huge picture).
const MAX_PREVIEW_PIXELS: u64 = 1 << 24;
/// The picture a `.designcraft` file saved of its first page; None when the file is gone, isn't
/// ours, or was saved before files had one (it gets one when saved again).
pub fn recent_preview(path: &str) -> Option<RecentPreview> {
@@ -21,6 +25,12 @@ pub fn recent_preview(path: &str) -> Option<RecentPreview> {
{
// Only the zip directory and the preview entry are read, not the whole document.
let png = designcraft_format::read_preview(std::io::BufReader::new(crate::io::open(path).ok()?))?;
// A preview is a small picture: one whose header claims more pixels than that isn't
// decoded (a decode is bounded, but not at thumbnail size).
let (w, h) = designcraft_images::pixel_size(&png)?;
if u64::from(w).saturating_mul(u64::from(h)) > MAX_PREVIEW_PIXELS {
return None;
}
let img = designcraft_images::decode_rgba(&png)?;
let (width, height) = img.dimensions();
Some(RecentPreview { width, height, rgba: img.into_raw() })
+40 -1
View File
@@ -2859,7 +2859,12 @@ fn clipping_path(s: &mut Session, p: &Value) -> Result<Value> {
let Some(px) = designcraft_render::decode_pixmap_page(&asset.data, asset.page) else { continue };
// A placed PDF cropped to a box: trace that box, which graphic space spans.
let px = match designcraft_render::shown_box(asset) {
Some(frac) => designcraft_render::crop_pixmap(&px, frac).unwrap_or(px),
// A crop too big to trace is skipped, as a graphic that fails to decode is: tracing
// the whole page instead would trace something else than the box.
Some(frac) => match designcraft_render::crop_pixmap(&px, frac) {
Some(cropped) => cropped,
None => continue,
},
None => px,
};
let (pw, ph) = (px.width() as usize, px.height() as usize);
@@ -4660,6 +4665,40 @@ mod clipping_tests {
assert!(s.execute("object.clippingPath", &json!({"ids": [f], "threshold": 255})).is_err());
}
/// A placed PDF whose crop box is too large to trace is skipped, not traced from the whole page.
#[test]
fn clipping_path_skips_a_pdf_whose_crop_is_too_large_to_trace() {
let content = "0 0 0 rg 10 10 50 50 re f";
let objs = [
"<</Type/Catalog/Pages 2 0 R>>".to_string(),
"<</Type/Pages/Kids[3 0 R]/Count 1>>".into(),
"<</Type/Page/Parent 2 0 R/MediaBox[-30000 -30000 30000 30000]/CropBox[0 0 100 100]/Contents 4 0 R>>".into(),
format!("<</Length {}>>\nstream\n{content}\nendstream", content.len()),
];
let mut bytes = b"%PDF-1.7\n".to_vec();
let mut offsets = Vec::new();
for (i, body) in objs.iter().enumerate() {
offsets.push(bytes.len());
bytes.extend_from_slice(format!("{} 0 obj\n{body}\nendobj\n", i + 1).as_bytes());
}
let xref = bytes.len();
bytes.extend_from_slice(b"xref\n0 5\n0000000000 65535 f\r\n");
for at in offsets {
bytes.extend_from_slice(format!("{at:010} 00000 n\r\n").as_bytes());
}
bytes.extend_from_slice(format!("trailer\n<</Size 5/Root 1 0 R>>\nstartxref\n{xref}\n%%EOF\n").as_bytes());
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let f = s.execute("frame.create", &json!({"rect": [100, 100, 200, 200]})).unwrap()["id"].as_u64().unwrap();
let placed = s
.execute("file.place", &json!({"base64": super::super::file::base64_encode(&bytes), "name": "wide.pdf", "frame": f, "pdfCrop": "media"}));
assert!(placed.is_ok(), "{placed:?}");
let before = s.doc().unwrap().doc.item(ItemId(f)).unwrap().shape;
let e = s.execute("object.clippingPath", &json!({"ids": [f], "type": "alpha"}));
assert!(e.is_err(), "the graphic is skipped, so there is nothing to trace: {e:?}");
assert_eq!(s.doc().unwrap().doc.item(ItemId(f)).unwrap().shape, before, "the frame keeps its shape");
}
#[test]
fn clipping_path_options_report_invert_and_remove_the_clip() {
let mut s = Session::new();
+19
View File
@@ -265,6 +265,11 @@ pub fn crop_pixmap(pm: &Pixmap, frac: [f64; 4]) -> Option<Pixmap> {
return None;
}
let (w, h) = (w as u16, h as u16);
// A box reaching far outside the page would mostly be transparent padding: it gets no more
// pixels than a page raster may have.
if u64::from(w) * u64::from(h) > crate::MAX_PAGE_PIXELS {
return None;
}
let mut out = Pixmap::new(w, h);
let src = pm.data();
let dst = out.data_mut();
@@ -286,3 +291,17 @@ pub fn crop_pixmap(pm: &Pixmap, frac: [f64; 4]) -> Option<Pixmap> {
}
Some(out)
}
#[cfg(test)]
mod crop_tests {
use super::*;
/// A crop box reaching far outside the page gets no bigger a pixmap than a page may have.
#[test]
fn crop_outside_the_page_is_bounded() {
let pm = Pixmap::new(10, 10);
assert!(crop_pixmap(&pm, [-0.5, -0.5, 1.5, 1.5]).is_some_and(|c| (c.width(), c.height()) == (20, 20)));
// 60,000 × 60,000 pixels: within u16 each way, far past the page raster budget.
assert!(crop_pixmap(&pm, [-3000.0, -3000.0, 3000.0, 3000.0]).is_none());
}
}