|
| 1 | +/** |
| 2 | + * Convert an SVG (URL or data URI) into a PNG Blob. |
| 3 | + * |
| 4 | + * @param svgUrl - URL or data URI pointing to the SVG to convert. If remote, |
| 5 | + * the resource must be accessible (CORS allowed) for use in a canvas. |
| 6 | + * @param width - Desired logical width (CSS pixels) of the output image. |
| 7 | + * @param height - Desired logical height (CSS pixels) of the output image. |
| 8 | + * @param scale - Optional pixel scale multiplier (use 2 for high-DPI output). Default: 2. |
| 9 | + * @returns A Promise that resolves with a PNG Blob containing the rendered image. |
| 10 | + * @throws {Error} If the SVG fails to load or if the canvas cannot produce a Blob. |
| 11 | + */ |
| 12 | +export async function svgToPng( |
| 13 | + svgUrl: string, |
| 14 | + width: number, |
| 15 | + height: number, |
| 16 | + scale = 2, |
| 17 | +): Promise<Blob> { |
| 18 | + await document.fonts.ready |
| 19 | + |
| 20 | + const img = new Image() |
| 21 | + img.crossOrigin = 'anonymous' |
| 22 | + |
| 23 | + const loaded = new Promise<void>((resolve, reject) => { |
| 24 | + // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) |
| 25 | + img.onload = () => resolve() |
| 26 | + // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) |
| 27 | + img.onerror = () => reject(new Error(`Failed to load SVG: ${svgUrl}`)) |
| 28 | + }) |
| 29 | + |
| 30 | + img.src = svgUrl |
| 31 | + await loaded |
| 32 | + |
| 33 | + const canvas = document.createElement('canvas') |
| 34 | + canvas.width = width * scale |
| 35 | + canvas.height = height * scale |
| 36 | + |
| 37 | + const ctx = canvas.getContext('2d')! |
| 38 | + ctx.scale(scale, scale) |
| 39 | + ctx.drawImage(img, 0, 0, width, height) |
| 40 | + |
| 41 | + return new Promise<Blob>((resolve, reject) => { |
| 42 | + canvas.toBlob(blob => { |
| 43 | + if (blob) resolve(blob) |
| 44 | + else reject(new Error('Canvas toBlob failed')) |
| 45 | + }, 'image/png') |
| 46 | + }) |
| 47 | +} |
0 commit comments