308 Commits
Author SHA1 Message Date
aiongg bcb0711a7c Merge remote-tracking branch 'upstream/main' into perf-main 2026-11-14 13:50:31 +00:00
aiongg 357c9dcd8c DES-10: exports check kept linked bytes against the file; PDF output cache only for links
- AssetData::load_for_output uses linked bytes already in memory only while
  the file still holds them (Loader::holds: fingerprint, else hash, else the
  kept bytes); a changed file is exported from disk with the warning.
- PDF export keeps output bytes per asset only for linked assets, and tells a
  placed PDF by its bytes (a proxy by the asset's type).
- links.list reports a link outside the automation read roots as missing
  while the open-time check still runs.
2026-11-14 13:40:37 +00:00
FredApps e230c4e406 Merge pull request #2751 from AlPaka1234/fix/2743-pdf-cmyk-profile
PDF export: attach working CMYK profile as /DefaultCMYK (#2743)
2026-11-14 14:37:17 +01: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 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
FredApps 11bd7d9c8d 2743: correct integrated imports and CI formatting
Remove duplicate flate2 imports introduced during integration and apply the three exact rustfmt corrections reported in main migration files. Hosted CI will validate per owner direction.
2026-11-14 13:27:46 +01:00
FredApps f07bf58d5b 2743: integrate DefaultCMYK export with shared PDF APIs
Resolve main dependency migration; retain flate2 production usage and route new PDF parsing through craft-pdf. Owner directs hosted CI validation for this integration; full local gate not run.
2026-11-14 13:17:29 +01: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 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
AlPaka1234 378c8c7ba8 PDF export: bound CmykScan work on self-referencing Form graphs (#2743) 2026-11-13 22:24:13 +01:00
AlPaka1234 13ccea15ca PDF export: address review feedback on DefaultCMYK attachment (#2743)
Do not use font.obj_id() for inline Type3 font resources so the enclosing container is never overwritten; inspect alternate/base spaces of Separation, DeviceN, Pattern, and nested Indexed spaces in uses_device_cmyk; scope scan memoization in CmykScan to each effective resource chain while keeping cycles bounded; return an explicit error when non-cyclic nesting exceeds MAX_DEPTH.
2026-11-13 21:20:37 +01:00
AlPaka1234 a905cb64e1 PDF export: attach working CMYK profile as /DefaultCMYK (#2743)
Fixes #2743.

Attach /DefaultCMYK [/ICCBased ...] referencing the CMYK working space profile on every page, inherited /Pages resource dict, Form XObject, SMask form, tiling pattern, and Type3 font resource scope outside PDF/X-1a and PDF/A-2b. Plain print exports and booklets embed one shared Flate-compressed /N 4 ICC profile stream without adding /OutputIntents or /Trapped; PDF/X-4 reuses the catalog GTS_PDFX /DestOutputProfile stream; RGB-blend documents attach /DefaultCMYK only when content or resources use DeviceCMYK.
2026-11-13 20:33:05 +01:00
Dylan M. Taylor 2769d0b88f Move DTD and raster encoding to craft-libs 2026-11-13 04:13:55 -05:00
Dylan M. Taylor 581ba450cd Delegate screen SVG parsing and rendering 2026-11-13 03:55:54 -05:00
Dylan M. Taylor 275b907c57 Use shared PDF SVG adapter and CSV sniffing 2026-11-13 03:43:21 -05:00
Dylan M. Taylor cc9156d8b2 Delegate PDF/X-1a finishing to craft-prepress 2026-11-13 03:15:13 -05:00
Dylan M. Taylor aff7435b81 Route file dispatch through shared format APIs 2026-11-13 01:34:50 -05:00
Dylan M. Taylor fee641be68 Fix current Rust clippy findings 2026-11-13 01:25:27 -05:00
Dylan M. Taylor 32348da073 Route PDF parsing and raster import through craft-libs 2026-11-13 01:19:41 -05:00
Dylan M. Taylor eb0ea78d9a Images: a WebP with an ICC chunk longer than the file is read without its profile
* Images: a WebP whose ICC chunk declares more than the file holds is read without it
2026-11-12 06:27:29 -05:00
aiongg 8ca6149325 Apply review fixes
- crates/pdf/src/gradient_inks.rs
2026-11-08 13:06:21 +00:00
Zinadin Zidan cd315908aa Preflight: a frame with states is looked at as PDF export draws it (review)
Flex Layout's convert turns a multi-state group into a frame that keeps
its states. PDF export draws every object pasted into a frame, but the
white overprint check walked only the active state, so a white
overprint in another state printed unreported. Which children export
draws is now one function, drawn_children, used by both.
2026-11-07 03:07:28 +06:00
Zinadin Zidan 231ee6d4c3 Preflight: white overprint is looked for where PDF export prints (review)
Ruby and kenten in a threaded story were reported against the story's
first frame, on its page, wherever the text was; they are now reported
against the frame that shows them, and text no frame has room for isn't.
Parent pages' items and anchored objects print too and weren't looked
at; items on a hidden or nonprintable layer, or in a hidden or
nonprinting group, don't print and were reported. The check now walks
the items as PDF export draws them, with its rules (the parent page
items a page shows now come from one function both use), and a frame
PDF export leaves out reports none of its ruby and kenten.
2026-11-07 02:09:42 +06:00
Hamza Naveed 4b63d26ac6 A hyperlink can point at a text anchor (#1446) (#1555)
* A hyperlink can point at a text anchor (#1446)

A frame or a text range could link to a URL, an email address or a page
number. A page number is the wrong thing to store for "this thumbnail links to
that photo": the photo moves and the link does not. InDesign stores the
destination, and IDML files do too — a `HyperlinkPageItemSource` pointing at a
`HyperlinkTextDestination` is how a report links its thumbnails to the
full-size pictures (#1446).

`HyperlinkDest::Anchor(id)` is a text anchor, the same destination a
cross-reference targets, and the PDF export resolves it the same way: through
the cross-reference index, to whichever page the anchor's text has landed on.
A link to an anchor that no longer exists is left out of the file rather than
pointing somewhere wrong.

`hyperlink.create` and `hyperlink.edit` take `anchor` — an anchor's id or its
name — and a link that is given no name of its own takes the anchor's.

Measured: a thumbnail on page 1 linked to an anchor in text on page 3 exports
`/Dest [<page 3> 0 R /XYZ 0 300 0]`; insert a page in front and the same link
exports page 4, with no edit to the link.

This is the groundwork for the IDML half of #1446 (import and export of
page-item and text hyperlinks), which is the next change and needs a
destination to put them in.

* An anchor a hyperlink cannot mean is refused, not ignored

This PR added anchors as link destinations and did not check that the anchor
exists, so the feature was wrong on its own and review was right not to take the
fix from a PR stacked above it.

`dest_param` returns `Result<Option<_>>` now. `None` still means no destination
was given, which `hyperlink.edit` needs for an edit that only renames; an
`anchor` that is given and names none is an error.

Two behaviours follow. An unknown *id* was accepted outright -- `as_u64`
answered for any number -- so a link was stored to an anchor nobody has and the
export quietly left it out. And on `hyperlink.edit` the `None` read as "no
destination given", so the command reported success and kept the old
destination, which is the case review reported.

An unknown *name* did already fail on `hyperlink.create`, but with "give `url`,
`email`, `page` or `anchor`", which named the wrong problem; it says "no text
anchor X (anchor.list names them)" now.

The test covers the id, the name, both on `edit`, that a refused edit leaves the
destination, the name and the exported page alone, and that a rename naming no
destination still works -- that last one is what `None` is for and it must not
have become an error on the way.

The page-number validation #1564 adds stays there; this is the anchor half,
which belongs with the anchor feature.

* A name two anchors share cannot pick one of them

`anchor.create` does not require an anchor's name to be unique, and an IDML
import can bring a document that already has two of the same. The lookup took
the first match, so a link made on a shared name went to whichever anchor came
first -- a different page, with nothing to say so (noted in review).

An id still names one anchor or none, and is checked as before. A name that two
anchors answer to is refused, with their ids in the message so the caller can
say which one they meant.

Not uniqueness at creation, which was the other way offered: a document can
already hold duplicates, so enforcing it there would refuse to open files that
are fine today, and the ambiguity only matters where a name is being resolved.

The test builds two anchors of one name on different pages, asserts both
`hyperlink.create` and `hyperlink.edit` refuse it and say what to do instead,
that the refused create made no link and the refused edit left the destination
alone, and that each anchor is still reachable by its id. It is a test of its
own because the twin anchor outlives the deletion the other test ends with.

* Test: two anchors sharing a name come from a file, now that anchor.create refuses a name in use

main's anchor.create now refuses a second anchor of a name, so the test makes the first and renames the second in the document, as a file opened with two can hold them. The comment on the guard says the same.
2026-11-05 22:24:10 +01:00
Hamza Naveed 59501a26f9 An item a parent overrode and deleted is gone from the pages that use it (#1469) (#1558)
* An item a parent overrode and deleted is gone from the pages that use it (#1469)

In InDesign, removing an inherited item from a parent page means overriding
it there and deleting the override: the parent's page keeps the item in its
override list with no copy of its own. Only the page being drawn was checked,
so the pages that use that parent still drew the base's item -- a signature
block from parent B over the notice text of every page using parent E.

The override lists of the whole parent chain are checked, not just the page's
own. An override always names an item of a parent further back, so one set
covers the chain.

Rebased onto the post-split main: the parent-item loop moved from render's
lib.rs to draw.rs and the tests to tests.rs.

One thing this does not touch: blendspace.rs checks `page.overridden` the same
way in two places, for deciding which spreads hold transparency. A deleted
override there would count an absent item as present, which can only cost an
unnecessary flatten rather than draw the wrong thing, so it is left for a
maintainer to judge rather than widened into this fix.

* Parent items: one set of overridden items for drawing, PDF and PPTX export, hit-testing, snapping and frame edges

* Override All: an item a parent removed is not brought back as a copy
2026-11-05 22:22:47 +01:00
Dylan M. Taylor b46ee47982 pdf: placements of one media asset share one embedded file (#2266) 2026-11-03 23:07:05 -05:00
Dylan M. Taylor 7faf7029fb fix(pdf): indexed membership in page ranges, optional content and PDF/X scans (#2207) 2026-11-03 22:32:10 -05:00
NetterSilverandClaude Opus 5.5 52232fc2c7 Read PDF/X output intent and Info as parsed objects in validate-pdf
Fixes #2050

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:47:33 +01:00
Brandon Thomas adee832ad1 Merge pull request #1975 from VoiceLessQ/fix/1586-image-decode-cap
Images: refuse a raster that would decode past 1 GiB of RGBA (fixes #1586)
2026-10-31 01:00:26 -04:00
aiongg 89ef0e3d81 Merge PR #1606: Paragraph Shading and Border pages
crates/ui-egui/src/panels/properties.rs, properties/object.rs: main's split module kept; preview_row and paint_corner_shape exported for the shading corner menus
crates/ui-egui/src/panels/properties/tests.rs: both sides' tests kept
crates/ui-egui/src/dialogs/tests.rs: main's tests kept; the border test uses the new corner labels; the shading layout test added
crates/ui-egui/src/i18n/*.rs: union of keys without duplicates
crates/ui-egui/src/dialogs.rs: the corner shape face uses corner_shape_button (no hard-coded colour in the dialog)
2026-10-30 23:17:40 +00:00
Dylan M. Taylor 15d13dde20 Update prompt shows the app icon and accent color instead of a letter (#2023)
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#7bb51c, its colour in ArtCraft Launcher). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).

craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/engine/src/cmd/export/text_tests.rs
- crates/pdf/src/gradient_inks.rs
2026-10-30 01:28:06 -04:00
VoiceLessQ b2d8d8809a Merge storytold/main into PR #1975 2026-10-30 00:42:27 -02:00
Brandon Thomas 56decd96e2 Merge storytold/main into PR #1990 2026-10-29 21:55:23 -04:00
Brandon Thomas e22e6038dd Merge storytold/main into PR #1990
# Conflicts:
#	docs/typography-parity.md
2026-10-29 21:53:38 -04:00
Brandon Thomas e5b30c4527 Merge storytold/main into PR #1238 2026-10-29 21:53:38 -04:00
Brandon Thomas 2a3f92e63b Merge pull request #1919 from Silvertree2010/fix/1881-form-fields-export-with-a-stroke
Export form field borders from the item stroke
2026-10-29 21:53:21 -04:00
Brandon Thomas 45369565ad Merge pull request #1872 from Hamza0101/fix/pdf-name-escaping
PDF export: a name escapes every byte a name cannot hold (#1582)
2026-10-29 21:52:30 -04:00
Brandon Thomas 61adb2063e Merge pull request #1306 from Hamza0101/reproducible-pdf-export
PDF export: SOURCE_DATE_EPOCH stands in for the clock, so exports repeat
2026-10-29 21:52:12 -04:00
Brandon ThomasandClaude Opus 5.5 eace4735da Draw the checkbox border in its own function
Keeps add_fields under the cognitive complexity limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:43:49 -04:00
Brandon ThomasandClaude Opus 5.5 680dfdc964 Format the checkbox border
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:41:41 -04:00
Brandon ThomasandClaude Opus 5.5 56eebe9556 Keep the host frame's text area across anchored text frames in PDF export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:40:24 -04:00
Brandon ThomasandClaude Opus 5.5 44b07403b1 Draw a checkbox's border at the item's stroke weight
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:40:04 -04:00
moppeeer 165d8c65ac Merge remote-tracking branch 'origin/main' into paragraph-shading-indesign
# Conflicts:
#	ROADMAP.md
#	crates/doc/src/attrs.rs
#	crates/idml/src/export.rs
#	crates/idml/src/import.rs
#	crates/ui-egui/src/dialogs.rs
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/panels/properties.rs
2026-10-29 11:50:25 +01:00
KnottyDyesandClaude Opus 5.5 d7bc6633e7 Compose: honor discretionary hyphens in single-line composition
Recognize U+00AD independently of automatic hyphenation in the greedy breaker, reusing the existing visible hyphen output. Cover both composers, zero-width unbroken soft hyphens, pattern suppression, and tagged and untagged PDF text extraction.

Fixes #1955

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:14:44 -06:00
VoiceLessQ 033fc2e17e PDF: count recompress decodes in the oversized-image test (#1586)
The test for a raster past the RGBA budget showed that recompress
skips the decode only through a one-second limit, which a slow CI
runner could fail and a fast machine could pass without the fix.
recompress now counts its decodes in a test-only per-thread counter,
and a unit test checks that the 16,385 px PNG doesn't reach the
decode while a 9 px one does. The export test keeps its output
checks and drops the time limit.
2026-10-29 00:12:33 -02:00
VoiceLessQ d5f07d30eb PDF: check the RGBA budget before recompressing an image (#1586)
With compressImages on, PDF export re-encodes rasters as JPEG in
recompress, which decoded with the image crate directly. A gray PNG
that decode_rgba refuses still decoded there, was copied and encoded
as JPEG, and only then dropped: krilla refuses a JPEG wider or taller
than 16,384 px, and every raster past the budget is. Exporting the
23,000 px PNG that way took 4.2 s and 1 GB more memory, for a PDF
without the image.

recompress now asks too_large_to_decode first, the check decode_rgba
uses, so the export skips the image with the same warning at once.
2026-10-28 23:28:11 -02:00
Claude 5c6909db46 Merge storytold/main (v0.14.0) into inline-objects-under-text
The PDF change moves to export/walk.rs (draw_leaf_content passes the drawn flag back) and the
render test to render/src/tests.rs, with upstream's splits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-28 18:03:03 +00:00
Hamza 87b49d9973 PDF export: SOURCE_DATE_EPOCH stands in for the clock, so exports repeat
A PDF export recorded the second it was made in, so exporting one document
twice gave different files although the pages matched -- and the instance id
krilla derives from the bytes followed the timestamp, so nothing about the two
files was comparable.

SOURCE_DATE_EPOCH stands in for the clock when the export is given no date, as
the reproducible-builds specification defines it. A date passed in still wins,
and a value the PDF date format cannot hold -- malformed, negative, or past
the range -- is ignored rather than refusing the export or writing a date no
reader can parse. With the epoch pinned the two files are identical byte for
byte.

Rebased onto the post-split main: export_time and pinned_or live in
export/metadata.rs beside now_unix, which they stand in front of, and the call
site is in export/pipeline.rs. The ROADMAP keeps upstream's 2026-10-17 entry
with this one above it.
2026-10-28 19:09:25 +05:00
Dylan M. Taylor 07366af1b6 Refactor pdf for strict Clippy checks 2026-10-28 11:39:26 +00:00