diff --git a/crates/engine/src/cmd/fileio/load.rs b/crates/engine/src/cmd/fileio/load.rs index 449b9637..374825d1 100644 --- a/crates/engine/src/cmd/fileio/load.rs +++ b/crates/engine/src/cmd/fileio/load.rs @@ -192,7 +192,7 @@ pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result let title = file_name(name); let mut converted = false; let mut preview_only = false; - let (mut doc, warnings, restored) = match format.id { + let (mut doc, mut warnings, restored) = match format.id { // EPS and PostScript .ai: the document our EPS files carry, else the PostScript read. _ if format.id == "eps" || vectorcraft_pdf::is_postscript(bytes) => { let editing = vectorcraft_eps::has_native(bytes).then_some((true, || vectorcraft_eps::native(bytes))); @@ -263,6 +263,7 @@ pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result if !matches!(format.id, "vectorcraft" | "template") || doc.title.is_empty() { doc.title = title; } + warnings.extend(doc.images.values().filter_map(|b| too_large_note(&b.bytes))); Ok(Loaded { doc, format, warnings, restored, converted, preview_only }) } @@ -394,6 +395,17 @@ pub(super) fn new_from_template(s: &mut Session, p: &Value) -> Result { /// that default: wasm32 has 4 GB to address and aborts when an allocation fails. pub(crate) const MAX_RASTER_ALLOC: u64 = if cfg!(target_arch = "wasm32") { 512 << 20 } else { 2 << 30 }; +/// What to say of encoded image `bytes` that need more than [`MAX_RASTER_ALLOC`] to decode (its +/// header says so; the pixels aren't read): the renderer, which has the same allowance, draws +/// nothing for them, so Open, Place and the Links panel say it (#1479). `None` for images that +/// decode, and for bytes that aren't an image. +pub(crate) fn too_large_note(bytes: &[u8]) -> Option { + use image::ImageDecoder as _; + let d = image::ImageReader::new(Cursor::new(bytes)).with_guessed_format().ok()?.into_decoder().ok()?; + let (w, h) = d.dimensions(); + (d.total_bytes() > MAX_RASTER_ALLOC).then(|| format!("an image of {w} × {h} pixels is too large to draw")) +} + /// Decode an image's header (and, for formats stored as PNG, its pixels). CMYK TIFFs are kept as /// they are, with their ink amounts ([`ImageBlob::cmyk`]); CMYK TIFFs with an alpha channel, which /// the decoder can't read, become RGBA in the active colour settings' CMYK, as do CMYK Photoshop diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs index 7a13657c..9a0194e3 100644 --- a/crates/engine/src/cmd/fileio/mod.rs +++ b/crates/engine/src/cmd/fileio/mod.rs @@ -28,7 +28,7 @@ pub mod eps; mod export; pub(crate) mod imagemap; mod load; -pub(crate) use load::MAX_RASTER_ALLOC; +pub(crate) use load::{MAX_RASTER_ALLOC, too_large_note}; mod metafile; mod native; pub mod pdf; diff --git a/crates/engine/src/cmd/links.rs b/crates/engine/src/cmd/links.rs index 831ca0dd..667d66da 100644 --- a/crates/engine/src/cmd/links.rs +++ b/crates/engine/src/cmd/links.rs @@ -75,7 +75,7 @@ pub fn specs() -> Vec { "Links", [], None, - "{show?: all (default)|missing|modified|embedded, sort?: name|kind (file format)|status (missing, modified, ok, embedded); default: stacking order; top first} every image object and placed document in the layers, as the Links panel lists them → {links: [{id, name, linked, status: ok|modified|missing|embedded, format, pixelWidth, pixelHeight, path?, found?: path (found away from its path), page?, preview?: true (showing the saved preview), document?: true (a placed document: pageWidth and pageHeight in pt instead of pixels)}], missing, modified, embedded}", + "{show?: all (default)|missing|modified|embedded, sort?: name|kind (file format)|status (missing, modified, ok, embedded); default: stacking order; top first} every image object and placed document in the layers, as the Links panel lists them → {links: [{id, name, linked, status: ok|modified|missing|embedded, format, pixelWidth, pixelHeight, path?, found?: path (found away from its path), page?, preview?: true (showing the saved preview), note?: said of an image too large to draw, document?: true (a placed document: pageWidth and pageHeight in pt instead of pixels)}], missing, modified, embedded}", has_doc, list ), @@ -1011,7 +1011,14 @@ fn list(s: &mut Session, p: &Value) -> Result { } let (link, mut row) = match &n.kind { NodeKind::Image(im) => { - (im.link.as_ref(), json!({ "format": format_label(&st.doc, im), "pixelWidth": im.width, "pixelHeight": im.height })) + let mut row = json!({ "format": format_label(&st.doc, im), "pixelWidth": im.width, "pixelHeight": im.height }); + // Only an image that could be past the decode allowance (at 16 bytes a pixel, the + // most a decoder gives) has its header read for this. + let large = im.width as u64 * im.height as u64 > fileio::MAX_RASTER_ALLOC / 16; + if let Some(note) = st.doc.images.get(&im.key).filter(|_| large).and_then(|b| fileio::too_large_note(&b.bytes)) { + row["note"] = json!(note); + } + (im.link.as_ref(), row) } NodeKind::PlacedDocument(p) => (Some(&p.link), document_row(p)), _ => continue, diff --git a/crates/engine/src/cmd/place/mod.rs b/crates/engine/src/cmd/place/mod.rs index 2cd31de4..6719dbed 100644 --- a/crates/engine/src/cmd/place/mod.rs +++ b/crates/engine/src/cmd/place/mod.rs @@ -183,7 +183,8 @@ fn load(p: &Value, cmd: &str, board: Option) -> Result { let (sx, sy) = pt_per_px(img.ppi); let natural = Rect::new(0.0, 0.0, img.width as f64 * sx, img.height as f64 * sy); let link = src.path.map(|path| super::links::link_info(path, &src.bytes)); - return Ok(Loaded { name, format, art: Art::Image(img), natural, warnings: vec![], link, board: None }); + let warnings = fileio::too_large_note(&img.blob.bytes).into_iter().collect(); + return Ok(Loaded { name, format, art: Art::Image(img), natural, warnings, link, board: None }); } let anchored = format.id == "dxf" && (opts.dxf.fit || !opts.dxf.center); let (mut doc, warnings) = match format.id { diff --git a/crates/engine/src/tests_linkspanel.rs b/crates/engine/src/tests_linkspanel.rs index 15809965..fc0de2a8 100644 --- a/crates/engine/src/tests_linkspanel.rs +++ b/crates/engine/src/tests_linkspanel.rs @@ -17,6 +17,38 @@ fn place_embedded(s: &mut Session, path: &str) -> NodeId { NodeId(r["ids"][0].as_u64().unwrap()) } +/// A PNG that says it is `w` × `h` 1-bit grey pixels (its header and no rows, which is all that +/// placing and listing read). +fn png_header(w: u32, h: u32) -> Vec { + let mut out = b"\x89PNG\r\n\x1a\n".to_vec(); + let ihdr: Vec = [&b"IHDR"[..], &w.to_be_bytes(), &h.to_be_bytes(), &[1, 0, 0, 0, 0]].concat(); + for chunk in [&ihdr[..], b"IDAT\x78\x9c", b"IEND"] { + out.extend((chunk.len() as u32 - 4).to_be_bytes()); + out.extend(chunk); + out.extend(crc32fast::hash(chunk).to_be_bytes()); + } + out +} + +/// An image past what a decode may take isn't drawn: Place and Open say so and the Links panel's +/// row carries the note, where the image used to be blank with no word (#1479). +#[test] +fn an_image_too_large_to_draw_is_reported() { + const NOTE: &str = "an image of 200000 × 200000 pixels is too large to draw"; + let dir = Folder::new("huge"); + let (big, small) = (dir.file("wall.png"), dir.file("small.png")); + write(&big, &png_header(200_000, 200_000)); + write(&small, &png(40, 20, BLUE)); + let mut s = session(); + assert_eq!(run(&mut s, "file.place", json!({"path": big, "at": [200, 150], "link": false}))["warnings"], json!([NOTE])); + assert_eq!(run(&mut s, "file.place", json!({"path": small, "at": [20, 20], "link": false}))["warnings"], json!([])); + let l = run(&mut s, "links.list", json!({})); + let notes: Vec<&Value> = l["links"].as_array().unwrap().iter().map(|r| &r["note"]).collect(); + assert_eq!(notes, [&Value::Null, &json!(NOTE)], "top first: the small image has nothing to say"); + // Opened as a document: said too. An image that decodes (16,000 px square) has no note. + assert_eq!(crate::cmd::fileio::load("wall.png", &png_header(200_000, 200_000)).unwrap().warnings, [NOTE]); + assert!(crate::cmd::fileio::load("fine.png", &png_header(16_000, 16_000)).unwrap().warnings.is_empty()); +} fn close(a: f64, b: f64) -> bool { (a - b).abs() < 1e-6 } diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 3cdd3e25..f74e232a 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4684,3 +4684,4 @@ mark-style Japanese Japonské @msg unknown parameter `{key}`; the command takes no parameters neznámý parametr `{key}`; příkaz nemá žádné parametry @msg unknown parameter `{key}`; the command takes: {keys} neznámý parametr `{key}`; příkaz přijímá: {keys} Load EPS into Clipboard Načíst EPS do schránky +@msg an image of {w} × {h} pixels is too large to draw obraz o velikosti {w} × {h} obrazových bodů je příliš velký na vykreslení diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 52929231..dd59b508 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4207,3 +4207,4 @@ theme Appearance Erscheinungsbild @msg unknown parameter `{key}`; the command takes no parameters unbekannter Parameter `{key}`; der Befehl hat keine Parameter @msg unknown parameter `{key}`; the command takes: {keys} unbekannter Parameter `{key}`; der Befehl akzeptiert: {keys} Load EPS into Clipboard EPS in die Zwischenablage laden +@msg an image of {w} × {h} pixels is too large to draw ein Bild mit {w} × {h} Pixeln ist zu groß, um gezeichnet zu werden diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d328e9dc..467763bf 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4624,3 +4624,4 @@ mark-style Japanese Japonesas @msg unknown parameter `{key}`; the command takes no parameters parámetro desconocido `{key}`; el comando no tiene parámetros @msg unknown parameter `{key}`; the command takes: {keys} parámetro desconocido `{key}`; el comando acepta: {keys} Load EPS into Clipboard Cargar EPS en el portapapeles +@msg an image of {w} × {h} pixels is too large to draw una imagen de {w} × {h} píxeles es demasiado grande para dibujarse diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index ea746ae5..1bbe6c4e 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4683,3 +4683,4 @@ gradient-kind Radial Radial mark-style Japanese Japonais theme Appearance Apparence Load EPS into Clipboard Charger un EPS dans le presse-papiers +@msg an image of {w} × {h} pixels is too large to draw une image de {w} × {h} pixels est trop grande pour être dessinée diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 97f989a4..8d200e38 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4650,3 +4650,4 @@ theme Appearance Kinézet @msg unknown parameter `{key}`; the command takes no parameters ismeretlen paraméter: `{key}`; a parancsnak nincs paramétere @msg unknown parameter `{key}`; the command takes: {keys} ismeretlen paraméter: `{key}`; a parancs ezeket fogadja el: {keys} Load EPS into Clipboard EPS betöltése a vágólapra +@msg an image of {w} × {h} pixels is too large to draw egy {w} × {h} pixeles kép túl nagy a kirajzoláshoz diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 2eb4bcae..831a25c8 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4252,3 +4252,4 @@ lighting Light Luce @msg unknown parameter `{key}`; the command takes no parameters parametro sconosciuto `{key}`; il comando non ha parametri @msg unknown parameter `{key}`; the command takes: {keys} parametro sconosciuto `{key}`; il comando accetta: {keys} Load EPS into Clipboard Carica EPS negli appunti +@msg an image of {w} × {h} pixels is too large to draw un'immagine di {w} × {h} pixel è troppo grande per essere disegnata diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 948e57fc..5a58ed6b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4246,3 +4246,4 @@ lighting Light ライト @msg unknown parameter `{key}`; the command takes no parameters `{key}` は不明なパラメーターです。このコマンドにパラメーターはありません @msg unknown parameter `{key}`; the command takes: {keys} `{key}` は不明なパラメーターです。このコマンドで使えるパラメーター: {keys} Load EPS into Clipboard EPS をクリップボードに読み込む +@msg an image of {w} × {h} pixels is too large to draw {w} × {h} ピクセルの画像は大きすぎて描画できません diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index be651886..51b3c826 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4216,3 +4216,4 @@ lighting Light 광원 @msg unknown parameter `{key}`; the command takes no parameters 알 수 없는 매개변수 `{key}`: 이 명령에는 매개변수가 없습니다 @msg unknown parameter `{key}`; the command takes: {keys} 알 수 없는 매개변수 `{key}`: 이 명령이 받는 매개변수: {keys} Load EPS into Clipboard EPS를 클립보드로 불러오기 +@msg an image of {w} × {h} pixels is too large to draw {w} × {h}픽셀 이미지는 너무 커서 그릴 수 없습니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 418685c3..e69defff 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4527,3 +4527,4 @@ mark-style Japanese Japońskie @msg unknown parameter `{key}`; the command takes no parameters nieznany parametr `{key}`; polecenie nie przyjmuje parametrów @msg unknown parameter `{key}`; the command takes: {keys} nieznany parametr `{key}`; polecenie przyjmuje: {keys} Load EPS into Clipboard Wczytaj EPS do schowka +@msg an image of {w} × {h} pixels is too large to draw obraz o wymiarach {w} × {h} pikseli jest zbyt duży, aby go narysować diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 20449dba..dd5983bc 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4248,3 +4248,4 @@ lighting Light Свет @msg unknown parameter `{key}`; the command takes no parameters неизвестный параметр `{key}`; у команды нет параметров @msg unknown parameter `{key}`; the command takes: {keys} неизвестный параметр `{key}`; команда принимает: {keys} Load EPS into Clipboard Загрузить EPS в буфер обмена +@msg an image of {w} × {h} pixels is too large to draw изображение размером {w} × {h} пикселей слишком велико для отрисовки diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 3b99f28d..a6abc016 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4232,3 +4232,4 @@ lighting Light Світло @msg unknown parameter `{key}`; the command takes no parameters невідомий параметр `{key}`; команда не має параметрів @msg unknown parameter `{key}`; the command takes: {keys} невідомий параметр `{key}`; команда приймає: {keys} Load EPS into Clipboard Завантажити EPS у буфер обміну +@msg an image of {w} × {h} pixels is too large to draw зображення розміром {w} × {h} пікселів завелике для відображення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index fc3792cc..fc1dbf0f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4234,3 +4234,4 @@ theme Appearance Giao diện @msg {status} ({e}) {status} ({e}) @msg {tool} isn't in a toolbar group of several tools {tool} không nằm trong nhóm thanh công cụ có nhiều công cụ Load EPS into Clipboard Tải EPS vào bảng tạm +@msg an image of {w} × {h} pixels is too large to draw ảnh {w} × {h} điểm ảnh quá lớn nên không thể vẽ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 4c740c7b..54d6595e 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3843,3 +3843,4 @@ lighting Light 光源 @msg unknown parameter `{key}`; the command takes no parameters 未知参数 `{key}`;该命令没有参数 @msg unknown parameter `{key}`; the command takes: {keys} 未知参数 `{key}`;该命令接受:{keys} Load EPS into Clipboard 将 EPS 载入剪贴板 +@msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的图像过大,无法绘制 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index f34c476f..302f0ac5 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3624,3 +3624,4 @@ lighting Light 光源 @msg unknown parameter `{key}`; the command takes no parameters 未知的參數 `{key}`;此指令沒有參數 @msg unknown parameter `{key}`; the command takes: {keys} 未知的參數 `{key}`;此指令接受:{keys} Load EPS into Clipboard 將 EPS 載入剪貼簿 +@msg an image of {w} × {h} pixels is too large to draw {w} × {h} 像素的影像過大,無法繪製 diff --git a/crates/ui-egui/src/panels/links.rs b/crates/ui-egui/src/panels/links.rs index 8ba4fb17..5250c854 100644 --- a/crates/ui-egui/src/panels/links.rs +++ b/crates/ui-egui/src/panels/links.rs @@ -219,11 +219,16 @@ fn badge(ui: &Ui, r: egui::Rect, status: &str) { fn status_tip(r: &Value) -> String { let path = r["found"].as_str().or(r["path"].as_str()).unwrap_or_default(); - match r["status"].as_str().unwrap_or_default() { + let tip = match r["status"].as_str().unwrap_or_default() { "missing" => crate::i18n::fmt(tl!("Missing: {path}"), &[("path", path)]), "modified" => crate::i18n::fmt(tl!("Modified since it was read: {path}"), &[("path", path)]), "embedded" => tl!("Embedded").into(), _ => path.to_string(), + }; + // An image too large to draw (`links.list`'s note). + match r["note"].as_str() { + Some(note) => format!("{tip}\n{}", crate::i18n::msg(note)), + None => tip, } } diff --git a/docs/mcp.md b/docs/mcp.md index d2044ed3..96296b36 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2193,7 +2193,8 @@ listed in `warnings`. Binary DXF and DWG files answer an error saying to save th `links.list {show?: all|missing|modified|embedded, sort?: name|kind|status}` lists every image in the layers, top first, as the Links panel does: `{id, name, linked, status: ok|modified|missing|embedded, format, pixelWidth, -pixelHeight, path?, found?, page?, preview?}`. `links.info {id?}` is the Link Info: `image.info`'s fields plus +pixelHeight, path?, found?, page?, preview?, note?}` (`note`: the image is too large to decode and isn't drawn; +`document.open` and `file.place` say the same in their `warnings`). `links.info {id?}` is the Link Info: `image.info`'s fields plus `status`, `format`, the file's `ppi` and the `effectivePpi`, `scale` (% of 100%), `rotation` (degrees, counter-clockwise), `placement` and, for a linked file, `fileName`, `location`, `fileSize`, `created`, `modified`. `links.goTo {id}` selects an image; `links.embed {ids?}` keeps linked files' pixels in the document (an image whose