@v1hz/md2docx 2.4.0 → 2.5.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
@@ -216,8 +216,8 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
216
216
  | `renderMermaid.theme` | beautiful-mermaid 主题 | `"tokyo-night-light"` |
217
217
  | `renderMermaid.density` | PNG 输出 DPI,最小值 72 | `200` |
218
218
  | `imageSize.enabled` | 等比缩小超过尺寸限制的图片 | `true` |
219
- | `imageSize.maxWidthCm` | DOCX 图片最大宽度(厘米) | `15.5` |
220
- | `imageSize.maxHeightCm` | DOCX 图片最大高度(厘米) | `22` |
219
+ | `imageSize.maxWidthCm` | DOCX 图片最大宽度(厘米) | `12` |
220
+ | `imageSize.maxHeightCm` | DOCX 图片最大高度(厘米) | `12` |
221
221
  | `removeThematicBreaks.enabled` | 移除 `---`、`***`、`___` 等分隔符行 | `true` |
222
222
 
223
223
  图片尺寸限制在 Pandoc 生成 DOCX 前通过 Lua filter 应用。程序读取图片像素尺寸和 DPI,仅缩小超限图片,不会放大小图;宽度和高度使用同一缩放比例。Markdown 中已经显式设置 `width` 或 `height` 的图片视为用户覆盖,不应用全局限制。单张图片无法读取尺寸时会输出警告并继续转换。
@@ -234,11 +234,32 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
234
234
  "schemaVersion": 1,
