fix: PDF généré côté serveur (pdf-lib) - 1 page A4 portrait garantie

This commit is contained in:
Manus
2026-06-05 11:27:58 -04:00
parent 5d919f891f
commit 51830b88be
4 changed files with 474 additions and 146 deletions

135
test_pdf_freepro.ts Normal file
View File

@@ -0,0 +1,135 @@
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()}`);