@particle-academy/fancy-docs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,382 @@
1
+ import { Fragment as Fragment$1 } from 'react';
2
+ import { FauxClient, Callout, sanitizeHref } from '@particle-academy/react-fancy';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+
5
+ // src/DocViewer.tsx
6
+
7
+ // src/style.ts
8
+ var DEFAULTS = {
9
+ font: "Calibri",
10
+ /** Points. */
11
+ size: 11,
12
+ /** w:spacing after="160": 8pt after every paragraph. */
13
+ spaceAfter: 8,
14
+ /** w:line="259" of 240: 1.08. */
15
+ lineHeight: 1.08,
16
+ /** Heading 1-6, points. */
17
+ headingSizes: [20, 16, 14, 13, 12, 11],
18
+ headingSpaceBefore: 12,
19
+ headingSpaceAfter: 6,
20
+ quoteIndent: 36,
21
+ quoteColor: "#595959",
22
+ codeFont: "Consolas",
23
+ codeSize: 10,
24
+ codeFill: "#F2F2F2",
25
+ linkColor: "#0563C1",
26
+ /** Cell margins 60 / 108 twips. */
27
+ cellPadding: { top: 3, right: 5.4, bottom: 3, left: 5.4 },
28
+ /** The header-cell grey the writer and all three engines share. */
29
+ headerFill: "#E7E7E7",
30
+ /** A border edge that names no width. */
31
+ borderWidth: 0.5,
32
+ /** Page margins when none are given: 1440 twips. */
33
+ margin: 72,
34
+ /** Header / footer distance from the page edge: 720 twips. */
35
+ bandDistance: 36,
36
+ bandSize: 9
37
+ };
38
+ var PAGE_SIZES = {
39
+ letter: [612, 792],
40
+ legal: [612, 1008],
41
+ a4: [595, 842]
42
+ };
43
+ function hex(value) {
44
+ if (typeof value !== "string") return void 0;
45
+ const v = value.trim();
46
+ if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toUpperCase();
47
+ if (/^[0-9a-fA-F]{6}$/.test(v)) return `#${v.toUpperCase()}`;
48
+ return void 0;
49
+ }
50
+ function num(value) {
51
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
52
+ }
53
+ function fontStack(font, generic = "sans-serif") {
54
+ if (typeof font !== "string" || font.trim() === "") return void 0;
55
+ const name = font.replace(/["\\;{}<>]/g, "").trim();
56
+ return `"${name}", ${generic}`;
57
+ }
58
+ var CSS_STYLE = { single: "solid", double: "double", dashed: "dashed", dotted: "dotted" };
59
+ function borderCss(border) {
60
+ if (!border) return void 0;
61
+ if (border.style === "none") return "none";
62
+ const width = num(border.width) ?? DEFAULTS.borderWidth;
63
+ const style = CSS_STYLE[border.style ?? "single"] ?? "solid";
64
+ return `${width}pt ${style} ${hex(border.color) ?? "#000000"}`;
65
+ }
66
+ function boxBorders(borders) {
67
+ if (!borders) return {};
68
+ const out = {};
69
+ const top = borderCss(borders.top);
70
+ const right = borderCss(borders.right);
71
+ const bottom = borderCss(borders.bottom);
72
+ const left = borderCss(borders.left);
73
+ if (top) out.borderTop = top;
74
+ if (right) out.borderRight = right;
75
+ if (bottom) out.borderBottom = bottom;
76
+ if (left) out.borderLeft = left;
77
+ return out;
78
+ }
79
+ function padding(sides, fallback) {
80
+ const pick = (k) => num(sides?.[k]) ?? num(fallback?.[k]);
81
+ const out = {};
82
+ const t = pick("top");
83
+ const r = pick("right");
84
+ const b = pick("bottom");
85
+ const l = pick("left");
86
+ if (t !== void 0) out.paddingTop = `${t}pt`;
87
+ if (r !== void 0) out.paddingRight = `${r}pt`;
88
+ if (b !== void 0) out.paddingBottom = `${b}pt`;
89
+ if (l !== void 0) out.paddingLeft = `${l}pt`;
90
+ return out;
91
+ }
92
+ var SINGLE_LINE = 1.2;
93
+ function lineHeightCss(lines) {
94
+ return Math.round(lines * SINGLE_LINE * 1e3) / 1e3;
95
+ }
96
+ function paragraphCss(p, defaults = {}) {
97
+ const out = {
98
+ marginTop: `${num(p.spaceBefore) ?? defaults.before ?? 0}pt`,
99
+ marginBottom: `${num(p.spaceAfter) ?? defaults.after ?? DEFAULTS.spaceAfter}pt`,
100
+ lineHeight: String(lineHeightCss(num(p.lineHeight) ?? DEFAULTS.lineHeight)),
101
+ ...boxBorders(p.borders)
102
+ };
103
+ if (p.align) out.textAlign = p.align;
104
+ const left = num(p.indentLeft);
105
+ const right = num(p.indentRight);
106
+ if (left !== void 0) out.marginLeft = `${left}pt`;
107
+ if (right !== void 0) out.marginRight = `${right}pt`;
108
+ const fill = hex(p.shading);
109
+ if (fill) {
110
+ out.backgroundColor = fill;
111
+ out.padding = "2pt 4pt";
112
+ }
113
+ return out;
114
+ }
115
+ function runCss(run) {
116
+ const out = {};
117
+ if (run.bold) out.fontWeight = 700;
118
+ if (run.italic) out.fontStyle = "italic";
119
+ const lines = [run.underline ? "underline" : "", run.strike ? "line-through" : ""].filter(Boolean);
120
+ if (lines.length) out.textDecorationLine = lines.join(" ");
121
+ const color = hex(run.color);
122
+ if (color) out.color = color;
123
+ const highlight = hex(run.highlight);
124
+ if (highlight) out.backgroundColor = highlight;
125
+ const size = num(run.size);
126
+ if (size !== void 0 && size > 0) out.fontSize = `${size}pt`;
127
+ const font = fontStack(run.font);
128
+ if (font) out.fontFamily = font;
129
+ if (run.smallCaps) out.fontVariant = "small-caps";
130
+ const spacing = num(run.letterSpacing);
131
+ if (spacing) out.letterSpacing = `${spacing}pt`;
132
+ return out;
133
+ }
134
+ function DocViewer({ doc, frame = true, frameProps, className }) {
135
+ const pages = splitPages(Array.isArray(doc?.blocks) ? doc.blocks : []);
136
+ const total = pages.length;
137
+ const sheets = /* @__PURE__ */ jsx(
138
+ "div",
139
+ {
140
+ "data-fancy-docs-viewer": "",
141
+ className,
142
+ style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 16, padding: 16, background: "#E5E7EB" },
143
+ children: pages.map((blocks, i) => /* @__PURE__ */ jsx(PageSheet, { doc, blocks, number: i + 1, total }, i))
144
+ }
145
+ );
146
+ if (!frame) return sheets;
147
+ return /* @__PURE__ */ jsx(
148
+ FauxClient,
149
+ {
150
+ variant: "device",
151
+ controls: true,
152
+ fill: true,
153
+ width: logicalWidth(doc),
154
+ scale: "fit",
155
+ bodyClassName: "overflow-auto",
156
+ ...frameProps,
157
+ children: sheets
158
+ }
159
+ );
160
+ }
161
+ function logicalWidth(doc) {
162
+ const page = doc?.page ?? {};
163
+ const [w, h] = PAGE_SIZES[page.size ?? "letter"] ?? PAGE_SIZES.letter;
164
+ const width = page.orientation === "landscape" ? h : w;
165
+ return Math.ceil(width * 4 / 3) + 32;
166
+ }
167
+ function splitPages(blocks) {
168
+ const pages = [[]];
169
+ for (const block of blocks) {
170
+ if (block?.type === "pageBreak") pages.push([]);
171
+ else pages[pages.length - 1].push(block);
172
+ }
173
+ return pages;
174
+ }
175
+ function PageSheet({ doc, blocks, number, total }) {
176
+ const page = doc.page ?? {};
177
+ const [w, h] = PAGE_SIZES[page.size ?? "letter"] ?? PAGE_SIZES.letter;
178
+ const [width, height] = page.orientation === "landscape" ? [h, w] : [w, h];
179
+ const m = page.margins ?? {};
180
+ const margin = (side) => num(m[side]) ?? DEFAULTS.margin;
181
+ const style = {
182
+ position: "relative",
183
+ width: `${width}pt`,
184
+ minHeight: `${height}pt`,
185
+ background: "#FFFFFF",
186
+ color: "#000000",
187
+ boxShadow: "0 1px 3px rgba(0,0,0,0.15)",
188
+ fontFamily: fontStack(doc.defaultFont ?? DEFAULTS.font),
189
+ fontSize: `${num(doc.defaultSize) ?? DEFAULTS.size}pt`,
190
+ flexShrink: 0
191
+ };
192
+ return /* @__PURE__ */ jsxs("section", { "data-fancy-docs-page": number, style, children: [
193
+ page.header && /* @__PURE__ */ jsx(BandLine, { band: page.header, kind: "header", number, total, left: margin("left"), right: margin("right") }),
194
+ /* @__PURE__ */ jsx(
195
+ "div",
196
+ {
197
+ "data-fancy-docs-body": "",
198
+ style: { paddingTop: `${margin("top")}pt`, paddingRight: `${margin("right")}pt`, paddingBottom: `${margin("bottom")}pt`, paddingLeft: `${margin("left")}pt` },
199
+ children: blocks.map((block, i) => /* @__PURE__ */ jsx(BlockView, { block }, i))
200
+ }
201
+ ),
202
+ page.footer && /* @__PURE__ */ jsx(BandLine, { band: page.footer, kind: "footer", number, total, left: margin("left"), right: margin("right") })
203
+ ] });
204
+ }
205
+ function BandLine({ band, kind, number, total, left, right }) {
206
+ const fill = (s) => typeof s === "string" ? s.split("{page}").join(String(number)).split("{pages}").join(String(total)) : "";
207
+ const style = {
208
+ position: "absolute",
209
+ left: `${left}pt`,
210
+ right: `${right}pt`,
211
+ [kind === "header" ? "top" : "bottom"]: `${DEFAULTS.bandDistance}pt`,
212
+ display: "grid",
213
+ gridTemplateColumns: "1fr auto 1fr",
214
+ fontSize: `${num(band.size) ?? DEFAULTS.bandSize}pt`,
215
+ color: hex(band.color) ?? "#595959"
216
+ };
217
+ return /* @__PURE__ */ jsxs("div", { "data-fancy-docs-band": kind, style, children: [
218
+ /* @__PURE__ */ jsx("span", { style: { textAlign: "left" }, children: fill(band.left) }),
219
+ /* @__PURE__ */ jsx("span", { style: { textAlign: "center" }, children: fill(band.center) }),
220
+ /* @__PURE__ */ jsx("span", { style: { textAlign: "right" }, children: fill(band.right) })
221
+ ] });
222
+ }
223
+ function Runs({ runs }) {
224
+ return /* @__PURE__ */ jsx(Fragment, { children: (Array.isArray(runs) ? runs : []).map((run, i) => /* @__PURE__ */ jsx(RunView, { run }, i)) });
225
+ }
226
+ function RunView({ run }) {
227
+ const text = typeof run?.text === "string" ? run.text : "";
228
+ const style = runCss(run);
229
+ const href = typeof run.link === "string" ? sanitizeHref(run.link) : void 0;
230
+ if (run.code) {
231
+ style.fontFamily = style.fontFamily ?? fontStack(DEFAULTS.codeFont, "monospace");
232
+ style.backgroundColor = style.backgroundColor ?? DEFAULTS.codeFill;
233
+ }
234
+ if (href) {
235
+ style.color = style.color ?? DEFAULTS.linkColor;
236
+ style.textDecorationLine = style.textDecorationLine ?? "underline";
237
+ }
238
+ const parts = text.split("\n");
239
+ const content = parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
240
+ i > 0 && /* @__PURE__ */ jsx("br", {}),
241
+ part
242
+ ] }, i));
243
+ const span = run.code ? /* @__PURE__ */ jsx("code", { "data-fancy-docs-run": "", style, children: content }) : /* @__PURE__ */ jsx("span", { "data-fancy-docs-run": "", style, children: content });
244
+ return href ? /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer nofollow", children: span }) : span;
245
+ }
246
+ function BlockView({ block }) {
247
+ switch (block?.type) {
248
+ case "heading": {
249
+ const level = Math.min(6, Math.max(1, Math.round(num(block.level) ?? 1)));
250
+ const Tag = `h${level}`;
251
+ const style = {
252
+ ...paragraphCss(block, { before: DEFAULTS.headingSpaceBefore, after: DEFAULTS.headingSpaceAfter }),
253
+ fontSize: `${DEFAULTS.headingSizes[level - 1]}pt`,
254
+ fontWeight: 700
255
+ };
256
+ return /* @__PURE__ */ jsx(Tag, { "data-fancy-docs-block": "heading", style, children: /* @__PURE__ */ jsx(Runs, { runs: block.runs }) });
257
+ }
258
+ case "paragraph":
259
+ return /* @__PURE__ */ jsx("p", { "data-fancy-docs-block": "paragraph", style: paragraphCss(block), children: /* @__PURE__ */ jsx(Runs, { runs: block.runs }) });
260
+ case "list":
261
+ return /* @__PURE__ */ jsx(ListView, { items: block.items, ordered: !!block.ordered });
262
+ case "table":
263
+ return /* @__PURE__ */ jsx(TableView, { block });
264
+ case "code":
265
+ return /* @__PURE__ */ jsx(
266
+ "pre",
267
+ {
268
+ "data-fancy-docs-block": "code",
269
+ style: {
270
+ margin: `0 0 ${DEFAULTS.spaceAfter}pt`,
271
+ padding: "4pt 6pt",
272
+ background: DEFAULTS.codeFill,
273
+ fontFamily: fontStack(DEFAULTS.codeFont, "monospace"),
274
+ fontSize: `${DEFAULTS.codeSize}pt`,
275
+ lineHeight: 1,
276
+ whiteSpace: "pre-wrap"
277
+ },
278
+ children: /* @__PURE__ */ jsx("code", { children: typeof block.text === "string" ? block.text : "" })
279
+ }
280
+ );
281
+ case "quote":
282
+ return /* @__PURE__ */ jsx(
283
+ "blockquote",
284
+ {
285
+ "data-fancy-docs-block": "quote",
286
+ style: { margin: `0 0 0 ${DEFAULTS.quoteIndent}pt`, fontStyle: "italic", color: DEFAULTS.quoteColor },
287
+ children: (Array.isArray(block.blocks) ? block.blocks : []).map((b, i) => /* @__PURE__ */ jsx(BlockView, { block: b }, i))
288
+ }
289
+ );
290
+ case "image": {
291
+ const src = typeof block.src === "string" && /^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/=]+$/.test(block.src) ? block.src : null;
292
+ if (!src) return null;
293
+ return /* @__PURE__ */ jsx(
294
+ "img",
295
+ {
296
+ "data-fancy-docs-block": "image",
297
+ src,
298
+ alt: typeof block.alt === "string" ? block.alt : "",
299
+ style: { display: "block", maxWidth: "100%", width: num(block.widthPx), height: num(block.heightPx) ? void 0 : "auto", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }
300
+ }
301
+ );
302
+ }
303
+ case "hr":
304
+ return /* @__PURE__ */ jsx("hr", { "data-fancy-docs-block": "hr", style: { border: 0, borderTop: "0.5pt solid #000000", margin: `0 0 ${DEFAULTS.spaceAfter}pt` } });
305
+ default:
306
+ return null;
307
+ }
308
+ }
309
+ function ListView({ items, ordered }) {
310
+ const Tag = ordered ? "ol" : "ul";
311
+ return /* @__PURE__ */ jsx(Tag, { "data-fancy-docs-block": "list", style: { margin: `0 0 ${DEFAULTS.spaceAfter}pt`, paddingLeft: "18pt", listStyleType: ordered ? "decimal" : "disc" }, children: (Array.isArray(items) ? items : []).map((item, i) => /* @__PURE__ */ jsxs("li", { style: { ...paragraphCss(item, { after: 0 }) }, children: [
312
+ /* @__PURE__ */ jsx(Runs, { runs: item.runs }),
313
+ Array.isArray(item.children) && item.children.length > 0 && /* @__PURE__ */ jsx(ListView, { items: item.children, ordered })
314
+ ] }, i)) });
315
+ }
316
+ function TableView({ block }) {
317
+ const rows = Array.isArray(block.rows) ? block.rows : [];
318
+ const widths = Array.isArray(block.widths) ? block.widths.filter((w) => num(w) !== void 0 && w > 0) : [];
319
+ const sum = widths.reduce((a, b) => a + b, 0);
320
+ const width = num(block.width);
321
+ const borders = block.borders ?? {};
322
+ const style = {
323
+ borderCollapse: "collapse",
324
+ width: width !== void 0 ? `${width}%` : "100%",
325
+ tableLayout: widths.length > 0 ? "fixed" : "auto",
326
+ marginBottom: `${DEFAULTS.spaceAfter}pt`,
327
+ ...block.align === "center" ? { marginLeft: "auto", marginRight: "auto" } : block.align === "right" ? { marginLeft: "auto" } : {}
328
+ };
329
+ const edge = (b) => borderCss(b) ?? `${DEFAULTS.borderWidth}pt solid #000000`;
330
+ return /* @__PURE__ */ jsxs("table", { "data-fancy-docs-block": "table", style, children: [
331
+ widths.length > 0 && /* @__PURE__ */ jsx("colgroup", { children: widths.map((w, i) => /* @__PURE__ */ jsx("col", { style: { width: `${Math.round(w / sum * 1e3) / 10}%` } }, i)) }),
332
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: (Array.isArray(row.cells) ? row.cells : []).map((cell, c, cells) => {
333
+ const header = !!row.header;
334
+ const Tag = header ? "th" : "td";
335
+ const first = c === 0;
336
+ const last = c === cells.length - 1;
337
+ const cellStyle = {
338
+ // Outer edges from the table's box; inner ones from insideH / insideV.
339
+ borderTop: r === 0 ? edge(borders.top) : edge(borders.insideH),
340
+ borderBottom: r === rows.length - 1 ? edge(borders.bottom) : edge(borders.insideH),
341
+ borderLeft: first ? edge(borders.left) : edge(borders.insideV),
342
+ borderRight: last ? edge(borders.right) : edge(borders.insideV),
343
+ ...padding(cell.padding, { ...DEFAULTS.cellPadding, ...block.cellPadding ?? {} }),
344
+ ...boxBorders(cell.borders),
345
+ verticalAlign: cell.valign === "center" ? "middle" : cell.valign ?? "top",
346
+ textAlign: "left",
347
+ fontWeight: header ? 700 : void 0,
348
+ backgroundColor: hex(cell.shading) ?? (header ? DEFAULTS.headerFill : void 0)
349
+ };
350
+ return /* @__PURE__ */ jsx(
351
+ Tag,
352
+ {
353
+ style: cellStyle,
354
+ colSpan: num(cell.colSpan) && cell.colSpan > 1 ? cell.colSpan : void 0,
355
+ rowSpan: num(cell.rowSpan) && cell.rowSpan > 1 ? cell.rowSpan : void 0,
356
+ children: /* @__PURE__ */ jsx(CellBlocks, { cell })
357
+ },
358
+ c
359
+ );
360
+ }) }, r)) })
361
+ ] });
362
+ }
363
+ function CellBlocks({ cell }) {
364
+ const blocks = Array.isArray(cell.blocks) ? cell.blocks : [];
365
+ return /* @__PURE__ */ jsx(Fragment, { children: blocks.map(
366
+ (b, i) => b?.type === "paragraph" ? /* @__PURE__ */ jsx("p", { "data-fancy-docs-block": "paragraph", style: { ...paragraphCss(b), ...i === blocks.length - 1 && num(b.spaceAfter) === void 0 ? { marginBottom: 0 } : {} }, children: /* @__PURE__ */ jsx(Runs, { runs: b.runs }) }, i) : /* @__PURE__ */ jsx(BlockView, { block: b }, i)
367
+ ) });
368
+ }
369
+ function DocFileView({ document, filename, frameProps }) {
370
+ const doc = document;
371
+ if (!doc || typeof doc !== "object" || !Array.isArray(doc.blocks)) {
372
+ return /* @__PURE__ */ jsxs(Callout, { color: "red", children: [
373
+ filename ?? "This file",
374
+ " did not read as a document."
375
+ ] });
376
+ }
377
+ return /* @__PURE__ */ jsx(DocViewer, { doc, frameProps });
378
+ }
379
+
380
+ export { DocFileView, DocViewer };
381
+ //# sourceMappingURL=index.js.map
382
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/style.ts","../src/DocViewer.tsx","../src/DocFileView.tsx"],"names":["Fragment","jsxs","jsx"],"mappings":";;;;;;;AASO,IAAM,QAAA,GAAW;AAAA,EACtB,IAAA,EAAM,SAAA;AAAA;AAAA,EAEN,IAAA,EAAM,EAAA;AAAA;AAAA,EAEN,UAAA,EAAY,CAAA;AAAA;AAAA,EAEZ,UAAA,EAAY,IAAA;AAAA;AAAA,EAEZ,cAAc,CAAC,EAAA,EAAI,IAAI,EAAA,EAAI,EAAA,EAAI,IAAI,EAAE,CAAA;AAAA,EACrC,kBAAA,EAAoB,EAAA;AAAA,EACpB,iBAAA,EAAmB,CAAA;AAAA,EACnB,WAAA,EAAa,EAAA;AAAA,EACb,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,EAAA;AAAA,EACV,QAAA,EAAU,SAAA;AAAA,EACV,SAAA,EAAW,SAAA;AAAA;AAAA,EAEX,WAAA,EAAa,EAAE,GAAA,EAAK,CAAA,EAAG,OAAO,GAAA,EAAK,MAAA,EAAQ,CAAA,EAAG,IAAA,EAAM,GAAA,EAAI;AAAA;AAAA,EAExD,UAAA,EAAY,SAAA;AAAA;AAAA,EAEZ,WAAA,EAAa,GAAA;AAAA;AAAA,EAEb,MAAA,EAAQ,EAAA;AAAA;AAAA,EAER,YAAA,EAAc,EAAA;AAAA,EACd,QAAA,EAAU;AACZ,CAAA;AAGO,IAAM,UAAA,GAA+C;AAAA,EAC1D,MAAA,EAAQ,CAAC,GAAA,EAAK,GAAG,CAAA;AAAA,EACjB,KAAA,EAAO,CAAC,GAAA,EAAK,IAAI,CAAA;AAAA,EACjB,EAAA,EAAI,CAAC,GAAA,EAAK,GAAG;AACf,CAAA;AAOO,SAAS,IAAI,KAAA,EAAoC;AACtD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,MAAA;AACtC,EAAA,MAAM,CAAA,GAAI,MAAM,IAAA,EAAK;AACrB,EAAA,IAAI,oBAAoB,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,EAAE,WAAA,EAAY;AACtD,EAAA,IAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,SAAU,CAAA,CAAA,EAAI,CAAA,CAAE,aAAa,CAAA,CAAA;AAC1D,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,IAAI,KAAA,EAAoC;AACtD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,MAAA;AACvE;AAGO,SAAS,SAAA,CAAU,IAAA,EAAe,OAAA,GAAU,YAAA,EAAkC;AACnF,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,KAAK,IAAA,EAAK,KAAM,IAAI,OAAO,MAAA;AAC3D,EAAA,MAAM,OAAO,IAAA,CAAK,OAAA,CAAQ,aAAA,EAAe,EAAE,EAAE,IAAA,EAAK;AAClD,EAAA,OAAO,CAAA,CAAA,EAAI,IAAI,CAAA,GAAA,EAAM,OAAO,CAAA,CAAA;AAC9B;AAEA,IAAM,SAAA,GAAoC,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAQ,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAS;AAG3G,SAAS,UAAU,MAAA,EAAgD;AACxE,EAAA,IAAI,CAAC,QAAQ,OAAO,MAAA;AACpB,EAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,EAAQ,OAAO,MAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,MAAA,CAAO,KAAK,KAAK,QAAA,CAAS,WAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,MAAA,CAAO,KAAA,IAAS,QAAQ,CAAA,IAAK,OAAA;AACrD,EAAA,OAAO,CAAA,EAAG,KAAK,CAAA,GAAA,EAAM,KAAK,IAAI,GAAA,CAAI,MAAA,CAAO,KAAK,CAAA,IAAK,SAAS,CAAA,CAAA;AAC9D;AAEO,SAAS,WAAW,OAAA,EAAgD;AACzE,EAAA,IAAI,CAAC,OAAA,EAAS,OAAO,EAAC;AACtB,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,MAAM,GAAA,GAAM,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA;AACrC,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,MAAM,CAAA;AACvC,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAA;AACnC,EAAA,IAAI,GAAA,MAAS,SAAA,GAAY,GAAA;AACzB,EAAA,IAAI,KAAA,MAAW,WAAA,GAAc,KAAA;AAC7B,EAAA,IAAI,MAAA,MAAY,YAAA,GAAe,MAAA;AAC/B,EAAA,IAAI,IAAA,MAAU,UAAA,GAAa,IAAA;AAC3B,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,OAAA,CAAQ,OAA6B,QAAA,EAAoC;AACvF,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAsB,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAC,CAAA,IAAK,GAAA,CAAI,QAAA,GAAW,CAAC,CAAC,CAAA;AACxE,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,MAAM,CAAA,GAAI,KAAK,KAAK,CAAA;AACpB,EAAA,MAAM,CAAA,GAAI,KAAK,OAAO,CAAA;AACtB,EAAA,MAAM,CAAA,GAAI,KAAK,QAAQ,CAAA;AACvB,EAAA,MAAM,CAAA,GAAI,KAAK,MAAM,CAAA;AACrB,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,UAAA,GAAa,GAAG,CAAC,CAAA,EAAA,CAAA;AAC1C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,YAAA,GAAe,GAAG,CAAC,CAAA,EAAA,CAAA;AAC5C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,aAAA,GAAgB,GAAG,CAAC,CAAA,EAAA,CAAA;AAC7C,EAAA,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,WAAA,GAAc,GAAG,CAAC,CAAA,EAAA,CAAA;AAC3C,EAAA,OAAO,GAAA;AACT;AAQO,IAAM,WAAA,GAAc,GAAA;AAEpB,SAAS,cAAc,KAAA,EAAuB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,WAAA,GAAc,GAAI,CAAA,GAAI,GAAA;AAClD;AAGO,SAAS,YAAA,CAAa,CAAA,EAAmB,QAAA,GAAgD,EAAC,EAAkB;AACjH,EAAA,MAAM,GAAA,GAAqB;AAAA,IACzB,SAAA,EAAW,GAAG,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA,IAAK,QAAA,CAAS,UAAU,CAAC,CAAA,EAAA,CAAA;AAAA,IACxD,YAAA,EAAc,GAAG,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA,IAAK,QAAA,CAAS,KAAA,IAAS,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,IAC3E,UAAA,EAAY,OAAO,aAAA,CAAc,GAAA,CAAI,EAAE,UAAU,CAAA,IAAK,QAAA,CAAS,UAAU,CAAC,CAAA;AAAA,IAC1E,GAAG,UAAA,CAAW,CAAA,CAAE,OAAO;AAAA,GACzB;AACA,EAAA,IAAI,CAAA,CAAE,KAAA,EAAO,GAAA,CAAI,SAAA,GAAY,CAAA,CAAE,KAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA;AAC/B,EAAA,IAAI,IAAA,KAAS,MAAA,EAAW,GAAA,CAAI,UAAA,GAAa,GAAG,IAAI,CAAA,EAAA,CAAA;AAChD,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,GAAA,CAAI,WAAA,GAAc,GAAG,KAAK,CAAA,EAAA,CAAA;AACnD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,OAAO,CAAA;AAC1B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,GAAA,CAAI,eAAA,GAAkB,IAAA;AAGtB,IAAA,GAAA,CAAI,OAAA,GAAU,SAAA;AAAA,EAChB;AACA,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,OAAO,GAAA,EAAyB;AAC9C,EAAA,MAAM,MAAqB,EAAC;AAC5B,EAAA,IAAI,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,UAAA,GAAa,GAAA;AAC/B,EAAA,IAAI,GAAA,CAAI,MAAA,EAAQ,GAAA,CAAI,SAAA,GAAY,QAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,CAAI,SAAA,GAAY,WAAA,GAAc,EAAA,EAAI,GAAA,CAAI,MAAA,GAAS,cAAA,GAAiB,EAAE,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AACjG,EAAA,IAAI,MAAM,MAAA,EAAQ,GAAA,CAAI,kBAAA,GAAqB,KAAA,CAAM,KAAK,GAAG,CAAA;AACzD,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,GAAA,CAAI,KAAK,CAAA;AAC3B,EAAA,IAAI,KAAA,MAAW,KAAA,GAAQ,KAAA;AACvB,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,GAAA,CAAI,SAAS,CAAA;AACnC,EAAA,IAAI,SAAA,MAAe,eAAA,GAAkB,SAAA;AACrC,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AACzB,EAAA,IAAI,SAAS,MAAA,IAAa,IAAA,GAAO,GAAG,GAAA,CAAI,QAAA,GAAW,GAAG,IAAI,CAAA,EAAA,CAAA;AAC1D,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAC/B,EAAA,IAAI,IAAA,MAAU,UAAA,GAAa,IAAA;AAC3B,EAAA,IAAI,GAAA,CAAI,SAAA,EAAW,GAAA,CAAI,WAAA,GAAc,YAAA;AACrC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,GAAA,CAAI,aAAa,CAAA;AACrC,EAAA,IAAI,OAAA,EAAS,GAAA,CAAI,aAAA,GAAgB,CAAA,EAAG,OAAO,CAAA,EAAA,CAAA;AAC3C,EAAA,OAAO,GAAA;AACT;ACtIO,SAAS,UAAU,EAAE,GAAA,EAAK,QAAQ,IAAA,EAAM,UAAA,EAAY,WAAU,EAAmB;AACtF,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,MAAM,CAAA,GAAI,GAAA,CAAI,MAAA,GAAS,EAAE,CAAA;AACrE,EAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AAEpB,EAAA,MAAM,MAAA,mBACJ,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,wBAAA,EAAuB,EAAA;AAAA,MACvB,SAAA;AAAA,MACA,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,aAAA,EAAe,QAAA,EAAU,UAAA,EAAY,QAAA,EAAU,GAAA,EAAK,EAAA,EAAI,OAAA,EAAS,EAAA,EAAI,YAAY,SAAA,EAAU;AAAA,MAEpH,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,MAAA,EAAQ,sBAClB,GAAA,CAAC,SAAA,EAAA,EAAkB,GAAA,EAAU,MAAA,EAAgB,MAAA,EAAQ,CAAA,GAAI,CAAA,EAAG,KAAA,EAAA,EAA5C,CAA0D,CAC3E;AAAA;AAAA,GACH;AAGF,EAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAInB,EAAA,uBACE,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,QAAA;AAAA,MACR,QAAA,EAAQ,IAAA;AAAA,MACR,IAAA,EAAI,IAAA;AAAA,MACJ,KAAA,EAAO,aAAa,GAAG,CAAA;AAAA,MACvB,KAAA,EAAM,KAAA;AAAA,MACN,aAAA,EAAc,eAAA;AAAA,MACb,GAAG,UAAA;AAAA,MAEH,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ;AAGA,SAAS,aAAa,GAAA,EAAkB;AACtC,EAAA,MAAM,IAAA,GAAO,GAAA,EAAK,IAAA,IAAQ,EAAC;AAC3B,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,WAAW,IAAA,CAAK,IAAA,IAAQ,QAAQ,CAAA,IAAK,UAAA,CAAW,MAAA;AAC/D,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,KAAgB,WAAA,GAAc,CAAA,GAAI,CAAA;AACrD,EAAA,OAAO,IAAA,CAAK,IAAA,CAAM,KAAA,GAAQ,CAAA,GAAK,CAAC,CAAA,GAAI,EAAA;AACtC;AAGA,SAAS,WAAW,MAAA,EAA4B;AAC9C,EAAA,MAAM,KAAA,GAAmB,CAAC,EAAE,CAAA;AAC5B,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,OAAO,IAAA,KAAS,WAAA,EAAa,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAAA,eACnC,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,CAAE,KAAK,KAAK,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,UAAU,EAAE,GAAA,EAAK,MAAA,EAAQ,MAAA,EAAQ,OAAM,EAAiE;AAC/G,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,IAAA,IAAQ,EAAC;AAC1B,EAAA,MAAM,CAAC,GAAG,CAAC,CAAA,GAAI,WAAW,IAAA,CAAK,IAAA,IAAQ,QAAQ,CAAA,IAAK,UAAA,CAAW,MAAA;AAC/D,EAAA,MAAM,CAAC,KAAA,EAAO,MAAM,CAAA,GAAI,IAAA,CAAK,WAAA,KAAgB,WAAA,GAAc,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,CAAC,GAAG,CAAC,CAAA;AACzE,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,IAAW,EAAC;AAC3B,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAA8C,GAAA,CAAI,EAAE,IAAI,CAAC,KAAK,QAAA,CAAS,MAAA;AAEvF,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,QAAA,EAAU,UAAA;AAAA,IACV,KAAA,EAAO,GAAG,KAAK,CAAA,EAAA,CAAA;AAAA,IACf,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,CAAA;AAAA,IACpB,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA,EAAO,SAAA;AAAA,IACP,SAAA,EAAW,4BAAA;AAAA,IACX,UAAA,EAAY,SAAA,CAAU,GAAA,CAAI,WAAA,IAAe,SAAS,IAAI,CAAA;AAAA,IACtD,UAAU,CAAA,EAAG,GAAA,CAAI,IAAI,WAAW,CAAA,IAAK,SAAS,IAAI,CAAA,EAAA,CAAA;AAAA,IAClD,UAAA,EAAY;AAAA,GACd;AAEA,EAAA,uBACE,IAAA,CAAC,SAAA,EAAA,EAAQ,sBAAA,EAAsB,MAAA,EAAQ,KAAA,EACpC,QAAA,EAAA;AAAA,IAAA,IAAA,CAAK,0BAAU,GAAA,CAAC,QAAA,EAAA,EAAS,IAAA,EAAM,IAAA,CAAK,QAAQ,IAAA,EAAK,QAAA,EAAS,MAAA,EAAgB,KAAA,EAAc,MAAM,MAAA,CAAO,MAAM,GAAG,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA,EAAG,CAAA;AAAA,oBACvI,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,sBAAA,EAAqB,EAAA;AAAA,QACrB,KAAA,EAAO,EAAE,UAAA,EAAY,CAAA,EAAG,MAAA,CAAO,KAAK,CAAC,CAAA,EAAA,CAAA,EAAM,YAAA,EAAc,CAAA,EAAG,MAAA,CAAO,OAAO,CAAC,CAAA,EAAA,CAAA,EAAM,aAAA,EAAe,CAAA,EAAG,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAA,CAAA,EAAM,WAAA,EAAa,CAAA,EAAG,MAAA,CAAO,MAAM,CAAC,CAAA,EAAA,CAAA,EAAK;AAAA,QAE3J,QAAA,EAAA,MAAA,CAAO,IAAI,CAAC,KAAA,EAAO,sBAClB,GAAA,CAAC,SAAA,EAAA,EAAkB,KAAA,EAAA,EAAH,CAAiB,CAClC;AAAA;AAAA,KACH;AAAA,IACC,KAAK,MAAA,oBAAU,GAAA,CAAC,YAAS,IAAA,EAAM,IAAA,CAAK,QAAQ,IAAA,EAAK,QAAA,EAAS,MAAA,EAAgB,KAAA,EAAc,MAAM,MAAA,CAAO,MAAM,GAAG,KAAA,EAAO,MAAA,CAAO,OAAO,CAAA,EAAG;AAAA,GAAA,EACzI,CAAA;AAEJ;AAGA,SAAS,QAAA,CAAS,EAAE,IAAA,EAAM,IAAA,EAAM,QAAQ,KAAA,EAAO,IAAA,EAAM,OAAM,EAA0G;AACnK,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAgB,OAAO,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,CAAM,QAAQ,CAAA,CAAE,IAAA,CAAK,OAAO,MAAM,CAAC,EAAE,KAAA,CAAM,SAAS,EAAE,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA,GAAI,EAAA;AACpI,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,QAAA,EAAU,UAAA;AAAA,IACV,IAAA,EAAM,GAAG,IAAI,CAAA,EAAA,CAAA;AAAA,IACb,KAAA,EAAO,GAAG,KAAK,CAAA,EAAA,CAAA;AAAA,IACf,CAAC,SAAS,QAAA,GAAW,KAAA,GAAQ,QAAQ,GAAG,CAAA,EAAG,SAAS,YAAY,CAAA,EAAA,CAAA;AAAA,IAChE,OAAA,EAAS,MAAA;AAAA,IACT,mBAAA,EAAqB,cAAA;AAAA,IACrB,UAAU,CAAA,EAAG,GAAA,CAAI,KAAK,IAAI,CAAA,IAAK,SAAS,QAAQ,CAAA,EAAA,CAAA;AAAA,IAChD,KAAA,EAAO,GAAA,CAAI,IAAA,CAAK,KAAK,CAAA,IAAK;AAAA,GAC5B;AACA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,sBAAA,EAAsB,IAAA,EAAM,KAAA,EAC/B,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,OAAO,EAAE,SAAA,EAAW,QAAO,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAE,CAAA;AAAA,oBACrD,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,SAAA,EAAW,UAAS,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA,EAAE,CAAA;AAAA,oBACzD,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,SAAA,EAAW,SAAQ,EAAI,QAAA,EAAA,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAE;AAAA,GAAA,EACzD,CAAA;AAEJ;AAEA,SAAS,IAAA,CAAK,EAAE,IAAA,EAAK,EAAgC;AACnD,EAAA,2BACEA,QAAAA,EAAA,EACI,iBAAM,OAAA,CAAQ,IAAI,IAAI,IAAA,GAAO,IAAI,GAAA,CAAI,CAAC,KAAK,CAAA,qBAC3C,GAAA,CAAC,WAAgB,GAAA,EAAA,EAAH,CAAa,CAC5B,CAAA,EACH,CAAA;AAEJ;AAEA,SAAS,OAAA,CAAQ,EAAE,GAAA,EAAI,EAAiB;AACtC,EAAA,MAAM,OAAO,OAAO,GAAA,EAAK,IAAA,KAAS,QAAA,GAAW,IAAI,IAAA,GAAO,EAAA;AACxD,EAAA,MAAM,KAAA,GAAQ,OAAO,GAAG,CAAA;AACxB,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,CAAI,IAAA,KAAS,WAAW,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,GAAI,MAAA;AAErE,EAAA,IAAI,IAAI,IAAA,EAAM;AACZ,IAAA,KAAA,CAAM,aAAa,KAAA,CAAM,UAAA,IAAc,SAAA,CAAU,QAAA,CAAS,UAAU,WAAW,CAAA;AAC/E,IAAA,KAAA,CAAM,eAAA,GAAkB,KAAA,CAAM,eAAA,IAAmB,QAAA,CAAS,QAAA;AAAA,EAC5D;AACA,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,IAAS,QAAA,CAAS,SAAA;AACtC,IAAA,KAAA,CAAM,kBAAA,GAAqB,MAAM,kBAAA,IAAsB,WAAA;AAAA,EACzD;AAGA,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,UAAU,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,0BAC9BA,UAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,CAAA,GAAI,CAAA,wBAAM,IAAA,EAAA,EAAG,CAAA;AAAA,IACb;AAAA,GAAA,EAAA,EAFY,CAGf,CACD,CAAA;AACD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,IAAA,mBACf,GAAA,CAAC,UAAK,qBAAA,EAAoB,EAAA,EAAG,KAAA,EAAe,QAAA,EAAA,OAAA,EAAQ,oBAEpD,GAAA,CAAC,MAAA,EAAA,EAAK,qBAAA,EAAoB,EAAA,EAAG,OAAe,QAAA,EAAA,OAAA,EAAQ,CAAA;AAGtD,EAAA,OAAO,IAAA,uBACJ,GAAA,EAAA,EAAE,IAAA,EAAY,QAAO,QAAA,EAAS,GAAA,EAAI,8BAAA,EAChC,QAAA,EAAA,IAAA,EACH,CAAA,GAEA,IAAA;AAEJ;AAEA,SAAS,SAAA,CAAU,EAAE,KAAA,EAAM,EAAgC;AACzD,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,SAAA,EAAW;AACd,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,IAAI,KAAA,CAAM,KAAK,CAAA,IAAK,CAAC,CAAC,CAAC,CAAA;AACxE,MAAA,MAAM,GAAA,GAAM,IAAI,KAAK,CAAA,CAAA;AACrB,MAAA,MAAM,KAAA,GAAuB;AAAA,QAC3B,GAAG,YAAA,CAAa,KAAA,EAAO,EAAE,MAAA,EAAQ,SAAS,kBAAA,EAAoB,KAAA,EAAO,QAAA,CAAS,iBAAA,EAAmB,CAAA;AAAA,QACjG,UAAU,CAAA,EAAG,QAAA,CAAS,YAAA,CAAa,KAAA,GAAQ,CAAC,CAAC,CAAA,EAAA,CAAA;AAAA,QAC7C,UAAA,EAAY;AAAA,OACd;AACA,MAAA,uBACE,GAAA,CAAC,GAAA,EAAA,EAAI,uBAAA,EAAsB,SAAA,EAAU,KAAA,EACnC,8BAAC,IAAA,EAAA,EAAK,IAAA,EAAM,KAAA,CAAM,IAAA,EAAM,CAAA,EAC1B,CAAA;AAAA,IAEJ;AAAA,IACA,KAAK,WAAA;AACH,MAAA,uBACE,GAAA,CAAC,GAAA,EAAA,EAAE,uBAAA,EAAsB,WAAA,EAAY,KAAA,EAAO,YAAA,CAAa,KAAK,CAAA,EAC5D,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA,EAC1B,CAAA;AAAA,IAEJ,KAAK,MAAA;AACH,MAAA,uBAAO,GAAA,CAAC,YAAS,KAAA,EAAO,KAAA,CAAM,OAAO,OAAA,EAAS,CAAC,CAAC,KAAA,CAAM,OAAA,EAAS,CAAA;AAAA,IACjE,KAAK,OAAA;AACH,MAAA,uBAAO,GAAA,CAAC,aAAU,KAAA,EAAc,CAAA;AAAA,IAClC,KAAK,MAAA;AACH,MAAA,uBACE,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,MAAA;AAAA,UACtB,KAAA,EAAO;AAAA,YACL,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,YAClC,OAAA,EAAS,SAAA;AAAA,YACT,YAAY,QAAA,CAAS,QAAA;AAAA,YACrB,UAAA,EAAY,SAAA,CAAU,QAAA,CAAS,QAAA,EAAU,WAAW,CAAA;AAAA,YACpD,QAAA,EAAU,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAA,CAAA;AAAA,YAC9B,UAAA,EAAY,CAAA;AAAA,YACZ,UAAA,EAAY;AAAA,WACd;AAAA,UAEA,QAAA,kBAAA,GAAA,CAAC,UAAM,QAAA,EAAA,OAAO,KAAA,CAAM,SAAS,QAAA,GAAW,KAAA,CAAM,OAAO,EAAA,EAAG;AAAA;AAAA,OAC1D;AAAA,IAEJ,KAAK,OAAA;AACH,MAAA,uBACE,GAAA;AAAA,QAAC,YAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,OAAA;AAAA,UACtB,KAAA,EAAO,EAAE,MAAA,EAAQ,CAAA,MAAA,EAAS,QAAA,CAAS,WAAW,CAAA,EAAA,CAAA,EAAM,SAAA,EAAW,QAAA,EAAU,KAAA,EAAO,QAAA,CAAS,UAAA,EAAW;AAAA,UAElG,iBAAM,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA,GAAI,KAAA,CAAM,SAAS,EAAC,EAAG,GAAA,CAAI,CAAC,GAAG,CAAA,qBACzD,GAAA,CAAC,aAAkB,KAAA,EAAO,CAAA,EAAA,EAAV,CAAa,CAC9B;AAAA;AAAA,OACH;AAAA,IAEJ,KAAK,OAAA,EAAS;AAGZ,MAAA,MAAM,GAAA,GAAM,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,IAAY,0DAAA,CAA2D,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,KAAA,CAAM,GAAA,GAAM,IAAA;AACtI,MAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,MAAA,uBACE,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,OAAA;AAAA,UACtB,GAAA;AAAA,UACA,KAAK,OAAO,KAAA,CAAM,GAAA,KAAQ,QAAA,GAAW,MAAM,GAAA,GAAM,EAAA;AAAA,UACjD,KAAA,EAAO,EAAE,OAAA,EAAS,OAAA,EAAS,UAAU,MAAA,EAAQ,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG,QAAQ,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA,GAAI,MAAA,GAAY,QAAQ,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAK;AAAA,OACnK;AAAA,IAEJ;AAAA,IACA,KAAK,IAAA;AACH,MAAA,uBAAO,GAAA,CAAC,IAAA,EAAA,EAAG,uBAAA,EAAsB,IAAA,EAAK,OAAO,EAAE,MAAA,EAAQ,CAAA,EAAG,SAAA,EAAW,uBAAuB,MAAA,EAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,MAAK,EAAG,CAAA;AAAA,IACxI;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAEA,SAAS,QAAA,CAAS,EAAE,KAAA,EAAO,OAAA,EAAQ,EAAwD;AACzF,EAAA,MAAM,GAAA,GAAM,UAAU,IAAA,GAAO,IAAA;AAC7B,EAAA,2BACG,GAAA,EAAA,EAAI,uBAAA,EAAsB,QAAO,KAAA,EAAO,EAAE,QAAQ,CAAA,IAAA,EAAO,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA,EAAM,aAAa,MAAA,EAAQ,aAAA,EAAe,UAAU,SAAA,GAAY,MAAA,IACzI,QAAA,EAAA,CAAA,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,QAAQ,EAAC,EAAG,IAAI,CAAC,IAAA,EAAM,sBAC9C,IAAA,CAAC,IAAA,EAAA,EAAW,OAAO,EAAE,GAAG,aAAa,IAAA,EAAM,EAAE,OAAO,CAAA,EAAG,GAAE,EACvD,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IACtB,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,QAAQ,KAAK,IAAA,CAAK,QAAA,CAAS,MAAA,GAAS,CAAA,oBAAK,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAO,IAAA,CAAK,UAAU,OAAA,EAAkB;AAAA,GAAA,EAAA,EAFxG,CAGT,CACD,CAAA,EACH,CAAA;AAEJ;AAIA,SAAS,SAAA,CAAU,EAAE,KAAA,EAAM,EAA0B;AACnD,EAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAM,OAAO,EAAC;AACvD,EAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,MAAM,MAAM,CAAA,GAAI,MAAM,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,IAAI,CAAC,CAAA,KAAM,UAAa,CAAA,GAAI,CAAC,IAAI,EAAC;AAC1G,EAAA,MAAM,GAAA,GAAM,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,KAAK,CAAA;AAC7B,EAAA,MAAM,OAAA,GAAwB,KAAA,CAAM,OAAA,IAAW,EAAC;AAEhD,EAAA,MAAM,KAAA,GAAuB;AAAA,IAC3B,cAAA,EAAgB,UAAA;AAAA,IAChB,KAAA,EAAO,KAAA,KAAU,MAAA,GAAY,CAAA,EAAG,KAAK,CAAA,CAAA,CAAA,GAAM,MAAA;AAAA,IAC3C,WAAA,EAAa,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,OAAA,GAAU,MAAA;AAAA,IAC3C,YAAA,EAAc,CAAA,EAAG,QAAA,CAAS,UAAU,CAAA,EAAA,CAAA;AAAA,IACpC,GAAI,KAAA,CAAM,KAAA,KAAU,QAAA,GAAW,EAAE,YAAY,MAAA,EAAQ,WAAA,EAAa,MAAA,EAAO,GAAI,MAAM,KAAA,KAAU,OAAA,GAAU,EAAE,UAAA,EAAY,MAAA,KAAW;AAAC,GACnI;AAGA,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAwC,SAAA,CAAU,CAAC,CAAA,IAAK,CAAA,EAAG,SAAS,WAAW,CAAA,gBAAA,CAAA;AAE7F,EAAA,uBACE,IAAA,CAAC,OAAA,EAAA,EAAM,uBAAA,EAAsB,OAAA,EAAQ,KAAA,EAClC,QAAA,EAAA;AAAA,IAAA,MAAA,CAAO,MAAA,GAAS,CAAA,oBACf,GAAA,CAAC,UAAA,EAAA,EACE,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,qBACd,GAAA,CAAC,KAAA,EAAA,EAAY,KAAA,EAAO,EAAE,OAAO,CAAA,EAAG,IAAA,CAAK,KAAA,CAAO,CAAA,GAAI,GAAA,GAAO,GAAI,CAAA,GAAI,EAAE,CAAA,CAAA,CAAA,EAAI,EAAA,EAA3D,CAA8D,CACzE,CAAA,EACH,CAAA;AAAA,oBAEF,GAAA,CAAC,WACE,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,EAAK,CAAA,qBACd,GAAA,CAAC,IAAA,EAAA,EACG,QAAA,EAAA,CAAA,KAAA,CAAM,OAAA,CAAQ,IAAI,KAAK,CAAA,GAAI,IAAI,KAAA,GAAQ,IAAI,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,EAAG,KAAA,KAAU;AACnE,MAAA,MAAM,MAAA,GAAS,CAAC,CAAC,GAAA,CAAI,MAAA;AACrB,MAAA,MAAM,GAAA,GAAM,SAAS,IAAA,GAAO,IAAA;AAC5B,MAAA,MAAM,QAAQ,CAAA,KAAM,CAAA;AACpB,MAAA,MAAM,IAAA,GAAO,CAAA,KAAM,KAAA,CAAM,MAAA,GAAS,CAAA;AAClC,MAAA,MAAM,SAAA,GAA2B;AAAA;AAAA,QAE/B,SAAA,EAAW,MAAM,CAAA,GAAI,IAAA,CAAK,QAAQ,GAAG,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,QAC7D,YAAA,EAAc,CAAA,KAAM,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,QACjF,UAAA,EAAY,QAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,QAC7D,WAAA,EAAa,OAAO,IAAA,CAAK,OAAA,CAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,QAC9D,GAAG,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAS,EAAE,GAAG,QAAA,CAAS,WAAA,EAAa,GAAI,KAAA,CAAM,WAAA,IAAe,IAAK,CAAA;AAAA,QAClF,GAAG,UAAA,CAAW,IAAA,CAAK,OAAO,CAAA;AAAA,QAC1B,eAAe,IAAA,CAAK,MAAA,KAAW,QAAA,GAAW,QAAA,GAAW,KAAK,MAAA,IAAU,KAAA;AAAA,QACpE,SAAA,EAAW,MAAA;AAAA,QACX,UAAA,EAAY,SAAS,GAAA,GAAM,MAAA;AAAA,QAC3B,iBAAiB,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,KAAM,MAAA,GAAS,SAAS,UAAA,GAAa,MAAA;AAAA,OACxE;AACA,MAAA,uBACE,GAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UAEC,KAAA,EAAO,SAAA;AAAA,UACP,OAAA,EAAS,IAAI,IAAA,CAAK,OAAO,KAAK,IAAA,CAAK,OAAA,GAAW,CAAA,GAAI,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,UACjE,OAAA,EAAS,IAAI,IAAA,CAAK,OAAO,KAAK,IAAA,CAAK,OAAA,GAAW,CAAA,GAAI,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,UAEjE,QAAA,kBAAA,GAAA,CAAC,cAAW,IAAA,EAAY;AAAA,SAAA;AAAA,QALnB;AAAA,OAMP;AAAA,IAEJ,CAAC,CAAA,EAAA,EA7BM,CA8BT,CACD,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAGA,SAAS,UAAA,CAAW,EAAE,IAAA,EAAK,EAAwB;AACjD,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA,CAAK,SAAS,EAAC;AAC3D,EAAA,uBACE,GAAA,CAAAA,QAAAA,EAAA,EACG,QAAA,EAAA,MAAA,CAAO,GAAA;AAAA,IAAI,CAAC,GAAG,CAAA,KACd,CAAA,EAAG,SAAS,WAAA,mBACV,GAAA,CAAC,OAAU,uBAAA,EAAsB,WAAA,EAAY,OAAO,EAAE,GAAG,aAAa,CAAC,CAAA,EAAG,GAAI,CAAA,KAAM,MAAA,CAAO,SAAS,CAAA,IAAK,GAAA,CAAI,EAAE,UAAU,CAAA,KAAM,SAAY,EAAE,YAAA,EAAc,GAAE,GAAI,IAAI,EACnK,QAAA,kBAAA,GAAA,CAAC,QAAK,IAAA,EAAM,CAAA,CAAE,MAAM,CAAA,EAAA,EADd,CAER,oBAEA,GAAA,CAAC,SAAA,EAAA,EAAkB,KAAA,EAAO,CAAA,EAAA,EAAV,CAAa;AAAA,GAEjC,EACF,CAAA;AAEJ;ACnVO,SAAS,WAAA,CAAY,EAAE,QAAA,EAAU,QAAA,EAAU,YAAW,EAAqB;AAChF,EAAA,MAAM,GAAA,GAAM,QAAA;AAGZ,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,EAAG;AACjE,IAAA,uBAAOC,IAAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAM,KAAA,EAAO,QAAA,EAAA;AAAA,MAAA,QAAA,IAAY,WAAA;AAAA,MAAY;AAAA,KAAA,EAA4B,CAAA;AAAA,EACnF;AAEA,EAAA,uBAAOC,GAAAA,CAAC,SAAA,EAAA,EAAU,GAAA,EAAiB,UAAA,EAAwB,CAAA;AAC7D","file":"index.js","sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { Border, BoxBorders, BoxSides, ParagraphProps, Run } from \"./types\";\n\n/**\n * What last-word's writer gives a document that asks for nothing, so an\n * unstyled document looks here as it does in Word. Read off the writer's\n * styles part (docDefaults, Heading1-6, Quote, CodeBlock, Hyperlink) rather\n * than guessed.\n */\nexport const DEFAULTS = {\n font: \"Calibri\",\n /** Points. */\n size: 11,\n /** w:spacing after=\"160\": 8pt after every paragraph. */\n spaceAfter: 8,\n /** w:line=\"259\" of 240: 1.08. */\n lineHeight: 1.08,\n /** Heading 1-6, points. */\n headingSizes: [20, 16, 14, 13, 12, 11],\n headingSpaceBefore: 12,\n headingSpaceAfter: 6,\n quoteIndent: 36,\n quoteColor: \"#595959\",\n codeFont: \"Consolas\",\n codeSize: 10,\n codeFill: \"#F2F2F2\",\n linkColor: \"#0563C1\",\n /** Cell margins 60 / 108 twips. */\n cellPadding: { top: 3, right: 5.4, bottom: 3, left: 5.4 },\n /** The header-cell grey the writer and all three engines share. */\n headerFill: \"#E7E7E7\",\n /** A border edge that names no width. */\n borderWidth: 0.5,\n /** Page margins when none are given: 1440 twips. */\n margin: 72,\n /** Header / footer distance from the page edge: 720 twips. */\n bandDistance: 36,\n bandSize: 9,\n};\n\n/** Page sizes in points, portrait. */\nexport const PAGE_SIZES: Record<string, [number, number]> = {\n letter: [612, 792],\n legal: [612, 1008],\n a4: [595, 842],\n};\n\n/**\n * `#RRGGBB` (or `RRGGBB`), or nothing. A colour comes out of a file, so anything\n * else -- a CSS expression smuggled into a colour field -- is dropped rather\n * than handed to a style.\n */\nexport function hex(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const v = value.trim();\n if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toUpperCase();\n if (/^[0-9a-fA-F]{6}$/.test(v)) return `#${v.toUpperCase()}`;\n return undefined;\n}\n\n/** A finite number, or nothing. */\nexport function num(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/** A font name as a CSS family list: quoted, with a generic fallback. */\nexport function fontStack(font: unknown, generic = \"sans-serif\"): string | undefined {\n if (typeof font !== \"string\" || font.trim() === \"\") return undefined;\n const name = font.replace(/[\"\\\\;{}<>]/g, \"\").trim();\n return `\"${name}\", ${generic}`;\n}\n\nconst CSS_STYLE: Record<string, string> = { single: \"solid\", double: \"double\", dashed: \"dashed\", dotted: \"dotted\" };\n\n/** One border edge as CSS. `style: \"none\"` removes the edge. */\nexport function borderCss(border: Border | undefined): string | undefined {\n if (!border) return undefined;\n if (border.style === \"none\") return \"none\";\n const width = num(border.width) ?? DEFAULTS.borderWidth;\n const style = CSS_STYLE[border.style ?? \"single\"] ?? \"solid\";\n return `${width}pt ${style} ${hex(border.color) ?? \"#000000\"}`;\n}\n\nexport function boxBorders(borders: BoxBorders | undefined): CSSProperties {\n if (!borders) return {};\n const out: CSSProperties = {};\n const top = borderCss(borders.top);\n const right = borderCss(borders.right);\n const bottom = borderCss(borders.bottom);\n const left = borderCss(borders.left);\n if (top) out.borderTop = top;\n if (right) out.borderRight = right;\n if (bottom) out.borderBottom = bottom;\n if (left) out.borderLeft = left;\n return out;\n}\n\nexport function padding(sides: BoxSides | undefined, fallback?: BoxSides): CSSProperties {\n const pick = (k: keyof BoxSides) => num(sides?.[k]) ?? num(fallback?.[k]);\n const out: CSSProperties = {};\n const t = pick(\"top\");\n const r = pick(\"right\");\n const b = pick(\"bottom\");\n const l = pick(\"left\");\n if (t !== undefined) out.paddingTop = `${t}pt`;\n if (r !== undefined) out.paddingRight = `${r}pt`;\n if (b !== undefined) out.paddingBottom = `${b}pt`;\n if (l !== undefined) out.paddingLeft = `${l}pt`;\n return out;\n}\n\n/**\n * Word's \"single\" line is the font's own line height -- ascent + descent + gap,\n * about 1.2 em for Calibri and most text faces -- where CSS's 1.0 is the bare\n * em. So a document's 1.08 lines is 1.08 x 1.2 here; set as 1.08 it drew every\n * paragraph visibly tighter than LibreOffice's render of the same file.\n */\nexport const SINGLE_LINE = 1.2;\n\nexport function lineHeightCss(lines: number): number {\n return Math.round(lines * SINGLE_LINE * 1000) / 1000;\n}\n\n/** A paragraph-shaped block's box: alignment, spacing, indents, shading, borders. */\nexport function paragraphCss(p: ParagraphProps, defaults: { before?: number; after?: number } = {}): CSSProperties {\n const out: CSSProperties = {\n marginTop: `${num(p.spaceBefore) ?? defaults.before ?? 0}pt`,\n marginBottom: `${num(p.spaceAfter) ?? defaults.after ?? DEFAULTS.spaceAfter}pt`,\n lineHeight: String(lineHeightCss(num(p.lineHeight) ?? DEFAULTS.lineHeight)),\n ...boxBorders(p.borders),\n };\n if (p.align) out.textAlign = p.align;\n const left = num(p.indentLeft);\n const right = num(p.indentRight);\n if (left !== undefined) out.marginLeft = `${left}pt`;\n if (right !== undefined) out.marginRight = `${right}pt`;\n const fill = hex(p.shading);\n if (fill) {\n out.backgroundColor = fill;\n // Word draws the shading behind the text with a little room; without it\n // the fill hugs the glyphs and a callout reads as a highlight.\n out.padding = \"2pt 4pt\";\n }\n return out;\n}\n\n/** A run's character style. */\nexport function runCss(run: Run): CSSProperties {\n const out: CSSProperties = {};\n if (run.bold) out.fontWeight = 700;\n if (run.italic) out.fontStyle = \"italic\";\n const lines = [run.underline ? \"underline\" : \"\", run.strike ? \"line-through\" : \"\"].filter(Boolean);\n if (lines.length) out.textDecorationLine = lines.join(\" \");\n const color = hex(run.color);\n if (color) out.color = color;\n const highlight = hex(run.highlight);\n if (highlight) out.backgroundColor = highlight;\n const size = num(run.size);\n if (size !== undefined && size > 0) out.fontSize = `${size}pt`;\n const font = fontStack(run.font);\n if (font) out.fontFamily = font;\n if (run.smallCaps) out.fontVariant = \"small-caps\";\n const spacing = num(run.letterSpacing);\n if (spacing) out.letterSpacing = `${spacing}pt`;\n return out;\n}\n","import { Fragment, type CSSProperties, type ReactNode } from \"react\";\nimport { FauxClient, sanitizeHref, type FauxClientProps } from \"@particle-academy/react-fancy\";\nimport type { Band, Block, Doc, ListItem, Run, TableBorders, TableCell } from \"./types\";\nimport { borderCss, boxBorders, DEFAULTS, fontStack, hex, num, padding, PAGE_SIZES, paragraphCss, runCss } from \"./style\";\n\nexport interface DocViewerProps {\n /** A last-word document, as its readers return it. */\n doc: Doc;\n /**\n * Show the document in react-fancy's `FauxClient` frame: zoom and full screen,\n * revealed on hover, like every file viewer. Default `true`.\n */\n frame?: boolean;\n /** Passed to the frame: `variant`, a controlled `zoom`, `onFullscreenChange`, … */\n frameProps?: Partial<FauxClientProps>;\n className?: string;\n}\n\n/**\n * A last-word document rendered the way Word renders it: page size, margins and\n * running header / footer per page; runs, paragraphs, lists, tables (fills,\n * borders, spans, alignment), code, quotes and images.\n *\n * Measurements stay in points, the model's own unit, so a 0.5pt rule is a 0.5pt\n * rule and the page is the page's size; the frame scales the whole sheet to fit\n * and zooms it.\n *\n * Everything shown came out of a file, so links go through react-fancy's\n * `sanitizeHref`, images are `data:` rasters only (a remote URL in a document\n * is a tracking pixel waiting to fire), and colours must be `#RRGGBB`.\n */\nexport function DocViewer({ doc, frame = true, frameProps, className }: DocViewerProps) {\n const pages = splitPages(Array.isArray(doc?.blocks) ? doc.blocks : []);\n const total = pages.length;\n\n const sheets = (\n <div\n data-fancy-docs-viewer=\"\"\n className={className}\n style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 16, padding: 16, background: \"#E5E7EB\" }}\n >\n {pages.map((blocks, i) => (\n <PageSheet key={i} doc={doc} blocks={blocks} number={i + 1} total={total} />\n ))}\n </div>\n );\n\n if (!frame) return sheets;\n\n // The frame lays the sheets out at their real width and scales them to fit,\n // as a word processor opens at \"fit page width\"; zoom works on top of that.\n return (\n <FauxClient\n variant=\"device\"\n controls\n fill\n width={logicalWidth(doc)}\n scale=\"fit\"\n bodyClassName=\"overflow-auto\"\n {...frameProps}\n >\n {sheets}\n </FauxClient>\n );\n}\n\n/** The sheets' width in CSS pixels, gutter included: the frame's logical width. */\nfunction logicalWidth(doc: Doc): number {\n const page = doc?.page ?? {};\n const [w, h] = PAGE_SIZES[page.size ?? \"letter\"] ?? PAGE_SIZES.letter;\n const width = page.orientation === \"landscape\" ? h : w;\n return Math.ceil((width * 4) / 3) + 32;\n}\n\n/** Page breaks split the flow into sheets; an empty document is one blank sheet. */\nfunction splitPages(blocks: Block[]): Block[][] {\n const pages: Block[][] = [[]];\n for (const block of blocks) {\n if (block?.type === \"pageBreak\") pages.push([]);\n else pages[pages.length - 1].push(block);\n }\n return pages;\n}\n\nfunction PageSheet({ doc, blocks, number, total }: { doc: Doc; blocks: Block[]; number: number; total: number }) {\n const page = doc.page ?? {};\n const [w, h] = PAGE_SIZES[page.size ?? \"letter\"] ?? PAGE_SIZES.letter;\n const [width, height] = page.orientation === \"landscape\" ? [h, w] : [w, h];\n const m = page.margins ?? {};\n const margin = (side: \"top\" | \"right\" | \"bottom\" | \"left\") => num(m[side]) ?? DEFAULTS.margin;\n\n const style: CSSProperties = {\n position: \"relative\",\n width: `${width}pt`,\n minHeight: `${height}pt`,\n background: \"#FFFFFF\",\n color: \"#000000\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.15)\",\n fontFamily: fontStack(doc.defaultFont ?? DEFAULTS.font),\n fontSize: `${num(doc.defaultSize) ?? DEFAULTS.size}pt`,\n flexShrink: 0,\n };\n\n return (\n <section data-fancy-docs-page={number} style={style}>\n {page.header && <BandLine band={page.header} kind=\"header\" number={number} total={total} left={margin(\"left\")} right={margin(\"right\")} />}\n <div\n data-fancy-docs-body=\"\"\n style={{ paddingTop: `${margin(\"top\")}pt`, paddingRight: `${margin(\"right\")}pt`, paddingBottom: `${margin(\"bottom\")}pt`, paddingLeft: `${margin(\"left\")}pt` }}\n >\n {blocks.map((block, i) => (\n <BlockView key={i} block={block} />\n ))}\n </div>\n {page.footer && <BandLine band={page.footer} kind=\"footer\" number={number} total={total} left={margin(\"left\")} right={margin(\"right\")} />}\n </section>\n );\n}\n\n/** A running header or footer: left / center / right, with `{page}` and `{pages}` filled in. */\nfunction BandLine({ band, kind, number, total, left, right }: { band: Band; kind: \"header\" | \"footer\"; number: number; total: number; left: number; right: number }) {\n const fill = (s: unknown) => (typeof s === \"string\" ? s.split(\"{page}\").join(String(number)).split(\"{pages}\").join(String(total)) : \"\");\n const style: CSSProperties = {\n position: \"absolute\",\n left: `${left}pt`,\n right: `${right}pt`,\n [kind === \"header\" ? \"top\" : \"bottom\"]: `${DEFAULTS.bandDistance}pt`,\n display: \"grid\",\n gridTemplateColumns: \"1fr auto 1fr\",\n fontSize: `${num(band.size) ?? DEFAULTS.bandSize}pt`,\n color: hex(band.color) ?? \"#595959\",\n };\n return (\n <div data-fancy-docs-band={kind} style={style}>\n <span style={{ textAlign: \"left\" }}>{fill(band.left)}</span>\n <span style={{ textAlign: \"center\" }}>{fill(band.center)}</span>\n <span style={{ textAlign: \"right\" }}>{fill(band.right)}</span>\n </div>\n );\n}\n\nfunction Runs({ runs }: { runs: Run[] | undefined }) {\n return (\n <>\n {(Array.isArray(runs) ? runs : []).map((run, i) => (\n <RunView key={i} run={run} />\n ))}\n </>\n );\n}\n\nfunction RunView({ run }: { run: Run }) {\n const text = typeof run?.text === \"string\" ? run.text : \"\";\n const style = runCss(run);\n const href = typeof run.link === \"string\" ? sanitizeHref(run.link) : undefined;\n\n if (run.code) {\n style.fontFamily = style.fontFamily ?? fontStack(DEFAULTS.codeFont, \"monospace\");\n style.backgroundColor = style.backgroundColor ?? DEFAULTS.codeFill;\n }\n if (href) {\n style.color = style.color ?? DEFAULTS.linkColor;\n style.textDecorationLine = style.textDecorationLine ?? \"underline\";\n }\n\n // Line breaks inside a run are real breaks, as Word shows them.\n const parts = text.split(\"\\n\");\n const content = parts.map((part, i) => (\n <Fragment key={i}>\n {i > 0 && <br />}\n {part}\n </Fragment>\n ));\n const span = run.code ? (\n <code data-fancy-docs-run=\"\" style={style}>{content}</code>\n ) : (\n <span data-fancy-docs-run=\"\" style={style}>{content}</span>\n );\n\n return href ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer nofollow\">\n {span}\n </a>\n ) : (\n span\n );\n}\n\nfunction BlockView({ block }: { block: Block }): ReactNode {\n switch (block?.type) {\n case \"heading\": {\n const level = Math.min(6, Math.max(1, Math.round(num(block.level) ?? 1)));\n const Tag = `h${level}` as \"h1\";\n const style: CSSProperties = {\n ...paragraphCss(block, { before: DEFAULTS.headingSpaceBefore, after: DEFAULTS.headingSpaceAfter }),\n fontSize: `${DEFAULTS.headingSizes[level - 1]}pt`,\n fontWeight: 700,\n };\n return (\n <Tag data-fancy-docs-block=\"heading\" style={style}>\n <Runs runs={block.runs} />\n </Tag>\n );\n }\n case \"paragraph\":\n return (\n <p data-fancy-docs-block=\"paragraph\" style={paragraphCss(block)}>\n <Runs runs={block.runs} />\n </p>\n );\n case \"list\":\n return <ListView items={block.items} ordered={!!block.ordered} />;\n case \"table\":\n return <TableView block={block} />;\n case \"code\":\n return (\n <pre\n data-fancy-docs-block=\"code\"\n style={{\n margin: `0 0 ${DEFAULTS.spaceAfter}pt`,\n padding: \"4pt 6pt\",\n background: DEFAULTS.codeFill,\n fontFamily: fontStack(DEFAULTS.codeFont, \"monospace\"),\n fontSize: `${DEFAULTS.codeSize}pt`,\n lineHeight: 1,\n whiteSpace: \"pre-wrap\",\n }}\n >\n <code>{typeof block.text === \"string\" ? block.text : \"\"}</code>\n </pre>\n );\n case \"quote\":\n return (\n <blockquote\n data-fancy-docs-block=\"quote\"\n style={{ margin: `0 0 0 ${DEFAULTS.quoteIndent}pt`, fontStyle: \"italic\", color: DEFAULTS.quoteColor }}\n >\n {(Array.isArray(block.blocks) ? block.blocks : []).map((b, i) => (\n <BlockView key={i} block={b} />\n ))}\n </blockquote>\n );\n case \"image\": {\n // Rasters embedded in the file only: a remote URL would fetch as soon as\n // the document opened -- a tracking pixel in a file someone sent you.\n const src = typeof block.src === \"string\" && /^data:image\\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/=]+$/.test(block.src) ? block.src : null;\n if (!src) return null;\n return (\n <img\n data-fancy-docs-block=\"image\"\n src={src}\n alt={typeof block.alt === \"string\" ? block.alt : \"\"}\n style={{ display: \"block\", maxWidth: \"100%\", width: num(block.widthPx), height: num(block.heightPx) ? undefined : \"auto\", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }}\n />\n );\n }\n case \"hr\":\n return <hr data-fancy-docs-block=\"hr\" style={{ border: 0, borderTop: \"0.5pt solid #000000\", margin: `0 0 ${DEFAULTS.spaceAfter}pt` }} />;\n default:\n return null;\n }\n}\n\nfunction ListView({ items, ordered }: { items: ListItem[] | undefined; ordered: boolean }) {\n const Tag = ordered ? \"ol\" : \"ul\";\n return (\n <Tag data-fancy-docs-block=\"list\" style={{ margin: `0 0 ${DEFAULTS.spaceAfter}pt`, paddingLeft: \"18pt\", listStyleType: ordered ? \"decimal\" : \"disc\" }}>\n {(Array.isArray(items) ? items : []).map((item, i) => (\n <li key={i} style={{ ...paragraphCss(item, { after: 0 }) }}>\n <Runs runs={item.runs} />\n {Array.isArray(item.children) && item.children.length > 0 && <ListView items={item.children} ordered={ordered} />}\n </li>\n ))}\n </Tag>\n );\n}\n\ntype TableBlock = Extract<Block, { type: \"table\" }>;\n\nfunction TableView({ block }: { block: TableBlock }) {\n const rows = Array.isArray(block.rows) ? block.rows : [];\n const widths = Array.isArray(block.widths) ? block.widths.filter((w) => num(w) !== undefined && w > 0) : [];\n const sum = widths.reduce((a, b) => a + b, 0);\n const width = num(block.width);\n const borders: TableBorders = block.borders ?? {};\n\n const style: CSSProperties = {\n borderCollapse: \"collapse\",\n width: width !== undefined ? `${width}%` : \"100%\",\n tableLayout: widths.length > 0 ? \"fixed\" : \"auto\",\n marginBottom: `${DEFAULTS.spaceAfter}pt`,\n ...(block.align === \"center\" ? { marginLeft: \"auto\", marginRight: \"auto\" } : block.align === \"right\" ? { marginLeft: \"auto\" } : {}),\n };\n\n // Every edge defaults to the writer's 0.5pt single line; `{ style: \"none\" }` removes one.\n const edge = (b: TableBorders[keyof TableBorders]) => borderCss(b) ?? `${DEFAULTS.borderWidth}pt solid #000000`;\n\n return (\n <table data-fancy-docs-block=\"table\" style={style}>\n {widths.length > 0 && (\n <colgroup>\n {widths.map((w, i) => (\n <col key={i} style={{ width: `${Math.round((w / sum) * 1000) / 10}%` }} />\n ))}\n </colgroup>\n )}\n <tbody>\n {rows.map((row, r) => (\n <tr key={r}>\n {(Array.isArray(row.cells) ? row.cells : []).map((cell, c, cells) => {\n const header = !!row.header;\n const Tag = header ? \"th\" : \"td\";\n const first = c === 0;\n const last = c === cells.length - 1;\n const cellStyle: CSSProperties = {\n // Outer edges from the table's box; inner ones from insideH / insideV.\n borderTop: r === 0 ? edge(borders.top) : edge(borders.insideH),\n borderBottom: r === rows.length - 1 ? edge(borders.bottom) : edge(borders.insideH),\n borderLeft: first ? edge(borders.left) : edge(borders.insideV),\n borderRight: last ? edge(borders.right) : edge(borders.insideV),\n ...padding(cell.padding, { ...DEFAULTS.cellPadding, ...(block.cellPadding ?? {}) }),\n ...boxBorders(cell.borders),\n verticalAlign: cell.valign === \"center\" ? \"middle\" : cell.valign ?? \"top\",\n textAlign: \"left\",\n fontWeight: header ? 700 : undefined,\n backgroundColor: hex(cell.shading) ?? (header ? DEFAULTS.headerFill : undefined),\n };\n return (\n <Tag\n key={c}\n style={cellStyle}\n colSpan={num(cell.colSpan) && cell.colSpan! > 1 ? cell.colSpan : undefined}\n rowSpan={num(cell.rowSpan) && cell.rowSpan! > 1 ? cell.rowSpan : undefined}\n >\n <CellBlocks cell={cell} />\n </Tag>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\n/** A cell's blocks, with the last paragraph's trailing space dropped as Word does inside a cell. */\nfunction CellBlocks({ cell }: { cell: TableCell }) {\n const blocks = Array.isArray(cell.blocks) ? cell.blocks : [];\n return (\n <>\n {blocks.map((b, i) =>\n b?.type === \"paragraph\" ? (\n <p key={i} data-fancy-docs-block=\"paragraph\" style={{ ...paragraphCss(b), ...(i === blocks.length - 1 && num(b.spaceAfter) === undefined ? { marginBottom: 0 } : {}) }}>\n <Runs runs={b.runs} />\n </p>\n ) : (\n <BlockView key={i} block={b} />\n ),\n )}\n </>\n );\n}\n","import { Callout, type FauxClientProps } from \"@particle-academy/react-fancy\";\nimport { DocViewer } from \"./DocViewer\";\nimport type { Doc } from \"./types\";\n\nexport interface DocFileViewProps {\n /** A document as last-word's reader returns it. */\n document: unknown;\n filename?: string;\n /** Passed to the FauxClient frame. */\n frameProps?: Partial<FauxClientProps>;\n}\n\n/**\n * A .docx in fancy-code's `FileViewer`: register it once at app start.\n *\n * ```ts\n * registerOfficeViewer(\"docx\", DocFileView, { read: (b) => new DocxReader().read(b) });\n * ```\n *\n * The props match fancy-code's `OfficeViewerProps` without importing them, so\n * this package does not depend on fancy-code.\n */\nexport function DocFileView({ document, filename, frameProps }: DocFileViewProps) {\n const doc = document as Partial<Doc> | null;\n\n // A reader that returned something else would otherwise draw a blank page.\n if (!doc || typeof doc !== \"object\" || !Array.isArray(doc.blocks)) {\n return <Callout color=\"red\">{filename ?? \"This file\"} did not read as a document.</Callout>;\n }\n\n return <DocViewer doc={doc as Doc} frameProps={frameProps} />;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-docs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Render a last-word document (docx) the way Word does: run, paragraph, table and cell styling, header and footer bands, in react-fancy's FauxClient frame",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Particle-Academy/fancy-docs.git"
8
+ },
9
+ "homepage": "https://github.com/Particle-Academy/fancy-docs#readme",
10
+ "bugs": "https://github.com/Particle-Academy/fancy-docs/issues",
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "CHANGELOG.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "dev": "tsup --watch",
35
+ "lint": "tsc --noEmit && eslint .",
36
+ "test": "vitest run",
37
+ "clean": "rm -rf dist",
38
+ "prepublishOnly": "tsup"
39
+ },
40
+ "peerDependencies": {
41
+ "@particle-academy/react-fancy": ">=5.49.1 <6.0.0",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@particle-academy/react-fancy": "^5.49.1",
47
+ "@types/react": "^19.2.18",
48
+ "@types/react-dom": "^19.2.4",
49
+ "eslint": "^10.8.0",
50
+ "eslint-plugin-react-hooks": "^7.1.1",
51
+ "jsdom": "^30.0.0",
52
+ "react": "^19.2.8",
53
+ "react-dom": "^19.2.8",
54
+ "tsup": "^8.5.0",
55
+ "typescript": "^5.8.0",
56
+ "typescript-eslint": "^8.65.0",
57
+ "vitest": "^4.1.11"
58
+ },
59
+ "overrides": {
60
+ "esbuild": "^0.28.1",
61
+ "source-map-js": "^1.2.2"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "license": "MIT",
67
+ "sideEffects": false,
68
+ "engines": {
69
+ "node": ">=22"
70
+ }
71
+ }