reamkit 1.6.0 → 1.9.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 +13 -1
- package/dist/esm/core/converter/ream.d.ts +1 -0
- package/dist/esm/core/converter/ream.js +1 -1
- package/dist/esm/core/document-model/types.d.ts +3 -1
- package/dist/esm/core/drawingml/chart-serializer.d.ts +2 -0
- package/dist/esm/core/drawingml/chart-serializer.js +53 -0
- package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
- package/dist/esm/core/drawingml/shape-render.js +27 -1
- package/dist/esm/core/vector.d.ts +10 -0
- package/dist/esm/excel/print-model.js +6 -3
- package/dist/esm/excel/xlsx-writer.js +81 -17
- package/dist/esm/html/html-writer.js +4 -2
- package/dist/esm/layout/styled-layout.js +5 -2
- package/dist/esm/pdf/shading.d.ts +12 -0
- package/dist/esm/pdf/shading.js +152 -0
- package/dist/esm/pdf/styled-page-emitter.js +29 -4
- package/dist/esm/pdf/vector-graphics.d.ts +1 -1
- package/dist/esm/pdf/vector-graphics.js +5 -3
- package/dist/esm/pdf-reader/ccitt.d.ts +15 -0
- package/dist/esm/pdf-reader/ccitt.js +394 -0
- package/dist/esm/pdf-reader/content.d.ts +43 -1
- package/dist/esm/pdf-reader/content.js +173 -4
- package/dist/esm/pdf-reader/crypto.d.ts +7 -0
- package/dist/esm/pdf-reader/crypto.js +609 -0
- package/dist/esm/pdf-reader/decrypt.d.ts +5 -0
- package/dist/esm/pdf-reader/decrypt.js +211 -0
- package/dist/esm/pdf-reader/document.d.ts +8 -1
- package/dist/esm/pdf-reader/document.js +210 -27
- package/dist/esm/pdf-reader/flow-build.d.ts +16 -1
- package/dist/esm/pdf-reader/flow-build.js +113 -3
- package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
- package/dist/esm/pdf-reader/image-decode.js +550 -0
- package/dist/esm/pdf-reader/images.d.ts +16 -0
- package/dist/esm/pdf-reader/images.js +90 -0
- package/dist/esm/pdf-reader/layout.d.ts +2 -2
- package/dist/esm/pdf-reader/layout.js +86 -14
- package/dist/esm/pdf-reader/png-encode.d.ts +2 -0
- package/dist/esm/pdf-reader/png-encode.js +98 -0
- package/dist/esm/pdf-reader/predictor.d.ts +7 -0
- package/dist/esm/pdf-reader/predictor.js +61 -0
- package/dist/esm/pdf-reader/reader.d.ts +1 -1
- package/dist/esm/pdf-reader/reader.js +14 -7
- package/dist/esm/pdf-reader/shading.d.ts +3 -0
- package/dist/esm/pdf-reader/shading.js +147 -0
- package/dist/esm/pdf-reader/tagged.d.ts +2 -2
- package/dist/esm/pdf-reader/tagged.js +60 -7
- package/dist/esm/pdf-reader/text.js +91 -10
- package/dist/esm/pdf-reader/vector.d.ts +16 -0
- package/dist/esm/pdf-reader/vector.js +63 -0
- package/dist/esm/svg/svg-writer.js +12 -5
- package/dist/esm/word/docx-writer.js +104 -8
- package/dist/esm/word/drawing-parser.js +28 -17
- package/dist/esm/word/omml-serializer.d.ts +2 -0
- package/dist/esm/word/omml-serializer.js +48 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,7 +177,19 @@ encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
|
|
|
177
177
|
preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
|
|
178
178
|
/ SpreadsheetML back out, incl. round-trips). Reads OOXML Transitional and Strict.
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
**Reads PDF, too.** `Ream.parse` accepts a PDF and reconstructs a `FlowDoc` — a
|
|
181
|
+
tagged PDF from its structure tree (headings, tables, lists, reading order), an
|
|
182
|
+
untagged one heuristically from glyph positions (lines, paragraphs, headings,
|
|
183
|
+
and a clean two-column split). It lifts back the text (via each font's
|
|
184
|
+
`/ToUnicode`), raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax decoded and
|
|
185
|
+
re-encoded), `/Link` hyperlinks, form-XObject content, and filled / stroked /
|
|
186
|
+
gradient vector shapes. It reads modern compressed files (cross-reference + object
|
|
187
|
+
streams) and encrypted ones (RC4 / AES — the user password is passed to
|
|
188
|
+
`Ream.parse(bytes, { password })`, defaulting to the permissions-only case).
|
|
189
|
+
|
|
190
|
+
See [`CHANGELOG.md`](./CHANGELOG.md) for the release history; the docs
|
|
191
|
+
[**Scope**](https://reamkit.dev/guides/scope/) guide has the full feature matrix
|
|
192
|
+
and known limitations.
|
|
181
193
|
|
|
182
194
|
## License
|
|
183
195
|
|
|
@@ -10,6 +10,7 @@ import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
|
|
|
10
10
|
export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
|
|
11
11
|
export interface ReamParseOptions {
|
|
12
12
|
readonly readers?: ReadonlyArray<DocumentReader<SourceDoc>>;
|
|
13
|
+
readonly password?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry' | 'styles'> {
|
|
15
16
|
readonly fonts?: FontBytesByVariant;
|
|
@@ -30,7 +30,7 @@ var Ream = class Ream {
|
|
|
30
30
|
const readers = options.readers ?? DEFAULT_READERS;
|
|
31
31
|
const reader = readers.find((r) => r.sniff(bytes));
|
|
32
32
|
if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
|
|
33
|
-
const { doc, losses } = reader.read(bytes);
|
|
33
|
+
const { doc, losses } = reader.read(bytes, { password: options.password });
|
|
34
34
|
const sheet = doc.kind === "sheet" ? doc : void 0;
|
|
35
35
|
return new Ream(toFlowDoc(doc), sheet, losses, bytes, reader.id);
|
|
36
36
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NativeBag, Pt, ResourceId } from '../ir/index.js';
|
|
2
|
+
import { ShapeGradient } from '../vector.js';
|
|
2
3
|
export type Alignment = 'left' | 'right' | 'center' | 'both' | 'distribute';
|
|
3
4
|
export type UnderlineStyle = 'none' | 'single' | 'double' | 'thick' | 'dotted' | 'dottedHeavy' | 'dash' | 'dashHeavy' | 'wave';
|
|
4
5
|
export type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
|
|
@@ -346,10 +347,11 @@ export interface ShapeGeometry {
|
|
|
346
347
|
readonly adjust?: ReadonlyMap<string, number>;
|
|
347
348
|
readonly custom?: CustomGeometry;
|
|
348
349
|
}
|
|
349
|
-
export type ShapeFillKind = 'none' | 'solid';
|
|
350
|
+
export type ShapeFillKind = 'none' | 'solid' | 'gradient';
|
|
350
351
|
export interface ShapeFill {
|
|
351
352
|
readonly kind: ShapeFillKind;
|
|
352
353
|
readonly colorHex?: string;
|
|
354
|
+
readonly gradient?: ShapeGradient;
|
|
353
355
|
}
|
|
354
356
|
export type ShapeDash = 'solid' | 'dot' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'sysDash' | 'sysDot';
|
|
355
357
|
export interface ShapeLine {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/core/drawingml/chart-serializer.ts
|
|
2
|
+
var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
3
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4
|
+
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
5
|
+
var GROUP_TAG = {
|
|
6
|
+
bar: "c:barChart",
|
|
7
|
+
line: "c:lineChart",
|
|
8
|
+
pie: "c:pieChart",
|
|
9
|
+
area: "c:areaChart",
|
|
10
|
+
scatter: "c:scatterChart",
|
|
11
|
+
unknown: "c:barChart"
|
|
12
|
+
};
|
|
13
|
+
var CAT_AX_ID = 111111111;
|
|
14
|
+
var VAL_AX_ID = 222222222;
|
|
15
|
+
function chartSpaceXml(chart) {
|
|
16
|
+
const isScatter = chart.type === "scatter";
|
|
17
|
+
const isPie = chart.type === "pie" || chart.doughnut === true;
|
|
18
|
+
const groupTag = chart.doughnut ? "c:doughnutChart" : GROUP_TAG[chart.type] ?? "c:barChart";
|
|
19
|
+
const inner = (chart.type === "bar" ? `<c:barDir val="${chart.barDir ?? "col"}"/>` : "") + (chart.grouping ? `<c:grouping val="${chart.grouping}"/>` : "") + "<c:varyColors val=\"0\"/>" + chart.series.map((s, i) => seriesXml(s, i, isScatter, chart.categories)).join("") + (chart.showValues ? "<c:dLbls><c:showVal val=\"1\"/></c:dLbls>" : "") + (chart.doughnut ? "<c:holeSize val=\"50\"/>" : "") + (isPie ? "" : `<c:axId val="${CAT_AX_ID}"/><c:axId val="${VAL_AX_ID}"/>`);
|
|
20
|
+
const axes = isPie ? "" : isScatter ? valAx(CAT_AX_ID, "b", VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle) : catAx(CAT_AX_ID, VAL_AX_ID, chart.catAxisTitle) + valAx(VAL_AX_ID, "l", CAT_AX_ID, chart.valAxisTitle);
|
|
21
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><c:chartSpace xmlns:c="${C_NS}" xmlns:a="${A_NS}" xmlns:r="${R_NS}"><c:chart>` + titleXml(chart.title) + `<c:autoTitleDeleted val="${chart.title ? 0 : 1}"/><c:plotArea><c:layout/><${groupTag}>${inner}</${groupTag}>${axes}</c:plotArea>` + (chart.hasLegend ? `<c:legend><c:legendPos val="${chart.legendPos ?? "r"}"/><c:overlay val="0"/></c:legend>` : "") + "<c:plotVisOnly val=\"1\"/></c:chart></c:chartSpace>";
|
|
22
|
+
}
|
|
23
|
+
function seriesXml(s, idx, isScatter, categories) {
|
|
24
|
+
const tx = s.name ? `<c:tx><c:strRef><c:f>Sheet1!$A$${idx + 1}</c:f><c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${esc(s.name)}</c:v></c:pt></c:strCache></c:strRef></c:tx>` : "";
|
|
25
|
+
const spPr = s.colorHex ? `<c:spPr><a:solidFill><a:srgbClr val="${s.colorHex}"/></a:solidFill></c:spPr>` : "";
|
|
26
|
+
const dPts = (s.pointColors ?? []).map((p) => `<c:dPt><c:idx val="${p.idx}"/><c:bubble3D val="0"/><c:spPr><a:solidFill><a:srgbClr val="${p.colorHex}"/></a:solidFill></c:spPr></c:dPt>`).join("");
|
|
27
|
+
const cat = isScatter ? `<c:xVal>${numRef("A", s.xValues ?? [])}</c:xVal>` : categories.length > 0 ? `<c:cat>${strRef(categories)}</c:cat>` : "";
|
|
28
|
+
const val = isScatter ? `<c:yVal>${numRef("B", s.values)}</c:yVal>` : `<c:val>${numRef("B", s.values)}</c:val>`;
|
|
29
|
+
return `<c:ser><c:idx val="${idx}"/><c:order val="${idx}"/>` + tx + spPr + dPts + cat + val + "</c:ser>";
|
|
30
|
+
}
|
|
31
|
+
function strRef(values) {
|
|
32
|
+
const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${esc(v)}</c:v></c:pt>`).join("");
|
|
33
|
+
return `<c:strRef><c:f>Sheet1!\$A\$1</c:f><c:strCache><c:ptCount val="${values.length}"/>${pts}</c:strCache></c:strRef>`;
|
|
34
|
+
}
|
|
35
|
+
function numRef(col, values) {
|
|
36
|
+
const pts = values.map((v, i) => `<c:pt idx="${i}"><c:v>${v}</c:v></c:pt>`).join("");
|
|
37
|
+
return `<c:numRef><c:f>Sheet1!$${col}$1</c:f><c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${values.length}"/>${pts}</c:numCache></c:numRef>`;
|
|
38
|
+
}
|
|
39
|
+
function titleXml(title) {
|
|
40
|
+
if (!title) return "";
|
|
41
|
+
return `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${esc(title)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title>`;
|
|
42
|
+
}
|
|
43
|
+
function catAx(id, crossId, title) {
|
|
44
|
+
return `<c:catAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="b"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:catAx>`;
|
|
45
|
+
}
|
|
46
|
+
function valAx(id, pos, crossId, title) {
|
|
47
|
+
return `<c:valAx><c:axId val="${id}"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="${pos}"/>${titleXml(title)}<c:crossAx val="${crossId}"/></c:valAx>`;
|
|
48
|
+
}
|
|
49
|
+
function esc(s) {
|
|
50
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { chartSpaceXml };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ShapeGeometry, ShapeLine } from '../document-model/index.js';
|
|
2
|
-
import { StrokeStyle, VectorPath } from '../vector.js';
|
|
2
|
+
import { ShapeGradient, StrokeStyle, VectorPath } from '../vector.js';
|
|
3
3
|
export declare const EMU_PER_PT = 12700;
|
|
4
4
|
export declare const DEFAULT_INSET_LR_PT: number;
|
|
5
5
|
export declare const DEFAULT_INSET_TB_PT: number;
|
|
6
6
|
export declare function buildShapePaths(geometry: ShapeGeometry, widthPt: number, heightPt: number): Array<VectorPath>;
|
|
7
|
+
export declare function gradientToSolid(gradient: ShapeGradient): string;
|
|
8
|
+
export declare function gradientSvgDef(id: string, g: ShapeGradient): string;
|
|
7
9
|
export declare function buildStroke(line: ShapeLine | undefined): StrokeStyle | undefined;
|
|
8
10
|
export declare function buildShapeTransform(pageX: number, pageY: number, widthPt: number, heightPt: number, rotation60k: number, flipH: boolean, flipV: boolean): [number, number, number, number, number, number];
|
|
@@ -9,6 +9,32 @@ function buildShapePaths(geometry, widthPt, heightPt) {
|
|
|
9
9
|
if (geometry.custom) return customPaths(geometry.custom, widthPt, heightPt);
|
|
10
10
|
return [rectPath(widthPt, heightPt)];
|
|
11
11
|
}
|
|
12
|
+
function gradientToSolid(gradient) {
|
|
13
|
+
let r = 0;
|
|
14
|
+
let g = 0;
|
|
15
|
+
let b = 0;
|
|
16
|
+
let n = 0;
|
|
17
|
+
for (const stop of gradient.stops) {
|
|
18
|
+
const num = parseInt(stop.colorHex, 16);
|
|
19
|
+
if (Number.isNaN(num)) continue;
|
|
20
|
+
r += num >> 16 & 255;
|
|
21
|
+
g += num >> 8 & 255;
|
|
22
|
+
b += num & 255;
|
|
23
|
+
n++;
|
|
24
|
+
}
|
|
25
|
+
if (n === 0) return "000000";
|
|
26
|
+
const hx = (x) => Math.round(x / n).toString(16).padStart(2, "0");
|
|
27
|
+
return (hx(r) + hx(g) + hx(b)).toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
function gradientSvgDef(id, g) {
|
|
30
|
+
const n = (x) => String(Math.round(x * 1e4) / 1e4);
|
|
31
|
+
const stops = g.stops.map((s) => `<stop offset="${n(s.offset)}" stop-color="#${s.colorHex}"/>`).join("");
|
|
32
|
+
if (g.kind === "radial") return `<radialGradient id="${id}">${stops}</radialGradient>`;
|
|
33
|
+
const rad = -(g.angle ?? 0) * Math.PI / 180;
|
|
34
|
+
const dx = Math.cos(rad) / 2;
|
|
35
|
+
const dy = Math.sin(rad) / 2;
|
|
36
|
+
return `<linearGradient id="${id}" x1="${n(.5 - dx)}" y1="${n(.5 - dy)}" x2="${n(.5 + dx)}" y2="${n(.5 + dy)}">${stops}</linearGradient>`;
|
|
37
|
+
}
|
|
12
38
|
function buildStroke(line) {
|
|
13
39
|
if (!line || line.fill === "none") return void 0;
|
|
14
40
|
const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
|
|
@@ -68,4 +94,4 @@ function buildShapeTransform(pageX, pageY, widthPt, heightPt, rotation60k, flipH
|
|
|
68
94
|
];
|
|
69
95
|
}
|
|
70
96
|
//#endregion
|
|
71
|
-
export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke };
|
|
97
|
+
export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef, gradientToSolid };
|
|
@@ -28,9 +28,19 @@ export interface StrokeStyle {
|
|
|
28
28
|
readonly join?: 'miter' | 'round' | 'bevel';
|
|
29
29
|
readonly dash?: ReadonlyArray<number>;
|
|
30
30
|
}
|
|
31
|
+
export interface GradientStop {
|
|
32
|
+
readonly offset: number;
|
|
33
|
+
readonly colorHex: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ShapeGradient {
|
|
36
|
+
readonly kind: 'linear' | 'radial';
|
|
37
|
+
readonly angle?: number;
|
|
38
|
+
readonly stops: ReadonlyArray<GradientStop>;
|
|
39
|
+
}
|
|
31
40
|
export interface VectorShape {
|
|
32
41
|
readonly paths: ReadonlyArray<VectorPath>;
|
|
33
42
|
readonly fillColorHex?: string;
|
|
43
|
+
readonly fillGradient?: ShapeGradient;
|
|
34
44
|
readonly stroke?: StrokeStyle;
|
|
35
45
|
readonly transform: readonly [number, number, number, number, number, number];
|
|
36
46
|
}
|
|
@@ -309,9 +309,12 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
309
309
|
const local = brk - colStart;
|
|
310
310
|
if (local > 0 && local < colCount) colBreaksLocal.add(local);
|
|
311
311
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
|
|
313
|
+
const bandWidths = scaled ? columnWidths.map((w) => Math.max(1, Math.floor(w * printScale))) : columnWidths;
|
|
314
|
+
const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
|
|
315
|
+
if (colCount > 1 && (!scaled || fitWide > 1) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
|
|
316
|
+
const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
|
|
317
|
+
if (bands.length > 1) return bandedTables(rows, bandWidths, bands, tableProperties);
|
|
315
318
|
}
|
|
316
319
|
const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
|
|
317
320
|
rows: worksheet.pane.frozenRows,
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { FEATURES } from "../core/ir/features.js";
|
|
2
2
|
import { buildOpcPackage } from "../core/opc/opc-writer.js";
|
|
3
3
|
import "../core/opc/index.js";
|
|
4
|
+
import { chartSpaceXml } from "../core/drawingml/chart-serializer.js";
|
|
4
5
|
//#region src/excel/xlsx-writer.ts
|
|
5
6
|
var encoder = new TextEncoder();
|
|
7
|
+
var EMU_PER_PT = 12700;
|
|
6
8
|
var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
7
9
|
var REL_WORKSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
|
8
10
|
var REL_SHARED_STRINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
|
9
11
|
var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
10
12
|
var REL_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
|
|
13
|
+
var REL_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
|
|
14
|
+
var REL_CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
|
|
15
|
+
var CT_DRAWING = "application/vnd.openxmlformats-officedocument.drawing+xml";
|
|
16
|
+
var CT_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
|
|
17
|
+
var XDR_NS = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
|
18
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
19
|
+
var C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
|
11
20
|
var CT_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
|
|
12
21
|
var CT_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
|
|
13
22
|
var CT_SHARED_STRINGS = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
|
|
@@ -19,16 +28,20 @@ function writeXlsx(sheet) {
|
|
|
19
28
|
const losses = [];
|
|
20
29
|
const worksheetParts = [];
|
|
21
30
|
const tableParts = [];
|
|
31
|
+
const drawingParts = [];
|
|
22
32
|
const worksheetRels = [];
|
|
33
|
+
const drawingRels = [];
|
|
23
34
|
const sheetEntries = [];
|
|
24
35
|
const workbookRels = [];
|
|
25
36
|
let tableCounter = 0;
|
|
37
|
+
let drawingCounter = 0;
|
|
38
|
+
let chartCounter = 0;
|
|
26
39
|
sheet.sheets.forEach((s, i) => {
|
|
27
40
|
const idx = i + 1;
|
|
28
41
|
const rid = `rId${idx}`;
|
|
29
42
|
const wsPath = `xl/worksheets/sheet${idx}.xml`;
|
|
43
|
+
const wsRels = [];
|
|
30
44
|
const tableRelIds = [];
|
|
31
|
-
const wsTableRels = [];
|
|
32
45
|
for (const t of s.grid.tables ?? []) {
|
|
33
46
|
const tid = ++tableCounter;
|
|
34
47
|
const tRid = `rIdTbl${tid}`;
|
|
@@ -38,15 +51,23 @@ function writeXlsx(sheet) {
|
|
|
38
51
|
data: encoder.encode(tableXml(t, tid)),
|
|
39
52
|
contentType: CT_TABLE
|
|
40
53
|
});
|
|
41
|
-
|
|
54
|
+
wsRels.push(rel(tRid, REL_TABLE, `../tables/table${tid}.xml`));
|
|
42
55
|
}
|
|
43
|
-
|
|
56
|
+
const drawingRelId = emitSheetCharts(s, sheet, idx, {
|
|
57
|
+
drawingParts,
|
|
58
|
+
drawingRels,
|
|
59
|
+
wsRels,
|
|
60
|
+
nextDrawing: () => ++drawingCounter,
|
|
61
|
+
nextChart: () => ++chartCounter,
|
|
62
|
+
losses
|
|
63
|
+
});
|
|
64
|
+
if (wsRels.length > 0) worksheetRels.push({
|
|
44
65
|
sourcePart: wsPath,
|
|
45
|
-
relationships:
|
|
66
|
+
relationships: wsRels
|
|
46
67
|
});
|
|
47
68
|
worksheetParts.push({
|
|
48
69
|
path: wsPath,
|
|
49
|
-
data: encoder.encode(worksheetXml(s.grid, tableRelIds)),
|
|
70
|
+
data: encoder.encode(worksheetXml(s.grid, tableRelIds, drawingRelId)),
|
|
50
71
|
contentType: CT_WORKSHEET
|
|
51
72
|
});
|
|
52
73
|
workbookRels.push(rel(rid, REL_WORKSHEET, `worksheets/sheet${idx}.xml`));
|
|
@@ -55,7 +76,6 @@ function writeXlsx(sheet) {
|
|
|
55
76
|
sheetId: idx,
|
|
56
77
|
rid
|
|
57
78
|
});
|
|
58
|
-
recordSheetLosses(s, losses);
|
|
59
79
|
});
|
|
60
80
|
const sstRid = `rId${sheet.sheets.length + 1}`;
|
|
61
81
|
const stylesRid = `rId${sheet.sheets.length + 2}`;
|
|
@@ -71,6 +91,7 @@ function writeXlsx(sheet) {
|
|
|
71
91
|
},
|
|
72
92
|
...worksheetParts,
|
|
73
93
|
...tableParts,
|
|
94
|
+
...drawingParts,
|
|
74
95
|
{
|
|
75
96
|
path: "xl/sharedStrings.xml",
|
|
76
97
|
data: encoder.encode(sharedStringsXml(sheet.sharedStrings)),
|
|
@@ -83,10 +104,14 @@ function writeXlsx(sheet) {
|
|
|
83
104
|
}
|
|
84
105
|
],
|
|
85
106
|
rootRelationships: [rel("rId1", REL_OFFICE_DOCUMENT, "xl/workbook.xml")],
|
|
86
|
-
partRelationships: [
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
partRelationships: [
|
|
108
|
+
{
|
|
109
|
+
sourcePart: "xl/workbook.xml",
|
|
110
|
+
relationships: workbookRels
|
|
111
|
+
},
|
|
112
|
+
...worksheetRels,
|
|
113
|
+
...drawingRels
|
|
114
|
+
]
|
|
90
115
|
}),
|
|
91
116
|
losses
|
|
92
117
|
};
|
|
@@ -100,19 +125,58 @@ function rel(id, type, target) {
|
|
|
100
125
|
targetMode: "Internal"
|
|
101
126
|
};
|
|
102
127
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
function emitSheetCharts(s, doc, idx, ctx) {
|
|
129
|
+
const refs = s.charts ?? [];
|
|
130
|
+
if (refs.length === 0 || !doc.chartData) return void 0;
|
|
131
|
+
const did = ctx.nextDrawing();
|
|
132
|
+
const drawingPath = `xl/drawings/drawing${did}.xml`;
|
|
133
|
+
const anchors = [];
|
|
134
|
+
const dRels = [];
|
|
135
|
+
refs.forEach((ref, ci) => {
|
|
136
|
+
const chart = doc.chartData?.get(ref.chartPartPath);
|
|
137
|
+
if (!chart) {
|
|
138
|
+
ctx.losses.push({
|
|
139
|
+
severity: "dropped",
|
|
140
|
+
feature: FEATURES.charts,
|
|
141
|
+
detail: `sheet "${s.name}": a chart frame had no resolved data and was dropped`
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const cid = ctx.nextChart();
|
|
146
|
+
ctx.drawingParts.push({
|
|
147
|
+
path: `xl/charts/chart${cid}.xml`,
|
|
148
|
+
data: encoder.encode(chartSpaceXml(chart)),
|
|
149
|
+
contentType: CT_CHART
|
|
150
|
+
});
|
|
151
|
+
const cRel = `rIdChart${ci + 1}`;
|
|
152
|
+
dRels.push(rel(cRel, REL_CHART, `../charts/chart${cid}.xml`));
|
|
153
|
+
anchors.push(chartAnchorXml(ref, cRel, ci));
|
|
154
|
+
});
|
|
155
|
+
if (anchors.length === 0) return void 0;
|
|
156
|
+
ctx.drawingParts.push({
|
|
157
|
+
path: drawingPath,
|
|
158
|
+
data: encoder.encode(`${XML_DECL}<xdr:wsDr xmlns:xdr="${XDR_NS}" xmlns:a="${A_NS}">${anchors.join("")}</xdr:wsDr>`),
|
|
159
|
+
contentType: CT_DRAWING
|
|
108
160
|
});
|
|
161
|
+
ctx.drawingRels.push({
|
|
162
|
+
sourcePart: drawingPath,
|
|
163
|
+
relationships: dRels
|
|
164
|
+
});
|
|
165
|
+
const dRid = `rIdDraw${idx}`;
|
|
166
|
+
ctx.wsRels.push(rel(dRid, REL_DRAWING, `../drawings/drawing${did}.xml`));
|
|
167
|
+
return dRid;
|
|
168
|
+
}
|
|
169
|
+
function chartAnchorXml(ref, chartRelId, index) {
|
|
170
|
+
const cx = Math.round(ref.widthPt * EMU_PER_PT);
|
|
171
|
+
const cy = Math.round(ref.heightPt * EMU_PER_PT);
|
|
172
|
+
return `<xdr:oneCellAnchor><xdr:from><xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>${index * 16}</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:from><xdr:ext cx="${cx}" cy="${cy}"/><xdr:graphicFrame macro=""><xdr:nvGraphicFramePr><xdr:cNvPr id="${index + 2}" name="Chart ${index + 1}"/><xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr><xdr:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></xdr:xfrm><a:graphic><a:graphicData uri="${C_NS}"><c:chart xmlns:c="${C_NS}" xmlns:r="${R_NS}" r:id="${chartRelId}"/></a:graphicData></a:graphic></xdr:graphicFrame><xdr:clientData/></xdr:oneCellAnchor>`;
|
|
109
173
|
}
|
|
110
174
|
function workbookXml(sheets, definedNames, date1904) {
|
|
111
175
|
const sheetTags = sheets.map((s) => `<sheet name="${escapeAttr(s.name)}" sheetId="${s.sheetId}" r:id="${s.rid}"/>`).join("");
|
|
112
176
|
const definedNamesXml = definedNames.length > 0 ? `<definedNames>${definedNames.map((d) => `<definedName name="${escapeAttr(d.name)}"${d.localSheetId !== void 0 ? ` localSheetId="${d.localSheetId}"` : ""}>${escapeText(d.value)}</definedName>`).join("")}</definedNames>` : "";
|
|
113
177
|
return XML_DECL + `<workbook xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (date1904 ? "<workbookPr date1904=\"1\"/>" : "") + `<sheets>${sheetTags}</sheets>` + definedNamesXml + "</workbook>";
|
|
114
178
|
}
|
|
115
|
-
function worksheetXml(grid, tableRelIds = []) {
|
|
179
|
+
function worksheetXml(grid, tableRelIds = [], drawingRelId) {
|
|
116
180
|
const dimension = grid.maxRow >= 0 && grid.maxColumn >= 0 ? `<dimension ref="A1:${cellRef(grid.maxRow, grid.maxColumn)}"/>` : "<dimension ref=\"A1\"/>";
|
|
117
181
|
const colsXml = grid.columns.length > 0 ? `<cols>${grid.columns.map((c) => `<col min="${c.min}" max="${c.max}" width="${c.widthChars}" customWidth="1"/>`).join("")}</cols>` : "";
|
|
118
182
|
const heightByRow = /* @__PURE__ */ new Map();
|
|
@@ -136,7 +200,7 @@ function worksheetXml(grid, tableRelIds = []) {
|
|
|
136
200
|
return `<row r="${r + 1}"${heightAttrs}>${cells.map(cellXml).join("")}</row>`;
|
|
137
201
|
}).join("");
|
|
138
202
|
const mergesXml = grid.merges.length > 0 ? `<mergeCells count="${grid.merges.length}">${grid.merges.map((m) => `<mergeCell ref="${cellRef(m.startRow, m.startColumn)}:${cellRef(m.endRow, m.endColumn)}"/>`).join("")}</mergeCells>` : "";
|
|
139
|
-
return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
|
|
203
|
+
return XML_DECL + `<worksheet xmlns="${MAIN_NS}" xmlns:r="${R_NS}">` + (grid.fitToPage ? "<sheetPr><pageSetUpPr fitToPage=\"1\"/></sheetPr>" : "") + dimension + sheetViewsXml(grid.pane) + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + conditionalFormattingXml(grid.conditionalFormats) + printOptionsXml(grid.printOptions) + pageMarginsXml(grid.pageMargins) + pageSetupXml(grid.pageSetup) + breaksXml("rowBreaks", grid.rowBreaks) + breaksXml("colBreaks", grid.colBreaks) + (drawingRelId ? `<drawing r:id="${drawingRelId}"/>` : "") + (tableRelIds.length > 0 ? `<tableParts count="${tableRelIds.length}">${tableRelIds.map((id) => `<tablePart r:id="${id}"/>`).join("")}</tableParts>` : "") + sparklineExtXml(grid.sparklines) + "</worksheet>";
|
|
140
204
|
}
|
|
141
205
|
function sheetViewsXml(pane) {
|
|
142
206
|
if (!pane || pane.frozenRows <= 0 && pane.frozenCols <= 0) return "";
|
|
@@ -6,7 +6,7 @@ import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
|
|
|
6
6
|
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
7
7
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
8
8
|
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
9
|
-
import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
|
|
9
|
+
import { buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef } from "../core/drawingml/shape-render.js";
|
|
10
10
|
import { sanitizeHref } from "../core/links.js";
|
|
11
11
|
import { toBase64 } from "../core/bytes.js";
|
|
12
12
|
//#region src/html/html-writer.ts
|
|
@@ -209,11 +209,13 @@ function emitShapeBlock(out, shape, ctx) {
|
|
|
209
209
|
const w = shape.width;
|
|
210
210
|
const h = shape.height;
|
|
211
211
|
const paths = buildShapePaths(shape.geometry, w, h);
|
|
212
|
-
const
|
|
212
|
+
const gradDef = shape.fill.kind === "gradient" && shape.fill.gradient ? gradientSvgDef("grad0", shape.fill.gradient) : void 0;
|
|
213
|
+
const fill = gradDef ? "url(#grad0)" : shape.fill.kind === "solid" && shape.fill.colorHex ? `#${shape.fill.colorHex}` : "none";
|
|
213
214
|
const stroke = strokeAttrs(buildStroke(shape.line));
|
|
214
215
|
const t = shape.transform;
|
|
215
216
|
const transform = ` transform="matrix(${flipTransform(buildShapeTransform(0, 0, w, h, t?.rotation60k ?? 0, t?.flipH ?? false, t?.flipV ?? false), h).map(fmt).join(" ")})"`;
|
|
216
217
|
const svg = [svgOpen(w, h, shape.altText)];
|
|
218
|
+
if (gradDef) svg.push(`<defs>${gradDef}</defs>`);
|
|
217
219
|
for (const path of paths) {
|
|
218
220
|
const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
|
|
219
221
|
svg.push(`<path d="${svgPathData(path.segments, fmt)}" fill="${fill}"${rule}${stroke}${transform}/>`);
|
|
@@ -17,7 +17,7 @@ import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
|
17
17
|
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
18
18
|
import { layoutMath, mathGlyphSegments, variantStyle } from "./math-layout.js";
|
|
19
19
|
import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
20
|
-
import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
|
|
20
|
+
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid } from "../core/drawingml/shape-render.js";
|
|
21
21
|
import { StructTreeBuilder } from "../pdf/struct-tree.js";
|
|
22
22
|
//#region src/layout/styled-layout.ts
|
|
23
23
|
function parsePdfAProfile(pdfA) {
|
|
@@ -454,7 +454,8 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
454
454
|
heightPt = maxHeight;
|
|
455
455
|
}
|
|
456
456
|
const paths = buildShapePaths(shape.geometry, widthPt, heightPt);
|
|
457
|
-
const
|
|
457
|
+
const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
|
|
458
|
+
const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
|
|
458
459
|
const stroke = buildStroke(shape.line);
|
|
459
460
|
const t = shape.transform;
|
|
460
461
|
const pp = shape.paragraphProperties;
|
|
@@ -483,6 +484,7 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
|
|
|
483
484
|
heightPt,
|
|
484
485
|
paths,
|
|
485
486
|
...fillColorHex ? { fillColorHex } : {},
|
|
487
|
+
...fillGradient ? { fillGradient } : {},
|
|
486
488
|
...stroke ? { stroke } : {},
|
|
487
489
|
rotation60k: t?.rotation60k ?? 0,
|
|
488
490
|
flipH: t?.flipH ?? false,
|
|
@@ -2013,6 +2015,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
2013
2015
|
shape: {
|
|
2014
2016
|
paths: block.paths,
|
|
2015
2017
|
...block.fillColorHex ? { fillColorHex: block.fillColorHex } : {},
|
|
2018
|
+
...block.fillGradient ? { fillGradient: block.fillGradient } : {},
|
|
2016
2019
|
...block.stroke ? { stroke: block.stroke } : {},
|
|
2017
2020
|
transform
|
|
2018
2021
|
},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ShapeGradient, VectorShape } from '../core/vector.js';
|
|
2
|
+
import { PdfRef } from './objects.js';
|
|
3
|
+
import { PdfDocument } from './writer.js';
|
|
4
|
+
interface Bbox {
|
|
5
|
+
readonly minX: number;
|
|
6
|
+
readonly minY: number;
|
|
7
|
+
readonly maxX: number;
|
|
8
|
+
readonly maxY: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function shapeBbox(shape: VectorShape): Bbox | undefined;
|
|
11
|
+
export declare function buildGradientPattern(doc: PdfDocument, gradient: ShapeGradient, bbox: Bbox, ctm: readonly [number, number, number, number, number, number]): PdfRef;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { dict, name, ref } from "./objects.js";
|
|
2
|
+
//#region src/pdf/shading.ts
|
|
3
|
+
function shapeBbox(shape) {
|
|
4
|
+
let minX = Infinity;
|
|
5
|
+
let minY = Infinity;
|
|
6
|
+
let maxX = -Infinity;
|
|
7
|
+
let maxY = -Infinity;
|
|
8
|
+
const add = (x, y) => {
|
|
9
|
+
minX = Math.min(minX, x);
|
|
10
|
+
minY = Math.min(minY, y);
|
|
11
|
+
maxX = Math.max(maxX, x);
|
|
12
|
+
maxY = Math.max(maxY, y);
|
|
13
|
+
};
|
|
14
|
+
for (const path of shape.paths) for (const seg of path.segments) if (seg.op === "move" || seg.op === "line") add(seg.x, seg.y);
|
|
15
|
+
else if (seg.op === "cubic") {
|
|
16
|
+
add(seg.x1, seg.y1);
|
|
17
|
+
add(seg.x2, seg.y2);
|
|
18
|
+
add(seg.x, seg.y);
|
|
19
|
+
}
|
|
20
|
+
return Number.isFinite(minX) && maxX > minX && maxY > minY ? {
|
|
21
|
+
minX,
|
|
22
|
+
minY,
|
|
23
|
+
maxX,
|
|
24
|
+
maxY
|
|
25
|
+
} : void 0;
|
|
26
|
+
}
|
|
27
|
+
function buildGradientPattern(doc, gradient, bbox, ctm) {
|
|
28
|
+
const fnRef = doc.add(buildRamp(gradient.stops));
|
|
29
|
+
const shading = gradient.kind === "radial" ? radialShading(bbox, fnRef) : axialShading(gradient.angle ?? 0, bbox, fnRef);
|
|
30
|
+
return doc.add(dict({
|
|
31
|
+
Type: name("Pattern"),
|
|
32
|
+
PatternType: 2,
|
|
33
|
+
Shading: shading,
|
|
34
|
+
Matrix: [...ctm]
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
function axialShading(angleDeg, b, fnRef) {
|
|
38
|
+
const rad = angleDeg * Math.PI / 180;
|
|
39
|
+
const dx = Math.cos(rad);
|
|
40
|
+
const dy = -Math.sin(rad);
|
|
41
|
+
const cx = (b.minX + b.maxX) / 2;
|
|
42
|
+
const cy = (b.minY + b.maxY) / 2;
|
|
43
|
+
const cproj = cx * dx + cy * dy;
|
|
44
|
+
let tmin = Infinity;
|
|
45
|
+
let tmax = -Infinity;
|
|
46
|
+
for (const [x, y] of [
|
|
47
|
+
[b.minX, b.minY],
|
|
48
|
+
[b.maxX, b.minY],
|
|
49
|
+
[b.minX, b.maxY],
|
|
50
|
+
[b.maxX, b.maxY]
|
|
51
|
+
]) {
|
|
52
|
+
const t = x * dx + y * dy;
|
|
53
|
+
tmin = Math.min(tmin, t);
|
|
54
|
+
tmax = Math.max(tmax, t);
|
|
55
|
+
}
|
|
56
|
+
return dict({
|
|
57
|
+
ShadingType: 2,
|
|
58
|
+
ColorSpace: name("DeviceRGB"),
|
|
59
|
+
Coords: [
|
|
60
|
+
cx + (tmin - cproj) * dx,
|
|
61
|
+
cy + (tmin - cproj) * dy,
|
|
62
|
+
cx + (tmax - cproj) * dx,
|
|
63
|
+
cy + (tmax - cproj) * dy
|
|
64
|
+
],
|
|
65
|
+
Function: ref(fnRef.id),
|
|
66
|
+
Extend: [true, true]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function radialShading(b, fnRef) {
|
|
70
|
+
const cx = (b.minX + b.maxX) / 2;
|
|
71
|
+
const cy = (b.minY + b.maxY) / 2;
|
|
72
|
+
const r = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) / 2;
|
|
73
|
+
return dict({
|
|
74
|
+
ShadingType: 3,
|
|
75
|
+
ColorSpace: name("DeviceRGB"),
|
|
76
|
+
Coords: [
|
|
77
|
+
cx,
|
|
78
|
+
cy,
|
|
79
|
+
0,
|
|
80
|
+
cx,
|
|
81
|
+
cy,
|
|
82
|
+
r
|
|
83
|
+
],
|
|
84
|
+
Function: ref(fnRef.id),
|
|
85
|
+
Extend: [true, true]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function buildRamp(stopsIn) {
|
|
89
|
+
const stops = normalizeStops(stopsIn);
|
|
90
|
+
if (stops.length <= 2) return dict({
|
|
91
|
+
FunctionType: 2,
|
|
92
|
+
Domain: [0, 1],
|
|
93
|
+
C0: rgb01(stops[0].colorHex),
|
|
94
|
+
C1: rgb01(stops[stops.length - 1].colorHex),
|
|
95
|
+
N: 1
|
|
96
|
+
});
|
|
97
|
+
const functions = [];
|
|
98
|
+
const bounds = [];
|
|
99
|
+
const encode = [];
|
|
100
|
+
for (let i = 0; i < stops.length - 1; i++) {
|
|
101
|
+
functions.push(dict({
|
|
102
|
+
FunctionType: 2,
|
|
103
|
+
Domain: [0, 1],
|
|
104
|
+
C0: rgb01(stops[i].colorHex),
|
|
105
|
+
C1: rgb01(stops[i + 1].colorHex),
|
|
106
|
+
N: 1
|
|
107
|
+
}));
|
|
108
|
+
if (i > 0) bounds.push(stops[i].offset);
|
|
109
|
+
encode.push(0, 1);
|
|
110
|
+
}
|
|
111
|
+
return dict({
|
|
112
|
+
FunctionType: 3,
|
|
113
|
+
Domain: [0, 1],
|
|
114
|
+
Functions: functions,
|
|
115
|
+
Bounds: bounds,
|
|
116
|
+
Encode: encode
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function normalizeStops(stops) {
|
|
120
|
+
const out = stops.map((s) => ({
|
|
121
|
+
offset: Math.max(0, Math.min(1, s.offset)),
|
|
122
|
+
colorHex: s.colorHex
|
|
123
|
+
})).sort((a, b) => a.offset - b.offset);
|
|
124
|
+
if (out.length === 0) return [{
|
|
125
|
+
offset: 0,
|
|
126
|
+
colorHex: "000000"
|
|
127
|
+
}];
|
|
128
|
+
out[0] = {
|
|
129
|
+
offset: 0,
|
|
130
|
+
colorHex: out[0].colorHex
|
|
131
|
+
};
|
|
132
|
+
out[out.length - 1] = {
|
|
133
|
+
offset: 1,
|
|
134
|
+
colorHex: out[out.length - 1].colorHex
|
|
135
|
+
};
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function rgb01(hex) {
|
|
139
|
+
const n = parseInt(hex, 16);
|
|
140
|
+
if (Number.isNaN(n)) return [
|
|
141
|
+
0,
|
|
142
|
+
0,
|
|
143
|
+
0
|
|
144
|
+
];
|
|
145
|
+
return [
|
|
146
|
+
(n >> 16 & 255) / 255,
|
|
147
|
+
(n >> 8 & 255) / 255,
|
|
148
|
+
(n & 255) / 255
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { buildGradientPattern, shapeBbox };
|