Compare commits

..

13 Commits

Author SHA1 Message Date
zsviczian
23b94da8f0 update LOCALE to read from localStorage
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-24 13:12:24 +01:00
zsviczian
1f227ddd24 Update manifest-beta.json 2025-01-24 07:24:50 +01:00
zsviczian
12152665af 2.8.0-beta-5
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-22 23:55:10 +01:00
zsviczian
064e17b29d rebuilt export PDF using Electron PDF export 2025-01-22 23:10:42 +01:00
zsviczian
0aaba80c82 replace foreign object
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-21 23:28:33 +01:00
zsviczian
1744668fbd 2.8.0-beta-4
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-20 23:02:58 +01:00
zsviczian
8e3e2ffb25 Merge pull request #2222 from zsviczian/imagepathhook
imagepath hook
2025-01-20 22:41:41 +01:00
zsviczian
f5475bfde6 imagepath hook 2025-01-20 22:40:27 +01:00
zsviczian
27fa270b42 Merge pull request #2221 from dmscode/master
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Update zh-cn.ts to a796621
2025-01-20 07:31:38 +01:00
dmscode
15ece75b5d Update zh-cn.ts to a796621 2025-01-20 07:50:42 +08:00
zsviczian
a796621f93 2.8.0-beta-3, import pdf-lib on demand, fixed area hover preview, fixed local font in popout windows, fixed scrollable elements panel and context menu on (not only mobile)
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-19 20:11:19 +01:00
zsviczian
3c943c6685 Merge pull request #2220 from dmscode/master
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Update zh-cn.ts to 01e3921
2025-01-19 18:50:27 +01:00
dmscode
af8a848d14 Update zh-cn.ts to 01e3921 2025-01-19 15:27:16 +08:00
19 changed files with 579 additions and 533 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.8.0-beta-2",
"version": "2.8.0-rc-1",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

