@cj-tech-master/excelts 9.6.1 → 10.0.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.
Files changed (207) hide show
  1. package/README.md +18 -3
  2. package/README_zh.md +18 -3
  3. package/dist/browser/modules/excel/cell.d.ts +4 -0
  4. package/dist/browser/modules/excel/note.js +5 -1
  5. package/dist/browser/modules/excel/row.js +35 -2
  6. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
  8. package/dist/browser/modules/excel/types.d.ts +81 -0
  9. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
  11. package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
  12. package/dist/browser/modules/excel/workbook.browser.js +32 -2
  13. package/dist/browser/modules/excel/worksheet.d.ts +31 -1
  14. package/dist/browser/modules/excel/worksheet.js +83 -0
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  16. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  17. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  18. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  28. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  30. package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
  31. package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
  33. package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
  34. package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
  35. package/dist/browser/modules/pdf/font/font-manager.js +35 -18
  36. package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
  37. package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
  38. package/dist/browser/modules/word/advanced/field-engine.js +45 -20
  39. package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
  40. package/dist/browser/modules/word/advanced/glossary.js +8 -9
  41. package/dist/browser/modules/word/advanced/math-convert.js +94 -12
  42. package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
  43. package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
  44. package/dist/browser/modules/word/advanced/style-map.js +31 -10
  45. package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
  46. package/dist/browser/modules/word/builder/run-builders.js +7 -1
  47. package/dist/browser/modules/word/constants.d.ts +4 -0
  48. package/dist/browser/modules/word/constants.js +5 -1
  49. package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
  50. package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
  51. package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
  52. package/dist/browser/modules/word/convert/html/html-import.js +167 -14
  53. package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
  54. package/dist/browser/modules/word/convert/html/html.js +1 -1
  55. package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  56. package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
  57. package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
  58. package/dist/browser/modules/word/convert/odt/odt.js +407 -56
  59. package/dist/browser/modules/word/html.d.ts +2 -2
  60. package/dist/browser/modules/word/html.js +1 -1
  61. package/dist/browser/modules/word/index.base.d.ts +3 -3
  62. package/dist/browser/modules/word/index.base.js +1 -1
  63. package/dist/browser/modules/word/layout/layout-full.js +326 -19
  64. package/dist/browser/modules/word/layout/render-page.js +35 -8
  65. package/dist/browser/modules/word/markdown.d.ts +1 -1
  66. package/dist/browser/modules/word/query/compat.d.ts +10 -2
  67. package/dist/browser/modules/word/query/compat.js +29 -21
  68. package/dist/browser/modules/word/reader/docx-reader.js +105 -2
  69. package/dist/browser/modules/word/reader/math-parser.js +8 -2
  70. package/dist/browser/modules/word/security/cfb-reader.js +5 -5
  71. package/dist/browser/modules/word/types.d.ts +96 -1
  72. package/dist/browser/modules/word/writer/docx-packager.js +108 -2
  73. package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
  74. package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
  75. package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
  76. package/dist/browser/modules/word/writer/math-writer.js +7 -2
  77. package/dist/browser/utils/font-metrics.d.ts +8 -0
  78. package/dist/browser/utils/font-metrics.js +43 -0
  79. package/dist/browser/utils/theme-colors.js +4 -1
  80. package/dist/cjs/modules/excel/note.js +5 -1
  81. package/dist/cjs/modules/excel/row.js +35 -2
  82. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
  83. package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
  84. package/dist/cjs/modules/excel/workbook.browser.js +31 -1
  85. package/dist/cjs/modules/excel/worksheet.js +83 -0
  86. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  87. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  88. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  89. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  90. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  91. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  92. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  93. package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
  94. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  95. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  96. package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
  97. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
  98. package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
  99. package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
  100. package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
  101. package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
  102. package/dist/cjs/modules/word/advanced/glossary.js +8 -9
  103. package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
  104. package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
  105. package/dist/cjs/modules/word/advanced/style-map.js +31 -10
  106. package/dist/cjs/modules/word/builder/run-builders.js +7 -1
  107. package/dist/cjs/modules/word/constants.js +5 -1
  108. package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
  109. package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
  110. package/dist/cjs/modules/word/convert/html/html.js +2 -1
  111. package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
  112. package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
  113. package/dist/cjs/modules/word/html.js +2 -1
  114. package/dist/cjs/modules/word/index.base.js +4 -3
  115. package/dist/cjs/modules/word/layout/layout-full.js +325 -18
  116. package/dist/cjs/modules/word/layout/render-page.js +35 -8
  117. package/dist/cjs/modules/word/query/compat.js +29 -21
  118. package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
  119. package/dist/cjs/modules/word/reader/math-parser.js +8 -2
  120. package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
  121. package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
  122. package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
  123. package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
  124. package/dist/cjs/modules/word/writer/math-writer.js +7 -2
  125. package/dist/cjs/utils/font-metrics.js +44 -0
  126. package/dist/cjs/utils/theme-colors.js +4 -1
  127. package/dist/esm/modules/excel/note.js +5 -1
  128. package/dist/esm/modules/excel/row.js +35 -2
  129. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
  130. package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
  131. package/dist/esm/modules/excel/workbook.browser.js +32 -2
  132. package/dist/esm/modules/excel/worksheet.js +83 -0
  133. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  134. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  135. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  136. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  137. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  138. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  139. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  140. package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  141. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  142. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  143. package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
  144. package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
  145. package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
  146. package/dist/esm/modules/pdf/font/font-manager.js +35 -18
  147. package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
  148. package/dist/esm/modules/word/advanced/field-engine.js +45 -20
  149. package/dist/esm/modules/word/advanced/glossary.js +8 -9
  150. package/dist/esm/modules/word/advanced/math-convert.js +94 -12
  151. package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
  152. package/dist/esm/modules/word/advanced/style-map.js +31 -10
  153. package/dist/esm/modules/word/builder/run-builders.js +7 -1
  154. package/dist/esm/modules/word/constants.js +5 -1
  155. package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
  156. package/dist/esm/modules/word/convert/html/html-import.js +167 -14
  157. package/dist/esm/modules/word/convert/html/html.js +1 -1
  158. package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
  159. package/dist/esm/modules/word/convert/odt/odt.js +407 -56
  160. package/dist/esm/modules/word/html.js +1 -1
  161. package/dist/esm/modules/word/index.base.js +1 -1
  162. package/dist/esm/modules/word/layout/layout-full.js +326 -19
  163. package/dist/esm/modules/word/layout/render-page.js +35 -8
  164. package/dist/esm/modules/word/query/compat.js +29 -21
  165. package/dist/esm/modules/word/reader/docx-reader.js +105 -2
  166. package/dist/esm/modules/word/reader/math-parser.js +8 -2
  167. package/dist/esm/modules/word/security/cfb-reader.js +5 -5
  168. package/dist/esm/modules/word/writer/docx-packager.js +108 -2
  169. package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
  170. package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
  171. package/dist/esm/modules/word/writer/math-writer.js +7 -2
  172. package/dist/esm/utils/font-metrics.js +43 -0
  173. package/dist/esm/utils/theme-colors.js +4 -1
  174. package/dist/iife/excelts.iife.js +496 -59
  175. package/dist/iife/excelts.iife.js.map +1 -1
  176. package/dist/iife/excelts.iife.min.js +39 -39
  177. package/dist/types/modules/excel/cell.d.ts +4 -0
  178. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  179. package/dist/types/modules/excel/types.d.ts +81 -0
  180. package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
  181. package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
  182. package/dist/types/modules/excel/worksheet.d.ts +31 -1
  183. package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  184. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  185. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  186. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  187. package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  188. package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
  189. package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
  190. package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
  191. package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
  192. package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
  193. package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
  194. package/dist/types/modules/word/constants.d.ts +4 -0
  195. package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
  196. package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
  197. package/dist/types/modules/word/convert/html/html.d.ts +2 -2
  198. package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  199. package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
  200. package/dist/types/modules/word/html.d.ts +2 -2
  201. package/dist/types/modules/word/index.base.d.ts +3 -3
  202. package/dist/types/modules/word/markdown.d.ts +1 -1
  203. package/dist/types/modules/word/query/compat.d.ts +10 -2
  204. package/dist/types/modules/word/types.d.ts +96 -1
  205. package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
  206. package/dist/types/utils/font-metrics.d.ts +8 -0
  207. package/package.json +3 -1
