|
|
@@ -4,7 +4,13 @@ import PDFKit
|
|
|
import UniformTypeIdentifiers
|
|
|
|
|
|
enum PrintService {
|
|
|
+ @MainActor
|
|
|
+ private static func requirePremiumPrint(from window: NSWindow?) -> Bool {
|
|
|
+ PremiumAccess.require(feature: .printText, from: window ?? NSApp.keyWindow)
|
|
|
+ }
|
|
|
+
|
|
|
static func printContacts(_ contacts: [PrintableContact], from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard !contacts.isEmpty else {
|
|
|
showNoContactsAlert()
|
|
|
return
|
|
|
@@ -18,6 +24,7 @@ enum PrintService {
|
|
|
}
|
|
|
|
|
|
static func printCanvas(_ image: NSImage, from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard image.size.width > 0, image.size.height > 0 else {
|
|
|
showEmptyCanvasAlert()
|
|
|
return
|
|
|
@@ -26,6 +33,7 @@ enum PrintService {
|
|
|
}
|
|
|
|
|
|
static func printPhoto(_ image: NSImage, title: String = "Photo", from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard image.size.width > 0, image.size.height > 0 else {
|
|
|
showPrintFailedAlert()
|
|
|
return
|
|
|
@@ -59,6 +67,7 @@ enum PrintService {
|
|
|
}
|
|
|
|
|
|
static func printText(_ text: String, title: String = "Print Text", from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
guard !trimmed.isEmpty else {
|
|
|
showEmptyTextAlert()
|
|
|
@@ -105,6 +114,7 @@ enum PrintService {
|
|
|
title: String = "Scan",
|
|
|
from window: NSWindow? = nil
|
|
|
) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard hasPrintableContent(attributed) else {
|
|
|
showEmptyTextAlert()
|
|
|
return
|
|
|
@@ -150,6 +160,7 @@ enum PrintService {
|
|
|
title: String = "Scan",
|
|
|
from window: NSWindow? = nil
|
|
|
) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard !pages.isEmpty else {
|
|
|
showEmptyTextAlert()
|
|
|
return
|
|
|
@@ -192,6 +203,7 @@ enum PrintService {
|
|
|
}
|
|
|
|
|
|
static func print(urls: [URL], from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
guard !urls.isEmpty else { return }
|
|
|
|
|
|
let hostWindow = window ?? NSApp.keyWindow
|
|
|
@@ -205,6 +217,7 @@ enum PrintService {
|
|
|
}
|
|
|
|
|
|
static func printFile(at url: URL, from window: NSWindow? = nil) {
|
|
|
+ guard requirePremiumPrint(from: window) else { return }
|
|
|
let title = url.lastPathComponent
|
|
|
guard let document = printablePDFDocument(from: url) else {
|
|
|
showUnsupportedAlert(for: url)
|
|
|
@@ -253,22 +266,32 @@ enum PrintService {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+ private static var activePrintContext: PrintOperationContext?
|
|
|
+
|
|
|
+ private final class PrintOperationContext {
|
|
|
+ let document: PDFDocument
|
|
|
+ let pdfView: PDFView
|
|
|
+
|
|
|
+ init(document: PDFDocument) {
|
|
|
+ self.document = document
|
|
|
+ self.pdfView = PDFView(frame: NSRect(x: 0, y: 0, width: 612, height: 792))
|
|
|
+ self.pdfView.document = document
|
|
|
+ self.pdfView.autoScales = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static func printPDF(_ document: PDFDocument, title: String, from window: NSWindow?) {
|
|
|
- guard let data = document.dataRepresentation(),
|
|
|
- let printable = PDFDocument(data: data) else {
|
|
|
+ guard document.pageCount > 0 else {
|
|
|
showPrintFailedAlert()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ let context = PrintOperationContext(document: document)
|
|
|
+ activePrintContext = context
|
|
|
+ defer { activePrintContext = nil }
|
|
|
+
|
|
|
let printInfo = configuredPrintInfo()
|
|
|
- guard let operation = printable.printOperation(
|
|
|
- for: printInfo,
|
|
|
- scalingMode: .pageScaleToFit,
|
|
|
- autoRotate: false
|
|
|
- ) else {
|
|
|
- showPrintFailedAlert()
|
|
|
- return
|
|
|
- }
|
|
|
+ let operation = NSPrintOperation(view: context.pdfView, printInfo: printInfo)
|
|
|
runPrintOperation(operation, title: title, from: window)
|
|
|
}
|
|
|
|
|
|
@@ -282,108 +305,59 @@ enum PrintService {
|
|
|
|
|
|
private static func runPrintOperation(_ operation: NSPrintOperation, title: String, from window: NSWindow?) {
|
|
|
operation.showsPrintPanel = true
|
|
|
- operation.showsProgressPanel = false
|
|
|
+ operation.showsProgressPanel = true
|
|
|
operation.jobTitle = title
|
|
|
- operation.printPanel.options.insert([
|
|
|
- .showsCopies,
|
|
|
- .showsPageRange,
|
|
|
- .showsPaperSize,
|
|
|
- .showsOrientation,
|
|
|
- .showsScaling,
|
|
|
- ])
|
|
|
|
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
|
let parentWindow = window ?? NSApp.keyWindow ?? NSApp.mainWindow
|
|
|
parentWindow?.makeKeyAndOrderFront(nil)
|
|
|
|
|
|
- let observer = observePrintPanelAppearance(relativeTo: parentWindow)
|
|
|
- defer { NotificationCenter.default.removeObserver(observer) }
|
|
|
-
|
|
|
if let parentWindow {
|
|
|
- let callback = PrintRatingCallback(window: parentWindow)
|
|
|
+ PrintRatingCallback.shared.prepare(window: parentWindow)
|
|
|
operation.runModal(
|
|
|
for: parentWindow,
|
|
|
- delegate: callback,
|
|
|
+ delegate: PrintRatingCallback.shared,
|
|
|
didRun: #selector(PrintRatingCallback.printOperationDidRun(_:success:contextInfo:)),
|
|
|
contextInfo: nil
|
|
|
)
|
|
|
- withExtendedLifetime(callback) {}
|
|
|
+ PrintRatingCallback.shared.reset()
|
|
|
} else {
|
|
|
operation.run()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private final class PrintRatingCallback: NSObject {
|
|
|
- private let window: NSWindow?
|
|
|
+ static let shared = PrintRatingCallback()
|
|
|
|
|
|
- init(window: NSWindow?) {
|
|
|
+ private var window: NSWindow?
|
|
|
+
|
|
|
+ private override init() {
|
|
|
+ super.init()
|
|
|
+ }
|
|
|
+
|
|
|
+ func prepare(window: NSWindow?) {
|
|
|
self.window = window
|
|
|
}
|
|
|
|
|
|
+ func reset() {
|
|
|
+ window = nil
|
|
|
+ }
|
|
|
+
|
|
|
@objc func printOperationDidRun(
|
|
|
_ printOperation: NSPrintOperation,
|
|
|
success: Bool,
|
|
|
contextInfo: UnsafeMutableRawPointer?
|
|
|
) {
|
|
|
+ let sourceWindow = window
|
|
|
+ reset()
|
|
|
guard success else { return }
|
|
|
- Task { @MainActor in
|
|
|
- AppRatingManager.shared.recordSuccessfulPrint(from: window)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static func observePrintPanelAppearance(relativeTo parentWindow: NSWindow?) -> NSObjectProtocol {
|
|
|
- NotificationCenter.default.addObserver(
|
|
|
- forName: NSWindow.didBecomeKeyNotification,
|
|
|
- object: nil,
|
|
|
- queue: .main
|
|
|
- ) { notification in
|
|
|
- guard let panelWindow = notification.object as? NSWindow,
|
|
|
- isPrintPanelWindow(panelWindow) else { return }
|
|
|
- configurePrintPanelWindow(panelWindow, relativeTo: parentWindow)
|
|
|
- DispatchQueue.main.async {
|
|
|
- configurePrintPanelWindow(panelWindow, relativeTo: parentWindow)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static func configurePrintPanelWindow(_ panelWindow: NSWindow, relativeTo parentWindow: NSWindow?) {
|
|
|
- hideTrafficLights(on: panelWindow)
|
|
|
- if let parentWindow {
|
|
|
- center(panelWindow, relativeTo: parentWindow)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static func center(_ panelWindow: NSWindow, relativeTo parentWindow: NSWindow) {
|
|
|
- let parentFrame = parentWindow.frame
|
|
|
- var panelFrame = panelWindow.frame
|
|
|
- panelFrame.origin.x = parentFrame.midX - panelFrame.width / 2
|
|
|
- panelFrame.origin.y = parentFrame.midY - panelFrame.height / 2
|
|
|
- panelWindow.setFrame(panelFrame, display: true)
|
|
|
- }
|
|
|
-
|
|
|
- private static func isPrintPanelWindow(_ window: NSWindow) -> Bool {
|
|
|
- let className = String(describing: type(of: window))
|
|
|
- if className.localizedCaseInsensitiveContains("printpanel") {
|
|
|
- return true
|
|
|
- }
|
|
|
- return window.title == "Print"
|
|
|
- }
|
|
|
-
|
|
|
- private static func hideTrafficLights(on window: NSWindow) {
|
|
|
- for buttonType: NSWindow.ButtonType in [.closeButton, .miniaturizeButton, .zoomButton] {
|
|
|
- window.standardWindowButton(buttonType)?.isHidden = true
|
|
|
+ AppRatingManager.shared.recordSuccessfulPrint(from: sourceWindow)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static func configuredPrintInfo() -> NSPrintInfo {
|
|
|
let printInfo = (NSPrintInfo.shared.copy() as? NSPrintInfo) ?? NSPrintInfo()
|
|
|
-
|
|
|
- let printerName = AppSettings.effectiveDefaultPrinter
|
|
|
- if let printer = NSPrinter(name: printerName),
|
|
|
- NSPrinter.printerNames.contains(printerName) {
|
|
|
- printInfo.printer = printer
|
|
|
- }
|
|
|
+ applyPrinter(to: printInfo)
|
|
|
|
|
|
switch AppSettings.defaultPaperSize {
|
|
|
case .a4: printInfo.paperName = NSPrinter.PaperName("iso-a4")
|
|
|
@@ -403,6 +377,21 @@ enum PrintService {
|
|
|
return printInfo
|
|
|
}
|
|
|
|
|
|
+ private static func applyPrinter(to printInfo: NSPrintInfo) {
|
|
|
+ let available = NSPrinter.printerNames
|
|
|
+ guard !available.isEmpty else { return }
|
|
|
+
|
|
|
+ let preferred = AppSettings.effectiveDefaultPrinter
|
|
|
+ if available.contains(preferred), let printer = NSPrinter(name: preferred) {
|
|
|
+ printInfo.printer = printer
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if let printer = NSPrinter(name: available[0]) {
|
|
|
+ printInfo.printer = printer
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static func hasPrintableContent(_ attributed: NSAttributedString) -> Bool {
|
|
|
if !attributed.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
|
return true
|