reamkit 1.4.0 → 1.5.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.
Files changed (36) hide show
  1. package/README.md +8 -6
  2. package/dist/esm/core/converter/facade.d.ts +12 -6
  3. package/dist/esm/core/converter/facade.js +28 -4
  4. package/dist/esm/core/converter/ream.d.ts +5 -3
  5. package/dist/esm/core/converter/ream.js +16 -3
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +18 -0
  8. package/dist/esm/core/drawingml/sparkline-geometry.d.ts +8 -0
  9. package/dist/esm/core/drawingml/sparkline-geometry.js +111 -0
  10. package/dist/esm/core/ir/adapters.d.ts +5 -5
  11. package/dist/esm/core/ir/sheet.d.ts +31 -0
  12. package/dist/esm/core/opc/relationships.js +1 -0
  13. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -0
  14. package/dist/esm/core/spreadsheet-model/types.d.ts +184 -0
  15. package/dist/esm/excel/conditional-format.d.ts +16 -0
  16. package/dist/esm/excel/conditional-format.js +305 -0
  17. package/dist/esm/excel/index.d.ts +4 -3
  18. package/dist/esm/excel/print-model.d.ts +2 -1
  19. package/dist/esm/excel/print-model.js +114 -2
  20. package/dist/esm/excel/sheet-drawing.d.ts +1 -1
  21. package/dist/esm/excel/sheet-to-flow.d.ts +3 -0
  22. package/dist/esm/excel/sheet-to-flow.js +51 -0
  23. package/dist/esm/excel/styles-parser.d.ts +1 -50
  24. package/dist/esm/excel/styles-parser.js +38 -1
  25. package/dist/esm/excel/table-parser.d.ts +2 -0
  26. package/dist/esm/excel/table-parser.js +51 -0
  27. package/dist/esm/excel/workbook-parser.d.ts +1 -5
  28. package/dist/esm/excel/worksheet-parser.d.ts +1 -60
  29. package/dist/esm/excel/worksheet-parser.js +239 -1
  30. package/dist/esm/excel/xlsx-reader.d.ts +3 -1
  31. package/dist/esm/excel/xlsx-reader.js +86 -51
  32. package/dist/esm/excel/xlsx-writer.d.ts +4 -0
  33. package/dist/esm/excel/xlsx-writer.js +271 -0
  34. package/dist/esm/html/html-writer.js +27 -0
  35. package/dist/esm/layout/styled-layout.js +91 -1
  36. package/package.json +4 -3
