@yinyoudexing/xml2word 0.1.4 → 0.1.6

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
@@ -39,7 +39,38 @@ function ensureXmlDeclaration(xml) {
39
39
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
40
40
  ${xml}`;
41
41
  }
42
- function wrapBodyXml(bodyXml) {
42
+ function resolvePageSetupTwips(page) {
43
+ const size = page?.size;
44
+ const defaultSize = { widthTwips: 11906, heightTwips: 16838 };
45
+ const resolvedSize = typeof size === "object" && size && "widthTwips" in size && "heightTwips" in size ? { widthTwips: Math.round(size.widthTwips), heightTwips: Math.round(size.heightTwips) } : size === "Letter" ? { widthTwips: 12240, heightTwips: 15840 } : size === "Letter_Landscape" ? { widthTwips: 15840, heightTwips: 12240 } : size === "A4_Landscape" ? { widthTwips: 16838, heightTwips: 11906 } : defaultSize;
46
+ const defaultMargins = {
47
+ top: 1440,
48
+ right: 1440,
49
+ bottom: 1440,
50
+ left: 1440,
51
+ header: 708,
52
+ footer: 708,
53
+ gutter: 0
54
+ };
55
+ const m = page?.marginTwips;
56
+ const margins = typeof m === "number" ? { ...defaultMargins, top: m, right: m, bottom: m, left: m } : m ? {
57
+ ...defaultMargins,
58
+ top: m.top ?? defaultMargins.top,
59
+ right: m.right ?? defaultMargins.right,
60
+ bottom: m.bottom ?? defaultMargins.bottom,
61
+ left: m.left ?? defaultMargins.left,
62
+ header: m.header ?? defaultMargins.header,
63
+ footer: m.footer ?? defaultMargins.footer,
64
+ gutter: m.gutter ?? defaultMargins.gutter
65
+ } : defaultMargins;
66
+ return {
67
+ pageWidthTwips: Math.max(1, Math.round(resolvedSize.widthTwips)),
68
+ pageHeightTwips: Math.max(1, Math.round(resolvedSize.heightTwips)),
69
+ margins
70
+ };
71
+ }
72
+ function wrapBodyXml(bodyXml, page) {
73
+ const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips(page);
43
74
  const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
44
75
  <w:document
45
76
  xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
@@ -50,10 +81,9 @@ ${bodyXml}
50
81
  <w:sectPr>
51
82
  <w:headerReference w:type="default" r:id="rId6"/>
52
83
  <w:footerReference w:type="default" r:id="rId7"/>
53
- <w:pgSz w:w="12240" w:h="15840"/>
54
- <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
84
+ <w:pgSz w:w="${pageWidthTwips}" w:h="${pageHeightTwips}"/>
85
+ <w:pgMar w:top="${margins.top}" w:right="${margins.right}" w:bottom="${margins.bottom}" w:left="${margins.left}" w:header="${margins.header}" w:footer="${margins.footer}" w:gutter="${margins.gutter}"/>
55
86
  <w:cols w:space="708"/>
56
- <w:docGrid w:linePitch="360"/>
57
87
  </w:sectPr>
58
88
  </w:body>
59
89
  </w:document>
@@ -70,7 +100,50 @@ function ensureWordNamespace(xml) {
70
100
  `<w:document xmlns:w="${WORD_MAIN_NS}"`
71
101
  );
72
102
  }
73
- function normalizeDocumentXml(xml, inputKind) {
103
+ function applyPageSetupToDocumentXml(xml, page) {
104
+ if (!page) return xml;
105
+ const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips(page);
106
+ const pgSzTag = `<w:pgSz w:w="${pageWidthTwips}" w:h="${pageHeightTwips}"/>`;
107
+ const pgMarTag = `<w:pgMar w:top="${margins.top}" w:right="${margins.right}" w:bottom="${margins.bottom}" w:left="${margins.left}" w:header="${margins.header}" w:footer="${margins.footer}" w:gutter="${margins.gutter}"/>`;
108
+ const sectPrRegex = /<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>/g;
109
+ const hasSectPr = sectPrRegex.test(xml);
110
+ if (!hasSectPr) {
111
+ const insertAt = xml.lastIndexOf("</w:body>");
112
+ if (insertAt < 0) return xml;
113
+ const sectPr = `
114
+ <w:sectPr>
115
+ ${pgSzTag}
116
+ ${pgMarTag}
117
+ <w:cols w:space="708"/>
118
+ </w:sectPr>
119
+ `;
120
+ return xml.slice(0, insertAt) + sectPr + xml.slice(insertAt);
121
+ }
122
+ return xml.replace(sectPrRegex, (sect) => {
123
+ let out = sect;
124
+ const insertTag = (tag) => {
125
+ const headerFooterRegex = /<w:(?:headerReference|footerReference)\b[^>]*\/>/g;
126
+ let last = null;
127
+ let m;
128
+ while (m = headerFooterRegex.exec(out)) last = m;
129
+ const open = out.match(/<w:sectPr\b[^>]*>/);
130
+ const insertPos = last ? last.index + last[0].length : open ? open[0].length : 0;
131
+ out = out.slice(0, insertPos) + tag + out.slice(insertPos);
132
+ };
133
+ if (/<w:pgSz\b[^>]*\/>/.test(out)) {
134
+ out = out.replace(/<w:pgSz\b[^>]*\/>/g, pgSzTag);
135
+ } else {
136
+ insertTag(pgSzTag);
137
+ }
138
+ if (/<w:pgMar\b[^>]*\/>/.test(out)) {
139
+ out = out.replace(/<w:pgMar\b[^>]*\/>/g, pgMarTag);
140
+ } else {
141
+ insertTag(pgMarTag);
142
+ }
143
+ return out;
144
+ });
145
+ }
146
+ function normalizeDocumentXml(xml, inputKind, page) {
74
147
  const trimmed = xml.trim();
75
148
  if (!trimmed) {
76
149
  throw new Error("XML is empty.");
@@ -81,16 +154,16 @@ function normalizeDocumentXml(xml, inputKind) {
81
154
  if (!hasWordDocumentRoot(withNs)) {
82
155
  throw new Error('inputKind="document" requires a <w:document> root.');
83
156
  }
84
- return withNs;
157
+ return applyPageSetupToDocumentXml(withNs, page);
85
158
  }
86
159
  if (inputKind === "body") {
87
- return wrapBodyXml(trimmed);
160
+ return wrapBodyXml(trimmed, page);
88
161
  }
89
162
  if (hasWordDocumentRoot(trimmed)) {
90
163
  const withDecl = ensureXmlDeclaration(trimmed);
91
- return ensureWordNamespace(withDecl);
164
+ return applyPageSetupToDocumentXml(ensureWordNamespace(withDecl), page);
92
165
  }
93
- return wrapBodyXml(trimmed);
166
+ return wrapBodyXml(trimmed, page);
94
167
  }
95
168
  var WORD_MAIN_NS;
96
169
  var init_normalizeDocumentXml = __esm({
@@ -175,7 +248,7 @@ ${relLines}
175
248
  `;
176
249
  }
