@v1hz/md2docx 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -46
- package/config/config.json +5 -0
- package/config/config.schema.json +26 -0
- package/config/lua/limit-image-size.lua +93 -0
- package/config/style-config.json +0 -1
- package/config/style-config.schema.json +1 -4
- package/dist/index.js +245 -98
- package/package.json +1 -1
- /package/config/{style.json → style-raw.json} +0 -0
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
| 标题层级归一化 | 将最浅标题归一化为 H1,并修复标题层级跳跃 |
|
|
24
24
|
| 标题编号 | 生成 `1`、`1.1`、`1.1.1` 等编号,并可剥离常见的已有中英文编号 |
|
|
25
25
|
| 表格与图片编号 | 自动生成“表 1”“图 1:标题”等题注 |
|
|
26
|
+
| 图片尺寸限制 | 按自然尺寸等比缩小超过最大宽度或高度的图片 |
|
|
26
27
|
| Mermaid 图表 | 使用 beautiful-mermaid 和 resvg-wasm 将 Mermaid 渲染为高 DPI PNG |
|
|
27
28
|
| Word 样式 | 支持受控语义化配置和完整底层样式,也可从现有 DOCX 提取样式 |
|
|
28
29
|
| Markdown 格式化 | 可只运行预处理流水线,输出格式化后的 Markdown |
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
前置依赖:
|
|
38
39
|
|
|
39
40
|
- [Node.js 22.12+](https://nodejs.org/)
|
|
40
|
-
- [Pandoc](https://pandoc.org/installing.html),并确保 `pandoc` 可通过 `PATH`
|
|
41
|
+
- [Pandoc](https://pandoc.org/installing.html),并确保 `pandoc` 可通过 `PATH` 调用;图片尺寸限制需要 Pandoc 3.1.13+
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
44
|
npm install -g @v1hz/md2docx
|
|
@@ -58,26 +59,27 @@ npx @v1hz/md2docx report.md
|
|
|
58
59
|
md2docx report.md
|
|
59
60
|
|
|
60
61
|
# 使用任何转换选项时,输入必须通过 --file 指定
|
|
61
|
-
md2docx --file report.md --output output/report.docx
|
|
62
|
+
md2docx --file report.md --output output/report.docx
|
|
62
63
|
|
|
63
|
-
#
|
|
64
|
-
md2docx -f report.md -c config.json -
|
|
64
|
+
# 使用自定义配置、底层样式和语义化样式配置
|
|
65
|
+
md2docx -f report.md -c config.json --style-raw style-raw.json --style-config style-config.json
|
|
65
66
|
|
|
66
67
|
# 只执行 Markdown 预处理
|
|
67
68
|
md2docx format -f report.md
|
|
68
69
|
|
|
69
|
-
#
|
|
70
|
+
# 导出内置默认配置、底层样式和语义化样式配置
|
|
70
71
|
md2docx export config
|
|
71
|
-
md2docx export style
|
|
72
|
+
md2docx export style-raw
|
|
73
|
+
md2docx export style-config
|
|
72
74
|
|
|
73
75
|
# 从现有 DOCX 提取样式
|
|
74
|
-
md2docx export style -f template.docx
|
|
76
|
+
md2docx export style-raw -f template.docx
|
|
75
77
|
|
|
76
78
|
# 删除 ~/.md2docx 中的中间文件和缓存
|
|
77
79
|
md2docx clean
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
所有写文件命令默认覆盖已有输出。
|
|
81
83
|
|
|
82
84
|
## CLI 参考
|
|
83
85
|
|
|
@@ -86,29 +88,30 @@ md2docx <markdown>
|
|
|
86
88
|
md2docx -f <markdown> [转换选项]
|
|
87
89
|
md2docx format -f <markdown> [选项]
|
|
88
90
|
md2docx export config [选项]
|
|
89
|
-
md2docx export style [选项]
|
|
91
|
+
md2docx export style-raw [选项]
|
|
92
|
+
md2docx export style-config [选项]
|
|
90
93
|
md2docx clean
|
|
91
94
|
```
|
|
92
95
|
|
|
93
96
|
### 转换
|
|
94
97
|
|
|
95
|
-
| 参数
|
|
96
|
-
|
|
|
97
|
-
| `<markdown>`
|
|
98
|
-
| `-f, --file <path>`
|
|
99
|
-
| `-c, --config <path>`
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
| `-h, --help`
|
|
104
|
-
| `-v, --version`
|
|
98
|
+
| 参数 | 说明 |
|
|
99
|
+
| ----------------------- | ---------------------------------------- |
|
|
100
|
+
| `<markdown>` | 位置参数;仅在没有其他转换选项时允许使用 |
|
|
101
|
+
| `-f, --file <path>` | Markdown 输入文件 |
|
|
102
|
+
| `-c, --config <path>` | 自定义配置 JSON |
|
|
103
|
+
| `--style-raw <path>` | 完整底层 Word 样式 JSON |
|
|
104
|
+
| `--style-config <path>` | 受控语义化样式配置 JSON |
|
|
105
|
+
| `-o, --output <path>` | DOCX 输出路径 |
|
|
106
|
+
| `-h, --help` | 显示帮助 |
|
|
107
|
+
| `-v, --version` | 显示版本号 |
|
|
105
108
|
|
|
106
|
-
位置参数不能和 `--file`、`--config`、`--style`、`--
|
|
109
|
+
位置参数不能和 `--file`、`--config`、`--style-raw`、`--style-config` 或 `--output` 混用。例如:
|
|
107
110
|
|
|
108
111
|
```bash
|
|
109
112
|
md2docx report.md # 正确
|
|
110
|
-
md2docx report.md
|
|
111
|
-
md2docx -f report.md
|
|
113
|
+
md2docx report.md -o report.docx # 错误
|
|
114
|
+
md2docx -f report.md -o report.docx # 正确
|
|
112
115
|
```
|
|
113
116
|
|
|
114
117
|
### format
|
|
@@ -120,16 +123,16 @@ md2docx -f report.md --force # 正确
|
|
|
120
123
|
| `-f, --file <path>` | 必填,Markdown 输入文件 |
|
|
121
124
|
| `-c, --config <path>` | 自定义配置 JSON |
|
|
122
125
|
| `-o, --output <path>` | 输出 Markdown,默认 `<文件名>_formatted.md` |
|
|
123
|
-
| `--force` | 覆盖已有输出 |
|
|
124
126
|
|
|
125
127
|
### export
|
|
126
128
|
|
|
127
129
|
```bash
|
|
128
|
-
md2docx export config [-o config.json]
|
|
129
|
-
md2docx export style [-f template.docx] [-o style.json]
|
|
130
|
+
md2docx export config [-o config.json]
|
|
131
|
+
md2docx export style-raw [-f template.docx] [-o style-raw.json]
|
|
132
|
+
md2docx export style-config [-o style-config.json]
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
`export config`
|
|
135
|
+
`export config` 导出内置 Markdown 处理配置。`export style-raw` 不带 `--file` 时导出内置底层 Word 样式;指定 DOCX 时从该文档提取底层样式。`export style-config` 导出默认语义化样式配置。
|
|
133
136
|
|
|
134
137
|
### clean
|
|
135
138
|
|
|
@@ -153,8 +156,9 @@ md2docx report.md → ./report.docx
|
|
|
153
156
|
md2docx -f report.md → ./report.docx
|
|
154
157
|
md2docx format -f report.md → ./report_formatted.md
|
|
155
158
|
md2docx export config → ./config.json
|
|
156
|
-
md2docx export style → ./style.json
|
|
157
|
-
md2docx export style -f template.docx → ./template_style.json
|
|
159
|
+
md2docx export style-raw → ./style-raw.json
|
|
160
|
+
md2docx export style-raw -f template.docx → ./template_style-raw.json
|
|
161
|
+
md2docx export style-config → ./style-config.json
|
|
158
162
|
```
|
|
159
163
|
|
|
160
164
|
## 中间文件与缓存
|
|
@@ -170,8 +174,9 @@ md2docx export style -f template.docx → ./template_style.json
|
|
|
170
174
|
├── resources/
|
|
171
175
|
│ ├── config.json
|
|
172
176
|
│ ├── style-config.json
|
|
173
|
-
│ ├── style.json
|
|
174
|
-
│
|
|
177
|
+
│ ├── style-raw.json
|
|
178
|
+
│ ├── add-inline-code.lua
|
|
179
|
+
│ └── limit-image-size.lua
|
|
175
180
|
└── style/
|
|
176
181
|
└── <样式内容哈希>.docx
|
|
177
182
|
```
|
|
@@ -210,19 +215,23 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
|
|
|
210
215
|
| `renderMermaid.enabled` | 将 Mermaid 渲染为 PNG | `true` |
|
|
211
216
|
| `renderMermaid.theme` | beautiful-mermaid 主题 | `"tokyo-night-light"` |
|
|
212
217
|
| `renderMermaid.density` | PNG 输出 DPI,最小值 72 | `200` |
|
|
218
|
+
| `imageSize.enabled` | 等比缩小超过尺寸限制的图片 | `true` |
|
|
219
|
+
| `imageSize.maxWidthCm` | DOCX 图片最大宽度(厘米) | `15.5` |
|
|
220
|
+
| `imageSize.maxHeightCm` | DOCX 图片最大高度(厘米) | `22` |
|
|
213
221
|
| `removeThematicBreaks.enabled` | 移除 `---`、`***`、`___` 等分隔符行 | `true` |
|
|
214
222
|
|
|
223
|
+
图片尺寸限制在 Pandoc 生成 DOCX 前通过 Lua filter 应用。程序读取图片像素尺寸和 DPI,仅缩小超限图片,不会放大小图;宽度和高度使用同一缩放比例。Markdown 中已经显式设置 `width` 或 `height` 的图片视为用户覆盖,不应用全局限制。单张图片无法读取尺寸时会输出警告并继续转换。
|
|
224
|
+
|
|
215
225
|
## 样式定制
|
|
216
226
|
|
|
217
227
|
普通用户推荐使用受控的语义化样式配置,只修改程序明确开放的高频选项。其余颜色、尺寸、对齐方式、间距和 Word 样式继承关系继续由内置预设管理。
|
|
218
228
|
|
|
219
|
-
仓库中的 `config/style-config.json` 是可直接复制和修改的默认配置,`config/style-config.schema.json`
|
|
229
|
+
仓库中的 `config/style-config.json` 是可直接复制和修改的默认配置,`config/style-config.schema.json` 用于编辑器提示和校验。完整底层 Word 样式位于 `config/style-raw.json`。
|
|
220
230
|
|
|
221
231
|
```json
|
|
222
232
|
{
|
|
223
233
|
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
224
234
|
"schemaVersion": 1,
|
|
225
|
-
"preset": "default",
|
|
226
235
|
"options": {
|
|
227
236
|
"body": {
|
|
228
237
|
"firstLineIndent": false
|
|
@@ -245,18 +254,27 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
|
|
|
245
254
|
将配置保存为 `style-config.json` 后使用:
|
|
246
255
|
|
|
247
256
|
```bash
|
|
248
|
-
md2docx -f report.md -
|
|
257
|
+
md2docx -f report.md --style-config style-config.json
|
|
249
258
|
```
|
|
250
259
|
|
|
251
|
-
|
|
260
|
+
字段缺失表示继承底层样式;`true` 表示显式启用完整效果;`false` 表示显式关闭。当前只开放正文首行缩进、一级标题另起一页、行内代码背景和代码块外框。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
|
|
261
|
+
|
|
262
|
+
转换时按以下规则选择输入:
|
|
252
263
|
|
|
253
|
-
|
|
264
|
+
| 参数 | 行为 |
|
|
265
|
+
| ------------------- | ----------------------------------- |
|
|
266
|
+
| 都不指定 | 默认 raw + 默认 config |
|
|
267
|
+
| 仅 `--style-raw` | 直接使用用户 raw,不应用默认 config |
|
|
268
|
+
| 仅 `--style-config` | 用户 config 应用到默认 raw |
|
|
269
|
+
| 两者都指定 | 用户 config 应用到用户 raw |
|
|
270
|
+
|
|
271
|
+
最终有效的底层样式用于生成 reference DOCX,并通过 Pandoc 的 `--reference-doc` 应用。`--style-raw` 和 `--style-config` 类型固定,不能互换。
|
|
254
272
|
|
|
255
273
|
### 从现有 DOCX 提取
|
|
256
274
|
|
|
257
275
|
```bash
|
|
258
|
-
md2docx export style -f template.docx
|
|
259
|
-
md2docx -f report.md -
|
|
276
|
+
md2docx export style-raw -f template.docx
|
|
277
|
+
md2docx -f report.md --style-raw template_style-raw.json
|
|
260
278
|
```
|
|
261
279
|
|
|
262
280
|
### 高级手动维护
|
|
@@ -323,7 +341,7 @@ numberPictures()
|
|
|
323
341
|
↓
|
|
324
342
|
生成或复用 reference DOCX
|
|
325
343
|
↓
|
|
326
|
-
Pandoc + Lua filter
|
|
344
|
+
Pandoc + 行内代码/图片尺寸 Lua filter
|
|
327
345
|
↓
|
|
328
346
|
DOCX
|
|
329
347
|
```
|
|
@@ -386,14 +404,6 @@ pandoc --version
|
|
|
386
404
|
|
|
387
405
|
如果命令不存在,请从 [Pandoc 官方安装页](https://pandoc.org/installing.html) 安装,并重新打开终端使 `PATH` 生效。
|
|
388
406
|
|
|
389
|
-
### 输出文件已存在
|
|
390
|
-
|
|
391
|
-
默认不会覆盖文件。确认目标可以覆盖后使用:
|
|
392
|
-
|
|
393
|
-
```bash
|
|
394
|
-
md2docx -f report.md --force
|
|
395
|
-
```
|
|
396
|
-
|
|
397
407
|
### 从其他目录转换时图片缺失
|
|
398
408
|
|
|
399
409
|
相对图片路径应以原始 Markdown 文件为基准:
|
package/config/config.json
CHANGED
|
@@ -117,6 +117,31 @@
|
|
|
117
117
|
},
|
|
118
118
|
"required": ["enabled", "theme", "density"]
|
|
119
119
|
},
|
|
120
|
+
"imageSize": {
|
|
121
|
+
"description": "DOCX 图片尺寸限制",
|
|
122
|
+
"type": "object",
|
|
123
|
+
"additionalProperties": false,
|
|
124
|
+
"properties": {
|
|
125
|
+
"enabled": {
|
|
126
|
+
"description": "按图片自然尺寸等比缩小超限图片",
|
|
127
|
+
"type": "boolean",
|
|
128
|
+
"default": true
|
|
129
|
+
},
|
|
130
|
+
"maxWidthCm": {
|
|
131
|
+
"description": "图片最大宽度(厘米)",
|
|
132
|
+
"type": "number",
|
|
133
|
+
"exclusiveMinimum": 0,
|
|
134
|
+
"default": 15.5
|
|
135
|
+
},
|
|
136
|
+
"maxHeightCm": {
|
|
137
|
+
"description": "图片最大高度(厘米)",
|
|
138
|
+
"type": "number",
|
|
139
|
+
"exclusiveMinimum": 0,
|
|
140
|
+
"default": 22
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"required": ["enabled", "maxWidthCm", "maxHeightCm"]
|
|
144
|
+
},
|
|
120
145
|
"detectTitle": {
|
|
121
146
|
"description": "文档标题",
|
|
122
147
|
"type": "object",
|
|
@@ -162,6 +187,7 @@
|
|
|
162
187
|
"normalizeHeadings",
|
|
163
188
|
"numberHeadings",
|
|
164
189
|
"renderMermaid",
|
|
190
|
+
"imageSize",
|
|
165
191
|
"detectTitle",
|
|
166
192
|
"removeThematicBreaks"
|
|
167
193
|
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
local max_width_cm
|
|
2
|
+
local max_height_cm
|
|
3
|
+
local size_cache = {}
|
|
4
|
+
local warned = {}
|
|
5
|
+
|
|
6
|
+
local function warn_once(source, message)
|
|
7
|
+
if warned[source] then
|
|
8
|
+
return
|
|
9
|
+
end
|
|
10
|
+
warned[source] = true
|
|
11
|
+
local text = "无法限制图片尺寸 " .. source .. ":" .. message
|
|
12
|
+
if pandoc.log and pandoc.log.warn then
|
|
13
|
+
pandoc.log.warn(text)
|
|
14
|
+
else
|
|
15
|
+
io.stderr:write("[WARNING] " .. text .. "\n")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
local function metadata_number(meta, key)
|
|
20
|
+
local value = meta[key]
|
|
21
|
+
if value == nil then
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
return tonumber(pandoc.utils.stringify(value))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
local function read_config(meta)
|
|
28
|
+
max_width_cm = metadata_number(meta, "md2docx-image-max-width-cm")
|
|
29
|
+
max_height_cm = metadata_number(meta, "md2docx-image-max-height-cm")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
local function image_size(source)
|
|
33
|
+
local cached = size_cache[source]
|
|
34
|
+
if cached ~= nil then
|
|
35
|
+
return cached or nil
|
|
36
|
+
end
|
|
37
|
+
if pandoc.image == nil or pandoc.image.size == nil then
|
|
38
|
+
warn_once(source, "当前 Pandoc 不支持 pandoc.image.size,请升级到 3.1.13 或更高版本")
|
|
39
|
+
size_cache[source] = false
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local fetched, _, contents = pcall(pandoc.mediabag.fetch, source)
|
|
44
|
+
if not fetched or contents == nil then
|
|
45
|
+
warn_once(source, fetched and "未读取到图片内容" or tostring(_))
|
|
46
|
+
size_cache[source] = false
|
|
47
|
+
return nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
local measured, size = pcall(pandoc.image.size, contents, PANDOC_WRITER_OPTIONS)
|
|
51
|
+
if not measured then
|
|
52
|
+
warn_once(source, tostring(size))
|
|
53
|
+
size_cache[source] = false
|
|
54
|
+
return nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
size_cache[source] = size
|
|
58
|
+
return size
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local function limit_image(image)
|
|
62
|
+
if max_width_cm == nil or max_height_cm == nil then
|
|
63
|
+
return nil
|
|
64
|
+
end
|
|
65
|
+
if image.attributes.width ~= nil or image.attributes.height ~= nil then
|
|
66
|
+
return nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
local size = image_size(image.src)
|
|
70
|
+
if size == nil then
|
|
71
|
+
return nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
local fallback_dpi = PANDOC_WRITER_OPTIONS.dpi or 96
|
|
75
|
+
local horizontal_dpi = size.dpi_horz > 0 and size.dpi_horz or fallback_dpi
|
|
76
|
+
local vertical_dpi = size.dpi_vert > 0 and size.dpi_vert or fallback_dpi
|
|
77
|
+
local width_cm = size.width / horizontal_dpi * 2.54
|
|
78
|
+
local height_cm = size.height / vertical_dpi * 2.54
|
|
79
|
+
local scale = math.min(1, max_width_cm / width_cm, max_height_cm / height_cm)
|
|
80
|
+
|
|
81
|
+
if scale >= 1 then
|
|
82
|
+
return nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
image.attributes.width = string.format("%.4fcm", width_cm * scale)
|
|
86
|
+
image.attributes.height = string.format("%.4fcm", height_cm * scale)
|
|
87
|
+
return image
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
{ Meta = read_config },
|
|
92
|
+
{ Image = limit_image },
|
|
93
|
+
}
|
package/config/style-config.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "md2docx 语义化样式配置",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": ["schemaVersion"
|
|
7
|
+
"required": ["schemaVersion"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": {
|
|
10
10
|
"type": "string"
|
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
"schemaVersion": {
|
|
13
13
|
"const": 1
|
|
14
14
|
},
|
|
15
|
-
"preset": {
|
|
16
|
-
"const": "default"
|
|
17
|
-
},
|
|
18
15
|
"options": {
|
|
19
16
|
"type": "object",
|
|
20
17
|
"additionalProperties": false,
|
package/dist/index.js
CHANGED
|
@@ -128401,7 +128401,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
128401
128401
|
// package.json
|
|
128402
128402
|
var package_default = {
|
|
128403
128403
|
name: "@v1hz/md2docx",
|
|
128404
|
-
version: "2.
|
|
128404
|
+
version: "2.4.0",
|
|
128405
128405
|
description: "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
|
|
128406
128406
|
keywords: [
|
|
128407
128407
|
"converter",
|
|
@@ -128471,8 +128471,8 @@ var package_default = {
|
|
|
128471
128471
|
// src/cli.ts
|
|
128472
128472
|
function createProgram(version, actions) {
|
|
128473
128473
|
const program2 = new Command;
|
|
128474
|
-
program2.name("md2docx").description("将 Markdown 转换为 Word 文档").version(version, "-v, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand(false).showHelpAfterError("使用 --help 查看帮助").showSuggestionAfterError().enablePositionalOptions().argument("[markdown]", "Markdown 文件(仅无其他转换选项时)").option("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("-
|
|
128475
|
-
const hasAdditionalOptions = options.config !== undefined || options.
|
|
128474
|
+
program2.name("md2docx").description("将 Markdown 转换为 Word 文档").version(version, "-v, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand(false).showHelpAfterError("使用 --help 查看帮助").showSuggestionAfterError().enablePositionalOptions().argument("[markdown]", "Markdown 文件(仅无其他转换选项时)").option("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("--style-raw <path>", "自定义底层 Word 样式").option("--style-config <path>", "自定义语义化样式配置").option("-o, --output <path>", "输出 DOCX 文件").action(async function(markdown, options) {
|
|
128475
|
+
const hasAdditionalOptions = options.config !== undefined || options.styleRaw !== undefined || options.styleConfig !== undefined || options.output !== undefined;
|
|
128476
128476
|
if (markdown !== undefined) {
|
|
128477
128477
|
if (options.file !== undefined || hasAdditionalOptions) {
|
|
128478
128478
|
this.error("位置参数不能与转换选项同时使用;使用选项时请通过 -f, --file 指定 Markdown 文件");
|
|
@@ -128493,11 +128493,13 @@ function createProgram(version, actions) {
|
|
|
128493
128493
|
this.help({ error: true });
|
|
128494
128494
|
});
|
|
128495
128495
|
exportCommand.exitOverride();
|
|
128496
|
-
const exportConfigCommand = exportCommand.command("config").description("导出默认配置").helpOption("-h, --help", "显示帮助").option("-o, --output <path>", "输出 JSON 文件,默认 ./config.json").
|
|
128496
|
+
const exportConfigCommand = exportCommand.command("config").description("导出默认配置").helpOption("-h, --help", "显示帮助").option("-o, --output <path>", "输出 JSON 文件,默认 ./config.json").action(actions.exportConfig);
|
|
128497
128497
|
exportConfigCommand.exitOverride();
|
|
128498
|
-
const
|
|
128499
|
-
|
|
128500
|
-
const
|
|
128498
|
+
const exportStyleRawCommand = exportCommand.command("style-raw").description("导出默认底层 Word 样式,或从 DOCX 提取底层样式").helpOption("-h, --help", "显示帮助").option("-f, --file <path>", "用于提取底层样式的 DOCX 文件").option("-o, --output <path>", "输出 JSON 文件").action(actions.exportStyleRaw);
|
|
128499
|
+
exportStyleRawCommand.exitOverride();
|
|
128500
|
+
const exportStyleConfigCommand = exportCommand.command("style-config").description("导出默认语义化样式配置").helpOption("-h, --help", "显示帮助").option("-o, --output <path>", "输出 JSON 文件,默认 ./style-config.json").action(actions.exportStyleConfig);
|
|
128501
|
+
exportStyleConfigCommand.exitOverride();
|
|
128502
|
+
const formatCommand = program2.command("format").description("预处理并格式化 Markdown").helpOption("-h, --help", "显示帮助").requiredOption("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("-o, --output <path>", "输出 Markdown 文件").action(actions.format);
|
|
128501
128503
|
formatCommand.exitOverride();
|
|
128502
128504
|
const cleanCommand = program2.command("clean").description("清除 ~/.md2docx 中的预处理文件和样式缓存").helpOption("-h, --help", "显示帮助").action(async () => {
|
|
128503
128505
|
await actions.clean();
|
|
@@ -128595,6 +128597,10 @@ function validateConfig(value, path2) {
|
|
|
128595
128597
|
if (!Number.isInteger(mermaid.density) || mermaid.density < 72) {
|
|
128596
128598
|
invalidConfig(path2, "renderMermaid.density", "必须是不小于 72 的整数");
|
|
128597
128599
|
}
|
|
128600
|
+
const imageSize = expectRecord(root.imageSize, path2, "imageSize");
|
|
128601
|
+
expectBoolean(imageSize.enabled, path2, "imageSize.enabled");
|
|
128602
|
+
expectPositiveNumber(imageSize.maxWidthCm, path2, "imageSize.maxWidthCm");
|
|
128603
|
+
expectPositiveNumber(imageSize.maxHeightCm, path2, "imageSize.maxHeightCm");
|
|
128598
128604
|
validateEnabled(root.removeThematicBreaks, path2, "removeThematicBreaks");
|
|
128599
128605
|
const title = expectRecord(root.detectTitle, path2, "detectTitle");
|
|
128600
128606
|
expectBoolean(title.enabled, path2, "detectTitle.enabled");
|
|
@@ -128629,6 +128635,12 @@ function expectString(value, path2, field) {
|
|
|
128629
128635
|
invalidConfig(path2, field, "必须是字符串");
|
|
128630
128636
|
return value;
|
|
128631
128637
|
}
|
|
128638
|
+
function expectPositiveNumber(value, path2, field) {
|
|
128639
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
128640
|
+
invalidConfig(path2, field, "必须是大于 0 的有限数字");
|
|
128641
|
+
}
|
|
128642
|
+
return value;
|
|
128643
|
+
}
|
|
128632
128644
|
function invalidConfig(path2, field, reason) {
|
|
128633
128645
|
throw new Error(`配置文件无效:${path2}
|
|
128634
128646
|
位置:${field}
|
|
@@ -128652,11 +128664,7 @@ function resolveOutputPath(path2, defaultName, extensions, label) {
|
|
|
128652
128664
|
assertExtension(resolved, extensions, label);
|
|
128653
128665
|
return resolved;
|
|
128654
128666
|
}
|
|
128655
|
-
function prepareOutput(path2
|
|
128656
|
-
if (existsSync2(path2) && !force) {
|
|
128657
|
-
throw new Error(`输出文件已存在:${path2}
|
|
128658
|
-
使用 --force 覆盖现有文件`);
|
|
128659
|
-
}
|
|
128667
|
+
function prepareOutput(path2) {
|
|
128660
128668
|
mkdirSync(dirname2(path2), { recursive: true });
|
|
128661
128669
|
}
|
|
128662
128670
|
function assertExtension(path2, extensions, label) {
|
|
@@ -146108,6 +146116,11 @@ var config_default = `{
|
|
|
146108
146116
|
"theme": "tokyo-night-light",
|
|
146109
146117
|
"density": 200
|
|
146110
146118
|
},
|
|
146119
|
+
"imageSize": {
|
|
146120
|
+
"enabled": true,
|
|
146121
|
+
"maxWidthCm": 12,
|
|
146122
|
+
"maxHeightCm": 12
|
|
146123
|
+
},
|
|
146111
146124
|
"detectTitle": {
|
|
146112
146125
|
"enabled": true,
|
|
146113
146126
|
"strategy": "first-h1"
|
|
@@ -146118,6 +146131,102 @@ var config_default = `{
|
|
|
146118
146131
|
}
|
|
146119
146132
|
`;
|
|
146120
146133
|
|
|
146134
|
+
// config/lua/limit-image-size.lua
|
|
146135
|
+
var limit_image_size_default = `local max_width_cm
|
|
146136
|
+
local max_height_cm
|
|
146137
|
+
local size_cache = {}
|
|
146138
|
+
local warned = {}
|
|
146139
|
+
|
|
146140
|
+
local function warn_once(source, message)
|
|
146141
|
+
if warned[source] then
|
|
146142
|
+
return
|
|
146143
|
+
end
|
|
146144
|
+
warned[source] = true
|
|
146145
|
+
local text = "无法限制图片尺寸 " .. source .. ":" .. message
|
|
146146
|
+
if pandoc.log and pandoc.log.warn then
|
|
146147
|
+
pandoc.log.warn(text)
|
|
146148
|
+
else
|
|
146149
|
+
io.stderr:write("[WARNING] " .. text .. "\\n")
|
|
146150
|
+
end
|
|
146151
|
+
end
|
|
146152
|
+
|
|
146153
|
+
local function metadata_number(meta, key)
|
|
146154
|
+
local value = meta[key]
|
|
146155
|
+
if value == nil then
|
|
146156
|
+
return nil
|
|
146157
|
+
end
|
|
146158
|
+
return tonumber(pandoc.utils.stringify(value))
|
|
146159
|
+
end
|
|
146160
|
+
|
|
146161
|
+
local function read_config(meta)
|
|
146162
|
+
max_width_cm = metadata_number(meta, "md2docx-image-max-width-cm")
|
|
146163
|
+
max_height_cm = metadata_number(meta, "md2docx-image-max-height-cm")
|
|
146164
|
+
end
|
|
146165
|
+
|
|
146166
|
+
local function image_size(source)
|
|
146167
|
+
local cached = size_cache[source]
|
|
146168
|
+
if cached ~= nil then
|
|
146169
|
+
return cached or nil
|
|
146170
|
+
end
|
|
146171
|
+
if pandoc.image == nil or pandoc.image.size == nil then
|
|
146172
|
+
warn_once(source, "当前 Pandoc 不支持 pandoc.image.size,请升级到 3.1.13 或更高版本")
|
|
146173
|
+
size_cache[source] = false
|
|
146174
|
+
return nil
|
|
146175
|
+
end
|
|
146176
|
+
|
|
146177
|
+
local fetched, _, contents = pcall(pandoc.mediabag.fetch, source)
|
|
146178
|
+
if not fetched or contents == nil then
|
|
146179
|
+
warn_once(source, fetched and "未读取到图片内容" or tostring(_))
|
|
146180
|
+
size_cache[source] = false
|
|
146181
|
+
return nil
|
|
146182
|
+
end
|
|
146183
|
+
|
|
146184
|
+
local measured, size = pcall(pandoc.image.size, contents, PANDOC_WRITER_OPTIONS)
|
|
146185
|
+
if not measured then
|
|
146186
|
+
warn_once(source, tostring(size))
|
|
146187
|
+
size_cache[source] = false
|
|
146188
|
+
return nil
|
|
146189
|
+
end
|
|
146190
|
+
|
|
146191
|
+
size_cache[source] = size
|
|
146192
|
+
return size
|
|
146193
|
+
end
|
|
146194
|
+
|
|
146195
|
+
local function limit_image(image)
|
|
146196
|
+
if max_width_cm == nil or max_height_cm == nil then
|
|
146197
|
+
return nil
|
|
146198
|
+
end
|
|
146199
|
+
if image.attributes.width ~= nil or image.attributes.height ~= nil then
|
|
146200
|
+
return nil
|
|
146201
|
+
end
|
|
146202
|
+
|
|
146203
|
+
local size = image_size(image.src)
|
|
146204
|
+
if size == nil then
|
|
146205
|
+
return nil
|
|
146206
|
+
end
|
|
146207
|
+
|
|
146208
|
+
local fallback_dpi = PANDOC_WRITER_OPTIONS.dpi or 96
|
|
146209
|
+
local horizontal_dpi = size.dpi_horz > 0 and size.dpi_horz or fallback_dpi
|
|
146210
|
+
local vertical_dpi = size.dpi_vert > 0 and size.dpi_vert or fallback_dpi
|
|
146211
|
+
local width_cm = size.width / horizontal_dpi * 2.54
|
|
146212
|
+
local height_cm = size.height / vertical_dpi * 2.54
|
|
146213
|
+
local scale = math.min(1, max_width_cm / width_cm, max_height_cm / height_cm)
|
|
146214
|
+
|
|
146215
|
+
if scale >= 1 then
|
|
146216
|
+
return nil
|
|
146217
|
+
end
|
|
146218
|
+
|
|
146219
|
+
image.attributes.width = string.format("%.4fcm", width_cm * scale)
|
|
146220
|
+
image.attributes.height = string.format("%.4fcm", height_cm * scale)
|
|
146221
|
+
return image
|
|
146222
|
+
end
|
|
146223
|
+
|
|
146224
|
+
return {
|
|
146225
|
+
{ Meta = read_config },
|
|
146226
|
+
{ Image = limit_image },
|
|
146227
|
+
}
|
|
146228
|
+
`;
|
|
146229
|
+
|
|
146121
146230
|
// config/lua/add-inline-code.lua
|
|
146122
146231
|
var add_inline_code_default = `-- Author: Achuan-2
|
|
146123
146232
|
function Code(el)
|
|
@@ -146126,32 +146235,8 @@ function Code(el)
|
|
|
146126
146235
|
end
|
|
146127
146236
|
return { Code = Code }`;
|
|
146128
146237
|
|
|
146129
|
-
// config/style-
|
|
146130
|
-
var
|
|
146131
|
-
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
146132
|
-
"schemaVersion": 1,
|
|
146133
|
-
"preset": "default",
|
|
146134
|
-
"options": {
|
|
146135
|
-
"body": {
|
|
146136
|
-
"firstLineIndent": true
|
|
146137
|
-
},
|
|
146138
|
-
"headings": {
|
|
146139
|
-
"1": {
|
|
146140
|
-
"startOnNewPage": false
|
|
146141
|
-
}
|
|
146142
|
-
},
|
|
146143
|
-
"inlineCode": {
|
|
146144
|
-
"background": true
|
|
146145
|
-
},
|
|
146146
|
-
"codeBlock": {
|
|
146147
|
-
"border": true
|
|
146148
|
-
}
|
|
146149
|
-
}
|
|
146150
|
-
}
|
|
146151
|
-
`;
|
|
146152
|
-
|
|
146153
|
-
// config/style.json
|
|
146154
|
-
var style_default = `{
|
|
146238
|
+
// config/style-raw.json
|
|
146239
|
+
var style_raw_default = `{
|
|
146155
146240
|
"default": {
|
|
146156
146241
|
"document": {
|
|
146157
146242
|
"run": {
|
|
@@ -147108,10 +147193,33 @@ var style_default = `{
|
|
|
147108
147193
|
}
|
|
147109
147194
|
`;
|
|
147110
147195
|
|
|
147196
|
+
// config/style-config.json
|
|
147197
|
+
var style_config_default = `{
|
|
147198
|
+
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
147199
|
+
"schemaVersion": 1,
|
|
147200
|
+
"options": {
|
|
147201
|
+
"body": {
|
|
147202
|
+
"firstLineIndent": true
|
|
147203
|
+
},
|
|
147204
|
+
"headings": {
|
|
147205
|
+
"1": {
|
|
147206
|
+
"startOnNewPage": false
|
|
147207
|
+
}
|
|
147208
|
+
},
|
|
147209
|
+
"inlineCode": {
|
|
147210
|
+
"background": true
|
|
147211
|
+
},
|
|
147212
|
+
"codeBlock": {
|
|
147213
|
+
"border": true
|
|
147214
|
+
}
|
|
147215
|
+
}
|
|
147216
|
+
}
|
|
147217
|
+
`;
|
|
147218
|
+
|
|
147111
147219
|
// src/resources.ts
|
|
147112
147220
|
var DEFAULT_CONFIG_TEXT = config_default;
|
|
147113
147221
|
var DEFAULT_STYLE_CONFIG_TEXT = style_config_default;
|
|
147114
|
-
var
|
|
147222
|
+
var DEFAULT_STYLE_RAW_TEXT = style_raw_default;
|
|
147115
147223
|
function materializeResource(name2, content3) {
|
|
147116
147224
|
const directory = join4(TMP_DIR, "resources");
|
|
147117
147225
|
const path3 = join4(directory, name2);
|
|
@@ -147124,41 +147232,30 @@ function materializeResource(name2, content3) {
|
|
|
147124
147232
|
function materializeDefaultConfig() {
|
|
147125
147233
|
return materializeResource("config.json", DEFAULT_CONFIG_TEXT);
|
|
147126
147234
|
}
|
|
147127
|
-
function
|
|
147128
|
-
return materializeResource("style.json",
|
|
147235
|
+
function materializeDefaultStyleRaw() {
|
|
147236
|
+
return materializeResource("style-raw.json", DEFAULT_STYLE_RAW_TEXT);
|
|
147129
147237
|
}
|
|
147130
147238
|
function materializeDefaultStyleConfig() {
|
|
147131
|
-
materializeDefaultStyle();
|
|
147132
147239
|
return materializeResource("style-config.json", DEFAULT_STYLE_CONFIG_TEXT);
|
|
147133
147240
|
}
|
|
147134
|
-
function
|
|
147241
|
+
function materializeInlineCodeFilter() {
|
|
147135
147242
|
return materializeResource("add-inline-code.lua", add_inline_code_default);
|
|
147136
147243
|
}
|
|
147137
|
-
|
|
147138
|
-
|
|
147139
|
-
|
|
147140
|
-
import { writeFile } from "node:fs/promises";
|
|
147141
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
147142
|
-
import { dirname as dirname4 } from "path";
|
|
147244
|
+
function materializeImageSizeFilter() {
|
|
147245
|
+
return materializeResource("limit-image-size.lua", limit_image_size_default);
|
|
147246
|
+
}
|
|
147143
147247
|
|
|
147144
147248
|
// src/style/compiler.ts
|
|
147145
147249
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
147146
147250
|
|
|
147147
147251
|
// src/style/config.ts
|
|
147148
|
-
function isStyleConfig(value2) {
|
|
147149
|
-
if (!isRecord(value2))
|
|
147150
|
-
return false;
|
|
147151
|
-
return "schemaVersion" in value2 || "preset" in value2 || "options" in value2;
|
|
147152
|
-
}
|
|
147153
147252
|
function validateStyleConfig(value2, path3) {
|
|
147154
147253
|
const root2 = expectRecord2(value2, path3, "样式配置");
|
|
147155
|
-
rejectUnknown(root2, ["$schema", "schemaVersion", "
|
|
147254
|
+
rejectUnknown(root2, ["$schema", "schemaVersion", "options"], path3, "样式配置");
|
|
147156
147255
|
if (root2.$schema !== undefined)
|
|
147157
147256
|
expectString2(root2.$schema, path3, "$schema");
|
|
147158
147257
|
if (root2.schemaVersion !== 1)
|
|
147159
147258
|
invalidStyleConfig(path3, "schemaVersion", "目前只支持版本 1");
|
|
147160
|
-
if (root2.preset !== "default")
|
|
147161
|
-
invalidStyleConfig(path3, "preset", '目前只支持 "default"');
|
|
147162
147259
|
if (root2.options !== undefined)
|
|
147163
147260
|
validateOptions(root2.options, path3);
|
|
147164
147261
|
return root2;
|
|
@@ -147227,16 +147324,41 @@ function invalidStyleConfig(path3, field, reason) {
|
|
|
147227
147324
|
}
|
|
147228
147325
|
|
|
147229
147326
|
// src/style/compiler.ts
|
|
147230
|
-
function
|
|
147231
|
-
const
|
|
147232
|
-
|
|
147233
|
-
|
|
147234
|
-
|
|
147235
|
-
|
|
147236
|
-
|
|
147237
|
-
|
|
147238
|
-
|
|
147239
|
-
|
|
147327
|
+
function resolveEffectiveStyles(sources, defaults) {
|
|
147328
|
+
const rawPath = sources.styleRawPath ?? defaults?.styleRawPath ?? materializeDefaultStyleRaw();
|
|
147329
|
+
const rawStyle = loadStyleRaw(rawPath);
|
|
147330
|
+
if (sources.styleConfigPath) {
|
|
147331
|
+
return compileStyleConfig(rawStyle, loadStyleConfig(sources.styleConfigPath));
|
|
147332
|
+
}
|
|
147333
|
+
if (sources.styleRawPath)
|
|
147334
|
+
return rawStyle;
|
|
147335
|
+
const defaultConfigPath = defaults?.styleConfigPath ?? materializeDefaultStyleConfig();
|
|
147336
|
+
return compileStyleConfig(rawStyle, loadStyleConfig(defaultConfigPath));
|
|
147337
|
+
}
|
|
147338
|
+
function loadStyleRaw(path3) {
|
|
147339
|
+
const source = parseJson(readFileSync2(path3, "utf-8"), path3, "底层样式文件");
|
|
147340
|
+
if (!isRecord2(source))
|
|
147341
|
+
throw new Error(`底层样式文件必须是 JSON 对象:${path3}`);
|
|
147342
|
+
if (looksLikeStyleConfig(source)) {
|
|
147343
|
+
throw new Error(`底层样式文件无效:${path3}
|
|
147344
|
+
当前文件看起来是语义化样式配置,请改用 --style-config`);
|
|
147345
|
+
}
|
|
147346
|
+
if (!looksLikeRawStyle(source)) {
|
|
147347
|
+
throw new Error(`底层样式文件无效:${path3}
|
|
147348
|
+
缺少 Word 样式定义`);
|
|
147349
|
+
}
|
|
147350
|
+
return source;
|
|
147351
|
+
}
|
|
147352
|
+
function loadStyleConfig(path3) {
|
|
147353
|
+
const source = parseJson(readFileSync2(path3, "utf-8"), path3, "语义化样式配置");
|
|
147354
|
+
if (isRecord2(source) && looksLikeRawStyle(source)) {
|
|
147355
|
+
throw new Error(`语义化样式配置无效:${path3}
|
|
147356
|
+
当前文件看起来是底层样式,请改用 --style-raw`);
|
|
147357
|
+
}
|
|
147358
|
+
return validateStyleConfig(source, path3);
|
|
147359
|
+
}
|
|
147360
|
+
function compileStyleConfig(rawStyle, config) {
|
|
147361
|
+
const styles = structuredClone(rawStyle);
|
|
147240
147362
|
const options = config.options;
|
|
147241
147363
|
if (!options)
|
|
147242
147364
|
return styles;
|
|
@@ -147297,12 +147419,12 @@ function applyCodeBlockBorder(styles, enabled) {
|
|
|
147297
147419
|
function findStyle(styles, collectionName, name2) {
|
|
147298
147420
|
const collection = styles[collectionName];
|
|
147299
147421
|
if (!Array.isArray(collection)) {
|
|
147300
|
-
throw new Error(
|
|
147422
|
+
throw new Error(`底层样式缺少 ${collectionName},无法应用语义化样式配置`);
|
|
147301
147423
|
}
|
|
147302
147424
|
const normalizedName = name2.toLowerCase();
|
|
147303
147425
|
const style = collection.find((entry) => isRecord2(entry) && typeof entry.name === "string" && entry.name.toLowerCase() === normalizedName);
|
|
147304
147426
|
if (!style)
|
|
147305
|
-
throw new Error(
|
|
147427
|
+
throw new Error(`底层样式缺少“${name2}”,无法应用语义化样式配置`);
|
|
147306
147428
|
return style;
|
|
147307
147429
|
}
|
|
147308
147430
|
function ensureObject(parent, key) {
|
|
@@ -147316,25 +147438,30 @@ function ensureObject(parent, key) {
|
|
|
147316
147438
|
function optionalObject(value2) {
|
|
147317
147439
|
return isRecord2(value2) ? value2 : {};
|
|
147318
147440
|
}
|
|
147319
|
-
function parseJson(text5, path3) {
|
|
147441
|
+
function parseJson(text5, path3, kind) {
|
|
147320
147442
|
try {
|
|
147321
147443
|
return JSON.parse(text5);
|
|
147322
147444
|
} catch (error) {
|
|
147323
147445
|
const message = error instanceof Error ? error.message : String(error);
|
|
147324
|
-
throw new Error(
|
|
147446
|
+
throw new Error(`${kind}不是有效的 JSON:${path3}
|
|
147325
147447
|
${message}`);
|
|
147326
147448
|
}
|
|
147327
147449
|
}
|
|
147328
|
-
function
|
|
147329
|
-
|
|
147330
|
-
|
|
147331
|
-
|
|
147450
|
+
function looksLikeStyleConfig(value2) {
|
|
147451
|
+
return "schemaVersion" in value2 || "options" in value2 || "preset" in value2;
|
|
147452
|
+
}
|
|
147453
|
+
function looksLikeRawStyle(value2) {
|
|
147454
|
+
return ["default", "paragraphStyles", "characterStyles", "tableStyles", "tableStylesXml"].some((key) => (key in value2));
|
|
147332
147455
|
}
|
|
147333
147456
|
function isRecord2(value2) {
|
|
147334
147457
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
147335
147458
|
}
|
|
147336
147459
|
|
|
147337
147460
|
// src/style/generate.ts
|
|
147461
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
147462
|
+
import { writeFile } from "node:fs/promises";
|
|
147463
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
147464
|
+
import { dirname as dirname4 } from "path";
|
|
147338
147465
|
async function generateTemplateDocxFromStyles(raw, outputPath) {
|
|
147339
147466
|
const { Document: Document2, Packer: Packer3 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
147340
147467
|
const tableStylesXml = raw.tableStylesXml;
|
|
@@ -147361,14 +147488,16 @@ async function generateTemplateDocxFromStyles(raw, outputPath) {
|
|
|
147361
147488
|
await writeFile(outputPath, buf);
|
|
147362
147489
|
}
|
|
147363
147490
|
var generatePromises = new Map;
|
|
147364
|
-
|
|
147365
|
-
|
|
147366
|
-
|
|
147491
|
+
function styleCacheHash(rawStyle) {
|
|
147492
|
+
return createHash2("sha256").update(JSON.stringify(rawStyle)).digest("hex").slice(0, 16);
|
|
147493
|
+
}
|
|
147494
|
+
async function ensureTemplateDocx(rawStyle) {
|
|
147495
|
+
const hash = styleCacheHash(rawStyle);
|
|
147367
147496
|
const outputPath = styleTemplateDocx(hash);
|
|
147368
147497
|
if (!existsSync5(outputPath)) {
|
|
147369
147498
|
let promise = generatePromises.get(outputPath);
|
|
147370
147499
|
if (!promise) {
|
|
147371
|
-
promise = generateTemplateDocxFromStyles(
|
|
147500
|
+
promise = generateTemplateDocxFromStyles(rawStyle, outputPath).catch((error) => {
|
|
147372
147501
|
generatePromises.delete(outputPath);
|
|
147373
147502
|
throw error;
|
|
147374
147503
|
});
|
|
@@ -147385,25 +147514,29 @@ async function convertMarkdown(options) {
|
|
|
147385
147514
|
throw new Error("缺少必填参数:--file");
|
|
147386
147515
|
const input = resolveInputPath(options.file, [".md", ".markdown"], "Markdown 文件");
|
|
147387
147516
|
const configPath = options.config ? resolveInputPath(options.config, [".json"], "配置文件") : materializeDefaultConfig();
|
|
147388
|
-
const
|
|
147517
|
+
const styleRawPath = options.styleRaw ? resolveInputPath(options.styleRaw, [".json"], "底层样式文件") : undefined;
|
|
147518
|
+
const styleConfigPath = options.styleConfig ? resolveInputPath(options.styleConfig, [".json"], "语义化样式配置") : undefined;
|
|
147389
147519
|
const baseName = parse4(input).name;
|
|
147390
147520
|
const output = resolveOutputPath(options.output, `${baseName}.docx`, [".docx"], "DOCX 输出文件");
|
|
147391
|
-
prepareOutput(output
|
|
147521
|
+
prepareOutput(output);
|
|
147392
147522
|
const config = await loadConfig(configPath);
|
|
147393
147523
|
const outDir = preprocessDir(input);
|
|
147394
147524
|
mkdirSync5(outDir, { recursive: true });
|
|
147395
147525
|
const formatted = await preprocess2(input, config, outDir);
|
|
147396
147526
|
const markdownOutput = formattedMdPath(input);
|
|
147397
147527
|
writeFileSync3(markdownOutput, formatted, "utf-8");
|
|
147398
|
-
const
|
|
147399
|
-
const
|
|
147528
|
+
const effectiveStyles = resolveEffectiveStyles({ styleRawPath, styleConfigPath });
|
|
147529
|
+
const templatePath = await ensureTemplateDocx(effectiveStyles);
|
|
147530
|
+
const inlineCodeFilter = materializeInlineCodeFilter();
|
|
147531
|
+
const imageSizeArgs = config.imageSize.enabled ? buildImageSizePandocArgs(config.imageSize, materializeImageSizeFilter()) : [];
|
|
147400
147532
|
const pandocArgs = [
|
|
147401
147533
|
markdownOutput,
|
|
147402
147534
|
"-o",
|
|
147403
147535
|
output,
|
|
147404
147536
|
...buildPandocResourcePathArgs(input),
|
|
147405
147537
|
`--reference-doc=${templatePath}`,
|
|
147406
|
-
...existsSync6(
|
|
147538
|
+
...existsSync6(inlineCodeFilter) ? [`--lua-filter=${inlineCodeFilter}`] : [],
|
|
147539
|
+
...imageSizeArgs
|
|
147407
147540
|
];
|
|
147408
147541
|
const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
|
|
147409
147542
|
if (exitCode !== 0) {
|
|
@@ -147411,6 +147544,15 @@ async function convertMarkdown(options) {
|
|
|
147411
147544
|
}
|
|
147412
147545
|
console.log(`已生成:${output}`);
|
|
147413
147546
|
}
|
|
147547
|
+
function buildImageSizePandocArgs(config, filterPath) {
|
|
147548
|
+
if (!config.enabled)
|
|
147549
|
+
return [];
|
|
147550
|
+
return [
|
|
147551
|
+
`--metadata=md2docx-image-max-width-cm:${config.maxWidthCm}`,
|
|
147552
|
+
`--metadata=md2docx-image-max-height-cm:${config.maxHeightCm}`,
|
|
147553
|
+
`--lua-filter=${filterPath}`
|
|
147554
|
+
];
|
|
147555
|
+
}
|
|
147414
147556
|
function buildPandocResourcePathArgs(inputPath, workingDirectory = process.cwd()) {
|
|
147415
147557
|
const workingDir = resolve5(workingDirectory);
|
|
147416
147558
|
const sourceDir = dirname5(resolve5(inputPath));
|
|
@@ -148054,23 +148196,32 @@ function extractStylesFromDocx(docxPath) {
|
|
|
148054
148196
|
// src/commands/export.ts
|
|
148055
148197
|
async function exportConfig(options) {
|
|
148056
148198
|
const output = resolveOutputPath(options.output, "config.json", [".json"], "配置输出文件");
|
|
148057
|
-
prepareOutput(output
|
|
148199
|
+
prepareOutput(output);
|
|
148058
148200
|
writeFileSync4(output, DEFAULT_CONFIG_TEXT, "utf-8");
|
|
148059
148201
|
console.log(`已导出配置:${output}`);
|
|
148060
148202
|
}
|
|
148061
|
-
async function
|
|
148203
|
+
async function exportStyleRaw(options) {
|
|
148062
148204
|
const source = options.file ? resolveInputPath(options.file, [".docx"], "DOCX 文件") : undefined;
|
|
148063
|
-
const defaultName =
|
|
148064
|
-
const output = resolveOutputPath(options.output, defaultName, [".json"], "
|
|
148065
|
-
prepareOutput(output
|
|
148205
|
+
const defaultName = defaultStyleRawOutputName(source);
|
|
148206
|
+
const output = resolveOutputPath(options.output, defaultName, [".json"], "底层样式输出文件");
|
|
148207
|
+
prepareOutput(output);
|
|
148066
148208
|
if (source) {
|
|
148067
148209
|
const styles = extractStylesFromDocx(source);
|
|
148068
148210
|
writeFileSync4(output, `${JSON.stringify(styles, null, 2)}
|
|
148069
148211
|
`, "utf-8");
|
|
148070
148212
|
} else {
|
|
148071
|
-
writeFileSync4(output,
|
|
148213
|
+
writeFileSync4(output, DEFAULT_STYLE_RAW_TEXT, "utf-8");
|
|
148072
148214
|
}
|
|
148073
|
-
console.log(
|
|
148215
|
+
console.log(`已导出底层样式:${output}`);
|
|
148216
|
+
}
|
|
148217
|
+
function defaultStyleRawOutputName(source) {
|
|
148218
|
+
return source ? `${parse5(source).name}_style-raw.json` : "style-raw.json";
|
|
148219
|
+
}
|
|
148220
|
+
async function exportStyleConfig(options) {
|
|
148221
|
+
const output = resolveOutputPath(options.output, "style-config.json", [".json"], "语义化样式配置输出文件");
|
|
148222
|
+
prepareOutput(output);
|
|
148223
|
+
writeFileSync4(output, DEFAULT_STYLE_CONFIG_TEXT, "utf-8");
|
|
148224
|
+
console.log(`已导出语义化样式配置:${output}`);
|
|
148074
148225
|
}
|
|
148075
148226
|
|
|
148076
148227
|
// src/commands/format.ts
|
|
@@ -148081,17 +148232,12 @@ async function formatMarkdown(options) {
|
|
|
148081
148232
|
const configPath = options.config ? resolveInputPath(options.config, [".json"], "配置文件") : materializeDefaultConfig();
|
|
148082
148233
|
const defaultName = `${parse6(input).name}_formatted.md`;
|
|
148083
148234
|
const output = resolveOutputPath(options.output, defaultName, [".md", ".markdown"], "Markdown 输出文件");
|
|
148084
|
-
|
|
148085
|
-
prepareOutput(output, force);
|
|
148235
|
+
prepareOutput(output);
|
|
148086
148236
|
const config = await loadConfig(configPath);
|
|
148087
148237
|
const outputName = parse6(output).name;
|
|
148088
148238
|
const assetsName = `${outputName}_assets`;
|
|
148089
148239
|
const assetsDir = resolve6(dirname6(output), assetsName);
|
|
148090
148240
|
if (config.renderMermaid.enabled && existsSync7(assetsDir)) {
|
|
148091
|
-
if (!force) {
|
|
148092
|
-
throw new Error(`资源目录已存在:${assetsDir}
|
|
148093
|
-
使用 --force 覆盖现有目录`);
|
|
148094
|
-
}
|
|
148095
148241
|
const parent = resolve6(dirname6(output));
|
|
148096
148242
|
if (dirname6(assetsDir) !== parent)
|
|
148097
148243
|
throw new Error(`拒绝清理非输出目录:${assetsDir}`);
|
|
@@ -148108,7 +148254,8 @@ async function run(args) {
|
|
|
148108
148254
|
convert: convertMarkdown,
|
|
148109
148255
|
format: formatMarkdown,
|
|
148110
148256
|
exportConfig,
|
|
148111
|
-
|
|
148257
|
+
exportStyleRaw,
|
|
148258
|
+
exportStyleConfig,
|
|
148112
148259
|
clean: cleanIntermediateFiles
|
|
148113
148260
|
});
|
|
148114
148261
|
program2.exitOverride();
|
package/package.json
CHANGED
|
File without changes
|