123
package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "2.2.5",
"license": "MIT",
"dependencies": {
"@cantoo/pdf-lib": "^2.2.4",
"@popperjs/core": "^2.11.8",
"@zsviczian/colormaster": "^1.2.2",
"@zsviczian/excalidraw": "0.17.6-27",
@@ -1820,9 +1819,9 @@
"dev": true
},
"node_modules/@babel/runtime": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz",
"integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==",
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -1884,19 +1883,6 @@
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
},
"node_modules/@cantoo/pdf-lib": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.2.4.tgz",
"integrity": "sha512-69bECXjl0QVMrBTEsPZDPFmhoyLGwXKiJI5JWPgBnAx0kS5+7v76rNCeYMTbDmZaPgvdIViMvTaz2HVCe8u1fw==",
"dependencies": {
"@pdf-lib/standard-fonts": "^1.0.0",
"@pdf-lib/upng": "^1.0.1",
"color": "^4.2.3",
"crypto-js": "^4.2.0",
"node-html-better-parser": "^1.4.0",
"pako": "^1.0.11"
}
},
"node_modules/@codemirror/commands": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
@@ -2361,22 +2347,6 @@
"node": ">= 8"
}
},
"node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
"dependencies": {
"pako": "^1.0.6"
}
},
"node_modules/@pdf-lib/upng": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
"dependencies": {
"pako": "^1.0.10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -3477,9 +3447,9 @@
}
},
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -3848,18 +3818,6 @@
"node": ">=6"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3872,32 +3830,8 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/colord": {
"version": "2.9.3",
@@ -4010,11 +3944,6 @@
"node": ">= 8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"node_modules/css-declaration-sorter": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
@@ -5594,21 +5523,6 @@
"node": ">= 0.4"
}
},
"node_modules/html-entities": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/mdevils"
},
{
"type": "patreon",
"url": "https://patreon.com/mdevils"
}
]
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -5703,11 +5617,6 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -6830,14 +6739,6 @@
"dev": true,
"peer": true
},
"node_modules/node-html-better-parser": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.4.7.tgz",
"integrity": "sha512-cJcBhlrn432XtbzPmzxxMLsHeSUnE5qFQtVbVlHXvUGt2ccqc0/wBsB43CFv39BIkLkP5rrPUN5Hg52CnheH+A==",
"dependencies": {
"html-entities": "^2.3.2"
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -8410,14 +8311,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",

View File

@@ -40,8 +40,7 @@
"react-dom": "^18.2.0",
"roughjs": "^4.5.2",
"woff2sfnt-sfnt2woff": "^1.0.0",
"es6-promise-pool": "2.5.0",
"@cantoo/pdf-lib": "^2.2.4"
"es6-promise-pool": "2.5.0"
},
"devDependencies": {
"jsesc": "^3.0.2",

View File

@@ -147,6 +147,7 @@ const BUILD_CONFIG = {
entryFileNames: 'main.js',
format: 'cjs',
exports: 'default',
inlineDynamicImports: true, // Add this line only
},
plugins: getRollupPlugins(
{

View File

@@ -105,6 +105,41 @@
*/
//ea.onFileCreateHook = (data) => {};
/**
* 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.
*
* @param data - An object containing the following properties:
* @property {string} [currentImageName] - Default name for the image.
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
*
* @returns {string} - The new filepath for the image including full vault path and extension.
*
* Example usage:
* ```
* onImageFilePathHook: (data) => {
* const { currentImageName, drawingFilePath } = data;
* const ext = currentImageName.split('.').pop();
* // Generate a new filepath based on the drawing file name and other criteria
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
* }
* ```
* onImageFilePathHook: (data: {
* currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
* drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
* }) => string = null;
*/
//ea.onImageFilePathHook = (data) => {};
/**
* If set, this callback is triggered whenever the active canvas color changes
* onCanvasColorChangeHook: (

View File

@@ -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)"/>`;

File diff suppressed because one or more lines are too long

View File

@@ -801,15 +801,16 @@ const tmpObsidianWYSIWYG = async (
if(Boolean(ctx.frontmatter)) {
el.empty();
} else {
const warningEl = el.querySelector("div>h3[data-heading^='Unable to find section #^");
//Obsidian changed this at some point from h3 to h5 and also the text...
const warningEl = el.querySelector("div>*[data-heading^='Unable to find ");
if(warningEl) {
const ref = warningEl.getAttr("data-heading").match(/Unable to find section (#\^(?:group=|area=|frame=|clippedframe=)[^ ]*)/)?.[1];
const dataHeading = warningEl.getAttr("data-heading");
const ref = warningEl.getAttr("data-heading").match(/Unable to find[^^]+(\^(?:group=|area=|frame=|clippedframe=)[^ ”]+)/)?.[1];
if(ref) {
attr.fname = file.path + ref;
attr.fname = file.path + "#" +ref;
areaPreview = true;
}
}
}
if(!isFrontmatterDiv && !areaPreview) {
if(el.parentElement === containerEl) containerEl.removeChild(el);

View File

@@ -507,7 +507,6 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
customPaperColor: "#ffffff",
alignment: "center",
margin: "normal",
exportDPI: 300,
},
};
@@ -2509,6 +2508,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.requestReloadDrawings = true;
this.plugin.settings.experimentalEnableFourthFont = value;
this.applySettingsUpdate();
if(value) {
this.plugin.initializeFonts();
}
}),
);

View File

@@ -1048,15 +1048,14 @@ 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_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",
@@ -1085,9 +1084,11 @@ FILENAME_HEAD: "Filename",
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG to Clipboard",
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_NOTICE: "Exporting PDF. If this image is large, it may take a while.",
EXPORTDIALOG_PDF_PROGRESS_DONE: "Export complete",
EXPORTDIALOG_PDF_PROGRESS_ERROR: "Error exporting PDF, check developer console for details",
//exportUtils.ts
PDF_EXPORT_DESKTOP_ONLY: "PDF export is only available on desktop",
};

View File

@@ -386,13 +386,14 @@ FILENAME_HEAD: "文件名",
"此设置不会影响您在 Excalidraw 模式下的绘图显示,或者在将绘图嵌入 Markdown 文档时,或在渲染悬停预览时。<br><ul>" +
"<li>请参阅下面‘嵌入和导出’部分的 <a href='#"+TAG_PDFEXPORT+"'>PDF 导出</a> 相关设置。</li></ul><br>" +
"您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在将 Excalidraw 文件导出为 PDF 时将文件渲染为图像",
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME : "在 Obsidian 中导出为 PDF 格式时将 Excalidraw 渲染为图像" ,
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
"处于 Markdown 视图模式时,此设置控制 Excalidraw 在使用 Obsidian 的 <b>导出为 PDF</b> 功能,将 Excalidraw 文件导出为 PDF 的行为。<br>" +
"<ul><li><b>启用</b> 时,PDF 将仅显示 Excalidraw 绘图</li>" +
"<li><b>禁用</b> 时,PDF 将显示文档的 Markdown 部分(背景笔记)。</li></ul>" +
"请参阅上面‘外观和行为’部分的 <<a href='#"+TAG_MDREADINGMODE+"'>>Markdown 阅读模式</a> 相关设置。" +
"⚠️ 注意,您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。⚠️",
"此设置控制在使用 Obsidian 内置的<b>导出为 PDF</b>功能,如何将 Excalidraw 文件导出为 PDF。<br>" +
"<ul><li><b>启用</b>PDF 将包含图像格式的 Excalidraw 绘图</li>" +
"<li><b>禁用</b>PDF 将包含作为文本的 Markdown 内容。</li></ul>" +
"注意:此设置不会影响 Excalidraw 本身的 PDF 导出功能。<br>" +
"请参阅上方“外观和行为”部分中与<a href='#" + TAG_MDREADINGMODE + "'>Markdown 阅读模式</a>相关的其他设置。<br>" +
"⚠️ 您必须关闭并重新打开 Excalidraw/Markdown 文件,设置更改才会生效。⚠️",
HOTKEY_OVERRIDE_HEAD: "热键覆盖",
HOTKEY_OVERRIDE_DESC: `一些 Excalidraw 的热键,例如 ${labelCTRL()}+Enter 用于编辑文本,或 ${labelCTRL()}+K 用于创建元素链接。` +
"与 Obsidian 的热键设置发生冲突。您在下面添加的热键组合将在使用 Excalidraw 时覆盖 Obsidian 的热键设置," +
@@ -661,6 +662,7 @@ FILENAME_HEAD: "文件名",
EXPORT_EMBED_SCENE_DESC:
"在导出的图像中嵌入 Excalidraw 场景。可以通过在文件级别添加 <code>excalidraw-export-embed-scene: true/false</code> frontmatter 元数据键来覆盖此设置。" +
"此设置仅在您下次(重新)打开绘图时生效。",
PDF_EXPORT_SETTINGS : "PDF 导出设置",
EXPORT_HEAD: "导出设置",
EXPORT_SYNC_NAME:
"保持 SVG/PNG 文件名与绘图文件同步",
@@ -1006,4 +1008,86 @@ FILENAME_HEAD: "文件名",
LINK_CLICK_POPOUT : "在弹出窗口中打开" ,
LINK_CLICK_NEW_TAB : "在新标签页中打开" ,
LINK_CLICK_MD_PROPS : "显示 Markdown 图片属性对话框(仅在嵌入 Markdown 文档为图片时适用)" ,
// 导出对话框
// 对话框和标签页
EXPORTDIALOG_TITLE : "导出图形",
EXPORTDIALOG_TAB_IMAGE : "图像",
EXPORTDIALOG_TAB_PDF : "PDF",
// 设置持久化
EXPORTDIALOG_SAVE_SETTINGS : "将图像设置保存到文件 doc.properties 吗?",
EXPORTDIALOG_SAVE_SETTINGS_SAVE : "保存为预设",
EXPORTDIALOG_SAVE_SETTINGS_ONETIME : "仅本次使用",
// 图像设置
EXPORTDIALOG_IMAGE_SETTINGS : "图像",
EXPORTDIALOG_IMAGE_DESC : "PNG 支持透明。外部文件可以包含 Excalidraw 场景数据。",
EXPORTDIALOG_PADDING : "边距",
EXPORTDIALOG_SCALE : "缩放",
EXPORTDIALOG_CURRENT_PADDING : "当前边距:",
EXPORTDIALOG_SIZE_DESC : "缩放会影响输出大小",
EXPORTDIALOG_SCALE_VALUE : "缩放:",
EXPORTDIALOG_IMAGE_SIZE : "大小:",
// 主题和背景
EXPORTDIALOG_EXPORT_THEME : "主题",
EXPORTDIALOG_THEME_LIGHT : "浅色",
EXPORTDIALOG_THEME_DARK : "深色",
EXPORTDIALOG_BACKGROUND : "背景",
EXPORTDIALOG_BACKGROUND_TRANSPARENT : "透明",
EXPORTDIALOG_BACKGROUND_USE_COLOR : "使用场景颜色",
// 选择
EXPORTDIALOG_SELECTED_ELEMENTS : "导出",
EXPORTDIALOG_SELECTED_ALL : "整个场景",
EXPORTDIALOG_SELECTED_SELECTED : "仅选中部分",
// 导出选项
EXPORTDIALOG_EMBED_SCENE : "包含场景数据吗?",
EXPORTDIALOG_EMBED_YES : "是",
EXPORTDIALOG_EMBED_NO : "否",
// PDF 设置
EXPORTDIALOG_PDF_SETTINGS : "PDF",
EXPORTDIALOG_PAGE_SIZE : "页面大小",
EXPORTDIALOG_PAGE_ORIENTATION : "方向",
EXPORTDIALOG_ORIENTATION_PORTRAIT : "纵向",
EXPORTDIALOG_ORIENTATION_LANDSCAPE : "横向",
EXPORTDIALOG_PDF_DPI : "图像质量 [DPI]",
EXPORTDIALOG_PDF_FIT_TO_PAGE : "页面适配",
EXPORTDIALOG_PDF_FIT_OPTION : "适配页面",
EXPORTDIALOG_PDF_FIT_2_OPTION : "适配至 2 页" ,
EXPORTDIALOG_PDF_FIT_4_OPTION : "适配至 4 页" ,
EXPORTDIALOG_PDF_FIT_6_OPTION : "适配至 6 页" ,
EXPORTDIALOG_PDF_FIT_8_OPTION : "适配至 8 页" ,
EXPORTDIALOG_PDF_FIT_12_OPTION : "适配至 12 页" ,
EXPORTDIALOG_PDF_FIT_16_OPTION : "适配至 16 页" ,
EXPORTDIALOG_PDF_SCALE_OPTION : "使用图像缩放(可能跨多页)",
EXPORTDIALOG_PDF_PAPER_COLOR : "纸张颜色",
EXPORTDIALOG_PDF_PAPER_WHITE : "白色",
EXPORTDIALOG_PDF_PAPER_SCENE : "使用场景颜色",
EXPORTDIALOG_PDF_PAPER_CUSTOM : "自定义颜色",
EXPORTDIALOG_PDF_ALIGNMENT : "页面位置",
EXPORTDIALOG_PDF_ALIGN_CENTER : "居中",
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT : "左上角",
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER : "顶部居中",
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT : "右上角",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT : "左下角",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER : "底部居中",
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT : "右下角",
EXPORTDIALOG_PDF_MARGIN : "边距",
EXPORTDIALOG_PDF_MARGIN_NONE : "无",
EXPORTDIALOG_PDF_MARGIN_TINY : "小",
EXPORTDIALOG_PDF_MARGIN_NORMAL : "正常",
EXPORTDIALOG_SAVE_PDF_SETTINGS : "保存 PDF 设置",
EXPORTDIALOG_SAVE_CONFIRMATION : "PDF 配置已保存为插件默认设置",
// 按钮
EXPORTDIALOG_PNGTOFILE : "导出 PNG 文件",
EXPORTDIALOG_SVGTOFILE : "导出 SVG 文件",
EXPORTDIALOG_PNGTOVAULT : "PNG 保存到 Vault",
EXPORTDIALOG_SVGTOVAULT : "SVG 保存到 Vault",
EXPORTDIALOG_EXCALIDRAW : "Excalidraw",
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG 复制到剪贴板",
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG 复制到剪贴板",
EXPORTDIALOG_PDF : "导出 PDF 文件",
EXPORTDIALOG_PDFTOVAULT : "PDF 保存到 Vault",
EXPORTDIALOG_PDF_PROGRESS_NOTICE : "正在导出页面" ,
EXPORTDIALOG_PDF_PROGRESS_IMAGE : "的图像" ,
EXPORTDIALOG_PDF_PROGRESS_DONE : "导出完成" ,
};

View File

@@ -6,7 +6,7 @@ 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, PDFMargin, PDFPageAlignment, PDFPageMarginString, STANDARD_PAGE_SIZES, exportSVGToClipboard } from "src/utils/exportUtils";
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, exportSVGToClipboard } from "src/utils/exportUtils";
import { t } from "src/lang/helpers";
import { PDFExportSettings, PDFExportSettingsComponent } from "./PDFExportSettingsComponent";
@@ -43,7 +43,6 @@ export class ExportDialog extends Modal {
public customPaperColor: string = "#ffffff";
public alignment: PDFPageAlignment = "center";
public margin: PDFPageMarginString = "normal";
public exportDPI: number = 300;
constructor(
private plugin: ExcalidrawPlugin,
@@ -69,7 +68,6 @@ 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;
}
@@ -279,7 +277,6 @@ export class ExportDialog extends Modal {
customPaperColor: this.customPaperColor,
alignment: this.alignment,
margin: this.margin,
exportDPI: this.exportDPI,
};
new PDFExportSettingsComponent(
@@ -293,7 +290,6 @@ export class ExportDialog extends Modal {
this.customPaperColor = pdfSettings.customPaperColor;
this.alignment = pdfSettings.alignment;
this.margin = pdfSettings.margin;
this.exportDPI = pdfSettings.exportDPI ?? 300;
}
).render();
}
@@ -383,26 +379,11 @@ export class ExportDialog extends Modal {
customPaperColor: this.customPaperColor,
alignment: this.alignment,
margin: this.margin,
exportDPI: this.exportDPI,
};
await this.plugin.saveSettings();
new Notice(t("EXPORTDIALOG_SAVE_CONFIRMATION"));
};
const bPDFVault = this.buttonContainerRow1.createEl("button", {
text: t("EXPORTDIALOG_PDFTOVAULT"),
cls: "excalidraw-export-button"
});
bPDFVault.onclick = () => {
this.view.exportPDF(
true,
this.hasSelectedElements && this.exportSelectedOnly,
this.pageSize,
this.pageOrientation
);
this.close();
};
if (!DEVICE.isDesktop) return;
const bPDFExport = this.buttonContainerRow1.createEl("button", {
text: t("EXPORTDIALOG_PDF"),

View File

@@ -10,7 +10,6 @@ export interface PDFExportSettings {
customPaperColor: string;
alignment: PDFPageAlignment;
margin: PDFPageMarginString;
exportDPI: number;
}
export class PDFExportSettingsComponent {
@@ -55,23 +54,6 @@ export class PDFExportSettingsComponent {
this.update();
})
);
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"))

View File

@@ -226,7 +226,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
},
{
field: "createPDF",
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties}): Promise<ArrayBuffer>",
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" +
"\n" +
@@ -241,11 +241,10 @@ 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 {PDFMargin} margin - The margins of the PDF pages in pixels.\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" +
@@ -256,22 +255,21 @@ 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" +
"});",
},
{
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" +
@@ -558,6 +556,21 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
desc: "If set Excalidraw will call this function onDrop events.\nA return of true will stop the default onDrop processing in Excalidraw.\n\ndraggable is the Obsidian draggable object\nfiles is the array of dropped files\nexcalidrawFile is the file receiving the drop event\nview is the excalidraw view receiving the drop.\npointerPosition is the pointer position on canvas at the time of drop.",
after: "",
},
{
field: "onImageFilePathHook",
code: `onImageFilePathHook: (data: {currentImageName: string; drawingFilePath: string;}): string;`,
desc: "If set, this callback is triggered when an image is being saved in Excalidraw.\n"
+ "You can use this callback to customize the naming and path of pasted images to avoid\n"
+ 'default names like "Pasted image 123147170.png" being saved in the attachments folder,\n'
+ "and instead use more meaningful names based on the Excalidraw file or other criteria,\n"
+ "plus save the image in a different folder.\n\n"
+ "If the function returns null or undefined, the normal Excalidraw operation will continue\n"
+ "with the excalidraw generated name and default path.\n"
+ "If a filepath is returned, that will be used. Include the full Vault filepath and filename\n"
+ "with the file extension.\n"
+ "The currentImageName is the name of the image generated by excalidraw or provided during paste.",
after: "",
},
{
field: "mostRecentMarkdownSVG",
code: "mostRecentMarkdownSVG: SVGSVGElement;",

View File

@@ -932,19 +932,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,11 +964,10 @@ 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,
* }
* });
*/
@@ -976,16 +975,17 @@ export class ExcalidrawAutomate {
SVG,
scale = { fitToPage: 1, zoom: 1 },
pageProps,
filename,
}: {
SVG: SVGSVGElement[];
scale?: PDFExportScale;
pageProps?: PDFPageProperties;
}): Promise<ArrayBuffer> {
filename: string;
}): Promise<void> {
if(!pageProps) {
pageProps = {
alignment: this.plugin.settings.pdfSettings.alignment,
margin: getMarginValue(this.plugin.settings.pdfSettings.margin),
exportDPI: this.plugin.settings.pdfSettings.exportDPI ?? 300,
};
}
@@ -999,7 +999,7 @@ export class ExcalidrawAutomate {
pageProps.backgroundColor = "#ffffff";
}
return await exportToPDF({SVG, scale, pageProps});
await exportToPDF({SVG, scale, pageProps, filename});
}
/**
@@ -2742,6 +2742,40 @@ export class ExcalidrawAutomate {
pointerPosition: { x: number; y: number }; //the pointer position on canvas
}) => boolean = null;
/**
* 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.
*
* @param data - An object containing the following properties:
* @property {string} [currentImageName] - Default name for the image.
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
*
* @returns {string} - The new filepath for the image including full vault path and extension.
*
* Example usage:
* ```
* onImageFilePathHook: (data) => {
* const { currentImageName, drawingFilePath } = data;
* // Generate a new filepath based on the drawing file name and other criteria
* const ext = currentImageName.split('.').pop();
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
* }
* ```
*/
onImageFilePathHook: (data: {
currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
}) => string = null;
/**
* if set, this callback is triggered, when an Excalidraw file is opened
* You can use this callback in case you want to do something additional when the file is opened.

View File

@@ -20,7 +20,7 @@ import {
loadSceneFonts,
} from "../constants/constants";
import ExcalidrawPlugin from "../core/main";
import { TextMode } from "../view/ExcalidrawView";
import ExcalidrawView, { TextMode } from "../view/ExcalidrawView";
import {
addAppendUpdateCustomData,
compress,
@@ -52,10 +52,11 @@ import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "..
import { DEBUGGING, debug } from "../utils/debugHelper";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { updateElementIdsInScene } from "../utils/excalidrawSceneUtils";
import { getNewUniqueFilepath } from "../utils/fileUtils";
import { checkAndCreateFolder, getNewUniqueFilepath, splitFolderAndFilename } from "../utils/fileUtils";
import { t } from "../lang/helpers";
import { displayFontMessage } from "../utils/excalidrawViewUtils";
import { getPDFRect } from "../utils/PDFUtils";
import { create } from "domain";
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
@@ -480,7 +481,7 @@ export class ExcalidrawData {
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
constructor(
private plugin: ExcalidrawPlugin,
private plugin: ExcalidrawPlugin, private view?: ExcalidrawView,
) {
this.app = this.plugin.app;
this.files = new Map<FileId, EmbeddedFile>();
@@ -1546,13 +1547,24 @@ export class ExcalidrawData {
}
}
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
const filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
let hookFilepath:string;
const ea = this.view?.getHookServer();
if(ea?.onImageFilePathHook) {
hookFilepath = ea.onImageFilePathHook({
currentImageName: fname,
drawingFilePath: this.view?.file?.path,
})
}
/*
const filepath = (
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
).filepath;*/
let filepath:string;
if(hookFilepath) {
const {folderpath, filename} = splitFolderAndFilename(hookFilepath);
await checkAndCreateFolder(folderpath);
filepath = getNewUniqueFilepath(this.app.vault,filename,folderpath);
} else {
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
}
const arrayBuffer = await getBinaryFileFromDataURL(dataURL);
if(!arrayBuffer) return null;

View File

@@ -1,10 +1,8 @@
import { PDFDocument, rgb } from '@cantoo/pdf-lib';
import exp from 'constants';
import { Notice } from 'obsidian';
import { getEA } from 'src/core';
import { DEVICE } from 'src/constants/constants';
import { t } from 'src/lang/helpers';
const PDF_DPI = 72;
const DPI = 96;
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
export type PDFPageMarginString = "none" | "tiny" | "normal";
@@ -26,7 +24,6 @@ export interface PDFPageProperties {
backgroundColor?: string;
margin: PDFMargin;
alignment: PDFPageAlignment;
exportDPI: number;
}
export interface PageDimensions {
@@ -36,21 +33,24 @@ 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 };
@@ -67,15 +67,246 @@ export function getPageDimensions(pageSize: PageSize, orientation: PageOrientati
: { width: dimensions.height, height: dimensions.width };
}
interface SVGDimensions {
width: number;
height: number;
x: number;
y: number;
sourceX?: number;
sourceY?: number;
sourceWidth?: number;
sourceHeight?: number;
// Electron IPC interfaces
interface PrintToPDFOptions {
includeName: boolean;
pageSize: string | { width: number; height: number };
landscape: boolean;
margins: { top: number; left: number; right: number; bottom: number };
scaleFactor: number;
scale: number;
open: boolean;
filepath: string;
}
interface SaveDialogOptions {
defaultPath: string;
filters: { name: string; extensions: string[] }[];
properties: string[];
}
interface SaveDialogReturnValue {
canceled: boolean;
filePath?: string;
}
interface ElectronAPI {
ipcRenderer: {
send(channel: string, ...args: any[]): void;
once(channel: string, func: (...args: any[]) => void): void;
};
remote: {
dialog: {
showSaveDialog(options: SaveDialogOptions): Promise<SaveDialogReturnValue>;
};
};
}
declare global {
interface Window {
electron: ElectronAPI;
}
}
function getPageSizePixels(pageSize: PageSize | PageDimensions, landscape = false): PageDimensions {
if (typeof pageSize === "object") return pageSize;
const pageDimensions = STANDARD_PAGE_SIZES[pageSize];
if (!pageDimensions) {
throw new Error(`Unsupported page size: ${pageSize}`);
}
return landscape
? { width: pageDimensions.height, height: pageDimensions.width }
: { width: pageDimensions.width, height: pageDimensions.height };
}
function getPageSize(pageSize: PageSize | PageDimensions): string | { width: number; height: number } {
if (typeof pageSize === "string") return pageSize;
if (!pageSize || typeof pageSize !== "object" ||
typeof pageSize.width !== "number" || typeof pageSize.height !== "number") {
throw new Error("Invalid page dimensions");
}
return {
width: (pageSize.width / DPI),
height: (pageSize.height / DPI)
};
}
async function getSavePath(defaultPath: string): Promise<string | undefined> {
const result = await window.electron.remote.dialog.showSaveDialog({
defaultPath,
filters: [
{ name: "PDF Files", extensions: ["pdf"] },
{ name: "All Files", extensions: ["*"] }
],
properties: ["showOverwriteConfirmation"]
});
return result.filePath;
}
async function printPdf(
elementToPrint: HTMLElement,
pdfPath: string,
bgColor: string,
pageSize: PageSize | PageDimensions,
isLandscape: boolean,
margins: { top: number; left: number; right: number; bottom: number }
): Promise<void> {
const styleTag = document.createElement('style');
styleTag.textContent = `
@media print {
.print {
background-color: ${bgColor} !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
flex-direction: column !important;
page-break-before: always;
margin: 0px !important;
padding: 0px !important;
}
}
`;
document.head.appendChild(styleTag);
const printDiv = document.body.createDiv('print');
printDiv.style.top = "0";
printDiv.style.left = "0";
printDiv.style.display = "flex";
printDiv.appendChild(elementToPrint);
const options: PrintToPDFOptions = {
includeName: false,
pageSize: getPageSize(pageSize),
landscape: isLandscape,
margins,
scaleFactor: 100,
scale: 1,
open: true,
filepath: pdfPath,
};
try {
await new Promise<void>((resolve) => {
window.electron.ipcRenderer.once('print-to-pdf', resolve);
window.electron.ipcRenderer.send('print-to-pdf', options);
});
} finally {
printDiv.remove();
styleTag.remove();
}
}
function calculateDimensions(
svgWidth: number,
svgHeight: number,
pageDim: PageDimensions,
margin: PDFMargin,
scale: PDFExportScale,
alignment: PDFPageAlignment
): {
tiles: {
viewBox: string,
width: number,
height: number,
x: number,
y: number
}[],
pages: number
} {
const availableWidth = pageDim.width - margin.left - margin.right;
const availableHeight = pageDim.height - margin.top - margin.bottom;
// If fitToPage is specified, find optimal zoom using binary search
if (scale.fitToPage > 0) {
let low = 0;
let high = 100;
let bestZoom = 1;
const tolerance = 0.000001;
while (high - low > tolerance) {
const mid = (low + high) / 2;
const scaledWidth = svgWidth * mid;
const scaledHeight = svgHeight * mid;
const pages = Math.ceil(scaledWidth / availableWidth) *
Math.ceil(scaledHeight / availableHeight);
if (pages > scale.fitToPage) {
high = mid;
} else {
bestZoom = mid;
low = mid;
}
}
scale.zoom = Math.round(bestZoom * 0.99999 * 1000000) / 1000000;
}
const finalWidth = svgWidth * (scale.zoom || 1);
const finalHeight = svgHeight * (scale.zoom || 1);
if (finalWidth <= availableWidth && finalHeight <= availableHeight) {
// Content fits on one page
const position = calculatePosition(
finalWidth,
finalHeight,
pageDim.width,
pageDim.height,
margin,
alignment
);
return {
tiles: [{
viewBox: `0 0 ${svgWidth} ${svgHeight}`,
width: finalWidth,
height: finalHeight,
x: position.x,
y: position.y
}],
pages: 1
};
}
// Content needs to be tiled
const tiles = [];
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 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 position for each tile
const position = calculatePosition(
scaledTileWidth,
scaledTileHeight,
pageDim.width,
pageDim.height,
margin,
alignment
);
tiles.push({
viewBox: `${tileX} ${tileY} ${tileWidth} ${tileHeight}`,
width: scaledTileWidth,
height: scaledTileHeight,
x: position.x,
y: position.y
});
}
}
return { tiles, pages: tiles.length };
}
function calculatePosition(
@@ -90,7 +321,7 @@ function calculatePosition(
const availableHeight = pageHeight - margin.top - margin.bottom;
let x = margin.left;
let y = margin.bottom;
let y = margin.top;
// Handle horizontal alignment
if (alignment.includes('center')) {
@@ -101,304 +332,92 @@ function calculatePosition(
// Handle vertical alignment
if (alignment.startsWith('center')) {
y = margin.bottom + (availableHeight - svgHeight) / 2;
} else if (alignment.startsWith('top')) {
y = margin.bottom;
y = margin.top + (availableHeight - svgHeight) / 2;
} else if (alignment.startsWith('bottom')) {
y = pageHeight - margin.top - svgHeight;
y = margin.top + availableHeight - svgHeight;
}
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,
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;
// 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;
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,
pageDim.width,
pageDim.height,
margin,
alignment,
);
return [{
width: finalWidth,
height: finalHeight,
x: position.x,
y: position.y
}];
} else {
// 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);
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 * 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;
}
}
async function addSVGToPage(
pdfDoc: PDFDocument,
svg: SVGSVGElement,
dimensions: SVGDimensions,
pageDim: PageDimensions,
pageProps: PDFPageProperties
) {
const page = pdfDoc.addPage([pageDim.width, pageDim.height]);
if (pageProps.backgroundColor && pageProps.backgroundColor !== '#ffffff') {
const { r, g, b } = hexToRGB(pageProps.backgroundColor);
page.drawRectangle({
x: 0,
y: 0,
width: pageDim.width,
height: pageDim.height,
color: rgb(r/255, g/255, b/255),
});
}
// 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);
// Adjust y-coordinate to account for PDF coordinate system
const adjustedY = pageDim.height - dimensions.y - dimensions.height;
// Draw the image
page.drawImage(image, {
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: 1, zoom: 1 },
pageProps,
filename
}: {
SVG: SVGSVGElement[];
scale: PDFExportScale;
pageProps: PDFPageProperties;
}): Promise<ArrayBuffer> {
const pdfDoc = await PDFDocument.create();
filename: string;
}): Promise<void> {
if (!DEVICE.isDesktop) {
new Notice(t("PDF_EXPORT_DESKTOP_ONLY"));
return;
}
const msg = t('EXPORTDIALOG_PDF_PROGRESS_NOTICE');
const imgmsg = t('EXPORTDIALOG_PDF_PROGRESS_IMAGE');
const savePath = await getSavePath(filename);
if (!savePath) return;
let notice = new Notice(msg, 0);
const {width, height} = getPageSizePixels(pageProps.dimensions, false);
const allPagesDiv = createDiv();
allPagesDiv.style.width = "100%";
allPagesDiv.style.height = "fit-content";
let j=1;
let j = 1;
for (const svg of SVG) {
const svgWidth = parseFloat(svg.getAttribute('width') || '0');
const svgHeight = parseFloat(svg.getAttribute('height') || '0');
const dimensions = calculateDimensions(
svgWidth,
svgHeight,
pageProps.dimensions,
pageProps.margin,
const {tiles} = calculateDimensions(
svgWidth,
svgHeight,
pageProps.dimensions,
pageProps.margin,
scale,
pageProps.alignment,
pageProps.exportDPI
pageProps.alignment
);
let i=1;
for (const dim of dimensions) {
//@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);
let i = 1;
for (const tile of tiles) {
const pageDiv = createDiv();
pageDiv.style.width = `${width}px`;
pageDiv.style.height = `${height}px`;
pageDiv.style.display = "flex";
pageDiv.style.justifyContent = "start";
pageDiv.style.alignItems = "left";
pageDiv.style.padding = `${pageProps.margin.top}px ${pageProps.margin.right}px ${pageProps.margin.bottom}px ${pageProps.margin.left}px`;
const clonedSVG = svg.cloneNode(true) as SVGSVGElement;
clonedSVG.setAttribute('viewBox', tile.viewBox);
clonedSVG.style.width = `${tile.width}px`;
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`;
pageDiv.appendChild(clonedSVG);
allPagesDiv.appendChild(pageDiv);
i++;
}
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'));
new Notice(t("EXPORTDIALOG_PDF_PROGRESS_NOTICE"));
try {
await printPdf(
allPagesDiv,
savePath,
pageProps.backgroundColor || "#ffffff",
pageProps.dimensions,
false,
{ top: 0, right: 0, bottom: 0, left: 0 }
);
} catch (error) {
console.error("Failed to export to PDF: ", error);
new Notice(t("EXPORTDIALOG_PDF_PROGRESS_ERROR"));
}
return pdfDoc.save();
}
function hexToRGB(hex: string): { r: number; g: number; b: number } {
const ea = getEA();
const color = ea.getCM(hex);
if (color) {
return { r: color.red, g: color.green, b: color.blue };
}
return {r: 255, g: 255, b: 255};
}
// Helper function to split SVG into pages if needed
function splitSVGIntoPages(
svg: SVGSVGElement,
maxWidth: number,
maxHeight: number
): SVGSVGElement[] {
const width = parseFloat(svg.getAttribute('width') || '0');
const height = parseFloat(svg.getAttribute('height') || '0');
if (width <= maxWidth && height <= maxHeight) {
return [svg];
}
const pages: SVGSVGElement[] = [];
const cols = Math.ceil(width / maxWidth);
const rows = Math.ceil(height / maxHeight);
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const viewBox = `${col * maxWidth} ${row * maxHeight} ${maxWidth} ${maxHeight}`;
const clonedSvg = svg.cloneNode(true) as SVGSVGElement;
clonedSvg.setAttribute('viewBox', viewBox);
clonedSvg.setAttribute('width', String(maxWidth));
clonedSvg.setAttribute('height', String(maxHeight));
pages.push(clonedSvg);
}
}
return pages;
new Notice(t("EXPORTDIALOG_PDF_PROGRESS_DONE"));
}
export async function exportSVGToClipboard(svg: SVGSVGElement) {

View File

@@ -372,7 +372,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
super(leaf);
this._plugin = plugin;
this.excalidrawData = new ExcalidrawData(plugin);
this.excalidrawData = new ExcalidrawData(plugin, this);
this.canvasNodeFactory = new CanvasNodeFactory(this);
this.setHookServer();
this.dropManager = new DropManager(this);
@@ -587,7 +587,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
return;
}
const pdfArrayBuffer = await exportToPDF({
exportToPDF({
SVG: [svg],
scale: {
zoom: this.exportDialog.scale,
@@ -598,36 +598,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
backgroundColor: this.exportDialog.getPaperColor(),
margin: getMarginValue(this.exportDialog.margin),
alignment: this.exportDialog.alignment,
exportDPI: this.exportDialog.exportDPI,
}
},
filename: this.file.basename,
});
if (!pdfArrayBuffer) {
return;
}
if(toVault) {
const filepath = getIMGFilename(this.file.path, "pdf");
const file = await createOrOverwriteFile(this.app, filepath, pdfArrayBuffer);
let leaf: WorkspaceLeaf;
this.app.workspace.getLeavesOfType("pdf").forEach((l) => {
//@ts-ignore
if(l.view?.file === file) {
leaf = l;
}
});
if(leaf) {
this.app.workspace.revealLeaf(leaf);
} else {
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> {
@@ -1578,6 +1551,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
this.packages = this.plugin.getPackage(this.ownerWindow);
if(DEVICE.isDesktop && !apiMissing) {
if(this.ownerWindow !== window) {
this.plugin.initializeFonts();
}
this.destroyers.push(
//this.containerEl.onWindowMigrated(this.leaf.rebuildView.bind(this))
this.containerEl.onWindowMigrated(async() => {

View File

@@ -116,6 +116,7 @@ li[data-testid] {
border: 0 !important;
box-shadow: 0 !important;
background-color: transparent !important;
overflow-y: auto !important;
}
.excalidraw .popover {
@@ -336,6 +337,16 @@ label.color-input-container > input {
display: none !important;
}
.excalidraw .App-toolbar-content .dropdown-menu {
max-height: 70vh;
overflow-y: auto;
}
.excalidraw .panelColumn {
max-height: 70vh;
overflow-y: auto;
}
.excalidraw .panelColumn .buttonList {
max-width: 13rem;
}