A second adversarial round (with empirical reproduction against the
vendored image 0.25) found the budget contract still had holes:
- The probe billed 4 bytes/px, but 16-bit formats allocate their
native layout during decode (8/px for RGBA16 PNG; TIFF stacks ~3x
codec scratch on top). In-budget 16-bit images were falsely refused
with self-contradictory notes, and the decode transient could exceed
the budget the tiers claimed to enforce. The probe now reads the
color type alongside the dimensions and bills the real peak (native
layout + RGBA where they coexist, plus the TIFF codec factor), so
refusals are honest and per-platform-correct, and the TIFF allowance
follows the same accounting.
- GIF: the probe reads the LOGICAL SCREEN, but a crafted frame may
declare itself arbitrarily larger (empirically: a 10x10 probed
screen with an 8192x8192 frame — 320 MB and 29 s from a
probe-claimed-400-byte image). The GIF frame buffer is now capped
to the probed size plus slack; frame bombs refuse like any other
oversize.
- Remote bodies on web streamed incrementally with the 64 MB cap
enforced DURING the read (array_buffer() materialized a hostile
chunked reply in full before any check could fire), aborting and
cancelling the reader the moment the cap is crossed.
- Degenerate raster entities (zero/non-finite size or vectors) no
longer produce NaN placement math that aborted whole PDF exports —
they resolve to the broken-reference placeholder.
- Same-name resolution now has a deterministic winner (newest
registration, via insertion order) instead of HashMap iteration
order; URL query strings no longer break by-name matching; the
image pick matches its refusal note by the quoted file name
(substring collisions like a.tif/2011.tif attributed errors to the
wrong file); a dropped remote outcome at the queue cap triggers one
unconditional catch-up re-resolution instead of waiting for another
event; the unknown-size refusal says so instead of printing 0x0;
and the build script fails cleanly when there are no fonts to
verify.
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.
The last four big web-vs-desktop gaps.
XREFs: XATTACH reads the picked drawing's bytes and registers them
name-keyed (the image/PDF-underlay pattern), pre-parses on the web
worker, and the commit merge produces the same xref block flow as
native. Opened drawings resolve xrefs from the registry (trailing-name
match) before cache build; unresolved references produce ONE warning
listing the names and the fix — drop the referenced files on the page
(drops and picker opens now register their bytes) and reopen or
XRELOAD. XRELOAD works off the registry on unsaved drawings too.
Data Link & Extraction: spreadsheet links read picked xlsx/csv bytes
(calamine from slices / CSV from string) through a session registry,
so preview, refresh, and table-insert work on web; extraction takes
external drawings via a multi-file picker parsed on the worker, and
outputs CSV/TXT/.dxex via download_bytes. The disabled 'Add folder'
button explains why.
ARCHIVE/ETRANSMIT: assembles the drawing plus registered images, PDF
underlays, and session plot tables into a deterministic ZIP (minimal
writer over flate2 — no new dependency) delivered as
{stem}_transmit.zip; skipped dependencies are reported by name.
Save/autosave off the UI thread: the parse worker gains a serialize
action (protocol v5) — the document is prepared once on the main
thread (clone, styles, dimension bake), then the DWG/DXF writer pass,
the dominant cost, runs on the worker; any worker failure falls back
to inline serialization with the identical shared implementation.
The save path also clears the dirty flag only when the tab didn't
change mid-write, and the runtime-fields sidecar now carries
graphic_data, fixing a pre-existing loss of raw DWG round-trip bytes
on web-opened documents.
Printer enumeration used EnumPrinters level 4 only, which is served
from the local registry cache and misses Win11 per-user network
printers; an empty result now retries with level 2 (spooler RPC) and
merges the resolved default. The dialog's 'System default printer'
used to delegate to the shell print verb — the default PDF handler's
own printer choice, not the system default; it now resolves
GetDefaultPrinterW and dispatches printto naming that printer
explicitly. Every dispatch probes OpenPrinterW first, so a printer
that vanished between listing and printing produces a clear error
instead of ShellExecute's silent nothing, and all SE_ERR_* codes carry
readable reasons.
Legacy CTB: picking an unparseable table used to swallow the failure
(.ok()) — the user saw nothing at all. Load errors now reach the
command line, the picker accepts .stb alongside .ctb (the container
gains the STBVER1 header; .stb application errors state the actual
reason), and a table loaded from anywhere is copied into the user's
plot-styles folder so it persists in the dialog, matching AutoCAD.
Buffer parsing for both EnumPrinters layouts is unit-tested; the
missing-printer path was exercised against the real spooler (existence
probe only, no job).
STLOUT/STEPOUT wrote via std::fs and IMPORTOBJ read via std::fs, all
of which fail on wasm32. The export pickers now mirror the PDF path
(pick_*_owned returns a name-only PathBuf; the browser download names
the file) and the write tail swaps fs::write for sys::download_bytes.
OBJ import reads the picked file's bytes directly, like the web open
picker.
Opening by drag-and-drop was dead on the web build: winit never emits
FileDropped there. A wasm-gated io::web_drop module installs DOM
dragover/drop listeners, filters .dwg/.dxf, reads the File bytes
async, and queues them; the existing 50 ms PollWebControl tick drains
the queue into the same open path the picker uses (progress overlay,
recovery, OPFS recent cache).
Also floors the model-space plot sheet: extents smaller than 10 mm
(or the 0x0 paper a fresh unsaved drawing carries) now fall back to
the standard sheet instead of producing a ~3 pt page.
- pdf_export: drop the stale wasm32 gates (printpdf 0.9.1 compiles for
wasm; the memchr/lopdf incompatibility is gone with memchr 2.8.3).
Rendering is portable via render_pdf_bytes; only persistence differs:
atomic temp-file write on desktop, browser download on the web.
pick_pdf_path_owned on wasm returns a virtual path so the existing
message flow works unchanged.
- printpdf moves from the native-only dependency table to the shared one.
- print_to_printer: preview_page_pdf / preview_pages_pdf render the same
bytes and open them in a browser tab on the web (the browser's print
dialog takes over); desktop keeps the temp-PDF + OS-viewer flow.
- sys: open_bytes_in_new_tab (blob URL, popup-blocked falls back to a
download).
- New PlotQuality preset caps embedded raster resolution (High = native,
Normal = 300 DPI, Low = 150 DPI effective); vector output is untouched.
The image resource cache keys on the post-resample dims so one pixels
Arc placed at different effective DPIs embeds per-placement XObjects.
Include decoded IMAGE/OLE content in the shared PDF plotting path,
with clipping, transforms, transparency, draw order, and resource reuse.
Use named page content for export and printing.
Fixes#1266
Populate the plot-dialog printer list from the Windows spooler cache.
Validate record counts and UTF-16 name bounds before reading the buffer.
Refs #1261
- Enable fallback font metrics for HarfBuzz shaping in TTF glyph resolver
- Implement Unicode Bidirectional Algorithm (UBA) paragraph base direction detection
- Anchor right-to-left paragraphs to right container boundaries by default
- Reorder line atoms with base direction level to preserve neutral punctuation attachment
- Right-align RTL single-line TEXT and handle editor caret placement on RTL glyphs