177
250
  async function createDocxZipUint8Array(xml, options = {}) {
178
- const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
251
+ const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto", options.page);
179
252
  validateXmlIfNeeded(documentXml, options.validateXml ?? true);
180
253
  const zip = new import_jszip.default();
181
254
  zip.file("[Content_Types].xml", buildContentTypesXml([]));
@@ -195,7 +268,7 @@ async function createDocxZipUint8Array(xml, options = {}) {
195
268
  return zip.generateAsync({ type: "uint8array" });
196
269
  }
197
270
  async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
198
- const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
271
+ const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto", options.page);
199
272
  validateXmlIfNeeded(documentXml, options.validateXml ?? true);
200
273
  const zip = new import_jszip.default();
201
274
  zip.file("[Content_Types].xml", buildContentTypesXml(assets));
@@ -627,6 +700,92 @@ function normalizeFontFamily(value) {
627
700
  if (!first) return void 0;
628
701
  return first.replace(/^["']|["']$/g, "");
629
702
  }
703
+ function cssSelectorsHitTarget(selectorsText, targetSelector) {
704
+ const target = targetSelector.trim().toLowerCase();
705
+ if (!target) return false;
706
+ const selectors = selectorsText.replace(/\/\*[\s\S]*?\*\//g, " ").split(",").map((s) => s.replace(/\s+/g, " ").trim().toLowerCase()).filter(Boolean);
707
+ return selectors.some((s) => {
708
+ if (s === target) return true;
709
+ if (!s.startsWith(target)) return false;
710
+ const rest = s.slice(target.length);
711
+ return rest.startsWith(".") || rest.startsWith(":") || rest.startsWith("#") || rest.startsWith("[");
712
+ });
713
+ }
714
+ function parseCssRuleFromHtml(html, selector) {
715
+ const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
716
+ const merged = {};
717
+ let m;
718
+ while (m = styleTagRegex.exec(html)) {
719
+ const cssText = m[1] ?? "";
720
+ const target = selector.trim().toLowerCase();
721
+ if (!target) continue;
722
+ let depth = 0;
723
+ let lastRuleEnd = 0;
724
+ let ruleStart = -1;
725
+ let declStart = -1;
726
+ for (let i = 0; i < cssText.length; i++) {
727
+ const ch = cssText[i];
728
+ if (ch === "{") {
729
+ if (depth === 0) {
730
+ ruleStart = lastRuleEnd;
731
+ declStart = i + 1;
732
+ }
733
+ depth++;
734
+ continue;
735
+ }
736
+ if (ch === "}") {
737
+ if (depth > 0) depth--;
738
+ if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
739
+ const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
740
+ const decl = cssText.slice(declStart, i).trim();
741
+ lastRuleEnd = i + 1;
742
+ ruleStart = -1;
743
+ declStart = -1;
744
+ const hit = cssSelectorsHitTarget(selectorsText, target);
745
+ if (hit) Object.assign(merged, parseStyleAttribute(decl));
746
+ }
747
+ }
748
+ }
749
+ }
750
+ return merged;
751
+ }
752
+ function parseCssRuleFromHtmlFirst(html, selector) {
753
+ const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
754
+ let m;
755
+ while (m = styleTagRegex.exec(html)) {
756
+ const cssText = m[1] ?? "";
757
+ const target = selector.trim().toLowerCase();
758
+ if (!target) continue;
759
+ let depth = 0;
760
+ let lastRuleEnd = 0;
761
+ let ruleStart = -1;
762
+ let declStart = -1;
763
+ for (let i = 0; i < cssText.length; i++) {
764
+ const ch = cssText[i];
765
+ if (ch === "{") {
766
+ if (depth === 0) {
767
+ ruleStart = lastRuleEnd;
768
+ declStart = i + 1;
769
+ }
770
+ depth++;
771
+ continue;
772
+ }
773
+ if (ch === "}") {
774
+ if (depth > 0) depth--;
775
+ if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
776
+ const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
777
+ const decl = cssText.slice(declStart, i).trim();
778
+ lastRuleEnd = i + 1;
779
+ ruleStart = -1;
780
+ declStart = -1;
781
+ const hit = cssSelectorsHitTarget(selectorsText, target);
782
+ if (hit) return parseStyleAttribute(decl);
783
+ }
784
+ }
785
+ }
786
+ }
787
+ return {};
788
+ }
630
789
  function mergeTextStyle(base, patch) {
631
790
  return {
632
791
  bold: patch.bold ?? base.bold,
@@ -765,7 +924,7 @@ function applyMaxBoxPx(size, maxBox) {
765
924
  const scale = Math.min(1, maxBox.maxWidthPx / w, maxBox.maxHeightPx / h);
766
925
  return { widthPx: Math.max(1, Math.round(w * scale)), heightPx: Math.max(1, Math.round(h * scale)) };
767
926
  }
768
- function computeImageSizePx(node, intrinsic) {
927
+ function computeImageSizePx(node, intrinsic, maxBox) {
769
928
  const wAttr = node.attribs?.width ? Number(node.attribs.width) : void 0;
770
929
  const hAttr = node.attribs?.height ? Number(node.attribs.height) : void 0;
771
930
  const css = parseStyleAttribute(node.attribs?.style);
@@ -777,9 +936,9 @@ function computeImageSizePx(node, intrinsic) {
777
936
  const widthPx = typeof wCss === "number" ? wCss : typeof widthAttrPx === "number" ? widthAttrPx : intrinsic?.widthPx ?? 300;
778
937
  const heightPx = typeof hCss === "number" ? hCss : typeof heightAttrPx === "number" ? heightAttrPx : intrinsic?.heightPx ?? 150;
779
938
  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 };
780
- return applyMaxBoxPx(finalSize, { maxWidthPx: 624, maxHeightPx: 864 });
939
+ return applyMaxBoxPx(finalSize, maxBox ?? { maxWidthPx: 624, maxHeightPx: 864 });
781
940
  }
782
- function collectInlineRuns(node, inherited, out, result) {
941
+ function collectInlineRuns(node, inherited, out, result, ctx) {
783
942
  if (node.type === "text") {
784
943
  const text = node.data ?? "";
785
944
  if (text) out.push({ kind: "text", text, style: inherited });
@@ -797,7 +956,11 @@ function collectInlineRuns(node, inherited, out, result) {
797
956
  const parsed = parseImageDataUrl(src);
798
957
  if (!parsed) return;
799
958
  const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
800
- const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
959
+ const { widthPx, heightPx } = computeImageSizePx(
960
+ node,
961
+ intrinsic,
962
+ ctx ? { maxWidthPx: ctx.maxImageWidthPx, maxHeightPx: ctx.maxImageHeightPx } : void 0
963
+ );
801
964
  const id = result.images.length + 1;
802
965
  const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
803
966
  const target = `media/image${id}.${parsed.extension}`;
@@ -820,7 +983,11 @@ function collectInlineRuns(node, inherited, out, result) {
820
983
  const bufferW = node.attribs?.width ? Number(node.attribs.width) : void 0;
821
984
  const bufferH = node.attribs?.height ? Number(node.attribs.height) : void 0;
822
985
  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);
823
- const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
986
+ const { widthPx, heightPx } = computeImageSizePx(
987
+ node,
988
+ intrinsic,
989
+ ctx ? { maxWidthPx: ctx.maxImageWidthPx, maxHeightPx: ctx.maxImageHeightPx } : void 0
990
+ );
824
991
  const id = result.images.length + 1;
825
992
  const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
826
993
  const target = `media/image${id}.${parsed.extension}`;
@@ -837,11 +1004,11 @@ function collectInlineRuns(node, inherited, out, result) {
837
1004
  }
838
1005
  const next = mergeTextStyle(inherited, styleFromElement(node));
839
1006
  const children2 = node.children ?? [];
840
- for (const c of children2) collectInlineRuns(c, next, out, result);
1007
+ for (const c of children2) collectInlineRuns(c, next, out, result, ctx);
841
1008
  return;
842
1009
  }
843
1010
  const children = node.children ?? [];
844
- for (const c of children) collectInlineRuns(c, inherited, out, result);
1011
+ for (const c of children) collectInlineRuns(c, inherited, out, result, ctx);
845
1012
  }
846
1013
  function buildRunXml(style, text) {
847
1014
  const rPrParts = [];
@@ -903,10 +1070,29 @@ function parseCssLengthToTwips(value, baseFontHalfPoints) {
903
1070
  const basePt = baseFontHalfPoints / 2;
904
1071
  return Math.round(Number(em[1]) * basePt * 20);
905
1072
  }
1073
+ const rem = v.match(/^(-?\d+(?:\.\d+)?)rem$/);
1074
+ if (rem) return Math.round(Number(rem[1]) * 16 * 72 * 20 / 96);
906
1075
  const num = v.match(/^(-?\d+(?:\.\d+)?)$/);
907
1076
  if (num) return Math.round(Number(num[1]));
908
1077
  return void 0;
909
1078
  }
1079
+ function extractMarginBeforeAfterTwips(css, baseFontHalfPoints) {
1080
+ const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
1081
+ const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
1082
+ if (typeof before === "number" || typeof after === "number") {
1083
+ return { beforeTwips: before, afterTwips: after };
1084
+ }
1085
+ const m = css.margin?.trim().toLowerCase();
1086
+ if (!m) return {};
1087
+ const tokens = m.split(/\s+/).filter(Boolean);
1088
+ if (!tokens.length) return {};
1089
+ const topToken = tokens[0];
1090
+ const bottomToken = tokens.length === 1 ? tokens[0] : tokens.length === 2 ? tokens[0] : tokens[2] ?? tokens[0];
1091
+ return {
1092
+ beforeTwips: parseCssLengthToTwips(topToken, baseFontHalfPoints),
1093
+ afterTwips: parseCssLengthToTwips(bottomToken, baseFontHalfPoints)
1094
+ };
1095
+ }
910
1096
  function inferFirstFontSizeHalfPoints(node) {
911
1097
  const stack = [node];
912
1098
  while (stack.length) {
@@ -923,8 +1109,10 @@ function inferFirstFontSizeHalfPoints(node) {
923
1109
  }
924
1110
  return void 0;
925
1111
  }
926
- function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
927
- const css = parseStyleAttribute(node.attribs?.style);
1112
+ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId, defaultCss) {
1113
+ const tag = node.type === "tag" ? node.name?.toLowerCase() : void 0;
1114
+ const inlineCss = parseStyleAttribute(node.attribs?.style);
1115
+ const css = defaultCss ? { ...defaultCss, ...inlineCss } : inlineCss;
928
1116
  const parts = [];
929
1117
  if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
930
1118
  const shdHex = extractBackgroundFillHex(css);
@@ -951,10 +1139,17 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
951
1139
  if (typeof hangingTwips === "number") indAttrs.push(`w:hanging="${hangingTwips}"`);
952
1140
  if (typeof firstLine === "number") indAttrs.push(`w:firstLine="${firstLine}"`);
953
1141
  if (indAttrs.length) parts.push(`<w:ind ${indAttrs.join(" ")}/>`);
954
- const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
955
- const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
1142
+ const hasInlineBefore = inlineCss["margin-top"] != null;
1143
+ const hasInlineAfter = inlineCss["margin-bottom"] != null;
1144
+ const beforeToken = inlineCss["margin-top"] ?? (pStyleId ? void 0 : defaultCss?.["margin-top"]);
1145
+ const afterToken = inlineCss["margin-bottom"] ?? (pStyleId ? void 0 : defaultCss?.["margin-bottom"]);
1146
+ let before = parseCssLengthToTwips(beforeToken, baseFontHalfPoints);
1147
+ let after = parseCssLengthToTwips(afterToken, baseFontHalfPoints);
1148
+ if (tag === "p" && !hasInlineBefore && typeof before === "number") before = Math.min(before, 160);
1149
+ if (tag === "p" && !hasInlineAfter && typeof after === "number") after = Math.min(after, 160);
956
1150
  const lineHeight = (() => {
957
- const lh = css["line-height"]?.trim().toLowerCase();
1151
+ const lhToken = inlineCss["line-height"] ?? (pStyleId ? void 0 : defaultCss?.["line-height"]);
1152
+ const lh = lhToken?.trim().toLowerCase();
958
1153
  if (!lh || lh === "normal") return void 0;
959
1154
  const unitless = lh.match(/^(\d+(?:\.\d+)?)$/);
960
1155
  if (unitless) {
@@ -969,8 +1164,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
969
1164
  })();
970
1165
  if (typeof before === "number" || typeof after === "number" || typeof lineHeight === "number") {
971
1166
  const attrs = [];
972
- if (typeof before === "number") attrs.push(`w:before="${Math.max(0, before)}"`);
973
- if (typeof after === "number") attrs.push(`w:after="${Math.max(0, after)}"`);
1167
+ if (typeof before === "number") {
1168
+ attrs.push(`w:before="${Math.max(0, before)}"`);
1169
+ } else if (typeof lineHeight === "number") {
1170
+ attrs.push('w:before="0"');
1171
+ }
1172
+ if (typeof after === "number") {
1173
+ attrs.push(`w:after="${Math.max(0, after)}"`);
1174
+ } else if (typeof lineHeight === "number") {
1175
+ attrs.push('w:after="160"');
1176
+ }
974
1177
  if (typeof lineHeight === "number") {
975
1178
  attrs.push(`w:line="${lineHeight}"`, 'w:lineRule="exact"');
976
1179
  }
@@ -979,16 +1182,19 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
979
1182
  if (!parts.length) return "";
980
1183
  return `<w:pPr>${parts.join("")}</w:pPr>`;
981
1184
  }
982
- function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
983
- const containerStyle = mergeTextStyle(baseStyle, styleFromElement(node));
1185
+ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result, ctx) {
1186
+ const seededBaseStyle = ctx ? { fontSizeHalfPoints: ctx.defaultBaseFontHalfPoints } : {};
1187
+ const containerStyle = mergeTextStyle(mergeTextStyle(seededBaseStyle, baseStyle), styleFromElement(node));
984
1188
  const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
985
- const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
1189
+ const computedBaseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
1190
+ const defaultCss = ctx ? node.type === "tag" && node.name?.toLowerCase() === "p" ? { ...ctx.defaultBodyCss, ...ctx.defaultPCss } : ctx.defaultBodyCss : void 0;
1191
+ const pPrXml = buildParagraphPrXml(node, computedBaseFontHalfPoints, extraInd, pStyleId, defaultCss);
986
1192
  const runs = [];
987
1193
  const res = result ?? {
988
1194
  bodyXml: "",
989
1195
  images: []
990
1196
  };
991
- for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res);
1197
+ for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res, ctx);
992
1198
  const rXml = [];
993
1199
  for (const token of runs) {
994
1200
  if (token.kind === "br") {
@@ -1007,6 +1213,11 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, res
1007
1213
  if (!rXml.length) return "";
1008
1214
  return `<w:p>${pPrXml}${rXml.join("")}</w:p>`;
1009
1215
  }
1216
+ function buildSpacerParagraphXml(afterTwips) {
1217
+ const after = Math.max(0, Math.round(afterTwips));
1218
+ if (!after) return "";
1219
+ return `<w:p><w:pPr><w:spacing w:before="0" w:after="${after}" w:line="1" w:lineRule="exact"/></w:pPr><w:r><w:rPr><w:sz w:val="1"/><w:szCs w:val="1"/></w:rPr><w:t></w:t></w:r></w:p>`;
1220
+ }
1010
1221
  function isExplicitPageBreak(node) {
1011
1222
  if (node.type !== "tag") return false;
1012
1223
  const tag = node.name?.toLowerCase();
@@ -1015,13 +1226,14 @@ function isExplicitPageBreak(node) {
1015
1226
  const classList = cls ? cls.split(/\s+/) : [];
1016
1227
  if (tag === "hr" && classList.includes("page-break")) return true;
1017
1228
  if (classList.includes("page-break")) return true;
1229
+ if (classList.includes("umo-page-break")) return true;
1018
1230
  if (node.attribs?.["data-page-break"] === "true") return true;
1019
1231
  const after = css["page-break-after"]?.toLowerCase() ?? css["break-after"]?.toLowerCase();
1020
1232
  const before = css["page-break-before"]?.toLowerCase() ?? css["break-before"]?.toLowerCase();
1021
1233
  if (after?.includes("always") || before?.includes("always")) return true;
1022
1234
  return false;
1023
1235
  }
1024
- function buildListBlocks(listNode, ordered, level, result) {
1236
+ function buildListBlocks(listNode, ordered, level, result, ctx) {
1025
1237
  const liNodes = (listNode.children ?? []).filter(
1026
1238
  (c) => c.type === "tag" && c.name?.toLowerCase() === "li"
1027
1239
  );
@@ -1043,7 +1255,7 @@ function buildListBlocks(listNode, ordered, level, result) {
1043
1255
  continue;
1044
1256
  }
1045
1257
  }
1046
- collectInlineRuns(c, baseStyle, runs, result);
1258
+ collectInlineRuns(c, baseStyle, runs, result, ctx);
1047
1259
  }
1048
1260
  const rXml = [];
1049
1261
  for (const token of runs) {
@@ -1061,12 +1273,13 @@ function buildListBlocks(listNode, ordered, level, result) {
1061
1273
  rXml.push(buildRunXml(token.style, text));
1062
1274
  }
1063
1275
  if (rXml.length) {
1064
- const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
1276
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
1065
1277
  const pPrXml = buildParagraphPrXml(
1066
1278
  li,
1067
1279
  baseFontHalfPoints,
1068
1280
  { leftTwips, hangingTwips },
1069
- void 0
1281
+ void 0,
1282
+ ctx?.defaultBodyCss
1070
1283
  );
1071
1284
  const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
1072
1285
  const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
@@ -1074,7 +1287,7 @@ function buildListBlocks(listNode, ordered, level, result) {
1074
1287
  }
1075
1288
  for (const nested of nestedLists) {
1076
1289
  const nestedOrdered = nested.name?.toLowerCase() === "ol";
1077
- out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
1290
+ out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result, ctx));
1078
1291
  }
1079
1292
  }
1080
1293
  return out;
@@ -1153,7 +1366,7 @@ function injectTableCellParagraphSpacing(pXml) {
1153
1366
  }
1154
1367
  return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
1155
1368
  }
1156
- function buildTableCellBlocksXml(cell, baseStyle, result) {
1369
+ function buildTableCellBlocksXml(cell, baseStyle, result, ctx) {
1157
1370
  const children = cell.children ?? [];
1158
1371
  const hasBlocks = children.some((c) => {
1159
1372
  if (c.type !== "tag") return false;
@@ -1162,7 +1375,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
1162
1375
  });
1163
1376
  const out = [];
1164
1377
  if (!hasBlocks) {
1165
- const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
1378
+ const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result, ctx);
1166
1379
  if (p) out.push(p);
1167
1380
  return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
1168
1381
  }
@@ -1170,18 +1383,18 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
1170
1383
  if (c.type === "tag") {
1171
1384
  const tag = c.name?.toLowerCase();
1172
1385
  if (tag === "p") {
1173
- const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
1386
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result, ctx);
1174
1387
  if (p) out.push(p);
1175
1388
  continue;
1176
1389
  }
1177
1390
  if (tag && /^h[1-6]$/.test(tag)) {
1178
1391
  const level = Number(tag.slice(1));
1179
- const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
1392
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result, ctx);
1180
1393
  if (p) out.push(p);
1181
1394
  continue;
1182
1395
  }
1183
1396
  if (tag === "ul" || tag === "ol") {
1184
- out.push(...buildListBlocks(c, tag === "ol", 0, result));
1397
+ out.push(...buildListBlocks(c, tag === "ol", 0, result, ctx));
1185
1398
  continue;
1186
1399
  }
1187
1400
  if (tag === "img" || tag === "canvas") {
@@ -1194,7 +1407,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
1194
1407
  if (!out.length) return "<w:p/>";
1195
1408
  return out.map(injectTableCellParagraphSpacing).join("");
1196
1409
  }
1197
- function buildTableXml(tableNode, result) {
1410
+ function buildTableXml(tableNode, result, ctx) {
1198
1411
  const rows = [];
1199
1412
  const stack = [...tableNode.children ?? []];
1200
1413
  while (stack.length) {
@@ -1206,9 +1419,19 @@ function buildTableXml(tableNode, result) {
1206
1419
  (tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
1207
1420
  );
1208
1421
  const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
1209
- const maxTableWidthTwips = 9360;
1422
+ const maxTableWidthTwips = ctx?.maxTableWidthTwips ?? 9360;
1423
+ const colGroup = (tableNode.children ?? []).find((c) => c.type === "tag" && c.name === "colgroup");
1424
+ const colWidthsFromGroup = [];
1425
+ if (colGroup) {
1426
+ const cols = (colGroup.children ?? []).filter((c) => c.type === "tag" && c.name === "col");
1427
+ for (const col of cols) {
1428
+ const css = parseStyleAttribute(col.attribs?.style);
1429
+ const w = parseCssLengthToTwips(css.width ?? css["min-width"], 28);
1430
+ colWidthsFromGroup.push(w);
1431
+ }
1432
+ }
1210
1433
  const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
1211
- let explicit;
1434
+ let explicit = colWidthsFromGroup[i];
1212
1435
  let estimated = 0;
1213
1436
  for (const cells of rowCells) {
1214
1437
  const cell = cells[i];
@@ -1221,7 +1444,7 @@ function buildTableXml(tableNode, result) {
1221
1444
  const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
1222
1445
  estimated = Math.max(estimated, wTwips);
1223
1446
  }
1224
- const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
1447
+ const base = typeof explicit === "number" ? Math.max(explicit, estimated) : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
1225
1448
  return Math.max(720, Math.min(6e3, Math.round(base)));
1226
1449
  });
1227
1450
  const normalizedColWidths = (() => {
@@ -1246,7 +1469,7 @@ function buildTableXml(tableNode, result) {
1246
1469
  const cell = cells[i];
1247
1470
  const isHeader = cell.name === "th";
1248
1471
  const baseStyle = isHeader ? { bold: true } : {};
1249
- const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
1472
+ const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result, ctx);
1250
1473
  const css = parseStyleAttribute(cell.attribs?.style);
1251
1474
  const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
1252
1475
  const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
@@ -1335,14 +1558,14 @@ function buildTableXml(tableNode, result) {
1335
1558
  const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
1336
1559
  return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
1337
1560
  }
1338
- function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
1561
+ function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result, ctx) {
1339
1562
  const wrapper = {
1340
1563
  type: "tag",
1341
1564
  name: "p",
1342
1565
  attribs: { style: "text-align: center;" },
1343
1566
  children: [node]
1344
1567
  };
1345
- return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
1568
+ return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result, ctx);
1346
1569
  }
1347
1570
  function isRecognizedBlockTag(tag) {
1348
1571
  if (!tag) return false;
@@ -1354,7 +1577,22 @@ function isRecognizedBlockTag(tag) {
1354
1577
  if (tag === "pre") return true;
1355
1578
  return false;
1356
1579
  }
1357
- function collectDivBlocks(node, out, result) {
1580
+ function subtreeHasRecognizedBlocks(root) {
1581
+ const stack = [root];
1582
+ while (stack.length) {
1583
+ const cur = stack.pop();
1584
+ if (cur.type === "tag") {
1585
+ if (isExplicitPageBreak(cur)) return true;
1586
+ if (isRecognizedBlockTag(cur.name?.toLowerCase())) return true;
1587
+ }
1588
+ const children = cur.children ?? [];
1589
+ for (let i = children.length - 1; i >= 0; i--) {
1590
+ stack.push(children[i]);
1591
+ }
1592
+ }
1593
+ return false;
1594
+ }
1595
+ function collectDivBlocks(node, out, result, ctx) {
1358
1596
  const parentStyle = node.attribs?.style;
1359
1597
  const inlineBuffer = [];
1360
1598
  const flushInline = () => {
@@ -1364,7 +1602,7 @@ function collectDivBlocks(node, out, result) {
1364
1602
  attribs: { style: parentStyle },
1365
1603
  children: inlineBuffer.splice(0)
1366
1604
  };
1367
- const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
1605
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
1368
1606
  if (pXml) out.push(pXml);
1369
1607
  };
1370
1608
  const children = node.children ?? [];
@@ -1378,19 +1616,15 @@ function collectDivBlocks(node, out, result) {
1378
1616
  }
1379
1617
  if (isRecognizedBlockTag(tag)) {
1380
1618
  if (inlineBuffer.length) flushInline();
1381
- collectBodyBlocks(child, out, result);
1619
+ collectBodyBlocks(child, out, result, ctx);
1620
+ continue;
1621
+ }
1622
+ if (subtreeHasRecognizedBlocks(child)) {
1623
+ if (inlineBuffer.length) flushInline();
1624
+ collectBodyBlocks(child, out, result, ctx);
1382
1625
  continue;
1383
1626
  }
1384
1627
  if (tag === "div") {
1385
- const childHasRecognizedBlocks = (child.children ?? []).some((gc) => {
1386
- if (gc.type !== "tag") return false;
1387
- return isRecognizedBlockTag(gc.name?.toLowerCase());
1388
- });
1389
- if (childHasRecognizedBlocks) {
1390
- if (inlineBuffer.length) flushInline();
1391
- collectBodyBlocks(child, out, result);
1392
- continue;
1393
- }
1394
1628
  if (inlineBuffer.length) flushInline();
1395
1629
  const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
1396
1630
  const wrapper = {
@@ -1399,7 +1633,7 @@ function collectDivBlocks(node, out, result) {
1399
1633
  attribs: { style: mergedStyle || void 0 },
1400
1634
  children: child.children ?? []
1401
1635
  };
1402
- const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
1636
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
1403
1637
  if (pXml) out.push(pXml);
1404
1638
  continue;
1405
1639
  }
@@ -1408,7 +1642,7 @@ function collectDivBlocks(node, out, result) {
1408
1642
  }
1409
1643
  if (inlineBuffer.length) flushInline();
1410
1644
  }
1411
- function collectBodyBlocks(node, out, result) {
1645
+ function collectBodyBlocks(node, out, result, ctx) {
1412
1646
  if (isSkippableSubtree(node)) return;
1413
1647
  if (node.type === "tag") {
1414
1648
  const tag = node.name?.toLowerCase();
@@ -1417,36 +1651,51 @@ function collectBodyBlocks(node, out, result) {
1417
1651
  return;
1418
1652
  }
1419
1653
  if (tag === "p") {
1420
- const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
1654
+ const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result, ctx);
1421
1655
  if (pXml) out.push(pXml);
1422
1656
  return;
1423
1657
  }
1424
1658
  if (tag === "img" || tag === "canvas") {
1425
- const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
1659
+ const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result, ctx);
1426
1660
  if (pXml) out.push(pXml);
1427
1661
  return;
1428
1662
  }
1429
1663
  if (tag && /^h[1-6]$/.test(tag)) {
1430
1664
  const level = Number(tag.slice(1));
1431
- const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
1665
+ const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result, ctx);
1432
1666
  if (hXml) out.push(hXml);
1433
1667
  return;
1434
1668
  }
1435
1669
  if (tag === "table") {
1436
- const tblXml = buildTableXml(node, result);
1670
+ const tblXml = buildTableXml(node, result, ctx);
1437
1671
  if (tblXml) out.push(tblXml);
1438
1672
  return;
1439
1673
  }
1440
1674
  if (tag === "ul" || tag === "ol") {
1441
- out.push(...buildListBlocks(node, tag === "ol", 0, result));
1675
+ out.push(...buildListBlocks(node, tag === "ol", 0, result, ctx));
1442
1676
  return;
1443
1677
  }
1444
1678
  if (tag === "div") {
1445
- collectDivBlocks(node, out, result);
1679
+ if (hasClass(node, "tableWrapper")) {
1680
+ const display = ctx.tableWrapperCss.display?.trim().toLowerCase();
1681
+ if (display !== "contents") {
1682
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(node) ?? ctx.defaultBaseFontHalfPoints;
1683
+ const { beforeTwips, afterTwips } = extractMarginBeforeAfterTwips(ctx.tableWrapperCss, baseFontHalfPoints);
1684
+ const beforeXml = typeof beforeTwips === "number" && beforeTwips > 0 ? buildSpacerParagraphXml(beforeTwips) : "";
1685
+ const afterXml = typeof afterTwips === "number" && afterTwips > 0 ? buildSpacerParagraphXml(afterTwips) : "";
1686
+ if (beforeXml) out.push(beforeXml);
1687
+ collectDivBlocks(node, out, result, ctx);
1688
+ if (afterXml) out.push(afterXml);
1689
+ return;
1690
+ }
1691
+ collectDivBlocks(node, out, result, ctx);
1692
+ return;
1693
+ }
1694
+ collectDivBlocks(node, out, result, ctx);
1446
1695
  return;
1447
1696
  }
1448
1697
  }
1449
- for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
1698
+ for (const c of node.children ?? []) collectBodyBlocks(c, out, result, ctx);
1450
1699
  }
1451
1700
  function textToWordBodyXml(text) {
1452
1701
  const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -1464,8 +1713,22 @@ function textToWordBodyXml(text) {
1464
1713
  }
1465
1714
  return out.join("");
1466
1715
  }
1467
- function htmlToWordBody(html) {
1716
+ function htmlToWordBody(html, layout) {
1468
1717
  const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1718
+ const bodyCss = parseCssRuleFromHtmlFirst(normalized, "body");
1719
+ const pCss = parseCssRuleFromHtmlFirst(normalized, "p");
1720
+ const defaultBaseFontHalfPoints = parseFontSizeToHalfPoints(bodyCss["font-size"]) ?? 28;
1721
+ const defaultBodyCss = {};
1722
+ if (bodyCss["line-height"]) defaultBodyCss["line-height"] = bodyCss["line-height"];
1723
+ const defaultPCss = {};
1724
+ if (pCss["line-height"]) defaultPCss["line-height"] = pCss["line-height"];
1725
+ if (pCss["margin-top"]) defaultPCss["margin-top"] = pCss["margin-top"];
1726
+ if (pCss["margin-bottom"]) defaultPCss["margin-bottom"] = pCss["margin-bottom"];
1727
+ if (pCss["text-align"]) defaultPCss["text-align"] = pCss["text-align"];
1728
+ const tableWrapperCss = {
1729
+ ...parseCssRuleFromHtml(normalized, ".tableWrapper"),
1730
+ ...parseCssRuleFromHtml(normalized, ".tiptap .tableWrapper")
1731
+ };
1469
1732
  const doc = (0, import_htmlparser2.parseDocument)(normalized, {
1470
1733
  lowerCaseAttributeNames: true,
1471
1734
  lowerCaseTags: true,
@@ -1473,12 +1736,21 @@ function htmlToWordBody(html) {
1473
1736
  });
1474
1737
  const result = { bodyXml: "", images: [] };
1475
1738
  const out = [];
1476
- collectBodyBlocks(doc, out, result);
1739
+ const ctx = {
1740
+ defaultBaseFontHalfPoints,
1741
+ defaultBodyCss,
1742
+ defaultPCss,
1743
+ tableWrapperCss,
1744
+ maxTableWidthTwips: layout?.maxTableWidthTwips ?? 9360,
1745
+ maxImageWidthPx: layout?.maxImageWidthPx ?? 624,
1746
+ maxImageHeightPx: layout?.maxImageHeightPx ?? 864
1747
+ };
1748
+ collectBodyBlocks(doc, out, result, ctx);
1477
1749
  result.bodyXml = out.join("");
1478
1750
  return result;
1479
1751
  }
1480
1752
  function htmlToWordBodyXml(html) {
1481
- const { bodyXml } = htmlToWordBody(html);
1753
+ const { bodyXml } = htmlToWordBody(html, void 0);
1482
1754
  if (!bodyXml) {
1483
1755
  const text = getTextContent(
1484
1756
  (0, import_htmlparser2.parseDocument)(html, {
@@ -1491,8 +1763,8 @@ function htmlToWordBodyXml(html) {
1491
1763
  }
1492
1764
  return bodyXml;
1493
1765
  }
1494
- function htmlToWordBodyWithAssets(html) {
1495
- const result = htmlToWordBody(html);
1766
+ function htmlToWordBodyWithAssets(html, layout) {
1767
+ const result = htmlToWordBody(html, layout);
1496
1768
  if (!result.bodyXml) {
1497
1769
  const text = getTextContent(
1498
1770
  (0, import_htmlparser2.parseDocument)(html, {
@@ -1532,6 +1804,47 @@ function looksLikeHtml(input) {
1532
1804
  if (!s.includes("<") || !s.includes(">")) return false;
1533
1805
  return /<\/?[a-zA-Z][\s\S]*?>/.test(s);
1534
1806
  }
1807
+ function resolvePageSetupTwips2(page) {
1808
+ const size = page?.size;
1809
+ const defaultSize = { widthTwips: 11906, heightTwips: 16838 };
1810
+ const resolvedSize = typeof size === "object" && size && "widthTwips" in size && "heightTwips" in size ? { widthTwips: Math.round(size.widthTwips), heightTwips: Math.round(size.heightTwips) } : size === "Letter" ? { widthTwips: 12240, heightTwips: 15840 } : size === "Letter_Landscape" ? { widthTwips: 15840, heightTwips: 12240 } : size === "A4_Landscape" ? { widthTwips: 16838, heightTwips: 11906 } : defaultSize;
1811
+ const defaultMargins = {
1812
+ top: 1440,
1813
+ right: 1440,
1814
+ bottom: 1440,
1815
+ left: 1440,
1816
+ header: 708,
1817
+ footer: 708,
1818
+ gutter: 0
1819
+ };
1820
+ const m = page?.marginTwips;
1821
+ const mergedMargins = typeof m === "number" ? { ...defaultMargins, top: m, right: m, bottom: m, left: m } : m ? {
1822
+ ...defaultMargins,
1823
+ top: m.top ?? defaultMargins.top,
1824
+ right: m.right ?? defaultMargins.right,
1825
+ bottom: m.bottom ?? defaultMargins.bottom,
1826
+ left: m.left ?? defaultMargins.left,
1827
+ header: m.header ?? defaultMargins.header,
1828
+ footer: m.footer ?? defaultMargins.footer,
1829
+ gutter: m.gutter ?? defaultMargins.gutter
1830
+ } : defaultMargins;
1831
+ return {
1832
+ pageWidthTwips: Math.max(1, Math.round(resolvedSize.widthTwips)),
1833
+ pageHeightTwips: Math.max(1, Math.round(resolvedSize.heightTwips)),
1834
+ margins: mergedMargins
1835
+ };
1836
+ }
1837
+ function computeHtmlConversionLayoutFromPage(page) {
1838
+ const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips2(page);
1839
+ const contentWidthTwips = Math.max(1, pageWidthTwips - margins.left - margins.right);
1840
+ const contentHeightTwips = Math.max(1, pageHeightTwips - margins.top - margins.bottom);
1841
+ const twipsToPx = (twips) => Math.max(1, Math.round(twips / 15));
1842
+ return {
1843
+ maxTableWidthTwips: contentWidthTwips,
1844
+ maxImageWidthPx: twipsToPx(contentWidthTwips),
1845
+ maxImageHeightPx: twipsToPx(contentHeightTwips)
1846
+ };
1847
+ }
1535
1848
  async function xmlToDocxUint8Array(xml, options = {}) {
1536
1849
  const { createDocxZipUint8Array: createDocxZipUint8Array2 } = await Promise.resolve().then(() => (init_createDocxZip(), createDocxZip_exports));
1537
1850
  return createDocxZipUint8Array2(xml, options);
@@ -1560,14 +1873,17 @@ async function htmlToDocxUint8Array(html, options = {}) {
1560
1873
  const bodyXml2 = textToWordBodyXml2(html);
1561
1874
  return xmlToDocxUint8Array(bodyXml2, {
1562
1875
  inputKind: "body",
1563
- validateXml: options.validateXml
1876
+ validateXml: options.validateXml,
1877
+ page: options.page
1564
1878
  });
1565
1879
  }
1566
- const { bodyXml, images } = htmlToWordBodyWithAssets2(html);
1880
+ const layout = options.page ? computeHtmlConversionLayoutFromPage(options.page) : void 0;
1881
+ const { bodyXml, images } = htmlToWordBodyWithAssets2(html, layout);
1567
1882
  if (!images.length) {
1568
1883
  return xmlToDocxUint8Array(bodyXml || htmlToWordBodyXml2(html), {
1569
1884
  inputKind: "body",
1570
- validateXml: options.validateXml
1885
+ validateXml: options.validateXml,
1886
+ page: options.page
1571
1887
  });
1572
1888
  }
1573
1889
  const assets = images.map((img) => ({
@@ -1579,7 +1895,7 @@ async function htmlToDocxUint8Array(html, options = {}) {
1579
1895
  }));
1580
1896
  return createDocxZipWithAssetsUint8Array2(
1581
1897
  bodyXml,
1582
- { inputKind: "body", validateXml: options.validateXml },
1898
+ { inputKind: "body", validateXml: options.validateXml, page: options.page },
1583
1899
  assets
1584
1900
  );
1585
1901
  }