feat: centered content bcg on svg export
This commit is contained in:
parent
a76aa5f7a1
commit
9caa05825d
@ -189,7 +189,11 @@ export const exportToSvg = async (
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [minX, minY, width, height] = getCanvasSize(elements, padding);
|
const [minX, minY, width, height] = !exportWithFancyBackground
|
||||||
|
? getCanvasSize(elements, padding)
|
||||||
|
: getCanvasSize(elements, padding, {
|
||||||
|
aspectRatio: { width: 16, height: 9 },
|
||||||
|
});
|
||||||
|
|
||||||
// initialize SVG root
|
// initialize SVG root
|
||||||
const svgRoot = document.createElementNS(SVG_NS, "svg");
|
const svgRoot = document.createElementNS(SVG_NS, "svg");
|
||||||
@ -267,12 +271,20 @@ export const exportToSvg = async (
|
|||||||
</defs>
|
</defs>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
let offsetXAdjustment = 0;
|
||||||
|
let offsetYAdjustment = 0;
|
||||||
|
|
||||||
// render background rect
|
// render background rect
|
||||||
if (appState.exportBackground && viewBackgroundColor) {
|
if (appState.exportBackground && viewBackgroundColor) {
|
||||||
if (
|
if (
|
||||||
appState.fancyBackgroundImageKey &&
|
appState.fancyBackgroundImageKey &&
|
||||||
appState.fancyBackgroundImageKey !== "solid"
|
appState.fancyBackgroundImageKey !== "solid"
|
||||||
) {
|
) {
|
||||||
|
const commonBounds = getCommonBounds(elements);
|
||||||
|
const contentSize: Dimensions = {
|
||||||
|
width: distance(commonBounds[0], commonBounds[2]),
|
||||||
|
height: distance(commonBounds[1], commonBounds[3]),
|
||||||
|
};
|
||||||
await applyFancyBackgroundOnSvg({
|
await applyFancyBackgroundOnSvg({
|
||||||
svgRoot,
|
svgRoot,
|
||||||
fancyBackgroundImageKey: `${appState.fancyBackgroundImageKey}`,
|
fancyBackgroundImageKey: `${appState.fancyBackgroundImageKey}`,
|
||||||
@ -280,7 +292,11 @@ export const exportToSvg = async (
|
|||||||
dimensions: { width, height },
|
dimensions: { width, height },
|
||||||
exportScale,
|
exportScale,
|
||||||
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
||||||
|
contentSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
offsetXAdjustment = (width - contentSize.width - padding * 2) / 2;
|
||||||
|
offsetYAdjustment = (height - contentSize.height - padding * 2) / 2;
|
||||||
} else {
|
} else {
|
||||||
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
||||||
rect.setAttribute("x", "0");
|
rect.setAttribute("x", "0");
|
||||||
@ -294,8 +310,8 @@ export const exportToSvg = async (
|
|||||||
|
|
||||||
const rsvg = rough.svg(svgRoot);
|
const rsvg = rough.svg(svgRoot);
|
||||||
renderSceneToSvg(elements, rsvg, svgRoot, files || {}, {
|
renderSceneToSvg(elements, rsvg, svgRoot, files || {}, {
|
||||||
offsetX,
|
offsetX: offsetX + offsetXAdjustment,
|
||||||
offsetY,
|
offsetY: offsetY + offsetYAdjustment,
|
||||||
exportWithDarkMode: appState.exportWithDarkMode,
|
exportWithDarkMode: appState.exportWithDarkMode,
|
||||||
exportingFrameId: exportingFrame?.id || null,
|
exportingFrameId: exportingFrame?.id || null,
|
||||||
renderEmbeddables: opts?.renderEmbeddables,
|
renderEmbeddables: opts?.renderEmbeddables,
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
|
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
|
||||||
import { getScaleToFill } from "../packages/utils";
|
import { getScaleToFill } from "../packages/utils";
|
||||||
import { roundRect } from "../renderer/roundRect";
|
import { roundRect } from "../renderer/roundRect";
|
||||||
import { AppState, Dimensions } from "../types";
|
import { AppState, DataURL, Dimensions } from "../types";
|
||||||
|
|
||||||
export const getFancyBackgroundPadding = (
|
export const getFancyBackgroundPadding = (
|
||||||
exportPadding = DEFAULT_EXPORT_PADDING,
|
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||||
@ -61,6 +61,32 @@ const addImageBackground = (
|
|||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getContentBackgound = (
|
||||||
|
contentSize: Dimensions,
|
||||||
|
normalizedDimensions: Dimensions,
|
||||||
|
exportScale: number,
|
||||||
|
): { x: number; y: number; width: number; height: number } => {
|
||||||
|
const x =
|
||||||
|
(normalizedDimensions.width - contentSize.width * exportScale) / 2 -
|
||||||
|
FANCY_BG_PADDING * exportScale;
|
||||||
|
|
||||||
|
const y =
|
||||||
|
(normalizedDimensions.height - contentSize.height * exportScale) / 2 -
|
||||||
|
FANCY_BG_PADDING * exportScale;
|
||||||
|
|
||||||
|
const width =
|
||||||
|
(contentSize.width +
|
||||||
|
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
||||||
|
exportScale;
|
||||||
|
|
||||||
|
const height =
|
||||||
|
(contentSize.height +
|
||||||
|
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
||||||
|
exportScale;
|
||||||
|
|
||||||
|
return { x, y, width, height };
|
||||||
|
};
|
||||||
|
|
||||||
const addContentBackground = (
|
const addContentBackground = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
normalizedDimensions: Dimensions,
|
normalizedDimensions: Dimensions,
|
||||||
@ -72,20 +98,20 @@ const addContentBackground = (
|
|||||||
const shadows = [
|
const shadows = [
|
||||||
{
|
{
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 0.7698959708213806,
|
offsetY: 0,
|
||||||
blur: 1.4945039749145508,
|
blur: 2,
|
||||||
alpha: 0.02,
|
alpha: 0.02,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 1.1299999952316284,
|
offsetY: 1,
|
||||||
blur: 4.1321120262146,
|
blur: 4,
|
||||||
alpha: 0.04,
|
alpha: 0.04,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 4.130000114440918,
|
offsetY: 4,
|
||||||
blur: 9.94853401184082,
|
blur: 10,
|
||||||
alpha: 0.05,
|
alpha: 0.05,
|
||||||
},
|
},
|
||||||
{ offsetX: 0, offsetY: 13, blur: 33, alpha: 0.07 },
|
{ offsetX: 0, offsetY: 13, blur: 33, alpha: 0.07 },
|
||||||
@ -99,24 +125,18 @@ const addContentBackground = (
|
|||||||
context.shadowOffsetX = shadow.offsetX * exportScale;
|
context.shadowOffsetX = shadow.offsetX * exportScale;
|
||||||
context.shadowOffsetY = shadow.offsetY * exportScale;
|
context.shadowOffsetY = shadow.offsetY * exportScale;
|
||||||
|
|
||||||
const x =
|
const { x, y, width, height } = getContentBackgound(
|
||||||
(normalizedDimensions.width - contentSize.width * exportScale) / 2 -
|
contentSize,
|
||||||
FANCY_BG_PADDING * exportScale;
|
normalizedDimensions,
|
||||||
const y =
|
exportScale,
|
||||||
(normalizedDimensions.height - contentSize.height * exportScale) / 2 -
|
);
|
||||||
FANCY_BG_PADDING * exportScale;
|
|
||||||
|
|
||||||
// fixme: position is no scaled to the center
|
|
||||||
if (context.roundRect) {
|
if (context.roundRect) {
|
||||||
context.roundRect(
|
context.roundRect(
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
(contentSize.width +
|
width,
|
||||||
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
height,
|
||||||
exportScale,
|
|
||||||
(contentSize.height +
|
|
||||||
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
|
||||||
exportScale,
|
|
||||||
FANCY_BG_BORDER_RADIUS * exportScale,
|
FANCY_BG_BORDER_RADIUS * exportScale,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -124,12 +144,8 @@ const addContentBackground = (
|
|||||||
context,
|
context,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
(contentSize.width +
|
width,
|
||||||
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
height,
|
||||||
exportScale,
|
|
||||||
(contentSize.height * exportScale +
|
|
||||||
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
|
|
||||||
exportScale,
|
|
||||||
FANCY_BG_BORDER_RADIUS * exportScale,
|
FANCY_BG_BORDER_RADIUS * exportScale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -195,27 +211,17 @@ export const applyFancyBackgroundOnCanvas = async ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const applyFancyBackgroundOnSvg = async ({
|
const addImageBackgroundToSvg = async ({
|
||||||
svgRoot,
|
svgRoot,
|
||||||
fancyBackgroundImageKey,
|
fancyBackgroundImageUrl,
|
||||||
backgroundColor,
|
|
||||||
dimensions,
|
dimensions,
|
||||||
exportScale,
|
|
||||||
theme,
|
theme,
|
||||||
}: {
|
}: {
|
||||||
svgRoot: SVGSVGElement;
|
svgRoot: SVGSVGElement;
|
||||||
fancyBackgroundImageKey: Exclude<
|
fancyBackgroundImageUrl: DataURL;
|
||||||
keyof typeof FANCY_BACKGROUND_IMAGES,
|
|
||||||
"solid"
|
|
||||||
>;
|
|
||||||
backgroundColor: string;
|
|
||||||
dimensions: Dimensions;
|
dimensions: Dimensions;
|
||||||
exportScale: AppState["exportScale"];
|
|
||||||
theme: AppState["theme"];
|
theme: AppState["theme"];
|
||||||
}) => {
|
}) => {
|
||||||
// Image background
|
|
||||||
const fancyBackgroundImageUrl =
|
|
||||||
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
|
|
||||||
const fancyBackgroundImage = await loadSVGElement(fancyBackgroundImageUrl);
|
const fancyBackgroundImage = await loadSVGElement(fancyBackgroundImageUrl);
|
||||||
|
|
||||||
fancyBackgroundImage.setAttribute("x", "0");
|
fancyBackgroundImage.setAttribute("x", "0");
|
||||||
@ -228,21 +234,126 @@ export const applyFancyBackgroundOnSvg = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
svgRoot.appendChild(fancyBackgroundImage);
|
svgRoot.appendChild(fancyBackgroundImage);
|
||||||
|
};
|
||||||
|
const addContentBackgroundToSvg = ({
|
||||||
|
svgRoot,
|
||||||
|
exportScale,
|
||||||
|
contentSize,
|
||||||
|
backgroundColor,
|
||||||
|
dimensions,
|
||||||
|
}: {
|
||||||
|
svgRoot: SVGSVGElement;
|
||||||
|
exportScale: number;
|
||||||
|
contentSize: Dimensions;
|
||||||
|
backgroundColor: string;
|
||||||
|
dimensions: Dimensions;
|
||||||
|
}) => {
|
||||||
|
// Create the shadow filter
|
||||||
|
const filter = svgRoot.ownerDocument!.createElementNS(SVG_NS, "filter");
|
||||||
|
filter.setAttribute("id", "shadow");
|
||||||
|
|
||||||
|
const feGaussianBlur = svgRoot.ownerDocument!.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feGaussianBlur",
|
||||||
|
);
|
||||||
|
feGaussianBlur.setAttribute("in", "SourceAlpha");
|
||||||
|
feGaussianBlur.setAttribute("stdDeviation", "3");
|
||||||
|
|
||||||
|
const feOffset = svgRoot.ownerDocument!.createElementNS(SVG_NS, "feOffset");
|
||||||
|
feOffset.setAttribute("dx", "4");
|
||||||
|
feOffset.setAttribute("dy", "4");
|
||||||
|
feOffset.setAttribute("result", "offsetblur");
|
||||||
|
|
||||||
|
const feFlood = svgRoot.ownerDocument!.createElementNS(SVG_NS, "feFlood");
|
||||||
|
feFlood.setAttribute("flood-color", "black");
|
||||||
|
feFlood.setAttribute("flood-opacity", "0.04");
|
||||||
|
feFlood.setAttribute("result", "color");
|
||||||
|
|
||||||
|
const feComposite = svgRoot.ownerDocument!.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feComposite",
|
||||||
|
);
|
||||||
|
feComposite.setAttribute("in", "color");
|
||||||
|
feComposite.setAttribute("in2", "offsetblur");
|
||||||
|
feComposite.setAttribute("operator", "in");
|
||||||
|
feComposite.setAttribute("result", "shadow");
|
||||||
|
|
||||||
|
const feMerge = svgRoot.ownerDocument!.createElementNS(SVG_NS, "feMerge");
|
||||||
|
const feMergeNodeIn = svgRoot.ownerDocument!.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feMergeNode",
|
||||||
|
);
|
||||||
|
feMergeNodeIn.setAttribute("in", "shadow");
|
||||||
|
const feMergeNodeGraphic = svgRoot.ownerDocument!.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feMergeNode",
|
||||||
|
);
|
||||||
|
feMergeNodeGraphic.setAttribute("in", "SourceGraphic");
|
||||||
|
feMerge.appendChild(feMergeNodeIn);
|
||||||
|
feMerge.appendChild(feMergeNodeGraphic);
|
||||||
|
|
||||||
|
filter.appendChild(feGaussianBlur);
|
||||||
|
filter.appendChild(feOffset);
|
||||||
|
filter.appendChild(feFlood);
|
||||||
|
filter.appendChild(feComposite);
|
||||||
|
filter.appendChild(feMerge);
|
||||||
|
|
||||||
|
svgRoot.appendChild(filter);
|
||||||
|
|
||||||
// Solid color background
|
// Solid color background
|
||||||
|
const { x, y, width, height } = getContentBackgound(
|
||||||
|
contentSize,
|
||||||
|
dimensions,
|
||||||
|
exportScale,
|
||||||
|
);
|
||||||
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
|
||||||
rect.setAttribute("x", (FANCY_BG_PADDING * exportScale).toString());
|
rect.setAttribute("x", x.toString());
|
||||||
rect.setAttribute("y", (FANCY_BG_PADDING * exportScale).toString());
|
rect.setAttribute("y", y.toString());
|
||||||
rect.setAttribute(
|
rect.setAttribute("width", width.toString());
|
||||||
"width",
|
rect.setAttribute("height", height.toString());
|
||||||
`${dimensions.width - FANCY_BG_PADDING * 2 * exportScale}`,
|
|
||||||
);
|
|
||||||
rect.setAttribute(
|
|
||||||
"height",
|
|
||||||
`${dimensions.height - FANCY_BG_PADDING * 2 * exportScale}`,
|
|
||||||
);
|
|
||||||
rect.setAttribute("rx", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
|
rect.setAttribute("rx", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
|
||||||
rect.setAttribute("ry", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
|
rect.setAttribute("ry", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
|
||||||
rect.setAttribute("fill", backgroundColor);
|
rect.setAttribute("fill", backgroundColor);
|
||||||
|
rect.setAttribute("filter", "url(#shadow)");
|
||||||
svgRoot.appendChild(rect);
|
svgRoot.appendChild(rect);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const applyFancyBackgroundOnSvg = async ({
|
||||||
|
svgRoot,
|
||||||
|
fancyBackgroundImageKey,
|
||||||
|
backgroundColor,
|
||||||
|
dimensions,
|
||||||
|
exportScale,
|
||||||
|
theme,
|
||||||
|
contentSize,
|
||||||
|
}: {
|
||||||
|
svgRoot: SVGSVGElement;
|
||||||
|
fancyBackgroundImageKey: Exclude<
|
||||||
|
keyof typeof FANCY_BACKGROUND_IMAGES,
|
||||||
|
"solid"
|
||||||
|
>;
|
||||||
|
backgroundColor: string;
|
||||||
|
dimensions: Dimensions;
|
||||||
|
exportScale: AppState["exportScale"];
|
||||||
|
theme: AppState["theme"];
|
||||||
|
contentSize: Dimensions;
|
||||||
|
}) => {
|
||||||
|
// Image background
|
||||||
|
const fancyBackgroundImageUrl =
|
||||||
|
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
|
||||||
|
|
||||||
|
await addImageBackgroundToSvg({
|
||||||
|
svgRoot,
|
||||||
|
fancyBackgroundImageUrl,
|
||||||
|
dimensions,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
|
||||||
|
addContentBackgroundToSvg({
|
||||||
|
svgRoot,
|
||||||
|
exportScale,
|
||||||
|
contentSize,
|
||||||
|
backgroundColor,
|
||||||
|
dimensions,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user