|
|
@@ -63,7 +63,7 @@ final class CVFilledPreviewPageView: NSView {
|
|
|
editCheckbox.target = self
|
|
|
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.textColor = Self.secondaryText
|
|
|
subtitle.maximumNumberOfLines = 0
|
|
|
@@ -165,7 +165,10 @@ final class CVFilledPreviewPageView: NSView {
|
|
|
doc.layoutSubtreeIfNeeded()
|
|
|
let bounds = doc.bounds
|
|
|
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 {
|
|
|
presentExportError("The résumé could not be rendered to PDF (empty output). Try scrolling the preview so it lays out, then export again.")
|
|
|
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
|
|
|
+ }
|
|
|
+}
|