docgen-utils 1.0.19 → 1.0.20
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/README.md +63 -18
- package/dist/bundle.js +3466 -2528
- package/dist/bundle.min.js +242 -252
- package/dist/cli.js +3485 -2531
- package/dist/packages/cli/commands/export-docs.js +1 -1
- package/dist/packages/cli/commands/export-docs.js.map +1 -1
- package/dist/packages/cli/commands/export-slides.d.ts.map +1 -1
- package/dist/packages/cli/commands/export-slides.js +11 -7
- package/dist/packages/cli/commands/export-slides.js.map +1 -1
- package/dist/packages/cli/index.js.map +1 -1
- package/dist/packages/docs/common.d.ts +1 -0
- package/dist/packages/docs/common.d.ts.map +1 -1
- package/dist/packages/docs/convert.d.ts.map +1 -1
- package/dist/packages/docs/convert.js +6 -15
- package/dist/packages/docs/convert.js.map +1 -1
- package/dist/packages/docs/import-docx.d.ts.map +1 -1
- package/dist/packages/docs/import-docx.js +168 -82
- package/dist/packages/docs/import-docx.js.map +1 -1
- package/dist/packages/docs/parse-colors.d.ts +0 -5
- package/dist/packages/docs/parse-colors.d.ts.map +1 -1
- package/dist/packages/docs/parse-colors.js +2 -2
- package/dist/packages/docs/parse-colors.js.map +1 -1
- package/dist/packages/docs/parse-css.d.ts +0 -9
- package/dist/packages/docs/parse-css.d.ts.map +1 -1
- package/dist/packages/docs/parse-css.js +4 -6
- package/dist/packages/docs/parse-css.js.map +1 -1
- package/dist/packages/docs/parse-helpers.d.ts +0 -1
- package/dist/packages/docs/parse-helpers.d.ts.map +1 -1
- package/dist/packages/docs/parse-helpers.js +1 -1
- package/dist/packages/docs/parse-helpers.js.map +1 -1
- package/dist/packages/docs/parse-inline.d.ts +0 -13
- package/dist/packages/docs/parse-inline.d.ts.map +1 -1
- package/dist/packages/docs/parse-inline.js +7 -7
- package/dist/packages/docs/parse-inline.js.map +1 -1
- package/dist/packages/docs/parse-layout.d.ts.map +1 -1
- package/dist/packages/docs/parse-layout.js +1 -14
- package/dist/packages/docs/parse-layout.js.map +1 -1
- package/dist/packages/docs/parse-special.js +1 -1
- package/dist/packages/docs/parse-special.js.map +1 -1
- package/dist/packages/docs/parse.d.ts.map +1 -1
- package/dist/packages/docs/parse.js +76 -30
- package/dist/packages/docs/parse.js.map +1 -1
- package/dist/packages/shared/zip-guard.d.ts +37 -0
- package/dist/packages/shared/zip-guard.d.ts.map +1 -0
- package/dist/packages/shared/zip-guard.js +101 -0
- package/dist/packages/shared/zip-guard.js.map +1 -0
- package/dist/packages/slides/convert.d.ts +1 -3
- package/dist/packages/slides/convert.d.ts.map +1 -1
- package/dist/packages/slides/convert.js +8 -74
- package/dist/packages/slides/convert.js.map +1 -1
- package/dist/packages/slides/createPresentation.d.ts +1 -1
- package/dist/packages/slides/createPresentation.d.ts.map +1 -1
- package/dist/packages/slides/createPresentation.js +1 -10
- package/dist/packages/slides/createPresentation.js.map +1 -1
- package/dist/packages/slides/import-pptx.d.ts.map +1 -1
- package/dist/packages/slides/import-pptx.js +5 -7
- package/dist/packages/slides/import-pptx.js.map +1 -1
- package/dist/packages/slides/parse.d.ts +0 -22
- package/dist/packages/slides/parse.d.ts.map +1 -1
- package/dist/packages/slides/parse.js +38 -42
- package/dist/packages/slides/parse.js.map +1 -1
- package/dist/packages/slides/transform.d.ts.map +1 -1
- package/dist/packages/slides/transform.js +1 -5
- package/dist/packages/slides/transform.js.map +1 -1
- package/dist/packages/slides/vendor/VENDORING.md +2 -2
- package/package.json +15 -8
- package/dist/packages/cli/commands/common.d.ts +0 -2
- package/dist/packages/cli/commands/common.d.ts.map +0 -1
- package/dist/packages/cli/commands/common.js +0 -22
- package/dist/packages/cli/commands/common.js.map +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage: const html = await importDocx(arrayBuffer);
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import { loadZipSafely } from "../shared/zip-guard";
|
|
8
8
|
import { EMFJS } from "rtf.js";
|
|
9
9
|
import { parseHTML } from "linkedom";
|
|
10
10
|
// ============================================================================
|
|
@@ -194,7 +194,7 @@ function readEmfHeader(buffer) {
|
|
|
194
194
|
frameHeight: frameBottom
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
|
-
//
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
198
198
|
function readEmfBounds(buffer) {
|
|
199
199
|
const header = readEmfHeader(buffer);
|
|
200
200
|
return { width: header.boundsWidth, height: header.boundsHeight };
|
|
@@ -332,11 +332,11 @@ function escapeSvgText(text) {
|
|
|
332
332
|
/**
|
|
333
333
|
* Convert EMF buffer to SVG for inline rendering
|
|
334
334
|
*/
|
|
335
|
-
function convertEmfToSvg(buffer) {
|
|
335
|
+
async function convertEmfToSvg(buffer) {
|
|
336
336
|
try {
|
|
337
337
|
setupEmfDom();
|
|
338
338
|
const header = readEmfHeader(buffer);
|
|
339
|
-
const { boundsWidth, boundsHeight
|
|
339
|
+
const { boundsWidth, boundsHeight } = header;
|
|
340
340
|
// Disable EMFJS logging by temporarily overriding console
|
|
341
341
|
const originalLog = console.log;
|
|
342
342
|
console.log = () => { }; // Silence EMFJS debug logs
|
|
@@ -538,6 +538,7 @@ function parseThemeFonts(themeDoc) {
|
|
|
538
538
|
}
|
|
539
539
|
function parseStyles(stylesDoc, themeColors, themeFonts) {
|
|
540
540
|
const styles = new Map();
|
|
541
|
+
const tableStyles = new Map();
|
|
541
542
|
const defaults = {};
|
|
542
543
|
// Parse document defaults (w:docDefaults)
|
|
543
544
|
const docDefaults = stylesDoc.getElementsByTagName("w:docDefaults")[0];
|
|
@@ -615,8 +616,27 @@ function parseStyles(stylesDoc, themeColors, themeFonts) {
|
|
|
615
616
|
const pPr = pPrEl ? parseParagraphProps(pPrEl) : undefined;
|
|
616
617
|
const rPr = rPrEl ? parseRunProps(rPrEl, themeColors, themeFonts) : undefined;
|
|
617
618
|
styles.set(styleId, { basedOn, pPr, rPr, name });
|
|
619
|
+
// Parse table styles (w:type="table") for border and cell margin info
|
|
620
|
+
const styleType = styleEl.getAttribute("w:type");
|
|
621
|
+
if (styleType === "table") {
|
|
622
|
+
const tblPrEl = findChild(styleEl, "tblPr");
|
|
623
|
+
const tblStyleProps = {};
|
|
624
|
+
if (tblPrEl) {
|
|
625
|
+
// Parse table borders from style
|
|
626
|
+
const tblBordersEl = findChild(tblPrEl, "tblBorders");
|
|
627
|
+
if (tblBordersEl) {
|
|
628
|
+
tblStyleProps.borders = parseBorders(tblBordersEl, themeColors);
|
|
629
|
+
}
|
|
630
|
+
// Parse default cell margins from style
|
|
631
|
+
const tblCellMarEl = findChild(tblPrEl, "tblCellMar");
|
|
632
|
+
if (tblCellMarEl) {
|
|
633
|
+
tblStyleProps.cellMargins = parseCellMargins(tblCellMarEl);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
tableStyles.set(styleId, { basedOn, tblPr: tblStyleProps });
|
|
637
|
+
}
|
|
618
638
|
}
|
|
619
|
-
return { styles, defaults };
|
|
639
|
+
return { styles, defaults, tableStyles };
|
|
620
640
|
}
|
|
621
641
|
/**
|
|
622
642
|
* Parse section properties from sectPr element.
|
|
@@ -765,7 +785,7 @@ function parsePositionedElement(drawing, themeColors, themeFonts) {
|
|
|
765
785
|
const stops = [];
|
|
766
786
|
const gsElements = findChildren(gsLst, "gs");
|
|
767
787
|
for (const gs of gsElements) {
|
|
768
|
-
const pos = parseInt(gs.getAttribute("pos") ?? "0", 10) / 1000; // Convert from
|
|
788
|
+
const pos = parseInt(gs.getAttribute("pos") ?? "0", 10) / 1000; // Convert from thousandths of a percent (0-100000) to percentage (0-100)
|
|
769
789
|
let color = "#000000";
|
|
770
790
|
const srgbClr = findChild(gs, "srgbClr");
|
|
771
791
|
const schemeClr = findChild(gs, "schemeClr");
|
|
@@ -1339,7 +1359,7 @@ function parseParagraph(p, themeColors, themeFonts) {
|
|
|
1339
1359
|
processElement(p);
|
|
1340
1360
|
return { runs, props };
|
|
1341
1361
|
}
|
|
1342
|
-
function parseTableCell(tc, themeColors, themeFonts) {
|
|
1362
|
+
function parseTableCell(tc, themeColors, themeFonts, tableStyles) {
|
|
1343
1363
|
const cell = { paragraphs: [] };
|
|
1344
1364
|
const tcPr = findChild(tc, "tcPr");
|
|
1345
1365
|
if (tcPr) {
|
|
@@ -1387,6 +1407,11 @@ function parseTableCell(tc, themeColors, themeFonts) {
|
|
|
1387
1407
|
if (noWrap) {
|
|
1388
1408
|
cell.noWrap = true;
|
|
1389
1409
|
}
|
|
1410
|
+
// Cell-level margins (w:tcMar) - override table-level cell margins
|
|
1411
|
+
const tcMar = findChild(tcPr, "tcMar");
|
|
1412
|
+
if (tcMar) {
|
|
1413
|
+
cell.cellMargins = parseCellMargins(tcMar);
|
|
1414
|
+
}
|
|
1390
1415
|
}
|
|
1391
1416
|
// Parse paragraphs in cell (including those inside SDT elements)
|
|
1392
1417
|
const collectParagraphs = (parent) => {
|
|
@@ -1430,10 +1455,43 @@ function parseTableCell(tc, themeColors, themeFonts) {
|
|
|
1430
1455
|
};
|
|
1431
1456
|
const nestedTables = collectTables(tc);
|
|
1432
1457
|
if (nestedTables.length > 0) {
|
|
1433
|
-
cell.nestedTables = nestedTables.map(tbl => parseTable(tbl, themeColors, themeFonts));
|
|
1458
|
+
cell.nestedTables = nestedTables.map(tbl => parseTable(tbl, themeColors, themeFonts, tableStyles));
|
|
1434
1459
|
}
|
|
1435
1460
|
return cell;
|
|
1436
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Parse cell margins from a w:tblCellMar or w:tcMar element.
|
|
1464
|
+
* Returns margins in twips.
|
|
1465
|
+
*/
|
|
1466
|
+
function parseCellMargins(marEl) {
|
|
1467
|
+
const margins = {};
|
|
1468
|
+
const topEl = findChild(marEl, "top");
|
|
1469
|
+
const bottomEl = findChild(marEl, "bottom");
|
|
1470
|
+
// Note: OOXML uses "start"/"end" in newer docs, "left"/"right" in older ones
|
|
1471
|
+
const leftEl = findChild(marEl, "left") ?? findChild(marEl, "start");
|
|
1472
|
+
const rightEl = findChild(marEl, "right") ?? findChild(marEl, "end");
|
|
1473
|
+
if (topEl) {
|
|
1474
|
+
const w = topEl.getAttribute("w:w");
|
|
1475
|
+
if (w)
|
|
1476
|
+
margins.top = parseInt(w, 10);
|
|
1477
|
+
}
|
|
1478
|
+
if (bottomEl) {
|
|
1479
|
+
const w = bottomEl.getAttribute("w:w");
|
|
1480
|
+
if (w)
|
|
1481
|
+
margins.bottom = parseInt(w, 10);
|
|
1482
|
+
}
|
|
1483
|
+
if (leftEl) {
|
|
1484
|
+
const w = leftEl.getAttribute("w:w");
|
|
1485
|
+
if (w)
|
|
1486
|
+
margins.left = parseInt(w, 10);
|
|
1487
|
+
}
|
|
1488
|
+
if (rightEl) {
|
|
1489
|
+
const w = rightEl.getAttribute("w:w");
|
|
1490
|
+
if (w)
|
|
1491
|
+
margins.right = parseInt(w, 10);
|
|
1492
|
+
}
|
|
1493
|
+
return margins;
|
|
1494
|
+
}
|
|
1437
1495
|
function parseBorders(bordersEl, themeColors) {
|
|
1438
1496
|
const borders = {};
|
|
1439
1497
|
const parseBorder = (el) => {
|
|
@@ -1465,14 +1523,37 @@ function parseBorders(bordersEl, themeColors) {
|
|
|
1465
1523
|
borders.insideV = parseBorder(findChild(bordersEl, "insideV"));
|
|
1466
1524
|
return borders;
|
|
1467
1525
|
}
|
|
1468
|
-
function parseTable(tbl, themeColors, themeFonts) {
|
|
1526
|
+
function parseTable(tbl, themeColors, themeFonts, tableStyles) {
|
|
1469
1527
|
const table = { rows: [] };
|
|
1470
1528
|
const tblPr = findChild(tbl, "tblPr");
|
|
1471
1529
|
if (tblPr) {
|
|
1530
|
+
// Resolve table style (w:tblStyle) - this provides default borders, cell margins, etc.
|
|
1531
|
+
const tblStyleEl = findChild(tblPr, "tblStyle");
|
|
1532
|
+
let resolvedStyleProps;
|
|
1533
|
+
if (tblStyleEl && tableStyles) {
|
|
1534
|
+
const styleId = tblStyleEl.getAttribute("w:val");
|
|
1535
|
+
if (styleId) {
|
|
1536
|
+
resolvedStyleProps = resolveTableStyle(styleId, tableStyles);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
// Parse explicit table borders (override style borders)
|
|
1472
1540
|
const tblBorders = findChild(tblPr, "tblBorders");
|
|
1473
1541
|
if (tblBorders) {
|
|
1474
1542
|
table.borders = parseBorders(tblBorders, themeColors);
|
|
1475
1543
|
}
|
|
1544
|
+
else if (resolvedStyleProps?.borders) {
|
|
1545
|
+
// Fall back to style-defined borders
|
|
1546
|
+
table.borders = resolvedStyleProps.borders;
|
|
1547
|
+
}
|
|
1548
|
+
// Parse explicit cell margins from table properties
|
|
1549
|
+
const tblCellMar = findChild(tblPr, "tblCellMar");
|
|
1550
|
+
if (tblCellMar) {
|
|
1551
|
+
table.cellMargins = parseCellMargins(tblCellMar);
|
|
1552
|
+
}
|
|
1553
|
+
else if (resolvedStyleProps?.cellMargins) {
|
|
1554
|
+
// Fall back to style-defined cell margins
|
|
1555
|
+
table.cellMargins = resolvedStyleProps.cellMargins;
|
|
1556
|
+
}
|
|
1476
1557
|
// Parse table width
|
|
1477
1558
|
const tblW = findChild(tblPr, "tblW");
|
|
1478
1559
|
if (tblW) {
|
|
@@ -1491,35 +1572,6 @@ function parseTable(tbl, themeColors, themeFonts) {
|
|
|
1491
1572
|
table.tableIndent = parseInt(w, 10);
|
|
1492
1573
|
}
|
|
1493
1574
|
}
|
|
1494
|
-
// Parse table cell margins (default margins for all cells)
|
|
1495
|
-
const tblCellMar = findChild(tblPr, "tblCellMar");
|
|
1496
|
-
if (tblCellMar) {
|
|
1497
|
-
table.cellMargins = {};
|
|
1498
|
-
const topMar = findChild(tblCellMar, "top");
|
|
1499
|
-
const bottomMar = findChild(tblCellMar, "bottom");
|
|
1500
|
-
const leftMar = findChild(tblCellMar, "left") || findChild(tblCellMar, "start");
|
|
1501
|
-
const rightMar = findChild(tblCellMar, "right") || findChild(tblCellMar, "end");
|
|
1502
|
-
if (topMar) {
|
|
1503
|
-
const w = topMar.getAttribute("w:w");
|
|
1504
|
-
if (w)
|
|
1505
|
-
table.cellMargins.top = parseInt(w, 10);
|
|
1506
|
-
}
|
|
1507
|
-
if (bottomMar) {
|
|
1508
|
-
const w = bottomMar.getAttribute("w:w");
|
|
1509
|
-
if (w)
|
|
1510
|
-
table.cellMargins.bottom = parseInt(w, 10);
|
|
1511
|
-
}
|
|
1512
|
-
if (leftMar) {
|
|
1513
|
-
const w = leftMar.getAttribute("w:w");
|
|
1514
|
-
if (w)
|
|
1515
|
-
table.cellMargins.left = parseInt(w, 10);
|
|
1516
|
-
}
|
|
1517
|
-
if (rightMar) {
|
|
1518
|
-
const w = rightMar.getAttribute("w:w");
|
|
1519
|
-
if (w)
|
|
1520
|
-
table.cellMargins.right = parseInt(w, 10);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
1575
|
}
|
|
1524
1576
|
// Parse column widths from tblGrid
|
|
1525
1577
|
const tblGrid = findChild(tbl, "tblGrid");
|
|
@@ -1556,12 +1608,43 @@ function parseTable(tbl, themeColors, themeFonts) {
|
|
|
1556
1608
|
}
|
|
1557
1609
|
const tcs = findChildren(tr, "tc");
|
|
1558
1610
|
for (const tc of tcs) {
|
|
1559
|
-
row.cells.push(parseTableCell(tc, themeColors, themeFonts));
|
|
1611
|
+
row.cells.push(parseTableCell(tc, themeColors, themeFonts, tableStyles));
|
|
1560
1612
|
}
|
|
1561
1613
|
table.rows.push(row);
|
|
1562
1614
|
}
|
|
1563
1615
|
return table;
|
|
1564
1616
|
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Resolve a table style by walking the basedOn chain.
|
|
1619
|
+
* Merges borders and cell margins from parent styles (parent first, child overrides).
|
|
1620
|
+
*/
|
|
1621
|
+
function resolveTableStyle(styleId, tableStyles) {
|
|
1622
|
+
const visited = new Set();
|
|
1623
|
+
const chain = [];
|
|
1624
|
+
let currentId = styleId;
|
|
1625
|
+
while (currentId && !visited.has(currentId)) {
|
|
1626
|
+
visited.add(currentId);
|
|
1627
|
+
const style = tableStyles.get(currentId);
|
|
1628
|
+
if (!style)
|
|
1629
|
+
break;
|
|
1630
|
+
if (style.tblPr)
|
|
1631
|
+
chain.unshift(style.tblPr); // parent first
|
|
1632
|
+
currentId = style.basedOn;
|
|
1633
|
+
}
|
|
1634
|
+
if (chain.length === 0)
|
|
1635
|
+
return undefined;
|
|
1636
|
+
// Merge: start from parent, child overrides
|
|
1637
|
+
const merged = {};
|
|
1638
|
+
for (const props of chain) {
|
|
1639
|
+
if (props.borders) {
|
|
1640
|
+
merged.borders = { ...merged.borders, ...props.borders };
|
|
1641
|
+
}
|
|
1642
|
+
if (props.cellMargins) {
|
|
1643
|
+
merged.cellMargins = { ...merged.cellMargins, ...props.cellMargins };
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return merged;
|
|
1647
|
+
}
|
|
1565
1648
|
function parseDrawing(drawing, themeColors, inlineOnly = false) {
|
|
1566
1649
|
// Look for inline or anchor images
|
|
1567
1650
|
const inline = findDescendant(drawing, "inline");
|
|
@@ -1738,7 +1821,7 @@ function parseDrawing(drawing, themeColors, inlineOnly = false) {
|
|
|
1738
1821
|
},
|
|
1739
1822
|
};
|
|
1740
1823
|
}
|
|
1741
|
-
function parseDocument(docDoc, themeColors, themeFonts) {
|
|
1824
|
+
function parseDocument(docDoc, themeColors, themeFonts, tableStyles) {
|
|
1742
1825
|
const elements = [];
|
|
1743
1826
|
const positionedElements = [];
|
|
1744
1827
|
const body = docDoc.getElementsByTagName("w:body")[0];
|
|
@@ -1766,7 +1849,7 @@ function parseDocument(docDoc, themeColors, themeFonts) {
|
|
|
1766
1849
|
}
|
|
1767
1850
|
}
|
|
1768
1851
|
else if (el.localName === "tbl") {
|
|
1769
|
-
elements.push({ kind: "table", data: parseTable(el, themeColors, themeFonts) });
|
|
1852
|
+
elements.push({ kind: "table", data: parseTable(el, themeColors, themeFonts, tableStyles) });
|
|
1770
1853
|
}
|
|
1771
1854
|
else if (el.localName === "sdt") {
|
|
1772
1855
|
// Structured document tag - process its content
|
|
@@ -1881,14 +1964,13 @@ function renderParagraphToHtml(para, styleMap, numberingMap, imageMap = new Map(
|
|
|
1881
1964
|
}
|
|
1882
1965
|
// Determine HTML tag based on style
|
|
1883
1966
|
let tag = "p";
|
|
1884
|
-
let headingLevel = 0;
|
|
1885
1967
|
let isTitleStyle = false;
|
|
1886
1968
|
let isSubtitleStyle = false;
|
|
1887
1969
|
if (props.styleId) {
|
|
1888
1970
|
const styleName = styleMap.get(props.styleId)?.name?.toLowerCase() ?? props.styleId.toLowerCase();
|
|
1889
1971
|
const headingMatch = styleName.match(/heading\s*(\d)/i);
|
|
1890
1972
|
if (headingMatch) {
|
|
1891
|
-
headingLevel = parseInt(headingMatch[1], 10);
|
|
1973
|
+
const headingLevel = parseInt(headingMatch[1], 10);
|
|
1892
1974
|
if (headingLevel >= 1 && headingLevel <= 6) {
|
|
1893
1975
|
tag = `h${headingLevel}`;
|
|
1894
1976
|
}
|
|
@@ -2058,8 +2140,6 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2058
2140
|
colIdx += cell.gridSpan ?? 1;
|
|
2059
2141
|
}
|
|
2060
2142
|
}
|
|
2061
|
-
// Track column index for applying widths
|
|
2062
|
-
let colIndex = 0;
|
|
2063
2143
|
for (let rowIdx = 0; rowIdx < table.rows.length; rowIdx++) {
|
|
2064
2144
|
const row = table.rows[rowIdx];
|
|
2065
2145
|
const rowStyles = [];
|
|
@@ -2069,8 +2149,8 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2069
2149
|
html += `<tr${rowStyles.length > 0 ? ` style="${rowStyles.join(";")}"` : ""}>`;
|
|
2070
2150
|
// First pass: find rowspan cells and calculate empty paragraphs to distribute
|
|
2071
2151
|
// We subtract the number of content lines in subsequent rows covered by the rowspan
|
|
2072
|
-
|
|
2073
|
-
colIndex = 0;
|
|
2152
|
+
const emptyParagraphsToDistribute = [];
|
|
2153
|
+
let colIndex = 0;
|
|
2074
2154
|
for (const cell of row.cells) {
|
|
2075
2155
|
if (cell.vMerge === "continue") {
|
|
2076
2156
|
colIndex += cell.gridSpan ?? 1;
|
|
@@ -2094,7 +2174,6 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2094
2174
|
// Find the cell at the same column in this row (it should be vMerge="continue")
|
|
2095
2175
|
// But we need to count lines from OTHER cells in that row
|
|
2096
2176
|
// Actually, we just need to count paragraphs in any non-continue cell in row r
|
|
2097
|
-
let colIdx = 0;
|
|
2098
2177
|
for (const nextRowCell of table.rows[r].cells) {
|
|
2099
2178
|
if (nextRowCell.vMerge !== "continue") {
|
|
2100
2179
|
// Count non-empty paragraphs in this cell
|
|
@@ -2107,7 +2186,6 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2107
2186
|
}
|
|
2108
2187
|
break; // Only count from first non-continue cell
|
|
2109
2188
|
}
|
|
2110
|
-
colIdx += nextRowCell.gridSpan ?? 1;
|
|
2111
2189
|
}
|
|
2112
2190
|
}
|
|
2113
2191
|
// Distribute: emptyParagraphs - linesInNextRows
|
|
@@ -2128,13 +2206,14 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2128
2206
|
}
|
|
2129
2207
|
const cellStyles = [];
|
|
2130
2208
|
const cellAttrs = [];
|
|
2131
|
-
// Apply cell padding: use extracted
|
|
2132
|
-
// Priority: 1)
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
const
|
|
2136
|
-
const
|
|
2137
|
-
const
|
|
2209
|
+
// Apply cell padding: use extracted cell margins if available
|
|
2210
|
+
// Priority: 1) cell-level margins (tcMar), 2) table-level margins, 3) docDefaults from TableNormal style, 4) Word defaults
|
|
2211
|
+
const margins = cell.cellMargins ?? table.cellMargins;
|
|
2212
|
+
if (margins) {
|
|
2213
|
+
const top = margins.top !== undefined ? twipsToPx(margins.top) : 0;
|
|
2214
|
+
const right = margins.right !== undefined ? twipsToPx(margins.right) : 0;
|
|
2215
|
+
const bottom = margins.bottom !== undefined ? twipsToPx(margins.bottom) : 0;
|
|
2216
|
+
const left = margins.left !== undefined ? twipsToPx(margins.left) : 0;
|
|
2138
2217
|
cellStyles.push(`padding:${top}px ${right}px ${bottom}px ${left}px`);
|
|
2139
2218
|
}
|
|
2140
2219
|
else if (docDefaults.tableCellMargins) {
|
|
@@ -2199,26 +2278,32 @@ function renderTableToHtml(table, styleMap, numberingMap, themeColors, imageMap,
|
|
|
2199
2278
|
}
|
|
2200
2279
|
}
|
|
2201
2280
|
else if (table.borders) {
|
|
2202
|
-
// Fall back to table borders
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
cellStyles.push(`border-bottom:${
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2281
|
+
// Fall back to table borders: use insideH/insideV for interior, edge borders for outer edges
|
|
2282
|
+
const rowIndex = table.rows.indexOf(row);
|
|
2283
|
+
const cellIndex = row.cells.filter(c => c.vMerge !== "continue").indexOf(cell);
|
|
2284
|
+
const isFirstRow = rowIndex === 0;
|
|
2285
|
+
const isLastRow = rowIndex === table.rows.length - 1;
|
|
2286
|
+
const isFirstCol = cellIndex === 0;
|
|
2287
|
+
const isLastCol = cellIndex === row.cells.filter(c => c.vMerge !== "continue").length - 1;
|
|
2288
|
+
// Top border: use table top border for first row, insideH for inner rows
|
|
2289
|
+
const topBorder = isFirstRow ? table.borders.top : table.borders.insideH;
|
|
2290
|
+
if (topBorder) {
|
|
2291
|
+
cellStyles.push(`border-top:${topBorder.size}pt ${getBorderStyleCss(topBorder.style)} #${topBorder.color}`);
|
|
2292
|
+
}
|
|
2293
|
+
// Bottom border: use table bottom border for last row, insideH for inner rows
|
|
2294
|
+
const bottomBorder = isLastRow ? table.borders.bottom : table.borders.insideH;
|
|
2295
|
+
if (bottomBorder) {
|
|
2296
|
+
cellStyles.push(`border-bottom:${bottomBorder.size}pt ${getBorderStyleCss(bottomBorder.style)} #${bottomBorder.color}`);
|
|
2297
|
+
}
|
|
2298
|
+
// Left border: use table left border for first col, insideV for inner cols
|
|
2299
|
+
const leftBorder = isFirstCol ? table.borders.left : table.borders.insideV;
|
|
2300
|
+
if (leftBorder) {
|
|
2301
|
+
cellStyles.push(`border-left:${leftBorder.size}pt ${getBorderStyleCss(leftBorder.style)} #${leftBorder.color}`);
|
|
2302
|
+
}
|
|
2303
|
+
// Right border: use table right border for last col, insideV for inner cols
|
|
2304
|
+
const rightBorder = isLastCol ? table.borders.right : table.borders.insideV;
|
|
2305
|
+
if (rightBorder) {
|
|
2306
|
+
cellStyles.push(`border-right:${rightBorder.size}pt ${getBorderStyleCss(rightBorder.style)} #${rightBorder.color}`);
|
|
2222
2307
|
}
|
|
2223
2308
|
}
|
|
2224
2309
|
const attrsStr = cellAttrs.length > 0 ? " " + cellAttrs.join(" ") : "";
|
|
@@ -2303,7 +2388,7 @@ function renderImageToHtml(img, imageMap) {
|
|
|
2303
2388
|
containerStyles.push(`border:${borderWidthPx}px solid #${img.border.color}`);
|
|
2304
2389
|
}
|
|
2305
2390
|
// Inject width/height into the SVG element to ensure proper sizing
|
|
2306
|
-
|
|
2391
|
+
const styledSvg = imageData.svgMarkup.replace(/<svg /, `<svg style="width:100%;height:100%;display:block;" `);
|
|
2307
2392
|
return `<div style="${containerStyles.join(";")}">${styledSvg}</div>`;
|
|
2308
2393
|
}
|
|
2309
2394
|
// Regular image (dataUri type)
|
|
@@ -2634,7 +2719,7 @@ function generateStylesCss(styleMap, themeFonts) {
|
|
|
2634
2719
|
* @returns HTML string representing the document
|
|
2635
2720
|
*/
|
|
2636
2721
|
export default async function importDocx(arrayBuffer) {
|
|
2637
|
-
const zip = await
|
|
2722
|
+
const zip = await loadZipSafely(arrayBuffer);
|
|
2638
2723
|
const parser = new DOMParser();
|
|
2639
2724
|
// Parse document.xml
|
|
2640
2725
|
const docXml = await zip.file("word/document.xml")?.async("text");
|
|
@@ -2652,6 +2737,7 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2652
2737
|
}
|
|
2653
2738
|
// Parse styles - try multiple locations
|
|
2654
2739
|
let styleMap = new Map();
|
|
2740
|
+
let tableStyleMap = new Map();
|
|
2655
2741
|
let docDefaults = {};
|
|
2656
2742
|
// Try word/styles.xml first (standard location)
|
|
2657
2743
|
let stylesXml = await zip.file("word/styles.xml")?.async("text");
|
|
@@ -2663,6 +2749,7 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2663
2749
|
const stylesDoc = parser.parseFromString(stylesXml, "application/xml");
|
|
2664
2750
|
const parsed = parseStyles(stylesDoc, themeColors, themeFonts);
|
|
2665
2751
|
styleMap = parsed.styles;
|
|
2752
|
+
tableStyleMap = parsed.tableStyles;
|
|
2666
2753
|
docDefaults = parsed.defaults;
|
|
2667
2754
|
}
|
|
2668
2755
|
// Parse numbering (for lists) - try multiple locations
|
|
@@ -2710,7 +2797,7 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2710
2797
|
// Handle EMF files specially - convert to SVG for web display
|
|
2711
2798
|
if (ext === "emf") {
|
|
2712
2799
|
const imgBuffer = await imgFile.async("arraybuffer");
|
|
2713
|
-
const svgData = convertEmfToSvg(imgBuffer);
|
|
2800
|
+
const svgData = await convertEmfToSvg(imgBuffer);
|
|
2714
2801
|
if (svgData) {
|
|
2715
2802
|
imageMap.set(rId, svgData);
|
|
2716
2803
|
}
|
|
@@ -2734,7 +2821,7 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2734
2821
|
// Parse headers and footers for positioned elements (decorative frames, etc.)
|
|
2735
2822
|
const headers = [];
|
|
2736
2823
|
const footers = [];
|
|
2737
|
-
for (const [
|
|
2824
|
+
for (const [, filePath] of headerFooterRels) {
|
|
2738
2825
|
const hfXml = await zip.file(filePath)?.async("text");
|
|
2739
2826
|
if (!hfXml)
|
|
2740
2827
|
continue;
|
|
@@ -2754,7 +2841,7 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2754
2841
|
positionedElements.push(...header.positionedElements);
|
|
2755
2842
|
}
|
|
2756
2843
|
// Parse document elements and body positioned elements
|
|
2757
|
-
const parsedDoc = parseDocument(docDoc, themeColors, themeFonts);
|
|
2844
|
+
const parsedDoc = parseDocument(docDoc, themeColors, themeFonts, tableStyleMap);
|
|
2758
2845
|
const elements = parsedDoc.elements;
|
|
2759
2846
|
// Add positioned elements from document body
|
|
2760
2847
|
positionedElements.push(...parsedDoc.positionedElements);
|
|
@@ -2767,7 +2854,6 @@ export default async function importDocx(arrayBuffer) {
|
|
|
2767
2854
|
const marginRightPx = sectionProps.marginRight ? twipsToPx(sectionProps.marginRight) : 72;
|
|
2768
2855
|
const marginTopPx = sectionProps.marginTop ? twipsToPx(sectionProps.marginTop) : 72;
|
|
2769
2856
|
const marginBottomPx = sectionProps.marginBottom ? twipsToPx(sectionProps.marginBottom) : 72;
|
|
2770
|
-
const contentWidth = pageWidthPx - marginLeftPx - marginRightPx;
|
|
2771
2857
|
// Generate column layout CSS if multi-column
|
|
2772
2858
|
const columnLayoutCss = generateColumnLayoutCss(sectionProps);
|
|
2773
2859
|
// Check if we have absolutely positioned elements that need a relative container
|