Skip to content
Closed
145 changes: 123 additions & 22 deletions desktop-app/resources/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,14 @@ This is a fully client-side application. Your content never leaves your browser
windowWidth: 1000, // html2canvas config
scale: 2 // html2canvas scale factor
};
// Browser canvas implementations commonly fail/blank around 32,767 px in either dimension.
// Keep a margin under that practical limit for cross-browser stability.
const MAX_PDF_CANVAS_DIMENSION = 32000;
// Very large total pixel areas can also fail on some engines even below dimension limits.
// 250M px is a conservative cap to avoid blank exports on large markdown documents.
const MAX_PDF_CANVAS_AREA = 250000000;
// Keep readability by not scaling below 50% for PDF capture.
const MIN_READABLE_PDF_SCALE = 0.5;

/**
* Task 1: Identifies all graphic elements that may need page-break handling
Expand Down Expand Up @@ -2586,35 +2594,128 @@ This is a fully client-side application. Your content never leaves your browser
const margin = 15;
const contentWidth = pageWidth - (margin * 2);

const canvas = await html2canvas(tempElement, {
scale: 2,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: 1000,
windowHeight: tempElement.scrollHeight
// Prevent oversized canvases for very large documents (can produce empty PDF output)
const actualElementWidth = Math.max(tempElement.offsetWidth || 0, 1);
const actualElementHeight = Math.max(tempElement.scrollHeight || 0, 1);
const pageContentHeightPx = actualElementWidth * (PAGE_CONFIG.contentHeight / PAGE_CONFIG.contentWidth);
const desiredScale = PAGE_CONFIG.scale;
const dimensionLimitedScale = Math.min(
MAX_PDF_CANVAS_DIMENSION / actualElementWidth,
MAX_PDF_CANVAS_DIMENSION / actualElementHeight
);
const elementArea = actualElementWidth * actualElementHeight;
const areaLimitedScale = Number.isFinite(elementArea) && elementArea > 0
? Math.sqrt(MAX_PDF_CANVAS_AREA / elementArea)
// Fallback to 1 (no extra area-based reduction) when area is invalid.
: 1;
const safeScale = Math.max(
MIN_READABLE_PDF_SCALE,
Math.min(desiredScale, dimensionLimitedScale, areaLimitedScale)
);

if (safeScale < desiredScale) {
console.warn(
`Reducing PDF render scale from ${desiredScale} to ${safeScale.toFixed(2)} ` +
`to avoid browser canvas limits for large content.`
);
}

const totalPages = Math.max(1, Math.ceil(actualElementHeight / pageContentHeightPx));
const shouldRenderInSlices = totalPages > 1;
const yieldToBrowser = () => new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve, 0);
});
});

const scaleFactor = canvas.width / contentWidth;
const imgHeight = canvas.height / scaleFactor;
const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
if (shouldRenderInSlices) {
statusText.textContent = `Rendering ${totalPages} pages...`;
await yieldToBrowser();

const sliceHeight = Math.max(1, Math.min(pageContentHeightPx, actualElementHeight));
const sliceDimensionLimitedScale = Math.min(
MAX_PDF_CANVAS_DIMENSION / actualElementWidth,
MAX_PDF_CANVAS_DIMENSION / sliceHeight
);
const sliceArea = actualElementWidth * sliceHeight;
const sliceAreaLimitedScale = Number.isFinite(sliceArea) && sliceArea > 0
? Math.sqrt(MAX_PDF_CANVAS_AREA / sliceArea)
// Fallback to 1 (no extra area-based reduction) when area is invalid.
: 1;
const sliceScale = Math.max(
MIN_READABLE_PDF_SCALE,
Math.min(desiredScale, sliceDimensionLimitedScale, sliceAreaLimitedScale)
);

if (sliceScale < desiredScale) {
console.warn(
`Reducing PDF slice scale from ${desiredScale} to ${sliceScale.toFixed(2)} ` +
`to stay within browser canvas limits.`
);
}

for (let page = 0; page < pagesCount; page++) {
if (page > 0) pdf.addPage();
for (let page = 0; page < totalPages; page++) {
statusText.textContent = `Rendering page ${page + 1} of ${totalPages}...`;
await yieldToBrowser();

const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
const destHeight = sourceHeight / scaleFactor;
if (page > 0) pdf.addPage();

const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = sourceHeight;
const sliceY = page * pageContentHeightPx;
const sliceHeightForPage = Math.min(actualElementHeight - sliceY, pageContentHeightPx);

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
const sliceCanvas = await html2canvas(tempElement, {
scale: sliceScale,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: Math.ceil(actualElementWidth),
windowHeight: Math.ceil(sliceHeightForPage),
y: Math.floor(sliceY),
height: Math.ceil(sliceHeightForPage)
});

const imgData = pageCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
const sliceScaleFactor = sliceCanvas.width / contentWidth;
const destHeight = sliceCanvas.height / sliceScaleFactor;
const imgData = sliceCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
}
} else {
statusText.textContent = 'Rendering PDF...';
await yieldToBrowser();

const canvas = await html2canvas(tempElement, {
scale: safeScale,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: Math.ceil(actualElementWidth),
windowHeight: Math.ceil(actualElementHeight)
});

const scaleFactor = canvas.width / contentWidth;
const imgHeight = canvas.height / scaleFactor;
const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));

for (let page = 0; page < pagesCount; page++) {
statusText.textContent = `Composing page ${page + 1} of ${pagesCount}...`;
await yieldToBrowser();

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

const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
const destHeight = sourceHeight / scaleFactor;

const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = sourceHeight;

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);

const imgData = pageCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
}
}

pdf.save("document.pdf");
Expand Down
145 changes: 123 additions & 22 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,14 @@ This is a fully client-side application. Your content never leaves your browser
windowWidth: 1000, // html2canvas config
scale: 2 // html2canvas scale factor
};
// Browser canvas implementations commonly fail/blank around 32,767 px in either dimension.
// Keep a margin under that practical limit for cross-browser stability.
const MAX_PDF_CANVAS_DIMENSION = 32000;
// Very large total pixel areas can also fail on some engines even below dimension limits.
// 250M px is a conservative cap to avoid blank exports on large markdown documents.
const MAX_PDF_CANVAS_AREA = 250000000;
// Keep readability by not scaling below 50% for PDF capture.
const MIN_READABLE_PDF_SCALE = 0.5;

/**
* Task 1: Identifies all graphic elements that may need page-break handling
Expand Down Expand Up @@ -2586,35 +2594,128 @@ This is a fully client-side application. Your content never leaves your browser
const margin = 15;
const contentWidth = pageWidth - (margin * 2);

const canvas = await html2canvas(tempElement, {
scale: 2,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: 1000,
windowHeight: tempElement.scrollHeight
// Prevent oversized canvases for very large documents (can produce empty PDF output)
const actualElementWidth = Math.max(tempElement.offsetWidth || 0, 1);
const actualElementHeight = Math.max(tempElement.scrollHeight || 0, 1);
const pageContentHeightPx = actualElementWidth * (PAGE_CONFIG.contentHeight / PAGE_CONFIG.contentWidth);
const desiredScale = PAGE_CONFIG.scale;
const dimensionLimitedScale = Math.min(
MAX_PDF_CANVAS_DIMENSION / actualElementWidth,
MAX_PDF_CANVAS_DIMENSION / actualElementHeight
);
const elementArea = actualElementWidth * actualElementHeight;
const areaLimitedScale = Number.isFinite(elementArea) && elementArea > 0
? Math.sqrt(MAX_PDF_CANVAS_AREA / elementArea)
// Fallback to 1 (no extra area-based reduction) when area is invalid.
: 1;
const safeScale = Math.max(
MIN_READABLE_PDF_SCALE,
Math.min(desiredScale, dimensionLimitedScale, areaLimitedScale)
);

if (safeScale < desiredScale) {
console.warn(
`Reducing PDF render scale from ${desiredScale} to ${safeScale.toFixed(2)} ` +
`to avoid browser canvas limits for large content.`
);
}

const totalPages = Math.max(1, Math.ceil(actualElementHeight / pageContentHeightPx));
const shouldRenderInSlices = totalPages > 1;
const yieldToBrowser = () => new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve, 0);
});
});

const scaleFactor = canvas.width / contentWidth;
const imgHeight = canvas.height / scaleFactor;
const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));
if (shouldRenderInSlices) {
statusText.textContent = `Rendering ${totalPages} pages...`;
await yieldToBrowser();

const sliceHeight = Math.max(1, Math.min(pageContentHeightPx, actualElementHeight));
const sliceDimensionLimitedScale = Math.min(
MAX_PDF_CANVAS_DIMENSION / actualElementWidth,
MAX_PDF_CANVAS_DIMENSION / sliceHeight
);
const sliceArea = actualElementWidth * sliceHeight;
const sliceAreaLimitedScale = Number.isFinite(sliceArea) && sliceArea > 0
? Math.sqrt(MAX_PDF_CANVAS_AREA / sliceArea)
// Fallback to 1 (no extra area-based reduction) when area is invalid.
: 1;
const sliceScale = Math.max(
MIN_READABLE_PDF_SCALE,
Math.min(desiredScale, sliceDimensionLimitedScale, sliceAreaLimitedScale)
);

if (sliceScale < desiredScale) {
console.warn(
`Reducing PDF slice scale from ${desiredScale} to ${sliceScale.toFixed(2)} ` +
`to stay within browser canvas limits.`
);
}

for (let page = 0; page < pagesCount; page++) {
if (page > 0) pdf.addPage();
for (let page = 0; page < totalPages; page++) {
statusText.textContent = `Rendering page ${page + 1} of ${totalPages}...`;
await yieldToBrowser();

const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
const destHeight = sourceHeight / scaleFactor;
if (page > 0) pdf.addPage();

const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = sourceHeight;
const sliceY = page * pageContentHeightPx;
const sliceHeightForPage = Math.min(actualElementHeight - sliceY, pageContentHeightPx);

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);
const sliceCanvas = await html2canvas(tempElement, {
scale: sliceScale,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: Math.ceil(actualElementWidth),
windowHeight: Math.ceil(sliceHeightForPage),
y: Math.floor(sliceY),
height: Math.ceil(sliceHeightForPage)
});

const imgData = pageCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
const sliceScaleFactor = sliceCanvas.width / contentWidth;
const destHeight = sliceCanvas.height / sliceScaleFactor;
const imgData = sliceCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
}
} else {
statusText.textContent = 'Rendering PDF...';
await yieldToBrowser();

const canvas = await html2canvas(tempElement, {
scale: safeScale,
useCORS: true,
allowTaint: true,
logging: false,
windowWidth: Math.ceil(actualElementWidth),
windowHeight: Math.ceil(actualElementHeight)
});

const scaleFactor = canvas.width / contentWidth;
const imgHeight = canvas.height / scaleFactor;
const pagesCount = Math.ceil(imgHeight / (pageHeight - margin * 2));

for (let page = 0; page < pagesCount; page++) {
statusText.textContent = `Composing page ${page + 1} of ${pagesCount}...`;
await yieldToBrowser();

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

const sourceY = page * (pageHeight - margin * 2) * scaleFactor;
const sourceHeight = Math.min(canvas.height - sourceY, (pageHeight - margin * 2) * scaleFactor);
const destHeight = sourceHeight / scaleFactor;

const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
pageCanvas.height = sourceHeight;

const ctx = pageCanvas.getContext('2d');
ctx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight);

const imgData = pageCanvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, destHeight);
}
}

pdf.save("document.pdf");
Expand Down