Bladeren bron

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 maand geleden
bovenliggende
commit
904d792357

+ 1 - 1
smart_printer/DocumentPickerService.swift

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

+ 1 - 1
smart_printer/PhotoPreviewView.swift

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

+ 98 - 24
smart_printer/PrintService.swift

@@ -3,7 +3,8 @@ import PDFKit
 import UniformTypeIdentifiers
 
 enum PrintService {
-    static func print(urls: [URL]) {
+    static func print(urls: [URL], from window: NSWindow? = nil) {
+        _ = window
         guard !urls.isEmpty else { return }
 
         for url in urls {
@@ -16,72 +17,145 @@ enum PrintService {
     }
 
     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) {
-            printPDF(document)
+            printPDF(document, title: url.lastPathComponent)
         } 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),
                   let text = try? String(contentsOf: url, encoding: .utf8) {
-            printText(text)
+            printText(text, title: url.lastPathComponent)
         } else if type.conforms(to: .rtf),
                   let data = try? Data(contentsOf: url),
                   let attributed = NSAttributedString(rtf: data, documentAttributes: nil) {
-            printAttributedText(attributed)
+            printAttributedText(attributed, title: url.lastPathComponent)
         } else if type.conforms(to: .html),
                   let data = try? Data(contentsOf: url),
                   let attributed = NSAttributedString(html: data, documentAttributes: nil) {
-            printAttributedText(attributed)
+            printAttributedText(attributed, title: url.lastPathComponent)
         } else {
             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()
         guard let operation = document.printOperation(
             for: printInfo,
             scalingMode: .pageScaleToFit,
             autoRotate: true
         ) 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.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()
     }
 
     private static func configuredPrintInfo() -> NSPrintInfo {
         let printInfo = NSPrintInfo.shared.copy() as! NSPrintInfo
+
         let printerName = AppSettings.effectiveDefaultPrinter
         if let printer = NSPrinter(name: printerName) {
             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
     }
 
+    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) {
         let alert = NSAlert()
         alert.messageText = "Unsupported File"

+ 2 - 0
smart_printer/smart_printer.entitlements

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