@coding01/docsjs 0.1.3 → 0.1.5

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/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineDocsWordElement
3
- } from "./chunk-PRPDJOB7.js";
3
+ } from "./chunk-IBVWD4UO.js";
4
4
 
5
5
  // src/react/WordFidelityEditorReact.tsx
6
6
  import React, { useEffect, useRef } from "react";
package/dist/vue.cjs CHANGED
@@ -79,6 +79,9 @@ function getAttr(node, name) {
79
79
  function emuToPx(emu) {
80
80
  return emu * 96 / 914400;
81
81
  }
82
+ function twipToPx(twip) {
83
+ return twip * 96 / 1440;
84
+ }
82
85
  function parseDrawingSizePx(drawing) {
83
86
  const extentNode = queryAllByLocalName(drawing, "extent").find((node) => {
84
87
  const parent = node.parentElement;
@@ -107,9 +110,7 @@ function imageDimensionAttributes(sizePx) {
107
110
  }
108
111
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
109
112
  }
110
- function parseAnchorPositionPx(drawing) {
111
- const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
112
- if (!anchor) return { leftPx: null, topPx: null };
113
+ function parseAnchorPositionPx(anchor) {
113
114
  let leftPx = null;
114
115
  let topPx = null;
115
116
  const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
@@ -124,34 +125,80 @@ function parseAnchorPositionPx(drawing) {
124
125
  if (Number.isFinite(top)) topPx = emuToPx(top);
125
126
  return { leftPx, topPx };
126
127
  }
127
- function parseAnchorWrapMode(drawing) {
128
- const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
129
- if (!anchor) return null;
128
+ function parseAnchorWrapMode(anchor) {
130
129
  if (directChildrenByLocalName(anchor, "wrapSquare")[0]) return "square";
131
130
  if (directChildrenByLocalName(anchor, "wrapTight")[0]) return "tight";
132
131
  if (directChildrenByLocalName(anchor, "wrapTopAndBottom")[0]) return "topAndBottom";
133
132
  if (directChildrenByLocalName(anchor, "wrapNone")[0]) return "none";
134
133
  return null;
135
134
  }
136
- function mergeImageStyle(baseAttrs, anchorPos, wrapMode) {
137
- if (anchorPos.leftPx === null && anchorPos.topPx === null) return baseAttrs;
135
+ function parseAnchorMeta(drawing) {
136
+ const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
137
+ if (!anchor) return null;
138
+ const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
139
+ const positionV = directChildrenByLocalName(anchor, "positionV")[0] ?? null;
140
+ const relativeFromH = getAttr(positionH, "relativeFrom");
141
+ const relativeFromV = getAttr(positionV, "relativeFrom");
142
+ const parseDistPx = (name) => {
143
+ const raw = getAttr(anchor, name);
144
+ const emu = raw ? Number.parseInt(raw, 10) : Number.NaN;
145
+ return Number.isFinite(emu) && emu >= 0 ? emuToPx(emu) : null;
146
+ };
147
+ const rawHeight = getAttr(anchor, "relativeHeight");
148
+ const parsedHeight = rawHeight ? Number.parseInt(rawHeight, 10) : Number.NaN;
149
+ const boolAttr = (name, fallback) => {
150
+ const raw = (getAttr(anchor, name) ?? "").toLowerCase();
151
+ if (raw === "1" || raw === "true" || raw === "on") return true;
152
+ if (raw === "0" || raw === "false" || raw === "off") return false;
153
+ return fallback;
154
+ };
155
+ return {
156
+ position: parseAnchorPositionPx(anchor),
157
+ wrapMode: parseAnchorWrapMode(anchor),
158
+ distTPx: parseDistPx("distT"),
159
+ distBPx: parseDistPx("distB"),
160
+ distLPx: parseDistPx("distL"),
161
+ distRPx: parseDistPx("distR"),
162
+ relativeFromH,
163
+ relativeFromV,
164
+ behindDoc: boolAttr("behindDoc", false),
165
+ allowOverlap: boolAttr("allowOverlap", true),
166
+ layoutInCell: boolAttr("layoutInCell", true),
167
+ relativeHeight: Number.isFinite(parsedHeight) ? parsedHeight : null
168
+ };
169
+ }
170
+ function mergeImageStyle(baseAttrs, anchorMeta) {
171
+ if (!anchorMeta) return baseAttrs;
172
+ const { position, wrapMode } = anchorMeta;
173
+ if (position.leftPx === null && position.topPx === null) return baseAttrs;
138
174
  const styleParts = [
139
175
  "position:absolute",
140
- anchorPos.leftPx !== null ? `left:${anchorPos.leftPx.toFixed(2)}px` : "",
141
- anchorPos.topPx !== null ? `top:${anchorPos.topPx.toFixed(2)}px` : "",
142
- "z-index:3"
176
+ position.leftPx !== null ? `left:${position.leftPx.toFixed(2)}px` : "",
177
+ position.topPx !== null ? `top:${position.topPx.toFixed(2)}px` : "",
178
+ `z-index:${anchorMeta.behindDoc ? 0 : anchorMeta.relativeHeight ?? 3}`,
179
+ anchorMeta.distTPx !== null ? `margin-top:${anchorMeta.distTPx.toFixed(2)}px` : "",
180
+ anchorMeta.distBPx !== null ? `margin-bottom:${anchorMeta.distBPx.toFixed(2)}px` : "",
181
+ anchorMeta.distLPx !== null ? `margin-left:${anchorMeta.distLPx.toFixed(2)}px` : "",
182
+ anchorMeta.distRPx !== null ? `margin-right:${anchorMeta.distRPx.toFixed(2)}px` : ""
143
183
  ].filter((x) => x.length > 0);
144
184
  if (wrapMode === "topAndBottom") {
145
- styleParts.push("display:block");
146
- }
185
+ styleParts.push("display:block", "clear:both");
186
+ }
187
+ const anchorAttrs = [
188
+ `data-word-anchor="1"`,
189
+ wrapMode ? `data-word-wrap="${wrapMode}"` : "",
190
+ anchorMeta.relativeFromH ? `data-word-anchor-relh="${escapeHtml(anchorMeta.relativeFromH)}"` : "",
191
+ anchorMeta.relativeFromV ? `data-word-anchor-relv="${escapeHtml(anchorMeta.relativeFromV)}"` : "",
192
+ anchorMeta.behindDoc ? `data-word-anchor-behind="1"` : `data-word-anchor-behind="0"`,
193
+ anchorMeta.allowOverlap ? `data-word-anchor-overlap="1"` : `data-word-anchor-overlap="0"`,
194
+ anchorMeta.layoutInCell ? `data-word-anchor-layout-cell="1"` : `data-word-anchor-layout-cell="0"`
195
+ ].filter((x) => x.length > 0).join(" ");
147
196
  if (!baseAttrs.includes("style=")) {
148
- const wrapAttr = wrapMode ? ` data-word-wrap="${wrapMode}"` : "";
149
- return `${baseAttrs} style="${styleParts.join(";")}" data-word-anchor="1"${wrapAttr}`;
197
+ return `${baseAttrs} style="${styleParts.join(";")}" ${anchorAttrs}`;
150
198
  }
151
199
  return baseAttrs.replace(/style="([^"]*)"/, (_m, styleText) => {
152
200
  const merged = [styleText, ...styleParts].filter((x) => x.length > 0).join(";");
153
- const wrapAttr = wrapMode ? ` data-word-wrap="${wrapMode}"` : "";
154
- return `style="${merged}" data-word-anchor="1"${wrapAttr}`;
201
+ return `style="${merged}" ${anchorAttrs}`;
155
202
  });
156
203
  }
157
204
  function parseDocRelsMap(relsXmlText) {
@@ -177,11 +224,16 @@ function extToMime(ext) {
177
224
  if (lower === "svg") return "image/svg+xml";
178
225
  return "application/octet-stream";
179
226
  }
227
+ function normalizeWordPath(relTarget) {
228
+ const normalized = relTarget.replace(/\\/g, "/").replace(/^\/+/, "");
229
+ if (normalized.startsWith("word/")) return normalized;
230
+ if (normalized.startsWith("../")) return `word/${normalized.replace(/^(\.\.\/)+/, "")}`;
231
+ return `word/${normalized}`;
232
+ }
180
233
  async function imageRidToDataUrl(zip, relMap, rid) {
181
234
  const relTarget = relMap[rid];
182
235
  if (!relTarget) return null;
183
- const normalized = relTarget.replace(/^\/+/, "");
184
- const path = normalized.startsWith("word/") ? normalized : `word/${normalized}`;
236
+ const path = normalizeWordPath(relTarget);
185
237
  const file = zip.file(path);
186
238
  if (!file) return null;
187
239
  const base64 = await file.async("base64");
@@ -189,6 +241,55 @@ async function imageRidToDataUrl(zip, relMap, rid) {
189
241
  const mime = extToMime(ext);
190
242
  return `data:${mime};base64,${base64}`;
191
243
  }
244
+ async function readXmlByRid(zip, relMap, rid) {
245
+ const relTarget = relMap[rid];
246
+ if (!relTarget) return null;
247
+ const path = normalizeWordPath(relTarget);
248
+ const file = zip.file(path);
249
+ return file ? file.async("string") : null;
250
+ }
251
+ function parseChartType(chartDoc) {
252
+ const known = ["barChart", "lineChart", "pieChart", "areaChart", "scatterChart", "radarChart", "doughnutChart"];
253
+ for (const type of known) {
254
+ if (queryByLocalName(chartDoc, type)) return type.replace(/Chart$/, "");
255
+ }
256
+ return "unknown";
257
+ }
258
+ function parseChartSummary(chartXmlText) {
259
+ const chartDoc = parseXml(chartXmlText);
260
+ const title = queryAllByLocalName(chartDoc, "t").map((n) => (n.textContent ?? "").trim()).find((v) => v.length > 0) ?? "Chart";
261
+ const seriesCount = queryAllByLocalName(chartDoc, "ser").length;
262
+ const pointCount = queryAllByLocalName(chartDoc, "pt").length;
263
+ const type = parseChartType(chartDoc);
264
+ return { title, type, seriesCount, pointCount };
265
+ }
266
+ function extractSmartArtText(diagramXmlText) {
267
+ const diagramDoc = parseXml(diagramXmlText);
268
+ return queryAllByLocalName(diagramDoc, "t").map((n) => (n.textContent ?? "").trim()).filter((v) => v.length > 0).slice(0, 12);
269
+ }
270
+ function ommlNodeToText(node) {
271
+ if (node.localName === "t") return node.textContent ?? "";
272
+ if (node.localName === "f") {
273
+ const num = queryByLocalName(node, "num");
274
+ const den = queryByLocalName(node, "den");
275
+ return `(${num ? ommlNodeToText(num) : "?"})/(${den ? ommlNodeToText(den) : "?"})`;
276
+ }
277
+ if (node.localName === "sSup") {
278
+ const e = queryByLocalName(node, "e");
279
+ const sup = queryByLocalName(node, "sup");
280
+ return `${e ? ommlNodeToText(e) : ""}^(${sup ? ommlNodeToText(sup) : ""})`;
281
+ }
282
+ if (node.localName === "sSub") {
283
+ const e = queryByLocalName(node, "e");
284
+ const sub = queryByLocalName(node, "sub");
285
+ return `${e ? ommlNodeToText(e) : ""}_(${sub ? ommlNodeToText(sub) : ""})`;
286
+ }
287
+ if (node.localName === "rad") {
288
+ const e = queryByLocalName(node, "e");
289
+ return `sqrt(${e ? ommlNodeToText(e) : ""})`;
290
+ }
291
+ return Array.from(node.children).map((child) => ommlNodeToText(child)).join("");
292
+ }
192
293
  function runStyleToCss(rPr) {
193
294
  if (!rPr) return "";
194
295
  const declarations = [];
@@ -307,44 +408,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
307
408
  const tag = paragraphTag(paragraph);
308
409
  const alignStyle = paragraphAlignStyle(paragraph);
309
410
  const dataAttr = paragraphDataAttr(paragraphIndex);
310
- const runs = queryAllByLocalName(paragraph, "r");
311
- if (runs.length === 0) {
411
+ const hasRenderableNode = queryAllByLocalName(paragraph, "r").length > 0 || queryAllByLocalName(paragraph, "oMath").length > 0 || queryAllByLocalName(paragraph, "oMathPara").length > 0;
412
+ if (!hasRenderableNode) {
312
413
  return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}><br/></${tag}>`;
313
414
  }
314
- const parts = [];
315
- const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
316
- for (let i = 0; i < renderedPageBreakCount; i += 1) {
317
- parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
415
+ function parseRevisionMeta(node, type) {
416
+ return {
417
+ type,
418
+ id: getAttr(node, "w:id") ?? getAttr(node, "id"),
419
+ author: getAttr(node, "w:author") ?? getAttr(node, "author"),
420
+ date: getAttr(node, "w:date") ?? getAttr(node, "date")
421
+ };
422
+ }
423
+ function inferRevisionMeta(run, fallback) {
424
+ if (fallback) return fallback;
425
+ let cursor = run;
426
+ while (cursor) {
427
+ if (cursor.localName === "ins") return parseRevisionMeta(cursor, "ins");
428
+ if (cursor.localName === "del") return parseRevisionMeta(cursor, "del");
429
+ if (cursor.localName === "p") break;
430
+ cursor = cursor.parentElement;
431
+ }
432
+ return null;
318
433
  }
319
- for (const run of runs) {
434
+ function revisionMetaAttrs(meta) {
435
+ const attrs = [`data-word-revision="${meta.type}"`];
436
+ if (meta.id) attrs.push(`data-word-revision-id="${escapeHtml(meta.id)}"`);
437
+ if (meta.author) attrs.push(`data-word-revision-author="${escapeHtml(meta.author)}"`);
438
+ if (meta.date) attrs.push(`data-word-revision-date="${escapeHtml(meta.date)}"`);
439
+ return attrs.join(" ");
440
+ }
441
+ async function runToHtml(run, revisionFallback) {
442
+ const result = [];
320
443
  const rPr = queryByLocalName(run, "rPr");
321
444
  const css = runStyleToCss(rPr);
322
445
  const footnoteRef = queryByLocalName(run, "footnoteReference");
323
446
  const footnoteId = getAttr(footnoteRef, "w:id") ?? getAttr(footnoteRef, "id");
324
447
  if (footnoteId && footnotesMap[footnoteId]) {
325
448
  usedFootnoteIds.push(footnoteId);
326
- parts.push(
449
+ result.push(
327
450
  `<sup data-word-footnote-ref="${footnoteId}"><a href="#word-footnote-${footnoteId}">[${footnoteId}]</a></sup>`
328
451
  );
329
- continue;
452
+ return result;
330
453
  }
331
454
  const endnoteRef = queryByLocalName(run, "endnoteReference");
332
455
  const endnoteId = getAttr(endnoteRef, "w:id") ?? getAttr(endnoteRef, "id");
333
456
  if (endnoteId && endnotesMap[endnoteId]) {
334
457
  usedEndnoteIds.push(endnoteId);
335
- parts.push(
458
+ result.push(
336
459
  `<sup data-word-endnote-ref="${endnoteId}"><a href="#word-endnote-${endnoteId}">[${endnoteId}]</a></sup>`
337
460
  );
338
- continue;
461
+ return result;
339
462
  }
340
463
  const commentRef = queryByLocalName(run, "commentReference");
341
464
  const commentId = getAttr(commentRef, "w:id") ?? getAttr(commentRef, "id");
342
465
  if (commentId && commentsMap[commentId]) {
343
466
  usedCommentIds.push(commentId);
344
- parts.push(
467
+ result.push(
345
468
  `<sup data-word-comment-ref="${commentId}"><a href="#word-comment-${commentId}">[c${commentId}]</a></sup>`
346
469
  );
347
- continue;
470
+ return result;
348
471
  }
349
472
  const drawing = queryByLocalName(run, "drawing");
350
473
  if (drawing) {
@@ -355,13 +478,35 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
355
478
  if (src) {
356
479
  const imageSize = parseDrawingSizePx(drawing);
357
480
  const dimensionAttrs = imageDimensionAttributes(imageSize);
358
- const anchorPos = parseAnchorPositionPx(drawing);
359
- const wrapMode = parseAnchorWrapMode(drawing);
360
- const attrs = mergeImageStyle(dimensionAttrs, anchorPos, wrapMode);
361
- parts.push(`<img src="${src}" alt="word-image"${attrs}/>`);
362
- continue;
481
+ const anchorMeta = parseAnchorMeta(drawing);
482
+ const attrs = mergeImageStyle(dimensionAttrs, anchorMeta);
483
+ result.push(`<img src="${src}" alt="word-image"${attrs}/>`);
484
+ return result;
363
485
  }
364
486
  }
487
+ const chartRef = queryByLocalName(drawing, "chart");
488
+ const chartRid = getAttr(chartRef, "r:id") ?? getAttr(chartRef, "id");
489
+ if (chartRid) {
490
+ const chartXmlText = await readXmlByRid(zip, relMap, chartRid);
491
+ if (chartXmlText) {
492
+ const summary = parseChartSummary(chartXmlText);
493
+ result.push(
494
+ `<figure data-word-chart="1" data-word-chart-type="${summary.type}" data-word-chart-series="${summary.seriesCount}" data-word-chart-points="${summary.pointCount}"><figcaption>${escapeHtml(summary.title)}</figcaption><div>Chart(${escapeHtml(summary.type)}): series=${summary.seriesCount}, points=${summary.pointCount}</div></figure>`
495
+ );
496
+ return result;
497
+ }
498
+ }
499
+ const smartArtRef = queryByLocalName(drawing, "relIds");
500
+ const smartArtRid = getAttr(smartArtRef, "r:dm") ?? getAttr(smartArtRef, "dm");
501
+ if (smartArtRid) {
502
+ const diagramXmlText = await readXmlByRid(zip, relMap, smartArtRid);
503
+ const textItems = diagramXmlText ? extractSmartArtText(diagramXmlText) : [];
504
+ const preview = textItems.length > 0 ? `: ${escapeHtml(textItems.join(" / "))}` : "";
505
+ result.push(
506
+ `<figure data-word-smartart="1" data-word-smartart-items="${textItems.length}"><figcaption>SmartArt fallback${preview}</figcaption></figure>`
507
+ );
508
+ return result;
509
+ }
365
510
  }
366
511
  const texts = queryAllByLocalName(run, "t").map((t) => t.textContent ?? "").join("");
367
512
  const delTexts = queryAllByLocalName(run, "delText").map((t) => t.textContent ?? "").join("");
@@ -372,40 +517,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
372
517
  }).length;
373
518
  const lineBreakCount = Math.max(0, brNodes.length - pageBreakCount);
374
519
  const runText2 = `${escapeHtml(texts || delTexts)}${"<br/>".repeat(lineBreakCount)}`;
375
- if (!runText2) continue;
376
- let revisionType = null;
377
- let cursor = run;
378
- while (cursor) {
379
- if (cursor.localName === "ins") {
380
- revisionType = "ins";
381
- break;
382
- }
383
- if (cursor.localName === "del") {
384
- revisionType = "del";
385
- break;
386
- }
387
- if (cursor.localName === "p") break;
388
- cursor = cursor.parentElement;
389
- }
390
- if (css) {
391
- const span = `<span style="${css}">${runText2}</span>`;
392
- if (revisionType) {
393
- const tag2 = revisionType === "ins" ? "ins" : "del";
394
- parts.push(`<${tag2} data-word-revision="${revisionType}">${span}</${tag2}>`);
395
- } else {
396
- parts.push(span);
397
- }
398
- } else {
399
- if (revisionType) {
400
- const tag2 = revisionType === "ins" ? "ins" : "del";
401
- parts.push(`<${tag2} data-word-revision="${revisionType}">${runText2}</${tag2}>`);
520
+ if (runText2) {
521
+ const revisionMeta = inferRevisionMeta(run, revisionFallback);
522
+ if (css) {
523
+ const span = `<span style="${css}">${runText2}</span>`;
524
+ if (revisionMeta) {
525
+ const tagName = revisionMeta.type === "ins" ? "ins" : "del";
526
+ result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${span}</${tagName}>`);
527
+ } else {
528
+ result.push(span);
529
+ }
530
+ } else if (revisionMeta) {
531
+ const tagName = revisionMeta.type === "ins" ? "ins" : "del";
532
+ result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${runText2}</${tagName}>`);
402
533
  } else {
403
- parts.push(runText2);
534
+ result.push(runText2);
404
535
  }
405
536
  }
406
537
  for (let i = 0; i < pageBreakCount; i += 1) {
407
- parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
538
+ result.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
539
+ }
540
+ return result;
541
+ }
542
+ async function nodeToHtml(node, revisionFallback) {
543
+ if (node.localName === "commentRangeStart") {
544
+ const id = getAttr(node, "w:id") ?? getAttr(node, "id");
545
+ return id ? [`<span data-word-comment-range-start="${id}"></span>`] : [];
546
+ }
547
+ if (node.localName === "commentRangeEnd") {
548
+ const id = getAttr(node, "w:id") ?? getAttr(node, "id");
549
+ return id ? [`<span data-word-comment-range-end="${id}"></span>`] : [];
550
+ }
551
+ if (node.localName === "r") {
552
+ return runToHtml(node, revisionFallback);
408
553
  }
554
+ if (node.localName === "oMath" || node.localName === "oMathPara") {
555
+ const linear = ommlNodeToText(node).trim();
556
+ if (!linear) return [];
557
+ return [`<span data-word-omml="1">${escapeHtml(linear)}</span>`];
558
+ }
559
+ if (node.localName === "ins" || node.localName === "del") {
560
+ const scopedMeta = parseRevisionMeta(node, node.localName === "ins" ? "ins" : "del");
561
+ const nested2 = [];
562
+ for (const child of Array.from(node.children)) {
563
+ nested2.push(...await nodeToHtml(child, scopedMeta));
564
+ }
565
+ return nested2;
566
+ }
567
+ const nested = [];
568
+ for (const child of Array.from(node.children)) {
569
+ nested.push(...await nodeToHtml(child, revisionFallback));
570
+ }
571
+ return nested;
572
+ }
573
+ const parts = [];
574
+ const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
575
+ for (let i = 0; i < renderedPageBreakCount; i += 1) {
576
+ parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
577
+ }
578
+ for (const child of Array.from(paragraph.children)) {
579
+ parts.push(...await nodeToHtml(child, null));
409
580
  }
410
581
  const content = parts.join("") || "<br/>";
411
582
  return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}>${content}</${tag}>`;
@@ -435,6 +606,101 @@ function parseTcVMerge(tc) {
435
606
  const rawVal = (getAttr(vMerge, "w:val") ?? getAttr(vMerge, "val") ?? "continue").toLowerCase();
436
607
  return rawVal === "restart" ? "restart" : "continue";
437
608
  }
609
+ function parseTblGridWidthsPx(table) {
610
+ const grid = directChildrenByLocalName(table, "tblGrid")[0] ?? null;
611
+ if (!grid) return [];
612
+ return directChildrenByLocalName(grid, "gridCol").map((col) => {
613
+ const raw = getAttr(col, "w:w") ?? getAttr(col, "w");
614
+ const twip = raw ? Number.parseInt(raw, 10) : Number.NaN;
615
+ return Number.isFinite(twip) && twip > 0 ? twipToPx(twip) : 0;
616
+ }).filter((px) => px > 0);
617
+ }
618
+ function borderSizeToPx(size) {
619
+ return size / 6;
620
+ }
621
+ function parseBorderCss(borderNode) {
622
+ if (!borderNode) return null;
623
+ const val = (getAttr(borderNode, "w:val") ?? getAttr(borderNode, "val") ?? "").toLowerCase();
624
+ if (!val || val === "nil" || val === "none") return "none";
625
+ const color = (getAttr(borderNode, "w:color") ?? getAttr(borderNode, "color") ?? "222222").replace(/^#/, "");
626
+ const rawSize = getAttr(borderNode, "w:sz") ?? getAttr(borderNode, "sz");
627
+ const size = rawSize ? Number.parseInt(rawSize, 10) : Number.NaN;
628
+ const px = Number.isFinite(size) && size > 0 ? borderSizeToPx(size) : 1;
629
+ const style = val === "single" ? "solid" : val;
630
+ return `${px.toFixed(2)}px ${style} #${color}`;
631
+ }
632
+ function parseTableStyleProfile(table) {
633
+ const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
634
+ const tblBorders = tblPr ? directChildrenByLocalName(tblPr, "tblBorders")[0] ?? null : null;
635
+ const layout = tblPr ? directChildrenByLocalName(tblPr, "tblLayout")[0] ?? null : null;
636
+ const spacing = tblPr ? directChildrenByLocalName(tblPr, "tblCellSpacing")[0] ?? null : null;
637
+ const spacingType = (getAttr(spacing, "w:type") ?? getAttr(spacing, "type") ?? "dxa").toLowerCase();
638
+ const spacingRaw = getAttr(spacing, "w:w") ?? getAttr(spacing, "w");
639
+ const spacingVal = spacingRaw ? Number.parseFloat(spacingRaw) : Number.NaN;
640
+ const borderSpacingPx = spacingType === "dxa" && Number.isFinite(spacingVal) && spacingVal > 0 ? twipToPx(spacingVal) : 0;
641
+ const borderCollapse = borderSpacingPx > 0 ? "separate" : "collapse";
642
+ const tableLayout = (getAttr(layout, "w:type") ?? getAttr(layout, "type") ?? "").toLowerCase() === "autofit" ? "auto" : "fixed";
643
+ const top = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "top")[0] ?? null : null);
644
+ const bottom = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "bottom")[0] ?? null : null);
645
+ const left = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "left")[0] ?? null : null);
646
+ const right = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "right")[0] ?? null : null);
647
+ const insideH = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideH")[0] ?? null : null);
648
+ const insideV = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideV")[0] ?? null : null);
649
+ const borderCss = top ?? right ?? bottom ?? left ?? "1px solid #222";
650
+ return {
651
+ tableLayout,
652
+ borderCollapse,
653
+ borderSpacingPx,
654
+ borderCss,
655
+ insideHCss: insideH,
656
+ insideVCss: insideV
657
+ };
658
+ }
659
+ function parseTableWidthStyle(table, gridWidthsPx) {
660
+ const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
661
+ const tblW = tblPr ? directChildrenByLocalName(tblPr, "tblW")[0] ?? null : null;
662
+ const type = (getAttr(tblW, "w:type") ?? getAttr(tblW, "type") ?? "").toLowerCase();
663
+ const rawVal = getAttr(tblW, "w:w") ?? getAttr(tblW, "w");
664
+ const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
665
+ if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
666
+ return `width:${twipToPx(numericVal).toFixed(2)}px`;
667
+ }
668
+ if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
669
+ return `width:${(numericVal / 50).toFixed(2)}%`;
670
+ }
671
+ const gridTotal = gridWidthsPx.reduce((sum, item) => sum + item, 0);
672
+ if (gridTotal > 0) return `width:${gridTotal.toFixed(2)}px;max-width:100%`;
673
+ return "width:100%";
674
+ }
675
+ function parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx) {
676
+ const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
677
+ const tcW = tcPr ? directChildrenByLocalName(tcPr, "tcW")[0] ?? null : null;
678
+ const type = (getAttr(tcW, "w:type") ?? getAttr(tcW, "type") ?? "").toLowerCase();
679
+ const rawVal = getAttr(tcW, "w:w") ?? getAttr(tcW, "w");
680
+ const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
681
+ if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
682
+ return `width:${twipToPx(numericVal).toFixed(2)}px`;
683
+ }
684
+ if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
685
+ return `width:${(numericVal / 50).toFixed(2)}%`;
686
+ }
687
+ const width = gridWidthsPx.slice(colCursor, colCursor + colSpan).reduce((sum, item) => sum + item, 0);
688
+ if (width > 0) return `width:${width.toFixed(2)}px`;
689
+ return "";
690
+ }
691
+ function parseCellBorderStyle(cell, tableStyle) {
692
+ const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
693
+ const tcBorders = tcPr ? directChildrenByLocalName(tcPr, "tcBorders")[0] ?? null : null;
694
+ if (!tcBorders) {
695
+ const fallback = tableStyle.insideHCss ?? tableStyle.insideVCss ?? tableStyle.borderCss;
696
+ return `border:${fallback}`;
697
+ }
698
+ const top = parseBorderCss(directChildrenByLocalName(tcBorders, "top")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
699
+ const right = parseBorderCss(directChildrenByLocalName(tcBorders, "right")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
700
+ const bottom = parseBorderCss(directChildrenByLocalName(tcBorders, "bottom")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
701
+ const left = parseBorderCss(directChildrenByLocalName(tcBorders, "left")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
702
+ return `border-top:${top};border-right:${right};border-bottom:${bottom};border-left:${left}`;
703
+ }
438
704
  function tableCellHtml(cell, paragraphIndexMap) {
439
705
  const blocks = [];
440
706
  for (const child of Array.from(cell.children)) {
@@ -455,6 +721,8 @@ function tableCellHtml(cell, paragraphIndexMap) {
455
721
  }
456
722
  function tableToHtml(table, paragraphIndexMap) {
457
723
  const rows = directChildrenByLocalName(table, "tr");
724
+ const gridWidthsPx = parseTblGridWidthsPx(table);
725
+ const tableStyle = parseTableStyleProfile(table);
458
726
  const activeByCol = /* @__PURE__ */ new Map();
459
727
  const allOrigins = [];
460
728
  let nextOriginId = 1;
@@ -481,6 +749,8 @@ function tableToHtml(table, paragraphIndexMap) {
481
749
  }
482
750
  const html = tableCellHtml(cell, paragraphIndexMap);
483
751
  const attrs = [];
752
+ const widthStyle = parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx);
753
+ const borderStyle = parseCellBorderStyle(cell, tableStyle);
484
754
  if (vMerge === "restart") {
485
755
  const origin = {
486
756
  id: `m${nextOriginId}`,
@@ -498,7 +768,7 @@ function tableToHtml(table, paragraphIndexMap) {
498
768
  }
499
769
  if (colSpan > 1) attrs.push(`colspan="${colSpan}"`);
500
770
  emittedCells.push(
501
- `<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="border:1px solid #222;vertical-align:top;">${html}</td>`
771
+ `<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${borderStyle};vertical-align:top;${widthStyle}">${html}</td>`
502
772
  );
503
773
  colCursor += colSpan;
504
774
  }
@@ -517,7 +787,9 @@ function tableToHtml(table, paragraphIndexMap) {
517
787
  const replacement = origin.rowSpan > 1 ? `rowspan="${origin.rowSpan}"` : "";
518
788
  merged = merged.replace(marker, replacement).replace(/\s{2,}/g, " ");
519
789
  }
520
- return `<table style="border-collapse:collapse;table-layout:fixed;width:100%;border:1px solid #222;">${merged}</table>`;
790
+ const tableWidthStyle = parseTableWidthStyle(table, gridWidthsPx);
791
+ const spacing = tableStyle.borderSpacingPx > 0 ? `border-spacing:${tableStyle.borderSpacingPx.toFixed(2)}px;` : "";
792
+ return `<table style="border-collapse:${tableStyle.borderCollapse};${spacing}table-layout:${tableStyle.tableLayout};${tableWidthStyle};border:${tableStyle.borderCss};">${merged}</table>`;
521
793
  }
522
794
  async function parseDocxToHtmlSnapshot(file) {
523
795
  const maybeArrayBuffer = file.arrayBuffer;
@@ -807,7 +1079,7 @@ function createFallbackWordStyleProfile(sourceFileName = "snapshot") {
807
1079
  paragraphProfiles: []
808
1080
  };
809
1081
  }
810
- function twipToPx(twip) {
1082
+ function twipToPx2(twip) {
811
1083
  return twip / 15;
812
1084
  }
813
1085
  function getAttr2(node, attr) {
@@ -851,10 +1123,10 @@ function parsePageGeometry(documentXml) {
851
1123
  const top = getTwipAttr(pgMar, "w:top") ?? getTwipAttr(pgMar, "top") ?? null;
852
1124
  const bottom = getTwipAttr(pgMar, "w:bottom") ?? getTwipAttr(pgMar, "bottom") ?? null;
853
1125
  return {
854
- contentWidthPx: pageW === null ? null : twipToPx(pageW - left - right),
855
- pageHeightPx: pageH === null ? null : twipToPx(pageH),
856
- marginTopPx: top === null ? null : twipToPx(top),
857
- marginBottomPx: bottom === null ? null : twipToPx(bottom)
1126
+ contentWidthPx: pageW === null ? null : twipToPx2(pageW - left - right),
1127
+ pageHeightPx: pageH === null ? null : twipToPx2(pageH),
1128
+ marginTopPx: top === null ? null : twipToPx2(top),
1129
+ marginBottomPx: bottom === null ? null : twipToPx2(bottom)
858
1130
  };
859
1131
  }
860
1132
  function parseHeadingAlignFromDocument(documentXml) {
@@ -1027,15 +1299,15 @@ function parseParagraphProfiles(documentXml, numberingMap) {
1027
1299
  text,
1028
1300
  isEmpty: text.length === 0,
1029
1301
  align: parseParagraphAlign(paragraph),
1030
- beforePx: before === null ? null : twipToPx(before),
1031
- afterPx: after === null ? null : twipToPx(after),
1302
+ beforePx: before === null ? null : twipToPx2(before),
1303
+ afterPx: after === null ? null : twipToPx2(after),
1032
1304
  lineHeightRatio: line === null || lineHeightRule !== "auto" ? null : line / 240,
1033
- lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx(line),
1305
+ lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx2(line),
1034
1306
  lineHeightRule,
1035
- indentLeftPx: left === null ? null : twipToPx(left),
1036
- indentRightPx: right === null ? null : twipToPx(right),
1037
- firstLinePx: firstLine === null ? null : twipToPx(firstLine),
1038
- hangingPx: hanging === null ? null : twipToPx(hanging),
1307
+ indentLeftPx: left === null ? null : twipToPx2(left),
1308
+ indentRightPx: right === null ? null : twipToPx2(right),
1309
+ firstLinePx: firstLine === null ? null : twipToPx2(firstLine),
1310
+ hangingPx: hanging === null ? null : twipToPx2(hanging),
1039
1311
  listNumId,
1040
1312
  listLevel,
1041
1313
  listFormat: listSpec?.numFmt ?? null,
@@ -1070,19 +1342,19 @@ function parseTableDefaults(stylesXml) {
1070
1342
  return {
1071
1343
  topPx: (() => {
1072
1344
  const v = getTwipAttr(top, "w:w") ?? getTwipAttr(top, "w") ?? null;
1073
- return v === null ? null : twipToPx(v);
1345
+ return v === null ? null : twipToPx2(v);
1074
1346
  })(),
1075
1347
  leftPx: (() => {
1076
1348
  const v = getTwipAttr(left, "w:w") ?? getTwipAttr(left, "w") ?? null;
1077
- return v === null ? null : twipToPx(v);
1349
+ return v === null ? null : twipToPx2(v);
1078
1350
  })(),
1079
1351
  bottomPx: (() => {
1080
1352
  const v = getTwipAttr(bottom, "w:w") ?? getTwipAttr(bottom, "w") ?? null;
1081
- return v === null ? null : twipToPx(v);
1353
+ return v === null ? null : twipToPx2(v);
1082
1354
  })(),
1083
1355
  rightPx: (() => {
1084
1356
  const v = getTwipAttr(right, "w:w") ?? getTwipAttr(right, "w") ?? null;
1085
- return v === null ? null : twipToPx(v);
1357
+ return v === null ? null : twipToPx2(v);
1086
1358
  })()
1087
1359
  };
1088
1360
  }
@@ -1180,9 +1452,9 @@ function parseDefaults(stylesXml) {
1180
1452
  const rawLineRule = (getAttr2(spacing, "w:lineRule") ?? getAttr2(spacing, "lineRule") ?? "auto").toLowerCase();
1181
1453
  const bodyLineHeightRule = rawLineRule === "exact" ? "exact" : rawLineRule === "atleast" ? "atLeast" : "auto";
1182
1454
  const bodyLineHeightRatio = line === null || bodyLineHeightRule !== "auto" ? null : line / 240;
1183
- const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx(line);
1455
+ const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx2(line);
1184
1456
  const after = getTwipAttr(spacing, "w:after") ?? getTwipAttr(spacing, "after") ?? null;
1185
- const paragraphAfterPx = after === null ? null : twipToPx(after);
1457
+ const paragraphAfterPx = after === null ? null : twipToPx2(after);
1186
1458
  return { bodyFontPx, bodyLineHeightRatio, bodyLineHeightPx, bodyLineHeightRule, paragraphAfterPx };
1187
1459
  }
1188
1460
  function parseHeading1Style(stylesXml) {