reamkit 1.1.0 → 1.3.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 (68) hide show
  1. package/README.md +11 -5
  2. package/dist/esm/{layout → core}/arc-to-bezier.d.ts +1 -1
  3. package/dist/esm/{layout → core}/arc-to-bezier.js +2 -2
  4. package/dist/esm/core/converter/project.js +2 -0
  5. package/dist/esm/core/converter/ream.js +4 -3
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +60 -0
  8. package/dist/esm/{layout → core/drawingml}/chart-geometry.d.ts +2 -2
  9. package/dist/esm/{layout → core/drawingml}/chart-geometry.js +79 -117
  10. package/dist/esm/core/drawingml/chart-parser.d.ts +3 -0
  11. package/dist/esm/core/drawingml/chart-parser.js +28 -1
  12. package/dist/esm/{layout → core/drawingml}/preset-geometry.d.ts +2 -2
  13. package/dist/esm/{layout → core/drawingml}/preset-geometry.js +3 -3
  14. package/dist/esm/core/drawingml/shape-render.d.ts +8 -0
  15. package/dist/esm/core/drawingml/shape-render.js +71 -0
  16. package/dist/esm/core/ir/features.d.ts +1 -0
  17. package/dist/esm/core/ir/features.js +1 -0
  18. package/dist/esm/core/ir/flow.d.ts +3 -0
  19. package/dist/esm/core/links.d.ts +6 -0
  20. package/dist/esm/core/links.js +19 -0
  21. package/dist/esm/core/numbering/apply.js +5 -2
  22. package/dist/esm/core/opc/index.d.ts +1 -0
  23. package/dist/esm/core/opc/package.js +3 -2
  24. package/dist/esm/core/opc/relationship-types.d.ts +1 -0
  25. package/dist/esm/core/opc/relationship-types.js +5 -2
  26. package/dist/esm/core/style-cascade/index.d.ts +1 -0
  27. package/dist/esm/core/style-cascade/index.js +1 -0
  28. package/dist/esm/core/style-cascade/table.d.ts +2 -0
  29. package/dist/esm/core/style-cascade/table.js +161 -0
  30. package/dist/esm/core/vector.d.ts +2 -0
  31. package/dist/esm/core/vector.js +19 -1
  32. package/dist/esm/excel/print-model.d.ts +3 -0
  33. package/dist/esm/excel/print-model.js +4 -8
  34. package/dist/esm/excel/sheet-drawing.d.ts +9 -0
  35. package/dist/esm/excel/sheet-drawing.js +113 -0
  36. package/dist/esm/excel/worksheet-parser.d.ts +1 -0
  37. package/dist/esm/excel/worksheet-parser.js +4 -1
  38. package/dist/esm/excel/xlsx-reader.js +41 -0
  39. package/dist/esm/excel/xlsx-to-pdf.js +25 -18
  40. package/dist/esm/html/html-writer.js +188 -16
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/layout/page-doc.d.ts +4 -0
  43. package/dist/esm/layout/styled-layout.d.ts +21 -3
  44. package/dist/esm/layout/styled-layout.js +745 -259
  45. package/dist/esm/pdf/encryption.d.ts +34 -0
  46. package/dist/esm/pdf/encryption.js +143 -0
  47. package/dist/esm/pdf/index.d.ts +2 -1
  48. package/dist/esm/pdf/struct-tree.d.ts +7 -1
  49. package/dist/esm/pdf/struct-tree.js +22 -6
  50. package/dist/esm/pdf/styled-page-emitter.d.ts +3 -1
  51. package/dist/esm/pdf/styled-page-emitter.js +185 -35
  52. package/dist/esm/pdf/styled-page-renderer.d.ts +1 -0
  53. package/dist/esm/pdf/styled-page-renderer.js +17 -2
  54. package/dist/esm/pdf/writer.d.ts +2 -0
  55. package/dist/esm/pdf/writer.js +6 -1
  56. package/dist/esm/pdf/xmp.d.ts +1 -0
  57. package/dist/esm/pdf/xmp.js +4 -0
  58. package/dist/esm/svg/svg-writer.js +2 -6
  59. package/dist/esm/word/document-parser.d.ts +3 -0
  60. package/dist/esm/word/document-parser.js +214 -23
  61. package/dist/esm/word/docx-reader.js +37 -15
  62. package/dist/esm/word/docx-to-pdf.js +23 -13
  63. package/dist/esm/word/drawing-parser.d.ts +4 -1
  64. package/dist/esm/word/drawing-parser.js +53 -2
  65. package/dist/esm/word/index.d.ts +2 -2
  66. package/dist/esm/word/styles-parser.js +134 -1
  67. package/dist/esm/word/table-parser.js +35 -0
  68. package/package.json +1 -1
