@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 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 --force
62
+ md2docx --file report.md --output output/report.docx
62
63
 
63
- # 使用自定义配置和样式
64
- md2docx -f report.md -c config.json -s style.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
- 所有写文件命令默认拒绝覆盖已有文件。确认覆盖时显式传入 `--force`。
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>` | Markdown 输入文件 |
99
- | `-c, --config <path>` | 自定义配置 JSON |
100
- | `-s, --style <path>` | 语义化样式配置或完整底层样式 JSON |
101
- | `-o, --output <path>` | DOCX 输出路径 |
102
- | `--force` | 覆盖已有输出 |
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`、`--output` 或 `--force` 混用。例如:
109
+ 位置参数不能和 `--file`、`--config`、`--style-raw`、`--style-config` 或 `--output` 混用。例如:
107
110
 
108
111
  ```bash
109
112
  md2docx report.md # 正确
110
- md2docx report.md --force # 错误
111
- md2docx -f report.md --force # 正确
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] [--force]
129
- md2docx export style [-f template.docx] [-o style.json] [--force]
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` 导出内置默认配置。`export style` 不带 `--file` 时导出内置默认样式;指定 DOCX 时从该文档提取样式。
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
- └── add-inline-code.lua
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` 用于编辑器提示和校验。未指定 `--style` 时,转换会自动加载内嵌的默认 `style-config.json`,再将它编译到内置底层样式;不需要手工传入 `-s`。
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 -s style-config.json
257
+ md2docx -f report.md --style-config style-config.json
249
258
  ```
250
259
 
251
- 字段缺失表示继承预设;`true` 表示使用预设效果;`false` 表示显式关闭。当前只开放正文首行缩进、一级标题另起一页、行内代码背景和代码块外框。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
260
+ 字段缺失表示继承底层样式;`true` 表示显式启用完整效果;`false` 表示显式关闭。当前只开放正文首行缩进、一级标题另起一页、行内代码背景和代码块外框。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
261
+
262
+ 转换时按以下规则选择输入:
252
263
 
253
- `config/style.json` 仍定义完整的底层 DOCX 样式,包括标题、段落、字符和表格样式。转换时,项目先将语义化配置编译到底层样式,再用 `docx` 生成 reference DOCX,最后通过 Pandoc 的 `--reference-doc` 应用样式。现有完整样式 JSON 可继续直接传给 `--style`。
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 -s template_style.json
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 文件为基准:
@@ -23,6 +23,11 @@
23
23
  "theme": "tokyo-night-light",
24
24
  "density": 200
25
25
  },
