@jiyeqian/md2pdf 1.7.5 → 1.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -7
- package/assets/base.css +15 -0
- package/package.json +1 -1
- package/src/md2pdf.mjs +35 -3
- package/vendor/highlight/highlight.cjs +1244 -0
package/README.md
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
把 Markdown 排成**优雅的中文 A4 PDF**:报头大标题、元信息条、精心排过的表格/代码/引用/列表、页脚页码,还支持数学公式与参考文献。不是 pandoc 的默认样式——是可以直接拿去打印、发给别人看的版式。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
**minimal 主题**(黑白公文风):
|
|
10
|
-
|
|
11
|
-

|
|
5
|
+
| elegant(默认,墨蓝 + 古铜) | minimal(黑白公文风) |
|
|
6
|
+
| :---: | :---: |
|
|
7
|
+
| [](https://cnb.cool/jiyeqian/md2pdf/-/git/raw/main/examples/demo-elegant.pdf) | [](https://cnb.cool/jiyeqian/md2pdf/-/git/raw/main/examples/demo-minimal.pdf) |
|
|
12
8
|
|
|
13
9
|
仓库:https://cnb.cool/jiyeqian/md2pdf
|
|
14
10
|
|
|
@@ -82,6 +78,7 @@ node ci/inspect-pdf.mjs out.pdf
|
|
|
82
78
|
- 首个 H1 提升为报头大标题;其后的首段自动成为导语。
|
|
83
79
|
- YAML frontmatter 的 `name` / `description` 生成元信息条;「适用于…/不用于…」自动拆两栏。
|
|
84
80
|
- H2 自动分节加色块;表格深色表头+隔行浅底;有序列表圆形序号。
|
|
81
|
+
- 代码块按语言自动语法高亮(内置 highlight.js,支持 Python/JS/Bash 等常见语言)。
|
|
85
82
|
- 数学公式:`$...$`(行内)与 `$$...$$`(独立成行)由内置 MathJax 渲染。
|
|
86
83
|
- 脚注:`[^id]` 引用 + `[^id]: 内容` 定义;BibTeX 脚注(`@article{...}` 等)按 GB/T 7714-2025 著录,默认收集为「参考文献」章节(`--no-bibliography` 关闭)。
|
|
87
84
|
- 相对路径图片自动解析进 PDF。
|
package/assets/base.css
CHANGED
|
@@ -197,6 +197,21 @@ pre code {
|
|
|
197
197
|
background: transparent; border: none; padding: 0;
|
|
198
198
|
color: inherit; font-size: .85em; line-height: 1.7;
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
/* ---------- 代码高亮(highlight.js,浅色底) ---------- */
|
|
202
|
+
.hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
|
|
203
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-doctag, .hljs-formula { color: #d73a49; }
|
|
204
|
+
.hljs-title, .hljs-title.function_, .hljs-title.class_ { color: #6f42c1; }
|
|
205
|
+
.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-number, .hljs-variable,
|
|
206
|
+
.hljs-template-variable, .hljs-selector-id, .hljs-selector-class,
|
|
207
|
+
.hljs-selector-attr, .hljs-selector-pseudo, .hljs-meta { color: #005cc5; }
|
|
208
|
+
.hljs-string, .hljs-regexp, .hljs-meta .hljs-string { color: #032f62; }
|
|
209
|
+
.hljs-built_in, .hljs-symbol, .hljs-bullet { color: #e36209; }
|
|
210
|
+
.hljs-name, .hljs-section, .hljs-tag { color: #22863a; }
|
|
211
|
+
.hljs-type, .hljs-class, .hljs-params { color: #24292e; }
|
|
212
|
+
.hljs-emphasis { font-style: italic; }
|
|
213
|
+
.hljs-strong { font-weight: 700; }
|
|
214
|
+
.hljs-link { color: #005cc5; text-decoration: underline; }
|
|
200
215
|
blockquote {
|
|
201
216
|
margin: 0 0 14px; padding: 10px 14px;
|
|
202
217
|
background: var(--tint); border-left: 3px solid var(--accent-soft);
|
package/package.json
CHANGED
package/src/md2pdf.mjs
CHANGED
|
@@ -36,11 +36,12 @@ import os from 'node:os';
|
|
|
36
36
|
import http from 'node:http';
|
|
37
37
|
import { spawn, spawnSync } from 'node:child_process';
|
|
38
38
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
39
|
+
import { createRequire } from 'node:module';
|
|
39
40
|
|
|
40
41
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
41
42
|
const ASSETS = path.join(ROOT, 'assets');
|
|
42
43
|
|
|
43
|
-
const VERSION = '1.7.
|
|
44
|
+
const VERSION = '1.7.7';
|
|
44
45
|
|
|
45
46
|
// Node ≥ 22 有全局 WebSocket;更老的版本退回到内置的极简实现
|
|
46
47
|
let _WS;
|
|
@@ -219,6 +220,32 @@ function sectionize(html) {
|
|
|
219
220
|
.join('\n');
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
/* ---------------- 代码高亮 ---------------- */
|
|
224
|
+
|
|
225
|
+
// 反转 marked 输出的 HTML 实体(highlight.js 需要原始代码再自行转义)
|
|
226
|
+
function decodeEntities(s) {
|
|
227
|
+
return s.replace(/</g, '<').replace(/>/g, '>')
|
|
228
|
+
.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 对 <pre><code> 块调用 highlight.js;带语言标记用对应语法,否则自动识别
|
|
232
|
+
function highlightCode(html, hljs) {
|
|
233
|
+
if (!hljs) return html;
|
|
234
|
+
return html.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g, (m, attrs, code) => {
|
|
235
|
+
const lm = /(?:language|lang)-([\w-]+)/.exec(attrs);
|
|
236
|
+
const lang = lm ? lm[1] : '';
|
|
237
|
+
const text = decodeEntities(code);
|
|
238
|
+
let out;
|
|
239
|
+
try {
|
|
240
|
+
out = (lang && hljs.getLanguage(lang))
|
|
241
|
+
? hljs.highlight(text, { language: lang }).value
|
|
242
|
+
: hljs.highlightAuto(text).value;
|
|
243
|
+
} catch { return m; }
|
|
244
|
+
const cls = 'hljs' + (lang ? ' language-' + lang : '');
|
|
245
|
+
return '<pre><code class="' + cls + '">' + out + '</code></pre>';
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
222
249
|
/* ---------------- 章节编号 ---------------- */
|
|
223
250
|
|
|
224
251
|
// 标题编号前缀识别(阿拉伯/中文/罗马数字、第X章、括号编号等)
|
|
@@ -531,7 +558,7 @@ class Chrome {
|
|
|
531
558
|
|
|
532
559
|
/* ---------------- 渲染 ---------------- */
|
|
533
560
|
|
|
534
|
-
async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
561
|
+
async function renderOne(mdPath, opts, chrome, marked, hljs, tmpRoot) {
|
|
535
562
|
const src = await readFile(mdPath, 'utf8');
|
|
536
563
|
const { fm, body: rawBody } = splitFrontmatter(src);
|
|
537
564
|
const isSkill = path.basename(mdPath) === 'SKILL.md' || !!fm.name;
|
|
@@ -555,6 +582,9 @@ async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
|
555
582
|
|
|
556
583
|
let html = marked.parse(bodyWithRefs, { gfm: true, breaks: false, async: false });
|
|
557
584
|
|
|
585
|
+
// 代码高亮:对带语言标记的代码块做 highlight.js 着色
|
|
586
|
+
html = highlightCode(html, hljs);
|
|
587
|
+
|
|
558
588
|
// 脚注/参考文献:先追加到正文末尾,再编号,使参考文献章节纳入编号体系
|
|
559
589
|
html += renderFootnotes(footnotes, opts.bibliography, marked);
|
|
560
590
|
|
|
@@ -717,6 +747,8 @@ async function main() {
|
|
|
717
747
|
|
|
718
748
|
const { Marked } = await import(pathToFileURL(path.join(ROOT, 'vendor', 'marked.esm.js')).href);
|
|
719
749
|
const marked = new Marked({ gfm: true });
|
|
750
|
+
const require = createRequire(import.meta.url);
|
|
751
|
+
const hljs = require(path.join(ROOT, 'vendor', 'highlight', 'highlight.cjs'));
|
|
720
752
|
|
|
721
753
|
const tmpRoot = await mkdtemp(path.join(os.tmpdir(), 'md2pdf-'));
|
|
722
754
|
// --html-only 不需要浏览器(CI / 调样式时用)
|
|
@@ -728,7 +760,7 @@ async function main() {
|
|
|
728
760
|
const mdPath = path.resolve(input);
|
|
729
761
|
if (!existsSync(mdPath)) { console.error(`✗ 找不到文件:${input}`); failed = true; continue; }
|
|
730
762
|
try {
|
|
731
|
-
const res = await renderOne(mdPath, opts, chrome, marked, tmpRoot);
|
|
763
|
+
const res = await renderOne(mdPath, opts, chrome, marked, hljs, tmpRoot);
|
|
732
764
|
const out = resolveOutput(mdPath, opts);
|
|
733
765
|
await mkdir(path.dirname(out), { recursive: true });
|
|
734
766
|
if (opts.htmlOnly) {
|