feat: render frames on export (#7210)

This commit is contained in:
David Luzar 2023-11-09 17:00:21 +01:00 committed by GitHub
parent a9a6f8eafb
commit 864c0b3ea8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 989 additions and 332 deletions

View file

@ -20,7 +20,13 @@ import type { Drawable } from "roughjs/bin/core";
import type { RoughSVG } from "roughjs/bin/svg";
import { StaticCanvasRenderConfig } from "../scene/types";
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
import {
distance,
getFontString,
getFontFamilyString,
isRTL,
isTestEnv,
} from "../utils";
import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
import rough from "roughjs/bin/rough";
import {
@ -589,11 +595,7 @@ export const renderElement = (
) => {
switch (element.type) {
case "frame": {
if (
!renderConfig.isExporting &&
appState.frameRendering.enabled &&
appState.frameRendering.outline
) {
if (appState.frameRendering.enabled && appState.frameRendering.outline) {
context.save();
context.translate(
element.x + appState.scrollX,
@ -601,7 +603,7 @@ export const renderElement = (
);
context.fillStyle = "rgba(0, 0, 200, 0.04)";
context.lineWidth = 2 / appState.zoom.value;
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
context.strokeStyle = FRAME_STYLE.strokeColor;
if (FRAME_STYLE.radius && context.roundRect) {
@ -841,10 +843,13 @@ const maybeWrapNodesInFrameClipPath = (
element: NonDeletedExcalidrawElement,
root: SVGElement,
nodes: SVGElement[],
exportedFrameId?: string | null,
frameRendering: AppState["frameRendering"],
) => {
if (!frameRendering.enabled || !frameRendering.clip) {
return null;
}
const frame = getContainingFrame(element);
if (frame && frame.id === exportedFrameId) {
if (frame) {
const g = root.ownerDocument!.createElementNS(SVG_NS, "g");
g.setAttributeNS(SVG_NS, "clip-path", `url(#${frame.id})`);
nodes.forEach((node) => g.appendChild(node));
@ -861,9 +866,11 @@ export const renderElementToSvg = (
files: BinaryFiles,
offsetX: number,
offsetY: number,
exportWithDarkMode?: boolean,
exportingFrameId?: string | null,
renderEmbeddables?: boolean,
renderConfig: {
exportWithDarkMode: boolean;
renderEmbeddables: boolean;
frameRendering: AppState["frameRendering"];
},
) => {
const offset = { x: offsetX, y: offsetY };
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
@ -897,6 +904,13 @@ export const renderElementToSvg = (
root = anchorTag;
}
const addToRoot = (node: SVGElement, element: ExcalidrawElement) => {
if (isTestEnv()) {
node.setAttribute("data-id", element.id);
}
root.appendChild(node);
};
const opacity =
((getContainingFrame(element)?.opacity ?? 100) * element.opacity) / 10000;
@ -931,10 +945,10 @@ export const renderElementToSvg = (
element,
root,
[node],
exportingFrameId,
renderConfig.frameRendering,
);
g ? root.appendChild(g) : root.appendChild(node);
addToRoot(g || node, element);
break;
}
case "embeddable": {
@ -957,7 +971,7 @@ export const renderElementToSvg = (
offsetY || 0
}) rotate(${degree} ${cx} ${cy})`,
);
root.appendChild(node);
addToRoot(node, element);
const label: ExcalidrawElement =
createPlaceholderEmbeddableLabel(element);
@ -968,9 +982,7 @@ export const renderElementToSvg = (
files,
label.x + offset.x - element.x,
label.y + offset.y - element.y,
exportWithDarkMode,
exportingFrameId,
renderEmbeddables,
renderConfig,
);
// render embeddable element + iframe
@ -999,7 +1011,10 @@ export const renderElementToSvg = (
// if rendering embeddables explicitly disabled or
// embedding documents via srcdoc (which doesn't seem to work for SVGs)
// replace with a link instead
if (renderEmbeddables === false || embedLink?.type === "document") {
if (
renderConfig.renderEmbeddables === false ||
embedLink?.type === "document"
) {
const anchorTag = svgRoot.ownerDocument!.createElementNS(SVG_NS, "a");
anchorTag.setAttribute("href", normalizeLink(element.link || ""));
anchorTag.setAttribute("target", "_blank");
@ -1033,8 +1048,7 @@ export const renderElementToSvg = (
embeddableNode.appendChild(foreignObject);
}
root.appendChild(embeddableNode);
addToRoot(embeddableNode, element);
break;
}
case "line":
@ -1119,12 +1133,13 @@ export const renderElementToSvg = (
element,
root,
[group, maskPath],
exportingFrameId,
renderConfig.frameRendering,
);
if (g) {
addToRoot(g, element);
root.appendChild(g);
} else {
root.appendChild(group);
addToRoot(group, element);
root.append(maskPath);
}
break;
@ -1158,10 +1173,10 @@ export const renderElementToSvg = (
element,
root,
[node],
exportingFrameId,
renderConfig.frameRendering,
);
g ? root.appendChild(g) : root.appendChild(node);
addToRoot(g || node, element);
break;
}
case "image": {
@ -1191,7 +1206,10 @@ export const renderElementToSvg = (
use.setAttribute("href", `#${symbolId}`);
// in dark theme, revert the image color filter
if (exportWithDarkMode && fileData.mimeType !== MIME_TYPES.svg) {
if (
renderConfig.exportWithDarkMode &&
fileData.mimeType !== MIME_TYPES.svg
) {
use.setAttribute("filter", IMAGE_INVERT_FILTER);
}
@ -1227,14 +1245,39 @@ export const renderElementToSvg = (
element,
root,
[g],
exportingFrameId,
renderConfig.frameRendering,
);
clipG ? root.appendChild(clipG) : root.appendChild(g);
addToRoot(clipG || g, element);
}
break;
}
// frames are not rendered and only acts as a container
case "frame": {
if (
renderConfig.frameRendering.enabled &&
renderConfig.frameRendering.outline
) {
const rect = document.createElementNS(SVG_NS, "rect");
rect.setAttribute(
"transform",
`translate(${offsetX || 0} ${
offsetY || 0
}) rotate(${degree} ${cx} ${cy})`,
);
rect.setAttribute("width", `${element.width}px`);
rect.setAttribute("height", `${element.height}px`);
// Rounded corners
rect.setAttribute("rx", FRAME_STYLE.radius.toString());
rect.setAttribute("ry", FRAME_STYLE.radius.toString());
rect.setAttribute("fill", "none");
rect.setAttribute("stroke", FRAME_STYLE.strokeColor);
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
addToRoot(rect, element);
}
break;
}
default: {
@ -1288,10 +1331,10 @@ export const renderElementToSvg = (
element,
root,
[node],
exportingFrameId,
renderConfig.frameRendering,
);
g ? root.appendChild(g) : root.appendChild(node);
addToRoot(g || node, element);
} else {
// @ts-ignore
throw new Error(`Unimplemented type ${element.type}`);