2.8.0-rc-1, 0.17.6-28
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run

This commit is contained in:
zsviczian
2025-01-24 17:11:28 +01:00
parent 23b94da8f0
commit b0bc03437a
8 changed files with 182 additions and 53 deletions

View File

@@ -24,7 +24,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.6-27",
"@zsviczian/excalidraw": "0.17.6-28",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",

View File

@@ -391,7 +391,6 @@ export class ExportDialog extends Modal {
});
bPDFExport.onclick = () => {
this.view.exportPDF(
false,
this.hasSelectedElements && this.exportSelectedOnly,
this.pageSize,
this.pageOrientation
@@ -402,7 +401,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";

View File

@@ -226,14 +226,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
},
{
field: "createPDF",
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties}): Promise<void>",
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" +
@@ -244,10 +244,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
"@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 in pixels.\n" +
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.\n" +
"\n" +
"@example\n" +
"const pdfData = await createPDF({\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" +
@@ -255,9 +253,40 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
" backgroundColor: \"#ffffff\",\n" +
" margin: { left: 20, right: 20, top: 20, bottom: 20 },\n" +
" alignment: \"center\"\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",
@@ -273,12 +302,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
"\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",

View File

@@ -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,
@@ -969,6 +972,7 @@ export class ExcalidrawAutomate {
* margin: { left: 20, right: 20, top: 20, bottom: 20 },
* alignment: "center",
* }
* filename: "example.pdf",
* });
*/
async createPDF({
@@ -1002,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.

View File

@@ -17,4 +17,11 @@ export enum PreviewImageType {
PNG = "PNG",
SVGIMG = "SVGIMG",
SVG = "SVG"
}
export interface FrameRenderingOptions {
enabled: boolean;
name: boolean;
outline: boolean;
clip: boolean;
}

View File

@@ -203,6 +203,7 @@ async function printPdf(
}
function calculateDimensions(
svg: SVGSVGElement,
svgWidth: number,
svgHeight: number,
pageDim: PageDimensions,
@@ -219,6 +220,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;
@@ -262,7 +266,7 @@ function calculateDimensions(
return {
tiles: [{
viewBox: `0 0 ${svgWidth} ${svgHeight}`,
viewBox: `${viewBoxX} ${viewBoxY} ${svgWidth} ${svgHeight}`,
width: finalWidth,
height: finalHeight,
x: position.x,
@@ -297,7 +301,7 @@ function calculateDimensions(
);
tiles.push({
viewBox: `${tileX} ${tileY} ${tileWidth} ${tileHeight}`,
viewBox: `${tileX + viewBoxX} ${tileY + viewBoxY} ${tileWidth} ${tileHeight}`,
width: scaledTileWidth,
height: scaledTileHeight,
x: position.x,
@@ -364,12 +368,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,
@@ -378,7 +383,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`;
@@ -394,7 +399,7 @@ export async function exportToPDF({
clonedSVG.style.height = `${tile.height}px`;
clonedSVG.style.position = 'absolute';
clonedSVG.style.left = `${tile.x}px`;
clonedSVG.style.top = `${tile.y + (i-1)*height}px`;
clonedSVG.style.top = `${tile.y + (i+j)*height}px`;
pageDiv.appendChild(clonedSVG);
allPagesDiv.appendChild(pageDiv);

View File

@@ -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,

View File

@@ -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(),
);
}