618 lines
25 KiB
Markdown
618 lines
25 KiB
Markdown
# ICCery-CPU — Specification
|
||
|
||
**ICCery Colour Print Utility**
|
||
**Binary name:** `TargetPrint`
|
||
**Repository:** `ICCery-CPU`
|
||
**Version:** 0.0.1 (initial)
|
||
**Status:** Specification / Implementation Ready
|
||
|
||
This document is the authoritative technical specification for the `TargetPrint` native macOS utility. It is intended to be placed at the root of a new empty repository named **ICCery-CPU** and used as the single source of truth for implementation.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Purpose & Scope](#1-purpose--scope)
|
||
2. [Architecture Overview](#2-architecture-overview)
|
||
3. [Target Environment & Constraints](#3-target-environment--constraints)
|
||
4. [Invocation Contract & IPC](#4-invocation-contract--ipc)
|
||
5. [JSON Job Schema (`TargetJob`)](#5-json-job-schema-targetjob)
|
||
6. [High-Fidelity Rendering Engine](#6-high-fidelity-rendering-engine)
|
||
7. [Colour Management Bypass](#7-colour-management-bypass)
|
||
8. [Multi-Page Pagination](#8-multi-page-pagination)
|
||
9. [User Interface (Standalone Mode)](#9-user-interface-standalone-mode)
|
||
10. [CUPS / PPD Integration](#10-cups--ppd-integration)
|
||
11. [Project Layout](#11-project-layout)
|
||
12. [Build System](#12-build-system)
|
||
13. [Error Handling, Logging & Diagnostics](#13-error-handling-logging--diagnostics)
|
||
14. [Security & Entitlements](#14-security--entitlements)
|
||
15. [Verification & Acceptance Tests](#15-verification--acceptance-tests)
|
||
16. [Tauri / Rust Integration Contract](#16-tauri--rust-integration-contract)
|
||
17. [Future Extensions (Non-Goals for v1)](#17-future-extensions-non-goals-for-v1)
|
||
|
||
---
|
||
|
||
## 1. Purpose & Scope
|
||
|
||
`TargetPrint` is a high-precision, compiled, native macOS utility written in Swift + AppKit (with Objective-C/Carbon bridging where required). It serves as an independent fallback companion for **ICCery**, a Rust/Tauri colour-profiling desktop application.
|
||
|
||
### Mission
|
||
|
||
Print multi-page raster colour profiling targets (`.tif` / `.tiff`) with **absolute geometric and pixel-level fidelity**, while completely disabling or strictly controlling:
|
||
|
||
- Operating-system ColourSync transformations
|
||
- Driver-level colour adjustments
|
||
- Any Quartz 2D interpolation or anti-aliasing during rendering
|
||
|
||
### Out of Scope (v1)
|
||
|
||
- PDF or vector target printing
|
||
- Windows / Linux support
|
||
- Network print queue management beyond the local CUPS system
|
||
- Colour management profile creation or editing
|
||
- Automatic printer driver installation
|
||
|
||
---
|
||
|
||
## 2. Architecture Overview
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ ICCery (Tauri) │
|
||
│ (Rust side serialises TargetJob JSON → temp file) │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│ std::process::Command
|
||
│ --job /tmp/iccery_job_UUID.json
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ TargetPrint.app │
|
||
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
|
||
│ │ main.swift │→ │ AppDelegate │→ │ PreviewWindow │ │
|
||
│ │ (CLI parse) │ │ │ │ Controller │ │
|
||
│ └─────────────┘ └──────────────┘ └─────────┬─────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────────────────────────────▼──────────┐ │
|
||
│ │ TargetCanvasView (NSView) │ │
|
||
│ │ • ImageIO decode (zero interpolation) │ │
|
||
│ │ • Exact DPI geometry in PostScript points │ │
|
||
│ │ • knowsPageRange / rectForPage pagination │ │
|
||
│ └────────────────────────────────────────────┬──────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────────────────────────────▼──────────┐ │
|
||
│ │ PrintEngine │ │
|
||
│ │ • NSPrintOperation + NSPrintInfo │ │
|
||
│ │ • Carbon CorePrinting dictionary injection │ │
|
||
│ │ • UI policy (lock Color Matching pane) │ │
|
||
│ └────────────────────────────────────────────┬──────────┘ │
|
||
│ │ │
|
||
│ ┌────────────────────────────────────────────▼──────────┐ │
|
||
│ │ CUPSManager │ │
|
||
│ │ • libcups (cupsGetNamedDest / cupsGetPPD) │ │
|
||
│ │ • AirPrint detection │ │
|
||
│ │ • Vendor-specific PPD option injection │ │
|
||
│ └───────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
The process is always a full AppKit GUI application (`NSApplication`). It never runs as a pure command-line tool; even when launched with `--job` it owns its own run loop and presents UI.
|
||
|
||
---
|
||
|
||
## 3. Target Environment & Constraints
|
||
|
||
| Item | Requirement |
|
||
|------|-------------|
|
||
| **Deployment Target** | macOS 10.15 Catalina → macOS 26+ Tahoe |
|
||
| **Architectures** | Universal 2 (`x86_64` + `arm64`) |
|
||
| **Language / Runtime** | Swift 5.x (compatible with the oldest supported Xcode that still targets 10.15) |
|
||
| **UI Framework** | AppKit only (no SwiftUI for v1) |
|
||
| **Third-party dependencies** | **None**. Link exclusively against system frameworks |
|
||
| **Frameworks** | `AppKit`, `CoreGraphics`, `ImageIO`, `ApplicationServices` (CorePrinting), `Foundation`, `libcups` (via bridging header) |
|
||
| **Sandbox** | Hardened Runtime **enabled**, App Sandbox **disabled** |
|
||
| **Code Signing** | Required for distribution; development builds may use ad-hoc signing |
|
||
| **Minimum Xcode** | Xcode 10+ (for 10.15 support); recommended modern Xcode with dual-arch support |
|
||
|
||
### Bridging Header
|
||
|
||
```objc
|
||
// Bridging-Header.h
|
||
#import <cups/cups.h>
|
||
#import <cups/ppd.h>
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Invocation Contract & IPC
|
||
|
||
Two mutually exclusive modes.
|
||
|
||
### Mode A — Structured Job Mode (ICCery → TargetPrint)
|
||
|
||
```bash
|
||
/Applications/TargetPrint.app/Contents/MacOS/TargetPrint --job /tmp/iccery_job_<uuid>.json
|
||
```
|
||
|
||
Alternative: file-association open of a `.targetjob` file (UTI to be registered in `Info.plist`).
|
||
|
||
#### Lifecycle Requirements
|
||
|
||
1. Process launches as a normal AppKit application.
|
||
2. Immediately parses the job file (or fails with a clear alert + non-zero exit).
|
||
3. Creates the preview window and loads the target pages.
|
||
4. Configures `NSPrintInfo` from the job.
|
||
5. Presents `NSPrintPanel` as a modal sheet on the preview window **without** requiring further user action to reach the print dialog.
|
||
6. The calling Tauri process must **not** block waiting for the print dialog; TargetPrint owns its run loop.
|
||
|
||
Exit codes:
|
||
|
||
| Code | Meaning |
|
||
|------|---------|
|
||
| 0 | Normal termination (user cancelled or print completed) |
|
||
| 1 | Invalid / missing job file |
|
||
| 2 | One or more target files unreadable |
|
||
| 3 | Printer / CUPS error |
|
||
| 4 | Internal fatal error |
|
||
|
||
### Mode B — Standalone Interactive Mode
|
||
|
||
Launched with no arguments or by double-clicking the app icon / dropping files onto it.
|
||
|
||
Behaviour:
|
||
|
||
- Empty drop-zone canvas.
|
||
- Accepts multi-file drag-and-drop of `.tif` / `.tiff`.
|
||
- Standard **File → Open…** (multi-select).
|
||
- Page thumbnail sidebar.
|
||
- Inspector for printer, media, tray, scale, colour-management mode.
|
||
- Explicit **Print…** button that shows `NSPrintPanel`.
|
||
|
||
---
|
||
|
||
## 5. JSON Job Schema (`TargetJob`)
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://iccery.local/schemas/targetjob.v1.json",
|
||
"version": 1,
|
||
"jobTitle": "Epson_XP55_IlfordLustre_Target_P1-2",
|
||
"files": [
|
||
"/absolute/path/to/target_page_1.tif",
|
||
"/absolute/path/to/target_page_2.tif"
|
||
],
|
||
"printSettings": {
|
||
"printerName": "EPSON_XP_55_Series",
|
||
"mediaSize": "A4",
|
||
"mediaType": "PremiumGlossy",
|
||
"paperSource": "Auto",
|
||
"resolution": "5760x1440dpi",
|
||
"scaling": 1.0,
|
||
"centered": true,
|
||
"forceUnmanagedColor": true
|
||
},
|
||
"uiPolicy": {
|
||
"lockColorManagement": true,
|
||
"allowBasicDriverChanges": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### Field Semantics
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `version` | int | yes | Currently only `1` is accepted |
|
||
| `jobTitle` | string | yes | Used as window title and CUPS job name |
|
||
| `files` | [string] | yes | Absolute paths; order defines page order |
|
||
| `printSettings.printerName` | string | yes | Exact CUPS queue name (`lpstat -a`) |
|
||
| `printSettings.mediaSize` | string | yes | e.g. `A4`, `Letter`, `4x6`, or PPD media name |
|
||
| `printSettings.mediaType` | string | no | PPD `MediaType` value |
|
||
| `printSettings.paperSource` | string | no | PPD `InputSlot` value (`Auto`, `Tray1`, …) |
|
||
| `printSettings.resolution` | string | no | Informational / PPD `Resolution` if present |
|
||
| `printSettings.scaling` | number | yes | 1.0 = 100 %. Only 1.0 is recommended for profiling |
|
||
| `printSettings.centered` | bool | yes | Centre image within imageable area |
|
||
| `printSettings.forceUnmanagedColor` | bool | yes | Master switch for colour bypass |
|
||
| `uiPolicy.lockColorManagement` | bool | yes | Hide / disable Color Matching pane |
|
||
| `uiPolicy.allowBasicDriverChanges` | bool | yes | Whether paper size / orientation remain editable |
|
||
|
||
Validation must be strict. Unknown keys are ignored; missing required keys cause an immediate fatal error with a user-visible alert.
|
||
|
||
---
|
||
|
||
## 6. High-Fidelity Rendering Engine
|
||
|
||
### 6.1 Image Loading (ImageIO)
|
||
|
||
```swift
|
||
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
|
||
let image = CGImageSourceCreateImageAtIndex(source, 0, [
|
||
kCGImageSourceShouldCache: true,
|
||
kCGImageSourceShouldAllowFloat: false
|
||
] as CFDictionary) else { /* error */ }
|
||
```
|
||
|
||
Extract DPI:
|
||
|
||
```swift
|
||
let props = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]
|
||
let dpiX = props?[kCGImagePropertyDPIWidth] as? CGFloat ?? 72.0
|
||
let dpiY = props?[kCGImagePropertyDPIHeight] as? CGFloat ?? 72.0
|
||
let pixelWidth = CGFloat(image.width)
|
||
let pixelHeight = CGFloat(image.height)
|
||
```
|
||
|
||
### 6.2 Geometry (PostScript Points)
|
||
|
||
macOS printing uses **72 points = 1 inch**.
|
||
|
||
```
|
||
physicalWidthInches = pixelWidth / dpiX
|
||
physicalHeightInches = pixelHeight / dpiY
|
||
|
||
pointWidth = physicalWidthInches * 72.0
|
||
pointHeight = physicalHeightInches * 72.0
|
||
```
|
||
|
||
All layout calculations **must** use these point values. Never multiply by `backingScaleFactor`.
|
||
|
||
### 6.3 Colour-Space Handling
|
||
|
||
When `forceUnmanagedColor == true`:
|
||
|
||
- Create a device RGB colour space: `CGColorSpaceCreateDeviceRGB()`
|
||
- Draw the `CGImage` into a bitmap context that uses this colour space, or draw the image directly while ensuring no source profile is applied.
|
||
- Do **not** call `CGImageCreateCopyWithColorSpace` with a calibrated space.
|
||
|
||
Goal: the pixel values that leave the process and enter the CUPS raster filter are identical (within quantisation) to the values stored in the TIFF.
|
||
|
||
### 6.4 Drawing Implementation (`TargetCanvasView`)
|
||
|
||
```swift
|
||
override func draw(_ dirtyRect: NSRect) {
|
||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||
|
||
context.interpolationQuality = .none
|
||
context.setShouldAntialias(false)
|
||
context.setAllowsAntialiasing(false)
|
||
|
||
// Calculate destination rect in points (1:1 with physical size)
|
||
let destRect = calculateDestinationRect(for: currentPage,
|
||
paperSize: printInfo.paperSize,
|
||
imageable: printInfo.imageablePageBounds,
|
||
centered: settings.centered,
|
||
scaling: settings.scaling)
|
||
|
||
context.draw(currentCGImage, in: destRect)
|
||
}
|
||
```
|
||
|
||
`calculateDestinationRect` must never introduce fractional pixel offsets that would cause the print pipeline to resample.
|
||
|
||
---
|
||
|
||
## 7. Colour Management Bypass
|
||
|
||
This is the most critical section. Colour translation must be prevented at **two** independent layers.
|
||
|
||
### 7.1 Quartz / ColorSync Layer (NSPrintInfo + CorePrinting)
|
||
|
||
```swift
|
||
let printInfo = NSPrintInfo.shared
|
||
var dict = printInfo.dictionary()
|
||
|
||
// Force custom (i.e. “no”) colour matching
|
||
dict[NSPrintInfo.AttributeKey(rawValue: "PMColorMatchingMode")] = "APCustomColorMatching"
|
||
dict[NSPrintInfo.AttributeKey(rawValue: "PMCustomColorMatchingProfile")] = ""
|
||
|
||
// Optional legacy keys still observed by some drivers
|
||
dict[NSPrintInfo.AttributeKey(rawValue: "com.apple.print.PrintSettings.PMColorMatchingMode")] = "APCustomColorMatching"
|
||
|
||
printInfo.dictionary().setDictionary(dict as! [AnyHashable: Any])
|
||
```
|
||
|
||
When the user (in standalone mode) explicitly requests ColorSync or Driver Managed:
|
||
|
||
| Mode | `PMColorMatchingMode` value |
|
||
|------|-----------------------------|
|
||
| Unmanaged (default for profiling) | `"APCustomColorMatching"` |
|
||
| ColorSync | `"APColorSync"` |
|
||
| Driver / Vendor | `"APPrinterExtension"` |
|
||
|
||
### 7.2 Print Panel UI Policy
|
||
|
||
When `uiPolicy.lockColorManagement == true`:
|
||
|
||
```swift
|
||
let panel = printOperation.printPanel
|
||
panel.options = [
|
||
.showsCopies,
|
||
.showsPageRange,
|
||
.showsPaperSize,
|
||
.showsOrientation,
|
||
.showsScaling,
|
||
.showsPreview
|
||
]
|
||
// Explicitly omit any colour-matching accessory
|
||
```
|
||
|
||
Further, implement `NSPrintPanelAccessorizing` (or inspect the panel’s accessory controllers after presentation) and remove any view controller whose title contains “Color Matching” or “Colour Matching”.
|
||
|
||
### 7.3 Driver / PPD Layer
|
||
|
||
See [Section 10](#10-cups--ppd-integration).
|
||
|
||
---
|
||
|
||
## 8. Multi-Page Pagination
|
||
|
||
`TargetCanvasView` (or a dedicated `NSDocument` / print operation data source) must implement:
|
||
|
||
```swift
|
||
override func knowsPageRange(_ range: NSRangePointer) -> Bool {
|
||
range.pointee = NSRange(location: 1, length: pages.count)
|
||
return true
|
||
}
|
||
|
||
override func rectForPage(_ pageNumber: Int) -> NSRect {
|
||
// pageNumber is 1-based
|
||
let page = pages[pageNumber - 1]
|
||
return calculatePageBounds(for: page, on: currentPrintInfo)
|
||
}
|
||
```
|
||
|
||
Each page is rendered in isolation. The graphics context origin must be reset so that page *N* never inherits a transform from page *N-1*.
|
||
|
||
Page order is exactly the order of the `files` array in the job (or the order files were added in standalone mode).
|
||
|
||
---
|
||
|
||
## 9. User Interface (Standalone Mode)
|
||
|
||
### Window Layout
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ TargetPrint — <jobTitle or “Untitled”> │
|
||
├────────────┬───────────────────────────────────┬─────────────┤
|
||
│ Thumbnail │ │ Inspector │
|
||
│ Sidebar │ TargetCanvasView │ - Printer │
|
||
│ │ (drop zone when empty) │ - Media │
|
||
│ [page 1] │ │ - Tray │
|
||
│ [page 2] │ │ - Scale │
|
||
│ … │ │ - Colour │
|
||
│ │ │ Mode │
|
||
│ │ │ │
|
||
│ │ │ [Print…] │
|
||
└────────────┴───────────────────────────────────┴─────────────┘
|
||
```
|
||
|
||
- Drop zone accepts multiple TIFF files.
|
||
- Thumbnails are generated with `NSImage` at low resolution for speed; the canvas always uses the full-resolution `CGImage`.
|
||
- Colour-mode segmented control: **No Colour Management** | ColorSync | Driver Managed.
|
||
- When a job is loaded via `--job`, the inspector is read-only for locked fields.
|
||
|
||
---
|
||
|
||
## 10. CUPS / PPD Integration
|
||
|
||
### 10.1 Queue Inspection
|
||
|
||
```c
|
||
// via Bridging-Header
|
||
cups_dest_t *dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printerName, NULL);
|
||
const char *ppdFile = cupsGetPPD(printerName);
|
||
ppd_file_t *ppd = ppdOpenFile(ppdFile);
|
||
```
|
||
|
||
### 10.2 AirPrint Detection
|
||
|
||
A queue is considered AirPrint (and therefore unsuitable for reliable unmanaged colour) when **any** of the following is true:
|
||
|
||
- Device URI begins with `apple-airprint://` or `ipps://` with AirPrint markers
|
||
- PPD contains `*APAirPrint: True` (or equivalent)
|
||
- PPD manufacturer is “Apple” and model contains “AirPrint”
|
||
|
||
In this case the preview window must show a persistent non-modal warning badge:
|
||
|
||
> **Warning:** Selected printer uses an AirPrint / driverless queue. Hardware colour management cannot be reliably disabled. Install the official OEM driver (Epson, Canon, etc.) for accurate profiling.
|
||
|
||
### 10.3 Vendor-Specific PPD Options
|
||
|
||
Inject into `NSPrintInfo.printSettings` (or the low-level dictionary) **before** the print operation runs:
|
||
|
||
| Vendor | Keys to set for “off” |
|
||
|--------|-----------------------|
|
||
| Epson | `ColorModel=RGB`, `EPSONColorControls=Off` (or `NoColorAdjustment`) |
|
||
| Canon | `CNColorMatching=None` |
|
||
| HP | `ColorModel=RGB`, `HPColorControl=Off` (where available) |
|
||
| Generic | Prefer any option whose name contains `Color` / `Colour` and whose choice is `None` / `Off` / `No` |
|
||
|
||
Discovery is performed by walking `ppd->options` and matching known patterns. Unknown printers receive only the CorePrinting colour-matching bypass.
|
||
|
||
---
|
||
|
||
## 11. Project Layout
|
||
|
||
```
|
||
ICCery-CPU/
|
||
├── README.md # Short project overview + build instructions
|
||
├── SPEC.md # This document
|
||
├── LICENSE # (to be chosen)
|
||
├── TargetPrint.xcodeproj/
|
||
├── Info.plist
|
||
├── TargetPrint.entitlements
|
||
├── Bridging-Header.h
|
||
├── Sources/
|
||
│ ├── main.swift
|
||
│ ├── AppDelegate.swift
|
||
│ ├── Models/
|
||
│ │ ├── TargetJob.swift # Codable + validation
|
||
│ │ └── TargetPage.swift # ImageIO + geometry
|
||
│ ├── Views/
|
||
│ │ ├── TargetCanvasView.swift
|
||
│ │ ├── DropZoneView.swift
|
||
│ │ ├── ThumbnailSidebarView.swift
|
||
│ │ └── InspectorView.swift
|
||
│ ├── Controllers/
|
||
│ │ └── PreviewWindowController.swift
|
||
│ └── Printing/
|
||
│ ├── PrintEngine.swift
|
||
│ └── CUPSManager.swift
|
||
├── Resources/
|
||
│ ├── Assets.xcassets
|
||
│ └── Base.lproj/
|
||
└── Scripts/
|
||
├── build_universal.sh
|
||
└── package_app.sh
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Build System
|
||
|
||
### Universal Binary Script (`Scripts/build_universal.sh`)
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
|
||
CONFIGURATION=${1:-Release}
|
||
SCHEME="TargetPrint"
|
||
PROJECT="TargetPrint.xcodeproj"
|
||
BUILD_DIR="build"
|
||
|
||
# Build each architecture
|
||
xcodebuild -project "$PROJECT" -scheme "$SCHEME" \
|
||
-configuration "$CONFIGURATION" \
|
||
-arch x86_64 \
|
||
-derivedDataPath "$BUILD_DIR/x86_64" \
|
||
BUILD_DIR="$BUILD_DIR/x86_64" \
|
||
ONLY_ACTIVE_ARCH=NO
|
||
|
||
xcodebuild -project "$PROJECT" -scheme "$SCHEME" \
|
||
-configuration "$CONFIGURATION" \
|
||
-arch arm64 \
|
||
-derivedDataPath "$BUILD_DIR/arm64" \
|
||
BUILD_DIR="$BUILD_DIR/arm64" \
|
||
ONLY_ACTIVE_ARCH=NO
|
||
|
||
# Lipo
|
||
mkdir -p "$BUILD_DIR/universal"
|
||
lipo -create \
|
||
"$BUILD_DIR/x86_64/Build/Products/$CONFIGURATION/TargetPrint.app/Contents/MacOS/TargetPrint" \
|
||
"$BUILD_DIR/arm64/Build/Products/$CONFIGURATION/TargetPrint.app/Contents/MacOS/TargetPrint" \
|
||
-output "$BUILD_DIR/universal/TargetPrint"
|
||
|
||
# Re-assemble .app bundle
|
||
# … (copy resources, Info.plist, frameworks, then replace binary)
|
||
```
|
||
|
||
The final artefact is a Universal 2 `.app` bundle.
|
||
|
||
---
|
||
|
||
## 13. Error Handling, Logging & Diagnostics
|
||
|
||
- All user-facing errors surface via `NSAlert`.
|
||
- Internal diagnostics are written to:
|
||
- `os_log` (subsystem `com.iccery.TargetPrint`)
|
||
- Optionally a rotating log file under `~/Library/Logs/TargetPrint/` when launched with `--verbose`.
|
||
- Job JSON is never deleted by TargetPrint; the caller (ICCery) is responsible for cleanup. Leaving the file in `/tmp` provides an audit trail.
|
||
- On fatal parse errors the process exits with the codes defined in §4 after showing an alert.
|
||
|
||
---
|
||
|
||
## 14. Security & Entitlements
|
||
|
||
`TargetPrint.entitlements`:
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<!-- Hardened Runtime -->
|
||
<key>com.apple.security.cs.allow-jit</key>
|
||
<false/>
|
||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||
<false/>
|
||
<key>com.apple.security.cs.disable-library-validation</key>
|
||
<false/>
|
||
|
||
<!-- Explicitly no App Sandbox -->
|
||
<!-- (absence of com.apple.security.app-sandbox is required) -->
|
||
</dict>
|
||
</plist>
|
||
```
|
||
|
||
Because the binary must call `cupsGetPPD` and inspect local print queues, the App Sandbox **cannot** be enabled. Hardened Runtime remains mandatory for notarisation.
|
||
|
||
---
|
||
|
||
## 15. Verification & Acceptance Tests
|
||
|
||
| ID | Test | Pass Criterion |
|
||
|----|------|----------------|
|
||
| G1 | Geometric accuracy | 300 DPI image of 2400 × 3000 px prints at exactly 8.00 × 10.00 in (± 0.1 mm) measured on the physical print or by examining the CUPS raster bounding box |
|
||
| P1 | Pixel integrity | Adjacent pure patches (RGB 255,0,0 next to 0,255,0) show zero anti-aliased transition pixels in the CUPS raster (inspect with `cupsfilter` or by capturing the raw raster) |
|
||
| C1 | Colour bypass | With `forceUnmanagedColor: true`, the printed patches match the source TIFF values within printer gamut limits (verified by spectrophotometer or known reference) |
|
||
| I1 | IPC compliance | `TargetPrint --job valid.json` opens the preview and immediately presents a correctly pre-configured `NSPrintPanel`; Color Matching controls are absent or disabled |
|
||
| A1 | AirPrint warning | Selecting an AirPrint queue displays the permanent warning badge |
|
||
| M1 | Multi-page | Two-page job prints page 1 then page 2 with correct geometry and no coordinate bleed |
|
||
|
||
Automated unit tests should cover:
|
||
|
||
- JSON decoding + validation
|
||
- DPI extraction edge cases (missing tags → 72 dpi)
|
||
- Geometry calculation pure functions
|
||
- AirPrint detection heuristics
|
||
|
||
UI and print-pipeline tests remain manual / semi-automated.
|
||
|
||
---
|
||
|
||
## 16. Tauri / Rust Integration Contract
|
||
|
||
On the ICCery (Rust) side the only required interaction is:
|
||
|
||
```rust
|
||
use std::env;
|
||
use std::fs;
|
||
use std::process::Command;
|
||
|
||
// 1. Serialise TargetJob
|
||
let job = TargetJob { /* … */ };
|
||
let json = serde_json::to_string_pretty(&job)?;
|
||
|
||
// 2. Write to temp file
|
||
let mut path = env::temp_dir();
|
||
path.push(format!("iccery_job_{}.json", uuid::Uuid::new_v4()));
|
||
fs::write(&path, json)?;
|
||
|
||
// 3. Launch asynchronously – do not wait
|
||
Command::new("/Applications/TargetPrint.app/Contents/MacOS/TargetPrint")
|
||
.arg("--job")
|
||
.arg(&path)
|
||
.spawn()?; // fire-and-forget
|
||
```
|
||
|
||
TargetPrint never signals completion back to Tauri; the user interacts solely with the native print dialog.
|
||
|
||
---
|
||
|
||
## 17. Future Extensions (Non-Goals for v1)
|
||
|
||
- Soft-proof overlay on the canvas
|
||
- Built-in target generator
|
||
- Direct CUPS job submission without `NSPrintPanel` (headless mode)
|
||
- Support for `.png` / 16-bit TIFF
|
||
- Per-page media / tray overrides
|
||
- Notarised Sparkle auto-update channel
|
||
|
||
These items are deliberately excluded from the initial implementation so that the core fidelity and colour-bypass guarantees can be stabilised first.
|
||
|
||
---
|
||
|
||
**End of Specification**
|
||
|
||
This document should be treated as the contract for all subsequent implementation work in the `ICCery-CPU` repository.
|