mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
export to vault, added pdf settings
This commit is contained in:
@@ -1050,11 +1050,30 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION: "Orientation",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT: "Portrait",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE: "Page Fitting",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION: "Fit to page",
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION: "Use image scale (may span multiple pages)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
|
||||
EXPORTDIALOG_PDF_PAPER_SCENE: "Use scene color",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT: "Bottom Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER: "Bottom Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT: "Bottom Right",
|
||||
EXPORTDIALOG_PDF_MARGIN: "Margin",
|
||||
EXPORTDIALOG_PDF_MARGIN_NONE: "None",
|
||||
EXPORTDIALOG_PDF_MARGIN_TINY: "Small",
|
||||
EXPORTDIALOG_PDF_MARGIN_NORMAL: "Normal",
|
||||
// Buttons
|
||||
EXPORTDIALOG_PNGTOFILE : "PNT to File",
|
||||
EXPORTDIALOG_SVGTOFILE : "SVG to File",
|
||||
EXPORTDIALOG_EXCALIDRAW: "Excalidraw",
|
||||
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG to Clipboard",
|
||||
EXPORTDIALOG_PDF: "Export PDF",
|
||||
EXPORTDIALOG_PDFTOVAULT: "Save PDF to Vault",
|
||||
EXPORTDIALOG_PDF: "Export",
|
||||
EXPORTDIALOG_PDFTOVAULT: "Save to Vault",
|
||||
};
|
||||
|
||||
@@ -6,9 +6,11 @@ import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/utils";
|
||||
import { PageOrientation, PageSize, STANDARD_PAGE_SIZES } from "src/utils/exportUtils";
|
||||
import { PageOrientation, PageSize, PDFMargin, PDFPageAlignment, PDFPageMarginString, STANDARD_PAGE_SIZES } from "src/utils/exportUtils";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
private api: ExcalidrawImperativeAPI;
|
||||
@@ -34,6 +36,11 @@ export class ExportDialog extends Modal {
|
||||
private activeTab: "image" | "pdf" = "image";
|
||||
private contentContainer: HTMLDivElement;
|
||||
private buttonContainer: HTMLDivElement;
|
||||
public fitToPage: boolean = true;
|
||||
public paperColor: "white" | "scene" | "custom" = "white";
|
||||
public customPaperColor: string = "#ffffff";
|
||||
public alignment: PDFPageAlignment = "center";
|
||||
public margin: PDFPageMarginString = "normal";
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
@@ -296,6 +303,82 @@ export class ExportDialog extends Modal {
|
||||
this.pageOrientation = value as PageOrientation;
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PDF_FIT_TO_PAGE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"fit": t("EXPORTDIALOG_PDF_FIT_OPTION"),
|
||||
"scale": t("EXPORTDIALOG_PDF_SCALE_OPTION")
|
||||
})
|
||||
.setValue(this.fitToPage ? "fit" : "scale")
|
||||
.onChange(value => {
|
||||
this.fitToPage = value === "fit";
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PDF_MARGIN"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"none": t("EXPORTDIALOG_PDF_MARGIN_NONE"),
|
||||
"tiny": t("EXPORTDIALOG_PDF_MARGIN_TINY"),
|
||||
"normal": t("EXPORTDIALOG_PDF_MARGIN_NORMAL")
|
||||
})
|
||||
.setValue(this.margin)
|
||||
.onChange(value => {
|
||||
this.margin = value as typeof this.margin;
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
const paperColorSetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PDF_PAPER_COLOR"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"white": t("EXPORTDIALOG_PDF_PAPER_WHITE"),
|
||||
"scene": t("EXPORTDIALOG_PDF_PAPER_SCENE"),
|
||||
"custom": t("EXPORTDIALOG_PDF_PAPER_CUSTOM")
|
||||
})
|
||||
.setValue(this.paperColor)
|
||||
.onChange(value => {
|
||||
this.paperColor = value as typeof this.paperColor;
|
||||
colorInput.style.display = (value === "custom") ? "block" : "none";
|
||||
})
|
||||
);
|
||||
|
||||
const colorInput = paperColorSetting.controlEl.createEl("input", {
|
||||
type: "color",
|
||||
value: this.customPaperColor
|
||||
});
|
||||
colorInput.style.width = "50px";
|
||||
colorInput.style.marginLeft = "10px";
|
||||
colorInput.style.display = this.paperColor === "custom" ? "block" : "none";
|
||||
colorInput.addEventListener("change", (e) => {
|
||||
this.customPaperColor = (e.target as HTMLInputElement).value;
|
||||
});
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PDF_ALIGNMENT"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"center": t("EXPORTDIALOG_PDF_ALIGN_CENTER"),
|
||||
"top-left": t("EXPORTDIALOG_PDF_ALIGN_TOP_LEFT"),
|
||||
"top-center": t("EXPORTDIALOG_PDF_ALIGN_TOP_CENTER"),
|
||||
"top-right": t("EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT"),
|
||||
"bottom-left": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT"),
|
||||
"bottom-center": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER"),
|
||||
"bottom-right": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT")
|
||||
})
|
||||
.setValue(this.alignment)
|
||||
.onChange(value => {
|
||||
this.alignment = value as typeof this.alignment;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createImageButtons() {
|
||||
@@ -328,13 +411,28 @@ export class ExportDialog extends Modal {
|
||||
}
|
||||
|
||||
private createPDFButton() {
|
||||
const bPDFVault = this.buttonContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PDFTOVAULT"),
|
||||
cls: "excalidraw-prompt-button"
|
||||
});
|
||||
bPDFVault.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
true,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
);
|
||||
this.close();
|
||||
};
|
||||
|
||||
if (!DEVICE.isDesktop) return;
|
||||
const bPDF = this.buttonContainer.createEl("button", {
|
||||
const bPDFExport = this.buttonContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PDF"),
|
||||
cls: "excalidraw-prompt-button"
|
||||
});
|
||||
bPDF.onclick = () => {
|
||||
bPDFExport.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
false,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
@@ -342,4 +440,13 @@ export class ExportDialog extends Modal {
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
public getPaperColor(): string {
|
||||
switch (this.paperColor) {
|
||||
case "white": return "#ffffff";
|
||||
case "scene": return this.api.getAppState().viewBackgroundColor;
|
||||
case "custom": return this.customPaperColor;
|
||||
default: return "#ffffff";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import { PDFDocument, rgb } from '@cantoo/pdf-lib';
|
||||
import { getEA } from 'src/core';
|
||||
import { svgToBase64 } from './utils';
|
||||
|
||||
|
||||
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
||||
export type PDFPageMarginString = "none" | "tiny" | "normal";
|
||||
|
||||
interface PDFExportScale {
|
||||
fitToPage: boolean;
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
export interface PDFMargin {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
interface PDFPageProperties {
|
||||
dimensions?: {width: number; height: number};
|
||||
backgroundColor?: string;
|
||||
margin?: {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
};
|
||||
margin: PDFMargin;
|
||||
alignment: PDFPageAlignment;
|
||||
}
|
||||
|
||||
interface PageDimensions {
|
||||
@@ -40,6 +46,15 @@ export const STANDARD_PAGE_SIZES = {
|
||||
|
||||
export type PageSize = keyof typeof STANDARD_PAGE_SIZES;
|
||||
|
||||
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: 20, right: 20, top: 20, bottom: 20 };
|
||||
default: return { left: 20, right: 20, top: 20, bottom: 20 };
|
||||
}
|
||||
}
|
||||
|
||||
export function getPageDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions {
|
||||
const dimensions = STANDARD_PAGE_SIZES[pageSize];
|
||||
return orientation === "portrait"
|
||||
@@ -47,8 +62,6 @@ export function getPageDimensions(pageSize: PageSize, orientation: PageOrientati
|
||||
: { width: dimensions.height, height: dimensions.width };
|
||||
}
|
||||
|
||||
const DEFAULT_MARGIN = 20; // 20pt margins
|
||||
|
||||
interface SVGDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -117,20 +130,15 @@ async function addSVGToPage(
|
||||
export async function exportToPDF({
|
||||
SVG,
|
||||
scale = { fitToPage: true, zoom: 1 },
|
||||
pageProps = {}
|
||||
pageProps,
|
||||
}: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale?: PDFExportScale;
|
||||
pageProps?: PDFPageProperties;
|
||||
scale: PDFExportScale;
|
||||
pageProps: PDFPageProperties;
|
||||
}): Promise<ArrayBuffer> {
|
||||
const margin = pageProps.margin ?? {
|
||||
left: DEFAULT_MARGIN,
|
||||
right: DEFAULT_MARGIN,
|
||||
top: DEFAULT_MARGIN,
|
||||
bottom: DEFAULT_MARGIN
|
||||
};
|
||||
|
||||
const pageDim = pageProps.dimensions ?? getPageDimensions("A4", "portrait");
|
||||
const margin = pageProps.margin;
|
||||
const pageDim = pageProps.dimensions;
|
||||
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
for (const svg of SVG) {
|
||||
|
||||
@@ -493,3 +493,22 @@ export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:
|
||||
const fileList = getExcalidrawEmbeddedFilesFiletree(sourceFile, plugin);
|
||||
return fileList.some(f=>f.stat.mtime > mtime);
|
||||
}
|
||||
|
||||
export async function createOrOverwriteFile(app: App, path: string, content: string | ArrayBuffer): Promise<TFile> {
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(path));
|
||||
if(content instanceof ArrayBuffer) {
|
||||
if(file && file instanceof TFile) {
|
||||
await app.vault.modifyBinary(file, content);
|
||||
return file;
|
||||
} else {
|
||||
return await app.vault.createBinary(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modify(file, content);
|
||||
return file;
|
||||
} else {
|
||||
return await app.vault.create(path, content);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,7 @@ import {
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
checkAndCreateFolder,
|
||||
createOrOverwriteFile,
|
||||
download,
|
||||
getDataURLFromURL,
|
||||
getIMGFilename,
|
||||
@@ -150,7 +151,8 @@ import { getPDFCropRect } from "../utils/PDFUtils";
|
||||
import { Position, ViewSemaphores } from "../types/excalidrawViewTypes";
|
||||
import { DropManager } from "./managers/DropManager";
|
||||
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { exportToPDF, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
|
||||
import { exportToPDF, getMarginValue, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
|
||||
import { create } from "domain";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
@@ -517,19 +519,13 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const svg = await this.svg(scene,theme, embedScene, true);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
|
||||
const svgString = svg.outerHTML;
|
||||
if (file && file instanceof TFile) {
|
||||
await this.app.vault.modify(file, svgString);
|
||||
} else {
|
||||
await this.app.vault.create(filepath, svgString);
|
||||
}
|
||||
await createOrOverwriteFile(this.app, filepath, svgString);
|
||||
}
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
@@ -558,6 +554,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
public async exportPDF(
|
||||
toVault: boolean,
|
||||
selectedOnly?: boolean,
|
||||
pageSize: PageSize = "A4",
|
||||
orientation: PageOrientation = "portrait"
|
||||
@@ -579,16 +576,16 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
const pdfArrayBuffer = await exportToPDF({
|
||||
SVG: [svg],
|
||||
scale: { fitToPage: true },
|
||||
scale: {
|
||||
...this.exportDialog.fitToPage
|
||||
? { fitToPage: true }
|
||||
: { zoom: this.exportDialog.scale, fitToPage: false },
|
||||
},
|
||||
pageProps: {
|
||||
dimensions: getPageDimensions(pageSize, orientation),
|
||||
backgroundColor: this.exportDialog.transparent ? undefined : "#ffffff",
|
||||
margin: {
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
}
|
||||
backgroundColor: this.exportDialog.getPaperColor(),
|
||||
margin: getMarginValue(this.exportDialog.margin),
|
||||
alignment: this.exportDialog.alignment,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -596,11 +593,17 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
return;
|
||||
}
|
||||
|
||||
download(
|
||||
"data:application/pdf;base64",
|
||||
arrayBufferToBase64(pdfArrayBuffer),
|
||||
`${this.file.basename}.pdf`
|
||||
);
|
||||
if(toVault) {
|
||||
const filepath = getIMGFilename(this.file.path, "pdf");
|
||||
const file = await createOrOverwriteFile(this.app, filepath, pdfArrayBuffer);
|
||||
this.app.workspace.getLeaf("split").openFile(file);
|
||||
} else {
|
||||
download(
|
||||
"data:application/pdf;base64",
|
||||
arrayBufferToBase64(pdfArrayBuffer),
|
||||
`${this.file.basename}.pdf`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async png(scene: any, theme?:string, embedScene?: boolean): Promise<Blob> {
|
||||
@@ -646,17 +649,11 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const png = await this.png(scene, theme, embedScene);
|
||||
if (!png) {
|
||||
return;
|
||||
}
|
||||
if (file && file instanceof TFile) {
|
||||
await this.app.vault.modifyBinary(file, await png.arrayBuffer());
|
||||
} else {
|
||||
await this.app.vault.createBinary(filepath, await png.arrayBuffer());
|
||||
}
|
||||
await createOrOverwriteFile(this.app, filepath, await png.arrayBuffer());
|
||||
}
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
|
||||
Reference in New Issue
Block a user