@@ -0,0 +1,271 @@
1
+ import { FEATURES } from "../core/ir/features.js";
2
+ import { buildOpcPackage } from "../core/opc/opc-writer.js";
3
+ import "../core/opc/index.js";
4
+ //#region src/excel/xlsx-writer.ts
5
+ var encoder = new TextEncoder();
6
+ var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
7
+ var REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
8
+ var REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
9
+ var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
10
+ var REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
11
+ var CT_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
12
+ var CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
13
+ var CT_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
14
+ var CT_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
15
+ var CT_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml";
16
+ var MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
17
+ var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
18
+ function writeXlsx(sheet) {
19
+ const losses = [];
20
+ const worksheetParts = [];
21
+ const tableParts = [];
22
+ const worksheetRels = [];
23
+ const sheetEntries = [];
24
+ const workbookRels = [];
25
+ let tableCounter = 0;
26
+ sheet.sheets.forEach((s, i) => {
27
+ const idx = i + 1;
28
+ const rid = `rId${idx}`;
29
+ const wsPath = `xl/worksheets/sheet${idx}.xml`;
30
+ const tableRelIds = [];
31
+ const wsTableRels = [];
32
+ for (const t of s.grid.tables ?? []) {
33
+ const tid = ++tableCounter;
34
+ const tRid = `rIdTbl${tid}`;
35
+ tableRelIds.push(tRid);
36
+ tableParts.push({
37
+ path: `xl/tables/table${tid}.xml`,
38
+ data: encoder.encode(tableXml(t, tid)),
39
+ contentType: CT_TABLE
40
+ });
41
+ wsTableRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
42
+ }
43
+ if (wsTableRels.length > 0) worksheetRels.push({
44
+ sourcePart: wsPath,
45
+ relationships: wsTableRels
46
+ });
47
+ worksheetParts.push({
48
+ path: wsPath,
49
+ data: encoder.encode(worksheetXml(s.grid, tableRelIds)),
50
+ contentType: CT_WORKSHEET
51
+ });
52
+ workbookRels.push(rel(rid, REL_WORKSHEET, `worksheets/sheet${idx}.xml`));
53
+ sheetEntries.push({
54
+ name: s.name,
55
+ sheetId: idx,
56
+ rid
57
+ });
58
+ recordSheetLosses(s, losses);
59
+ });
60
+ const sstRid = `rId${sheet.sheets.length + 1}`;
61
+ const stylesRid = `rId${sheet.sheets.length + 2}`;
62
+ workbookRels.push(rel(sstRid, REL_SHARED_STRINGS, "sharedStrings.xml"));
63
+ workbookRels.push(rel(stylesRid, REL_STYLES, "styles.xml"));
64
+ return {
65
+ bytes: buildOpcPackage({
66
+ parts: [
67
+ {
68
+ path: "xl/workbook.xml",
69
+ data: encoder.encode(workbookXml(sheetEntries, sheet.definedNames, sheet.date1904)),
70
+ contentType: CT_WORKBOOK
71
+ },
72
+ ...worksheetParts,
73
+ ...tableParts,
74
+ {
75
+ path: "xl/sharedStrings.xml",
76
+ data: encoder.encode(sharedStringsXml(sheet.sharedStrings)),
77
+ contentType: CT_SHARED_STRINGS
78
+ },
79
+ {
80
+ path: "xl/styles.xml",
81
+ data: encoder.encode(stylesXml(sheet.styles)),
82
+ contentType: CT_STYLES
83
+ }
84
+ ],
85
+ rootRelationships: [rel("rId1", REL_OFFICE_DOCUMENT, "xl/workbook.xml")],
86
+ partRelationships: [{
87
+ sourcePart: "xl/workbook.xml",
88
+ relationships: workbookRels
89
+ }, ...worksheetRels]
90
+ }),
91
+ losses
92
+ };
93
+ }
94
+ new Set([FEATURES.text, FEATURES.tables]);
95
+ function rel(id, type, target) {
96
+ return {
97
+ id,
98
+ type,
99
+ target,
100
+ targetMode: "Internal"
101
+ };
102
+ }
103
+ function recordSheetLosses(s, losses) {
104
+ if (s.charts && s.charts.length > 0) losses.push({
105
+ severity: "dropped",
106
+ feature: FEATURES.charts,
107
+ detail: `sheet "${s.name}": embedded charts not written back`
108
+ });
109
+ }
110
+ function workbookXml(sheets, definedNames, date1904) {
111
+ const sheetTags = sheets.map((s) => `<sheet name="${escapeAttr(s.name)}" sheetId="${s.sheetId}" r:id="${s.rid}"/>`).join("");
112
+ const definedNamesXml = definedNames.length > 0 ? `<definedNames>${definedNames.map((d) => `<definedName name="${escapeAttr(d.name)}"${d.localSheetId !== void 0 ? ` localSheetId="${d.localSheetId}"` : ""}>${escapeText(d.value)}</definedName>`).join("")}</definedNames>` : "";
113
+ return XML_DECL + `<workbook xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (date1904 ? "<workbookPr date1904=\"1\"/>" : "") + `<sheets>${sheetTags}</sheets>` + definedNamesXml + "</workbook>";
114
+ }
115
+ function worksheetXml(grid, tableRelIds = []) {
116
+ const dimension = grid.maxRow >= 0 && grid.maxColumn >= 0 ? `<dimension ref="A1:${cellRef(grid.maxRow, grid.maxColumn)}"/>` : "<dimension ref=\"A1\"/>";
117
+ const colsXml = grid.columns.length > 0 ? `<cols>${grid.columns.map((c) => `<col min="${c.min}" max="${c.max}" width="${c.widthChars}" customWidth="1"/>`).join("")}</cols>` : "";
118
+ const heightByRow = /* @__PURE__ */ new Map();
119
+ for (const rh of grid.rowHeights) heightByRow.set(rh.row, {
120
+ heightPt: rh.heightPt,
121
+ customHeight: rh.customHeight
122
+ });
123
+ const cellsByRow = /* @__PURE__ */ new Map();
124
+ for (const cell of grid.cells) {
125
+ let row = cellsByRow.get(cell.row);
126
+ if (!row) {
127
+ row = [];
128
+ cellsByRow.set(cell.row, row);
129
+ }
130
+ row.push(cell);
131
+ }
132
+ const rowsXml = [...new Set([...cellsByRow.keys(), ...heightByRow.keys()])].sort((a, b) => a - b).map((r) => {
133
+ const cells = (cellsByRow.get(r) ?? []).slice().sort((a, b) => a.column - b.column);
134
+ const h = heightByRow.get(r);
135
+ const heightAttrs = h ? ` ht="${h.heightPt}"${h.customHeight ? " customHeight=\"1\"" : ""}` : "";
136
+ return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
137
+ }).join("");
138
+ const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
139
+ return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
140
+ }
141
+ function tableXml(t, id) {
142
+ const ref = rangeRef(t.ref);
143
+ const name = t.name ?? `Table${id}`;
144
+ const ncols = t.ref.endColumn - t.ref.startColumn + 1;
145
+ const columns = Array.from({ length: ncols }, (_, c) => `<tableColumn id="${c + 1}" name="Column${c + 1}"/>`).join("");
146
+ const styleInfo = t.styleName ? `<tableStyleInfo name="${escapeAttr(t.styleName)}" showFirstColumn="${t.showFirstColumn ? 1 : 0}" showLastColumn="${t.showLastColumn ? 1 : 0}" showRowStripes="${t.showRowStripes ? 1 : 0}" showColumnStripes="${t.showColumnStripes ? 1 : 0}"/>` : "";
147
+ return XML_DECL + `<table xmlns="${MAIN_NS}" id="${id}" name="${escapeAttr(name)}" displayName="${escapeAttr(name)}" ref="${ref}"${t.headerRowCount !== 1 ? ` headerRowCount="${t.headerRowCount}"` : ""} totalsRowShown="0">` + (t.autoFilter ? `<autoFilter ref="${ref}"/>` : "") + `<tableColumns count="${ncols}">${columns}</tableColumns>` + styleInfo + "</table>";
148
+ }
149
+ function conditionalFormattingXml(cfs) {
150
+ if (!cfs || cfs.length === 0) return "";
151
+ return cfs.map((cf) => {
152
+ return `<conditionalFormatting sqref="${cf.ranges.map(rangeRef).join(" ")}">${cf.rules.map(cfRuleXml).join("")}</conditionalFormatting>`;
153
+ }).join("");
154
+ }
155
+ function cfRuleXml(rule) {
156
+ const p = ` priority="${rule.priority}"`;
157
+ switch (rule.type) {
158
+ case "cellIs": return `<cfRule type="cellIs"${p} operator="${rule.operator}" dxfId="${rule.dxfId}">` + rule.formulas.map((f) => `<formula>${escapeText(f)}</formula>`).join("") + "</cfRule>";
159
+ case "colorScale": return `<cfRule type="colorScale"${p}><colorScale>` + rule.cfvos.map(cfvoXml).join("") + rule.colorsHex.map((c) => `<color rgb="FF${c}"/>`).join("") + "</colorScale></cfRule>";
160
+ case "dataBar": return `<cfRule type="dataBar"${p}><dataBar` + (rule.minLength !== void 0 ? ` minLength="${rule.minLength}"` : "") + (rule.maxLength !== void 0 ? ` maxLength="${rule.maxLength}"` : "") + ">" + rule.cfvos.map(cfvoXml).join("") + `<color rgb="FF${rule.colorHex}"/></dataBar></cfRule>`;
161
+ case "iconSet": return `<cfRule type="iconSet"${p}><iconSet iconSet="${rule.iconSet}"` + (rule.reverse ? " reverse=\"1\"" : "") + ">" + rule.cfvos.map(cfvoXml).join("") + "</iconSet></cfRule>";
162
+ }
163
+ }
164
+ function cfvoXml(cfvo) {
165
+ return `<cfvo type="${cfvo.type}"${cfvo.val !== void 0 ? ` val="${escapeAttr(cfvo.val)}"` : ""}/>`;
166
+ }
167
+ var X14_NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
168
+ var XM_NS = "http://schemas.microsoft.com/office/excel/2006/main";
169
+ function sparklineExtXml(sparklines) {
170
+ if (!sparklines || sparklines.length === 0) return "";
171
+ return `<extLst><ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" xmlns:x14="${X14_NS}"><x14:sparklineGroups xmlns:xm="${XM_NS}">${sparklines.map((sp) => {
172
+ return `<x14:sparklineGroup${sp.kind === "column" ? " type=\"column\"" : sp.kind === "winLoss" ? " type=\"stacked\"" : ""}>${sp.colorHex ? `<x14:colorSeries rgb="FF${sp.colorHex}"/>` : ""}<x14:sparklines><x14:sparkline><xm:f>${escapeText(sp.dataRange)}</xm:f><xm:sqref>${escapeText(sp.sqref)}</xm:sqref></x14:sparkline></x14:sparklines></x14:sparklineGroup>`;
173
+ }).join("")}</x14:sparklineGroups></ext></extLst>`;
174
+ }
175
+ function rangeRef(r) {
176
+ const a = cellRef(r.startRow, r.startColumn);
177
+ if (r.startRow === r.endRow && r.startColumn === r.endColumn) return a;
178
+ return `${a}:${cellRef(r.endRow, r.endColumn)}`;
179
+ }
180
+ function printOptionsXml(po) {
181
+ if (!po) return "";
182
+ const flag = (name, v) => v !== void 0 ? ` ${name}="${v ? "1" : "0"}"` : "";
183
+ const attrs = flag("gridLines", po.gridLines) + flag("horizontalCentered", po.horizontalCentered) + flag("verticalCentered", po.verticalCentered);
184
+ return attrs ? `<printOptions${attrs}/>` : "";
185
+ }
186
+ function pageMarginsXml(m) {
187
+ if (!m) return "";
188
+ return `<pageMargins left="${m.leftInches}" right="${m.rightInches}" top="${m.topInches}" bottom="${m.bottomInches}"` + (m.headerInches !== void 0 ? ` header="${m.headerInches}"` : "") + (m.footerInches !== void 0 ? ` footer="${m.footerInches}"` : "") + "/>";
189
+ }
190
+ function pageSetupXml(s) {
191
+ if (!s) return "";
192
+ const attr = (name, v) => v !== void 0 ? ` ${name}="${v}"` : "";
193
+ const attrs = attr("paperSize", s.paperSize) + attr("orientation", s.orientation) + attr("scale", s.scale) + attr("fitToWidth", s.fitToWidth) + attr("fitToHeight", s.fitToHeight);
194
+ return attrs ? `<pageSetup${attrs}/>` : "";
195
+ }
196
+ function breaksXml(tag, breaks) {
197
+ if (!breaks || breaks.length === 0) return "";
198
+ const brks = breaks.map((id) => `<brk id="${id}" man="1"/>`).join("");
199
+ return `<${tag} count="${breaks.length}" manualBreakCount="${breaks.length}">${brks}</${tag}>`;
200
+ }
201
+ function cellXml(cell) {
202
+ const ref = cellRef(cell.row, cell.column);
203
+ const sAttr = cell.styleIndex !== void 0 ? ` s="${cell.styleIndex}"` : "";
204
+ const tAttr = cell.type === "n" ? "" : ` t="${cell.type}"`;
205
+ if (cell.type === "inlineStr") return `<c r="${ref}"${sAttr}${tAttr}><is><t xml:space="preserve">${escapeText(cell.inlineText ?? "")}</t></is></c>`;
206
+ if (cell.rawValue === "") return `<c r="${ref}"${sAttr}${tAttr}/>`;
207
+ return `<c r="${ref}"${sAttr}${tAttr}><v>${escapeText(cell.rawValue)}</v></c>`;
208
+ }
209
+ function sharedStringsXml(strings) {
210
+ const items = strings.map((s) => `<si><t xml:space="preserve">${escapeText(s)}</t></si>`).join("");
211
+ return XML_DECL + `<sst xmlns="${MAIN_NS}" count="${strings.length}" uniqueCount="${strings.length}">` + items + "</sst>";
212
+ }
213
+ function stylesXml(styles) {
214
+ const numFmts = styles.numFmts.size > 0 ? `<numFmts count="${styles.numFmts.size}">${[...styles.numFmts].map(([id, code]) => `<numFmt numFmtId="${id}" formatCode="${escapeAttr(code)}"/>`).join("")}</numFmts>` : "";
215
+ const fonts = `<fonts count="${styles.fonts.length}">${styles.fonts.map(fontXml).join("")}</fonts>`;
216
+ const fills = `<fills count="${styles.fills.length}">${styles.fills.map(fillXml).join("")}</fills>`;
217
+ const borders = `<borders count="${styles.borders.length}">${styles.borders.map(borderXml).join("")}</borders>`;
218
+ const cellStyleXfs = "<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>";
219
+ const cellXfs = `<cellXfs count="${styles.cellXfs.length}">${styles.cellXfs.map(cellXfXml).join("")}</cellXfs>`;
220
+ const dxfs = styles.dxfs && styles.dxfs.length > 0 ? `<dxfs count="${styles.dxfs.length}">${styles.dxfs.map((d) => `<dxf>${d.font ? `<font>${fontInner(d.font)}</font>` : ""}${d.fill ? `<fill>${patternFill(d.fill)}</fill>` : ""}</dxf>`).join("")}</dxfs>` : "";
221
+ return XML_DECL + `<styleSheet xmlns="${MAIN_NS}">` + numFmts + fonts + fills + borders + cellStyleXfs + cellXfs + dxfs + "</styleSheet>";
222
+ }
223
+ function fontXml(f) {
224
+ return `<font>${fontInner(f)}</font>`;
225
+ }
226
+ function fontInner(f) {
227
+ return (f.sizePt !== void 0 ? `<sz val="${f.sizePt}"/>` : "") + (f.bold ? "<b/>" : "") + (f.italic ? "<i/>" : "") + (f.underline ? "<u/>" : "") + (f.colorHex ? `<color rgb="FF${f.colorHex}"/>` : "") + (f.name ? `<name val="${escapeAttr(f.name)}"/>` : "");
228
+ }
229
+ function fillXml(f) {
230
+ return `<fill>${patternFill(f)}</fill>`;
231
+ }
232
+ function patternFill(f) {
233
+ const inner = (f.fgColorHex ? `<fgColor rgb="FF${f.fgColorHex}"/>` : "") + (f.bgColorHex ? `<bgColor rgb="FF${f.bgColorHex}"/>` : "");
234
+ return `<patternFill${f.patternType ? ` patternType="${f.patternType}"` : ""}>${inner}</patternFill>`;
235
+ }
236
+ function borderXml(b) {
237
+ return `<border>${edgeXml("left", b.left)}${edgeXml("right", b.right)}${edgeXml("top", b.top)}${edgeXml("bottom", b.bottom)}</border>`;
238
+ }
239
+ function edgeXml(name, edge) {
240
+ if (!edge || !edge.style) return `<${name}/>`;
241
+ const color = edge.colorHex ? `<color rgb="FF${edge.colorHex}"/>` : "";
242
+ return `<${name} style="${edge.style}">${color}</${name}>`;
243
+ }
244
+ function cellXfXml(xf) {
245
+ const apply = applyAttr("applyNumberFormat", xf.applyNumberFormat) + applyAttr("applyFont", xf.applyFont) + applyAttr("applyFill", xf.applyFill) + applyAttr("applyBorder", xf.applyBorder) + applyAttr("applyAlignment", xf.applyAlignment);
246
+ const a = xf.alignment;
247
+ const alignment = a ? `<alignment${a.horizontal ? ` horizontal="${a.horizontal}"` : ""}${a.vertical ? ` vertical="${a.vertical}"` : ""}${a.wrapText ? " wrapText=\"1\"" : ""}/>` : "";
248
+ return `<xf numFmtId="${xf.numFmtId}" fontId="${xf.fontId}" fillId="${xf.fillId}" borderId="${xf.borderId}"${apply}>` + alignment + "</xf>";
249
+ }
250
+ function applyAttr(name, value) {
251
+ return value !== void 0 ? ` ${name}="${value ? "1" : "0"}"` : "";
252
+ }
253
+ var XML_DECL = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
254
+ function cellRef(row, col) {
255
+ let n = col + 1;
256
+ let letters = "";
257
+ while (n > 0) {
258
+ const rem = (n - 1) % 26;
259
+ letters = String.fromCharCode(65 + rem) + letters;
260
+ n = Math.floor((n - 1) / 26);
261
+ }
262
+ return `${letters}${row + 1}`;
263
+ }
264
+ function escapeText(s) {
265
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
266
+ }
267
+ function escapeAttr(s) {
268
+ return escapeText(s).replace(/"/g, "&quot;");
269
+ }
270
+ //#endregion
271
+ export { writeXlsx };
@@ -3,6 +3,7 @@ import { detectImageFormat } from "../core/images.js";
3
3
  import { EMPTY_STYLE_SHEET, resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
4
4
  import "../core/style-cascade/index.js";
5
5
  import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
6
+ import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
6
7
  import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
7
8
  import { buildChartScene } from "../core/drawingml/chart-geometry.js";
8
9
  import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
@@ -402,6 +403,14 @@ function emitCell(out, cell, table, ctx, pos) {
402
403
  pushBorder(css, "left", c?.left ?? (pos.firstCol ? t?.left : t?.insideV));
403
404
  pushBorder(css, "right", c?.right ?? (pos.lastCol ? t?.right : t?.insideV));
404
405
  if (cell.properties.shading) css.push(`background-color:#${cell.properties.shading.colorHex}`);
