reamkit 1.5.0 → 1.7.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.
- package/README.md +2 -2
- package/dist/esm/core/converter/facade.js +6 -1
- package/dist/esm/core/converter/ream.js +2 -1
- package/dist/esm/core/document-model/types.d.ts +9 -1
- package/dist/esm/core/drawingml/chart-serializer.d.ts +2 -0
- package/dist/esm/core/drawingml/chart-serializer.js +53 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +5 -0
- package/dist/esm/excel/column-bands.d.ts +7 -0
- package/dist/esm/excel/column-bands.js +87 -0
- package/dist/esm/excel/conditional-format.js +22 -0
- package/dist/esm/excel/print-model.js +33 -11
- package/dist/esm/excel/worksheet-parser.js +26 -0
- package/dist/esm/excel/xlsx-writer.js +88 -17
- package/dist/esm/html/html-writer.js +100 -5
- package/dist/esm/layout/styled-layout.js +88 -19
- package/dist/esm/pdf-reader/cmap.d.ts +5 -0
- package/dist/esm/pdf-reader/cmap.js +72 -0
- package/dist/esm/pdf-reader/content.d.ts +52 -0
- package/dist/esm/pdf-reader/content.js +420 -0
- package/dist/esm/pdf-reader/crypto.d.ts +7 -0
- package/dist/esm/pdf-reader/crypto.js +609 -0
- package/dist/esm/pdf-reader/decrypt.d.ts +5 -0
- package/dist/esm/pdf-reader/decrypt.js +199 -0
- package/dist/esm/pdf-reader/document.d.ts +30 -0
- package/dist/esm/pdf-reader/document.js +413 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +19 -0
- package/dist/esm/pdf-reader/flow-build.js +126 -0
- package/dist/esm/pdf-reader/font.d.ts +4 -0
- package/dist/esm/pdf-reader/font.js +69 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
- package/dist/esm/pdf-reader/image-decode.js +442 -0
- package/dist/esm/pdf-reader/images.d.ts +16 -0
- package/dist/esm/pdf-reader/images.js +90 -0
- package/dist/esm/pdf-reader/layout.d.ts +3 -0
- package/dist/esm/pdf-reader/layout.js +103 -0
- package/dist/esm/pdf-reader/lexer.d.ts +43 -0
- package/dist/esm/pdf-reader/lexer.js +250 -0
- package/dist/esm/pdf-reader/parser.d.ts +10 -0
- package/dist/esm/pdf-reader/parser.js +86 -0
- package/dist/esm/pdf-reader/png-encode.d.ts +2 -0
- package/dist/esm/pdf-reader/png-encode.js +98 -0
- package/dist/esm/pdf-reader/predictor.d.ts +7 -0
- package/dist/esm/pdf-reader/predictor.js +61 -0
- package/dist/esm/pdf-reader/reader.d.ts +4 -0
- package/dist/esm/pdf-reader/reader.js +50 -0
- package/dist/esm/pdf-reader/struct-tree.d.ts +14 -0
- package/dist/esm/pdf-reader/struct-tree.js +92 -0
- package/dist/esm/pdf-reader/tagged.d.ts +3 -0
- package/dist/esm/pdf-reader/tagged.js +141 -0
- package/dist/esm/pdf-reader/text.d.ts +3 -0
- package/dist/esm/pdf-reader/text.js +65 -0
- package/dist/esm/pdf-reader/vector.d.ts +12 -0
- package/dist/esm/pdf-reader/vector.js +54 -0
- package/dist/esm/word/docx-writer.js +96 -7
- package/dist/esm/word/omml-serializer.d.ts +2 -0
- package/dist/esm/word/omml-serializer.js +48 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ import { Ream } from 'reamkit';
|
|
|
30
30
|
// e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
|
|
31
31
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
32
32
|
|
|
33
|
-
const doc = Ream.parse(bytes); // docx or
|
|
33
|
+
const doc = Ream.parse(bytes); // docx, xlsx or pdf — sniffed
|
|
34
34
|
const pdf = await doc.convert('pdf'); // async — fetches a font if needed
|
|
35
35
|
const svg = await doc.convert('svg'); // same parse, different target
|
|
36
36
|
const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
|
|
@@ -130,7 +130,7 @@ rendering — inspect or analyze it without converting:
|
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
132
|
const doc = Ream.parse(bytes);
|
|
133
|
-
doc.format; // 'docx' | 'xlsx'
|
|
133
|
+
doc.format; // 'docx' | 'xlsx' | 'pdf'
|
|
134
134
|
doc.flow.body; // paragraphs / tables / images / charts …
|
|
135
135
|
doc.losses; // read-time losses
|
|
136
136
|
```
|
|
@@ -13,6 +13,7 @@ import { writeHtml } from "../../html/html-writer.js";
|
|
|
13
13
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
14
14
|
import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
|
|
15
15
|
import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
|
|
16
|
+
import { pdfReader } from "../../pdf-reader/reader.js";
|
|
16
17
|
//#region src/core/converter/facade.ts
|
|
17
18
|
function toFlowDoc(doc) {
|
|
18
19
|
return doc.kind === "sheet" ? projectSheetDoc(doc) : doc;
|
|
@@ -24,7 +25,11 @@ function readToFlow(reader, bytes) {
|
|
|
24
25
|
losses
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
|
-
var DEFAULT_READERS = [
|
|
28
|
+
var DEFAULT_READERS = [
|
|
29
|
+
docxReader,
|
|
30
|
+
xlsxReader,
|
|
31
|
+
pdfReader
|
|
32
|
+
];
|
|
28
33
|
function createConverter(opts = {}) {
|
|
29
34
|
const readers = opts.readers ?? DEFAULT_READERS;
|
|
30
35
|
const detect = (bytes) => readers.find((r) => r.sniff(bytes));
|
|
@@ -15,7 +15,8 @@ import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
|
|
|
15
15
|
//#region src/core/converter/ream.ts
|
|
16
16
|
var SOURCE_MIME = {
|
|
17
17
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
18
|
-
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
18
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
19
|
+
pdf: "application/pdf"
|
|
19
20
|
};
|
|
20
21
|
var Ream = class Ream {
|
|
21
22
|
constructor(flow, sheet, losses, source, readerId) {
|
|
@@ -226,10 +226,14 @@ export interface CellDataBar {
|
|
|
226
226
|
readonly colorHex: string;
|
|
227
227
|
readonly startFraction?: number;
|
|
228
228
|
}
|
|
229
|
-
export type CellIconShape = 'circle' | 'square' | 'diamond' | 'triangleUp' | 'triangleDown' | 'triangleRight';
|
|
229
|
+
export type CellIconShape = 'circle' | 'square' | 'diamond' | 'triangleUp' | 'triangleDown' | 'triangleRight' | 'check' | 'cross' | 'exclamation' | 'bars' | 'pie';
|
|
230
230
|
export interface CellIcon {
|
|
231
231
|
readonly shape: CellIconShape;
|
|
232
232
|
readonly colorHex: string;
|
|
233
|
+
readonly fill?: {
|
|
234
|
+
readonly filled: number;
|
|
235
|
+
readonly levels: number;
|
|
236
|
+
};
|
|
233
237
|
}
|
|
234
238
|
export interface CellSparkline {
|
|
235
239
|
readonly kind: 'line' | 'column' | 'winLoss';
|
|
@@ -273,6 +277,10 @@ export interface TableProperties {
|
|
|
273
277
|
readonly defaultCellMargins?: CellMargins;
|
|
274
278
|
readonly borders?: CellBorders;
|
|
275
279
|
readonly alignment?: 'left' | 'center' | 'right';
|
|
280
|
+
readonly frozen?: {
|
|
281
|
+
readonly rows: number;
|
|
282
|
+
readonly cols: number;
|
|
283
|
+
};
|
|
276
284
|
}
|
|
277
285
|
export interface TableCell {
|
|
278
286
|
readonly properties: CellProperties;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/core/drawingml/chart-serializer.ts
|
|
2
|
+
var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
3
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4
|
+
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
5
|
+
var GROUP_TAG = {
|
|
6
|
+
bar: "c:barChart",
|
|
7
|
+
line: "c:lineChart",
|
|
8
|
+
pie: "c:pieChart",
|
|
9
|
+
area: "c:areaChart",
|
|
10
|
+
scatter: "c:scatterChart",
|
|
11
|
+
unknown: "c:barChart"
|
|
12
|
+
};
|
|
13
|
+
var CAT_AX_ID = 111111111;
|
|
14
|
+
var VAL_AX_ID = 222222222;
|
|
15
|
+
function chartSpaceXml(chart) {
|
|
16
|
+
const isScatter = chart.type === "scatter";
|
|
17
|
+
const isPie = chart.type === "pie" || chart.doughnut === true;
|
|
18
|
+
const groupTag = chart.doughnut ? "c:doughnutChart" : GROUP_TAG[chart.type] ?? "c:barChart";
|
|
19
|
+
const inner = (chart.type === "bar" ? `<c:barDir val="${chart.barDir ?? "col"}"/>` : "") + (chart.grouping ? `<c:grouping val="${chart.grouping}"/>` : "") + "<c:varyColors val=\"0\"/>" + chart.series.map((s, i) => seriesXml(s, i, isScatter, chart.categories)).join("") + (chart.showValues ? "<c:dLbls><c:showVal val=\"1\"/></c:dLbls>" : "") + (chart.doughnut ? "<c:holeSize val=\"50\"/>" : "") + (isPie ? "" : `<c:axId val="${CAT_AX_ID}"/><c:axId val="${VAL_AX_ID}"/>`);
|
|
20
|
+
const axes = isPie ? "" : isScatter ? valAx(CAT_AX_ID, "b", VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle) : catAx(CAT_AX_ID, VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle);
|
|
21
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><c:chartSpace xmlns:c="${C_NS}" xmlns:a="${A_NS}" xmlns:r="${R_NS}"><c:chart>` + titleXml(chart.title) + `<c:autoTitleDeleted val="${chart.title ? 0 : 1}"/><c:plotArea><c:layout/><${groupTag}>${inner}</${groupTag}>${axes}</c:plotArea>` + (chart.hasLegend ? `<c:legend><c:legendPos val="${chart.legendPos ?? "r"}"/><c:overlay val="0"/></c:legend>` : "") + "<c:plotVisOnly val=\"1\"/></c:chart></c:chartSpace>";
|
|
22
|
+
}
|
|
23
|
+
function seriesXml(s, idx, isScatter, categories) {
|
|
24
|
+
const tx = s.name ? `<c:tx><c:strRef><c:f>Sheet1!$A$${idx + 1}</c:f><c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${esc(s.name)}</c:v></c:pt></c:strCache></c:strRef></c:tx>` : "";
|
|
25
|
+
const spPr = s.colorHex ? `<c:spPr><a:solidFill><a:srgbClr val="${s.colorHex}"/></a:solidFill></c:spPr>` : "";
|
|
26
|
+
const dPts = (s.pointColors ?? []).map((p) => `<c:dPt><c:idx val="${p.idx}"/><c:bubble3D val="0"/><c:spPr><a:solidFill><a:srgbClr val="${p.colorHex}"/></a:solidFill></c:spPr></c:dPt>`).join("");
|
|
27
|
+
const cat = isScatter ? `<c:xVal>${numRef("A", s.xValues ?? [])}</c:xVal>` : categories.length > 0 ? `<c:cat>${strRef(categories)}</c:cat>` : "";
|
|
28
|
+
const val = isScatter ? `<c:yVal>${numRef("B", s.values)}</c:yVal>` : `<c:val>${numRef("B", s.values)}</c:val>`;
|
|
29
|
+
return `<c:ser><c:idx val="${idx}"/><c:order val="${idx}"/>` + tx + spPr + dPts + cat + val + "</c:ser>";
|
|
30
|
+
}
|
|
31
|
+
function strRef(values) {
|
|
32
|
+
const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${esc(v)}</c:v></c:pt>`).join("");
|
|
33
|
+
return `<c:strRef><c:f>Sheet1!\$A\$1</c:f><c:strCache><c:ptCount val="${values.length}"/>${pts}</c:strCache></c:strRef>`;
|
|
34
|
+
}
|
|
35
|
+
function numRef(col, values) {
|
|
36
|
+
const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${v}</c:v></c:pt>`).join("");
|
|
37
|
+
return `<c:numRef><c:f>Sheet1!$${col}$1</c:f><c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${values.length}"/>${pts}</c:numCache></c:numRef>`;
|
|
38
|
+
}
|
|
39
|
+
function titleXml(title) {
|
|
40
|
+
if (!title) return "";
|
|
41
|
+
return `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${esc(title)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title>`;
|
|
42
|
+
}
|
|
43
|
+
function catAx(id, crossId, title) {
|
|
44
|
+
return `<c:catAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="b"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:catAx>`;
|
|
45
|
+
}
|
|
46
|
+
function valAx(id, pos, crossId, title) {
|
|
47
|
+
return `<c:valAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="${pos}"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:valAx>`;
|
|
48
|
+
}
|
|
49
|
+
function esc(s) {
|
|
50
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { chartSpaceXml };
|
|
@@ -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, } 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, SheetPane, } from './types.js';
|
|
@@ -61,6 +61,11 @@ export interface ParsedWorksheet {
|
|
|
61
61
|
readonly sparklines?: ReadonlyArray<ParsedSparkline>;
|
|
62
62
|
readonly tablePartRelIds?: ReadonlyArray<string>;
|
|
63
63
|
readonly tables?: ReadonlyArray<ExcelTable>;
|
|
64
|
+
readonly pane?: SheetPane;
|
|
65
|
+
}
|
|
66
|
+
export interface SheetPane {
|
|
67
|
+
readonly frozenRows: number;
|
|
68
|
+
readonly frozenCols: number;
|
|
64
69
|
}
|
|
65
70
|
export interface ExcelTable {
|
|
66
71
|
readonly ref: MergedRange;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BodyElement, TableProperties, TableRow } from '../core/document-model/index.js';
|
|
2
|
+
export interface ColumnBand {
|
|
3
|
+
readonly start: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function computeColumnBands(columnWidths: ReadonlyArray<number>, contentWidthTwips: number, colBreaks: ReadonlySet<number>): Array<ColumnBand>;
|
|
7
|
+
export declare function bandedTables(rows: ReadonlyArray<TableRow>, columnWidths: ReadonlyArray<number>, bands: ReadonlyArray<ColumnBand>, properties: TableProperties): Array<BodyElement>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { twipsToPt } from "../core/ir/units.js";
|
|
2
|
+
//#region src/excel/column-bands.ts
|
|
3
|
+
function computeColumnBands(columnWidths, contentWidthTwips, colBreaks) {
|
|
4
|
+
const n = columnWidths.length;
|
|
5
|
+
if (n === 0) return [];
|
|
6
|
+
const bands = [];
|
|
7
|
+
let start = 0;
|
|
8
|
+
let acc = 0;
|
|
9
|
+
for (let c = 0; c < n; c++) {
|
|
10
|
+
const w = columnWidths[c] ?? 0;
|
|
11
|
+
const atBreak = c > start && colBreaks.has(c);
|
|
12
|
+
const overflows = c > start && acc + w > contentWidthTwips;
|
|
13
|
+
if (atBreak || overflows) {
|
|
14
|
+
bands.push({
|
|
15
|
+
start,
|
|
16
|
+
end: c - 1
|
|
17
|
+
});
|
|
18
|
+
start = c;
|
|
19
|
+
acc = 0;
|
|
20
|
+
}
|
|
21
|
+
acc += w;
|
|
22
|
+
}
|
|
23
|
+
bands.push({
|
|
24
|
+
start,
|
|
25
|
+
end: n - 1
|
|
26
|
+
});
|
|
27
|
+
return bands;
|
|
28
|
+
}
|
|
29
|
+
function bandedTables(rows, columnWidths, bands, properties) {
|
|
30
|
+
return bands.map((band, bandIndex) => {
|
|
31
|
+
return {
|
|
32
|
+
kind: "table",
|
|
33
|
+
table: {
|
|
34
|
+
properties,
|
|
35
|
+
grid: columnWidths.slice(band.start, band.end + 1).map((w) => twipsToPt(w)),
|
|
36
|
+
rows: rows.map((row, rowIndex) => {
|
|
37
|
+
const cells = sliceRowCells(row.cells, band);
|
|
38
|
+
return {
|
|
39
|
+
properties: bandIndex > 0 && rowIndex === 0 ? {
|
|
40
|
+
...row.properties,
|
|
41
|
+
pageBreakBefore: true
|
|
42
|
+
} : row.properties,
|
|
43
|
+
cells
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function sliceRowCells(cells, band) {
|
|
51
|
+
const out = [];
|
|
52
|
+
let col = 0;
|
|
53
|
+
for (const cell of cells) {
|
|
54
|
+
const span = cell.properties.colSpan ?? 1;
|
|
55
|
+
const cellStart = col;
|
|
56
|
+
const cellEnd = col + span - 1;
|
|
57
|
+
col += span;
|
|
58
|
+
if (cellEnd < band.start || cellStart > band.end) continue;
|
|
59
|
+
const clampedSpan = Math.min(cellEnd, band.end) - Math.max(cellStart, band.start) + 1;
|
|
60
|
+
if (cellStart >= band.start) out.push(clampedSpan === span ? cell : withColSpan(cell, clampedSpan));
|
|
61
|
+
else out.push(blankCell(clampedSpan));
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function withColSpan(cell, span) {
|
|
66
|
+
const props = { ...cell.properties };
|
|
67
|
+
if (span > 1) props.colSpan = span;
|
|
68
|
+
else delete props.colSpan;
|
|
69
|
+
return {
|
|
70
|
+
...cell,
|
|
71
|
+
properties: props
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function blankCell(span) {
|
|
75
|
+
return {
|
|
76
|
+
properties: span > 1 ? { colSpan: span } : {},
|
|
77
|
+
content: [{
|
|
78
|
+
kind: "paragraph",
|
|
79
|
+
paragraph: {
|
|
80
|
+
properties: {},
|
|
81
|
+
runs: []
|
|
82
|
+
}
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { bandedTables, computeColumnBands };
|
|
@@ -256,6 +256,22 @@ function iconBucket(thresholds, value) {
|
|
|
256
256
|
}
|
|
257
257
|
function iconToCell(setName, count, index, reverse) {
|
|
258
258
|
const e = reverse ? count - 1 - index : index;
|
|
259
|
+
if (/Rating/i.test(setName)) return {
|
|
260
|
+
shape: "bars",
|
|
261
|
+
colorHex: METER_HEX,
|
|
262
|
+
fill: {
|
|
263
|
+
filled: e + 1,
|
|
264
|
+
levels: count
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
if (/Quarters/i.test(setName)) return {
|
|
268
|
+
shape: "pie",
|
|
269
|
+
colorHex: METER_HEX,
|
|
270
|
+
fill: {
|
|
271
|
+
filled: e,
|
|
272
|
+
levels: count - 1
|
|
273
|
+
}
|
|
274
|
+
};
|
|
259
275
|
const colorHex = /Gray/i.test(setName) ? GRAY_HEX : iconColor(count, e);
|
|
260
276
|
return {
|
|
261
277
|
shape: iconShape(setName, e, count),
|
|
@@ -263,12 +279,18 @@ function iconToCell(setName, count, index, reverse) {
|
|
|
263
279
|
};
|
|
264
280
|
}
|
|
265
281
|
var GRAY_HEX = "808080";
|
|
282
|
+
var METER_HEX = "595959";
|
|
266
283
|
function iconShape(setName, e, count) {
|
|
267
284
|
if (setName.includes("Arrows")) {
|
|
268
285
|
if (e <= 0) return "triangleDown";
|
|
269
286
|
if (e >= count - 1) return "triangleUp";
|
|
270
287
|
return "triangleRight";
|
|
271
288
|
}
|
|
289
|
+
if (setName.includes("Symbols")) {
|
|
290
|
+
if (e <= 0) return "cross";
|
|
291
|
+
if (e >= count - 1) return "check";
|
|
292
|
+
return "exclamation";
|
|
293
|
+
}
|
|
272
294
|
if (setName.includes("Signs")) {
|
|
273
295
|
if (e <= 0) return "diamond";
|
|
274
296
|
if (e >= count - 1) return "circle";
|
|
@@ -2,6 +2,7 @@ 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
|
+
import { bandedTables, computeColumnBands } from "./column-bands.js";
|
|
5
6
|
import { buildConditionalFormatter } from "./conditional-format.js";
|
|
6
7
|
var TWIPS_PER_POINT = 20;
|
|
7
8
|
var TWIPS_PER_INCH = 1440;
|
|
@@ -291,20 +292,41 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
291
292
|
width: eighthPtToPt(4)
|
|
292
293
|
};
|
|
293
294
|
const centered = worksheet.printOptions?.horizontalCentered === true;
|
|
295
|
+
const tableProperties = {
|
|
296
|
+
...print.gridLines ? { borders: {
|
|
297
|
+
top: thin,
|
|
298
|
+
bottom: thin,
|
|
299
|
+
left: thin,
|
|
300
|
+
right: thin,
|
|
301
|
+
insideH: thin,
|
|
302
|
+
insideV: thin
|
|
303
|
+
} } : {},
|
|
304
|
+
...centered ? { alignment: "center" } : {}
|
|
305
|
+
};
|
|
306
|
+
const contentWidthTwips = sheetContentWidthTwips(worksheet);
|
|
307
|
+
const colBreaksLocal = /* @__PURE__ */ new Set();
|
|
308
|
+
for (const brk of worksheet.colBreaks ?? []) {
|
|
309
|
+
const local = brk - colStart;
|
|
310
|
+
if (local > 0 && local < colCount) colBreaksLocal.add(local);
|
|
311
|
+
}
|
|
312
|
+
const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
|
|
313
|
+
const bandWidths = scaled ? columnWidths.map((w) => Math.max(1, Math.floor(w * printScale))) : columnWidths;
|
|
314
|
+
const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
|
|
315
|
+
if (colCount > 1 && (!scaled || fitWide > 1) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
|
|
316
|
+
const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
|
|
317
|
+
if (bands.length > 1) return bandedTables(rows, bandWidths, bands, tableProperties);
|
|
318
|
+
}
|
|
319
|
+
const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
|
|
320
|
+
rows: worksheet.pane.frozenRows,
|
|
321
|
+
cols: worksheet.pane.frozenCols
|
|
322
|
+
} : void 0;
|
|
294
323
|
return [{
|
|
295
324
|
kind: "table",
|
|
296
325
|
table: {
|
|
297
|
-
properties: {
|
|
298
|
-
...
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
left: thin,
|
|
302
|
-
right: thin,
|
|
303
|
-
insideH: thin,
|
|
304
|
-
insideV: thin
|
|
305
|
-
} } : {},
|
|
306
|
-
...centered ? { alignment: "center" } : {}
|
|
307
|
-
},
|
|
326
|
+
properties: frozen ? {
|
|
327
|
+
...tableProperties,
|
|
328
|
+
frozen
|
|
329
|
+
} : tableProperties,
|
|
308
330
|
grid: columnWidths.map((w) => twipsToPt(w)),
|
|
309
331
|
rows
|
|
310
332
|
}
|
|
@@ -33,6 +33,7 @@ function parseWorksheet(data) {
|
|
|
33
33
|
const printOptions = parsePrintOptions(wsObj);
|
|
34
34
|
const rowBreaks = parseBreaks(wsObj, "rowBreaks");
|
|
35
35
|
const colBreaks = parseBreaks(wsObj, "colBreaks");
|
|
36
|
+
const pane = parsePane(wsObj);
|
|
36
37
|
const drawingNode = wsObj["drawing"];
|
|
37
38
|
const drawingRelId = drawingNode && typeof drawingNode === "object" ? strAttr(drawingNode, "id") : void 0;
|
|
38
39
|
const conditionalFormats = parseConditionalFormatting(wsObj);
|
|
@@ -45,6 +46,7 @@ function parseWorksheet(data) {
|
|
|
45
46
|
...printOptions ? { printOptions } : {},
|
|
46
47
|
...rowBreaks.length > 0 ? { rowBreaks } : {},
|
|
47
48
|
...colBreaks.length > 0 ? { colBreaks } : {},
|
|
49
|
+
...pane ? { pane } : {},
|
|
48
50
|
...drawingRelId !== void 0 ? { drawingRelId } : {},
|
|
49
51
|
...conditionalFormats.length > 0 ? { conditionalFormats } : {},
|
|
50
52
|
...sparklines.length > 0 ? { sparklines } : {},
|
|
@@ -181,6 +183,30 @@ function parsePrintOptions(ws) {
|
|
|
181
183
|
...verticalCentered !== void 0 ? { verticalCentered } : {}
|
|
182
184
|
};
|
|
183
185
|
}
|
|
186
|
+
function parsePane(ws) {
|
|
187
|
+
const views = ws["sheetViews"];
|
|
188
|
+
if (!views || typeof views !== "object") return void 0;
|
|
189
|
+
const viewRaw = views["sheetView"];
|
|
190
|
+
const view = Array.isArray(viewRaw) ? viewRaw[0] : viewRaw;
|
|
191
|
+
if (!view || typeof view !== "object") return void 0;
|
|
192
|
+
const paneNode = view["pane"];
|
|
193
|
+
if (!paneNode || typeof paneNode !== "object") return void 0;
|
|
194
|
+
const pane = paneNode;
|
|
195
|
+
const state = strAttr(pane, "state");
|
|
196
|
+
if (state !== "frozen" && state !== "frozenSplit") return void 0;
|
|
197
|
+
const count = (key) => {
|
|
198
|
+
const raw = strAttr(pane, key);
|
|
199
|
+
const n = raw !== void 0 ? Number(raw) : NaN;
|
|
200
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
201
|
+
};
|
|
202
|
+
const frozenCols = count("xSplit");
|
|
203
|
+
const frozenRows = count("ySplit");
|
|
204
|
+
if (frozenCols === 0 && frozenRows === 0) return void 0;
|
|
205
|
+
return {
|
|
206
|
+
frozenRows,
|
|
207
|
+
frozenCols
|
|
208
|
+
};
|
|
209
|
+
}
|
|
184
210
|
function parseBreaks(ws, tag) {
|
|
185
211
|
const node = ws[tag];
|
|
186
212
|
if (!node || typeof node !== "object") return [];
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { FEATURES } from "../core/ir/features.js";
|
|
2
2
|
import { buildOpcPackage } from "../core/opc/opc-writer.js";
|
|
3
3
|
import "../core/opc/index.js";
|
|
4
|
+
import { chartSpaceXml } from "../core/drawingml/chart-serializer.js";
|
|
4
5
|
//#region src/excel/xlsx-writer.ts
|
|
5
6
|
var encoder = new TextEncoder();
|
|
7
|
+
var EMU_PER_PT = 12700;
|
|
6
8
|
var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
7
9
|
var REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
|
8
10
|
var REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
|
9
11
|
var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
10
12
|
var REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
|
|
13
|
+
var REL_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
|
|
14
|
+
var REL_CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
|
|
15
|
+
var CT_DRAWING = "application/vnd.openxmlformats-officedocument.drawing+xml";
|
|
16
|
+
var CT_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
|
|
17
|
+
var XDR_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
|
18
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
19
|
+
var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
11
20
|
var CT_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
12
21
|
var CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
13
22
|
var CT_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
@@ -19,16 +28,20 @@ function writeXlsx(sheet) {
|
|
|
19
28
|
const losses = [];
|
|
20
29
|
const worksheetParts = [];
|
|
21
30
|
const tableParts = [];
|
|
31
|
+
const drawingParts = [];
|
|
22
32
|
const worksheetRels = [];
|
|
33
|
+
const drawingRels = [];
|
|
23
34
|
const sheetEntries = [];
|
|
24
35
|
const workbookRels = [];
|
|
25
36
|
let tableCounter = 0;
|
|
37
|
+
let drawingCounter = 0;
|
|
38
|
+
let chartCounter = 0;
|
|
26
39
|
sheet.sheets.forEach((s, i) => {
|
|
27
40
|
const idx = i + 1;
|
|
28
41
|
const rid = `rId${idx}`;
|
|
29
42
|
const wsPath = `xl/worksheets/sheet${idx}.xml`;
|
|
43
|
+
const wsRels = [];
|
|
30
44
|
const tableRelIds = [];
|
|
31
|
-
const wsTableRels = [];
|
|
32
45
|
for (const t of s.grid.tables ?? []) {
|
|
33
46
|
const tid = ++tableCounter;
|
|
34
47
|
const tRid = `rIdTbl${tid}`;
|
|
@@ -38,15 +51,23 @@ function writeXlsx(sheet) {
|
|
|
38
51
|
data: encoder.encode(tableXml(t, tid)),
|
|
39
52
|
contentType: CT_TABLE
|
|
40
53
|
});
|
|
41
|
-
|
|
54
|
+
wsRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
|
|
42
55
|
}
|
|
43
|
-
|
|
56
|
+
const drawingRelId = emitSheetCharts(s, sheet, idx, {
|
|
57
|
+
drawingParts,
|
|
58
|
+
drawingRels,
|
|
59
|
+
wsRels,
|
|
60
|
+
nextDrawing: () => ++drawingCounter,
|
|
61
|
+
nextChart: () => ++chartCounter,
|
|
62
|
+
losses
|
|
63
|
+
});
|
|
64
|
+
if (wsRels.length > 0) worksheetRels.push({
|
|
44
65
|
sourcePart: wsPath,
|
|
45
|
-
relationships:
|
|
66
|
+
relationships: wsRels
|
|
46
67
|
});
|
|
47
68
|
worksheetParts.push({
|
|
48
69
|
path: wsPath,
|
|
49
|
-
data: encoder.encode(worksheetXml(s.grid, tableRelIds)),
|
|
70
|
+
data: encoder.encode(worksheetXml(s.grid, tableRelIds, drawingRelId)),
|
|
50
71
|
contentType: CT_WORKSHEET
|
|
51
72
|
});
|
|
52
73
|
workbookRels.push(rel(rid, REL_WORKSHEET, `worksheets/sheet${idx}.xml`));
|
|
@@ -55,7 +76,6 @@ function writeXlsx(sheet) {
|
|
|
55
76
|
sheetId: idx,
|
|
56
77
|
rid
|
|
57
78
|
});
|
|
58
|
-
recordSheetLosses(s, losses);
|
|
59
79
|
});
|
|
60
80
|
const sstRid = `rId${sheet.sheets.length + 1}`;
|
|
61
81
|
const stylesRid = `rId${sheet.sheets.length + 2}`;
|
|
@@ -71,6 +91,7 @@ function writeXlsx(sheet) {
|
|
|
71
91
|
},
|
|
72
92
|
...worksheetParts,
|
|
73
93
|
...tableParts,
|
|
94
|
+
...drawingParts,
|
|
74
95
|
{
|
|
75
96
|
path: "xl/sharedStrings.xml",
|
|
76
97
|
data: encoder.encode(sharedStringsXml(sheet.sharedStrings)),
|
|
@@ -83,10 +104,14 @@ function writeXlsx(sheet) {
|
|
|
83
104
|
}
|
|
84
105
|
],
|
|
85
106
|
rootRelationships: [rel("rId1", REL_OFFICE_DOCUMENT, "xl/workbook.xml")],
|
|
86
|
-
partRelationships: [
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
partRelationships: [
|
|
108
|
+
{
|
|
109
|
+
sourcePart: "xl/workbook.xml",
|
|
110
|
+
relationships: workbookRels
|
|
111
|
+
},
|
|
112
|
+
...worksheetRels,
|
|
113
|
+
...drawingRels
|
|
114
|
+
]
|
|
90
115
|
}),
|
|
91
116
|
losses
|
|
92
117
|
};
|
|
@@ -100,19 +125,58 @@ function rel(id, type, target) {
|
|
|
100
125
|
targetMode: "Internal"
|
|
101
126
|
};
|
|
102
127
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
function emitSheetCharts(s, doc, idx, ctx) {
|
|
129
|
+
const refs = s.charts ?? [];
|
|
130
|
+
if (refs.length === 0 || !doc.chartData) return void 0;
|
|
131
|
+
const did = ctx.nextDrawing();
|
|
132
|
+
const drawingPath = `xl/drawings/drawing${did}.xml`;
|
|
133
|
+
const anchors = [];
|
|
134
|
+
const dRels = [];
|
|
135
|
+
refs.forEach((ref, ci) => {
|
|
136
|
+
const chart = doc.chartData?.get(ref.chartPartPath);
|
|
137
|
+
if (!chart) {
|
|
138
|
+
ctx.losses.push({
|
|
139
|
+
severity: "dropped",
|
|
140
|
+
feature: FEATURES.charts,
|
|
141
|
+
detail: `sheet "${s.name}": a chart frame had no resolved data and was dropped`
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const cid = ctx.nextChart();
|
|
146
|
+
ctx.drawingParts.push({
|
|
147
|
+
path: `xl/charts/chart${cid}.xml`,
|
|
148
|
+
data: encoder.encode(chartSpaceXml(chart)),
|
|
149
|
+
contentType: CT_CHART
|
|
150
|
+
});
|
|
151
|
+
const cRel = `rIdChart${ci + 1}`;
|
|
152
|
+
dRels.push(rel(cRel, REL_CHART, `../charts/chart${cid}.xml`));
|
|
153
|
+
anchors.push(chartAnchorXml(ref, cRel, ci));
|
|
154
|
+
});
|
|
155
|
+
if (anchors.length === 0) return void 0;
|
|
156
|
+
ctx.drawingParts.push({
|
|
157
|
+
path: drawingPath,
|
|
158
|
+
data: encoder.encode(`${XML_DECL}<xdr:wsDr xmlns:xdr="${XDR_NS}" xmlns:a="${A_NS}">${anchors.join("")}</xdr:wsDr>`),
|
|
159
|
+
contentType: CT_DRAWING
|
|
160
|
+
});
|
|
161
|
+
ctx.drawingRels.push({
|
|
162
|
+
sourcePart: drawingPath,
|
|
163
|
+
relationships: dRels
|
|
108
164
|
});
|
|
165
|
+
const dRid = `rIdDraw${idx}`;
|
|
166
|
+
ctx.wsRels.push(rel(dRid, REL_DRAWING, `../drawings/drawing${did}.xml`));
|
|
167
|
+
return dRid;
|
|
168
|
+
}
|
|
169
|
+
function chartAnchorXml(ref, chartRelId, index) {
|
|
170
|
+
const cx = Math.round(ref.widthPt * EMU_PER_PT);
|
|
171
|
+
const cy = Math.round(ref.heightPt * EMU_PER_PT);
|
|
172
|
+
return `<xdr:oneCellAnchor><xdr:from><xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>${index * 16}</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:from><xdr:ext cx="${cx}" cy="${cy}"/><xdr:graphicFrame macro=""><xdr:nvGraphicFramePr><xdr:cNvPr id="${index + 2}" name="Chart ${index + 1}"/><xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr><xdr:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></xdr:xfrm><a:graphic><a:graphicData uri="${C_NS}"><c:chart xmlns:c="${C_NS}" xmlns:r="${R_NS}" r:id="${chartRelId}"/></a:graphicData></a:graphic></xdr:graphicFrame><xdr:clientData/></xdr:oneCellAnchor>`;
|
|
109
173
|
}
|
|
110
174
|
function workbookXml(sheets, definedNames, date1904) {
|
|
111
175
|
const sheetTags = sheets.map((s) => `<sheet name="${escapeAttr(s.name)}" sheetId="${s.sheetId}" r:id="${s.rid}"/>`).join("");
|
|
112
176
|
const definedNamesXml = definedNames.length > 0 ? `<definedNames>${definedNames.map((d) => `<definedName name="${escapeAttr(d.name)}"${d.localSheetId !== void 0 ? ` localSheetId="${d.localSheetId}"` : ""}>${escapeText(d.value)}</definedName>`).join("")}</definedNames>` : "";
|
|
113
177
|
return XML_DECL + `<workbook xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (date1904 ? "<workbookPr date1904=\"1\"/>" : "") + `<sheets>${sheetTags}</sheets>` + definedNamesXml + "</workbook>";
|
|
114
178
|
}
|
|
115
|
-
function worksheetXml(grid, tableRelIds = []) {
|
|
179
|
+
function worksheetXml(grid, tableRelIds = [], drawingRelId) {
|
|
116
180
|
const dimension = grid.maxRow >= 0 && grid.maxColumn >= 0 ? `<dimension ref="A1:${cellRef(grid.maxRow, grid.maxColumn)}"/>` : "<dimension ref=\"A1\"/>";
|
|
117
181
|
const colsXml = grid.columns.length > 0 ? `<cols>${grid.columns.map((c) => `<col min="${c.min}" max="${c.max}" width="${c.widthChars}" customWidth="1"/>`).join("")}</cols>` : "";
|
|
118
182
|
const heightByRow = /* @__PURE__ */ new Map();
|
|
@@ -136,7 +200,14 @@ function worksheetXml(grid, tableRelIds = []) {
|
|
|
136
200
|
return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
|
|
137
201
|
}).join("");
|
|
138
202
|
const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
|
|
139
|
-
return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
|
|
203
|
+
return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
|
|
204
|
+
}
|
|
205
|
+
function sheetViewsXml(pane) {
|
|
206
|
+
if (!pane || pane.frozenRows <= 0 && pane.frozenCols <= 0) return "";
|
|
207
|
+
const { frozenRows, frozenCols } = pane;
|
|
208
|
+
const topLeftCell = cellRef(frozenRows, frozenCols);
|
|
209
|
+
const activePane = frozenRows > 0 && frozenCols > 0 ? "bottomRight" : frozenRows > 0 ? "bottomLeft" : "topRight";
|
|
210
|
+
return `<sheetViews><sheetView workbookViewId="0"><pane${(frozenCols > 0 ? ` xSplit="${frozenCols}"` : "") + (frozenRows > 0 ? ` ySplit="${frozenRows}"` : "") + ` topLeftCell="${topLeftCell}" activePane="${activePane}" state="frozen"`}/></sheetView></sheetViews>`;
|
|
140
211
|
}
|
|
141
212
|
function tableXml(t, id) {
|
|
142
213
|
const ref = rangeRef(t.ref);
|