fix export to support scrolling (#133)

This commit is contained in:
David Luzar 2020-01-04 21:46:50 +01:00 committed by Christopher Chedeau
parent 60b06dab73
commit 490438960d

View File

@ -351,10 +351,23 @@ function handlerRectangles(
function renderScene( function renderScene(
rc: RoughCanvas, rc: RoughCanvas,
context: CanvasRenderingContext2D, canvas: HTMLCanvasElement,
sceneState: SceneState sceneState: SceneState,
// extra options, currently passed by export helper
{
offsetX,
offsetY,
renderScrollbars = true,
renderSelection = true
}: {
offsetX?: number;
offsetY?: number;
renderScrollbars?: boolean;
renderSelection?: boolean;
} = {}
) { ) {
if (!context) return; if (!canvas) return;
const context = canvas.getContext("2d")!;
const fillStyle = context.fillStyle; const fillStyle = context.fillStyle;
if (typeof sceneState.viewBackgroundColor === "string") { if (typeof sceneState.viewBackgroundColor === "string") {
@ -367,9 +380,15 @@ function renderScene(
const selectedIndices = getSelectedIndices(); const selectedIndices = getSelectedIndices();
sceneState = {
...sceneState,
scrollX: typeof offsetX === "number" ? offsetX : sceneState.scrollX,
scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY
};
elements.forEach(element => { elements.forEach(element => {
element.draw(rc, context, sceneState); element.draw(rc, context, sceneState);
if (element.isSelected) { if (renderSelection && element.isSelected) {
const margin = 4; const margin = 4;
const elementX1 = getElementAbsoluteX1(element); const elementX1 = getElementAbsoluteX1(element);
@ -405,119 +424,93 @@ function renderScene(
} }
}); });
const scrollBars = getScrollbars( if (renderScrollbars) {
context.canvas.width, const scrollBars = getScrollbars(
context.canvas.height, context.canvas.width,
sceneState.scrollX, context.canvas.height,
sceneState.scrollY sceneState.scrollX,
); sceneState.scrollY
);
context.fillStyle = SCROLLBAR_COLOR; context.fillStyle = SCROLLBAR_COLOR;
context.fillRect( context.fillRect(
scrollBars.horizontal.x, scrollBars.horizontal.x,
scrollBars.horizontal.y, scrollBars.horizontal.y,
scrollBars.horizontal.width, scrollBars.horizontal.width,
scrollBars.horizontal.height scrollBars.horizontal.height
); );
context.fillRect( context.fillRect(
scrollBars.vertical.x, scrollBars.vertical.x,
scrollBars.vertical.y, scrollBars.vertical.y,
scrollBars.vertical.width, scrollBars.vertical.width,
scrollBars.vertical.height scrollBars.vertical.height
); );
context.fillStyle = fillStyle; context.fillStyle = fillStyle;
}
} }
function exportAsPNG({ function exportAsPNG({
exportBackground, exportBackground,
exportVisibleOnly,
exportPadding = 10, exportPadding = 10,
viewBackgroundColor viewBackgroundColor
}: { }: {
exportBackground: boolean; exportBackground: boolean;
exportVisibleOnly: boolean;
exportPadding?: number; exportPadding?: number;
viewBackgroundColor: string; viewBackgroundColor: string;
scrollX: number;
scrollY: number;
}) { }) {
if (!elements.length) return window.alert("Cannot export empty canvas."); if (!elements.length) return window.alert("Cannot export empty canvas.");
// deselect & rerender // calculate smallest area to fit the contents in
clearSelection(); let subCanvasX1 = Infinity;
ReactDOM.render(<App />, rootElement, () => { let subCanvasX2 = 0;
// calculate visible-area coords let subCanvasY1 = Infinity;
let subCanvasY2 = 0;
let subCanvasX1 = Infinity; elements.forEach(element => {
let subCanvasX2 = 0; subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));
let subCanvasY1 = Infinity; subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));
let subCanvasY2 = 0; subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));
subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));
elements.forEach(element => {
subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));
subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));
subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));
subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));
});
// create temporary canvas from which we'll export
const tempCanvas = document.createElement("canvas");
const tempCanvasCtx = tempCanvas.getContext("2d")!;
tempCanvas.style.display = "none";
document.body.appendChild(tempCanvas);
tempCanvas.width = exportVisibleOnly
? subCanvasX2 - subCanvasX1 + exportPadding * 2
: canvas.width;
tempCanvas.height = exportVisibleOnly
? subCanvasY2 - subCanvasY1 + exportPadding * 2
: canvas.height;
// if we're exporting without bg, we need to rerender the scene without it
// (it's reset again, below)
if (!exportBackground) {
renderScene(rc, context, {
viewBackgroundColor: null,
scrollX: 0,
scrollY: 0
});
}
// copy our original canvas onto the temp canvas
tempCanvasCtx.drawImage(
canvas, // source
exportVisibleOnly // sx
? subCanvasX1 - exportPadding
: 0,
exportVisibleOnly // sy
? subCanvasY1 - exportPadding
: 0,
exportVisibleOnly // sWidth
? subCanvasX2 - subCanvasX1 + exportPadding * 2
: canvas.width,
exportVisibleOnly // sHeight
? subCanvasY2 - subCanvasY1 + exportPadding * 2
: canvas.height,
0, // dx
0, // dy
exportVisibleOnly ? tempCanvas.width : canvas.width, // dWidth
exportVisibleOnly ? tempCanvas.height : canvas.height // dHeight
);
// reset transparent bg back to original
if (!exportBackground) {
renderScene(rc, context, { viewBackgroundColor, scrollX: 0, scrollY: 0 });
}
// create a temporary <a> elem which we'll use to download the image
const link = document.createElement("a");
link.setAttribute("download", "excalidraw.png");
link.setAttribute("href", tempCanvas.toDataURL("image/png"));
link.click();
// clean up the DOM
link.remove();
if (tempCanvas !== canvas) tempCanvas.remove();
}); });
function distance(x: number, y: number) {
return Math.abs(x > y ? x - y : y - x);
}
const tempCanvas = document.createElement("canvas");
tempCanvas.style.display = "none";
document.body.appendChild(tempCanvas);
tempCanvas.width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
tempCanvas.height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
renderScene(
rough.canvas(tempCanvas),
tempCanvas,
{
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
scrollX: 0,
scrollY: 0
},
{
offsetX: -subCanvasX1 + exportPadding,
offsetY: -subCanvasY1 + exportPadding,
renderScrollbars: false,
renderSelection: false
}
);
// create a temporary <a> elem which we'll use to download the image
const link = document.createElement("a");
link.setAttribute("download", "excalidraw.png");
link.setAttribute("href", tempCanvas.toDataURL("image/png"));
link.click();
// clean up the DOM
link.remove();
if (tempCanvas !== canvas) tempCanvas.remove();
} }
function rotate(x1: number, y1: number, x2: number, y2: number, angle: number) { function rotate(x1: number, y1: number, x2: number, y2: number, angle: number) {
@ -723,8 +716,6 @@ type AppState = {
resizingElement: ExcalidrawElement | null; resizingElement: ExcalidrawElement | null;
elementType: string; elementType: string;
exportBackground: boolean; exportBackground: boolean;
exportVisibleOnly: boolean;
exportPadding: number;
currentItemStrokeColor: string; currentItemStrokeColor: string;
currentItemBackgroundColor: string; currentItemBackgroundColor: string;
viewBackgroundColor: string; viewBackgroundColor: string;
@ -821,9 +812,7 @@ class App extends React.Component<{}, AppState> {
draggingElement: null, draggingElement: null,
resizingElement: null, resizingElement: null,
elementType: "selection", elementType: "selection",
exportBackground: false, exportBackground: true,
exportVisibleOnly: true,
exportPadding: 10,
currentItemStrokeColor: "#000000", currentItemStrokeColor: "#000000",
currentItemBackgroundColor: "#ffffff", currentItemBackgroundColor: "#ffffff",
viewBackgroundColor: "#ffffff", viewBackgroundColor: "#ffffff",
@ -1052,12 +1041,7 @@ class App extends React.Component<{}, AppState> {
<div className="panelColumn"> <div className="panelColumn">
<button <button
onClick={() => { onClick={() => {
exportAsPNG({ exportAsPNG(this.state);
exportBackground: this.state.exportBackground,
exportVisibleOnly: this.state.exportVisibleOnly,
exportPadding: this.state.exportPadding,
viewBackgroundColor: this.state.viewBackgroundColor
});
}} }}
> >
Export to png Export to png
@ -1072,28 +1056,6 @@ class App extends React.Component<{}, AppState> {
/> />
background background
</label> </label>
<label>
<input
type="checkbox"
checked={this.state.exportVisibleOnly}
onChange={e => {
this.setState({ exportVisibleOnly: e.target.checked });
}}
/>
visible area only
</label>
<div>
(padding:
<input
type="number"
value={this.state.exportPadding}
onChange={e => {
this.setState({ exportPadding: Number(e.target.value) });
}}
disabled={!this.state.exportVisibleOnly}
/>
px)
</div>
</div> </div>
{someElementIsSelected() && ( {someElementIsSelected() && (
<> <>
@ -1411,7 +1373,7 @@ class App extends React.Component<{}, AppState> {
}; };
componentDidUpdate() { componentDidUpdate() {
renderScene(rc, context, { renderScene(rc, canvas, {
scrollX: this.state.scrollX, scrollX: this.state.scrollX,
scrollY: this.state.scrollY, scrollY: this.state.scrollY,
viewBackgroundColor: this.state.viewBackgroundColor viewBackgroundColor: this.state.viewBackgroundColor