132 lines
4.9 KiB
Swift
132 lines
4.9 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 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
|
|
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
|
|
resolution = nil
|
|
scaling = 1.0
|
|
centered = true
|
|
landscape = false
|
|
colorMode = .unmanaged
|
|
lockColorManagement = true
|
|
allowBasicDriverChanges = true
|
|
}
|
|
|
|
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)
|
|
|
|
do {
|
|
if let queue = try CUPSManager.namedQueue(printerName) {
|
|
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)
|
|
return info
|
|
}
|
|
|
|
private func applyOptionalPPDKeys(to info: NSPrintInfo) {
|
|
var extras: [String: String] = [:]
|
|
if let mediaType = mediaType { extras["MediaType"] = mediaType }
|
|
if let paperSource = paperSource { extras["InputSlot"] = paperSource }
|
|
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
|
|
}
|
|
}
|