import jsPDFModule from "jspdf"; import * as fs from "fs"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const jsPDF = (jsPDFModule as any).default ?? jsPDFModule; const lines = [ { structure: "1001BPT", type: "Lien 5G", montantCentimes: 0 }, { structure: "1001VAR - ITEP SESSAD VAREY", type: "Tél. mobile", montantCentimes: 48 }, { structure: "1031MER", type: "Lien 5G", montantCentimes: 2398 }, { structure: "1038MBN", type: "Lien fibre", montantCentimes: 5999 }, { structure: "1038RAC", type: "Lien fibre", montantCentimes: 5999 }, { structure: "1042CLA", type: "Lien fibre", montantCentimes: 5999 }, { structure: "1069BOUIME", type: "Lien fibre", montantCentimes: 5999 }, { structure: "1069IVP", type: "Lien fibre", montantCentimes: 5999 }, { structure: "1083ADV", type: "Lien 5G", montantCentimes: 1199 }, { structure: "1083ADV", type: "Lien fibre", montantCentimes: 23996 }, { structure: "1083ADV", type: "Tél. mobile", montantCentimes: 14261 }, { structure: "1083MIS", type: "Tél. mobile", montantCentimes: 9592 }, { structure: "1083QVT", type: "Tél. mobile", montantCentimes: 1199 }, { structure: "1083SYL", type: "Lien 5G", montantCentimes: 1199 }, { structure: "1083SYL", type: "Lien fibre", montantCentimes: 11998 }, { structure: "1083SYL", type: "Tél. mobile", montantCentimes: 7194 }, { structure: "1084CAS", type: "Tél. mobile", montantCentimes: 1199 }, { structure: "2001BRP", type: "Lien 5G", montantCentimes: 1199 }, { structure: "2001MUS", type: "Lien 5G", montantCentimes: 0 }, { structure: "2001ROS", type: "Tél. mobile", montantCentimes: 1223 }, { structure: "2011MON", type: "Lien fibre", montantCentimes: 5999 }, { structure: "2013ANG", type: "Lien 5G", montantCentimes: 1199 }, { structure: "2021MOU", type: "Lien fibre", montantCentimes: 5999 }, { structure: "2063VSJ", type: "Lien fibre", montantCentimes: 5999 }, { structure: "2069MAU", type: "Lien 5G", montantCentimes: 1199 }, { structure: "2069SAL", type: "Tél. mobile", montantCentimes: 1199 }, { structure: "2081ANC", type: "Lien 5G", montantCentimes: 1199 }, { structure: "2081BLA", type: "Lien 5G", montantCentimes: 0 }, { structure: "3069UDA", type: "Lien 5G", montantCentimes: 1199 }, { structure: "3069UDA", type: "Lien fibre", montantCentimes: 27599 }, { structure: "3069UDA", type: "Tél. mobile", montantCentimes: -1 }, ]; const formatMontant = (centimes: number): string => { const euros = centimes / 100; const abs = Math.abs(euros); const str = abs.toFixed(2).replace(".", ","); const parts = str.split(","); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " "); return (euros < 0 ? "-" : "") + parts.join(",") + " EUR"; }; const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" }); const pageW = 210; const pageH = 297; const margin = 14; const tableStartY = 31; const tableEndY = pageH - 10; const usableW = pageW - margin * 2; // En-tête doc.setFontSize(7); doc.setFont("helvetica", "normal"); doc.text("Edite le 05/06/2026 - 17:00", pageW - margin, 7, { align: "right" }); doc.setFontSize(13); doc.setFont("helvetica", "bold"); doc.text("Ventilation facture FREE PRO", pageW / 2, 13, { align: "center" }); doc.setFontSize(10); doc.text("01/01/2025", pageW / 2, 20, { align: "center" }); doc.setFontSize(8); doc.setFont("helvetica", "bold"); doc.text("ref_piece :", margin, 27); doc.setFont("helvetica", "normal"); doc.text("F202501004510", margin + 22, 27); // Calcul dimensions const nbRows = lines.length + 2; const availH = tableEndY - tableStartY; const rowH = availH / nbRows; const fontSize = Math.max(5, Math.min(9, Math.floor(rowH * 0.55 / 0.353))); console.log(`nbRows=${nbRows}, availH=${availH.toFixed(1)}mm, rowH=${rowH.toFixed(2)}mm, fontSize=${fontSize}pt`); console.log(`Tableau: ${tableStartY}mm → ${(tableStartY + nbRows * rowH).toFixed(1)}mm (limite=${tableEndY}mm)`); const col0W = usableW * 0.45; const col1W = usableW * 0.32; const col2W = usableW * 0.23; const col1X = margin + col0W; const col2X = col1X + col1W; doc.setFontSize(fontSize); const drawRow = (y: number, c0: string, c1: string, c2: string, bold: boolean, bg?: [number,number,number]) => { if (bg) { doc.setFillColor(bg[0], bg[1], bg[2]); doc.rect(margin, y, usableW, rowH, "F"); } doc.setFont("helvetica", bold ? "bold" : "normal"); const textY = y + rowH * 0.65; const pad = 1.5; doc.text(c0, margin + pad, textY, { maxWidth: col0W - pad * 2 }); doc.text(c1, col1X + pad, textY, { maxWidth: col1W - pad * 2 }); doc.text(c2, col2X + col2W - pad, textY, { align: "right", maxWidth: col2W - pad * 2 }); }; const drawHLine = (y: number, lw: number, r: number, g: number, b: number) => { doc.setDrawColor(r, g, b); doc.setLineWidth(lw); doc.line(margin, y, margin + usableW, y); }; const drawVLines = (y: number, h: number) => { doc.setDrawColor(180, 180, 180); doc.setLineWidth(0.1); doc.line(margin, y, margin, y + h); doc.line(col1X, y, col1X, y + h); doc.line(col2X, y, col2X, y + h); doc.line(margin + usableW, y, margin + usableW, y + h); }; const headerY = tableStartY; drawHLine(headerY, 0.4, 0, 0, 0); drawRow(headerY, "Structure", "Type", "Montant TTC", true); drawHLine(headerY + rowH, 0.4, 0, 0, 0); drawVLines(headerY, rowH); for (let i = 0; i < lines.length; i++) { const l = lines[i]; const y = headerY + rowH * (i + 1); const bg: [number,number,number] | undefined = i % 2 === 1 ? [248,248,248] : undefined; drawRow(y, l.structure, l.type, formatMontant(l.montantCentimes), false, bg); drawHLine(y + rowH, 0.1, 200, 200, 200); drawVLines(y, rowH); } const totalCentimes = lines.reduce((s, l) => s + l.montantCentimes, 0); const footerY = headerY + rowH * (lines.length + 1); drawHLine(footerY, 0.4, 0, 0, 0); drawRow(footerY, "Total general", "", formatMontant(totalCentimes), true, [240,240,240]); drawHLine(footerY + rowH, 0.4, 0, 0, 0); drawVLines(footerY, rowH); const pdfBytes = doc.output("arraybuffer"); fs.writeFileSync("/tmp/test_freepro_01_25.pdf", Buffer.from(pdfBytes)); console.log("PDF généré : /tmp/test_freepro_01_25.pdf"); console.log(`Nombre de pages : ${doc.getNumberOfPages()}`);