@yinyoudexing/xml2word 0.1.0 → 0.1.2

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.cjs CHANGED
@@ -48,6 +48,8 @@ function wrapBodyXml(bodyXml) {
48
48
  <w:body>
49
49
  ${bodyXml}
50
50
  <w:sectPr>
51
+ <w:headerReference w:type="default" r:id="rId6"/>
52
+ <w:footerReference w:type="default" r:id="rId7"/>
51
53
  <w:pgSz w:w="12240" w:h="15840"/>
52
54
  <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
53
55
  <w:cols w:space="708"/>
@@ -121,32 +123,421 @@ var init_validateXml = __esm({
121
123
  // src/lib/createDocxZip.ts
122
124
  var createDocxZip_exports = {};
123
125
  __export(createDocxZip_exports, {
124
- createDocxZipUint8Array: () => createDocxZipUint8Array
126
+ createDocxZipUint8Array: () => createDocxZipUint8Array,
127
+ createDocxZipWithAssetsUint8Array: () => createDocxZipWithAssetsUint8Array
125
128
  });
129
+ function buildContentTypesXml(assets) {
130
+ const defaults = /* @__PURE__ */ new Map();
131
+ defaults.set("rels", "application/vnd.openxmlformats-package.relationships+xml");
132
+ defaults.set("xml", "application/xml");
133
+ for (const asset of assets) {
134
+ if (!asset.contentType) continue;
135
+ const extMatch = asset.target.match(/\.([a-zA-Z0-9]+)$/);
136
+ if (!extMatch) continue;
137
+ const ext = extMatch[1].toLowerCase();
138
+ defaults.set(ext, asset.contentType);
139
+ }
140
+ const defaultLines = [...defaults.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([ext, ct]) => ` <Default Extension="${ext}" ContentType="${ct}"/>`).join("\n");
141
+ const overrides = [
142
+ ' <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>',
143
+ ' <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>',
144
+ ' <Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>',
145
+ ' <Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>',
146
+ ' <Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>',
147
+ ' <Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>',
148
+ ' <Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>',
149
+ ' <Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>'
150
+ ].join("\n");
151
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
152
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
153
+ ${defaultLines}
154
+ ${overrides}
155
+ </Types>
156
+ `;
157
+ }
158
+ function buildDocumentRelsXml(assets) {
159
+ const relLines = [
160
+ ' <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>',
161
+ ' <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>',
162
+ ' <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>',
163
+ ' <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>',
164
+ ' <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>',
165
+ ' <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>',
166
+ ' <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>',
167
+ ...assets.map(
168
+ (a) => ` <Relationship Id="${a.relationshipId}" Type="${a.relationshipType}" Target="${a.target}"/>`
169
+ )
170
+ ].join("\n");
171
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
172
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
173
+ ${relLines}
174
+ </Relationships>
175
+ `;
176
+ }
126
177
  async function createDocxZipUint8Array(xml, options = {}) {
127
178
  const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
128
179
  validateXmlIfNeeded(documentXml, options.validateXml ?? true);
129
180
  const zip = new import_jszip.default();
130
- zip.file("[Content_Types].xml", CONTENT_TYPES_XML);
181
+ zip.file("[Content_Types].xml", buildContentTypesXml([]));
131
182
  const relsFolder = zip.folder("_rels");
132
183
  relsFolder?.file(".rels", ROOT_RELS_XML);
133
184
  const wordFolder = zip.folder("word");
134
185
  wordFolder?.file("document.xml", documentXml);
186
+ wordFolder?.file("styles.xml", STYLES_XML);
187
+ wordFolder?.file("settings.xml", SETTINGS_XML);
188
+ wordFolder?.file("fontTable.xml", FONT_TABLE_XML);
189
+ wordFolder?.folder("theme")?.file("theme1.xml", THEME_XML);
190
+ wordFolder?.file("numbering.xml", NUMBERING_XML);
191
+ wordFolder?.file("header1.xml", HEADER1_XML);
192
+ wordFolder?.file("footer1.xml", FOOTER1_XML);
193
+ const wordRelsFolder = wordFolder?.folder("_rels");
194
+ wordRelsFolder?.file("document.xml.rels", buildDocumentRelsXml([]));
135
195
  return zip.generateAsync({ type: "uint8array" });
136
196
  }
137
- var import_jszip, CONTENT_TYPES_XML, ROOT_RELS_XML;
197
+ async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
198
+ const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
199
+ validateXmlIfNeeded(documentXml, options.validateXml ?? true);
200
+ const zip = new import_jszip.default();
201
+ zip.file("[Content_Types].xml", buildContentTypesXml(assets));
202
+ const relsFolder = zip.folder("_rels");
203
+ relsFolder?.file(".rels", ROOT_RELS_XML);
204
+ const wordFolder = zip.folder("word");
205
+ wordFolder?.file("document.xml", documentXml);
206
+ wordFolder?.file("styles.xml", STYLES_XML);
207
+ wordFolder?.file("settings.xml", SETTINGS_XML);
208
+ wordFolder?.file("fontTable.xml", FONT_TABLE_XML);
209
+ wordFolder?.folder("theme")?.file("theme1.xml", THEME_XML);
210
+ wordFolder?.file("numbering.xml", NUMBERING_XML);
211
+ wordFolder?.file("header1.xml", HEADER1_XML);
212
+ wordFolder?.file("footer1.xml", FOOTER1_XML);
213
+ const wordRelsFolder = wordFolder?.folder("_rels");
214
+ wordRelsFolder?.file("document.xml.rels", buildDocumentRelsXml(assets));
215
+ if (assets.length) {
216
+ for (const asset of assets) {
217
+ const targetPath = asset.target.replace(/^\.\//, "");
218
+ const normalized = targetPath.startsWith("word/") ? targetPath.slice("word/".length) : targetPath;
219
+ wordFolder?.file(normalized, asset.data);
220
+ }
221
+ }
222
+ return zip.generateAsync({ type: "uint8array" });
223
+ }
224
+ var import_jszip, STYLES_XML, SETTINGS_XML, NUMBERING_XML, HEADER1_XML, FOOTER1_XML, THEME_XML, FONT_TABLE_XML, ROOT_RELS_XML;
138
225
  var init_createDocxZip = __esm({
139
226
  "src/lib/createDocxZip.ts"() {
140
227
  "use strict";
141
228
  import_jszip = __toESM(require("jszip"), 1);
142
229
  init_normalizeDocumentXml();
143
230
  init_validateXml();
144
- CONTENT_TYPES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
145
- <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
146
- <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
147
- <Default Extension="xml" ContentType="application/xml"/>
148
- <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
149
- </Types>
231
+ STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
232
+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
233
+ <w:docDefaults>
234
+ <w:rPrDefault>
235
+ <w:rPr>
236
+ <w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/>
237
+ <w:sz w:val="28"/>
238
+ <w:szCs w:val="28"/>
239
+ <w:lang w:val="en-US" w:eastAsia="zh-CN"/>
240
+ </w:rPr>
241
+ </w:rPrDefault>
242
+ <w:pPrDefault>
243
+ <w:pPr>
244
+ <w:spacing w:before="0" w:after="160" w:line="360" w:lineRule="auto"/>
245
+ </w:pPr>
246
+ </w:pPrDefault>
247
+ </w:docDefaults>
248
+
249
+ <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
250
+ <w:name w:val="Normal"/>
251
+ <w:qFormat/>
252
+ <w:pPr>
253
+ <w:spacing w:before="0" w:after="160" w:line="360" w:lineRule="auto"/>
254
+ </w:pPr>
255
+ <w:rPr>
256
+ <w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/>
257
+ <w:sz w:val="28"/>
258
+ <w:szCs w:val="28"/>
259
+ </w:rPr>
260
+ </w:style>
261
+
262
+ <w:style w:type="paragraph" w:styleId="Heading1">
263
+ <w:name w:val="heading 1"/>
264
+ <w:basedOn w:val="Normal"/>
265
+ <w:next w:val="Normal"/>
266
+ <w:uiPriority w:val="9"/>
267
+ <w:qFormat/>
268
+ <w:pPr>
269
+ <w:keepNext/>
270
+ <w:keepLines/>
271
+ <w:outlineLvl w:val="0"/>
272
+ <w:spacing w:before="240" w:after="120" w:line="360" w:lineRule="auto"/>
273
+ </w:pPr>
274
+ <w:rPr>
275
+ <w:b/>
276
+ <w:sz w:val="44"/>
277
+ <w:szCs w:val="44"/>
278
+ </w:rPr>
279
+ </w:style>
280
+
281
+ <w:style w:type="paragraph" w:styleId="Heading2">
282
+ <w:name w:val="heading 2"/>
283
+ <w:basedOn w:val="Normal"/>
284
+ <w:next w:val="Normal"/>
285
+ <w:uiPriority w:val="9"/>
286
+ <w:qFormat/>
287
+ <w:pPr>
288
+ <w:keepNext/>
289
+ <w:keepLines/>
290
+ <w:outlineLvl w:val="1"/>
291
+ <w:spacing w:before="200" w:after="100" w:line="360" w:lineRule="auto"/>
292
+ </w:pPr>
293
+ <w:rPr>
294
+ <w:b/>
295
+ <w:sz w:val="32"/>
296
+ <w:szCs w:val="32"/>
297
+ </w:rPr>
298
+ </w:style>
299
+
300
+ <w:style w:type="paragraph" w:styleId="Heading3">
301
+ <w:name w:val="heading 3"/>
302
+ <w:basedOn w:val="Normal"/>
303
+ <w:next w:val="Normal"/>
304
+ <w:uiPriority w:val="9"/>
305
+ <w:qFormat/>
306
+ <w:pPr>
307
+ <w:keepNext/>
308
+ <w:keepLines/>
309
+ <w:outlineLvl w:val="2"/>
310
+ <w:spacing w:before="180" w:after="90" w:line="360" w:lineRule="auto"/>
311
+ </w:pPr>
312
+ <w:rPr>
313
+ <w:b/>
314
+ <w:sz w:val="28"/>
315
+ <w:szCs w:val="28"/>
316
+ </w:rPr>
317
+ </w:style>
318
+
319
+ <w:style w:type="paragraph" w:styleId="Heading4">
320
+ <w:name w:val="heading 4"/>
321
+ <w:basedOn w:val="Normal"/>
322
+ <w:next w:val="Normal"/>
323
+ <w:uiPriority w:val="9"/>
324
+ <w:qFormat/>
325
+ <w:pPr>
326
+ <w:keepNext/>
327
+ <w:keepLines/>
328
+ <w:outlineLvl w:val="3"/>
329
+ <w:spacing w:before="160" w:after="80" w:line="360" w:lineRule="auto"/>
330
+ </w:pPr>
331
+ <w:rPr>
332
+ <w:b/>
333
+ <w:sz w:val="24"/>
334
+ <w:szCs w:val="24"/>
335
+ </w:rPr>
336
+ </w:style>
337
+
338
+ <w:style w:type="paragraph" w:styleId="Heading5">
339
+ <w:name w:val="heading 5"/>
340
+ <w:basedOn w:val="Normal"/>
341
+ <w:next w:val="Normal"/>
342
+ <w:uiPriority w:val="9"/>
343
+ <w:qFormat/>
344
+ <w:pPr>
345
+ <w:keepNext/>
346
+ <w:keepLines/>
347
+ <w:outlineLvl w:val="4"/>
348
+ <w:spacing w:before="140" w:after="70" w:line="360" w:lineRule="auto"/>
349
+ </w:pPr>
350
+ <w:rPr>
351
+ <w:b/>
352
+ <w:sz w:val="22"/>
353
+ <w:szCs w:val="22"/>
354
+ </w:rPr>
355
+ </w:style>
356
+
357
+ <w:style w:type="paragraph" w:styleId="Heading6">
358
+ <w:name w:val="heading 6"/>
359
+ <w:basedOn w:val="Normal"/>
360
+ <w:next w:val="Normal"/>
361
+ <w:uiPriority w:val="9"/>
362
+ <w:qFormat/>
363
+ <w:pPr>
364
+ <w:keepNext/>
365
+ <w:keepLines/>
366
+ <w:outlineLvl w:val="5"/>
367
+ <w:spacing w:before="120" w:after="60" w:line="360" w:lineRule="auto"/>
368
+ </w:pPr>
369
+ <w:rPr>
370
+ <w:b/>
371
+ <w:sz w:val="22"/>
372
+ <w:szCs w:val="22"/>
373
+ </w:rPr>
374
+ </w:style>
375
+ </w:styles>
376
+ `;
377
+ SETTINGS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
378
+ <w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
379
+ <w:themeFontLang w:val="en-US" w:eastAsia="zh-CN"/>
380
+ <w:defaultTabStop w:val="720"/>
381
+ <w:compat/>
382
+ </w:settings>
383
+ `;
384
+ NUMBERING_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
385
+ <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
386
+ <w:abstractNum w:abstractNumId="1">
387
+ <w:multiLevelType w:val="hybridMultilevel"/>
388
+ <w:lvl w:ilvl="0">
389
+ <w:start w:val="1"/>
390
+ <w:numFmt w:val="bullet"/>
391
+ <w:lvlText w:val="\u2022"/>
392
+ <w:lvlJc w:val="left"/>
393
+ <w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>
394
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
395
+ </w:lvl>
396
+ <w:lvl w:ilvl="1">
397
+ <w:start w:val="1"/>
398
+ <w:numFmt w:val="bullet"/>
399
+ <w:lvlText w:val="\u25CB"/>
400
+ <w:lvlJc w:val="left"/>
401
+ <w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr>
402
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
403
+ </w:lvl>
404
+ <w:lvl w:ilvl="2">
405
+ <w:start w:val="1"/>
406
+ <w:numFmt w:val="bullet"/>
407
+ <w:lvlText w:val="\u25A0"/>
408
+ <w:lvlJc w:val="left"/>
409
+ <w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr>
410
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
411
+ </w:lvl>
412
+ </w:abstractNum>
413
+ <w:abstractNum w:abstractNumId="2">
414
+ <w:multiLevelType w:val="hybridMultilevel"/>
415
+ <w:lvl w:ilvl="0">
416
+ <w:start w:val="1"/>
417
+ <w:numFmt w:val="decimal"/>
418
+ <w:lvlText w:val="%1."/>
419
+ <w:lvlJc w:val="left"/>
420
+ <w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>
421
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
422
+ </w:lvl>
423
+ <w:lvl w:ilvl="1">
424
+ <w:start w:val="1"/>
425
+ <w:numFmt w:val="decimal"/>
426
+ <w:lvlText w:val="%1.%2."/>
427
+ <w:lvlJc w:val="left"/>
428
+ <w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr>
429
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
430
+ </w:lvl>
431
+ <w:lvl w:ilvl="2">
432
+ <w:start w:val="1"/>
433
+ <w:numFmt w:val="decimal"/>
434
+ <w:lvlText w:val="%1.%2.%3."/>
435
+ <w:lvlJc w:val="left"/>
436
+ <w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr>
437
+ <w:rPr><w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman" w:eastAsia="FangSong_GB2312" w:cs="Times New Roman"/></w:rPr>
438
+ </w:lvl>
439
+ </w:abstractNum>
440
+ <w:num w:numId="1"><w:abstractNumId w:val="1"/></w:num>
441
+ <w:num w:numId="2"><w:abstractNumId w:val="2"/></w:num>
442
+ </w:numbering>
443
+ `;
444
+ HEADER1_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
445
+ <w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
446
+ <w:p>
447
+ <w:pPr><w:jc w:val="center"/></w:pPr>
448
+ <w:r><w:t></w:t></w:r>
449
+ </w:p>
450
+ </w:hdr>
451
+ `;
452
+ FOOTER1_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
453
+ <w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
454
+ <w:p>
455
+ <w:pPr><w:jc w:val="right"/></w:pPr>
456
+ <w:r><w:t>\u7B2C </w:t></w:r>
457
+ <w:r><w:fldChar w:fldCharType="begin"/></w:r>
458
+ <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
459
+ <w:r><w:fldChar w:fldCharType="separate"/></w:r>
460
+ <w:r><w:t>1</w:t></w:r>
461
+ <w:r><w:fldChar w:fldCharType="end"/></w:r>
462
+ <w:r><w:t> \u9875 / \u5171 </w:t></w:r>
463
+ <w:r><w:fldChar w:fldCharType="begin"/></w:r>
464
+ <w:r><w:instrText xml:space="preserve"> NUMPAGES </w:instrText></w:r>
465
+ <w:r><w:fldChar w:fldCharType="separate"/></w:r>
466
+ <w:r><w:t>1</w:t></w:r>
467
+ <w:r><w:fldChar w:fldCharType="end"/></w:r>
468
+ <w:r><w:t> \u9875</w:t></w:r>
469
+ </w:p>
470
+ </w:ftr>
471
+ `;
472
+ THEME_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
473
+ <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
474
+ <a:themeElements>
475
+ <a:clrScheme name="Office">
476
+ <a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
477
+ <a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
478
+ <a:dk2><a:srgbClr val="1F497D"/></a:dk2>
479
+ <a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
480
+ <a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
481
+ <a:accent2><a:srgbClr val="C0504D"/></a:accent2>
482
+ <a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
483
+ <a:accent4><a:srgbClr val="8064A2"/></a:accent4>
484
+ <a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
485
+ <a:accent6><a:srgbClr val="F79646"/></a:accent6>
486
+ <a:hlink><a:srgbClr val="0000FF"/></a:hlink>
487
+ <a:folHlink><a:srgbClr val="800080"/></a:folHlink>
488
+ </a:clrScheme>
489
+ <a:fontScheme name="Office">
490
+ <a:majorFont>
491
+ <a:latin typeface="Times New Roman"/>
492
+ <a:ea typeface="FangSong_GB2312"/>
493
+ <a:cs typeface="Times New Roman"/>
494
+ </a:majorFont>
495
+ <a:minorFont>
496
+ <a:latin typeface="Times New Roman"/>
497
+ <a:ea typeface="FangSong_GB2312"/>
498
+ <a:cs typeface="Times New Roman"/>
499
+ </a:minorFont>
500
+ </a:fontScheme>
501
+ <a:fmtScheme name="Office">
502
+ <a:fillStyleLst>
503
+ <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
504
+ <a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"/></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"/></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>
505
+ <a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"/></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"/></a:gs></a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>
506
+ </a:fillStyleLst>
507
+ <a:lnStyleLst>
508
+ <a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
509
+ <a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
510
+ <a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>
511
+ </a:lnStyleLst>
512
+ <a:effectStyleLst>
513
+ <a:effectStyle><a:effectLst/></a:effectStyle>
514
+ <a:effectStyle><a:effectLst/></a:effectStyle>
515
+ <a:effectStyle><a:effectLst/></a:effectStyle>
516
+ </a:effectStyleLst>
517
+ <a:bgFillStyleLst>
518
+ <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
519
+ <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
520
+ <a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
521
+ </a:bgFillStyleLst>
522
+ </a:fmtScheme>
523
+ </a:themeElements>
524
+ <a:objectDefaults/>
525
+ <a:extraClrSchemeLst/>
526
+ </a:theme>
527
+ `;
528
+ FONT_TABLE_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
529
+ <w:fonts xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
530
+ <w:font w:name="Times New Roman">
531
+ <w:charset w:val="00"/>
532
+ <w:family w:val="roman"/>
533
+ <w:pitch w:val="variable"/>
534
+ </w:font>
535
+ <w:font w:name="FangSong_GB2312">
536
+ <w:charset w:val="86"/>
537
+ <w:family w:val="auto"/>
538
+ <w:pitch w:val="variable"/>
539
+ </w:font>
540
+ </w:fonts>
150
541
  `;
151
542
  ROOT_RELS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
152
543
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
@@ -161,6 +552,7 @@ var init_createDocxZip = __esm({
161
552
  // src/lib/htmlToWordBodyXml.ts
162
553
  var htmlToWordBodyXml_exports = {};
163
554
  __export(htmlToWordBodyXml_exports, {
555
+ htmlToWordBodyWithAssets: () => htmlToWordBodyWithAssets,
164
556
  htmlToWordBodyXml: () => htmlToWordBodyXml,
165
557
  textToWordBodyXml: () => textToWordBodyXml
166
558
  });
@@ -171,6 +563,12 @@ function shouldPreserveSpace(text) {
171
563
  if (!text) return false;
172
564
  return /^\s/.test(text) || /\s$/.test(text) || /\s{2,}/.test(text);
173
565
  }
566
+ function shouldKeepWhitespaceOnlyRun(text) {
567
+ if (!text) return false;
568
+ if (/\r|\n/.test(text)) return false;
569
+ if (text.includes("\xA0")) return true;
570
+ return /\s{2,}/.test(text);
571
+ }
174
572
  function parseStyleAttribute(style) {
175
573
  if (!style) return {};
176
574
  const normalized = style.replace(/\r/g, "\n");
@@ -270,7 +668,107 @@ function getTextContent(node) {
270
668
  for (const c of children) out += getTextContent(c);
271
669
  return out;
272
670
  }
273
- function collectInlineRuns(node, inherited, out) {
671
+ function decodeBase64ToUint8Array(base64) {
672
+ const BufferCtor = globalThis.Buffer;
673
+ if (BufferCtor) {
674
+ return new Uint8Array(BufferCtor.from(base64, "base64"));
675
+ }
676
+ const atobFn = globalThis.atob;
677
+ if (!atobFn) {
678
+ throw new Error("Base64 decode is not available in this environment.");
679
+ }
680
+ const bin = atobFn(base64);
681
+ const bytes = new Uint8Array(bin.length);
682
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
683
+ return bytes;
684
+ }
685
+ function parseImageDataUrl(src) {
686
+ const m = src.match(/^data:(image\/png|image\/jpeg);base64,([\s\S]+)$/i);
687
+ if (!m) return void 0;
688
+ const contentType = m[1].toLowerCase();
689
+ const base64 = m[2].replace(/\s+/g, "");
690
+ const data = decodeBase64ToUint8Array(base64);
691
+ const extension = contentType === "image/png" ? "png" : "jpeg";
692
+ return { contentType, data, extension };
693
+ }
694
+ function parseCssLengthToPx(value) {
695
+ if (!value) return void 0;
696
+ const v = value.trim().toLowerCase();
697
+ const px = v.match(/^(\d+(?:\.\d+)?)px$/);
698
+ if (px) return Math.max(1, Math.round(Number(px[1])));
699
+ return void 0;
700
+ }
701
+ function readUInt32BE(bytes, offset) {
702
+ if (offset < 0 || offset + 4 > bytes.length) return void 0;
703
+ return ((bytes[offset] ?? 0) << 24 | (bytes[offset + 1] ?? 0) << 16 | (bytes[offset + 2] ?? 0) << 8 | (bytes[offset + 3] ?? 0)) >>> 0;
704
+ }
705
+ function parsePngDimensions(data) {
706
+ if (data.length < 24) return void 0;
707
+ const signature = [137, 80, 78, 71, 13, 10, 26, 10];
708
+ for (let i = 0; i < signature.length; i++) {
709
+ if ((data[i] ?? 0) !== signature[i]) return void 0;
710
+ }
711
+ const widthPx = readUInt32BE(data, 16);
712
+ const heightPx = readUInt32BE(data, 20);
713
+ if (!widthPx || !heightPx) return void 0;
714
+ return { widthPx, heightPx };
715
+ }
716
+ function parseJpegDimensions(data) {
717
+ if (data.length < 4) return void 0;
718
+ if (data[0] !== 255 || data[1] !== 216) return void 0;
719
+ let offset = 2;
720
+ while (offset + 4 <= data.length) {
721
+ if (data[offset] !== 255) {
722
+ offset++;
723
+ continue;
724
+ }
725
+ while (offset < data.length && data[offset] === 255) offset++;
726
+ if (offset >= data.length) return void 0;
727
+ const marker = data[offset];
728
+ offset++;
729
+ const isStandalone = marker === 217 || marker === 218;
730
+ if (isStandalone) break;
731
+ if (offset + 2 > data.length) return void 0;
732
+ const length = data[offset] << 8 | data[offset + 1];
733
+ if (length < 2 || offset + length > data.length) return void 0;
734
+ const isSof = marker === 192 || marker === 193 || marker === 194 || marker === 195 || marker === 197 || marker === 198 || marker === 199 || marker === 201 || marker === 202 || marker === 203 || marker === 205 || marker === 206 || marker === 207;
735
+ if (isSof) {
736
+ if (offset + 7 > data.length) return void 0;
737
+ const heightPx = data[offset + 3] << 8 | data[offset + 4];
738
+ const widthPx = data[offset + 5] << 8 | data[offset + 6];
739
+ if (!widthPx || !heightPx) return void 0;
740
+ return { widthPx, heightPx };
741
+ }
742
+ offset += length;
743
+ }
744
+ return void 0;
745
+ }
746
+ function parseIntrinsicImageSizePx(contentType, data) {
747
+ if (contentType === "image/png") return parsePngDimensions(data);
748
+ if (contentType === "image/jpeg") return parseJpegDimensions(data);
749
+ return void 0;
750
+ }
751
+ function applyMaxBoxPx(size, maxBox) {
752
+ const w = Math.max(1, Math.round(size.widthPx));
753
+ const h = Math.max(1, Math.round(size.heightPx));
754
+ const scale = Math.min(1, maxBox.maxWidthPx / w, maxBox.maxHeightPx / h);
755
+ return { widthPx: Math.max(1, Math.round(w * scale)), heightPx: Math.max(1, Math.round(h * scale)) };
756
+ }
757
+ function computeImageSizePx(node, intrinsic) {
758
+ const wAttr = node.attribs?.width ? Number(node.attribs.width) : void 0;
759
+ const hAttr = node.attribs?.height ? Number(node.attribs.height) : void 0;
760
+ const css = parseStyleAttribute(node.attribs?.style);
761
+ const wCss = parseCssLengthToPx(css.width);
762
+ const hCss = parseCssLengthToPx(css.height);
763
+ const widthAttrPx = Number.isFinite(wAttr) && wAttr ? Math.max(1, Math.round(wAttr)) : void 0;
764
+ const heightAttrPx = Number.isFinite(hAttr) && hAttr ? Math.max(1, Math.round(hAttr)) : void 0;
765
+ const ratio = intrinsic && intrinsic.widthPx > 0 && intrinsic.heightPx > 0 ? intrinsic.heightPx / intrinsic.widthPx : widthAttrPx && heightAttrPx ? heightAttrPx / widthAttrPx : 0.5;
766
+ const widthPx = typeof wCss === "number" ? wCss : typeof widthAttrPx === "number" ? widthAttrPx : intrinsic?.widthPx ?? 300;
767
+ const heightPx = typeof hCss === "number" ? hCss : typeof heightAttrPx === "number" ? heightAttrPx : intrinsic?.heightPx ?? 150;
768
+ const finalSize = typeof wCss === "number" && typeof hCss !== "number" ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof hCss === "number" && typeof wCss !== "number" ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : typeof widthAttrPx === "number" && typeof heightAttrPx !== "number" && intrinsic ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof heightAttrPx === "number" && typeof widthAttrPx !== "number" && intrinsic ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : { widthPx, heightPx };
769
+ return applyMaxBoxPx(finalSize, { maxWidthPx: 624, maxHeightPx: 864 });
770
+ }
771
+ function collectInlineRuns(node, inherited, out, result) {
274
772
  if (node.type === "text") {
275
773
  const text = node.data ?? "";
276
774
  if (text) out.push({ kind: "text", text, style: inherited });
@@ -282,13 +780,57 @@ function collectInlineRuns(node, inherited, out) {
282
780
  out.push({ kind: "br" });
283
781
  return;
284
782
  }
783
+ if (tag === "img") {
784
+ const src = node.attribs?.src;
785
+ if (!src) return;
786
+ const parsed = parseImageDataUrl(src);
787
+ if (!parsed) return;
788
+ const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
789
+ const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
790
+ const id = result.images.length + 1;
791
+ const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
792
+ const target = `media/image${id}.${parsed.extension}`;
793
+ result.images.push({
794
+ relationshipId,
795
+ target,
796
+ data: parsed.data,
797
+ contentType: parsed.contentType,
798
+ widthPx,
799
+ heightPx
800
+ });
801
+ out.push({ kind: "image", image: { relationshipId, widthPx, heightPx } });
802
+ return;
803
+ }
804
+ if (tag === "canvas") {
805
+ const dataUrl = node.attribs?.["data-image"] ?? node.attribs?.["data-src"];
806
+ if (!dataUrl) return;
807
+ const parsed = parseImageDataUrl(dataUrl);
808
+ if (!parsed) return;
809
+ const bufferW = node.attribs?.width ? Number(node.attribs.width) : void 0;
810
+ const bufferH = node.attribs?.height ? Number(node.attribs.height) : void 0;
811
+ const intrinsic = Number.isFinite(bufferW) && bufferW && Number.isFinite(bufferH) && bufferH ? { widthPx: Math.max(1, Math.round(bufferW)), heightPx: Math.max(1, Math.round(bufferH)) } : parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
812
+ const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
813
+ const id = result.images.length + 1;
814
+ const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
815
+ const target = `media/image${id}.${parsed.extension}`;
816
+ result.images.push({
817
+ relationshipId,
818
+ target,
819
+ data: parsed.data,
820
+ contentType: parsed.contentType,
821
+ widthPx,
822
+ heightPx
823
+ });
824
+ out.push({ kind: "image", image: { relationshipId, widthPx, heightPx } });
825
+ return;
826
+ }
285
827
  const next = mergeTextStyle(inherited, styleFromElement(node));
286
828
  const children2 = node.children ?? [];
287
- for (const c of children2) collectInlineRuns(c, next, out);
829
+ for (const c of children2) collectInlineRuns(c, next, out, result);
288
830
  return;
289
831
  }
290
832
  const children = node.children ?? [];
291
- for (const c of children) collectInlineRuns(c, inherited, out);
833
+ for (const c of children) collectInlineRuns(c, inherited, out, result);
292
834
  }
293
835
  function buildRunXml(style, text) {
294
836
  const rPrParts = [];
@@ -309,6 +851,16 @@ function buildRunXml(style, text) {
309
851
  const preserve = shouldPreserveSpace(text) ? ' xml:space="preserve"' : "";
310
852
  return `<w:r>${rPrXml}<w:t${preserve}>${escaped}</w:t></w:r>`;
311
853
  }
854
+ function pxToEmu(px) {
855
+ return Math.max(1, Math.round(px * 9525));
856
+ }
857
+ function buildImageRunXml(image) {
858
+ const cx = pxToEmu(image.widthPx);
859
+ const cy = pxToEmu(image.heightPx);
860
+ const docPrId = image.relationshipId.replace(/^rId/, "");
861
+ const name = `Picture ${docPrId}`;
862
+ return `<w:r><w:drawing xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${docPrId}" name="${escapeXmlText(name)}"/><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:nvPicPr><pic:cNvPr id="0" name="${escapeXmlText(name)}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${image.relationshipId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
863
+ }
312
864
  function hasClass(node, className) {
313
865
  const cls = node.attribs?.class;
314
866
  if (!cls) return false;
@@ -317,7 +869,11 @@ function hasClass(node, className) {
317
869
  function isSkippableSubtree(node) {
318
870
  if (node.type !== "tag") return false;
319
871
  const tag = node.name?.toLowerCase();
320
- if (tag === "button" || tag === "canvas") return true;
872
+ if (tag === "button") return true;
873
+ if (tag === "canvas") {
874
+ const dataUrl = node.attribs?.["data-image"] ?? node.attribs?.["data-src"];
875
+ if (!dataUrl) return true;
876
+ }
321
877
  if (tag === "img" && hasClass(node, "ProseMirror-separator")) return true;
322
878
  if (node.attribs?.id === "pages") return true;
323
879
  if (hasClass(node, "ProseMirror-widget")) return true;
@@ -356,9 +912,10 @@ function inferFirstFontSizeHalfPoints(node) {
356
912
  }
357
913
  return void 0;
358
914
  }
359
- function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
915
+ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
360
916
  const css = parseStyleAttribute(node.attribs?.style);
361
917
  const parts = [];
918
+ if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
362
919
  const align = css["text-align"]?.trim().toLowerCase();
363
920
  const jcVal = align === "center" ? "center" : align === "right" ? "right" : align === "justify" ? "both" : void 0;
364
921
  if (jcVal) parts.push(`<w:jc w:val="${jcVal}"/>`);
@@ -409,20 +966,28 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
409
966
  if (!parts.length) return "";
410
967
  return `<w:pPr>${parts.join("")}</w:pPr>`;
411
968
  }
412
- function buildParagraphXmlFromContainer(node, baseStyle, extraInd) {
969
+ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
413
970
  const baseFontHalfPoints = baseStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
414
- const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd);
971
+ const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
415
972
  const runs = [];
416
- for (const c of node.children ?? []) collectInlineRuns(c, baseStyle, runs);
973
+ const res = result ?? {
974
+ bodyXml: "",
975
+ images: []
976
+ };
977
+ for (const c of node.children ?? []) collectInlineRuns(c, baseStyle, runs, res);
417
978
  const rXml = [];
418
979
  for (const token of runs) {
419
980
  if (token.kind === "br") {
420
981
  rXml.push("<w:r><w:br/></w:r>");
421
982
  continue;
422
983
  }
984
+ if (token.kind === "image") {
985
+ rXml.push(buildImageRunXml(token.image));
986
+ continue;
987
+ }
423
988
  const text = token.text;
424
989
  if (!text) continue;
425
- if (!text.trim()) continue;
990
+ if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
426
991
  rXml.push(buildRunXml(token.style, text));
427
992
  }
428
993
  if (!rXml.length) return "";
@@ -442,46 +1007,180 @@ function isExplicitPageBreak(node) {
442
1007
  if (after?.includes("always") || before?.includes("always")) return true;
443
1008
  return false;
444
1009
  }
445
- function buildHeadingBaseStyle(level) {
446
- const size = level === 1 ? 44 : level === 2 ? 32 : level === 3 ? 28 : level === 4 ? 24 : 22;
447
- return { bold: true, fontSizeHalfPoints: size };
448
- }
449
- function buildListBlocks(listNode, ordered) {
450
- const items = [];
451
- const stack = [...listNode.children ?? []];
452
- while (stack.length) {
453
- const n = stack.shift();
454
- if (n.type === "tag" && n.name?.toLowerCase() === "li") items.push(n);
455
- }
1010
+ function buildListBlocks(listNode, ordered, level, result) {
1011
+ const liNodes = (listNode.children ?? []).filter(
1012
+ (c) => c.type === "tag" && c.name?.toLowerCase() === "li"
1013
+ );
1014
+ if (!liNodes.length) return [];
456
1015
  const out = [];
457
- for (let i = 0; i < items.length; i++) {
458
- const prefix = ordered ? `${i + 1}. ` : "\u2022 ";
459
- const li = items[i];
1016
+ const numId = ordered ? 2 : 1;
1017
+ const ilvl = Math.max(0, Math.min(8, Math.floor(level)));
1018
+ const leftTwips = 720 * (ilvl + 1);
1019
+ const hangingTwips = 360;
1020
+ for (const li of liNodes) {
1021
+ const nestedLists = [];
460
1022
  const baseStyle = {};
461
1023
  const runs = [];
462
- runs.push({ kind: "text", text: prefix, style: baseStyle });
463
- for (const c of li.children ?? []) collectInlineRuns(c, baseStyle, runs);
1024
+ for (const c of li.children ?? []) {
1025
+ if (c.type === "tag") {
1026
+ const tag = c.name?.toLowerCase();
1027
+ if (tag === "ul" || tag === "ol") {
1028
+ nestedLists.push(c);
1029
+ continue;
1030
+ }
1031
+ }
1032
+ collectInlineRuns(c, baseStyle, runs, result);
1033
+ }
464
1034
  const rXml = [];
465
1035
  for (const token of runs) {
466
1036
  if (token.kind === "br") {
467
1037
  rXml.push("<w:r><w:br/></w:r>");
468
1038
  continue;
469
1039
  }
1040
+ if (token.kind === "image") {
1041
+ rXml.push(buildImageRunXml(token.image));
1042
+ continue;
1043
+ }
470
1044
  const text = token.text;
471
1045
  if (!text) continue;
472
- if (!text.trim()) continue;
1046
+ if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
473
1047
  rXml.push(buildRunXml(token.style, text));
474
1048
  }
475
- if (!rXml.length) continue;
476
- const pPrXml = buildParagraphPrXml(li, inferFirstFontSizeHalfPoints(li) ?? 28, {
477
- leftTwips: 720,
478
- hangingTwips: 360
479
- });
480
- out.push(`<w:p>${pPrXml}${rXml.join("")}</w:p>`);
1049
+ if (rXml.length) {
1050
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
1051
+ const pPrXml = buildParagraphPrXml(
1052
+ li,
1053
+ baseFontHalfPoints,
1054
+ { leftTwips, hangingTwips },
1055
+ void 0
1056
+ );
1057
+ const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
1058
+ const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
1059
+ out.push(`<w:p>${mergedPPrXml}${rXml.join("")}</w:p>`);
1060
+ }
1061
+ for (const nested of nestedLists) {
1062
+ const nestedOrdered = nested.name?.toLowerCase() === "ol";
1063
+ out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
1064
+ }
481
1065
  }
482
1066
  return out;
483
1067
  }
484
- function buildTableXml(tableNode) {
1068
+ function parseCellWidthTwips(node) {
1069
+ const css = parseStyleAttribute(node.attribs?.style);
1070
+ const width = parseCssLengthToTwips(css.width, 28);
1071
+ if (typeof width !== "number" || width <= 0) return void 0;
1072
+ return width;
1073
+ }
1074
+ function estimateTextWidthTwips(text, baseFontHalfPoints) {
1075
+ const basePt = baseFontHalfPoints / 2;
1076
+ const cjkRegex = /[\u3400-\u4dbf\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
1077
+ let cjk = 0;
1078
+ let latin = 0;
1079
+ let spaces = 0;
1080
+ for (const ch of text) {
1081
+ if (ch === " " || ch === " ") {
1082
+ spaces++;
1083
+ continue;
1084
+ }
1085
+ if (cjkRegex.test(ch)) {
1086
+ cjk++;
1087
+ continue;
1088
+ }
1089
+ latin++;
1090
+ }
1091
+ const cjkTwips = Math.round(basePt * 20);
1092
+ const latinTwips = Math.round(basePt * 11);
1093
+ const spaceTwips = Math.round(basePt * 6);
1094
+ return cjk * cjkTwips + latin * latinTwips + spaces * spaceTwips;
1095
+ }
1096
+ function parseBorderShorthand(value, baseFontHalfPoints) {
1097
+ if (!value) return void 0;
1098
+ const raw = value.trim().toLowerCase();
1099
+ if (!raw) return void 0;
1100
+ if (raw === "none" || raw === "0") return { val: "nil", sz: 0 };
1101
+ const tokens = raw.split(/\s+/).filter(Boolean);
1102
+ if (!tokens.length) return void 0;
1103
+ const css = Object.fromEntries(tokens.map((t, i) => [`${i}`, t]));
1104
+ const widthToken = Object.values(css).find((t) => /^(?:\d+(?:\.\d+)?)(?:px|pt)?$/.test(t));
1105
+ const styleToken = Object.values(css).find(
1106
+ (t) => ["none", "solid", "dashed", "dotted", "double", "hidden"].includes(t)
1107
+ );
1108
+ const colorToken = Object.values(css).find((t) => t.startsWith("#") || t.startsWith("rgb("));
1109
+ const widthTwips = parseCssLengthToTwips(widthToken, baseFontHalfPoints);
1110
+ const sz = (() => {
1111
+ if (typeof widthTwips !== "number") return 4;
1112
+ if (widthTwips <= 0) return 0;
1113
+ return Math.max(2, Math.round(widthTwips * 0.4));
1114
+ })();
1115
+ const val = (() => {
1116
+ if (!styleToken) return "single";
1117
+ if (styleToken === "none" || styleToken === "hidden") return "nil";
1118
+ if (styleToken === "solid") return "single";
1119
+ if (styleToken === "dashed") return "dashed";
1120
+ if (styleToken === "dotted") return "dotted";
1121
+ if (styleToken === "double") return "double";
1122
+ return "single";
1123
+ })();
1124
+ const colorHex = parseCssColorToHex(colorToken);
1125
+ return { val, sz, colorHex };
1126
+ }
1127
+ function buildBorderTag(tag, border, fallbackColorHex) {
1128
+ const b = border ?? { val: "single", sz: 4, colorHex: fallbackColorHex };
1129
+ const color = (b.colorHex ?? fallbackColorHex).toUpperCase();
1130
+ return `<w:${tag} w:val="${b.val}" w:sz="${b.sz}" w:space="0" w:color="${color}"/>`;
1131
+ }
1132
+ function injectTableCellParagraphSpacing(pXml) {
1133
+ if (!pXml.includes("<w:p")) return pXml;
1134
+ if (!pXml.includes("<w:p>")) return pXml;
1135
+ const spacingXml = '<w:spacing w:before="0" w:after="0" w:line="360" w:lineRule="auto"/><w:wordWrap w:val="1"/>';
1136
+ if (pXml.includes("<w:pPr>")) {
1137
+ if (pXml.includes("<w:spacing ")) return pXml;
1138
+ return pXml.replace("<w:pPr>", `<w:pPr>${spacingXml}`);
1139
+ }
1140
+ return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
1141
+ }
1142
+ function buildTableCellBlocksXml(cell, baseStyle, result) {
1143
+ const children = cell.children ?? [];
1144
+ const hasBlocks = children.some((c) => {
1145
+ if (c.type !== "tag") return false;
1146
+ const tag = c.name?.toLowerCase();
1147
+ return tag === "p" || tag === "ul" || tag === "ol" || tag === "img" || tag === "canvas" || /^h[1-6]$/.test(tag ?? "");
1148
+ });
1149
+ const out = [];
1150
+ if (!hasBlocks) {
1151
+ const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
1152
+ if (p) out.push(p);
1153
+ return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
1154
+ }
1155
+ for (const c of children) {
1156
+ if (c.type === "tag") {
1157
+ const tag = c.name?.toLowerCase();
1158
+ if (tag === "p") {
1159
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
1160
+ if (p) out.push(p);
1161
+ continue;
1162
+ }
1163
+ if (tag && /^h[1-6]$/.test(tag)) {
1164
+ const level = Number(tag.slice(1));
1165
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
1166
+ if (p) out.push(p);
1167
+ continue;
1168
+ }
1169
+ if (tag === "ul" || tag === "ol") {
1170
+ out.push(...buildListBlocks(c, tag === "ol", 0, result));
1171
+ continue;
1172
+ }
1173
+ if (tag === "img" || tag === "canvas") {
1174
+ const p = buildParagraphXmlFromSingleInlineNode(c, baseStyle, result);
1175
+ if (p) out.push(p);
1176
+ continue;
1177
+ }
1178
+ }
1179
+ }
1180
+ if (!out.length) return "<w:p/>";
1181
+ return out.map(injectTableCellParagraphSpacing).join("");
1182
+ }
1183
+ function buildTableXml(tableNode, result) {
485
1184
  const rows = [];
486
1185
  const stack = [...tableNode.children ?? []];
487
1186
  while (stack.length) {
@@ -489,28 +1188,149 @@ function buildTableXml(tableNode) {
489
1188
  if (n.type === "tag" && n.name?.toLowerCase() === "tr") rows.push(n);
490
1189
  if (n.children?.length) stack.unshift(...n.children);
491
1190
  }
492
- const rowXml = [];
493
- for (const tr of rows) {
494
- const cells = (tr.children ?? []).filter(
495
- (c) => c.type === "tag" && (c.name === "td" || c.name === "th")
1191
+ const rowCells = rows.map(
1192
+ (tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
1193
+ );
1194
+ const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
1195
+ const maxTableWidthTwips = 9360;
1196
+ const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
1197
+ let explicit;
1198
+ let estimated = 0;
1199
+ for (const cells of rowCells) {
1200
+ const cell = cells[i];
1201
+ if (!cell) continue;
1202
+ const w = parseCellWidthTwips(cell);
1203
+ if (typeof w === "number") explicit = explicit ?? w;
1204
+ const text = getTextContent(cell).replace(/\s+/g, " ").trim();
1205
+ if (!text) continue;
1206
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(cell) ?? 28;
1207
+ const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
1208
+ estimated = Math.max(estimated, wTwips);
1209
+ }
1210
+ const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
1211
+ return Math.max(720, Math.min(6e3, Math.round(base)));
1212
+ });
1213
+ const normalizedColWidths = (() => {
1214
+ const sum = estimatedColWidths.reduce((a, b) => a + b, 0);
1215
+ if (!sum) return estimatedColWidths;
1216
+ if (sum <= maxTableWidthTwips) return estimatedColWidths;
1217
+ const scaled = estimatedColWidths.map(
1218
+ (w) => Math.max(720, Math.floor(w * maxTableWidthTwips / sum))
496
1219
  );
1220
+ const scaledSum = scaled.reduce((a, b) => a + b, 0);
1221
+ const diff = maxTableWidthTwips - scaledSum;
1222
+ if (diff !== 0 && scaled.length) scaled[scaled.length - 1] = Math.max(720, scaled[scaled.length - 1] + diff);
1223
+ return scaled;
1224
+ })();
1225
+ const tblGrid = `<w:tblGrid>${normalizedColWidths.map((w) => `<w:gridCol w:w="${w}"/>`).join("")}</w:tblGrid>`;
1226
+ const rowXml = [];
1227
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
1228
+ const tr = rows[rowIdx];
1229
+ const cells = rowCells[rowIdx] ?? [];
497
1230
  const cellXml = [];
498
- for (const cell of cells) {
1231
+ for (let i = 0; i < cells.length; i++) {
1232
+ const cell = cells[i];
499
1233
  const isHeader = cell.name === "th";
500
1234
  const baseStyle = isHeader ? { bold: true } : {};
501
- const pXml = buildParagraphXmlFromContainer(cell, baseStyle);
502
- const paragraphs = pXml ? pXml : "<w:p/>";
503
- cellXml.push(
504
- `<w:tc><w:tcPr><w:tcW w:w="0" w:type="auto"/></w:tcPr>${paragraphs}</w:tc>`
505
- );
1235
+ const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
1236
+ const css = parseStyleAttribute(cell.attribs?.style);
1237
+ const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
1238
+ const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
1239
+ const vAlign = (() => {
1240
+ const v = css["vertical-align"]?.trim().toLowerCase();
1241
+ if (!v) return "";
1242
+ if (v === "middle" || v === "center") return '<w:vAlign w:val="center"/>';
1243
+ if (v === "bottom") return '<w:vAlign w:val="bottom"/>';
1244
+ if (v === "top") return '<w:vAlign w:val="top"/>';
1245
+ return "";
1246
+ })();
1247
+ const shd = (() => {
1248
+ const hex = parseCssColorToHex(css["background-color"]);
1249
+ if (!hex) return "";
1250
+ return `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
1251
+ })();
1252
+ const noWrap = (() => {
1253
+ const ws = css["white-space"]?.trim().toLowerCase();
1254
+ if (ws?.includes("nowrap")) return "<w:noWrap/>";
1255
+ return "";
1256
+ })();
1257
+ const cellBorder = (() => {
1258
+ const bAll = parseBorderShorthand(css.border, 28);
1259
+ const bTop = parseBorderShorthand(css["border-top"] ?? css.border, 28);
1260
+ const bLeft = parseBorderShorthand(css["border-left"] ?? css.border, 28);
1261
+ const bBottom = parseBorderShorthand(css["border-bottom"] ?? css.border, 28);
1262
+ const bRight = parseBorderShorthand(css["border-right"] ?? css.border, 28);
1263
+ const any = bAll || css.border || css["border-top"] || css["border-left"] || css["border-bottom"] || css["border-right"];
1264
+ if (!any) return "";
1265
+ const fallback = bAll?.colorHex ?? "D9D9D9";
1266
+ return `<w:tcBorders>${buildBorderTag("top", bTop, fallback)}${buildBorderTag(
1267
+ "left",
1268
+ bLeft,
1269
+ fallback
1270
+ )}${buildBorderTag("bottom", bBottom, fallback)}${buildBorderTag(
1271
+ "right",
1272
+ bRight,
1273
+ fallback
1274
+ )}</w:tcBorders>`;
1275
+ })();
1276
+ cellXml.push(`<w:tc><w:tcPr>${tcW}${vAlign}${shd}${noWrap}${cellBorder}</w:tcPr>${paragraphs}</w:tc>`);
506
1277
  }
507
1278
  if (cellXml.length) rowXml.push(`<w:tr>${cellXml.join("")}</w:tr>`);
508
1279
  }
509
- const tblPr = `<w:tblPr><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:left w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:right w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="D9D9D9"/></w:tblBorders></w:tblPr>`;
510
- const tblGrid = `<w:tblGrid/>`;
1280
+ const tblCss = parseStyleAttribute(tableNode.attribs?.style);
1281
+ const tblAlign = (() => {
1282
+ const ml = tblCss["margin-left"]?.trim().toLowerCase();
1283
+ const mr = tblCss["margin-right"]?.trim().toLowerCase();
1284
+ const m = tblCss.margin?.trim().toLowerCase();
1285
+ if (ml === "auto" && mr === "auto" || (m?.includes("auto") ?? false)) return '<w:tblJc w:val="center"/>';
1286
+ const ta = tblCss["text-align"]?.trim().toLowerCase();
1287
+ if (ta === "center") return '<w:tblJc w:val="center"/>';
1288
+ if (ta === "right") return '<w:tblJc w:val="right"/>';
1289
+ return "";
1290
+ })();
1291
+ const tblBorder = (() => {
1292
+ const border = parseBorderShorthand(tblCss.border, 28);
1293
+ if (tblCss.border) {
1294
+ const fallback2 = border?.colorHex ?? "D9D9D9";
1295
+ return `<w:tblBorders>${buildBorderTag("top", border, fallback2)}${buildBorderTag(
1296
+ "left",
1297
+ border,
1298
+ fallback2
1299
+ )}${buildBorderTag("bottom", border, fallback2)}${buildBorderTag(
1300
+ "right",
1301
+ border,
1302
+ fallback2
1303
+ )}${buildBorderTag("insideH", border, fallback2)}${buildBorderTag(
1304
+ "insideV",
1305
+ border,
1306
+ fallback2
1307
+ )}</w:tblBorders>`;
1308
+ }
1309
+ const fallback = "D9D9D9";
1310
+ return `<w:tblBorders>${buildBorderTag("top", void 0, fallback)}${buildBorderTag(
1311
+ "left",
1312
+ void 0,
1313
+ fallback
1314
+ )}${buildBorderTag("bottom", void 0, fallback)}${buildBorderTag(
1315
+ "right",
1316
+ void 0,
1317
+ fallback
1318
+ )}${buildBorderTag("insideH", void 0, fallback)}${buildBorderTag("insideV", void 0, fallback)}</w:tblBorders>`;
1319
+ })();
1320
+ const tblW = `<w:tblW w:w="${normalizedColWidths.reduce((a, b) => a + b, 0)}" w:type="dxa"/>`;
1321
+ const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
511
1322
  return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
512
1323
  }
513
- function collectBodyBlocks(node, out) {
1324
+ function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
1325
+ const wrapper = {
1326
+ type: "tag",
1327
+ name: "p",
1328
+ attribs: { style: "text-align: center;" },
1329
+ children: [node]
1330
+ };
1331
+ return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
1332
+ }
1333
+ function collectBodyBlocks(node, out, result) {
514
1334
  if (isSkippableSubtree(node)) return;
515
1335
  if (node.type === "tag") {
516
1336
  const tag = node.name?.toLowerCase();
@@ -519,27 +1339,32 @@ function collectBodyBlocks(node, out) {
519
1339
  return;
520
1340
  }
521
1341
  if (tag === "p") {
522
- const pXml = buildParagraphXmlFromContainer(node, {});
1342
+ const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
1343
+ if (pXml) out.push(pXml);
1344
+ return;
1345
+ }
1346
+ if (tag === "img" || tag === "canvas") {
1347
+ const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
523
1348
  if (pXml) out.push(pXml);
524
1349
  return;
525
1350
  }
526
1351
  if (tag && /^h[1-6]$/.test(tag)) {
527
1352
  const level = Number(tag.slice(1));
528
- const hXml = buildParagraphXmlFromContainer(node, buildHeadingBaseStyle(level));
1353
+ const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
529
1354
  if (hXml) out.push(hXml);
530
1355
  return;
531
1356
  }
532
1357
  if (tag === "table") {
533
- const tblXml = buildTableXml(node);
1358
+ const tblXml = buildTableXml(node, result);
534
1359
  if (tblXml) out.push(tblXml);
535
1360
  return;
536
1361
  }
537
1362
  if (tag === "ul" || tag === "ol") {
538
- out.push(...buildListBlocks(node, tag === "ol"));
1363
+ out.push(...buildListBlocks(node, tag === "ol", 0, result));
539
1364
  return;
540
1365
  }
541
1366
  }
542
- for (const c of node.children ?? []) collectBodyBlocks(c, out);
1367
+ for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
543
1368
  }
544
1369
  function textToWordBodyXml(text) {
545
1370
  const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -557,26 +1382,53 @@ function textToWordBodyXml(text) {
557
1382
  }
558
1383
  return out.join("");
559
1384
  }
560
- function htmlToWordBodyXml(html) {
1385
+ function htmlToWordBody(html) {
561
1386
  const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
562
1387
  const doc = (0, import_htmlparser2.parseDocument)(normalized, {
563
1388
  lowerCaseAttributeNames: true,
564
1389
  lowerCaseTags: true,
565
1390
  recognizeSelfClosing: true
566
1391
  });
1392
+ const result = { bodyXml: "", images: [] };
567
1393
  const out = [];
568
- collectBodyBlocks(doc, out);
569
- if (!out.length) {
570
- const text = getTextContent(doc);
1394
+ collectBodyBlocks(doc, out, result);
1395
+ result.bodyXml = out.join("");
1396
+ return result;
1397
+ }
1398
+ function htmlToWordBodyXml(html) {
1399
+ const { bodyXml } = htmlToWordBody(html);
1400
+ if (!bodyXml) {
1401
+ const text = getTextContent(
1402
+ (0, import_htmlparser2.parseDocument)(html, {
1403
+ lowerCaseAttributeNames: true,
1404
+ lowerCaseTags: true,
1405
+ recognizeSelfClosing: true
1406
+ })
1407
+ );
571
1408
  return textToWordBodyXml(text);
572
1409
  }
573
- return out.join("");
1410
+ return bodyXml;
574
1411
  }
575
- var import_htmlparser2, PAGE_BREAK_XML;
1412
+ function htmlToWordBodyWithAssets(html) {
1413
+ const result = htmlToWordBody(html);
1414
+ if (!result.bodyXml) {
1415
+ const text = getTextContent(
1416
+ (0, import_htmlparser2.parseDocument)(html, {
1417
+ lowerCaseAttributeNames: true,
1418
+ lowerCaseTags: true,
1419
+ recognizeSelfClosing: true
1420
+ })
1421
+ );
1422
+ return { bodyXml: textToWordBodyXml(text), images: [] };
1423
+ }
1424
+ return result;
1425
+ }
1426
+ var import_htmlparser2, IMAGE_RELATIONSHIP_ID_OFFSET, PAGE_BREAK_XML;
576
1427
  var init_htmlToWordBodyXml = __esm({
577
1428
  "src/lib/htmlToWordBodyXml.ts"() {
578
1429
  "use strict";
579
1430
  import_htmlparser2 = require("htmlparser2");
1431
+ IMAGE_RELATIONSHIP_ID_OFFSET = 7;
580
1432
  PAGE_BREAK_XML = '<w:p><w:r><w:br w:type="page"/></w:r></w:p>';
581
1433
  }
582
1434
  });
@@ -618,13 +1470,36 @@ async function xmlToDocxBuffer(xml, options = {}) {
618
1470
  return BufferCtor.from(docx);
619
1471
  }
620
1472
  async function htmlToDocxUint8Array(html, options = {}) {
621
- const { htmlToWordBodyXml: htmlToWordBodyXml2, textToWordBodyXml: textToWordBodyXml2 } = await Promise.resolve().then(() => (init_htmlToWordBodyXml(), htmlToWordBodyXml_exports));
1473
+ const { htmlToWordBodyWithAssets: htmlToWordBodyWithAssets2, htmlToWordBodyXml: htmlToWordBodyXml2, textToWordBodyXml: textToWordBodyXml2 } = await Promise.resolve().then(() => (init_htmlToWordBodyXml(), htmlToWordBodyXml_exports));
1474
+ const { createDocxZipWithAssetsUint8Array: createDocxZipWithAssetsUint8Array2 } = await Promise.resolve().then(() => (init_createDocxZip(), createDocxZip_exports));
622
1475
  const format = options.inputFormat ?? "auto";
623
- const bodyXml = format === "html" ? htmlToWordBodyXml2(html) : format === "text" ? textToWordBodyXml2(html) : looksLikeHtml(html) ? htmlToWordBodyXml2(html) : textToWordBodyXml2(html);
624
- return xmlToDocxUint8Array(bodyXml, {
625
- inputKind: "body",
626
- validateXml: options.validateXml
627
- });
1476
+ const isHtml = format === "html" ? true : format === "text" ? false : looksLikeHtml(html) ? true : false;
1477
+ if (!isHtml) {
1478
+ const bodyXml2 = textToWordBodyXml2(html);
1479
+ return xmlToDocxUint8Array(bodyXml2, {
1480
+ inputKind: "body",
1481
+ validateXml: options.validateXml
1482
+ });
1483
+ }
1484
+ const { bodyXml, images } = htmlToWordBodyWithAssets2(html);
1485
+ if (!images.length) {
1486
+ return xmlToDocxUint8Array(bodyXml || htmlToWordBodyXml2(html), {
1487
+ inputKind: "body",
1488
+ validateXml: options.validateXml
1489
+ });
1490
+ }
1491
+ const assets = images.map((img) => ({
1492
+ relationshipId: img.relationshipId,
1493
+ relationshipType: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
1494
+ target: img.target,
1495
+ data: img.data,
1496
+ contentType: img.contentType
1497
+ }));
1498
+ return createDocxZipWithAssetsUint8Array2(
1499
+ bodyXml,
1500
+ { inputKind: "body", validateXml: options.validateXml },
1501
+ assets
1502
+ );
628
1503
  }
629
1504
  async function htmlToDocxBlob(html, options = {}) {
630
1505
  const docx = await htmlToDocxUint8Array(html, options);