@lichens-innovation/ts-common 1.7.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 +40 -0
- package/dist/csv.cjs +37 -0
- package/dist/csv.cjs.map +1 -0
- package/dist/csv.d.cts +5 -0
- package/dist/csv.d.ts +5 -0
- package/dist/csv.js +29 -0
- package/dist/csv.js.map +1 -0
- package/dist/dimensions.utils-BwIBA5op.d.cts +6 -0
- package/dist/dimensions.utils-BwIBA5op.d.ts +6 -0
- package/dist/excel.cjs +177 -0
- package/dist/excel.cjs.map +1 -0
- package/dist/excel.d.cts +109 -0
- package/dist/excel.d.ts +109 -0
- package/dist/excel.js +167 -0
- package/dist/excel.js.map +1 -0
- package/dist/index.cjs +115 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +40 -95
- package/dist/index.js.map +1 -1
- package/dist/mime.cjs +20 -0
- package/dist/mime.cjs.map +1 -0
- package/dist/mime.d.cts +3 -0
- package/dist/mime.d.ts +3 -0
- package/dist/mime.js +14 -0
- package/dist/mime.js.map +1 -0
- package/dist/pdf.cjs +273 -0
- package/dist/pdf.cjs.map +1 -0
- package/dist/pdf.d.cts +140 -0
- package/dist/pdf.d.ts +140 -0
- package/dist/pdf.js +264 -0
- package/dist/pdf.js.map +1 -0
- package/dist/web.cjs +235 -0
- package/dist/web.cjs.map +1 -0
- package/dist/web.d.cts +82 -0
- package/dist/web.d.ts +82 -0
- package/dist/web.js +219 -0
- package/dist/web.js.map +1 -0
- package/package.json +55 -13
package/dist/pdf.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { jsPDF } from 'jspdf';
|
|
2
|
+
import { autoTable } from 'jspdf-autotable';
|
|
3
|
+
import { format } from 'date-fns';
|
|
4
|
+
|
|
5
|
+
// src/utils/types.utils.ts
|
|
6
|
+
var isNullish = (value) => value === null || value === void 0;
|
|
7
|
+
var DEFAULT_REPORT_FILENAME = "report.pdf";
|
|
8
|
+
var IMAGE_GAP = 0.5;
|
|
9
|
+
var DEFAULT_THEME = {
|
|
10
|
+
primaryColor: { r: 0, g: 0, b: 0 },
|
|
11
|
+
// black
|
|
12
|
+
fontSize: {
|
|
13
|
+
title: 16,
|
|
14
|
+
header: 10,
|
|
15
|
+
normal: 9,
|
|
16
|
+
small: 8,
|
|
17
|
+
tiny: 7
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var DEFAULT_FOOTER_CELL_BUILDER = ({ align, currentPage, totalPages }) => {
|
|
21
|
+
if (align === "left") return format(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm");
|
|
22
|
+
if (align === "center") return "";
|
|
23
|
+
return `Page ${currentPage}/${totalPages}`;
|
|
24
|
+
};
|
|
25
|
+
var DEFAULT_OPTIONS = {
|
|
26
|
+
orientation: "portrait",
|
|
27
|
+
paperFormat: "letter",
|
|
28
|
+
filename: DEFAULT_REPORT_FILENAME,
|
|
29
|
+
theme: DEFAULT_THEME,
|
|
30
|
+
margin: 0.5,
|
|
31
|
+
displayAvailableAreaRectangle: false,
|
|
32
|
+
hasFooter: true,
|
|
33
|
+
footerCellBuilder: DEFAULT_FOOTER_CELL_BUILDER
|
|
34
|
+
};
|
|
35
|
+
var Gaps = {
|
|
36
|
+
TINY: 0.02,
|
|
37
|
+
SMALL: 0.04,
|
|
38
|
+
MEDIUM: 0.08,
|
|
39
|
+
LARGE: 0.16,
|
|
40
|
+
X_LARGE: 0.32
|
|
41
|
+
};
|
|
42
|
+
var PdfColors = {
|
|
43
|
+
LIGHT_GRAY: [240, 240, 240]
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/pdf/pdf-generator.ts
|
|
47
|
+
var PdfGenerator = class {
|
|
48
|
+
doc;
|
|
49
|
+
options;
|
|
50
|
+
_currentY;
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.options = {
|
|
53
|
+
...DEFAULT_OPTIONS,
|
|
54
|
+
...options
|
|
55
|
+
};
|
|
56
|
+
const { orientation, paperFormat } = this.options;
|
|
57
|
+
this.doc = new jsPDF({ orientation, format: paperFormat, unit: "in" });
|
|
58
|
+
this.doc.setFont("helvetica");
|
|
59
|
+
this._currentY = this.margin;
|
|
60
|
+
}
|
|
61
|
+
get font() {
|
|
62
|
+
return this.doc.getFont().fontName;
|
|
63
|
+
}
|
|
64
|
+
set font(font) {
|
|
65
|
+
this.doc.setFont(font);
|
|
66
|
+
}
|
|
67
|
+
get theme() {
|
|
68
|
+
return this.options.theme;
|
|
69
|
+
}
|
|
70
|
+
get filename() {
|
|
71
|
+
return this.options.filename;
|
|
72
|
+
}
|
|
73
|
+
get margin() {
|
|
74
|
+
return this.options.margin;
|
|
75
|
+
}
|
|
76
|
+
get pageSize() {
|
|
77
|
+
return this.doc.internal.pageSize;
|
|
78
|
+
}
|
|
79
|
+
get pageWidth() {
|
|
80
|
+
return this.pageSize.width;
|
|
81
|
+
}
|
|
82
|
+
get pageHeight() {
|
|
83
|
+
return this.pageSize.height;
|
|
84
|
+
}
|
|
85
|
+
get fullImageHeight() {
|
|
86
|
+
return this.availableHeight - this.footerHeight - IMAGE_GAP;
|
|
87
|
+
}
|
|
88
|
+
get availableWidth() {
|
|
89
|
+
return this.pageWidth - 2 * this.margin;
|
|
90
|
+
}
|
|
91
|
+
get availableHeight() {
|
|
92
|
+
return this.pageHeight - 2 * this.margin;
|
|
93
|
+
}
|
|
94
|
+
get footerFontSizePoints() {
|
|
95
|
+
return this.theme.fontSize.tiny;
|
|
96
|
+
}
|
|
97
|
+
/** Current Y position (in doc units) for layout. */
|
|
98
|
+
get currentY() {
|
|
99
|
+
return this._currentY;
|
|
100
|
+
}
|
|
101
|
+
/** Set current Y position (e.g. after custom content like header or chart). */
|
|
102
|
+
setCurrentY(y) {
|
|
103
|
+
this._currentY = y;
|
|
104
|
+
}
|
|
105
|
+
addVerticalGap(delta) {
|
|
106
|
+
this._currentY += delta;
|
|
107
|
+
}
|
|
108
|
+
checkTableOverflow(columnWidths) {
|
|
109
|
+
const computedSum = columnWidths.reduce((acc, width) => acc + width, 0);
|
|
110
|
+
if (computedSum <= this.availableWidth) return;
|
|
111
|
+
const infos = {
|
|
112
|
+
availableWidth: this.availableWidth,
|
|
113
|
+
computedSum,
|
|
114
|
+
columnWidths: JSON.stringify(columnWidths)
|
|
115
|
+
};
|
|
116
|
+
console.warn("[checkTableOverflow] table overflow", infos);
|
|
117
|
+
}
|
|
118
|
+
addTable(addTableArgs) {
|
|
119
|
+
const {
|
|
120
|
+
lineWidth = 5e-3,
|
|
121
|
+
lineColor = 0,
|
|
122
|
+
drawLineForTable = true,
|
|
123
|
+
drawLineForCells = true,
|
|
124
|
+
head,
|
|
125
|
+
body,
|
|
126
|
+
startY = this._currentY,
|
|
127
|
+
columnWidths
|
|
128
|
+
} = addTableArgs;
|
|
129
|
+
if (body.length === 0) {
|
|
130
|
+
this._currentY = startY;
|
|
131
|
+
return startY;
|
|
132
|
+
}
|
|
133
|
+
const cellPadding = 0.05;
|
|
134
|
+
const fontSize = this.theme.fontSize.small;
|
|
135
|
+
const colCount = columnWidths?.length ?? Math.max(...body.map((row) => row.length), 0);
|
|
136
|
+
const widths = columnWidths ?? Array(colCount).fill(this.availableWidth / colCount);
|
|
137
|
+
const columnStyles = {};
|
|
138
|
+
widths.forEach((cellWidth, i) => {
|
|
139
|
+
columnStyles[i] = { cellWidth, ...addTableArgs.columnStyles?.[i] };
|
|
140
|
+
});
|
|
141
|
+
const tableLineWidth = drawLineForTable ? lineWidth : 0;
|
|
142
|
+
const cellLineWidth = drawLineForCells ? lineWidth : 0;
|
|
143
|
+
autoTable(this.doc, {
|
|
144
|
+
startY,
|
|
145
|
+
head: !isNullish(head) ? [head] : void 0,
|
|
146
|
+
body,
|
|
147
|
+
theme: "grid",
|
|
148
|
+
tableLineWidth,
|
|
149
|
+
tableLineColor: lineColor,
|
|
150
|
+
margin: { left: this.margin, right: this.margin },
|
|
151
|
+
bodyStyles: { fontSize, font: this.font, cellPadding, lineWidth: cellLineWidth, lineColor },
|
|
152
|
+
headStyles: { fontSize, font: this.font, cellPadding, lineWidth: cellLineWidth, lineColor },
|
|
153
|
+
columnStyles
|
|
154
|
+
});
|
|
155
|
+
const finalY = this.doc.lastAutoTable?.finalY ?? startY;
|
|
156
|
+
this._currentY = finalY;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Add an image at the given position and size. Does not update currentY.
|
|
160
|
+
*/
|
|
161
|
+
addImage({ dataUri, x, y, width, height }) {
|
|
162
|
+
const imageFormat = "PNG";
|
|
163
|
+
const imageCompression = "NONE";
|
|
164
|
+
this.doc.addImage(dataUri, imageFormat, x, y, width, height, void 0, imageCompression);
|
|
165
|
+
}
|
|
166
|
+
/** Exposes the jsPDF document for custom drawing (e.g. header text, status badge, certification). */
|
|
167
|
+
getDoc() {
|
|
168
|
+
return this.doc;
|
|
169
|
+
}
|
|
170
|
+
addPage() {
|
|
171
|
+
this.doc.addPage();
|
|
172
|
+
}
|
|
173
|
+
addFullPagePNG(dataURI, aspectRatio) {
|
|
174
|
+
const imageFormat = "PNG";
|
|
175
|
+
const imageCompression = "NONE";
|
|
176
|
+
const alias = void 0;
|
|
177
|
+
const x = this.margin;
|
|
178
|
+
const y = this.margin;
|
|
179
|
+
const { width, height } = this.calculateImageDimensions(aspectRatio);
|
|
180
|
+
this.doc.addImage(dataURI, imageFormat, x, y, width, height, alias, imageCompression);
|
|
181
|
+
this._currentY = this.margin + height + IMAGE_GAP;
|
|
182
|
+
}
|
|
183
|
+
calculateImageDimensions(aspectRatio) {
|
|
184
|
+
const width = this.availableWidth;
|
|
185
|
+
const height = width / aspectRatio;
|
|
186
|
+
const maxHeight = this.availableHeight - this.footerHeight - IMAGE_GAP;
|
|
187
|
+
const finalHeight = Math.min(height, maxHeight);
|
|
188
|
+
const finalWidth = finalHeight < height ? finalHeight * aspectRatio : width;
|
|
189
|
+
return { width: finalWidth, height: finalHeight };
|
|
190
|
+
}
|
|
191
|
+
save() {
|
|
192
|
+
this.doc.save(this.filename);
|
|
193
|
+
}
|
|
194
|
+
buildFooterColumnStyles() {
|
|
195
|
+
const cellWidth = this.availableWidth / 3;
|
|
196
|
+
return {
|
|
197
|
+
0: { cellWidth, halign: "left" },
|
|
198
|
+
1: { cellWidth, halign: "center" },
|
|
199
|
+
2: { cellWidth, halign: "right" }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
renderFooters() {
|
|
203
|
+
if (!this.options.hasFooter) {
|
|
204
|
+
console.warn("[renderFooters] Footer is disabled");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const totalPages = this.doc.getNumberOfPages();
|
|
208
|
+
const columnStyles = this.buildFooterColumnStyles();
|
|
209
|
+
const startY = this.pageHeight - this.margin - this.footerHeight;
|
|
210
|
+
const footerCellBuilder = this.options.footerCellBuilder ?? DEFAULT_FOOTER_CELL_BUILDER;
|
|
211
|
+
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
|
212
|
+
this.doc.setPage(currentPage);
|
|
213
|
+
this.drawAvailableAreaRectangle();
|
|
214
|
+
const footerCells = ["left", "center", "right"].map((align) => ({
|
|
215
|
+
content: footerCellBuilder({ align, currentPage, totalPages })
|
|
216
|
+
}));
|
|
217
|
+
autoTable(this.doc, {
|
|
218
|
+
startY,
|
|
219
|
+
body: [footerCells],
|
|
220
|
+
theme: "plain",
|
|
221
|
+
bodyStyles: { fontSize: this.footerFontSizePoints, font: this.font },
|
|
222
|
+
margin: { left: this.margin, right: this.margin },
|
|
223
|
+
columnStyles
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Footer is a single line; height derived from font size and table padding. */
|
|
228
|
+
get footerHeight() {
|
|
229
|
+
if (!this.options.hasFooter) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
const fontSizeInches = this.footerFontSizePoints / 72;
|
|
233
|
+
const lineHeight = fontSizeInches * 1.5;
|
|
234
|
+
return lineHeight + 0.2;
|
|
235
|
+
}
|
|
236
|
+
drawAvailableAreaRectangle() {
|
|
237
|
+
if (!this.options.displayAvailableAreaRectangle) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const currentDrawColor = this.doc.getDrawColor();
|
|
241
|
+
const currentLineWidth = this.doc.getLineWidth();
|
|
242
|
+
try {
|
|
243
|
+
this.doc.setDrawColor(200, 200, 200);
|
|
244
|
+
this.doc.setLineWidth(0.01);
|
|
245
|
+
this.doc.rect(
|
|
246
|
+
this.margin,
|
|
247
|
+
this.margin,
|
|
248
|
+
this.availableWidth,
|
|
249
|
+
this.availableHeight,
|
|
250
|
+
"S"
|
|
251
|
+
// Stroke only, no fill
|
|
252
|
+
);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error("[drawAvailableAreaRectangle] Error drawing available area rect", error);
|
|
255
|
+
} finally {
|
|
256
|
+
this.doc.setDrawColor(currentDrawColor);
|
|
257
|
+
this.doc.setLineWidth(currentLineWidth);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export { DEFAULT_FOOTER_CELL_BUILDER, DEFAULT_OPTIONS, DEFAULT_REPORT_FILENAME, DEFAULT_THEME, Gaps, IMAGE_GAP, PdfColors, PdfGenerator };
|
|
263
|
+
//# sourceMappingURL=pdf.js.map
|
|
264
|
+
//# sourceMappingURL=pdf.js.map
|
package/dist/pdf.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/types.utils.ts","../src/pdf/pdf-generator.types.ts","../src/pdf/pdf-generator.ts"],"names":[],"mappings":"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","file":"pdf.js","sourcesContent":["export const NO_OP: () => void = () => {};\n\nexport const isNullish = (value: unknown): value is null | undefined => value === null || value === undefined;\n\nexport const isNumber = (value?: unknown | null): value is number => {\n if (isNullish(value)) {\n return false;\n }\n\n if (typeof value !== 'number') {\n return false;\n }\n\n if (isNaN(value)) {\n return false;\n }\n\n return true;\n};\n\nexport const isString = (value?: unknown | null): value is string => {\n if (isNullish(value)) {\n return false;\n }\n\n if (typeof value !== 'string') {\n return false;\n }\n\n return true;\n};\n","import { format } from \"date-fns\";\nimport type { jsPDF, jsPDFOptions } from \"jspdf\";\n\ninterface FontSize {\n title: number;\n header: number;\n normal: number;\n small: number;\n tiny: number;\n}\n\ninterface RgbColor {\n r: number;\n g: number;\n b: number;\n}\n\nexport interface ThemeConfig {\n primaryColor: RgbColor;\n fontSize: FontSize;\n}\n\nexport type PageSize = jsPDF[\"internal\"][\"pageSize\"];\nexport type PageOrientation = jsPDFOptions[\"orientation\"];\nexport type PaperFormat = jsPDFOptions[\"format\"];\n\nexport const DEFAULT_REPORT_FILENAME = \"report.pdf\";\nexport const IMAGE_GAP = 0.5; // space in inches between image and footer\n\nexport const DEFAULT_THEME: ThemeConfig = {\n primaryColor: { r: 0, g: 0, b: 0 }, // black\n fontSize: {\n title: 16,\n header: 10,\n normal: 9,\n small: 8,\n tiny: 7,\n },\n};\n\nexport interface FooterCellArgs {\n align: TableCellHalign;\n currentPage: number;\n totalPages: number;\n}\nexport type FooterCellBuilder = (footerCellArgs: FooterCellArgs) => string;\n\nexport interface PdfOptions {\n orientation?: PageOrientation;\n paperFormat?: PaperFormat;\n hasFooter?: boolean;\n filename?: string;\n theme?: ThemeConfig;\n margin?: number;\n displayAvailableAreaRectangle?: boolean;\n footerCellBuilder?: FooterCellBuilder;\n}\n\nexport const DEFAULT_FOOTER_CELL_BUILDER = ({ align, currentPage, totalPages }: FooterCellArgs): string => {\n if (align === \"left\") return format(new Date(), \"yyyy-MM-dd HH:mm\");\n if (align === \"center\") return \"\";\n return `Page ${currentPage}/${totalPages}`;\n};\n\nexport const DEFAULT_OPTIONS: Readonly<Required<PdfOptions>> = {\n orientation: \"portrait\",\n paperFormat: \"letter\",\n filename: DEFAULT_REPORT_FILENAME,\n theme: DEFAULT_THEME,\n margin: 0.5,\n displayAvailableAreaRectangle: false,\n hasFooter: true,\n footerCellBuilder: DEFAULT_FOOTER_CELL_BUILDER,\n};\n\nexport const Gaps = {\n TINY: 0.02,\n SMALL: 0.04,\n MEDIUM: 0.08,\n LARGE: 0.16,\n X_LARGE: 0.32,\n};\n\nexport const PdfColors: Record<string, FillColorRgbTuple> = {\n LIGHT_GRAY: [240, 240, 240],\n};\n\nexport type TableCellHalign = \"left\" | \"center\" | \"right\";\nexport type TableCellValign = \"top\" | \"middle\" | \"bottom\";\nexport type FontStyle = \"normal\" | \"bold\";\nexport type FillColorRgbTuple = [number, number, number];\n\nexport type CellStyle = {\n cellWidth?: number;\n halign?: TableCellHalign;\n valign?: TableCellValign;\n fontStyle?: FontStyle;\n fillColor?: FillColorRgbTuple;\n};\n\nexport type CellValue = string | number;\n\n/** Cell with rowSpan/colSpan for merged cells (passed through to autoTable). */\nexport interface TableCellDef {\n content: string | string[] | number;\n rowSpan?: number;\n colSpan?: number;\n}\n\nexport type TableCellInput = CellValue | TableCellDef;\n\n/** Parameters for adding a table (generic API, no jspdf-autotable leak). */\nexport interface AddTableArgs {\n /** Each row is an array of cell values or cell defs (with rowSpan/colSpan). */\n body: TableCellInput[][];\n /** Optional header row (single row of cell values). */\n head?: CellValue[];\n /** Y position to start the table (uses generator currentY if omitted). */\n startY?: number;\n\n /** Optional column widths (in same units as doc, e.g. inches). Sum should match available width. */\n columnWidths?: number[];\n /** Optional per-column styles (e.g. halign, fontStyle). Merged with auto-generated cellWidth. */\n columnStyles?: Record<number, CellStyle>;\n\n /** Whether to draw the table outer border (default true). */\n drawLineForTable?: boolean;\n /** Whether to draw cell borders (default true). */\n drawLineForCells?: boolean;\n /** Table border line width (default 0.005). */\n lineWidth?: number;\n /** Table border color as a grayscale value: 0 = black, 200 = light gray, 255 = white (default 0). */\n lineColor?: number;\n}\n\nexport interface AddImageArgs {\n dataUri: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n","import { isNullish, type Dimensions } from \"../utils\";\nimport { jsPDF, type ImageCompression, type ImageFormat } from \"jspdf\";\nimport { autoTable } from \"jspdf-autotable\";\nimport {\n DEFAULT_FOOTER_CELL_BUILDER,\n DEFAULT_OPTIONS,\n IMAGE_GAP,\n type AddImageArgs,\n type AddTableArgs,\n type CellStyle,\n type PageSize,\n type PdfOptions,\n type ThemeConfig,\n} from \"./pdf-generator.types\";\n\nexport class PdfGenerator {\n private doc: jsPDF;\n private options: Required<PdfOptions>;\n private _currentY: number;\n\n constructor(options: PdfOptions = {}) {\n this.options = {\n ...DEFAULT_OPTIONS,\n ...options,\n };\n\n const { orientation, paperFormat } = this.options;\n this.doc = new jsPDF({ orientation, format: paperFormat, unit: \"in\" });\n this.doc.setFont(\"helvetica\");\n\n this._currentY = this.margin;\n }\n\n get font(): string {\n return this.doc.getFont().fontName;\n }\n\n set font(font: string) {\n this.doc.setFont(font);\n }\n\n get theme(): ThemeConfig {\n return this.options.theme;\n }\n\n get filename(): string {\n return this.options.filename;\n }\n\n get margin(): number {\n return this.options.margin;\n }\n\n get pageSize(): PageSize {\n return this.doc.internal.pageSize;\n }\n\n get pageWidth(): number {\n return this.pageSize.width;\n }\n\n get pageHeight(): number {\n return this.pageSize.height;\n }\n\n get fullImageHeight(): number {\n return this.availableHeight - this.footerHeight - IMAGE_GAP;\n }\n\n get availableWidth(): number {\n return this.pageWidth - 2 * this.margin;\n }\n\n get availableHeight(): number {\n return this.pageHeight - 2 * this.margin;\n }\n\n get footerFontSizePoints(): number {\n return this.theme.fontSize.tiny;\n }\n\n /** Current Y position (in doc units) for layout. */\n get currentY(): number {\n return this._currentY;\n }\n\n /** Set current Y position (e.g. after custom content like header or chart). */\n public setCurrentY(y: number): void {\n this._currentY = y;\n }\n\n public addVerticalGap(delta: number): void {\n this._currentY += delta;\n }\n\n public checkTableOverflow(columnWidths: number[]): void {\n const computedSum = columnWidths.reduce((acc, width) => acc + width, 0);\n if (computedSum <= this.availableWidth) return;\n\n const infos = {\n availableWidth: this.availableWidth,\n computedSum,\n columnWidths: JSON.stringify(columnWidths),\n };\n console.warn(\"[checkTableOverflow] table overflow\", infos);\n }\n\n public addTable(addTableArgs: AddTableArgs) {\n const {\n lineWidth = 0.005,\n lineColor = 0,\n drawLineForTable = true,\n drawLineForCells = true,\n head,\n body,\n startY = this._currentY,\n columnWidths,\n } = addTableArgs;\n\n if (body.length === 0) {\n this._currentY = startY;\n return startY;\n }\n\n const cellPadding = 0.05;\n const fontSize = this.theme.fontSize.small;\n const colCount = columnWidths?.length ?? Math.max(...body.map((row) => row.length), 0);\n const widths = columnWidths ?? Array(colCount).fill(this.availableWidth / colCount);\n const columnStyles: Record<number, CellStyle> = {};\n widths.forEach((cellWidth, i) => {\n columnStyles[i] = { cellWidth, ...addTableArgs.columnStyles?.[i] };\n });\n\n const tableLineWidth = drawLineForTable ? lineWidth : 0;\n const cellLineWidth = drawLineForCells ? lineWidth : 0;\n\n autoTable(this.doc, {\n startY,\n head: !isNullish(head) ? [head] : undefined,\n body,\n theme: \"grid\",\n tableLineWidth,\n tableLineColor: lineColor,\n margin: { left: this.margin, right: this.margin },\n bodyStyles: { fontSize, font: this.font, cellPadding, lineWidth: cellLineWidth, lineColor },\n headStyles: { fontSize, font: this.font, cellPadding, lineWidth: cellLineWidth, lineColor },\n columnStyles,\n });\n\n const finalY = (this.doc as unknown as { lastAutoTable: { finalY: number } }).lastAutoTable?.finalY ?? startY;\n this._currentY = finalY;\n }\n\n /**\n * Add an image at the given position and size. Does not update currentY.\n */\n public addImage({ dataUri, x, y, width, height }: AddImageArgs): void {\n const imageFormat: ImageFormat = \"PNG\";\n const imageCompression: ImageCompression = \"NONE\";\n this.doc.addImage(dataUri, imageFormat, x, y, width, height, undefined, imageCompression);\n }\n\n /** Exposes the jsPDF document for custom drawing (e.g. header text, status badge, certification). */\n public getDoc(): jsPDF {\n return this.doc;\n }\n\n public addPage() {\n this.doc.addPage();\n }\n\n public addFullPagePNG(dataURI: string, aspectRatio: number): void {\n const imageFormat: ImageFormat = \"PNG\";\n const imageCompression: ImageCompression = \"NONE\";\n\n const alias = undefined;\n const x = this.margin;\n const y = this.margin;\n const { width, height } = this.calculateImageDimensions(aspectRatio);\n\n this.doc.addImage(dataURI, imageFormat, x, y, width, height, alias, imageCompression);\n\n this._currentY = this.margin + height + IMAGE_GAP;\n }\n\n private calculateImageDimensions(aspectRatio: number): Dimensions {\n const width = this.availableWidth;\n\n // Calculate height based on canvas aspect ratio to maintain proper proportions\n const height = width / aspectRatio;\n\n // Ensure the image doesn't exceed available space (accounting for footer)\n const maxHeight = this.availableHeight - this.footerHeight - IMAGE_GAP;\n const finalHeight = Math.min(height, maxHeight);\n\n // If image is reduced, adjust width to maintain aspect ratio\n const finalWidth = finalHeight < height ? finalHeight * aspectRatio : width;\n\n return { width: finalWidth, height: finalHeight };\n }\n\n public save(): void {\n this.doc.save(this.filename);\n }\n\n private buildFooterColumnStyles() {\n const cellWidth = this.availableWidth / 3;\n\n return {\n 0: { cellWidth, halign: \"left\" as const },\n 1: { cellWidth, halign: \"center\" as const },\n 2: { cellWidth, halign: \"right\" as const },\n };\n }\n\n public renderFooters(): void {\n if (!this.options.hasFooter) {\n console.warn(\"[renderFooters] Footer is disabled\");\n return;\n }\n\n const totalPages = this.doc.getNumberOfPages();\n const columnStyles = this.buildFooterColumnStyles();\n const startY = this.pageHeight - this.margin - this.footerHeight;\n\n const footerCellBuilder = this.options.footerCellBuilder ?? DEFAULT_FOOTER_CELL_BUILDER;\n\n for (let currentPage = 1; currentPage <= totalPages; currentPage++) {\n this.doc.setPage(currentPage);\n this.drawAvailableAreaRectangle();\n const footerCells = ([\"left\", \"center\", \"right\"] as const).map((align) => ({\n content: footerCellBuilder({ align, currentPage, totalPages }),\n }));\n\n autoTable(this.doc, {\n startY,\n body: [footerCells],\n theme: \"plain\",\n bodyStyles: { fontSize: this.footerFontSizePoints, font: this.font },\n margin: { left: this.margin, right: this.margin },\n columnStyles,\n });\n }\n }\n\n /** Footer is a single line; height derived from font size and table padding. */\n get footerHeight(): number {\n if (!this.options.hasFooter) {\n return 0;\n }\n\n const fontSizeInches = this.footerFontSizePoints / 72; // 1 inch = 72 points\n const lineHeight = fontSizeInches * 1.5; // autoTable line height\n return lineHeight + 0.2; // table borders and minimal padding\n }\n\n private drawAvailableAreaRectangle(): void {\n if (!this.options.displayAvailableAreaRectangle) {\n return;\n }\n\n // Save current style state\n const currentDrawColor = this.doc.getDrawColor();\n const currentLineWidth = this.doc.getLineWidth();\n\n try {\n this.doc.setDrawColor(200, 200, 200); // Light gray\n this.doc.setLineWidth(0.01);\n this.doc.rect(\n this.margin,\n this.margin,\n this.availableWidth,\n this.availableHeight,\n \"S\" // Stroke only, no fill\n );\n } catch (error) {\n console.error(\"[drawAvailableAreaRectangle] Error drawing available area rect\", error);\n } finally {\n // Restore previous state\n this.doc.setDrawColor(currentDrawColor);\n this.doc.setLineWidth(currentLineWidth);\n }\n }\n}\n"]}
|
package/dist/web.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/web/browser.utils.ts
|
|
4
|
+
var getCurrentUrl = () => {
|
|
5
|
+
if (typeof window === "undefined") {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
return window.location.href;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/utils/errors.utils.ts
|
|
12
|
+
var getErrorMessage = (error) => {
|
|
13
|
+
if (!error) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
if (typeof error === "string") {
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
if (typeof error === "object" && "message" in error) {
|
|
20
|
+
return error.message;
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(error);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/web/fetch.utils.ts
|
|
26
|
+
var blobToDataUri = (blob) => {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const reader = new FileReader();
|
|
29
|
+
reader.onerror = () => reject(reader.error);
|
|
30
|
+
reader.onloadend = () => {
|
|
31
|
+
const { result } = reader;
|
|
32
|
+
resolve(typeof result === "string" ? result : null);
|
|
33
|
+
};
|
|
34
|
+
reader.readAsDataURL(blob);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
var fetchUrlAsDataUri = async (url) => {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(url);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
console.warn(`[fetchUrlAsDataUri] Response not ok (${response.status}). url: "${url}"`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const blob = await response.blob();
|
|
45
|
+
return blobToDataUri(blob);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
const message = getErrorMessage(e);
|
|
48
|
+
console.error(`[fetchUrlAsDataUri] "${message}". url: "${url}"`, e);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/web/image.utils.ts
|
|
54
|
+
var hasRechartsElements = (svg) => {
|
|
55
|
+
return svg.querySelector("g.recharts-layer") !== null || svg.querySelector("g.recharts-cartesian-axis") !== null || svg.querySelector("g.recharts-cartesian-grid") !== null || svg.querySelector("path.recharts-curve") !== null;
|
|
56
|
+
};
|
|
57
|
+
var findRechartsSvg = (svgs) => {
|
|
58
|
+
for (const svg of Array.from(svgs)) {
|
|
59
|
+
if (hasRechartsElements(svg)) {
|
|
60
|
+
return svg;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
var getElement = (id) => {
|
|
66
|
+
const element = document?.getElementById(id);
|
|
67
|
+
if (!element) {
|
|
68
|
+
console.info(`[getElement] Element with id "${id}" not found`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return element;
|
|
72
|
+
};
|
|
73
|
+
var getRechartsSvgFromElement = ({
|
|
74
|
+
chartElement,
|
|
75
|
+
chartElementId
|
|
76
|
+
}) => {
|
|
77
|
+
const allSvgs = chartElement.querySelectorAll("svg");
|
|
78
|
+
if (allSvgs.length === 0) {
|
|
79
|
+
console.info(`[getRechartsSvgFromElement] SVG element not found inside element with id "${chartElementId}"`);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const rechartSvg = findRechartsSvg(allSvgs);
|
|
83
|
+
if (!rechartSvg) {
|
|
84
|
+
console.info(`[getRechartsSvgFromElement] No Recharts SVG element found inside element with id "${chartElementId}"`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return rechartSvg;
|
|
88
|
+
};
|
|
89
|
+
var getSvgDimensions = (svgElement) => {
|
|
90
|
+
const width = svgElement.clientWidth || parseInt(svgElement.getAttribute("width") || "800", 10);
|
|
91
|
+
const height = svgElement.clientHeight || parseInt(svgElement.getAttribute("height") || "600", 10);
|
|
92
|
+
return { width, height };
|
|
93
|
+
};
|
|
94
|
+
var prepareSvgClone = ({ svgElement, dimensions }) => {
|
|
95
|
+
const clonedSvg = svgElement.cloneNode(true);
|
|
96
|
+
if (!clonedSvg.hasAttribute("width")) {
|
|
97
|
+
clonedSvg.setAttribute("width", dimensions.width.toString());
|
|
98
|
+
}
|
|
99
|
+
if (!clonedSvg.hasAttribute("height")) {
|
|
100
|
+
clonedSvg.setAttribute("height", dimensions.height.toString());
|
|
101
|
+
}
|
|
102
|
+
return clonedSvg;
|
|
103
|
+
};
|
|
104
|
+
var convertSvgToDataUri = (svgXml) => {
|
|
105
|
+
const encoder = new TextEncoder();
|
|
106
|
+
const data = encoder.encode(svgXml);
|
|
107
|
+
let binaryString = "";
|
|
108
|
+
for (let i = 0; i < data.length; i++) {
|
|
109
|
+
binaryString += String.fromCharCode(data[i]);
|
|
110
|
+
}
|
|
111
|
+
const base64 = btoa(binaryString);
|
|
112
|
+
return `data:image/svg+xml;base64,${base64}`;
|
|
113
|
+
};
|
|
114
|
+
var resizeSvgXml = ({ svgXml, targetWidth, targetHeight }) => {
|
|
115
|
+
const widthAttr = `width="${targetWidth}"`;
|
|
116
|
+
const heightAttr = `height="${targetHeight}"`;
|
|
117
|
+
let result = svgXml.replace(/\bwidth=["'][^"']*["']/, widthAttr);
|
|
118
|
+
if (!/\bwidth\s*=/.test(result)) {
|
|
119
|
+
result = result.replace(/<svg/, `<svg ${widthAttr}`);
|
|
120
|
+
}
|
|
121
|
+
result = result.replace(/\bheight=["'][^"']*["']/, heightAttr);
|
|
122
|
+
if (!/\bheight\s*=/.test(result)) {
|
|
123
|
+
result = result.replace(/<svg/, `<svg ${heightAttr}`);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
};
|
|
127
|
+
var getChartAsPngDataUri = async ({
|
|
128
|
+
chartElementId,
|
|
129
|
+
width,
|
|
130
|
+
height
|
|
131
|
+
}) => {
|
|
132
|
+
if (!chartElementId) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const chartElement = getElement(chartElementId);
|
|
136
|
+
if (!chartElement) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const svgElement = getRechartsSvgFromElement({ chartElement, chartElementId });
|
|
140
|
+
if (!svgElement) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const svgXml = new XMLSerializer().serializeToString(svgElement);
|
|
144
|
+
const resizedXml = resizeSvgXml({ svgXml, targetWidth: width, targetHeight: height });
|
|
145
|
+
const svgDataUri = convertSvgToDataUri(resizedXml);
|
|
146
|
+
return svgToPngDataUri({
|
|
147
|
+
svgDataUri,
|
|
148
|
+
dimensions: { width, height },
|
|
149
|
+
backgroundColor: "white"
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
var getSvgAsBase64DataUri = (chartElementId) => {
|
|
153
|
+
const svgXml = getRechartSvgXml(chartElementId);
|
|
154
|
+
if (!svgXml) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return convertSvgToDataUri(svgXml);
|
|
158
|
+
};
|
|
159
|
+
var getRechartSvgXml = (chartElementId) => {
|
|
160
|
+
if (!chartElementId) {
|
|
161
|
+
console.info(`[getRechartSvgXml] No chart element id provided`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const chartElement = getElement(chartElementId);
|
|
165
|
+
if (!chartElement) {
|
|
166
|
+
console.info(`[getRechartSvgXml] Element with id "${chartElementId}" not found`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const svgElement = getRechartsSvgFromElement({ chartElement, chartElementId });
|
|
170
|
+
if (!svgElement) {
|
|
171
|
+
console.info(`[getRechartSvgXml] No SVG element found inside element with id "${chartElementId}"`);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const dimensions = getSvgDimensions(svgElement);
|
|
175
|
+
const clonedSvg = prepareSvgClone({ svgElement, dimensions });
|
|
176
|
+
const svgXml = new XMLSerializer().serializeToString(clonedSvg);
|
|
177
|
+
return svgXml;
|
|
178
|
+
};
|
|
179
|
+
var svgToPngDataUri = async ({
|
|
180
|
+
svgDataUri,
|
|
181
|
+
dimensions,
|
|
182
|
+
backgroundColor
|
|
183
|
+
}) => {
|
|
184
|
+
if (!svgDataUri) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const { width, height } = dimensions;
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const img = new Image();
|
|
190
|
+
img.onload = () => {
|
|
191
|
+
try {
|
|
192
|
+
const canvas = document.createElement("canvas");
|
|
193
|
+
canvas.width = width;
|
|
194
|
+
canvas.height = height;
|
|
195
|
+
const ctx = canvas.getContext("2d");
|
|
196
|
+
if (!ctx) {
|
|
197
|
+
reject(new Error("Failed to get canvas 2D context"));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (backgroundColor) {
|
|
201
|
+
ctx.fillStyle = backgroundColor;
|
|
202
|
+
ctx.fillRect(0, 0, width, height);
|
|
203
|
+
}
|
|
204
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
205
|
+
const pngDataUri = canvas.toDataURL("image/png");
|
|
206
|
+
resolve(pngDataUri);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
reject(error);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
img.onerror = (error) => {
|
|
212
|
+
const message = getErrorMessage(error);
|
|
213
|
+
reject(new Error(`Failed to load SVG image: ${message}`));
|
|
214
|
+
};
|
|
215
|
+
img.src = svgDataUri;
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
exports.blobToDataUri = blobToDataUri;
|
|
220
|
+
exports.convertSvgToDataUri = convertSvgToDataUri;
|
|
221
|
+
exports.fetchUrlAsDataUri = fetchUrlAsDataUri;
|
|
222
|
+
exports.findRechartsSvg = findRechartsSvg;
|
|
223
|
+
exports.getChartAsPngDataUri = getChartAsPngDataUri;
|
|
224
|
+
exports.getCurrentUrl = getCurrentUrl;
|
|
225
|
+
exports.getElement = getElement;
|
|
226
|
+
exports.getRechartSvgXml = getRechartSvgXml;
|
|
227
|
+
exports.getRechartsSvgFromElement = getRechartsSvgFromElement;
|
|
228
|
+
exports.getSvgAsBase64DataUri = getSvgAsBase64DataUri;
|
|
229
|
+
exports.getSvgDimensions = getSvgDimensions;
|
|
230
|
+
exports.hasRechartsElements = hasRechartsElements;
|
|
231
|
+
exports.prepareSvgClone = prepareSvgClone;
|
|
232
|
+
exports.resizeSvgXml = resizeSvgXml;
|
|
233
|
+
exports.svgToPngDataUri = svgToPngDataUri;
|
|
234
|
+
//# sourceMappingURL=web.cjs.map
|
|
235
|
+
//# sourceMappingURL=web.cjs.map
|
package/dist/web.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web/browser.utils.ts","../src/utils/errors.utils.ts","../src/web/fetch.utils.ts","../src/web/image.utils.ts"],"names":[],"mappings":";;;AAAO,IAAM,gBAAgB,MAAc;AACzC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,OAAO,OAAO,QAAA,CAAS,IAAA;AACzB;;;ACNO,IAAM,eAAA,GAAkB,CAAC,KAAA,KAA2B;AACzD,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,SAAA,IAAa,KAAA,EAAO;AACnD,IAAA,OAAQ,KAAA,CAA8B,OAAA;AAAA,EACxC;AAEA,EAAA,OAAO,IAAA,CAAK,UAAU,KAAK,CAAA;AAC7B,CAAA;;;ACTO,IAAM,aAAA,GAAgB,CAAC,IAAA,KAAuC;AACnE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,MAAA,GAAS,IAAI,UAAA,EAAW;AAE9B,IAAA,MAAA,CAAO,OAAA,GAAU,MAAM,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AAC1C,IAAA,MAAA,CAAO,YAAY,MAAM;AACvB,MAAA,MAAM,EAAE,QAAO,GAAI,MAAA;AACnB,MAAA,OAAA,CAAQ,OAAO,MAAA,KAAW,QAAA,GAAW,MAAA,GAAS,IAAI,CAAA;AAAA,IACpD,CAAA;AAEA,IAAA,MAAA,CAAO,cAAc,IAAI,CAAA;AAAA,EAC3B,CAAC,CAAA;AACH;AAMO,IAAM,iBAAA,GAAoB,OAAO,GAAA,KAAwC;AAC9E,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,OAAA,CAAQ,KAAK,CAAA,qCAAA,EAAwC,QAAA,CAAS,MAAM,CAAA,SAAA,EAAY,GAAG,CAAA,CAAA,CAAG,CAAA;AACtF,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,cAAc,IAAI,CAAA;AAAA,EAC3B,SAAS,CAAA,EAAY;AACnB,IAAA,MAAM,OAAA,GAAU,gBAAgB,CAAC,CAAA;AACjC,IAAA,OAAA,CAAQ,MAAM,CAAA,qBAAA,EAAwB,OAAO,CAAA,SAAA,EAAY,GAAG,KAAK,CAAC,CAAA;AAClE,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;ACpCO,IAAM,mBAAA,GAAsB,CAAC,GAAA,KAAgC;AAClE,EAAA,OACE,IAAI,aAAA,CAAc,kBAAkB,MAAM,IAAA,IAC1C,GAAA,CAAI,cAAc,2BAA2B,CAAA,KAAM,IAAA,IACnD,GAAA,CAAI,cAAc,2BAA2B,CAAA,KAAM,QACnD,GAAA,CAAI,aAAA,CAAc,qBAAqB,CAAA,KAAM,IAAA;AAEjD;AAEO,IAAM,eAAA,GAAkB,CAAC,IAAA,KAA4E;AAC1G,EAAA,KAAA,MAAW,GAAA,IAAO,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,EAAG;AAClC,IAAA,IAAI,mBAAA,CAAoB,GAAG,CAAA,EAAG;AAC5B,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAEO,IAAM,UAAA,GAAa,CAAC,EAAA,KAAmC;AAC5D,EAAA,MAAM,OAAA,GAAU,QAAA,EAAU,cAAA,CAAe,EAAE,CAAA;AAC3C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,8BAAA,EAAiC,EAAE,CAAA,WAAA,CAAa,CAAA;AAC7D,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,OAAA;AACT;AAOO,IAAM,4BAA4B,CAAC;AAAA,EACxC,YAAA;AAAA,EACA;AACF,CAAA,KAA2D;AACzD,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,gBAAA,CAAgC,KAAK,CAAA;AAClE,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,0EAAA,EAA6E,cAAc,CAAA,CAAA,CAAG,CAAA;AAC3G,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAA,GAAa,gBAAgB,OAAO,CAAA;AAC1C,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,kFAAA,EAAqF,cAAc,CAAA,CAAA,CAAG,CAAA;AACnH,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,UAAA;AACT;AAEO,IAAM,gBAAA,GAAmB,CAAC,UAAA,KAA0C;AACzE,EAAA,MAAM,KAAA,GAAQ,WAAW,WAAA,IAAe,QAAA,CAAS,WAAW,YAAA,CAAa,OAAO,CAAA,IAAK,KAAA,EAAO,EAAE,CAAA;AAC9F,EAAA,MAAM,MAAA,GAAS,WAAW,YAAA,IAAgB,QAAA,CAAS,WAAW,YAAA,CAAa,QAAQ,CAAA,IAAK,KAAA,EAAO,EAAE,CAAA;AACjG,EAAA,OAAO,EAAE,OAAO,MAAA,EAAO;AACzB;AAyBO,IAAM,eAAA,GAAkB,CAAC,EAAE,UAAA,EAAY,YAAW,KAA0C;AACjG,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,SAAA,CAAU,IAAI,CAAA;AAE3C,EAAA,IAAI,CAAC,SAAA,CAAU,YAAA,CAAa,OAAO,CAAA,EAAG;AACpC,IAAA,SAAA,CAAU,YAAA,CAAa,OAAA,EAAS,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA;AAAA,EAC7D;AACA,EAAA,IAAI,CAAC,SAAA,CAAU,YAAA,CAAa,QAAQ,CAAA,EAAG;AACrC,IAAA,SAAA,CAAU,YAAA,CAAa,QAAA,EAAU,UAAA,CAAW,MAAA,CAAO,UAAU,CAAA;AAAA,EAC/D;AAEA,EAAA,OAAO,SAAA;AACT;AAEO,IAAM,mBAAA,GAAsB,CAAC,MAAA,KAA2B;AAC7D,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA;AAClC,EAAA,IAAI,YAAA,GAAe,EAAA;AACnB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACpC,IAAA,YAAA,IAAgB,MAAA,CAAO,YAAA,CAAa,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,EAC7C;AACA,EAAA,MAAM,MAAA,GAAS,KAAK,YAAY,CAAA;AAChC,EAAA,OAAO,6BAA6B,MAAM,CAAA,CAAA;AAC5C;AAYO,IAAM,eAAe,CAAC,EAAE,MAAA,EAAQ,WAAA,EAAa,cAAa,KAAgC;AAC/F,EAAA,MAAM,SAAA,GAAY,UAAU,WAAW,CAAA,CAAA,CAAA;AACvC,EAAA,MAAM,UAAA,GAAa,WAAW,YAAY,CAAA,CAAA,CAAA;AAE1C,EAAA,IAAI,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,wBAAA,EAA0B,SAAS,CAAA;AAC/D,EAAA,IAAI,CAAC,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA,EAAG;AAC/B,IAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,CAAA,KAAA,EAAQ,SAAS,CAAA,CAAE,CAAA;AAAA,EACrD;AAEA,EAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,yBAAA,EAA2B,UAAU,CAAA;AAC7D,EAAA,IAAI,CAAC,cAAA,CAAe,IAAA,CAAK,MAAM,CAAA,EAAG;AAChC,IAAA,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,CAAA;AAAA,EACtD;AAEA,EAAA,OAAO,MAAA;AACT;AAYO,IAAM,uBAAuB,OAAO;AAAA,EACzC,cAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,KAAwD;AACtD,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAmC,WAAW,cAAc,CAAA;AAClE,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAA,GAAmC,yBAAA,CAA0B,EAAE,YAAA,EAAc,gBAAgB,CAAA;AACnG,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAiB,IAAI,aAAA,EAAc,CAAE,kBAAkB,UAAU,CAAA;AACvE,EAAA,MAAM,UAAA,GAAqB,aAAa,EAAE,MAAA,EAAQ,aAAa,KAAA,EAAO,YAAA,EAAc,QAAQ,CAAA;AAC5F,EAAA,MAAM,UAAA,GAAqB,oBAAoB,UAAU,CAAA;AAEzD,EAAA,OAAO,eAAA,CAAgB;AAAA,IACrB,UAAA;AAAA,IACA,UAAA,EAAY,EAAE,KAAA,EAAO,MAAA,EAAO;AAAA,IAC5B,eAAA,EAAiB;AAAA,GAClB,CAAA;AACH;AAOO,IAAM,qBAAA,GAAwB,CAAC,cAAA,KAA0C;AAC9E,EAAA,MAAM,MAAA,GAAS,iBAAiB,cAAc,CAAA;AAC9C,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,oBAAoB,MAAM,CAAA;AACnC;AAEO,IAAM,gBAAA,GAAmB,CAAC,cAAA,KAA0C;AACzE,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,OAAA,CAAQ,KAAK,CAAA,+CAAA,CAAiD,CAAA;AAC9D,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAmC,WAAW,cAAc,CAAA;AAClE,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,oCAAA,EAAuC,cAAc,CAAA,WAAA,CAAa,CAAA;AAC/E,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAA,GAAmC,yBAAA,CAA0B,EAAE,YAAA,EAAc,gBAAgB,CAAA;AACnG,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,gEAAA,EAAmE,cAAc,CAAA,CAAA,CAAG,CAAA;AACjG,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAA,GAAa,iBAAiB,UAAU,CAAA;AAC9C,EAAA,MAAM,SAAA,GAA2B,eAAA,CAAgB,EAAE,UAAA,EAAY,YAAY,CAAA;AAC3E,EAAA,MAAM,MAAA,GAAiB,IAAI,aAAA,EAAc,CAAE,kBAAkB,SAAS,CAAA;AAEtE,EAAA,OAAO,MAAA;AACT;AAQO,IAAM,kBAAkB,OAAO;AAAA,EACpC,UAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,KAA4C;AAC1C,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAI,UAAA;AAE1B,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,GAAA,GAAM,IAAI,KAAA,EAAM;AAEtB,IAAA,GAAA,CAAI,SAAS,MAAM;AACjB,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,QAAA,MAAA,CAAO,KAAA,GAAQ,KAAA;AACf,QAAA,MAAA,CAAO,MAAA,GAAS,MAAA;AAEhB,QAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,GAAA,EAAK;AACR,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,iCAAiC,CAAC,CAAA;AACnD,UAAA;AAAA,QACF;AAEA,QAAA,IAAI,eAAA,EAAiB;AACnB,UAAA,GAAA,CAAI,SAAA,GAAY,eAAA;AAChB,UAAA,GAAA,CAAI,QAAA,CAAS,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,MAAM,CAAA;AAAA,QAClC;AAEA,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,CAAA,EAAG,CAAA,EAAG,OAAO,MAAM,CAAA;AACtC,QAAA,MAAM,UAAA,GAAa,MAAA,CAAO,SAAA,CAAU,WAAW,CAAA;AAC/C,QAAA,OAAA,CAAQ,UAAU,CAAA;AAAA,MACpB,SAAS,KAAA,EAAO;AACd,QAAA,MAAA,CAAO,KAAK,CAAA;AAAA,MACd;AAAA,IACF,CAAA;AAEA,IAAA,GAAA,CAAI,OAAA,GAAU,CAAC,KAAA,KAAU;AACvB,MAAA,MAAM,OAAA,GAAU,gBAAgB,KAAK,CAAA;AACrC,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,OAAO,EAAE,CAAC,CAAA;AAAA,IAC1D,CAAA;AAEA,IAAA,GAAA,CAAI,GAAA,GAAM,UAAA;AAAA,EACZ,CAAC,CAAA;AACH","file":"web.cjs","sourcesContent":["export const getCurrentUrl = (): string => {\n if (typeof window === \"undefined\") {\n return \"\";\n }\n\n return window.location.href;\n};\n","export const getErrorMessage = (error: unknown): string => {\n if (!error) {\n return \"\";\n }\n\n if (typeof error === \"string\") {\n return error;\n }\n\n if (typeof error === \"object\" && \"message\" in error) {\n return (error as { message: string }).message;\n }\n\n return JSON.stringify(error);\n};\n","import { getErrorMessage } from \"../utils\";\n\n/**\n * Converts a Blob to a data URI string.\n */\nexport const blobToDataUri = (blob: Blob): Promise<string | null> => {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => reject(reader.error);\n reader.onloadend = () => {\n const { result } = reader;\n resolve(typeof result === \"string\" ? result : null);\n };\n\n reader.readAsDataURL(blob);\n });\n};\n\n/**\n * Fetches a resource by URL and returns its content as a data URI.\n * Content type is inferred from the response Content-Type header when possible.\n */\nexport const fetchUrlAsDataUri = async (url: string): Promise<string | null> => {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[fetchUrlAsDataUri] Response not ok (${response.status}). url: \"${url}\"`);\n return null;\n }\n\n const blob = await response.blob();\n return blobToDataUri(blob);\n } catch (e: unknown) {\n const message = getErrorMessage(e);\n console.error(`[fetchUrlAsDataUri] \"${message}\". url: \"${url}\"`, e);\n return null;\n }\n};\n","import { getErrorMessage, type Dimensions } from \"../utils\";\n\nexport const hasRechartsElements = (svg: SVGSVGElement): boolean => {\n return (\n svg.querySelector(\"g.recharts-layer\") !== null ||\n svg.querySelector(\"g.recharts-cartesian-axis\") !== null ||\n svg.querySelector(\"g.recharts-cartesian-grid\") !== null ||\n svg.querySelector(\"path.recharts-curve\") !== null\n );\n};\n\nexport const findRechartsSvg = (svgs: NodeListOf<SVGSVGElement> | SVGSVGElement[]): SVGSVGElement | null => {\n for (const svg of Array.from(svgs)) {\n if (hasRechartsElements(svg)) {\n return svg;\n }\n }\n\n return null;\n};\n\nexport const getElement = (id: string): HTMLElement | null => {\n const element = document?.getElementById(id);\n if (!element) {\n console.info(`[getElement] Element with id \"${id}\" not found`);\n return null;\n }\n\n return element;\n};\n\nexport interface GetRechartsSvgFromElementArgs {\n chartElement: HTMLElement;\n chartElementId: string;\n}\n\nexport const getRechartsSvgFromElement = ({\n chartElement,\n chartElementId,\n}: GetRechartsSvgFromElementArgs): SVGSVGElement | null => {\n const allSvgs = chartElement.querySelectorAll<SVGSVGElement>(\"svg\");\n if (allSvgs.length === 0) {\n console.info(`[getRechartsSvgFromElement] SVG element not found inside element with id \"${chartElementId}\"`);\n return null;\n }\n\n const rechartSvg = findRechartsSvg(allSvgs);\n if (!rechartSvg) {\n console.info(`[getRechartsSvgFromElement] No Recharts SVG element found inside element with id \"${chartElementId}\"`);\n return null;\n }\n\n return rechartSvg;\n};\n\nexport const getSvgDimensions = (svgElement: SVGSVGElement): Dimensions => {\n const width = svgElement.clientWidth || parseInt(svgElement.getAttribute(\"width\") || \"800\", 10);\n const height = svgElement.clientHeight || parseInt(svgElement.getAttribute(\"height\") || \"600\", 10);\n return { width, height };\n};\n\nexport interface PrepareSvgCloneArgs {\n svgElement: SVGSVGElement;\n dimensions: Dimensions;\n}\n\n/**\n * Clones an SVG element and ensures it has the required width and height attributes.\n *\n * This function is necessary for two main reasons:\n *\n * 1. **Defensive cloning**: Cloning prevents modifying the original SVG element in the DOM.\n * Although `XMLSerializer.serializeToString()` does not modify the element, cloning ensures\n * that no accidental modifications are made to the source element.\n *\n * 2. **Required width/height attributes**: When converting SVG → PNG via `svgToPngDataUri`,\n * the HTML Image element requires the SVG to have explicit intrinsic dimensions.\n * Without width/height attributes, the SVG may not render correctly on the canvas,\n * resulting in an incorrect or empty PNG image.\n *\n * @param svgElement - The original SVG element to clone\n * @param dimensions - The dimensions to apply to the cloned SVG if attributes are missing\n * @returns A new cloned SVG element with guaranteed width/height attributes\n */\nexport const prepareSvgClone = ({ svgElement, dimensions }: PrepareSvgCloneArgs): SVGSVGElement => {\n const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement;\n\n if (!clonedSvg.hasAttribute(\"width\")) {\n clonedSvg.setAttribute(\"width\", dimensions.width.toString());\n }\n if (!clonedSvg.hasAttribute(\"height\")) {\n clonedSvg.setAttribute(\"height\", dimensions.height.toString());\n }\n\n return clonedSvg;\n};\n\nexport const convertSvgToDataUri = (svgXml: string): string => {\n const encoder = new TextEncoder();\n const data = encoder.encode(svgXml);\n let binaryString = \"\";\n for (let i = 0; i < data.length; i++) {\n binaryString += String.fromCharCode(data[i]);\n }\n const base64 = btoa(binaryString);\n return `data:image/svg+xml;base64,${base64}`;\n};\n\nexport interface ResizeSvgXmlArgs {\n svgXml: string;\n targetWidth: number;\n targetHeight: number;\n}\n\n/**\n * Modifies SVG XML to set the root <svg> width and height attributes.\n * Used to resize the SVG before converting to PNG at target dimensions (e.g. from PDF layout).\n */\nexport const resizeSvgXml = ({ svgXml, targetWidth, targetHeight }: ResizeSvgXmlArgs): string => {\n const widthAttr = `width=\"${targetWidth}\"`;\n const heightAttr = `height=\"${targetHeight}\"`;\n\n let result = svgXml.replace(/\\bwidth=[\"'][^\"']*[\"']/, widthAttr);\n if (!/\\bwidth\\s*=/.test(result)) {\n result = result.replace(/<svg/, `<svg ${widthAttr}`);\n }\n\n result = result.replace(/\\bheight=[\"'][^\"']*[\"']/, heightAttr);\n if (!/\\bheight\\s*=/.test(result)) {\n result = result.replace(/<svg/, `<svg ${heightAttr}`);\n }\n\n return result;\n};\n\nexport interface GetChartAsPngDataUriArgs {\n chartElementId: string;\n width: number;\n height: number;\n}\n\n/**\n * Gets the Recharts chart as a PNG data URI at the given dimensions (e.g. from PDF zone).\n * Resizes the SVG via resizeSvgXml before conversion; does not use the SVG's intrinsic dimensions.\n */\nexport const getChartAsPngDataUri = async ({\n chartElementId,\n width,\n height,\n}: GetChartAsPngDataUriArgs): Promise<string | null> => {\n if (!chartElementId) {\n return null;\n }\n\n const chartElement: HTMLElement | null = getElement(chartElementId);\n if (!chartElement) {\n return null;\n }\n\n const svgElement: SVGSVGElement | null = getRechartsSvgFromElement({ chartElement, chartElementId });\n if (!svgElement) {\n return null;\n }\n\n const svgXml: string = new XMLSerializer().serializeToString(svgElement);\n const resizedXml: string = resizeSvgXml({ svgXml, targetWidth: width, targetHeight: height });\n const svgDataUri: string = convertSvgToDataUri(resizedXml);\n\n return svgToPngDataUri({\n svgDataUri,\n dimensions: { width, height },\n backgroundColor: \"white\",\n });\n};\n\n/**\n * Retrieves the SVG from a Recharts chart element and converts it to SVG base64\n * @param chartElementId - The ID of the element containing the chart\n * @returns The SVG encoded in base64 with data URI prefix (data:image/svg+xml;base64,...), or null if an error occurs\n */\nexport const getSvgAsBase64DataUri = (chartElementId: string): string | null => {\n const svgXml = getRechartSvgXml(chartElementId);\n if (!svgXml) {\n return null;\n }\n\n return convertSvgToDataUri(svgXml);\n};\n\nexport const getRechartSvgXml = (chartElementId: string): string | null => {\n if (!chartElementId) {\n console.info(`[getRechartSvgXml] No chart element id provided`);\n return null;\n }\n\n const chartElement: HTMLElement | null = getElement(chartElementId);\n if (!chartElement) {\n console.info(`[getRechartSvgXml] Element with id \"${chartElementId}\" not found`);\n return null;\n }\n\n const svgElement: SVGSVGElement | null = getRechartsSvgFromElement({ chartElement, chartElementId });\n if (!svgElement) {\n console.info(`[getRechartSvgXml] No SVG element found inside element with id \"${chartElementId}\"`);\n return null;\n }\n\n const dimensions = getSvgDimensions(svgElement);\n const clonedSvg: SVGSVGElement = prepareSvgClone({ svgElement, dimensions });\n const svgXml: string = new XMLSerializer().serializeToString(clonedSvg);\n\n return svgXml;\n};\n\nexport interface SvgToPngArgs {\n svgDataUri?: string | null;\n dimensions: Dimensions;\n backgroundColor?: string;\n}\n\nexport const svgToPngDataUri = async ({\n svgDataUri,\n dimensions,\n backgroundColor,\n}: SvgToPngArgs): Promise<string | null> => {\n if (!svgDataUri) {\n return null;\n }\n\n const { width, height } = dimensions;\n\n return new Promise((resolve, reject) => {\n const img = new Image();\n\n img.onload = () => {\n try {\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n reject(new Error(\"Failed to get canvas 2D context\"));\n return;\n }\n\n if (backgroundColor) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, width, height);\n }\n\n ctx.drawImage(img, 0, 0, width, height);\n const pngDataUri = canvas.toDataURL(\"image/png\");\n resolve(pngDataUri);\n } catch (error) {\n reject(error);\n }\n };\n\n img.onerror = (error) => {\n const message = getErrorMessage(error);\n reject(new Error(`Failed to load SVG image: ${message}`));\n };\n\n img.src = svgDataUri;\n });\n};\n"]}
|
package/dist/web.d.cts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { D as Dimensions } from './dimensions.utils-BwIBA5op.cjs';
|
|
2
|
+
|
|
3
|
+
declare const getCurrentUrl: () => string;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Blob to a data URI string.
|
|
7
|
+
*/
|
|
8
|
+
declare const blobToDataUri: (blob: Blob) => Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Fetches a resource by URL and returns its content as a data URI.
|
|
11
|
+
* Content type is inferred from the response Content-Type header when possible.
|
|
12
|
+
*/
|
|
13
|
+
declare const fetchUrlAsDataUri: (url: string) => Promise<string | null>;
|
|
14
|
+
|
|
15
|
+
declare const hasRechartsElements: (svg: SVGSVGElement) => boolean;
|
|
16
|
+
declare const findRechartsSvg: (svgs: NodeListOf<SVGSVGElement> | SVGSVGElement[]) => SVGSVGElement | null;
|
|
17
|
+
declare const getElement: (id: string) => HTMLElement | null;
|
|
18
|
+
interface GetRechartsSvgFromElementArgs {
|
|
19
|
+
chartElement: HTMLElement;
|
|
20
|
+
chartElementId: string;
|
|
21
|
+
}
|
|
22
|
+
declare const getRechartsSvgFromElement: ({ chartElement, chartElementId, }: GetRechartsSvgFromElementArgs) => SVGSVGElement | null;
|
|
23
|
+
declare const getSvgDimensions: (svgElement: SVGSVGElement) => Dimensions;
|
|
24
|
+
interface PrepareSvgCloneArgs {
|
|
25
|
+
svgElement: SVGSVGElement;
|
|
26
|
+
dimensions: Dimensions;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Clones an SVG element and ensures it has the required width and height attributes.
|
|
30
|
+
*
|
|
31
|
+
* This function is necessary for two main reasons:
|
|
32
|
+
*
|
|
33
|
+
* 1. **Defensive cloning**: Cloning prevents modifying the original SVG element in the DOM.
|
|
34
|
+
* Although `XMLSerializer.serializeToString()` does not modify the element, cloning ensures
|
|
35
|
+
* that no accidental modifications are made to the source element.
|
|
36
|
+
*
|
|
37
|
+
* 2. **Required width/height attributes**: When converting SVG → PNG via `svgToPngDataUri`,
|
|
38
|
+
* the HTML Image element requires the SVG to have explicit intrinsic dimensions.
|
|
39
|
+
* Without width/height attributes, the SVG may not render correctly on the canvas,
|
|
40
|
+
* resulting in an incorrect or empty PNG image.
|
|
41
|
+
*
|
|
42
|
+
* @param svgElement - The original SVG element to clone
|
|
43
|
+
* @param dimensions - The dimensions to apply to the cloned SVG if attributes are missing
|
|
44
|
+
* @returns A new cloned SVG element with guaranteed width/height attributes
|
|
45
|
+
*/
|
|
46
|
+
declare const prepareSvgClone: ({ svgElement, dimensions }: PrepareSvgCloneArgs) => SVGSVGElement;
|
|
47
|
+
declare const convertSvgToDataUri: (svgXml: string) => string;
|
|
48
|
+
interface ResizeSvgXmlArgs {
|
|
49
|
+
svgXml: string;
|
|
50
|
+
targetWidth: number;
|
|
51
|
+
targetHeight: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Modifies SVG XML to set the root <svg> width and height attributes.
|
|
55
|
+
* Used to resize the SVG before converting to PNG at target dimensions (e.g. from PDF layout).
|
|
56
|
+
*/
|
|
57
|
+
declare const resizeSvgXml: ({ svgXml, targetWidth, targetHeight }: ResizeSvgXmlArgs) => string;
|
|
58
|
+
interface GetChartAsPngDataUriArgs {
|
|
59
|
+
chartElementId: string;
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Gets the Recharts chart as a PNG data URI at the given dimensions (e.g. from PDF zone).
|
|
65
|
+
* Resizes the SVG via resizeSvgXml before conversion; does not use the SVG's intrinsic dimensions.
|
|
66
|
+
*/
|
|
67
|
+
declare const getChartAsPngDataUri: ({ chartElementId, width, height, }: GetChartAsPngDataUriArgs) => Promise<string | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves the SVG from a Recharts chart element and converts it to SVG base64
|
|
70
|
+
* @param chartElementId - The ID of the element containing the chart
|
|
71
|
+
* @returns The SVG encoded in base64 with data URI prefix (data:image/svg+xml;base64,...), or null if an error occurs
|
|
72
|
+
*/
|
|
73
|
+
declare const getSvgAsBase64DataUri: (chartElementId: string) => string | null;
|
|
74
|
+
declare const getRechartSvgXml: (chartElementId: string) => string | null;
|
|
75
|
+
interface SvgToPngArgs {
|
|
76
|
+
svgDataUri?: string | null;
|
|
77
|
+
dimensions: Dimensions;
|
|
78
|
+
backgroundColor?: string;
|
|
79
|
+
}
|
|
80
|
+
declare const svgToPngDataUri: ({ svgDataUri, dimensions, backgroundColor, }: SvgToPngArgs) => Promise<string | null>;
|
|
81
|
+
|
|
82
|
+
export { type GetChartAsPngDataUriArgs, type GetRechartsSvgFromElementArgs, type PrepareSvgCloneArgs, type ResizeSvgXmlArgs, type SvgToPngArgs, blobToDataUri, convertSvgToDataUri, fetchUrlAsDataUri, findRechartsSvg, getChartAsPngDataUri, getCurrentUrl, getElement, getRechartSvgXml, getRechartsSvgFromElement, getSvgAsBase64DataUri, getSvgDimensions, hasRechartsElements, prepareSvgClone, resizeSvgXml, svgToPngDataUri };
|