reamkit 1.10.0 → 1.12.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 (48) hide show
  1. package/README.md +3 -3
  2. package/dist/esm/core/converter/facade.js +1 -1
  3. package/dist/esm/core/converter/project.js +1 -0
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +23 -0
  6. package/dist/esm/core/font/measure.d.ts +1 -1
  7. package/dist/esm/core/font/measure.js +5 -3
  8. package/dist/esm/core/font/ttf-parser.d.ts +9 -0
  9. package/dist/esm/core/font/ttf-parser.js +27 -2
  10. package/dist/esm/core/fonts/provider.d.ts +2 -1
  11. package/dist/esm/core/fonts/provider.js +2 -1
  12. package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
  13. package/dist/esm/core/fonts/remote-fonts.js +32 -11
  14. package/dist/esm/core/ir/features.d.ts +1 -0
  15. package/dist/esm/core/ir/features.js +1 -0
  16. package/dist/esm/core/ir/flow.d.ts +3 -1
  17. package/dist/esm/core/line-breaker/greedy.d.ts +2 -0
  18. package/dist/esm/core/line-breaker/greedy.js +67 -0
  19. package/dist/esm/core/line-breaker/index.d.ts +1 -0
  20. package/dist/esm/core/line-breaker/knuth-plass.d.ts +1 -0
  21. package/dist/esm/core/line-breaker/knuth-plass.js +3 -3
  22. package/dist/esm/core/po-helpers.d.ts +2 -0
  23. package/dist/esm/core/po-helpers.js +14 -1
  24. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  25. package/dist/esm/core/spreadsheet-model/types.d.ts +16 -0
  26. package/dist/esm/excel/pivot-table-parser.d.ts +2 -0
  27. package/dist/esm/excel/pivot-table-parser.js +68 -0
  28. package/dist/esm/excel/print-model.js +28 -6
  29. package/dist/esm/excel/xlsx-reader.js +33 -2
  30. package/dist/esm/excel/xlsx-to-pdf.js +1 -1
  31. package/dist/esm/html/html-writer.js +52 -2
  32. package/dist/esm/layout/page-doc.d.ts +3 -0
  33. package/dist/esm/layout/styled-layout.d.ts +10 -1
  34. package/dist/esm/layout/styled-layout.js +156 -15
  35. package/dist/esm/pdf/styled-page-emitter.js +38 -0
  36. package/dist/esm/pptx/pptx-reader.d.ts +2 -0
  37. package/dist/esm/pptx/pptx-reader.js +30 -3
  38. package/dist/esm/pptx/slide-parser.d.ts +8 -2
  39. package/dist/esm/pptx/slide-parser.js +80 -12
  40. package/dist/esm/word/document-parser.d.ts +17 -2
  41. package/dist/esm/word/document-parser.js +178 -18
  42. package/dist/esm/word/docx-reader.js +54 -4
  43. package/dist/esm/word/docx-to-pdf.js +2 -2
  44. package/dist/esm/word/docx-writer.js +84 -1
  45. package/dist/esm/word/drawing-parser.d.ts +7 -0
  46. package/dist/esm/word/drawing-parser.js +13 -0
  47. package/dist/esm/word/index.d.ts +2 -1
  48. package/package.json +1 -1
@@ -15,6 +15,7 @@ import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
15
15
  import "./index.js";
16
16
  import { parseSheetDrawing } from "./sheet-drawing.js";
17
17
  import { parseTablePart } from "./table-parser.js";
18
+ import { parsePivotTablePart } from "./pivot-table-parser.js";
18
19
  import { projectSheetDoc } from "./sheet-to-flow.js";
19
20
  //#region src/excel/xlsx-reader.ts
20
21
  var WORKBOOK_PART = "xl/workbook.xml";
@@ -79,9 +80,21 @@ function readXlsxToSheetDoc(xlsx) {
79
80
  }
80
81
  if (resolvedTables.length > 0) tables = resolvedTables;
81
82
  }