@@ -8,6 +8,7 @@ var FEATURES = {
8
8
  headersFooters: "headersFooters",
9
9
  images: "images",
10
10
  imagesJp2: "images.jp2",
11
+ hyperlinks: "hyperlinks",
11
12
  shapes: "shapes",
12
13
  charts: "charts",
13
14
  math: "math",
@@ -11,6 +11,9 @@ export interface FlowDoc {
11
11
  readonly styles: StyleSheet;
12
12
  readonly numbering?: Numbering;
13
13
  readonly headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
14
+ /** §17.11 footnotes/endnotes content by id (separator stubs excluded). */
15
+ readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
16
+ readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
14
17
  /** Parsed charts keyed by relationship id (ChartBlock.chartRelId). */
15
18
  readonly charts?: ReadonlyMap<string, Chart>;
16
19
  /** Content-addressed binary resources (images). */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns the trimmed URL when its scheme is on the allowlist, undefined
3
+ * otherwise (including scheme-less/relative targets — those are not external
4
+ * links for a converted document).
5
+ */
6
+ export declare function sanitizeHref(href: string): string | undefined;
@@ -0,0 +1,19 @@
1
+ //#region src/core/links.ts
2
+ var ALLOWED_SCHEMES = new Set([
3
+ "http",
4
+ "https",
5
+ "mailto"
6
+ ]);
7
+ /**
8
+ * Returns the trimmed URL when its scheme is on the allowlist, undefined
9
+ * otherwise (including scheme-less/relative targets — those are not external
10
+ * links for a converted document).
11
+ */
12
+ function sanitizeHref(href) {
13
+ const url = href.trim();
14
+ const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
15
+ if (!m) return void 0;
16
+ return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? url : void 0;
17
+ }
18
+ //#endregion
19
+ export { sanitizeHref };
@@ -12,10 +12,13 @@ function applyNumbering(body, numbering) {
12
12
  const level = (instance ? numbering.abstractNums.get(instance.abstractNumId) : void 0)?.levels.get(ref.ilvl);
13
13
  const markerRun = {
14
14
  text: `${marker}\t`,
15
- properties: level?.runProperties ?? {}
15
+ properties: level?.runProperties ?? {},
16
+ listMarker: true
16
17
  };
18
+ const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
17
19
  return {
18
- properties: mergeIndentFromLevel(p.properties, level?.paragraphProperties),
20
+ ...p,
21
+ properties: newProps,
19
22
  runs: [markerRun, ...p.runs]
20
23
  };
21
24
  };
@@ -3,3 +3,4 @@ export type { Relationship } from './relationships.js';
3
3
  export { REL_OFFICE_DOCUMENT, REL_CORE_PROPERTIES, REL_EXTENDED_PROPERTIES, } from './relationship-types.js';
4
4
  export { parseCoreProperties } from './core-properties.js';
5
5
  export type { CoreProperties } from './core-properties.js';
6
+ export { isOoxmlRel } from './relationship-types.js';
@@ -1,5 +1,5 @@
1
1
  import { parseRelationships } from "./relationships.js";
2
- import "./relationship-types.js";
2
+ import { isOoxmlRel } from "./relationship-types.js";
3
3
  import { unzipSync } from "fflate";
4
4
  //#region src/core/opc/package.ts
5
5
  var ROOT_RELS_PATH = "_rels/.rels";
@@ -11,6 +11,7 @@ var OpcPackage = class OpcPackage {
11
11
  }
12
12
  relsCache = /* @__PURE__ */ new Map();
13
13
  static open(buffer, options = {}) {
14
+ if (buffer.length >= 4 && buffer[0] === 208 && buffer[1] === 207 && buffer[2] === 17 && buffer[3] === 224) throw new Error("OLE compound file: a password-protected (encrypted) OOXML document or a legacy binary .doc/.xls — re-save as an unencrypted .docx/.xlsx");
14
15
  const maxArchive = options.maxArchiveBytes ?? 128 * MIB;
15
16
  const maxEntry = options.maxEntryBytes ?? 256 * MIB;
16
17
  const maxTotal = options.maxTotalBytes ?? 512 * MIB;
@@ -77,7 +78,7 @@ var OpcPackage = class OpcPackage {
77
78
  };
78
79
  }
79
80
  getMainDocumentPath() {
80
- const candidates = this.rootRelationships.filter((r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" && r.targetMode === "Internal");
81
+ const candidates = this.rootRelationships.filter((r) => isOoxmlRel(r.type, "officeDocument") && r.targetMode === "Internal");
81
82
  if (candidates.length === 0) throw new Error("OPC package has no officeDocument relationship");
82
83
  if (candidates.length > 1) throw new Error(`OPC package has multiple officeDocument relationships (${candidates.length})`);
83
84
  return resolveTarget("/", candidates[0].target);
@@ -1,3 +1,4 @@
1
1
  export declare const REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
2
2
  export declare const REL_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
3
3
  export declare const REL_EXTENDED_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
4
+ export declare function isOoxmlRel(type: string, name: string): boolean;
@@ -1,4 +1,7 @@
1
1
  //#region src/core/opc/relationship-types.ts
2
- var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
2
+ var OOXML_REL_BASES = ["http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "http://purl.oclc.org/ooxml/officeDocument/relationships/"];
3
+ function isOoxmlRel(type, name) {
4
+ return OOXML_REL_BASES.some((base) => type === base + name);
5
+ }
3
6
  //#endregion
4
- export { REL_OFFICE_DOCUMENT };
7
+ export { isOoxmlRel };
@@ -1,3 +1,4 @@
1
1
  export { EMPTY_STYLE_SHEET, resolveBodyStyles, resolveHeadersFootersStyles, resolveParagraphProperties, resolveRunProperties, } from './resolver.js';
2
2
  export type { ResolvedRunProperties, ResolvedParagraphProperties, } from './types.js';
3
3
  export { DEFAULT_RESOLVED_RUN, DEFAULT_RESOLVED_PARAGRAPH } from './types.js';
4
+ export { resolveTableStyles } from './table.js';
@@ -1,2 +1,3 @@
1
1
  import "./types.js";
2
2
  import "./resolver.js";
3
+ import "./table.js";
@@ -0,0 +1,2 @@
1
+ import { BodyElement, StyleSheet } from '../document-model/index.js';
2
+ export declare function resolveTableStyles(body: ReadonlyArray<BodyElement>, sheet: StyleSheet): ReadonlyArray<BodyElement>;
@@ -0,0 +1,161 @@
1
+ //#region src/core/style-cascade/table.ts
2
+ function resolveTableStyles(body, sheet) {
3
+ const visit = (el) => {
4
+ if (el.kind === "table") {
5
+ applyTableStyle(el.table, sheet);
6
+ for (const row of el.table.rows) for (const cell of row.cells) for (const child of cell.content) visit(child);
7
+ } else if (el.kind === "shape" && el.shape.text) for (const child of el.shape.text.content) visit(child);
8
+ };
9
+ for (const el of body) visit(el);
10
+ return body;
11
+ }
12
+ function applyTableStyle(table, sheet) {
13
+ const styleId = table.properties.styleId;
14
+ if (!styleId) return;
15
+ const folded = foldTableStyleChain(styleId, sheet);
16
+ if (!folded) return;
17
+ const tp = table.properties;
18
+ if (!tp.borders && folded.base.borders) tp.borders = folded.base.borders;
19
+ if (!tp.defaultCellMargins && folded.base.cellMargins) tp.defaultCellMargins = folded.base.cellMargins;
20
+ const look = table.properties.look ?? {};
21
+ const rows = table.rows;
22
+ const colCount = Math.max(table.grid.length, ...rows.map((r) => r.cells.reduce((s, c) => s + (c.properties.colSpan ?? 1), 0)));
23
+ for (let r = 0; r < rows.length; r++) {
24
+ const row = rows[r];
25
+ let colStart = 0;
26
+ for (const cell of row.cells) {
27
+ const span = cell.properties.colSpan ?? 1;
28
+ const layer = cellLayer(folded, look, {
29
+ firstRow: r === 0,
30
+ lastRow: r === rows.length - 1,
31
+ firstCol: colStart === 0,
32
+ lastCol: colStart + span >= colCount,
33
+ rowBand: bandIndex(r, look.firstRow === true, folded.rowBandSize),
34
+ colBand: bandIndex(colStart, look.firstColumn === true, folded.colBandSize),
35
+ noHBand: look.noHBand === true,
36
+ noVBand: look.noVBand === true
37
+ });
38
+ colStart += span;
39
+ if (!layer) continue;
40
+ applyLayerToCell(cell, layer);
41
+ }
42
+ }
43
+ }
44
+ function applyLayerToCell(tableCell, layer) {
45
+ const cell = tableCell;
46
+ if (layer.shading && !cell.properties.shading) cell.properties.shading = layer.shading;
47
+ if (layer.cellMargins && !cell.properties.margins) cell.properties.margins = layer.cellMargins;
48
+ if (layer.borders) {
49
+ const own = cell.properties.borders ?? {};
50
+ const merged = {
51
+ ...layer.borders,
52
+ ...definedOnly(own)
53
+ };
54
+ cell.properties.borders = merged;
55
+ }
56
+ if (layer.runProperties || layer.paragraphProperties) for (const el of cell.content) {
57
+ if (el.kind !== "paragraph") continue;
58
+ const p = el.paragraph;
59
+ if (layer.paragraphProperties) p.properties = {
60
+ ...layer.paragraphProperties,
61
+ ...definedOnly(p.properties)
62
+ };
63
+ if (layer.runProperties) for (const run of p.runs) run.properties = {
64
+ ...layer.runProperties,
65
+ ...definedOnly(run.properties)
66
+ };
67
+ }
68
+ }
69
+ function bandIndex(index, skipFirst, bandSize) {
70
+ const adjusted = index - (skipFirst ? 1 : 0);
71
+ if (adjusted < 0) return -1;
72
+ return Math.floor(adjusted / Math.max(1, bandSize));
73
+ }
74
+ function cellLayer(folded, look, pos) {
75
+ const layers = [folded.base];
76
+ const cond = (t) => folded.conditions.get(t);
77
+ if (!pos.noVBand && pos.colBand >= 0) layers.push(pos.colBand % 2 === 0 ? cond("band1Vert") : cond("band2Vert"));
78
+ if (!pos.noHBand && pos.rowBand >= 0) layers.push(pos.rowBand % 2 === 0 ? cond("band1Horz") : cond("band2Horz"));
79
+ if (look.firstColumn && pos.firstCol) layers.push(cond("firstCol"));
80
+ if (look.lastColumn && pos.lastCol) layers.push(cond("lastCol"));
81
+ if (look.firstRow && pos.firstRow) layers.push(cond("firstRow"));
82
+ if (look.lastRow && pos.lastRow) layers.push(cond("lastRow"));
83
+ if (look.firstRow && look.firstColumn && pos.firstRow && pos.firstCol) layers.push(cond("nwCell"));
84
+ if (look.firstRow && look.lastColumn && pos.firstRow && pos.lastCol) layers.push(cond("neCell"));
85
+ if (look.lastRow && look.firstColumn && pos.lastRow && pos.firstCol) layers.push(cond("swCell"));
86
+ if (look.lastRow && look.lastColumn && pos.lastRow && pos.lastCol) layers.push(cond("seCell"));
87
+ let out;
88
+ for (const layer of layers) {
89
+ if (!layer) continue;
90
+ out = out ? mergeLayer(out, layer) : layer;
91
+ }
92
+ return out;
93
+ }
94
+ function foldTableStyleChain(styleId, sheet) {
95
+ const chain = [];
96
+ const visited = /* @__PURE__ */ new Set();
97
+ let cursor = styleId;
98
+ while (cursor && !visited.has(cursor)) {
99
+ visited.add(cursor);
100
+ const style = sheet.styles.get(cursor);
101
+ if (!style || style.type !== "table") break;
102
+ chain.unshift(style);
103
+ cursor = style.basedOn;
104
+ }
105
+ if (chain.length === 0) return void 0;
106
+ let base = {};
107
+ const conditions = /* @__PURE__ */ new Map();
108
+ let rowBandSize = 1;
109
+ let colBandSize = 1;
110
+ for (const style of chain) {
111
+ const own = {
112
+ ...style.tableLayer ?? {},
113
+ ...Object.keys(style.runProperties).length > 0 && !style.tableLayer?.runProperties ? { runProperties: style.runProperties } : {},
114
+ ...Object.keys(style.paragraphProperties).length > 0 && !style.tableLayer?.paragraphProperties ? { paragraphProperties: style.paragraphProperties } : {}
115
+ };
116
+ base = mergeLayer(base, own);
117
+ for (const c of style.tableConditions ?? []) {
118
+ const prev = conditions.get(c.type);
119
+ conditions.set(c.type, prev ? mergeLayer(prev, c.layer) : c.layer);
120
+ }
121
+ if (style.rowBandSize !== void 0) rowBandSize = style.rowBandSize;
122
+ if (style.colBandSize !== void 0) colBandSize = style.colBandSize;
123
+ }
124
+ return {
125
+ base,
126
+ conditions,
127
+ rowBandSize,
128
+ colBandSize
129
+ };
130
+ }
131
+ function mergeLayer(base, override) {
132
+ return {
133
+ ...override.borders ?? base.borders ? { borders: {
134
+ ...base.borders,
135
+ ...definedOnly(override.borders ?? {})
136
+ } } : {},
137
+ ...override.cellMargins ?? base.cellMargins ? { cellMargins: {
138
+ ...base.cellMargins,
139
+ ...definedOnly(override.cellMargins ?? {})
140
+ } } : {},
141
+ ...override.shading ?? base.shading ? { shading: override.shading ?? base.shading } : {},
142
+ ...override.runProperties ?? base.runProperties ? { runProperties: {
143
+ ...base.runProperties,
144
+ ...definedOnly(override.runProperties ?? {})
145
+ } } : {},
146
+ ...override.paragraphProperties ?? base.paragraphProperties ? { paragraphProperties: {
147
+ ...base.paragraphProperties,
148
+ ...definedOnly(override.paragraphProperties ?? {})
149
+ } } : {}
150
+ };
151
+ }
152
+ function definedOnly(obj) {
153
+ const out = {};
154
+ for (const key of Object.keys(obj)) {
155
+ const v = obj[key];
156
+ if (v !== void 0) out[key] = v;
157
+ }
158
+ return out;
159
+ }
160
+ //#endregion
161
+ export { resolveTableStyles };
@@ -43,3 +43,5 @@ export declare class PathBuilder {
43
43
  close(): this;
44
44
  build(fillRule?: 'nonzero' | 'evenodd'): VectorPath;
45
45
  }
46
+ export declare function flipTransform(m: readonly [number, number, number, number, number, number], pageHeight: number): [number, number, number, number, number, number];
47
+ export declare function svgPathData(segments: ReadonlyArray<PathSegment>, fmt: (n: number) => string): string;
@@ -44,5 +44,23 @@ var PathBuilder = class {
44
44
  };
45
45
  }
46
46
  };
47
+ function flipTransform(m, pageHeight) {
48
+ return [
49
+ m[0],
50
+ -m[1],
51
+ m[2],
52
+ -m[3],
53
+ m[4],
54
+ pageHeight - m[5]
55
+ ];
56
+ }
57
+ function svgPathData(segments, fmt) {
58
+ const parts = [];
59
+ for (const s of segments) if (s.op === "move") parts.push(`M ${fmt(s.x)} ${fmt(s.y)}`);
60
+ else if (s.op === "line") parts.push(`L ${fmt(s.x)} ${fmt(s.y)}`);
61
+ else if (s.op === "cubic") parts.push(`C ${fmt(s.x1)} ${fmt(s.y1)} ${fmt(s.x2)} ${fmt(s.y2)} ${fmt(s.x)} ${fmt(s.y)}`);
62
+ else parts.push("Z");
63
+ return parts.join(" ");
64
+ }
47
65
  //#endregion
48
- export { PathBuilder };
66
+ export { PathBuilder, flipTransform, svgPathData };
@@ -1,5 +1,8 @@
1
1
  import { BodyElement, SectionProperties } from '../core/document-model/index.js';
2
2
  import { CellRange, DefinedName, ParsedWorksheet, XlsxStyles } from './index.js';
3
+ export declare const TWIPS_PER_EXCEL_CHAR = 105;
4
+ export declare const DEFAULT_COL_TWIPS = 960;
5
+ export declare const DEFAULT_ROW_TWIPS = 300;
3
6
  export declare function sectionFromWorksheet(worksheet: ParsedWorksheet): SectionProperties | undefined;
4
7
  export declare function resolvePrintArea(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): CellRange | undefined;
5
8
  export declare function resolvePrintTitleRows(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): {
@@ -2,10 +2,6 @@ import { eighthPtToPt, halfPtToPt, twipsToPt } from "../core/ir/units.js";
2
2
  import { parseAreaRef, parseTitleRowRange } from "./defined-name-ref.js";
3
3
  import { applyNumberFormat } from "./number-format.js";
4
4
  import "./index.js";
5
- //#region src/excel/print-model.ts
6
- var TWIPS_PER_EXCEL_CHAR = 105;
7
- var DEFAULT_COL_TWIPS = 960;
8
- var DEFAULT_ROW_TWIPS = 300;
9
5
  var TWIPS_PER_POINT = 20;
10
6
  var TWIPS_PER_INCH = 1440;
11
7
  var PAPER_SIZES_TWIPS = new Map([
@@ -154,9 +150,9 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
154
150
  const cEnd = Math.min(m.endColumn, colWindowEnd);
155
151
  for (let r = Math.max(m.startRow, rowStart); r <= rEnd; r++) for (let c = Math.max(m.startColumn, colStart); c <= cEnd; c++) if (!(r === m.startRow && c === m.startColumn)) insideMerge.add(key(r, c));
156
152
  }
157
- const columnWidths = new Array(colCount).fill(DEFAULT_COL_TWIPS);
153
+ const columnWidths = new Array(colCount).fill(960);
158
154
  for (const col of worksheet.columns) {
159
- const twips = Math.round(col.widthChars * TWIPS_PER_EXCEL_CHAR);
155
+ const twips = Math.round(col.widthChars * 105);
160
156
  for (let abs = col.min - 1; abs <= col.max - 1; abs++) {
161
157
  const i = abs - colStart;
162
158
  if (i >= 0 && i < colCount) columnWidths[i] = twips;
@@ -164,7 +160,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
164
160
  }
165
161
  const totalGridTwips = columnWidths.reduce((sum, w) => sum + w, 0);
166
162
  let totalGridHeightTwips = 0;
167
- for (let r = 0; r < rowCount; r++) totalGridHeightTwips += rowHeightMap.get(r)?.heightTwips ?? DEFAULT_ROW_TWIPS;
163
+ for (let r = 0; r < rowCount; r++) totalGridHeightTwips += rowHeightMap.get(r)?.heightTwips ?? 300;
168
164
  const printScale = computePrintScale(worksheet, totalGridTwips, sheetContentWidthTwips(worksheet), totalGridHeightTwips, sheetContentHeightTwips(worksheet));
169
165
  const scaled = printScale < .999;
170
166
  const breakRows = new Set(worksheet.rowBreaks ?? []);
@@ -217,7 +213,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
217
213
  cc++;
218
214
  }
219
215
  if (cc < colCount) {
220
- const charsFit = Math.max(1, Math.round(availTwips / TWIPS_PER_EXCEL_CHAR));
216
+ const charsFit = Math.max(1, Math.round(availTwips / 105));
221
217
  if (text.length > charsFit) text = text.slice(0, charsFit);
222
218
  }
223
219
  }
@@ -0,0 +1,9 @@
1
+ import { OpcPackage } from '../core/opc/index.js';
2
+ import { ParsedWorksheet } from './worksheet-parser.js';
3
+ export interface SheetChartRef {
4
+ readonly chartPartPath: string;
5
+ readonly widthPt: number;
6
+ readonly heightPt: number;
7
+ readonly anchorRow: number;
8
+ }
9
+ export declare function parseSheetDrawing(drawingXml: Uint8Array, drawingPartPath: string, pkg: OpcPackage, worksheet: ParsedWorksheet): Array<SheetChartRef>;
@@ -0,0 +1,113 @@
1
+ import { emuToPt } from "../core/ir/units.js";
2
+ import "./print-model.js";
3
+ import { XMLParser } from "fast-xml-parser";
4
+ //#region src/excel/sheet-drawing.ts
5
+ var CHART_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
6
+ var parser = new XMLParser({
7
+ ignoreAttributes: false,
8
+ attributeNamePrefix: "@_",
9
+ removeNSPrefix: true,
10
+ parseTagValue: false
11
+ });
12
+ var TWIPS_PER_PT = 20;
13
+ function parseSheetDrawing(drawingXml, drawingPartPath, pkg, worksheet) {
14
+ const root = parser.parse(new TextDecoder().decode(drawingXml))["wsDr"];
15
+ if (!root || typeof root !== "object") return [];
16
+ const rootObj = root;
17
+ const colWidthPt = makeColWidthPt(worksheet);
18
+ const rowHeightPt = makeRowHeightPt(worksheet);
19
+ const rels = pkg.getPartRelationships(drawingPartPath);
20
+ const out = [];
21
+ for (const kind of [
22
+ "twoCellAnchor",
23
+ "oneCellAnchor",
24
+ "absoluteAnchor"
25
+ ]) for (const anchor of asArray(rootObj[kind])) {
26
+ if (!anchor || typeof anchor !== "object") continue;
27
+ const a = anchor;
28
+ const chartRelId = chartRelIdOf(a);
29
+ if (!chartRelId) continue;
30
+ const rel = rels.find((r) => r.id === chartRelId);
31
+ const resolved = rel ? pkg.resolveRelatedPart(drawingPartPath, rel) : void 0;
32
+ if (!resolved) continue;
33
+ const from = cellMarker(a["from"]);
34
+ let widthPt = 0;
35
+ let heightPt = 0;
36
+ if (kind === "twoCellAnchor") {
37
+ const to = cellMarker(a["to"]);
38
+ if (!from || !to) continue;
39
+ widthPt = spanPt(from.col, from.colOffPt, to.col, to.colOffPt, colWidthPt);
40
+ heightPt = spanPt(from.row, from.rowOffPt, to.row, to.rowOffPt, rowHeightPt);
41
+ } else {
42
+ const ext = a["ext"];
43
+ if (!ext || typeof ext !== "object") continue;
44
+ const e = ext;
45
+ widthPt = emuToPt(num(e["@_cx"]) ?? 0);
46
+ heightPt = emuToPt(num(e["@_cy"]) ?? 0);
47
+ }
48
+ if (widthPt <= 0 || heightPt <= 0) continue;
49
+ out.push({
50
+ chartPartPath: resolved.path,
51
+ widthPt,
52
+ heightPt,
53
+ anchorRow: from?.row ?? 0
54
+ });
55
+ }
56
+ return out.sort((x, y) => x.anchorRow - y.anchorRow);
57
+ }
58
+ function chartRelIdOf(anchor) {
59
+ const frame = anchor["graphicFrame"];
60
+ if (!frame || typeof frame !== "object") return void 0;
61
+ const graphic = frame["graphic"];
62
+ if (!graphic || typeof graphic !== "object") return void 0;
63
+ const data = graphic["graphicData"];
64
+ if (!data || typeof data !== "object") return void 0;
65
+ const d = data;
66
+ if (d["@_uri"] !== CHART_URI) return void 0;
67
+ const chart = d["chart"];
68
+ if (!chart || typeof chart !== "object") return void 0;
69
+ const id = chart["@_id"];
70
+ return typeof id === "string" && id !== "" ? id : void 0;
71
+ }
72
+ function cellMarker(node) {
73
+ if (!node || typeof node !== "object") return void 0;
74
+ const m = node;
75
+ const col = num(m["col"]);
76
+ const row = num(m["row"]);
77
+ if (col === void 0 || row === void 0) return void 0;
78
+ return {
79
+ col,
80
+ row,
81
+ colOffPt: emuToPt(num(m["colOff"]) ?? 0),
82
+ rowOffPt: emuToPt(num(m["rowOff"]) ?? 0)
83
+ };
84
+ }
85
+ function spanPt(from, fromOffPt, to, toOffPt, trackPt) {
86
+ let span = 0;
87
+ for (let i = from; i < to; i++) span += trackPt(i);
88
+ return span - fromOffPt + toOffPt;
89
+ }
90
+ function makeColWidthPt(ws) {
91
+ return (col) => {
92
+ for (const c of ws.columns) if (col >= c.min - 1 && col <= c.max - 1) return c.widthChars * 105 / TWIPS_PER_PT;
93
+ return 960 / TWIPS_PER_PT;
94
+ };
95
+ }
96
+ function makeRowHeightPt(ws) {
97
+ return (row) => {
98
+ for (const r of ws.rowHeights) if (r.row === row) return r.heightPt;
99
+ return 300 / TWIPS_PER_PT;
100
+ };
101
+ }
102
+ function num(v) {
103
+ if (typeof v === "number" && Number.isFinite(v)) return v;
104
+ if (typeof v === "string" && v !== "") {
105
+ const n = Number(v);
106
+ if (Number.isFinite(n)) return n;
107
+ }
108
+ }
109
+ function asArray(v) {
110
+ return Array.isArray(v) ? v : v !== void 0 ? [v] : [];
111
+ }
112
+ //#endregion
113
+ export { parseSheetDrawing };
@@ -56,5 +56,6 @@ export interface ParsedWorksheet {
56
56
  readonly printOptions?: XlsxPrintOptions;
57
57
  readonly rowBreaks?: ReadonlyArray<number>;
58
58
  readonly colBreaks?: ReadonlyArray<number>;
59
+ readonly drawingRelId?: string;
59
60
  }
60
61
  export declare function parseWorksheet(data: Uint8Array): ParsedWorksheet;
@@ -32,13 +32,16 @@ function parseWorksheet(data) {
32
32
  const printOptions = parsePrintOptions(wsObj);
33
33
  const rowBreaks = parseBreaks(wsObj, "rowBreaks");
34
34
  const colBreaks = parseBreaks(wsObj, "colBreaks");
35
+ const drawingNode = wsObj["drawing"];
36
+ const drawingRelId = drawingNode && typeof drawingNode === "object" ? strAttr(drawingNode, "id") : void 0;
35
37
  const printModel = {
36
38
  ...pageMargins ? { pageMargins } : {},
37
39
  ...pageSetup ? { pageSetup } : {},
38
40
  ...fitToPage ? { fitToPage } : {},
39
41
  ...printOptions ? { printOptions } : {},
40
42
  ...rowBreaks.length > 0 ? { rowBreaks } : {},
41
- ...colBreaks.length > 0 ? { colBreaks } : {}
43
+ ...colBreaks.length > 0 ? { colBreaks } : {},
44
+ ...drawingRelId !== void 0 ? { drawingRelId } : {}
42
45
  };
43
46
  const sheetData = wsObj["sheetData"];
44
47
  if (!sheetData || typeof sheetData !== "object") return {
@@ -1,8 +1,13 @@
1
+ import { pt } from "../core/ir/units.js";
1
2
  import { ResourceStore } from "../core/ir/resources.js";
2
3
  import { FEATURES } from "../core/ir/features.js";
3
4
  import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
4
5
  import "../core/style-cascade/index.js";
5
6
  import { bytesInclude } from "../core/bytes.js";
7
+ import { DEFAULT_THEME_PALETTE, makeColorResolver } from "../core/drawingml/colors.js";
8
+ import { parseChart, withChartColorStyle } from "../core/drawingml/chart-parser.js";
9
+ import { parseTheme } from "../core/drawingml/theme-parser.js";
10
+ import { isOoxmlRel } from "../core/opc/relationship-types.js";
6
11
  import { OpcPackage } from "../core/opc/package.js";
7
12
  import { parseCoreProperties } from "../core/opc/core-properties.js";
8
13
  import "../core/opc/index.js";
@@ -12,6 +17,7 @@ import { parseWorksheet } from "./worksheet-parser.js";
12
17
  import { EMPTY_XLSX_STYLES, parseXlsxStyles } from "./styles-parser.js";
13
18
  import "./index.js";
14
19
  import { resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, worksheetToBody } from "./print-model.js";
20
+ import { parseSheetDrawing } from "./sheet-drawing.js";
15
21
  //#region src/excel/xlsx-reader.ts
16
22
  var WORKBOOK_PART = "xl/workbook.xml";
17
23
  var SHARED_STRINGS_PART = "xl/sharedStrings.xml";
@@ -29,6 +35,8 @@ function readXlsx(xlsx) {
29
35
  const styles = stylesData ? parseXlsxStyles(stylesData) : EMPTY_XLSX_STYLES;
30
36
  const workbookRels = pkg.getPartRelationships(WORKBOOK_PART);
31
37
  const body = [];
38
+ const charts = /* @__PURE__ */ new Map();
39
+ const resolveColor = buildXlsxColorResolver(pkg, workbookRels);
32
40
  let firstSheetSection;
33
41
  for (let sheetIdx = 0; sheetIdx < sheets.length; sheetIdx++) {
34
42
  const sheet = sheets[sheetIdx];
@@ -53,6 +61,27 @@ function readXlsx(xlsx) {
53
61
  ...titleRows ? { titleRows } : {},
54
62
  gridLines
55
63
  }));
64
+ if (worksheet.drawingRelId) {
65
+ const drawingRel = pkg.getPartRelationships(resolved.path).find((r) => r.id === worksheet.drawingRelId);
66
+ const drawing = drawingRel ? pkg.resolveRelatedPart(resolved.path, drawingRel) : void 0;
67
+ if (drawing) for (const ref of parseSheetDrawing(drawing.data, drawing.path, pkg, worksheet)) {
68
+ if (!charts.has(ref.chartPartPath)) {
69
+ const chartData = pkg.getPart(ref.chartPartPath);
70
+ const parsed = chartData ? parseChart(chartData, resolveColor) : null;
71
+ if (!parsed) continue;
72
+ charts.set(ref.chartPartPath, withChartColorStyle(parsed, pkg, ref.chartPartPath, resolveColor));
73
+ }
74
+ body.push({
75
+ kind: "chart",
76
+ chart: {
77
+ chartRelId: ref.chartPartPath,
78
+ width: pt(ref.widthPt),
79
+ height: pt(ref.heightPt),
80
+ paragraphProperties: {}
81
+ }
82
+ });
83
+ }
84
+ }
56
85
  }
57
86
  const coreData = pkg.getPart(CORE_PROPS_PART);
58
87
  const info = infoFromCore(coreData ? parseCoreProperties(coreData) : void 0);
@@ -64,11 +93,23 @@ function readXlsx(xlsx) {
64
93
  ...firstSheetSection ? { section: firstSheetSection } : {},
65
94
  styles: EMPTY_STYLE_SHEET,
66
95
  resources: new ResourceStore(),
96
+ ...charts.size > 0 ? { charts } : {},
67
97
  ...info ? { info } : {}
68
98
  },
69
99
  losses: []
70
100
  };
71
101
  }
102
+ function buildXlsxColorResolver(pkg, workbookRels) {
103
+ const palette = new Map(DEFAULT_THEME_PALETTE);
104
+ for (const rel of workbookRels) {
105
+ if (!isOoxmlRel(rel.type, "theme")) continue;
106
+ const resolved = pkg.resolveRelatedPart(WORKBOOK_PART, rel);
107
+ if (!resolved) continue;
108
+ for (const [slot, hex] of parseTheme(resolved.data)) palette.set(slot, hex);
109
+ break;
110
+ }
111
+ return makeColorResolver(palette);
112
+ }
72
113
  var xlsxReader = {
73
114
  id: "xlsx",
74
115
  produces: "flow",