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