markdown-paper 2.4.0 → 3.0.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/CHANGELOG.md +14 -0
- package/README.md +11 -1
- package/bin/mdp-themes.ts +20 -0
- package/bin/mdp.ts +43 -34
- package/biome.json +24 -0
- package/bun.lockb +0 -0
- package/lib/main.ts +34 -178
- package/lib/themes/aps.css +164 -0
- package/lib/themes/aps.ts +97 -0
- package/lib/types.ts +76 -0
- package/package.json +55 -30
- package/tsconfig.json +22 -22
- package/theme/aps/aps.css +0 -137
- package/theme/aps/aps.ts +0 -122
- package/theme/theme.ts +0 -33
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
## 3.0.0 (2025-06-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* 重构代码
|
|
11
|
+
|
|
12
|
+
### Refactoring
|
|
13
|
+
|
|
14
|
+
* 重构代码 ([1702525](https://github.com/LeafYeeXYZ/MarkdownPaper/commit/1702525d7bf4a9470de8aeabb0cad1e7abff6e56))
|
package/README.md
CHANGED
|
@@ -53,6 +53,10 @@ $$
|
|
|
53
53
|
- 文献2
|
|
54
54
|
- 文献3
|
|
55
55
|
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
(上面的是分页符)
|
|
59
|
+
|
|
56
60
|
##### 附录
|
|
57
61
|
```
|
|
58
62
|
|
|
@@ -60,7 +64,7 @@ $$
|
|
|
60
64
|
|
|
61
65
|
## 2 安装 `Bun`
|
|
62
66
|
|
|
63
|
-
`Bun` 是一个现代的 `JavaScript` / `TypeScript` 运行环境, 本项目基于 `Bun`
|
|
67
|
+
`Bun` 是一个现代的 `JavaScript` / `TypeScript` 运行环境, 本项目基于 `Bun` 环境运行; 请在官网 [bun.sh](https://bun.sh) 下载并安装 `Bun`, 也可以直接使用 `npm install -g bun` 安装
|
|
64
68
|
|
|
65
69
|
## 3 安装 `MarkdownPaper`
|
|
66
70
|
|
|
@@ -97,6 +101,7 @@ mdp example.md --outputDOCX
|
|
|
97
101
|
| `--outputDOCX` | 输出 `DOCX` 文件, 默认不输出<br>**导出后样式可能无法完全保留, 请自行调整** |
|
|
98
102
|
|
|
99
103
|
# 模板说明
|
|
104
|
+
|
|
100
105
|
`/theme/theme.ts` 中的 `MarkdownnPaperTheme` 接口定义了模板的样式, 按照类似于 `aps` 文件夹的结构可自定义模板; 模板可以提供自定义功能
|
|
101
106
|
|
|
102
107
|
模板制作完成后, 在 `/lib/main.ts` 中导入并添加到 `class MarkdownPaperOptions -> constructor -> case '--theme':` 中, 并在下方添加使用文档即可
|
|
@@ -104,7 +109,9 @@ mdp example.md --outputDOCX
|
|
|
104
109
|
推荐所有主题的文档和编写格式都尽量与 `aps` 主题保持一致
|
|
105
110
|
|
|
106
111
|
## APS 模板
|
|
112
|
+
|
|
107
113
|
### 额外命令行参数
|
|
114
|
+
|
|
108
115
|
| 参数 | 说明 |
|
|
109
116
|
| :---: | :---: |
|
|
110
117
|
| `--showTitle` | 在页眉显示文件名, 默认不显示 |
|
|
@@ -113,9 +120,12 @@ mdp example.md --outputDOCX
|
|
|
113
120
|
| `--enPunctuation` | 将正文中的中文标点符号替换为英文标点符号, 默认不替换<br>仅替换 `PDF` 和 `DOCX` 文件 |
|
|
114
121
|
|
|
115
122
|
### 编写格式
|
|
123
|
+
|
|
116
124
|
同上
|
|
117
125
|
|
|
118
126
|
# 更新日志
|
|
127
|
+
|
|
128
|
+
- `2.5.0` (2025-02-07): 支持分页符
|
|
119
129
|
- `2.4.0` (2024-12-08): 导出 `DOCX` 文件时, 不再依赖 `Python`
|
|
120
130
|
- `2.3.0` (2024-08-31): 支持数学公式
|
|
121
131
|
- `2.2.0` (2024-08-26): 半重构, 优化导入导出, 优化文档
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander'
|
|
4
|
+
import { themeLabels } from '../lib/types'
|
|
5
|
+
import { version } from '../package.json'
|
|
6
|
+
|
|
7
|
+
const program = new Command()
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('mdp-themes')
|
|
11
|
+
.description('MarkdownPaper CLI - 显示所有可用的论文模板')
|
|
12
|
+
.version(version)
|
|
13
|
+
.action(() => {
|
|
14
|
+
console.log('可用的论文模板:')
|
|
15
|
+
for (const [id, label] of Object.entries(themeLabels)) {
|
|
16
|
+
console.log(`- ${id} (${label})`)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
program.parse(process.argv)
|
package/bin/mdp.ts
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
import { render } from '../lib/main'
|
|
6
|
+
import { cliOptions } from '../lib/types'
|
|
7
|
+
import { version } from '../package.json'
|
|
8
|
+
|
|
9
|
+
const program = new Command()
|
|
7
10
|
const cwd = process.cwd()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('mdp')
|
|
14
|
+
.description('MarkdownPaper CLI')
|
|
15
|
+
.version(version)
|
|
16
|
+
.argument('<markdown>', 'Markdown文件相对路径')
|
|
17
|
+
.option('--out <path>', '输出文件相对路径 (默认和Markdown文件相同)')
|
|
18
|
+
.option('--theme <name>', '论文模板 (默认为心理学报)', 'APS')
|
|
19
|
+
.option('--html --output-html', '输出HTML文件', false)
|
|
20
|
+
.option('--docx --output-docx', '输出DOCX文件', false)
|
|
21
|
+
.option('--hide-footer', '隐藏页脚页码', false)
|
|
22
|
+
.option('--punctuation <zh/en/origin>', '标点符号格式', 'zh')
|
|
23
|
+
.command('themes', '显示所有可用的论文模板')
|
|
24
|
+
.action((markdown, options) => {
|
|
25
|
+
console.log('开始生成')
|
|
26
|
+
const src = resolve(cwd, markdown)
|
|
27
|
+
const out = options.out
|
|
28
|
+
? resolve(cwd, options.out)
|
|
29
|
+
: src.replace(/\.md$/, '.pdf')
|
|
30
|
+
const opts = cliOptions.parse({
|
|
31
|
+
...options,
|
|
32
|
+
src,
|
|
33
|
+
out,
|
|
34
|
+
})
|
|
35
|
+
render(opts)
|
|
36
|
+
.then(() => {
|
|
37
|
+
console.log('生成成功')
|
|
38
|
+
process.exit(0)
|
|
39
|
+
})
|
|
40
|
+
.catch((error) => {
|
|
41
|
+
console.error(`生成失败: ${error.message}`)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
program.parse(process.argv)
|
package/biome.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"organizeImports": {
|
|
9
|
+
"enabled": true
|
|
10
|
+
},
|
|
11
|
+
"linter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"rules": {
|
|
14
|
+
"recommended": true
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"javascript": {
|
|
18
|
+
"formatter": {
|
|
19
|
+
"trailingCommas": "all",
|
|
20
|
+
"semicolons": "asNeeded",
|
|
21
|
+
"quoteStyle": "single"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/bun.lockb
ADDED
|
Binary file
|
package/lib/main.ts
CHANGED
|
@@ -1,194 +1,50 @@
|
|
|
1
|
-
import { marked } from 'marked'
|
|
2
|
-
import puppeteer from 'puppeteer'
|
|
3
1
|
import fs from 'node:fs/promises'
|
|
4
|
-
import
|
|
5
|
-
import { readFileSync } from 'node:fs'
|
|
6
|
-
import { APS } from '../theme/aps/aps'
|
|
7
|
-
import type { MarkdownPaperTheme } from '../theme/theme'
|
|
8
|
-
import type { PDFOptions } from 'puppeteer'
|
|
9
|
-
import markedKatex from 'marked-katex-extension'
|
|
2
|
+
import { asBlob } from 'html-docx-js-typescript'
|
|
10
3
|
// @ts-ignore
|
|
11
4
|
import katexCss from 'katex/dist/katex.css' with { type: 'text' }
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/** markdown 文件绝对路径 */
|
|
17
|
-
src: string
|
|
18
|
-
/** pdf 文件绝对路径 */
|
|
19
|
-
out: string
|
|
20
|
-
/** 是否输出 html */
|
|
21
|
-
outputHTML: boolean
|
|
22
|
-
/** 是否输出 docx */
|
|
23
|
-
outputDOCX: boolean
|
|
24
|
-
/** 样式 */
|
|
25
|
-
theme: MarkdownPaperTheme
|
|
26
|
-
/** 正确格式 */
|
|
27
|
-
static format = `mdp <markdown> [--options]
|
|
28
|
-
|
|
29
|
-
<markdown>: markdown 文件相对路径
|
|
30
|
-
--out=<path>: 输出文件相对路径 (默认为 <markdown>)
|
|
31
|
-
--theme=<name>: 论文模板 (默认为 APS)
|
|
32
|
-
--outputHTML: 输出 html 文件 (默认不输出)
|
|
33
|
-
--outputDOCX: 输出 docx 文件 (默认不输出)
|
|
34
|
-
|
|
35
|
-
模板的自定义参数见模板说明`
|
|
36
|
-
/**
|
|
37
|
-
* 生成应用参数
|
|
38
|
-
* @param args 命令行参数
|
|
39
|
-
* @param cwd 当前工作目录
|
|
40
|
-
*/
|
|
41
|
-
constructor(args: string[], cwd: string) {
|
|
42
|
-
// 默认参数
|
|
43
|
-
this.src = ''
|
|
44
|
-
this.out = ''
|
|
45
|
-
this.outputHTML = false
|
|
46
|
-
this.outputDOCX = false
|
|
47
|
-
this.theme = new APS(args)
|
|
48
|
-
// 解析路径参数
|
|
49
|
-
if (args.length === 0) throw SyntaxError()
|
|
50
|
-
else args[0] = `--src=${args[0]}`
|
|
51
|
-
// 解析其他参数
|
|
52
|
-
args.forEach(arg => {
|
|
53
|
-
switch (arg.split('=')[0]) {
|
|
54
|
-
case '--src': {
|
|
55
|
-
const a = arg.split('=')
|
|
56
|
-
if (a.length !== 2 || a[1] === '') throw SyntaxError()
|
|
57
|
-
this.src = a[1].endsWith('.md') ? path.resolve(cwd, a[1]) : path.resolve(cwd, a[1] + '.md')
|
|
58
|
-
break
|
|
59
|
-
}
|
|
60
|
-
case '--out': {
|
|
61
|
-
const a = arg.split('=')
|
|
62
|
-
if (a.length !== 2 || a[1] === '') throw SyntaxError()
|
|
63
|
-
this.out = a[1].endsWith('.pdf') ? path.resolve(cwd, a[1]) : path.resolve(cwd, a[1] + '.pdf')
|
|
64
|
-
break
|
|
65
|
-
}
|
|
66
|
-
case '--theme': {
|
|
67
|
-
const a = arg.split('=')
|
|
68
|
-
if (a.length !== 2 || a[1] === '') throw SyntaxError()
|
|
69
|
-
switch (a[1].toUpperCase()) {
|
|
70
|
-
case 'APS': {
|
|
71
|
-
break
|
|
72
|
-
}
|
|
73
|
-
default: {
|
|
74
|
-
throw Error(`模板 ${a[1]} 不存在`)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
break
|
|
78
|
-
}
|
|
79
|
-
case '--outputHTML': {
|
|
80
|
-
this.outputHTML = true
|
|
81
|
-
break
|
|
82
|
-
}
|
|
83
|
-
case '--outputDOCX': {
|
|
84
|
-
this.outputDOCX = true
|
|
85
|
-
break
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
// 检查参数
|
|
90
|
-
if (this.out === '') this.out = this.src.replace('.md', '.pdf')
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 渲染 markdown
|
|
96
|
-
* @param options 参数
|
|
97
|
-
*/
|
|
98
|
-
async function renderMarkdown(
|
|
99
|
-
options: MarkdownPaperOptions,
|
|
100
|
-
): Promise<void> {
|
|
101
|
-
// 读取 markdown 文件
|
|
102
|
-
const raw = await fs.readFile(options.src, { encoding: 'utf-8' })
|
|
103
|
-
// 生成 html 文件
|
|
104
|
-
const html = await mdToHtml(raw, options.theme, path.basename(options.src).replace('.md', ''))
|
|
105
|
-
// 保存 pdf 文件
|
|
106
|
-
await htmlToPdf(
|
|
107
|
-
html.replace(/<img src="(.+?)"/g, (match, p1) => {
|
|
108
|
-
if (p1.startsWith('http')) return match
|
|
109
|
-
try {
|
|
110
|
-
const url = path.resolve(path.dirname(options.src), decodeURI(p1))
|
|
111
|
-
const data = readFileSync(url).toString('base64')
|
|
112
|
-
return `<img src="data:image/${path.extname(p1).replace('.', '')};base64,${data}"`
|
|
113
|
-
} catch (_) {
|
|
114
|
-
console.error(`图片 ${p1} 不存在`)
|
|
115
|
-
return match
|
|
116
|
-
}
|
|
117
|
-
}),
|
|
118
|
-
options.out,
|
|
119
|
-
options.theme.pdfOptions,
|
|
120
|
-
options.theme.script
|
|
121
|
-
)
|
|
122
|
-
// 保存 html 文件
|
|
123
|
-
options.outputHTML && await fs.writeFile(options.out.replace('.pdf', '.html'), html)
|
|
124
|
-
// 保存 docx 文件
|
|
125
|
-
const docx = await asBlob(html)
|
|
126
|
-
const docxBuffer = new Uint8Array(docx instanceof Blob ? await docx.arrayBuffer() : docx.buffer)
|
|
127
|
-
options.outputDOCX && await fs.writeFile(options.out.replace('.pdf', '.docx'), docxBuffer)
|
|
128
|
-
options.outputDOCX && console.warn('导出的 DOCX 文件可能存在格式丢失, 请手动调整\n')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 把 html 转换为 pdf
|
|
133
|
-
* @param html html 字符串, 图片为 base64
|
|
134
|
-
* @param dist pdf 文件绝对路径
|
|
135
|
-
* @param options pdf 参数, 无需设置路径
|
|
136
|
-
* @param script 在网页中要执行的函数
|
|
137
|
-
*/
|
|
138
|
-
async function htmlToPdf(html: string, dist: string, options: PDFOptions, script: () => void): Promise<void> {
|
|
139
|
-
const browser = await puppeteer.launch()
|
|
140
|
-
const page = await browser.newPage()
|
|
141
|
-
await page.setContent(html)
|
|
142
|
-
// 执行脚本
|
|
143
|
-
await page.evaluate(script)
|
|
144
|
-
await page.pdf({ path: dist, ...options })
|
|
145
|
-
await browser.close()
|
|
146
|
-
}
|
|
5
|
+
import { marked } from 'marked'
|
|
6
|
+
import markedKatex from 'marked-katex-extension'
|
|
7
|
+
import puppeteer from 'puppeteer'
|
|
8
|
+
import { type MarkdownPaperOptions, themes } from './types'
|
|
147
9
|
|
|
148
10
|
/**
|
|
149
|
-
*
|
|
150
|
-
* @param
|
|
151
|
-
* @param theme 论文模板
|
|
152
|
-
* @param pageTitle 页面标题
|
|
153
|
-
* @returns html 字符串
|
|
11
|
+
* 渲染 MarkdownPaper 文档
|
|
12
|
+
* @param options MarkdownPaper CLI 选项
|
|
154
13
|
*/
|
|
155
|
-
async function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// 设置 marked
|
|
161
|
-
marked.use(markedKatex({ throwOnError: false }))
|
|
162
|
-
// 预处理 markdown
|
|
163
|
-
let html = await theme.preParseMarkdown(md)
|
|
164
|
-
// 转换 markdown 为 html
|
|
165
|
-
html = `
|
|
14
|
+
export async function render(options: MarkdownPaperOptions): Promise<void> {
|
|
15
|
+
const theme = await themes[options.theme](options)
|
|
16
|
+
const markdown = await fs.readFile(options.src, { encoding: 'utf-8' })
|
|
17
|
+
marked.use(markedKatex({ throwOnError: false }))
|
|
18
|
+
const html = await theme.preParseHtml(`
|
|
166
19
|
<!DOCTYPE html>
|
|
167
20
|
<html lang="zh-CN">
|
|
168
21
|
<head>
|
|
169
22
|
<meta charset="UTF-8">
|
|
170
|
-
<title
|
|
171
|
-
<style
|
|
23
|
+
<title>MarkdownPaper</title>
|
|
24
|
+
<style>\n${theme.css}\n</style>
|
|
172
25
|
<style>\n${katexCss}\n</style>
|
|
173
26
|
</head>
|
|
174
27
|
<body>
|
|
175
|
-
${await marked(
|
|
28
|
+
${await marked(await theme.preParseMarkdown(markdown))}
|
|
176
29
|
</body>
|
|
177
30
|
</html>
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
31
|
+
`)
|
|
32
|
+
const browser = await puppeteer.launch()
|
|
33
|
+
const page = await browser.newPage()
|
|
34
|
+
await page.setContent(html)
|
|
35
|
+
await page.evaluate(theme.script)
|
|
36
|
+
await page.pdf({ path: options.out, ...theme.pdfOptions })
|
|
37
|
+
const finalHtml = await page.content()
|
|
38
|
+
if (options.outputHtml) {
|
|
39
|
+
await fs.writeFile(options.out.replace('.pdf', '.html'), finalHtml)
|
|
40
|
+
}
|
|
41
|
+
if (options.outputDocx) {
|
|
42
|
+
const docx = await asBlob(finalHtml)
|
|
43
|
+
const docxBuffer = new Uint8Array(
|
|
44
|
+
docx instanceof Blob ? await docx.arrayBuffer() : docx.buffer,
|
|
45
|
+
)
|
|
46
|
+
await fs.writeFile(options.out.replace('.pdf', '.docx'), docxBuffer)
|
|
47
|
+
console.warn('注意: 导出的Word文件可能存在格式丢失, 请手动调整')
|
|
48
|
+
}
|
|
49
|
+
await browser.close()
|
|
191
50
|
}
|
|
192
|
-
export type {
|
|
193
|
-
MarkdownPaperTheme
|
|
194
|
-
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
* {
|
|
2
|
+
font-family: "Times", "Times New Roman", "宋体", "SimSun", "华文宋体",
|
|
3
|
+
"STSong", sans-serif; /* 所有数字和英文字体都用 Times New Roman */
|
|
4
|
+
line-height: 1.55em; /* 1.5倍行距 */
|
|
5
|
+
margin: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
hr {
|
|
9
|
+
/* 用作分页符 */
|
|
10
|
+
page-break-after: always;
|
|
11
|
+
border: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
h1 {
|
|
15
|
+
/* 中文题目: 二号黑体 */
|
|
16
|
+
font-size: 29px;
|
|
17
|
+
font-weight: normal;
|
|
18
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
19
|
+
text-align: center;
|
|
20
|
+
margin-bottom: 9px;
|
|
21
|
+
}
|
|
22
|
+
.author {
|
|
23
|
+
/* 作者姓名: 四号仿宋 */
|
|
24
|
+
font-size: 18px;
|
|
25
|
+
font-weight: normal;
|
|
26
|
+
font-family: "仿宋", "Fangsong", "华文仿宋", "STFangsong", sans-serif;
|
|
27
|
+
text-align: center;
|
|
28
|
+
margin-bottom: 3px;
|
|
29
|
+
}
|
|
30
|
+
.school {
|
|
31
|
+
/* 作者单位: 小五宋体 */
|
|
32
|
+
font-size: 12px;
|
|
33
|
+
text-align: center;
|
|
34
|
+
margin-bottom: 38px;
|
|
35
|
+
}
|
|
36
|
+
.abstract {
|
|
37
|
+
/* 摘要和关键词: 五号宋体 */
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
text-align: justify;
|
|
40
|
+
padding: 0 28px;
|
|
41
|
+
&::before {
|
|
42
|
+
content: "摘 要";
|
|
43
|
+
font-weight: normal;
|
|
44
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
45
|
+
display: inline-block;
|
|
46
|
+
margin-right: 14px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
.keywords {
|
|
50
|
+
font-size: 14px;
|
|
51
|
+
margin-bottom: 31px;
|
|
52
|
+
padding: 0 28px;
|
|
53
|
+
&::before {
|
|
54
|
+
content: "关键词";
|
|
55
|
+
font-weight: normal;
|
|
56
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
57
|
+
display: inline-block;
|
|
58
|
+
margin-right: 14px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
h2 {
|
|
63
|
+
/* 一级标题: 四号宋体 */
|
|
64
|
+
font-size: 18px;
|
|
65
|
+
font-weight: normal;
|
|
66
|
+
margin: 7px 0;
|
|
67
|
+
}
|
|
68
|
+
h3 {
|
|
69
|
+
/* 二级标题: 五号黑体 */
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
font-weight: normal;
|
|
72
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
73
|
+
margin: 5px 0;
|
|
74
|
+
}
|
|
75
|
+
h4 {
|
|
76
|
+
/* 三级标题: 五号黑体 */
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
font-weight: normal;
|
|
79
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
80
|
+
margin: 3px 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
p {
|
|
84
|
+
/* 正文: 五号宋体 */
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
text-indent: 24px;
|
|
87
|
+
text-align: justify;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
h5 {
|
|
91
|
+
/* "参考文献": 五号黑体 */
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
font-weight: normal;
|
|
94
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
95
|
+
text-align: center;
|
|
96
|
+
margin-bottom: 7px;
|
|
97
|
+
margin-top: 20px;
|
|
98
|
+
}
|
|
99
|
+
ul,
|
|
100
|
+
ol {
|
|
101
|
+
/* 参考文献的项目: 小五号宋体 */
|
|
102
|
+
list-style-type: none;
|
|
103
|
+
padding: 0;
|
|
104
|
+
& > li {
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
margin: 6px 0;
|
|
107
|
+
text-align: justify;
|
|
108
|
+
text-indent: -24px;
|
|
109
|
+
padding-left: 24px;
|
|
110
|
+
}
|
|
111
|
+
& a {
|
|
112
|
+
text-decoration: none;
|
|
113
|
+
color: black;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
img {
|
|
118
|
+
display: block;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
margin: 0 auto;
|
|
121
|
+
margin-top: 10px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
blockquote,
|
|
125
|
+
blockquote > p {
|
|
126
|
+
/* 图片和表格的标题: 小五号宋体 */
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
font-weight: normal;
|
|
129
|
+
text-align: center;
|
|
130
|
+
margin: 0;
|
|
131
|
+
}
|
|
132
|
+
blockquote > p {
|
|
133
|
+
margin: 6px 0;
|
|
134
|
+
}
|
|
135
|
+
table {
|
|
136
|
+
/* 表格: 小五号宋体 */
|
|
137
|
+
font-size: 12px;
|
|
138
|
+
position: relative;
|
|
139
|
+
border-top: 1px solid black;
|
|
140
|
+
border-bottom: 1px solid black;
|
|
141
|
+
width: 100%;
|
|
142
|
+
max-width: 100%;
|
|
143
|
+
margin: 0 auto;
|
|
144
|
+
margin-bottom: 10px;
|
|
145
|
+
& th,
|
|
146
|
+
& td {
|
|
147
|
+
font-weight: normal;
|
|
148
|
+
}
|
|
149
|
+
& thead::after {
|
|
150
|
+
/* 用来做三线表中间的横线 */
|
|
151
|
+
content: "";
|
|
152
|
+
display: block;
|
|
153
|
+
position: absolute;
|
|
154
|
+
border-top: 1px solid #00000060;
|
|
155
|
+
width: 100%;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
b,
|
|
160
|
+
strong {
|
|
161
|
+
/* 加粗按黑体处理 */
|
|
162
|
+
font-weight: normal;
|
|
163
|
+
font-family: "黑体", "SimHei", "华文黑体", "STHeiti", sans-serif;
|
|
164
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { type MarkdownPaperTheme, MarkdownPaperThemes } from '../types'
|
|
4
|
+
|
|
5
|
+
const cssPath = resolve(import.meta.dirname, 'aps.css')
|
|
6
|
+
const cssContent = await readFile(cssPath, 'utf-8')
|
|
7
|
+
|
|
8
|
+
export const themeAPS: MarkdownPaperTheme = async (args) => {
|
|
9
|
+
let script: () => void = () => {}
|
|
10
|
+
if (args.punctuation === 'zh') {
|
|
11
|
+
script = () => {
|
|
12
|
+
const nodes = document.querySelectorAll(
|
|
13
|
+
'body > p, .abstract, .keywords, .author, .school',
|
|
14
|
+
)
|
|
15
|
+
// biome-ignore lint/complexity/noForEach: ...
|
|
16
|
+
nodes.forEach((node) => {
|
|
17
|
+
let text = node.textContent ?? ''
|
|
18
|
+
// 替换中英文逗号和句号
|
|
19
|
+
text = text
|
|
20
|
+
.replace(/, /g, ',')
|
|
21
|
+
.replace(/\. /g, '。')
|
|
22
|
+
.replace(/\.$/, '。')
|
|
23
|
+
// 替换中英文冒号
|
|
24
|
+
text = text.replace(/: /g, ':')
|
|
25
|
+
// 替换中英文分号
|
|
26
|
+
text = text.replace(/; /g, ';')
|
|
27
|
+
// 替换中英文感叹号
|
|
28
|
+
text = text.replace(/! /g, '!').replace(/!$/, '!')
|
|
29
|
+
// 替换中英文问号
|
|
30
|
+
text = text.replace(/\? /g, '?').replace(/\?$/, '?')
|
|
31
|
+
// 替换中英文括号
|
|
32
|
+
text = text.replace(/ \(/g, '(').replace(/\) /g, ')')
|
|
33
|
+
// 恢复et al.,
|
|
34
|
+
text = text
|
|
35
|
+
.replace(/et al\.,/g, 'et al., ')
|
|
36
|
+
.replace(/et al。/g, 'et al. ')
|
|
37
|
+
// 设置新文本
|
|
38
|
+
node.textContent = text
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
} else if (args.punctuation === 'en') {
|
|
42
|
+
script = () => {
|
|
43
|
+
const nodes = document.querySelectorAll(
|
|
44
|
+
'body > p, .abstract, .keywords, .author, .school',
|
|
45
|
+
)
|
|
46
|
+
// biome-ignore lint/complexity/noForEach: ...
|
|
47
|
+
nodes.forEach((node) => {
|
|
48
|
+
let text = node.textContent ?? ''
|
|
49
|
+
// 替换中英文逗号和句号
|
|
50
|
+
text = text.replace(/,/g, ', ').replace(/。 /g, '. ')
|
|
51
|
+
// 替换中英文冒号
|
|
52
|
+
text = text.replace(/:/g, ': ')
|
|
53
|
+
// 替换中英文分号
|
|
54
|
+
text = text.replace(/;/g, '; ')
|
|
55
|
+
// 替换中英文感叹号
|
|
56
|
+
text = text.replace(/!/g, '! ')
|
|
57
|
+
// 替换中英文问号
|
|
58
|
+
text = text.replace(/?/g, '? ')
|
|
59
|
+
// 替换中英文括号
|
|
60
|
+
text = text.replace(/(/g, ' (').replace(/)/g, ') ')
|
|
61
|
+
// 设置新文本
|
|
62
|
+
node.textContent = text
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: MarkdownPaperThemes.APS,
|
|
68
|
+
label: '心理学报',
|
|
69
|
+
css: cssContent,
|
|
70
|
+
script,
|
|
71
|
+
preParseMarkdown: async (md: string): Promise<string> => {
|
|
72
|
+
return md
|
|
73
|
+
.replace(/#author# (.*)/gm, '<div class="author">$1</div>')
|
|
74
|
+
.replace(/#school# (.*)/gm, '<div class="school">$1</div>')
|
|
75
|
+
.replace(/#keywords# (.*)/gm, '<div class="keywords">$1</div>')
|
|
76
|
+
.replace(/#abstract# (.*)/gm, '<div class="abstract">$1</div>')
|
|
77
|
+
},
|
|
78
|
+
preParseHtml: async (html: string): Promise<string> => {
|
|
79
|
+
// 把包裹图片的 p 标签去掉
|
|
80
|
+
return html.replace(/<p><img (.*?)><\/p>/g, '<img $1>')
|
|
81
|
+
},
|
|
82
|
+
pdfOptions: {
|
|
83
|
+
format: 'A4',
|
|
84
|
+
margin: {
|
|
85
|
+
top: '2cm',
|
|
86
|
+
right: '2.5cm',
|
|
87
|
+
bottom: '2cm',
|
|
88
|
+
left: '2.5cm',
|
|
89
|
+
},
|
|
90
|
+
displayHeaderFooter: !args.hideFooter,
|
|
91
|
+
headerTemplate: '<div></div>',
|
|
92
|
+
footerTemplate: args.hideFooter
|
|
93
|
+
? '<div></div>'
|
|
94
|
+
: '<div style="font-size: 9px; font-family: \'SimSun\'; color: #333; padding: 5px; margin: 0 auto;">第 <span class="pageNumber"></span> 页 / 共 <span class="totalPages"></span> 页</div>',
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
}
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PDFOptions } from 'puppeteer'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { themeAPS } from './themes/aps'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MarkdownPaper 主题 ID
|
|
7
|
+
*/
|
|
8
|
+
export enum MarkdownPaperThemes {
|
|
9
|
+
APS = 'APS',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const themeLabels: Record<MarkdownPaperThemes, string> = {
|
|
13
|
+
[MarkdownPaperThemes.APS]: '心理学报',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const themes: Record<MarkdownPaperThemes, MarkdownPaperTheme> = {
|
|
17
|
+
[MarkdownPaperThemes.APS]: themeAPS,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* MarkdownPaper CLI 选项的 Zod Schema
|
|
22
|
+
*/
|
|
23
|
+
export const cliOptions = z.object({
|
|
24
|
+
src: z.string(),
|
|
25
|
+
out: z.string(),
|
|
26
|
+
theme: z.nativeEnum(MarkdownPaperThemes),
|
|
27
|
+
outputHtml: z.boolean(),
|
|
28
|
+
outputDocx: z.boolean(),
|
|
29
|
+
hideFooter: z.boolean(),
|
|
30
|
+
punctuation: z.enum(['zh', 'en', 'origin']),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MarkdownPaper CLI 选项 (详见 `bin/mdp.ts`)
|
|
35
|
+
*/
|
|
36
|
+
export type MarkdownPaperOptions = z.infer<typeof cliOptions>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* MarkdownPaper 论文模板
|
|
40
|
+
* @param args 命令行参数, 如果不提供, 则只有效返回 id 和 label 参数
|
|
41
|
+
*/
|
|
42
|
+
export type MarkdownPaperTheme = (args: MarkdownPaperOptions) => Promise<{
|
|
43
|
+
/**
|
|
44
|
+
* 主题ID
|
|
45
|
+
*/
|
|
46
|
+
id: MarkdownPaperThemes
|
|
47
|
+
/**
|
|
48
|
+
* 主题名称
|
|
49
|
+
*/
|
|
50
|
+
label: string
|
|
51
|
+
/**
|
|
52
|
+
* css 样式
|
|
53
|
+
* 不含 \<style>\</style>
|
|
54
|
+
*/
|
|
55
|
+
css: string
|
|
56
|
+
/**
|
|
57
|
+
* 预处理 markdown 字符串 (用于转换自定义标签等)
|
|
58
|
+
* @param md markdown 字符串
|
|
59
|
+
* @returns 转换后的 markdown 字符串
|
|
60
|
+
*/
|
|
61
|
+
preParseMarkdown(md: string): Promise<string>
|
|
62
|
+
/**
|
|
63
|
+
* 预处理 html 字符串
|
|
64
|
+
* @param html html 字符串
|
|
65
|
+
* @returns 转换后的 html 字符串
|
|
66
|
+
*/
|
|
67
|
+
preParseHtml(html: string): Promise<string>
|
|
68
|
+
/**
|
|
69
|
+
* 在网页中要执行的函数
|
|
70
|
+
*/
|
|
71
|
+
script(): void
|
|
72
|
+
/**
|
|
73
|
+
* PDF 参数 (无需设置路径)
|
|
74
|
+
*/
|
|
75
|
+
pdfOptions: PDFOptions
|
|
76
|
+
}>
|
package/package.json
CHANGED
|
@@ -1,31 +1,56 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
2
|
+
"name": "markdown-paper",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "LeafYeeXYZ",
|
|
6
|
+
"email": "xiaoyezi@leafyee.xyz"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"commander": "^14.0.0",
|
|
10
|
+
"html-docx-js-typescript": "^0.1.5",
|
|
11
|
+
"marked": "^12.0.2",
|
|
12
|
+
"marked-katex-extension": "^5.1.4",
|
|
13
|
+
"puppeteer": "^22.15.0",
|
|
14
|
+
"zod": "^3.25.56"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"typescript": "^5.4.5"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"mdp": "bun ./bin/mdp.ts",
|
|
21
|
+
"mdp-themes": "bun ./bin/mdp-themes.ts"
|
|
22
|
+
},
|
|
23
|
+
"license": "GPL-3.0-only",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"mdp": "bun ./bin/mdp.ts",
|
|
26
|
+
"try": "bun mdp ./demo/论文.md --html --docx",
|
|
27
|
+
"release:create": "commit-and-tag-version",
|
|
28
|
+
"release:publish": "git push --follow-tags origin main && npm publish",
|
|
29
|
+
"check": "biome check --write ."
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@biomejs/biome": "1.9.4",
|
|
34
|
+
"commit-and-tag-version": "^12.5.1"
|
|
35
|
+
},
|
|
36
|
+
"commit-and-tag-version": {
|
|
37
|
+
"types": [
|
|
38
|
+
{
|
|
39
|
+
"type": "feat",
|
|
40
|
+
"section": "Features"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "fix",
|
|
44
|
+
"section": "Bug Fixes"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "refactor",
|
|
48
|
+
"section": "Refactoring"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "perf",
|
|
52
|
+
"section": "Improvements"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// Some stricter flags (disabled by default)
|
|
23
|
+
"noUnusedLocals": true,
|
|
24
|
+
"noUnusedParameters": true,
|
|
25
|
+
"noPropertyAccessFromIndexSignature": true
|
|
26
|
+
}
|
|
27
27
|
}
|
package/theme/aps/aps.css
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
* {
|
|
2
|
-
font-family: 'Times', 'Times New Roman', '宋体', 'SimSun', '华文宋体', 'STSong'; /* 所有数字和英文字体都用 Times New Roman */
|
|
3
|
-
line-height: 1.55em; /* 1.5倍行距 */
|
|
4
|
-
margin: 0;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
h1 { /* 中文题目: 二号黑体 */
|
|
8
|
-
font-size: 29px;
|
|
9
|
-
font-weight: normal;
|
|
10
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
11
|
-
text-align: center;
|
|
12
|
-
margin-bottom: 9px;
|
|
13
|
-
}
|
|
14
|
-
.author { /* 作者姓名: 四号仿宋 */
|
|
15
|
-
font-size: 18px;
|
|
16
|
-
font-weight: normal;
|
|
17
|
-
font-family: '仿宋', 'Fangsong', '华文仿宋', 'STFangsong';
|
|
18
|
-
text-align: center;
|
|
19
|
-
margin-bottom: 3px;
|
|
20
|
-
}
|
|
21
|
-
.school { /* 作者单位: 小五宋体 */
|
|
22
|
-
font-size: 12px;
|
|
23
|
-
text-align: center;
|
|
24
|
-
margin-bottom: 38px;
|
|
25
|
-
}
|
|
26
|
-
.abstract { /* 摘要和关键词: 五号宋体 */
|
|
27
|
-
font-size: 14px;
|
|
28
|
-
text-align: justify;
|
|
29
|
-
padding: 0 28px;
|
|
30
|
-
&::before {
|
|
31
|
-
content: '摘 要';
|
|
32
|
-
font-weight: normal;
|
|
33
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
34
|
-
display: inline-block;
|
|
35
|
-
margin-right: 14px;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
.keywords {
|
|
39
|
-
font-size: 14px;
|
|
40
|
-
margin-bottom: 31px;
|
|
41
|
-
padding: 0 28px;
|
|
42
|
-
&::before {
|
|
43
|
-
content: '关键词';
|
|
44
|
-
font-weight: normal;
|
|
45
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
46
|
-
display: inline-block;
|
|
47
|
-
margin-right: 14px;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
h2 { /* 一级标题: 四号宋体 */
|
|
52
|
-
font-size: 18px;
|
|
53
|
-
font-weight: normal;
|
|
54
|
-
margin: 7px 0;
|
|
55
|
-
}
|
|
56
|
-
h3 { /* 二级标题: 五号黑体 */
|
|
57
|
-
font-size: 14px;
|
|
58
|
-
font-weight: normal;
|
|
59
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
60
|
-
margin: 5px 0;
|
|
61
|
-
}
|
|
62
|
-
h4 { /* 三级标题: 五号黑体 */
|
|
63
|
-
font-size: 14px;
|
|
64
|
-
font-weight: normal;
|
|
65
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
66
|
-
margin: 3px 0;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
p { /* 正文: 五号宋体 */
|
|
70
|
-
font-size: 14px;
|
|
71
|
-
text-indent: 24px;
|
|
72
|
-
text-align: justify;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
h5 { /* "参考文献": 五号黑体 */
|
|
76
|
-
font-size: 14px;
|
|
77
|
-
font-weight: normal;
|
|
78
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
79
|
-
text-align: center;
|
|
80
|
-
margin-bottom: 7px;
|
|
81
|
-
margin-top: 20px;
|
|
82
|
-
}
|
|
83
|
-
ul, ol { /* 参考文献的项目: 小五号宋体 */
|
|
84
|
-
list-style-type: none;
|
|
85
|
-
padding: 0;
|
|
86
|
-
& > li {
|
|
87
|
-
font-size: 12px;
|
|
88
|
-
margin: 6px 0;
|
|
89
|
-
text-align: justify;
|
|
90
|
-
text-indent: -24px;
|
|
91
|
-
padding-left: 24px;
|
|
92
|
-
}
|
|
93
|
-
& a {
|
|
94
|
-
text-decoration: none;
|
|
95
|
-
color: black;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
img {
|
|
100
|
-
display: block;
|
|
101
|
-
max-width: 100%;
|
|
102
|
-
margin: 0 auto;
|
|
103
|
-
margin-top: 10px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
blockquote, blockquote > p { /* 图片和表格的标题: 小五号宋体 */
|
|
107
|
-
font-size: 12px;
|
|
108
|
-
font-weight: normal;
|
|
109
|
-
text-align: center;
|
|
110
|
-
margin: 0;
|
|
111
|
-
}
|
|
112
|
-
blockquote > p { margin: 6px 0; }
|
|
113
|
-
table { /* 表格: 小五号宋体 */
|
|
114
|
-
font-size: 12px;
|
|
115
|
-
position: relative;
|
|
116
|
-
border-top: 1px solid black;
|
|
117
|
-
border-bottom: 1px solid black;
|
|
118
|
-
width: 100%;
|
|
119
|
-
max-width: 100%;
|
|
120
|
-
margin: 0 auto;
|
|
121
|
-
margin-bottom: 10px;
|
|
122
|
-
& th, & td {
|
|
123
|
-
font-weight: normal;
|
|
124
|
-
}
|
|
125
|
-
& thead::after { /* 用来做三线表中间的横线 */
|
|
126
|
-
content: '';
|
|
127
|
-
display: block;
|
|
128
|
-
position: absolute;
|
|
129
|
-
border-top: 1px solid #00000060;
|
|
130
|
-
width: 100%;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
b, strong { /* 加粗按黑体处理 */
|
|
135
|
-
font-weight: normal;
|
|
136
|
-
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
137
|
-
}
|
package/theme/aps/aps.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { MarkdownPaperTheme } from '../theme'
|
|
2
|
-
import type { PDFOptions } from 'puppeteer'
|
|
3
|
-
import fs from 'node:fs'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
|
|
6
|
-
export class APS implements MarkdownPaperTheme {
|
|
7
|
-
|
|
8
|
-
css: string
|
|
9
|
-
preParseMarkdown: (md: string) => Promise<string>
|
|
10
|
-
preParseHTML: (html: string) => Promise<string>
|
|
11
|
-
script: () => void
|
|
12
|
-
pdfOptions: PDFOptions
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
args: string[] = [],
|
|
16
|
-
) {
|
|
17
|
-
|
|
18
|
-
// 默认自定义参数
|
|
19
|
-
let showTitle: boolean = false
|
|
20
|
-
let hideFooter: boolean = false
|
|
21
|
-
let zhPunctuation: boolean = false
|
|
22
|
-
let enPunctuation: boolean = false
|
|
23
|
-
// 解析参数
|
|
24
|
-
args.forEach(arg => {
|
|
25
|
-
switch (arg.split('=')[0]) {
|
|
26
|
-
case '--showTitle': showTitle = true; break
|
|
27
|
-
case '--hideFooter': hideFooter = true; break
|
|
28
|
-
case '--zhPunctuation': zhPunctuation = true; break
|
|
29
|
-
case '--enPunctuation': enPunctuation = true; break
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// css
|
|
34
|
-
this.css = fs.readFileSync(path.resolve(import.meta.dirname, 'aps.css'), 'utf-8')
|
|
35
|
-
|
|
36
|
-
// preParseMarkdown
|
|
37
|
-
this.preParseMarkdown = async (md: string): Promise<string> => {
|
|
38
|
-
// 作者
|
|
39
|
-
md = md.replace(/#author# (.*)/mg, '<div class="author">$1</div>')
|
|
40
|
-
// 单位
|
|
41
|
-
md = md.replace(/#school# (.*)/mg, '<div class="school">$1</div>')
|
|
42
|
-
// 关键词
|
|
43
|
-
md = md.replace(/#keywords# (.*)/mg, '<div class="keywords">$1</div>')
|
|
44
|
-
// 摘要
|
|
45
|
-
md = md.replace(/#abstract# (.*)/mg, '<div class="abstract">$1</div>')
|
|
46
|
-
// 返回处理后的字符串
|
|
47
|
-
return md
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// preParseHTML
|
|
51
|
-
this.preParseHTML = async (html: string): Promise<string> => {
|
|
52
|
-
// 把包裹图片的 p 标签去掉
|
|
53
|
-
html = html.replace(/<p><img (.*?)><\/p>/g, '<img $1>')
|
|
54
|
-
// 返回处理后的字符串
|
|
55
|
-
return html
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// script
|
|
59
|
-
// 替换标点
|
|
60
|
-
if (zhPunctuation && !enPunctuation) {
|
|
61
|
-
this.script = () => {
|
|
62
|
-
const nodes = document.querySelectorAll('body > p, .abstract, .keywords, .author, .school')
|
|
63
|
-
nodes.forEach(node => {
|
|
64
|
-
let text = node.textContent ?? ''
|
|
65
|
-
// 替换中英文逗号和句号
|
|
66
|
-
text = text.replace(/, /g, ',').replace(/\. /g, '。').replace(/\.$/, '。')
|
|
67
|
-
// 替换中英文冒号
|
|
68
|
-
text = text.replace(/: /g, ':')
|
|
69
|
-
// 替换中英文分号
|
|
70
|
-
text = text.replace(/; /g, ';')
|
|
71
|
-
// 替换中英文感叹号
|
|
72
|
-
text = text.replace(/! /g, '!').replace(/!$/, '!')
|
|
73
|
-
// 替换中英文问号
|
|
74
|
-
text = text.replace(/\? /g, '?').replace(/\?$/, '?')
|
|
75
|
-
// 替换中英文括号
|
|
76
|
-
text = text.replace(/ \(/g, '(').replace(/\) /g, ')')
|
|
77
|
-
// 恢复et al.,
|
|
78
|
-
text = text.replace(/et al\.,/g, 'et al., ').replace(/et al。/g, 'et al. ')
|
|
79
|
-
// 设置新文本
|
|
80
|
-
node.textContent = text
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
} else if (!zhPunctuation && enPunctuation) {
|
|
84
|
-
this.script = () => {
|
|
85
|
-
const nodes = document.querySelectorAll('body > p, .abstract, .keywords, .author, .school')
|
|
86
|
-
nodes.forEach(node => {
|
|
87
|
-
let text = node.textContent ?? ''
|
|
88
|
-
// 替换中英文逗号和句号
|
|
89
|
-
text = text.replace(/,/g, ', ').replace(/。 /g, '. ')
|
|
90
|
-
// 替换中英文冒号
|
|
91
|
-
text = text.replace(/:/g, ': ')
|
|
92
|
-
// 替换中英文分号
|
|
93
|
-
text = text.replace(/;/g, '; ')
|
|
94
|
-
// 替换中英文感叹号
|
|
95
|
-
text = text.replace(/!/g, '! ')
|
|
96
|
-
// 替换中英文问号
|
|
97
|
-
text = text.replace(/?/g, '? ')
|
|
98
|
-
// 替换中英文括号
|
|
99
|
-
text = text.replace(/(/g, ' (').replace(/)/g, ') ')
|
|
100
|
-
// 设置新文本
|
|
101
|
-
node.textContent = text
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
this.script = () => {}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// pdfOptions
|
|
109
|
-
this.pdfOptions = {
|
|
110
|
-
format: 'A4',
|
|
111
|
-
margin: {
|
|
112
|
-
top: '2cm',
|
|
113
|
-
right: '2.5cm',
|
|
114
|
-
bottom: '2cm',
|
|
115
|
-
left: '2.5cm'
|
|
116
|
-
},
|
|
117
|
-
displayHeaderFooter: showTitle || !hideFooter,
|
|
118
|
-
headerTemplate: showTitle ? `<div style="font-size: 9px; font-family: 'SimSun'; color: #333; padding: 5px; margin-left: 0.6cm;"> <span class="title"></span> </div>` : `<div></div>`,
|
|
119
|
-
footerTemplate: hideFooter ? `<div></div>` : `<div style="font-size: 9px; font-family: 'SimSun'; color: #333; padding: 5px; margin: 0 auto;">第 <span class="pageNumber"></span> 页 / 共 <span class="totalPages"></span> 页</div>`,
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
package/theme/theme.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { PDFOptions } from 'puppeteer'
|
|
2
|
-
|
|
3
|
-
export interface MarkdownPaperTheme {
|
|
4
|
-
/**
|
|
5
|
-
* css 样式
|
|
6
|
-
* 不含 \<style>\</style>
|
|
7
|
-
*/
|
|
8
|
-
css: string
|
|
9
|
-
/**
|
|
10
|
-
* 预处理 markdown 字符串
|
|
11
|
-
* 用于转换自定义标签等
|
|
12
|
-
* @param md markdown 字符串
|
|
13
|
-
* @returns 转换后的 markdown 字符串
|
|
14
|
-
*/
|
|
15
|
-
preParseMarkdown(md: string): Promise<string>
|
|
16
|
-
/**
|
|
17
|
-
* 预处理 html 字符串
|
|
18
|
-
* 将在保存 html 文件前调用
|
|
19
|
-
* @param html html 字符串
|
|
20
|
-
* @returns 转换后的 html 字符串
|
|
21
|
-
*/
|
|
22
|
-
preParseHTML(html: string): Promise<string>
|
|
23
|
-
/**
|
|
24
|
-
* 在网页中要执行的函数
|
|
25
|
-
* 将在保存 html 文件后调用
|
|
26
|
-
*/
|
|
27
|
-
script(): void
|
|
28
|
-
/**
|
|
29
|
-
* PDF 参数
|
|
30
|
-
* 无需设置路径
|
|
31
|
-
*/
|
|
32
|
-
pdfOptions: PDFOptions
|
|
33
|
-
}
|