💄 show novel title in single chapter export PDF
This commit is contained in:
@@ -192,13 +192,14 @@ export function registerIpcHandlers(): void {
|
|||||||
if (result.canceled || !result.filePath) return
|
if (result.canceled || !result.filePath) return
|
||||||
|
|
||||||
const { marked } = await import('marked')
|
const { marked } = await import('marked')
|
||||||
|
const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib')
|
||||||
// Draft files use single \n between paragraphs; marked needs \n\n to create <p> elements
|
// Draft files use single \n between paragraphs; marked needs \n\n to create <p> elements
|
||||||
const normalized = content.replace(/\r\n/g, '\n').replace(/\n(?!\n)/g, '\n\n')
|
const normalized = content.replace(/\r\n/g, '\n').replace(/\n(?!\n)/g, '\n\n')
|
||||||
const bodyHtml = await marked(normalized)
|
const bodyHtml = await marked(normalized)
|
||||||
|
|
||||||
// Escape for safe injection into HTML attributes
|
|
||||||
const safeTitle = fileName.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
const safeTitle = fileName.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
||||||
const headerTitle = fileName.toUpperCase().replace(/</g, '<').replace(/>/g, '>')
|
const projectTitle = getProjectTitle().toUpperCase()
|
||||||
|
const chapterTitle = fileName.toUpperCase()
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -206,7 +207,7 @@ export function registerIpcHandlers(): void {
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>${safeTitle}</title>
|
<title>${safeTitle}</title>
|
||||||
<style>
|
<style>
|
||||||
@page { size: letter; margin: 1in; }
|
@page { size: letter; }
|
||||||
body {
|
body {
|
||||||
font-family: "Courier New", Courier, monospace;
|
font-family: "Courier New", Courier, monospace;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
@@ -264,12 +265,31 @@ export function registerIpcHandlers(): void {
|
|||||||
await offscreen.loadFile(tempPath)
|
await offscreen.loadFile(tempPath)
|
||||||
const pdfBuffer = await offscreen.webContents.printToPDF({
|
const pdfBuffer = await offscreen.webContents.printToPDF({
|
||||||
pageSize: 'Letter',
|
pageSize: 'Letter',
|
||||||
displayHeaderFooter: true,
|
displayHeaderFooter: false,
|
||||||
headerTemplate: `<div style="font-size:11pt;font-family:'Courier New',Courier,monospace;width:100%;text-align:right;padding-right:72pt;">${headerTitle} / <span class="pageNumber"></span></div>`,
|
margins: { marginType: 'custom', top: 1.0, bottom: 1.0, left: 1.0, right: 1.0 }
|
||||||
footerTemplate: '<div></div>',
|
|
||||||
margins: { marginType: 'custom', top: 1.25, bottom: 1.0, left: 1.0, right: 1.0 }
|
|
||||||
})
|
})
|
||||||
writeFileSync(result.filePath, pdfBuffer)
|
|
||||||
|
// Stamp the running header on every page using pdf-lib
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfBuffer)
|
||||||
|
const courier = await pdfDoc.embedFont(StandardFonts.Courier)
|
||||||
|
const pages = pdfDoc.getPages()
|
||||||
|
for (let i = 0; i < pages.length; i++) {
|
||||||
|
const page = pages[i]
|
||||||
|
const { width, height } = page.getSize()
|
||||||
|
const headerText = `${projectTitle} / ${chapterTitle} / ${i + 1}`
|
||||||
|
const fontSize = 11
|
||||||
|
const textWidth = courier.widthOfTextAtSize(headerText, fontSize)
|
||||||
|
page.drawText(headerText, {
|
||||||
|
x: width - 72 - textWidth, // 1 in from right edge
|
||||||
|
y: height - 36, // 0.5 in from top edge
|
||||||
|
size: fontSize,
|
||||||
|
font: courier,
|
||||||
|
color: rgb(0, 0, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save()
|
||||||
|
writeFileSync(result.filePath, pdfBytes)
|
||||||
} finally {
|
} finally {
|
||||||
offscreen.destroy()
|
offscreen.destroy()
|
||||||
try { unlinkSync(tempPath) } catch { /* ignore */ }
|
try { unlinkSync(tempPath) } catch { /* ignore */ }
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user