forked from 1b-agent/opencadstudio-upstream
feat: bounded huge-image decoding, webp/gif, remote rasters, Georgian/Armenian fonts
Giant rasters (500 MB aerial/GIS TIFFs) decoded whole: multiple GB of
RGBA on a wasm heap is tab death, and even desktop silently failed
past the image crate's 512 MiB default allocation cap. Decoding now
probes the header for dimensions first and runs in tiers — full decode
under a per-target budget (512 MB wasm / 4 GB native, enforced through
the decoder's allocation limits so oversize fails fast instead of
ballooning), Lanczos3 downsample to a display cap (4096² wasm /
8192² native) when the decode fits but the buffer is oversized, and a
clear localized refusal naming the dimensions and the limit when it
cannot. The probed source dimensions stay the entity's sizing truth,
so a downsampled buffer never shrinks the on-paper footprint; PDF
export self-corrects (effective DPI derives from placement × stored
pixels). Refusals surface once per image through the pick, open, and
drop/re-resolve paths.
Decode coverage gains webp and gif (pure Rust in image 0.25 — no C,
no lockfile churn), with the attach picker and drop filters matching.
Remote http(s) raster references now fetch on web: CORS-classified
failures name CORS, HTTP errors name the status, bodies over 64 MB and
8 s deadlines are bounded, and fetched bytes join the session source
registry (so ARCHIVE can package them and re-resolution reads cache).
Georgian and Armenian Noto subsets ship (generation blocks included),
so those scripts render instead of skipping; scripts still without
subsets remain silently skipped, unchanged.
(cherry picked from commit ca0d7b96)
Ported onto main (#1717 review): Armenian and Georgian take the script
indices after Lao, which main added meanwhile.
This commit is contained in:
1 parent
8a5d343927
commit
93e97bdf18
31 files changed
+840
-39
No files matched your search
+16
-4
@@ -2012,12 +2012,16 @@ pub async fn pick_plot_style() -> Result<Option<plot_style::PlotStyleTable>, Str
|
||||
/// mirroring the PDF-underlay pick. Decoding then comes from those bytes, and
|
||||
/// the entity the IMAGE command commits resolves through the same registry
|
||||
/// instead of a `image::open` on a path that only exists natively.
|
||||
///
|
||||
/// The filter names exactly the formats the `image` crate features build
|
||||
/// (Cargo.toml) — png/jpeg/bmp/tiff/webp/gif — and `io::web_drop` accepts the
|
||||
/// same set for drops.
|
||||
pub async fn pick_image_file() -> Result<(PathBuf, u32, u32), String> {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Select Image File").as_ref())
|
||||
.add_filter(
|
||||
crate::t!("Images").as_ref(),
|
||||
&["png", "jpg", "jpeg", "bmp", "tiff", "tif"],
|
||||
&["png", "jpg", "jpeg", "bmp", "tiff", "tif", "webp", "gif"],
|
||||
)
|
||||
.add_filter(crate::t!("PNG").as_ref(), &["png"])
|
||||
.add_filter(crate::t!("JPEG").as_ref(), &["jpg", "jpeg"])
|
||||
@@ -2033,9 +2037,17 @@ pub async fn pick_image_file() -> Result<(PathBuf, u32, u32), String> {
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| reference.clone());
|
||||
crate::scene::model::image_model::register_source(&reference, bytes);
|
||||
let decoded = crate::scene::model::image_model::resolve_image(&reference)
|
||||
.ok_or_else(|| format!("Unsupported or corrupt image \"{short}\""))?;
|
||||
Ok((path, decoded.width, decoded.height))
|
||||
let decoded = crate::scene::model::image_model::resolve_image(&reference).ok_or_else(|| {
|
||||
// An over-budget decode refusal explains itself (dims + limit);
|
||||
// anything else stays the generic unsupported/corrupt message.
|
||||
crate::scene::model::image_model::take_decode_refusals()
|
||||
.pop()
|
||||
.unwrap_or_else(|| format!("Unsupported or corrupt image \"{short}\""))
|
||||
})?;
|
||||
// Entity sizing truth: the file's ORIGINAL pixel dimensions, not the
|
||||
// display-capped buffer's, so a downsampled aerial still places at its
|
||||
// true aspect and default size (the cap is a rendering detail).
|
||||
Ok((path, decoded.source_width, decoded.source_height))
|
||||
}
|
||||
|
||||
/// Pick an image file and prepare it for embedding (IMAGEEMBED). The bytes
|
||||
|
||||
+30
-4
@@ -278,8 +278,8 @@ fn is_drawing_name(name: &str) -> bool {
|
||||
}
|
||||
|
||||
/// The decodable raster set matches the IMAGE picker's filter (and the `image`
|
||||
/// crate features the app builds with) — anything else would queue bytes that
|
||||
/// can only ever fail to decode.
|
||||
/// crate features the app builds with: png/jpeg/bmp/tiff/webp/gif) — anything
|
||||
/// else would queue bytes that can only ever fail to decode.
|
||||
fn is_image_name(name: &str) -> bool {
|
||||
matches!(
|
||||
name.rsplit('.')
|
||||
@@ -287,13 +287,13 @@ fn is_image_name(name: &str) -> bool {
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase()
|
||||
.as_str(),
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tiff" | "tif"
|
||||
"png" | "jpg" | "jpeg" | "bmp" | "tiff" | "tif" | "webp" | "gif"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_drawing_name;
|
||||
use super::{is_drawing_name, is_image_name};
|
||||
|
||||
#[test]
|
||||
fn drawing_names_use_the_worker_convention() {
|
||||
@@ -306,4 +306,30 @@ mod tests {
|
||||
assert!(!is_drawing_name("plan.dwg.txt"));
|
||||
assert!(!is_drawing_name(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_names_match_the_picker_filter() {
|
||||
for name in [
|
||||
"site.png",
|
||||
"photo.JPG",
|
||||
"scan.Jpeg",
|
||||
"tile.bmp",
|
||||
"map.tiff",
|
||||
"map.TIF",
|
||||
"hero.webp",
|
||||
"icon.WEBP",
|
||||
"anim.gif",
|
||||
"ANIM.GIF",
|
||||
] {
|
||||
assert!(is_image_name(name), "{name} should be accepted");
|
||||
}
|
||||
// Formats the `image` features do not decode must not queue bytes.
|
||||
for name in ["clip.avif", "pix.heic", "raw.svg", "logo.ico", "noext"] {
|
||||
assert!(!is_image_name(name), "{name} should be rejected");
|
||||
}
|
||||
// A dot-less uppercase name is its own extension, mirroring the
|
||||
// drawing-name convention above.
|
||||
assert!(is_image_name("WEBP"));
|
||||
assert!(is_image_name("GIF"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user