Commit Graph
108 Commits
Author SHA1 Message Date
skylar taylor-barrick 80d57189b8 archive: xml scan requires the <! prefix, skips DTD PIs, refuses UTF-16
Review findings, all reproduced before this fix:
- opens_doctype matched the keyword without requiring <! first, so the
  valid element <aDOCTYPE> was treated as a declaration: depth-capped
  documents passed under DeclarationOnly and ordinary elements were
  refused under Refuse.
- A processing instruction inside an internal subset was not skipped,
  so a ] inside it ended the subset measurement early and the rest of
  the subset escaped the byte cap.
- A UTF-16 document was silently admitted: the scan saw no ASCII
  markup at all, so a refused DTD passed. A UTF-16 BOM now returns
  UnsupportedEncoding; the caller decodes to UTF-8 first.

Each has a regression test; workspace tests, clippy -D warnings and
fmt pass.
2026-10-14 15:34:52 -06:00
skylar taylor-barrick 1fea3964a2 archive: add craft-archive parsing budgets for ZIP and XML
One small crate for the checks that must run before an archive or XML parser
touches a file the app did not write, following craft-libs#126.

- Budget/Exceeded: a named limit and a running total of real bytes, charged
  before allocating, reporting the name, limit and request on a breach.
- zip::preflight: allocation-free validation of the end records and the
  central directory before ZipArchive::new indexes every entry. Exactly one
  end record must span the end of the file (ambiguous records are refused);
  the ZIP64 record and locator are checked against each other; entry count,
  directory size and name lengths are limited; the directory must fill its
  declared size and reach the end records. Single-disk only; malformed input
  is an error, never a panic.
- zip::read_entry: bounds one entry against the per-entry limit, a fixed
  preallocation and the total budget, charges only bytes actually read, and
  reports a lying declared size or a decompression bomb as an overrun.
- xml::check: one iterative linear pass that measures nesting (comments,
  CDATA, processing instructions and quoted values cannot fake a tag) and
  applies a DTD policy, so a recursive parser never sees input that would
  exhaust the stack. xml::Depth does the same for readers that count as they
  parse.

Zero dependencies, no unsafe, no numbers of the crate's own. Tests cover bombs,
lying declared sizes, entry-count and directory-size caps, lying EOCD counts,
truncation, ZIP64 variants, DTD policies, quote/comment/CDATA confusion, the
depth boundary and a 250k-deep hostile document.
2026-10-14 15:02:49 -06:00
skylar taylor-barrick 457a078809 Merge pull request #42: qualify native Windows printing
Validate native Windows submission and share RGB printing
2026-10-13 22:38:03 -06:00
skylartaylor 8b8576f0b0 Use fixed-size chunks in the CMS total-ink regression 2026-10-14 06:27:12 +02:00
skylartaylor 21e297f440 Merge commit '3d37da389a3618ab9b76109da11416e9ca4ea719' into codex/final-merge-native-libs-20261014-root 2026-10-14 06:16:39 +02:00
skylar taylor-barrick e9546e191d Merge pull request #43 from storytold/codex/atomic-normal-permissions-20261013
craft-atomic: default to normal file permissions with explicit private writes
2026-10-12 23:38:37 -06:00
skylar taylor-barrick 278d276ae9 Merge pull request #39 from aiongg/craft-cms
Add craft-cms: ICC colour management from PhotoCraft, with a FOGRA39-fitted CMYK profile
2026-10-12 23:04:29 -06:00
aiongg 9e2b2b03d8 craft-cms: separate exact profile-name matches from stand-ins
Builtin::from_profile_name matches only a built-in's own description.
Builtin::stand_in_for adds the FOGRA39 approximations (Coated FOGRA39, ISO
Coated v2) for documents that name a profile without carrying its bytes.
ISO Coated v2 300% (ECI) has no stand-in: the built-in separates to up to
330 % total ink. The README example parses the document's ICC bytes first
and handles a missing stand-in explicitly.
2026-10-13 03:22:39 +00:00
aiongg d282bd06c6 craft-cms: reference-value regression cases, including the reported DesignCraft orange
The reference value for CMYK 0/41/90/4 in Coated FOGRA39 is 239,161,35.
The fitted profile gives 239,161,33 (ΔE00 0.16); the uncalibrated formula
DesignCraft used gives 245,144,25 (ΔE00 6.36). Six more reference values
(K50 grey, rich black, light tint, skin tone, C+Y green, near-neutral)
are within 0.7 ΔE00.
2026-10-13 03:17:20 +00:00
skylar taylor-barrick 3d37da389a Merge current core print progress while retaining submission guards 2026-10-13 05:03:53 +02:00
skylar taylor-barrick d873514e8a Document driver option refusal and equivalent grayscale capability 2026-10-13 04:50:19 +02:00
skylar taylor-barrick 3929c3d14c Refuse explicit print options dropped by Windows drivers 2026-10-13 04:48:42 +02:00
skylartaylor b55b21cdeb Apply permission policy review before first release 2026-10-13 04:33:01 +02:00
aiongg 7f05840bc0 craft-cms: portable moxcms CMYK oracle with the built-in Coated FOGRA39
The Generic CMYK oracle only runs where macOS ships that profile. The
same comparison (CMYK -> sRGB, 8-bit, relative colorimetric without
BPC, same bytes for both engines) with the built-in Coated FOGRA39
runs everywhere. Measured: max 10, p99 5, mean 0.89 codes; the worst
pixel matches an independent float computation, not moxcms.
2026-10-13 01:50:14 +00:00
aiongg 1f0d815477 craft-cms: map device white to device white on every path
With a LUT destination, sRGB white came out as 0.3-0.5 % C/M/Y through
Transform::eval and precise float conversions: the legacy Lab encoding
puts L* 100 at 0.996 and a* = 0 at 0.50001, between the BToA grid
nodes, and fix_white only patched the device-link node, so only the
table paths were right.

