Ver código fonte

Export filled CV PDF from the on-screen composite so styling matches the preview.

dataWithPDF often strips layer-backed chrome; rasterising via cacheDisplay preserves it, with a vector fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 2 meses atrás
pai
commit
d57207a2d3
1 arquivos alterados com 38 adições e 2 exclusões
  1. 38 2
      App for Indeed/Views/CVFilledPreviewPageView.swift

+ 38 - 2
App for Indeed/Views/CVFilledPreviewPageView.swift

@@ -63,7 +63,7 @@ final class CVFilledPreviewPageView: NSView {
         editCheckbox.target = self
         editCheckbox.target = self
         editCheckbox.action = #selector(didToggleEditMode)
         editCheckbox.action = #selector(didToggleEditMode)
 
 
-        let subtitle = NSTextField(wrappingLabelWithString: "Layout matches the CV Maker thumbnail for this template. Turn on editing to adjust wording, then export a vector PDF.")
+        let subtitle = NSTextField(wrappingLabelWithString: "Layout matches the CV Maker thumbnail for this template. Turn on editing to adjust wording, then export a PDF that matches what you see here (fonts, columns, colours, and rules).")
         subtitle.font = .systemFont(ofSize: 12, weight: .regular)
         subtitle.font = .systemFont(ofSize: 12, weight: .regular)
         subtitle.textColor = Self.secondaryText
         subtitle.textColor = Self.secondaryText
         subtitle.maximumNumberOfLines = 0
         subtitle.maximumNumberOfLines = 0
@@ -165,7 +165,10 @@ final class CVFilledPreviewPageView: NSView {
         doc.layoutSubtreeIfNeeded()
         doc.layoutSubtreeIfNeeded()
         let bounds = doc.bounds
         let bounds = doc.bounds
         guard !bounds.isEmpty else { return }
         guard !bounds.isEmpty else { return }
-        let data = doc.dataWithPDF(inside: bounds)
+        // `dataWithPDF` uses the print pipeline and often drops layer-backed chrome (card fill,
+        // borders, hairlines, tinted sidebars) while keeping text — so the PDF looks like plain
+        // text. Rasterising what is actually drawn on screen preserves the full layout.
+        let data = doc.pdfDataMatchingScreenAppearance() ?? doc.dataWithPDF(inside: bounds)
         guard !data.isEmpty else {
         guard !data.isEmpty else {
             presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
             presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
             return
             return
@@ -215,3 +218,36 @@ final class CVFilledPreviewPageView: NSView {
         }
         }
     }
     }
 }
 }
+
+// MARK: - PDF export (screen-accurate)
+
+private extension NSView {
+
+    /// Single-page PDF that matches the composited on-screen appearance, including
+    /// `CALayer` backgrounds, borders, and rules. Callers fall back to `dataWithPDF` when this returns nil.
+    func pdfDataMatchingScreenAppearance() -> Data? {
+        layoutSubtreeIfNeeded()
+        let rect = bounds
+        guard rect.width > 1, rect.height > 1 else { return nil }
+
+        guard let rep = bitmapImageRepForCachingDisplay(in: rect) else { return nil }
+        cacheDisplay(in: rect, to: rep)
+        guard let cgImage = rep.cgImage else { return nil }
+
+        let data = NSMutableData()
+        guard let consumer = CGDataConsumer(data: data as CFMutableData) else { return nil }
+        var mediaBox = CGRect(origin: .zero, size: NSSize(width: rect.width, height: rect.height))
+        guard let ctx = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else { return nil }
+
+        ctx.beginPDFPage(nil)
+        ctx.interpolationQuality = .high
+        // Draw without an extra Y-flip: `cacheDisplay`’s bitmap + `cgImage` already match PDF user space
+        // on macOS here; a translate/scale(-1) was inverting the whole page in Preview.
+        ctx.draw(cgImage, in: mediaBox)
+        ctx.endPDFPage()
+        ctx.closePDF()
+
+        let out = data as Data
+        return out.isEmpty ? nil : out
+    }
+}