mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
3 Commits
2.8.0-beta
...
2.8.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4209774b4e | ||
|
|
b18637f7d0 | ||
|
|
01e392158d |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.8.0-beta-1",
|
||||
"version": "2.8.0-beta-2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -502,11 +502,12 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
pdfSettings: {
|
||||
pageSize: "A4",
|
||||
pageOrientation: "portrait",
|
||||
fitToPage: true,
|
||||
fitToPage: 1,
|
||||
paperColor: "white",
|
||||
customPaperColor: "#ffffff",
|
||||
alignment: "center",
|
||||
margin: "normal"
|
||||
margin: "normal",
|
||||
exportDPI: 300,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1048,8 +1048,15 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION: "Orientation",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT: "Portrait",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
|
||||
EXPORTDIALOG_PDF_DPI: "Image quality [DPI]",
|
||||
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_SCALE_OPTION: "Use image scale (may span multiple pages)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
|
||||
@@ -1079,4 +1086,8 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG to Clipboard",
|
||||
EXPORTDIALOG_PDF: "Export PDF",
|
||||
EXPORTDIALOG_PDFTOVAULT: "PDF to Vault",
|
||||
|
||||
EXPORTDIALOG_PDF_PROGRESS_NOTICE: "Exporting page",
|
||||
EXPORTDIALOG_PDF_PROGRESS_IMAGE: "of image",
|
||||
EXPORTDIALOG_PDF_PROGRESS_DONE: "Export complete",
|
||||
};
|
||||
|
||||
@@ -38,11 +38,12 @@ export class ExportDialog extends Modal {
|
||||
private contentContainer: HTMLDivElement;
|
||||
private buttonContainerRow1: HTMLDivElement;
|
||||
private buttonContainerRow2: HTMLDivElement;
|
||||
public fitToPage: boolean = true;
|
||||
public fitToPage: number = 1;
|
||||
public paperColor: "white" | "scene" | "custom" = "white";
|
||||
public customPaperColor: string = "#ffffff";
|
||||
public alignment: PDFPageAlignment = "center";
|
||||
public margin: PDFPageMarginString = "normal";
|
||||
public exportDPI: number = 300;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
@@ -68,6 +69,7 @@ export class ExportDialog extends Modal {
|
||||
this.customPaperColor = plugin.settings.pdfSettings.customPaperColor;
|
||||
this.alignment = plugin.settings.pdfSettings.alignment;
|
||||
this.margin = plugin.settings.pdfSettings.margin;
|
||||
this.exportDPI = plugin.settings.pdfSettings.exportDPI ?? 300;
|
||||
|
||||
this.saveSettings = false;
|
||||
}
|
||||
@@ -276,7 +278,8 @@ export class ExportDialog extends Modal {
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin
|
||||
margin: this.margin,
|
||||
exportDPI: this.exportDPI,
|
||||
};
|
||||
|
||||
new PDFExportSettingsComponent(
|
||||
@@ -290,6 +293,7 @@ export class ExportDialog extends Modal {
|
||||
this.customPaperColor = pdfSettings.customPaperColor;
|
||||
this.alignment = pdfSettings.alignment;
|
||||
this.margin = pdfSettings.margin;
|
||||
this.exportDPI = pdfSettings.exportDPI ?? 300;
|
||||
}
|
||||
).render();
|
||||
}
|
||||
@@ -378,7 +382,8 @@ export class ExportDialog extends Modal {
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin
|
||||
margin: this.margin,
|
||||
exportDPI: this.exportDPI,
|
||||
};
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(t("EXPORTDIALOG_SAVE_CONFIRMATION"));
|
||||
|
||||
@@ -5,11 +5,12 @@ import { t } from "src/lang/helpers";
|
||||
export interface PDFExportSettings {
|
||||
pageSize: PageSize;
|
||||
pageOrientation: PageOrientation;
|
||||
fitToPage: boolean;
|
||||
fitToPage: number;
|
||||
paperColor: "white" | "scene" | "custom";
|
||||
customPaperColor: string;
|
||||
alignment: PDFPageAlignment;
|
||||
margin: PDFPageMarginString;
|
||||
exportDPI: number;
|
||||
}
|
||||
|
||||
export class PDFExportSettingsComponent {
|
||||
@@ -55,17 +56,42 @@ export class PDFExportSettingsComponent {
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_DPI"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"150": "150",
|
||||
"300": "300",
|
||||
"600": "600",
|
||||
"1200": "1200"
|
||||
})
|
||||
.setValue(`${this.settings.exportDPI}`)
|
||||
.onChange(value => {
|
||||
this.settings.exportDPI = parseInt(value);
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_FIT_TO_PAGE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"scale": t("EXPORTDIALOG_PDF_SCALE_OPTION"),
|
||||
"fit": t("EXPORTDIALOG_PDF_FIT_OPTION"),
|
||||
"scale": t("EXPORTDIALOG_PDF_SCALE_OPTION")
|
||||
"fit-2": t("EXPORTDIALOG_PDF_FIT_2_OPTION"),
|
||||
"fit-4": t("EXPORTDIALOG_PDF_FIT_4_OPTION"),
|
||||
"fit-6": t("EXPORTDIALOG_PDF_FIT_6_OPTION"),
|
||||
"fit-8": t("EXPORTDIALOG_PDF_FIT_8_OPTION"),
|
||||
"fit-12": t("EXPORTDIALOG_PDF_FIT_12_OPTION"),
|
||||
"fit-16": t("EXPORTDIALOG_PDF_FIT_16_OPTION")
|
||||
})
|
||||
.setValue(this.settings.fitToPage ? "fit" : "scale")
|
||||
.setValue(this.settings.fitToPage === 1 ? "fit" :
|
||||
(typeof this.settings.fitToPage === "number" ? `fit-${this.settings.fitToPage}` : "scale"))
|
||||
.onChange(value => {
|
||||
this.settings.fitToPage = value === "fit";
|
||||
this.settings.fitToPage = value === "scale" ? 0 :
|
||||
(value === "fit" ? 1 : parseInt(value.split("-")[1]));
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -245,6 +245,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"@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" +
|
||||
@@ -255,6 +256,7 @@ 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" +
|
||||
" }\n" +
|
||||
"});",
|
||||
},
|
||||
|
||||
@@ -955,25 +955,26 @@ export class ExcalidrawAutomate {
|
||||
*
|
||||
* @param {Object} params - The parameters for creating the PDF.
|
||||
* @param {SVGSVGElement[]} params.SVG - An array of SVG elements to be included in the PDF.
|
||||
* @param {PDFExportScale} [params.scale={ fitToPage: true, zoom: 1 }] - The scaling options for the SVG elements.
|
||||
* @param {PDFExportScale} [params.scale={ fitToPage: 1, zoom: 1 }] - The scaling options for the SVG elements.
|
||||
* @param {PDFPageProperties} [params.pageProps] - The properties for the PDF pages.
|
||||
* @returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.
|
||||
*
|
||||
* @example
|
||||
* const pdfData = await createToPDF({
|
||||
* SVG: [svgElement1, svgElement2],
|
||||
* scale: { fitToPage: true },
|
||||
* scale: { fitToPage: 1 },
|
||||
* pageProps: {
|
||||
* dimensions: { width: 595.28, height: 841.89 },
|
||||
* backgroundColor: "#ffffff",
|
||||
* margin: { left: 20, right: 20, top: 20, bottom: 20 },
|
||||
* alignment: "center"
|
||||
* alignment: "center",
|
||||
* exportDPI: 300,
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
async createPDF({
|
||||
SVG,
|
||||
scale = { fitToPage: true, zoom: 1 },
|
||||
scale = { fitToPage: 1, zoom: 1 },
|
||||
pageProps,
|
||||
}: {
|
||||
SVG: SVGSVGElement[];
|
||||
@@ -984,6 +985,7 @@ export class ExcalidrawAutomate {
|
||||
pageProps = {
|
||||
alignment: this.plugin.settings.pdfSettings.alignment,
|
||||
margin: getMarginValue(this.plugin.settings.pdfSettings.margin),
|
||||
exportDPI: this.plugin.settings.pdfSettings.exportDPI ?? 300,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { PDFDocument, rgb } from '@cantoo/pdf-lib';
|
||||
import exp from 'constants';
|
||||
import { Notice } from 'obsidian';
|
||||
import { getEA } from 'src/core';
|
||||
import { t } from 'src/lang/helpers';
|
||||
|
||||
const PDF_DPI = 72;
|
||||
|
||||
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
||||
export type PDFPageMarginString = "none" | "tiny" | "normal";
|
||||
|
||||
export interface PDFExportScale {
|
||||
fitToPage: boolean;
|
||||
fitToPage: number; // 0 means use zoom, >1 means fit to that many pages exactly
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
@@ -22,6 +26,7 @@ export interface PDFPageProperties {
|
||||
backgroundColor?: string;
|
||||
margin: PDFMargin;
|
||||
alignment: PDFPageAlignment;
|
||||
exportDPI: number;
|
||||
}
|
||||
|
||||
export interface PageDimensions {
|
||||
@@ -80,28 +85,10 @@ function calculatePosition(
|
||||
pageHeight: number,
|
||||
margin: PDFMargin,
|
||||
alignment: PDFPageAlignment,
|
||||
scale: PDFExportScale
|
||||
): {x: number, y: number} {
|
||||
const availableWidth = pageWidth - margin.left - margin.right;
|
||||
const availableHeight = pageHeight - margin.top - margin.bottom;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Debug',
|
||||
input: {
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
},
|
||||
calculated: {
|
||||
availableWidth,
|
||||
availableHeight
|
||||
}
|
||||
}));
|
||||
|
||||
let x = margin.left;
|
||||
let y = margin.bottom;
|
||||
|
||||
@@ -121,52 +108,70 @@ function calculatePosition(
|
||||
y = pageHeight - margin.top - svgHeight;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Intermediate',
|
||||
x,
|
||||
y,
|
||||
alignment,
|
||||
availableHeight,
|
||||
marginTop: margin.top,
|
||||
marginBottom: margin.bottom,
|
||||
svgHeight,
|
||||
pageHeight
|
||||
}));
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Result',
|
||||
x,
|
||||
y,
|
||||
finalPosition: {
|
||||
bottom: y,
|
||||
top: y + svgHeight,
|
||||
left: x,
|
||||
right: x + svgWidth
|
||||
}
|
||||
}));
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
function getNumberOfPages(
|
||||
width: number,
|
||||
height: number,
|
||||
availableWidth: number,
|
||||
availableHeight: number
|
||||
): number {
|
||||
const cols = Math.ceil(width / availableWidth);
|
||||
const rows = Math.ceil(height / availableHeight);
|
||||
return cols * rows;
|
||||
}
|
||||
|
||||
function calculateDimensions(
|
||||
svgWidth: number,
|
||||
svgHeight: number,
|
||||
pageDim: PageDimensions,
|
||||
margin: PDFPageProperties['margin'],
|
||||
scale: PDFExportScale,
|
||||
alignment: PDFPageAlignment
|
||||
alignment: PDFPageAlignment,
|
||||
exportDPI: number,
|
||||
): SVGDimensions[] {
|
||||
const svg_to_pdf_scale = PDF_DPI / exportDPI;
|
||||
const pdfWidth = svgWidth * svg_to_pdf_scale;
|
||||
const pdfHeight = svgHeight * svg_to_pdf_scale;
|
||||
const availableWidth = pageDim.width - margin.left - margin.right;
|
||||
const availableHeight = pageDim.height - margin.top - margin.bottom;
|
||||
|
||||
let finalWidth: number;
|
||||
let finalHeight: number;
|
||||
// If fitToPage is specified, find optimal zoom using binary search
|
||||
if (scale.fitToPage > 0) {
|
||||
let low = 0;
|
||||
let high = 100; // Start with a reasonable upper bound
|
||||
let bestZoom = 1;
|
||||
const tolerance = 0.000001;
|
||||
|
||||
if (scale.fitToPage) {
|
||||
const ratio = Math.min(availableWidth / svgWidth, availableHeight / svgHeight);
|
||||
finalWidth = svgWidth * ratio;
|
||||
finalHeight = svgHeight * ratio;
|
||||
|
||||
while (high - low > tolerance) {
|
||||
const mid = (low + high) / 2;
|
||||
const scaledWidth = pdfWidth * mid;
|
||||
const scaledHeight = pdfHeight * mid;
|
||||
const pages = getNumberOfPages(scaledWidth, scaledHeight, availableWidth, availableHeight);
|
||||
|
||||
if (pages > scale.fitToPage) {
|
||||
high = mid;
|
||||
} else {
|
||||
bestZoom = mid;
|
||||
low = mid;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a small reduction to prevent floating-point issues
|
||||
scale.zoom = Math.round(bestZoom * 0.99999 * 1000000) / 1000000;
|
||||
}
|
||||
|
||||
// Now handle as regular scale mode
|
||||
const finalWidth = Math.round(pdfWidth * (scale.zoom || 1) * 1000) / 1000;
|
||||
const finalHeight = Math.round(pdfHeight * (scale.zoom || 1) * 1000) / 1000;
|
||||
|
||||
// Round the available dimensions as well for consistent comparison
|
||||
const roundedAvailableWidth = Math.round(availableWidth * 1000) / 1000;
|
||||
const roundedAvailableHeight = Math.round(availableHeight * 1000) / 1000;
|
||||
|
||||
if (finalWidth <= roundedAvailableWidth && finalHeight <= roundedAvailableHeight) {
|
||||
// Content fits on one page
|
||||
const position = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
@@ -174,9 +179,8 @@ function calculateDimensions(
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
);
|
||||
|
||||
|
||||
return [{
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
@@ -184,57 +188,37 @@ function calculateDimensions(
|
||||
y: position.y
|
||||
}];
|
||||
} else {
|
||||
// Scale mode - may need multiple pages
|
||||
finalWidth = svgWidth * (scale.zoom || 1);
|
||||
finalHeight = svgHeight * (scale.zoom || 1);
|
||||
// Content needs to be tiled across multiple pages
|
||||
const dimensions: SVGDimensions[] = [];
|
||||
// Calculate exact number of needed columns and rows
|
||||
const cols = Math.ceil(finalWidth / roundedAvailableWidth);
|
||||
const rows = Math.ceil(finalHeight / roundedAvailableHeight);
|
||||
|
||||
if (finalWidth <= availableWidth && finalHeight <= availableHeight) {
|
||||
// Content fits on one page
|
||||
const position = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
pageDim.width,
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
);
|
||||
|
||||
return [{
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
x: position.x,
|
||||
y: position.y
|
||||
}];
|
||||
} else {
|
||||
// Content needs to be tiled across multiple pages
|
||||
const dimensions: SVGDimensions[] = [];
|
||||
const cols = Math.ceil(finalWidth / availableWidth);
|
||||
const rows = Math.ceil(finalHeight / availableHeight);
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const tileWidth = Math.min(availableWidth, finalWidth - col * availableWidth);
|
||||
const tileHeight = Math.min(availableHeight, finalHeight - row * availableHeight);
|
||||
|
||||
// Calculate y coordinate following the same logic as single-page rendering
|
||||
// We start from the bottom margin and work our way up
|
||||
//const y = margin.bottom + row * availableHeight;
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
// Calculate remaining width and height for this tile
|
||||
const remainingWidth = finalWidth - col * roundedAvailableWidth;
|
||||
const remainingHeight = finalHeight - row * roundedAvailableHeight;
|
||||
|
||||
// Only create tile if there's actual content to show
|
||||
if (remainingWidth > 0 && remainingHeight > 0) {
|
||||
const tileWidth = Math.min(roundedAvailableWidth, remainingWidth);
|
||||
const tileHeight = Math.min(roundedAvailableHeight, remainingHeight);
|
||||
|
||||
dimensions.push({
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
x: margin.left,
|
||||
y: margin.top,
|
||||
sourceX: col * availableWidth / (scale.zoom || 1),
|
||||
sourceY: row * availableHeight / (scale.zoom || 1),
|
||||
sourceWidth: tileWidth / (scale.zoom || 1),
|
||||
sourceHeight: tileHeight / (scale.zoom || 1)
|
||||
sourceX: (col * roundedAvailableWidth) / ((scale.zoom || 1) * svg_to_pdf_scale),
|
||||
sourceY: (row * roundedAvailableHeight) / ((scale.zoom || 1) * svg_to_pdf_scale),
|
||||
sourceWidth: tileWidth / ((scale.zoom || 1) * svg_to_pdf_scale),
|
||||
sourceHeight: tileHeight / ((scale.zoom || 1) * svg_to_pdf_scale)
|
||||
});
|
||||
}
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,12 +227,12 @@ async function addSVGToPage(
|
||||
svg: SVGSVGElement,
|
||||
dimensions: SVGDimensions,
|
||||
pageDim: PageDimensions,
|
||||
backgroundColor?: string
|
||||
pageProps: PDFPageProperties
|
||||
) {
|
||||
const page = pdfDoc.addPage([pageDim.width, pageDim.height]);
|
||||
|
||||
if (backgroundColor && backgroundColor !== '#ffffff') {
|
||||
const { r, g, b } = hexToRGB(backgroundColor);
|
||||
if (pageProps.backgroundColor && pageProps.backgroundColor !== '#ffffff') {
|
||||
const { r, g, b } = hexToRGB(pageProps.backgroundColor);
|
||||
page.drawRectangle({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -258,52 +242,88 @@ async function addSVGToPage(
|
||||
});
|
||||
}
|
||||
|
||||
// Clone and modify SVG for tiling if needed
|
||||
let svgToEmbed = svg;
|
||||
if (dimensions.sourceX !== undefined) {
|
||||
svgToEmbed = svg.cloneNode(true) as SVGSVGElement;
|
||||
const viewBox = `${dimensions.sourceX} ${dimensions.sourceY} ${dimensions.sourceWidth} ${dimensions.sourceHeight}`;
|
||||
svgToEmbed.setAttribute('viewBox', viewBox);
|
||||
svgToEmbed.setAttribute('width', String(dimensions.sourceWidth));
|
||||
svgToEmbed.setAttribute('height', String(dimensions.sourceHeight));
|
||||
}
|
||||
|
||||
const svgImage = await pdfDoc.embedSvg(svgToEmbed.outerHTML);
|
||||
// Render SVG to canvas with specified DPI
|
||||
const canvas = await renderSVGToCanvas(svg, dimensions, pageProps.exportDPI);
|
||||
|
||||
// Convert canvas to PNG
|
||||
const pngData = canvas.toDataURL('image/png');
|
||||
|
||||
// Embed the PNG in the PDF
|
||||
const image = await pdfDoc.embedPng(pngData);
|
||||
|
||||
console.log(JSON.stringify({message: "addSVGToPage", dimensions, html: svgToEmbed.outerHTML}));
|
||||
|
||||
// Adjust y-coordinate to account for PDF coordinate system
|
||||
const adjustedY = pageDim.height - dimensions.y;
|
||||
const adjustedY = pageDim.height - dimensions.y - dimensions.height;
|
||||
|
||||
page.drawSvg(svgImage, {
|
||||
// Draw the image
|
||||
page.drawImage(image, {
|
||||
x: dimensions.x,
|
||||
y: adjustedY,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Draw SVG',
|
||||
x: dimensions.x,
|
||||
y: adjustedY,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height
|
||||
}));
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
async function renderSVGToCanvas(
|
||||
svg: SVGSVGElement,
|
||||
dimensions: SVGDimensions,
|
||||
exportDPI: number = 300,
|
||||
): Promise<HTMLCanvasElement> {
|
||||
const canvas = document.createElement('canvas');
|
||||
const scale = exportDPI / PDF_DPI;
|
||||
|
||||
canvas.width = dimensions.width * scale;
|
||||
canvas.height = dimensions.height * scale;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('Failed to get canvas context');
|
||||
|
||||
let svgToRender = svg;
|
||||
if (dimensions.sourceX !== undefined) {
|
||||
svgToRender = svg.cloneNode(true) as SVGSVGElement;
|
||||
const viewBox = `${dimensions.sourceX} ${dimensions.sourceY} ${dimensions.sourceWidth} ${dimensions.sourceHeight}`;
|
||||
svgToRender.setAttribute('viewBox', viewBox);
|
||||
svgToRender.setAttribute('width', String(dimensions.sourceWidth));
|
||||
svgToRender.setAttribute('height', String(dimensions.sourceHeight));
|
||||
}
|
||||
|
||||
const svgBlob = new Blob([svgToRender.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const blobUrl = URL.createObjectURL(svgBlob);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
resolve(canvas);
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
reject(new Error('Failed to load SVG'));
|
||||
};
|
||||
img.src = blobUrl;
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportToPDF({
|
||||
SVG,
|
||||
scale = { fitToPage: true, zoom: 1 },
|
||||
scale = { fitToPage: 1, zoom: 1 },
|
||||
pageProps,
|
||||
}: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale: PDFExportScale;
|
||||
pageProps: PDFPageProperties;
|
||||
}): Promise<ArrayBuffer> {
|
||||
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
const msg = t('EXPORTDIALOG_PDF_PROGRESS_NOTICE');
|
||||
const imgmsg = t('EXPORTDIALOG_PDF_PROGRESS_IMAGE');
|
||||
|
||||
let notice = new Notice(msg, 0);
|
||||
|
||||
let j=1;
|
||||
for (const svg of SVG) {
|
||||
const svgWidth = parseFloat(svg.getAttribute('width') || '0');
|
||||
const svgHeight = parseFloat(svg.getAttribute('height') || '0');
|
||||
@@ -314,14 +334,30 @@ export async function exportToPDF({
|
||||
pageProps.dimensions,
|
||||
pageProps.margin,
|
||||
scale,
|
||||
pageProps.alignment
|
||||
pageProps.alignment,
|
||||
pageProps.exportDPI
|
||||
);
|
||||
|
||||
let i=1;
|
||||
for (const dim of dimensions) {
|
||||
await addSVGToPage(pdfDoc, svg, dim, pageProps.dimensions, pageProps.backgroundColor);
|
||||
//@ts-ignore
|
||||
if(notice.containerEl.parentElement) {
|
||||
notice.setMessage(`${msg} ${i++}/${dimensions.length}${SVG.length>1?` ${imgmsg} ${j}`:""}`);
|
||||
} else {
|
||||
notice = new Notice(`${msg} ${i++}/${dimensions.length}${SVG.length>1?` ${imgmsg} ${j}`:""}`, 0);
|
||||
}
|
||||
await addSVGToPage(pdfDoc, svg, dim, pageProps.dimensions, pageProps);
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if(notice.containerEl.parentElement) {
|
||||
notice.setMessage(t('EXPORTDIALOG_PDF_PROGRESS_DONE'));
|
||||
setTimeout(() => notice.hide(), 4000);
|
||||
} else {
|
||||
new Notice(t('EXPORTDIALOG_PDF_PROGRESS_DONE'));
|
||||
}
|
||||
return pdfDoc.save();
|
||||
}
|
||||
|
||||
|
||||
@@ -589,16 +589,16 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
const pdfArrayBuffer = await exportToPDF({
|
||||
SVG: [svg],
|
||||
scale: {
|
||||
...this.exportDialog.fitToPage
|
||||
? { fitToPage: true }
|
||||
: { zoom: this.exportDialog.scale, fitToPage: false },
|
||||
scale: {
|
||||
zoom: this.exportDialog.scale,
|
||||
fitToPage: this.exportDialog.fitToPage
|
||||
},
|
||||
pageProps: {
|
||||
dimensions: getPageDimensions(pageSize, orientation),
|
||||
backgroundColor: this.exportDialog.getPaperColor(),
|
||||
margin: getMarginValue(this.exportDialog.margin),
|
||||
alignment: this.exportDialog.alignment,
|
||||
exportDPI: this.exportDialog.exportDPI,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user