238 lines
5.1 KiB
JavaScript
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, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
}
|
|
|
|
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')
|