1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2024-11-02 03:25:53 +01:00

add PNG export (#31)

This commit is contained in:
David Luzar 2020-01-02 15:43:59 +01:00 committed by Christopher Chedeau
parent bb151d83bc
commit 68eeaa3c7d
2 changed files with 116 additions and 7 deletions

@ -6,18 +6,70 @@ import "./styles.css";
var elements = []; var elements = [];
function newElement(type, x, y) { function newElement(type, x, y, width = 0, height = 0) {
const element = { const element = {
type: type, type: type,
x: x, x: x,
y: y, y: y,
width: 0, width: width,
height: 0, height: height,
isSelected: false isSelected: false
}; };
return element; return element;
} }
function exportAsPNG({ background, visibleOnly, padding = 10 }) {
clearSelection();
drawScene();
let subCanvasX1 = Infinity;
let subCanvasX2 = 0;
let subCanvasY1 = Infinity;
let subCanvasY2 = 0;
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));
});
let targetCanvas = canvas;
if ( visibleOnly ) {
targetCanvas = document.createElement('canvas');
targetCanvas.style.display = 'none';
document.body.appendChild(targetCanvas);
targetCanvas.width = subCanvasX2 - subCanvasX1 + padding * 2;
targetCanvas.height = subCanvasY2 - subCanvasY1 + padding * 2;
const targetCanvas_ctx = targetCanvas.getContext('2d');
if ( background ) {
targetCanvas_ctx.fillStyle = "#FFF";
targetCanvas_ctx.fillRect(0, 0, canvas.width, canvas.height);
}
targetCanvas_ctx.drawImage(
canvas,
subCanvasX1 - padding, // x
subCanvasY1 - padding, // y
subCanvasX2 - subCanvasX1 + padding * 2, // width
subCanvasY2 - subCanvasY1 + padding * 2, // height
0,
0,
targetCanvas.width,
targetCanvas.height
);
}
const link = document.createElement('a');
link.setAttribute('download', 'excalibur.png');
link.setAttribute('href', targetCanvas.toDataURL("image/png"));
link.click();
link.remove();
if ( targetCanvas !== canvas ) targetCanvas.remove();
}
function rotate(x1, y1, x2, y2, angle) { function rotate(x1, y1, x2, y2, angle) {
// 𝑎𝑥=(𝑎𝑥𝑐𝑥)cos𝜃(𝑎𝑦𝑐𝑦)sin𝜃+𝑐𝑥 // 𝑎𝑥=(𝑎𝑥𝑐𝑥)cos𝜃(𝑎𝑦𝑐𝑦)sin𝜃+𝑐𝑥
// 𝑎𝑦=(𝑎𝑥𝑐𝑥)sin𝜃+(𝑎𝑦𝑐𝑦)cos𝜃+𝑐𝑦. // 𝑎𝑦=(𝑎𝑥𝑐𝑥)sin𝜃+(𝑎𝑦𝑐𝑦)cos𝜃+𝑐𝑦.
@ -150,7 +202,7 @@ function clearSelection() {
class App extends React.Component { class App extends React.Component {
componentDidMount() { componentDidMount() {
this.onKeyDown = event => { this.onKeyDown = event => {
if (event.key === "Backspace") { if (event.key === "Backspace" && event.target.nodeName !== "INPUT") {
for (var i = elements.length - 1; i >= 0; --i) { for (var i = elements.length - 1; i >= 0; --i) {
if (elements[i].isSelected) { if (elements[i].isSelected) {
elements.splice(i, 1); elements.splice(i, 1);
@ -188,7 +240,10 @@ class App extends React.Component {
super(); super();
this.state = { this.state = {
draggingElement: null, draggingElement: null,
elementType: "selection" elementType: "selection",
exportBackground: false,
exportVisibleOnly: true,
exportPadding: 10
}; };
} }
@ -210,7 +265,40 @@ class App extends React.Component {
); );
}; };
return ( return <>
<div className="exportWrapper">
<button onClick={() => {
exportAsPNG({
background: this.state.exportBackground,
visibleOnly: this.state.exportVisibleOnly,
padding: this.state.exportPadding
})
}}>Export to png</button>
<label>
<input type="checkbox"
checked={this.state.exportBackground}
onChange={e => {
this.setState({ exportBackground: e.target.checked })
}}
/> background
</label>
<label>
<input type="checkbox"
checked={this.state.exportVisibleOnly}
onChange={e => {
this.setState({ exportVisibleOnly: e.target.checked })
}}
/>
visible area only
</label>
(padding:
<input type="number" value={this.state.exportPadding}
onChange={e => {
this.setState({ exportPadding: e.target.value });
}}
disabled={!this.state.exportVisibleOnly}/>
px)
</div>
<div> <div>
{/* Can't use the <ElementOption> form because ElementOption is re-defined {/* Can't use the <ElementOption> form because ElementOption is re-defined
on every render, which would blow up and re-create the entire DOM tree, on every render, which would blow up and re-create the entire DOM tree,
@ -352,7 +440,7 @@ class App extends React.Component {
}} }}
/> />
</div> </div>
); </>;
} }
} }

@ -3,3 +3,24 @@
font-family: "Virgil"; font-family: "Virgil";
src: url("https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf"); src: url("https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf");
} }
.exportWrapper {
margin-bottom: 10px;
display: flex;
align-items: center;
}
.exportWrapper label {
display: flex;
align-items: center;
margin: 0 5px;
}
.exportWrapper button {
margin-right: 10px;
}
.exportWrapper input[type="number"] {
width: 40px;
padding: 2px;
margin-left: 10px;
}