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
package/README.md CHANGED
@@ -20,9 +20,9 @@ Runtime dependencies are minimal: `fflate` (ZIP/Deflate) and `fast-xml-parser`.
20
20
 
21
21
  Parse once into the format-neutral interlayer, convert to any target. The
22
22
  format (docx/xlsx) is sniffed from the bytes; no fonts to wire up — an open
23
- substitute font (Roboto for sans, Tinos for serif, Cousine for monospace — the
24
- same families LibreOffice substitutes) is fetched automatically based on the
25
- document's referenced fonts:
23
+ metric-compatible substitute font (Arimo for sans, Tinos for serif, Cousine for
24
+ monospace, plus Carlito/Caladea for Calibri/Cambria — the same families LibreOffice
25
+ substitutes) is fetched automatically based on the document's referenced fonts:
26
26
 
27
27
  ```ts
28
28
  import { Ream } from 'reamkit';
@@ -2,6 +2,7 @@ import { FontRegistry } from "../font/font-registry.js";
2
2
  import { FEATURES } from "../ir/features.js";
3
3
  import { ConversionLossError } from "../ir/loss.js";
4
4
  import { layoutStyledDocument } from "../../layout/styled-layout.js";
5
+ import { pptxReader } from "../../pptx/pptx-reader.js";
5
6
  import { docxReader } from "../../word/docx-reader.js";
6
7
  import { projectSheetDoc } from "../../excel/sheet-to-flow.js";
7
8
  import { xlsxReader } from "../../excel/xlsx-reader.js";
@@ -13,7 +14,6 @@ import { writeHtml } from "../../html/html-writer.js";
13
14
  import { writeSvg } from "../../svg/svg-writer.js";
14
15
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
15
16
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
16
- import { pptxReader } from "../../pptx/pptx-reader.js";
17
17
  import { pdfReader } from "../../pdf-reader/reader.js";
18
18
  //#region src/core/converter/facade.ts
19
19
  function toFlowDoc(doc) {
@@ -9,6 +9,7 @@ function flowRenderOptions(flow) {
9
9
  ...flow.headersFooters ? { headersFooters: flow.headersFooters } : {},
10
10
  ...flow.footnotes ? { footnotes: flow.footnotes } : {},
11
11
  ...flow.endnotes ? { endnotes: flow.endnotes } : {},
12
+ ...flow.comments ? { comments: flow.comments } : {},
12
13
  resources: flow.resources,
13
14
  ...flow.charts ? { charts: flow.charts } : {},
14
15
  ...flow.embeddedFonts ? { embeddedFonts: flow.embeddedFonts } : {},
@@ -1 +1 @@
1
- export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, Run, RunProperties, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
1
+ export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, Comment, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, Run, RunProperties, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
@@ -132,6 +132,8 @@ export interface Run {
132
132
  readonly anchor?: string;
133
133
  readonly footnoteRef?: string;
134
134
  readonly endnoteRef?: string;
135
+ readonly commentRef?: string;
136
+ readonly commentRangeRefs?: ReadonlyArray<string>;
135
137
  readonly noteNumber?: true;
136
138
  readonly listMarker?: true;
137
139
  readonly inlineImage?: InlineImage;
@@ -144,6 +146,27 @@ export interface Paragraph {
144
146
  readonly runs: ReadonlyArray<Run>;
145
147
  readonly bookmarks?: ReadonlyArray<string>;
146
148
  }
149
+ export interface Comment {
150
+ readonly content: ReadonlyArray<BodyElement>;
151
+ readonly author?: string;
152
+ readonly initials?: string;
153
+ /** Raw w:date timestamp (ISO 8601), as authored — not reformatted. */
154
+ readonly date?: string;
155
+ /**
156
+ * The author's resolved identity (usually an email) from word/people.xml
157
+ * (`w15:person` → `w15:presenceInfo/@w15:userId`), matched on the author name.
158
+ * Absent when the file ships no people part or the author is not listed.
159
+ */
160
+ readonly authorId?: string;
161
+ /**
162
+ * Microsoft commentsExtended (w15) — the id of the comment this one replies
163
+ * to, forming a thread. Set only when word/commentsExtended.xml links this
164
+ * comment's paragraph to a parent (w15:paraIdParent). Top-level comments omit it.
165
+ */
166
+ readonly parentId?: string;
167
+ /** w15:done — the comment thread was marked resolved. */
168
+ readonly done?: boolean;
169
+ }
147
170
  export type NumberingFormat = 'decimal' | 'lowerLetter' | 'upperLetter' | 'lowerRoman' | 'upperRoman' | 'bullet' | 'none';
148
171
  export interface NumberingReference {
149
172
  readonly numId: string;
@@ -4,4 +4,4 @@ export interface FontMeasure {
4
4
  readonly textWidthPt: (text: string, fontSize: number) => number;
5
5
  readonly encodeTextAsCidHex: (text: string) => string;
6
6
  }
7
- export declare function createFontMeasure(parsed: ParsedTtf): FontMeasure;
7
+ export declare function createFontMeasure(parsed: ParsedTtf, kern?: boolean): FontMeasure;
@@ -1,7 +1,9 @@
1
1
  import { shapeText } from "./opentype-layout.js";
2
2
  //#region src/core/font/measure.ts
3
- function createFontMeasure(parsed) {
3
+ var EMPTY_KERNING = /* @__PURE__ */ new Map();
4
+ function createFontMeasure(parsed, kern = true) {
4
5
  const scale = 1e3 / parsed.unitsPerEm;
6
+ const kerning = kern ? parsed.kerning : EMPTY_KERNING;
5
7
  const widths = new Array(parsed.numGlyphs);
6
8
  for (let i = 0; i < parsed.numGlyphs; i++) widths[i] = Math.round((parsed.advanceWidths[i] ?? 0) * scale);
7
9
  const pdfWidthForGid = (gid) => {
@@ -9,13 +11,13 @@ function createFontMeasure(parsed) {
9
11
  return widths[gid];
10
12
  };
11
13
  const textWidthPt = (text, fontSize) => {
12
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms);
14
+ const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
13
15
  let totalEm = 0;
14
16
  for (const a of shaped.advances) totalEm += a;
15
17
  return totalEm * fontSize / parsed.unitsPerEm;
16
18
  };
17
19
  const encodeTextAsCidHex = (text) => {
18
- const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms);
20
+ const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
19
21
  let out = "";
20
22
  for (const gid of shaped.gids) out += gid.toString(16).padStart(4, "0").toUpperCase();
21
23
  return out;
@@ -12,8 +12,17 @@ export interface ParsedTtf {
12
12
  readonly fontBBox: readonly [number, number, number, number];
13
13
  readonly ascender: number;
14
14
  readonly descender: number;
15
+ readonly lineGap: number;
15
16
  readonly capHeight: number;
16
17
  readonly xHeight: number;
18
+ readonly vmetrics: {
19
+ readonly typoAscent: number;
20
+ readonly typoDescent: number;
21
+ readonly typoLineGap: number;
22
+ readonly winAscent: number;
23
+ readonly winDescent: number;
24
+ readonly useTypoMetrics: boolean;
25
+ };
17
26
  readonly italicAngle: number;
18
27
  readonly stemV: number;
19
28
  readonly flags: number;
@@ -41,7 +41,7 @@ function parseTtf(raw) {
41
41
  r.seek(hhea.offset + 4);
42
42
  const ascender = r.i16();
43
43
  const descender = r.i16();
44
- r.skip(2);
44
+ const lineGap = r.i16();
45
45
  r.skip(24);
46
46
  const numberOfHMetrics = r.u16();
47
47
  const maxp = require("maxp");
@@ -74,6 +74,12 @@ function parseTtf(raw) {
74
74
  let xHeight = 0;
75
75
  let isItalic = false;
76
76
  let usWeightClass = 400;
77
+ let typoAscent = ascender;
78
+ let typoDescent = descender;
79
+ let typoLineGap = lineGap;
80
+ let winAscent = Math.abs(ascender);
81
+ let winDescent = Math.abs(descender);
82
+ let useTypoMetrics = false;
77
83
  const os2 = tables.get("OS/2");
78
84
  if (os2) {
79
85
  r.seek(os2.offset);
@@ -81,7 +87,17 @@ function parseTtf(raw) {
81
87
  r.skip(2);
82
88
  usWeightClass = r.u16();
83
89
  r.seek(os2.offset + 62);
84
- isItalic = (r.u16() & 1) !== 0;
90
+ const fsSelection = r.u16();
91
+ isItalic = (fsSelection & 1) !== 0;
92
+ useTypoMetrics = (fsSelection & 128) !== 0;
93
+ if (os2.length >= 78) {
94
+ r.seek(os2.offset + 68);
95
+ typoAscent = r.i16();
96
+ typoDescent = r.i16();
97
+ typoLineGap = r.i16();
98
+ winAscent = r.u16();
99
+ winDescent = r.u16();
100
+ }
85
101
  if (os2Version >= 2 && os2.length >= 90) {
86
102
  r.seek(os2.offset + 86);
87
103
  xHeight = r.i16();
@@ -113,8 +129,17 @@ function parseTtf(raw) {
113
129
  ],
114
130
  ascender,
115
131
  descender,
132
+ lineGap,
116
133
  capHeight,
117
134
  xHeight,
135
+ vmetrics: {
136
+ typoAscent,
137
+ typoDescent,
138
+ typoLineGap,
139
+ winAscent,
140
+ winDescent,
141
+ useTypoMetrics
142
+ },
118
143
  italicAngle,
119
144
  stemV,
120
145
  flags,
@@ -35,7 +35,8 @@ export declare function callerFontProvider(fonts: FontBytesByVariant): FontProvi
35
35
  */
36
36
  export declare function embeddedDocFontProvider(embedded: ReadonlyMap<string, FontRegistry>): FontProvider;
37
37
  /**
38
- * Open CDN substitutes (Roboto / Tinos / Cousine — the LibreOffice mapping).
38
+ * Open CDN substitutes (Arimo / Tinos / Cousine / Carlito / Caladea — the
39
+ * LibreOffice metric-compatible mapping).
39
40
  * Always answers; the chain reports it as a substitution.
40
41
  */
41
42
  export declare function remoteFontProvider(options?: {
@@ -62,7 +62,8 @@ function embeddedDocFontProvider(embedded) {
62
62
  };
63
63
  }
64
64
  /**
65
- * Open CDN substitutes (Roboto / Tinos / Cousine — the LibreOffice mapping).
65
+ * Open CDN substitutes (Arimo / Tinos / Cousine / Carlito / Caladea — the
66
+ * LibreOffice metric-compatible mapping).
66
67
  * Always answers; the chain reports it as a substitution.
67
68
  */
68
69
  function remoteFontProvider(options = {}) {
@@ -1,5 +1,5 @@
1
1
  import { FontBytesByVariant } from '../font/index.js';
2
- export type FamilyKey = 'roboto' | 'tinos' | 'cousine';
2
+ export type FamilyKey = 'arimo' | 'tinos' | 'cousine' | 'carlito' | 'caladea';
3
3
  export declare function resolveFamilyKey(name: string | undefined): FamilyKey;
4
4
  export type FetchLike = (url: string) => Promise<{
5
5
  ok: boolean;
@@ -7,9 +7,9 @@ var VARIANT_SUFFIX = {
7
7
  boldItalic: "700Bold_Italic"
8
8
  };
9
9
  var FAMILIES = {
10
- roboto: {
11
- pkg: "roboto",
12
- file: "Roboto"
10
+ arimo: {
11
+ pkg: "arimo",
12
+ file: "Arimo"
13
13
  },
14
14
  tinos: {
15
15
  pkg: "tinos",
@@ -18,13 +18,32 @@ var FAMILIES = {
18
18
  cousine: {
19
19
  pkg: "cousine",
20
20
  file: "Cousine"
21
+ },
22
+ carlito: {
23
+ pkg: "carlito",
24
+ file: "Carlito",
25
+ nested: true
26
+ },
27
+ caladea: {
28
+ pkg: "caladea",
29
+ file: "Caladea"
21
30
  }
22
31
  };
32
+ var EXACT = {
33
+ calibri: "carlito",
34
+ cambria: "caladea",
35
+ arial: "arimo",
36
+ helvetica: "arimo",
37
+ "liberation sans": "arimo",
38
+ "times new roman": "tinos",
39
+ times: "tinos",
40
+ "liberation serif": "tinos",
41
+ "courier new": "cousine",
42
+ courier: "cousine",
43
+ "liberation mono": "cousine"
44
+ };
23
45
  var SERIF = new Set([
24
- "times",
25
- "times new roman",
26
46
  "georgia",
27
- "cambria",
28
47
  "garamond",
29
48
  "book antiqua",
30
49
  "palatino",
@@ -33,8 +52,6 @@ var SERIF = new Set([
33
52
  "serif"
34
53
  ]);
35
54
  var MONO = new Set([
36
- "courier",
37
- "courier new",
38
55
  "consolas",
39
56
  "monaco",
40
57
  "menlo",
@@ -43,14 +60,18 @@ var MONO = new Set([
43
60
  "monospace"
44
61
  ]);
45
62
  function resolveFamilyKey(name) {
46
- if (!name) return "roboto";
63
+ if (!name) return "arimo";
47
64
  const n = name.trim().toLowerCase();
65
+ const exact = EXACT[n];
66
+ if (exact) return exact;
48
67
  if (MONO.has(n)) return "cousine";
49
68
  if (SERIF.has(n)) return "tinos";
50
- return "roboto";
69
+ return "arimo";
51
70
  }
52
71
  function fontUrl(family, variant) {
53
- return `${CDN_BASE}/${family.pkg}/${family.file}_${VARIANT_SUFFIX[variant]}.ttf`;
72
+ const suffix = VARIANT_SUFFIX[variant];
73
+ const leaf = `${family.file}_${suffix}.ttf`;
74
+ return family.nested ? `${CDN_BASE}/${family.pkg}/${suffix}/${leaf}` : `${CDN_BASE}/${family.pkg}/${leaf}`;
54
75
  }
55
76
  var cache = /* @__PURE__ */ new Map();
56
77
  async function fetchTtf(url, fetchImpl, required) {
@@ -10,6 +10,7 @@ export declare const FEATURES: {
10
10
  readonly imagesJp2: "images.jp2";
11
11
  readonly hyperlinks: "hyperlinks";
12
12
  readonly shapes: "shapes";
13
+ readonly smartArt: "shapes.smartArt";
13
14
  readonly charts: "charts";
14
15
  readonly math: "math";
15
16
  readonly rtl: "rtl";
@@ -10,6 +10,7 @@ var FEATURES = {
10
10
  imagesJp2: "images.jp2",
11
11
  hyperlinks: "hyperlinks",
12
12
  shapes: "shapes",
13
+ smartArt: "shapes.smartArt",
13
14
  charts: "charts",
14
15
  math: "math",
15
16
  rtl: "rtl",
@@ -1,4 +1,4 @@
1
- import { BodyElement, Chart, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../document-model/index.js';
1
+ import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../document-model/index.js';
2
2
  import { FontRegistry } from '../font/index.js';
3
3
  import { ResourceStore } from './resources.js';
4
4
  export interface FlowDoc {
@@ -14,6 +14,8 @@ export interface FlowDoc {
14
14
  /** §17.11 footnotes/endnotes content by id (separator stubs excluded). */
15
15
  readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
16
16
  readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
17
+ /** §17.13.4 review comments by id, anchored from a run's `commentRef`. */
18
+ readonly comments?: ReadonlyMap<string, Comment>;
17
19
  /** Parsed charts keyed by relationship id (ChartBlock.chartRelId). */
18
20
  readonly charts?: ReadonlyMap<string, Chart>;
19
21
  /** Content-addressed binary resources (images). */
@@ -0,0 +1,2 @@
1
+ import { Item } from './knuth-plass.js';
2
+ export declare function greedyBreakLines(items: ReadonlyArray<Item>, lineWidths: number | ReadonlyArray<number>): ReadonlyArray<number>;
@@ -0,0 +1,67 @@
1
+ import { FORBIDDEN_BREAK } from "./knuth-plass.js";
2
+ //#region src/core/line-breaker/greedy.ts
3
+ function greedyBreakLines(items, lineWidths) {
4
+ if (items.length === 0) return [];
5
+ const widthFor = (line) => typeof lineWidths === "number" ? lineWidths : lineWidths[Math.min(line, lineWidths.length - 1)];
6
+ const widthBefore = new Array(items.length + 1);
7
+ widthBefore[0] = 0;
8
+ for (let i = 0; i < items.length; i++) {
9
+ const it = items[i];
10
+ widthBefore[i + 1] = widthBefore[i] + (it.type === "penalty" ? 0 : it.width);
11
+ }
12
+ const lineWidth = (start, b) => {
13
+ let w = widthBefore[b] - widthBefore[start];
14
+ const item = items[b];
15
+ if (item.type === "penalty") w += item.width;
16
+ return w;
17
+ };
18
+ const isFeasible = (i) => {
19
+ const item = items[i];
20
+ if (item.type === "penalty") return item.penalty < FORBIDDEN_BREAK;
21
+ if (item.type === "glue") {
22
+ const prev = items[i - 1];
23
+ return !!prev && prev.type === "box";
24
+ }
25
+ return false;
26
+ };
27
+ const breaks = [];
28
+ let lineStart = 0;
29
+ let lineNo = 0;
30
+ let lastGood = -1;
31
+ let i = 0;
32
+ while (i < items.length) {
33
+ if (!isFeasible(i)) {
34
+ i++;
35
+ continue;
36
+ }
37
+ const item = items[i];
38
+ const forced = item.type === "penalty" && item.penalty <= -1e4;
39
+ const fits = lineWidth(lineStart, i) <= widthFor(lineNo);
40
+ if (!fits && lastGood >= 0) {
41
+ breaks.push(lastGood);
42
+ lineStart = lastGood + 1;
43
+ lineNo++;
44
+ lastGood = -1;
45
+ continue;
46
+ }
47
+ if (forced) {
48
+ breaks.push(i);
49
+ lineStart = i + 1;
50
+ lineNo++;
51
+ lastGood = -1;
52
+ i++;
53
+ continue;
54
+ }
55
+ if (fits) lastGood = i;
56
+ else {
57
+ breaks.push(i);
58
+ lineStart = i + 1;
59
+ lineNo++;
60
+ lastGood = -1;
61
+ }
62
+ i++;
63
+ }
64
+ return breaks;
65
+ }
66
+ //#endregion
67
+ export { greedyBreakLines };
@@ -1,2 +1,3 @@
1
1
  export { FORCED_BREAK, breakLines } from './knuth-plass.js';
2
+ export { greedyBreakLines } from './greedy.js';
2
3
  export type { Item, BoxItem, GlueItem, PenaltyItem, BreakResult, } from './knuth-plass.js';
@@ -20,6 +20,7 @@ export interface BreakResult {
20
20
  readonly ratios: ReadonlyArray<number>;
21
21
  }
22
22
  export declare const FORCED_BREAK = -10000;
23
+ export declare const FORBIDDEN_BREAK = 10000;
23
24
  /**
24
25
  * Break a paragraph into lines using the Knuth-Plass total-fit algorithm.
25
26
  *
@@ -47,7 +47,7 @@ function breakLines(items, lineWidths) {
47
47
  let active = [startNode];
48
48
  const isFeasibleBreakpoint = (i) => {
49
49
  const item = items[i];
50
- if (item.type === "penalty" && item.penalty < FORBIDDEN_BREAK) return true;
50
+ if (item.type === "penalty" && item.penalty < 1e4) return true;
51
51
  if (item.type === "glue") {
52
52
  const prev = items[i - 1];
53
53
  return !!prev && prev.type === "box";
@@ -90,7 +90,7 @@ function breakLines(items, lineWidths) {
90
90
  const lineDemerits = (r, penaltyAtBreak, lastItemPenalty) => {
91
91
  const badness = 100 * Math.pow(Math.abs(r), 3);
92
92
  let d;
93
- if (penaltyAtBreak >= 0 && penaltyAtBreak < FORBIDDEN_BREAK) d = Math.pow(LINE_PENALTY + badness + penaltyAtBreak, 2);
93
+ if (penaltyAtBreak >= 0 && penaltyAtBreak < 1e4) d = Math.pow(LINE_PENALTY + badness + penaltyAtBreak, 2);
94
94
  else if (penaltyAtBreak > -1e4 && penaltyAtBreak < 0) d = Math.pow(LINE_PENALTY + badness, 2) - penaltyAtBreak * penaltyAtBreak;
95
95
  else d = Math.pow(LINE_PENALTY + badness, 2);
96
96
  return d;
@@ -169,4 +169,4 @@ function breakLines(items, lineWidths) {
169
169
  };
170
170
  }
171
171
  //#endregion
172
- export { FORCED_BREAK, breakLines };
172
+ export { FORBIDDEN_BREAK, FORCED_BREAK, breakLines };
@@ -6,6 +6,8 @@ export declare function poChildren(node: PoNode | undefined): ReadonlyArray<PoNo
6
6
  export declare function poChildrenWith(node: PoNode | undefined, tag: string): Array<PoNode>;
7
7
  export declare function poFirstChild(node: PoNode | undefined, tag: string): PoNode | undefined;
8
8
  export declare function poAttr(node: PoNode | undefined, name: string): string | undefined;
9
+ export declare function poAttrLocal(node: PoNode | undefined, name: string): string | undefined;
10
+ export declare function poIsLocal(node: PoNode | undefined, name: string): boolean;
9
11
  export declare function poIntAttr(node: PoNode | undefined, name: string): number | undefined;
10
12
  export declare function poVal(node: PoNode | undefined): string | undefined;
11
13
  export declare function poToggle(node: PoNode | undefined): boolean | undefined;
@@ -28,6 +28,19 @@ function poAttr(node, name) {
28
28
  const v = a[`@_w:${name}`] ?? a[`@_r:${name}`] ?? a[`@_m:${name}`] ?? a[`@_xml:${name}`] ?? a[`@_${name}`];
29
29
  return typeof v === "string" ? v : void 0;
30
30
  }
31
+ function poAttrLocal(node, name) {
32
+ if (!node || typeof node !== "object") return void 0;
33
+ const attrs = node[ATTRS_KEY];
34
+ if (!attrs || typeof attrs !== "object") return void 0;
35
+ for (const [k, v] of Object.entries(attrs)) {
36
+ if (!k.startsWith("@_")) continue;
37
+ if (k.slice(2).split(":").pop() === name && typeof v === "string") return v;
38
+ }
39
+ }
40
+ function poIsLocal(node, name) {
41
+ const tag = poTag(node);
42
+ return tag !== void 0 && (tag === name || tag.split(":").pop() === name);
43
+ }
31
44
  function poIntAttr(node, name) {
32
45
  const v = poAttr(node, name);
33
46
  if (v === void 0) return void 0;
@@ -73,4 +86,4 @@ function poFindDescendant(node, tag) {
73
86
  }
74
87
  }
75
88
  //#endregion
76
- export { poAttr, poChildren, poChildrenWith, poFindByPath, poFindDescendant, poFirstChild, poIntAttr, poIs, poTag, poText, poToggle, poVal };
89
+ export { poAttr, poAttrLocal, poChildren, poChildrenWith, poFindByPath, poFindDescendant, poFirstChild, poIntAttr, poIs, poIsLocal, poTag, poText, poToggle, poVal };
@@ -1 +1 @@
1
- export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRule, ConditionalFormat, SparklineKind, ParsedSparkline, ExcelTable, SheetPane, } from './types.js';
1
+ export type { CellType, WorksheetCell, ColumnWidth, MergedRange, RowHeight, XlsxPageMargins, XlsxPageSetup, XlsxPrintOptions, ParsedWorksheet, XlsxFont, XlsxFill, XlsxBorderStyleName, XlsxBorderEdge, XlsxBorder, XlsxHorizontalAlign, XlsxVerticalAlign, XlsxCellAlignment, XlsxCellXf, XlsxStyles, DefinedName, Dxf, CfOperator, CfRuleCellIs, CfvoType, Cfvo, CfRuleColorScale, CfRuleDataBar, CfRuleIconSet, CfRule, ConditionalFormat, SparklineKind, ParsedSparkline, ExcelTable, PivotTable, SheetPane, } from './types.js';
@@ -61,6 +61,7 @@ export interface ParsedWorksheet {
61
61
  readonly sparklines?: ReadonlyArray<ParsedSparkline>;
62
62
  readonly tablePartRelIds?: ReadonlyArray<string>;
63
63
  readonly tables?: ReadonlyArray<ExcelTable>;
64
+ readonly pivotTables?: ReadonlyArray<PivotTable>;
64
65
  readonly pane?: SheetPane;
65
66
  }
66
67
  export interface SheetPane {
@@ -81,6 +82,21 @@ export interface ExcelTable {
81
82
  readonly bandHex?: string;
82
83
  readonly headerTextHex?: string;
83
84
  }
85
+ export interface PivotTable {
86
+ readonly ref: MergedRange;
87
+ readonly name?: string;
88
+ readonly styleName?: string;
89
+ readonly firstHeaderRow: number;
90
+ readonly firstDataRow: number;
91
+ readonly firstDataCol: number;
92
+ readonly showRowStripes: boolean;
93
+ readonly showColStripes: boolean;
94
+ readonly rowItemTypes?: ReadonlyArray<string | undefined>;
95
+ readonly colItemTypes?: ReadonlyArray<string | undefined>;
96
+ readonly headerHex?: string;
97
+ readonly bandHex?: string;
98
+ readonly headerTextHex?: string;
99
+ }
84
100
  export type SparklineKind = 'line' | 'column' | 'winLoss';
85
101
  export interface ParsedSparkline {
86
102
  readonly kind: SparklineKind;
@@ -0,0 +1,2 @@
1
+ import { PivotTable } from '../core/spreadsheet-model/index.js';
2
+ export declare function parsePivotTablePart(data: Uint8Array): PivotTable | undefined;
@@ -0,0 +1,68 @@
1
+ import { parseAreaRef } from "./defined-name-ref.js";
2
+ import { XMLParser } from "fast-xml-parser";
3
+ //#region src/excel/pivot-table-parser.ts
4
+ var decoder = new TextDecoder("utf-8");
5
+ var parser = new XMLParser({
6
+ ignoreAttributes: false,
7
+ attributeNamePrefix: "@_",
8
+ parseAttributeValue: false,
9
+ parseTagValue: false,
10
+ removeNSPrefix: true
11
+ });
12
+ function parsePivotTablePart(data) {
13
+ const def = asObj(parser.parse(decoder.decode(data))["pivotTableDefinition"]);
14
+ if (!def) return void 0;
15
+ const loc = asObj(def["location"]);
16
+ const refStr = loc ? strAttr(loc, "ref") : void 0;
17
+ const ref = refStr ? parseAreaRef(refStr) : void 0;
18
+ if (!ref) return void 0;
19
+ const si = asObj(def["pivotTableStyleInfo"]);
20
+ const name = strAttr(def, "name");
21
+ const styleName = si ? strAttr(si, "name") : void 0;
22
+ const rowItemTypes = itemTypes(asObj(def["rowItems"]));
23
+ const colItemTypes = itemTypes(asObj(def["colItems"]));
24
+ const hasRowTotals = rowItemTypes?.some((t) => t !== void 0) ?? false;
25
+ const hasColTotals = colItemTypes?.some((t) => t !== void 0) ?? false;
26
+ return {
27
+ ref,
28
+ ...name ? { name } : {},
29
+ ...styleName ? { styleName } : {},
30
+ firstHeaderRow: (loc ? numAttr(loc, "firstHeaderRow") : void 0) ?? 1,
31
+ firstDataRow: (loc ? numAttr(loc, "firstDataRow") : void 0) ?? 1,
32
+ firstDataCol: (loc ? numAttr(loc, "firstDataCol") : void 0) ?? 0,
33
+ showRowStripes: si ? boolAttr(si, "showRowStripes") : false,
34
+ showColStripes: si ? boolAttr(si, "showColStripes") : false,
35
+ ...hasRowTotals && rowItemTypes ? { rowItemTypes } : {},
36
+ ...hasColTotals && colItemTypes ? { colItemTypes } : {}
37
+ };
38
+ }
39
+ function itemTypes(node) {
40
+ if (!node) return void 0;
41
+ return toArray(node["i"]).map((i) => {
42
+ const o = asObj(i);
43
+ return o ? strAttr(o, "t") : void 0;
44
+ });
45
+ }
46
+ function toArray(v) {
47
+ if (v === void 0 || v === null) return [];
48
+ return Array.isArray(v) ? v : [v];
49
+ }
50
+ function asObj(v) {
51
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
52
+ }
53
+ function strAttr(obj, name) {
54
+ const v = obj[`@_${name}`];
55
+ return typeof v === "string" ? v : void 0;
56
+ }
57
+ function numAttr(obj, name) {
58
+ const v = strAttr(obj, name);
59
+ if (v === void 0) return void 0;
60
+ const n = Number(v);
61
+ return Number.isFinite(n) ? n : void 0;
62
+ }
63
+ function boolAttr(obj, name) {
64
+ const v = strAttr(obj, name);
65
+ return v === "1" || v === "true";
66
+ }
67
+ //#endregion
68
+ export { parsePivotTablePart };
@@ -525,18 +525,23 @@ function collectSeriesValues(cells, area) {
525
525
  for (let r = area.startRow; r <= area.endRow; r++) for (let col = area.startColumn; col <= area.endColumn; col++) out.push(byKey.get(key(r, col)) ?? null);
526
526
  return out;
527
527
  }
528
+ function isPivotTotal(t) {
529
+ return t !== void 0 && t !== "data" && t !== "blank";
530
+ }
528
531
  function buildTableFormatLookup(worksheet) {
529
532
  const out = /* @__PURE__ */ new Map();
530
- for (const t of worksheet.tables ?? []) {
531
- const { ref } = t;
532
- const firstDataRow = ref.startRow + t.headerRowCount;
533
+ const band = (ref, headerRows, style, isTotalRow) => {
534
+ const firstDataRow = ref.startRow + headerRows;
533
535
  for (let r = ref.startRow; r <= ref.endRow; r++) {
534
536
  let colorHex;
535
537
  let fontColorHex;
536
538
  if (r < firstDataRow) {
537
- colorHex = t.headerHex;
538
- fontColorHex = t.headerTextHex;
539
- } else if (t.showRowStripes && t.bandHex) colorHex = (r - firstDataRow) % 2 === 1 ? t.bandHex : void 0;
539
+ colorHex = style.headerHex;
540
+ fontColorHex = style.headerTextHex;
541
+ } else if (isTotalRow?.(r - firstDataRow)) {
542
+ colorHex = style.headerHex;
543
+ fontColorHex = style.headerTextHex;
544
+ } else if (style.showRowStripes && style.bandHex) colorHex = (r - firstDataRow) % 2 === 1 ? style.bandHex : void 0;
540
545
  if (!colorHex && !fontColorHex) continue;
541
546
  const fmt = {
542
547
  ...colorHex ? { shading: { colorHex } } : {},
@@ -544,6 +549,23 @@ function buildTableFormatLookup(worksheet) {
544
549
  };
545
550
  for (let c = ref.startColumn; c <= ref.endColumn; c++) out.set(key(r, c), fmt);
546
551
  }
552
+ };
553
+ for (const t of worksheet.tables ?? []) band(t.ref, t.headerRowCount, t);
554
+ for (const p of worksheet.pivotTables ?? []) band(p.ref, p.firstDataRow, p, (off) => isPivotTotal(p.rowItemTypes?.[off]));
555
+ for (const p of worksheet.pivotTables ?? []) {
556
+ if (!p.colItemTypes || p.headerHex === void 0) continue;
557
+ const firstDataRow = p.ref.startRow + p.firstDataRow;
558
+ const firstDataCol = p.ref.startColumn + p.firstDataCol;
559
+ const totalFmt = {
560
+ shading: { colorHex: p.headerHex },
561
+ ...p.headerTextHex ? { fontColorHex: p.headerTextHex } : {}
562
+ };
563
+ for (let i = 0; i < p.colItemTypes.length; i++) {
564
+ if (!isPivotTotal(p.colItemTypes[i])) continue;
565
+ const c = firstDataCol + i;
566
+ if (c > p.ref.endColumn) continue;
567
+ for (let r = firstDataRow; r <= p.ref.endRow; r++) out.set(key(r, c), totalFmt);
568
+ }
547
569
  }
548
570
  return out;
549
571
  }