Przeglądaj źródła

feat: fancyBackgrounds in svg

Arnošt Pleskot 2 lat temu
rodzic
commit
e8cc787edc
4 zmienionych plików z 106 dodań i 16 usunięć
  1. 1 0
      src/data/index.ts
  2. 21 0
      src/element/image.ts
  3. 39 13
      src/scene/export.ts
  4. 45 3
      src/scene/fancyBackground.ts

+ 1 - 0
src/data/index.ts

@@ -47,6 +47,7 @@ export const exportCanvas = async (
         exportPadding,
         exportScale: appState.exportScale,
         exportEmbedScene: appState.exportEmbedScene && type === "svg",
+        fancyBackgroundImageUrl: appState.fancyBackgroundImageUrl,
       },
       files,
     );

+ 21 - 0
src/element/image.ts

@@ -123,3 +123,24 @@ export const normalizeSVG = async (SVGString: string) => {
     return svg.outerHTML;
   }
 };
+
+export const loadSVGElement = (filePath: string) => {
+  return new Promise<SVGSVGElement>((resolve, reject) => {
+    fetch(filePath)
+      .then((response) => response.text())
+      .then((svgString) => {
+        const parser = new DOMParser();
+        const svgDoc = parser.parseFromString(svgString, "image/svg+xml");
+        const svgElement = svgDoc.documentElement;
+
+        if (svgElement instanceof SVGSVGElement) {
+          resolve(svgElement);
+        } else {
+          reject(new Error("Parsed element is not an SVGSVGElement"));
+        }
+      })
+      .catch((error) => {
+        reject(error);
+      });
+  });
+};

+ 39 - 13
src/scene/export.ts

@@ -3,7 +3,7 @@ import { NonDeletedExcalidrawElement } from "../element/types";
 import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
 import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
 import { distance, isOnlyExportingSingleFrame } from "../utils";
