Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 124 additions & 47 deletions desktop-app/resources/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -7964,18 +7964,51 @@ document.addEventListener("DOMContentLoaded", function () {
return Promise.all(promises);
}

// ============================================
// End Oversized Graphics Scaling Functions
// ============================================
function showLargePdfExportDialog(onChoosePrint, onChooseCanvas) {
const dialogOverlay = document.createElement("div");
dialogOverlay.className = "reset-modal-overlay";
dialogOverlay.style.display = "flex";
dialogOverlay.setAttribute("role", "dialog");
dialogOverlay.setAttribute("aria-modal", "true");

dialogOverlay.innerHTML = `
<div class="reset-modal-box reset-modal-box--wide">
<p class="reset-modal-message" style="font-weight: 600; margin-bottom: 0.5rem; color: var(--text-color);">Large Document Detected</p>
<p style="font-size: 0.9rem; margin-bottom: 1.5rem; opacity: 0.85; line-height: 1.4; color: var(--text-color);">
This document is very large. Generating a canvas-based PDF can take a long time and might cause browser lagging.
<br><br>
We recommend using the <strong>Browser Print Dialog</strong> (select 'Save as PDF' as the destination). It is instant, uses vector graphics for perfectly sharp text, and has zero document size limits.
</p>
<div class="reset-modal-actions" style="display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;">
<button class="reset-modal-btn reset-modal-cancel" id="large-pdf-cancel" style="border: 1px solid var(--border-color); background: none; color: var(--text-color);">Cancel</button>
<button class="reset-modal-btn" id="large-pdf-canvas" style="background-color: var(--button-bg); border: 1px solid var(--border-color); color: var(--text-color);">Use Canvas (Slow)</button>
<button class="reset-modal-btn" id="large-pdf-print" style="background-color: var(--accent-color); border: none; color: #ffffff; font-weight: 600;">Browser Print (Recommended)</button>
</div>
</div>
`;

exportPdf.addEventListener("click", async function (event) {
event.preventDefault();
logPdfExportDebug("PDF export button clicked!");
if (activePdfExport) {
logPdfExportDebug("PDF export already active, ignoring click");
return;
}
document.body.appendChild(dialogOverlay);

const cleanup = () => {
dialogOverlay.remove();
};

dialogOverlay.querySelector("#large-pdf-cancel").addEventListener("click", () => {
cleanup();
});

dialogOverlay.querySelector("#large-pdf-canvas").addEventListener("click", () => {
cleanup();
onChooseCanvas();
});

dialogOverlay.querySelector("#large-pdf-print").addEventListener("click", () => {
cleanup();
onChoosePrint();
});
}

async function startCanvasPdfExport() {
const progressState = createPdfProgressState();
activePdfExport = progressState;
setPdfExportTriggersBusy(progressState, true);
Expand Down Expand Up @@ -8158,50 +8191,68 @@ document.addEventListener("DOMContentLoaded", function () {
const contentWidth = pageWidth - (margin * 2);
const captureScale = choosePdfCanvasScale(tempElement);

updatePdfProgress(progressState, 65, "Capturing document");
const canvas = await runPdfAbortable(progressState, html2canvas(tempElement, {
scale: captureScale,
useCORS: true,
allowTaint: false,
logging: false,
windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
windowHeight: Math.ceil(tempElement.getBoundingClientRect().height)
}));
await waitForPdfFrame(progressState);
throwIfPdfExportAborted(progressState.signal);
const pageCount = pageBreakAnalysis.pageCount;
const pageHeightPx = pageBreakAnalysis.pageHeightPx;
const totalHeight = Math.ceil(tempElement.getBoundingClientRect().height);

const PAGES_PER_CHUNK = 8;
const chunkCount = Math.ceil(pageCount / PAGES_PER_CHUNK);

logPdfExportDebug("Starting hybrid chunked capture loop. Total pages:", pageCount, "Chunks:", chunkCount);
updatePdfProgress(progressState, 65, "Rendering pages");

console.log(`[PDF DEBUG] canvas.width = ${canvas.width}, canvas.height = ${canvas.height}`);
console.log(`[PDF DEBUG] tempElement.offsetWidth = ${tempElement.offsetWidth}, rect.width = ${tempElement.getBoundingClientRect().width}`);
const scaleFactor = canvas.width / contentWidth;
console.log(`[PDF DEBUG] scaleFactor = ${scaleFactor}, PAGE_CONFIG.scale = ${PAGE_CONFIG.scale}, captureScale = ${captureScale}`);
const imgHeight = canvas.height / scaleFactor;
console.log(`[PDF DEBUG] imgHeight = ${imgHeight}, contentHeight = ${pageHeight - margin * 2}`);
// Introduce a 0.5mm tolerance to prevent rounding errors from creating a trailing blank page
const pagesCount = Math.ceil((imgHeight - 0.5) / (pageHeight - margin * 2));
console.log(`[PDF DEBUG] pagesCount = ${pagesCount}`);

updatePdfProgress(progressState, 76, "Rendering pages");
for (let page = 0; page < pagesCount; page++) {
for (let c = 0; c < chunkCount; c++) {
throwIfPdfExportAborted(progressState.signal);
const pageProgress = 76 + ((page + 1) / pagesCount) * 18;
updatePdfProgress(progressState, pageProgress, `Rendering page ${page + 1} of ${pagesCount}`);

const startPage = c * PAGES_PER_CHUNK;
const endPage = Math.min((c + 1) * PAGES_PER_CHUNK, pageCount);

const yStart = startPage * pageHeightPx;
const chunkHeight = Math.min(totalHeight - yStart, (endPage - startPage) * pageHeightPx);

if (page > 0) pdf.addPage();
const progressPercent = 65 + ((c + 1) / chunkCount) * 33;
updatePdfProgress(progressState, progressPercent, `Rendering page ${startPage + 1} to ${endPage} of ${pageCount}`);

const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
const destHeight = sourceHeight / scaleFactor;
console.log(`[PDF DEBUG] Rendering chunk ${c + 1}/${chunkCount}: yStart=${yStart}, chunkHeight=${chunkHeight}`);

const chunkCanvas = await runPdfAbortable(progressState, html2canvas(tempElement, {
scale: captureScale,
useCORS: true,
allowTaint: false,
logging: false,
x: 0,
y: yStart,
width: tempElement.offsetWidth,
height: chunkHeight,
windowWidth: Math.max(PAGE_CONFIG.windowWidth, Math.ceil(tempElement.getBoundingClientRect().width)),
windowHeight: totalHeight,
scrollX: 0,
scrollY: 0
}));

const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = sourceHeight;
const chunkPagesCount = endPage - startPage;
for (let p = 0; p < chunkPagesCount; p++) {
throwIfPdfExportAborted(progressState.signal);
const pageIndex = startPage + p;

if (pageIndex > 0) pdf.addPage();

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
const pageYInChunkCanvas = p * pageHeightPx * captureScale;
const pageHeightInChunkCanvas = Math.min(chunkCanvas.height - pageYInChunkCanvas, pageHeightPx * captureScale);

const imgData = pageCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
await waitForPdfFrame(progressState);
const pageCanvas = document.createElement('canvas');
pageCanvas.width = chunkCanvas.width;
pageCanvas.height = pageHeightInChunkCanvas;

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(chunkCanvas, 0, pageYInChunkCanvas, chunkCanvas.width, pageHeightInChunkCanvas, 0, 0, chunkCanvas.width, pageHeightInChunkCanvas);

const imgData = pageCanvas.toDataURL('image/jpeg', 0.95);
const destHeight = (pageHeightInChunkCanvas / captureScale) / (pageCanvas.width / contentWidth);
pdf.addImage(imgData, 'JPEG', margin, margin, contentWidth, destHeight);

await waitForPdfFrame(progressState);
}
}

throwIfPdfExportAborted(progressState.signal);
Expand All @@ -8219,6 +8270,32 @@ document.addEventListener("DOMContentLoaded", function () {
} finally {
cleanupPdfExport(progressState);
}
}

exportPdf.addEventListener("click", async function (event) {
event.preventDefault();
logPdfExportDebug("PDF export button clicked!");
if (activePdfExport) {
logPdfExportDebug("PDF export already active, ignoring click");
return;
}

const markdown = markdownEditor.value;
// Set a threshold of 30,000 characters for large documents
if (markdown.length > 30000) {
showLargePdfExportDialog(
() => {
// Choice: Browser Print
window.print();
},
() => {
// Choice: Canvas rendering
startCanvasPdfExport();
}
);
} else {
startCanvasPdfExport();
}
});

copyMarkdownButton.addEventListener("click", function () {
Expand Down
Loading
Loading