fix: proper canvas size on frame export

This commit is contained in:
Arnošt Pleskot 2023-09-18 12:01:44 +02:00
parent 2e1da5537d
commit 0c4d6fbe95
No known key found for this signature in database

View File

@ -1,9 +1,13 @@
import rough from "roughjs/bin/rough"; import rough from "roughjs/bin/rough";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds"; import {
Bounds,
getCommonBounds,
getElementAbsoluteCoords,
} from "../element/bounds";
import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene"; import { renderSceneToSvg, renderStaticScene } from "../renderer/renderScene";
import { import {
convertToExportPadding, convertToExportPadding as convertExportPadding,
distance, distance,
expandToAspectRatio, expandToAspectRatio,
isOnlyExportingSingleFrame, isOnlyExportingSingleFrame,
@ -61,19 +65,29 @@ export const exportToCanvas = async (
); );
const padding = !exportingWithFancyBackground const padding = !exportingWithFancyBackground
? convertToExportPadding(exportPadding) ? convertExportPadding(exportPadding)
: getFancyBackgroundPadding( : getFancyBackgroundPadding(
convertToExportPadding(exportPadding), convertExportPadding(exportPadding),
FANCY_BG_INCLUDE_LOGO, FANCY_BG_INCLUDE_LOGO,
); );
const onlyExportingSingleFrame = const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
isOnlyExportingSingleFrame(elements) && !exportingWithFancyBackground;
const [minX, minY, width, height] = !exportingWithFancyBackground const [minX, minY, width, height] = !exportingWithFancyBackground
? getCanvasSize(elements, padding, onlyExportingSingleFrame) ? getCanvasSize({
: getCanvasSize(elements, padding, onlyExportingSingleFrame, { elements,
padding,
onlyExportingSingleFrame,
exportingWithFancyBackground,
})
: getCanvasSize({
elements,
padding,
onlyExportingSingleFrame,
exportingWithFancyBackground,
opts: {
aspectRatio: { width: 16, height: 9 }, aspectRatio: { width: 16, height: 9 },
},
}); });
const { canvas, scale = 1 } = createCanvas(width, height); const { canvas, scale = 1 } = createCanvas(width, height);
@ -95,7 +109,10 @@ export const exportToCanvas = async (
exportingWithFancyBackground && exportingWithFancyBackground &&
appState.fancyBackgroundImageKey !== "solid" appState.fancyBackgroundImageKey !== "solid"
) { ) {
const commonBounds = getCommonBounds(elements); const commonBounds = getElementsSize({
elements,
onlyExportingSingleFrame,
});
const contentSize: Dimensions = { const contentSize: Dimensions = {
width: distance(commonBounds[0], commonBounds[2]), width: distance(commonBounds[0], commonBounds[2]),
height: distance(commonBounds[1], commonBounds[3]), height: distance(commonBounds[1], commonBounds[3]),
@ -131,9 +148,15 @@ export const exportToCanvas = async (
? viewBackgroundColor ? viewBackgroundColor
: null, : null,
scrollX: scrollX:
-minX + (onlyExportingSingleFrame ? 0 : padding[3] + scrollXAdjustment), -minX +
(onlyExportingSingleFrame && !exportingWithFancyBackground
? 0
: padding[3] + scrollXAdjustment),
scrollY: scrollY:
-minY + (onlyExportingSingleFrame ? 0 : padding[0] + scrollYAdjustment), -minY +
(onlyExportingSingleFrame && !exportingWithFancyBackground
? 0
: padding[0] + scrollYAdjustment),
zoom: defaultAppState.zoom, zoom: defaultAppState.zoom,
shouldCacheIgnoreZoom: false, shouldCacheIgnoreZoom: false,
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT, theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
@ -183,9 +206,9 @@ export const exportToSvg = async (
); );
const padding = !exportingWithFancyBackground const padding = !exportingWithFancyBackground
? convertToExportPadding(exportPadding) ? convertExportPadding(exportPadding)
: getFancyBackgroundPadding( : getFancyBackgroundPadding(
convertToExportPadding(exportPadding), convertExportPadding(exportPadding),
FANCY_BG_INCLUDE_LOGO, FANCY_BG_INCLUDE_LOGO,
); );
@ -204,13 +227,23 @@ export const exportToSvg = async (
} }
} }
const onlyExportingSingleFrame = const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
isOnlyExportingSingleFrame(elements) && !exportingWithFancyBackground;
const [minX, minY, width, height] = !exportingWithFancyBackground const [minX, minY, width, height] = !exportingWithFancyBackground
? getCanvasSize(elements, padding, onlyExportingSingleFrame) ? getCanvasSize({
: getCanvasSize(elements, padding, onlyExportingSingleFrame, { elements,
padding,
onlyExportingSingleFrame,
exportingWithFancyBackground,
})
: getCanvasSize({
elements,
padding,
onlyExportingSingleFrame,
exportingWithFancyBackground,
opts: {
aspectRatio: { width: 16, height: 9 }, aspectRatio: { width: 16, height: 9 },
},
}); });
// initialize SVG root // initialize SVG root
@ -244,8 +277,16 @@ export const exportToSvg = async (
Scene.getScene(elements[0])?.getNonDeletedElements()?.length === Scene.getScene(elements[0])?.getNonDeletedElements()?.length ===
elements.length; elements.length;
const offsetX = -minX + (onlyExportingSingleFrame ? 0 : padding[3]); const offsetX =
const offsetY = -minY + (onlyExportingSingleFrame ? 0 : padding[0]); -minX +
(onlyExportingSingleFrame && !exportingWithFancyBackground
? 0
: padding[3]);
const offsetY =
-minY +
(onlyExportingSingleFrame && !exportingWithFancyBackground
? 0
: padding[0]);
const exportingFrame = const exportingFrame =
isExportingWholeCanvas || !onlyExportingSingleFrame isExportingWholeCanvas || !onlyExportingSingleFrame
@ -297,7 +338,10 @@ export const exportToSvg = async (
appState.fancyBackgroundImageKey && appState.fancyBackgroundImageKey &&
appState.fancyBackgroundImageKey !== "solid" appState.fancyBackgroundImageKey !== "solid"
) { ) {
const commonBounds = getCommonBounds(elements); const commonBounds = getElementsSize({
elements,
onlyExportingSingleFrame,
});
const contentSize: Dimensions = { const contentSize: Dimensions = {
width: distance(commonBounds[0], commonBounds[2]), width: distance(commonBounds[0], commonBounds[2]),
height: distance(commonBounds[1], commonBounds[3]), height: distance(commonBounds[1], commonBounds[3]),
@ -342,17 +386,16 @@ export const exportToSvg = async (
return svgRoot; return svgRoot;
}; };
// calculate smallest area to fit the contents in const getElementsSize = ({
const getCanvasSize = ( elements,
elements: readonly NonDeletedExcalidrawElement[], onlyExportingSingleFrame,
exportPadding: ExportPadding, }: {
onlyExportingSingleFrame: boolean, elements: readonly NonDeletedExcalidrawElement[];
opts?: { aspectRatio: Dimensions }, onlyExportingSingleFrame: boolean;
): [number, number, number, number] => { }): Bounds => {
// we should decide if we are exporting the whole canvas // we should decide if we are exporting the whole canvas
// if so, we are not clipping elements in the frame // if so, we are not clipping elements in the frame
// and therefore, we should not do anything special // and therefore, we should not do anything special
const isExportingWholeCanvas = const isExportingWholeCanvas =
Scene.getScene(elements[0])?.getNonDeletedElements()?.length === Scene.getScene(elements[0])?.getNonDeletedElements()?.length ===
elements.length; elements.length;
@ -372,17 +415,37 @@ const getCanvasSize = (
); );
} }
const [minX, minY, maxX, maxY] = getCommonBounds(elements); return getCommonBounds(elements);
};
// calculate smallest area to fit the contents in
const getCanvasSize = ({
elements,
padding,
onlyExportingSingleFrame,
exportingWithFancyBackground,
opts,
}: {
elements: readonly NonDeletedExcalidrawElement[];
padding: ExportPadding;
onlyExportingSingleFrame: boolean;
exportingWithFancyBackground: boolean;
opts?: { aspectRatio: Dimensions };
}): [number, number, number, number] => {
const [minX, minY, maxX, maxY] = getElementsSize({
elements,
onlyExportingSingleFrame,
});
let width = 0; let width = 0;
let height = 0; let height = 0;
if (onlyExportingSingleFrame) { if (onlyExportingSingleFrame && !exportingWithFancyBackground) {
width = distance(minX, maxX); width = distance(minX, maxX);
height = distance(minY, maxY); height = distance(minY, maxY);
} else { } else {
width = distance(minX, maxX) + exportPadding[1] + exportPadding[3]; width = distance(minX, maxX) + padding[1] + padding[3];
height = distance(minY, maxY) + exportPadding[0] + exportPadding[2]; height = distance(minY, maxY) + padding[0] + padding[2];
} }
if (opts?.aspectRatio) { if (opts?.aspectRatio) {
@ -403,11 +466,15 @@ export const getExportSize = (
scale: number, scale: number,
appState: AppState, appState: AppState,
): [number, number] => { ): [number, number] => {
const [, , width, height] = getCanvasSize( const [, , width, height] = getCanvasSize({
elements, elements,
convertToExportPadding(exportPadding), padding: convertExportPadding(exportPadding),
isExportingWithFacnyBackground(appState, elements), onlyExportingSingleFrame: isOnlyExportingSingleFrame(elements),
).map((dimension) => Math.trunc(dimension * scale)); exportingWithFancyBackground: isExportingWithFacnyBackground(
appState,
elements,
),
}).map((dimension) => Math.trunc(dimension * scale));
return [width, height]; return [width, height];
}; };