@@ -25,46 +25,60 @@ const text_utils_1 = require("../../core/text-utils");
25
25
  /**
26
26
  * Convert a Markdown string into a complete DocxDocument.
27
27
  *
28
+ * Supports the full GFM feature set including inline images (embedded via the
29
+ * `resolveImage` callback) and footnotes (`[^id]` references with `[^id]: …`
30
+ * definitions). Because image resolution and document packaging are inherently
31
+ * asynchronous, this function is async.
32
+ *
28
33
  * @param markdown - The GFM Markdown string.
29
34
  * @param options - Optional conversion settings.
30
- * @returns A DocxDocument ready to be packaged.
35
+ * @returns A Promise resolving to a DocxDocument ready to be packaged.
31
36
  */
32
- function markdownToDocx(markdown, options) {
33
- const { body, state } = markdownToDocxBodyInternal(markdown, options);
37
+ async function markdownToDocx(markdown, options) {
38
+ const { body, state } = await markdownToDocxBodyInternal(markdown, options);
34
39
  return {
35
40
  body,
36
41
  styles: defaultMarkdownStyles(),
37
42
  abstractNumberings: state.abstractNumberings,
38
- numberingInstances: state.numberingInstances
43
+ numberingInstances: state.numberingInstances,
44
+ ...(state.footnotes.length > 0 ? { footnotes: state.footnotes } : {}),
45
+ ...(state.images.length > 0 ? { images: state.images } : {})
39
46
  };
40
47
  }
41
48
  /**
42
- * Convert a Markdown string into an array of DOCX body content blocks.
49
+ * Convert a Markdown string into DOCX body content plus the supporting
50
+ * document-level definitions it references.
43
51
  *
44
- * **Caveat — body content is not self-contained.**
45
- * - **Lists** (bullet / numbered / task) reference numbering ids that
46
- * live in document-level `abstractNumberings` + `numberingInstances`,
47
- * which this helper does NOT return.
48
- * - **Block quotes** reference the named `Quote` style.
49
- * - **Code blocks** reference the named code styles.
52
+ * **Caveat — body content is not self-contained.** The returned `body` may
53
+ * reference:
54
+ * - **Numbering** (`abstractNumberings` / `numberingInstances`) — used by
55
+ * bullet / numbered / task lists.
56
+ * - **Footnotes** (`footnotes`) referenced by footnote-reference runs.
57
+ * - **Images** (`images`) referenced by inline image runs.
58
+ * - The named `Quote` / `CodeBlock` styles (for block quotes / code blocks).
50
59
  *
51
- * Splicing markdown that uses any of these constructs into a document that
52
- * lacks the matching numbering / styles yields invalid OOXML. Either keep
53
- * the input flat (paragraphs + headings + inline formatting) before
54
- * splicing, or use the higher-level {@link markdownToDocx} which returns a
55
- * complete `DocxDocument` with the supporting definitions populated.
60
+ * Splice the relevant arrays into your host document alongside the body, or
61
+ * use the higher-level {@link markdownToDocx} which returns a complete
62
+ * `DocxDocument` with everything populated.
56
63
  *
57
64
  * @param markdown - The GFM Markdown string.
58
65
  * @param options - Optional conversion settings.
59
- * @returns Array of BodyContent blocks (no numbering / styles attached).
66
+ * @returns A Promise resolving to the body and its supporting definitions.
60
67
  */
61
- function markdownToDocxBody(markdown, options) {
62
- return markdownToDocxBodyInternal(markdown, options).body;
68
+ async function markdownToDocxBody(markdown, options) {
69
+ const { body, state } = await markdownToDocxBodyInternal(markdown, options);
70
+ return {
71
+ body,
72
+ abstractNumberings: state.abstractNumberings,
73
+ numberingInstances: state.numberingInstances,
74
+ footnotes: state.footnotes,
75
+ images: state.images
76
+ };
63
77
  }
64
78
  /**
65
79
  * Internal implementation: converts markdown and returns both body and state.
66
80
  */
67
- function markdownToDocxBodyInternal(markdown, options) {
81
+ async function markdownToDocxBodyInternal(markdown, options) {
68
82
  const state = createState();
69
83
  const opts = {
70
84
  codeFont: "Courier New",
@@ -72,8 +86,14 @@ function markdownToDocxBodyInternal(markdown, options) {
72
86
  ...options
73
87
  };
74
88
  const lines = markdown.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
75
- const blocks = parseMarkdownBlocks(lines, 0, lines.length);
76
- const body = convertBlocks(blocks, opts, state);
89
+ // First pass: extract footnote definitions (`[^id]: …`) so references can
90
+ // resolve regardless of definition order, then parse the remaining blocks.
91
+ const { lines: contentLines, definitions } = extractFootnoteDefinitions(lines);
92
+ state.footnoteDefinitions = definitions;
93
+ const blocks = parseMarkdownBlocks(contentLines, 0, contentLines.length);
94
+ const body = await convertBlocks(blocks, opts, state);
95
+ // Emit footnote definitions that were actually referenced, in reference order.
96
+ await finalizeFootnotes(state, opts);
77
97
  return { body, state };
78
98
  }
79
99
  function createState() {
@@ -82,10 +102,106 @@ function createState() {
82
102
  numberingInstances: [],
83
103
  nextNumId: 1,
84
104
  bulletNumId: undefined,
85
- orderedNumId: undefined
105
+ orderedNumId: undefined,
106
+ footnotes: [],
107
+ images: [],
108
+ footnoteDefinitions: new Map(),
109
+ footnoteIds: new Map(),
110
+ footnoteOrder: [],
111
+ nextFootnoteId: 1,
112
+ nextImageId: 1,
113
+ nextDrawingId: 1
86
114
  };
87
115
  }
88
116
  // =============================================================================
117
+ // Footnote definition extraction (first pass)
118
+ // =============================================================================
119
+ /**
120
+ * Matches a footnote definition line: `[^label]: content`.
121
+ * Continuation lines (indented under a definition) are folded in.
122
+ */
123
+ const FOOTNOTE_DEF_RE = /^\[\^([^\]]+)\]:\s?(.*)$/;
124
+ /**
125
+ * Strip footnote definitions (`[^id]: …`) out of the line stream before block
126
+ * parsing, returning the remaining content lines plus a label→text map. A
127
+ * definition may span continuation lines that are indented by at least one
128
+ * space; those are joined with a single space.
129
+ */
130
+ function extractFootnoteDefinitions(lines) {
131
+ const definitions = new Map();
132
+ const contentLines = [];
133
+ let i = 0;
134
+ while (i < lines.length) {
135
+ // Preserve fenced code blocks verbatim — a `[^id]:`-looking line inside a
136
+ // code fence is code, not a footnote definition.
137
+ const fenceMatch = lines[i].match(/^(`{3,}|~{3,})/);
138
+ if (fenceMatch) {
139
+ const fence = fenceMatch[1];
140
+ const closeRe = new RegExp(`^${fence[0]}{${fence.length},}$`);
141
+ contentLines.push(lines[i]);
142
+ i++;
143
+ while (i < lines.length) {
144
+ contentLines.push(lines[i]);
145
+ const isClose = closeRe.test(lines[i].trim());
146
+ i++;
147
+ if (isClose) {
148
+ break;
149
+ }
150
+ }
151
+ continue;
152
+ }
153
+ const match = lines[i].match(FOOTNOTE_DEF_RE);
154
+ if (match) {
155
+ const label = match[1];
156
+ const parts = [match[2]];
157
+ i++;
158
+ // Fold indented continuation lines into the definition.
159
+ while (i < lines.length && /^\s+\S/.test(lines[i])) {
160
+ parts.push(lines[i].trim());
161
+ i++;
162
+ }
163
+ definitions.set(label, parts.join(" ").trim());
164
+ continue;
165
+ }
166
+ contentLines.push(lines[i]);
167
+ i++;
168
+ }
169
+ return { lines: contentLines, definitions };
170
+ }
171
+ /**
172
+ * After conversion, emit a `FootnoteDef` for every footnote that was actually
173
+ * referenced, in reference order. References to undefined labels still get a
174
+ * footnote (with empty content) so the reference mark remains valid OOXML.
175
+ */
176
+ function finalizeFootnotes(state, opts) {
177
+ return (async () => {
178
+ for (const label of state.footnoteOrder) {
179
+ const id = state.footnoteIds.get(label);
180
+ if (id === undefined) {
181
+ continue;
182
+ }
183
+ const text = state.footnoteDefinitions.get(label) ?? "";
184
+ const inlines = parseInlines(text);
185
+ const para = await convertParagraph(inlines, opts, state, { style: "FootnoteText" });
186
+ state.footnotes.push({ id, content: [para] });
187
+ }
188
+ })();
189
+ }
190
+ /**
191
+ * Resolve (or assign) the numeric footnote id for a markdown label, recording
192
+ * reference order on first use.
193
+ */
194
+ function resolveFootnoteId(label, state) {
195
+ const existing = state.footnoteIds.get(label);
196
+ if (existing !== undefined) {
197
+ return existing;
198
+ }
199
+ const id = state.nextFootnoteId++;
200
+ state.footnoteIds.set(label, id);
201
+ state.footnoteOrder.push(label);
202
+ return id;
203
+ }
204
+ // =============================================================================
89
205
  // Block Parser
90
206
  // =============================================================================
91
207
  function parseMarkdownBlocks(lines, start, end) {
@@ -436,6 +552,15 @@ function parseInlines(text) {
436
552
  continue;
437
553
  }
438
554
  }
555
+ // Footnote reference [^label]
556
+ if (text[i] === "[" && text[i + 1] === "^") {
557
+ const result = parseFootnoteRef(text, i);
558
+ if (result) {
559
+ nodes.push(result.node);
560
+ i = result.end;
561
+ continue;
562
+ }
563
+ }
439
564
  // Link [text](url "title")
440
565
  if (text[i] === "[") {
441
566
  const result = parseLink(text, i);
@@ -541,6 +666,17 @@ function parseInlineCode(text, start) {
541
666
  }
542
667
  return { node: { type: "code", text: code }, end: afterClose };
543
668
  }
669
+ function parseFootnoteRef(text, start) {
670
+ // [^label] — label may not contain ']' or whitespace.
671
+ const match = text.slice(start).match(/^\[\^([^\]\s]+)\]/);
672
+ if (!match) {
673
+ return null;
674
+ }
675
+ return {
676
+ node: { type: "footnoteRef", label: match[1] },
677
+ end: start + match[0].length
678
+ };
679
+ }
544
680
  function parseImage(text, start) {
545
681
  // ![alt](url "title")
546
682
  const altStart = start + 2; // skip "!["
@@ -763,20 +899,20 @@ function parseStrikethrough(text, start) {
763
899
  end: closeIdx + 2
764
900
  };
765
901
  }
766
- function convertBlocks(blocks, opts, state) {
902
+ async function convertBlocks(blocks, opts, state) {
767
903
  const result = [];
768
904
  for (const block of blocks) {
769
- const converted = convertBlock(block, opts, 0, state);
905
+ const converted = await convertBlock(block, opts, 0, state);
770
906
  result.push(...converted);
771
907
  }
772
908
  return result;
773
909
  }
774
- function convertBlock(block, opts, listLevel, state) {
910
+ async function convertBlock(block, opts, listLevel, state) {
775
911
  switch (block.type) {
776
912
  case "heading":
777
- return [convertHeading(block)];
913
+ return [await convertHeading(block, opts, state)];
778
914
  case "paragraph":
779
- return [convertParagraph(block.inlines, opts)];
915
+ return [await convertParagraph(block.inlines, opts, state)];
780
916
  case "blockquote":
781
917
  return convertBlockquote(block, opts, state);
782
918
  case "fencedCode":
@@ -786,7 +922,7 @@ function convertBlock(block, opts, listLevel, state) {
786
922
  case "list":
787
923
  return convertList(block, opts, listLevel, state);
788
924
  case "table":
789
- return [convertTable(block, opts)];
925
+ return [await convertTable(block, opts, state)];
790
926
  case "html":
791
927
  // Pass through as plain text paragraph
792
928
  return [
@@ -796,8 +932,8 @@ function convertBlock(block, opts, listLevel, state) {
796
932
  return [];
797
933
  }
798
934
  }
799
- function convertHeading(block) {
800
- const children = inlinesToRuns(block.inlines, {});
935
+ async function convertHeading(block, opts, state) {
936
+ const children = await inlinesToRuns(block.inlines, opts, state, true);
801
937
  return {
802
938
  type: "paragraph",
803
939
  properties: {
@@ -807,19 +943,19 @@ function convertHeading(block) {
807
943
  children
808
944
  };
809
945
  }
810
- function convertParagraph(inlines, opts, props) {
811
- const children = inlinesToRuns(inlines, opts);
946
+ async function convertParagraph(inlines, opts, state, props) {
947
+ const children = await inlinesToRuns(inlines, opts, state, false);
812
948
  return {
813
949
  type: "paragraph",
814
950
  properties: props,
815
951
  children
816
952
  };
817
953
  }
818
- function convertBlockquote(block, opts, state) {
954
+ async function convertBlockquote(block, opts, state) {
819
955
  // Convert blockquote children with "Quote" style and left indent
820
956
  const result = [];
821
957
  for (const child of block.children) {
822
- const converted = convertBlock(child, opts, 0, state);
958
+ const converted = await convertBlock(child, opts, 0, state);
823
959
  for (const item of converted) {
824
960
  if (item.type === "paragraph") {
825
961
  result.push({
@@ -879,7 +1015,7 @@ function convertThematicBreak() {
879
1015
  children: []
880
1016
  };
881
1017
  }
882
- function convertList(block, opts, parentLevel, state) {
1018
+ async function convertList(block, opts, parentLevel, state) {
883
1019
  // Ordered lists with a non-default `start` need their own numbering
884
1020
  // instance so the override actually takes effect — sharing one numId
885
1021
  // across all lists would force every list to start at the same number.
@@ -892,7 +1028,7 @@ function convertList(block, opts, parentLevel, state) {
892
1028
  for (const child of item.children) {
893
1029
  if (child.type === "list") {
894
1030
  // Nested list — increase level
895
- const nested = convertList(child, opts, parentLevel + 1, state);
1031
+ const nested = await convertList(child, opts, parentLevel + 1, state);
896
1032
  result.push(...nested);
897
1033
  }
898
1034
  else if (child.type === "paragraph") {
@@ -901,7 +1037,7 @@ function convertList(block, opts, parentLevel, state) {
901
1037
  const props = {
902
1038
  numbering: { numId, level: parentLevel }
903
1039
  };
904
- const para = convertParagraph(child.inlines, opts, props);
1040
+ const para = await convertParagraph(child.inlines, opts, state, props);
905
1041
  // Handle task list checkbox prefix
906
1042
  if (item.checked !== undefined) {
907
1043
  const checkbox = item.checked ? "☑ " : "☐ ";
@@ -922,11 +1058,11 @@ function convertList(block, opts, parentLevel, state) {
922
1058
  const props = {
923
1059
  indent: { left: 720 * (parentLevel + 1) }
924
1060
  };
925
- result.push(convertParagraph(child.inlines, opts, props));
1061
+ result.push(await convertParagraph(child.inlines, opts, state, props));
926
1062
  }
927
1063
  }
928
1064
  else {
929
- const converted = convertBlock(child, opts, parentLevel, state);
1065
+ const converted = await convertBlock(child, opts, parentLevel, state);
930
1066
  result.push(...converted);
931
1067
  firstBlock = false;
932
1068
  }
@@ -934,10 +1070,12 @@ function convertList(block, opts, parentLevel, state) {
934
1070
  }
935
1071
  return result;
936
1072
  }
937
- function convertTable(block, opts) {
1073
+ async function convertTable(block, opts, state) {
938
1074
  const colCount = block.headers.length;
939
1075
  // Header row
940
- const headerCells = block.headers.map((cell, ci) => {
1076
+ const headerCells = [];
1077
+ for (let ci = 0; ci < block.headers.length; ci++) {
1078
+ const cell = block.headers[ci];
941
1079
  const cellProps = {
942
1080
  shading: { fill: "F0F0F0" },
943
1081
  verticalAlign: "center"
@@ -945,7 +1083,7 @@ function convertTable(block, opts) {
945
1083
  const paraProps = {
946
1084
  alignment: block.alignments[ci]
947
1085
  };
948
- const para = convertParagraph(cell, opts, paraProps);
1086
+ const para = await convertParagraph(cell, opts, state, paraProps);
949
1087
  // Bold header text
950
1088
  const boldPara = {
951
1089
  ...para,
@@ -956,21 +1094,22 @@ function convertTable(block, opts) {
956
1094
  return child;
957
1095
  })
958
1096
  };
959
- return { properties: cellProps, content: [boldPara] };
960
- });
1097
+ headerCells.push({ properties: cellProps, content: [boldPara] });
1098
+ }
961
1099
  // Data rows
962
- const dataRows = block.rows.map(rowCells => {
1100
+ const dataRows = [];
1101
+ for (const rowCells of block.rows) {
963
1102
  const cells = [];
964
1103
  for (let ci = 0; ci < colCount; ci++) {
965
1104
  const cellInlines = ci < rowCells.length ? rowCells[ci] : [];
966
1105
  const paraProps = {
967
1106
  alignment: block.alignments[ci]
968
1107
  };
969
- const para = convertParagraph(cellInlines, opts, paraProps);
1108
+ const para = await convertParagraph(cellInlines, opts, state, paraProps);
970
1109
  cells.push({ content: [para] });
971
1110
  }
972
- return { cells };
973
- });
1111
+ dataRows.push({ cells });
1112
+ }
974
1113
  const allRows = [
975
1114
  { properties: { tableHeader: true }, cells: headerCells },
976
1115
  ...dataRows
@@ -995,37 +1134,41 @@ function convertTable(block, opts) {
995
1134
  // =============================================================================
996
1135
  // Inline to Run Conversion
997
1136
  // =============================================================================
998
- function inlinesToRuns(inlines, opts) {
1137
+ async function inlinesToRuns(inlines, opts, state, isHeading) {
999
1138
  const result = [];
1000
1139
  for (const node of inlines) {
1001
- inlineToRuns(node, result, {}, opts);
1140
+ await inlineToRuns(node, result, {}, opts, state, isHeading);
1002
1141
  }
1003
1142
  return result;
1004
1143
  }
1005
- function inlineToRuns(node, output, inheritedProps, opts) {
1144
+ async function inlineToRuns(node, output, inheritedProps, opts, state, isHeading) {
1006
1145
  switch (node.type) {
1007
1146
  case "text": {
1008
- const textProps = {
1009
- ...inheritedProps,
1010
- ...(opts.defaultFont && !inheritedProps.font ? { font: opts.defaultFont } : {}),
1011
- ...(opts.defaultFontSize && !inheritedProps.size ? { size: opts.defaultFontSize } : {})
1012
- };
1147
+ // Headings derive their font/size from the named Heading style; only
1148
+ // body text picks up the document default font/size.
1149
+ const textProps = isHeading
1150
+ ? inheritedProps
1151
+ : {
1152
+ ...inheritedProps,
1153
+ ...(opts.defaultFont && !inheritedProps.font ? { font: opts.defaultFont } : {}),
1154
+ ...(opts.defaultFontSize && !inheritedProps.size ? { size: opts.defaultFontSize } : {})
1155
+ };
1013
1156
  output.push(makeRun(node.text, textProps));
1014
1157
  break;
1015
1158
  }
1016
1159
  case "bold":
1017
1160
  for (const child of node.children) {
1018
- inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts);
1161
+ await inlineToRuns(child, output, { ...inheritedProps, bold: true }, opts, state, isHeading);
1019
1162
  }
1020
1163
  break;
1021
1164
  case "italic":
1022
1165
  for (const child of node.children) {
1023
- inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts);
1166
+ await inlineToRuns(child, output, { ...inheritedProps, italic: true }, opts, state, isHeading);
1024
1167
  }
1025
1168
  break;
1026
1169
  case "strikethrough":
1027
1170
  for (const child of node.children) {
1028
- inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts);
1171
+ await inlineToRuns(child, output, { ...inheritedProps, strike: true }, opts, state, isHeading);
1029
1172
  }
1030
1173
  break;
1031
1174
  case "code":
@@ -1040,7 +1183,7 @@ function inlineToRuns(node, output, inheritedProps, opts) {
1040
1183
  const linkChildren = [];
1041
1184
  for (const child of node.children) {
1042
1185
  const tempOutput = [];
1043
- inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts);
1186
+ await inlineToRuns(child, tempOutput, { ...inheritedProps, color: "0563C1", underline: "single" }, opts, state, isHeading);
1044
1187
  for (const run of tempOutput) {
1045
1188
  if ("content" in run) {
1046
1189
  linkChildren.push(run);
@@ -1057,20 +1200,71 @@ function inlineToRuns(node, output, inheritedProps, opts) {
1057
1200
  output.push(link);
1058
1201
  break;
1059
1202
  }
1060
- case "image":
1061
- // Images require async resolution for sync API we insert a placeholder
1062
- // The resolveImage callback in options would be used in an async variant
1063
- output.push(makeRun(`[Image: ${node.alt || node.url}]`, {
1064
- ...inheritedProps,
1065
- italic: true,
1066
- color: "666666"
1067
- }));
1203
+ case "image": {
1204
+ const run = await resolveImageRun(node, inheritedProps, opts, state);
1205
+ output.push(run);
1068
1206
  break;
1207
+ }
1208
+ case "footnoteRef": {
1209
+ const id = resolveFootnoteId(node.label, state);
1210
+ output.push({
1211
+ properties: { ...inheritedProps, style: "FootnoteReference" },
1212
+ content: [{ type: "footnoteRef", id }]
1213
+ });
1214
+ break;
1215
+ }
1069
1216
  case "lineBreak":
1070
1217
  output.push(makeRun("", undefined, [{ type: "break" }]));
1071
1218
  break;
1072
1219
  }
1073
1220
  }
1221
+ /**
1222
+ * Resolve an inline image. If `resolveImage` returns image data, embed it as a
1223
+ * real `InlineImageContent` run and register the media in `state.images`;
1224
+ * otherwise fall back to an italic `[Image: alt]` placeholder so the output
1225
+ * remains valid.
1226
+ */
1227
+ async function resolveImageRun(node, inheritedProps, opts, state) {
1228
+ const data = opts.resolveImage ? await opts.resolveImage(node.url, node.alt) : undefined;
1229
+ if (!data) {
1230
+ return makeRun(`[Image: ${node.alt || node.url}]`, {
1231
+ ...inheritedProps,
1232
+ italic: true,
1233
+ color: "666666"
1234
+ });
1235
+ }
1236
+ const n = state.nextImageId++;
1237
+ const fileName = `image${n}.${imageExtension(data.mediaType)}`;
1238
+ const rId = `mdimg${n}`;
1239
+ // For SVG, the packager auto-splits the PNG fallback into its own media
1240
+ // part, assigns a second relationship, and back-fills `svgRId` on the inline
1241
+ // image — so we only ever push a single ImageDef and a single rId here.
1242
+ state.images.push({
1243
+ data: data.data,
1244
+ mediaType: data.mediaType,
1245
+ fileName,
1246
+ rId,
1247
+ ...(data.fallbackData ? { fallbackData: data.fallbackData } : {})
1248
+ });
1249
+ const DEFAULT_DIM = 914400; // 1 inch fallback when no size given
1250
+ return {
1251
+ content: [
1252
+ {
1253
+ type: "image",
1254
+ rId,
1255
+ width: (data.width ?? DEFAULT_DIM),
1256
+ height: (data.height ?? DEFAULT_DIM),
1257
+ altText: node.alt || undefined,
1258
+ name: node.alt || fileName,
1259
+ drawingId: state.nextDrawingId++
1260
+ }
1261
+ ]
1262
+ };
1263
+ }
1264
+ /** Map an image media type to its word/media/ file extension. */
1265
+ function imageExtension(mediaType) {
1266
+ return mediaType === "jpeg" ? "jpg" : mediaType;
1267
+ }
1074
1268
  // =============================================================================
1075
1269
  // Numbering Helpers
1076
1270
  // =============================================================================
@@ -1324,6 +1518,22 @@ function defaultMarkdownStyles() {
1324
1518
  name: "Code Block",
1325
1519
  basedOn: "Normal",
1326
1520
  runProperties: { font: "Courier New", size: 20 }
1521
+ },
1522
+ {
1523
+ // Character style applied to the in-text footnote reference mark so it
1524
+ // renders as a superscript number, matching Word's built-in style.
1525
+ type: "character",
1526
+ styleId: "FootnoteReference",
1527
+ name: "footnote reference",
1528
+ runProperties: { vertAlign: "superscript" }
1529
+ },
1530
+ {
1531
+ // Paragraph style for footnote body text (smaller font, like Word).
1532
+ type: "paragraph",
1533
+ styleId: "FootnoteText",
1534
+ name: "footnote text",
1535
+ basedOn: "Normal",
1536
+ runProperties: { size: 20 }
1327
1537
  }
1328
1538
  ];
1329
1539
  }