markdown-paper 2.2.2 → 2.2.4
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 +2 -2
- package/lib/main.ts +50 -30
- package/package.json +1 -1
- package/theme/aps/aps.ts +172 -10
- package/theme/theme.ts +6 -18
package/README.md
CHANGED
|
@@ -84,9 +84,9 @@ mdp example.md --outputDOCX
|
|
|
84
84
|
| `--outputDOCX` | 输出 `DOCX` 文件, 默认不输出<br>**须先通过 `python` 安装依赖 `pdf2docx`**<br>使用时推荐开启 `--hideFooter` 参数 |
|
|
85
85
|
|
|
86
86
|
# 模板说明
|
|
87
|
-
`/theme/theme.ts` 中的 `
|
|
87
|
+
`/theme/theme.ts` 中的 `MarkdownnPaperTheme` 接口定义了模板的样式, 按照类似于 `aps` 文件夹的结构可自定义模板; 模板可以提供自定义功能
|
|
88
88
|
|
|
89
|
-
模板制作完成后, 在 `/lib/main.ts` 中导入并添加到 `class
|
|
89
|
+
模板制作完成后, 在 `/lib/main.ts` 中导入并添加到 `class MarkdownPaperOptions -> constructor -> case '--theme':` 中, 并在下方添加使用文档即可
|
|
90
90
|
|
|
91
91
|
推荐所有主题的文档和编写格式都尽量与 `aps` 主题保持一致
|
|
92
92
|
|
package/lib/main.ts
CHANGED
|
@@ -3,8 +3,8 @@ import puppeteer from 'puppeteer'
|
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { readFileSync } from 'node:fs'
|
|
6
|
-
import { APS } from '../theme/aps/aps'
|
|
7
|
-
import { MarkdownPaperTheme } from '../theme/theme'
|
|
6
|
+
import { APS, baseAPS } from '../theme/aps/aps'
|
|
7
|
+
import type { MarkdownPaperTheme } from '../theme/theme'
|
|
8
8
|
import type { PDFOptions } from 'puppeteer'
|
|
9
9
|
|
|
10
10
|
/** 应用参数 */
|
|
@@ -40,7 +40,7 @@ class MarkdownPaperOptions {
|
|
|
40
40
|
this.out = ''
|
|
41
41
|
this.outputHTML = false
|
|
42
42
|
this.outputDOCX = false
|
|
43
|
-
this.theme = new APS(args
|
|
43
|
+
this.theme = new APS(args)
|
|
44
44
|
// 解析路径参数
|
|
45
45
|
if (args.length === 0) throw SyntaxError()
|
|
46
46
|
else args[0] = `--src=${args[0]}`
|
|
@@ -90,32 +90,14 @@ class MarkdownPaperOptions {
|
|
|
90
90
|
/**
|
|
91
91
|
* 渲染 markdown
|
|
92
92
|
* @param options 参数
|
|
93
|
-
* @param rawMarkdown 如果传入此参数, 则不再读取文件
|
|
94
93
|
*/
|
|
95
94
|
async function renderMarkdown(
|
|
96
95
|
options: MarkdownPaperOptions,
|
|
97
|
-
rawMarkdown?: string,
|
|
98
96
|
): Promise<void> {
|
|
99
97
|
// 读取 markdown 文件
|
|
100
|
-
const raw =
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
// 转换 markdown 为 html
|
|
104
|
-
let html = `
|
|
105
|
-
<!DOCTYPE html>
|
|
106
|
-
<html lang="zh-CN">
|
|
107
|
-
<head>
|
|
108
|
-
<meta charset="UTF-8">
|
|
109
|
-
<title>${path.basename(options.src).replace('.md', '')}</title>
|
|
110
|
-
<style>${options.theme.css}</style>
|
|
111
|
-
</head>
|
|
112
|
-
<body>
|
|
113
|
-
${await marked(md)}
|
|
114
|
-
</body>
|
|
115
|
-
</html>
|
|
116
|
-
`
|
|
117
|
-
// 预处理 html
|
|
118
|
-
html = await options.theme.preParseHTML(html)
|
|
98
|
+
const raw = await fs.readFile(options.src, { encoding: 'utf-8' })
|
|
99
|
+
// 生成 html 文件
|
|
100
|
+
const html = await mdToHtml(raw, options.theme, path.basename(options.src).replace('.md', ''))
|
|
119
101
|
// 保存 html 文件
|
|
120
102
|
options.outputHTML && await fs.writeFile(options.out.replace('.pdf', '.html'), html)
|
|
121
103
|
// 保存 pdf 文件
|
|
@@ -132,7 +114,8 @@ async function renderMarkdown(
|
|
|
132
114
|
}
|
|
133
115
|
}),
|
|
134
116
|
options.out,
|
|
135
|
-
options.theme.pdfOptions
|
|
117
|
+
options.theme.pdfOptions,
|
|
118
|
+
options.theme.script
|
|
136
119
|
)
|
|
137
120
|
// 保存 docx 文件
|
|
138
121
|
options.outputDOCX && await pdfToDocx(options.out)
|
|
@@ -143,11 +126,14 @@ async function renderMarkdown(
|
|
|
143
126
|
* @param html html 字符串, 图片为 base64
|
|
144
127
|
* @param dist pdf 文件绝对路径
|
|
145
128
|
* @param options pdf 参数, 无需设置路径
|
|
129
|
+
* @param script 在网页中要执行的函数
|
|
146
130
|
*/
|
|
147
|
-
async function htmlToPdf(html: string, dist: string, options: PDFOptions): Promise<void> {
|
|
131
|
+
async function htmlToPdf(html: string, dist: string, options: PDFOptions, script: () => void): Promise<void> {
|
|
148
132
|
const browser = await puppeteer.launch()
|
|
149
133
|
const page = await browser.newPage()
|
|
150
134
|
await page.setContent(html)
|
|
135
|
+
// 执行脚本
|
|
136
|
+
await page.evaluate(script)
|
|
151
137
|
await page.pdf({ path: dist, ...options })
|
|
152
138
|
await browser.close()
|
|
153
139
|
}
|
|
@@ -180,14 +166,48 @@ function pdfToDocx(pdfPath: string): Promise<void> {
|
|
|
180
166
|
})
|
|
181
167
|
}
|
|
182
168
|
|
|
183
|
-
|
|
184
|
-
|
|
169
|
+
/**
|
|
170
|
+
* 把 markdown 转换为 html
|
|
171
|
+
* @param md markdown 字符串
|
|
172
|
+
* @param theme 论文模板
|
|
173
|
+
* @param pageTitle 页面标题
|
|
174
|
+
* @returns html 字符串
|
|
175
|
+
*/
|
|
176
|
+
async function mdToHtml(
|
|
177
|
+
md: string,
|
|
178
|
+
theme: MarkdownPaperTheme,
|
|
179
|
+
pageTitle: string = 'MarkdownPaper'
|
|
180
|
+
): Promise<string> {
|
|
181
|
+
// 预处理 markdown
|
|
182
|
+
let html = await theme.preParseMarkdown(md)
|
|
183
|
+
// 转换 markdown 为 html
|
|
184
|
+
html = `
|
|
185
|
+
<!DOCTYPE html>
|
|
186
|
+
<html lang="zh-CN">
|
|
187
|
+
<head>
|
|
188
|
+
<meta charset="UTF-8">
|
|
189
|
+
<title>${pageTitle}</title>
|
|
190
|
+
<style>${theme.css}</style>
|
|
191
|
+
</head>
|
|
192
|
+
<body>
|
|
193
|
+
${await marked(html)}
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
|
196
|
+
`
|
|
197
|
+
// 预处理 html
|
|
198
|
+
html = await theme.preParseHTML(html)
|
|
199
|
+
return html
|
|
185
200
|
}
|
|
201
|
+
|
|
202
|
+
|
|
186
203
|
export {
|
|
187
|
-
MarkdownPaperTheme,
|
|
188
204
|
MarkdownPaperOptions,
|
|
189
205
|
renderMarkdown,
|
|
190
206
|
htmlToPdf,
|
|
191
207
|
pdfToDocx,
|
|
192
|
-
|
|
208
|
+
mdToHtml,
|
|
209
|
+
baseAPS
|
|
210
|
+
}
|
|
211
|
+
export type {
|
|
212
|
+
MarkdownPaperTheme
|
|
193
213
|
}
|
package/package.json
CHANGED
package/theme/aps/aps.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { MarkdownPaperTheme } from '../theme'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
5
|
|
|
6
|
-
export class APS
|
|
6
|
+
export class APS implements MarkdownPaperTheme {
|
|
7
7
|
|
|
8
8
|
css: string
|
|
9
9
|
preParseMarkdown: (md: string) => Promise<string>
|
|
@@ -11,10 +11,10 @@ export class APS extends MarkdownPaperTheme {
|
|
|
11
11
|
script: () => void
|
|
12
12
|
pdfOptions: PDFOptions
|
|
13
13
|
|
|
14
|
-
constructor(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
constructor(
|
|
15
|
+
args: string[] = [],
|
|
16
|
+
) {
|
|
17
|
+
|
|
18
18
|
// 默认自定义参数
|
|
19
19
|
let showTitle: boolean = false
|
|
20
20
|
let hideFooter: boolean = false
|
|
@@ -31,7 +31,7 @@ export class APS extends MarkdownPaperTheme {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
// css
|
|
34
|
-
this.css = readFileSync(resolve(import.meta.
|
|
34
|
+
this.css = fs.readFileSync(path.resolve(import.meta.dirname, 'aps.css'), 'utf-8')
|
|
35
35
|
|
|
36
36
|
// preParseMarkdown
|
|
37
37
|
this.preParseMarkdown = async (md: string): Promise<string> => {
|
|
@@ -119,4 +119,166 @@ export class APS extends MarkdownPaperTheme {
|
|
|
119
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
120
|
}
|
|
121
121
|
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const baseAPS: MarkdownPaperTheme = {
|
|
125
|
+
css: `
|
|
126
|
+
* {
|
|
127
|
+
font-family: 'Times', 'Times New Roman', '宋体', 'SimSun', '华文宋体', 'STSong'; /* 所有数字和英文字体都用 Times New Roman */
|
|
128
|
+
line-height: 1.55em; /* 1.5倍行距 */
|
|
129
|
+
margin: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
h1 { /* 中文题目: 二号黑体 */
|
|
133
|
+
font-size: 29px;
|
|
134
|
+
font-weight: normal;
|
|
135
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
136
|
+
text-align: center;
|
|
137
|
+
margin-bottom: 9px;
|
|
138
|
+
}
|
|
139
|
+
.author { /* 作者姓名: 四号仿宋 */
|
|
140
|
+
font-size: 18px;
|
|
141
|
+
font-weight: normal;
|
|
142
|
+
font-family: '仿宋', 'Fangsong', '华文仿宋', 'STFangsong';
|
|
143
|
+
text-align: center;
|
|
144
|
+
margin-bottom: 3px;
|
|
145
|
+
}
|
|
146
|
+
.school { /* 作者单位: 小五宋体 */
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
text-align: center;
|
|
149
|
+
margin-bottom: 38px;
|
|
150
|
+
}
|
|
151
|
+
.abstract { /* 摘要和关键词: 五号宋体 */
|
|
152
|
+
font-size: 14px;
|
|
153
|
+
text-align: justify;
|
|
154
|
+
padding: 0 28px;
|
|
155
|
+
&::before {
|
|
156
|
+
content: '摘 要';
|
|
157
|
+
font-weight: normal;
|
|
158
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
159
|
+
display: inline-block;
|
|
160
|
+
margin-right: 14px;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
.keywords {
|
|
164
|
+
font-size: 14px;
|
|
165
|
+
margin-bottom: 31px;
|
|
166
|
+
padding: 0 28px;
|
|
167
|
+
&::before {
|
|
168
|
+
content: '关键词';
|
|
169
|
+
font-weight: normal;
|
|
170
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
171
|
+
display: inline-block;
|
|
172
|
+
margin-right: 14px;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
h2 { /* 一级标题: 四号宋体 */
|
|
177
|
+
font-size: 18px;
|
|
178
|
+
font-weight: normal;
|
|
179
|
+
margin: 7px 0;
|
|
180
|
+
}
|
|
181
|
+
h3 { /* 二级标题: 五号黑体 */
|
|
182
|
+
font-size: 14px;
|
|
183
|
+
font-weight: normal;
|
|
184
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
185
|
+
margin: 5px 0;
|
|
186
|
+
}
|
|
187
|
+
h4 { /* 三级标题: 五号黑体 */
|
|
188
|
+
font-size: 14px;
|
|
189
|
+
font-weight: normal;
|
|
190
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
191
|
+
margin: 3px 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
p { /* 正文: 五号宋体 */
|
|
195
|
+
font-size: 14px;
|
|
196
|
+
text-indent: 24px;
|
|
197
|
+
text-align: justify;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
h5 { /* "参考文献": 五号黑体 */
|
|
201
|
+
font-size: 14px;
|
|
202
|
+
font-weight: normal;
|
|
203
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
204
|
+
text-align: center;
|
|
205
|
+
margin-bottom: 7px;
|
|
206
|
+
margin-top: 20px;
|
|
207
|
+
}
|
|
208
|
+
ul, ol { /* 参考文献的项目: 小五号宋体 */
|
|
209
|
+
list-style-type: none;
|
|
210
|
+
padding: 0;
|
|
211
|
+
& > li {
|
|
212
|
+
font-size: 12px;
|
|
213
|
+
margin: 6px 0;
|
|
214
|
+
text-align: justify;
|
|
215
|
+
text-indent: -24px;
|
|
216
|
+
padding-left: 24px;
|
|
217
|
+
}
|
|
218
|
+
& a {
|
|
219
|
+
text-decoration: none;
|
|
220
|
+
color: black;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
img {
|
|
225
|
+
display: block;
|
|
226
|
+
max-width: 100%;
|
|
227
|
+
margin: 0 auto;
|
|
228
|
+
margin-top: 10px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
blockquote, blockquote > p { /* 图片和表格的标题: 小五号宋体 */
|
|
232
|
+
font-size: 12px;
|
|
233
|
+
font-weight: normal;
|
|
234
|
+
text-align: center;
|
|
235
|
+
margin: 0;
|
|
236
|
+
}
|
|
237
|
+
blockquote > p { margin: 6px 0; }
|
|
238
|
+
table { /* 表格: 小五号宋体 */
|
|
239
|
+
font-size: 12px;
|
|
240
|
+
position: relative;
|
|
241
|
+
border-top: 1px solid black;
|
|
242
|
+
border-bottom: 1px solid black;
|
|
243
|
+
width: 100%;
|
|
244
|
+
max-width: 100%;
|
|
245
|
+
margin: 0 auto;
|
|
246
|
+
margin-bottom: 10px;
|
|
247
|
+
& th, & td {
|
|
248
|
+
font-weight: normal;
|
|
249
|
+
}
|
|
250
|
+
& thead::after { /* 用来做三线表中间的横线 */
|
|
251
|
+
content: '';
|
|
252
|
+
display: block;
|
|
253
|
+
position: absolute;
|
|
254
|
+
border-top: 1px solid #00000060;
|
|
255
|
+
width: 100%;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
b, strong { /* 加粗按黑体处理 */
|
|
260
|
+
font-weight: normal;
|
|
261
|
+
font-family: '黑体', 'SimHei', '华文黑体', 'STHeiti';
|
|
262
|
+
}
|
|
263
|
+
`,
|
|
264
|
+
preParseMarkdown: async (md: string): Promise<string> => {
|
|
265
|
+
// 作者
|
|
266
|
+
md = md.replace(/#author# (.*)/mg, '<div class="author">$1</div>')
|
|
267
|
+
// 单位
|
|
268
|
+
md = md.replace(/#school# (.*)/mg, '<div class="school">$1</div>')
|
|
269
|
+
// 关键词
|
|
270
|
+
md = md.replace(/#keywords# (.*)/mg, '<div class="keywords">$1</div>')
|
|
271
|
+
// 摘要
|
|
272
|
+
md = md.replace(/#abstract# (.*)/mg, '<div class="abstract">$1</div>')
|
|
273
|
+
// 返回处理后的字符串
|
|
274
|
+
return md
|
|
275
|
+
},
|
|
276
|
+
preParseHTML: async (html: string): Promise<string> => {
|
|
277
|
+
// 把包裹图片的 p 标签去掉
|
|
278
|
+
html = html.replace(/<p><img (.*?)><\/p>/g, '<img $1>')
|
|
279
|
+
// 返回处理后的字符串
|
|
280
|
+
return html
|
|
281
|
+
},
|
|
282
|
+
script: () => {},
|
|
283
|
+
pdfOptions: {}
|
|
122
284
|
}
|
package/theme/theme.ts
CHANGED
|
@@ -1,45 +1,33 @@
|
|
|
1
1
|
import type { PDFOptions } from 'puppeteer-core'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
/**
|
|
5
|
-
* @param args 命令行参数
|
|
6
|
-
* @param cwd 当前工作目录
|
|
7
|
-
*/
|
|
8
|
-
constructor(args?: string[], cwd?: string) {
|
|
9
|
-
this.args = args ?? []
|
|
10
|
-
this.cwd = cwd ?? process.cwd()
|
|
11
|
-
}
|
|
12
|
-
/** 命令行参数 */
|
|
13
|
-
args: string[]
|
|
14
|
-
/** 当前工作目录 */
|
|
15
|
-
cwd: string
|
|
3
|
+
export interface MarkdownPaperTheme {
|
|
16
4
|
/**
|
|
17
5
|
* css 样式
|
|
18
6
|
* 不含 \<style>\</style>
|
|
19
7
|
*/
|
|
20
|
-
|
|
8
|
+
css: string
|
|
21
9
|
/**
|
|
22
10
|
* 预处理 markdown 字符串
|
|
23
11
|
* 用于转换自定义标签等
|
|
24
12
|
* @param md markdown 字符串
|
|
25
13
|
* @returns 转换后的 markdown 字符串
|
|
26
14
|
*/
|
|
27
|
-
|
|
15
|
+
preParseMarkdown(md: string): Promise<string>
|
|
28
16
|
/**
|
|
29
17
|
* 预处理 html 字符串
|
|
30
18
|
* 将在保存 html 文件前调用
|
|
31
19
|
* @param html html 字符串
|
|
32
20
|
* @returns 转换后的 html 字符串
|
|
33
21
|
*/
|
|
34
|
-
|
|
22
|
+
preParseHTML(html: string): Promise<string>
|
|
35
23
|
/**
|
|
36
24
|
* 在网页中要执行的函数
|
|
37
25
|
* 将在保存 html 文件后调用
|
|
38
26
|
*/
|
|
39
|
-
|
|
27
|
+
script(): void
|
|
40
28
|
/**
|
|
41
29
|
* PDF 参数
|
|
42
30
|
* 无需设置路径
|
|
43
31
|
*/
|
|
44
|
-
|
|
32
|
+
pdfOptions: PDFOptions
|
|
45
33
|
}
|