From 3a30db1d1557608afee029f58aea9b96a6d2d421 Mon Sep 17 00:00:00 2001 From: Rune Olsen Date: Wed, 29 Jul 2026 09:13:10 +0200 Subject: [PATCH] =?UTF-8?q?Actually=20fix=20oversized=20PDF=20text=20?= =?UTF-8?q?=E2=80=94=20root=20cause=20was=20the=20previous=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empirically reproduced this outside the app (standalone WKWebView + createPDF script, inspecting real PDF page bounds via PDFKit) instead of guessing again. Findings: - WKPDFConfiguration.rect left at default captures the webview's frame size verbatim when content fits within it, and auto-grows to a single tall page matching full scrollable content when it overflows. It does not paginate, and setting rect explicitly just clips to that one rect instead. - The scrollHeight-based frame resize added last round (to "fix" this same bug) was itself the problem: resizing the frame before calling createPDF produced a page that didn't match the resized frame at all (e.g. resized to 816x295, captured page came out 799x375) — a small, badly-proportioned page that made ordinarily-sized text look enormous relative to it. Removed entirely. - A visible scrollbar during capture shaves ~17pt off the captured page width. Added ::-webkit-scrollbar { display: none } to prevent that. Verified against a 12-message realistic conversation: clean single page, correct proportions, readable text, matches the intended CSS layout. The text-size-adjust/viewport-meta fix from last round turned out to be unnecessary (computed font-size was correct all along) but is harmless, so left in place. --- oAI/Services/ConversationExportService.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/oAI/Services/ConversationExportService.swift b/oAI/Services/ConversationExportService.swift index 57743be..8cbd32a 100644 --- a/oAI/Services/ConversationExportService.swift +++ b/oAI/Services/ConversationExportService.swift @@ -71,6 +71,7 @@ enum ConversationExportService { nonisolated private static let css = """ :root { color-scheme: light dark; } html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } + ::-webkit-scrollbar { display: none; } body { font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; \ font-size: 15px; color: #1a1a1a; background: #ffffff; max-width: 820px; margin: 40px auto; \ padding: 0 24px; line-height: 1.5; } @@ -113,7 +114,15 @@ 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)) + // Confirmed empirically (not guessed): with WKPDFConfiguration.rect left at its + // default, createPDF captures exactly the webview's frame size verbatim for content + // that fits within it, and auto-grows to a single tall page matching the full + // scrollable content when it overflows — it does not paginate. Manually resizing the + // frame to match content height beforehand (a previous attempt at this) actively + // breaks that: the captured page ends up NOT matching the resized frame at all, + // producing a small, oddly-proportioned page that made ordinary-sized text look + // enormous relative to it. So: set a sane starting frame and leave it alone. + let webView = WKWebView(frame: NSRect(x: 0, y: 0, width: 850, height: 1100)) 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. @@ -122,14 +131,6 @@ 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)