@opendata-ai/openchart-vanilla 2.12.2 → 2.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-vanilla",
3
- "version": "2.12.2",
3
+ "version": "2.13.0",
4
4
  "description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@floating-ui/dom": "^1.7.6",
53
- "@opendata-ai/openchart-core": "2.12.2",
54
- "@opendata-ai/openchart-engine": "2.12.2",
53
+ "@opendata-ai/openchart-core": "2.13.0",
54
+ "@opendata-ai/openchart-engine": "2.13.0",
55
55
  "d3-force": "^3.0.0",
56
56
  "d3-quadtree": "^3.0.1"
57
57
  },
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Export utility tests.
3
3
  *
4
- * Tests exportSVG, exportCSV, and exportJPG functions directly, verifying SVG string
5
- * validity, CSV formatting with headers and proper escaping, and JPG export interface.
4
+ * Tests exportSVG, exportSVGWithFonts, exportCSV, exportPNG, and exportJPG
5
+ * functions directly, verifying SVG string validity, font embedding,
6
+ * dimension parsing, CSV formatting, and raster export interfaces.
6
7
  */
7
8
 
8
9
  import type { CompileOptions } from '@opendata-ai/openchart-engine';
@@ -10,7 +11,7 @@ import { compileChart } from '@opendata-ai/openchart-engine';
10
11
  import { afterEach, describe, expect, it } from 'vitest';
11
12
  import { createContainer } from '../__test-fixtures__/dom';
12
13
  import { barSpec, lineSpec } from '../__test-fixtures__/specs';
13
- import { exportCSV, exportJPG, exportSVG } from '../export';
14
+ import { exportCSV, exportJPG, exportPNG, exportSVG, exportSVGWithFonts } from '../export';
14
15
  import { renderChartSVG } from '../svg-renderer';
15
16
 
16
17
  // ---------------------------------------------------------------------------
@@ -76,6 +77,57 @@ describe('exportSVG', () => {
76
77
  });
77
78
  });
78
79
 
80
+ // ---------------------------------------------------------------------------
81
+ // exportSVGWithFonts
82
+ // ---------------------------------------------------------------------------
83
+
84
+ describe('exportSVGWithFonts', () => {
85
+ it('returns a promise', () => {
86
+ const svg = renderToSVG();
87
+ const result = exportSVGWithFonts(svg);
88
+ expect(result).toBeInstanceOf(Promise);
89
+ });
90
+
91
+ it('resolves to a valid SVG string', async () => {
92
+ const svg = renderToSVG();
93
+ const result = await exportSVGWithFonts(svg);
94
+ expect(result.startsWith('<svg')).toBe(true);
95
+ expect(result.endsWith('</svg>')).toBe(true);
96
+ });
97
+
98
+ it('skips font embedding when embedFonts is false', async () => {
99
+ const svg = renderToSVG();
100
+ const result = await exportSVGWithFonts(svg, { embedFonts: false });
101
+ // Should not contain @font-face (no stylesheets in test env anyway)
102
+ expect(result).not.toContain('@font-face');
103
+ });
104
+
105
+ it('produces valid SVG even without stylesheets in the document', async () => {
106
+ const svg = renderToSVG();
107
+ // In test env, no Google Fonts stylesheets exist, so font collection
108
+ // should gracefully return nothing and the export should still work
109
+ const result = await exportSVGWithFonts(svg);
110
+ expect(result).toContain('viewBox="0 0 600 400"');
111
+ });
112
+ });
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Dimension parsing (via exportPNG which uses getSVGDimensions internally)
116
+ // ---------------------------------------------------------------------------
117
+
118
+ describe('dimension parsing', () => {
119
+ it('exportPNG reads dimensions from viewBox when width/height are absent', () => {
120
+ const svg = renderToSVG();
121
+ // Verify the SVG has viewBox but no explicit width/height
122
+ expect(svg.getAttribute('viewBox')).toBe('0 0 600 400');
123
+ expect(svg.getAttribute('width')).toBeNull();
124
+ // exportPNG should still work (not fall back to 600x400 by accident)
125
+ const result = exportPNG(svg, { dpi: 1, embedFonts: false });
126
+ expect(result).toBeInstanceOf(Promise);
127
+ result.catch(() => {}); // happy-dom canvas limitations
128
+ });
129
+ });
130
+
79
131
  // ---------------------------------------------------------------------------
