reamkit 1.11.0 → 1.13.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 (42) hide show
  1. package/dist/esm/core/converter/facade.js +1 -1
  2. package/dist/esm/core/converter/project.js +1 -0
  3. package/dist/esm/core/document-model/index.d.ts +1 -1
  4. package/dist/esm/core/document-model/types.d.ts +24 -0
  5. package/dist/esm/core/ir/features.d.ts +1 -0
  6. package/dist/esm/core/ir/features.js +1 -0
  7. package/dist/esm/core/ir/flow.d.ts +3 -1
  8. package/dist/esm/core/ir/sheet.d.ts +14 -0
  9. package/dist/esm/core/po-helpers.d.ts +2 -0
  10. package/dist/esm/core/po-helpers.js +14 -1
  11. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  12. package/dist/esm/core/spreadsheet-model/types.d.ts +34 -0
  13. package/dist/esm/excel/index.d.ts +1 -1
  14. package/dist/esm/excel/pivot-table-parser.d.ts +2 -0
  15. package/dist/esm/excel/pivot-table-parser.js +68 -0
  16. package/dist/esm/excel/print-model.d.ts +5 -2
  17. package/dist/esm/excel/print-model.js +123 -9
  18. package/dist/esm/excel/sheet-to-flow.js +5 -1
  19. package/dist/esm/excel/slicer-parser.d.ts +15 -0
  20. package/dist/esm/excel/slicer-parser.js +76 -0
  21. package/dist/esm/excel/table-parser.d.ts +10 -0
  22. package/dist/esm/excel/table-parser.js +36 -4
  23. package/dist/esm/excel/worksheet-parser.js +56 -0
  24. package/dist/esm/excel/xlsx-reader.js +129 -5
  25. package/dist/esm/excel/xlsx-writer.js +9 -1
  26. package/dist/esm/html/html-writer.js +56 -2
  27. package/dist/esm/layout/page-doc.d.ts +1 -0
  28. package/dist/esm/layout/styled-layout.d.ts +8 -1
  29. package/dist/esm/layout/styled-layout.js +154 -6
  30. package/dist/esm/pdf/styled-page-emitter.js +38 -0
  31. package/dist/esm/pptx/pptx-reader.d.ts +2 -0
  32. package/dist/esm/pptx/pptx-reader.js +30 -3
  33. package/dist/esm/pptx/slide-parser.d.ts +8 -2
  34. package/dist/esm/pptx/slide-parser.js +80 -12
  35. package/dist/esm/word/document-parser.d.ts +17 -2
  36. package/dist/esm/word/document-parser.js +178 -18
  37. package/dist/esm/word/docx-reader.js +54 -4
  38. package/dist/esm/word/docx-writer.js +84 -1
  39. package/dist/esm/word/drawing-parser.d.ts +7 -0
  40. package/dist/esm/word/drawing-parser.js +13 -0
  41. package/dist/esm/word/index.d.ts +2 -1
  42. package/package.json +1 -1
@@ -9,7 +9,7 @@ var parser = new XMLParser({
9
9
  parseTagValue: false,
10
10
  removeNSPrefix: true
11
11
  });
