🎨 Desktop export PDF supports no pagination https://ld246.com/article/1764933918963 (#16537)

This commit is contained in:
Jeffrey Chen 2025-12-26 10:54:17 +08:00 committed by GitHub
parent cfd25e85b6
commit cef8ca8cf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 117 additions and 35 deletions

View file

@ -221,6 +221,9 @@ export const initWindow = async (app: App) => {
});
ipcRenderer.on(Constants.SIYUAN_EXPORT_PDF, async (e, ipcData) => {
const msgId = showMessage(window.siyuan.languages.exporting, -1);
const isPaged = ipcData.paged !== undefined ? ipcData.paged : true;
// 分页模式下 pageSize 是字符串,不分页模式下是对象,统一使用 pageSizeValue 获取原始页面大小
const pageSizeValue = ipcData.pageSizeValue !== undefined ? ipcData.pageSizeValue : (isPaged ? ipcData.pdfOptions.pageSize : "A4");
window.siyuan.storage[Constants.LOCAL_EXPORTPDF] = {
removeAssets: ipcData.removeAssets,
keepFold: ipcData.keepFold,
@ -228,16 +231,17 @@ export const initWindow = async (app: App) => {
watermark: ipcData.watermark,
landscape: ipcData.pdfOptions.landscape,
marginType: ipcData.pdfOptions.marginType,
pageSize: ipcData.pdfOptions.pageSize,
pageSize: pageSizeValue,
scale: ipcData.pdfOptions.scale,
marginTop: ipcData.pdfOptions.margins.top,
marginRight: ipcData.pdfOptions.margins.right,
marginBottom: ipcData.pdfOptions.margins.bottom,
marginLeft: ipcData.pdfOptions.margins.left,
paged: isPaged,
};
setStorageVal(Constants.LOCAL_EXPORTPDF, window.siyuan.storage[Constants.LOCAL_EXPORTPDF]);
try {
if (window.siyuan.config.export.pdfFooter.trim()) {
if (window.siyuan.config.export.pdfFooter.trim() && isPaged) {
const response = await fetchSyncPost("/api/template/renderSprig", {template: window.siyuan.config.export.pdfFooter});
ipcData.pdfOptions.displayHeaderFooter = true;
ipcData.pdfOptions.headerTemplate = "<span></span>";

View file

@ -304,7 +304,7 @@ const renderPDF = async (id: string) => {
${window.siyuan.languages.exportPDF1}
</div>
<span class="fn__hr"></span>
<input id="landscape" class="b3-switch" type="checkbox" ${localData.landscape ? "checked" : ""}>
<input id="landscape" class="b3-switch" type="checkbox" ${localData.landscape && localData.paged ? "checked" : ""}>
</label>
<label class="b3-label">
<div>
@ -334,6 +334,13 @@ const renderPDF = async (id: string) => {
<span class="fn__hr"></span>
<input id="watermark" class="b3-switch" type="checkbox" ${localData.watermark ? "checked" : ""}>
</label>
<label class="b3-label">
<div>
${window.siyuan.languages.paged}
</div>
<span class="fn__hr"></span>
<input id="paged" class="b3-switch" type="checkbox" ${(localData.paged === undefined ? true : localData.paged) ? "checked" : ""}>
</label>
</div>
<div class="fn__flex" style="padding: 0 16px">
<div class="fn__flex-1"></div>
@ -555,7 +562,7 @@ ${getIconScript(servePath)}
renderPreview(response2.data);
})
};
actionElement.querySelector("#scale").addEventListener("input", () => {
const scale = actionElement.querySelector("#scale").value;
actionElement.querySelector("#scaleTip").innerText = scale;
@ -585,38 +592,93 @@ ${getIconScript(servePath)}
actionElement.querySelector("#marginsLeft").addEventListener('change', () => {
setPadding();
});
actionElement.querySelector("#landscape").addEventListener('change', () => {
actionElement.querySelector("#landscape").addEventListener('change', (e) => {
if (e.target.checked) {
actionElement.querySelector("#paged").checked = true;
}
setPadding();
});
actionElement.querySelector("#paged").addEventListener('change', (e) => {
// 不分页模式没有适配横向页面,强制纵向
if (!e.target.checked) {
const landscapeElement = actionElement.querySelector("#landscape");
if (landscapeElement.checked) {
landscapeElement.checked = false;
setPadding();
}
}
});
actionElement.querySelector('.b3-button--cancel').addEventListener('click', () => {
const {ipcRenderer} = require("electron");
ipcRenderer.send("${Constants.SIYUAN_CMD}", "destroy")
});
actionElement.querySelector('.b3-button--text').addEventListener('click', () => {
const {ipcRenderer} = require("electron");
ipcRenderer.send("${Constants.SIYUAN_EXPORT_PDF}", {
title: "${window.siyuan.languages.export} PDF",
pdfOptions:{
printBackground: true,
landscape: actionElement.querySelector("#landscape").checked,
marginType: actionElement.querySelector("#marginsType").value,
margins: {
top: parseFloat(document.querySelector("#marginsTop").value),
bottom: parseFloat(document.querySelector("#marginsBottom").value),
left: parseFloat(document.querySelector("#marginsLeft").value),
right: parseFloat(document.querySelector("#marginsRight").value),
},
scale: parseFloat(actionElement.querySelector("#scale").value),
pageSize: actionElement.querySelector("#pageSize").value,
},
keepFold: keepFoldElement.checked,
mergeSubdocs: mergeSubdocsElement.checked,
watermark: watermarkElement.checked,
removeAssets: actionElement.querySelector("#removeAssets").checked,
rootId: "${id}",
rootTitle: response.data.name,
parentWindowId: ${currentWindowId},
})
const pageSizeValue = actionElement.querySelector("#pageSize").value;
const isPaged = actionElement.querySelector("#paged").checked;
const buildExportConfig = (pageSize) => {
// https://www.electronjs.org/docs/latest/api/web-contents#contentsprinttopdfoptions
// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
return {
title: "${window.siyuan.languages.export} PDF",
pdfOptions: {
printBackground: true,
landscape: actionElement.querySelector("#landscape").checked,
marginType: actionElement.querySelector("#marginsType").value,
margins: {
top: parseFloat(document.querySelector("#marginsTop").value) || 0,
bottom: parseFloat(document.querySelector("#marginsBottom").value) || 0,
left: parseFloat(document.querySelector("#marginsLeft").value) || 0,
right: parseFloat(document.querySelector("#marginsRight").value) || 0,
},
scale: parseFloat(actionElement.querySelector("#scale").value),
pageSize: pageSize,
},
keepFold: keepFoldElement.checked,
mergeSubdocs: mergeSubdocsElement.checked,
watermark: watermarkElement.checked,
removeAssets: actionElement.querySelector("#removeAssets").checked,
paged: isPaged,
pageSizeValue: pageSizeValue,
rootId: "${id}",
rootTitle: response.data.name,
parentWindowId: ${currentWindowId},
};
};
if (!isPaged) {
const getPageSizeDimensions = (pageSizeName) => {
// https://github.com/electron/electron/blob/3df3a6a736b93e0d69fa3b0c403b33f201287780/lib/browser/api/web-contents.ts#L89-L101
const pageSizes = {
"A3": { width: 11.7, height: 16.54 },
"A4": { width: 8.27, height: 11.7 },
"A5": { width: 5.83, height: 8.27 },
"Legal": { width: 8.5, height: 14 },
"Letter": { width: 8.5, height: 11 },
"Tabloid": { width: 11, height: 17 },
};
return pageSizes[pageSizeName] || pageSizes["A4"];
};
const previewContent = previewElement.querySelector(".protyle-wysiwyg") || previewElement;
const contentHeight = previewContent.scrollHeight || previewContent.offsetHeight;
// 英寸 = 像素 / PPI但不知道 PPI 怎么得到,直接使用经典 96 DPI实际导出之后页面末尾会有空白
const heightInches = contentHeight / 96;
// 获取页面大小对应的宽度
const selectedPageSize = getPageSizeDimensions(pageSizeValue);
// 高度根据内容动态计算
const marginTop = parseFloat(document.querySelector("#marginsTop").value) || 0;
const marginBottom = parseFloat(document.querySelector("#marginsBottom").value) || 0;
const totalHeightInches = heightInches + marginTop + marginBottom;
const dynamicPageSize = {
width: selectedPageSize.width,
height: totalHeightInches
};
ipcRenderer.send("${Constants.SIYUAN_EXPORT_PDF}", buildExportConfig(dynamicPageSize));
} else {
ipcRenderer.send("${Constants.SIYUAN_EXPORT_PDF}", buildExportConfig(pageSizeValue));
}
previewElement.classList.add("exporting");
previewElement.style.zoom = "";
previewElement.style.paddingTop = "6px";

View file

@ -440,7 +440,8 @@ export const getLocalStorage = (cb: () => void) => {
removeAssets: true,
keepFold: false,
mergeSubdocs: false,
watermark: false
watermark: false,
paged: true
};
defaultStorage[Constants.LOCAL_EXPORTIMG] = {
keepFold: false,