406
+ if (cell.properties.dataBar) {
407
+ const db = cell.properties.dataBar;
408
+ const clamp = (n) => Math.max(0, Math.min(1, n));
409
+ const start = (clamp(db.startFraction ?? 0) * 100).toFixed(2);
410
+ const end = (clamp((db.startFraction ?? 0) + db.fraction) * 100).toFixed(2);
411
+ const c = db.colorHex;
412
+ css.push(`background-image:linear-gradient(to right,transparent ${start}%,#${c} ${start}%,#${c} ${end}%,transparent ${end}%)`);
413
+ }
405
414
  const margins = cell.properties.margins ?? table.properties.defaultCellMargins;
406
415
  const padTop = margins?.top ?? 0;
407
416
  const padRight = margins?.right ?? 5.4;
@@ -409,9 +418,27 @@ function emitCell(out, cell, table, ctx, pos) {
409
418
  const padLeft = margins?.left ?? 5.4;
410
419
  css.push(`padding:${fmt(padTop)}pt ${fmt(padRight)}pt ${fmt(padBottom)}pt ${fmt(padLeft)}pt`);
411
420
  out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
421
+ if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
422
+ if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
412
423
  for (const child of cell.content) emitBlock(out, child, ctx);
413
424
  out.push(`</${tag}>`);
414
425
  }
