@jiyeqian/md2pdf 1.7.6 → 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 CHANGED
@@ -78,6 +78,7 @@ node ci/inspect-pdf.mjs out.pdf
78
78
  - 首个 H1 提升为报头大标题;其后的首段自动成为导语。
79
79
  - YAML frontmatter 的 `name` / `description` 生成元信息条;「适用于…/不用于…」自动拆两栏。
80
80
  - H2 自动分节加色块;表格深色表头+隔行浅底;有序列表圆形序号。
81
+ - 代码块按语言自动语法高亮(内置 highlight.js,支持 Python/JS/Bash 等常见语言)。
81
82
  - 数学公式:`$...$`(行内)与 `$$...$$`(独立成行)由内置 MathJax 渲染。
82
83
  - 脚注:`[^id]` 引用 + `[^id]: 内容` 定义;BibTeX 脚注(`@article{...}` 等)按 GB/T 7714-2025 著录,默认收集为「参考文献」章节(`--no-bibliography` 关闭)。
83
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiyeqian/md2pdf",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "description": "把 Markdown 排成优雅的中文 A4 PDF(无头 Chrome 渲染,带报头、表格/代码排版与页脚页码)",
5
5
  "type": "module",
6
6
  "bin": {
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.6';
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(/&lt;/g, '<').replace(/&gt;/g, '>')
228
+ .replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/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) {