From 3d0ec27038c104ce1738e2d02f5e4e874ea6b09d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 31 Mar 2025 18:54:57 +0300 Subject: [PATCH] fix(mermaid): fix export to PNG for some diagram types --- src/public/app/services/utils.spec.ts | 22 +++++++++++++ src/public/app/services/utils.ts | 46 +++++++++++++++++++++------ 2 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 src/public/app/services/utils.spec.ts diff --git a/src/public/app/services/utils.spec.ts b/src/public/app/services/utils.spec.ts new file mode 100644 index 000000000..2885fa270 --- /dev/null +++ b/src/public/app/services/utils.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { getSizeFromSvg } from "./utils.js"; + +describe("getSizeFromSvg", () => { + it("parses width & height attribute", () => { + const svg = ``; + const result = getSizeFromSvg(svg); + expect(result).toMatchObject({ + width: 714, + height: 574, + }); + }); + + it("parses viewbox", () => { + const svg = ``; + const result = getSizeFromSvg(svg); + expect(result).toMatchObject({ + width: 872.2750244140625, + height: 655 + }); + }); +}); diff --git a/src/public/app/services/utils.ts b/src/public/app/services/utils.ts index 9d39bb279..bb85729ba 100644 --- a/src/public/app/services/utils.ts +++ b/src/public/app/services/utils.ts @@ -2,6 +2,8 @@ import dayjs from "dayjs"; import { Modal } from "bootstrap"; import type { ViewScope } from "./link.js"; +const SVG_MIME = "image/svg+xml"; + function reloadFrontendApp(reason?: string) { if (reason) { logInfo(`Frontend app reload: ${reason}`); @@ -654,27 +656,23 @@ function triggerDownload(fileName: string, dataUrl: string) { */ function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) { return new Promise((resolve, reject) => { - const mime = "image/svg+xml"; - // First, we need to determine the width and the height from the input SVG. - const svgDocument = (new DOMParser()).parseFromString(svgContent, mime); - const width = svgDocument.documentElement?.getAttribute("width"); - const height = svgDocument.documentElement?.getAttribute("height"); - - if (!width || !height) { + const result = getSizeFromSvg(svgContent); + if (!result) { reject(); return; } // Convert the image to a blob. + const { width, height } = result; const svgBlob = new Blob([ svgContent ], { - type: mime + type: SVG_MIME }) // Create an image element and load the SVG. const imageEl = new Image(); - imageEl.width = parseFloat(width); - imageEl.height = parseFloat(height); + imageEl.width = width; + imageEl.height = height; imageEl.onload = () => { try { // Draw the image with a canvas. @@ -704,6 +702,34 @@ function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) { }); } +export function getSizeFromSvg(svgContent: string) { + const svgDocument = (new DOMParser()).parseFromString(svgContent, SVG_MIME); + + // Try to use width & height attributes if available. + let width = svgDocument.documentElement?.getAttribute("width"); + let height = svgDocument.documentElement?.getAttribute("height"); + + // If not, use the viewbox. + if (!width || !height) { + const viewBox = svgDocument.documentElement?.getAttribute("viewBox"); + if (viewBox) { + const viewBoxParts = viewBox.split(" "); + width = viewBoxParts[2]; + height = viewBoxParts[3]; + } + } + + if (width && height) { + return { + width: parseFloat(width), + height: parseFloat(height) + } + } else { + console.warn("SVG export error", svgDocument.documentElement); + return null; + } +} + /** * Compares two semantic version strings. * Returns: