reamkit 1.3.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 (47) hide show
  1. package/README.md +14 -9
  2. package/dist/esm/core/converter/facade.d.ts +12 -6
  3. package/dist/esm/core/converter/facade.js +39 -3
  4. package/dist/esm/core/converter/ream.d.ts +5 -3
  5. package/dist/esm/core/converter/ream.js +26 -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/index.d.ts +2 -0
  13. package/dist/esm/core/opc/index.js +1 -0
  14. package/dist/esm/core/opc/opc-writer.d.ts +28 -0
  15. package/dist/esm/core/opc/opc-writer.js +60 -0
  16. package/dist/esm/core/opc/relationships.js +1 -0
  17. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -0
  18. package/dist/esm/core/spreadsheet-model/types.d.ts +184 -0
  19. package/dist/esm/excel/conditional-format.d.ts +16 -0
  20. package/dist/esm/excel/conditional-format.js +305 -0
  21. package/dist/esm/excel/index.d.ts +4 -3
  22. package/dist/esm/excel/print-model.d.ts +2 -1
  23. package/dist/esm/excel/print-model.js +114 -2
  24. package/dist/esm/excel/sheet-drawing.d.ts +1 -1
  25. package/dist/esm/excel/sheet-to-flow.d.ts +3 -0
  26. package/dist/esm/excel/sheet-to-flow.js +51 -0
  27. package/dist/esm/excel/styles-parser.d.ts +1 -50
  28. package/dist/esm/excel/styles-parser.js +38 -1
  29. package/dist/esm/excel/table-parser.d.ts +2 -0
  30. package/dist/esm/excel/table-parser.js +51 -0
  31. package/dist/esm/excel/workbook-parser.d.ts +1 -5
  32. package/dist/esm/excel/worksheet-parser.d.ts +1 -60
  33. package/dist/esm/excel/worksheet-parser.js +239 -1
  34. package/dist/esm/excel/xlsx-reader.d.ts +3 -1
  35. package/dist/esm/excel/xlsx-reader.js +86 -51
  36. package/dist/esm/excel/xlsx-writer.d.ts +4 -0
  37. package/dist/esm/excel/xlsx-writer.js +271 -0
  38. package/dist/esm/html/html-writer.js +27 -0
  39. package/dist/esm/index.d.ts +1 -0
  40. package/dist/esm/index.js +2 -1
  41. package/dist/esm/layout/styled-layout.js +91 -1
  42. package/dist/esm/word/document-parser.js +47 -13
  43. package/dist/esm/word/docx-writer.d.ts +4 -0
  44. package/dist/esm/word/docx-writer.js +602 -0
  45. package/dist/esm/word/drawing-parser.d.ts +1 -0
  46. package/dist/esm/word/drawing-parser.js +37 -2
  47. package/package.json +5 -3
@@ -1,8 +1,5 @@
1
- import { pt } from "../core/ir/units.js";
2
1
  import { ResourceStore } from "../core/ir/resources.js";
3
2
  import { FEATURES } from "../core/ir/features.js";
4
- import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
5
- import "../core/style-cascade/index.js";
6
3
  import { bytesInclude } from "../core/bytes.js";
7
4
  import { DEFAULT_THEME_PALETTE, makeColorResolver } from "../core/drawingml/colors.js";
8
5
  import { parseChart, withChartColorStyle } from "../core/drawingml/chart-parser.js";
@@ -16,14 +13,21 @@ import { parseWorkbook } from "./workbook-parser.js";
16
13
  import { parseWorksheet } from "./worksheet-parser.js";
17
14
  import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
18
15
  import "./index.js";
19
- import { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody } from "./print-model.js";
20
16
  import { parseSheetDrawing } from "./sheet-drawing.js";
17
+ import { parseTablePart } from "./table-parser.js";
18
+ import { projectSheetDoc } from "./sheet-to-flow.js";
21
19
  //#region src/excel/xlsx-reader.ts
22
20
  var WORKBOOK_PART = "xl/workbook.xml";
23
21
  var SHARED_STRINGS_PART = "xl/sharedStrings.xml";
