Commit Graph
5439 Commits
Author SHA1 Message Date
aiongg 0fc62ec30b DES-10 review: package warnings in the file-format parity doc 2026-11-14 12:47:11 +00:00
aiongg 3bd9332a6c DES-10 review: export linked graphics in EPUB/HTML, PPTX, IDML and packages; cache output bytes in PDF export 2026-11-14 12:47:03 +00:00
aiongg 829e5b86c3 Merge remote-tracking branch 'upstream/main' into image-perf
# Conflicts:
#	crates/render/src/decode.rs
2026-11-14 12:47:03 +00:00
aiongg b8be148b4c DES-10 review: link status after open, preflight without reading originals
- Open queues every linked file's lookup (stat, then hash) for the background
  check instead of stating candidates on the UI thread.
- links.list, links.update and relinkFolder use the open's status only while
  its check is still running; afterwards the file is looked at as it is now.
- Preflight tells Modified from size, time and the remembered hash checks
  (the worker keeps them between passes) and the vector test from the mime
  type; it never loads a linked original.
- A modification time of 0 never vouches for a file's bytes.
- Migration keeps an unsettled time as UNVERIFIED_MTIME; recovery snapshots
  keep images not yet saved as links embedded.
- Links panel test fixture has real files, so its Embedded assertions hold.
2026-11-14 12:46:23 +00:00
aiongg b73c3dd87a Build: restore designcraft_format::write; render decode without the image crate (#2780)
* Build: restore designcraft_format::write for recovery; render decodes without naming the image crate

* Build: collapse the nested if in images tone (clippy)

* IDML export: placements in cells and footnotes embed their PDF layer choices

* Tests: Word fixtures carry the document relationships part

* PDF/X-4: untagged WebP images carry sRGB

* Images: CMYK TIFFs with other inks fall back to RGB; associated alpha keeps its inks

craft-image-codecs' TiffPage lists InkSet (storytold/craft-libs#1307), so decode_cmyk_tiff
again refuses InkSet 2 instead of reading other inks as C, M, Y and K. Associated alpha
is made straight by the decoder; the PDF tests check those inks and that alpha.

* PDF tests: CMYK fallback expects the screen's RGB; alpha images read the masked image

The RGB fallback for other inks converts through the working CMYK space, as decode_rgba
does, so the test takes its expected samples from decode_rgba. image_color_samples took the
first image stream, which for an image with alpha is its soft mask.

* Images: repin craft-image-codecs to the InkSet enum (storytold/craft-libs#1307)

* Pin craft-image-codecs to storytold/craft-libs ea1c842 (#1307)
2026-11-14 07:42:07 -05:00
aiongg 18e1ba14b3 DES-10 T11: docs for linked artwork and multi-image placement 2026-11-14 12:28:58 +00:00
aiongg 191489652c DES-10 T10: Links panel shows storage, status badges, proxy thumbnails; Embed, Unembed, Update; progress polling and migration notice 2026-11-14 12:28:44 +00:00
aiongg e3f14a8552 DES-10 T8: export, package and preflight handle linked assets
Export and package read a linked original from disk (hash-checked), a package copies it and keeps a link to the copy, preflight reports a missing link as an error and a modified one as a warning, and a linked alpha-wrap image opens without reading its original.
2026-11-14 11:58:21 +00:00
aiongg 9c66115fce DES-10: linked graphics from IDML, packages and file checks agree with T6 linking
IDML imports link the files they read through links::link_to_file, the
path File > Place takes, so a file read just after it was written keeps
an unverified fingerprint and its status comes from the hash instead of
reading as missing. File > Package and Links > Copy Links To point a
linked graphic's record (paths, unverified fingerprint) at its copy.
Regular-file metadata goes through io::file_metadata, which the file
guard accepts.
2026-11-14 11:56:59 +00:00
aiongg e8701e3f4c DES-10: pin craft-designcraft-format to storytold/craft-libs b1b4602 (#1264) 2026-11-14 07:13:44 +00:00
aiongg dadb2993e9 DES-10 T6: Links status by hash, update, embed and unembed for linked graphics
links.list gains storage; a linked graphic's status comes from the open
check while its bytes are unchanged, else from its files: OK by size and
time without reading, else by content hash; Modified or Missing. Update,
Relink and Relink to Folder read the file into a new linked version with
a new hash and proxy. Embed checks the file first and keeps the link
record; links.unembed links to the original when it holds the same
bytes, else writes them into dir. Not on the web.

(cherry picked from commit 8d617b872f7d7b01b9551d07a3e82be03e1c1362)
2026-11-14 06:46:11 +00:00
aiongg ade2d3cfd4 DES-10 T6: graphics placed from a file link to it by default
File > Place, the place cursor (place.load, grid, contact sheet), media,
media posters and table graphic cells record the file's absolute path,
its path relative to the document, size, time and content hash, and a
proxy image, and keep the bytes read for placing in the linked-bytes
store for the first draw. embed: true keeps the bytes in the document
with the link record. base64 bytes and the web always embed.

(cherry picked from commit 4f5bd99088b2f32bece3be08258ec66c15dd3b44)
2026-11-14 06:46:11 +00:00
aiongg 957e073664 DES-10 T9: IDML and INDD imports link the graphics they reference
(cherry picked from commit 995b248cd3b591cae6dd7dd0f9a1b10cac89f22d)
2026-11-14 06:45:09 +00:00
aiongg da524dec9b DES-10 T7: save, open, recovery and migration for linked assets
Save records each link's absolute path and its path relative to the
destination; linked assets write their record and proxy only, and Save a
Copy {embedLinks: true} copies every linked file in (failing on a missing
or changed one). On the web, links stay links with a warning.

Recovery snapshots record links as a save to the document's own file
would, never their originals; recovered documents resolve relative links
against the recorded document path.

Open (and Revert, Open Bytes, Recover) points each linked asset at its
file without reading it: a file with the recorded size and time is the
one; one with the recorded size and another time is hashed on a
cancellable background thread (native_links), absolute path first, and
the first hash match wins and keeps its new time. No match is Modified.
Status is kept on DocState::links for the Links panel; links.progress
applies results and gives the status-line notice once.

Documents saved before linked storage (embedded assets with a link and
no content hash), opened from a file, get a background byte comparison;
confirmed images get their hash and a proxy and save as links, and their
in-memory bytes become a link after that save. Changed or missing files
stay embedded.

Based on #1108 by ForestParks.

(cherry picked from commit 3a453b7f3752d255b6f74cfb55ea2459c5d9a074)
2026-11-14 06:25:01 +00:00
aiongg a53bf7203c DES-10 T7: AssetData::linked_under turns hash-checked embedded bytes into a link under their key
(cherry picked from commit e5599145c5cf80903e41ba74b505b2409bfd1249)
2026-11-14 06:24:45 +00:00
aiongg 3f1cbb4d23 Tests: Word fixtures carry the document relationships part
(cherry picked from commit 0ba9ad4fe5)
2026-11-14 06:23:07 +00:00
aiongg eafc04a2e1 IDML export: placements in cells and footnotes embed their PDF layer choices
(cherry picked from commit c017ca777c)
2026-11-14 06:23:07 +00:00
aiongg 7579d0d987 Place: test helper avoids unwrap; fmt 2026-11-14 06:22:51 +00:00
aiongg b6952941b3 Web: show a status line when a dropped file can't be read 2026-11-14 06:11:51 +00:00
aiongg 39cb508cca Place: a single dropped image is placed where it lands; several load the cursor 2026-11-14 06:11:51 +00:00
aiongg 7c34fce67c Docs: place.skip, place.cycle, and Undo leaving the cursor as it is 2026-11-14 05:56:51 +00:00
aiongg f024edb544 Place: arrow keys cycle the loaded images between drags (place.cycle) 2026-11-14 05:56:46 +00:00
aiongg 91f78b2db9 Place: Escape drops the current loaded image and loads the next (place.skip) 2026-11-14 05:54:59 +00:00
aiongg 7f0c2818f2 Place: closing a background document keeps the active document's place cursor 2026-11-14 05:50:35 +00:00
aiongg c8f105dd65 Merge pr-2308 (place contact sheet) into multi-place 2026-11-14 05:49:47 +00:00
aiongg 62f70cc03c Merge branch 'pr-2309' into multi-place 2026-11-14 05:49:14 +00:00
aiongg 2ee301bd4b Merge pr-2302 (place cursor queue) into multi-place 2026-11-14 05:49:14 +00:00
aiongg 4ad3038517 DES-10 T4: build linked assets and proxies from the T3 API; pin craft-libs 7df4cc9ab 2026-11-14 05:42:33 +00:00
aiongg e9a2712a5d DES-10 T4: native format 5 with linked assets and proxies 2026-11-14 05:37:57 +00:00
aiongg 47ae12fec3 DES-10 T3: render loads linked bytes on decode threads and draws proxies
Linked bytes load only for a level that isn't cached, on the decode thread
under GATE. A linked file that can't be had is remembered apart from failed
decodes until the link generation advances, and the asset's proxy is drawn
meanwhile. PDF layer variants keep their key across rebuilds while their
decodes are cached.
2026-11-14 05:37:37 +00:00
aiongg b9a5bcbecc DES-10 T3: linked asset store and the engine's file loader
AssetData::linked carries a LinkedSource (absolute and relative path, document,
size, fingerprint, content hash). Linked bytes load through a process-wide
single-flight LRU store (512 MiB, 1 GiB per file) and the Loader the engine
installs in Session:🆕 absolute path first, then relative; a changed
fingerprint is checked against the hash, and other bytes are Modified.
Proxy.data is an AssetData so its decodes have a key of their own.
2026-11-14 05:37:36 +00:00
aiongg dc43db30da Build: collapse the nested if in images tone (clippy)
(cherry picked from commit de33682242)
2026-11-14 05:15:29 +00:00
aiongg dffb04aed0 DES-10 T5: proxy previews for linked images
(cherry picked from commit 8e543fe81f2a5adbfe7aadd4e9b60a5b6fed4e2d)
2026-11-14 05:15:29 +00:00
aiongg 55a0c7da3d DES-10 T2: port asset byte users to AssetData
Export, import, links, preflight, alpha wrap and the UI read bytes with
AssetData::load; every asset is still embedded.
2026-11-14 05:15:03 +00:00
aiongg db16af3a15 DES-10 T2: render caches key decoded images by SourceKey
The image cache, layer variants, content boxes and tone cache key by
SourceKey instead of the bytes' address, so they no longer hold or
reserve the encoded bytes: decoded levels outlive them.
2026-11-14 05:15:03 +00:00
aiongg fc85dc9853 DES-10 T2: AssetData and SourceKey hold asset bytes in the document model
Asset.data becomes AssetData (embedded bytes or a linked length, each
under a process-unique SourceKey), with the link_relative, link_hash,
storage and proxy fields. AlphaOutline names its source by SourceKey.
Dedup in assets.rs compares embedded bytes as before.
2026-11-14 05:15:03 +00:00
aiongg e9f58f5fd0 Build: restore designcraft_format::write for recovery; render decodes without naming the image crate
(cherry picked from commit 078f5994c4)
2026-11-14 04:37:44 +00:00
aiongg 1a156fa564 Merge remote-tracking branch 'upstream/main' into image-perf
# Conflicts:
#	crates/images/src/jpeg.rs
#	crates/images/src/lib.rs
2026-11-14 04:29:08 +00:00
aiongg 7d9a3ad302 Canvas: an overlay of an image that fails to decode shows nothing 2026-11-14 04:00:52 +00:00
aiongg e5576e7ec2 Images: wake a turned-away decode once, keep colour and zoom state right
- A request turned away as recently decoded schedules its wake only for the
  first decode of that image, level and epoch: a view larger than the cache
  (or more failed images than are remembered) no longer decodes every 2 s.
- A worker that takes a job while a wake is scheduled hands the timed wait on.
- A full decode drawn at level 0 from the cache leaves the zoom-in list.
- Pixels decoded under colour settings changed since are not cached.
- Shadows and glows drawn while an image in them decodes are not kept.
2026-11-14 04:00:52 +00:00
aiongg 51b4a95cd9 Canvas, Pages: overlays and thumbnails don't wait for image decodes
The content-grabber ghost and the place-gun thumbnail take their proxy
with mip_now: a level not cached yet decodes in the background and they
draw another cached level, or the outline, meanwhile. A stand-in texture
is drawn again when the image generation advances.

Pages panel thumbnails render with defer_images, so they no longer run
warm on the UI thread or wait at the decode gate. A thumbnail that drew
images still decoding renders again once more have finished, at most
every 250 ms.
2026-11-14 03:49:26 +00:00
aiongg 0a97679647 Images: harden the background decodes
- A panic anywhere in a background job, sizing and the wait at the gate
  included, marks the image bad. A guard takes the job off the busy
  list however it ends, and a thread that ends is counted out so
  another starts.
- Background decodes reserve their bytes where mip chooses the decode:
  the reduced JPEG estimate before a reduced decode (a JPEG without one
  goes to the full decode), the full peak before a full decode, nothing
  when a cached level is halved.
- A request turned away as recently decoded schedules one generation
  advance for when its window has passed, so its stand-in doesn't stay
  until the view changes. Emptying the cache (new colour settings,
  clear) starts a new epoch, and decodes into the old cache no longer
  count as recent.
- At most two full decodes are kept for zooming in; a newer one evicts
  the oldest.
- warm's planning runs under catch_unwind; a panic skips warming.
- Renderer::images_pending says whether the last render drew an image
  still decoding.

Regression tests use a test-only fault hook that panics inside the job
and inside warm's planning.
2026-11-14 03:49:26 +00:00
Dylan M. Taylor 7107a64d6c Merge pull request #2766 from storytold/codex/designcraft-craftlibs-clean
Cut over document formats and codecs to craft-libs (#778)
2026-11-13 22:36:04 -05:00
Dylan M. Taylor 614feb8607 Merge remote-tracking branch 'origin/main' into codex/designcraft-craftlibs-clean 2026-11-14 03:36:00 +00:00
Dylan M. Taylor 382fc27a5b fix(idml,images,pptx,textimport): update adapters and tests for craft-libs cutover 2026-11-14 03:35:09 +00:00
aiongg eefdfbb688 Docs: image cache keeps the levels drawn; canvas decodes in the background 2026-11-14 03:32:18 +00:00
aiongg 28fdd9a50e Canvas: render again for finished image decodes in batches
The canvas renders with RenderOptions::defer_images and keeps the image
generation each texture was rendered at. Finished background decodes
are taken up at most every 250 ms and not while a render is in flight,
so a page of photos decoding one after another re-renders a few times
instead of once per photo, and a newer request doesn't drop the render
in flight before it is shown. The eyedropper still decodes the image it
samples.

Based on #2051 by texjer.
2026-11-14 03:32:08 +00:00
aiongg 9563e93bd2 Images: keep only the levels drawn and decode off the canvas thread
The image cache kept the full-resolution level 0 of every TIFF, PNG,
PSD and CMYK JPEG, and of any image shown at High Quality: about 48 MB
for a 12 MP photo, so after a dozen photos every edit evicted and
decoded them again on the render thread.

- mip keeps only the level drawn. It remembers each asset's full size
  (from its header, then from a decode), builds a coarser level from a
  finer cached one, and tries the reduced JPEG decode before a full one.
  When an image is zoomed into (a finer level asked for while a coarser
  one is cached) it keeps the full decode too, up to 96 MB, so the
  following steps halve it instead of decoding again.
- With RenderOptions::defer_images (the canvas) the image draw uses
  mip_now: a level that isn't cached is decoded on one of three
  background threads, newest request first, and a coarser cached level
  or a light box is drawn meanwhile. images::generation advances and
  the waker runs when a decode finishes. A decoder panic marks the
  image bad. Background decodes reserve their peak bytes at the same
  gate as warm, so they wait while decodes in flight hold half the
  budget.
- An image asked for again within 2 s of its decode (evicted at once:
  the view holds more than the cache) isn't decoded again until then,
  in place or in the background, so the canvas never decodes on its
  own thread.
- warm counts an asset as cached when the level each drawing takes, or
  a finer one, is cached, and sizes a job by the levels it keeps. It
  leaves a decode larger than the whole budget to the render, and the
  canvas skips it.
- A render that deferred an image isn't kept as the CMYK view snapshot.

Based on #2051 by texjer.
2026-11-14 03:32:00 +00:00
aiongg 445eb502ec Render: a PDF layer variant keeps its identity while anything holds it
The image cache keys decodes by the bytes' address, so a variant rebuilt
as a new Arc misses the decodes it already has. With decodes in the
background, more variants on screen than the variant cache kept would
decode and re-render without end. Variants are now looked up weakly,
with the 64 most recent held strongly.

Based on #2051 by texjer.
2026-11-14 03:29:47 +00:00
aiongg 34f0b702e5 Images: a decoder panic in a warm worker marks the image bad; checked level lookup 2026-11-14 03:15:24 +00:00