diff --git a/packages/excalidraw/data/blob.ts b/packages/excalidraw/data/blob.ts index 435009884..2293f860c 100644 --- a/packages/excalidraw/data/blob.ts +++ b/packages/excalidraw/data/blob.ts @@ -5,6 +5,7 @@ import { clearElementsForExport } from "../element"; import type { ExcalidrawElement, FileId } from "../element/types"; import { CanvasError, ImageSceneDataError } from "../errors"; import { calculateScrollCenter } from "../scene"; +import { decodeSvgBase64Payload } from "../scene/export"; import type { AppState, DataURL, LibraryItem } from "../types"; import type { ValueOf } from "../utility-types"; import { bytesToHexString, isPromiseLike } from "../utils"; @@ -47,7 +48,7 @@ const parseFileContents = async (blob: Blob | File): Promise => { } if (blob.type === MIME_TYPES.svg) { try { - return (await import("./image")).decodeSvgMetadata({ + return decodeSvgBase64Payload({ svg: contents, }); } catch (error: any) { diff --git a/packages/excalidraw/data/image.ts b/packages/excalidraw/data/image.ts index 02d059fd3..0359a9c32 100644 --- a/packages/excalidraw/data/image.ts +++ b/packages/excalidraw/data/image.ts @@ -1,7 +1,7 @@ import decodePng from "png-chunks-extract"; import tEXt from "png-chunk-text"; import encodePng from "png-chunks-encode"; -import { stringToBase64, encode, decode, base64ToString } from "./encode"; +import { encode, decode } from "./encode"; import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { blobToArrayBuffer } from "./blob"; @@ -67,56 +67,3 @@ export const decodePngMetadata = async (blob: Blob) => { } throw new Error("INVALID"); }; - -// ----------------------------------------------------------------------------- -// SVG -// ----------------------------------------------------------------------------- - -export const encodeSvgMetadata = ({ text }: { text: string }) => { - const base64 = stringToBase64( - JSON.stringify(encode({ text })), - true /* is already byte string */, - ); - - let metadata = ""; - metadata += ``; - metadata += ``; - metadata += ""; - metadata += base64; - metadata += ""; - return metadata; -}; - -export const decodeSvgMetadata = ({ svg }: { svg: string }) => { - if (svg.includes(`payload-type:${MIME_TYPES.excalidraw}`)) { - const match = svg.match( - /\s*(.+?)\s*/, - ); - if (!match) { - throw new Error("INVALID"); - } - const versionMatch = svg.match(//); - const version = versionMatch?.[1] || "1"; - const isByteString = version !== "1"; - - try { - const json = base64ToString(match[1], isByteString); - const encodedData = JSON.parse(json); - if (!("encoded" in encodedData)) { - // legacy, un-encoded scene JSON - if ( - "type" in encodedData && - encodedData.type === EXPORT_DATA_TYPES.excalidraw - ) { - return json; - } - throw new Error("FAILED"); - } - return decode(encodedData); - } catch (error: any) { - console.error(error); - throw new Error("FAILED"); - } - } - throw new Error("INVALID"); -}; diff --git a/packages/excalidraw/renderer/staticSvgScene.ts b/packages/excalidraw/renderer/staticSvgScene.ts index 5570ad8c3..e6cbb8d7c 100644 --- a/packages/excalidraw/renderer/staticSvgScene.ts +++ b/packages/excalidraw/renderer/staticSvgScene.ts @@ -449,7 +449,7 @@ const renderElementToSvg = ( symbol.appendChild(image); - root.prepend(symbol); + (root.querySelector("defs") || root).prepend(symbol); } const use = svgRoot.ownerDocument!.createElementNS(SVG_NS, "use"); diff --git a/packages/excalidraw/scene/export.ts b/packages/excalidraw/scene/export.ts index c4ab1b865..7b3325690 100644 --- a/packages/excalidraw/scene/export.ts +++ b/packages/excalidraw/scene/export.ts @@ -18,6 +18,8 @@ import { SVG_NS, THEME, THEME_FILTER, + MIME_TYPES, + EXPORT_DATA_TYPES, } from "../constants"; import { getDefaultAppState } from "../appState"; import { serializeAsJSON } from "../data/json"; @@ -39,8 +41,7 @@ import type { RenderableElementsMap } from "./types"; import { syncInvalidIndices } from "../fractionalIndex"; import { renderStaticScene } from "../renderer/staticScene"; import { Fonts } from "../fonts"; - -const SVG_EXPORT_TAG = ``; +import { base64ToString, decode, encode, stringToBase64 } from "../data/encode"; const truncateText = (element: ExcalidrawTextElement, maxWidth: number) => { if (element.width <= maxWidth) { @@ -254,6 +255,13 @@ export const exportToCanvas = async ( return canvas; }; +const createHTMLComment = (text: string) => { + // surrounding with spaces to maintain prettified consistency with previous + // iterations + // + return document.createComment(` ${text} `); +}; + export const exportToSvg = async ( elements: readonly NonDeletedExcalidrawElement[], appState: { @@ -302,87 +310,128 @@ export const exportToSvg = async ( exportPadding = 0; } - let metadata = ""; + const [minX, minY, width, height] = getCanvasSize( + exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), + exportPadding, + ); + + const offsetX = -minX + exportPadding; + const offsetY = -minY + exportPadding; + + // --------------------------------------------------------------------------- + // initialize SVG root element + // --------------------------------------------------------------------------- + + const svgRoot = document.createElementNS(SVG_NS, "svg"); + + svgRoot.setAttribute("version", "1.1"); + svgRoot.setAttribute("xmlns", SVG_NS); + svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`); + svgRoot.setAttribute("width", `${width * exportScale}`); + svgRoot.setAttribute("height", `${height * exportScale}`); + if (exportWithDarkMode) { + svgRoot.setAttribute("filter", THEME_FILTER); + } + + const defsElement = svgRoot.ownerDocument.createElementNS(SVG_NS, "defs"); + + const metadataElement = svgRoot.ownerDocument.createElementNS( + SVG_NS, + "metadata", + ); + + svgRoot.appendChild(createHTMLComment("svg-source:excalidraw")); + svgRoot.appendChild(metadataElement); + svgRoot.appendChild(defsElement); + + // --------------------------------------------------------------------------- + // scene embed + // --------------------------------------------------------------------------- // we need to serialize the "original" elements before we put them through // the tempScene hack which duplicates and regenerates ids if (exportEmbedScene) { try { - metadata = (await import("../data/image")).encodeSvgMetadata({ + encodeSvgBase64Payload({ + metadataElement, // when embedding scene, we want to embed the origionally supplied // elements which don't contain the temp frame labels. // But it also requires that the exportToSvg is being supplied with // only the elements that we're exporting, and no extra. - text: serializeAsJSON(elements, appState, files || {}, "local"), + payload: serializeAsJSON(elements, appState, files || {}, "local"), }); } catch (error: any) { console.error(error); } } - const [minX, minY, width, height] = getCanvasSize( - exportingFrame ? [exportingFrame] : getRootElements(elementsForRender), - exportPadding, - ); + // --------------------------------------------------------------------------- + // frame clip paths + // --------------------------------------------------------------------------- - // initialize SVG root - const svgRoot = document.createElementNS(SVG_NS, "svg"); - svgRoot.setAttribute("version", "1.1"); - svgRoot.setAttribute("xmlns", SVG_NS); - svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`); - svgRoot.setAttribute("width", `${width * exportScale}`); - svgRoot.setAttribute("height", `${height * exportScale}`); - if (exportWithDarkMode) { - svgRoot.setAttribute("filter", THEME_FILTER); - } + const frameElements = getFrameLikeElements(elements); - const offsetX = -minX + exportPadding; - const offsetY = -minY + exportPadding; + if (frameElements.length) { + const elementsMap = arrayToMap(elements); + + for (const frame of frameElements) { + const clipPath = svgRoot.ownerDocument.createElementNS( + SVG_NS, + "clipPath", + ); + + clipPath.setAttribute("id", frame.id); + + const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap); + const cx = (x2 - x1) / 2 - (frame.x - x1); + const cy = (y2 - y1) / 2 - (frame.y - y1); + + const rect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect"); + rect.setAttribute( + "transform", + `translate(${frame.x + offsetX} ${frame.y + offsetY}) rotate(${ + frame.angle + } ${cx} ${cy})`, + ); + rect.setAttribute("width", `${frame.width}`); + rect.setAttribute("height", `${frame.height}`); + + if (!exportingFrame) { + rect.setAttribute("rx", `${FRAME_STYLE.radius}`); + rect.setAttribute("ry", `${FRAME_STYLE.radius}`); + } - const frameElements = getFrameLikeElements(elements); + clipPath.appendChild(rect); - let exportingFrameClipPath = ""; - const elementsMap = arrayToMap(elements); - for (const frame of frameElements) { - const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap); - const cx = (x2 - x1) / 2 - (frame.x - x1); - const cy = (y2 - y1) / 2 - (frame.y - y1); - - exportingFrameClipPath += ` - - - `; + defsElement.appendChild(clipPath); + } } + // --------------------------------------------------------------------------- + // inline font faces + // --------------------------------------------------------------------------- + const fontFaces = !opts?.skipInliningFonts ? await Fonts.generateFontFaceDeclarations(elements) : []; const delimiter = "\n "; // 6 spaces - svgRoot.innerHTML = ` - ${SVG_EXPORT_TAG} - ${metadata} - - - ${exportingFrameClipPath} - - `; + const style = svgRoot.ownerDocument.createElementNS(SVG_NS, "style"); + style.classList.add("style-fonts"); + style.appendChild( + document.createTextNode(`${delimiter}${fontFaces.join(delimiter)}`), + ); + + defsElement.appendChild(style); + + // --------------------------------------------------------------------------- + // background + // --------------------------------------------------------------------------- // render background rect if (appState.exportBackground && viewBackgroundColor) { - const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect"); + const rect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect"); rect.setAttribute("x", "0"); rect.setAttribute("y", "0"); rect.setAttribute("width", `${width}`); @@ -391,6 +440,10 @@ export const exportToSvg = async ( svgRoot.appendChild(rect); } + // --------------------------------------------------------------------------- + // render elements + // --------------------------------------------------------------------------- + const rsvg = rough.svg(svgRoot); const renderEmbeddables = opts?.renderEmbeddables ?? false; @@ -420,9 +473,66 @@ export const exportToSvg = async ( }, ); + // --------------------------------------------------------------------------- + return svgRoot; }; +export const encodeSvgBase64Payload = ({ + payload, + metadataElement, +}: { + payload: string; + metadataElement: SVGMetadataElement; +}) => { + const base64 = stringToBase64( + JSON.stringify(encode({ text: payload })), + true /* is already byte string */, + ); + + metadataElement.appendChild( + createHTMLComment(`payload-type:${MIME_TYPES.excalidraw}`), + ); + metadataElement.appendChild(createHTMLComment("payload-version:2")); + metadataElement.appendChild(createHTMLComment("payload-start")); + metadataElement.appendChild(document.createTextNode(base64)); + metadataElement.appendChild(createHTMLComment("payload-end")); +}; + +export const decodeSvgBase64Payload = ({ svg }: { svg: string }) => { + if (svg.includes(`payload-type:${MIME_TYPES.excalidraw}`)) { + const match = svg.match( + /\s*(.+?)\s*/, + ); + if (!match) { + throw new Error("INVALID"); + } + const versionMatch = svg.match(//); + const version = versionMatch?.[1] || "1"; + const isByteString = version !== "1"; + + try { + const json = base64ToString(match[1], isByteString); + const encodedData = JSON.parse(json); + if (!("encoded" in encodedData)) { + // legacy, un-encoded scene JSON + if ( + "type" in encodedData && + encodedData.type === EXPORT_DATA_TYPES.excalidraw + ) { + return json; + } + throw new Error("FAILED"); + } + return decode(encodedData); + } catch (error: any) { + console.error(error); + throw new Error("FAILED"); + } + } + throw new Error("INVALID"); +}; + // calculate smallest area to fit the contents in const getCanvasSize = ( elements: readonly NonDeletedExcalidrawElement[], diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index a1d45e0a1..b79ced0ed 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -1006,14 +1006,14 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "roundness": { "type": 3, }, - "seed": 1278240551, + "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1278240551, "width": 100, "x": 0, "y": 0, @@ -1042,14 +1042,14 @@ exports[`contextMenu element > right-clicking on a group should select whole gro "roundness": { "type": 3, }, - "seed": 449462985, + "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 449462985, "width": 100, "x": 0, "y": 0, @@ -9792,14 +9792,14 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "roundness": { "type": 3, }, - "seed": 1278240551, + "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 453191, + "versionNonce": 1278240551, "width": 200, "x": 0, "y": 0, @@ -9826,14 +9826,14 @@ exports[`contextMenu element > shows context menu for element > [end of test] el "roundness": { "type": 3, }, - "seed": 449462985, + "seed": 1, "strokeColor": "#1e1e1e", "strokeStyle": "solid", "strokeWidth": 2, "type": "rectangle", "updated": 1, "version": 2, - "versionNonce": 401146281, + "versionNonce": 449462985, "width": 200, "x": 0, "y": 0, diff --git a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap index 1ba16e2ee..7f766e1f9 100644 --- a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap @@ -1,14 +1,11 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = ` +"eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTTU/jMFx1MDAxML33V0TmulrSXCL20Fx1MDAxYrtcdTAwMGKCw+5hi8RcdTAwMDFxMPE0XHUwMDE51bUte0JbUCV+xt72L/JcdTAwMTNcdTAwMTi7JW5SNpFcIvnN15vnl5dRUVxi2jhcdTAwMTDTQsC6klx1MDAxYZWXK/El4k/gXHUwMDAzWsOhSTpcdTAwMDfb+iplNkRuenqqLVx1MDAxNzQ20PSsLMtdXHUwMDExaFiCocBp93wuipf05Vxiqlh6kdJcdTAwMTLwMZdgTVx1MDAxOV0zVHanTe+0QkVcciPjb1x1MDAxZNRcdTAwMDDWXHL1MWlqXHK9wkDeLuCH1dbHiSdjiG9cdTAwMWX6KKtF7W1rVJdDXprgpOdlct5cdTAwMWO1ntEmdWc9WC0xmHG3pzhcdTAwMTng/6vioXVjIETBxlx1MDAxZGqdrJDi8uMyb1x1MDAxMVx1MDAxObpcdTAwMWKVtH3InLxcXMJNXHUwMDE017RadzBcdTAwMWFcdTAwMDXrIZhW3E/7uJh8XHUwMDEzZ3tkm7lcdTAwMDOoXHUwMDFlseyJI+y3NVVfdVxmP9lcdTAwMGWUWsylXHUwMDBlkPWOPC6zVXokW6ckXHLmajSLYVx1MDAxZdtv8UnvZCdcdTAwMTb67d/f14Obs4Zm+Fx1MDAxY1x0TspcdTAwMWV6JZeoo9TnvVx1MDAxNlx1MDAxN1x1MDAxYeu4p9AwP3BcdTAwMDAvS8i278JkXY5W3E+iXHUwMDAxf3xcdTAwMWbWY41G6ttP6cmW7Fx1MDAxZlxiO4LkWzjcXHUwMDFjrjuTf52cp8CWv8lcdTAwMDJCOjcj1qu7UbZcdKrBqjuMwOU1XHUwMDEz9MsquDTyUVx1MDAwZnVcdTAwMTRPXGKr78d/xck8PWK0d0n8IyC5aTvavlx1MDAwM9lcdTAwMDIhXHUwMDFjIn0=😀" +`; + exports[`export > exporting svg containing transformed images > svg export output 1`] = ` -" - - - - - - - " +"" `; diff --git a/packages/excalidraw/tests/export.test.tsx b/packages/excalidraw/tests/export.test.tsx index 65b399dbb..3547b2978 100644 --- a/packages/excalidraw/tests/export.test.tsx +++ b/packages/excalidraw/tests/export.test.tsx @@ -2,16 +2,17 @@ import React from "react"; import { render, waitFor } from "./test-utils"; import { Excalidraw } from "../index"; import { API } from "./helpers/api"; -import { - encodePngMetadata, - encodeSvgMetadata, - decodeSvgMetadata, -} from "../data/image"; +import { encodePngMetadata } from "../data/image"; import { serializeAsJSON } from "../data/json"; -import { exportToSvg } from "../scene/export"; +import { + decodeSvgBase64Payload, + encodeSvgBase64Payload, + exportToSvg, +} from "../scene/export"; import type { FileId } from "../element/types"; import { getDataURL } from "../data/blob"; import { getDefaultAppState } from "../appState"; +import { SVG_NS } from "../constants"; const { h } = window; @@ -62,15 +63,32 @@ describe("export", () => { }); it("test encoding/decoding scene for SVG export", async () => { - const encoded = encodeSvgMetadata({ - text: serializeAsJSON(testElements, h.state, {}, "local"), + const metadataElement = document.createElementNS(SVG_NS, "metadata"); + + encodeSvgBase64Payload({ + metadataElement, + payload: serializeAsJSON(testElements, h.state, {}, "local"), }); - const decoded = JSON.parse(decodeSvgMetadata({ svg: encoded })); + + const decoded = JSON.parse( + decodeSvgBase64Payload({ svg: metadataElement.innerHTML }), + ); expect(decoded.elements).toEqual([ expect.objectContaining({ type: "text", text: "😀" }), ]); }); + it("export svg-embedded scene", async () => { + const svg = await exportToSvg( + testElements, + { ...getDefaultAppState(), exportEmbedScene: true }, + {}, + ); + const svgText = svg.outerHTML; + + expect(svgText).toMatchSnapshot(`svg-embdedded scene export output`); + }); + it("import embedded png (legacy v1)", async () => { await API.drop(await API.loadFile("./fixtures/test_embedded_v1.png")); await waitFor(() => { diff --git a/packages/excalidraw/tests/helpers/api.ts b/packages/excalidraw/tests/helpers/api.ts index 66c940860..fd36a29d1 100644 --- a/packages/excalidraw/tests/helpers/api.ts +++ b/packages/excalidraw/tests/helpers/api.ts @@ -220,7 +220,6 @@ export class API { | "width" | "height" | "type" - | "seed" | "version" | "versionNonce" | "isDeleted" @@ -228,6 +227,7 @@ export class API { | "link" | "updated" > = { + seed: 1, x, y, frameId: rest.frameId ?? null, diff --git a/packages/excalidraw/tests/scene/__snapshots__/export.test.ts.snap b/packages/excalidraw/tests/scene/__snapshots__/export.test.ts.snap index ee168ad2e..fdf514d90 100644 --- a/packages/excalidraw/tests/scene/__snapshots__/export.test.ts.snap +++ b/packages/excalidraw/tests/scene/__snapshots__/export.test.ts.snap @@ -8,15 +8,9 @@ exports[`exportToSvg > with a CJK font 1`] = ` width="120" xmlns="http://www.w3.org/2000/svg" > - - - - - + - - - - - - - with default arguments 1`] = ` width="120" xmlns="http://www.w3.org/2000/svg" > - - - - - + - - - - - - - with default arguments 1`] = ` `; exports[`exportToSvg > with elements that have a link 1`] = ` -" - - - - - - - " +"" `; exports[`exportToSvg > with exportEmbedScene 1`] = ` -" - - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WW2vbMFx1MDAxNH7vrzDaa1llJ2myvGXrLoWxwTIorPRBtY5tYVlyJTmXhfz3SvJiOV7Z81x1MDAxYeJcdTAwMDfD+c5Vn76DvbuIXCJktjWgeYRgk1x1MDAxMs6oXCJrdOnwXHUwMDE1KM2ksK7E21o2KvWRhTH1/OqKS5tQSG3mI4xxm1x1MDAwNFx1MDAxYypcdTAwMTBG27B7a0fRzr+th1GXurpTT99cdTAwMTdZyVx1MDAwNE2e0mr69Wbx+ZdP9UFcdTAwMWJcdTAwMWIzjsedvXXdR9POXjNqXG6LxVx1MDAxOHdYXHUwMDAxLC/MXHUwMDAwJFwi527WgGijZFx0XHUwMDFmJJfKXHLyXHUwMDA2+ye0fiRpmSvZXGJcdTAwMWFi4lx0IY9ZiMlcdTAwMTjnS7PlLVx1MDAwYiQtXHUwMDFhXHUwMDA1aNDh7jDiXHUwMDAw7/K0tCyHLNsyL1x1MDAwNGh9lCNrkjKzXHUwMDFknMrNV99Sz+5DmEqRXG5uXHUwMDFkvaLhvF9Y0D+Fj1x1MDAxY5Z7cEQj0ju+XHUwMDA2oL7bOL6eTN/hWedcdDqIXHUwMDEzPES/SeE1XHUwMDExx7NcdTAwMTnG42R6XHUwMDFk2uhcdTAwMWKrXHUwMDA248tmhGtcYlS7yT5cdTAwMDalXHUwMDFjTdfUlLRJgVxyzkQ5jLPqK1+ofVAzZaSSglwij+8vz1r837VcdTAwMTifrlx1MDAxNoFzVms4a/G1aDE5XS1cdTAwMWHYmN7FSWGW7Lfz9M7g0E+kYtzxPFx0JVxcqi0hXHUwMDE1y5kgPDqudYB//jvMWVx1MDAwYs5yR1x1MDAxY+KQ9VxcljvD7D9F5zayXHUwMDBl3tRORZhcdTAwMDD195VaXHUwMDFl4Esn97dJmJk0Rv5cdTAwMDDdXHUwMDFl0TNyXsJXsoSj81x1MDAxMnZLXHUwMDE4XHUwMDA2P6UltG8vXHUwMDE0ROp6aSyx1t2uJFoxWL9/QfSZf9yX1K+wXHUwMDEzPbhb2u0v9s9cdTAwMGXWRI8ifQ== - - - - - original textoriginal text" + @font-face { font-family: Nunito; src: url(data:font/woff2;base64,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); }original textoriginal text" `; diff --git a/packages/utils/utils.unmocked.test.ts b/packages/utils/utils.unmocked.test.ts index 7c892cb59..e79a981c4 100644 --- a/packages/utils/utils.unmocked.test.ts +++ b/packages/utils/utils.unmocked.test.ts @@ -1,7 +1,8 @@ -import { decodePngMetadata, decodeSvgMetadata } from "../excalidraw/data/image"; import type { ImportedDataState } from "../excalidraw/data/types"; import * as utils from "../utils"; import { API } from "../excalidraw/tests/helpers/api"; +import { decodeSvgBase64Payload } from "../excalidraw/scene/export"; +import { decodePngMetadata } from "../excalidraw/data/image"; // NOTE this test file is using the actual API, unmocked. Hence splitting it // from the other test file, because I couldn't figure out how to test @@ -27,7 +28,7 @@ describe("embedding scene data", () => { const svg = svgNode.outerHTML; - const parsedString = decodeSvgMetadata({ svg }); + const parsedString = decodeSvgBase64Payload({ svg }); const importedData: ImportedDataState = JSON.parse(parsedString); expect(sourceElements.map((x) => x.id)).toEqual(