@open-guji/markdown-pdf 0.1.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/LICENSE +201 -0
- package/README.md +112 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +110 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +60 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +10 -0
- package/dist/options.js.map +1 -0
- package/dist/pdf.d.ts +15 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +65 -0
- package/dist/pdf.js.map +1 -0
- package/dist/print-html.d.ts +12 -0
- package/dist/print-html.d.ts.map +1 -0
- package/dist/print-html.js +46 -0
- package/dist/print-html.js.map +1 -0
- package/dist/styles.generated.d.ts +3 -0
- package/dist/styles.generated.d.ts.map +1 -0
- package/dist/styles.generated.js +7 -0
- package/dist/styles.generated.js.map +1 -0
- package/package.json +74 -0
- package/styles/print.css +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @open-guji/markdown-pdf
|
|
2
|
+
|
|
3
|
+
古籍 Markdown 的 **PDF 生成器**。
|
|
4
|
+
|
|
5
|
+
> **状态:预览。** 可打印 HTML 的生成已经稳定;Chromium 后端能出 PDF,
|
|
6
|
+
> 但竖排的断行、双行夹注的精确排布仍在调。
|
|
7
|
+
|
|
8
|
+
规范见仓库 [`spec/`](../../spec),本包实现规范 **0.1.0**。
|
|
9
|
+
|
|
10
|
+
## 分层
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
源文 ──[@open-guji/markdown-web]──> HTML 片段
|
|
14
|
+
│
|
|
15
|
+
gujiToPrintHtml │ + @page 规则 + 竖排样式 + 可选 Paged.js
|
|
16
|
+
▼
|
|
17
|
+
可打印 HTML ──[Chromium]──> PDF
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
第一层是**纯函数**,没有外部依赖,浏览器里也能跑,输出可以交给任何排版引擎;
|
|
21
|
+
第二层才需要 playwright。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i @open-guji/markdown-pdf
|
|
27
|
+
|
|
28
|
+
# 要真的出 PDF
|
|
29
|
+
npm i -D playwright && npx playwright install chromium
|
|
30
|
+
|
|
31
|
+
# 想要更好的分页控制(页眉页脚、跨页避断),可选
|
|
32
|
+
npm i pagedjs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 用法
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import { gujiToPdf, gujiToPrintHtml } from '@open-guji/markdown-pdf'
|
|
39
|
+
|
|
40
|
+
// 只要可打印 HTML
|
|
41
|
+
const html = gujiToPrintHtml(source, { pageSize: 'A5', vertical: true })
|
|
42
|
+
|
|
43
|
+
// 出 PDF
|
|
44
|
+
const bytes = await gujiToPdf(source, { title: '明太祖實錄', banks: true })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 命令行
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
guji-pdf 實錄.gmd # → 實錄.pdf
|
|
51
|
+
guji-pdf 實錄.gmd --html # → 實錄.html,不调用排版后端
|
|
52
|
+
guji-pdf 實錄.gmd --size A4 --banks --title 明太祖實錄
|
|
53
|
+
guji-pdf --check # 检查排版后端是否可用
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 选项
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
interface PdfOptions {
|
|
60
|
+
pageSize?: 'A4' | 'A5' | 'B5' | 'Letter' | string // 默认 'A5'
|
|
61
|
+
pageMargin?: string // CSS margin,默认 '20mm 18mm'
|
|
62
|
+
fontSize?: string // 默认 '11pt'
|
|
63
|
+
lineHeight?: number // 默认 1.85
|
|
64
|
+
vertical?: boolean // 竖排,默认 true
|
|
65
|
+
banks?: boolean // 版框与界行,默认 false
|
|
66
|
+
paged?: 'auto' | 'always' | 'never' // 是否内联 Paged.js,默认 'auto'
|
|
67
|
+
title?: string
|
|
68
|
+
extraCss?: string
|
|
69
|
+
executablePath?: string // 自带的 Chromium 路径
|
|
70
|
+
browserArgs?: string[]
|
|
71
|
+
timeout?: number // 默认 30000
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
外加 [web 包的全部选项](../web/README.md#选项)。
|
|
76
|
+
|
|
77
|
+
## 为什么用浏览器排版
|
|
78
|
+
|
|
79
|
+
竖排(`writing-mode: vertical-rl`)、专名线画在字的左侧
|
|
80
|
+
(`text-underline-position: left`)、抬头的逻辑方向外移——
|
|
81
|
+
这些正好都是 CSS 书写模式已经解决的问题,而 web 包已经把语义标好了。
|
|
82
|
+
让 PDF 复用同一份 HTML 与 CSS,意味着**网页和印本天然一致**,
|
|
83
|
+
不必维护两套排版规则。
|
|
84
|
+
|
|
85
|
+
评估过的其他后端:
|
|
86
|
+
|
|
87
|
+
| 方案 | 结论 |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| **Chromium + Paged.js**(当前) | 竖排与逻辑属性支持最好;分页控制靠 Paged.js 补齐 |
|
|
90
|
+
| Typst | 排版质量高、无需浏览器,但竖排 CJK 支持尚不完整 |
|
|
91
|
+
| XeLaTeX + xeCJK | 竖排成熟,但要另写一套从 mdast 到 TeX 的映射,且与网页样式两套维护 |
|
|
92
|
+
| 纯 pdfkit / PDF 库 | 需要自己实现断行与避头尾,工作量最大 |
|
|
93
|
+
|
|
94
|
+
如果将来 Typst 的竖排 CJK 成熟,会作为第二后端加入——
|
|
95
|
+
`gujiToPrintHtml` 之前的那一段是共用的。
|
|
96
|
+
|
|
97
|
+
## 已知限制
|
|
98
|
+
|
|
99
|
+
* 竖排下**双行夹注**用 CSS 多列近似,注文很长时会溢出列高。
|
|
100
|
+
* 没装 `pagedjs` 时由 Chromium 自行分页,页码与跨页避断的控制较弱。
|
|
101
|
+
* 版框与界行(`banks`)是用渐变画的,行距变化时需要同步调 `--guji-print-line-height`。
|
|
102
|
+
|
|
103
|
+
## 路线图
|
|
104
|
+
|
|
105
|
+
- [ ] 双行夹注的精确断行(在 Paged.js 的 chunker 阶段做)
|
|
106
|
+
- [ ] 书口、鱼尾、版心页码
|
|
107
|
+
- [ ] Typst 后端
|
|
108
|
+
- [ ] 字体子集化,减小产物体积
|
|
109
|
+
|
|
110
|
+
## 许可证
|
|
111
|
+
|
|
112
|
+
Apache-2.0
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,iDAAiD"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** guji-pdf —— 把古籍 Markdown 转成 PDF 或可打印 HTML。 */
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, extname } from 'node:path';
|
|
5
|
+
import { checkBackend, gujiToPdf, gujiToPrintHtml } from './index.js';
|
|
6
|
+
const USAGE = `用法:guji-pdf <输入.gmd> [选项]
|
|
7
|
+
|
|
8
|
+
选项:
|
|
9
|
+
-o, --out <文件> 输出路径(默认与输入同名,改扩展名)
|
|
10
|
+
--html 只生成可打印 HTML,不调用排版后端
|
|
11
|
+
--size <纸张> A4 | A5 | B5 | Letter,默认 A5
|
|
12
|
+
--margin <边距> CSS margin 语法,默认 "20mm 18mm"
|
|
13
|
+
--font-size <值> 正文字号,默认 11pt
|
|
14
|
+
--line-height <值> 正文行距倍数,默认 1.85
|
|
15
|
+
--horizontal 横排(默认竖排)
|
|
16
|
+
--banks 加版框与界行
|
|
17
|
+
--title <标题> 文档标题
|
|
18
|
+
--check 检查排版后端是否可用
|
|
19
|
+
-h, --help 显示本帮助
|
|
20
|
+
`;
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const options = {};
|
|
23
|
+
let input;
|
|
24
|
+
let out;
|
|
25
|
+
let html = false;
|
|
26
|
+
let check = false;
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
const arg = argv[i];
|
|
29
|
+
const next = () => {
|
|
30
|
+
const value = argv[++i];
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
throw new Error(`选项 ${arg} 缺少参数`);
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
switch (arg) {
|
|
36
|
+
case '-h':
|
|
37
|
+
case '--help':
|
|
38
|
+
process.stdout.write(USAGE);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
break;
|
|
41
|
+
case '--check':
|
|
42
|
+
check = true;
|
|
43
|
+
break;
|
|
44
|
+
case '--html':
|
|
45
|
+
html = true;
|
|
46
|
+
break;
|
|
47
|
+
case '-o':
|
|
48
|
+
case '--out':
|
|
49
|
+
out = next();
|
|
50
|
+
break;
|
|
51
|
+
case '--size':
|
|
52
|
+
options.pageSize = next();
|
|
53
|
+
break;
|
|
54
|
+
case '--margin':
|
|
55
|
+
options.pageMargin = next();
|
|
56
|
+
break;
|
|
57
|
+
case '--font-size':
|
|
58
|
+
options.fontSize = next();
|
|
59
|
+
break;
|
|
60
|
+
case '--line-height':
|
|
61
|
+
options.lineHeight = Number(next());
|
|
62
|
+
break;
|
|
63
|
+
case '--horizontal':
|
|
64
|
+
options.vertical = false;
|
|
65
|
+
break;
|
|
66
|
+
case '--banks':
|
|
67
|
+
options.banks = true;
|
|
68
|
+
break;
|
|
69
|
+
case '--title':
|
|
70
|
+
options.title = next();
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
if (arg.startsWith('-'))
|
|
74
|
+
throw new Error(`未知选项 ${arg}`);
|
|
75
|
+
input = arg;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { input, out, html, check, options };
|
|
79
|
+
}
|
|
80
|
+
async function main() {
|
|
81
|
+
const { input, out, html, check, options } = parseArgs(process.argv.slice(2));
|
|
82
|
+
if (check) {
|
|
83
|
+
const backend = await checkBackend();
|
|
84
|
+
process.stdout.write(`playwright: ${backend.playwright ? '可用' : '未安装'}\n` +
|
|
85
|
+
`pagedjs: ${backend.pagedjs ? '可用' : '未安装(将由 Chromium 自行分页)'}\n`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!input) {
|
|
89
|
+
process.stderr.write(USAGE);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const source = await readFile(input, 'utf8');
|
|
94
|
+
const stem = basename(input, extname(input));
|
|
95
|
+
options.title ??= stem;
|
|
96
|
+
if (html) {
|
|
97
|
+
const target = out ?? `${stem}.html`;
|
|
98
|
+
await writeFile(target, gujiToPrintHtml(source, options));
|
|
99
|
+
process.stdout.write(`${target}\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const target = out ?? `${stem}.pdf`;
|
|
103
|
+
await writeFile(target, await gujiToPdf(source, options));
|
|
104
|
+
process.stdout.write(`${target}\n`);
|
|
105
|
+
}
|
|
106
|
+
main().catch((error) => {
|
|
107
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@open-guji/markdown-pdf`
|
|
3
|
+
*
|
|
4
|
+
* 古籍 Markdown 的 PDF 生成器。分两层:
|
|
5
|
+
*
|
|
6
|
+
* - {@link gujiToPrintHtml} —— 纯函数,源文 → 带 `@page` 规则的可打印 HTML;
|
|
7
|
+
* - {@link gujiToPdf} —— 把上面的 HTML 交给 Chromium 出 PDF(需要 playwright)。
|
|
8
|
+
*
|
|
9
|
+
* 规范见仓库 `spec/` 目录,排版决策见本包 README。
|
|
10
|
+
*/
|
|
11
|
+
export { gujiToPrintHtml, loadPagedPolyfill } from './print-html.js';
|
|
12
|
+
export { gujiToPdf, checkBackend } from './pdf.js';
|
|
13
|
+
export { DEFAULT_PRINT_OPTIONS, type PageSize, type PrintOptions, type PdfOptions } from './options.js';
|
|
14
|
+
export { printCss } from './styles.generated.js';
|
|
15
|
+
/** 本包实现的规范版本。 */
|
|
16
|
+
export declare const SPEC_VERSION = "0.1.0";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,qBAAqB,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AACvG,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,iBAAiB;AACjB,eAAO,MAAM,YAAY,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@open-guji/markdown-pdf`
|
|
3
|
+
*
|
|
4
|
+
* 古籍 Markdown 的 PDF 生成器。分两层:
|
|
5
|
+
*
|
|
6
|
+
* - {@link gujiToPrintHtml} —— 纯函数,源文 → 带 `@page` 规则的可打印 HTML;
|
|
7
|
+
* - {@link gujiToPdf} —— 把上面的 HTML 交给 Chromium 出 PDF(需要 playwright)。
|
|
8
|
+
*
|
|
9
|
+
* 规范见仓库 `spec/` 目录,排版决策见本包 README。
|
|
10
|
+
*/
|
|
11
|
+
export { gujiToPrintHtml, loadPagedPolyfill } from './print-html.js';
|
|
12
|
+
export { gujiToPdf, checkBackend } from './pdf.js';
|
|
13
|
+
export { DEFAULT_PRINT_OPTIONS } from './options.js';
|
|
14
|
+
export { printCss } from './styles.generated.js';
|
|
15
|
+
/** 本包实现的规范版本。 */
|
|
16
|
+
export const SPEC_VERSION = '0.1.0';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,qBAAqB,EAAqD,MAAM,cAAc,CAAA;AACvG,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,iBAAiB;AACjB,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DocumentOptions } from '@open-guji/markdown-web';
|
|
2
|
+
/** 纸张。字符串会原样写进 `@page { size: … }`。 */
|
|
3
|
+
export type PageSize = 'A4' | 'A5' | 'B5' | 'Letter' | (string & {});
|
|
4
|
+
export interface PrintOptions extends DocumentOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 纸张尺寸。
|
|
7
|
+
* @default 'A5'
|
|
8
|
+
*/
|
|
9
|
+
pageSize?: PageSize;
|
|
10
|
+
/**
|
|
11
|
+
* 页边距,CSS `margin` 语法。
|
|
12
|
+
* @default '20mm 18mm'
|
|
13
|
+
*/
|
|
14
|
+
pageMargin?: string;
|
|
15
|
+
/**
|
|
16
|
+
* 正文字号。
|
|
17
|
+
* @default '11pt'
|
|
18
|
+
*/
|
|
19
|
+
fontSize?: string;
|
|
20
|
+
/**
|
|
21
|
+
* 正文行距(无单位倍数)。
|
|
22
|
+
* @default 1.85
|
|
23
|
+
*/
|
|
24
|
+
lineHeight?: number;
|
|
25
|
+
/**
|
|
26
|
+
* 加版框与界行(线装书版式)。
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
banks?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 是否内联 Paged.js。
|
|
32
|
+
* `auto` 表示能解析到 `pagedjs` 依赖时才内联,否则交给排版引擎自己分页。
|
|
33
|
+
* @default 'auto'
|
|
34
|
+
*/
|
|
35
|
+
paged?: 'auto' | 'always' | 'never';
|
|
36
|
+
}
|
|
37
|
+
export interface PdfOptions extends PrintOptions {
|
|
38
|
+
/**
|
|
39
|
+
* 渲染超时(毫秒)。
|
|
40
|
+
* @default 30000
|
|
41
|
+
*/
|
|
42
|
+
timeout?: number;
|
|
43
|
+
/**
|
|
44
|
+
* 浏览器可执行文件路径。留空则用 playwright 自带的 Chromium;
|
|
45
|
+
* CI 里常有一份预装的浏览器,指过去可以省掉一次下载。
|
|
46
|
+
*/
|
|
47
|
+
executablePath?: string;
|
|
48
|
+
/** 传给 Chromium 的额外命令行参数。 */
|
|
49
|
+
browserArgs?: string[];
|
|
50
|
+
}
|
|
51
|
+
export declare const DEFAULT_PRINT_OPTIONS: {
|
|
52
|
+
readonly pageSize: "A5";
|
|
53
|
+
readonly pageMargin: "20mm 18mm";
|
|
54
|
+
readonly fontSize: "11pt";
|
|
55
|
+
readonly lineHeight: 1.85;
|
|
56
|
+
readonly banks: false;
|
|
57
|
+
readonly paged: "auto";
|
|
58
|
+
readonly vertical: true;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAE9D,uCAAuC;AACvC,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEpE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;CACpC;AAED,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,eAAO,MAAM,qBAAqB;;;;;;;;CAQQ,CAAA"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAsDA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,WAAW;IACvB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,MAAM;IACb,QAAQ,EAAE,IAAI;CAC0B,CAAA"}
|
package/dist/pdf.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chromium 排版后端。
|
|
3
|
+
*
|
|
4
|
+
* PDF 复用网页渲染的结果,理由见 README「为什么用浏览器排版」。
|
|
5
|
+
* playwright 是可选依赖,只有真的要出 PDF 时才需要。
|
|
6
|
+
*/
|
|
7
|
+
import { type PdfOptions } from './options.js';
|
|
8
|
+
/** 源文 → PDF 字节流。 */
|
|
9
|
+
export declare function gujiToPdf(source: string, options?: PdfOptions): Promise<Uint8Array>;
|
|
10
|
+
/** 当前环境是否具备出 PDF 的条件。 */
|
|
11
|
+
export declare function checkBackend(): Promise<{
|
|
12
|
+
playwright: boolean;
|
|
13
|
+
pagedjs: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=pdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../src/pdf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAuCrE,oBAAoB;AACpB,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAwB7F;AAED,yBAAyB;AACzB,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAC5C,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAC,CASD"}
|
package/dist/pdf.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chromium 排版后端。
|
|
3
|
+
*
|
|
4
|
+
* PDF 复用网页渲染的结果,理由见 README「为什么用浏览器排版」。
|
|
5
|
+
* playwright 是可选依赖,只有真的要出 PDF 时才需要。
|
|
6
|
+
*/
|
|
7
|
+
import { gujiToPrintHtml, loadPagedPolyfill } from './print-html.js';
|
|
8
|
+
import { DEFAULT_PRINT_OPTIONS } from './options.js';
|
|
9
|
+
async function loadChromium() {
|
|
10
|
+
try {
|
|
11
|
+
const mod = (await import('playwright'));
|
|
12
|
+
return mod.chromium;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error('生成 PDF 需要 playwright:\n' +
|
|
16
|
+
' npm i -D playwright && npx playwright install chromium\n' +
|
|
17
|
+
'若只需要可打印的 HTML,请改用 gujiToPrintHtml()。');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** 等待 Paged.js 完成分页;没有 Paged.js 时立即返回。 */
|
|
21
|
+
const WAIT_FOR_PAGED = `new Promise((resolve) => {
|
|
22
|
+
if (!window.PagedPolyfill) return resolve('native')
|
|
23
|
+
if (window.PagedPolyfill.rendered) return resolve('paged')
|
|
24
|
+
window.addEventListener('pagedjs:rendered', () => resolve('paged'), { once: true })
|
|
25
|
+
setTimeout(() => resolve('timeout'), 20000)
|
|
26
|
+
})`;
|
|
27
|
+
/** 源文 → PDF 字节流。 */
|
|
28
|
+
export async function gujiToPdf(source, options = {}) {
|
|
29
|
+
const o = { ...DEFAULT_PRINT_OPTIONS, timeout: 30_000, ...options };
|
|
30
|
+
const html = gujiToPrintHtml(source, o);
|
|
31
|
+
const chromium = await loadChromium();
|
|
32
|
+
const browser = await chromium.launch({
|
|
33
|
+
...(o.executablePath ? { executablePath: o.executablePath } : {}),
|
|
34
|
+
...(o.browserArgs ? { args: o.browserArgs } : {})
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
const page = await browser.newPage();
|
|
38
|
+
await page.setContent(html, { waitUntil: 'load', timeout: o.timeout });
|
|
39
|
+
await page.evaluate(WAIT_FOR_PAGED);
|
|
40
|
+
await page.emulateMedia({ media: 'print' });
|
|
41
|
+
// Paged.js 已经把内容切成了固定尺寸的页盒,此时再让 Chromium 分页会重复分页;
|
|
42
|
+
// 没有 Paged.js 时则交给 Chromium 自己按 @page 规则分页。
|
|
43
|
+
return await page.pdf({
|
|
44
|
+
printBackground: true,
|
|
45
|
+
preferCSSPageSize: true,
|
|
46
|
+
displayHeaderFooter: false
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await browser.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** 当前环境是否具备出 PDF 的条件。 */
|
|
54
|
+
export async function checkBackend() {
|
|
55
|
+
let playwright = false;
|
|
56
|
+
try {
|
|
57
|
+
await import('playwright');
|
|
58
|
+
playwright = true;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
playwright = false;
|
|
62
|
+
}
|
|
63
|
+
return { playwright, pagedjs: loadPagedPolyfill() !== undefined };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=pdf.js.map
|
package/dist/pdf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.js","sourceRoot":"","sources":["../src/pdf.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EAAE,qBAAqB,EAAmB,MAAM,cAAc,CAAA;AAkBrE,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAA0C,CAAA;QACjF,OAAO,GAAG,CAAC,QAAQ,CAAA;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yBAAyB;YACvB,4DAA4D;YAC5D,sCAAsC,CACzC,CAAA;IACH,CAAC;AACH,CAAC;AAED,0CAA0C;AAC1C,MAAM,cAAc,GAAG;;;;;GAKpB,CAAA;AAEH,oBAAoB;AACpB,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,UAAsB,EAAE;IACtE,MAAM,CAAC,GAAG,EAAE,GAAG,qBAAqB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAAA;IACnE,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QACpC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClD,CAAC,CAAA;IAEF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QACtE,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAA;QACnC,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;QAC3C,kDAAkD;QAClD,4CAA4C;QAC5C,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC;YACpB,eAAe,EAAE,IAAI;YACrB,iBAAiB,EAAE,IAAI;YACvB,mBAAmB,EAAE,KAAK;SAC3B,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;AACH,CAAC;AAED,yBAAyB;AACzB,MAAM,CAAC,KAAK,UAAU,YAAY;IAIhC,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;QAC1B,UAAU,GAAG,IAAI,CAAA;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,GAAG,KAAK,CAAA;IACpB,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,CAAA;AACnE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 源文 → 可打印 HTML。
|
|
3
|
+
*
|
|
4
|
+
* 这一步是纯函数、无外部依赖,浏览器里也能跑;
|
|
5
|
+
* 真正生成 PDF 的排版后端只是把它喂给一个排版引擎。
|
|
6
|
+
*/
|
|
7
|
+
import { type PrintOptions } from './options.js';
|
|
8
|
+
/** 解析 `pagedjs` 的 polyfill 脚本;没装则返回 undefined。 */
|
|
9
|
+
export declare function loadPagedPolyfill(): string | undefined;
|
|
10
|
+
/** 源文 → 一张带 `@page` 规则、可以直接打印的 HTML 页面。 */
|
|
11
|
+
export declare function gujiToPrintHtml(source: string, options?: PrintOptions): string;
|
|
12
|
+
//# sourceMappingURL=print-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-html.d.ts","sourceRoot":"","sources":["../src/print-html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAGvE,kDAAkD;AAClD,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAQtD;AAYD,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAiBlF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 源文 → 可打印 HTML。
|
|
3
|
+
*
|
|
4
|
+
* 这一步是纯函数、无外部依赖,浏览器里也能跑;
|
|
5
|
+
* 真正生成 PDF 的排版后端只是把它喂给一个排版引擎。
|
|
6
|
+
*/
|
|
7
|
+
import { gujiToHtml, renderDocument } from '@open-guji/markdown-web';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { DEFAULT_PRINT_OPTIONS } from './options.js';
|
|
11
|
+
import { printCss } from './styles.generated.js';
|
|
12
|
+
/** 解析 `pagedjs` 的 polyfill 脚本;没装则返回 undefined。 */
|
|
13
|
+
export function loadPagedPolyfill() {
|
|
14
|
+
try {
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const entry = require.resolve('pagedjs/dist/paged.polyfill.js');
|
|
17
|
+
return readFileSync(entry, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function pageVariables(options) {
|
|
24
|
+
return `:root {
|
|
25
|
+
--guji-page-size: ${options.pageSize};
|
|
26
|
+
--guji-page-margin: ${options.pageMargin};
|
|
27
|
+
--guji-print-font-size: ${options.fontSize};
|
|
28
|
+
--guji-print-line-height: ${options.lineHeight};
|
|
29
|
+
}`;
|
|
30
|
+
}
|
|
31
|
+
/** 源文 → 一张带 `@page` 规则、可以直接打印的 HTML 页面。 */
|
|
32
|
+
export function gujiToPrintHtml(source, options = {}) {
|
|
33
|
+
const o = { ...DEFAULT_PRINT_OPTIONS, ...options };
|
|
34
|
+
const polyfill = o.paged === 'never' ? undefined : loadPagedPolyfill();
|
|
35
|
+
if (o.paged === 'always' && !polyfill) {
|
|
36
|
+
throw new Error("paged: 'always' 需要安装 pagedjs(npm i pagedjs),或改用 paged: 'auto'。");
|
|
37
|
+
}
|
|
38
|
+
return renderDocument(gujiToHtml(source, o), {
|
|
39
|
+
...o,
|
|
40
|
+
className: [o.banks ? 'guji-banks' : '', o.className ?? ''].filter(Boolean).join(' '),
|
|
41
|
+
extraCss: [pageVariables(o), printCss, o.extraCss ?? ''].filter(Boolean).join('\n'),
|
|
42
|
+
extraHead: [o.extraHead ?? '', polyfill ? `<script>\n${polyfill}\n</script>` : ''].
|
|
43
|
+
filter(Boolean).join('\n')
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=print-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-html.js","sourceRoot":"","sources":["../src/print-html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,qBAAqB,EAAqB,MAAM,cAAc,CAAA;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,kDAAkD;AAClD,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAA;QAC/D,OAAO,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OACkC;IACvD,OAAO;sBACa,OAAO,CAAC,QAAQ;wBACd,OAAO,CAAC,UAAU;4BACd,OAAO,CAAC,QAAQ;8BACd,OAAO,CAAC,UAAU;EAC9C,CAAA;AACF,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,UAAwB,EAAE;IACxE,MAAM,CAAC,GAAG,EAAE,GAAG,qBAAqB,EAAE,GAAG,OAAO,EAAE,CAAA;IAElD,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAA;IACtE,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAA;IACH,CAAC;IAED,OAAO,cAAc,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE;QAC3C,GAAG,CAAC;QACJ,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACrF,QAAQ,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACnF,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,aAAa,QAAQ,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;YAChF,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;KAC7B,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const printCss = "/*!\n * print.css \u2014 \u53E4\u7C4D Markdown \u7684\u5206\u9875\u6392\u7248\u6837\u5F0F\n * @open-guji/markdown-pdf \u00B7 Apache-2.0\n *\n * \u4E0E guji.css / guji-vertical.css \u53E0\u52A0\u4F7F\u7528\u3002\u8FD9\u91CC\u53EA\u7BA1\"\u9875\"\u7684\u4E8B\uFF1A\n * \u9875\u9762\u5C3A\u5BF8\u3001\u9875\u8FB9\u8DDD\u3001\u7248\u5FC3\u3001\u4E66\u53E3\u4E0E\u5206\u9875\u63A7\u5236\u3002\n */\n\n@page {\n size: var(--guji-page-size, A5);\n margin: var(--guji-page-margin, 20mm 18mm);\n\n @bottom-center {\n content: counter(page);\n font-family: var(--guji-font);\n font-size: 9pt;\n color: #666;\n }\n}\n\n@page :first {\n @bottom-center {\n content: none;\n }\n}\n\n.guji-doc {\n /* \u6253\u5370\u65F6\u62AC\u5934\u7559\u767D\u6309\u5B9E\u9645\u5B57\u53F7\u6298\u7B97\uFF0C\u4E0D\u518D\u4F9D\u8D56\u89C6\u53E3 */\n --guji-raise-unit: 1em;\n font-size: var(--guji-print-font-size, 11pt);\n line-height: var(--guji-print-line-height, 1.85);\n}\n\n/* \u6807\u9898\u4E0D\u843D\u5728\u9875\u672B */\n.guji-doc h1,\n.guji-doc h2,\n.guji-doc h3 {\n break-after: avoid-page;\n break-inside: avoid;\n}\n\n/* \u62AC\u5934\u884C\u4E0D\u88AB\u5207\u5F00 */\n.guji-tt,\n.guji-tt-block {\n break-inside: avoid;\n}\n\n/* \u5939\u6CE8\u8DDF\u968F\u6B63\u6587\uFF0C\u4E0D\u5355\u72EC\u6210\u9875 */\n.guji-jz {\n break-inside: avoid;\n}\n\n.guji-doc p {\n orphans: 2;\n widows: 2;\n}\n\n/* ------------------------------------------------------ \u7EBF\u88C5\u4E66\u7248\u5F0F */\n\n/*\n * \u53EF\u9009\uFF1A\u52A0\u8FB9\u6846\u4E0E\u754C\u884C\uFF0C\u63A5\u8FD1\u96D5\u7248\u7684\u7248\u6846\u3002\n * \u7528\u6CD5\uFF1A<article class=\"guji-doc guji-vertical guji-banks\">\n */\n.guji-banks {\n --guji-frame-color: #3a3a3a;\n --guji-frame-width: 0.6pt;\n padding: 4mm;\n border: calc(var(--guji-frame-width) * 3) double var(--guji-frame-color);\n}\n\n/* \u754C\u884C\uFF1A\u7528\u91CD\u590D\u7EBF\u6027\u6E10\u53D8\u753B\u51FA\u6BCF\u5217\u4E4B\u95F4\u7684\u7EC6\u7EBF */\n.guji-banks.guji-vertical {\n background-image: repeating-linear-gradient(\n to left,\n transparent,\n transparent calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),\n var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),\n var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em)\n );\n}\n";
|
|
2
|
+
export declare const STYLE_FILES: readonly ["print.css"];
|
|
3
|
+
//# sourceMappingURL=styles.generated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.generated.d.ts","sourceRoot":"","sources":["../src/styles.generated.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,u1EAA0qD,CAAA;AAE/rD,eAAO,MAAM,WAAW,wBAEd,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// 本文件由 scripts/build-styles.mjs 从 styles/*.css 生成,请勿手改。
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
export const printCss = "/*!\n * print.css — 古籍 Markdown 的分页排版样式\n * @open-guji/markdown-pdf · Apache-2.0\n *\n * 与 guji.css / guji-vertical.css 叠加使用。这里只管\"页\"的事:\n * 页面尺寸、页边距、版心、书口与分页控制。\n */\n\n@page {\n size: var(--guji-page-size, A5);\n margin: var(--guji-page-margin, 20mm 18mm);\n\n @bottom-center {\n content: counter(page);\n font-family: var(--guji-font);\n font-size: 9pt;\n color: #666;\n }\n}\n\n@page :first {\n @bottom-center {\n content: none;\n }\n}\n\n.guji-doc {\n /* 打印时抬头留白按实际字号折算,不再依赖视口 */\n --guji-raise-unit: 1em;\n font-size: var(--guji-print-font-size, 11pt);\n line-height: var(--guji-print-line-height, 1.85);\n}\n\n/* 标题不落在页末 */\n.guji-doc h1,\n.guji-doc h2,\n.guji-doc h3 {\n break-after: avoid-page;\n break-inside: avoid;\n}\n\n/* 抬头行不被切开 */\n.guji-tt,\n.guji-tt-block {\n break-inside: avoid;\n}\n\n/* 夹注跟随正文,不单独成页 */\n.guji-jz {\n break-inside: avoid;\n}\n\n.guji-doc p {\n orphans: 2;\n widows: 2;\n}\n\n/* ------------------------------------------------------ 线装书版式 */\n\n/*\n * 可选:加边框与界行,接近雕版的版框。\n * 用法:<article class=\"guji-doc guji-vertical guji-banks\">\n */\n.guji-banks {\n --guji-frame-color: #3a3a3a;\n --guji-frame-width: 0.6pt;\n padding: 4mm;\n border: calc(var(--guji-frame-width) * 3) double var(--guji-frame-color);\n}\n\n/* 界行:用重复线性渐变画出每列之间的细线 */\n.guji-banks.guji-vertical {\n background-image: repeating-linear-gradient(\n to left,\n transparent,\n transparent calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),\n var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),\n var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em)\n );\n}\n";
|
|
4
|
+
export const STYLE_FILES = [
|
|
5
|
+
"print.css"
|
|
6
|
+
];
|
|
7
|
+
//# sourceMappingURL=styles.generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.generated.js","sourceRoot":"","sources":["../src/styles.generated.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,oBAAoB;AAEpB,MAAM,CAAC,MAAM,QAAQ,GAAG,uqDAAuqD,CAAA;AAE/rD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;CACH,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-guji/markdown-pdf",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "古籍 Markdown 的 PDF 生成器:可打印 HTML 与 Chromium 排版后端",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"markdown",
|
|
7
|
+
"pdf",
|
|
8
|
+
"古籍",
|
|
9
|
+
"guji",
|
|
10
|
+
"chinese",
|
|
11
|
+
"classics",
|
|
12
|
+
"typesetting",
|
|
13
|
+
"pagedjs"
|
|
14
|
+
],
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/open-guji/guji-markdown.git",
|
|
19
|
+
"directory": "packages/pdf"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/open-guji/guji-markdown/tree/main/packages/pdf",
|
|
22
|
+
"bugs": "https://github.com/open-guji/guji-markdown/issues",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.18"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./styles/print.css": "./styles/print.css",
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"guji-pdf": "./dist/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"styles",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@open-guji/markdown-core": "^0.1.0",
|
|
46
|
+
"@open-guji/markdown-web": "^0.1.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"pagedjs": "^0.4.3",
|
|
50
|
+
"playwright": "^1.49.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"playwright": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"pagedjs": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"vitest": "^3.2.4",
|
|
63
|
+
"@types/node": "^22.10.0"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build:styles": "node scripts/build-styles.mjs",
|
|
70
|
+
"build": "npm run build:styles && tsc -p tsconfig.json",
|
|
71
|
+
"typecheck": "npm run build:styles && tsc -p tsconfig.json --noEmit",
|
|
72
|
+
"test": "npm run build:styles && vitest run"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/styles/print.css
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* print.css — 古籍 Markdown 的分页排版样式
|
|
3
|
+
* @open-guji/markdown-pdf · Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* 与 guji.css / guji-vertical.css 叠加使用。这里只管"页"的事:
|
|
6
|
+
* 页面尺寸、页边距、版心、书口与分页控制。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
@page {
|
|
10
|
+
size: var(--guji-page-size, A5);
|
|
11
|
+
margin: var(--guji-page-margin, 20mm 18mm);
|
|
12
|
+
|
|
13
|
+
@bottom-center {
|
|
14
|
+
content: counter(page);
|
|
15
|
+
font-family: var(--guji-font);
|
|
16
|
+
font-size: 9pt;
|
|
17
|
+
color: #666;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@page :first {
|
|
22
|
+
@bottom-center {
|
|
23
|
+
content: none;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.guji-doc {
|
|
28
|
+
/* 打印时抬头留白按实际字号折算,不再依赖视口 */
|
|
29
|
+
--guji-raise-unit: 1em;
|
|
30
|
+
font-size: var(--guji-print-font-size, 11pt);
|
|
31
|
+
line-height: var(--guji-print-line-height, 1.85);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* 标题不落在页末 */
|
|
35
|
+
.guji-doc h1,
|
|
36
|
+
.guji-doc h2,
|
|
37
|
+
.guji-doc h3 {
|
|
38
|
+
break-after: avoid-page;
|
|
39
|
+
break-inside: avoid;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* 抬头行不被切开 */
|
|
43
|
+
.guji-tt,
|
|
44
|
+
.guji-tt-block {
|
|
45
|
+
break-inside: avoid;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* 夹注跟随正文,不单独成页 */
|
|
49
|
+
.guji-jz {
|
|
50
|
+
break-inside: avoid;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.guji-doc p {
|
|
54
|
+
orphans: 2;
|
|
55
|
+
widows: 2;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ------------------------------------------------------ 线装书版式 */
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* 可选:加边框与界行,接近雕版的版框。
|
|
62
|
+
* 用法:<article class="guji-doc guji-vertical guji-banks">
|
|
63
|
+
*/
|
|
64
|
+
.guji-banks {
|
|
65
|
+
--guji-frame-color: #3a3a3a;
|
|
66
|
+
--guji-frame-width: 0.6pt;
|
|
67
|
+
padding: 4mm;
|
|
68
|
+
border: calc(var(--guji-frame-width) * 3) double var(--guji-frame-color);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* 界行:用重复线性渐变画出每列之间的细线 */
|
|
72
|
+
.guji-banks.guji-vertical {
|
|
73
|
+
background-image: repeating-linear-gradient(
|
|
74
|
+
to left,
|
|
75
|
+
transparent,
|
|
76
|
+
transparent calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),
|
|
77
|
+
var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em - var(--guji-frame-width)),
|
|
78
|
+
var(--guji-frame-color) calc(var(--guji-print-line-height, 1.85) * 1em)
|
|
79
|
+
);
|
|
80
|
+
}
|