@v1hz/md2docx 2.2.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/dist/index.js CHANGED
@@ -103482,7 +103482,7 @@ Actual: ` + parser.attribValue);
103482
103482
  var options;
103483
103483
  var pureJsParser = true;
103484
103484
  var currentElement;
103485
- function validateOptions(userOptions) {
103485
+ function validateOptions2(userOptions) {
103486
103486
  options = helper.copyOptions(userOptions);
103487
103487
  helper.ensureFlagExists("ignoreDeclaration", options);
103488
103488
  helper.ensureFlagExists("ignoreInstruction", options);
@@ -103730,7 +103730,7 @@ Actual: ` + parser.attribValue);
103730
103730
  var parser = pureJsParser ? sax.parser(true, {}) : parser = new expat.Parser("UTF-8");
103731
103731
  var result = {};
103732
103732
  currentElement = result;
103733
- options = validateOptions(userOptions);
103733
+ options = validateOptions2(userOptions);
103734
103734
  if (pureJsParser) {
103735
103735
  parser.opt = { strictEntities: true };
103736
103736
  parser.onopentag = onStartElement;
@@ -103764,13 +103764,13 @@ Actual: ` + parser.attribValue);
103764
103764
  require_xml2json = /* @__PURE__ */ __commonJSMin((exports, module) => {
103765
103765
  var helper = require_options_helper();
103766
103766
  var xml2js = require_xml2js();
103767
- function validateOptions(userOptions) {
103767
+ function validateOptions2(userOptions) {
103768
103768
  var options = helper.copyOptions(userOptions);
103769
103769
  helper.ensureSpacesExists(options);
103770
103770
  return options;
103771
103771
  }
103772
103772
  module.exports = function(xml, userOptions) {
103773
- var options = validateOptions(userOptions), js = xml2js(xml, options), json, parentKey = "compact" in options && options.compact ? "_parent" : "parent";
103773
+ var options = validateOptions2(userOptions), js = xml2js(xml, options), json, parentKey = "compact" in options && options.compact ? "_parent" : "parent";
103774
103774
  if ("addParent" in options && options.addParent)
103775
103775
  json = JSON.stringify(js, function(k, v) {
103776
103776
  return k === parentKey ? "_" : v;
@@ -103784,7 +103784,7 @@ Actual: ` + parser.attribValue);
103784
103784
  var helper = require_options_helper();
103785
103785
  var isArray = require_array_helper().isArray;
103786
103786
  var currentElement, currentElementName;
