96 Commits
Author SHA1 Message Date
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
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
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 b9f475471a Support normal new-file permissions with private atomic staging 2026-10-13 02:26:24 +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 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
aiongg 818c0c0753 Share one replace path between the byte and streaming writers
atomic_write, atomic_write_with and atomic_write_custom all run through
one internal function that takes the AtomicFs, the RenameRetry and a
writer callback, so fault injection also covers streaming writes.
AtomicFs creates and flushes the temporary file (create_new, sync)
instead of writing a byte slice.
2026-10-12 20:23:17 +02:00
Zach Shallbetter 8df3d87d8b Add craft-atomic: crash-safe atomic write and document replacement (refs #2, fixes #14) 2026-10-12 20:23:17 +02:00
skylar taylor-barrick 4a4ec8c1ca Merge pull request #21 from jmacek/quit-app-menu
craft-quit: macOS app menu whose Quit asks the app first
2026-10-12 12:22:49 -06:00
skylar taylor-barrick c68d145c9f docs: list craft-quit in the library catalog 2026-10-12 20:21:37 +02:00
skylar taylor-barrick 8e9e437e78 craft-quit: route menu Quit to the root viewport 2026-10-12 20:18:18 +02:00
Jake MacekandClaude Opus 5.5 f0ae2b3f13 craft-quit: return the app menu install error to the app
install_app_menu printed the error and carried on, so an app could not
tell that ⌘Q had fallen back to winit's Quit, which skips the prompt.
It now returns the error; the app decides whether it is fatal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:18:18 +02:00
Jake MacekandClaude Opus 5.5 673c14439b craft-quit: Quit menu id and handler for apps with their own menu
Apps that build their own muda app menu can't use install_app_menu: it
replaces the whole menu and sets muda's process-wide event handler.
QUIT_MENU_ID and on_menu_event(ctx, id) let them give their own Quit
item the id and route its event through the app's close handling.
Neither needs muda, so any muda version works. install_app_menu now
uses both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:18:18 +02:00
Jake MacekandClaude Opus 5.5 ebf3f552cd craft-quit: macOS app menu whose Quit asks the app first
winit's default app menu sends Quit (Cmd+Q) straight to
-[NSApplication terminate:] and winit doesn't implement
applicationShouldTerminate:, so a Craft app quits without its
unsaved-changes prompt. The new opt-in `menu` feature adds
`install_app_menu(ctx, app_name)`, which replaces that menu with a
muda-built copy whose Quit sends ViewportCommand::Close. The app's own
close handling then runs, as for the close button, and
on_close_request quits.

muda is a macOS-only, optional dependency; no unsafe code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:18:18 +02:00
skylar taylor-barrick f207cdd016 Merge pull request #5 from mjohnson7cd/macos-quit-terminate
Add craft-quit: quit macOS apps through NSApplication terminate:
2026-10-12 12:17:38 -06:00
skylar taylor-barrick bafeec6b7d Merge remote-tracking branch 'origin/main' into fix-quit5-20261012 2026-10-12 20:15:22 +02:00
skylar taylor-barrick c6a847b7d0 craft-quit: document logic-based close handling for hidden windows 2026-10-12 20:11:55 +02:00
skylar taylor-barrick 3c9702725d Merge pull request #26 from storytold/codex/craft-print-settings
Share printer settings and asynchronous print feedback
2026-10-12 08:25:02 -06:00