26
+ "imageSize": {
27
+ "enabled": true,
28
+ "maxWidthCm": 12,
29
+ "maxHeightCm": 12
30
+ },
26
31
  "detectTitle": {
27
32
  "enabled": true,
28
33
  "strategy": "first-h1"
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
3
3
  "schemaVersion": 1,
4
- "preset": "default",
5
4
  "options": {
6
5
  "body": {
7
6
  "firstLineIndent": true
@@ -4,7 +4,7 @@
4
4
  "title": "md2docx 语义化样式配置",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
- "required": ["schemaVersion", "preset"],
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.3.0",
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("-s, --style <path>", "自定义底层样式或语义化样式配置").option("-o, --output <path>", "输出 DOCX 文件").option("--force", "覆盖已有文件").action(async function(markdown, options) {
128475
- const hasAdditionalOptions = options.config !== undefined || options.style !== undefined || options.output !== undefined || options.force !== undefined;
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").option("--force", "覆盖已有文件").action(actions.exportConfig);
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 exportStyleCommand = exportCommand.command("style").description("导出默认样式,或从 DOCX 提取样式").helpOption("-h, --help", "显示帮助").option("-f, --file <path>", "用于提取样式的 DOCX 文件").option("-o, --output <path>", "输出 JSON 文件").option("--force", "覆盖已有文件").action(actions.exportStyle);
128499
- exportStyleCommand.exitOverride();
128500
- const formatCommand = program2.command("format").description("预处理并格式化 Markdown").helpOption("-h, --help", "显示帮助").requiredOption("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("-o, --output <path>", "输出 Markdown 文件").option("--force", "覆盖已有文件").action(actions.format);
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, force) {
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-config.json
146130
- var style_config_default = `{
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 DEFAULT_STYLE_TEXT = style_default;
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 materializeDefaultStyle() {
147128
- return materializeResource("style.json", DEFAULT_STYLE_TEXT);
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 materializeLuaFilter() {
147241
+ function materializeInlineCodeFilter() {
147135
147242
  return materializeResource("add-inline-code.lua", add_inline_code_default);
147136
147243
  }
147137
-
147138
- // src/style/generate.ts
147139
- import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
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", "preset", "options"], path3, "样式配置");
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 loadEffectiveStyles(stylePath) {
147231
- const source = parseJson(readFileSync2(stylePath, "utf-8"), stylePath);
147232
- if (!isStyleConfig(source))
147233
- return expectStyleDefinition(source, stylePath);
147234
- const config = validateStyleConfig(source, stylePath);
147235
- const preset = expectStyleDefinition(parseJson(DEFAULT_STYLE_TEXT, "内置默认样式"), "内置默认样式");
147236
- return compileStyleConfig(preset, config);
147237
- }
147238
- function compileStyleConfig(preset, config) {
147239
- const styles = structuredClone(preset);
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(`内置样式缺少 ${collectionName},无法应用样式配置`);
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(`内置样式缺少“${name2}”,无法应用样式配置`);
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(`样式文件不是有效的 JSON:${path3}
147446
+ throw new Error(`${kind}不是有效的 JSON:${path3}
147325
147447
  ${message}`);
147326
147448
  }
147327
147449
  }
147328
- function expectStyleDefinition(value2, path3) {
147329
- if (!isRecord2(value2))
147330
- throw new Error(`样式文件必须是 JSON 对象:${path3}`);
147331
- return value2;
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
- async function ensureTemplateDocx(styleJsonPath) {
147365
- const styles = loadEffectiveStyles(styleJsonPath);
147366
- const hash = createHash2("sha256").update(JSON.stringify(styles)).digest("hex").slice(0, 16);
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(styles, outputPath).catch((error) => {
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 stylePath = options.style ? resolveInputPath(options.style, [".json"], "样式文件") : materializeDefaultStyleConfig();
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, options.force ?? false);
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 templatePath = await ensureTemplateDocx(stylePath);
147399
- const luaFilter = materializeLuaFilter();
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(luaFilter) ? [`--lua-filter=${luaFilter}`] : []
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, options.force ?? false);
148199
+ prepareOutput(output);
148058
148200
  writeFileSync4(output, DEFAULT_CONFIG_TEXT, "utf-8");
148059
148201
  console.log(`已导出配置:${output}`);
148060
148202
  }
148061
- async function exportStyle(options) {
148203
+ async function exportStyleRaw(options) {
148062
148204
  const source = options.file ? resolveInputPath(options.file, [".docx"], "DOCX 文件") : undefined;
148063
- const defaultName = source ? `${parse5(source).name}_style.json` : "style.json";
148064
- const output = resolveOutputPath(options.output, defaultName, [".json"], "样式输出文件");
148065
- prepareOutput(output, options.force ?? false);
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, DEFAULT_STYLE_TEXT, "utf-8");
148213
+ writeFileSync4(output, DEFAULT_STYLE_RAW_TEXT, "utf-8");
148072
148214
  }
148073
- console.log(`已导出样式:${output}`);
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
- const force = options.force ?? false;
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
- exportStyle,
148257
+ exportStyleRaw,
148258
+ exportStyleConfig,
148112
148259
  clean: cleanIntermediateFiles
148113
148260
  });
148114
148261
  program2.exitOverride();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v1hz/md2docx",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
5
5
  "keywords": [
6
6
  "converter",
File without changes