markdown-paper 2.1.1 → 2.2.1

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
@@ -1,33 +1,97 @@
1
- 以心理学报的格式从 Markdown 生成 PDF / HTML / DOCX 文件
1
+ 以心理学报等学术论文的格式从 Markdown 生成 PDF / HTML / DOCX 文件
2
+
3
+ # 使用方法
4
+
5
+ ## 1 用 Markdown 撰写论文
6
+
7
+ ```markdown
8
+ # 中文标题
9
+
10
+ #author# 作者信息
11
+
12
+ #school# 单位信息
13
+
14
+ #abstract# 摘要内容
15
+
16
+ #keywords# 关键词内容
17
+
18
+ ## 1 一级标题
19
+
20
+ ### 1.1 二级标题
21
+
22
+ #### 1.1.1 三级标题
23
+
24
+ 正文
25
+
26
+ ![](图片路径)
27
+
28
+ > 图片标题
29
+
30
+ > 表格标题
31
+
32
+ | 表头1 | 表头2 |
33
+ | :---: | :---: |
34
+ | 内容1 | 内容2 |
35
+
36
+ ##### 参考文献
37
+
38
+ - 文献1
39
+ - 文献2
40
+ - 文献3
41
+
42
+ ##### 附录
43
+ ```
44
+
45
+ > 详见下方的模板说明
46
+
47
+ ## 2 安装 `Bun`
48
+
49
+ `Bun` 是一个现代的 `JavaScript` / `TypeScript` 运行环境, 本项目基于 `Bun` 环境开发; 请在官网 [bun.sh](https://bun.sh) 下载并安装 `Bun`, 也可以直接使用 `npm install -g bun` 安装
50
+
51
+ ## 3 安装 `MarkdownPaper`
2
52
 
3
- ## 使用方法
4
53
  ```bash
5
- # 安装 Bun (参见 bun.sh)
6
- ...
7
- # 安装 MarkdownPaper
8
54
  bun add -g markdown-paper
9
- # 使用 (第一个参数为源文件相对路径)
10
- mdp <path> [--option]
11
- # 查看使用方法
12
- mdp
13
55
  ```
14
56
 
15
57
  > 如果您安装过旧版本的 `MarkdownPaper` (小于 `2.0.0`), 请先卸载旧版本再安装新版本
16
58
 
59
+ ## 4 生成论文
60
+
61
+ 运行 `mdp` 命令以使用 `MarkdownPaper` 命令行工具从 `Markdown` 文件生成论文
62
+
63
+ 如果您不熟悉命令行工具, 可以尝试我的另一个项目 [EasyPaper](https://github.com/LeafYeeXYZ/EasyPaper), 它基于 `MarkdownPaper` 并提供了图形界面
64
+
65
+ ```bash
66
+ # 查看帮助信息
67
+ mdp
68
+ # 从当前工作目录下的 example.md 文件生成 PDF 文件
69
+ mdp example.md
70
+ # 从当前工作目录下的 example.md 文件生成 PDF 文件并输出到指定路径
71
+ mdp example.md --out=D:/example.pdf
72
+ # 从当前工作目录下的 example.md 文件生成 PDF 和 HTML 文件
73
+ mdp example.md --outputHTML
74
+ # 从当前工作目录下的 example.md 文件生成 PDF 和 DOCX 文件
75
+ pip install pdf2docx # 仅生成 DOCX 文件时需要安装 pdf2docx, 只需安装一次
76
+ mdp example.md --outputDOCX
77
+ ```
78
+
17
79
  | 参数 | 说明 |
18
80
  | :---: | :---: |
19
81
  | `--out=xxx` | 输出文件相对路径<br>默认为源文件路径的同名 `PDF` 文件 |
20
82
  | `--theme=xxx` | 论文模板, 默认为 `aps` (`Acta Psychologica Sinica`)<br>暂时没有其他模板, 欢迎贡献 |
21
83
  | `--outputHTML` | 输出 `HTML` 文件, 默认不输出 |
22
- | `--outputDOCX` | 输出 `DOCX` 文件, 默认不输出<br>**须先执行 `pip install pdf2docx` 安装依赖**<br>使用时推荐开启 `--hideFooter` 参数 |
84
+ | `--outputDOCX` | 输出 `DOCX` 文件, 默认不输出<br>**须先通过 `python` 安装依赖 `pdf2docx`**<br>使用时推荐开启 `--hideFooter` 参数 |
23
85
 
24
- ## 模板说明
86
+ # 模板说明
25
87
  `/theme/theme.ts` 中的 `Theme` 抽象类定义了模板的样式, 按照类似于 `aps` 文件夹的结构可自定义模板; 模板可以提供自定义功能
26
88
 
27
- 模板制作完成后, 在 `/lib/main.ts` 中导入并添加到 `class Options -> constructor -> case '--theme':` 中即可使用
89
+ 模板制作完成后, 在 `/lib/main.ts` 中导入并添加到 `class Options -> constructor -> case '--theme':` 中, 并在下方添加使用文档即可
90
+
91
+ 推荐所有主题的文档和编写格式都尽量与 `aps` 主题保持一致
28
92
 
29
- ### APS 模板
30
- #### 额外命令行参数
93
+ ## APS 模板
94
+ ### 额外命令行参数
31
95
  | 参数 | 说明 |
32
96
  | :---: | :---: |
33
97
  | `--showTitle` | 在页眉显示文件名, 默认不显示 |
@@ -35,7 +99,7 @@ mdp
35
99
  | `--zhPunctuation` | 将正文中的英文标点符号替换为中文标点符号, 默认不替换<br>仅替换 `PDF` 和 `DOCX` 文件 |
36
100
  | `--enPunctuation` | 将正文中的中文标点符号替换为英文标点符号, 默认不替换<br>仅替换 `PDF` 和 `DOCX` 文件 |
37
101
 
38
- #### 编写格式
102
+ ### 编写格式
39
103
  ```markdown
40
104
  # 中文标题
41
105
  #author# 作者信息
@@ -66,7 +130,8 @@ mdp
66
130
  ##### 附录
67
131
  ```
68
132
 
69
- ## 更新日志
133
+ # 更新日志
134
+ - `2.2.0` (2024-08-26): 优化项目导入导出内容, 优化文档
70
135
  - `2.1.1` (2024-07-12): 修复字体错误
71
136
  - `2.1.0` (2024-06-26): 支持 `MacOS` 系统, 改为在线加载字体
72
137
  - `2.0.0` (2024-06-20): 重构代码, 完善模板功能
package/bin/mdp.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { Options, renderMarkdown } from '../lib/main.ts'
3
+ import { MarkdownPaperOptions, renderMarkdown } from '../lib/main.ts'
4
4
  /** 命令行参数 */
5
5
  const args = process.argv.slice(2)
6
6
  /** 当前工作目录 */
@@ -14,19 +14,19 @@ void async function main(args: string[], cwd: string) {
14
14
  try {
15
15
  // 如果没有参数, 显示帮助信息
16
16
  if (args.length === 0) {
17
- console.log(`\n使用方法:\n${Options.format}\n`)
17
+ console.log(`\n使用方法:\n${MarkdownPaperOptions.format}\n`)
18
18
  process.exit(0)
19
19
  }
20
20
  // 解析参数
21
21
  console.log('\n开始生成\n')
22
- const options = new Options(args, cwd)
22
+ const options = new MarkdownPaperOptions(args, cwd)
23
23
  // 渲染 markdown
24
24
  await renderMarkdown(options)
25
25
  console.log('生成成功\n')
26
26
  }
27
27
  catch (e) {
28
28
  if (e instanceof SyntaxError) {
29
- console.error(`参数错误, 正确格式:\n${Options.format}\n`)
29
+ console.error(`参数错误, 正确格式:\n${MarkdownPaperOptions.format}\n`)
30
30
  } else if (e instanceof Error) {
31
31
  console.error(`错误:\n${e.message}\n`)
32
32
  }
package/lib/docx.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  declare var self: Worker
2
2
 
3
3
  import { $ } from 'bun'
4
- import { Options } from './main'
5
4
 
6
5
  self.onmessage = async (e) => {
7
- const options = e.data as Options
8
- await $`pdf2docx convert ${options.out}`
6
+ const pdfPath = e.data as string
7
+ await $`pdf2docx convert ${pdfPath}`
9
8
  .then(() => {
10
9
  postMessage('success')
11
10
  })
package/lib/main.ts CHANGED
@@ -4,10 +4,11 @@ import fs from 'node:fs/promises'
4
4
  import path from 'node:path'
5
5
  import { readFileSync } from 'node:fs'
6
6
  import { APS } from '../theme/aps/aps'
7
- import { Theme } from '../theme/theme'
7
+ import { MarkdownPaperTheme } from '../theme/theme'
8
+ import type { PDFOptions } from 'puppeteer'
8
9
 
9
10
  /** 应用参数 */
10
- export class Options {
11
+ class MarkdownPaperOptions {
11
12
  /** markdown 文件绝对路径 */
12
13
  src: string
13
14
  /** pdf 文件绝对路径 */
@@ -17,7 +18,7 @@ export class Options {
17
18
  /** 是否输出 docx */
18
19
  outputDOCX: boolean
19
20
  /** 样式 */
20
- theme: Theme
21
+ theme: MarkdownPaperTheme
21
22
  /** 正确格式 */
22
23
  static format = `mdp <markdown> [--options]
23
24
 
@@ -26,7 +27,6 @@ export class Options {
26
27
  --theme=<name>: 论文模板 (默认为 APS)
27
28
  --outputHTML: 输出 html 文件 (默认不输出)
28
29
  --outputDOCX: 输出 docx 文件 (默认不输出)
29
- --browser=<path>: 自定义浏览器路径 (默认为 Edge)
30
30
 
31
31
  模板的自定义参数见模板说明`
32
32
  /**
@@ -90,79 +90,100 @@ export class Options {
90
90
  /**
91
91
  * 渲染 markdown
92
92
  * @param options 参数
93
+ * @param rawMarkdown 如果传入此参数, 则不再读取文件
93
94
  */
94
- export async function renderMarkdown(options: Options): Promise<void> {
95
+ async function renderMarkdown(
96
+ options: MarkdownPaperOptions,
97
+ rawMarkdown?: string,
98
+ ): Promise<void> {
95
99
  // 读取 markdown 文件
96
- let md = await fs.readFile(options.src, { encoding: 'utf-8' })
100
+ const raw = rawMarkdown ?? await fs.readFile(options.src, { encoding: 'utf-8' })
97
101
  // 预处理 markdown
98
- md = options.theme.preParseMarkdown(md)
102
+ const md = await options.theme.preParseMarkdown(raw)
99
103
  // 转换 markdown 为 html
100
- const html = await marked(md)
101
- // 创建网页文件
102
- const title = path.basename(options.src).replace('.md', '')
103
- let web = `
104
+ let html = `
104
105
  <!DOCTYPE html>
105
106
  <html lang="zh-CN">
106
107
  <head>
107
108
  <meta charset="UTF-8">
108
- <title>${title}</title>
109
+ <title>${path.basename(options.src).replace('.md', '')}</title>
109
110
  <style>${options.theme.css}</style>
110
111
  </head>
111
112
  <body>
112
- ${html}
113
+ ${await marked(md)}
113
114
  </body>
114
115
  </html>
115
116
  `
116
117
  // 预处理 html
117
- web = options.theme.preParseHTML(web)
118
+ html = await options.theme.preParseHTML(html)
118
119
  // 保存 html 文件
119
- options.outputHTML && await fs.writeFile(options.out.replace('.pdf', '.html'), web)
120
- // 把图片转换为 base64
121
- web = web.replace(/<img src="(.+?)"/g, (match, p1) => {
122
- if (p1.startsWith('http')) return match
123
- try {
124
- const url = path.resolve(path.dirname(options.src), decodeURI(p1))
125
- const data = readFileSync(url).toString('base64')
126
- return `<img src="data:image/${path.extname(p1).replace('.', '')};base64,${data}"`
127
- } catch (_) {
128
- console.error(`图片 ${p1} 不存在`)
129
- return match
130
- }
131
- })
132
- // 创建浏览器
120
+ options.outputHTML && await fs.writeFile(options.out.replace('.pdf', '.html'), html)
121
+ // 保存 pdf 文件
122
+ await htmlToPdf(
123
+ html.replace(/<img src="(.+?)"/g, (match, p1) => {
124
+ if (p1.startsWith('http')) return match
125
+ try {
126
+ const url = path.resolve(path.dirname(options.src), decodeURI(p1))
127
+ const data = readFileSync(url).toString('base64')
128
+ return `<img src="data:image/${path.extname(p1).replace('.', '')};base64,${data}"`
129
+ } catch (_) {
130
+ console.error(`图片 ${p1} 不存在`)
131
+ return match
132
+ }
133
+ }),
134
+ options.out,
135
+ options.theme.pdfOptions
136
+ )
137
+ // 保存 docx 文件
138
+ options.outputDOCX && await pdfToDocx(options.out)
139
+ }
140
+
141
+ /**
142
+ * 把 html 转换为 pdf
143
+ * @param html html 字符串, 图片为 base64
144
+ * @param dist pdf 文件绝对路径
145
+ * @param options pdf 参数, 无需设置路径
146
+ */
147
+ async function htmlToPdf(html: string, dist: string, options: PDFOptions): Promise<void> {
133
148
  const browser = await puppeteer.launch()
134
- // 创建页面
135
149
  const page = await browser.newPage()
136
- // 设置页面内容
137
- await page.setContent(web)
138
- // 执行脚本
139
- await page.evaluate(options.theme.script)
140
- // 生成 pdf
141
- await page.pdf({ path: options.out, ...options.theme.pdfOptions })
142
- // 关闭浏览器
150
+ await page.setContent(html)
151
+ await page.pdf({ path: dist, ...options })
143
152
  await browser.close()
144
- // 保存 docx 文件
145
- if (options.outputDOCX) {
146
- await new Promise<void>((resolve, reject) => {
147
- const worker = new Worker(new URL('docx.ts', import.meta.url).href)
148
- worker.onmessage = (e) => {
149
- switch (e.data) {
150
- case 'success': {
151
- console.log('')
152
- resolve()
153
- break
154
- }
155
- case 'error': {
156
- reject(Error('生成 docx 文件失败'))
157
- break
158
- }
159
- default: {
160
- reject(Error('调用 Python 时发生未知错误'))
161
- break
162
- }
153
+ }
154
+
155
+ /**
156
+ * pdf 转换为 docx
157
+ * @param pdfPath pdf 文件绝对路径
158
+ */
159
+ function pdfToDocx(pdfPath: string): Promise<void> {
160
+ return new Promise<void>((resolve, reject) => {
161
+ const worker = new Worker(new URL('docx.ts', import.meta.url).href)
162
+ worker.onmessage = (e) => {
163
+ switch (e.data) {
164
+ case 'success': {
165
+ console.log('')
166
+ resolve()
167
+ break
168
+ }
169
+ case 'error': {
170
+ reject(Error('生成 docx 文件失败'))
171
+ break
172
+ }
173
+ default: {
174
+ reject(Error('调用 python 时发生未知错误'))
175
+ break
163
176
  }
164
177
  }
165
- worker.postMessage(options)
166
- })
167
- }
178
+ }
179
+ worker.postMessage(pdfPath)
180
+ })
181
+ }
182
+
183
+ export {
184
+ MarkdownPaperTheme,
185
+ MarkdownPaperOptions,
186
+ renderMarkdown,
187
+ htmlToPdf,
188
+ pdfToDocx,
168
189
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "markdown-paper",
3
3
  "type": "module",
4
- "version": "2.1.1",
4
+ "version": "2.2.1",
5
5
  "author": {
6
6
  "name": "LeafYeeXYZ",
7
7
  "email": "xiaoyezi@leafyee.xyz"
@@ -10,8 +10,9 @@
10
10
  "scripts": {
11
11
  "mdp": "bun ./bin/mdp.ts",
12
12
  "pub": "npm publish",
13
- "try": "bun mdp ./论文.md --out=./demo/论文 --outputHTML"
13
+ "try": "bun mdp ./demo/论文.md --outputHTML --outputDOCX"
14
14
  },
15
+ "main": "./lib/main.ts",
15
16
  "bin": {
16
17
  "mdp": "./bin/mdp.ts"
17
18
  },
package/theme/aps/aps.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { Theme } from '../theme'
1
+ import { MarkdownPaperTheme } from '../theme'
2
2
  import { readFileSync } from 'node:fs'
3
3
  import { resolve } from 'node:path'
4
4
  import type { PDFOptions } from 'puppeteer-core'
5
5
 
6
- export class APS extends Theme {
6
+ export class APS extends MarkdownPaperTheme {
7
7
 
8
8
  css: string
9
- preParseMarkdown: (md: string) => string
10
- preParseHTML: (html: string) => string
9
+ preParseMarkdown: (md: string) => Promise<string>
10
+ preParseHTML: (html: string) => Promise<string>
11
11
  script: () => void
12
12
  pdfOptions: PDFOptions
13
13
 
@@ -34,7 +34,7 @@ export class APS extends Theme {
34
34
  this.css = readFileSync(resolve(import.meta.dir, 'aps.css'), 'utf-8')
35
35
 
36
36
  // preParseMarkdown
37
- this.preParseMarkdown = (md: string): string => {
37
+ this.preParseMarkdown = async (md: string): Promise<string> => {
38
38
  // 作者
39
39
  md = md.replace(/#author# (.*)/mg, '<div class="author">$1</div>')
40
40
  // 单位
@@ -48,17 +48,10 @@ export class APS extends Theme {
48
48
  }
49
49
 
50
50
  // preParseHTML
51
- this.preParseHTML = (html: string): string => {
51
+ this.preParseHTML = async (html: string): Promise<string> => {
52
52
  // 把包裹图片的 p 标签去掉
53
53
  html = html.replace(/<p><img (.*?)><\/p>/g, '<img $1>')
54
- // 加载字体
55
- // html = html.replace(/<\/head>/, `
56
- // <link rel="preconnect" href="https://fonts.googleapis.com">
57
- // <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
58
- // <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@200..900&display=swap" rel="stylesheet">
59
- // <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200..900&display=swap" rel="stylesheet">
60
- // <link href="https://fonts.googleapis.com/css2?family=Noto+Serif:wght@200..900&display=swap" rel="stylesheet">
61
- // `)
54
+ // 返回处理后的字符串
62
55
  return html
63
56
  }
64
57
 
@@ -125,6 +118,5 @@ export class APS extends Theme {
125
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>`,
126
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>`,
127
120
  }
128
-
129
121
  }
130
122
  }
package/theme/theme.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PDFOptions } from 'puppeteer-core'
2
2
 
3
- export abstract class Theme {
3
+ export abstract class MarkdownPaperTheme {
4
4
  /**
5
5
  * @param args 命令行参数
6
6
  * @param cwd 当前工作目录
@@ -24,14 +24,14 @@ export abstract class Theme {
24
24
  * @param md markdown 字符串
25
25
  * @returns 转换后的 markdown 字符串
26
26
  */
27
- abstract preParseMarkdown(md: string): string
27
+ abstract preParseMarkdown(md: string): Promise<string>
28
28
  /**
29
29
  * 预处理 html 字符串
30
30
  * 将在保存 html 文件前调用
31
31
  * @param html html 字符串
32
32
  * @returns 转换后的 html 字符串
33
33
  */
34
- abstract preParseHTML(html: string): string
34
+ abstract preParseHTML(html: string): Promise<string>
35
35
  /**
36
36
  * 在网页中要执行的函数
37
37
  * 将在保存 html 文件后调用
package/theme/aps/aps.md DELETED
@@ -1,41 +0,0 @@
1
- # APS 模板
2
- `Acta Psychologica Sinica`, 心理学报
3
-
4
- ## 额外命令行参数
5
- | 参数 | 说明 |
6
- | :---: | :---: |
7
- | `--showTitle` | 在页眉显示文件名, 默认不显示 |
8
- | `--hideFooter` | 隐藏页码, 默认显示 |
9
- | `--zhPunctuation` | 将正文中的英文标点符号替换为中文标点符号, 默认不替换<br>仅替换 `PDF` 和 `DOCX` 文件 |
10
- | `--enPunctuation` | 将正文中的中文标点符号替换为英文标点符号, 默认不替换<br>仅替换 `PDF` 和 `DOCX` 文件 |
11
-
12
- ## 编写格式
13
- ```markdown
14
- # 中文标题
15
- #author# 作者信息
16
- #school# 单位信息
17
- #abstract# 摘要内容
18
- #keywords# 关键词内容
19
-
20
- ## 1 一级标题
21
- ### 1.1 二级标题
22
- #### 1.1.1 三级标题
23
- 正文
24
-
25
- ![](图片路径)
26
-
27
- > 图片标题
28
-
29
- > 表格标题
30
-
31
- | 表头1 | 表头2 |
32
- | :---: | :---: |
33
- | 内容1 | 内容2 |
34
-
35
- ##### 参考文献
36
- - 文献1
37
- - 文献2
38
- - 文献3
39
-
40
- ##### 附录
41
- ```
package/theme/bnu/bnu.css DELETED
File without changes
package/theme/bnu/bnu.md DELETED
File without changes
package/theme/bnu/bnu.ts DELETED
File without changes