Files
ICCery-CPU/Sources/Printing/PrintEngine.swift
gronod 474888541b
Build macOS Packages / Build macOS (Intel) (pull_request) Successful in 32s
Build macOS Packages / Build macOS (Apple Silicon) (pull_request) Successful in 37s
Build macOS Packages / Build macOS (Universal) (pull_request) Failing after 10s
feat: add hardware print quality option supporting Canon 5-notch Custom slider and Epson modes (Closes #5)
2026-09-07 23:37:01 +01:00

190 lines
7.7 KiB
Swift

import AppKit
import Foundation
/// SPEC §2 / §7 — `NSPrintOperation` + `NSPrintInfo` + CorePrinting dictionary
/// injection + UI policy (lock Color Matching pane).
final class PrintEngine {
var jobTitle: String
var printerName: String
var mediaSize: String
var mediaType: String?
var paperSource: String?
var printQuality: String?
var resolution: String?
var scaling: Double
var centered: Bool
var landscape: Bool
var colorMode: ColorMatchingMode
var lockColorManagement: Bool
var allowBasicDriverChanges: Bool
init(job: TargetJob) {
jobTitle = job.jobTitle
printerName = job.printSettings.printerName
mediaSize = job.printSettings.mediaSize
mediaType = job.printSettings.mediaType
paperSource = job.printSettings.paperSource
printQuality = job.printSettings.printQuality
resolution = job.printSettings.resolution
scaling = job.printSettings.scaling
centered = job.printSettings.centered
landscape = false
colorMode = ColorMatchingMode.from(forceUnmanagedColor: job.printSettings.forceUnmanagedColor)
lockColorManagement = job.uiPolicy.lockColorManagement
allowBasicDriverChanges = job.uiPolicy.allowBasicDriverChanges
}
init() {
jobTitle = "Untitled"
printerName = ""
mediaSize = "A4"
mediaType = nil
paperSource = nil
printQuality = nil
resolution = nil
scaling = 1.0
centered = true
landscape = false
colorMode = .unmanaged
lockColorManagement = true
allowBasicDriverChanges = true
}
/// Configures `NSPrintInfo` for `NSPrintOperation` (SPEC §4.4, §10.3).
func makePrintInfo() throws -> NSPrintInfo {
let info = NSPrintInfo.shared.copy() as! NSPrintInfo
info.jobDisposition = .spool
info.isHorizontallyCentered = false
info.isVerticallyCentered = false
info.leftMargin = 0
info.rightMargin = 0
info.topMargin = 0
info.bottomMargin = 0
info.horizontalPagination = .clip
info.verticalPagination = .clip
info.orientation = landscape ? .landscape : .portrait
info.scalingFactor = 1.0 // geometric scaling is applied in the view, not here
if !printerName.isEmpty {
info.printer = NSPrinter(name: printerName) ?? info.printer
}
let paper = Geometry.paper(named: mediaSize).oriented(landscape: landscape)
info.paperSize = paper.size
info.paperName = NSPrinter.PaperName(rawValue: mediaSize)
ColorMatching.apply(colorMode, to: info)
var resolvedQueue: PrinterQueue? = nil
do {
if let queue = try CUPSManager.namedQueue(printerName) {
resolvedQueue = queue
let bypass = CUPSManager.vendorColorBypass(make: queue.make, model: queue.model, ppdText: queue.ppdText)
CUPSManager.applyVendorBypass(bypass, to: info)
}
} catch {
TPLog.error("CUPS lookup failed: \(error)")
throw AppError.printer(String(describing: error))
}
applyOptionalPPDKeys(to: info, queue: resolvedQueue)
return info
}
func applyOptionalPPDKeys(to info: NSPrintInfo, queue: PrinterQueue?) {
var extras: [String: String] = [:]
if let mediaType = mediaType {
let key = queue?.mediaTypeKeyword ?? "MediaType"
if let match = queue?.mediaTypeChoices.first(where: { $0.name == mediaType || $0.title.caseInsensitiveCompare(mediaType) == .orderedSame }) {
extras[key] = match.name
} else {
extras[key] = mediaType
}
}
if let paperSource = paperSource {
let key = queue?.trayKeyword ?? "InputSlot"
if let match = queue?.trayChoices.first(where: { $0.name == paperSource || $0.title.caseInsensitiveCompare(paperSource) == .orderedSame }) {
extras[key] = match.name
} else if paperSource.lowercased() != "auto" || key == "InputSlot" {
extras[key] = paperSource
}
}
if let printQuality = printQuality {
let key = queue?.qualityKeyword ?? "PrintQuality"
let resolvedCode: String
if let match = queue?.qualityChoices.first(where: { $0.name == printQuality || $0.title.caseInsensitiveCompare(printQuality) == .orderedSame }) {
resolvedCode = match.name
} else {
resolvedCode = printQuality
}
if key == "CNIJPrintQuality" {
switch resolvedCode {
case "0": // Super Fine (notch 5 — Custom mode)
extras["CNIJPrintQuality"] = "0"
extras["CNIJPrintMode2"] = "5"
extras["CNIJPQualitySlider"] = "5"
case "5": // Fine (notch 4 — High preset)
extras["CNIJPrintQuality"] = "5"
extras["CNIJPrintMode2"] = "1"
extras["CNIJPQualitySlider"] = "4"
case "10": // Normal(Fine) (notch 3 — Standard preset)
extras["CNIJPrintQuality"] = "10"
extras["CNIJPrintMode2"] = "2"
extras["CNIJPQualitySlider"] = "3"
case "15": // Normal(Fast) (notch 2)
extras["CNIJPrintQuality"] = "15"
extras["CNIJPrintMode2"] = "5"
extras["CNIJPQualitySlider"] = "2"
case "20": // Fast (notch 1 — Fast preset)
extras["CNIJPrintQuality"] = "20"
extras["CNIJPrintMode2"] = "3"
extras["CNIJPQualitySlider"] = "1"
default:
extras["CNIJPrintQuality"] = resolvedCode
}
} else if resolvedCode.lowercased() != "auto" || key == "cupsPrintQuality" {
extras[key] = resolvedCode
}
}
if let resolution = resolution { extras["Resolution"] = resolution }
if extras.isEmpty { return }
CUPSManager.applyVendorBypass(extras, to: info)
}
/// Presents `NSPrintPanel` as a sheet on `window` (SPEC §4.5).
func run(view: TargetCanvasView, window: NSWindow, presentPanel: Bool) {
do {
let info = try makePrintInfo()
view.printInfo = info
view.centered = centered
view.scaling = CGFloat(scaling)
let op = NSPrintOperation(view: view, printInfo: info)
op.jobTitle = jobTitle
op.showsPrintPanel = presentPanel
op.showsProgressPanel = true
op.canSpawnSeparateThread = false
ColorMatching.configurePanel(
op.printPanel,
lockColorManagement: lockColorManagement,
allowBasicDriverChanges: allowBasicDriverChanges
)
// Strip again just before the panel runs — system accessories may
// be installed lazily when the panel is created.
ColorMatching.stripColorMatchingAccessories(from: op.printPanel)
TPLog.info("NSPrintOperation begin — printer=\(printerName) media=\(mediaSize) mode=\(colorMode.pmValue)")
op.runModal(for: window, delegate: self, didRun: #selector(printOpDidRun(_:success:contextInfo:)), contextInfo: nil)
} catch {
PreviewWindowController.presentError(error, on: window)
}
}
@objc private func printOpDidRun(_ op: NSPrintOperation, success: Bool, contextInfo: UnsafeMutableRawPointer?) {
TPLog.info("NSPrintOperation finished success=\(success)")
// SPEC §4: exit 0 for cancel or completed print.
AppDelegate.shared?.pendingExitCode = .success
}
}