103787
- function validateOptions(userOptions) {
103787
+ function validateOptions2(userOptions) {
103788
103788
  var options = helper.copyOptions(userOptions);
103789
103789
  helper.ensureFlagExists("ignoreDeclaration", options);
103790
103790
  helper.ensureFlagExists("ignoreInstruction", options);
@@ -104064,7 +104064,7 @@ Actual: ` + parser.attribValue);
104064
104064
  return xml.join("");
104065
104065
  }
104066
104066
  module.exports = function(js, options) {
104067
- options = validateOptions(options);
104067
+ options = validateOptions2(options);
104068
104068
  var xml = [];
104069
104069
  currentElement = js;
104070
104070
  currentElementName = "_root_";
@@ -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.2.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,8 +146235,8 @@ function Code(el)
146126
146235
  end
146127
146236
  return { Code = Code }`;
146128
146237
 
146129
- // config/style.json
146130
- var style_default = `{
146238
+ // config/style-raw.json
146239
+ var style_raw_default = `{
146131
146240
  "default": {
146132
146241
  "document": {
146133
146242
  "run": {
@@ -146688,7 +146797,7 @@ var style_default = `{
146688
146797
  "eastAsia": "zh-CN"
146689
146798
  },
146690
146799
  "shading": {
146691
- "val": "pct5",
146800
+ "type": "pct5",
146692
146801
  "color": "auto",
146693
146802
  "fill": "auto"
146694
146803
  }
@@ -146700,40 +146809,40 @@ var style_default = `{
146700
146809
  },
146701
146810
  "wordWrap": false,
146702
146811
  "shading": {
146703
- "val": "clear",
146812
+ "type": "clear",
146704
146813
  "color": "F3F4F4",
146705
146814
  "fill": "FFFFFF",
146706
146815
  "themeFill": "background1"
146707
146816
  },
146708
146817
  "border": {
146709
146818
  "top": {
146710
- "val": "single",
146819
+ "style": "single",
146711
146820
  "color": "BFBFBF",
146712
- "sz": 8,
146821
+ "size": 8,
146713
146822
  "space": 10,
146714
146823
  "themeColor": "background1",
146715
146824
  "themeShade": "BF"
146716
146825
  },
146717
146826
  "left": {
146718
- "val": "single",
146827
+ "style": "single",
146719
146828
  "color": "BFBFBF",
146720
- "sz": 8,
146829
+ "size": 8,
146721
146830
  "space": 10,
146722
146831
  "themeColor": "background1",
146723
146832
  "themeShade": "BF"
146724
146833
  },
146725
146834
  "bottom": {
146726
- "val": "single",
146835
+ "style": "single",
146727
146836
  "color": "BFBFBF",
146728
- "sz": 8,
146837
+ "size": 8,
146729
146838
  "space": 10,
146730
146839
  "themeColor": "background1",
146731
146840
  "themeShade": "BF"
146732
146841
  },
146733
146842
  "right": {
146734
- "val": "single",
146843
+ "style": "single",
146735
146844
  "color": "BFBFBF",
146736
- "sz": 8,
146845
+ "size": 8,
146737
146846
  "space": 10,
146738
146847
  "themeColor": "background1",
146739
146848
  "themeShade": "BF"
@@ -146879,7 +146988,7 @@ var style_default = `{
146879
146988
  "eastAsia": "zh-CN"
146880
146989
  },
146881
146990
  "shading": {
146882
- "val": "clear",
146991
+ "type": "clear",
146883
146992
  "color": "F3F4F4",
146884
146993
  "fill": "FFFFFF",
146885
146994
  "themeFill": "background1"
@@ -146973,16 +147082,16 @@ var style_default = `{
146973
147082
  "eastAsia": "zh-CN"
146974
147083
  },
146975
147084
  "shading": {
146976
- "val": "clear",
147085
+ "type": "clear",
146977
147086
  "color": "F3F4F4",
146978
147087
  "fill": "F2F2F2",
146979
147088
  "themeFill": "background1",
146980
147089
  "themeFillShade": "F2"
146981
147090
  },
146982
147091
  "border": {
146983
- "val": "none",
147092
+ "style": "none",
146984
147093
  "color": "auto",
146985
- "sz": 0,
147094
+ "size": 0,
146986
147095
  "space": 0
146987
147096
  }
146988
147097
  }
@@ -147084,9 +147193,33 @@ var style_default = `{
147084
147193
  }
147085
147194
  `;
147086
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
+
147087
147219
  // src/resources.ts
147088
147220
  var DEFAULT_CONFIG_TEXT = config_default;
147089
- var DEFAULT_STYLE_TEXT = style_default;
147221
+ var DEFAULT_STYLE_CONFIG_TEXT = style_config_default;
147222
+ var DEFAULT_STYLE_RAW_TEXT = style_raw_default;
147090
147223
  function materializeResource(name2, content3) {
147091
147224
  const directory = join4(TMP_DIR, "resources");
147092
147225
  const path3 = join4(directory, name2);
@@ -147099,21 +147232,238 @@ function materializeResource(name2, content3) {
147099
147232
  function materializeDefaultConfig() {
147100
147233
  return materializeResource("config.json", DEFAULT_CONFIG_TEXT);
147101
147234
  }
147102
- function materializeDefaultStyle() {
147103
- return materializeResource("style.json", DEFAULT_STYLE_TEXT);
147235
+ function materializeDefaultStyleRaw() {
147236
+ return materializeResource("style-raw.json", DEFAULT_STYLE_RAW_TEXT);
147237
+ }
147238
+ function materializeDefaultStyleConfig() {
147239
+ return materializeResource("style-config.json", DEFAULT_STYLE_CONFIG_TEXT);
147104
147240
  }
147105
- function materializeLuaFilter() {
147241
+ function materializeInlineCodeFilter() {
147106
147242
  return materializeResource("add-inline-code.lua", add_inline_code_default);
147107
147243
  }
147244
+ function materializeImageSizeFilter() {
147245
+ return materializeResource("limit-image-size.lua", limit_image_size_default);
147246
+ }
147247
+
147248
+ // src/style/compiler.ts
147249
+ import { readFileSync as readFileSync2 } from "node:fs";
147250
+
147251
+ // src/style/config.ts
147252
+ function validateStyleConfig(value2, path3) {
147253
+ const root2 = expectRecord2(value2, path3, "样式配置");
147254
+ rejectUnknown(root2, ["$schema", "schemaVersion", "options"], path3, "样式配置");
147255
+ if (root2.$schema !== undefined)
147256
+ expectString2(root2.$schema, path3, "$schema");
147257
+ if (root2.schemaVersion !== 1)
147258
+ invalidStyleConfig(path3, "schemaVersion", "目前只支持版本 1");
147259
+ if (root2.options !== undefined)
147260
+ validateOptions(root2.options, path3);
147261
+ return root2;
147262
+ }
147263
+ function validateOptions(value2, path3) {
147264
+ const options = expectRecord2(value2, path3, "options");
147265
+ rejectUnknown(options, ["body", "headings", "inlineCode", "codeBlock"], path3, "options");
147266
+ if (options.body !== undefined) {
147267
+ const body = expectRecord2(options.body, path3, "options.body");
147268
+ rejectUnknown(body, ["firstLineIndent"], path3, "options.body");
147269
+ if (body.firstLineIndent !== undefined) {
147270
+ expectBoolean2(body.firstLineIndent, path3, "options.body.firstLineIndent");
147271
+ }
147272
+ }
147273
+ if (options.headings !== undefined) {
147274
+ 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
+ }
147283
+ }
147284
+ if (options.inlineCode !== undefined) {
147285
+ const inlineCode2 = expectRecord2(options.inlineCode, path3, "options.inlineCode");
147286
+ rejectUnknown(inlineCode2, ["background"], path3, "options.inlineCode");
147287
+ if (inlineCode2.background !== undefined) {
147288
+ expectBoolean2(inlineCode2.background, path3, "options.inlineCode.background");
147289
+ }
147290
+ }
147291
+ if (options.codeBlock !== undefined) {
147292
+ const codeBlock = expectRecord2(options.codeBlock, path3, "options.codeBlock");
147293
+ rejectUnknown(codeBlock, ["border"], path3, "options.codeBlock");
147294
+ if (codeBlock.border !== undefined) {
147295
+ expectBoolean2(codeBlock.border, path3, "options.codeBlock.border");
147296
+ }
147297
+ }
147298
+ }
147299
+ function rejectUnknown(value2, allowed, path3, field) {
147300
+ const unknown2 = Object.keys(value2).find((key) => !allowed.includes(key));
147301
+ if (unknown2 !== undefined)
147302
+ invalidStyleConfig(path3, `${field}.${unknown2}`, "不是支持的配置项");
147303
+ }
147304
+ function expectRecord2(value2, path3, field) {
147305
+ if (!isRecord(value2))
147306
+ invalidStyleConfig(path3, field, "必须是对象");
147307
+ return value2;
147308
+ }
147309
+ function isRecord(value2) {
147310
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
147311
+ }
147312
+ function expectBoolean2(value2, path3, field) {
147313
+ if (typeof value2 !== "boolean")
147314
+ invalidStyleConfig(path3, field, "必须是 boolean");
147315
+ }
147316
+ function expectString2(value2, path3, field) {
147317
+ if (typeof value2 !== "string")
147318
+ invalidStyleConfig(path3, field, "必须是字符串");
147319
+ }
147320
+ function invalidStyleConfig(path3, field, reason) {
147321
+ throw new Error(`样式配置无效:${path3}
147322
+ 位置:${field}
147323
+ 原因:${reason}`);
147324
+ }
147325
+
147326
+ // src/style/compiler.ts
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);
147362
+ const options = config.options;
147363
+ if (!options)
147364
+ return styles;
147365
+ const firstLineIndent = options.body?.firstLineIndent;
147366
+ if (firstLineIndent !== undefined)
147367
+ applyBodyFirstLineIndent(styles, firstLineIndent);
147368
+ const startOnNewPage = options.headings?.["1"]?.startOnNewPage;
147369
+ if (startOnNewPage !== undefined)
147370
+ applyHeading1PageBreak(styles, startOnNewPage);
147371
+ const inlineCodeBackground = options.inlineCode?.background;
147372
+ if (inlineCodeBackground !== undefined) {
147373
+ applyInlineCodeBackground(styles, inlineCodeBackground);
147374
+ }
147375
+ const codeBlockBorder = options.codeBlock?.border;
147376
+ if (codeBlockBorder !== undefined)
147377
+ applyCodeBlockBorder(styles, codeBlockBorder);
147378
+ return styles;
147379
+ }
147380
+ function applyBodyFirstLineIndent(styles, enabled) {
147381
+ const firstParagraph = findStyle(styles, "paragraphStyles", "First Paragraph");
147382
+ const bodyText = findStyle(styles, "paragraphStyles", "Body Text");
147383
+ for (const style of [firstParagraph, bodyText]) {
147384
+ const paragraph2 = ensureObject(style, "paragraph");
147385
+ paragraph2.indent = {
147386
+ ...optionalObject(paragraph2.indent),
147387
+ firstLine: enabled ? 200 : 0,
147388
+ firstLineChars: enabled ? 200 : 0
147389
+ };
147390
+ }
147391
+ }
147392
+ function applyHeading1PageBreak(styles, enabled) {
147393
+ const heading1 = findStyle(styles, "paragraphStyles", "heading 1");
147394
+ ensureObject(heading1, "paragraph").pageBreakBefore = enabled;
147395
+ }
147396
+ function applyInlineCodeBackground(styles, enabled) {
147397
+ const inlineCode2 = findStyle(styles, "characterStyles", "Inline Code");
147398
+ ensureObject(inlineCode2, "run").shading = {
147399
+ type: "clear",
147400
+ color: enabled ? "F3F4F4" : "auto",
147401
+ fill: enabled ? "F2F2F2" : "auto"
147402
+ };
147403
+ }
147404
+ function applyCodeBlockBorder(styles, enabled) {
147405
+ const sourceCode = findStyle(styles, "paragraphStyles", "Source Code");
147406
+ const border = () => ({
147407
+ style: enabled ? "single" : "none",
147408
+ color: enabled ? "BFBFBF" : "auto",
147409
+ size: enabled ? 8 : 0,
147410
+ space: enabled ? 10 : 0
147411
+ });
147412
+ ensureObject(sourceCode, "paragraph").border = {
147413
+ top: border(),
147414
+ left: border(),
147415
+ bottom: border(),
147416
+ right: border()
147417
+ };
147418
+ }
147419
+ function findStyle(styles, collectionName, name2) {
147420
+ const collection = styles[collectionName];
147421
+ if (!Array.isArray(collection)) {
147422
+ throw new Error(`底层样式缺少 ${collectionName},无法应用语义化样式配置`);
147423
+ }
147424
+ const normalizedName = name2.toLowerCase();
147425
+ const style = collection.find((entry) => isRecord2(entry) && typeof entry.name === "string" && entry.name.toLowerCase() === normalizedName);
147426
+ if (!style)
147427
+ throw new Error(`底层样式缺少“${name2}”,无法应用语义化样式配置`);
147428
+ return style;
147429
+ }
147430
+ function ensureObject(parent, key) {
147431
+ const current = parent[key];
147432
+ if (isRecord2(current))
147433
+ return current;
147434
+ const created = {};
147435
+ parent[key] = created;
147436
+ return created;
147437
+ }
147438
+ function optionalObject(value2) {
147439
+ return isRecord2(value2) ? value2 : {};
147440
+ }
147441
+ function parseJson(text5, path3, kind) {
147442
+ try {
147443
+ return JSON.parse(text5);
147444
+ } catch (error) {
147445
+ const message = error instanceof Error ? error.message : String(error);
147446
+ throw new Error(`${kind}不是有效的 JSON:${path3}
147447
+ ${message}`);
147448
+ }
147449
+ }
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));
147455
+ }
147456
+ function isRecord2(value2) {
147457
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
147458
+ }
147108
147459
 
147109
147460
  // src/style/generate.ts
147110
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync2 } from "node:fs";
147461
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
147111
147462
  import { writeFile } from "node:fs/promises";
147112
147463
  import { createHash as createHash2 } from "node:crypto";
147113
147464
  import { dirname as dirname4 } from "path";
147114
- async function generateTemplateDocx(styleJsonPath, outputPath) {
147465
+ async function generateTemplateDocxFromStyles(raw, outputPath) {
147115
147466
  const { Document: Document2, Packer: Packer3 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
147116
- const raw = JSON.parse(readFileSync2(styleJsonPath, "utf-8"));
147117
147467
  const tableStylesXml = raw.tableStylesXml;
147118
147468
  const { tableStylesXml: _, ...styles } = raw;
147119
147469
  const doc = new Document2({
@@ -147138,14 +147488,16 @@ async function generateTemplateDocx(styleJsonPath, outputPath) {
147138
147488
  await writeFile(outputPath, buf);
147139
147489
  }
147140
147490
  var generatePromises = new Map;
147141
- async function ensureTemplateDocx(styleJsonPath) {
147142
- const raw = readFileSync2(styleJsonPath);
147143
- const hash = createHash2("sha256").update(raw).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);
147144
147496
  const outputPath = styleTemplateDocx(hash);
147145
147497
  if (!existsSync5(outputPath)) {
147146
147498
  let promise = generatePromises.get(outputPath);
147147
147499
  if (!promise) {
147148
- promise = generateTemplateDocx(styleJsonPath, outputPath).catch((error) => {
147500
+ promise = generateTemplateDocxFromStyles(rawStyle, outputPath).catch((error) => {
147149
147501
  generatePromises.delete(outputPath);
147150
147502
  throw error;
147151
147503
  });
@@ -147162,25 +147514,29 @@ async function convertMarkdown(options) {
147162
147514
  throw new Error("缺少必填参数:--file");
147163
147515
  const input = resolveInputPath(options.file, [".md", ".markdown"], "Markdown 文件");
147164
147516
  const configPath = options.config ? resolveInputPath(options.config, [".json"], "配置文件") : materializeDefaultConfig();
147165
- const stylePath = options.style ? resolveInputPath(options.style, [".json"], "样式文件") : materializeDefaultStyle();
147517
+ const styleRawPath = options.styleRaw ? resolveInputPath(options.styleRaw, [".json"], "底层样式文件") : undefined;
147518
+ const styleConfigPath = options.styleConfig ? resolveInputPath(options.styleConfig, [".json"], "语义化样式配置") : undefined;
147166
147519
  const baseName = parse4(input).name;
147167
147520
  const output = resolveOutputPath(options.output, `${baseName}.docx`, [".docx"], "DOCX 输出文件");
147168
- prepareOutput(output, options.force ?? false);
147521
+ prepareOutput(output);
147169
147522
  const config = await loadConfig(configPath);
147170
147523
  const outDir = preprocessDir(input);
147171
147524
  mkdirSync5(outDir, { recursive: true });
147172
147525
  const formatted = await preprocess2(input, config, outDir);
147173
147526
  const markdownOutput = formattedMdPath(input);
147174
147527
  writeFileSync3(markdownOutput, formatted, "utf-8");
147175
- const templatePath = await ensureTemplateDocx(stylePath);
147176
- 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()) : [];
147177
147532
  const pandocArgs = [
147178
147533
  markdownOutput,
147179
147534
  "-o",
147180
147535
  output,
147181
147536
  ...buildPandocResourcePathArgs(input),
147182
147537
  `--reference-doc=${templatePath}`,
147183
- ...existsSync6(luaFilter) ? [`--lua-filter=${luaFilter}`] : []
147538
+ ...existsSync6(inlineCodeFilter) ? [`--lua-filter=${inlineCodeFilter}`] : [],
147539
+ ...imageSizeArgs
147184
147540
  ];
147185
147541
  const { exitCode, stderr } = await runProcess("pandoc", pandocArgs);
147186
147542
  if (exitCode !== 0) {
@@ -147188,6 +147544,15 @@ async function convertMarkdown(options) {
147188
147544
  }
147189
147545
  console.log(`已生成:${output}`);
147190
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
+ }
147191
147556
  function buildPandocResourcePathArgs(inputPath, workingDirectory = process.cwd()) {
147192
147557
  const workingDir = resolve5(workingDirectory);
147193
147558
  const sourceDir = dirname5(resolve5(inputPath));
@@ -147299,31 +147664,31 @@ function mapShd(shd) {
147299
147664
  if (!shd)
147300
147665
  return;
147301
147666
  const sh = {};
147302
- for (const a of [
147303
- "val",
147304
- "color",
147305
- "fill",
147306
- "themeColor",
147307
- "themeTint",
147308
- "themeFill",
147309
- "themeFillShade",
147310
- "themeShade"
147311
- ]) {
147667
+ for (const a of ["color", "fill"]) {
147312
147668
  const v = wAttr(shd, a);
147313
147669
  if (v !== null)
147314
147670
  sh[a] = v;
147315
147671
  }
147672
+ const type = wAttr(shd, "val");
147673
+ if (type !== null)
147674
+ sh.type = type;
147316
147675
  return Object.keys(sh).length > 0 ? sh : undefined;
147317
147676
  }
147318
147677
  function mapBorder(bdr) {
147319
147678
  if (!bdr)
147320
147679
  return;
147321
147680
  const b = {};
147322
- for (const a of ["val", "color", "sz", "space", "themeColor", "themeShade"]) {
147681
+ for (const a of ["color", "space"]) {
147323
147682
  const v = wAttr(bdr, a);
147324
147683
  if (v !== null)
147325
147684
  b[a] = /^\d+$/.test(v) ? Number(v) : v;
147326
147685
  }
147686
+ const style = wAttr(bdr, "val");
147687
+ if (style !== null)
147688
+ b.style = style;
147689
+ const size = wAttr(bdr, "sz");
147690
+ if (size !== null)
147691
+ b.size = /^\d+$/.test(size) ? Number(size) : size;
147327
147692
  return Object.keys(b).length > 0 ? b : undefined;
147328
147693
  }
147329
147694
  function mapRunProps(rPr) {
@@ -147831,23 +148196,32 @@ function extractStylesFromDocx(docxPath) {
147831
148196
  // src/commands/export.ts
147832
148197
  async function exportConfig(options) {
147833
148198
  const output = resolveOutputPath(options.output, "config.json", [".json"], "配置输出文件");
147834
- prepareOutput(output, options.force ?? false);
148199
+ prepareOutput(output);
147835
148200
  writeFileSync4(output, DEFAULT_CONFIG_TEXT, "utf-8");
147836
148201
  console.log(`已导出配置:${output}`);
147837
148202
  }
147838
- async function exportStyle(options) {
148203
+ async function exportStyleRaw(options) {
147839
148204
  const source = options.file ? resolveInputPath(options.file, [".docx"], "DOCX 文件") : undefined;
147840
- const defaultName = source ? `${parse5(source).name}_style.json` : "style.json";
147841
- const output = resolveOutputPath(options.output, defaultName, [".json"], "样式输出文件");
147842
- prepareOutput(output, options.force ?? false);
148205
+ const defaultName = defaultStyleRawOutputName(source);
148206
+ const output = resolveOutputPath(options.output, defaultName, [".json"], "底层样式输出文件");
148207
+ prepareOutput(output);
147843
148208
  if (source) {
147844
148209
  const styles = extractStylesFromDocx(source);
147845
148210
  writeFileSync4(output, `${JSON.stringify(styles, null, 2)}
147846
148211
  `, "utf-8");
147847
148212
  } else {
147848
- writeFileSync4(output, DEFAULT_STYLE_TEXT, "utf-8");
148213
+ writeFileSync4(output, DEFAULT_STYLE_RAW_TEXT, "utf-8");
147849
148214
  }
147850
- 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}`);
147851
148225
  }
147852
148226
 
147853
148227
  // src/commands/format.ts
@@ -147858,17 +148232,12 @@ async function formatMarkdown(options) {
147858
148232
  const configPath = options.config ? resolveInputPath(options.config, [".json"], "配置文件") : materializeDefaultConfig();
147859
148233
  const defaultName = `${parse6(input).name}_formatted.md`;
147860
148234
  const output = resolveOutputPath(options.output, defaultName, [".md", ".markdown"], "Markdown 输出文件");
147861
- const force = options.force ?? false;
147862
- prepareOutput(output, force);
148235
+ prepareOutput(output);
147863
148236
  const config = await loadConfig(configPath);
147864
148237
  const outputName = parse6(output).name;
147865
148238
  const assetsName = `${outputName}_assets`;
147866
148239
  const assetsDir = resolve6(dirname6(output), assetsName);
147867
148240
  if (config.renderMermaid.enabled && existsSync7(assetsDir)) {
147868
- if (!force) {
147869
- throw new Error(`资源目录已存在:${assetsDir}
147870
- 使用 --force 覆盖现有目录`);
147871
- }
147872
148241
  const parent = resolve6(dirname6(output));
147873
148242
  if (dirname6(assetsDir) !== parent)
147874
148243
  throw new Error(`拒绝清理非输出目录:${assetsDir}`);
@@ -147885,7 +148254,8 @@ async function run(args) {
147885
148254
  convert: convertMarkdown,
147886
148255
  format: formatMarkdown,
147887
148256
  exportConfig,
147888
- exportStyle,
148257
+ exportStyleRaw,
148258
+ exportStyleConfig,
147889
148259
  clean: cleanIntermediateFiles
147890
148260
  });
147891
148261
  program2.exitOverride();