From a3fd2c0eabc930e2d0564889cbe2aef5ab90d9c4 Mon Sep 17 00:00:00 2001 From: Rune Olsen Date: Wed, 29 Jul 2026 09:01:12 +0200 Subject: [PATCH] Fix extremely oversized text in PDF export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defensive fixes for WKWebView.createPDF() rendering text far larger than the source HTML's actual font sizes specify: - Added a viewport meta tag and explicit -webkit-text-size-adjust: 100% — WebKit's shared engine has a text-autosizing heuristic (normally associated with mobile Safari, but can trigger in WKWebView contexts too, especially for content loaded via loadHTMLString with no base URL) that boosts font sizes for perceived readability; this disables it explicitly rather than relying on it not firing. - Explicit body font-size (was previously unset, relying on WebKit's default) and webView.pageZoom = 1.0. - Resize the webview to the actual rendered content height (via document.body.scrollHeight) before calling createPDF, instead of relying on its undocumented auto-sizing against the arbitrary initial frame — a bounds mismatch here is a known cause of blown-up/rescaled PDF output. --- oAI/Services/ConversationExportService.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/oAI/Services/ConversationExportService.swift b/oAI/Services/ConversationExportService.swift index 5374070..57743be 100644 --- a/oAI/Services/ConversationExportService.swift +++ b/oAI/Services/ConversationExportService.swift @@ -56,6 +56,7 @@ enum ConversationExportService { + \(htmlEscape(name)) @@ -69,9 +70,10 @@ enum ConversationExportService { nonisolated private static let css = """ :root { color-scheme: light dark; } + html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } body { font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; \ - color: #1a1a1a; background: #ffffff; max-width: 820px; margin: 40px auto; padding: 0 24px; \ - line-height: 1.5; } + font-size: 15px; color: #1a1a1a; background: #ffffff; max-width: 820px; margin: 40px auto; \ + padding: 0 24px; line-height: 1.5; } h1 { font-size: 24px; border-bottom: 1px solid #ddd; padding-bottom: 12px; } .message { margin: 20px 0; padding: 12px 16px; border-radius: 8px; border-left: 4px solid transparent; } .message.user { background: #f5f7fa; border-left-color: #4a90d9; } @@ -112,6 +114,7 @@ enum ConversationExportService { static func pdfData(name: String, messages: [Message]) async throws -> Data { let htmlString = html(name: name, messages: messages) let webView = WKWebView(frame: NSRect(x: 0, y: 0, width: 816, height: 1056)) + webView.pageZoom = 1.0 // PDF content is baked at export time, so it can't respond to prefers-color-scheme // live like the HTML export does — match whatever appearance the app is in right now. webView.appearance = NSApp.effectiveAppearance @@ -119,6 +122,14 @@ enum ConversationExportService { webView.navigationDelegate = delegate try await delegate.load(htmlString, in: webView) + // Resize to the actual rendered content height so createPDF's capture bounds match + // reality, rather than relying on its undocumented auto-sizing against the arbitrary + // initial frame — a mismatch here is a common cause of blown-up/rescaled PDF output. + if let heightValue = try? await webView.evaluateJavaScript("document.body.scrollHeight"), + let height = (heightValue as? NSNumber)?.doubleValue, height > 0 { + webView.frame = NSRect(x: 0, y: 0, width: webView.frame.width, height: height) + } + return try await withCheckedThrowingContinuation { continuation in webView.createPDF(configuration: WKPDFConfiguration()) { result in continuation.resume(with: result)