simple-super-doc 0.11.4 → 0.11.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/README.md +14 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +138 -36
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ Browser-only TypeScript library that parses a `.docx` file into a typed
|
|
|
4
4
|
intermediate representation (IR) and renders it to HTML. Built for document
|
|
5
5
|
editors and viewers that need a faithful, inspectable model of the document.
|
|
6
6
|
|
|
7
|
+
**▶️ [Try it live](https://rafael-t-santos.github.io/simple-super-doc/)** — drag
|
|
8
|
+
and drop any `.docx` in your browser to see it rendered. Nothing is uploaded;
|
|
9
|
+
parsing and rendering happen entirely on your machine.
|
|
10
|
+
|
|
7
11
|
- **Typed IR** — `parse()` returns a `DocxDocument` of `Block`s (paragraphs,
|
|
8
12
|
tables) with computed styles, lists, images, and page size. Inspect or
|
|
9
13
|
transform it before rendering.
|
|
@@ -140,12 +144,18 @@ following are intentionally out of scope:
|
|
|
140
144
|
## Development
|
|
141
145
|
|
|
142
146
|
```bash
|
|
143
|
-
npm run typecheck
|
|
144
|
-
npm test
|
|
145
|
-
npm run
|
|
146
|
-
npm run build
|
|
147
|
+
npm run typecheck # tsc --noEmit
|
|
148
|
+
npm test # vitest (node) — parser + pure layout heuristics
|
|
149
|
+
npm run test:browser # Playwright (headless Chromium) — page-aware rendering
|
|
150
|
+
npm run build # esm + d.ts into dist/
|
|
151
|
+
npm run build:demo # IIFE bundle into demo/ for the drag-and-drop demo
|
|
147
152
|
```
|
|
148
153
|
|
|
154
|
+
The two-pass, page-aware pagination (table splitting, per-page footnotes) needs
|
|
155
|
+
a real layout engine, so it's covered by browser tests in
|
|
156
|
+
[`test/browser/`](test/browser/) rather than the node suite. Install the browser
|
|
157
|
+
once with `npx playwright-core install chromium`.
|
|
158
|
+
|
|
149
159
|
## License
|
|
150
160
|
|
|
151
161
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -76,6 +76,8 @@ type ImageRun = {
|
|
|
76
76
|
heightPx: number;
|
|
77
77
|
isPageBackground?: boolean;
|
|
78
78
|
href?: string;
|
|
79
|
+
anchorXPx?: number;
|
|
80
|
+
anchorYPx?: number;
|
|
79
81
|
};
|
|
80
82
|
type TableBlock = {
|
|
81
83
|
type: 'table';
|
|
@@ -113,12 +115,17 @@ type ListRef = {
|
|
|
113
115
|
ordered: boolean;
|
|
114
116
|
start: number;
|
|
115
117
|
format: string;
|
|
118
|
+
bulletText?: string;
|
|
116
119
|
};
|
|
117
120
|
type ComputedStyle = {
|
|
118
121
|
bold?: boolean;
|
|
119
122
|
italic?: boolean;
|
|
120
123
|
underline?: boolean;
|
|
121
124
|
strike?: boolean;
|
|
125
|
+
doubleStrike?: boolean;
|
|
126
|
+
hidden?: boolean;
|
|
127
|
+
caps?: boolean;
|
|
128
|
+
smallCaps?: boolean;
|
|
122
129
|
vertAlign?: 'super' | 'sub';
|
|
123
130
|
fontSize?: number;
|
|
124
131
|
fontFamily?: string;
|
package/dist/index.js
CHANGED
|
@@ -85,6 +85,14 @@ function extractRPr(rPr) {
|
|
|
85
85
|
const val = typeof st === "object" && st !== null ? st.val : void 0;
|
|
86
86
|
s.strike = !(val === "0" || val === "false" || val === "off");
|
|
87
87
|
}
|
|
88
|
+
const toggle = (node) => {
|
|
89
|
+
const val = typeof node === "object" && node !== null ? node.val : void 0;
|
|
90
|
+
return !(val === "0" || val === "false" || val === "off");
|
|
91
|
+
};
|
|
92
|
+
if ("dstrike" in rPr) s.doubleStrike = toggle(rPr.dstrike);
|
|
93
|
+
if ("vanish" in rPr) s.hidden = toggle(rPr.vanish);
|
|
94
|
+
if ("caps" in rPr) s.caps = toggle(rPr.caps);
|
|
95
|
+
if ("smallCaps" in rPr) s.smallCaps = toggle(rPr.smallCaps);
|
|
88
96
|
if ("vertAlign" in rPr) {
|
|
89
97
|
const va = rPr.vertAlign?.val;
|
|
90
98
|
if (va === "superscript") s.vertAlign = "super";
|
|
@@ -168,6 +176,10 @@ function extractPPr(pPr) {
|
|
|
168
176
|
const val = pPr.bidi?.val;
|
|
169
177
|
s.rtl = !(val === "0" || val === "false" || val === "off");
|
|
170
178
|
}
|
|
179
|
+
if ("shd" in pPr) {
|
|
180
|
+
const fill = pPr.shd?.fill;
|
|
181
|
+
if (fill && fill !== "auto") s.backgroundColor = fill;
|
|
182
|
+
}
|
|
171
183
|
if ("pBdr" in pPr) {
|
|
172
184
|
const bdr = pPr.pBdr;
|
|
173
185
|
const sides = [["top", "borderTop"], ["bottom", "borderBottom"], ["left", "borderLeft"], ["right", "borderRight"]];
|
|
@@ -290,9 +302,11 @@ function parseNumbering(xml) {
|
|
|
290
302
|
const ilvl = parseInt(String(lvl.ilvl), 10);
|
|
291
303
|
const fmtNode = lvl.numFmt;
|
|
292
304
|
const startNode = lvl.start;
|
|
305
|
+
const textNode = lvl.lvlText;
|
|
293
306
|
const format = fmtNode?.val ?? "bullet";
|
|
294
307
|
const start = parseInt(startNode?.val ?? "1", 10);
|
|
295
|
-
|
|
308
|
+
const text = textNode?.val;
|
|
309
|
+
abstractNumMap[id][ilvl] = { format, start: isNaN(start) ? 1 : start, ...text !== void 0 ? { text } : {} };
|
|
296
310
|
}
|
|
297
311
|
}
|
|
298
312
|
const numMap = {};
|
|
@@ -465,7 +479,14 @@ function resolveListRef(pPr, ctx) {
|
|
|
465
479
|
if (!levelInfo) return void 0;
|
|
466
480
|
const start = numEntry.startOverride ?? levelInfo.start;
|
|
467
481
|
const ordered = levelInfo.format !== "bullet" && levelInfo.format !== "none";
|
|
468
|
-
return {
|
|
482
|
+
return {
|
|
483
|
+
numId,
|
|
484
|
+
ilvl,
|
|
485
|
+
ordered,
|
|
486
|
+
start,
|
|
487
|
+
format: levelInfo.format,
|
|
488
|
+
...levelInfo.text !== void 0 ? { bulletText: levelInfo.text } : {}
|
|
489
|
+
};
|
|
469
490
|
}
|
|
470
491
|
async function parseRun(r, paraStyle, ctx, href) {
|
|
471
492
|
const rPr = r.rPr;
|
|
@@ -482,6 +503,7 @@ async function parseRun(r, paraStyle, ctx, href) {
|
|
|
482
503
|
if (brType === "column") return null;
|
|
483
504
|
lineBreak = true;
|
|
484
505
|
}
|
|
506
|
+
if ("cr" in r) lineBreak = true;
|
|
485
507
|
const rStyleId = getVal(rPr?.rStyle);
|
|
486
508
|
const charStyle = rStyleId ? ctx.styleMap[rStyleId] ?? {} : {};
|
|
487
509
|
const runStyle = Object.assign({}, paraStyle, charStyle, extractRPr(rPr));
|
|
@@ -513,13 +535,22 @@ async function parseRun(r, paraStyle, ctx, href) {
|
|
|
513
535
|
if (!rId) return null;
|
|
514
536
|
const resolved = await resolveImage(rId, ctx.relationshipMap, ctx.zip);
|
|
515
537
|
if (!resolved) return null;
|
|
538
|
+
const offsetPx = (pos) => {
|
|
539
|
+
const off = pos?.posOffset;
|
|
540
|
+
const n = off != null ? parseInt(String(off["#text"] ?? off), 10) : NaN;
|
|
541
|
+
return Number.isNaN(n) ? void 0 : Math.round(n / 9525);
|
|
542
|
+
};
|
|
543
|
+
const anchorXPx = anchor ? offsetPx(anchor.positionH) : void 0;
|
|
544
|
+
const anchorYPx = anchor ? offsetPx(anchor.positionV) : void 0;
|
|
516
545
|
const img = {
|
|
517
546
|
type: "image",
|
|
518
547
|
src: resolved.src,
|
|
519
548
|
widthPx: Math.round(cx / 9525),
|
|
520
549
|
heightPx: Math.round(cy / 9525),
|
|
521
550
|
...isPageBackground ? { isPageBackground: true } : {},
|
|
522
|
-
...href ? { href } : {}
|
|
551
|
+
...href ? { href } : {},
|
|
552
|
+
...anchorXPx !== void 0 ? { anchorXPx } : {},
|
|
553
|
+
...anchorYPx !== void 0 ? { anchorYPx } : {}
|
|
523
554
|
};
|
|
524
555
|
return img;
|
|
525
556
|
}
|
|
@@ -540,27 +571,58 @@ async function parseRun(r, paraStyle, ctx, href) {
|
|
|
540
571
|
}
|
|
541
572
|
return null;
|
|
542
573
|
}
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
574
|
+
const collectText = (node) => {
|
|
575
|
+
if (node == null) return [];
|
|
576
|
+
if (Array.isArray(node)) return node.flatMap(collectText);
|
|
577
|
+
if (typeof node === "string") return [node];
|
|
578
|
+
if (typeof node === "object") {
|
|
579
|
+
const t = node;
|
|
580
|
+
return [String(t["#text"] ?? t._ ?? "")];
|
|
581
|
+
}
|
|
582
|
+
return [String(node)];
|
|
583
|
+
};
|
|
584
|
+
const segments = collectText(r.t ?? r.delText);
|
|
553
585
|
const tabNode = r.tab;
|
|
554
|
-
const
|
|
555
|
-
const
|
|
586
|
+
const tabCount = Array.isArray(tabNode) ? tabNode.length : "tab" in r ? 1 : 0;
|
|
587
|
+
const asArray = (n) => Array.isArray(n) ? n : n != null ? [n] : [];
|
|
588
|
+
let symText = "";
|
|
589
|
+
let symFont;
|
|
590
|
+
for (const sy of asArray(r.sym)) {
|
|
591
|
+
const code = parseInt(String(sy?.char ?? ""), 16);
|
|
592
|
+
if (code >= 0 && code <= 1114111) symText += String.fromCodePoint(code);
|
|
593
|
+
symFont ?? (symFont = sy?.font);
|
|
594
|
+
}
|
|
595
|
+
const hyphens = "\u2011".repeat(asArray(r.noBreakHyphen).length) + "\xAD".repeat(asArray(r.softHyphen).length);
|
|
596
|
+
if (hyphens) {
|
|
597
|
+
if (segments.length >= 2) segments[1] = hyphens + segments[1];
|
|
598
|
+
else segments[0] = (segments[0] ?? "") + hyphens;
|
|
599
|
+
}
|
|
600
|
+
if (symText) {
|
|
601
|
+
if (segments.length === 0) {
|
|
602
|
+
const style = symFont ? Object.assign({}, runStyle, { fontFamily: symFont }) : runStyle;
|
|
603
|
+
return {
|
|
604
|
+
type: "run",
|
|
605
|
+
text: symText,
|
|
606
|
+
style,
|
|
607
|
+
...href ? { href } : {},
|
|
608
|
+
...lineBreak ? { lineBreak: true } : {}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
segments[segments.length - 1] += symText;
|
|
612
|
+
}
|
|
613
|
+
const makeRun = (text, tabs, isLast) => ({
|
|
556
614
|
type: "run",
|
|
557
615
|
text,
|
|
558
616
|
style: runStyle,
|
|
559
617
|
...href ? { href } : {},
|
|
560
|
-
...lineBreak ? { lineBreak: true } : {},
|
|
618
|
+
...isLast && lineBreak ? { lineBreak: true } : {},
|
|
561
619
|
...tabs ? { tabs } : {}
|
|
562
|
-
};
|
|
563
|
-
return
|
|
620
|
+
});
|
|
621
|
+
if (segments.length <= 1) return makeRun(segments[0] ?? "", tabCount, true);
|
|
622
|
+
const interleave = tabCount === segments.length - 1;
|
|
623
|
+
return segments.map(
|
|
624
|
+
(seg, i) => makeRun(seg, i === 0 ? 0 : interleave ? 1 : i === 1 ? tabCount : 0, i === segments.length - 1)
|
|
625
|
+
);
|
|
564
626
|
}
|
|
565
627
|
function resolveHyperlinkHref(hl, ctx) {
|
|
566
628
|
const rId = hl.id;
|
|
@@ -677,7 +739,7 @@ async function parseParagraph(p, ctx, rawXml) {
|
|
|
677
739
|
else {
|
|
678
740
|
for (const ir of innerRuns) {
|
|
679
741
|
const run2 = await parseRun(ir, paraStyle, ctx);
|
|
680
|
-
if (run2 !== null) runs.push(run2);
|
|
742
|
+
if (run2 !== null) runs.push(...Array.isArray(run2) ? run2 : [run2]);
|
|
681
743
|
}
|
|
682
744
|
}
|
|
683
745
|
continue;
|
|
@@ -705,9 +767,11 @@ async function parseParagraph(p, ctx, rawXml) {
|
|
|
705
767
|
if (fieldStack.some((f) => f.inResult && LIVE_FIELDS.has(f.name))) continue;
|
|
706
768
|
const run = await parseRun(r, paraStyle, ctx, href);
|
|
707
769
|
if (run !== null) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
770
|
+
for (const rn of Array.isArray(run) ? run : [run]) {
|
|
771
|
+
if (deleted && rn.type === "run") rn.deleted = true;
|
|
772
|
+
if (inserted && rn.type === "run") rn.inserted = true;
|
|
773
|
+
runs.push(rn);
|
|
774
|
+
}
|
|
711
775
|
}
|
|
712
776
|
}
|
|
713
777
|
const segments = [[]];
|
|
@@ -1241,8 +1305,10 @@ function styleToCss(s) {
|
|
|
1241
1305
|
const parts = [];
|
|
1242
1306
|
if (s.bold) parts.push("font-weight:bold");
|
|
1243
1307
|
if (s.italic) parts.push("font-style:italic");
|
|
1244
|
-
const deco = [s.underline ? "underline" : "", s.strike ? "line-through" : ""].filter(Boolean);
|
|
1245
|
-
if (deco.length) parts.push(`text-decoration:${deco.join(" ")}`);
|
|
1308
|
+
const deco = [s.underline ? "underline" : "", s.strike || s.doubleStrike ? "line-through" : ""].filter(Boolean);
|
|
1309
|
+
if (deco.length) parts.push(`text-decoration:${deco.join(" ")}${s.doubleStrike && !s.underline ? " double" : ""}`);
|
|
1310
|
+
if (s.caps) parts.push("text-transform:uppercase");
|
|
1311
|
+
if (s.smallCaps) parts.push("font-variant:small-caps");
|
|
1246
1312
|
if (s.vertAlign) {
|
|
1247
1313
|
parts.push(`vertical-align:${s.vertAlign}`);
|
|
1248
1314
|
parts.push("font-size:0.83em");
|
|
@@ -1268,16 +1334,24 @@ function renderRun(run, parent, skipLeadingTabs = false) {
|
|
|
1268
1334
|
target = a;
|
|
1269
1335
|
}
|
|
1270
1336
|
if (run.type === "image") {
|
|
1337
|
+
const ir = run;
|
|
1271
1338
|
const img = document.createElement("img");
|
|
1272
|
-
setImageSrc(img,
|
|
1273
|
-
img.width =
|
|
1274
|
-
img.height =
|
|
1339
|
+
setImageSrc(img, ir.src);
|
|
1340
|
+
img.width = ir.widthPx;
|
|
1341
|
+
img.height = ir.heightPx;
|
|
1275
1342
|
img.style.display = "inline-block";
|
|
1276
1343
|
img.style.maxWidth = "100%";
|
|
1344
|
+
if (inHeaderFooter && ir.anchorXPx !== void 0) {
|
|
1345
|
+
img.style.position = "absolute";
|
|
1346
|
+
img.style.left = `${ir.anchorXPx}px`;
|
|
1347
|
+
img.style.top = `${ir.anchorYPx ?? 0}px`;
|
|
1348
|
+
img.style.maxWidth = "none";
|
|
1349
|
+
}
|
|
1277
1350
|
target.appendChild(img);
|
|
1278
1351
|
return;
|
|
1279
1352
|
}
|
|
1280
1353
|
const textRun = run;
|
|
1354
|
+
if (textRun.style.hidden) return;
|
|
1281
1355
|
if (textRun.deleted) {
|
|
1282
1356
|
if (!showRevisions) return;
|
|
1283
1357
|
const del = document.createElement("del");
|
|
@@ -1386,6 +1460,7 @@ function ensureLineBox(el) {
|
|
|
1386
1460
|
}
|
|
1387
1461
|
}
|
|
1388
1462
|
var inTableCell = false;
|
|
1463
|
+
var inHeaderFooter = false;
|
|
1389
1464
|
var currentPageNumber = 0;
|
|
1390
1465
|
var FOOTNOTE_SEPARATOR_H = 12;
|
|
1391
1466
|
function footnoteNumbersIn(el) {
|
|
@@ -1434,7 +1509,10 @@ function renderFooter(doc, pageDiv, pageNum, pm, footerPx) {
|
|
|
1434
1509
|
const el = document.createElement("div");
|
|
1435
1510
|
el.className = "ssd-footer";
|
|
1436
1511
|
el.style.cssText = `position:absolute;left:${pm.left}px;right:${pm.right}px;bottom:${footerPx}px`;
|
|
1512
|
+
const prevHF = inHeaderFooter;
|
|
1513
|
+
inHeaderFooter = true;
|
|
1437
1514
|
renderBlocks(footer, el);
|
|
1515
|
+
inHeaderFooter = prevHF;
|
|
1438
1516
|
pageDiv.appendChild(el);
|
|
1439
1517
|
currentPageNumber = prev;
|
|
1440
1518
|
}
|
|
@@ -1451,7 +1529,10 @@ function renderHeader(doc, pageDiv, pageNum, pm, headerPx) {
|
|
|
1451
1529
|
const el = document.createElement("div");
|
|
1452
1530
|
el.className = "ssd-header";
|
|
1453
1531
|
el.style.cssText = `position:absolute;left:${pm.left}px;right:${pm.right}px;top:${headerPx}px`;
|
|
1532
|
+
const prevHF = inHeaderFooter;
|
|
1533
|
+
inHeaderFooter = true;
|
|
1454
1534
|
renderBlocks(header, el);
|
|
1535
|
+
inHeaderFooter = prevHF;
|
|
1455
1536
|
pageDiv.appendChild(el);
|
|
1456
1537
|
currentPageNumber = prev;
|
|
1457
1538
|
}
|
|
@@ -1478,8 +1559,14 @@ function renderParagraph(block) {
|
|
|
1478
1559
|
const p = document.createElement("p");
|
|
1479
1560
|
p.style.cssText = paragraphCss(block.style);
|
|
1480
1561
|
if (block.style.rtl) p.dir = "rtl";
|
|
1481
|
-
|
|
1562
|
+
let stops = block.style.tabStops;
|
|
1482
1563
|
const hasTabRun = block.runs.some((r) => r.type === "run" && (r.tabs ?? 0) > 0);
|
|
1564
|
+
if (inHeaderFooter && hasTabRun && !stops?.some((s) => s.val === "right" || s.val === "center" || s.val === "decimal")) {
|
|
1565
|
+
stops = [
|
|
1566
|
+
{ posPx: 0, val: "center", leader: "none" },
|
|
1567
|
+
{ posPx: 0, val: "right", leader: "none" }
|
|
1568
|
+
];
|
|
1569
|
+
}
|
|
1483
1570
|
const alignStop = stops?.some((s) => s.val === "right" || s.val === "center" || s.val === "decimal");
|
|
1484
1571
|
if (hasTabRun && stops && alignStop) {
|
|
1485
1572
|
renderTabbedParagraph(block, p, stops);
|
|
@@ -1594,6 +1681,12 @@ var LIST_STYLE = {
|
|
|
1594
1681
|
bullet: "disc",
|
|
1595
1682
|
none: "none"
|
|
1596
1683
|
};
|
|
1684
|
+
function listStyleTypeFor(format, bulletText) {
|
|
1685
|
+
if (format === "bullet" && bulletText && /^[\x20-\x7E]+$/.test(bulletText)) {
|
|
1686
|
+
return `"${bulletText.replace(/[\\"]/g, "\\$&")}"`;
|
|
1687
|
+
}
|
|
1688
|
+
return LIST_STYLE[format];
|
|
1689
|
+
}
|
|
1597
1690
|
function renderBlocks(blocks, container) {
|
|
1598
1691
|
let stack = [];
|
|
1599
1692
|
const closeLists = () => {
|
|
@@ -1602,13 +1695,13 @@ function renderBlocks(blocks, container) {
|
|
|
1602
1695
|
};
|
|
1603
1696
|
for (const block of blocks) {
|
|
1604
1697
|
if (block.type === "paragraph" && block.list) {
|
|
1605
|
-
const { numId, ordered, start, ilvl, format } = block.list;
|
|
1698
|
+
const { numId, ordered, start, ilvl, format, bulletText } = block.list;
|
|
1606
1699
|
if (stack.length > 0 && stack[0].numId !== numId) closeLists();
|
|
1607
1700
|
while (stack.length > ilvl + 1) stack.pop();
|
|
1608
1701
|
while (stack.length < ilvl + 1) {
|
|
1609
1702
|
const listEl = document.createElement(ordered ? "ol" : "ul");
|
|
1610
1703
|
if (ordered) listEl.start = start;
|
|
1611
|
-
const styleType =
|
|
1704
|
+
const styleType = listStyleTypeFor(format, bulletText);
|
|
1612
1705
|
if (styleType) listEl.style.listStyleType = styleType;
|
|
1613
1706
|
const indent = block.style.indentLeft ?? 24;
|
|
1614
1707
|
listEl.style.margin = "0";
|
|
@@ -1721,25 +1814,29 @@ function renderPlainPaginated(doc, container, pw, ph, pm, pageOffset = 0) {
|
|
|
1721
1814
|
for (; ; ) {
|
|
1722
1815
|
pageDiv.appendChild(table);
|
|
1723
1816
|
const th = table.offsetHeight;
|
|
1724
|
-
|
|
1817
|
+
const pieceRefs = footnoteNumbersIn(table);
|
|
1818
|
+
const pieceReserve = fnReserve(pieceRefs, pageHasFn);
|
|
1819
|
+
if (used + th <= contentH - reserve - pieceReserve) {
|
|
1725
1820
|
used += th;
|
|
1821
|
+
recordFootnotes(pieceRefs, pieceReserve);
|
|
1726
1822
|
break;
|
|
1727
1823
|
}
|
|
1728
|
-
const rest = splitTableRows(table, contentH - reserve - used);
|
|
1824
|
+
const rest = splitTableRows(table, contentH - reserve - pieceReserve - used);
|
|
1729
1825
|
if (!rest) {
|
|
1730
1826
|
if (used === 0) {
|
|
1731
1827
|
used += th;
|
|
1828
|
+
recordFootnotes(pieceRefs, pieceReserve);
|
|
1732
1829
|
break;
|
|
1733
1830
|
}
|
|
1734
1831
|
pageDiv.removeChild(table);
|
|
1735
1832
|
pageDiv = newPage();
|
|
1736
1833
|
continue;
|
|
1737
1834
|
}
|
|
1835
|
+
const fitRefs = footnoteNumbersIn(table);
|
|
1836
|
+
recordFootnotes(fitRefs, fnReserve(fitRefs, pageHasFn));
|
|
1738
1837
|
pageDiv = newPage();
|
|
1739
1838
|
table = rest;
|
|
1740
1839
|
}
|
|
1741
|
-
const refs2 = footnoteNumbersIn(table);
|
|
1742
|
-
recordFootnotes(refs2, fnReserve(refs2, pageHasFn));
|
|
1743
1840
|
continue;
|
|
1744
1841
|
}
|
|
1745
1842
|
const h = heights[i];
|
|
@@ -2198,8 +2295,13 @@ async function resolvePart(documentXml, relationshipMap, zip, ctx, kind, parseXm
|
|
|
2198
2295
|
}
|
|
2199
2296
|
async function resolveRid(rId, relationshipMap, zip, ctx, parseXml) {
|
|
2200
2297
|
if (!rId || !relationshipMap[rId]) return void 0;
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2298
|
+
const target = relationshipMap[rId].target;
|
|
2299
|
+
const xml = await readEntry(zip, `word/${target}`, false);
|
|
2300
|
+
if (!xml) return void 0;
|
|
2301
|
+
const base = target.replace(/^.*\//, "");
|
|
2302
|
+
const partRelsXml = await readEntry(zip, `word/_rels/${base}.rels`, false);
|
|
2303
|
+
const partCtx = partRelsXml ? { ...ctx, relationshipMap: parseRelationships(partRelsXml) } : ctx;
|
|
2304
|
+
return parseXml(xml, partCtx);
|
|
2203
2305
|
}
|
|
2204
2306
|
async function resolveNotes(xml, kind, refIds, ctx) {
|
|
2205
2307
|
if (!xml || refIds.length === 0) return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-super-doc",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
4
|
"description": "Faithful DOCX renderer with exposed typed IR for building document editors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
|
+
"test:browser": "vitest run --config vitest.browser.config.ts",
|
|
25
26
|
"prepublishOnly": "npm run build && npm run typecheck && npm test",
|
|
26
27
|
"postversion": "git push origin HEAD --follow-tags"
|
|
27
28
|
},
|
|
@@ -51,6 +52,8 @@
|
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@types/node": "^26.0.1",
|
|
55
|
+
"esbuild": "^0.27.7",
|
|
56
|
+
"playwright-core": "^1.61.1",
|
|
54
57
|
"tsup": "^8.5.1",
|
|
55
58
|
"typescript": "^6.0.3",
|
|
56
59
|
"vitest": "^4.1.9"
|