82
- const grid = tables ? {
83
+ let pivotTables;
84
+ {
85
+ const resolvedPivots = [];
86
+ for (const rel of pkg.getPartRelationships(resolved.path)) {
87
+ if (!isOoxmlRel(rel.type, "pivotTable")) continue;
88
+ const part = pkg.resolveRelatedPart(resolved.path, rel);
89
+ const parsed = part ? parsePivotTablePart(part.data) : void 0;
90
+ if (parsed) resolvedPivots.push(resolvePivotStyle(parsed, palette));
91
+ }
92
+ if (resolvedPivots.length > 0) pivotTables = resolvedPivots;
93
+ }
94
+ const grid = tables || pivotTables ? {
83
95
  ...worksheet,
84
- tables
96
+ ...tables ? { tables } : {},
97
+ ...pivotTables ? { pivotTables } : {}
85
98
  } : worksheet;
86
99
  sheetsOut.push({
87
100
  name: sheet.name,
@@ -132,6 +145,24 @@ function resolveTableStyle(t, palette) {
132
145
  headerTextHex: "FFFFFF"
133
146
  };
134
147
  }
148
+ function resolvePivotStyle(p, palette) {
149
+ const m = p.styleName ? /PivotStyle(Light|Medium|Dark)(\d+)/i.exec(p.styleName) : null;
150
+ if (!m) return p;
151
+ const kind = m[1].toLowerCase();
152
+ const column = (Number(m[2]) - 1) % 7;
153
+ const base = column === 0 ? "7F7F7F" : palette.get(`accent${column}`) ?? "4472C4";
154
+ if (kind === "light") return {
155
+ ...p,
156
+ headerHex: lighten(base, .6),
157
+ bandHex: lighten(base, .85)
158
+ };
159
+ return {
160
+ ...p,
161
+ headerHex: base,
162
+ bandHex: lighten(base, .8),
163
+ headerTextHex: "FFFFFF"
164
+ };
165
+ }
135
166
  function lighten(hex, amount) {
136
167
  const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
137
168
  const n = m ? parseInt(m[1], 16) : 4485828;
@@ -46,7 +46,7 @@ function prepareXlsxStyledRender(xlsx, options) {
46
46
  if (!fonts) throw new Error("convertXlsxToPdfSync requires options.fonts or options.fontBytes");
47
47
  const registry = FontRegistry.fromBytes(fonts);
48
48
  const { doc: flow } = readXlsx(xlsx);
49
- const { fontBytes: _ignoreA, fonts: _ignoreB, section: sectionOverride, info: callerInfo, embedSource, attachments: callerAttachments, signature: _ignoreSig, ...renderOptions } = options;
49
+ const { fontBytes: _ignoreA, fonts: _ignoreB, section: sectionOverride, info: callerInfo, embedSource, attachments: callerAttachments, signature: _ignoreSig, layoutProfile: _ignoreProfile, ...renderOptions } = options;
50
50
  const section = sectionOverride ?? flow.section;
51
51
  const info = flow.info || callerInfo ? {
52
52
  ...flow.info,
@@ -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);
@@ -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;
@@ -70,6 +71,8 @@ export interface Line {
70
71
  isLastInParagraph: boolean;
71
72
  readonly mathAscentPt?: number;
72
73
  readonly mathDescentPt?: number;
74
+ readonly metricHeightPt?: number;
75
+ readonly metricDescentPt?: number;
73
76
  }
74
77
  export interface ImageResource {
75
78
  readonly resourceName: string;
@@ -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';
@@ -15,8 +15,10 @@ export interface PdfAProfile {
15
15
  readonly tagged: boolean;
16
16
  readonly version: '1.4' | '1.7';
17
17
  }
18
+ export type LayoutProfile = 'ream' | 'word' | 'libreoffice';
18
19
  export interface StyledRenderOptions {
19
20
  readonly registry: FontRegistry;
21
+ readonly layoutProfile?: LayoutProfile;
20
22
  readonly registriesByFamily?: ReadonlyMap<FamilyKey, FontRegistry>;
21
23
  readonly embeddedFonts?: ReadonlyMap<string, FontRegistry>;
22
24
  readonly styles: StyleSheet;
@@ -26,6 +28,8 @@ export interface StyledRenderOptions {
26
28
  readonly headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
27
29
  readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
28
30
  readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
31
+ readonly comments?: ReadonlyMap<string, Comment>;
32
+ readonly commentAnnotations?: boolean;
29
33
  readonly resources?: ResourceStore;
30
34
  readonly charts?: ReadonlyMap<string, Chart>;
31
35
  readonly pageWidth?: number;
@@ -51,12 +55,17 @@ export interface BookmarkPosition {
51
55
  readonly pageIdx: number;
52
56
  readonly yTopPt: number;
53
57
  }
58
+ export interface CommentNote {
59
+ readonly author?: string;
60
+ readonly contents: string;
61
+ }
54
62
  export interface PdfLayoutAux {
55
63
  readonly structBuilder: StructTreeBuilder | undefined;
56
64
  readonly sectionCtxs: ReadonlyArray<SectionRenderCtx>;
57
65
  readonly pdfaProfile: PdfAProfile | undefined;
58
66
  readonly tagged: boolean;
59
67
  readonly bookmarks: ReadonlyMap<string, BookmarkPosition>;
68
+ readonly commentNotes?: ReadonlyMap<string, CommentNote>;
60
69
  }
61
70
  export interface LaidOutPdfDocument extends LaidOutDocument {
62
71
  readonly pdf: PdfLayoutAux;
@@ -7,6 +7,7 @@ import { prepareImage } from "../core/images.js";
7
7
  import { segmentLevels } from "../core/bidi/segments.js";
8
8
  import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
9
9
  import { FORCED_BREAK, breakLines } from "../core/line-breaker/knuth-plass.js";
10
+ import { greedyBreakLines } from "../core/line-breaker/greedy.js";
10
11
  import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
11
12
  import { DEFAULT_RESOLVED_PARAGRAPH, DEFAULT_RESOLVED_RUN } from "../core/style-cascade/types.js";
12
13
  import { resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
@@ -43,9 +44,10 @@ function isOutOfFlowFloat(f) {
43
44
  return f !== void 0 && f.wrap !== "topAndBottom";
44
45
  }
45
46
  var FOOTNOTE_RULE_WIDTH = 144;
46
- function assignNoteNumbers(body) {
47
+ function assignNoteNumbers(body, commentIds) {
47
48
  const footnotes = /* @__PURE__ */ new Map();
48
49
  const endnotes = /* @__PURE__ */ new Map();
50
+ const comments = /* @__PURE__ */ new Map();
49
51
  const paragraphFootnotes = /* @__PURE__ */ new Set();
50
52
  const numberRun = (run, n) => ({
51
53
  ...run,
@@ -55,8 +57,17 @@ function assignNoteNumbers(body) {
55
57
  verticalAlign: "superscript"
56
58
  }
57
59
  });
60
+ const commentMarkerRun = (run, n) => ({
61
+ ...run,
62
+ text: `[${n}]`,
63
+ ...run.commentRef !== void 0 && commentIds?.has(run.commentRef) ? { anchor: `comment-${n}` } : {},
64
+ properties: {
65
+ ...run.properties,
66
+ verticalAlign: "superscript"
67
+ }
68
+ });
58
69
  const mapParagraph = (paragraph) => {
59
- if (!paragraph.runs.some((r) => r.footnoteRef !== void 0 || r.endnoteRef !== void 0)) return paragraph;
70
+ if (!paragraph.runs.some((r) => r.footnoteRef !== void 0 || r.endnoteRef !== void 0 || r.commentRef !== void 0)) return paragraph;
60
71
  return {
61
72
  ...paragraph,
62
73
  runs: paragraph.runs.map((run) => {
@@ -76,6 +87,14 @@ function assignNoteNumbers(body) {
76
87
  }
77
88
  return numberRun(run, n);
78
89
  }
90
+ if (run.commentRef !== void 0) {
91
+ let n = comments.get(run.commentRef);
92
+ if (n === void 0) {
93
+ n = comments.size + 1;
94
+ comments.set(run.commentRef, n);
95
+ }
96
+ return commentMarkerRun(run, n);
97
+ }
79
98
  return run;
80
99
  })
81
100
  };
@@ -116,7 +135,7 @@ function assignNoteNumbers(body) {
116
135
  return el;
117
136
  };
118
137
  const hasRefs = (els) => els.some((el) => {
119
- if (el.kind === "paragraph") return el.paragraph.runs.some((r) => r.footnoteRef !== void 0 || r.endnoteRef !== void 0);
138
+ if (el.kind === "paragraph") return el.paragraph.runs.some((r) => r.footnoteRef !== void 0 || r.endnoteRef !== void 0 || r.commentRef !== void 0);
120
139
  if (el.kind === "table") return el.table.rows.some((row) => row.cells.some((c) => hasRefs(c.content)));
121
140
  if (el.kind === "shape" && el.shape.text) return hasRefs(el.shape.text.content);
122
141
  return false;
@@ -125,12 +144,14 @@ function assignNoteNumbers(body) {
125
144
  body,
126
145
  footnotes,
127
146
  endnotes,
147
+ comments,
128
148
  deferredFootnotes: []
129
149
  };
130
150
  return {
131
151
  body: body.map((el) => mapElement(el, true)),
132
152
  footnotes,
133
153
  endnotes,
154
+ comments,
134
155
  deferredFootnotes: [...footnotes.keys()].filter((id) => !paragraphFootnotes.has(id))
135
156
  };
136
157
  }
@@ -168,9 +189,62 @@ function substituteNoteNumber(content, n) {
168
189
  }
169
190
  }, ...out.slice(1)];
170
191
  }
192
+ function flattenCommentText(content) {
193
+ const lines = [];
194
+ const visit = (els) => {
195
+ for (const el of els) if (el.kind === "paragraph") lines.push(el.paragraph.runs.map((r) => r.text).join(""));
196
+ else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
197
+ };
198
+ visit(content);
199
+ return lines.join("\n").trim();
200
+ }
201
+ function buildCommentNotes(numbers, comments) {
202
+ const out = /* @__PURE__ */ new Map();
203
+ for (const [id, n] of numbers) {
204
+ const c = comments.get(id);
205
+ if (!c) continue;
206
+ out.set(`comment-${n}`, {
207
+ ...c.author !== void 0 ? { author: c.author } : {},
208
+ contents: flattenCommentText(c.content)
209
+ });
210
+ }
211
+ return out;
212
+ }
213
+ function commentTailBlocks(comment, n, opts) {
214
+ const who = [comment.author, comment.date].filter((s) => s !== void 0 && s.length > 0).join(", ");
215
+ const inReplyTo = opts?.parentN !== void 0 ? ` (in reply to [${opts.parentN}])` : "";
216
+ const resolved = opts?.done ? " (resolved)" : "";
217
+ const labelRun = {
218
+ text: who ? `[${n}] ${who}${inReplyTo}${resolved}: ` : `[${n}]${inReplyTo}${resolved} `,
219
+ properties: {}
220
+ };
221
+ const bm = `comment-${n}`;
222
+ const indent = opts?.depth && opts.depth > 0 ? { indentLeft: pt(18 * opts.depth) } : {};
223
+ const first = comment.content[0];
224
+ if (first && first.kind === "paragraph") return [{
225
+ kind: "paragraph",
226
+ paragraph: {
227
+ ...first.paragraph,
228
+ properties: {
229
+ ...first.paragraph.properties,
230
+ ...indent
231
+ },
232
+ runs: [labelRun, ...first.paragraph.runs],
233
+ bookmarks: [bm, ...first.paragraph.bookmarks ?? []]
234
+ }
235
+ }, ...comment.content.slice(1)];
236
+ return [{
237
+ kind: "paragraph",
238
+ paragraph: {
239
+ properties: { ...indent },
240
+ runs: [labelRun],
241
+ bookmarks: [bm]
242
+ }
243
+ }, ...comment.content];
244
+ }
171
245
  function layoutStyledDocument(body, options) {
172
246
  const sectionList = resolveSectionList(body, options);
173
- const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering));
247
+ const noteAssigned = assignNoteNumbers(applyNumbering(body, options.numbering), options.comments ? new Set(options.comments.keys()) : void 0);
174
248
  const numberedBody = noteAssigned.body;
175
249
  const numberedHeadersFooters = applyNumberingToHeadersFooters(options.headersFooters, options.numbering);
176
250
  const pdfaProfile = options.pdfA ? parsePdfAProfile(options.pdfA) : void 0;
@@ -228,8 +302,35 @@ function layoutStyledDocument(body, options) {
228
302
  });
229
303
  }
230
304
  for (const note of tailNotes.sort((a, b) => a.n - b.n)) for (const el of substituteNoteNumber(note.content, note.n)) blocks.push(layoutBodyElement(el, options, fontResources, imageResources, lastCtx.contentWidth, lastCtx.pageContentHeight));
305
+ const commentNums = noteAssigned.comments;
306
+ const threadDepth = (id) => {
307
+ let depth = 0;
308
+ const seen = new Set([id]);
309
+ let cur = options.comments?.get(id)?.parentId;
310
+ while (cur !== void 0 && commentNums.has(cur) && !seen.has(cur)) {
311
+ depth++;
312
+ seen.add(cur);
313
+ cur = options.comments?.get(cur)?.parentId;
314
+ }
315
+ return depth;
316
+ };
317
+ const commentTail = [...commentNums].map(([id, n]) => ({
318
+ id,
319
+ comment: options.comments?.get(id),
320
+ n
321
+ })).filter((e) => e.comment !== void 0).sort((a, b) => a.n - b.n);
322
+ for (const { id, comment, n } of commentTail) {
323
+ const parentId = comment.parentId;
324
+ const parentN = parentId !== void 0 && commentNums.has(parentId) ? commentNums.get(parentId) : void 0;
325
+ for (const el of commentTailBlocks(comment, n, {
326
+ ...parentN !== void 0 ? { parentN } : {},
327
+ depth: threadDepth(id),
328
+ ...comment.done ? { done: true } : {}
329
+ })) blocks.push(layoutBodyElement(el, options, fontResources, imageResources, lastCtx.contentWidth, lastCtx.pageContentHeight));
330
+ }
231
331
  }
232
332
  const bookmarks = /* @__PURE__ */ new Map();
333
+ const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
233
334
  const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
234
335
  return {
235
336
  pages: paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph),
@@ -241,7 +342,8 @@ function layoutStyledDocument(body, options) {
241
342
  sectionCtxs,
242
343
  pdfaProfile,
243
344
  tagged,
244
- bookmarks
345
+ bookmarks,
346
+ ...commentNotes ? { commentNotes } : {}
245
347
  }
246
348
  };
247
349
  }
@@ -755,6 +857,7 @@ function collectImageResources(body, headersFooters, options) {
755
857
  for (const hf of headersFooters.values()) visit(hf);
756
858
  for (const note of options.footnotes?.values() ?? []) visit(note);
757
859
  for (const note of options.endnotes?.values() ?? []) visit(note);
860
+ for (const comment of options.comments?.values() ?? []) visit(comment.content);
758
861
  const flattenAlpha = options.pdfA ? parsePdfAProfile(options.pdfA).part === 1 : false;
759
862
  let counter = 0;
760
863
  for (const resourceId of seen) {
@@ -915,6 +1018,7 @@ function collectFontResources(body, headersFooters, options) {
915
1018
  for (const hf of headersFooters.values()) visit(hf);
916
1019
  for (const note of options.footnotes?.values() ?? []) visit(note);
917
1020
  for (const note of options.endnotes?.values() ?? []) visit(note);
1021
+ for (const comment of options.comments?.values() ?? []) visit(comment.content);
918
1022
  if (used.size === 0) {
919
1023
  const regular = options.registry.resolveByStyle(false, false);
920
1024
  used.set(regular.variant, {
@@ -930,7 +1034,7 @@ function collectFontResources(body, headersFooters, options) {
930
1034
  out.set(variant, {
931
1035
  resourceName,
932
1036
  parsed: info.parsed,
933
- measure: createFontMeasure(info.parsed),
1037
+ measure: createFontMeasure(info.parsed, options.layoutProfile !== "word"),
934
1038
  gids: info.gids
935
1039
  });
936
1040
  }
@@ -943,7 +1047,7 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
943
1047
  ...baseResolved,
944
1048
  alignment: "right"
945
1049
  } : baseResolved;
946
- const lines = wrap(tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir), paragraphMaxWidth(resolved, contentWidth, true), paragraphMaxWidth(resolved, contentWidth, false), resolved, options.hyphenator, lineWidths);
1050
+ const lines = wrap(tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir), paragraphMaxWidth(resolved, contentWidth, true), paragraphMaxWidth(resolved, contentWidth, false), resolved, options.hyphenator, options.layoutProfile ?? "ream", lineWidths);
947
1051
  let heightPt = 0;
948
1052
  for (const line of lines) heightPt += computeLineHeight(line, resolved);
949
1053
  const numbering = paragraph.properties.numbering;
@@ -1085,6 +1189,7 @@ function tokenizePlansLtr(plans) {
1085
1189
  });
1086
1190
  continue;
1087
1191
  }
1192
+ const highlight = (plan.run.commentRangeRefs?.length ?? 0) > 0;
1088
1193
  for (const t of tokenizeText(plan.run.text)) tokens.push({
1089
1194
  kind: "text",
1090
1195
  text: t.text,
@@ -1093,6 +1198,7 @@ function tokenizePlansLtr(plans) {
1093
1198
  ...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
1094
1199
  ...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
1095
1200
  ...plan.run.listMarker ? { listMarker: true } : {},
1201
+ ...highlight ? { highlight: true } : {},
1096
1202
  resolvedRun: plan.resolvedRun,
1097
1203
  font: plan.font,
1098
1204
  fontSizePt: plan.fontSizePt,
@@ -1147,6 +1253,7 @@ function tokenizePlansBidi(plans, realLevels) {
1147
1253
  ...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
1148
1254
  ...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
1149
1255
  ...plan.run.listMarker ? { listMarker: true } : {},
1256
+ ...(plan.run.commentRangeRefs?.length ?? 0) > 0 ? { highlight: true } : {},
1150
1257
  resolvedRun: plan.resolvedRun,
1151
1258
  font: plan.font,
1152
1259
  fontSizePt: plan.fontSizePt,
@@ -1290,7 +1397,7 @@ function paragraphItemStream(tokens, hyphenator) {
1290
1397
  });
1291
1398
  return entries;
1292
1399
  }
1293
- function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved) {
1400
+ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, resolved, profile) {
1294
1401
  let st = start;
1295
1402
  let et = breakIdx;
1296
1403
  while (st < et && (entries[st].token === null || entries[st].token?.isSpace)) st++;
@@ -1316,6 +1423,10 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
1316
1423
  let maxSize = 0;
1317
1424
  let mathAscent = 0;
1318
1425
  let mathDescent = 0;
1426
+ const useMetric = profile !== "ream";
1427
+ let metricAscent = 0;
1428
+ let metricDescent = 0;
1429
+ let metricLineGap = 0;
1319
1430
  for (const t of lineTokens) {
1320
1431
  contentWidth += t.widthPt;
1321
1432
  const sz = tokenLineSize(t);
@@ -1323,8 +1434,14 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
1323
1434
  if (t.kind === "math") {
1324
1435
  mathAscent = Math.max(mathAscent, t.ascentPt);
1325
1436
  mathDescent = Math.max(mathDescent, t.descentPt);
1437
+ } else if (useMetric && t.kind === "text") {
1438
+ const m = fontLeadingPt(t.font.parsed, t.fontSizePt, profile);
1439
+ if (m.ascentPt > metricAscent) metricAscent = m.ascentPt;
1440
+ if (m.descentPt > metricDescent) metricDescent = m.descentPt;
1441
+ if (m.lineGapPt > metricLineGap) metricLineGap = m.lineGapPt;
1326
1442
  }
1327
1443
  }
1444
+ const hasMetric = useMetric && metricAscent + metricDescent > 0;
1328
1445
  return {
1329
1446
  tokens: lineTokens,
1330
1447
  contentWidthPt: contentWidth,
@@ -1334,21 +1451,26 @@ function lineFromRange(entries, start, breakIdx, availableWidthPt, isFirst, reso
1334
1451
  resolved,
1335
1452
  isLastInParagraph: false,
1336
1453
  mathAscentPt: mathAscent,
1337
- mathDescentPt: mathDescent
1454
+ mathDescentPt: mathDescent,
1455
+ ...hasMetric ? {
1456
+ metricHeightPt: metricAscent + metricDescent + metricLineGap,
1457
+ metricDescentPt: metricDescent
1458
+ } : {}
1338
1459
  };
1339
1460
  }
1340
- function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, lineWidths) {
1461
+ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile, lineWidths) {
1341
1462
  if (tokens.length === 0) return [];
1342
1463
  const widths = lineWidths ?? [firstLineWidth, otherWidth];
1343
1464
  const entries = paragraphItemStream(tokens, hyphenator);
1344
- const { breaks } = breakLines(entries.map((e) => e.item), widths);
1465
+ const items = entries.map((e) => e.item);
1466
+ const breaks = profile === "ream" ? breakLines(items, widths).breaks : greedyBreakLines(items, widths);
1345
1467
  const lines = [];
1346
1468
  let start = 0;
1347
1469
  let isFirst = true;
1348
1470
  let lineIdx = 0;
1349
1471
  for (const breakIdx of breaks) {
1350
1472
  const width = lineIdx < widths.length ? widths[lineIdx] : widths[widths.length - 1];
1351
- const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved);
1473
+ const line = lineFromRange(entries, start, breakIdx, width, isFirst, resolved, profile);
1352
1474
  if (line) lines.push(line);
1353
1475
  start = breakIdx + 1;
1354
1476
  isFirst = false;
@@ -1357,19 +1479,38 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, lineWidt
1357
1479
  if (lines.length > 0) lines[lines.length - 1].isLastInParagraph = true;
1358
1480
  return lines;
1359
1481
  }
1482
+ function fontLeadingPt(parsed, sizePt, profile) {
1483
+ const s = sizePt / parsed.unitsPerEm;
1484
+ const vm = parsed.vmetrics;
1485
+ if (profile === "word") return {
1486
+ ascentPt: vm.winAscent * s,
1487
+ descentPt: vm.winDescent * s,
1488
+ lineGapPt: 0
1489
+ };
1490
+ const asc = vm.useTypoMetrics ? vm.typoAscent : parsed.ascender;
1491
+ const desc = vm.useTypoMetrics ? vm.typoDescent : parsed.descender;
1492
+ const gap = vm.useTypoMetrics ? vm.typoLineGap : parsed.lineGap;
1493
+ return {
1494
+ ascentPt: asc * s,
1495
+ descentPt: -desc * s,
1496
+ lineGapPt: gap * s
1497
+ };
1498
+ }
1360
1499
  function computeLineHeight(line, p) {
1361
1500
  const fontSize = line.maxFontSizePt || 12;
1501
+ const natural = line.metricHeightPt ?? fontSize * 1.2;
1362
1502
  const mathH = (line.mathAscentPt ?? 0) + (line.mathDescentPt ?? 0);
1363
1503
  const mathNeed = mathH > 0 ? mathH * 1.05 : 0;
1364
1504
  if (p.spacingLineRule === "exact" && p.spacingLine > 0) return Math.max(p.spacingLine, mathNeed);
1365
- if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(fontSize * 1.2, p.spacingLine, mathNeed);
1505
+ if (p.spacingLineRule === "atLeast" && p.spacingLine > 0) return Math.max(natural, p.spacingLine, mathNeed);
1366
1506
  const lineTwips = Math.round(p.spacingLine * 20);
1367
1507
  const multiple = lineTwips > 0 ? lineTwips / 240 : 1;
1368
- return Math.max(fontSize * 1.2 * multiple, mathNeed);
1508
+ return Math.max(natural * multiple, mathNeed);
1369
1509
  }
1370
1510
  function lineDescent(line) {
1371
1511
  const fs = line.maxFontSizePt || 12;
1372
- return Math.max(fs * .2, line.mathDescentPt ?? 0);
1512
+ const base = line.metricDescentPt ?? fs * .2;
1513
+ return Math.max(base, line.mathDescentPt ?? 0);
1373
1514
  }
1374
1515
  function alignmentOffset(alignment, lineWidth, available) {
1375
1516
  if (alignment === "right") return Math.max(0, available - lineWidth);
@@ -33,6 +33,7 @@ async function emitStyledPdfEncrypted(laid, options, doc, encrypt) {
33
33
  function assembleStyledPdf(laid, options, doc) {
34
34
  const { pages: renderedPages } = laid;
35
35
  const { structBuilder, sectionCtxs, pdfaProfile, tagged } = laid.pdf;
36
+ const commentNotes = !pdfaProfile && !structBuilder ? laid.pdf.commentNotes : void 0;
36
37
  const docInfo = options.pdfUA && !options.info?.title ? {
37
38
  ...options.info,
38
39
  title: "Untitled document"
@@ -158,6 +159,24 @@ function assembleStyledPdf(laid, options, doc) {
158
159
  annotParentCount++;
159
160
  }
160
161
  annots.push(ref(annotRef.id));
162
+ const note = l.anchor !== void 0 ? commentNotes?.get(l.anchor) : void 0;
163
+ if (note) {
164
+ const textAnnot = doc.add(dict({
165
+ Type: name("Annot"),
166
+ Subtype: name("Text"),
167
+ Name: name("Comment"),
168
+ Rect: [
169
+ l.rect[0],
170
+ l.rect[1],
171
+ l.rect[2],
172
+ l.rect[3]
173
+ ],
174
+ F: 28,
175
+ Contents: note.contents,
176
+ ...note.author !== void 0 ? { T: note.author } : {}
177
+ }));
178
+ annots.push(ref(textAnnot.id));
179
+ }
161
180
  }
162
181
  if (annots.length > 0) {
163
182
  pageEntries["Annots"] = annots;
@@ -624,6 +643,25 @@ function emitPageContent(page, tagging, gradientNames) {
624
643
  const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
625
644
  return tok.font.measure.encodeTextAsCidHex(text);
626
645
  };
646
+ if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
647
+ if (inBT) {
648
+ out.push("ET");
649
+ inBT = false;
650
+ }
651
+ const hlAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
652
+ const hlDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
653
+ const [hr, hg, hb] = hexToRgb01("fff3a3");
654
+ out.push("q");
655
+ out.push(`${formatNumber(hr)} ${formatNumber(hg)} ${formatNumber(hb)} rg`);
656
+ let hx = originX;
657
+ for (const tok of line.tokens) {
658
+ const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
659
+ if (tok.kind === "text" && tok.highlight === true) out.push(`${formatNumber(hx)} ${formatNumber(baselineY - hlDescent)} ${formatNumber(w)} ${formatNumber(hlAscent + hlDescent)} re`);
660
+ hx += w;
661
+ }
662
+ out.push("f");
663
+ out.push("Q");
664
+ }
627
665
  if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl) {
628
666
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
629
667
  let x = originX;
@@ -1,4 +1,6 @@
1
1
  import { DocumentReader, ReadResult } from '../core/ir/adapters.js';
2
2
  import { FlowDoc } from '../core/ir/flow.js';
3
+ import { PoNode } from '../core/po-helpers.js';
3
4
  export declare function readPptx(bytes: Uint8Array): ReadResult<FlowDoc>;
5
+ export declare function parseXml(data: Uint8Array): Array<PoNode>;
4
6
  export declare const pptxReader: DocumentReader<FlowDoc>;