Transform: for every intent but absolute colorimetric (and proofs
without paper simulation), a pixel equal to the source's device white
gives exactly the destination's device white. fix_white still sets the
device-link node; the paths that do not read that node exactly (the
exact pipeline, float interpolation, and 8-bit lookups when white is no
node) compare each pixel with white once. No other colour changes.
device_white(Lab) had a* = b* = 0.5; Lab white is 128/255 in the v4
encoding, which is no node of the 33-point grid, so Lab sources take
the per-pixel check there too.

Profile: synth::CmykParams::white_on_node maps no ink to exactly
L* 100, a* = b* = 0 in the AToB and gives the BToA tables input curves
that put that white on a node holding no ink. The Coated FOGRA39
profile is regenerated with it and separates PCS white to 2e-5 ink
(was 0.5 %), so the step into white is as smooth as its neighbours.
The default parameters and the Photocraft profile are unchanged; its
white still falls between nodes, so its last grey step into white is
up to about 0.4 % C larger than the one before, as with lcms's
white-node fix, and the test allows that for this profile.

FOGRA39 accuracy against 16a66e1: AToB against the patches unchanged
(mean 0.07, p95 0.18, max 0.53 ΔE00), reference sRGB values unchanged
(max 0.76), Lab -> CMYK -> Lab p95 1.04 -> 1.07, max 2.54 -> 2.55
(mean 0.24).
2026-10-13 01:50:14 +00:00
aiongg d0107d1130 craft-cms: return errors instead of panicking on caller input
The crate denies panics, but asserts and slice indexing were reachable
from arguments: pixel strides, buffers that are not whole pixels or a
destination shorter than the source, single-colour slices shorter than
the channel count, CLUT and pipeline dimensions, and LUT sizes.

Every such check now returns CmsError::InvalidArgument. Buffer
conversions validate once per call, before any pixel is written, so the
per-pixel loops carry no extra checks; internal callers that already
guarantee lengths use crate-private unchecked evaluation.

Pipeline, Clut and Lut3d keep their fields private (read through
accessors) so a validated value cannot be changed into one that panics.
Pipeline::new checks matrix shapes and channel counts; Clut::new and
Clut::sample check grid, outputs and the table size limit.

API changes (unreleased crate):
- Transform::eval, eval_fast, convert_u8/u16/f32, apply,
  convert_bytes_many and from_pipeline return Result.
- Pipeline::new, extend and eval, Clut::new, sample, eval,
  eval_trilinear and eval_multilinear return Result.
- GamutCheck::delta_e and out_of_gamut, Lut3d::from_transform,
  LutFile::identity and from_fn, synth::cmyk_profile and coated_cmyk
  return Result.
