Pure node rendering (#443)

This commit is contained in:
Christopher Chedeau 2020-01-19 13:21:33 -08:00 committed by GitHub
parent 5ce5e5ac1e
commit 7f6e1f420e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 647 additions and 84 deletions

View file

@ -1,5 +1,5 @@
import { Action } from "./types";
import { META_KEY } from "../keys";
import { KEYS } from "../keys";
export const actionSelectAll: Action = {
name: "selectAll",
@ -9,5 +9,5 @@ export const actionSelectAll: Action = {
};
},
contextItemLabel: "Select All",
keyTest: event => event[META_KEY] && event.code === "KeyA"
keyTest: event => event[KEYS.META] && event.code === "KeyA"
};

View file

@ -1,6 +1,6 @@
import { Action } from "./types";
import { isTextElement, redrawTextBoundingBox } from "../element";
import { META_KEY } from "../keys";
import { KEYS } from "../keys";
let copiedStyles: string = "{}";
@ -14,7 +14,7 @@ export const actionCopyStyles: Action = {
return {};
},
contextItemLabel: "Copy Styles",
keyTest: event => event[META_KEY] && event.shiftKey && event.code === "KeyC",
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyC",
contextMenuOrder: 0
};
@ -46,6 +46,6 @@ export const actionPasteStyles: Action = {
};
},
contextItemLabel: "Paste Styles",
keyTest: event => event[META_KEY] && event.shiftKey && event.code === "KeyV",
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyV",
contextMenuOrder: 1
};

View file

@ -6,7 +6,7 @@ import {
moveAllRight
} from "../zindex";
import { getSelectedIndices } from "../scene";
import { META_KEY } from "../keys";
import { KEYS } from "../keys";
export const actionSendBackward: Action = {
name: "sendBackward",
@ -19,7 +19,7 @@ export const actionSendBackward: Action = {
contextItemLabel: "Send Backward",
keyPriority: 40,
keyTest: event =>
event[META_KEY] && event.shiftKey && event.altKey && event.code === "KeyB"
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyB"
};
export const actionBringForward: Action = {
@ -33,7 +33,7 @@ export const actionBringForward: Action = {
contextItemLabel: "Bring Forward",
keyPriority: 40,
keyTest: event =>
event[META_KEY] && event.shiftKey && event.altKey && event.code === "KeyF"
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyF"
};
export const actionSendToBack: Action = {
@ -45,7 +45,7 @@ export const actionSendToBack: Action = {
};
},
contextItemLabel: "Send to Back",
keyTest: event => event[META_KEY] && event.shiftKey && event.code === "KeyB"
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyB"
};
export const actionBringToFront: Action = {
@ -57,5 +57,5 @@ export const actionBringToFront: Action = {
};
},
contextItemLabel: "Bring to Front",
keyTest: event => event[META_KEY] && event.shiftKey && event.code === "KeyF"
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyF"
};

View file

@ -8,7 +8,7 @@ import { clipboard, exportFile, downloadFile } from "./icons";
import { Island } from "./Island";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { getExportCanvasPreview } from "../scene/data";
import { getExportCanvasPreview } from "../scene/getExportCanvasPreview";
import { ActionsManagerInterface, UpdaterFn } from "../actions/types";
import Stack from "./Stack";

74
src/index-node.ts Normal file
View file

@ -0,0 +1,74 @@
import { getExportCanvasPreview } from "../src/scene/getExportCanvasPreview";
const { registerFont, createCanvas } = require("canvas");
const elements = [
{
id: "eVzaxG3YnHhqjEmD7NdYo",
type: "diamond",
x: 519,
y: 199,
width: 113,
height: 115,
strokeColor: "#000000",
backgroundColor: "transparent",
fillStyle: "hachure",
strokeWidth: 1,
roughness: 1,
opacity: 100,
isSelected: false,
seed: 749612521
},
{
id: "7W-iw5pEBPTU3eaCaLtFo",
type: "ellipse",
x: 552,
y: 238,
width: 49,
height: 44,
strokeColor: "#000000",
backgroundColor: "transparent",
fillStyle: "hachure",
strokeWidth: 1,
roughness: 1,
opacity: 100,
isSelected: false,
seed: 952056308
},
{
id: "kqKI231mvTrcsYo2DkUsR",
type: "text",
x: 557.5,
y: 317.5,
width: 43,
height: 31,
strokeColor: "#000000",
backgroundColor: "transparent",
fillStyle: "hachure",
strokeWidth: 1,
roughness: 1,
opacity: 100,
isSelected: false,
seed: 1683771448,
text: "test",
font: "20px Virgil",
baseline: 22
}
];
registerFont("./public/FG_Virgil.ttf", { family: "Virgil" });
const canvas = getExportCanvasPreview(
elements as any,
{
exportBackground: true,
viewBackgroundColor: "#ffffff",
scale: 1
},
createCanvas
);
const fs = require("fs");
const out = fs.createWriteStream("test.png");
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on("finish", () => console.log("test.png was created."));

View file

