Pārlūkot izejas kodu

Enable sandboxed printing with the macOS print dialog.

Add the print entitlement and route photos through PDF print operations so the system print panel opens reliably from the photo preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 4 stundas atpakaļ
vecāks
revīzija
904d792357

+ 1 - 1
smart_printer/DocumentPickerService.swift

@@ -131,7 +131,7 @@ enum DocumentPickerService {
131
                 PhotoPreviewService.present(urls: photos, from: window)
131
                 PhotoPreviewService.present(urls: photos, from: window)
132
             }
132
             }
133
             if !documents.isEmpty {
133
             if !documents.isEmpty {
134
-                PrintService.print(urls: documents)
134
+                PrintService.print(urls: documents, from: window)
135
             }
135
             }
136
         case .importFile:
136
         case .importFile:
137
             let added = ImportedFilesStore.add(urls: matched)
137
             let added = ImportedFilesStore.add(urls: matched)

+ 1 - 1
smart_printer/PhotoPreviewView.swift

@@ -207,7 +207,7 @@ final class PhotoPreviewOverlayView: NSView, AppearanceRefreshable {
207
 
207
 
208
     private func printCurrentPhoto() {
208
     private func printCurrentPhoto() {
209
         guard let url = currentURL() else { return }
209
         guard let url = currentURL() else { return }
210
-        PrintService.print(urls: [url])
210
+        PrintService.print(urls: [url], from: window)
211
 
211
 
212
         if currentIndex < urls.count - 1 {
212
         if currentIndex < urls.count - 1 {
213
             showPhoto(at: currentIndex + 1)
213
             showPhoto(at: currentIndex + 1)

+ 98 - 24
smart_printer/PrintService.swift

@@ -3,7 +3,8 @@ import PDFKit
3
 import UniformTypeIdentifiers
3
 import UniformTypeIdentifiers
4
 
4
 
5
 enum PrintService {
5
 enum PrintService {
6
-    static func print(urls: [URL]) {
6
+    static func print(urls: [URL], from window: NSWindow? = nil) {
7
+        _ = window
7
         guard !urls.isEmpty else { return }
8
         guard !urls.isEmpty else { return }
8
 
9
 
9
         for url in urls {
10
         for url in urls {
@@ -16,72 +17,145 @@ enum PrintService {
16
     }
17
     }
17
 
18
 
18
     private static func printFile(at url: URL) {
19
     private static func printFile(at url: URL) {
19
-        let type = UTType(filenameExtension: url.pathExtension) ?? .data
20
+        let type = contentType(for: url) ?? UTType(filenameExtension: url.pathExtension) ?? .data
20
 
21
 
21
         if type.conforms(to: .pdf), let document = PDFDocument(url: url) {
22
         if type.conforms(to: .pdf), let document = PDFDocument(url: url) {
22
-            printPDF(document)
23
+            printPDF(document, title: url.lastPathComponent)
23
         } else if type.conforms(to: .image), let image = NSImage(contentsOf: url) {
24
         } else if type.conforms(to: .image), let image = NSImage(contentsOf: url) {
24
-            printImage(image)
25
+            printImage(image, title: url.lastPathComponent)
26
+        } else if let image = NSImage(contentsOf: url) {
27
+            printImage(image, title: url.lastPathComponent)
25
         } else if type.conforms(to: .plainText) || type.conforms(to: .text),
28
         } else if type.conforms(to: .plainText) || type.conforms(to: .text),
26
                   let text = try? String(contentsOf: url, encoding: .utf8) {
29
                   let text = try? String(contentsOf: url, encoding: .utf8) {
27
-            printText(text)
30
+            printText(text, title: url.lastPathComponent)
28
         } else if type.conforms(to: .rtf),
31
         } else if type.conforms(to: .rtf),
29
                   let data = try? Data(contentsOf: url),
32
                   let data = try? Data(contentsOf: url),
30
                   let attributed = NSAttributedString(rtf: data, documentAttributes: nil) {
33
                   let attributed = NSAttributedString(rtf: data, documentAttributes: nil) {
31
-            printAttributedText(attributed)
34
+            printAttributedText(attributed, title: url.lastPathComponent)
32
         } else if type.conforms(to: .html),
35
         } else if type.conforms(to: .html),
33
                   let data = try? Data(contentsOf: url),
36
                   let data = try? Data(contentsOf: url),
34
                   let attributed = NSAttributedString(html: data, documentAttributes: nil) {
37
                   let attributed = NSAttributedString(html: data, documentAttributes: nil) {
35
-            printAttributedText(attributed)
38
+            printAttributedText(attributed, title: url.lastPathComponent)
36
         } else {
39
         } else {
37
             showUnsupportedAlert(for: url)
40
             showUnsupportedAlert(for: url)
38
         }
41
         }
39
     }
42
     }
40
 
43
 
41
-    private static func printPDF(_ document: PDFDocument) {
44
+    private static func contentType(for url: URL) -> UTType? {
45
+        if let values = try? url.resourceValues(forKeys: [.contentTypeKey]),
46
+           let type = values.contentType {
47
+            return type
48
+        }
49
+        let ext = url.pathExtension.lowercased()
50
+        guard !ext.isEmpty else { return nil }
51
+        return UTType(filenameExtension: ext)
52
+    }
53
+
54
+    private static func printPDF(_ document: PDFDocument, title: String) {
42
         let printInfo = configuredPrintInfo()
55
         let printInfo = configuredPrintInfo()
43
         guard let operation = document.printOperation(
56
         guard let operation = document.printOperation(
44
             for: printInfo,
57
             for: printInfo,
45
             scalingMode: .pageScaleToFit,
58
             scalingMode: .pageScaleToFit,
46
             autoRotate: true
59
             autoRotate: true
47
         ) else { return }
60
         ) else { return }
48
-        operation.run()
61
+        runPrintOperation(operation, title: title)
49
     }
62
     }
50
 
63
 
51
-    private static func printImage(_ image: NSImage) {
52
-        let size = image.size
53
-        guard size.width > 0, size.height > 0 else { return }
54
-
55
-        let imageView = NSImageView(frame: NSRect(origin: .zero, size: size))
56
-        imageView.image = image
57
-        imageView.imageScaling = .scaleProportionallyUpOrDown
58
-
59
-        let operation = NSPrintOperation(view: imageView, printInfo: configuredPrintInfo())
60
-        operation.run()
64
+    private static func printImage(_ image: NSImage, title: String) {
65
+        guard let document = pdfDocument(from: image) else { return }
66
+        printPDF(document, title: title)
61
     }
67
     }
62
 
68
 
63
-    private static func printText(_ text: String) {
64
-        printAttributedText(NSAttributedString(string: text))
69
+    private static func printText(_ text: String, title: String) {
70
+        printAttributedText(NSAttributedString(string: text), title: title)
65
     }
71
     }
66
 
72
 
67
-    private static func printAttributedText(_ text: NSAttributedString) {
68
-        let textView = NSTextView(frame: NSRect(x: 0, y: 0, width: 612, height: 792))
73
+    private static func printAttributedText(_ text: NSAttributedString, title: String) {
74
+        let printInfo = configuredPrintInfo()
75
+        let pageSize = printInfo.paperSize
76
+        let textView = NSTextView(frame: NSRect(origin: .zero, size: pageSize))
69
         textView.textStorage?.setAttributedString(text)
77
         textView.textStorage?.setAttributedString(text)
70
         textView.isEditable = false
78
         textView.isEditable = false
71
 
79
 
72
-        let operation = NSPrintOperation(view: textView, printInfo: configuredPrintInfo())
80
+        let operation = NSPrintOperation(view: textView, printInfo: printInfo)
81
+        runPrintOperation(operation, title: title)
82
+    }
83
+
84
+    private static func runPrintOperation(_ operation: NSPrintOperation, title: String) {
85
+        operation.showsPrintPanel = true
86
+        operation.showsProgressPanel = true
87
+        operation.jobTitle = title
88
+        operation.printPanel.options.insert([
89
+            .showsCopies,
90
+            .showsPageRange,
91
+            .showsPaperSize,
92
+            .showsOrientation,
93
+            .showsScaling,
94
+        ])
73
         operation.run()
95
         operation.run()
74
     }
96
     }
75
 
97
 
76
     private static func configuredPrintInfo() -> NSPrintInfo {
98
     private static func configuredPrintInfo() -> NSPrintInfo {
77
         let printInfo = NSPrintInfo.shared.copy() as! NSPrintInfo
99
         let printInfo = NSPrintInfo.shared.copy() as! NSPrintInfo
100
+
78
         let printerName = AppSettings.effectiveDefaultPrinter
101
         let printerName = AppSettings.effectiveDefaultPrinter
79
         if let printer = NSPrinter(name: printerName) {
102
         if let printer = NSPrinter(name: printerName) {
80
             printInfo.printer = printer
103
             printInfo.printer = printer
81
         }
104
         }
105
+
106
+        switch AppSettings.defaultPaperSize {
107
+        case .a4: printInfo.paperName = NSPrinter.PaperName("iso-a4")
108
+        case .letter: printInfo.paperName = NSPrinter.PaperName("na-letter")
109
+        case .legal: printInfo.paperName = NSPrinter.PaperName("na-legal")
110
+        }
111
+
112
+        printInfo.horizontalPagination = .fit
113
+        printInfo.verticalPagination = .fit
114
+        printInfo.isHorizontallyCentered = true
115
+        printInfo.isVerticallyCentered = true
116
+        printInfo.topMargin = 36
117
+        printInfo.bottomMargin = 36
118
+        printInfo.leftMargin = 36
119
+        printInfo.rightMargin = 36
120
+
82
         return printInfo
121
         return printInfo
83
     }
122
     }
84
 
123
 
124
+    private static func pdfDocument(from image: NSImage) -> PDFDocument? {
125
+        let printInfo = configuredPrintInfo()
126
+        let pageSize = printInfo.paperSize
127
+        guard pageSize.width > 0, pageSize.height > 0 else { return nil }
128
+
129
+        let pdfData = NSMutableData()
130
+        var mediaBox = CGRect(origin: .zero, size: pageSize)
131
+
132
+        guard let consumer = CGDataConsumer(data: pdfData as CFMutableData),
133
+              let context = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else {
134
+            return nil
135
+        }
136
+
137
+        context.beginPDFPage(nil)
138
+
139
+        let imageSize = image.size
140
+        if imageSize.width > 0, imageSize.height > 0,
141
+           let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
142
+            let scale = min(pageSize.width / imageSize.width, pageSize.height / imageSize.height)
143
+            let drawSize = CGSize(width: imageSize.width * scale, height: imageSize.height * scale)
144
+            let drawRect = CGRect(
145
+                x: (pageSize.width - drawSize.width) / 2,
146
+                y: (pageSize.height - drawSize.height) / 2,
147
+                width: drawSize.width,
148
+                height: drawSize.height
149
+            )
150
+            context.draw(cgImage, in: drawRect)
151
+        }
152
+
153
+        context.endPDFPage()
154
+        context.closePDF()
155
+
156
+        return PDFDocument(data: pdfData as Data)
157
+    }
158
+
85
     private static func showUnsupportedAlert(for url: URL) {
159
     private static func showUnsupportedAlert(for url: URL) {
86
         let alert = NSAlert()
160
         let alert = NSAlert()
87
         alert.messageText = "Unsupported File"
161
         alert.messageText = "Unsupported File"

+ 2 - 0
smart_printer/smart_printer.entitlements

@@ -8,5 +8,7 @@
8
 	<true/>
8
 	<true/>
9
 	<key>com.apple.security.network.client</key>
9
 	<key>com.apple.security.network.client</key>
10
 	<true/>
10
 	<true/>
11
+	<key>com.apple.security.print</key>
12
+	<true/>
11
 </dict>
13
 </dict>
12
 </plist>
14
 </plist>