@jiyeqian/md2pdf 1.4.0 → 1.5.0
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 +9 -2
- package/assets/shell.html +2 -0
- package/package.json +1 -1
- package/src/md2pdf.mjs +37 -2
- package/vendor/mathjax/tex-svg.js +1 -0
package/README.md
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
把 Markdown 排成**优雅的中文 A4 PDF**:报头大标题、元信息条、精心排过的表格/代码/引用/列表、页脚页码。
|
|
4
4
|
不是 pandoc 的默认样式 —— 是可以直接拿去打印、发给别人看的版式。
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
**elegant 主题**(默认,墨蓝 + 古铜):
|
|
7
|
+
|
|
8
|
+

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

|
|
7
13
|
|
|
8
14
|
仓库:https://cnb.cool/jiyeqian/md2pdf
|
|
9
15
|
|
|
@@ -121,6 +127,7 @@ node ci/inspect-pdf.mjs out.pdf
|
|
|
121
127
|
- 首个 H1 提升为报头大标题,正文不再重复;其后的首段自动成为导语。
|
|
122
128
|
- YAML frontmatter 的 `name` / `description` 生成元信息条;description 里「适用于…」「不用于…」会自动拆成「适用 / 不适用」两栏。
|
|
123
129
|
- H2 自动分节并加色块标记;表格深色表头+隔行浅底;有序列表用圆形序号。
|
|
130
|
+
- 数学公式:正文里的 `$...$`(行内)与 `$$...$$`(独立成行)由内置 MathJax 渲染(SVG 输出,零字体依赖)。
|
|
124
131
|
- 相对路径图片自动解析成绝对地址,能正常进入 PDF。
|
|
125
132
|
|
|
126
133
|
## 改样式
|
|
@@ -220,4 +227,4 @@ skill/SKILL.md Agent 技能说明书(postinstall 会装到技能目
|
|
|
220
227
|
|
|
221
228
|
MIT
|
|
222
229
|
|
|
223
|
-
第三方组件:`vendor/marked.esm.js` 来自 [marked](https://github.com/markedjs/marked)(MIT License
|
|
230
|
+
第三方组件:`vendor/marked.esm.js` 来自 [marked](https://github.com/markedjs/marked)(MIT License);`vendor/mathjax/tex-svg.js` 来自 [MathJax](https://github.com/mathjax/MathJax)(Apache-2.0 License)。均随仓库分发以便零依赖安装。
|
package/assets/shell.html
CHANGED
package/package.json
CHANGED
package/src/md2pdf.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
40
40
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
41
41
|
const ASSETS = path.join(ROOT, 'assets');
|
|
42
42
|
|
|
43
|
-
const VERSION = '1.
|
|
43
|
+
const VERSION = '1.5.0';
|
|
44
44
|
|
|
45
45
|
// Node ≥ 22 有全局 WebSocket;更老的版本退回到内置的极简实现
|
|
46
46
|
let _WS;
|
|
@@ -293,6 +293,19 @@ class Chrome {
|
|
|
293
293
|
await this.send('Page.navigate', { url: pathToFileURL(htmlPath).href });
|
|
294
294
|
await loaded;
|
|
295
295
|
await new Promise(r => setTimeout(r, 250));
|
|
296
|
+
// MathJax 排版:正文含公式时,轮询等待 typeset 完成再打印(否则公式是空白)
|
|
297
|
+
if (opts.waitMath) {
|
|
298
|
+
const t0 = Date.now();
|
|
299
|
+
while (Date.now() - t0 < 10000) {
|
|
300
|
+
let done = false;
|
|
301
|
+
try {
|
|
302
|
+
const r = await this.send('Runtime.evaluate', { expression: 'window.__md2pdfMathReady === true', returnByValue: true });
|
|
303
|
+
done = !!(r && r.result && r.result.value);
|
|
304
|
+
} catch { /* 忽略运行时异常,继续等待 */ }
|
|
305
|
+
if (done) break;
|
|
306
|
+
await new Promise(r => setTimeout(r, 100));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
296
309
|
const base = {
|
|
297
310
|
printBackground: true,
|
|
298
311
|
preferCSSPageSize: true,
|
|
@@ -328,6 +341,9 @@ async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
|
328
341
|
const { fm, body } = splitFrontmatter(src);
|
|
329
342
|
const isSkill = path.basename(mdPath) === 'SKILL.md' || !!fm.name;
|
|
330
343
|
|
|
344
|
+
// 数学公式:检测 $...$ 或 $...$,命中则注入 MathJax(SVG 输出,零字体依赖)
|
|
345
|
+
const hasMath = /\$\$|\$[^$\n]+\$/.test(body);
|
|
346
|
+
|
|
331
347
|
let html = marked.parse(body, { gfm: true, breaks: false, async: false });
|
|
332
348
|
|
|
333
349
|
// 标题:正文首个 H1 → 报头
|
|
@@ -406,6 +422,24 @@ async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
|
406
422
|
const fill = (tpl, map) => Object.entries(map).reduce(
|
|
407
423
|
(s, [k, v]) => s.split(k).join(v), tpl);
|
|
408
424
|
|
|
425
|
+
const mathUrl = pathToFileURL(path.join(ROOT, 'vendor', 'mathjax', 'tex-svg.js')).href;
|
|
426
|
+
const mathScript = hasMath ? [
|
|
427
|
+
'<script>',
|
|
428
|
+
'window.__md2pdfMathReady = false;',
|
|
429
|
+
'window.MathJax = {',
|
|
430
|
+
' tex: { inlineMath: [["$", "$"]] },',
|
|
431
|
+
' svg: { fontCache: "none" },',
|
|
432
|
+
' startup: {',
|
|
433
|
+
' ready: function () {',
|
|
434
|
+
' MathJax.startup.defaultReady();',
|
|
435
|
+
' MathJax.startup.promise.then(function () { window.__md2pdfMathReady = true; });',
|
|
436
|
+
' }',
|
|
437
|
+
' }',
|
|
438
|
+
'};',
|
|
439
|
+
'</script>',
|
|
440
|
+
'<script src="' + mathUrl + '" id="MathJax-script"></script>',
|
|
441
|
+
].join('\n') : '';
|
|
442
|
+
|
|
409
443
|
const shell = await readFile(path.join(ASSETS, 'shell.html'), 'utf8');
|
|
410
444
|
const out = fill(shell, {
|
|
411
445
|
'{{TITLE}}': esc(title),
|
|
@@ -417,6 +451,7 @@ async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
|
417
451
|
'{{BODY}}': html,
|
|
418
452
|
'{{COLOPHON_LEFT}}': esc(colophonLeft),
|
|
419
453
|
'{{COLOPHON_RIGHT}}': esc(colophonRight),
|
|
454
|
+
'{{MATHJAX}}': mathScript,
|
|
420
455
|
});
|
|
421
456
|
|
|
422
457
|
if (opts.htmlOnly) return { title, html: out };
|
|
@@ -431,7 +466,7 @@ async function renderOne(mdPath, opts, chrome, marked, tmpRoot) {
|
|
|
431
466
|
<span style="flex:1;text-align:right;">${opts.footerRight || ''}</span>
|
|
432
467
|
</div>`;
|
|
433
468
|
|
|
434
|
-
const buf = await chrome.print(htmlPath, { footer: opts.footer, footerTemplate, landscape: opts.landscape, outline: opts.outline });
|
|
469
|
+
const buf = await chrome.print(htmlPath, { footer: opts.footer, footerTemplate, landscape: opts.landscape, outline: opts.outline, waitMath: hasMath });
|
|
435
470
|
|
|
436
471
|
if (opts.keepHtml) {
|
|
437
472
|
await writeFile(mdPath.replace(/\.md$/i, '.html'), out, 'utf8');
|