Răsfoiți Sursa

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 1 lună în urmă
părinte
comite
904d792357

+ 1 - 1
smart_printer/DocumentPickerService.swift

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

+ 1 - 1
smart_printer/PhotoPreviewView.swift

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

+ 98 - 24
smart_printer/PrintService.swift

@@ -3,7 +3,8 @@ import PDFKit
 import UniformTypeIdentifiers
 import UniformTypeIdentifiers
 
 
 enum PrintService {
 enum PrintService {
-    static func print(urls: [URL]) {
+    static func print(urls: [URL], from window: NSWindow? = nil) {
+        _ = window
         guard !urls.isEmpty else { return }
         guard !urls.isEmpty else { return }
 
 
         for url in urls {
         for url in urls {
@@ -16,72 +17,145 @@ enum PrintService {
     }
     }
 
 
     private static func printFile(at url: URL) {
     private static func printFile(at url: URL) {
-        let type = UTType(filenameExtension: url.pathExtension) ?? .data
+        let type = contentType(for: url) ?? UTType(filenameExtension: url.pathExtension) ?? .data
 
 
         if type.conforms(to: .pdf), let document = PDFDocument(url: url) {
         if type.conforms(to: .pdf), let document = PDFDocument(url: url) {
-            printPDF(document)
+            printPDF(document, title: url.lastPathComponent)
         } else if type.conforms(to: .image), let image = NSImage(contentsOf: url) {
         } else if type.conforms(to: .image), let image = NSImage(contentsOf: url) {
-            printImage(image)
+            printImage(image, title: url.lastPathComponent)
+        } else if let image = NSImage(contentsOf: url) {
+            printImage(image, title: url.lastPathComponent)
         } else if type.conforms(to: .plainText) || type.conforms(to: .text),
         } else if type.conforms(to: .plainText) || type.conforms(to: .text),
                   let text = try? String(contentsOf: url, encoding: .utf8) {
                   let text = try? String(contentsOf: url, encoding: .utf8) {
-            printText(text)
+            printText(text, title: url.lastPathComponent)
         } else if type.conforms(to: .rtf),
         } else if type.conforms(to: .rtf),
                   let data = try? Data(contentsOf: url),
                   let data = try? Data(contentsOf: url),
                   let attributed = NSAttributedString(rtf: data, documentAttributes: nil) {
                   let attributed = NSAttributedString(rtf: data, documentAttributes: nil) {
-            printAttributedText(attributed)
+            printAttributedText(attributed, title: url.lastPathComponent)
         } else if type.conforms(to: .html),
         } else if type.conforms(to: .html),
                   let data = try? Data(contentsOf: url),
                   let data = try? Data(contentsOf: url),
                   let attributed = NSAttributedString(html: data, documentAttributes: nil) {
                   let attributed = NSAttributedString(html: data, documentAttributes: nil) {
-            printAttributedText(attributed)
+            printAttributedText(attributed, title: url.lastPathComponent)
         } else {
         } else {
             showUnsupportedAlert(for: url)
             showUnsupportedAlert(for: url)
         }
         }
     }
     }
 
 
-    private static func printPDF(_ document: PDFDocument) {
+    private static func contentType(for url: URL) -> UTType? {
+        if let values = try? url.resourceValues(forKeys: [.contentTypeKey]),
+           let type = values.contentType {
+            return type
+        }
+        let ext = url.pathExtension.lowercased()
+        guard !ext.isEmpty else { return nil }
+        return UTType(filenameExtension: ext)
+    }
+
+    private static func printPDF(_ document: PDFDocument, title: String) {
         let printInfo = configuredPrintInfo()
         let printInfo = configuredPrintInfo()
         guard let operation = document.printOperation(
         guard let operation = document.printOperation(
             for: printInfo,
             for: printInfo,
             scalingMode: .pageScaleToFit,
             scalingMode: .pageScaleToFit,
             autoRotate: true
             autoRotate: true
         ) else { return }
         ) else { return }
-        operation.run()
+        runPrintOperation(operation, title: title)
     }
     }
 
 
-    private static func printImage(_ image: NSImage) {
-        let size = image.size
-        guard size.width > 0, size.height > 0 else { return }
-
-        let imageView = NSImageView(frame: NSRect(origin: .zero, size: size))
-        imageView.image = image
-        imageView.imageScaling = .scaleProportionallyUpOrDown
-
-        let operation = NSPrintOperation(view: imageView, printInfo: configuredPrintInfo())
-        operation.run()
+    private static func printImage(_ image: NSImage, title: String) {
+        guard let document = pdfDocument(from: image) else { return }
+        printPDF(document, title: title)
     }
     }
 
 
-    private static func printText(_ text: String) {
-        printAttributedText(NSAttributedString(string: text))
+    private static func printText(_ text: String, title: String) {
+        printAttributedText(NSAttributedString(string: text), title: title)
     }
     }
 
 
-    private static func printAttributedText(_ text: NSAttributedString) {
-        let textView = NSTextView(frame: NSRect(x: 0, y: 0, width: 612, height: 792))
+    private static func printAttributedText(_ text: NSAttributedString, title: String) {
+        let printInfo = configuredPrintInfo()
+        let pageSize = printInfo.paperSize
+        let textView = NSTextView(frame: NSRect(origin: .zero, size: pageSize))
         textView.textStorage?.setAttributedString(text)
         textView.textStorage?.setAttributedString(text)
         textView.isEditable = false
         textView.isEditable = false
 
 
-        let operation = NSPrintOperation(view: textView, printInfo: configuredPrintInfo())
+        let operation = NSPrintOperation(view: textView, printInfo: printInfo)
+        runPrintOperation(operation, title: title)
+    }
+
+    private static func runPrintOperation(_ operation: NSPrintOperation, title: String) {
+        operation.showsPrintPanel = true
+        operation.showsProgressPanel = true
+        operation.jobTitle = title
+        operation.printPanel.options.insert([
+            .showsCopies,
+            .showsPageRange,
+            .showsPaperSize,
+            .showsOrientation,
+            .showsScaling,
+        ])
         operation.run()
         operation.run()
     }
     }
 
 
     private static func configuredPrintInfo() -> NSPrintInfo {
     private static func configuredPrintInfo() -> NSPrintInfo {
         let printInfo = NSPrintInfo.shared.copy() as! NSPrintInfo
         let printInfo = NSPrintInfo.shared.copy() as! NSPrintInfo
+
         let printerName = AppSettings.effectiveDefaultPrinter
         let printerName = AppSettings.effectiveDefaultPrinter
         if let printer = NSPrinter(name: printerName) {
         if let printer = NSPrinter(name: printerName) {
             printInfo.printer = printer
             printInfo.printer = printer
         }
         }
+
+        switch AppSettings.defaultPaperSize {
+        case .a4: printInfo.paperName = NSPrinter.PaperName("iso-a4")
+        case .letter: printInfo.paperName = NSPrinter.PaperName("na-letter")
+        case .legal: printInfo.paperName = NSPrinter.PaperName("na-legal")
+        }
+
+        printInfo.horizontalPagination = .fit
+        printInfo.verticalPagination = .fit
+        printInfo.isHorizontallyCentered = true
+        printInfo.isVerticallyCentered = true
+        printInfo.topMargin = 36
+        printInfo.bottomMargin = 36
+        printInfo.leftMargin = 36
+        printInfo.rightMargin = 36
+
         return printInfo
         return printInfo
     }
     }
 
 
+    private static func pdfDocument(from image: NSImage) -> PDFDocument? {
+        let printInfo = configuredPrintInfo()
+        let pageSize = printInfo.paperSize
+        guard pageSize.width > 0, pageSize.height > 0 else { return nil }
+
+        let pdfData = NSMutableData()
+        var mediaBox = CGRect(origin: .zero, size: pageSize)
+
+        guard let consumer = CGDataConsumer(data: pdfData as CFMutableData),
+              let context = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else {
+            return nil
+        }
+
+        context.beginPDFPage(nil)
+
+        let imageSize = image.size
+        if imageSize.width > 0, imageSize.height > 0,
+           let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
+            let scale = min(pageSize.width / imageSize.width, pageSize.height / imageSize.height)
+            let drawSize = CGSize(width: imageSize.width * scale, height: imageSize.height * scale)
+            let drawRect = CGRect(
+                x: (pageSize.width - drawSize.width) / 2,
+                y: (pageSize.height - drawSize.height) / 2,
+                width: drawSize.width,
+                height: drawSize.height
+            )
+            context.draw(cgImage, in: drawRect)
+        }
+
+        context.endPDFPage()
+        context.closePDF()
+
+        return PDFDocument(data: pdfData as Data)
+    }
+
     private static func showUnsupportedAlert(for url: URL) {
     private static func showUnsupportedAlert(for url: URL) {
         let alert = NSAlert()
         let alert = NSAlert()
         alert.messageText = "Unsupported File"
         alert.messageText = "Unsupported File"

+ 2 - 0
smart_printer/smart_printer.entitlements

@@ -8,5 +8,7 @@
 	<true/>
 	<true/>
 	<key>com.apple.security.network.client</key>
 	<key>com.apple.security.network.client</key>
 	<true/>
 	<true/>
+	<key>com.apple.security.print</key>
+	<true/>
 </dict>
 </dict>
 </plist>
 </plist>