reamkit 1.12.0 → 1.14.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 (35) hide show
  1. package/dist/esm/core/document-model/types.d.ts +3 -0
  2. package/dist/esm/core/ir/sheet.d.ts +49 -3
  3. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  4. package/dist/esm/core/spreadsheet-model/types.d.ts +80 -1
  5. package/dist/esm/excel/comments-parser.d.ts +4 -0
  6. package/dist/esm/excel/comments-parser.js +104 -0
  7. package/dist/esm/excel/conditional-format.d.ts +2 -2
  8. package/dist/esm/excel/conditional-format.js +113 -11
  9. package/dist/esm/excel/form-control-parser.d.ts +6 -0
  10. package/dist/esm/excel/form-control-parser.js +29 -0
  11. package/dist/esm/excel/header-footer.d.ts +2 -0
  12. package/dist/esm/excel/header-footer.js +127 -0
  13. package/dist/esm/excel/index.d.ts +1 -1
  14. package/dist/esm/excel/print-model.d.ts +7 -2
  15. package/dist/esm/excel/print-model.js +218 -19
  16. package/dist/esm/excel/shared-strings-parser.d.ts +6 -1
  17. package/dist/esm/excel/shared-strings-parser.js +91 -12
  18. package/dist/esm/excel/sheet-drawing.d.ts +13 -1
  19. package/dist/esm/excel/sheet-drawing.js +47 -14
  20. package/dist/esm/excel/sheet-shape-parser.d.ts +4 -0
  21. package/dist/esm/excel/sheet-shape-parser.js +96 -0
  22. package/dist/esm/excel/sheet-to-flow.js +130 -3
  23. package/dist/esm/excel/slicer-parser.d.ts +15 -0
  24. package/dist/esm/excel/slicer-parser.js +76 -0
  25. package/dist/esm/excel/styles-parser.js +37 -0
  26. package/dist/esm/excel/table-parser.d.ts +10 -0
  27. package/dist/esm/excel/table-parser.js +36 -4
  28. package/dist/esm/excel/worksheet-parser.js +181 -1
  29. package/dist/esm/excel/xlsx-reader.js +203 -17
  30. package/dist/esm/excel/xlsx-writer.js +19 -3
  31. package/dist/esm/html/html-writer.js +4 -0
  32. package/dist/esm/layout/styled-layout.js +72 -1
  33. package/dist/esm/pptx/slide-parser.d.ts +4 -2
  34. package/dist/esm/pptx/slide-parser.js +1 -1
  35. package/package.json +1 -1
@@ -8,20 +8,31 @@ import { isOoxmlRel } from "../core/opc/relationship-types.js";
8
8
  import { OpcPackage } from "../core/opc/package.js";
9
9
  import { parseCoreProperties } from "../core/opc/core-properties.js";
10
10
  import "../core/opc/index.js";
11
+ import { parseAreaRef } from "./defined-name-ref.js";
11
12
  import { parseSharedStrings } from "./shared-strings-parser.js";
12
13
  import { parseWorkbook } from "./workbook-parser.js";
13
14
  import { parseWorksheet } from "./worksheet-parser.js";
14
15
  import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
15
16
  import "./index.js";
17
+ import { resolveCellText } from "./print-model.js";
16
18
  import { parseSheetDrawing } from "./sheet-drawing.js";
17
- import { parseTablePart } from "./table-parser.js";
19
+ import { parseTablePartFull } from "./table-parser.js";
18
20
  import { parsePivotTablePart } from "./pivot-table-parser.js";
21
+ import { parseSlicerCachePart, parseSlicerPart } from "./slicer-parser.js";
22
+ import { parseLegacyComments, parsePersons, parseThreadedComments } from "./comments-parser.js";
23
+ import { parseFormControlProps } from "./form-control-parser.js";
24
+ import { parseSheetShapes } from "./sheet-shape-parser.js";
19
25
  import { projectSheetDoc } from "./sheet-to-flow.js";
20
26
  //#region src/excel/xlsx-reader.ts
21
27
  var WORKBOOK_PART = "xl/workbook.xml";
22
28
  var SHARED_STRINGS_PART = "xl/sharedStrings.xml";
23
29
  var STYLES_PART = "xl/styles.xml";
24
30
  var CORE_PROPS_PART = "docProps/core.xml";
