reamkit 1.2.0 → 1.4.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 +10 -6
- package/dist/esm/core/converter/facade.d.ts +2 -2
- package/dist/esm/core/converter/facade.js +12 -0
- package/dist/esm/core/converter/ream.d.ts +1 -1
- package/dist/esm/core/converter/ream.js +14 -3
- package/dist/esm/core/document-model/types.d.ts +2 -0
- package/dist/esm/core/drawingml/chart-geometry.d.ts +1 -1
- package/dist/esm/core/drawingml/chart-geometry.js +79 -117
- package/dist/esm/core/drawingml/chart-parser.d.ts +3 -0
- package/dist/esm/core/drawingml/chart-parser.js +28 -1
- package/dist/esm/core/numbering/apply.js +2 -1
- package/dist/esm/core/opc/index.d.ts +3 -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/package.js +3 -2
- package/dist/esm/core/opc/relationship-types.d.ts +1 -0
- package/dist/esm/core/opc/relationship-types.js +5 -2
- package/dist/esm/excel/print-model.d.ts +3 -0
- package/dist/esm/excel/print-model.js +4 -8
- package/dist/esm/excel/sheet-drawing.d.ts +9 -0
- package/dist/esm/excel/sheet-drawing.js +113 -0
- package/dist/esm/excel/worksheet-parser.d.ts +1 -0
- package/dist/esm/excel/worksheet-parser.js +4 -1
- package/dist/esm/excel/xlsx-reader.js +41 -0
- package/dist/esm/excel/xlsx-to-pdf.js +25 -18
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/layout/page-doc.d.ts +1 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +321 -171
- package/dist/esm/pdf/encryption.d.ts +34 -0
- package/dist/esm/pdf/encryption.js +143 -0
- package/dist/esm/pdf/index.d.ts +2 -1
- package/dist/esm/pdf/styled-page-emitter.d.ts +2 -0
- package/dist/esm/pdf/styled-page-emitter.js +25 -7
- package/dist/esm/pdf/styled-page-renderer.d.ts +1 -0
- package/dist/esm/pdf/styled-page-renderer.js +17 -2
- package/dist/esm/pdf/writer.d.ts +2 -0
- package/dist/esm/pdf/writer.js +6 -1
- package/dist/esm/word/document-parser.js +51 -13
- package/dist/esm/word/docx-reader.js +7 -11
- package/dist/esm/word/docx-to-pdf.js +23 -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 +4 -3
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ 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
|
|
37
38
|
|
|
38
39
|
// Hand the bytes to the browser: preview, download, upload, …
|
|
39
40
|
const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
|
|
@@ -150,10 +151,11 @@ automatically from the document's `docProps/core.xml`), `attachments`
|
|
|
150
151
|
|
|
151
152
|
### Lower-level APIs
|
|
152
153
|
|
|
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)
|
|
154
|
+
- `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter`, `docxWriter` — the `@experimental`
|
|
155
|
+
reader/writer interfaces of the interlayer, for building custom pipelines (and
|
|
156
|
+
keeping unused formats out of your bundle); `layoutStyledDocument` produces the
|
|
157
|
+
frozen page model (`PageItem` pages in a top-left `Pt` frame) the page-based
|
|
158
|
+
writers consume (`docxWriter` works from the flow model, before layout).
|
|
157
159
|
- `renderStyledPdf` drives the layout engine directly; the typed document
|
|
158
160
|
model is on the `reamkit/document-model` subpath.
|
|
159
161
|
|
|
@@ -168,8 +170,10 @@ repeated titles, page breaks), DrawingML shapes and charts, OMML math,
|
|
|
168
170
|
Type0+CIDFontType2 embedding with subsetting, Knuth-Plass line breaking,
|
|
169
171
|
Liang hyphenation, OpenType ligatures/kerning + Arabic cursive joining,
|
|
170
172
|
BiDi (UAX #9), hyperlinks (PDF link annotations + HTML anchors,
|
|
171
|
-
scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u),
|
|
172
|
-
(PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
|
|
173
|
+
scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), PDF/UA-1, AES-256
|
|
174
|
+
encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
|
|
175
|
+
preview, flowed HTML export, and **docx output** (write WordprocessingML back
|
|
176
|
+
out, incl. docx → docx). Reads OOXML Transitional and Strict.
|
|
173
177
|
|
|
174
178
|
See `handoff.md` for the full feature matrix and known limitations.
|
|
175
179
|
|
|
@@ -5,8 +5,8 @@ import { FontProvider } from '../fonts/provider.js';
|
|
|
5
5
|
import { Loss, LossReport } from '../ir/index.js';
|
|
6
6
|
import { FontBytesByVariant } from '../font/index.js';
|
|
7
7
|
export interface ConvertOptions extends ConvertDocxOptions {
|
|
8
|
-
/** Target: 'pdf' (default), 'svg' (page-stack preview) or '
|
|
9
|
-
readonly to?: 'pdf' | 'svg' | 'html';
|
|
8
|
+
/** Target: 'pdf' (default), 'svg' (page-stack preview), 'html' or 'docx' (flowed). */
|
|
9
|
+
readonly to?: 'pdf' | 'svg' | 'html' | 'docx';
|
|
10
10
|
/**
|
|
11
11
|
* Strict mode (handoff v1 §5): throw ConversionLossError on the first
|
|
12
12
|
* recorded loss instead of returning it in the report.
|
|
@@ -6,6 +6,7 @@ import { docxReader } from "../../word/docx-reader.js";
|
|
|
6
6
|
import { xlsxReader } from "../../excel/xlsx-reader.js";
|
|
7
7
|
import { chainProviders } from "../fonts/provider.js";
|
|
8
8
|
import { flowRenderOptions } from "./project.js";
|
|
9
|
+
import { writeDocx } from "../../word/docx-writer.js";
|
|
9
10
|
import { writeHtml } from "../../html/html-writer.js";
|
|
10
11
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
11
12
|
import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
|
|
@@ -20,6 +21,17 @@ function createConverter(opts = {}) {
|
|
|
20
21
|
const reader = detect(bytes);
|
|
21
22
|
if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
|
|
22
23
|
const losses = [];
|
|
24
|
+
if (to === "docx") {
|
|
25
|
+
const { doc: flow, losses: readLosses } = reader.read(bytes);
|
|
26
|
+
losses.push(...readLosses);
|
|
27
|
+
const out = writeDocx(flow);
|
|
28
|
+
losses.push(...out.losses);
|
|
29
|
+
if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
|
|
30
|
+
return {
|
|
31
|
+
bytes: out.bytes,
|
|
32
|
+
losses
|
|
33
|
+
};
|
|
34
|
+
}
|
|
23
35
|
if (to === "html") {
|
|
24
36
|
const { doc: flow, losses: readLosses } = reader.read(bytes);
|
|
25
37
|
losses.push(...readLosses);
|
|
@@ -6,7 +6,7 @@ import { Loss } from '../ir/index.js';
|
|
|
6
6
|
import { DocumentReader } from '../ir/adapters.js';
|
|
7
7
|
import { FlowDoc } from '../ir/flow.js';
|
|
8
8
|
import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
|
|
9
|
-
export type ReamTarget = 'pdf' | 'svg' | 'html';
|
|
9
|
+
export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx';
|
|
10
10
|
export interface ReamParseOptions {
|
|
11
11
|
readonly readers?: ReadonlyArray<DocumentReader>;
|
|
12
12
|
}
|
|
@@ -3,9 +3,10 @@ import { FontRegistry } from "../font/font-registry.js";
|
|
|
3
3
|
import { ConversionLossError } from "../ir/loss.js";
|
|
4
4
|
import { layoutStyledDocument } from "../../layout/styled-layout.js";
|
|
5
5
|
import { signPdf } from "../../pdf/signature.js";
|
|
6
|
-
import { renderStyledPdf } from "../../pdf/styled-page-renderer.js";
|
|
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";
|
|
9
10
|
import { writeHtml } from "../../html/html-writer.js";
|
|
10
11
|
import { writeSvg } from "../../svg/svg-writer.js";
|
|
11
12
|
import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
|
|
@@ -46,6 +47,15 @@ var Ream = class Ream {
|
|
|
46
47
|
losses
|
|
47
48
|
};
|
|
48
49
|
}
|
|
50
|
+
if (to === "docx") {
|
|
51
|
+
const docx = writeDocx(this.flow);
|
|
52
|
+
losses.push(...docx.losses);
|
|
53
|
+
this.enforceStrict(options, losses);
|
|
54
|
+
return {
|
|
55
|
+
bytes: docx.bytes,
|
|
56
|
+
losses
|
|
57
|
+
};
|
|
58
|
+
}
|
|
49
59
|
const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
|
|
50
60
|
const registry = FontRegistry.fromBytes(fonts);
|
|
51
61
|
if (to === "svg") {
|
|
@@ -73,7 +83,7 @@ var Ream = class Ream {
|
|
|
73
83
|
relationship: "Source",
|
|
74
84
|
description: "Source document"
|
|
75
85
|
});
|
|
76
|
-
|
|
86
|
+
const styled = {
|
|
77
87
|
registry,
|
|
78
88
|
...registriesByFamily ? { registriesByFamily } : {},
|
|
79
89
|
...flowRenderOptions(this.flow),
|
|
@@ -81,7 +91,8 @@ var Ream = class Ream {
|
|
|
81
91
|
...attachments.length > 0 ? { attachments } : {},
|
|
82
92
|
...signature ? { signaturePlaceholder: signature } : {},
|
|
83
93
|
...renderOptions
|
|
84
|
-
}
|
|
94
|
+
};
|
|
95
|
+
let pdf = styled.encrypt ? await renderStyledPdfEncrypted(this.flow.body, styled) : renderStyledPdf(this.flow.body, styled);
|
|
85
96
|
if (signature) pdf = await signPdf(pdf, signature);
|
|
86
97
|
this.enforceStrict(options, losses);
|
|
87
98
|
return {
|
|
@@ -132,6 +132,7 @@ export interface Run {
|
|
|
132
132
|
readonly footnoteRef?: string;
|
|
133
133
|
readonly endnoteRef?: string;
|
|
134
134
|
readonly noteNumber?: true;
|
|
135
|
+
readonly listMarker?: true;
|
|
135
136
|
readonly inlineImage?: InlineImage;
|
|
136
137
|
readonly math?: MathNode;
|
|
137
138
|
readonly pageBreak?: boolean;
|
|
@@ -382,6 +383,7 @@ export interface Chart {
|
|
|
382
383
|
readonly showValues?: boolean;
|
|
383
384
|
readonly catAxisTitle?: string;
|
|
384
385
|
readonly valAxisTitle?: string;
|
|
386
|
+
readonly seriesColorCycle?: ReadonlyArray<string>;
|
|
385
387
|
}
|
|
386
388
|
export interface ChartBlock {
|
|
387
389
|
readonly float?: FloatAnchor;
|
|
@@ -48,7 +48,7 @@ export type MeasureText = (text: string, sizePt: number) => number;
|
|
|
48
48
|
export declare const CHART_LABEL_PT = 9;
|
|
49
49
|
export declare const CHART_TITLE_PT = 13;
|
|
50
50
|
export declare const SERIES_COLORS: string[];
|
|
51
|
-
export declare const seriesColor: (s: ChartSeries, i: number) => string;
|
|
51
|
+
export declare const seriesColor: (s: ChartSeries, i: number, cycle?: ReadonlyArray<string>) => string;
|
|
52
52
|
export interface Scale {
|
|
53
53
|
readonly min: number;
|
|
54
54
|
readonly max: number;
|
|
@@ -10,7 +10,7 @@ var SERIES_COLORS = [
|
|
|
10
10
|
"5B9BD5",
|
|
11
11
|
"70AD47"
|
|
12
12
|
];
|
|
13
|
-
var seriesColor = (s, i) => s.colorHex ?? SERIES_COLORS[i % SERIES_COLORS.length];
|
|
13
|
+
var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cycle[i % cycle.length] : SERIES_COLORS[i % SERIES_COLORS.length]);
|
|
14
14
|
function niceNum(range, round) {
|
|
15
15
|
const exp = Math.floor(Math.log10(range));
|
|
16
16
|
const f = range / 10 ** exp;
|
|
@@ -40,6 +40,68 @@ var ticks = (s) => {
|
|
|
40
40
|
for (let v = s.min; v <= s.max + 1e-9; v += s.step) out.push(Math.abs(v) < 1e-9 ? 0 : v);
|
|
41
41
|
return out;
|
|
42
42
|
};
|
|
43
|
+
function pushChartTitle(labels, chart, wPt, hPt) {
|
|
44
|
+
if (!chart.title) return;
|
|
45
|
+
labels.push({
|
|
46
|
+
text: chart.title,
|
|
47
|
+
x: wPt / 2,
|
|
48
|
+
y: hPt - 4 - 13,
|
|
49
|
+
sizePt: 13,
|
|
50
|
+
colorHex: TITLE_COLOR,
|
|
51
|
+
align: "center"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
55
|
+
return layoutLegend(chart.series.map((s, i) => ({
|
|
56
|
+
name: s.name ?? "",
|
|
57
|
+
colorHex: seriesColor(s, i, chart.seriesColorCycle)
|
|
58
|
+
})).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
|
|
59
|
+
}
|
|
60
|
+
function pushGridTicks(polylines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH) {
|
|
61
|
+
for (const v of tickVals) if (axis === "x") {
|
|
62
|
+
const gx = at(v);
|
|
63
|
+
polylines.push({
|
|
64
|
+
points: [[gx, y0], [gx, y0 + plotH]],
|
|
65
|
+
strokeHex: GRID_COLOR,
|
|
66
|
+
widthPt: .75
|
|
67
|
+
});
|
|
68
|
+
labels.push({
|
|
69
|
+
text: fmt(v),
|
|
70
|
+
x: gx,
|
|
71
|
+
y: y0 - 9,
|
|
72
|
+
sizePt: 9,
|
|
73
|
+
colorHex: LABEL_COLOR,
|
|
74
|
+
align: "center"
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
const gy = at(v);
|
|
78
|
+
polylines.push({
|
|
79
|
+
points: [[x0, gy], [x0 + plotW, gy]],
|
|
80
|
+
strokeHex: GRID_COLOR,
|
|
81
|
+
widthPt: .75
|
|
82
|
+
});
|
|
83
|
+
labels.push({
|
|
84
|
+
text: fmt(v),
|
|
85
|
+
x: x0 - 3,
|
|
86
|
+
y: gy - 9 / 3,
|
|
87
|
+
sizePt: 9,
|
|
88
|
+
colorHex: LABEL_COLOR,
|
|
89
|
+
align: "right"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function pushAxisLines(polylines, x0, y0, plotW, plotH) {
|
|
94
|
+
polylines.push({
|
|
95
|
+
points: [[x0, y0], [x0, y0 + plotH]],
|
|
96
|
+
strokeHex: AXIS_COLOR,
|
|
97
|
+
widthPt: 1
|
|
98
|
+
});
|
|
99
|
+
polylines.push({
|
|
100
|
+
points: [[x0, y0], [x0 + plotW, y0]],
|
|
101
|
+
strokeHex: AXIS_COLOR,
|
|
102
|
+
widthPt: 1
|
|
103
|
+
});
|
|
104
|
+
}
|
|
43
105
|
function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
44
106
|
const rects = [];
|
|
45
107
|
const polylines = [];
|
|
@@ -53,10 +115,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
53
115
|
let top = 4;
|
|
54
116
|
if (chart.title) top += 13 * 1.6;
|
|
55
117
|
if (chart.valAxisTitle) top += 9 * 1.4;
|
|
56
|
-
const legend =
|
|
57
|
-
name: s.name ?? "",
|
|
58
|
-
colorHex: seriesColor(s, i)
|
|
59
|
-
})).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
|
|
118
|
+
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
60
119
|
const plotRight = wPt - 4 - legend.rightWidth;
|
|
61
120
|
const tickLabelW = Math.max(0, ...tickVals.map((v) => measure(fmtVal(v), 9))) + 4;
|
|
62
121
|
const catLabelH = 9 * 1.6;
|
|
@@ -67,14 +126,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
67
126
|
const plotH = Math.max(1, hPt - top - y0);
|
|
68
127
|
const valueOffset = (v) => (v - scale.min) / (scale.max - scale.min) * (horizontal ? plotW : plotH);
|
|
69
128
|
const zeroOffset = valueOffset(0);
|
|
70
|
-
|
|
71
|
-
text: chart.title,
|
|
72
|
-
x: wPt / 2,
|
|
73
|
-
y: hPt - 4 - 13,
|
|
74
|
-
sizePt: 13,
|
|
75
|
-
colorHex: TITLE_COLOR,
|
|
76
|
-
align: "center"
|
|
77
|
-
});
|
|
129
|
+
pushChartTitle(labels, chart, wPt, hPt);
|
|
78
130
|
if (chart.valAxisTitle) labels.push({
|
|
79
131
|
text: chart.valAxisTitle,
|
|
80
132
|
x: x0,
|
|
@@ -91,40 +143,8 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
91
143
|
colorHex: LABEL_COLOR,
|
|
92
144
|
align: "center"
|
|
93
145
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (horizontal) {
|
|
97
|
-
const gx = x0 + off;
|
|
98
|
-
polylines.push({
|
|
99
|
-
points: [[gx, y0], [gx, y0 + plotH]],
|
|
100
|
-
strokeHex: GRID_COLOR,
|
|
101
|
-
widthPt: .75
|
|
102
|
-
});
|
|
103
|
-
labels.push({
|
|
104
|
-
text: fmtVal(v),
|
|
105
|
-
x: gx,
|
|
106
|
-
y: y0 - 9,
|
|
107
|
-
sizePt: 9,
|
|
108
|
-
colorHex: LABEL_COLOR,
|
|
109
|
-
align: "center"
|
|
110
|
-
});
|
|
111
|
-
} else {
|
|
112
|
-
const gy = y0 + off;
|
|
113
|
-
polylines.push({
|
|
114
|
-
points: [[x0, gy], [x0 + plotW, gy]],
|
|
115
|
-
strokeHex: GRID_COLOR,
|
|
116
|
-
widthPt: .75
|
|
117
|
-
});
|
|
118
|
-
labels.push({
|
|
119
|
-
text: fmtVal(v),
|
|
120
|
-
x: x0 - 3,
|
|
121
|
-
y: gy - 9 / 3,
|
|
122
|
-
sizePt: 9,
|
|
123
|
-
colorHex: LABEL_COLOR,
|
|
124
|
-
align: "right"
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
146
|
+
if (horizontal) pushGridTicks(polylines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH);
|
|
147
|
+
else pushGridTicks(polylines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH);
|
|
128
148
|
const slot = (horizontal ? plotH : plotW) / nCats;
|
|
129
149
|
for (let c = 0; c < nCats; c++) {
|
|
130
150
|
const cat = chart.categories[c] ?? "";
|
|
@@ -147,16 +167,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
147
167
|
align: "center"
|
|
148
168
|
});
|
|
149
169
|
}
|
|
150
|
-
polylines
|
|
151
|
-
points: [[x0, y0], [x0, y0 + plotH]],
|
|
152
|
-
strokeHex: AXIS_COLOR,
|
|
153
|
-
widthPt: 1
|
|
154
|
-
});
|
|
155
|
-
polylines.push({
|
|
156
|
-
points: [[x0, y0], [x0 + plotW, y0]],
|
|
157
|
-
strokeHex: AXIS_COLOR,
|
|
158
|
-
widthPt: 1
|
|
159
|
-
});
|
|
170
|
+
pushAxisLines(polylines, x0, y0, plotW, plotH);
|
|
160
171
|
legend.emit(rects, labels);
|
|
161
172
|
return {
|
|
162
173
|
x0,
|
|
@@ -232,7 +243,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
232
243
|
const o1 = f.valueOffset(top);
|
|
233
244
|
const lo = Math.min(o0, o1);
|
|
234
245
|
const span = Math.abs(o1 - o0);
|
|
235
|
-
const color = pointColor(series, c) ?? seriesColor(series, s);
|
|
246
|
+
const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
|
|
236
247
|
if (horizontal) f.rects.push({
|
|
237
248
|
x: f.x0 + lo,
|
|
238
249
|
y: along,
|
|
@@ -271,7 +282,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
271
282
|
for (let s = 0; s < chart.series.length; s++) {
|
|
272
283
|
const series = chart.series[s];
|
|
273
284
|
const len = f.valueOffset(series.values[c] ?? 0) - f.zeroOffset;
|
|
274
|
-
const color = pointColor(series, c) ?? seriesColor(series, s);
|
|
285
|
+
const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
|
|
275
286
|
const along = slotStart + s * barW;
|
|
276
287
|
if (horizontal) {
|
|
277
288
|
const bx = f.x0 + f.zeroOffset + Math.min(0, len);
|
|
@@ -356,7 +367,7 @@ function buildAreaScene(chart, wPt, hPt, measure) {
|
|
|
356
367
|
const denom = percent ? chart.series.reduce((a, ss) => a + Math.abs(ss.values[c] ?? 0), 0) || 1 : 1;
|
|
357
368
|
return b + (series.values[c] ?? 0) / denom;
|
|
358
369
|
});
|
|
359
|
-
polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s)));
|
|
370
|
+
polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s, chart.seriesColorCycle)));
|
|
360
371
|
for (let c = 0; c < nCats; c++) cum[c] = top[c];
|
|
361
372
|
}
|
|
362
373
|
} else {
|
|
@@ -364,7 +375,7 @@ function buildAreaScene(chart, wPt, hPt, measure) {
|
|
|
364
375
|
for (let s = chart.series.length - 1; s >= 0; s--) {
|
|
365
376
|
const series = chart.series[s];
|
|
366
377
|
const top = Array.from({ length: nCats }, (_, c) => series.values[c] ?? 0);
|
|
367
|
-
polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s)));
|
|
378
|
+
polygons.push(areaBand(top, base, f, xAt, seriesColor(series, s, chart.seriesColorCycle)));
|
|
368
379
|
}
|
|
369
380
|
}
|
|
370
381
|
return {
|
|
@@ -397,69 +408,20 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
397
408
|
const yTicks = ticks(yScale);
|
|
398
409
|
let top = 4;
|
|
399
410
|
if (chart.title) top += 13 * 1.6;
|
|
400
|
-
const legend =
|
|
401
|
-
name: s.name ?? "",
|
|
402
|
-
colorHex: seriesColor(s, i)
|
|
403
|
-
})).filter((e) => e.name !== ""), chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure);
|
|
411
|
+
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
404
412
|
const x0 = 4 + (Math.max(0, ...yTicks.map((v) => measure(formatTick(v, yScale.step), 9))) + 4);
|
|
405
413
|
const y0 = 4 + legend.bottomHeight + 9 * 1.6;
|
|
406
414
|
const plotW = Math.max(1, wPt - 4 - legend.rightWidth - x0);
|
|
407
415
|
const plotH = Math.max(1, hPt - top - y0);
|
|
408
416
|
const xAt = (v) => x0 + (v - xScale.min) / (xScale.max - xScale.min) * plotW;
|
|
409
417
|
const yAt = (v) => y0 + (v - yScale.min) / (yScale.max - yScale.min) * plotH;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
sizePt: 13,
|
|
415
|
-
colorHex: TITLE_COLOR,
|
|
416
|
-
align: "center"
|
|
417
|
-
});
|
|
418
|
-
for (const v of yTicks) {
|
|
419
|
-
const gy = yAt(v);
|
|
420
|
-
polylines.push({
|
|
421
|
-
points: [[x0, gy], [x0 + plotW, gy]],
|
|
422
|
-
strokeHex: GRID_COLOR,
|
|
423
|
-
widthPt: .75
|
|
424
|
-
});
|
|
425
|
-
labels.push({
|
|
426
|
-
text: formatTick(v, yScale.step),
|
|
427
|
-
x: x0 - 3,
|
|
428
|
-
y: gy - 9 / 3,
|
|
429
|
-
sizePt: 9,
|
|
430
|
-
colorHex: LABEL_COLOR,
|
|
431
|
-
align: "right"
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
for (const v of xTicks) {
|
|
435
|
-
const gx = xAt(v);
|
|
436
|
-
polylines.push({
|
|
437
|
-
points: [[gx, y0], [gx, y0 + plotH]],
|
|
438
|
-
strokeHex: GRID_COLOR,
|
|
439
|
-
widthPt: .75
|
|
440
|
-
});
|
|
441
|
-
labels.push({
|
|
442
|
-
text: formatTick(v, xScale.step),
|
|
443
|
-
x: gx,
|
|
444
|
-
y: y0 - 9,
|
|
445
|
-
sizePt: 9,
|
|
446
|
-
colorHex: LABEL_COLOR,
|
|
447
|
-
align: "center"
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
polylines.push({
|
|
451
|
-
points: [[x0, y0], [x0, y0 + plotH]],
|
|
452
|
-
strokeHex: AXIS_COLOR,
|
|
453
|
-
widthPt: 1
|
|
454
|
-
});
|
|
455
|
-
polylines.push({
|
|
456
|
-
points: [[x0, y0], [x0 + plotW, y0]],
|
|
457
|
-
strokeHex: AXIS_COLOR,
|
|
458
|
-
widthPt: 1
|
|
459
|
-
});
|
|
418
|
+
pushChartTitle(labels, chart, wPt, hPt);
|
|
419
|
+
pushGridTicks(polylines, labels, yTicks, (v) => formatTick(v, yScale.step), "y", yAt, x0, y0, plotW, plotH);
|
|
420
|
+
pushGridTicks(polylines, labels, xTicks, (v) => formatTick(v, xScale.step), "x", xAt, x0, y0, plotW, plotH);
|
|
421
|
+
pushAxisLines(polylines, x0, y0, plotW, plotH);
|
|
460
422
|
for (let s = 0; s < chart.series.length; s++) {
|
|
461
423
|
const series = chart.series[s];
|
|
462
|
-
const color = seriesColor(series, s);
|
|
424
|
+
const color = seriesColor(series, s, chart.seriesColorCycle);
|
|
463
425
|
for (let i = 0; i < series.values.length; i++) {
|
|
464
426
|
const px = xAt(series.xValues?.[i] ?? i);
|
|
465
427
|
const py = yAt(series.values[i] ?? 0);
|
|
@@ -485,7 +447,7 @@ function buildLineScene(chart, wPt, hPt, measure) {
|
|
|
485
447
|
const f = buildFrame(chart, wPt, hPt, measure, false, { dataRange: allVals.length > 0 ? [Math.min(...allVals), Math.max(...allVals)] : [0, 1] });
|
|
486
448
|
for (let s = 0; s < chart.series.length; s++) {
|
|
487
449
|
const series = chart.series[s];
|
|
488
|
-
const color = seriesColor(series, s);
|
|
450
|
+
const color = seriesColor(series, s, chart.seriesColorCycle);
|
|
489
451
|
const pts = [];
|
|
490
452
|
for (let c = 0; c < f.nCats; c++) {
|
|
491
453
|
const x = f.x0 + c * f.slot + f.slot / 2;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { Chart } from '../document-model/index.js';
|
|
2
|
+
import { OpcPackage } from '../opc/index.js';
|
|
2
3
|
import { ColorResolver } from './colors.js';
|
|
3
4
|
export declare function parseChart(chartXml: Uint8Array, resolveColor: ColorResolver): Chart | null;
|
|
5
|
+
export declare function parseChartColorStyle(colorsXml: Uint8Array, resolveColor: ColorResolver): Array<string>;
|
|
6
|
+
export declare function withChartColorStyle(chart: Chart, pkg: OpcPackage, chartPartPath: string, resolveColor: ColorResolver): Chart;
|
|
@@ -191,5 +191,32 @@ function isLegendPos(v) {
|
|
|
191
191
|
function isGrouping(v) {
|
|
192
192
|
return v === "clustered" || v === "stacked" || v === "percentStacked" || v === "standard";
|
|
193
193
|
}
|
|
194
|
+
function parseChartColorStyle(colorsXml, resolveColor) {
|
|
195
|
+
const root = parser.parse(new TextDecoder().decode(colorsXml)).find((n) => {
|
|
196
|
+
const tag = Object.keys(n).find((k) => k !== ":@" && k !== "#text");
|
|
197
|
+
return tag !== void 0 && tag.endsWith("colorStyle");
|
|
198
|
+
});
|
|
199
|
+
if (!root) return [];
|
|
200
|
+
const out = [];
|
|
201
|
+
for (const child of poChildren(root)) {
|
|
202
|
+
const hex = resolveColorNode(child, resolveColor);
|
|
203
|
+
if (hex !== void 0) out.push(hex);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
function withChartColorStyle(chart, pkg, chartPartPath, resolveColor) {
|
|
208
|
+
for (const rel of pkg.getPartRelationships(chartPartPath)) {
|
|
209
|
+
if (rel.type !== REL_CHART_COLOR_STYLE) continue;
|
|
210
|
+
const resolved = pkg.resolveRelatedPart(chartPartPath, rel);
|
|
211
|
+
if (!resolved) continue;
|
|
212
|
+
const cycle = parseChartColorStyle(resolved.data, resolveColor);
|
|
213
|
+
if (cycle.length > 0) return {
|
|
214
|
+
...chart,
|
|
215
|
+
seriesColorCycle: cycle
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return chart;
|
|
219
|
+
}
|
|
220
|
+
var REL_CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
|
|
194
221
|
//#endregion
|
|
195
|
-
export { parseChart };
|
|
222
|
+
export { parseChart, withChartColorStyle };
|
|
@@ -12,7 +12,8 @@ function applyNumbering(body, numbering) {
|
|
|
12
12
|
const level = (instance ? numbering.abstractNums.get(instance.abstractNumId) : void 0)?.levels.get(ref.ilvl);
|
|
13
13
|
const markerRun = {
|
|
14
14
|
text: `${marker}\t`,
|
|
15
|
-
properties: level?.runProperties ?? {}
|
|
15
|
+
properties: level?.runProperties ?? {},
|
|
16
|
+
listMarker: true
|
|
16
17
|
};
|
|
17
18
|
const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
|
|
18
19
|
return {
|
|
@@ -3,3 +3,6 @@ export type { Relationship } from './relationships.js';
|
|
|
3
3
|
export { REL_OFFICE_DOCUMENT, REL_CORE_PROPERTIES, REL_EXTENDED_PROPERTIES, } from './relationship-types.js';
|
|
4
4
|
export { parseCoreProperties } from './core-properties.js';
|
|
5
5
|
export type { CoreProperties } from './core-properties.js';
|
|
6
|
+
export { isOoxmlRel } from './relationship-types.js';
|
|
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 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseRelationships } from "./relationships.js";
|
|
2
|
-
import "./relationship-types.js";
|
|
2
|
+
import { isOoxmlRel } from "./relationship-types.js";
|
|
3
3
|
import { unzipSync } from "fflate";
|
|
4
4
|
//#region src/core/opc/package.ts
|
|
5
5
|
var ROOT_RELS_PATH = "_rels/.rels";
|
|
@@ -11,6 +11,7 @@ var OpcPackage = class OpcPackage {
|
|
|
11
11
|
}
|
|
12
12
|
relsCache = /* @__PURE__ */ new Map();
|
|
13
13
|
static open(buffer, options = {}) {
|
|
14
|
+
if (buffer.length >= 4 && buffer[0] === 208 && buffer[1] === 207 && buffer[2] === 17 && buffer[3] === 224) throw new Error("OLE compound file: a password-protected (encrypted) OOXML document or a legacy binary .doc/.xls — re-save as an unencrypted .docx/.xlsx");
|
|
14
15
|
const maxArchive = options.maxArchiveBytes ?? 128 * MIB;
|
|
15
16
|
const maxEntry = options.maxEntryBytes ?? 256 * MIB;
|
|
16
17
|
const maxTotal = options.maxTotalBytes ?? 512 * MIB;
|
|
@@ -77,7 +78,7 @@ var OpcPackage = class OpcPackage {
|
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
getMainDocumentPath() {
|
|
80
|
-
const candidates = this.rootRelationships.filter((r) => r.type
|
|
81
|
+
const candidates = this.rootRelationships.filter((r) => isOoxmlRel(r.type, "officeDocument") && r.targetMode === "Internal");
|
|
81
82
|
if (candidates.length === 0) throw new Error("OPC package has no officeDocument relationship");
|
|
82
83
|
if (candidates.length > 1) throw new Error(`OPC package has multiple officeDocument relationships (${candidates.length})`);
|
|
83
84
|
return resolveTarget("/", candidates[0].target);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
2
2
|
export declare const REL_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
|
|
3
3
|
export declare const REL_EXTENDED_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
|
|
4
|
+
export declare function isOoxmlRel(type: string, name: string): boolean;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
//#region src/core/opc/relationship-types.ts
|
|
2
|
-
var
|
|
2
|
+
var OOXML_REL_BASES = ["http://schemas.openxmlformats.org/officeDocument/2006/relationships/", "http://purl.oclc.org/ooxml/officeDocument/relationships/"];
|
|
3
|
+
function isOoxmlRel(type, name) {
|
|
4
|
+
return OOXML_REL_BASES.some((base) => type === base + name);
|
|
5
|
+
}
|
|
3
6
|
//#endregion
|
|
4
|
-
export {
|
|
7
|
+
export { isOoxmlRel };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BodyElement, SectionProperties } from '../core/document-model/index.js';
|
|
2
2
|
import { CellRange, DefinedName, ParsedWorksheet, XlsxStyles } from './index.js';
|
|
3
|
+
export declare const TWIPS_PER_EXCEL_CHAR = 105;
|
|
4
|
+
export declare const DEFAULT_COL_TWIPS = 960;
|
|
5
|
+
export declare const DEFAULT_ROW_TWIPS = 300;
|
|
3
6
|
export declare function sectionFromWorksheet(worksheet: ParsedWorksheet): SectionProperties | undefined;
|
|
4
7
|
export declare function resolvePrintArea(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): CellRange | undefined;
|
|
5
8
|
export declare function resolvePrintTitleRows(definedNames: ReadonlyArray<DefinedName>, sheetIdx: number): {
|