80
132
  // exportCSV
81
133
  // ---------------------------------------------------------------------------
@@ -149,6 +201,19 @@ describe('exportCSV', () => {
149
201
  });
150
202
  });
151
203
 
204
+ // ---------------------------------------------------------------------------
205
+ // exportPNG
206
+ // ---------------------------------------------------------------------------
207
+
208
+ describe('exportPNG', () => {
209
+ it('returns a Promise when called with a rendered SVG element', () => {
210
+ const svg = renderToSVG();
211
+ const result = exportPNG(svg, { embedFonts: false });
212
+ expect(result).toBeInstanceOf(Promise);
213
+ result.catch(() => {});
214
+ });
215
+ });
216
+
152
217
  // ---------------------------------------------------------------------------
153
218
  // exportJPG
154
219
  // ---------------------------------------------------------------------------
@@ -161,7 +226,7 @@ describe('exportJPG', () => {
161
226
 
162
227
  it('returns a Promise when called with a rendered SVG element', () => {
163
228
  const svg = renderToSVG();
164
- const result = exportJPG(svg);
229
+ const result = exportJPG(svg, { embedFonts: false });
165
230
  expect(result).toBeInstanceOf(Promise);
166
231
  // Clean up: catch any rejection from happy-dom canvas limitations
167
232
  result.catch(() => {});
@@ -169,7 +234,7 @@ describe('exportJPG', () => {
169
234
 
170
235
  it('accepts quality option between 0 and 1', () => {
171
236
  const svg = renderToSVG();
172
- const result = exportJPG(svg, { quality: 0.5, dpi: 1 });
237
+ const result = exportJPG(svg, { quality: 0.5, dpi: 1, embedFonts: false });
173
238
  expect(result).toBeInstanceOf(Promise);
174
239
  result.catch(() => {});
175
240
  });
package/src/export.ts CHANGED
@@ -1,11 +1,249 @@
1
1
  /**
2
- * Export utilities: serialize charts to SVG, PNG, or CSV.
2
+ * Export utilities: serialize charts to SVG, PNG, JPG, or CSV.
3
3
  *
4
4
  * - SVG: serializes the rendered DOM element via XMLSerializer
5
+ * - SVG with fonts: async version that embeds @font-face data URIs
5
6
  * - PNG: renders SVG to canvas, then extracts as Blob
7
+ * - JPG: same as PNG but with JPEG compression and background fill
6
8
  * - CSV: converts a data array to comma-separated text
7
9
  */
8
10
 
11
+ // ---------------------------------------------------------------------------
12
+ // Types
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface SVGExportOptions {
16
+ /** Embed fonts as base64 data URIs in the SVG. Defaults to true. */
17
+ embedFonts?: boolean;
18
+ }
19
+
20
+ export interface PNGExportOptions extends SVGExportOptions {
21
+ /** DPI scaling factor. Defaults to 2 for retina-quality output. */
22
+ dpi?: number;
23
+ }
24
+
25
+ export interface JPGExportOptions extends PNGExportOptions {
26
+ /** JPEG quality from 0 to 1. Defaults to 0.92. */
27
+ quality?: number;
28
+ }
29
+
30
+ interface FontFaceData {
31
+ family: string;
32
+ weight: string;
33
+ style: string;
34
+ base64: string;
35
+ format: string;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Dimension parsing
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * Extract dimensions from an SVG element, trying width/height attributes
44
+ * first, then falling back to viewBox.
45
+ */
46
+ function getSVGDimensions(svg: SVGElement): { width: number; height: number } {
47
+ const w = parseFloat(svg.getAttribute('width') || '');
48
+ const h = parseFloat(svg.getAttribute('height') || '');
49
+ if (w && h) return { width: w, height: h };
50
+
51
+ const vb = svg.getAttribute('viewBox');
52
+ if (vb) {
53
+ const parts = vb.split(/[\s,]+/).map(Number);
54
+ if (parts.length >= 4 && parts[2] && parts[3]) {
55
+ return { width: parts[2], height: parts[3] };
56
+ }
57
+ }
58
+
59
+ return { width: 600, height: 400 };
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Font embedding
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Collect unique font-family + font-weight combos from all <text> elements.
68
+ */
69
+ function collectUsedFonts(svgElement: SVGElement): Map<string, Set<string>> {
70
+ const fonts = new Map<string, Set<string>>();
71
+ const textElements = svgElement.querySelectorAll('text');
72
+
73
+ for (const el of textElements) {
74
+ const family = el.getAttribute('font-family');
75
+ const weight = el.getAttribute('font-weight') || '400';
76
+ if (family) {
77
+ // Take the first font in the stack (e.g., "Inter, sans-serif" → "Inter")
78
+ const primary = family.split(',')[0].trim().replace(/["']/g, '');
79
+ if (!fonts.has(primary)) fonts.set(primary, new Set());
80
+ fonts.get(primary)!.add(String(weight));
81
+ }
82
+ }
83
+
84
+ return fonts;
85
+ }
86
+
87
+ /**
88
+ * Find @font-face rules in document stylesheets that match the requested fonts.
89
+ * Returns the src URLs for .woff2 files.
90
+ */
91
+ function findFontFaceRules(
92
+ usedFonts: Map<string, Set<string>>,
93
+ ): Array<{ family: string; weight: string; style: string; url: string; format: string }> {
94
+ const results: Array<{
95
+ family: string;
96
+ weight: string;
97
+ style: string;
98
+ url: string;
99
+ format: string;
100
+ }> = [];
101
+
102
+ try {
103
+ for (const sheet of document.styleSheets) {
104
+ let rules: CSSRuleList;
105
+ try {
106
+ rules = sheet.cssRules;
107
+ } catch {
108
+ // Cross-origin stylesheet, skip
109
+ continue;
110
+ }
111
+
112
+ for (const rule of rules) {
113
+ if (!(rule instanceof CSSFontFaceRule)) continue;
114
+
115
+ const familyRaw = rule.style.getPropertyValue('font-family').replace(/["']/g, '').trim();
116
+ const weight = rule.style.getPropertyValue('font-weight') || '400';
117
+ const style = rule.style.getPropertyValue('font-style') || 'normal';
118
+ const src = rule.style.getPropertyValue('src');
119
+
120
+ const weights = usedFonts.get(familyRaw);
121
+ if (!weights) continue;
122
+
123
+ // Check if this weight is used (handle ranges like "100 900")
124
+ const weightMatch = weights.has(weight) || weight.includes(' ');
125
+ if (!weightMatch) continue;
126
+
127
+ // Extract woff2 URL from src descriptor
128
+ const woff2Match = src.match(/url\(["']?([^"')]+\.woff2[^"')]*?)["']?\)/);
129
+ if (woff2Match) {
130
+ results.push({
131
+ family: familyRaw,
132
+ weight,
133
+ style,
134
+ url: woff2Match[1],
135
+ format: 'woff2',
136
+ });
137
+ }
138
+ }
139
+ }
140
+ } catch {
141
+ // Stylesheet access failed entirely, return empty
142
+ }
143
+
144
+ return results;
145
+ }
146
+
147
+ /**
148
+ * Fetch font files and convert to base64 data URIs.
149
+ */
150
+ async function fetchFontsAsBase64(
151
+ fontRules: Array<{ family: string; weight: string; style: string; url: string; format: string }>,
152
+ ): Promise<FontFaceData[]> {
153
+ const results: FontFaceData[] = [];
154
+
155
+ const fetches = fontRules.map(async (rule) => {
156
+ try {
157
+ const response = await fetch(rule.url);
158
+ if (!response.ok) return;
159
+ const buffer = await response.arrayBuffer();
160
+ const base64 = arrayBufferToBase64(buffer);
161
+ results.push({
162
+ family: rule.family,
163
+ weight: rule.weight,
164
+ style: rule.style,
165
+ base64,
166
+ format: rule.format,
167
+ });
168
+ } catch {
169
+ // Font fetch failed (CORS, network, etc.) - skip this font
170
+ }
171
+ });
172
+
173
+ await Promise.all(fetches);
174
+ return results;
175
+ }
176
+
177
+ function arrayBufferToBase64(buffer: ArrayBuffer): string {
178
+ let binary = '';
179
+ const bytes = new Uint8Array(buffer);
180
+ for (let i = 0; i < bytes.byteLength; i++) {
181
+ binary += String.fromCharCode(bytes[i]);
182
+ }
183
+ return btoa(binary);
184
+ }
185
+
186
+ /**
187
+ * Inject @font-face rules with base64 data URIs into the SVG's <defs>.
188
+ */
189
+ function injectFontsIntoSVG(svgElement: SVGElement, fonts: FontFaceData[]): void {
190
+ if (fonts.length === 0) return;
191
+
192
+ const cssRules = fonts
193
+ .map(
194
+ (f) =>
195
+ `@font-face { font-family: '${f.family}'; font-weight: ${f.weight}; font-style: ${f.style}; src: url(data:font/${f.format};base64,${f.base64}) format('${f.format}'); }`,
196
+ )
197
+ .join('\n');
198
+
199
+ const ns = 'http://www.w3.org/2000/svg';
200
+ let defs = svgElement.querySelector('defs');
201
+ if (!defs) {
202
+ defs = document.createElementNS(ns, 'defs');
203
+ svgElement.insertBefore(defs, svgElement.firstChild);
204
+ }
205
+
206
+ const styleEl = document.createElementNS(ns, 'style');
207
+ styleEl.textContent = cssRules;
208
+ defs.insertBefore(styleEl, defs.firstChild);
209
+ }
210
+
211
+ /**
212
+ * Embed fonts into an SVG element by finding matching @font-face rules
213
+ * in the page's stylesheets, fetching the font files, and injecting
214
+ * them as base64 data URIs.
215
+ *
216
+ * Modifies the SVG element in place. Call this before serialization.
217
+ * If font fetching fails for any font, that font is silently skipped
218
+ * and the export proceeds with system font fallback for that face.
219
+ */
220
+ async function embedFonts(svgElement: SVGElement): Promise<void> {
221
+ const usedFonts = collectUsedFonts(svgElement);
222
+ if (usedFonts.size === 0) return;
223
+
224
+ const fontRules = findFontFaceRules(usedFonts);
225
+ if (fontRules.length === 0) return;
226
+
227
+ const fontData = await fetchFontsAsBase64(fontRules);
228
+ injectFontsIntoSVG(svgElement, fontData);
229
+ }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // SVG background color
233
+ // ---------------------------------------------------------------------------
234
+
235
+ /**
236
+ * Read the chart's background color from its first rect element.
237
+ */
238
+ function getSVGBackgroundColor(svgElement: SVGElement): string {
239
+ const firstRect = svgElement.querySelector('rect');
240
+ return firstRect?.getAttribute('fill') || '#ffffff';
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // SVG export
245
+ // ---------------------------------------------------------------------------
246
+
9
247
  /**
10
248
  * Serialize an SVG element to an XML string.
11
249
  *
@@ -17,24 +255,53 @@ export function exportSVG(svgElement: SVGElement): string {
17
255
  return serializer.serializeToString(svgElement);
18
256
  }
19
257
 
20
- export interface PNGExportOptions {
21
- /** DPI scaling factor. Defaults to 2 for retina-quality output. */
22
- dpi?: number;
258
+ /**
259
+ * Serialize an SVG element with embedded fonts to an XML string.
260
+ *
261
+ * Collects font-family declarations from the SVG's text elements,
262
+ * finds matching @font-face rules in the page's stylesheets, fetches
263
+ * the font files, and embeds them as base64 data URIs. The resulting
264
+ * SVG is self-contained and renders correctly without external fonts.
265
+ *
266
+ * @param svgElement - The rendered SVG element to serialize.
267
+ * @param options - Export options.
268
+ * @returns A Promise resolving to the SVG markup as a string.
269
+ */
270
+ export async function exportSVGWithFonts(
271
+ svgElement: SVGElement,
272
+ options?: SVGExportOptions,
273
+ ): Promise<string> {
274
+ const shouldEmbed = options?.embedFonts ?? true;
275
+ if (shouldEmbed) {
276
+ await embedFonts(svgElement);
277
+ }
278
+ return exportSVG(svgElement);
23
279
  }
24
280
 
281
+ // ---------------------------------------------------------------------------
282
+ // Raster export (PNG / JPG)
283
+ // ---------------------------------------------------------------------------
284
+
25
285
  /**
26
286
  * Render an SVG element to a PNG Blob via a canvas.
27
287
  *
288
+ * Embeds fonts by default so the exported image matches on-screen rendering.
289
+ * Set `embedFonts: false` to skip font embedding for faster exports.
290
+ *
28
291
  * @param svgElement - The rendered SVG element.
29
- * @param options - Optional DPI scaling.
292
+ * @param options - Optional DPI scaling and font embedding.
30
293
  * @returns A Promise resolving to the PNG Blob.
31
294
  */
32
295
  export async function exportPNG(svgElement: SVGElement, options?: PNGExportOptions): Promise<Blob> {
33
296
  const dpi = options?.dpi ?? 2;
34
- const svgString = exportSVG(svgElement);
297
+ const shouldEmbed = options?.embedFonts ?? true;
298
+
299
+ if (shouldEmbed) {
300
+ await embedFonts(svgElement);
301
+ }
35
302
 
36
- const width = parseFloat(svgElement.getAttribute('width') || '600');
37
- const height = parseFloat(svgElement.getAttribute('height') || '400');
303
+ const svgString = exportSVG(svgElement);
304
+ const { width, height } = getSVGDimensions(svgElement);
38
305
 
39
306
  const canvas = document.createElement('canvas');
40
307
  canvas.width = width * dpi;
@@ -74,29 +341,28 @@ export async function exportPNG(svgElement: SVGElement, options?: PNGExportOptio
74
341
  });
75
342
  }
76
343
 
77
- export interface JPGExportOptions extends PNGExportOptions {
78
- /** JPEG quality from 0 to 1. Defaults to 0.92. */
79
- quality?: number;
80
- }
81
-
82
344
  /**
83
345
  * Render an SVG element to a JPEG Blob via a canvas.
84
346
  *
85
347
  * Same pipeline as exportPNG but outputs JPEG with configurable quality.
86
- * The canvas is filled with white before drawing to avoid transparent
87
- * backgrounds rendering as black in JPEG format.
348
+ * The canvas is filled with the chart's background color before drawing
349
+ * to avoid transparent backgrounds rendering as black in JPEG format.
88
350
  *
89
351
  * @param svgElement - The rendered SVG element.
90
- * @param options - Optional DPI scaling and JPEG quality.
352
+ * @param options - Optional DPI scaling, JPEG quality, and font embedding.
91
353
  * @returns A Promise resolving to the JPEG Blob.
92
354
  */
93
355
  export async function exportJPG(svgElement: SVGElement, options?: JPGExportOptions): Promise<Blob> {
94
356
  const dpi = options?.dpi ?? 2;
95
357
  const quality = options?.quality ?? 0.92;
96
- const svgString = exportSVG(svgElement);
358
+ const shouldEmbed = options?.embedFonts ?? true;
359
+
360
+ if (shouldEmbed) {
361
+ await embedFonts(svgElement);
362
+ }
97
363
 
98
- const width = parseFloat(svgElement.getAttribute('width') || '600');
99
- const height = parseFloat(svgElement.getAttribute('height') || '400');
364
+ const svgString = exportSVG(svgElement);
365
+ const { width, height } = getSVGDimensions(svgElement);
100
366
 
101
367
  const canvas = document.createElement('canvas');
102
368
  canvas.width = width * dpi;
@@ -107,8 +373,8 @@ export async function exportJPG(svgElement: SVGElement, options?: JPGExportOptio
107
373
  throw new Error('Canvas 2D context not available');
108
374
  }
109
375
 
110
- // Fill white background since JPEG doesn't support transparency
111
- ctx.fillStyle = '#ffffff';
376
+ // Fill with the chart's actual background color (not hardcoded white)
377
+ ctx.fillStyle = getSVGBackgroundColor(svgElement);
112
378
  ctx.fillRect(0, 0, canvas.width, canvas.height);
113
379
 
114
380
  ctx.scale(dpi, dpi);
@@ -144,6 +410,10 @@ export async function exportJPG(svgElement: SVGElement, options?: JPGExportOptio
144
410
  });
145
411
  }
146
412
 
413
+ // ---------------------------------------------------------------------------
414
+ // CSV export
415
+ // ---------------------------------------------------------------------------
416
+
147
417
  /**
148
418
  * Convert an array of data objects to a CSV string.
149
419
  *
package/src/index.ts CHANGED
@@ -17,9 +17,9 @@ export type {
17
17
  TableSpec,
18
18
  VizSpec,
19
19
  } from '@opendata-ai/openchart-engine';
20
- export type { JPGExportOptions, PNGExportOptions } from './export';
20
+ export type { JPGExportOptions, PNGExportOptions, SVGExportOptions } from './export';
21
21
  // Export utilities
22
- export { exportCSV, exportJPG, exportPNG, exportSVG } from './export';
22
+ export { exportCSV, exportJPG, exportPNG, exportSVG, exportSVGWithFonts } from './export';
23
23
  // Graph simulation worker
24
24
  export { createSimulationWorker } from './graph/simulation-worker-url';
25
25
  export type { GraphInstance, GraphMountOptions } from './graph-mount';
package/src/mount.ts CHANGED
@@ -26,7 +26,15 @@ import type {
26
26
  TooltipContent,
27
27
  } from '@opendata-ai/openchart-core';
28
28
  import { compileChart } from '@opendata-ai/openchart-engine';
29
- import { exportCSV, exportJPG, exportPNG, exportSVG, type JPGExportOptions } from './export';
29
+ import {
30
+ exportCSV,
31
+ exportJPG,
32
+ exportPNG,
33
+ exportSVG,
34
+ exportSVGWithFonts,
35
+ type JPGExportOptions,
36
+ type SVGExportOptions,
37
+ } from './export';
30
38
  import { observeResize } from './resize-observer';
31
39
  import { renderChartSVG } from './svg-renderer';
32
40
  import { createTooltipManager, type TooltipManager } from './tooltip';
@@ -57,10 +65,14 @@ export interface ChartInstance {
57
65
  resize(): void;
58
66
  /** Export the chart. */
59
67
  export(format: 'svg'): string;
68
+ export(format: 'svg-with-fonts', options?: SVGExportOptions): Promise<string>;
60
69
  export(format: 'png', options?: ExportOptions): Promise<Blob>;
61
70
  export(format: 'jpg', options?: ExportOptions): Promise<Blob>;
62
71
  export(format: 'csv'): string;
63
- export(format: 'svg' | 'png' | 'jpg' | 'csv', options?: ExportOptions): string | Promise<Blob>;
72
+ export(
73
+ format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg' | 'csv',
74
+ options?: ExportOptions,
75
+ ): string | Promise<Blob> | Promise<string>;
64
76
  /** Remove all DOM elements and disconnect observers. */
65
77
  destroy(): void;
66
78
  /** The current compiled layout (for hooks / debugging). */
@@ -1571,13 +1583,14 @@ export function createChart(
1571
1583
  }
1572
1584
 
1573
1585
  function doExport(format: 'svg'): string;
1586
+ function doExport(format: 'svg-with-fonts', exportOptions?: SVGExportOptions): Promise<string>;
1574
1587
  function doExport(format: 'png', exportOptions?: ExportOptions): Promise<Blob>;
1575
1588
  function doExport(format: 'jpg', exportOptions?: ExportOptions): Promise<Blob>;
1576
1589
  function doExport(format: 'csv'): string;
1577
1590
  function doExport(
1578
- format: 'svg' | 'png' | 'jpg' | 'csv',
1591
+ format: 'svg' | 'svg-with-fonts' | 'png' | 'jpg' | 'csv',
1579
1592
  exportOptions?: ExportOptions,
1580
- ): string | Promise<Blob> {
1593
+ ): string | Promise<Blob> | Promise<string> {
1581
1594
  if (!svgElement) {
1582
1595
  throw new Error('Chart is not rendered yet');
1583
1596
  }
@@ -1585,6 +1598,8 @@ export function createChart(
1585
1598
  switch (format) {
1586
1599
  case 'svg':
1587
1600
  return exportSVG(svgElement);
1601
+ case 'svg-with-fonts':
1602
+ return exportSVGWithFonts(svgElement, exportOptions);
1588
1603
  case 'png':
1589
1604
  return exportPNG(svgElement, exportOptions);
1590
1605
  case 'jpg':