Files
JY1.0/scripts/markdown-to-pdf.js

238 lines
5.1 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const input = process.argv[2]
const output = process.argv[3]
if (!input || !output) {
console.error('Usage: node scripts/markdown-to-pdf.js <input.md> <output.html>')
process.exit(1)
}
const markdown = fs.readFileSync(input, 'utf8')
function escapeHtml(value) {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
function inlineFormat(value) {
return escapeHtml(value)
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
}
function renderTable(lines) {
const rows = lines
.filter(line => line.trim())
.map(line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => inlineFormat(cell.trim())))
if (rows.length < 2) return ''
const head = rows[0]
const body = rows.slice(2)
return [
'<table>',
'<thead><tr>' + head.map(cell => '<th>' + cell + '</th>').join('') + '</tr></thead>',
'<tbody>',
body.map(row => '<tr>' + row.map(cell => '<td>' + cell + '</td>').join('') + '</tr>').join('\n'),
'</tbody>',
'</table>'
].join('\n')
}
function renderMarkdown(text) {
const lines = text.replace(/\r\n/g, '\n').split('\n')
const html = []
let paragraph = []
let list = []
let code = []
let inCode = false
let table = []
function flushParagraph() {
if (paragraph.length) {
html.push('<p>' + inlineFormat(paragraph.join(' ')) + '</p>')
paragraph = []
}
}
function flushList() {
if (list.length) {
html.push('<ul>' + list.map(item => '<li>' + inlineFormat(item) + '</li>').join('') + '</ul>')
list = []
}
}
function flushTable() {
if (table.length) {
html.push(renderTable(table))
table = []
}
}
lines.forEach(line => {
if (/^```/.test(line.trim())) {
if (inCode) {
html.push('<pre><code>' + escapeHtml(code.join('\n')) + '</code></pre>')
code = []
inCode = false
} else {
flushParagraph()
flushList()
flushTable()
inCode = true
}
return
}
if (inCode) {
code.push(line)
return
}
if (/^\|.*\|$/.test(line.trim())) {
flushParagraph()
flushList()
table.push(line)
return
}
flushTable()
if (!line.trim()) {
flushParagraph()
flushList()
return
}
const heading = line.match(/^(#{1,6})\s+(.+)$/)
if (heading) {
flushParagraph()
flushList()
const level = heading[1].length
html.push('<h' + level + '>' + inlineFormat(heading[2]) + '</h' + level + '>')
return
}
const image = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
if (image) {
flushParagraph()
flushList()
html.push('<figure><img src="' + escapeHtml(image[2]) + '" alt="' + escapeHtml(image[1]) + '"><figcaption>' + inlineFormat(image[1]) + '</figcaption></figure>')
return
}
const listItem = line.match(/^- (.+)$/)
if (listItem) {
flushParagraph()
list.push(listItem[1])
return
}
paragraph.push(line.trim())
})
flushParagraph()
flushList()
flushTable()
return html.join('\n')
}
const body = renderMarkdown(markdown)
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>零件改制功能设计方案</title>
<style>
@page { size: A4; margin: 18mm 16mm; }
body {
font-family: "Microsoft YaHei", "SimSun", Arial, sans-serif;
color: #1f2933;
font-size: 13px;
line-height: 1.65;
}
h1 {
font-size: 26px;
text-align: center;
margin: 0 0 24px;
padding-bottom: 12px;
border-bottom: 2px solid #2f80ed;
}
h2 {
font-size: 19px;
margin: 24px 0 10px;
padding-left: 8px;
border-left: 4px solid #2f80ed;
page-break-after: avoid;
}
h3 {
font-size: 16px;
margin: 18px 0 8px;
page-break-after: avoid;
}
p { margin: 7px 0; }
ul { margin: 6px 0 10px 22px; padding: 0; }
li { margin: 3px 0; }
table {
width: 100%;
border-collapse: collapse;
margin: 10px 0 16px;
page-break-inside: avoid;
}
th, td {
border: 1px solid #cbd5e1;
padding: 6px 8px;
text-align: left;
vertical-align: top;
word-break: break-word;
}
th {
background: #eef4ff;
font-weight: 600;
}
code {
font-family: Consolas, "Microsoft YaHei", monospace;
background: #f3f4f6;
padding: 1px 4px;
border-radius: 3px;
}
pre {
background: #f8fafc;
border: 1px solid #d8dee9;
padding: 10px;
overflow-wrap: break-word;
white-space: pre-wrap;
page-break-inside: avoid;
}
figure {
margin: 14px 0 20px;
page-break-inside: avoid;
}
figure img {
display: block;
width: 100%;
max-width: 100%;
height: auto;
border: 1px solid #d8dee9;
}
figcaption {
margin-top: 6px;
color: #64748b;
font-size: 12px;
text-align: center;
}
</style>
</head>
<body>
${body}
</body>
</html>
`
fs.writeFileSync(output, html, 'utf8')