24
22
  var STYLES_PART = "xl/styles.xml";
25
23
  var CORE_PROPS_PART = "docProps/core.xml";
26
24
  function readXlsx(xlsx) {
25
+ return {
26
+ doc: projectSheetDoc(readXlsxToSheetDoc(xlsx)),
27
+ losses: []
28
+ };
29
+ }
30
+ function readXlsxToSheetDoc(xlsx) {
27
31
  const pkg = OpcPackage.open(xlsx);
28
32
  const workbookData = pkg.getPart(WORKBOOK_PART);
29
33
  if (!workbookData) throw new Error("Not a valid xlsx: missing xl/workbook.xml");
@@ -34,10 +38,10 @@ function readXlsx(xlsx) {
34
38
  const stylesData = pkg.getPart(STYLES_PART);
35
39
  const styles = stylesData ? parseXlsxStyles(stylesData) : EMPTY_XLSX_STYLES;
36
40
  const workbookRels = pkg.getPartRelationships(WORKBOOK_PART);
37
- const body = [];
38
- const charts = /* @__PURE__ */ new Map();
39
- const resolveColor = buildXlsxColorResolver(pkg, workbookRels);
40
- let firstSheetSection;
41
+ const chartData = /* @__PURE__ */ new Map();
42
+ const palette = buildThemePalette(pkg, workbookRels);
43
+ const resolveColor = makeColorResolver(palette);
44
+ const sheetsOut = [];
41
45
  for (let sheetIdx = 0; sheetIdx < sheets.length; sheetIdx++) {
42
46
  const sheet = sheets[sheetIdx];
43
47
  const sheetRel = workbookRels.find((r) => r.id === sheet.relationshipId);
@@ -45,61 +49,61 @@ function readXlsx(xlsx) {
45
49
  const resolved = pkg.resolveRelatedPart(WORKBOOK_PART, sheetRel);
46
50
  if (!resolved) continue;
47
51
  const worksheet = parseWorksheet(resolved.data);
48
- if (sheetIdx === 0) firstSheetSection = sectionFromWorksheet(worksheet);
49
- if (sheetIdx > 0) body.push({
50
- kind: "paragraph",
51
- paragraph: {
52
- properties: { pageBreakBefore: true },
53
- runs: []
54
- }
55
- });
56
- const printArea = resolvePrintArea(definedNames, sheetIdx);
57
- const titleRows = resolvePrintTitleRows(definedNames, sheetIdx);
58
- const gridLines = worksheet.printOptions?.gridLines === true;
59
- body.push(...worksheetToBody(worksheet, sharedStrings, styles, date1904, {
60
- ...printArea ? { printArea } : {},
61
- ...titleRows ? { titleRows } : {},
62
- gridLines
63
- }));
52
+ const charts = [];
64
53
  if (worksheet.drawingRelId) {
65
54
  const drawingRel = pkg.getPartRelationships(resolved.path).find((r) => r.id === worksheet.drawingRelId);
66
55
  const drawing = drawingRel ? pkg.resolveRelatedPart(resolved.path, drawingRel) : void 0;
67
56
  if (drawing) for (const ref of parseSheetDrawing(drawing.data, drawing.path, pkg, worksheet)) {
68
- if (!charts.has(ref.chartPartPath)) {
69
- const chartData = pkg.getPart(ref.chartPartPath);
70
- const parsed = chartData ? parseChart(chartData, resolveColor) : null;
57
+ if (!chartData.has(ref.chartPartPath)) {
58
+ const chartXml = pkg.getPart(ref.chartPartPath);
59
+ const parsed = chartXml ? parseChart(chartXml, resolveColor) : null;
71
60
  if (!parsed) continue;
72
- charts.set(ref.chartPartPath, withChartColorStyle(parsed, pkg, ref.chartPartPath, resolveColor));
61
+ chartData.set(ref.chartPartPath, withChartColorStyle(parsed, pkg, ref.chartPartPath, resolveColor));
73
62
  }
74
- body.push({
75
- kind: "chart",
76
- chart: {
77
- chartRelId: ref.chartPartPath,
78
- width: pt(ref.widthPt),
79
- height: pt(ref.heightPt),
80
- paragraphProperties: {}
81
- }
63
+ charts.push({
64
+ chartPartPath: ref.chartPartPath,
65
+ widthPt: ref.widthPt,
66
+ heightPt: ref.heightPt
82
67
  });
83
68
  }
84
69
  }
70
+ let tables;
71
+ if (worksheet.tablePartRelIds && worksheet.tablePartRelIds.length > 0) {
72
+ const wsRels = pkg.getPartRelationships(resolved.path);
73
+ const resolvedTables = [];
74
+ for (const rid of worksheet.tablePartRelIds) {
75
+ const rel = wsRels.find((r) => r.id === rid);
76
+ const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
77
+ const parsed = part ? parseTablePart(part.data) : void 0;
78
+ if (parsed) resolvedTables.push(resolveTableStyle(parsed, palette));
79
+ }
80
+ if (resolvedTables.length > 0) tables = resolvedTables;
81
+ }
82
+ const grid = tables ? {
83
+ ...worksheet,
84
+ tables
85
+ } : worksheet;
86
+ sheetsOut.push({
87
+ name: sheet.name,
88
+ grid,
89
+ ...charts.length > 0 ? { charts } : {}
90
+ });
85
91
  }
86
92
  const coreData = pkg.getPart(CORE_PROPS_PART);
87
93
  const info = infoFromCore(coreData ? parseCoreProperties(coreData) : void 0);
88
94
  return {
89
- doc: {
90
- kind: "flow",
91
- body: resolveBodyStyles(body, EMPTY_STYLE_SHEET),
92
- sections: [],
93
- ...firstSheetSection ? { section: firstSheetSection } : {},
94
- styles: EMPTY_STYLE_SHEET,
95
- resources: new ResourceStore(),
96
- ...charts.size > 0 ? { charts } : {},
97
- ...info ? { info } : {}
98
- },
99
- losses: []
95
+ kind: "sheet",
96
+ sheets: sheetsOut,
97
+ styles,
98
+ sharedStrings,
99
+ definedNames,
100
+ date1904,
101
+ ...chartData.size > 0 ? { chartData } : {},
102
+ resources: new ResourceStore(),
103
+ ...info ? { info } : {}
100
104
  };
101
105
  }
102
- function buildXlsxColorResolver(pkg, workbookRels) {
106
+ function buildThemePalette(pkg, workbookRels) {
103
107
  const palette = new Map(DEFAULT_THEME_PALETTE);
104
108
  for (const rel of workbookRels) {
105
109
  if (!isOoxmlRel(rel.type, "theme")) continue;
@@ -108,14 +112,45 @@ function buildXlsxColorResolver(pkg, workbookRels) {
108
112
  for (const [slot, hex] of parseTheme(resolved.data)) palette.set(slot, hex);
109
113
  break;
110
114
  }
111
- return makeColorResolver(palette);
115
+ return palette;
116
+ }
117
+ function resolveTableStyle(t, palette) {
118
+ const m = t.styleName ? /TableStyle(Light|Medium|Dark)(\d+)/i.exec(t.styleName) : null;
119
+ if (!m) return t;
120
+ const kind = m[1].toLowerCase();
121
+ const column = (Number(m[2]) - 1) % 7;
122
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
123
+ if (kind === "light") return {
124
+ ...t,
125
+ headerHex: lighten(base, .6),
126
+ bandHex: lighten(base, .85)
127
+ };
128
+ return {
129
+ ...t,
130
+ headerHex: base,
131
+ bandHex: lighten(base, .8),
132
+ headerTextHex: "FFFFFF"
133
+ };
134
+ }
135
+ function lighten(hex, amount) {
136
+ const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
137
+ const n = m ? parseInt(m[1], 16) : 4485828;
138
+ const ch = (shift) => {
139
+ const c = n >> shift & 255;
140
+ return Math.round(c + (255 - c) * amount);
141
+ };
142
+ const hx = (c) => c.toString(16).padStart(2, "0").toUpperCase();
143
+ return `${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
112
144
  }
113
145
  var xlsxReader = {
114
146
  id: "xlsx",
115
- produces: "flow",
147
+ produces: "sheet",
116
148
  supports: new Set([FEATURES.text, FEATURES.tables]),
117
149
  sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && bytesInclude(bytes, "xl/workbook.xml"),
118
- read: (bytes) => readXlsx(bytes)
150
+ read: (bytes) => ({
151
+ doc: readXlsxToSheetDoc(bytes),
152
+ losses: []
153
+ })
119
154
  };
120
155
  function infoFromCore(core) {
121
156
  if (!core) return void 0;
@@ -0,0 +1,4 @@
1
+ import { DocumentWriter, WriteResult } from '../core/ir/adapters.js';
2
+ import { SheetDoc } from '../core/ir/sheet.js';
3
+ export declare function writeXlsx(sheet: SheetDoc): WriteResult;
4
+ export declare const xlsxWriter: DocumentWriter<SheetDoc>;
@@ -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;
@@ -25,5 +25,6 @@ export type { FontProvider, FontRequest, FontAnswer } from './core/fonts/provide
25
25
  export { svgWriter, writeSvg } from './svg/svg-writer.js';
26
26
  export type { SvgWriteOptions } from './svg/svg-writer.js';
27
27
  export { htmlWriter, writeHtml } from './html/html-writer.js';
28
+ export { docxWriter, writeDocx } from './word/docx-writer.js';
28
29
  export { layoutStyledDocument } from './layout/styled-layout.js';
29
30
  export type { LaidOutDocument } from './layout/page-doc.js';
package/dist/esm/index.js CHANGED
@@ -15,8 +15,9 @@ import { createLanguageHyphenator, getHyphenator } from "./core/hyphenation/inde
15
15
  import { docxReader, readDocx } from "./word/docx-reader.js";
16
16
  import { readXlsx, xlsxReader } from "./excel/xlsx-reader.js";
17
17
  import { NO_FONT, callerFontProvider, chainProviders, embeddedDocFontProvider, isEmbeddingRestricted, localFontProvider, readOs2FsType, remoteFontProvider } from "./core/fonts/provider.js";
18
+ import { docxWriter, writeDocx } from "./word/docx-writer.js";
18
19
  import { htmlWriter, writeHtml } from "./html/html-writer.js";
19
20
  import { svgWriter, writeSvg } from "./svg/svg-writer.js";
20
21
  import { createConverter } from "./core/converter/facade.js";
21
22
  import { Ream } from "./core/converter/ream.js";
22
- export { ConversionLossError, FEATURES, FontRegistry, NO_FONT, Ream, ResourceStore, callerFontProvider, chainProviders, createConverter, createHyphenator, createLanguageHyphenator, docxReader, eighthPtToPt, embeddedDocFontProvider, emuToPt, fetchFontSet, formatLoss, getHyphenator, halfPtToPt, htmlWriter, inchToPt, isEmbeddingRestricted, layoutStyledDocument, localFontProvider, mmToPt, parseTtf, pt, pxToPt, readDocx, readOs2FsType, readXlsx, remoteFontProvider, renderStyledPdf, resolveFamilyKey, signPdf, splitPatternBundle, subsetTtf, svgWriter, twipsToPt, writeHtml, writeSvg, xlsxReader };
23
+ export { ConversionLossError, FEATURES, FontRegistry, NO_FONT, Ream, ResourceStore, callerFontProvider, chainProviders, createConverter, createHyphenator, createLanguageHyphenator, docxReader, docxWriter, eighthPtToPt, embeddedDocFontProvider, emuToPt, fetchFontSet, formatLoss, getHyphenator, halfPtToPt, htmlWriter, inchToPt, isEmbeddingRestricted, layoutStyledDocument, localFontProvider, mmToPt, parseTtf, pt, pxToPt, readDocx, readOs2FsType, readXlsx, remoteFontProvider, renderStyledPdf, resolveFamilyKey, signPdf, splitPatternBundle, subsetTtf, svgWriter, twipsToPt, writeDocx, writeHtml, writeSvg, xlsxReader };