@ -35,7 +35,7 @@ import { AppState } from "./types";
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
import { isInputLike, measureText, debounce, capitalizeString } from "./utils";
import { KEYS, META_KEY, isArrowKey } from "./keys";
import { KEYS, isArrowKey } from "./keys";
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
import { createHistory } from "./history";
@ -303,7 +303,7 @@ export class App extends React.Component<{}, AppState> {
this.state.elementType !== "selection")
) {
this.setState({ elementType: findShapeByKey(event.key) });
} else if (event[META_KEY] && event.code === "KeyZ") {
} else if (event[KEYS.META] && event.code === "KeyZ") {
if (event.shiftKey) {
// Redo action
const data = history.redoOnce();

View file

@ -6,13 +6,14 @@ export const KEYS = {
ENTER: "Enter",
ESCAPE: "Escape",
DELETE: "Delete",
BACKSPACE: "Backspace"
BACKSPACE: "Backspace",
get META() {
return /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
? "metaKey"
: "ctrlKey";
}
};
export const META_KEY = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
? "metaKey"
: "ctrlKey";
export function isArrowKey(keyCode: string) {
return (
keyCode === KEYS.ARROW_LEFT ||

View file

@ -1,13 +1,10 @@
import rough from "roughjs/bin/rough";
import { ExcalidrawElement } from "../element/types";
import { getElementAbsoluteCoords } from "../element";
import { getDefaultAppState } from "../appState";
import { renderScene } from "../renderer";
import { AppState } from "../types";
import { ExportType } from "./types";
import { getExportCanvasPreview } from "./getExportCanvasPreview";
import nanoid from "nanoid";
const LOCAL_STORAGE_KEY = "excalidraw";
@ -169,66 +166,6 @@ export async function loadFromJSON() {
}
}
export function getExportCanvasPreview(
elements: readonly ExcalidrawElement[],
{
exportBackground,
exportPadding = 10,
viewBackgroundColor,
scale = 1
}: {
exportBackground: boolean;
exportPadding?: number;
scale?: number;
viewBackgroundColor: string;
}
) {
// calculate smallest area to fit the contents in
let subCanvasX1 = Infinity;
let subCanvasX2 = 0;
let subCanvasY1 = Infinity;
let subCanvasY2 = 0;
elements.forEach(element => {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
subCanvasX1 = Math.min(subCanvasX1, x1);
subCanvasY1 = Math.min(subCanvasY1, y1);
subCanvasX2 = Math.max(subCanvasX2, x2);
subCanvasY2 = Math.max(subCanvasY2, y2);
});
function distance(x: number, y: number) {
return Math.abs(x > y ? x - y : y - x);
}
const tempCanvas = document.createElement("canvas");
const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
tempCanvas.style.width = width + "px";
tempCanvas.style.height = height + "px";
tempCanvas.width = width * scale;
tempCanvas.height = height * scale;
tempCanvas.getContext("2d")?.scale(scale, scale);
renderScene(
elements,
rough.canvas(tempCanvas),
tempCanvas,
{
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
scrollX: 0,
scrollY: 0
},
{
offsetX: -subCanvasX1 + exportPadding,
offsetY: -subCanvasY1 + exportPadding,
renderScrollbars: false,
renderSelection: false
}
);
return tempCanvas;
}
export async function exportCanvas(
type: ExportType,
elements: readonly ExcalidrawElement[],
@ -262,7 +199,7 @@ export async function exportCanvas(
if (type === "png") {
const fileName = `${name}.png`;
if ("chooseFileSystemEntries" in window) {
tempCanvas.toBlob(async blob => {
tempCanvas.toBlob(async (blob: any) => {
if (blob) {
await saveFileNative(fileName, blob);
}
@ -272,7 +209,7 @@ export async function exportCanvas(
}
} else if (type === "clipboard") {
try {
tempCanvas.toBlob(async function(blob) {
tempCanvas.toBlob(async function(blob: any) {
try {
await navigator.clipboard.write([
new window.ClipboardItem({ "image/png": blob })

View file

@ -0,0 +1,71 @@
import rough from "roughjs/bin/rough";
import { ExcalidrawElement } from "../element/types";
import { getElementAbsoluteCoords } from "../element/bounds";
import { renderScene } from "../renderer/renderScene";
export function getExportCanvasPreview(
elements: readonly ExcalidrawElement[],
{
exportBackground,
exportPadding = 10,
viewBackgroundColor,
scale = 1
}: {
exportBackground: boolean;
exportPadding?: number;
scale?: number;
viewBackgroundColor: string;
},
createCanvas: (width: number, height: number) => any = function(
width,
height
) {
const tempCanvas = document.createElement("canvas");
tempCanvas.style.width = width + "px";
tempCanvas.style.height = height + "px";
tempCanvas.width = width * scale;
tempCanvas.height = height * scale;
return tempCanvas;
}
) {
// calculate smallest area to fit the contents in
let subCanvasX1 = Infinity;
let subCanvasX2 = 0;
let subCanvasY1 = Infinity;
let subCanvasY2 = 0;
elements.forEach(element => {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
subCanvasX1 = Math.min(subCanvasX1, x1);
subCanvasY1 = Math.min(subCanvasY1, y1);
subCanvasX2 = Math.max(subCanvasX2, x2);
subCanvasY2 = Math.max(subCanvasY2, y2);
});
function distance(x: number, y: number) {
return Math.abs(x > y ? x - y : y - x);
}
const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
const tempCanvas: any = createCanvas(width, height);
tempCanvas.getContext("2d")?.scale(scale, scale);
renderScene(
elements,
rough.canvas(tempCanvas),
tempCanvas,
{
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
scrollX: 0,
scrollY: 0
},
{
offsetX: -subCanvasX1 + exportPadding,
offsetY: -subCanvasY1 + exportPadding,
renderScrollbars: false,
renderSelection: false
}
);
return tempCanvas;
}