reamkit 1.4.0 → 1.5.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 +8 -6
- package/dist/esm/core/converter/facade.d.ts +12 -6
- package/dist/esm/core/converter/facade.js +28 -4
- package/dist/esm/core/converter/ream.d.ts +5 -3
- package/dist/esm/core/converter/ream.js +16 -3
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +18 -0
- package/dist/esm/core/drawingml/sparkline-geometry.d.ts +8 -0
- package/dist/esm/core/drawingml/sparkline-geometry.js +111 -0
- package/dist/esm/core/ir/adapters.d.ts +5 -5
- package/dist/esm/core/ir/sheet.d.ts +31 -0
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -0
- package/dist/esm/core/spreadsheet-model/types.d.ts +184 -0
- package/dist/esm/excel/conditional-format.d.ts +16 -0
- package/dist/esm/excel/conditional-format.js +305 -0
- package/dist/esm/excel/index.d.ts +4 -3
- package/dist/esm/excel/print-model.d.ts +2 -1
- package/dist/esm/excel/print-model.js +114 -2
- package/dist/esm/excel/sheet-drawing.d.ts +1 -1
- package/dist/esm/excel/sheet-to-flow.d.ts +3 -0
- package/dist/esm/excel/sheet-to-flow.js +51 -0
- package/dist/esm/excel/styles-parser.d.ts +1 -50
- package/dist/esm/excel/styles-parser.js +38 -1
- package/dist/esm/excel/table-parser.d.ts +2 -0
- package/dist/esm/excel/table-parser.js +51 -0
- package/dist/esm/excel/workbook-parser.d.ts +1 -5
- package/dist/esm/excel/worksheet-parser.d.ts +1 -60
- package/dist/esm/excel/worksheet-parser.js +239 -1
- package/dist/esm/excel/xlsx-reader.d.ts +3 -1
- package/dist/esm/excel/xlsx-reader.js +86 -51
- package/dist/esm/excel/xlsx-writer.d.ts +4 -0
- package/dist/esm/excel/xlsx-writer.js +271 -0
- package/dist/esm/html/html-writer.js +27 -0
- package/dist/esm/layout/styled-layout.js +91 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ 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
|
|
37
37
|
const docx = await doc.convert('docx'); // write WordprocessingML back out
|
|
38
|
+
const xlsx = await doc.convert('xlsx'); // write SpreadsheetML back (xlsx source)
|
|
38
39
|
|
|
39
40
|
// Hand the bytes to the browser: preview, download, upload, …
|
|
40
41
|
const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
|
|
@@ -166,14 +167,15 @@ multi-section and multi-column layout/headers-footers (incl. PAGE/NUMPAGES
|
|
|
166
167
|
fields)/footnotes and endnotes/hyperlinks and bookmarks/floating drawings/
|
|
167
168
|
images/tracked changes, SpreadsheetML grids,
|
|
168
169
|
number formats and the print model (gridlines, print area, fit-to-page,
|
|
169
|
-
repeated titles, page breaks),
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
repeated titles, page breaks), **conditional formatting** (color scales, data
|
|
171
|
+
bars, icon sets), **sparklines** and **Excel tables**, DrawingML shapes and
|
|
172
|
+
charts, OMML math, Type0+CIDFontType2 embedding with subsetting, Knuth-Plass
|
|
173
|
+
line breaking, Liang hyphenation, OpenType ligatures/kerning + Arabic cursive
|
|
174
|
+
joining, BiDi (UAX #9), hyperlinks (PDF link annotations + HTML anchors,
|
|
173
175
|
scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), PDF/UA-1, AES-256
|
|
174
176
|
encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
|
|
175
|
-
preview, flowed HTML export, and **docx output** (write WordprocessingML
|
|
176
|
-
out, incl.
|
|
177
|
+
preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
|
|
178
|
+
/ SpreadsheetML back out, incl. round-trips). Reads OOXML Transitional and Strict.
|
|
177
179
|
|
|
178
180
|
See `handoff.md` for the full feature matrix and known limitations.
|
|
179
181
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ConvertDocxOptions } from '../../word/docx-to-pdf.js';
|
|
2
2
|
import { DocumentReader } from '../ir/adapters.js';
|
|
3
3
|
import { FlowDoc } from '../ir/flow.js';
|
|
4
|
+
import { SheetDoc } from '../ir/sheet.js';
|
|
4
5
|
import { FontProvider } from '../fonts/provider.js';
|
|
5
6
|
import { Loss, LossReport } from '../ir/index.js';
|
|
6
7
|
import { FontBytesByVariant } from '../font/index.js';
|
|
7
8
|
export interface ConvertOptions extends ConvertDocxOptions {
|
|
8
|
-
/**
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Target: 'pdf' (default), 'svg' (page-stack preview), 'html'/'docx' (flowed),
|
|
11
|
+
* or 'xlsx' (the native grid writer — spreadsheet input only).
|
|
12
|
+
*/
|
|
13
|
+
readonly to?: 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
|
|
10
14
|
/**
|
|
11
15
|
* Strict mode (handoff v1 §5): throw ConversionLossError on the first
|
|
12
16
|
* recorded loss instead of returning it in the report.
|
|
@@ -26,18 +30,20 @@ export interface ConvertResult {
|
|
|
26
30
|
readonly bytes: Uint8Array;
|
|
27
31
|
readonly losses: LossReport;
|
|
28
32
|
}
|
|
33
|
+
export type SourceDoc = FlowDoc | SheetDoc;
|
|
34
|
+
export declare function toFlowDoc(doc: SourceDoc): FlowDoc;
|
|
29
35
|
export interface Converter {
|
|
30
36
|
/** The registered readers, in sniffing order. */
|
|
31
|
-
readonly readers: ReadonlyArray<DocumentReader<
|
|
37
|
+
readonly readers: ReadonlyArray<DocumentReader<SourceDoc>>;
|
|
32
38
|
/** Detect the input format by reader sniffing; undefined when unknown. */
|
|
33
|
-
detect: (bytes: Uint8Array) => DocumentReader<
|
|
39
|
+
detect: (bytes: Uint8Array) => DocumentReader<SourceDoc> | undefined;
|
|
34
40
|
convert: (bytes: Uint8Array, options?: ConvertOptions) => Promise<ConvertResult>;
|
|
35
41
|
}
|
|
36
42
|
export interface CreateConverterOptions {
|
|
37
43
|
/** Override / extend the reader registry (defaults to docx + xlsx). */
|
|
38
|
-
readonly readers?: ReadonlyArray<DocumentReader<
|
|
44
|
+
readonly readers?: ReadonlyArray<DocumentReader<SourceDoc>>;
|
|
39
45
|
}
|
|
40
|
-
export declare const DEFAULT_READERS: ReadonlyArray<DocumentReader<
|
|
46
|
+
export declare const DEFAULT_READERS: ReadonlyArray<DocumentReader<SourceDoc>>;
|
|
41
47
|
export declare function createConverter(opts?: CreateConverterOptions): Converter;
|
|
42
48
|
export declare function resolveFontsViaChain(providers: ReadonlyArray<FontProvider>): Promise<{
|
|
43
49
|
fonts?: FontBytesByVariant;
|
|
@@ -3,15 +3,27 @@ import { FEATURES } from "../ir/features.js";
|
|
|
3
3
|
import { ConversionLossError } from "../ir/loss.js";
|
|
4
4
|
import { layoutStyledDocument } from "../../layout/styled-layout.js";
|
|
5
5
|
import { docxReader } from "../../word/docx-reader.js";
|
|
6
|
+
import { projectSheetDoc } from "../../excel/sheet-to-flow.js";
|
|
6
7
|
import { xlsxReader } from "../../excel/xlsx-reader.js";
|
|
7
8
|
import { chainProviders } from "../fonts/provider.js";
|
|
8
9
|
import { flowRenderOptions } from "./project.js";
|
|
9
10
|
import { writeDocx } from "../../word/docx-writer.js";
|
|
11
|
+
import { writeXlsx } from "../../excel/xlsx-writer.js";
|
|
10
12
|
import { writeHtml } from "../../html/html-writer.js";
|
|
11
13
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
12
14
|
import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
|
|
13
15
|
import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
|
|
14
16
|
//#region src/core/converter/facade.ts
|
|
17
|
+
function toFlowDoc(doc) {
|
|
18
|
+
return doc.kind === "sheet" ? projectSheetDoc(doc) : doc;
|
|
19
|
+
}
|
|
20
|
+
function readToFlow(reader, bytes) {
|
|
21
|
+
const { doc, losses } = reader.read(bytes);
|
|
22
|
+
return {
|
|
23
|
+
doc: toFlowDoc(doc),
|
|
24
|
+
losses
|
|
25
|
+
};
|
|
26
|
+
}
|
|
15
27
|
var DEFAULT_READERS = [docxReader, xlsxReader];
|
|
16
28
|
function createConverter(opts = {}) {
|
|
17
29
|
const readers = opts.readers ?? DEFAULT_READERS;
|
|
@@ -22,7 +34,7 @@ function createConverter(opts = {}) {
|
|
|
22
34
|
if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
|
|
23
35
|
const losses = [];
|
|
24
36
|
if (to === "docx") {
|
|
25
|
-
const { doc: flow, losses: readLosses } = reader
|
|
37
|
+
const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
|
|
26
38
|
losses.push(...readLosses);
|
|
27
39
|
const out = writeDocx(flow);
|
|
28
40
|
losses.push(...out.losses);
|
|
@@ -33,7 +45,7 @@ function createConverter(opts = {}) {
|
|
|
33
45
|
};
|
|
34
46
|
}
|
|
35
47
|
if (to === "html") {
|
|
36
|
-
const { doc: flow, losses: readLosses } = reader
|
|
48
|
+
const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
|
|
37
49
|
losses.push(...readLosses);
|
|
38
50
|
const html = writeHtml(flow);
|
|
39
51
|
losses.push(...html.losses);
|
|
@@ -43,6 +55,18 @@ function createConverter(opts = {}) {
|
|
|
43
55
|
losses
|
|
44
56
|
};
|
|
45
57
|
}
|
|
58
|
+
if (to === "xlsx") {
|
|
59
|
+
const { doc, losses: readLosses } = reader.read(bytes);
|
|
60
|
+
losses.push(...readLosses);
|
|
61
|
+
if (doc.kind !== "sheet") throw new Error("to: 'xlsx' requires a spreadsheet input; a flow document has no grid");
|
|
62
|
+
const out = writeXlsx(doc);
|
|
63
|
+
losses.push(...out.losses);
|
|
64
|
+
if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
|
|
65
|
+
return {
|
|
66
|
+
bytes: out.bytes,
|
|
67
|
+
losses
|
|
68
|
+
};
|
|
69
|
+
}
|
|
46
70
|
let conv = rest;
|
|
47
71
|
if (fontProviders && fontProviders.length > 0 && !rest.fonts && !rest.fontBytes) {
|
|
48
72
|
const { fonts, loss } = await resolveFontsViaChain(fontProviders);
|
|
@@ -55,7 +79,7 @@ function createConverter(opts = {}) {
|
|
|
55
79
|
if (to === "svg") {
|
|
56
80
|
const fonts = conv.fonts ?? (conv.fontBytes ? { regular: conv.fontBytes } : void 0);
|
|
57
81
|
if (!fonts) throw new Error("to: 'svg' requires options.fonts/fontBytes or fontProviders");
|
|
58
|
-
const { doc: flow } = reader
|
|
82
|
+
const { doc: flow } = readToFlow(reader, bytes);
|
|
59
83
|
const svg = writeSvg(layoutStyledDocument(flow.body, {
|
|
60
84
|
registry: FontRegistry.fromBytes(fonts),
|
|
61
85
|
...flowRenderOptions(flow)
|
|
@@ -110,4 +134,4 @@ async function resolveFontsViaChain(providers) {
|
|
|
110
134
|
};
|
|
111
135
|
}
|
|
112
136
|
//#endregion
|
|
113
|
-
export { DEFAULT_READERS, createConverter, resolveFontsViaChain };
|
|
137
|
+
export { DEFAULT_READERS, createConverter, resolveFontsViaChain, toFlowDoc };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { ConvertResult } from './facade.js';
|
|
1
|
+
import { ConvertResult, SourceDoc } from './facade.js';
|
|
2
2
|
import { FontBytesByVariant } from '../font/index.js';
|
|
3
3
|
import { FetchLike } from '../fonts/index.js';
|
|
4
4
|
import { FontProvider } from '../fonts/provider.js';
|
|
5
5
|
import { Loss } from '../ir/index.js';
|
|
6
6
|
import { DocumentReader } from '../ir/adapters.js';
|
|
7
7
|
import { FlowDoc } from '../ir/flow.js';
|
|
8
|
+
import { SheetDoc } from '../ir/sheet.js';
|
|
8
9
|
import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
|
|
9
|
-
export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx';
|
|
10
|
+
export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
|
|
10
11
|
export interface ReamParseOptions {
|
|
11
|
-
readonly readers?: ReadonlyArray<DocumentReader
|
|
12
|
+
readonly readers?: ReadonlyArray<DocumentReader<SourceDoc>>;
|
|
12
13
|
}
|
|
13
14
|
export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry' | 'styles'> {
|
|
14
15
|
readonly fonts?: FontBytesByVariant;
|
|
@@ -22,6 +23,7 @@ export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry'
|
|
|
22
23
|
}
|
|
23
24
|
export declare class Ream {
|
|
24
25
|
readonly flow: FlowDoc;
|
|
26
|
+
readonly sheet: SheetDoc | undefined;
|
|
25
27
|
readonly losses: ReadonlyArray<Loss>;
|
|
26
28
|
private readonly source;
|
|
27
29
|
private readonly readerId;
|
|
@@ -7,18 +7,20 @@ import { renderStyledPdf, renderStyledPdfEncrypted } from "../../pdf/styled-page
|
|
|
7
7
|
import "../../pdf/index.js";
|
|
8
8
|
import { flowRenderOptions } from "./project.js";
|
|
9
9
|
import { writeDocx } from "../../word/docx-writer.js";
|
|
10
|
+
import { writeXlsx } from "../../excel/xlsx-writer.js";
|
|
10
11
|
import { writeHtml } from "../../html/html-writer.js";
|
|
11
12
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
12
13
|
import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
|
|
13
|
-
import { DEFAULT_READERS, resolveFontsViaChain } from "./facade.js";
|
|
14
|
+
import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
|
|
14
15
|
//#region src/core/converter/ream.ts
|
|
15
16
|
var SOURCE_MIME = {
|
|
16
17
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
17
18
|
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
18
19
|
};
|
|
19
20
|
var Ream = class Ream {
|
|
20
|
-
constructor(flow, losses, source, readerId) {
|
|
21
|
+
constructor(flow, sheet, losses, source, readerId) {
|
|
21
22
|
this.flow = flow;
|
|
23
|
+
this.sheet = sheet;
|
|
22
24
|
this.losses = losses;
|
|
23
25
|
this.source = source;
|
|
24
26
|
this.readerId = readerId;
|
|
@@ -28,7 +30,8 @@ var Ream = class Ream {
|
|
|
28
30
|
const reader = readers.find((r) => r.sniff(bytes));
|
|
29
31
|
if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
|
|
30
32
|
const { doc, losses } = reader.read(bytes);
|
|
31
|
-
|
|
33
|
+
const sheet = doc.kind === "sheet" ? doc : void 0;
|
|
34
|
+
return new Ream(toFlowDoc(doc), sheet, losses, bytes, reader.id);
|
|
32
35
|
}
|
|
33
36
|
get format() {
|
|
34
37
|
return this.readerId;
|
|
@@ -56,6 +59,16 @@ var Ream = class Ream {
|
|
|
56
59
|
losses
|
|
57
60
|
};
|
|
58
61
|
}
|
|
62
|
+
if (to === "xlsx") {
|
|
63
|
+
if (!this.sheet) throw new Error("convert('xlsx') requires a spreadsheet source; this document has no grid");
|
|
64
|
+
const xlsx = writeXlsx(this.sheet);
|
|
65
|
+
losses.push(...xlsx.losses);
|
|
66
|
+
this.enforceStrict(options, losses);
|
|
67
|
+
return {
|
|
68
|
+
bytes: xlsx.bytes,
|
|
69
|
+
losses
|
|
70
|
+
};
|
|
71
|
+
}
|
|
59
72
|
const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
|
|
60
73
|
const registry = FontRegistry.fromBytes(fonts);
|
|
61
74
|
if (to === "svg") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, 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, 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';
|
|
@@ -221,6 +221,21 @@ export interface CellMargins {
|
|
|
221
221
|
export interface CellShading {
|
|
222
222
|
readonly colorHex: string;
|
|
223
223
|
}
|
|
224
|
+
export interface CellDataBar {
|
|
225
|
+
readonly fraction: number;
|
|
226
|
+
readonly colorHex: string;
|
|
227
|
+
readonly startFraction?: number;
|
|
228
|
+
}
|
|
229
|
+
export type CellIconShape = 'circle' | 'square' | 'diamond' | 'triangleUp' | 'triangleDown' | 'triangleRight';
|
|
230
|
+
export interface CellIcon {
|
|
231
|
+
readonly shape: CellIconShape;
|
|
232
|
+
readonly colorHex: string;
|
|
233
|
+
}
|
|
234
|
+
export interface CellSparkline {
|
|
235
|
+
readonly kind: 'line' | 'column' | 'winLoss';
|
|
236
|
+
readonly values: ReadonlyArray<number | null>;
|
|
237
|
+
readonly colorHex?: string;
|
|
238
|
+
}
|
|
224
239
|
export type CellMerge = 'start' | 'middle' | 'end';
|
|
225
240
|
export interface CellProperties {
|
|
226
241
|
readonly width?: Pt;
|
|
@@ -229,6 +244,9 @@ export interface CellProperties {
|
|
|
229
244
|
readonly borders?: CellBorders;
|
|
230
245
|
readonly margins?: CellMargins;
|
|
231
246
|
readonly shading?: CellShading;
|
|
247
|
+
readonly dataBar?: CellDataBar;
|
|
248
|
+
readonly icon?: CellIcon;
|
|
249
|
+
readonly sparkline?: CellSparkline;
|
|
232
250
|
}
|
|
233
251
|
export interface RowProperties {
|
|
234
252
|
readonly height?: Pt;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StrokeStyle, VectorPath } from '../vector.js';
|
|
2
|
+
export type SparklineKind = 'line' | 'column' | 'winLoss';
|
|
3
|
+
export interface SparklinePrim {
|
|
4
|
+
readonly paths: ReadonlyArray<VectorPath>;
|
|
5
|
+
readonly fillColorHex?: string;
|
|
6
|
+
readonly stroke?: StrokeStyle;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildSparkline(kind: SparklineKind, values: ReadonlyArray<number | null>, width: number, height: number, colorHex?: string): Array<SparklinePrim>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { PathBuilder } from "../vector.js";
|
|
2
|
+
//#region src/core/drawingml/sparkline-geometry.ts
|
|
3
|
+
var DEFAULT_SERIES_HEX = "376092";
|
|
4
|
+
var LOSS_HEX = "D00000";
|
|
5
|
+
function buildSparkline(kind, values, width, height, colorHex) {
|
|
6
|
+
if (width <= 0 || height <= 0 || values.every((v) => v === null)) return [];
|
|
7
|
+
const color = colorHex ?? DEFAULT_SERIES_HEX;
|
|
8
|
+
switch (kind) {
|
|
9
|
+
case "line": return lineSpark(values, width, height, color);
|
|
10
|
+
case "column": return columnSpark(values, width, height, color);
|
|
11
|
+
case "winLoss": return winLossSpark(values, width, height, color);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function lineSpark(values, w, h, color) {
|
|
15
|
+
const n = values.length;
|
|
16
|
+
const pad = Math.min(1, h * .12);
|
|
17
|
+
const { min, span } = extent(values);
|
|
18
|
+
const x = (i) => n === 1 ? w / 2 : i / (n - 1) * w;
|
|
19
|
+
const y = (v) => pad + (v - min) / span * (h - 2 * pad);
|
|
20
|
+
const b = new PathBuilder();
|
|
21
|
+
let penDown = false;
|
|
22
|
+
for (let i = 0; i < n; i++) {
|
|
23
|
+
const v = values[i];
|
|
24
|
+
if (v === null || v === void 0) {
|
|
25
|
+
penDown = false;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (penDown) b.lineTo(x(i), y(v));
|
|
29
|
+
else {
|
|
30
|
+
b.moveTo(x(i), y(v));
|
|
31
|
+
penDown = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [{
|
|
35
|
+
paths: [b.build()],
|
|
36
|
+
stroke: {
|
|
37
|
+
colorHex: color,
|
|
38
|
+
widthPt: .75,
|
|
39
|
+
join: "round"
|
|
40
|
+
}
|
|
41
|
+
}];
|
|
42
|
+
}
|
|
43
|
+
function columnSpark(values, w, h, color) {
|
|
44
|
+
const n = values.length;
|
|
45
|
+
const { min, span } = extent(values);
|
|
46
|
+
const slot = w / n;
|
|
47
|
+
const bw = slot * .72;
|
|
48
|
+
const off = (slot - bw) / 2;
|
|
49
|
+
const pad = h * .06;
|
|
50
|
+
const paths = [];
|
|
51
|
+
for (let i = 0; i < n; i++) {
|
|
52
|
+
const v = values[i];
|
|
53
|
+
if (v === null || v === void 0) continue;
|
|
54
|
+
const frac = (v - min) / span;
|
|
55
|
+
const bh = Math.max(.4, pad + frac * (h - pad));
|
|
56
|
+
paths.push(rectPath(i * slot + off, 0, bw, bh));
|
|
57
|
+
}
|
|
58
|
+
return paths.length > 0 ? [{
|
|
59
|
+
paths,
|
|
60
|
+
fillColorHex: color
|
|
61
|
+
}] : [];
|
|
62
|
+
}
|
|
63
|
+
function winLossSpark(values, w, h, color) {
|
|
64
|
+
const n = values.length;
|
|
65
|
+
const slot = w / n;
|
|
66
|
+
const bw = slot * .72;
|
|
67
|
+
const off = (slot - bw) / 2;
|
|
68
|
+
const mid = h / 2;
|
|
69
|
+
const barH = h * .4;
|
|
70
|
+
const wins = [];
|
|
71
|
+
const losses = [];
|
|
72
|
+
for (let i = 0; i < n; i++) {
|
|
73
|
+
const v = values[i];
|
|
74
|
+
if (v === null || v === void 0 || v === 0) continue;
|
|
75
|
+
const x = i * slot + off;
|
|
76
|
+
if (v > 0) wins.push(rectPath(x, mid, bw, barH));
|
|
77
|
+
else losses.push(rectPath(x, mid - barH, bw, barH));
|
|
78
|
+
}
|
|
79
|
+
const out = [];
|
|
80
|
+
if (wins.length > 0) out.push({
|
|
81
|
+
paths: wins,
|
|
82
|
+
fillColorHex: color
|
|
83
|
+
});
|
|
84
|
+
if (losses.length > 0) out.push({
|
|
85
|
+
paths: losses,
|
|
86
|
+
fillColorHex: LOSS_HEX
|
|
87
|
+
});
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function extent(values) {
|
|
91
|
+
let min = Infinity;
|
|
92
|
+
let max = -Infinity;
|
|
93
|
+
for (const v of values) {
|
|
94
|
+
if (v === null) continue;
|
|
95
|
+
if (v < min) min = v;
|
|
96
|
+
if (v > max) max = v;
|
|
97
|
+
}
|
|
98
|
+
if (min === Infinity) return {
|
|
99
|
+
min: 0,
|
|
100
|
+
span: 1
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
min,
|
|
104
|
+
span: max - min || 1
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function rectPath(x, y, w, h) {
|
|
108
|
+
return new PathBuilder().moveTo(x, y).lineTo(x + w, y).lineTo(x + w, y + h).lineTo(x, y + h).close().build();
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { buildSparkline };
|
|
@@ -12,8 +12,8 @@ export interface ReadResult<TDoc> {
|
|
|
12
12
|
export interface DocumentReader<TDoc = FlowDoc> {
|
|
13
13
|
/** Format id: 'docx', 'xlsx', 'pdf', … */
|
|
14
14
|
readonly id: string;
|
|
15
|
-
/** Which tree this reader produces. */
|
|
16
|
-
readonly produces: 'flow' | 'page';
|
|
15
|
+
/** Which tree this reader produces ('sheet' projects to flow at the boundary). */
|
|
16
|
+
readonly produces: 'flow' | 'page' | 'sheet';
|
|
17
17
|
/** Feature vocabulary this reader understands (capability matrix source). */
|
|
18
18
|
readonly supports: ReadonlySet<Feature>;
|
|
19
19
|
/** Cheap format detection (magic bytes / container markers). */
|
|
@@ -28,10 +28,10 @@ export interface WriteResult {
|
|
|
28
28
|
readonly losses: LossReport;
|
|
29
29
|
}
|
|
30
30
|
export interface DocumentWriter<TDoc> {
|
|
31
|
-
/** Output format id: 'pdf', 'svg', 'html', 'docx', … */
|
|
31
|
+
/** Output format id: 'pdf', 'svg', 'html', 'docx', 'xlsx', … */
|
|
32
32
|
readonly id: string;
|
|
33
|
-
/** Which tree this writer consumes. */
|
|
34
|
-
readonly consumes: 'flow' | 'page';
|
|
33
|
+
/** Which tree this writer consumes ('sheet' for the native grid writer). */
|
|
34
|
+
readonly consumes: 'flow' | 'page' | 'sheet';
|
|
35
35
|
readonly supports: ReadonlySet<Feature>;
|
|
36
36
|
write: (doc: TDoc, opts?: WriteOptions) => WriteResult;
|
|
37
37
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Chart, DocumentInfo } from '../document-model/index.js';
|
|
2
|
+
import { DefinedName, ParsedWorksheet, XlsxStyles } from '../spreadsheet-model/index.js';
|
|
3
|
+
import { ResourceStore } from './resources.js';
|
|
4
|
+
export interface SheetChartRef {
|
|
5
|
+
readonly chartPartPath: string;
|
|
6
|
+
readonly widthPt: number;
|
|
7
|
+
readonly heightPt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Sheet {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly grid: ParsedWorksheet;
|
|
12
|
+
readonly charts?: ReadonlyArray<SheetChartRef>;
|
|
13
|
+
}
|
|
14
|
+
export interface SheetDoc {
|
|
15
|
+
readonly kind: 'sheet';
|
|
16
|
+
readonly sheets: ReadonlyArray<Sheet>;
|
|
17
|
+
/** §18.8 workbook style table (cellXfs + fonts/fills/borders/numFmts). */
|
|
18
|
+
readonly styles: XlsxStyles;
|
|
19
|
+
/** §18.4 shared string table; a cell with type 's' indexes into it. */
|
|
20
|
+
readonly sharedStrings: ReadonlyArray<string>;
|
|
21
|
+
/** §18.2.5 workbook defined names (print areas/titles, named ranges). */
|
|
22
|
+
readonly definedNames: ReadonlyArray<DefinedName>;
|
|
23
|
+
/** §18.2.28 1904 date system (serial-to-date epoch). */
|
|
24
|
+
readonly date1904: boolean;
|
|
25
|
+
/** Parsed charts keyed by part path (SheetChartRef.chartPartPath). */
|
|
26
|
+
readonly chartData?: ReadonlyMap<string, Chart>;
|
|
27
|
+
/** Content-addressed binary resources (sheet images). */
|
|
28
|
+
readonly resources: ResourceStore;
|
|
29
|
+
/** Document metadata from docProps/core.xml. */
|
|
30
|
+
readonly info?: DocumentInfo;
|
|
31
|
+
}
|
|
@@ -0,0 +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';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export type CellType = 'n' | 's' | 'str' | 'b' | 'd' | 'e' | 'inlineStr';
|
|
2
|
+
export interface WorksheetCell {
|
|
3
|
+
readonly column: number;
|
|
4
|
+
readonly row: number;
|
|
5
|
+
readonly type: CellType;
|
|
6
|
+
readonly rawValue: string;
|
|
7
|
+
readonly inlineText?: string;
|
|
8
|
+
readonly styleIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ColumnWidth {
|
|
11
|
+
readonly min: number;
|
|
12
|
+
readonly max: number;
|
|
13
|
+
readonly widthChars: number;
|
|
14
|
+
}
|
|
15
|
+
export interface MergedRange {
|
|
16
|
+
readonly startColumn: number;
|
|
17
|
+
readonly startRow: number;
|
|
18
|
+
readonly endColumn: number;
|
|
19
|
+
readonly endRow: number;
|
|
20
|
+
}
|
|
21
|
+
export interface RowHeight {
|
|
22
|
+
readonly row: number;
|
|
23
|
+
readonly heightPt: number;
|
|
24
|
+
readonly customHeight: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface XlsxPageMargins {
|
|
27
|
+
readonly leftInches: number;
|
|
28
|
+
readonly rightInches: number;
|
|
29
|
+
readonly topInches: number;
|
|
30
|
+
readonly bottomInches: number;
|
|
31
|
+
readonly headerInches?: number;
|
|
32
|
+
readonly footerInches?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface XlsxPageSetup {
|
|
35
|
+
readonly paperSize?: number;
|
|
36
|
+
readonly orientation?: 'portrait' | 'landscape' | 'default';
|
|
37
|
+
readonly scale?: number;
|
|
38
|
+
readonly fitToWidth?: number;
|
|
39
|
+
readonly fitToHeight?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface XlsxPrintOptions {
|
|
42
|
+
readonly gridLines?: boolean;
|
|
43
|
+
readonly horizontalCentered?: boolean;
|
|
44
|
+
readonly verticalCentered?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ParsedWorksheet {
|
|
47
|
+
readonly cells: ReadonlyArray<WorksheetCell>;
|
|
48
|
+
readonly maxRow: number;
|
|
49
|
+
readonly maxColumn: number;
|
|
50
|
+
readonly columns: ReadonlyArray<ColumnWidth>;
|
|
51
|
+
readonly merges: ReadonlyArray<MergedRange>;
|
|
52
|
+
readonly rowHeights: ReadonlyArray<RowHeight>;
|
|
53
|
+
readonly pageMargins?: XlsxPageMargins;
|
|
54
|
+
readonly pageSetup?: XlsxPageSetup;
|
|
55
|
+
readonly fitToPage?: boolean;
|
|
56
|
+
readonly printOptions?: XlsxPrintOptions;
|
|
57
|
+
readonly rowBreaks?: ReadonlyArray<number>;
|
|
58
|
+
readonly colBreaks?: ReadonlyArray<number>;
|
|
59
|
+
readonly drawingRelId?: string;
|
|
60
|
+
readonly conditionalFormats?: ReadonlyArray<ConditionalFormat>;
|
|
61
|
+
readonly sparklines?: ReadonlyArray<ParsedSparkline>;
|
|
62
|
+
readonly tablePartRelIds?: ReadonlyArray<string>;
|
|
63
|
+
readonly tables?: ReadonlyArray<ExcelTable>;
|
|
64
|
+
}
|
|
65
|
+
export interface ExcelTable {
|
|
66
|
+
readonly ref: MergedRange;
|
|
67
|
+
readonly name?: string;
|
|
68
|
+
readonly styleName?: string;
|
|
69
|
+
readonly headerRowCount: number;
|
|
70
|
+
readonly showRowStripes: boolean;
|
|
71
|
+
readonly showColumnStripes: boolean;
|
|
72
|
+
readonly showFirstColumn: boolean;
|
|
73
|
+
readonly showLastColumn: boolean;
|
|
74
|
+
readonly autoFilter: boolean;
|
|
75
|
+
readonly headerHex?: string;
|
|
76
|
+
readonly bandHex?: string;
|
|
77
|
+
readonly headerTextHex?: string;
|
|
78
|
+
}
|
|
79
|
+
export type SparklineKind = 'line' | 'column' | 'winLoss';
|
|
80
|
+
export interface ParsedSparkline {
|
|
81
|
+
readonly kind: SparklineKind;
|
|
82
|
+
readonly dataRange: string;
|
|
83
|
+
readonly sqref: string;
|
|
84
|
+
readonly colorHex?: string;
|
|
85
|
+
}
|
|
86
|
+
export interface XlsxFont {
|
|
87
|
+
readonly sizePt?: number;
|
|
88
|
+
readonly bold?: boolean;
|
|
89
|
+
readonly italic?: boolean;
|
|
90
|
+
readonly underline?: boolean;
|
|
91
|
+
readonly colorHex?: string;
|
|
92
|
+
readonly name?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface XlsxFill {
|
|
95
|
+
readonly patternType?: string;
|
|
96
|
+
readonly fgColorHex?: string;
|
|
97
|
+
readonly bgColorHex?: string;
|
|
98
|
+
}
|
|
99
|
+
export type XlsxBorderStyleName = 'none' | 'thin' | 'medium' | 'thick' | 'hair' | 'dashed' | 'dotted' | 'double' | 'mediumDashed' | 'dashDot' | 'mediumDashDot' | 'dashDotDot' | 'mediumDashDotDot' | 'slantDashDot';
|
|
100
|
+
export interface XlsxBorderEdge {
|
|
101
|
+
readonly style?: XlsxBorderStyleName;
|
|
102
|
+
readonly colorHex?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface XlsxBorder {
|
|
105
|
+
readonly top?: XlsxBorderEdge;
|
|
106
|
+
readonly right?: XlsxBorderEdge;
|
|
107
|
+
readonly bottom?: XlsxBorderEdge;
|
|
108
|
+
readonly left?: XlsxBorderEdge;
|
|
109
|
+
}
|
|
110
|
+
export type XlsxHorizontalAlign = 'left' | 'center' | 'right' | 'fill' | 'justify' | 'centerContinuous' | 'distributed';
|
|
111
|
+
export type XlsxVerticalAlign = 'top' | 'center' | 'bottom' | 'justify' | 'distributed';
|
|
112
|
+
export interface XlsxCellAlignment {
|
|
113
|
+
readonly horizontal?: XlsxHorizontalAlign;
|
|
114
|
+
readonly vertical?: XlsxVerticalAlign;
|
|
115
|
+
readonly wrapText?: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface XlsxCellXf {
|
|
118
|
+
readonly numFmtId: number;
|
|
119
|
+
readonly fontId: number;
|
|
120
|
+
readonly fillId: number;
|
|
121
|
+
readonly borderId: number;
|
|
122
|
+
readonly applyNumberFormat?: boolean;
|
|
123
|
+
readonly applyFont?: boolean;
|
|
124
|
+
readonly applyFill?: boolean;
|
|
125
|
+
readonly applyBorder?: boolean;
|
|
126
|
+
readonly applyAlignment?: boolean;
|
|
127
|
+
readonly alignment?: XlsxCellAlignment;
|
|
128
|
+
}
|
|
129
|
+
export interface XlsxStyles {
|
|
130
|
+
readonly numFmts: ReadonlyMap<number, string>;
|
|
131
|
+
readonly fonts: ReadonlyArray<XlsxFont>;
|
|
132
|
+
readonly fills: ReadonlyArray<XlsxFill>;
|
|
133
|
+
readonly borders: ReadonlyArray<XlsxBorder>;
|
|
134
|
+
readonly cellXfs: ReadonlyArray<XlsxCellXf>;
|
|
135
|
+
readonly dxfs?: ReadonlyArray<Dxf>;
|
|
136
|
+
}
|
|
137
|
+
export interface Dxf {
|
|
138
|
+
readonly font?: XlsxFont;
|
|
139
|
+
readonly fill?: XlsxFill;
|
|
140
|
+
}
|
|
141
|
+
export interface DefinedName {
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly localSheetId?: number;
|
|
144
|
+
readonly value: string;
|
|
145
|
+
}
|
|
146
|
+
export type CfOperator = 'lessThan' | 'lessThanOrEqual' | 'equal' | 'notEqual' | 'greaterThanOrEqual' | 'greaterThan' | 'between' | 'notBetween';
|
|
147
|
+
export interface CfRuleCellIs {
|
|
148
|
+
readonly type: 'cellIs';
|
|
149
|
+
readonly priority: number;
|
|
150
|
+
readonly operator: CfOperator;
|
|
151
|
+
readonly formulas: ReadonlyArray<string>;
|
|
152
|
+
readonly dxfId: number;
|
|
153
|
+
}
|
|
154
|
+
export type CfvoType = 'num' | 'percent' | 'max' | 'min' | 'percentile' | 'formula' | 'autoMin' | 'autoMax';
|
|
155
|
+
export interface Cfvo {
|
|
156
|
+
readonly type: CfvoType;
|
|
157
|
+
readonly val?: string;
|
|
158
|
+
}
|
|
159
|
+
export interface CfRuleColorScale {
|
|
160
|
+
readonly type: 'colorScale';
|
|
161
|
+
readonly priority: number;
|
|
162
|
+
readonly cfvos: ReadonlyArray<Cfvo>;
|
|
163
|
+
readonly colorsHex: ReadonlyArray<string>;
|
|
164
|
+
}
|
|
165
|
+
export interface CfRuleDataBar {
|
|
166
|
+
readonly type: 'dataBar';
|
|
167
|
+
readonly priority: number;
|
|
168
|
+
readonly cfvos: ReadonlyArray<Cfvo>;
|
|
169
|
+
readonly colorHex: string;
|
|
170
|
+
readonly minLength?: number;
|
|
171
|
+
readonly maxLength?: number;
|
|
172
|
+
}
|
|
173
|
+
export interface CfRuleIconSet {
|
|
174
|
+
readonly type: 'iconSet';
|
|
175
|
+
readonly priority: number;
|
|
176
|
+
readonly iconSet: string;
|
|
177
|
+
readonly cfvos: ReadonlyArray<Cfvo>;
|
|
178
|
+
readonly reverse?: boolean;
|
|
179
|
+
}
|
|
180
|
+
export type CfRule = CfRuleCellIs | CfRuleColorScale | CfRuleDataBar | CfRuleIconSet;
|
|
181
|
+
export interface ConditionalFormat {
|
|
182
|
+
readonly ranges: ReadonlyArray<MergedRange>;
|
|
183
|
+
readonly rules: ReadonlyArray<CfRule>;
|
|
184
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CellIcon } from '../core/document-model/index.js';
|
|
2
|
+
import { ConditionalFormat, WorksheetCell, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
3
|
+
export interface CfOverride {
|
|
4
|
+
readonly fillHex?: string;
|
|
5
|
+
readonly fontColorHex?: string;
|
|
6
|
+
readonly bold?: boolean;
|
|
7
|
+
readonly italic?: boolean;
|
|
8
|
+
readonly dataBar?: {
|
|
9
|
+
readonly fraction: number;
|
|
10
|
+
readonly colorHex: string;
|
|
11
|
+
readonly startFraction?: number;
|
|
12
|
+
};
|
|
13
|
+
readonly icon?: CellIcon;
|
|
14
|
+
}
|
|
15
|
+
export type CellConditionalFormatter = (row: number, col: number, numericValue: number | undefined) => CfOverride | undefined;
|
|
16
|
+
export declare function buildConditionalFormatter(conditionalFormats: ReadonlyArray<ConditionalFormat> | undefined, styles: XlsxStyles, cells: ReadonlyArray<WorksheetCell>): CellConditionalFormatter | undefined;
|