fix: PDF généré côté serveur (pdf-lib) - 1 page A4 portrait garantie
This commit is contained in:
135
test_pdf_freepro.ts
Normal file
135
test_pdf_freepro.ts
Normal 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()}`);
|
||||
Reference in New Issue
Block a user