235
235
  "options": {
236
236
  "body": {
237
- "firstLineIndent": false
237
+ "firstLineIndent": false,
238
+ "lineSpacing": "onePointFive"
238
239
  },
239
240
  "headings": {
240
241
  "1": {
241
- "startOnNewPage": false
242
+ "startOnNewPage": false,
243
+ "alignment": "left",
244
+ "bold": true
245
+ },
246
+ "2": {
247
+ "bold": true
248
+ },
249
+ "3": {
250
+ "bold": true
251
+ },
252
+ "4": {
253
+ "bold": true,
254
+ "italic": false
255
+ },
256
+ "5": {
257
+ "bold": true,
258
+ "italic": false
259
+ },
260
+ "6": {
261
+ "bold": true,
262
+ "italic": false
242
263
  }
243
264
  },
244
265
  "inlineCode": {
@@ -257,7 +278,7 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
257
278
  md2docx -f report.md --style-config style-config.json
258
279
  ```
259
280
 
260
- 字段缺失表示继承底层样式;`true` 表示显式启用完整效果;`false` 表示显式关闭。当前只开放正文首行缩进、一级标题另起一页、行内代码背景和代码块外框。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
281
+ 字段缺失表示继承底层样式;`true` 表示显式启用完整效果;`false` 表示显式关闭。正文行距为浮点数倍数,例如 `1.5` 表示 1.5 倍行距;一级标题对齐只接受 `left`、`center`。当前还开放一至六级标题粗体,以及四至六级标题斜体。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
261
282
 
262
283
  转换时按以下规则选择输入:
263
284
 
@@ -131,13 +131,13 @@
131
131
  "description": "图片最大宽度(厘米)",
132
132
  "type": "number",
133
133
  "exclusiveMinimum": 0,
134
- "default": 15.5
134
+ "default": 12
135
135
  },
136
136
  "maxHeightCm": {
137
137
  "description": "图片最大高度(厘米)",
138
138
  "type": "number",
139
139
  "exclusiveMinimum": 0,
140
- "default": 22
140
+ "default": 12
141
141
  }
142
142
  },
143
143
  "required": ["enabled", "maxWidthCm", "maxHeightCm"]
@@ -3,11 +3,32 @@
3
3
  "schemaVersion": 1,
4
4
  "options": {
5
5
  "body": {
6
- "firstLineIndent": true
6
+ "firstLineIndent": true,
7
+ "lineSpacing": 1.5
7
8
  },
8
9
  "headings": {
9
10
  "1": {
10
- "startOnNewPage": false
11
+ "startOnNewPage": false,
12
+ "alignment": "left",
13
+ "bold": true
14
+ },
15
+ "2": {
16
+ "bold": true
17
+ },
18
+ "3": {
19
+ "bold": true
20
+ },
21
+ "4": {
22
+ "bold": true,
23
+ "italic": false
24
+ },
25
+ "5": {
26
+ "bold": true,
27
+ "italic": false
28
+ },
29
+ "6": {
30
+ "bold": true,
31
+ "italic": false
11
32
  }
12
33
  },
13
34
  "inlineCode": {
@@ -23,6 +23,11 @@
23
23
  "firstLineIndent": {
24
24
  "type": "boolean",
25
25
  "description": "正文是否使用预设的首行缩进"
26
+ },
27
+ "lineSpacing": {
28
+ "type": "number",
29
+ "exclusiveMinimum": 0,
30
+ "description": "正文行距倍数,如 1.5 表示 1.5 倍行距"
26
31
  }
27
32
  }
28
33
  },
@@ -37,8 +42,32 @@
37
42
  "startOnNewPage": {
38
43
  "type": "boolean",
39
44
  "description": "一级标题是否另起一页"
45
+ },
46
+ "alignment": {
47
+ "type": "string",
48
+ "enum": ["left", "center"],
49
+ "description": "一级标题左对齐或居中"
50
+ },
51
+ "bold": {
52
+ "type": "boolean",
53
+ "description": "一级标题是否加粗"
40
54
  }
41
55
  }
56
+ },
57
+ "2": {
58
+ "$ref": "#/$defs/headingBold"
59
+ },
60
+ "3": {
61
+ "$ref": "#/$defs/headingBold"
62
+ },
63
+ "4": {
64
+ "$ref": "#/$defs/headingBoldItalic"
65
+ },
66
+ "5": {
67
+ "$ref": "#/$defs/headingBoldItalic"
68
+ },
69
+ "6": {
70
+ "$ref": "#/$defs/headingBoldItalic"
42
71
  }
43
72
  }
44
73
  },
@@ -64,5 +93,31 @@
64
93
  }
65
94
  }
66
95
  }
96
+ },
97
+ "$defs": {
98
+ "headingBold": {
99
+ "type": "object",
100
+ "additionalProperties": false,
101
+ "properties": {
102
+ "bold": {
103
+ "type": "boolean",
104
+ "description": "标题是否加粗"
105
+ }
106
+ }
107
+ },
108
+ "headingBoldItalic": {
109
+ "type": "object",
110
+ "additionalProperties": false,
111
+ "properties": {
112
+ "bold": {
113
+ "type": "boolean",
114
+ "description": "标题是否加粗"
115
+ },
116
+ "italic": {
117
+ "type": "boolean",
118
+ "description": "标题是否斜体"
119
+ }
120
+ }
121
+ }
67
122
  }
68
123
  }
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.4.0",
128404
+ version: "2.5.0",
128405
128405
  description: "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
128406
128406
  keywords: [
128407
128407
  "converter",
@@ -147199,11 +147199,32 @@ var style_config_default = `{
147199
147199
  "schemaVersion": 1,
147200
147200
  "options": {
147201
147201
  "body": {
147202
- "firstLineIndent": true
147202
+ "firstLineIndent": true,
147203
+ "lineSpacing": 1.5
147203
147204
  },
147204
147205
  "headings": {
147205
147206
  "1": {
147206
- "startOnNewPage": false
147207
+ "startOnNewPage": false,
147208
+ "alignment": "left",
147209
+ "bold": true
147210
+ },
147211
+ "2": {
147212
+ "bold": true
147213
+ },
147214
+ "3": {
147215
+ "bold": true
147216
+ },
147217
+ "4": {
147218
+ "bold": true,
147219
+ "italic": false
147220
+ },
147221
+ "5": {
147222
+ "bold": true,
147223
+ "italic": false
147224
+ },
147225
+ "6": {
147226
+ "bold": true,
147227
+ "italic": false
147207
147228
  }
147208
147229
  },
147209
147230
  "inlineCode": {
@@ -147265,21 +147286,25 @@ function validateOptions(value2, path3) {
147265
147286
  rejectUnknown(options, ["body", "headings", "inlineCode", "codeBlock"], path3, "options");
147266
147287
  if (options.body !== undefined) {
147267
147288
  const body = expectRecord2(options.body, path3, "options.body");
147268
- rejectUnknown(body, ["firstLineIndent"], path3, "options.body");
147289
+ rejectUnknown(body, ["firstLineIndent", "lineSpacing"], path3, "options.body");
147269
147290
  if (body.firstLineIndent !== undefined) {
147270
147291
  expectBoolean2(body.firstLineIndent, path3, "options.body.firstLineIndent");
147271
147292
  }
147293
+ if (body.lineSpacing !== undefined) {
147294
+ if (typeof body.lineSpacing !== "number" || !Number.isFinite(body.lineSpacing) || body.lineSpacing <= 0) {
147295
+ invalidStyleConfig(path3, "options.body.lineSpacing", "必须是大于 0 的有限数字");
147296
+ }
147297
+ }
147272
147298
  }
147273
147299
  if (options.headings !== undefined) {
147274
147300
  const headings = expectRecord2(options.headings, path3, "options.headings");
147275
- rejectUnknown(headings, ["1"], path3, "options.headings");
147276
- if (headings["1"] !== undefined) {
147277
- const heading1 = expectRecord2(headings["1"], path3, 'options.headings["1"]');
147278
- rejectUnknown(heading1, ["startOnNewPage"], path3, 'options.headings["1"]');
147279
- if (heading1.startOnNewPage !== undefined) {
147280
- expectBoolean2(heading1.startOnNewPage, path3, 'options.headings["1"].startOnNewPage');
147281
- }
147282
- }
147301
+ rejectUnknown(headings, ["1", "2", "3", "4", "5", "6"], path3, "options.headings");
147302
+ validateHeading(headings, "1", ["startOnNewPage", "alignment", "bold"], path3);
147303
+ validateHeading(headings, "2", ["bold"], path3);
147304
+ validateHeading(headings, "3", ["bold"], path3);
147305
+ validateHeading(headings, "4", ["bold", "italic"], path3);
147306
+ validateHeading(headings, "5", ["bold", "italic"], path3);
147307
+ validateHeading(headings, "6", ["bold", "italic"], path3);
147283
147308
  }
147284
147309
  if (options.inlineCode !== undefined) {
147285
147310
  const inlineCode2 = expectRecord2(options.inlineCode, path3, "options.inlineCode");
@@ -147296,6 +147321,23 @@ function validateOptions(value2, path3) {
147296
147321
  }
147297
147322
  }
147298
147323
  }
147324
+ function validateHeading(headings, level, allowed, path3) {
147325
+ if (headings[level] === undefined)
147326
+ return;
147327
+ const field = `options.headings["${level}"]`;
147328
+ const heading2 = expectRecord2(headings[level], path3, field);
147329
+ rejectUnknown(heading2, allowed, path3, field);
147330
+ if (heading2.startOnNewPage !== undefined) {
147331
+ expectBoolean2(heading2.startOnNewPage, path3, `${field}.startOnNewPage`);
147332
+ }
147333
+ if (heading2.alignment !== undefined) {
147334
+ expectEnum(heading2.alignment, ["left", "center"], path3, `${field}.alignment`);
147335
+ }
147336
+ if (heading2.bold !== undefined)
147337
+ expectBoolean2(heading2.bold, path3, `${field}.bold`);
147338
+ if (heading2.italic !== undefined)
147339
+ expectBoolean2(heading2.italic, path3, `${field}.italic`);
147340
+ }
147299
147341
  function rejectUnknown(value2, allowed, path3, field) {
147300
147342
  const unknown2 = Object.keys(value2).find((key) => !allowed.includes(key));
147301
147343
  if (unknown2 !== undefined)
@@ -147317,6 +147359,11 @@ function expectString2(value2, path3, field) {
147317
147359
  if (typeof value2 !== "string")
147318
147360
  invalidStyleConfig(path3, field, "必须是字符串");
147319
147361
  }
147362
+ function expectEnum(value2, allowed, path3, field) {
147363
+ if (typeof value2 !== "string" || !allowed.includes(value2)) {
147364
+ invalidStyleConfig(path3, field, `必须是 ${allowed.join("、")} 之一`);
147365
+ }
147366
+ }
147320
147367
  function invalidStyleConfig(path3, field, reason) {
147321
147368
  throw new Error(`样式配置无效:${path3}
147322
147369
  位置:${field}
@@ -147324,6 +147371,7 @@ function invalidStyleConfig(path3, field, reason) {
147324
147371
  }
147325
147372
 
147326
147373
  // src/style/compiler.ts
147374
+ var HEADING_LEVELS = ["1", "2", "3", "4", "5", "6"];
147327
147375
  function resolveEffectiveStyles(sources, defaults) {
147328
147376
  const rawPath = sources.styleRawPath ?? defaults?.styleRawPath ?? materializeDefaultStyleRaw();
147329
147377
  const rawStyle = loadStyleRaw(rawPath);
@@ -147365,9 +147413,22 @@ function compileStyleConfig(rawStyle, config) {
147365
147413
  const firstLineIndent = options.body?.firstLineIndent;
147366
147414
  if (firstLineIndent !== undefined)
147367
147415
  applyBodyFirstLineIndent(styles, firstLineIndent);
147416
+ const lineSpacing = options.body?.lineSpacing;
147417
+ if (lineSpacing !== undefined)
147418
+ applyBodyLineSpacing(styles, lineSpacing);
147368
147419
  const startOnNewPage = options.headings?.["1"]?.startOnNewPage;
147369
147420
  if (startOnNewPage !== undefined)
147370
147421
  applyHeading1PageBreak(styles, startOnNewPage);
147422
+ const heading1Alignment = options.headings?.["1"]?.alignment;
147423
+ if (heading1Alignment !== undefined)
147424
+ applyHeading1Alignment(styles, heading1Alignment);
147425
+ for (const level of HEADING_LEVELS) {
147426
+ const heading2 = options.headings?.[level];
147427
+ if (heading2?.bold !== undefined)
147428
+ applyHeadingBold(styles, level, heading2.bold);
147429
+ if (heading2?.italic !== undefined)
147430
+ applyHeadingItalic(styles, level, heading2.italic);
147431
+ }
147371
147432
  const inlineCodeBackground = options.inlineCode?.background;
147372
147433
  if (inlineCodeBackground !== undefined) {
147373
147434
  applyInlineCodeBackground(styles, inlineCodeBackground);
@@ -147377,6 +147438,18 @@ function compileStyleConfig(rawStyle, config) {
147377
147438
  applyCodeBlockBorder(styles, codeBlockBorder);
147378
147439
  return styles;
147379
147440
  }
147441
+ function applyBodyLineSpacing(styles, multiplier) {
147442
+ const line = Math.round(multiplier * 240);
147443
+ for (const name2 of ["First Paragraph", "Body Text"]) {
147444
+ const style = findStyle(styles, "paragraphStyles", name2);
147445
+ const paragraph2 = ensureObject(style, "paragraph");
147446
+ paragraph2.spacing = {
147447
+ ...optionalObject(paragraph2.spacing),
147448
+ line,
147449
+ lineRule: "auto"
147450
+ };
147451
+ }
147452
+ }
147380
147453
  function applyBodyFirstLineIndent(styles, enabled) {
147381
147454
  const firstParagraph = findStyle(styles, "paragraphStyles", "First Paragraph");
147382
147455
  const bodyText = findStyle(styles, "paragraphStyles", "Body Text");
@@ -147393,6 +147466,22 @@ function applyHeading1PageBreak(styles, enabled) {
147393
147466
  const heading1 = findStyle(styles, "paragraphStyles", "heading 1");
147394
147467
  ensureObject(heading1, "paragraph").pageBreakBefore = enabled;
147395
147468
  }
147469
+ function applyHeading1Alignment(styles, alignment) {
147470
+ const heading1 = findStyle(styles, "paragraphStyles", "heading 1");
147471
+ ensureObject(heading1, "paragraph").alignment = alignment;
147472
+ }
147473
+ function applyHeadingBold(styles, level, enabled) {
147474
+ const heading2 = findStyle(styles, "paragraphStyles", `heading ${level}`);
147475
+ const run = ensureObject(heading2, "run");
147476
+ run.bold = enabled;
147477
+ run.boldComplexScript = enabled;
147478
+ }
147479
+ function applyHeadingItalic(styles, level, enabled) {
147480
+ const heading2 = findStyle(styles, "paragraphStyles", `heading ${level}`);
147481
+ const run = ensureObject(heading2, "run");
147482
+ run.italics = enabled;
147483
+ run.italicsComplexScript = enabled;
147484
+ }
147396
147485
  function applyInlineCodeBackground(styles, enabled) {
147397
147486
  const inlineCode2 = findStyle(styles, "characterStyles", "Inline Code");
147398
147487
  ensureObject(inlineCode2, "run").shading = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v1hz/md2docx",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
5
5
  "keywords": [
6
6
  "converter",