31
+ var SLICER_REL_TAIL = "/slicer";
32
+ var SLICER_CACHE_REL_TAIL = "/slicerCache";
33
+ var THREADED_COMMENTS_REL_TAIL = "/threadedComment";
34
+ var PERSON_REL_TAIL = "/person";
35
+ var MAX_SLICER_ITEMS = 256;
25
36
  function readXlsx(xlsx) {
26
37
  return {
27
38
  doc: projectSheetDoc(readXlsxToSheetDoc(xlsx)),
@@ -35,14 +46,26 @@ function readXlsxToSheetDoc(xlsx) {
35
46
  const { sheets, date1904, definedNames } = parseWorkbook(workbookData);
36
47
  if (sheets.length === 0) throw new Error("xlsx has no sheets");
37
48
  const sharedStringsData = pkg.getPart(SHARED_STRINGS_PART);
38
- const sharedStrings = sharedStringsData ? parseSharedStrings(sharedStringsData) : [];
49
+ const { texts: sharedStrings, runs: sharedStringRuns } = sharedStringsData ? parseSharedStrings(sharedStringsData) : {
50
+ texts: [],
51
+ runs: []
52
+ };
39
53
  const stylesData = pkg.getPart(STYLES_PART);
40
54
  const styles = stylesData ? parseXlsxStyles(stylesData) : EMPTY_XLSX_STYLES;
41
55
  const workbookRels = pkg.getPartRelationships(WORKBOOK_PART);
56
+ const persons = /* @__PURE__ */ new Map();
57
+ for (const rel of workbookRels) {
58
+ if (!rel.type.endsWith(PERSON_REL_TAIL)) continue;
59
+ const part = pkg.resolveRelatedPart(WORKBOOK_PART, rel);
60
+ if (part) for (const [id, name] of parsePersons(part.data)) persons.set(id, name);
61
+ }
42
62
  const chartData = /* @__PURE__ */ new Map();
63
+ const resources = new ResourceStore();
43
64
  const palette = buildThemePalette(pkg, workbookRels);
44
65
  const resolveColor = makeColorResolver(palette);
45
66
  const sheetsOut = [];
67
+ const tableIndex = /* @__PURE__ */ new Map();
68
+ const pendingSlicers = [];
46
69
  for (let sheetIdx = 0; sheetIdx < sheets.length; sheetIdx++) {
47
70
  const sheet = sheets[sheetIdx];
48
71
  const sheetRel = workbookRels.find((r) => r.id === sheet.relationshipId);
@@ -51,21 +74,39 @@ function readXlsxToSheetDoc(xlsx) {
51
74
  if (!resolved) continue;
52
75
  const worksheet = parseWorksheet(resolved.data);
53
76
  const charts = [];
77
+ const images = [];
78
+ let shapes;
54
79
  if (worksheet.drawingRelId) {
55
80
  const drawingRel = pkg.getPartRelationships(resolved.path).find((r) => r.id === worksheet.drawingRelId);
56
81
  const drawing = drawingRel ? pkg.resolveRelatedPart(resolved.path, drawingRel) : void 0;
57
- if (drawing) for (const ref of parseSheetDrawing(drawing.data, drawing.path, pkg, worksheet)) {
58
- if (!chartData.has(ref.chartPartPath)) {
59
- const chartXml = pkg.getPart(ref.chartPartPath);
60
- const parsed = chartXml ? parseChart(chartXml, resolveColor) : null;
61
- if (!parsed) continue;
62
- chartData.set(ref.chartPartPath, withChartColorStyle(parsed, pkg, ref.chartPartPath, resolveColor));
82
+ if (drawing) {
83
+ const { charts: chartRefs, pictures } = parseSheetDrawing(drawing.data, drawing.path, pkg, worksheet);
84
+ for (const ref of chartRefs) {
85
+ if (!chartData.has(ref.chartPartPath)) {
86
+ const chartXml = pkg.getPart(ref.chartPartPath);
87
+ const parsed = chartXml ? parseChart(chartXml, resolveColor) : null;
88
+ if (!parsed) continue;
89
+ chartData.set(ref.chartPartPath, withChartColorStyle(parsed, pkg, ref.chartPartPath, resolveColor));
90
+ }
91
+ charts.push({
92
+ chartPartPath: ref.chartPartPath,
93
+ widthPt: ref.widthPt,
94
+ heightPt: ref.heightPt
95
+ });
96
+ }
97
+ for (const pic of pictures) {
98
+ const bytes = pkg.getPart(pic.imagePartPath);
99
+ if (!bytes) continue;
100
+ images.push({
101
+ resourceId: resources.put(bytes),
102
+ widthPt: pic.widthPt,
103
+ heightPt: pic.heightPt
104
+ });
105
+ }
106
+ if (bytesInclude(drawing.data, ":sp>") || bytesInclude(drawing.data, ":sp ")) {
107
+ const parsed = parseSheetShapes(drawing.data, worksheet, resolveColor);
108
+ if (parsed.length > 0) shapes = parsed;
63
109
  }
64
- charts.push({
65
- chartPartPath: ref.chartPartPath,
66
- widthPt: ref.widthPt,
67
- heightPt: ref.heightPt
68
- });
69
110
  }
70
111
  }
71
112
  let tables;
@@ -75,11 +116,31 @@ function readXlsxToSheetDoc(xlsx) {
75
116
  for (const rid of worksheet.tablePartRelIds) {
76
117
  const rel = wsRels.find((r) => r.id === rid);
77
118
  const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
78
- const parsed = part ? parseTablePart(part.data) : void 0;
79
- if (parsed) resolvedTables.push(resolveTableStyle(parsed, palette));
119
+ const full = part ? parseTablePartFull(part.data) : void 0;
120
+ if (!full) continue;
121
+ resolvedTables.push(resolveTableStyle(full.table, palette));
122
+ if (full.id !== void 0) tableIndex.set(full.id, {
123
+ cells: worksheet.cells,
124
+ ref: full.table.ref,
125
+ headerRows: full.table.headerRowCount,
126
+ filters: full.filters
127
+ });
80
128
  }
81
129
  if (resolvedTables.length > 0) tables = resolvedTables;
82
130
  }
131
+ {
132
+ const wsRels = pkg.getPartRelationships(resolved.path);
133
+ const defs = [];
134
+ for (const rel of wsRels) {
135
+ if (!rel.type.endsWith(SLICER_REL_TAIL)) continue;
136
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
137
+ if (part) defs.push(...parseSlicerPart(part.data));
138
+ }
139
+ if (defs.length > 0) pendingSlicers.push({
140
+ outIdx: sheetsOut.length,
141
+ defs
142
+ });
143
+ }
83
144
  let pivotTables;
84
145
  {
85
146
  const resolvedPivots = [];
@@ -91,6 +152,59 @@ function readXlsxToSheetDoc(xlsx) {
91
152
  }
92
153
  if (resolvedPivots.length > 0) pivotTables = resolvedPivots;
93
154
  }
155
+ let hyperlinks;
156
+ if (worksheet.hyperlinks && worksheet.hyperlinks.length > 0) {
157
+ const wsRels = pkg.getPartRelationships(resolved.path);
158
+ const resolvedLinks = [];
159
+ for (const h of worksheet.hyperlinks) {
160
+ if (h.relId === void 0) continue;
161
+ const rel = wsRels.find((r) => r.id === h.relId);
162
+ if (!rel || rel.targetMode !== "External" || !rel.target) continue;
163
+ const area = parseAreaRef(h.ref);
164
+ if (!area) continue;
165
+ resolvedLinks.push({
166
+ ref: {
167
+ startColumn: area.startColumn,
168
+ startRow: area.startRow,
169
+ endColumn: area.endColumn,
170
+ endRow: area.endRow
171
+ },
172
+ url: rel.target
173
+ });
174
+ }
175
+ if (resolvedLinks.length > 0) hyperlinks = resolvedLinks;
176
+ }
177
+ let comments;
178
+ {
179
+ const wsRels = pkg.getPartRelationships(resolved.path);
180
+ const resolvedComments = [];
181
+ for (const rel of wsRels) {
182
+ if (!isOoxmlRel(rel.type, "comments")) continue;
183
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
184
+ if (part) resolvedComments.push(...parseLegacyComments(part.data));
185
+ }
186
+ for (const rel of wsRels) {
187
+ if (!rel.type.endsWith(THREADED_COMMENTS_REL_TAIL)) continue;
188
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
189
+ if (part) resolvedComments.push(...parseThreadedComments(part.data, persons));
190
+ }
191
+ if (resolvedComments.length > 0) comments = resolvedComments;
192
+ }
193
+ let formControls;
194
+ if (worksheet.formControls && worksheet.formControls.length > 0) {
195
+ const wsRels = pkg.getPartRelationships(resolved.path);
196
+ const resolvedControls = [];
197
+ for (const fc of worksheet.formControls) {
198
+ const rel = wsRels.find((r) => r.id === fc.relId);
199
+ const part = rel ? pkg.resolveRelatedPart(resolved.path, rel) : void 0;
200
+ const props = part ? parseFormControlProps(part.data) : {};
201
+ resolvedControls.push({
202
+ ...fc.name ? { name: fc.name } : {},
203
+ ...props
204
+ });
205
+ }
206
+ if (resolvedControls.length > 0) formControls = resolvedControls;
207
+ }
94
208
  const grid = tables || pivotTables ? {
95
209
  ...worksheet,
96
210
  ...tables ? { tables } : {},
@@ -99,9 +213,32 @@ function readXlsxToSheetDoc(xlsx) {
99
213
  sheetsOut.push({
100
214
  name: sheet.name,
101
215
  grid,
102
- ...charts.length > 0 ? { charts } : {}
216
+ ...charts.length > 0 ? { charts } : {},
217
+ ...images.length > 0 ? { images } : {},
218
+ ...shapes ? { shapes } : {},
219
+ ...hyperlinks ? { hyperlinks } : {},
220
+ ...comments ? { comments } : {},
221
+ ...formControls ? { formControls } : {}
103
222
  });
104
223
  }
224
+ if (pendingSlicers.length > 0) {
225
+ const cacheByName = /* @__PURE__ */ new Map();
226
+ for (const rel of workbookRels) {
227
+ if (!rel.type.endsWith(SLICER_CACHE_REL_TAIL)) continue;
228
+ const part = pkg.resolveRelatedPart(WORKBOOK_PART, rel);
229
+ const cache = part ? parseSlicerCachePart(part.data) : void 0;
230
+ if (cache) cacheByName.set(cache.name, cache);
231
+ }
232
+ for (const { outIdx, defs } of pendingSlicers) {
233
+ const sheet = sheetsOut[outIdx];
234
+ if (!sheet) continue;
235
+ const slicers = defs.map((def) => resolveSlicer(def, cacheByName.get(def.cacheName), tableIndex, styles, sharedStrings, date1904, palette));
236
+ sheetsOut[outIdx] = {
237
+ ...sheet,
238
+ slicers
239
+ };
240
+ }
241
+ }
105
242
  const coreData = pkg.getPart(CORE_PROPS_PART);
106
243
  const info = infoFromCore(coreData ? parseCoreProperties(coreData) : void 0);
107
244
  return {
@@ -109,10 +246,11 @@ function readXlsxToSheetDoc(xlsx) {
109
246
  sheets: sheetsOut,
110
247
  styles,
111
248
  sharedStrings,
249
+ ...sharedStringRuns.some((r) => r !== void 0) ? { sharedStringRuns } : {},
112
250
  definedNames,
113
251
  date1904,
114
252
  ...chartData.size > 0 ? { chartData } : {},
115
- resources: new ResourceStore(),
253
+ resources,
116
254
  ...info ? { info } : {}
117
255
  };
118
256
  }
@@ -163,6 +301,54 @@ function resolvePivotStyle(p, palette) {
163
301
  headerTextHex: "FFFFFF"
164
302
  };
165
303
  }
304
+ function resolveSlicer(def, cache, tableIndex, styles, sharedStrings, date1904, palette) {
305
+ const items = cache ? resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) : [];
306
+ return {
307
+ caption: def.caption,
308
+ columnCount: def.columnCount,
309
+ items,
310
+ ...resolveSlicerStyle(def.styleName, palette)
311
+ };
312
+ }
313
+ function resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) {
314
+ const loc = cache.tableId !== void 0 ? tableIndex.get(cache.tableId) : void 0;
315
+ if (!loc || cache.columnId === void 0) return [];
316
+ const colOffset = cache.columnId - 1;
317
+ const absCol = loc.ref.startColumn + colOffset;
318
+ if (absCol < loc.ref.startColumn || absCol > loc.ref.endColumn) return [];
319
+ const firstDataRow = loc.ref.startRow + Math.max(1, loc.headerRows);
320
+ const filter = loc.filters.find((f) => f.colId === colOffset);
321
+ const kept = filter ? new Set(filter.values) : void 0;
322
+ const byRow = /* @__PURE__ */ new Map();
323
+ for (const c of loc.cells) {
324
+ if (c.column !== absCol || c.row < firstDataRow || c.row > loc.ref.endRow) continue;
325
+ byRow.set(c.row, resolveCellText(c, sharedStrings, styles, date1904));
326
+ }
327
+ const seen = /* @__PURE__ */ new Set();
328
+ const out = [];
329
+ for (const row of [...byRow.keys()].sort((a, b) => a - b)) {
330
+ const label = byRow.get(row);
331
+ if (label === "" || seen.has(label)) continue;
332
+ seen.add(label);
333
+ out.push({
334
+ label,
335
+ selected: kept ? kept.has(label) : true
336
+ });
337
+ if (out.length >= MAX_SLICER_ITEMS) break;
338
+ }
339
+ return out;
340
+ }
341
+ function resolveSlicerStyle(styleName, palette) {
342
+ const m = styleName ? /SlicerStyle[A-Za-z]*?(\d+)/i.exec(styleName) : null;
343
+ const column = m ? (Number(m[1]) - 1) % 7 : 1;
344
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
345
+ return {
346
+ headerHex: base,
347
+ headerTextHex: "FFFFFF",
348
+ selectedHex: base,
349
+ selectedTextHex: "FFFFFF"
350
+ };
351
+ }
166
352
  function lighten(hex, amount) {
167
353
  const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
168
354
  const n = m ? parseInt(m[1], 16) : 4485828;
@@ -200,7 +200,7 @@ function worksheetXml(grid, tableRelIds = [], drawingRelId) {
200
200
  return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
201
201
  }).join("");
202
202
  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>` : "";
203
- return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + 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) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
203
+ return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + dataValidationsXml(grid.dataValidations) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
204
204
  }
205
205
  function sheetViewsXml(pane) {
206
206
  if (!pane || pane.frozenRows <= 0 && pane.frozenCols <= 0) return "";
@@ -230,11 +230,27 @@ function cfRuleXml(rule) {
230
230
  case "colorScale": return `<cfRule type="colorScale"${p}><colorScale>` + rule.cfvos.map(cfvoXml).join("") + rule.colorsHex.map((c) => `<color rgb="FF${c}"/>`).join("") + "</colorScale></cfRule>";
231
231
  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>`;
232
232
  case "iconSet": return `<cfRule type="iconSet"${p}><iconSet iconSet="${rule.iconSet}"` + (rule.reverse ? " reverse=\"1\"" : "") + ">" + rule.cfvos.map(cfvoXml).join("") + "</iconSet></cfRule>";
233
+ case "top10": return `<cfRule type="top10"${p} dxfId="${rule.dxfId}" rank="${rule.rank}"` + (rule.percent ? " percent=\"1\"" : "") + (rule.bottom ? " bottom=\"1\"" : "") + "/>";
234
+ case "aboveAverage": return `<cfRule type="aboveAverage"${p} dxfId="${rule.dxfId}"` + (rule.aboveAverage ? "" : " aboveAverage=\"0\"") + (rule.equalAverage ? " equalAverage=\"1\"" : "") + (rule.stdDev !== void 0 ? ` stdDev="${rule.stdDev}"` : "") + "/>";
235
+ case "duplicateValues":
236
+ case "uniqueValues": return `<cfRule type="${rule.type}"${p} dxfId="${rule.dxfId}"/>`;
237
+ case "containsText":
238
+ case "notContainsText":
239
+ case "beginsWith":
240
+ case "endsWith": return `<cfRule type="${rule.type}"${p} operator="${rule.type}" dxfId="${rule.dxfId}" text="${escapeAttr(rule.text)}">` + (rule.formula !== void 0 ? `<formula>${escapeText(rule.formula)}</formula>` : "") + "</cfRule>";
233
241
  }
234
242
  }
235
243
  function cfvoXml(cfvo) {
236
244
  return `<cfvo type="${cfvo.type}"${cfvo.val !== void 0 ? ` val="${escapeAttr(cfvo.val)}"` : ""}/>`;
237
245
  }
246
+ function dataValidationsXml(dvs) {
247
+ if (!dvs || dvs.length === 0) return "";
248
+ return `<dataValidations count="${dvs.length}">${dvs.map(dataValidationXml).join("")}</dataValidations>`;
249
+ }
250
+ function dataValidationXml(dv) {
251
+ const sqref = dv.ranges.map(rangeRef).join(" ");
252
+ return `<dataValidation${(dv.type !== "none" ? ` type="${dv.type}"` : "") + (dv.operator !== void 0 ? ` operator="${escapeAttr(dv.operator)}"` : "") + (dv.allowBlank ? " allowBlank=\"1\"" : "") + (dv.showDropDown ? " showDropDown=\"1\"" : "") + (dv.showInputMessage ? " showInputMessage=\"1\"" : "") + (dv.showErrorMessage ? " showErrorMessage=\"1\"" : "") + (dv.errorStyle !== void 0 ? ` errorStyle="${escapeAttr(dv.errorStyle)}"` : "") + (dv.promptTitle !== void 0 ? ` promptTitle="${escapeAttr(dv.promptTitle)}"` : "") + (dv.prompt !== void 0 ? ` prompt="${escapeAttr(dv.prompt)}"` : "") + (dv.errorTitle !== void 0 ? ` errorTitle="${escapeAttr(dv.errorTitle)}"` : "") + (dv.error !== void 0 ? ` error="${escapeAttr(dv.error)}"` : "") + ` sqref="${sqref}"`}>${(dv.formula1 !== void 0 ? `<formula1>${escapeText(dv.formula1)}</formula1>` : "") + (dv.formula2 !== void 0 ? `<formula2>${escapeText(dv.formula2)}</formula2>` : "")}</dataValidation>`;
253
+ }
238
254
  var X14_NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
239
255
  var XM_NS = "http://schemas.microsoft.com/office/excel/2006/main";
240
256
  function sparklineExtXml(sparklines) {
@@ -305,7 +321,7 @@ function patternFill(f) {
305
321
  return `<patternFill${f.patternType ? ` patternType="${f.patternType}"` : ""}>${inner}</patternFill>`;
306
322
  }
307
323
  function borderXml(b) {
308
- return `<border>${edgeXml("left", b.left)}${edgeXml("right", b.right)}${edgeXml("top", b.top)}${edgeXml("bottom", b.bottom)}</border>`;
324
+ return `<border${b.diagonal ? `${b.diagonalUp ? " diagonalUp=\"1\"" : ""}${b.diagonalDown ? " diagonalDown=\"1\"" : ""}` : ""}>${edgeXml("left", b.left)}${edgeXml("right", b.right)}${edgeXml("top", b.top)}${edgeXml("bottom", b.bottom)}${edgeXml("diagonal", b.diagonal)}</border>`;
309
325
  }
310
326
  function edgeXml(name, edge) {
311
327
  if (!edge || !edge.style) return `<${name}/>`;
@@ -315,7 +331,7 @@ function edgeXml(name, edge) {
315
331
  function cellXfXml(xf) {
316
332
  const apply = applyAttr("applyNumberFormat", xf.applyNumberFormat) + applyAttr("applyFont", xf.applyFont) + applyAttr("applyFill", xf.applyFill) + applyAttr("applyBorder", xf.applyBorder) + applyAttr("applyAlignment", xf.applyAlignment);
317
333
  const a = xf.alignment;
318
- const alignment = a ? `<alignment${a.horizontal ? ` horizontal="${a.horizontal}"` : ""}${a.vertical ? ` vertical="${a.vertical}"` : ""}${a.wrapText ? " wrapText=\"1\"" : ""}/>` : "";
334
+ const alignment = a ? `<alignment${a.horizontal ? ` horizontal="${a.horizontal}"` : ""}${a.vertical ? ` vertical="${a.vertical}"` : ""}${a.wrapText ? " wrapText=\"1\"" : ""}${a.textRotation !== void 0 ? ` textRotation="${a.textRotation}"` : ""}${a.indent !== void 0 ? ` indent="${a.indent}"` : ""}${a.shrinkToFit ? " shrinkToFit=\"1\"" : ""}/>` : "";
319
335
  return `<xf numFmtId="${xf.numFmtId}" fontId="${xf.fontId}" fillId="${xf.fillId}" borderId="${xf.borderId}"${apply}>` + alignment + "</xf>";
320
336
  }
321
337
  function applyAttr(name, value) {
@@ -496,6 +496,7 @@ function emitCell(out, cell, table, ctx, pos) {
496
496
  if (!cell.properties.shading) css.push("background-color:#fff");
497
497
  }
498
498
  out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
499
+ if (cell.properties.dropdown) out.push(cellDropdownSvg());
499
500
  if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
500
501
  if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
501
502
  for (const child of cell.content) emitBlock(out, child, ctx);
@@ -514,6 +515,9 @@ function cellSparklineSvg(sp) {
514
515
  }
515
516
  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>`;
516
517
  }
518
+ function cellDropdownSvg() {
519
+ return "<svg width=\"13\" height=\"13\" viewBox=\"0 0 13 13\" style=\"float:right;margin-left:3px\"><rect x=\"0.5\" y=\"0.5\" width=\"12\" height=\"12\" fill=\"#F0F0F0\" stroke=\"#B0B0B0\"/><polygon points=\"3.4,5 9.6,5 6.5,8.6\" fill=\"#595959\"/></svg>";
520
+ }
517
521
  var ICON_EMPTY_HEX = "BFBFBF";
518
522
  function cellIconSvg(icon) {
519
523
  const fill = `#${icon.colorHex}`;
@@ -668,6 +668,24 @@ function rectAtPath(x, y, w, h) {
668
668
  var CF_ICON_SIZE_PT = 9;
669
669
  var CF_ICON_GUTTER_PT = 12;
670
670
  var CF_ICON_EMPTY_HEX = "BFBFBF";
671
+ var DROPDOWN_BUTTON_PT = 11;
672
+ var DROPDOWN_GUTTER_PT = 13;
673
+ var DROPDOWN_FILL_HEX = "F0F0F0";
674
+ var DROPDOWN_BORDER_HEX = "B0B0B0";
675
+ var DROPDOWN_ARROW_HEX = "595959";
676
+ function buildDropdownPrims(s) {
677
+ return [{
678
+ paths: [rectAtPath(0, 0, s, s)],
679
+ fillColorHex: DROPDOWN_FILL_HEX,
680
+ stroke: {
681
+ colorHex: DROPDOWN_BORDER_HEX,
682
+ widthPt: .5
683
+ }
684
+ }, {
685
+ paths: [trianglePath([s / 2, s * .34], [s * .26, s * .64], [s * .74, s * .64])],
686
+ fillColorHex: DROPDOWN_ARROW_HEX
687
+ }];
688
+ }
671
689
  function buildCellIconShape(icon, s) {
672
690
  const color = icon.colorHex;
673
691
  switch (icon.shape) {
@@ -1671,8 +1689,9 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1671
1689
  const padTopPt = cellMar?.top ?? tableMar?.top ?? 0;
1672
1690
  const padBottomPt = cellMar?.bottom ?? tableMar?.bottom ?? 0;
1673
1691
  const padLeftBase = cellMar?.left ?? tableMar?.left ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1674
- const padRightPt = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1692
+ const padRightBase = cellMar?.right ?? tableMar?.right ?? DEFAULT_CELL_PADDING_TWIPS * TWIP_TO_PT;
1675
1693
  const padLeftPt = padLeftBase + (cell.properties.icon ? CF_ICON_GUTTER_PT : 0);
1694
+ const padRightPt = padRightBase + (cell.properties.dropdown ? DROPDOWN_GUTTER_PT : 0);
1676
1695
  const innerWidth = Math.max(1, widthPt - padLeftPt - padRightPt);
1677
1696
  const lines = [];
1678
1697
  const nestedTables = [];
@@ -1705,6 +1724,7 @@ function layoutTableCell(cell, tableProps, widthPt, options, fontResources, imag
1705
1724
  ...cell.properties.dataBar ? { dataBar: cell.properties.dataBar } : {},
1706
1725
  ...cell.properties.icon ? { icon: cell.properties.icon } : {},
1707
1726
  ...cell.properties.sparkline ? { sparkline: cell.properties.sparkline } : {},
1727
+ ...cell.properties.dropdown ? { dropdown: true } : {},
1708
1728
  lines,
1709
1729
  ...nestedTables.length > 0 ? { nestedTables } : {},
1710
1730
  contentHeightPt,
@@ -2362,6 +2382,28 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
2362
2382
  });
2363
2383
  }
2364
2384
  }
2385
+ if (cell.dropdown && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2386
+ const btn = Math.min(DROPDOWN_BUTTON_PT, Math.max(0, row.heightPt - 2));
2387
+ if (btn > 1) {
2388
+ const btnTransform = flipTransform([
2389
+ 1,
2390
+ 0,
2391
+ 0,
2392
+ 1,
2393
+ cellX + cell.widthPt - btn - 1.5,
2394
+ rowBottom + (row.heightPt - btn) / 2
2395
+ ], pageHeight);
2396
+ for (const prim of buildDropdownPrims(btn)) out.push({
2397
+ type: "shape",
2398
+ shape: {
2399
+ paths: prim.paths,
2400
+ ...prim.fillColorHex ? { fillColorHex: prim.fillColorHex } : {},
2401
+ ...prim.stroke ? { stroke: prim.stroke } : {},
2402
+ transform: btnTransform
2403
+ }
2404
+ });
2405
+ }
2406
+ }
2365
2407
  if (cell.sparkline && cell.sparkline.values.length > 0 && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2366
2408
  const inset = 1.5;
2367
2409
  const sw = cell.widthPt - 2 * inset;
@@ -2388,6 +2430,35 @@ function emitRowChunk(out, row, marginLeft, cursorY, pageHeight, colCount, cellS
2388
2430
  }
2389
2431
  }
2390
2432
  emitCellBorders(out, cell, cellX, rowBottom, row.heightPt, pageHeight, row.rowIdx, row.rowCount, colCount);
2433
+ const diagDown = cell.borders.diagonalDown;
2434
+ const diagUp = cell.borders.diagonalUp;
2435
+ if ((diagDown || diagUp) && cell.mergeRole !== "middle" && cell.mergeRole !== "end") {
2436
+ const transform = flipTransform([
2437
+ 1,
2438
+ 0,
2439
+ 0,
2440
+ 1,
2441
+ cellX,
2442
+ rowBottom
2443
+ ], pageHeight);
2444
+ const w = cell.widthPt;
2445
+ const h = row.heightPt;
2446
+ const pushDiagonal = (b, y0, y1) => {
2447
+ out.push({
2448
+ type: "shape",
2449
+ shape: {
2450
+ paths: [new PathBuilder().moveTo(0, y0).lineTo(w, y1).build()],
2451
+ stroke: {
2452
+ colorHex: b.colorHex ?? "000000",
2453
+ widthPt: b.width ?? .75
2454
+ },
2455
+ transform
2456
+ }
2457
+ });
2458
+ };
2459
+ if (diagDown) pushDiagonal(diagDown, h, 0);
2460
+ if (diagUp) pushDiagonal(diagUp, 0, h);
2461
+ }
2391
2462
  if (cell.mergeRole === "middle" || cell.mergeRole === "end") continue;
2392
2463
  let textY = rowTop - cell.padTopPt;
2393
2464
  for (const line of cell.lines) {
@@ -1,9 +1,9 @@
1
- import { BodyElement, FloatAnchor, ShapeBlock, ShapeFill } from '../core/document-model/index.js';
1
+ import { BodyElement, FloatAnchor, ShapeBlock, ShapeFill, ShapeGeometry, ShapeTextBody } from '../core/document-model/index.js';
2
2
  import { ColorResolver } from '../core/drawingml/colors.js';
3
3
  import { Loss, Pt, ResourceId } from '../core/ir/index.js';
4
4
  import { PoNode } from '../core/po-helpers.js';
5
5
  import { PlaceholderCascade } from './placeholder-cascade.js';
6
- import { ShapeBoxEmu } from './sp-helpers.js';
6
+ import { PlaceholderRef, ShapeBoxEmu } from './sp-helpers.js';
7
7
  export interface SlideContext {
8
8
  readonly cascade?: PlaceholderCascade;
9
9
  readonly resolveImage?: (relId: string) => ResourceId | undefined;
@@ -21,4 +21,6 @@ export declare function noDiagramOverrideLoss(where?: string): Loss;
21
21
  export declare function parseDiagramDrawing(spTree: PoNode, transform: GroupTransform, makeFloat: (box: ShapeBoxEmu) => FloatAnchor, colors: ColorResolver, resolveLink: LinkResolver): Array<ShapeBlock>;
22
22
  export declare function parseBackgroundFill(bg: PoNode, colors: ColorResolver): ShapeFill | undefined;
23
23
  export declare function backdropElement(fill: ShapeFill, widthPt: Pt, heightPt: Pt): BodyElement;
24
+ export declare function parseGeometry(spPr: PoNode | undefined): ShapeGeometry;
25
+ export declare function parseTxBody(txBody: PoNode, ph: PlaceholderRef | undefined, cascade: PlaceholderCascade | undefined, colors: ColorResolver, resolveLink: LinkResolver): ShapeTextBody | undefined;
24
26
  export {};
@@ -424,4 +424,4 @@ function cellFillHex(tcPr, colors) {
424
424
  }
425
425
  }
426
426
  //#endregion
427
- export { backdropElement, diagramTransform, noDiagramOverrideLoss, parseBackgroundFill, parseDiagramDrawing, parseSlideShapes };
427
+ export { backdropElement, diagramTransform, noDiagramOverrideLoss, parseBackgroundFill, parseDiagramDrawing, parseGeometry, parseSlideShapes, parseTxBody };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and 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>",