Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Replaced `html2canvas` with `html2canvas-pro` for improved rendering in document capture. - Enhanced error handling in print functions to provide user feedback on print failures. - Introduced new `docs-page-capture` module to streamline page capture logic for PDF exports. - Refactored PDF export process to utilize captured canvases, improving performance and reliability. - Updated print styles for better document layout during printing and PDF generation.
203 lines
5.2 KiB
TypeScript
203 lines
5.2 KiB
TypeScript
import type { DocPageLayout } from "@/lib/drive/doc-page-setup"
|
|
import type { DocsExportSnapshot } from "@/lib/drive/docs-export-snapshot"
|
|
import { captureDocsPagesFromCanvas } from "@/lib/drive/docs-page-capture"
|
|
|
|
const PRINT_IFRAME_ID = "docs-print-iframe"
|
|
|
|
function buildPageRule(pageLayout: DocPageLayout): string {
|
|
const wMm = pageLayout.format.widthMm
|
|
const hMm = pageLayout.format.heightMm
|
|
const landscape = pageLayout.format.widthMm > pageLayout.format.heightMm
|
|
const size = landscape ? `${hMm}mm ${wMm}mm` : `${wMm}mm ${hMm}mm`
|
|
return `@page { size: ${size}; margin: 0; }`
|
|
}
|
|
|
|
function buildPrintStylesheet(pageLayout: DocPageLayout): string {
|
|
const pageWidth = pageLayout.widthPx
|
|
const pageHeight = pageLayout.heightPx
|
|
return `
|
|
${buildPageRule(pageLayout)}
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
background: white;
|
|
}
|
|
.docs-print-page {
|
|
width: ${pageWidth}px;
|
|
height: ${pageHeight}px;
|
|
overflow: hidden;
|
|
break-after: page;
|
|
page-break-after: always;
|
|
print-color-adjust: exact;
|
|
-webkit-print-color-adjust: exact;
|
|
}
|
|
.docs-print-page:last-child {
|
|
break-after: auto;
|
|
page-break-after: auto;
|
|
}
|
|
.docs-print-page img {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
`
|
|
}
|
|
|
|
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
|
return new Promise((resolve, reject) => {
|
|
canvas.toBlob(
|
|
(blob) => {
|
|
if (blob) resolve(blob)
|
|
else reject(new Error("Impossible de convertir la page en image"))
|
|
},
|
|
"image/jpeg",
|
|
0.92
|
|
)
|
|
})
|
|
}
|
|
|
|
function removePrintIframe(): void {
|
|
document.getElementById(PRINT_IFRAME_ID)?.remove()
|
|
}
|
|
|
|
async function mountPrintDocument(
|
|
doc: Document,
|
|
canvases: HTMLCanvasElement[],
|
|
pageLayout: DocPageLayout
|
|
): Promise<() => void> {
|
|
const pageWidth = pageLayout.widthPx
|
|
const pageHeight = pageLayout.heightPx
|
|
const objectUrls: string[] = []
|
|
|
|
doc.documentElement.lang = "fr"
|
|
doc.head.replaceChildren()
|
|
doc.body.replaceChildren()
|
|
|
|
const style = doc.createElement("style")
|
|
style.textContent = buildPrintStylesheet(pageLayout)
|
|
doc.head.appendChild(style)
|
|
|
|
for (const canvas of canvases) {
|
|
const blob = await canvasToBlob(canvas)
|
|
const url = URL.createObjectURL(blob)
|
|
objectUrls.push(url)
|
|
|
|
const page = doc.createElement("div")
|
|
page.className = "docs-print-page"
|
|
page.style.width = `${pageWidth}px`
|
|
page.style.height = `${pageHeight}px`
|
|
|
|
const img = doc.createElement("img")
|
|
img.alt = ""
|
|
img.width = pageWidth
|
|
img.height = pageHeight
|
|
img.src = url
|
|
|
|
page.appendChild(img)
|
|
doc.body.appendChild(page)
|
|
await img.decode().catch(() => undefined)
|
|
}
|
|
|
|
return () => {
|
|
for (const url of objectUrls) {
|
|
URL.revokeObjectURL(url)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function printPageCanvasesInIframe(
|
|
canvases: HTMLCanvasElement[],
|
|
pageLayout: DocPageLayout
|
|
): Promise<void> {
|
|
if (canvases.length === 0) {
|
|
throw new Error("Aucune page à imprimer")
|
|
}
|
|
|
|
removePrintIframe()
|
|
|
|
const iframe = document.createElement("iframe")
|
|
iframe.id = PRINT_IFRAME_ID
|
|
iframe.setAttribute("aria-hidden", "true")
|
|
iframe.style.cssText =
|
|
"position:fixed;left:-100000px;top:0;width:1px;height:1px;border:0;opacity:0;pointer-events:none"
|
|
|
|
const docReady = new Promise<Document>((resolve, reject) => {
|
|
iframe.onload = () => {
|
|
const doc = iframe.contentDocument
|
|
if (doc) resolve(doc)
|
|
else reject(new Error("Impossible de préparer la fenêtre d'impression"))
|
|
}
|
|
iframe.onerror = () => reject(new Error("Impossible de préparer la fenêtre d'impression"))
|
|
})
|
|
|
|
iframe.src = "about:blank"
|
|
document.body.appendChild(iframe)
|
|
|
|
const doc = await docReady
|
|
const win = iframe.contentWindow
|
|
if (!win) {
|
|
removePrintIframe()
|
|
throw new Error("Impossible de préparer la fenêtre d'impression")
|
|
}
|
|
|
|
let revokeObjectUrls = () => {}
|
|
try {
|
|
revokeObjectUrls = await mountPrintDocument(doc, canvases, pageLayout)
|
|
|
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
|
if (doc.fonts?.ready) {
|
|
await doc.fonts.ready
|
|
}
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const cleanup = () => {
|
|
win.removeEventListener("afterprint", onAfterPrint)
|
|
revokeObjectUrls()
|
|
removePrintIframe()
|
|
}
|
|
|
|
const onAfterPrint = () => {
|
|
cleanup()
|
|
resolve()
|
|
}
|
|
|
|
win.addEventListener("afterprint", onAfterPrint)
|
|
|
|
try {
|
|
win.focus()
|
|
win.print()
|
|
} catch (error) {
|
|
cleanup()
|
|
reject(error)
|
|
return
|
|
}
|
|
|
|
window.setTimeout(() => {
|
|
if (document.getElementById(PRINT_IFRAME_ID)) {
|
|
cleanup()
|
|
resolve()
|
|
}
|
|
}, 2000)
|
|
})
|
|
} catch (error) {
|
|
revokeObjectUrls()
|
|
removePrintIframe()
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export type DocsPrintMode = "print" | "pdf-capture"
|
|
|
|
/** @deprecated Live DOM is no longer mutated; kept for PDF export compatibility. */
|
|
export async function prepareDocsPrintEnvironment(
|
|
_snapshot: DocsExportSnapshot,
|
|
_mode: DocsPrintMode = "print"
|
|
): Promise<() => void> {
|
|
return () => {}
|
|
}
|
|
|
|
export async function printDocsDocument(snapshot: DocsExportSnapshot): Promise<void> {
|
|
const canvases = await captureDocsPagesFromCanvas(snapshot, { scale: 2 })
|
|
await printPageCanvasesInIframe(canvases, snapshot.pageLayout)
|
|
}
|