Files
fotospiel-app/resources/js/admin/mobile/invite-layout/export-utils.ts
Codex Agent ce43cac145
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Fix foldable background layout
2026-01-24 10:41:24 +01:00

138 lines
4.2 KiB
TypeScript

import * as fabric from 'fabric';
import { PDFDocument } from 'pdf-lib';
import { CANVAS_HEIGHT, CANVAS_WIDTH } from './schema';
import { FabricRenderOptions, renderFabricLayout } from './DesignerCanvas';
const PDF_PAGE_SIZES: Record<string, { width: number; height: number }> = {
a4: { width: 595.28, height: 841.89 },
a5: { width: 419.53, height: 595.28 },
letter: { width: 612, height: 792 },
};
export async function withFabricCanvas<T>(
options: FabricRenderOptions,
handler: (canvas: fabric.Canvas, element: HTMLCanvasElement) => Promise<T>,
): Promise<T> {
const canvasElement = document.createElement('canvas');
const targetWidth = options.canvasWidth ?? CANVAS_WIDTH;
const targetHeight = options.canvasHeight ?? CANVAS_HEIGHT;
canvasElement.width = targetWidth;
canvasElement.height = targetHeight;
const canvas = new fabric.Canvas(canvasElement, {
selection: false,
});
try {
await renderFabricLayout(canvas, {
...options,
readOnly: true,
});
return await handler(canvas, canvasElement);
} finally {
canvas.dispose();
canvasElement.remove();
}
}
export async function generatePngDataUrl(
options: FabricRenderOptions,
multiplier = 2,
): Promise<string> {
return withFabricCanvas(options, async (canvas) =>
canvas.toDataURL({ format: 'png', multiplier }),
);
}
export async function generatePdfBytes(
options: FabricRenderOptions,
paper: string,
orientation: string,
multiplier = 2,
): Promise<Uint8Array> {
const dataUrl = await generatePngDataUrl(options, multiplier);
return createPdfFromPng(dataUrl, paper, orientation);
}
export async function createPdfFromPng(
dataUrl: string,
paper: string,
orientation: string,
): Promise<Uint8Array> {
const pdfDoc = await PDFDocument.create();
const baseSize = PDF_PAGE_SIZES[paper.toLowerCase()] ?? PDF_PAGE_SIZES.a4;
const landscape = orientation === 'landscape';
const pageWidth = landscape ? baseSize.height : baseSize.width;
const pageHeight = landscape ? baseSize.width : baseSize.height;
const page = pdfDoc.addPage([pageWidth, pageHeight]);
const pngBytes = dataUrlToUint8Array(dataUrl);
const pngImage = await pdfDoc.embedPng(pngBytes);
const imageWidth = pngImage.width;
const imageHeight = pngImage.height;
const scale = Math.min(pageWidth / imageWidth, pageHeight / imageHeight);
const drawWidth = imageWidth * scale;
const drawHeight = imageHeight * scale;
page.drawImage(pngImage, {
x: (pageWidth - drawWidth) / 2,
y: (pageHeight - drawHeight) / 2,
width: drawWidth,
height: drawHeight,
});
return pdfDoc.save();
}
export function triggerDownloadFromDataUrl(dataUrl: string, filename: string): Promise<void> {
return fetch(dataUrl)
.then((response) => response.blob())
.then((blob) => triggerDownloadFromBlob(blob, filename));
}
export function triggerDownloadFromBlob(blob: Blob, filename: string): void {
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}
export async function openPdfInNewTab(pdfBytes: Uint8Array): Promise<void> {
const arrayBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength) as ArrayBuffer;
const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
const blobUrl = URL.createObjectURL(blob);
const printWindow = window.open(blobUrl, '_blank', 'noopener,noreferrer');
if (!printWindow) {
URL.revokeObjectURL(blobUrl);
throw new Error('window-blocked');
}
printWindow.onload = () => {
try {
printWindow.focus();
printWindow.print();
} catch (error) {
console.error('[FabricExport] Browser print failed', error);
}
};
setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
}
function dataUrlToUint8Array(dataUrl: string): Uint8Array {
const [, base64] = dataUrl.split(',');
const decoded = atob(base64 ?? '');
const bytes = new Uint8Array(decoded.length);
for (let index = 0; index < decoded.length; index += 1) {
bytes[index] = decoded.charCodeAt(index);
}
return bytes;
}