mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
20 Commits
2.8.0-beta
...
2.8.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dbae61212 | ||
|
|
75c65d61c5 | ||
|
|
37e0de41af | ||
|
|
57f9e43508 | ||
|
|
3b7f931f28 | ||
|
|
b37a7aad4f | ||
|
|
667ab31ed9 | ||
|
|
b15ddef7fe | ||
|
|
ef785e5fb0 | ||
|
|
15ba4146ac | ||
|
|
9956fd1756 | ||
|
|
b6f2161f1c | ||
|
|
22b8b1f707 | ||
|
|
98f6871caa | ||
|
|
aae588249a | ||
|
|
ef890d51e3 | ||
|
|
b0bc03437a | ||
|
|
23b94da8f0 | ||
|
|
1f227ddd24 | ||
|
|
12152665af |
@@ -38,7 +38,7 @@ async function getImageSize(src: string): Promise<{ height: number; width: numbe
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app?: any
|
||||
plugin?: any
|
||||
): Promise<{
|
||||
mimeType: string;
|
||||
fileId: FileId;
|
||||
@@ -50,9 +50,9 @@ export async function tex2dataURL(
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
|
||||
if(!adaptor) {
|
||||
if (app) {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
preamble = file ? await app.vault.read(file) : null;
|
||||
if (plugin) {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(plugin.settings.latexPreambleLocation || "preamble.sty");
|
||||
preamble = file ? await plugin.app.vault.read(file) : null;
|
||||
}
|
||||
adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
@@ -69,10 +69,18 @@ export async function tex2dataURL(
|
||||
|
||||
try {
|
||||
const node = html.convert(
|
||||
preamble ? `${preamble}${tex}` : tex,
|
||||
preamble ? `${preamble}\n${tex}` : tex,
|
||||
{ display: true, scale }
|
||||
);
|
||||
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195
|
||||
//https://stackoverflow.com/a/77181931
|
||||
let styleNode = document.createElement('style');
|
||||
styleNode.setAttribute("type", "text/css");
|
||||
styleNode.appendChild(document.createTextNode(".mjx-solid { stroke-width: 80px; }"));
|
||||
svg.appendChild(styleNode);
|
||||
|
||||
if (svg) {
|
||||
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
|
||||
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
|
||||
|
||||
@@ -89,8 +89,8 @@ Plugin settings are grouped into the following sections:
|
||||
- **Basic settings**: such as default folders to use.
|
||||
- **Saving**: compression and autosave timer.
|
||||
- **Filename**: configure the automatically created Excalidraw filename.
|
||||
- **Display**: settings that effect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings).
|
||||
- **Links and transclusions**: Settings that effect how links and embedded items behave on the Excalidraw canvas.
|
||||
- **Display**: settings that affect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings).
|
||||
- **Links and transclusions**: Settings that affect how links and embedded items behave on the Excalidraw canvas.
|
||||
- **Markdown-embed settings**: These settings control how markdown documents from your Vault embedded into Excalidraw drawings will behave.
|
||||
- **Embed & Export**: Settings that control how Excalidraw images are displayed when embedding them into markdown documents.
|
||||
- **Auto-export Settings**: You can configure Excalidraw to create a PNG or SVG copy of your drawing each time it gets saved.
|
||||
|
||||
@@ -21,7 +21,7 @@ The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.8.0")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -46,6 +46,8 @@ const TRANSITION_DELAY = 1000; //maximum time for transition between slides in m
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
const PRINT_SLIDE_WIDTH = 1920;
|
||||
const PRINT_SLIDE_HEIGHT = 1080;
|
||||
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
|
||||
//but excalidraw might be open in a popout window which has a different document object
|
||||
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
|
||||
@@ -57,6 +59,7 @@ const SVG_MAXIMIZE = ea.obsidian.getIcon("lucide-maximize").outerHTML;
|
||||
const SVG_MINIMIZE = ea.obsidian.getIcon("lucide-minimize").outerHTML;
|
||||
const SVG_LASER_ON = ea.obsidian.getIcon("lucide-hand").outerHTML;
|
||||
const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
|
||||
const SVG_PRINTER = ea.obsidian.getIcon("lucide-printer").outerHTML;
|
||||
|
||||
//-------------------------------
|
||||
//utility & convenience functions
|
||||
@@ -202,7 +205,7 @@ const gotoFullscreen = async () => {
|
||||
}
|
||||
await waitForExcalidrawResize();
|
||||
const layerUIWrapper = contentEl.querySelector(".layer-ui__wrapper");
|
||||
if(!layerUIWrapper.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
|
||||
if(!layerUIWrapper?.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
|
||||
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MINIMIZE;
|
||||
resetControlPanelElPosition();
|
||||
isFullscreen = true;
|
||||
@@ -271,8 +274,8 @@ if(presentationPathType==="line") {
|
||||
//-----------------------------
|
||||
// scroll-to-location functions
|
||||
//-----------------------------
|
||||
const getNavigationRect = ({ x1, y1, x2, y2 }) => {
|
||||
const { width, height } = excalidrawAPI.getAppState();
|
||||
const getNavigationRect = ({ x1, y1, x2, y2, printDimensions }) => {
|
||||
const { width, height } = printDimensions ? printDimensions : excalidrawAPI.getAppState();
|
||||
const ratioX = width / Math.abs(x1 - x2);
|
||||
const ratioY = height / Math.abs(y1 - y2);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
|
||||
@@ -534,6 +537,20 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
if(ea.DEVICE.isDesktop) {
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
margin-right: calc(var(--default-button-size)*0.25);`,
|
||||
title: `Print to PDF\nClick to print slides at ${PRINT_SLIDE_WIDTH}x${
|
||||
PRINT_SLIDE_HEIGHT}\nHold SHIFT to print the presentation as displayed`
|
||||
//${!presentationPathLineEl ? "\nHold ALT/OPT to clip frames":""}`
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_PRINTER;
|
||||
button.onclick = (e) => printToPDF(e);
|
||||
});
|
||||
}
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
@@ -542,7 +559,7 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_FINISH;
|
||||
button.onclick = () => exitPresentation()
|
||||
button.onclick = () => exitPresentation();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -741,6 +758,96 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Print to PDF
|
||||
//--------------------------
|
||||
let notice;
|
||||
let noticeEl;
|
||||
function setSingleNotice(message) {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.setMessage(message);
|
||||
return;
|
||||
}
|
||||
notice = new Notice(message, 0);
|
||||
noticeEl = notice.containerEl ?? notice.noticeEl;
|
||||
}
|
||||
|
||||
function hideSingleNotice() {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.hide();
|
||||
}
|
||||
}
|
||||
|
||||
const translateToZero = ({ top, left, bottom, right }, padding) => {
|
||||
const {topX, topY, width, height} = ea.getBoundingBox(ea.getViewElements());
|
||||
const newTop = top - (topY - padding);
|
||||
const newLeft = left - (topX - padding);
|
||||
const newBottom = bottom - (topY - padding);
|
||||
const newRight = right - (topX - padding);
|
||||
|
||||
return {
|
||||
top: newTop,
|
||||
left: newLeft,
|
||||
bottom: newBottom,
|
||||
right: newRight,
|
||||
};
|
||||
}
|
||||
|
||||
const printToPDF = async (e) => {
|
||||
const slideWidth = e.shiftKey ? excalidrawAPI.getAppState().width : PRINT_SLIDE_WIDTH;
|
||||
const slideHeight = e.shiftKey ? excalidrawAPI.getAppState().height : PRINT_SLIDE_HEIGHT;
|
||||
//const shouldClipFrames = !presentationPathLineEl && e.altKey;
|
||||
const shouldClipFrames = false;
|
||||
//huge padding to ensure the HD window always fits the width
|
||||
//no padding if frames are clipped
|
||||
const padding = shouldClipFrames ? 0 : Math.round(Math.max(slideWidth,slideHeight)/2)+10;
|
||||
const st = ea.getExcalidrawAPI().getAppState();
|
||||
setSingleNotice("Generating image. This can take a longer time depending on the size of the image and speed of your device");
|
||||
const svg = await ea.createViewSVG({
|
||||
withBackground: true,
|
||||
theme: st.theme,
|
||||
frameRendering: { enabled: shouldClipFrames, name: false, outline: false, clip: shouldClipFrames },
|
||||
padding,
|
||||
selectedOnly: false,
|
||||
skipInliningFonts: false,
|
||||
embedScene: false,
|
||||
});
|
||||
const pages = [];
|
||||
for(i=0;i<slides.length;i++) {
|
||||
setSingleNotice(`Generating slide ${i+1}`);
|
||||
const s = slides[i];
|
||||
const { top, left, bottom, right } = translateToZero(
|
||||
getNavigationRect({
|
||||
...s,
|
||||
printDimensions: {width: slideWidth, height: slideHeight}
|
||||
}), padding
|
||||
);
|
||||
//always create the new SVG in the main Obsidian workspace (not the popout window, if present)
|
||||
const host = window.createDiv();
|
||||
host.innerHTML = svg.outerHTML;
|
||||
const clonedSVG = host.firstElementChild;
|
||||
const width = Math.abs(left-right);
|
||||
const height = Math.abs(top-bottom);
|
||||
clonedSVG.setAttribute("viewBox", `${left} ${top} ${width} ${height}`);
|
||||
clonedSVG.setAttribute("width", `${width}`);
|
||||
clonedSVG.setAttribute("height", `${height}`);
|
||||
pages.push(clonedSVG);
|
||||
}
|
||||
const bgColor = ea.getExcalidrawAPI().getAppState().viewBackgroundColor;
|
||||
setSingleNotice("Creating PDF Document");
|
||||
ea.createPDF({
|
||||
SVG: pages,
|
||||
scale: { fitToPage: true },
|
||||
pageProps: {
|
||||
dimensions: { width: slideWidth, height: slideHeight },
|
||||
backgroundColor: bgColor,
|
||||
margin: { left: 0, right: 0, top: 0, bottom: 0 },
|
||||
alignment: "center"
|
||||
},
|
||||
filename: ea.targetView.file.basename + ".pdf",
|
||||
}).then(()=>hideSingleNotice());
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Start presentation or open presentation settings on double click
|
||||
//--------------------------
|
||||
@@ -768,7 +875,11 @@ const start = async () => {
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
|
||||
if(
|
||||
window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) &&
|
||||
(timestamp - window.ExcalidrawSlideshow.timestamp <400)
|
||||
) {
|
||||
if(window.ExcalidrawSlideshowStartTimer) {
|
||||
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
@@ -789,4 +900,4 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
window.ExcalidrawSlideshow.slide[ea.targetView.file.path] = 0;
|
||||
|
||||
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.8.0-beta-4",
|
||||
"version": "2.8.1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.5",
|
||||
"version": "2.8.1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
"@zsviczian/excalidraw": "0.17.6-27",
|
||||
"@zsviczian/excalidraw": "0.17.6-29",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"es6-promise-pool": "2.5.0",
|
||||
@@ -3336,9 +3336,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@zsviczian/excalidraw": {
|
||||
"version": "0.17.6-27",
|
||||
"resolved": "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.17.6-27.tgz",
|
||||
"integrity": "sha512-8E5pfMbKO80d9oXyApF43SCKaR0CcLhVHjiQYEuAXzS7v6XzrDHODsNg+HuN9kOiShXmmn/e1MKVawo7bmeuOQ==",
|
||||
"version": "0.17.6-29",
|
||||
"resolved": "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.17.6-29.tgz",
|
||||
"integrity": "sha512-4UpAk1JGlHacjr3pyIhBU6e5+tgSggSq0+FhHEYPbeahLUAhnLXnpSLZUpEXTaftO4xooXOOvObB6BG/cfjaRA==",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@excalidraw/random-username": "1.1.0",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-27",
|
||||
"@zsviczian/excalidraw": "0.17.6-29",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
declare const excalidrawLib: typeof ExcalidrawLib;
|
||||
|
||||
export const LOCALE = moment.locale();
|
||||
export const LOCALE = localStorage.getItem("language")?.toLowerCase() || "en";
|
||||
export const CJK_FONTS = "CJK Fonts";
|
||||
|
||||
export const obsidianToExcalidrawMap: { [key: string]: string } = {
|
||||
@@ -446,4 +446,4 @@ export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.3
|
||||
export const DISK_ICON_NAME = "save";
|
||||
export const EXPORT_IMG_ICON = ` <g transform="scale(4.166)" strokeWidth="1.25" fill="none" stroke="currentColor"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 8h.01"></path><path d="M12 20h-5a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v5"></path><path d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l4 4"></path><path d="M14 14l1 -1c.617 -.593 1.328 -.793 2.009 -.598"></path><path d="M19 16v6"></path><path d="M22 19l-3 3l-3 -3"></path></g>`;
|
||||
export const EXPORT_IMG_ICON_NAME = `export-img`;
|
||||
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;
|
||||
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;
|
||||
|
||||
@@ -756,7 +756,6 @@ export class CommandManager {
|
||||
if (view) {
|
||||
if(!view.exportDialog) {
|
||||
view.exportDialog = new ExportDialog(this.plugin, view,view.file);
|
||||
view.exportDialog.createForm();
|
||||
}
|
||||
view.exportDialog.open();
|
||||
return true;
|
||||
|
||||
@@ -173,6 +173,7 @@ export interface ExcalidrawSettings {
|
||||
showNewVersionNotification: boolean;
|
||||
//mathjaxSourceURL: string;
|
||||
latexBoilerplate: string;
|
||||
latexPreambleLocation: string;
|
||||
taskboneEnabled: boolean;
|
||||
taskboneAPIkey: string;
|
||||
pinnedScripts: string[];
|
||||
@@ -348,6 +349,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
showNewVersionNotification: true,
|
||||
//mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
|
||||
latexBoilerplate: "\\color{blue}",
|
||||
latexPreambleLocation: "preamble.sty",
|
||||
taskboneEnabled: false,
|
||||
taskboneAPIkey: "",
|
||||
pinnedScripts: [],
|
||||
@@ -2616,6 +2618,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LATEX_PREAMBLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LATEX_PREAMBLE_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: preamble.sty")
|
||||
.setValue(this.plugin.settings.latexPreambleLocation)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.latexPreambleLocation = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FILETYPE_NAME"))
|
||||
.setDesc(fragWithHTML(t("FILETYPE_DESC")))
|
||||
|
||||
@@ -722,6 +722,8 @@ FILENAME_HEAD: "Filename",
|
||||
"restart Obsidian after closing settings, for this change to take effect.",
|
||||
LATEX_DEFAULT_NAME: "Default LaTeX formula for new equations",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
LATEX_PREAMBLE_NAME: "LaTeX preamble file (CasE SEnSiTivE!)",
|
||||
LATEX_PREAMBLE_DESC: "Full filepath to the preamble file, leave empty for default. If the file doesn't exist this option will be ignored.<br><strong>Important:</strong> Requires obsidian reload after change to take effect!",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
|
||||
@@ -1050,12 +1052,12 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE: "Page Fitting",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION: "Fit to page",
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION: "Fit to 2-pages",
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION: "Fit to 4-pages",
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION: "Fit to 6-pages",
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION: "Fit to 8-pages",
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION: "Fit to 12-pages",
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION: "Fit to 16-pages",
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION: "Fit to max 2-pages",
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION: "Fit to max 4-pages",
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION: "Fit to max 6-pages",
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION: "Fit to max 8-pages",
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION: "Fit to max 12-pages",
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION: "Fit to max 16-pages",
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION: "Use image scale (may span multiple pages)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
|
||||
@@ -1063,6 +1065,8 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT: "Center Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT: "Center Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
|
||||
|
||||
@@ -70,6 +70,7 @@ export class ExportDialog extends Modal {
|
||||
this.margin = plugin.settings.pdfSettings.margin;
|
||||
|
||||
this.saveSettings = false;
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -97,7 +98,7 @@ export class ExportDialog extends Modal {
|
||||
this.dirty = this.saveSettings;
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
createForm() {
|
||||
if(DEVICE.isDesktop) {
|
||||
// Create tab container
|
||||
const tabContainer = this.contentEl.createDiv("nav-buttons-container");
|
||||
@@ -391,7 +392,6 @@ export class ExportDialog extends Modal {
|
||||
});
|
||||
bPDFExport.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
false,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
@@ -402,7 +402,7 @@ export class ExportDialog extends Modal {
|
||||
|
||||
public getPaperColor(): string {
|
||||
switch (this.paperColor) {
|
||||
case "white": return "#ffffff";
|
||||
case "white": return this.theme === "light" ? "#ffffff" : "#000000";
|
||||
case "scene": return this.api.getAppState().viewBackgroundColor;
|
||||
case "custom": return this.customPaperColor;
|
||||
default: return "#ffffff";
|
||||
|
||||
@@ -17,6 +17,95 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
|
||||
`,
|
||||
"2.8.1":`
|
||||
## Fixed
|
||||
- Unable to open Excalidraw files after the 2.8.0 update. [#2235](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2235)
|
||||
`,
|
||||
"2.8.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/tWi5xTUTz7E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Updated "Export Image" dialog
|
||||
- 🚀 PDF Export option including tiling of images over multiple pages. Only available on desktop :(
|
||||
- SVG to clipboard
|
||||
- More granular setting for padding and scale
|
||||
- Slideshow script can now print slides to PDF (update script from script store)
|
||||
- Set local graph to show the links in the embeddable when it is activated/deactivated [#2200](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2200)
|
||||
|
||||
## Fixed
|
||||
- Fixed several LaTeX issues. 🙏 @Sintuz [#1631](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1631), [#2195](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195), [#1842](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1842)
|
||||
- Fixed support for *.jfif and *.avif images [#2212](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2212)
|
||||
- PDF++ selection is not correctly showing after embedded into a drawing (for some specific files) [#2213](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2213)
|
||||
- iOS 18 can't upload image and library [#2182](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2182)
|
||||
- Image block references are broken in hover previews [#2218](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2218)
|
||||
- ⚠️ Note there is a known issue in Obsidian 1.8.2 ⚠️ affecting preview windows in Excalidraw. I received confirmation that this will be fixed in 1.8.3. For now, if hover previews are important to you, you can downgrade to Obsidian 1.8.1 [#2228](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2225)
|
||||
- Mobile elements panel and context menu are not scrollable [#2216](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2216)
|
||||
- "Local Font" menu disappears when opening a drawing in an Obsidian popout-window [#2205](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2205)
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Pressing delete on a frame will only delete the children [#9011](https://github.com/excalidraw/excalidraw/pull/9011)
|
||||
- New crowfoot arrowheads and a new arrowhead picker [#8942](https://github.com/excalidraw/excalidraw/pull/8942)
|
||||
- Fixed some of the arrow binding issues [#9010](https://github.com/excalidraw/excalidraw/pull/9010), [#2209](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2209)
|
||||
- New context menu action: "Wrap selection in frame" [#9005](https://github.com/excalidraw/excalidraw/pull/9005)
|
||||
- Elbow arrow segment fixing and positioning [#8952](https://github.com/excalidraw/excalidraw/pull/8952)
|
||||
- When drag creating a new frame, do not add a partial group to it. When wrapping a selected partial group in a frame however, do add it to the wrapping frame. But such that it should be separated from the previous containing group. [#9014](https://github.com/excalidraw/excalidraw/pull/9014)
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- New hook: ${String.fromCharCode(96)}onImageFileNameHook${String.fromCharCode(96)}. When set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
- PDF export functions, paving the way for slideshow to export slides to PDF
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Returns the dimensions of a standard page size in pixels.
|
||||
*/
|
||||
function getPagePDFDimensions(
|
||||
pageSize: PageSize,
|
||||
orientation: PageOrientation
|
||||
): PageDimensions;
|
||||
|
||||
/**
|
||||
* Creates a PDF from the provided SVG elements with specified scaling and page properties.
|
||||
*/
|
||||
function createPDF(props: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale?: PDFExportScale;
|
||||
pageProps?: PDFPageProperties;
|
||||
filename: string;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates an SVG representation of the current view.
|
||||
*/
|
||||
function createViewSVG(props : {
|
||||
withBackground?: boolean;
|
||||
theme?: "light" | "dark";
|
||||
frameRendering?: FrameRenderingOptions;
|
||||
padding?: number;
|
||||
selectedOnly?: boolean;
|
||||
skipInliningFonts?: boolean;
|
||||
embedScene?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
* You can use this callback to customize the naming and path of pasted images to avoid
|
||||
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
|
||||
* and instead use more meaningful names based on the Excalidraw file or other criteria,
|
||||
* plus save the image in a different folder.
|
||||
*
|
||||
* If the function returns null or undefined, the normal Excalidraw operation will continue
|
||||
* with the excalidraw generated name and default path.
|
||||
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
|
||||
* with the file extension.
|
||||
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
|
||||
*/
|
||||
function onImageFilePathHook: (data: {
|
||||
currentImageName: string;
|
||||
drawingFilePath: string;
|
||||
}) => string = null;
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.7.5":`
|
||||
## Fixed
|
||||
- PDF export scenario described in [#2184](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2184)
|
||||
|
||||
@@ -129,6 +129,8 @@ export class PDFExportSettingsComponent {
|
||||
dropdown
|
||||
.addOptions({
|
||||
"center": t("EXPORTDIALOG_PDF_ALIGN_CENTER"),
|
||||
"center-left": t("EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT"),
|
||||
"center-right": t("EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT"),
|
||||
"top-left": t("EXPORTDIALOG_PDF_ALIGN_TOP_LEFT"),
|
||||
"top-center": t("EXPORTDIALOG_PDF_ALIGN_TOP_CENTER"),
|
||||
"top-right": t("EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT"),
|
||||
|
||||
@@ -226,14 +226,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "createPDF",
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties}): Promise<ArrayBuffer>",
|
||||
desc: "",
|
||||
after: "Creates a PDF from the provided SVG elements with specified scaling and page properties.\n" +
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties, filename: string}): Promise<void>",
|
||||
desc: "Creates a PDF from the provided SVG elements with specified scaling and page properties.\n" +
|
||||
"\n" +
|
||||
"@param {Object} params - The parameters for creating the PDF.\n" +
|
||||
"@param {SVGSVGElement[]} params.SVG - An array of SVG elements to be included in the PDF. If multiple SVGs are provided, each will be added to a new page.\n" +
|
||||
"@param {PDFExportScale} [params.scale={ fitToPage: true, zoom: 1 }] - The scaling options for the SVG elements.\n" +
|
||||
"@param {PDFPageProperties} [params.pageProps] - The properties for the PDF pages.\n" +
|
||||
"@param {string} params.filename - The name of the PDF file to be created.\n" +
|
||||
"@returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFExportScale\n" +
|
||||
@@ -241,14 +241,11 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"@property {number} [zoom=1] - The zoom level for the SVG. Used only if fitToPage is false. If the SVG does not fit the page, it will be tiled over multiple pages.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFPageProperties\n" +
|
||||
"@property {{width: number, height: number}} [dimensions] - The dimensions of the PDF pages. Use getPageDimensions to get standard page sizes.\n" +
|
||||
"@property {{width: number, height: number}} [dimensions] - The dimensions of the PDF pages in pixels. Use getPageDimensions to get standard page sizes.\n" +
|
||||
"@property {string} [backgroundColor] - The background color of the PDF pages.\n" +
|
||||
"@property {PDFMargin} margin - The margins of the PDF pages.\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.\n" +
|
||||
"@property {number} exportDPI - The DPI of the exported PDF (150/300/600/1200).\n" +
|
||||
"\n" +
|
||||
"@example\n" +
|
||||
"const pdfData = await createPDF({\n" +
|
||||
"@property {PDFMargin} margin - The margins of the PDF pages in pixels.\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.",
|
||||
after: "({\n" +
|
||||
" SVG: [svgElement1, svgElement2],\n" +
|
||||
" scale: { fitToPage: true },\n" +
|
||||
" pageProps: {\n" +
|
||||
@@ -256,31 +253,57 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
" backgroundColor: \"#ffffff\",\n" +
|
||||
" margin: { left: 20, right: 20, top: 20, bottom: 20 },\n" +
|
||||
" alignment: \"center\"\n" +
|
||||
" exportDPI: 300\n" +
|
||||
" filename: \"myPDF.pdf\"\n" +
|
||||
" }\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "createViewSVG",
|
||||
code: "async createViewSVG({withBackground?: boolean, theme?: 'light' | 'dark', frameRendering?: FrameRenderingOptions, padding?: number, selectedOnly?: boolean, skipInliningFonts?: boolean, embedScene?: boolean}): Promise<SVGSVGElement>",
|
||||
desc: "Creates an SVG representation of the current view with specified options.\n" +
|
||||
"\n" +
|
||||
"@param {Object} options - The options for creating the SVG.\n" +
|
||||
"@param {boolean} [options.withBackground=true] - Whether to include the background in the SVG.\n" +
|
||||
"@param {\"light\" | \"dark\"} [options.theme] - The theme to use for the SVG.\n" +
|
||||
"@param {FrameRenderingOptions} [options.frameRendering={enabled: true, name: true, outline: true, clip: true}] - The frame rendering options.\n" +
|
||||
"@param {number} [options.padding] - The padding to apply around the SVG.\n" +
|
||||
"@param {boolean} [options.selectedOnly=false] - Whether to include only the selected elements in the SVG.\n" +
|
||||
"@param {boolean} [options.skipInliningFonts=false] - Whether to skip inlining fonts in the SVG.\n" +
|
||||
"@param {boolean} [options.embedScene=false] - Whether to embed the scene in the SVG.\n" +
|
||||
"@returns {Promise<SVGSVGElement>} A promise that resolves to the SVG element.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} FrameRenderingOptions\n" +
|
||||
"@property {boolean} enabled - Whether frame rendering is enabled.\n" +
|
||||
"@property {boolean} name - Whether to include the name in the frame rendering.\n" +
|
||||
"@property {boolean} outline - Whether to include the outline in the frame rendering.\n" +
|
||||
"@property {boolean} clip - Whether to clip the frame rendering.\n",
|
||||
after: "({\n" +
|
||||
" withBackground: true,\n" +
|
||||
" theme: 'light',\n" +
|
||||
" frameRendering: { enabled: true, name: true, outline: true, clip: true },\n" +
|
||||
" padding: 10,\n" +
|
||||
" selectedOnly: false,\n" +
|
||||
" skipInliningFonts: false,\n" +
|
||||
" embedScene: false,\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "getPagePDFDimensions",
|
||||
code: "getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions",
|
||||
desc: "Returns the dimensions of a standard page size in points (pt).\n" +
|
||||
desc: "Returns the dimensions of a standard page size in pixels.\n" +
|
||||
"\n" +
|
||||
"@param {PageSize} pageSize - The standard page size. Possible values are \"A0\", \"A1\", \"A2\", \"A3\", \"A4\", \"A5\", \"Letter\", \"Legal\", \"Tabloid\".\n" +
|
||||
"@param {PageOrientation} orientation - The orientation of the page. Possible values are \"portrait\" and \"landscape\".\n" +
|
||||
"@returns {PageDimensions} - An object containing the width and height of the page in points (pt).\n" +
|
||||
"@returns {PageDimensions} - An object containing the width and height of the page in pixels.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PageDimensions\n" +
|
||||
"@property {number} width - The width of the page in points (pt).\n" +
|
||||
"@property {number} height - The height of the page in points (pt).\n" +
|
||||
"@property {number} width - The width of the page in pixels.\n" +
|
||||
"@property {number} height - The height of the page in pixels.\n" +
|
||||
"\n" +
|
||||
"@typedef {\"A0\" | \"A1\" | \"A2\" | \"A3\" | \"A4\" | \"A5\" | \"Letter\" | \"Legal\" | \"Tabloid\"} PageSize\n" +
|
||||
"\n" +
|
||||
"@typedef {\"portrait\" | \"landscape\"} PageOrientation\n" +
|
||||
"\n" +
|
||||
"@example\n" +
|
||||
"const dimensions = getPDFPageDimensions(\"A4\", \"portrait\");\n" +
|
||||
"console.log(dimensions); // { width: 595.28, height: 841.89 }",
|
||||
after: "",
|
||||
"@typedef {\"portrait\" | \"landscape\"} PageOrientation",
|
||||
after: "(\"A4\", \"portrait\");",
|
||||
},
|
||||
{
|
||||
field: "createPNG",
|
||||
|
||||
@@ -704,7 +704,7 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
if (!excalidrawData.getEquation(id).isLoaded) {
|
||||
const latex = equation.latex;
|
||||
const data = await tex2dataURL(latex, 4, this.plugin.app);
|
||||
const data = await tex2dataURL(latex, 4, this.plugin);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
mimeType: data.mimeType,
|
||||
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
addAppendUpdateCustomData,
|
||||
getSVG,
|
||||
getWithBackground,
|
||||
} from "src/utils/utils";
|
||||
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/obsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
@@ -84,6 +86,7 @@ import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
import { AddImageOptions, ImageInfo, SVGColorInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { _measureText, cloneElement, createPNG, createSVG, errorMessage, filterColorMap, getEmbeddedFileForImageElment, getFontFamily, getLineBox, getTemplate, isColorStringTransparent, isSVGColorInfo, mergeColorMapIntoSVGColorInfo, normalizeLinePoints, repositionElementsToCursor, svgColorInfoToColorMap, updateOrAddSVGColorInfo, verifyMinimumPluginVersion } from "src/utils/excalidrawAutomateUtils";
|
||||
import { exportToPDF, getMarginValue, getPageDimensions, PageDimensions, PageOrientation, PageSize, PDFExportScale, PDFPageProperties } from "src/utils/exportUtils";
|
||||
import { FrameRenderingOptions } from "src/types/utilTypes";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -885,7 +888,7 @@ export class ExcalidrawAutomate {
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
const item = this.imagesDict[key];
|
||||
if(item.latex) {
|
||||
outString += `${key}: $$${item.latex}$$\n\n`;
|
||||
outString += `${key}: $$${item.latex.trim()}$$\n\n`;
|
||||
} else {
|
||||
if(item.file) {
|
||||
if(item.file instanceof TFile) {
|
||||
@@ -932,19 +935,19 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the dimensions of a standard page size in points (pt).
|
||||
* Returns the dimensions of a standard page size in pixels.
|
||||
*
|
||||
* @param {PageSize} pageSize - The standard page size. Possible values are "A0", "A1", "A2", "A3", "A4", "A5", "Letter", "Legal", "Tabloid".
|
||||
* @param {PageOrientation} orientation - The orientation of the page. Possible values are "portrait" and "landscape".
|
||||
* @returns {PageDimensions} - An object containing the width and height of the page in points (pt).
|
||||
* @returns {PageDimensions} - An object containing the width and height of the page in pixels.
|
||||
*
|
||||
* @typedef {Object} PageDimensions
|
||||
* @property {number} width - The width of the page in points (pt).
|
||||
* @property {number} height - The height of the page in points (pt).
|
||||
* @property {number} width - The width of the page in pixels.
|
||||
* @property {number} height - The height of the page in pixels.
|
||||
*
|
||||
* @example
|
||||
* const dimensions = getPageDimensions("A4", "portrait");
|
||||
* console.log(dimensions); // { width: 595.28, height: 841.89 }
|
||||
* console.log(dimensions); // { width: 794.56, height: 1122.56 }
|
||||
*/
|
||||
getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions {
|
||||
return getPageDimensions(pageSize, orientation);
|
||||
@@ -964,12 +967,12 @@ export class ExcalidrawAutomate {
|
||||
* SVG: [svgElement1, svgElement2],
|
||||
* scale: { fitToPage: 1 },
|
||||
* pageProps: {
|
||||
* dimensions: { width: 595.28, height: 841.89 },
|
||||
* dimensions: { width: 794.56, height: 1122.56 },
|
||||
* backgroundColor: "#ffffff",
|
||||
* margin: { left: 20, right: 20, top: 20, bottom: 20 },
|
||||
* alignment: "center",
|
||||
* exportDPI: 300,
|
||||
* }
|
||||
* filename: "example.pdf",
|
||||
* });
|
||||
*/
|
||||
async createPDF({
|
||||
@@ -1003,6 +1006,68 @@ export class ExcalidrawAutomate {
|
||||
await exportToPDF({SVG, scale, pageProps, filename});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SVG representation of the current view.
|
||||
*
|
||||
* @param {Object} options - The options for creating the SVG.
|
||||
* @param {boolean} [options.withBackground=true] - Whether to include the background in the SVG.
|
||||
* @param {"light" | "dark"} [options.theme] - The theme to use for the SVG.
|
||||
* @param {FrameRenderingOptions} [options.frameRendering={enabled: true, name: true, outline: true, clip: true}] - The frame rendering options.
|
||||
* @param {number} [options.padding] - The padding to apply around the SVG.
|
||||
* @param {boolean} [options.selectedOnly=false] - Whether to include only the selected elements in the SVG.
|
||||
* @param {boolean} [options.skipInliningFonts=false] - Whether to skip inlining fonts in the SVG.
|
||||
* @param {boolean} [options.embedScene=false] - Whether to embed the scene in the SVG.
|
||||
* @returns {Promise<SVGSVGElement>} A promise that resolves to the SVG element.
|
||||
*/
|
||||
async createViewSVG({
|
||||
withBackground = true,
|
||||
theme,
|
||||
frameRendering = {enabled: true, name: true, outline: true, clip: true},
|
||||
padding,
|
||||
selectedOnly = false,
|
||||
skipInliningFonts = false,
|
||||
embedScene = false,
|
||||
} : {
|
||||
withBackground?: boolean,
|
||||
theme?: "light" | "dark",
|
||||
frameRendering?: FrameRenderingOptions,
|
||||
padding?: number,
|
||||
selectedOnly?: boolean,
|
||||
skipInliningFonts?: boolean,
|
||||
embedScene?: boolean,
|
||||
}): Promise<SVGSVGElement> {
|
||||
if(!this.targetView || !this.targetView.file || !this.targetView._loaded) {
|
||||
console.log("No view loaded");
|
||||
return;
|
||||
}
|
||||
const view = this.targetView;
|
||||
const scene = this.targetView.getScene(selectedOnly);
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: view.getViewExportWithBackground(withBackground),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, view.file),
|
||||
skipInliningFonts,
|
||||
frameRendering,
|
||||
};
|
||||
|
||||
return await getSVG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: view.getViewExportTheme(theme),
|
||||
exportEmbedScene: view.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
view.getViewExportPadding(padding),
|
||||
view.file,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SVG image from the ExcalidrawAutomate elements and the template provided.
|
||||
* @param {string} [templatePath] - The template path to use for the SVG.
|
||||
@@ -1849,7 +1914,7 @@ export class ExcalidrawAutomate {
|
||||
*/
|
||||
async addLaTex(topX: number, topY: number, tex: string): Promise<string> {
|
||||
const id = nanoid();
|
||||
const image = await tex2dataURL(tex, 4, this.plugin.app);
|
||||
const image = await tex2dataURL(tex, 4, this.plugin);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
@@ -1891,7 +1956,7 @@ export class ExcalidrawAutomate {
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
return await tex2dataURL(tex,scale, this.plugin.app);
|
||||
return await tex2dataURL(tex,scale, this.plugin);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1462,7 +1462,7 @@ export class ExcalidrawData {
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
outString += `${key}: $$${this.equations.get(key).latex}$$\n\n`;
|
||||
outString += `${key}: $$${this.equations.get(key).latex.trim()}$$\n\n`;
|
||||
}
|
||||
}
|
||||
if (this.files.size > 0) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import ExcalidrawView from "../view/ExcalidrawView";
|
||||
import { FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { App } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
declare const loadMathjaxToSVG: Function;
|
||||
let mathjaxLoaded = false;
|
||||
@@ -52,7 +52,7 @@ export const updateEquation = async (
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app: App,
|
||||
plugin: ExcalidrawPlugin,
|
||||
): Promise<{
|
||||
mimeType: MimeType;
|
||||
fileId: FileId;
|
||||
@@ -61,7 +61,7 @@ export async function tex2dataURL(
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
await loadMathJax();
|
||||
return tex2dataURLExternal(tex, scale, app);
|
||||
return tex2dataURLExternal(tex, scale, plugin);
|
||||
}
|
||||
|
||||
export const clearMathJaxVariables = () => {
|
||||
|
||||
@@ -17,4 +17,11 @@ export enum PreviewImageType {
|
||||
PNG = "PNG",
|
||||
SVGIMG = "SVGIMG",
|
||||
SVG = "SVG"
|
||||
}
|
||||
|
||||
export interface FrameRenderingOptions {
|
||||
enabled: boolean;
|
||||
name: boolean;
|
||||
outline: boolean;
|
||||
clip: boolean;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { DEVICE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { getParentOfClass } from "./obsidianUtils";
|
||||
import { App, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { getLinkParts } from "./utils";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
|
||||
@@ -62,8 +62,12 @@ export function setFileToLocalGraph(app: App, file: TFile) {
|
||||
app.workspace.iterateAllLeaves((l) => {
|
||||
if (l.view?.getViewType() === "localgraph") lgv = l.view;
|
||||
});
|
||||
if (lgv) {
|
||||
//@ts-ignore
|
||||
lgv.loadFile(file);
|
||||
try {
|
||||
if (lgv) {
|
||||
//@ts-ignore
|
||||
lgv.loadFile(file);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,16 @@ import { t } from 'src/lang/helpers';
|
||||
|
||||
const DPI = 96;
|
||||
|
||||
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
||||
export type PDFPageAlignment =
|
||||
| "center"
|
||||
| "top-left"
|
||||
| "top-center"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-center"
|
||||
| "bottom-right"
|
||||
| "center-left"
|
||||
| "center-right";
|
||||
export type PDFPageMarginString = "none" | "tiny" | "normal";
|
||||
|
||||
export interface PDFExportScale {
|
||||
@@ -33,27 +42,30 @@ export interface PageDimensions {
|
||||
|
||||
export type PageOrientation = "portrait" | "landscape";
|
||||
|
||||
// All dimensions in points (pt)
|
||||
// All dimensions in pixels (pt)
|
||||
export const STANDARD_PAGE_SIZES = {
|
||||
"A0": { width: 2383.94, height: 3370.39 },
|
||||
"A1": { width: 1683.78, height: 2383.94 },
|
||||
"A2": { width: 1190.55, height: 1683.78 },
|
||||
"A3": { width: 841.89, height: 1190.55 },
|
||||
"A4": { width: 595.28, height: 841.89 },
|
||||
"A5": { width: 419.53, height: 595.28 },
|
||||
"Letter": { width: 612, height: 792 },
|
||||
"Legal": { width: 612, height: 1008 },
|
||||
"Tabloid": { width: 792, height: 1224 },
|
||||
A0: { width: 3179.52, height: 4494.96 }, // 33.11 × 46.81 inches
|
||||
A1: { width: 2245.76, height: 3179.52 }, // 23.39 × 33.11 inches
|
||||
A2: { width: 1587.76, height: 2245.76 }, // 16.54 × 23.39 inches
|
||||
A3: { width: 1122.56, height: 1587.76 }, // 11.69 × 16.54 inches
|
||||
A4: { width: 794.56, height: 1122.56 }, // 8.27 × 11.69 inches
|
||||
A5: { width: 559.37, height: 794.56 }, // 5.83 × 8.27 inches
|
||||
A6: { width: 397.28, height: 559.37 }, // 4.13 × 5.83 inches
|
||||
Legal: { width: 816, height: 1344 }, // 8.5 × 14 inches
|
||||
Letter: { width: 816, height: 1056 }, // 8.5 × 11 inches
|
||||
Tabloid: { width: 1056, height: 1632 }, // 11 × 17 inches
|
||||
Ledger: { width: 1632, height: 1056 } // 17 × 11 inches
|
||||
} as const;
|
||||
|
||||
export type PageSize = keyof typeof STANDARD_PAGE_SIZES;
|
||||
|
||||
//margins are in pixels
|
||||
export function getMarginValue(margin:PDFPageMarginString): PDFMargin {
|
||||
switch(margin) {
|
||||
case "none": return { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
case "tiny": return { left: 5, right: 5, top: 5, bottom: 5 };
|
||||
case "normal": return { left: 25, right: 25, top: 25, bottom: 25 };
|
||||
default: return { left: 25, right: 25, top: 25, bottom: 25 };
|
||||
case "tiny": return { left: 10, right: 10, top: 10, bottom: 10 };
|
||||
case "normal": return { left: 60, right: 60, top: 60, bottom: 60 };
|
||||
default: return { left: 60, right: 60, top: 60, bottom: 60 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +212,7 @@ async function printPdf(
|
||||
}
|
||||
|
||||
function calculateDimensions(
|
||||
svg: SVGSVGElement,
|
||||
svgWidth: number,
|
||||
svgHeight: number,
|
||||
pageDim: PageDimensions,
|
||||
@@ -216,6 +229,9 @@ function calculateDimensions(
|
||||
}[],
|
||||
pages: number
|
||||
} {
|
||||
const viewBox = svg.getAttribute('viewBox')?.split(' ').map(Number) || [0, 0, svgWidth, svgHeight];
|
||||
const [viewBoxX, viewBoxY] = viewBox;
|
||||
|
||||
const availableWidth = pageDim.width - margin.left - margin.right;
|
||||
const availableHeight = pageDim.height - margin.top - margin.bottom;
|
||||
|
||||
@@ -259,7 +275,7 @@ function calculateDimensions(
|
||||
|
||||
return {
|
||||
tiles: [{
|
||||
viewBox: `0 0 ${svgWidth} ${svgHeight}`,
|
||||
viewBox: `${viewBoxX} ${viewBoxY} ${svgWidth} ${svgHeight}`,
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
x: position.x,
|
||||
@@ -270,35 +286,98 @@ function calculateDimensions(
|
||||
}
|
||||
|
||||
// Content needs to be tiled
|
||||
const tiles = [];
|
||||
const cols = Math.ceil(finalWidth / availableWidth);
|
||||
const rows = Math.ceil(finalHeight / availableHeight);
|
||||
|
||||
// Calculate total available space considering all margins
|
||||
const totalAvailableWidth = cols * availableWidth;
|
||||
const totalAvailableHeight = rows * availableHeight;
|
||||
|
||||
// Calculate global position in the total available space
|
||||
const globalPosition = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
totalAvailableWidth,
|
||||
totalAvailableHeight,
|
||||
{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
},
|
||||
alignment
|
||||
);
|
||||
|
||||
const tiles = [];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const tileX = (col * availableWidth) / (scale.zoom || 1);
|
||||
const tileY = (row * availableHeight) / (scale.zoom || 1);
|
||||
const tileWidth = Math.min(svgWidth - tileX, availableWidth / (scale.zoom || 1));
|
||||
const tileHeight = Math.min(svgHeight - tileY, availableHeight / (scale.zoom || 1));
|
||||
const scaledTileWidth = tileWidth * (scale.zoom || 1);
|
||||
const scaledTileHeight = tileHeight * (scale.zoom || 1);
|
||||
// Calculate where this tile intersects with the image in global space
|
||||
const tileGlobalX = col * availableWidth;
|
||||
const tileGlobalY = row * availableHeight;
|
||||
|
||||
// Calculate the intersection of the tile with the image
|
||||
const intersectX = Math.max(tileGlobalX, globalPosition.x);
|
||||
const intersectY = Math.max(tileGlobalY, globalPosition.y);
|
||||
const intersectRight = Math.min(tileGlobalX + availableWidth, globalPosition.x + finalWidth);
|
||||
const intersectBottom = Math.min(tileGlobalY + availableHeight, globalPosition.y + finalHeight);
|
||||
|
||||
// Calculate position for each tile
|
||||
const position = calculatePosition(
|
||||
scaledTileWidth,
|
||||
scaledTileHeight,
|
||||
pageDim.width,
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment
|
||||
);
|
||||
// Calculate actual tile dimensions
|
||||
const scaledTileWidth = Math.max(0, intersectRight - intersectX);
|
||||
const scaledTileHeight = Math.max(0, intersectBottom - intersectY);
|
||||
|
||||
if (scaledTileWidth <= 0 || scaledTileHeight <= 0) continue;
|
||||
|
||||
// Calculate viewBox coordinates
|
||||
const tileX = (intersectX - globalPosition.x) / (scale.zoom || 1);
|
||||
const tileY = (intersectY - globalPosition.y) / (scale.zoom || 1);
|
||||
const tileWidth = scaledTileWidth / (scale.zoom || 1);
|
||||
const tileHeight = scaledTileHeight / (scale.zoom || 1);
|
||||
|
||||
let x = margin.left;
|
||||
let y = margin.top;
|
||||
|
||||
// Handle horizontal positioning
|
||||
const widthRatio = scaledTileWidth / availableWidth;
|
||||
if (widthRatio >= 0.99 && widthRatio <= 1.01) {
|
||||
x = margin.left;
|
||||
} else {
|
||||
if (alignment === "center" || alignment === "top-center" || alignment === "bottom-center") {
|
||||
if(col === 0) {
|
||||
x = margin.left + (availableWidth - scaledTileWidth);
|
||||
} else {
|
||||
x = margin.left;
|
||||
}
|
||||
} else if (alignment.endsWith('right')) {
|
||||
x = pageDim.width - margin.right - scaledTileWidth;
|
||||
} else {
|
||||
x = margin.left;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle vertical positioning
|
||||
const heightRatio = scaledTileHeight / availableHeight;
|
||||
if (heightRatio >= 0.99 && heightRatio <= 1.01) {
|
||||
y = margin.top;
|
||||
} else {
|
||||
if (alignment === "center" || alignment === "center-left" || alignment === "center-right") {
|
||||
if(row === 0) {
|
||||
y = margin.top + (availableHeight - scaledTileHeight);
|
||||
} else {
|
||||
y = margin.top;
|
||||
}
|
||||
} else if (alignment.startsWith('bottom')) {
|
||||
y = pageDim.height - margin.bottom - scaledTileHeight;
|
||||
} else {
|
||||
y = margin.top;
|
||||
}
|
||||
}
|
||||
|
||||
tiles.push({
|
||||
viewBox: `${tileX} ${tileY} ${tileWidth} ${tileHeight}`,
|
||||
viewBox: `${tileX + viewBoxX} ${tileY + viewBoxY} ${tileWidth} ${tileHeight}`,
|
||||
width: scaledTileWidth,
|
||||
height: scaledTileHeight,
|
||||
x: position.x,
|
||||
y: position.y
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -321,14 +400,14 @@ function calculatePosition(
|
||||
let y = margin.top;
|
||||
|
||||
// Handle horizontal alignment
|
||||
if (alignment.includes('center')) {
|
||||
if (alignment === "center" || alignment === "top-center" || alignment === "bottom-center") {
|
||||
x = margin.left + (availableWidth - svgWidth) / 2;
|
||||
} else if (alignment.includes('right')) {
|
||||
} else if (alignment.endsWith('right')) {
|
||||
x = margin.left + availableWidth - svgWidth;
|
||||
}
|
||||
|
||||
// Handle vertical alignment
|
||||
if (alignment.startsWith('center')) {
|
||||
if (alignment === "center" || alignment === "center-left" || alignment === "center-right") {
|
||||
y = margin.top + (availableHeight - svgHeight) / 2;
|
||||
} else if (alignment.startsWith('bottom')) {
|
||||
y = margin.top + availableHeight - svgHeight;
|
||||
@@ -361,12 +440,13 @@ export async function exportToPDF({
|
||||
allPagesDiv.style.width = "100%";
|
||||
allPagesDiv.style.height = "fit-content";
|
||||
|
||||
let j = 1;
|
||||
let j = 0;
|
||||
for (const svg of SVG) {
|
||||
const svgWidth = parseFloat(svg.getAttribute('width') || '0');
|
||||
const svgHeight = parseFloat(svg.getAttribute('height') || '0');
|
||||
|
||||
const {tiles} = calculateDimensions(
|
||||
svg,
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
pageProps.dimensions,
|
||||
@@ -375,7 +455,7 @@ export async function exportToPDF({
|
||||
pageProps.alignment
|
||||
);
|
||||
|
||||
let i = 1;
|
||||
let i = 0;
|
||||
for (const tile of tiles) {
|
||||
const pageDiv = createDiv();
|
||||
pageDiv.style.width = `${width}px`;
|
||||
@@ -389,9 +469,9 @@ export async function exportToPDF({
|
||||
clonedSVG.setAttribute('viewBox', tile.viewBox);
|
||||
clonedSVG.style.width = `${tile.width}px`;
|
||||
clonedSVG.style.height = `${tile.height}px`;
|
||||
clonedSVG.style.position = 'relative';
|
||||
clonedSVG.style.position = 'absolute';
|
||||
clonedSVG.style.left = `${tile.x}px`;
|
||||
clonedSVG.style.top = `${tile.y}px`;
|
||||
clonedSVG.style.top = `${tile.y + (i+j)*height}px`;
|
||||
|
||||
pageDiv.appendChild(clonedSVG);
|
||||
allPagesDiv.appendChild(pageDiv);
|
||||
|
||||
@@ -307,7 +307,7 @@ export async function getSVG (
|
||||
: {},
|
||||
},
|
||||
files: scene.files,
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
exportPadding: exportSettings.frameRendering?.enabled ? 0 : padding,
|
||||
exportingFrame: null,
|
||||
renderEmbeddables: true,
|
||||
skipInliningFonts: exportSettings.skipInliningFonts,
|
||||
@@ -365,7 +365,7 @@ export async function getPNG (
|
||||
: {},
|
||||
},
|
||||
files: filterFiles(scene.files),
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
exportPadding: exportSettings.frameRendering?.enabled ? 0 : padding,
|
||||
mimeType: "image/png",
|
||||
getDimensions: (width: number, height: number) => ({
|
||||
width: width * scale,
|
||||
|
||||
@@ -153,6 +153,7 @@ import { DropManager } from "./managers/DropManager";
|
||||
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { exportToPDF, getMarginValue, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
|
||||
import { create } from "domain";
|
||||
import { FrameRenderingOptions } from "src/types/utilTypes";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
@@ -186,12 +187,7 @@ export interface ExportSettings {
|
||||
withBackground: boolean;
|
||||
withTheme: boolean;
|
||||
isMask: boolean;
|
||||
frameRendering?: { //optional, overrides relevant appState settings for rendering the frame
|
||||
enabled: boolean;
|
||||
name: boolean;
|
||||
outline: boolean;
|
||||
clip: boolean;
|
||||
};
|
||||
frameRendering?: FrameRenderingOptions; //optional, overrides relevant appState settings for rendering the frame
|
||||
skipInliningFonts?: boolean;
|
||||
}
|
||||
|
||||
@@ -475,36 +471,75 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
);
|
||||
}
|
||||
|
||||
public getViewExportTheme(theme?:string):string {
|
||||
if(theme) return theme;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.theme : getExportTheme(this.plugin, this.file, this.excalidrawAPI.getAppState().theme)
|
||||
}
|
||||
|
||||
public getViewExportEmbedScene(embedScene?:boolean):boolean {
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene;
|
||||
}
|
||||
|
||||
public getViewExportPadding(padding?: number): number {
|
||||
if(typeof padding !== "undefined") return padding;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.padding : getExportPadding(this.plugin, this.file)
|
||||
}
|
||||
|
||||
public getViewExportScale(scale?: number): number {
|
||||
if(typeof scale !== "undefined") return scale;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.scale : getPNGScale(this.plugin, this.file);
|
||||
}
|
||||
|
||||
public getViewExportWithBackground(withBackground?:boolean) {
|
||||
if(typeof withBackground !== "undefined") return withBackground;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? !ed.transparent : getWithBackground(this.plugin, this.file)
|
||||
}
|
||||
|
||||
public async svg(scene: any, theme?:string, embedScene?: boolean, embedFont: boolean = false): Promise<SVGSVGElement> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.svg, "ExcalidrawView.svg", scene, theme, embedScene);
|
||||
const ed = this.exportDialog;
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: ed ? !ed.transparent : getWithBackground(this.plugin, this.file),
|
||||
withBackground: this.getViewExportWithBackground(),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
skipInliningFonts: !embedFont,
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getSVG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: theme ?? (ed ? ed.theme : getExportTheme(this.plugin, this.file, scene.appState.theme)),
|
||||
exportEmbedScene: typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene,
|
||||
theme: this.getViewExportTheme(theme),
|
||||
exportEmbedScene: this.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
ed ? ed.padding : getExportPadding(this.plugin, this.file),
|
||||
this.getViewExportPadding(),
|
||||
this.file,
|
||||
);
|
||||
}
|
||||
@@ -567,7 +602,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
public async exportPDF(
|
||||
toVault: boolean,
|
||||
selectedOnly?: boolean,
|
||||
pageSize: PageSize = "A4",
|
||||
orientation: PageOrientation = "portrait"
|
||||
@@ -605,34 +639,27 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
public async png(scene: any, theme?:string, embedScene?: boolean): Promise<Blob> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.png, "ExcalidrawView.png", scene, theme, embedScene);
|
||||
const ed = this.exportDialog;
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: ed ? !ed.transparent : getWithBackground(this.plugin, this.file),
|
||||
withBackground: this.getViewExportWithBackground(),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: theme ?? (ed ? ed.theme : getExportTheme(this.plugin, this.file, scene.appState.theme)),
|
||||
exportEmbedScene: typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene,
|
||||
theme: this.getViewExportTheme(theme),
|
||||
exportEmbedScene: this.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
ed ? ed.padding : getExportPadding(this.plugin, this.file),
|
||||
ed ? ed.scale : getPNGScale(this.plugin, this.file),
|
||||
this.getViewExportPadding(),
|
||||
this.getViewExportScale(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3095,7 +3122,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
this.exportDialog.createForm();
|
||||
}
|
||||
this.exportDialog.open();
|
||||
})
|
||||
@@ -4892,7 +4918,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
private actionOpenExportImageDialog() {
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
this.exportDialog.createForm();
|
||||
}
|
||||
this.exportDialog.open();
|
||||
}
|
||||
|
||||
30
styles.css
30
styles.css
@@ -342,7 +342,9 @@ label.color-input-container > input {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.excalidraw .panelColumn {
|
||||
.excalidraw .selected-shape-actions .panelColumn,
|
||||
.excalidraw .App-mobile-menu .panelColumn
|
||||
{
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -388,11 +390,14 @@ div.excalidraw-draginfo {
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
/*Arrow Picker Popover Overflow*/
|
||||
/*
|
||||
.excalidraw .selected-shape-actions .Island.App-menu__left,
|
||||
.excalidraw .selected-shape-actions .Island.App-menu__left .panelColumn,
|
||||
.excalidraw .Island .App-mobile-menu,
|
||||
.excalidraw .Island.App-menu__left {
|
||||
/*Arrow Picker Popover Overflow*/
|
||||
.excalidraw .Island .App-mobile-menu .panelColumn {
|
||||
overflow: visible;
|
||||
}
|
||||
}*/
|
||||
|
||||
.excalidraw__embeddable-container .view-header {
|
||||
display: none !important;
|
||||
@@ -712,4 +717,21 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
.excalidraw-release .nav-button.is-active {
|
||||
border-bottom: 2px solid var(--interactive-accent);
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.excalidraw .picker-content {
|
||||
grid-gap: 0.2rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .picker-content button.picker-option{
|
||||
width: 1.85rem !important;
|
||||
height: 1.85rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .picker {
|
||||
padding: 0.2rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .context-menu {
|
||||
height: fit-content;
|
||||
}
|
||||
Reference in New Issue
Block a user