Просмотр исходного кода

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 недель назад: 3
Родитель
Сommit
d57207a2d3
1 измененных файлов с 38 добавлено и 2 удалено
  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 {
63
         editCheckbox.target = self
63
         editCheckbox.target = self
64
         editCheckbox.action = #selector(didToggleEditMode)
64
         editCheckbox.action = #selector(didToggleEditMode)
65
 
65
 
66
-        let subtitle = NSTextField(wrappingLabelWithString: "Layout matches the CV Maker thumbnail for this template. Turn on editing to adjust wording, then export a vector PDF.")
66
+        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).")
67
         subtitle.font = .systemFont(ofSize: 12, weight: .regular)
67
         subtitle.font = .systemFont(ofSize: 12, weight: .regular)
68
         subtitle.textColor = Self.secondaryText
68
         subtitle.textColor = Self.secondaryText
69
         subtitle.maximumNumberOfLines = 0
69
         subtitle.maximumNumberOfLines = 0
@@ -165,7 +165,10 @@ final class CVFilledPreviewPageView: NSView {
165
         doc.layoutSubtreeIfNeeded()
165
         doc.layoutSubtreeIfNeeded()
166
         let bounds = doc.bounds
166
         let bounds = doc.bounds
167
         guard !bounds.isEmpty else { return }
167
         guard !bounds.isEmpty else { return }
168
-        let data = doc.dataWithPDF(inside: bounds)
168
+        // `dataWithPDF` uses the print pipeline and often drops layer-backed chrome (card fill,
169
+        // borders, hairlines, tinted sidebars) while keeping text — so the PDF looks like plain
170
+        // text. Rasterising what is actually drawn on screen preserves the full layout.
171
+        let data = doc.pdfDataMatchingScreenAppearance() ?? doc.dataWithPDF(inside: bounds)
169
         guard !data.isEmpty else {
172
         guard !data.isEmpty else {
170
             presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
173
             presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
171
             return
174
             return
@@ -215,3 +218,36 @@ final class CVFilledPreviewPageView: NSView {
215
         }
218
         }
216
     }
219
     }
217
 }
220
 }
221
+
222
+// MARK: - PDF export (screen-accurate)
223
+
224
+private extension NSView {
225
+
226
+    /// Single-page PDF that matches the composited on-screen appearance, including
227
+    /// `CALayer` backgrounds, borders, and rules. Callers fall back to `dataWithPDF` when this returns nil.
228
+    func pdfDataMatchingScreenAppearance() -> Data? {
229
+        layoutSubtreeIfNeeded()
230
+        let rect = bounds
231
+        guard rect.width > 1, rect.height > 1 else { return nil }
232
+
233
+        guard let rep = bitmapImageRepForCachingDisplay(in: rect) else { return nil }
234
+        cacheDisplay(in: rect, to: rep)
235
+        guard let cgImage = rep.cgImage else { return nil }
236
+
237
+        let data = NSMutableData()
238
+        guard let consumer = CGDataConsumer(data: data as CFMutableData) else { return nil }
239
+        var mediaBox = CGRect(origin: .zero, size: NSSize(width: rect.width, height: rect.height))
240
+        guard let ctx = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else { return nil }
241
+
242
+        ctx.beginPDFPage(nil)
243
+        ctx.interpolationQuality = .high
244
+        // Draw without an extra Y-flip: `cacheDisplay`’s bitmap + `cgImage` already match PDF user space
245
+        // on macOS here; a translate/scale(-1) was inverting the whole page in Preview.
246
+        ctx.draw(cgImage, in: mediaBox)
247
+        ctx.endPDFPage()
248
+        ctx.closePDF()
249
+
250
+        let out = data as Data
251
+        return out.isEmpty ? nil : out
252
+    }
253
+}