- Pipeline, Clut and Lut3d fields are read through methods.
2026-10-13 01:33:07 +00:00
skylartaylor 6ba4d0b97a Refuse annotation visibility the native renderer cannot print correctly 2026-10-13 03:29:55 +02:00
aiongg f97ed953f4 craft-cms: built-in Coated FOGRA39 profile fitted to Fogra's data
Add Builtin::CoatedFogra39 ("Coated FOGRA39 (fitted, craft-cms)",
profiles/craft-coated-fogra39.icc: AToB 11^4, BToA 33^3, 330% TAC,
medium GCR). It is fitted to Fogra's FOGRA39 characterisation data
(tests/data/FOGRA39.txt, unmodified, 1617 patches): a Yule-Nielsen
Neugebauer model with free overprints, per-ink tone curves and n, plus
a Gaussian-kernel ridge correction of the residuals. Against the
patches, relative colorimetric through the AToB: mean dE00 0.07,
p95 0.18, max 0.53. The BToA tables invert the AToB table
(synth::cmyk_profile_from_model); the Photocraft coated profile and its
synth defaults are unchanged.

Builtin::from_profile_name maps the names documents carry for
FOGRA39-based profiles (Coated FOGRA39, ISO Coated v2 (ECI)) to the new
profile and every built-in by its description. Adds math::delta_e2000.

Refs #36
2026-10-13 01:23:45 +00:00
+2 dd9e3f1bc3 Add craft-cms: ICC colour management extracted from PhotoCraft
Move PhotoCraft's crates/cms (storytold/photocraft@b3222ee8e7)
into crates/cms as craft-cms: ICC v2/v4 profile parsing and writing, transforms, rendering
intents, black point compensation, soft proofing, gamut checks, synthetic CMYK, 3D LUTs, LUT
files and built-in profiles, with all tests.

The Bradford inverse and the built-in RGB colorant matrices are computed in constants, so the
build rejects a singular matrix and the two expect calls are gone; the values are bit-identical
to the runtime results. Built-in profile descriptions and copyright text keep their PhotoCraft
wording so the profile bytes do not change. rayon joins the workspace dependencies.

Refs #36

Co-authored-by: Alex Gorevski <agorevski@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Elvis33LE <67311580+Elvis33LE@users.noreply.github.com>
Co-authored-by: F4LCON <107556180+itsF4LCON@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: HEOJUNFO <100739943+HEOJUNFO@users.noreply.github.com>
Co-authored-by: Shehan Kavishka <kavishkashehan726@gmail.com>
Co-authored-by: Valentin Klein <vangel1976@googlemail.com>
Co-authored-by: ljkx <177671075+ljkx@users.noreply.github.com>
Co-authored-by: storybored379 <storybored379@gmail.com>
Co-authored-by: wstock <w.stock@yahoo.com>
2026-10-13 01:23:45 +00:00
skylartaylor ee70afc29a Pin driver copy-validity qualification fix 2026-10-13 02:49:34 +02:00
skylartaylor b9f475471a Support normal new-file permissions with private atomic staging 2026-10-13 02:26:24 +02:00
skylartaylor 8fb89562d1 Expose shared safe driver settings and RGB print transport 2026-10-13 02:15:46 +02:00
skylar taylor-barrick 666c358065 Merge pull request #27 from TKIPisalegacycipher/windows-print-progress-listing
craft-print: native per-sheet processing progress
2026-10-12 18:12:53 -06:00
skylartaylor ccb1eae613 fix(print): refuse fitting that would discard annotations 2026-10-13 01:47:19 +02:00
skylartaylor 4af92ad6aa feat(print): bridge native page processing to submitter callbacks 2026-10-13 01:20:21 +02:00
skylartaylorandTKIPisalegacycipher ebc4d8cfbd Add caller-thread progress for native sheet processing
Co-authored-by: TKIPisalegacycipher <git@chir.us>
2026-10-13 01:20:21 +02:00
skylar taylor-barrick f03ec27ab2 Merge pull request #28 from jcl-bit/claude/print-native-windows-73c826
craft-print: print on Windows in-process, without PowerShell
2026-10-12 17:19:38 -06:00
skylartaylor eb49a247a3 Gate Unix-only atomic test imports by platform 2026-10-13 00:29:22 +02:00
skylar taylor-barrick 79e89dc129 Merge remote-tracking branch 'origin/main' into codex/craft-print-fix-merge-20261012T210558Z
# Conflicts:
#	Cargo.lock
2026-10-12 23:49:49 +02:00
Alejandro Londono 6006abb757 feat(settings): add shared config storage for Craft apps
Add shared config.toml storage while keeping settings types, defaults and UI policy in each app.

Includes isolated in-memory operation, configurable portable paths, atomic saves with private backups, damaged-file preservation, schema migrations, and documented best-effort handling of external edits.

