reamkit 1.3.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 +14 -9
- package/dist/esm/core/converter/facade.d.ts +12 -6
- package/dist/esm/core/converter/facade.js +39 -3
- package/dist/esm/core/converter/ream.d.ts +5 -3
- package/dist/esm/core/converter/ream.js +26 -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/index.d.ts +2 -0
- package/dist/esm/core/opc/index.js +1 -0
- package/dist/esm/core/opc/opc-writer.d.ts +28 -0
- package/dist/esm/core/opc/opc-writer.js +60 -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/index.d.ts +1 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/layout/styled-layout.js +91 -1
- package/dist/esm/word/document-parser.js +47 -13
- package/dist/esm/word/docx-writer.d.ts +4 -0
- package/dist/esm/word/docx-writer.js +602 -0
- package/dist/esm/word/drawing-parser.d.ts +1 -0
- package/dist/esm/word/drawing-parser.js +37 -2
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ const doc = Ream.parse(bytes); // docx or xlsx — 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
|
|
37
|
+
const docx = await doc.convert('docx'); // write WordprocessingML back out
|
|
38
|
+
const xlsx = await doc.convert('xlsx'); // write SpreadsheetML back (xlsx source)
|
|
37
39
|
|
|
38
40
|
// Hand the bytes to the browser: preview, download, upload, …
|
|
39
41
|
const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
|
|
@@ -150,10 +152,11 @@ automatically from the document's `docProps/core.xml`), `attachments`
|
|
|
150
152
|
|
|
151
153
|
### Lower-level APIs
|
|
152
154
|
|
|
153
|
-
- `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter` — the `@experimental`
|
|
154
|
-
interfaces of the interlayer, for building custom pipelines (and
|
|
155
|
-
unused formats out of your bundle); `layoutStyledDocument` produces the
|
|
156
|
-
frozen page model (`PageItem` pages in a top-left `Pt` frame)
|
|
155
|
+
- `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter`, `docxWriter` — the `@experimental`
|
|
156
|
+
reader/writer interfaces of the interlayer, for building custom pipelines (and
|
|
157
|
+
keeping unused formats out of your bundle); `layoutStyledDocument` produces the
|
|
158
|
+
frozen page model (`PageItem` pages in a top-left `Pt` frame) the page-based
|
|
159
|
+
writers consume (`docxWriter` works from the flow model, before layout).
|
|
157
160
|
- `renderStyledPdf` drives the layout engine directly; the typed document
|
|
158
161
|
model is on the `reamkit/document-model` subpath.
|
|
159
162
|
|
|
@@ -164,13 +167,15 @@ multi-section and multi-column layout/headers-footers (incl. PAGE/NUMPAGES
|
|
|
164
167
|
fields)/footnotes and endnotes/hyperlinks and bookmarks/floating drawings/
|
|
165
168
|
images/tracked changes, SpreadsheetML grids,
|
|
166
169
|
number formats and the print model (gridlines, print area, fit-to-page,
|
|
167
|
-
repeated titles, page breaks),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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,
|
|
171
175
|
scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), PDF/UA-1, AES-256
|
|
172
176
|
encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
|
|
173
|
-
preview, flowed HTML export
|
|
177
|
+
preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
|
|
178
|
+
/ SpreadsheetML back out, incl. round-trips). Reads OOXML Transitional and Strict.
|
|
174
179
|
|
|
175
180
|
See `handoff.md` for the full feature matrix and known limitations.
|
|
176
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,14 +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";
|
|
10
|
+
import { writeDocx } from "../../word/docx-writer.js";
|
|
11
|
+
import { writeXlsx } from "../../excel/xlsx-writer.js";
|
|
9
12
|
import { writeHtml } from "../../html/html-writer.js";
|
|
10
13
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
11
14
|
import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
|
|
12
15
|
import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
|
|
13
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
|
+
}
|
|
14
27
|
var DEFAULT_READERS = [docxReader, xlsxReader];
|
|
15
28
|
function createConverter(opts = {}) {
|
|
16
29
|
const readers = opts.readers ?? DEFAULT_READERS;
|
|
@@ -20,8 +33,19 @@ function createConverter(opts = {}) {
|
|
|
20
33
|
const reader = detect(bytes);
|
|
21
34
|
if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
|
|
22
35
|
const losses = [];
|
|
36
|
+
if (to === "docx") {
|
|
37
|
+
const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
|
|
38
|
+
losses.push(...readLosses);
|
|
39
|
+
const out = writeDocx(flow);
|
|
40
|
+
losses.push(...out.losses);
|
|
41
|
+
if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
|
|
42
|
+
return {
|
|
43
|
+
bytes: out.bytes,
|
|
44
|
+
losses
|
|
45
|
+
};
|
|
46
|
+
}
|
|
23
47
|
if (to === "html") {
|
|
24
|
-
const { doc: flow, losses: readLosses } = reader
|
|
48
|
+
const { doc: flow, losses: readLosses } = readToFlow(reader, bytes);
|
|
25
49
|
losses.push(...readLosses);
|
|
26
50
|
const html = writeHtml(flow);
|
|
27
51
|
losses.push(...html.losses);
|
|
@@ -31,6 +55,18 @@ function createConverter(opts = {}) {
|
|
|
31
55
|
losses
|
|
32
56
|
};
|
|
33
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
|
+
}
|
|
34
70
|
let conv = rest;
|
|
35
71
|
if (fontProviders && fontProviders.length > 0 && !rest.fonts && !rest.fontBytes) {
|
|
36
72
|
const { fonts, loss } = await resolveFontsViaChain(fontProviders);
|
|
@@ -43,7 +79,7 @@ function createConverter(opts = {}) {
|
|
|
43
79
|
if (to === "svg") {
|
|
44
80
|
const fonts = conv.fonts ?? (conv.fontBytes ? { regular: conv.fontBytes } : void 0);
|
|
45
81
|
if (!fonts) throw new Error("to: 'svg' requires options.fonts/fontBytes or fontProviders");
|
|
46
|
-
const { doc: flow } = reader
|
|
82
|
+
const { doc: flow } = readToFlow(reader, bytes);
|
|
47
83
|
const svg = writeSvg(layoutStyledDocument(flow.body, {
|
|
48
84
|
registry: FontRegistry.fromBytes(fonts),
|
|
49
85
|
...flowRenderOptions(flow)
|
|
@@ -98,4 +134,4 @@ async function resolveFontsViaChain(providers) {
|
|
|
98
134
|
};
|
|
99
135
|
}
|
|
100
136
|
//#endregion
|
|
101
|
-
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';
|
|
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;
|
|
@@ -6,18 +6,21 @@ import { signPdf } from "../../pdf/signature.js";
|
|
|
6
6
|
import { renderStyledPdf, renderStyledPdfEncrypted } from "../../pdf/styled-page-renderer.js";
|
|
7
7
|
import "../../pdf/index.js";
|
|
8
8
|
import { flowRenderOptions } from "./project.js";
|
|
9
|
+
import { writeDocx } from "../../word/docx-writer.js";
|
|
10
|
+
import { writeXlsx } from "../../excel/xlsx-writer.js";
|
|
9
11
|
import { writeHtml } from "../../html/html-writer.js";
|
|
10
12
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
11
13
|
import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
|
|
12
|
-
import { DEFAULT_READERS, resolveFontsViaChain } from "./facade.js";
|
|
14
|
+
import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
|
|
13
15
|
//#region src/core/converter/ream.ts
|
|
14
16
|
var SOURCE_MIME = {
|
|
15
17
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
16
18
|
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
17
19
|
};
|
|
18
20
|
var Ream = class Ream {
|
|
19
|
-
constructor(flow, losses, source, readerId) {
|
|
21
|
+
constructor(flow, sheet, losses, source, readerId) {
|
|
20
22
|
this.flow = flow;
|
|
23
|
+
this.sheet = sheet;
|
|
21
24
|
this.losses = losses;
|
|
22
25
|
this.source = source;
|
|
23
26
|
this.readerId = readerId;
|
|
@@ -27,7 +30,8 @@ var Ream = class Ream {
|
|
|
27
30
|
const reader = readers.find((r) => r.sniff(bytes));
|
|
28
31
|
if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
|
|
29
32
|
const { doc, losses } = reader.read(bytes);
|
|
30
|
-
|
|
33
|
+
const sheet = doc.kind === "sheet" ? doc : void 0;
|
|
34
|
+
return new Ream(toFlowDoc(doc), sheet, losses, bytes, reader.id);
|
|
31
35
|
}
|
|
32
36
|
get format() {
|
|
33
37
|
return this.readerId;
|
|
@@ -46,6 +50,25 @@ var Ream = class Ream {
|
|
|
46
50
|
losses
|
|
47
51
|
};
|
|
48
52
|
}
|
|
53
|
+
if (to === "docx") {
|
|
54
|
+
const docx = writeDocx(this.flow);
|
|
55
|
+
losses.push(...docx.losses);
|
|
56
|
+
this.enforceStrict(options, losses);
|
|
57
|
+
return {
|
|
58
|
+
bytes: docx.bytes,
|
|
59
|
+
losses
|
|
60
|
+
};
|
|
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
|
+
}
|
|
49
72
|
const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
|
|
50
73
|
const registry = FontRegistry.fromBytes(fonts);
|
|
51
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
|
+
}
|
|
@@ -4,3 +4,5 @@ export { REL_OFFICE_DOCUMENT, REL_CORE_PROPERTIES, REL_EXTENDED_PROPERTIES, } fr
|
|
|
4
4
|
export { parseCoreProperties } from './core-properties.js';
|
|
5
5
|
export type { CoreProperties } from './core-properties.js';
|
|
6
6
|
export { isOoxmlRel } from './relationship-types.js';
|
|
7
|
+
export { buildOpcPackage, relsPathFor } from './opc-writer.js';
|
|
8
|
+
export type { OpcPart, OpcPartRelationships, OpcWriteOptions } from './opc-writer.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Relationship } from './relationships.js';
|
|
2
|
+
export interface OpcPart {
|
|
3
|
+
/** Package path, e.g. 'word/document.xml'. No leading slash. */
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly data: Uint8Array;
|
|
6
|
+
/** Content type for the [Content_Types].xml Override (XML parts). */
|
|
7
|
+
readonly contentType: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OpcPartRelationships {
|
|
10
|
+
/** The owning part, e.g. 'word/document.xml' (or '' for the package root). */
|
|
11
|
+
readonly sourcePart: string;
|
|
12
|
+
readonly relationships: ReadonlyArray<Relationship>;
|
|
13
|
+
}
|
|
14
|
+
export interface OpcWriteOptions {
|
|
15
|
+
readonly parts: ReadonlyArray<OpcPart>;
|
|
16
|
+
/** Package-level relationships (_rels/.rels). */
|
|
17
|
+
readonly rootRelationships: ReadonlyArray<Relationship>;
|
|
18
|
+
/** Per-part relationships (word/_rels/<part>.rels). */
|
|
19
|
+
readonly partRelationships?: ReadonlyArray<OpcPartRelationships>;
|
|
20
|
+
/**
|
|
21
|
+
* Default content types by lowercase extension, for parts not given an
|
|
22
|
+
* Override (typically binaries: png, jpeg…). `rels` and `xml` are always
|
|
23
|
+
* present and need not be repeated.
|
|
24
|
+
*/
|
|
25
|
+
readonly defaultsByExtension?: Readonly<Record<string, string>>;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildOpcPackage(options: OpcWriteOptions): Uint8Array;
|
|
28
|
+
export declare function relsPathFor(partPath: string): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { zipSync } from "fflate";
|
|
2
|
+
//#region src/core/opc/opc-writer.ts
|
|
3
|
+
var encoder = new TextEncoder();
|
|
4
|
+
var RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
5
|
+
var CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
6
|
+
var FIXED_MTIME = Date.UTC(1980, 0, 1, 0, 0, 0);
|
|
7
|
+
function buildOpcPackage(options) {
|
|
8
|
+
const entries = {};
|
|
9
|
+
const add = (path, data) => {
|
|
10
|
+
entries[path] = [data, { mtime: FIXED_MTIME }];
|
|
11
|
+
};
|
|
12
|
+
add("[Content_Types].xml", encoder.encode(contentTypesXml(options)));
|
|
13
|
+
add("_rels/.rels", encoder.encode(relationshipsXml(options.rootRelationships)));
|
|
14
|
+
for (const part of options.parts) add(part.path, part.data);
|
|
15
|
+
for (const pr of options.partRelationships ?? []) {
|
|
16
|
+
if (pr.relationships.length === 0) continue;
|
|
17
|
+
add(relsPathFor(pr.sourcePart), encoder.encode(relationshipsXml(pr.relationships)));
|
|
18
|
+
}
|
|
19
|
+
return zipSync(entries);
|
|
20
|
+
}
|
|
21
|
+
function relsPathFor(partPath) {
|
|
22
|
+
const slash = partPath.lastIndexOf("/");
|
|
23
|
+
const dir = slash >= 0 ? partPath.slice(0, slash) : "";
|
|
24
|
+
const name = slash >= 0 ? partPath.slice(slash + 1) : partPath;
|
|
25
|
+
return dir ? `${dir}/_rels/${name}.rels` : `_rels/${name}.rels`;
|
|
26
|
+
}
|
|
27
|
+
function contentTypesXml(options) {
|
|
28
|
+
const defaults = {
|
|
29
|
+
rels: "application/vnd.openxmlformats-package.relationships+xml",
|
|
30
|
+
xml: "application/xml",
|
|
31
|
+
...options.defaultsByExtension ?? {}
|
|
32
|
+
};
|
|
33
|
+
const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", `<Types xmlns="${CT_NS}">`];
|
|
34
|
+
for (const ext of Object.keys(defaults).sort()) lines.push(`<Default Extension="${ext}" ContentType="${escapeAttr(defaults[ext])}"/>`);
|
|
35
|
+
for (const part of options.parts) {
|
|
36
|
+
const ext = extensionOf(part.path);
|
|
37
|
+
if (ext !== void 0 && defaults[ext] === part.contentType) continue;
|
|
38
|
+
lines.push(`<Override PartName="/${escapeAttr(part.path)}" ContentType="${escapeAttr(part.contentType)}"/>`);
|
|
39
|
+
}
|
|
40
|
+
lines.push("</Types>");
|
|
41
|
+
return lines.join("");
|
|
42
|
+
}
|
|
43
|
+
function relationshipsXml(rels) {
|
|
44
|
+
const lines = ["<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", `<Relationships xmlns="${RELS_NS}">`];
|
|
45
|
+
for (const r of rels) {
|
|
46
|
+
const mode = r.targetMode === "External" ? " TargetMode=\"External\"" : "";
|
|
47
|
+
lines.push(`<Relationship Id="${escapeAttr(r.id)}" Type="${escapeAttr(r.type)}" Target="${escapeAttr(r.target)}"${mode}/>`);
|
|
48
|
+
}
|
|
49
|
+
lines.push("</Relationships>");
|
|
50
|
+
return lines.join("");
|
|
51
|
+
}
|
|
52
|
+
function extensionOf(path) {
|
|
53
|
+
const dot = path.lastIndexOf(".");
|
|
54
|
+
return dot >= 0 ? path.slice(dot + 1).toLowerCase() : void 0;
|
|
55
|
+
}
|
|
56
|
+
function escapeAttr(s) {
|
|
57
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { buildOpcPackage, relsPathFor };
|
|
@@ -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';
|