Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
13 KiB
14 — ICCery-CPU / TargetPrint (macOS companion)
Line numbers refer to ICCery v0.8.5 (
/tmp/ICCeryat analysis time) and the Gronod ArgyllCMS 3.5.0 fork.
ICCery-CPU / TargetPrint
Separate native macOS AppKit app. Spec: /tmp/ICCery-CPU/SPEC.md. Binary name TargetPrint. Zero third-party deps. Universal 2. Hardened Runtime on, App Sandbox off (needs cupsGetPPD).
Why it exists
ICCery's Tauri path spools TIFF via lp and never goes through Quartz. That is correct for "don't let ColorSync touch the file", but:
- No 1:1 physical-size preview
- Windows-style
StretchDIBitsscaler (macOSlpmay still scale inside the filter) - No AirPrint warning
- NSPrintPanel is settings-only; the job is a second step
TargetPrint is the Adobe Color Print Utility analogue: decode TIFF with ImageIO, draw 1:1 in PostScript points with interpolation off, present NSPrintPanel as the actual print operation, inject ColorSync-off and vendor PPD keys into NSPrintInfo.
Architecture (SPEC §2)
ICCery (Tauri) --job /tmp/iccery_job_<uuid>.json (fire-and-forget, do not wait)
▼
TargetPrint.app (always NSApplication, never a CLI tool)
main.swift → AppDelegate → PreviewWindowController
TargetCanvasView (ImageIO, knowsPageRange/rectForPage)
PrintEngine (NSPrintOperation + NSPrintInfo + ColorMatching)
CUPSManager (libcups, AirPrint, vendor PPD)
Invocation contract (SPEC §4, main.swift, AppDelegate.consumeArguments)
TargetPrint.app/Contents/MacOS/TargetPrint --job /tmp/iccery_job_<uuid>.json
TargetPrint --job=/path.json
TargetPrint --verbose --job … # ~/Library/Logs/TargetPrint/TargetPrint.log
# or open a .targetjob / .json via file association (UTI com.iccery.targetjob)
Exit codes (AppError.swift): 0 ok/cancel, 1 invalid job, 2 unreadable TIFF, 3 CUPS, 4 fatal. Job JSON is never deleted by TargetPrint.
Mode B: no args / drop TIFFs / File→Open — inspector + Print….
--job presents NSPrintPanel immediately (AppDelegate:32-33, SPEC §4.5). Tauri must not wait().
JSON TargetJob v1 (SPEC §5, TargetJob.swift, Resources/sample.targetjob)
{
"version": 1,
"jobTitle": "Epson_XP55_IlfordLustre_Target_P1-2",
"files": ["/abs/page1.tif", "/abs/page2.tif"],
"printSettings": {
"printerName": "EPSON_XP_55_Series",
"mediaSize": "A4",
"mediaType": "PremiumGlossy",
"paperSource": "Auto",
"resolution": "5760x1440dpi",
"printQuality": null,
"scaling": 1.0,
"centered": true,
"forceUnmanagedColor": true
},
"uiPolicy": {
"lockColorManagement": true,
"allowBasicDriverChanges": true
}
}
Required: version==1, jobTitle, non-empty files, printSettings.{printerName,mediaSize,scaling,centered,forceUnmanagedColor}, uiPolicy.{lockColorManagement,allowBasicDriverChanges}. Unknown keys ignored. printQuality is implemented in Swift beyond the spec sample.
Geometry & pixel integrity (SPEC §6, §15)
- 72 pt = 1 in. Never
backingScaleFactor(Geometry.swift). physicalInches = pixels / dpi;pointSize = physical * 72 * scaling.- Papers: A4/A3/A5/Letter/Legal/Tabloid/4x6/5x7. Unknown names fall back to A4, with substring match for
A4.Borderless. - Destination origin rounded to 0.001 pt to avoid fractional-device resampling while staying inside G1 ±0.1 mm.
- ImageIO load:
kCGImageSourceShouldCache=true,ShouldAllowFloat=false. DPI fromkCGImagePropertyDPIWidth/Height, default 72. TargetPage.makeUnmanagedDeviceRGB: redraw intoCGColorSpaceCreateDeviceRGB()with interpolation none, antialias off,shouldInterpolate: false. SPEC §6.3: do notCGImageCreateCopyWithColorSpaceinto a calibrated space.- Print draw (
TargetCanvasView.drawPrintedPage):interpolationQuality = .none,setShouldAntialias(false),setAllowsAntialiasing(false),setShouldSmoothFonts(false), fill paper white,context.draw(cgImage, in: dest). GState saved/restored so page N does not inherit page N-1's transform. PixelIntegrity.scanVerticalSeam: any pixel on a red|green seam that is not exactly left or right fails P1 (interpolation/antialias detected).- Pagination:
knowsPageRange1…N,rectForPage= paper size, empty header/footer.
ColorSync disable in TargetPrint (SPEC §7) — different keys from ICCery
ColorMatching.swift:
| User mode | PMColorMatchingMode value |
|---|---|
| Unmanaged (profiling default) | "APCustomColorMatching" |
| ColorSync | "APColorSync" |
| Driver / Vendor | "APPrinterExtension" |
Injected into:
printInfo.dictionary()["PMColorMatchingMode"]printInfo.dictionary()["PMCustomColorMatchingProfile"]=""when unmanaged else"System"printInfo.dictionary()["com.apple.print.PrintSettings.PMColorMatchingMode"](legacy)- nested
com.apple.print.printSettingsdictionary, same keys
This is not AP_ApplicationColorMatching. TargetPrint talks to Quartz/NSPrintOperation. ICCery talks to the CUPS lp ticket / cgpdftoraster. A rewrite that unifies them must keep both vocabularies or prove one is honored on both paths.
Panel policy (ColorMatching.configurePanel):
- Always: copies, page range, preview
- If
allowBasicDriverChanges: paper size, orientation, scaling - If
lockColorManagement:stripColorMatchingAccessories— walkpanel.accessoryControllers, remove any whose class/title/nib contains "color matching" / "colour matching" / "colormatch", requiringNSPrintPanelAccessorizing. Stripped twice (configure + immediately beforerunModal) because system accessories install lazily.
CUPS / PPD (CUPSManager.swift, Bridging-Header.h)
Bridging header:
#import <cups/cups.h>
#import <cups/ppd.h>
static inline const char *TPCupsGetPPD(const char *name) {
return cupsGetPPD(name); // deprecated; Swift overlay marks cupsGetPPD unavailable
}
cupsGetDests/cupsFreeDests/cupsGetOptionforprinter-uri-supported,device-uri,printer-make-and-model.TPCupsGetPPD→ read ISO-Latin-1 →unlinkthe temp PPD.- AirPrint (SPEC §10.2) if any of: URI
apple-airprint://; PPD*APAirPrint: True; make Apple + model contains AirPrint;ipps://and PPD text containsairprint. Persistent warning badge; unmanaged color cannot be trusted. Tests inAirPrintTests.swift. - Vendor bypass (SPEC §10.3) — different keys from ICCery's lpoptions detector:
| Vendor | TargetPrint keys | ICCery macOS lp keys |
|---|---|---|
| Epson | ColorModel=RGB, EPSONColorControls=Off |
EPIJ_CMat=3 / EPIJ_CCor=0 / EpsonColorMode=Off |
| Canon | CNColorMatching=None |
CNIJIntent2=4 / CNIJIntent=4 |
| HP | ColorModel=RGB, HPColorControl=Off |
(none auto) |
| Generic | any Color/Colour OpenUI choice in {none,off,no,nocoloradjustment} |
ColorCorrection=Uncorrected / StpColorCorrection=Uncorrected |
A rewrite should apply both dictionaries (ICCery's empirically captured PDE keys and TargetPrint's SPEC keys).
Media/tray/quality discovery walks *Keyword code/Title: and *OpenUI translations (media type, paper source, print quality, …). Keywords tried:
- Media:
MediaType,CNIJMediaType - Tray:
InputSlot,EPIJ_FdSo,CNIJMediaSupply - Quality:
CNIJPrintQuality,EPIJ_Qual,cupsPrintQuality,PrintQuality,CNIJPrintMode2,Quality,StpQuality
Canon quality is a triple (CNIJPrintQuality + CNIJPrintMode2 + CNIJPQualitySlider) mapped in PrintEngine.applyOptionalPPDKeys.
PPD hex escapes (<2F> → /) decoded by decodePPDString.
PrintEngine (PrintEngine.swift)
makePrintInfo():
jobDisposition = .spool- margins 0, centering off (geometry is in the view), pagination
.clip,scalingFactor = 1.0 NSPrinter(name: printerName)- paper size from
Geometry.paper(named:) ColorMatching.applyCUPSManager.namedQueue→vendorColorBypass+ optional media/tray/quality/resolution extrascanSpawnSeparateThread = falseon the operationrunModal(for: window, delegate:didRun:)— exit code 0 on success or cancel (SPEC §4)
How ICCery would invoke it (SPEC §16, VENDOR.md)
Not implemented in current ICCery Rust. Specified as:
let json = serde_json::to_string_pretty(&job)?;
let mut path = env::temp_dir();
path.push(format!("iccery_job_{}.json", uuid::Uuid::new_v4()));
fs::write(&path, json)?;
Command::new("/Applications/TargetPrint.app/Contents/MacOS/TargetPrint")
// or vendored: src-tauri/targetprint/macos-{x86_64,aarch64,universal}/TargetPrint.app/Contents/MacOS/TargetPrint
.arg("--job")
.arg(&path)
.spawn()?; // fire-and-forget — do not wait
CI publishes vendor-iccery.zip with macos-x86_64 / macos-aarch64 / macos-universal app bundles to drop into src-tauri/targetprint/.
Suggested ICCery integration:
- Keep current
lppath as the headless/fast path (and the only path on Linux). - On macOS, Preferences / Print can spawn TargetPrint with a
TargetJobbuilt fromPrintOptions+ TIFF list +forceUnmanagedColor: true+lockColorManagement: true. - Do not
CREATE_NO_WINDOW(macOS); do notwait(). Cleanup of the JSON is ICCery's job after process exit, or leave in/tmpas an audit trail (SPEC §13).
TargetPrint vs ICCery macOS — decision table for the rewrite
| Concern | ICCery macos.rs |
TargetPrint | Rewrite recommendation |
|---|---|---|---|
| Spool | lp TIFF |
Quartz NSPrintOperation |
Keep lp for unattended; TargetPrint for preview+panel |
| ColorSync ticket | AP_ApplicationColorMatching (+ dotted) |
PMColorMatchingMode=APCustomColorMatching |
Set both if using NSPrintOperation; keep AP_* on lp |
| Lock PDE UI | private PMSessionSetColorMatchingMode* SPI |
strip Color Matching accessories | Use SPI and strip; accessories API misses driver PDEs (the #188 failure mode) |
| Canon off | CNIJIntent2=4 |
CNColorMatching=None |
Apply both |
| Epson off | EPIJ_CMat=3 / EPIJ_CCor=0 |
EPSONColorControls=Off + ColorModel=RGB |
Apply both; prefer captured panel values |
| Geometry | none (filter decides) | 1:1 pt from DPI | TargetPrint (or do not scale in GDI/lp) |
| Interpolation | n/a (file passthrough) | explicitly disabled | Required for patch edges |
| AirPrint | none | detected + warned | Port detector into ICCery printer list |
| Linux | -o raw |
n/a (macOS only) | Keep raw + PPD fallback |
| Windows | GDI ICM_OFF | n/a | Keep GDI; do not route through TargetPrint |
Issue cross-reference
| # | Title | Print relevance |
|---|---|---|
| 25 | Raw OS Printing Engine | Parent feature. ACPU-equivalent, no window.print(). |
| 26 | Windows GDI/ICM | SetICMMode, DMICMMETHOD_NONE, BGR DIB, StretchDIBits. |
| 27 | macOS/Linux CUPS | Originally -o raw. macOS later diverged to AP_* (#92/#188). |
| 28 | UI + IPC | get_printers, printer <select>, Print Target button, toasts. |
| 34 | Driver properties, trays, orientation, auto-fit | Windows dialog + scaler; Unix lpoptions; PrintOptions. |
| 36 | DEVMODE not applied | Full buffer + dmDriverExtra retained into CreateDCW. |
| 46 | Console windows on Windows | CREATE_NO_WINDOW on Argyll spawn, not on print. |
| 48 | Hide PPD checkbox on Windows | #cupsOptionsGroup.hidden. |
| 50 | Stage 2 badge cleanup | Removed ACPU / auto-fit badges. |
| 67 | Consolidate print commands | get_printers / print_target_native only. |
| 68 | printtarg JSON manifest | Gallery + per-page print consume event: manifest. |
| 92 | macOS CUPS raw spooler | macos.rs created; ColorSync research. Milestone 9. |
| 127 | Fetch Argyll + NSIS USB | Instrument USB, not printer drivers. |
| 188 | Cannot disable Epson/Canon CM on macOS | Preferences was System Settings. Now NSPrintPanel + SPI + AP_* + PPD bypass + capture. |
Files (absolute)
/tmp/ICCery/src-tauri/src/print/mod.rs
/tmp/ICCery/src-tauri/src/print/macos.rs
/tmp/ICCery/src-tauri/src/print/windows.rs
/tmp/ICCery/src-tauri/src/print/unix.rs
/tmp/ICCery/src-tauri/src/print/tests.rs
/tmp/ICCery/src-tauri/src/commands.rs # get_printers, get_printer_capabilities,
# show_printer_properties, print_target_native
/tmp/ICCery/src-tauri/src/lib.rs # PrinterDevModeStore manage + command list
/tmp/ICCery/src-tauri/src/process_manager.rs # CREATE_NO_WINDOW for Argyll
/tmp/ICCery/src-tauri/src/calibration.rs # CREATE_NO_WINDOW for printcal
/tmp/ICCery/src-tauri/Cargo.toml # objc2* / windows features
/tmp/ICCery/src-tauri/windows/hooks.nsh # NSIS USB
/tmp/ICCery/src/js/printtarg.js
/tmp/ICCery/src/index.html # #rawPrintPanel
/tmp/ICCery/AGENTS.md # #188 contract
/tmp/ICCery-CPU/SPEC.md
/tmp/ICCery-CPU/VENDOR.md
/tmp/ICCery-CPU/Bridging-Header.h
/tmp/ICCery-CPU/Sources/main.swift
/tmp/ICCery-CPU/Sources/AppDelegate.swift
/tmp/ICCery-CPU/Sources/AppError.swift
/tmp/ICCery-CPU/Sources/Printing/ColorMatching.swift
/tmp/ICCery-CPU/Sources/Printing/PrintEngine.swift
/tmp/ICCery-CPU/Sources/Printing/CUPSManager.swift
/tmp/ICCery-CPU/Sources/Models/TargetJob.swift
/tmp/ICCery-CPU/Sources/Models/TargetPage.swift
/tmp/ICCery-CPU/Sources/Models/Geometry.swift
/tmp/ICCery-CPU/Sources/Models/PixelIntegrity.swift
/tmp/ICCery-CPU/Sources/Views/TargetCanvasView.swift
/tmp/ICCery-CPU/Sources/Controllers/PreviewWindowController.swift