Validation: native macOS formatting, strict workspace clippy, 172 workspace tests and the craft-settings WASM check. Independent backup-permission and failed external-migration repros fail before the fixes and pass afterward.

Hosted CI: all seven UI and printing jobs pass across macOS, Linux, Windows and UI WASM.

Refs #12
2026-10-12 15:40:50 -06:00
skylar taylor-barrick 697b473c0b Stop PDF work immediately after a parser budget is exhausted 2026-10-12 23:27:53 +02:00
skylar taylor-barrick c22935270b Parse duplicate PDF dictionary keys in linear time 2026-10-12 23:22:05 +02:00
skylar taylor-barrick 85ef8b92ac Bound PDF parsing and preserve indirect page content arrays 2026-10-12 23:14:06 +02:00
skylar taylor-barrick 02d08fe9cc fix(print): pin native completion repair and document bounded PDF handling 2026-10-12 23:12:41 +02:00
skylar taylor-barrick a7e3c76c2f Merge remote-tracking branch 'origin/main' into codex/craft-print-fix-merge-20261012T210558Z 2026-10-12 23:05:59 +02:00
James Locke 36ce57c929 Merge remote-tracking branch 'origin/main' into claude/print-native-windows-73c826
# Conflicts:
#	Cargo.lock
2026-10-12 11:33:06 -07:00
skylar taylor-barrick f58bc1a3df Merge pull request #16 from zachshallbetter/feat/craft-atomic
Add craft-atomic: crash-safe atomic write and document replacement
2026-10-12 12:25:33 -06:00
skylar taylor-barrick 811558a3ad Create Unix atomic-write staging files with private permissions 2026-10-12 20:23:17 +02:00
Zach Shallbetter 5b273dd82f Clarify application recovery sweep responsibility and hardware durability limits 2026-10-12 20:23:17 +02:00
Zach Shallbetter d4a38f90ee Clarify user-space cleanup semantics and best-effort directory sync in craft-atomic 2026-10-12 20:23:17 +02:00
Zach Shallbetter 1c9baae1bf Lock tempfile and fastrand dependencies for workspace 2026-10-12 20:23:17 +02:00
Zach Shallbetter f75e9c9bc8 Document atomicity vs durability guarantees and add sequence verification test 2026-10-12 20:23:17 +02:00
aiongg 6bcd350d49 README: credit PhotoCraft only, document what a replace does not keep
The crate holds PhotoCraft's engine, and unsafe code is forbidden by the
workspace lint rather than in lib.rs.
2026-10-12 20:23:17 +02:00
aiongg 33b393bd88 Remove the unused proptest dev-dependency 2026-10-12 20:23:17 +02:00
aiongg 9d422a18b1 Retry Windows sharing and lock violations, with a capped backoff
retry_rename also retries ERROR_SHARING_VIOLATION (32) and
ERROR_LOCK_VIOLATION (33), which a rename can report while a scanner
holds the file, and caps each pause at 200 ms.
2026-10-12 20:23:17 +02:00
aiongg 253fd775f0 Set the replaced file's permissions on the open temporary file
The original's permissions are applied to the temporary file before it
is flushed, and a failure fails the save instead of being ignored.
2026-10-12 20:23:17 +02:00
aiongg d78b352004 Refuse to replace a file the user cannot write (Unix)
A rename needs only a writable folder, so a read-only file would be
replaced where writing it in place would fail. Before staging, the
existing file is opened for writing without truncating; when that fails
the save fails with a read-only error.
2026-10-12 20:23:17 +02:00
aiongg 658ca37c8e Follow symbolic links without canonicalize
The target is found by reading links one hop at a time, resolving
relative links against the link's folder. A dangling link writes the
file it names and stays a link, and a loop of links is an error.
2026-10-12 20:23:17 +02:00
aiongg fb26a9f90f Report a missing folder instead of creating it
A save into a folder that does not exist fails with an error naming the
folder, so a save to an unmounted network share does not create a local
folder in its place.
2026-10-12 20:23:17 +02:00
aiongg 9e160690fc Never replace an existing file in write_new
write_new_with gives the staged file its name with a hard link on every
platform. Where hard links fail for any reason other than an existing
target (FAT/exFAT, some SMB and FUSE volumes), it copies the staged
content into a file opened with create_new. It shares the temporary-file
retry, error context and folder flush with the replace path, and its
errors only mention an original file when there is one.
2026-10-12 20:23:17 +02:00