feat: centered content bcg on svg export

This commit is contained in:
Arnošt Pleskot 2023-08-24 15:48:22 +02:00
parent a76aa5f7a1
commit 9caa05825d
No known key found for this signature in database
2 changed files with 179 additions and 52 deletions

View File

@ -189,7 +189,11 @@ export const exportToSvg = async (
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
const svgRoot = document.createElementNS(SVG_NS, "svg");
@ -267,12 +271,20 @@ export const exportToSvg = async (
</defs>
`;
let offsetXAdjustment = 0;
let offsetYAdjustment = 0;
// render background rect
if (appState.exportBackground && viewBackgroundColor) {
if (
appState.fancyBackgroundImageKey &&
appState.fancyBackgroundImageKey !== "solid"
) {
const commonBounds = getCommonBounds(elements);
const contentSize: Dimensions = {
width: distance(commonBounds[0], commonBounds[2]),
height: distance(commonBounds[1], commonBounds[3]),
};
await applyFancyBackgroundOnSvg({
svgRoot,
fancyBackgroundImageKey: `${appState.fancyBackgroundImageKey}`,
@ -280,7 +292,11 @@ export const exportToSvg = async (
dimensions: { width, height },
exportScale,
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
contentSize,
});
offsetXAdjustment = (width - contentSize.width - padding * 2) / 2;
offsetYAdjustment = (height - contentSize.height - padding * 2) / 2;
} else {
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
rect.setAttribute("x", "0");
@ -294,8 +310,8 @@ export const exportToSvg = async (
const rsvg = rough.svg(svgRoot);
renderSceneToSvg(elements, rsvg, svgRoot, files || {}, {
offsetX,
offsetY,
offsetX: offsetX + offsetXAdjustment,
offsetY: offsetY + offsetYAdjustment,
exportWithDarkMode: appState.exportWithDarkMode,
exportingFrameId: exportingFrame?.id || null,
renderEmbeddables: opts?.renderEmbeddables,

View File

@ -11,7 +11,7 @@ import {
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
import { getScaleToFill } from "../packages/utils";
import { roundRect } from "../renderer/roundRect";
import { AppState, Dimensions } from "../types";
import { AppState, DataURL, Dimensions } from "../types";
export const getFancyBackgroundPadding = (
exportPadding = DEFAULT_EXPORT_PADDING,
@ -61,6 +61,32 @@ const addImageBackground = (
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 = (
context: CanvasRenderingContext2D,
normalizedDimensions: Dimensions,
@ -72,20 +98,20 @@ const addContentBackground = (
const shadows = [
{
offsetX: 0,
offsetY: 0.7698959708213806,
blur: 1.4945039749145508,
offsetY: 0,
blur: 2,
alpha: 0.02,
},
{
offsetX: 0,
offsetY: 1.1299999952316284,
blur: 4.1321120262146,
offsetY: 1,
blur: 4,
alpha: 0.04,
},
{
offsetX: 0,
offsetY: 4.130000114440918,
blur: 9.94853401184082,
offsetY: 4,
blur: 10,
alpha: 0.05,
},
{ offsetX: 0, offsetY: 13, blur: 33, alpha: 0.07 },
@ -99,24 +125,18 @@ const addContentBackground = (
context.shadowOffsetX = shadow.offsetX * exportScale;
context.shadowOffsetY = shadow.offsetY * exportScale;
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 { x, y, width, height } = getContentBackgound(
contentSize,
normalizedDimensions,
exportScale,
);
// fixme: position is no scaled to the center
if (context.roundRect) {
context.roundRect(
x,
y,
(contentSize.width +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
(contentSize.height +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
width,
height,
FANCY_BG_BORDER_RADIUS * exportScale,
);
} else {
@ -124,12 +144,8 @@ const addContentBackground = (
context,
x,
y,
(contentSize.width +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
(contentSize.height * exportScale +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
width,
height,
FANCY_BG_BORDER_RADIUS * exportScale,
);
}
@ -195,27 +211,17 @@ export const applyFancyBackgroundOnCanvas = async ({
);
};
export const applyFancyBackgroundOnSvg = async ({
const addImageBackgroundToSvg = async ({
svgRoot,
fancyBackgroundImageKey,
backgroundColor,
fancyBackgroundImageUrl,
dimensions,
exportScale,
theme,
}: {
svgRoot: SVGSVGElement;
fancyBackgroundImageKey: Exclude<
keyof typeof FANCY_BACKGROUND_IMAGES,
"solid"
>;
backgroundColor: string;
fancyBackgroundImageUrl: DataURL;
dimensions: Dimensions;
exportScale: AppState["exportScale"];
theme: AppState["theme"];
}) => {
// Image background
const fancyBackgroundImageUrl =
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
const fancyBackgroundImage = await loadSVGElement(fancyBackgroundImageUrl);
fancyBackgroundImage.setAttribute("x", "0");
@ -228,21 +234,126 @@ export const applyFancyBackgroundOnSvg = async ({
}
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
const { x, y, width, height } = getContentBackgound(
contentSize,
dimensions,
exportScale,
);
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.width - FANCY_BG_PADDING * 2 * exportScale}`,
);
rect.setAttribute(
"height",
`${dimensions.height - FANCY_BG_PADDING * 2 * exportScale}`,
);
rect.setAttribute("x", x.toString());
rect.setAttribute("y", y.toString());
rect.setAttribute("width", width.toString());
rect.setAttribute("height", height.toString());
rect.setAttribute("rx", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
rect.setAttribute("ry", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
rect.setAttribute("fill", backgroundColor);
rect.setAttribute("filter", "url(#shadow)");
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,
});
};