12
- function parseTablePart(data) {
12
+ function parseTablePartFull(data) {
13
13
  const t = asObj(parser.parse(decoder.decode(data))["table"]);
14
14
  if (!t) return void 0;
15
15
  const refStr = strAttr(t, "ref");
@@ -18,7 +18,8 @@ function parseTablePart(data) {
18
18
  const si = asObj(t["tableStyleInfo"]);
19
19
  const name = strAttr(t, "name");
20
20
  const styleName = si ? strAttr(si, "name") : void 0;
21
- return {
21
+ const af = asObj(t["autoFilter"]);
22
+ const table = {
22
23
  ref,
23
24
  ...name ? { name } : {},
24
25
  ...styleName ? { styleName } : {},
@@ -27,9 +28,40 @@ function parseTablePart(data) {
27
28
  showColumnStripes: si ? boolAttr(si, "showColumnStripes") : false,
28
29
  showFirstColumn: si ? boolAttr(si, "showFirstColumn") : false,
29
30
  showLastColumn: si ? boolAttr(si, "showLastColumn") : false,
30
- autoFilter: asObj(t["autoFilter"]) !== void 0
31
+ autoFilter: af !== void 0
32
+ };
33
+ const id = numAttr(t, "id");
34
+ return {
35
+ table,
36
+ ...id !== void 0 ? { id } : {},
37
+ filters: parseFilterColumns(af)
31
38
  };
32
39
  }
40
+ function parseFilterColumns(af) {
41
+ if (!af) return [];
42
+ const out = [];
43
+ for (const fc of asArray(af["filterColumn"])) {
44
+ const col = asObj(fc);
45
+ if (!col) continue;
46
+ const colId = numAttr(col, "colId");
47
+ if (colId === void 0) continue;
48
+ const filters = asObj(col["filters"]);
49
+ if (!filters) continue;
50
+ const values = [];
51
+ for (const f of asArray(filters["filter"])) {
52
+ const v = asObj(f) ? strAttr(asObj(f), "val") : void 0;
53
+ if (v !== void 0) values.push(v);
54
+ }
55
+ out.push({
56
+ colId,
57
+ values
58
+ });
59
+ }
60
+ return out;
61
+ }
62
+ function asArray(v) {
63
+ return Array.isArray(v) ? v : v === void 0 || v === null ? [] : [v];
64
+ }
33
65
  function asObj(v) {
34
66
  return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
35
67
  }
@@ -48,4 +80,4 @@ function boolAttr(obj, name) {
48
80
  return v === "1" || v === "true";
49
81
  }
50
82
  //#endregion
51
- export { parseTablePart };
83
+ export { parseTablePartFull };
@@ -37,6 +37,7 @@ function parseWorksheet(data) {
37
37
  const drawingNode = wsObj["drawing"];
38
38
  const drawingRelId = drawingNode && typeof drawingNode === "object" ? strAttr(drawingNode, "id") : void 0;
39
39
  const conditionalFormats = parseConditionalFormatting(wsObj);
40
+ const dataValidations = parseDataValidations(wsObj);
40
41
  const sparklines = parseSparklines(wsObj);
41
42
  const tablePartRelIds = parseTableParts(wsObj);
42
43
  const printModel = {
@@ -49,6 +50,7 @@ function parseWorksheet(data) {
49
50
  ...pane ? { pane } : {},
50
51
  ...drawingRelId !== void 0 ? { drawingRelId } : {},
51
52
  ...conditionalFormats.length > 0 ? { conditionalFormats } : {},
53
+ ...dataValidations.length > 0 ? { dataValidations } : {},
52
54
  ...sparklines.length > 0 ? { sparklines } : {},
53
55
  ...tablePartRelIds.length > 0 ? { tablePartRelIds } : {}
54
56
  };
@@ -298,6 +300,60 @@ function parseConditionalFormatting(ws) {
298
300
  }
299
301
  return out;
300
302
  }
303
+ var DV_TYPES = new Set([
304
+ "none",
305
+ "whole",
306
+ "decimal",
307
+ "list",
308
+ "date",
309
+ "time",
310
+ "textLength",
311
+ "custom"
312
+ ]);
313
+ function parseDataValidations(ws) {
314
+ const node = asObjectNode(ws["dataValidations"]);
315
+ if (!node) return [];
316
+ const out = [];
317
+ for (const dv of toArray(node["dataValidation"])) {
318
+ const obj = asObjectNode(dv);
319
+ if (!obj) continue;
320
+ const sqref = strAttr(obj, "sqref");
321
+ if (!sqref) continue;
322
+ const ranges = parseSqref(sqref);
323
+ if (ranges.length === 0) continue;
324
+ const typeRaw = strAttr(obj, "type");
325
+ const type = typeRaw && DV_TYPES.has(typeRaw) ? typeRaw : "none";
326
+ const operator = strAttr(obj, "operator");
327
+ const errorStyle = strAttr(obj, "errorStyle");
328
+ const formula1 = formulaText(obj["formula1"]);
329
+ const formula2 = formulaText(obj["formula2"]);
330
+ const promptTitle = strAttr(obj, "promptTitle");
331
+ const prompt = strAttr(obj, "prompt");
332
+ const errorTitle = strAttr(obj, "errorTitle");
333
+ const error = strAttr(obj, "error");
334
+ out.push({
335
+ type,
336
+ ranges,
337
+ ...operator !== void 0 ? { operator } : {},
338
+ ...boolAttr(obj, "allowBlank") ? { allowBlank: true } : {},
339
+ ...boolAttr(obj, "showDropDown") ? { showDropDown: true } : {},
340
+ ...boolAttr(obj, "showInputMessage") ? { showInputMessage: true } : {},
341
+ ...boolAttr(obj, "showErrorMessage") ? { showErrorMessage: true } : {},
342
+ ...errorStyle !== void 0 ? { errorStyle } : {},
343
+ ...formula1 !== void 0 ? { formula1 } : {},
344
+ ...formula2 !== void 0 ? { formula2 } : {},
345
+ ...promptTitle !== void 0 ? { promptTitle } : {},
346
+ ...prompt !== void 0 ? { prompt } : {},
347
+ ...errorTitle !== void 0 ? { errorTitle } : {},
348
+ ...error !== void 0 ? { error } : {}
349
+ });
350
+ }
351
+ return out;
352
+ }
353
+ function boolAttr(obj, key) {
354
+ const raw = strAttr(obj, key);
355
+ return raw === "1" || raw === "true";
356
+ }
301
357
  function parseSqref(sqref) {
302
358
  const out = [];
303
359
  for (const token of sqref.split(/\s+/)) {
@@ -13,14 +13,20 @@ import { parseWorkbook } from "./workbook-parser.js";
13
13
  import { parseWorksheet } from "./worksheet-parser.js";
14
14
  import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
15
15
  import "./index.js";
16
+ import { resolveCellText } from "./print-model.js";
16
17
  import { parseSheetDrawing } from "./sheet-drawing.js";
17
- import { parseTablePart } from "./table-parser.js";
18
+ import { parseTablePartFull } from "./table-parser.js";
19
+ import { parsePivotTablePart } from "./pivot-table-parser.js";
20
+ import { parseSlicerCachePart, parseSlicerPart } from "./slicer-parser.js";
18
21
  import { projectSheetDoc } from "./sheet-to-flow.js";
19
22
  //#region src/excel/xlsx-reader.ts
20
23
  var WORKBOOK_PART = "xl/workbook.xml";
21
24
  var SHARED_STRINGS_PART = "xl/sharedStrings.xml";
22
25
  var STYLES_PART = "xl/styles.xml";
23
26
  var CORE_PROPS_PART = "docProps/core.xml";
27
+ var SLICER_REL_TAIL = "/slicer";
28
+ var SLICER_CACHE_REL_TAIL = "/slicerCache";
29
+ var MAX_SLICER_ITEMS = 256;
24
30
  function readXlsx(xlsx) {
25
31
  return {
26
32
  doc: projectSheetDoc(readXlsxToSheetDoc(xlsx)),
@@ -42,6 +48,8 @@ function readXlsxToSheetDoc(xlsx) {
42
48
  const palette = buildThemePalette(pkg, workbookRels);
43
49
  const resolveColor = makeColorResolver(palette);
44
50
  const sheetsOut = [];
51
+ const tableIndex = /* @__PURE__ */ new Map();
52
+ const pendingSlicers = [];
45
53
  for (let sheetIdx = 0; sheetIdx < sheets.length; sheetIdx++) {
46
54
  const sheet = sheets[sheetIdx];
47
55
  const sheetRel = workbookRels.find((r) => r.id === sheet.relationshipId);
@@ -74,14 +82,46 @@ function readXlsxToSheetDoc(xlsx) {
74
82
  for (const rid of worksheet.tablePartRelIds) {
75
83
  const rel = wsRels.find((r) => r.id === rid);
76
84
  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));
85
+ const full = part ? parseTablePartFull(part.data) : void 0;
86
+ if (!full) continue;
87
+ resolvedTables.push(resolveTableStyle(full.table, palette));
88
+ if (full.id !== void 0) tableIndex.set(full.id, {
89
+ cells: worksheet.cells,
90
+ ref: full.table.ref,
91
+ headerRows: full.table.headerRowCount,
92
+ filters: full.filters
93
+ });
79
94
  }
80
95
  if (resolvedTables.length > 0) tables = resolvedTables;
81
96
  }
82
- const grid = tables ? {
97
+ {
98
+ const wsRels = pkg.getPartRelationships(resolved.path);
99
+ const defs = [];
100
+ for (const rel of wsRels) {
101
+ if (!rel.type.endsWith(SLICER_REL_TAIL)) continue;
102
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
103
+ if (part) defs.push(...parseSlicerPart(part.data));
104
+ }
105
+ if (defs.length > 0) pendingSlicers.push({
106
+ outIdx: sheetsOut.length,
107
+ defs
108
+ });
109
+ }
110
+ let pivotTables;
111
+ {
112
+ const resolvedPivots = [];
113
+ for (const rel of pkg.getPartRelationships(resolved.path)) {
114
+ if (!isOoxmlRel(rel.type, "pivotTable")) continue;
115
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
116
+ const parsed = part ? parsePivotTablePart(part.data) : void 0;
117
+ if (parsed) resolvedPivots.push(resolvePivotStyle(parsed, palette));
118
+ }
119
+ if (resolvedPivots.length > 0) pivotTables = resolvedPivots;
120
+ }
121
+ const grid = tables || pivotTables ? {
83
122
  ...worksheet,
84
- tables
123
+ ...tables ? { tables } : {},
124
+ ...pivotTables ? { pivotTables } : {}
85
125
  } : worksheet;
86
126
  sheetsOut.push({
87
127
  name: sheet.name,
@@ -89,6 +129,24 @@ function readXlsxToSheetDoc(xlsx) {
89
129
  ...charts.length > 0 ? { charts } : {}
90
130
  });
91
131
  }
132
+ if (pendingSlicers.length > 0) {
133
+ const cacheByName = /* @__PURE__ */ new Map();
134
+ for (const rel of workbookRels) {
135
+ if (!rel.type.endsWith(SLICER_CACHE_REL_TAIL)) continue;
136
+ const part = pkg.resolveRelatedPart(WORKBOOK_PART, rel);
137
+ const cache = part ? parseSlicerCachePart(part.data) : void 0;
138
+ if (cache) cacheByName.set(cache.name, cache);
139
+ }
140
+ for (const { outIdx, defs } of pendingSlicers) {
141
+ const sheet = sheetsOut[outIdx];
142
+ if (!sheet) continue;
143
+ const slicers = defs.map((def) => resolveSlicer(def, cacheByName.get(def.cacheName), tableIndex, styles, sharedStrings, date1904, palette));
144
+ sheetsOut[outIdx] = {
145
+ ...sheet,
146
+ slicers
147
+ };
148
+ }
149
+ }
92
150
  const coreData = pkg.getPart(CORE_PROPS_PART);
93
151
  const info = infoFromCore(coreData ? parseCoreProperties(coreData) : void 0);
94
152
  return {
@@ -132,6 +190,72 @@ function resolveTableStyle(t, palette) {
132
190
  headerTextHex: "FFFFFF"
133
191
  };
134
192
  }
193
+ function resolvePivotStyle(p, palette) {
194
+ const m = p.styleName ? /PivotStyle(Light|Medium|Dark)(\d+)/i.exec(p.styleName) : null;
195
+ if (!m) return p;
196
+ const kind = m[1].toLowerCase();
197
+ const column = (Number(m[2]) - 1) % 7;
198
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
199
+ if (kind === "light") return {
200
+ ...p,
201
+ headerHex: lighten(base, .6),
202
+ bandHex: lighten(base, .85)
203
+ };
204
+ return {
205
+ ...p,
206
+ headerHex: base,
207
+ bandHex: lighten(base, .8),
208
+ headerTextHex: "FFFFFF"
209
+ };
210
+ }
211
+ function resolveSlicer(def, cache, tableIndex, styles, sharedStrings, date1904, palette) {
212
+ const items = cache ? resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) : [];
213
+ return {
214
+ caption: def.caption,
215
+ columnCount: def.columnCount,
216
+ items,
217
+ ...resolveSlicerStyle(def.styleName, palette)
218
+ };
219
+ }
220
+ function resolveTableSlicerItems(cache, tableIndex, styles, sharedStrings, date1904) {
221
+ const loc = cache.tableId !== void 0 ? tableIndex.get(cache.tableId) : void 0;
222
+ if (!loc || cache.columnId === void 0) return [];
223
+ const colOffset = cache.columnId - 1;
224
+ const absCol = loc.ref.startColumn + colOffset;
225
+ if (absCol < loc.ref.startColumn || absCol > loc.ref.endColumn) return [];
226
+ const firstDataRow = loc.ref.startRow + Math.max(1, loc.headerRows);
227
+ const filter = loc.filters.find((f) => f.colId === colOffset);
228
+ const kept = filter ? new Set(filter.values) : void 0;
229
+ const byRow = /* @__PURE__ */ new Map();
230
+ for (const c of loc.cells) {
231
+ if (c.column !== absCol || c.row < firstDataRow || c.row > loc.ref.endRow) continue;
232
+ byRow.set(c.row, resolveCellText(c, sharedStrings, styles, date1904));
233
+ }
234
+ const seen = /* @__PURE__ */ new Set();
235
+ const out = [];
236
+ for (const row of [...byRow.keys()].sort((a, b) => a - b)) {
237
+ const label = byRow.get(row);
238
+ if (label === "" || seen.has(label)) continue;
239
+ seen.add(label);
240
+ out.push({
241
+ label,
242
+ selected: kept ? kept.has(label) : true
243
+ });
244
+ if (out.length >= MAX_SLICER_ITEMS) break;
245
+ }
246
+ return out;
247
+ }
248
+ function resolveSlicerStyle(styleName, palette) {
249
+ const m = styleName ? /SlicerStyle[A-Za-z]*?(\d+)/i.exec(styleName) : null;
250
+ const column = m ? (Number(m[1]) - 1) % 7 : 1;
251
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
252
+ return {
253
+ headerHex: base,
254
+ headerTextHex: "FFFFFF",
255
+ selectedHex: base,
256
+ selectedTextHex: "FFFFFF"
257
+ };
258
+ }
135
259
  function lighten(hex, amount) {
136
260
  const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
137
261
  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 "";
@@ -235,6 +235,14 @@ function cfRuleXml(rule) {
235
235
  function cfvoXml(cfvo) {
236
236
  return `<cfvo type="${cfvo.type}"${cfvo.val !== void 0 ? ` val="${escapeAttr(cfvo.val)}"` : ""}/>`;
237
237
  }
238
+ function dataValidationsXml(dvs) {
239
+ if (!dvs || dvs.length === 0) return "";
240
+ return `<dataValidations count="${dvs.length}">${dvs.map(dataValidationXml).join("")}</dataValidations>`;
241
+ }
242
+ function dataValidationXml(dv) {
243
+ const sqref = dv.ranges.map(rangeRef).join(" ");
244
+ 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>`;
245
+ }
238
246
  var X14_NS = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
239
247
  var XM_NS = "http://schemas.microsoft.com/office/excel/2006/main";
240
248
  function sparklineExtXml(sparklines) {
@@ -41,6 +41,7 @@ function writeHtml(flow) {
41
41
  for (const el of flow.body) emitBlock(out, el, ctx);
42
42
  emitNotesSection(out, flow.footnotes, ctx.notes.footnotes, "fn", ctx);
43
43
  emitNotesSection(out, flow.endnotes, ctx.notes.endnotes, "en", ctx);
44
+ emitCommentsSection(out, flow.comments, ctx.notes.comments, ctx);
44
45
  out.push("</article>");
45
46
  out.push("</body>");
46
47
  out.push("</html>");
@@ -76,7 +77,10 @@ var BASE_CSS = [
76
77
  ".tab{display:inline-block;min-width:18pt}",
77
78
  "img{vertical-align:baseline}",
78
79
  ".notes{margin-top:18pt;font-size:smaller}",
79
- ".notes hr{margin:0 0 6pt;border:none;border-top:0.75pt solid #000;width:144pt;margin-left:0}"
80
+ ".notes hr{margin:0 0 6pt;border:none;border-top:0.75pt solid #000;width:144pt;margin-left:0}",
81
+ ".comment-range{background:#fff3a3}",
82
+ ".comment-replies{margin-left:18pt}",
83
+ ".comments .resolved>.comment-meta::after{content:\" \\2713\";color:#3a7d3a}"
80
84
  ].join("");
81
85
  function collectReferencedAnchors(body) {
82
86
  const out = /* @__PURE__ */ new Set();
@@ -92,10 +96,12 @@ function collectReferencedAnchors(body) {
92
96
  function collectNoteNumbers(flow) {
93
97
  const footnotes = /* @__PURE__ */ new Map();
94
98
  const endnotes = /* @__PURE__ */ new Map();
99
+ const comments = /* @__PURE__ */ new Map();
95
100
  const visit = (els) => {
96
101
  for (const el of els) if (el.kind === "paragraph") for (const r of el.paragraph.runs) {
97
102
  if (r.footnoteRef !== void 0 && !footnotes.has(r.footnoteRef)) footnotes.set(r.footnoteRef, footnotes.size + 1);
98
103
  if (r.endnoteRef !== void 0 && !endnotes.has(r.endnoteRef)) endnotes.set(r.endnoteRef, endnotes.size + 1);
104
+ if (r.commentRef !== void 0 && !comments.has(r.commentRef)) comments.set(r.commentRef, comments.size + 1);
99
105
  }
100
106
  else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
101
107
  else if (el.kind === "shape" && el.shape.text) visit(el.shape.text.content);
@@ -103,7 +109,8 @@ function collectNoteNumbers(flow) {
103
109
  visit(flow.body);
104
110
  return {
105
111
  footnotes,
106
- endnotes
112
+ endnotes,
113
+ comments
107
114
  };
108
115
  }
109
116
  function emitNotesSection(out, content, numbers, prefix, ctx) {
@@ -118,6 +125,43 @@ function emitNotesSection(out, content, numbers, prefix, ctx) {
118
125
  }
119
126
  out.push("</ol></section>");
120
127
  }
128
+ function emitCommentsSection(out, comments, numbers, ctx) {
129
+ if (!comments || numbers.size === 0) return;
130
+ const ordered = [...numbers.entries()].sort((a, b) => a[1] - b[1]).map(([id]) => id);
131
+ const childrenOf = /* @__PURE__ */ new Map();
132
+ const roots = [];
133
+ for (const id of ordered) {
134
+ const parent = comments.get(id)?.parentId;
135
+ if (parent !== void 0 && parent !== id && numbers.has(parent)) {
136
+ const kids = childrenOf.get(parent);
137
+ if (kids) kids.push(id);
138
+ else childrenOf.set(parent, [id]);
139
+ } else roots.push(id);
140
+ }
141
+ const seen = /* @__PURE__ */ new Set();
142
+ const emitOne = (id) => {
143
+ if (seen.has(id)) return;
144
+ seen.add(id);
145
+ const c = comments.get(id);
146
+ const n = numbers.get(id);
147
+ if (!c || n === void 0) return;
148
+ out.push(`<li id="cm-${n}" class="${c.done ? "comment resolved" : "comment"}">`);
149
+ const meta = [[c.author !== void 0 ? c.author + (c.authorId ? ` <${c.authorId}>` : "") : c.authorId, c.date].filter((s) => s !== void 0 && s.length > 0).join(", "), c.done ? "Resolved" : void 0].filter((s) => s).join(" — ");
150
+ if (meta) out.push(`<p class="comment-meta">${textHtml(meta)}</p>`);
151
+ for (const el of c.content) emitBlock(out, el, ctx);
152
+ out.push(`<a href="#cmref-${n}">\u21a9</a>`);
153
+ const kids = childrenOf.get(id);
154
+ if (kids && kids.length > 0) {
155
+ out.push("<ol class=\"comment-replies\">");
156
+ for (const k of kids) emitOne(k);
157
+ out.push("</ol>");
158
+ }
159
+ out.push("</li>");
160
+ };
161
+ out.push("<section class=\"comments\"><hr/><h2>Comments</h2><ol>");
162
+ for (const id of roots) emitOne(id);
163
+ out.push("</ol></section>");
164
+ }
121
165
  function emitBlock(out, el, ctx) {
122
166
  if (el.kind === "paragraph") emitParagraph(out, el.paragraph, ctx);
123
167
  else if (el.kind === "table") emitTable(out, el.table, ctx);
@@ -272,6 +316,11 @@ function runHtml(run, p, ctx) {
272
316
  const prefix = isFoot ? "fn" : "en";
273
317
  return `<sup><a href="#${prefix}-${n}" id="${prefix}ref-${n}">${n}</a></sup>`;
274
318
  }
319
+ if (run.commentRef !== void 0) {
320
+ const n = ctx.notes.comments.get(run.commentRef);
321
+ if (n === void 0) return "";
322
+ return `<sup class="comment-ref"><a href="#cm-${n}" id="cmref-${n}">[${n}]</a></sup>`;
323
+ }
275
324
  if (run.noteNumber) return "";
276
325
  if (run.math !== void 0) {
277
326
  ctx.losses.push({
@@ -293,6 +342,7 @@ function runHtml(run, p, ctx) {
293
342
  let html = `<span${resolved.rtl ? " dir=\"rtl\"" : ""}${style ? ` style="${style}"` : ""}>${textHtml(run.text)}</span>`;
294
343
  if (resolved.verticalAlign === "superscript") html = `<sup>${html}</sup>`;
295
344
  else if (resolved.verticalAlign === "subscript") html = `<sub>${html}</sub>`;
345
+ if (run.commentRangeRefs && run.commentRangeRefs.length > 0) html = `<span class="comment-range">${html}</span>`;
296
346
  if (run.href === void 0 && run.anchor !== void 0) return `<a href="#bm-${encodeURIComponent(run.anchor)}">${html}</a>`;
297
347
  if (run.href !== void 0) {
298
348
  const safe = sanitizeHref(run.href);
@@ -446,6 +496,7 @@ function emitCell(out, cell, table, ctx, pos) {
446
496
  if (!cell.properties.shading) css.push("background-color:#fff");
447
497
  }
448
498
  out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
499
+ if (cell.properties.dropdown) out.push(cellDropdownSvg());
449
500
  if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
450
501
  if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
451
502
  for (const child of cell.content) emitBlock(out, child, ctx);
@@ -464,6 +515,9 @@ function cellSparklineSvg(sp) {
464
515
  }
465
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>`;
466
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
+ }
467
521
  var ICON_EMPTY_HEX = "BFBFBF";
468
522
  function cellIconSvg(icon) {
469
523
  const fill = `#${icon.colorHex}`;
@@ -17,6 +17,7 @@ export interface TextToken {
17
17
  readonly footnoteRef?: string;
18
18
  readonly anchor?: string;
19
19
  readonly listMarker?: true;
20
+ readonly highlight?: true;
20
21
  readonly resolvedRun: ResolvedRunProperties;
21
22
  readonly font: FontResource;
22
23
  readonly fontSizePt: number;
@@ -1,4 +1,4 @@
1
- import { BodyElement, Chart, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../core/document-model/index.js';
1
+ import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../core/document-model/index.js';
2
2
  import { FontRegistry } from '../core/font/index.js';
3
3
  import { FamilyKey } from '../core/fonts/index.js';
4
4
  import { Hyphenator } from '../core/hyphenation/index.js';
@@ -28,6 +28,8 @@ export interface StyledRenderOptions {
28
28
  readonly headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
29
29
  readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
30
30
  readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
31
+ readonly comments?: ReadonlyMap<string, Comment>;
32
+ readonly commentAnnotations?: boolean;
31
33
  readonly resources?: ResourceStore;
32
34
  readonly charts?: ReadonlyMap<string, Chart>;
33
35
  readonly pageWidth?: number;
@@ -53,12 +55,17 @@ export interface BookmarkPosition {
53
55
  readonly pageIdx: number;
54
56
  readonly yTopPt: number;
55
57
  }
58
+ export interface CommentNote {
59
+ readonly author?: string;
60
+ readonly contents: string;
61
+ }
56
62
  export interface PdfLayoutAux {
57
63
  readonly structBuilder: StructTreeBuilder | undefined;
58
64
  readonly sectionCtxs: ReadonlyArray<SectionRenderCtx>;
59
65
  readonly pdfaProfile: PdfAProfile | undefined;
60
66
  readonly tagged: boolean;
61
67
  readonly bookmarks: ReadonlyMap<string, BookmarkPosition>;
68
+ readonly commentNotes?: ReadonlyMap<string, CommentNote>;
62
69
  }
63
70
  export interface LaidOutPdfDocument extends LaidOutDocument {
64
71
  readonly pdf: PdfLayoutAux;