426
+ function cellSparklineSvg(sp) {
427
+ const W = 56;
428
+ const H = 16;
429
+ if (sp.values.length === 0) return "";
430
+ const prims = buildSparkline(sp.kind, sp.values, W, H, sp.colorHex);
431
+ const fmt = (n) => (Math.round(n * 100) / 100).toString();
432
+ const parts = [];
433
+ for (const prim of prims) {
434
+ const d = prim.paths.map((p) => svgPathData(p.segments, fmt)).join(" ");
435
+ parts.push(prim.stroke ? `<path d="${d}" fill="none" stroke="#${prim.stroke.colorHex}" stroke-width="${prim.stroke.widthPt}"/>` : `<path d="${d}" fill="#${prim.fillColorHex ?? "376092"}"/>`);
436
+ }
437
+ return `<svg width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" style="vertical-align:middle"><g transform="translate(0,${H}) scale(1,-1)">${parts.join("")}</g></svg>`;
438
+ }
439
+ function cellIconSvg(icon) {
440
+ return `<svg width="10" height="10" viewBox="0 0 10 10" fill="${`#${icon.colorHex}`}" style="vertical-align:middle;margin-right:3px">${icon.shape === "square" ? `<rect x="1" y="1" width="8" height="8"/>` : icon.shape === "diamond" ? `<polygon points="5,1 9,5 5,9 1,5"/>` : icon.shape === "triangleUp" ? `<polygon points="5,1 9,9 1,9"/>` : icon.shape === "triangleDown" ? `<polygon points="1,1 9,1 5,9"/>` : icon.shape === "triangleRight" ? `<polygon points="9,5 1,1 1,9"/>` : `<circle cx="5" cy="5" r="4"/>`}</svg>`;
441
+ }
415
442
  function pushBorder(css, side, border) {
416
443
  if (!border || border.style === "none") return;
417
444
  const width = border.width ?? .5;
@@ -12,6 +12,7 @@ import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-
12
12
  import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
13
13
  import "../core/style-cascade/index.js";
14
14
  import { PathBuilder, flipTransform } from "../core/vector.js";
15
+ import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
15
16
  import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
16
17
  import { buildChartScene } from "../core/drawingml/chart-geometry.js";
17
18
  import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
@@ -560,6 +561,31 @@ function buildChartLayout(chart, widthPt, heightPt, font) {
560
561
  function rectAtPath(x, y, w, h) {
561
562
  return new PathBuilder().moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).close().build();
562
563
  }
564
+ var CF_ICON_SIZE_PT = 9;
565
+ var CF_ICON_GUTTER_PT = 12;
566
+ function buildCellIconShape(icon, size) {
567
+ return {
568
+ paths: [cellIconPath(icon.shape, size)],
569
+ fillColorHex: icon.colorHex
570
+ };
571
+ }
572
+ function cellIconPath(shape, s) {
573
+ switch (shape) {
574
+ case "square": return rectAtPath(s * .12, s * .12, s * .76, s * .76);
575
+ case "diamond": return new PathBuilder().moveTo(s / 2, s * .92).lineTo(s * .92, s / 2).lineTo(s / 2, s * .08).lineTo(s * .08, s / 2).close().build();
576
+ case "triangleUp": return trianglePath([s / 2, s * .9], [s * .1, s * .12], [s * .9, s * .12]);
577
+ case "triangleDown": return trianglePath([s / 2, s * .1], [s * .1, s * .88], [s * .9, s * .88]);
578
+ case "triangleRight": return trianglePath([s * .9, s / 2], [s * .12, s * .1], [s * .12, s * .9]);
579
+ case "circle": return circlePath(s / 2, s / 2, s * .42);
580
+ }
581
+ }
582
+ function trianglePath(a, b, c) {
583
+ return new PathBuilder().moveTo(a[0], a[1]).lineTo(b[0], b[1]).lineTo(c[0], c[1]).close().build();
584
+ }
585
+ function circlePath(cx, cy, r) {
586
+ const k = .5522847498307936 * r;
587
+ return new PathBuilder().moveTo(cx + r, cy).cubicTo(cx + r, cy + k, cx + k, cy + r, cx, cy + r).cubicTo(cx - k, cy + r, cx - r, cy + k, cx - r, cy).cubicTo(cx - r, cy - k, cx - k, cy - r, cx, cy - r).cubicTo(cx + k, cy - r, cx + r, cy - k, cx + r, cy).close().build();
588
+ }
563
589
  function rectPrim(r) {
564
590
  return {
565
591
  paths: [rectAtPath(r.x, r.y, r.w, r.h)],
@@ -1431,8 +1457,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1431
1457
  const tableMar = tableProps.defaultCellMargins;
1432
1458
  const padTopPt = cellMar?.top ?? tableMar?.top ?? 0;
1433
1459
  const padBottomPt = cellMar?.bottom ?? tableMar?.bottom ?? 0;
1434
- const padLeftPt = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1460
+ const padLeftBase = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1435
1461
  const padRightPt = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1462
+ const padLeftPt = padLeftBase + (cell.properties.icon ? CF_ICON_GUTTER_PT : 0);
1436
1463
  const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
1437
1464
  const lines = [];
1438
1465
  const nestedTables = [];
@@ -1462,6 +1489,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1462
1489
  padLeftPt,
1463
1490
  borders,
1464
1491
  ...cell.properties.shading?.colorHex ? { shadingColorHex: cell.properties.shading.colorHex } : {},
1492
+ ...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
1493
+ ...cell.properties.icon ? { icon: cell.properties.icon } : {},
1494
+ ...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
1465
1495
  lines,
1466
1496
  ...nestedTables.length > 0 ? { nestedTables } : {},
1467
1497
  contentHeightPt,
@@ -2084,6 +2114,66 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
2084
2114
  height: pt(row.heightPt),
2085
2115
  fillColorHex: cell.shadingColorHex
2086
2116
  });
2117
+ if (cell.dataBar && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2118
+ const start = Math.max(0, Math.min(1, cell.dataBar.startFraction ?? 0));
2119
+ const barWidth = cell.widthPt * Math.max(0, Math.min(1, cell.dataBar.fraction));
2120
+ if (barWidth > 0) out.push({
2121
+ type: "fill",
2122
+ x: pt(cellX + cell.widthPt * start),
2123
+ y: pt(pageHeight - rowBottom - row.heightPt),
2124
+ width: pt(barWidth),
2125
+ height: pt(row.heightPt),
2126
+ fillColorHex: cell.dataBar.colorHex
2127
+ });
2128
+ }
2129
+ if (cell.icon && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2130
+ const iconSize = Math.min(CF_ICON_SIZE_PT, Math.max(0, row.heightPt - 1));
2131
+ if (iconSize > 0) {
2132
+ const iconX = cellX + (CF_ICON_GUTTER_PT - iconSize) / 2;
2133
+ const iconBottomYUp = rowBottom + (row.heightPt - iconSize) / 2;
2134
+ const { paths, fillColorHex } = buildCellIconShape(cell.icon, iconSize);
2135
+ out.push({
2136
+ type: "shape",
2137
+ shape: {
2138
+ paths,
2139
+ fillColorHex,
2140
+ transform: flipTransform([
2141
+ 1,
2142
+ 0,
2143
+ 0,
2144
+ 1,
2145
+ iconX,
2146
+ iconBottomYUp
2147
+ ], pageHeight)
2148
+ }
2149
+ });
2150
+ }
2151
+ }
2152
+ if (cell.sparkline && cell.sparkline.values.length > 0 && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2153
+ const inset = 1.5;
2154
+ const sw = cell.widthPt - 2 * inset;
2155
+ const sh = row.heightPt - 2 * inset;
2156
+ if (sw > 0 && sh > 0) {
2157
+ const prims = buildSparkline(cell.sparkline.kind, cell.sparkline.values, sw, sh, cell.sparkline.colorHex);
2158
+ const transform = flipTransform([
2159
+ 1,
2160
+ 0,
2161
+ 0,
2162
+ 1,
2163
+ cellX + inset,
2164
+ rowBottom + inset
2165
+ ], pageHeight);
2166
+ for (const prim of prims) out.push({
2167
+ type: "shape",
2168
+ shape: {
2169
+ paths: prim.paths,
2170
+ ...prim.fillColorHex ? { fillColorHex: prim.fillColorHex } : {},
2171
+ ...prim.stroke ? { stroke: prim.stroke } : {},
2172
+ transform
2173
+ }
2174
+ });
2175
+ }
2176
+ }
2087
2177
  emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount);
2088
2178
  if (cell.mergeRole === "middle" || cell.mergeRole === "end") continue;
2089
2179
  let textY = rowTop - cell.padTopPt;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.4.0",
4
- "description": "Ream — DOCX/XLSX to PDF, SVG, HTML and back to DOCX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
3
+ "version": "1.5.0",
4
+ "description": "Ream — DOCX/XLSX to PDF, SVG, HTML and back to DOCX/XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",
7
7
  "repository": {
@@ -67,7 +67,8 @@
67
67
  "corpus:fetch": "tsx scripts/corpus/fetch-corpus.ts",
68
68
  "corpus:sandbox:build": "docker build -t docgen-losandbox:latest scripts/corpus/sandbox/",
69
69
  "corpus": "tsx scripts/corpus/run.ts",
70
- "corpus:roundtrip": "tsx scripts/corpus/roundtrip.ts"
70
+ "corpus:roundtrip": "tsx scripts/corpus/roundtrip.ts",
71
+ "corpus:roundtrip:xlsx": "tsx scripts/corpus/xlsx-roundtrip.ts"
71
72
  },
72
73
  "dependencies": {
73
74
  "fast-xml-parser": "5.7.0",