@@ -8,7 +8,6 @@ import pg from 'pg'
88import { spawn } from 'child_process'
99import { autoUpdater } from 'electron-updater'
1010import MarkdownIt from 'markdown-it'
11- import puppeteer from 'puppeteer'
1211
1312let mainWindow : BrowserWindow | null = null
1413
@@ -145,8 +144,8 @@ ipcMain.handle("export-pdf", async (_, html, fileName) => {
145144 win . close ( )
146145} )
147146
148- // Helper for PDF generation
149- async function generatePdfBuffer ( markdownContent : string ) {
147+ // Helper for PDF generation — uses Electron's built-in Chromium (no external Chrome needed)
148+ async function generatePdfBuffer ( markdownContent : string ) : Promise < Buffer > {
150149 const md = new MarkdownIt ( {
151150 html : true ,
152151 linkify : true ,
@@ -162,7 +161,7 @@ async function generatePdfBuffer(markdownContent: string) {
162161 <style>
163162 @page {
164163 size: A4;
165- margin: 20mm ;
164+ margin: 25mm ;
166165 }
167166 body {
168167 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -213,12 +212,11 @@ async function generatePdfBuffer(markdownContent: string) {
213212 h4 {
214213 font-size: 1.1rem;
215214 font-weight: 700;
216- color: #374151 ;
215+ color: #6B7280 ;
217216 margin-top: 28px;
218217 margin-bottom: 12px;
219218 text-transform: uppercase;
220219 letter-spacing: 0.05em;
221- color: #6B7280;
222220 }
223221 p {
224222 margin: 16px 0;
@@ -258,126 +256,34 @@ async function generatePdfBuffer(markdownContent: string) {
258256 .status-5xx { background: #FEE2E2; color: #991B1B; }
259257
260258 /* --- Table of Contents (Premium Modern) --- */
261- .toc-container {
262- margin: 80px 0;
263- }
264- .toc-title-bar {
265- border-bottom: 3px solid #3B82F6;
266- margin-bottom: 48px;
267- padding-bottom: 24px;
268- }
269- .toc-title-bar h2 {
270- margin: 0 !important;
271- font-size: 3rem !important;
272- border-bottom: none !important;
273- color: #111827 !important;
274- }
259+ .toc-container { margin: 80px 0; }
260+ .toc-title-bar { border-bottom: 3px solid #3B82F6; margin-bottom: 48px; padding-bottom: 24px; }
261+ .toc-title-bar h2 { margin: 0 !important; font-size: 3rem !important; border-bottom: none !important; color: #111827 !important; }
275262 .toc-list { display: flex; flex-direction: column; gap: 32px; }
276263 .toc-folder-group { position: relative; }
277264 .toc-folder-item { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; }
278- .toc-folder-number {
279- background: #F3F4F6;
280- color: #3B82F6;
281- font-weight: 800;
282- padding: 2px 8px;
283- border-radius: 6px;
284- font-size: 0.9em;
285- min-width: 32px;
286- text-align: center;
287- font-family: monospace;
288- }
289- .toc-folder-link {
290- color: #111827;
291- text-decoration: none;
292- font-size: 1.4rem;
293- font-weight: 800;
294- }
295- .toc-endpoints-container {
296- border-left: 2px solid #F3F4F6;
297- margin-left: 15px;
298- padding-left: 32px;
299- display: flex;
300- flex-direction: column;
301- gap: 12px;
302- }
265+ .toc-folder-number { background: #F3F4F6; color: #3B82F6; font-weight: 800; padding: 2px 8px; border-radius: 6px; font-size: 0.9em; min-width: 32px; text-align: center; font-family: monospace; }
266+ .toc-folder-link { color: #111827; text-decoration: none; font-size: 1.4rem; font-weight: 800; }
267+ .toc-endpoints-container { border-left: 2px solid #F3F4F6; margin-left: 15px; padding-left: 32px; display: flex; flex-direction: column; gap: 12px; }
303268 .toc-endpoint-item { display: flex; align-items: center; gap: 12px; position: relative; }
304- .toc-endpoint-item::before {
305- content: "";
306- position: absolute;
307- left: -33px;
308- top: 50%;
309- width: 12px;
310- height: 2px;
311- background: #F3F4F6;
312- }
269+ .toc-endpoint-item::before { content: ""; position: absolute; left: -33px; top: 50%; width: 12px; height: 2px; background: #F3F4F6; }
313270 .toc-endpoint-bullet { display: none; }
314271 .toc-endpoint-link { color: #4B5563; text-decoration: none; font-size: 1.1rem; font-weight: 500; }
315272
316273 /* --- Code & Pre --- */
317- pre {
318- background-color: #f8fafc;
319- color: #1e293b;
320- padding: 24px;
321- border-radius: 12px;
322- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
323- font-size: 0.9rem;
324- line-height: 1.7;
325- overflow-x: auto;
326- margin: 24px 0;
327- border: 1px solid #e2e8f0;
328- }
329- code {
330- font-family: inherit;
331- background-color: #f1f5f9;
332- color: #475569;
333- padding: 0.2rem 0.4rem;
334- border-radius: 4px;
335- font-size: 0.9em;
336- }
337- pre code {
338- background-color: transparent;
339- color: inherit;
340- padding: 0;
341- }
274+ pre { background-color: #f8fafc; color: #1e293b; padding: 24px; border-radius: 12px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 0.9rem; line-height: 1.7; overflow-x: auto; margin: 24px 0; border: 1px solid #e2e8f0; }
275+ code { font-family: inherit; background-color: #f1f5f9; color: #475569; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
276+ pre code { background-color: transparent; color: inherit; padding: 0; }
342277
343278 /* --- Tables --- */
344- table {
345- width: 100%;
346- border-collapse: collapse;
347- margin: 32px 0;
348- }
349- table th, table td {
350- padding: 14px 16px;
351- border: 1px solid #E5E7EB;
352- text-align: left;
353- vertical-align: top;
354- }
355- table th {
356- background-color: #F9FAFB;
357- font-weight: 700;
358- color: #374151;
359- text-transform: uppercase;
360- font-size: 0.75rem;
361- letter-spacing: 0.05em;
362- }
279+ table { width: 100%; border-collapse: collapse; margin: 32px 0; }
280+ table th, table td { padding: 14px 16px; border: 1px solid #E5E7EB; text-align: left; vertical-align: top; }
281+ table th { background-color: #F9FAFB; font-weight: 700; color: #374151; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; }
363282
364283 /* --- Helpers --- */
365- .page-break {
366- page-break-after: always;
367- }
368- blockquote {
369- border-left: 4px solid #3B82F6;
370- padding: 8px 16px;
371- margin: 24px 0;
372- background: #F9FAFB;
373- color: #4B5563;
374- font-style: italic;
375- border-radius: 0 8px 8px 0;
376- }
377- img {
378- max-width: 100%;
379- border-radius: 12px;
380- }
284+ .page-break { page-break-after: always; }
285+ blockquote { border-left: 4px solid #3B82F6; padding: 8px 16px; margin: 24px 0; background: #F9FAFB; color: #4B5563; font-style: italic; border-radius: 0 8px 8px 0; }
286+ img { max-width: 100%; border-radius: 12px; }
381287 </style>
382288 </head>
383289 <body>
@@ -388,32 +294,33 @@ async function generatePdfBuffer(markdownContent: string) {
388294 </html>
389295 `
390296
391- const browser = await puppeteer . launch ( {
392- headless : true ,
393- args : [ '--no-sandbox' , '--disable-setuid-sandbox' ]
394- } )
395- const page = await browser . newPage ( )
396- await page . setContent ( fullHtml , { waitUntil : 'networkidle0' } )
397-
398- const buffer = await page . pdf ( {
399- format : 'A4' ,
400- margin : {
401- top : '25mm' ,
402- right : '25mm' ,
403- bottom : '25mm' ,
404- left : '25mm'
405- } ,
406- displayHeaderFooter : true ,
407- headerTemplate : '<span></span>' ,
408- footerTemplate : `
409- <div style="font-size: 10px; color: #aaa; width: 100%; text-align: center; font-family: 'Segoe UI', Arial, sans-serif;">
410- Page <span class="pageNumber"></span> of <span class="totalPages"></span>
411- </div>` ,
412- printBackground : true
297+ // Write HTML to a temp file so BrowserWindow can load it as a local file
298+ const tempHtmlPath = path . join ( os . tmpdir ( ) , `api-doc-pdf-${ Date . now ( ) } .html` )
299+ fs . writeFileSync ( tempHtmlPath , fullHtml , 'utf-8' )
300+
301+ const win = new BrowserWindow ( {
302+ show : false ,
303+ width : 1200 ,
304+ height : 1600 ,
305+ webPreferences : { javascript : false }
413306 } )
414307
415- await browser . close ( )
416- return buffer
308+ try {
309+ await win . loadFile ( tempHtmlPath )
310+ // Wait for fonts/images to settle
311+ await new Promise ( resolve => setTimeout ( resolve , 500 ) )
312+
313+ const pdfBuffer = await win . webContents . printToPDF ( {
314+ printBackground : true ,
315+ preferCSSPageSize : true ,
316+ margins : { marginType : 'none' }
317+ } )
318+
319+ return Buffer . from ( pdfBuffer )
320+ } finally {
321+ win . close ( )
322+ try { fs . unlinkSync ( tempHtmlPath ) } catch ( _ ) { /* ignore cleanup errors */ }
323+ }
417324}
418325
419326ipcMain . handle ( 'preview-doc-pdf' , async ( _event , markdownContent : string ) => {
0 commit comments