-import { AppState, BinaryFiles } from "../types";
+import { AppState, BinaryFiles, DataURL } from "../types";
 import {
   DEFAULT_EXPORT_PADDING,
   FANCY_BG_BORDER_RADIUS,
@@ -19,7 +19,10 @@ import {
   updateImageCache,
 } from "../element/image";
 import Scene from "./Scene";
-import { applyFancyBackground } from "./fancyBackground";
+import {
+  applyFancyBackgroundOnCanvas,
+  applyFancyBackgroundOnSvg,
+} from "./fancyBackground";
 
 export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
 
@@ -93,7 +96,7 @@ export const exportToCanvas = async (
   };
 
   if (exportWithFancyBackground) {
-    await applyFancyBackground({
+    await applyFancyBackgroundOnCanvas({
       canvas,
       fancyBackgroundImageUrl: appState.fancyBackgroundImageUrl!,
       backgroundColor: viewBackgroundColor,
@@ -136,6 +139,7 @@ export const exportToSvg = async (
     exportWithDarkMode?: boolean;
     exportEmbedScene?: boolean;
     renderFrame?: boolean;
+    fancyBackgroundImageUrl: DataURL | null;
   },
   files: BinaryFiles | null,
   opts?: {
@@ -148,7 +152,18 @@ export const exportToSvg = async (
     viewBackgroundColor,
     exportScale = 1,
     exportEmbedScene,
+    exportBackground,
   } = appState;
+
+  const exportWithFancyBackground =
+    exportBackground &&
+    !!appState.fancyBackgroundImageUrl &&
+    elements.length > 0;
+
+  const padding = !exportWithFancyBackground
+    ? exportPadding
+    : (exportPadding + FANCY_BG_PADDING + FANCY_BG_BORDER_RADIUS) * exportScale;
+
   let metadata = "";
   if (exportEmbedScene) {
     try {
@@ -163,7 +178,7 @@ export const exportToSvg = async (
       console.error(error);
     }
   }
-  const [minX, minY, width, height] = getCanvasSize(elements, exportPadding);
+  const [minX, minY, width, height] = getCanvasSize(elements, padding);
 
   // initialize SVG root
   const svgRoot = document.createElementNS(SVG_NS, "svg");
@@ -198,8 +213,8 @@ export const exportToSvg = async (
 
   const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
 
-  const offsetX = -minX + (onlyExportingSingleFrame ? 0 : exportPadding);
-  const offsetY = -minY + (onlyExportingSingleFrame ? 0 : exportPadding);
+  const offsetX = -minX + (onlyExportingSingleFrame ? 0 : padding);
+  const offsetY = -minY + (onlyExportingSingleFrame ? 0 : padding);
 
   const exportingFrame =
     isExportingWholeCanvas || !onlyExportingSingleFrame
@@ -243,13 +258,24 @@ export const exportToSvg = async (
 
   // render background rect
   if (appState.exportBackground && viewBackgroundColor) {
-    const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
-    rect.setAttribute("x", "0");
-    rect.setAttribute("y", "0");
-    rect.setAttribute("width", `${width}`);
-    rect.setAttribute("height", `${height}`);
-    rect.setAttribute("fill", viewBackgroundColor);
-    svgRoot.appendChild(rect);
+    if (appState.fancyBackgroundImageUrl) {
+      await applyFancyBackgroundOnSvg({
+        svgRoot,
+        fancyBackgroundImageUrl:
+          `${appState.fancyBackgroundImageUrl}` as DataURL,
+        backgroundColor: viewBackgroundColor,
+        dimensions: { w: width, h: height },
+        exportScale,
+      });
+    } else {
+      const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
+      rect.setAttribute("x", "0");
+      rect.setAttribute("y", "0");
+      rect.setAttribute("width", `${width}`);
+      rect.setAttribute("height", `${height}`);
+      rect.setAttribute("fill", viewBackgroundColor);
+      svgRoot.appendChild(rect);
+    }
   }
 
   const rsvg = rough.svg(svgRoot);

+ 45 - 3
src/scene/fancyBackground.ts

@@ -1,5 +1,5 @@
-import { FANCY_BG_BORDER_RADIUS, FANCY_BG_PADDING } from "../constants";
-import { loadHTMLImageElement } from "../element/image";
+import { FANCY_BG_BORDER_RADIUS, FANCY_BG_PADDING, SVG_NS } from "../constants";
+import { loadHTMLImageElement, loadSVGElement } from "../element/image";
 import { roundRect } from "../renderer/roundRect";
 import { AppState, DataURL } from "../types";
 
@@ -121,7 +121,7 @@ const addContentBackground = (
   });
 };
 
-export const applyFancyBackground = async ({
+export const applyFancyBackgroundOnCanvas = async ({
   canvas,
   fancyBackgroundImageUrl,
   backgroundColor,
@@ -144,3 +144,45 @@ export const applyFancyBackground = async ({
 
   addContentBackground(context, canvasDimensions, backgroundColor, exportScale);
 };
+
+export const applyFancyBackgroundOnSvg = async ({
+  svgRoot,
+  fancyBackgroundImageUrl,
+  backgroundColor,
+  dimensions,
+  exportScale,
+}: {
+  svgRoot: SVGSVGElement;
+  fancyBackgroundImageUrl: DataURL;
+  backgroundColor: string;
+  dimensions: Dimensions;
+  exportScale: AppState["exportScale"];
+}) => {
+  const fancyBackgroundImage = await loadSVGElement(
+    `${fancyBackgroundImageUrl}`,
+  );
+
+  fancyBackgroundImage.setAttribute("x", "0");
+  fancyBackgroundImage.setAttribute("y", "0");
+  fancyBackgroundImage.setAttribute("width", `${dimensions.w}`);
+  fancyBackgroundImage.setAttribute("height", `${dimensions.h}`);
+  fancyBackgroundImage.setAttribute("preserveAspectRatio", "none");
+
+  svgRoot.appendChild(fancyBackgroundImage);
+
+  const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
+  rect.setAttribute("x", (FANCY_BG_PADDING * exportScale).toString());
+  rect.setAttribute("y", (FANCY_BG_PADDING * exportScale).toString());
+  rect.setAttribute(
+    "width",
+    `${dimensions.w - FANCY_BG_PADDING * 2 * exportScale}`,
+  );
+  rect.setAttribute(
+    "height",
+    `${dimensions.h - FANCY_BG_PADDING * 2 * exportScale}`,
+  );
+  rect.setAttribute("rx", (FANCY_BG_PADDING * exportScale).toString());
+  rect.setAttribute("ry", (FANCY_BG_PADDING * exportScale).toString());
+  rect.setAttribute("fill", backgroundColor);
+  svgRoot.appendChild(rect);
+};