using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using System; #if UNITY_EDITOR using PdfSharpCore; using PdfSharpCore.Pdf; using PdfSharpCore.Drawing; #endif // ── PDFItem — accessible depuis tous les scripts ── [System.Serializable] public class PDFItem { public string label; public string value; public string numero; public string qty; public string code; public string description; public string priceUnit; public string priceTotal; public PDFItem() { } public PDFItem(string numero, string qty, string code, string description, string priceUnit, string priceTotal) { this.numero = numero; this.qty = qty; this.code = code; this.description = description; this.priceUnit = priceUnit; this.priceTotal = priceTotal; this.label = description; this.value = code; } } // ───────────────────────────────────────────────── public class PDFExporter : MonoBehaviour { #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern void GeneratePDF( string title, string itemsJson, string imageBase64); #endif public Camera captureCamera; // ── Point d'entrée ──────────────────────────── public void ExportPDF(string title, List items) { StartCoroutine(CaptureAndExport(title, items)); } // ── Capture + Export ────────────────────────── private IEnumerator CaptureAndExport(string title, List items) { yield return new WaitForEndOfFrame(); byte[] png1 = null; byte[] png2 = null; byte[] logoBytes = null; byte[] logo2Bytes = null; if (captureCamera != null) { // Capture 1 var rt1 = new RenderTexture(1280, 750, 24); captureCamera.targetTexture = rt1; captureCamera.Render(); RenderTexture.active = rt1; var s1 = new Texture2D(1280, 750, TextureFormat.RGB24, false); s1.ReadPixels(new Rect(0, 0, 1280, 750), 0, 0); s1.Apply(); captureCamera.targetTexture = null; RenderTexture.active = null; Destroy(rt1); png1 = s1.EncodeToPNG(); Destroy(s1); // Capture 2 var rt2 = new RenderTexture(1280, 750, 24); captureCamera.targetTexture = rt2; captureCamera.Render(); RenderTexture.active = rt2; var s2 = new Texture2D(1280, 750, TextureFormat.RGB24, false); s2.ReadPixels(new Rect(0, 0, 1280, 750), 0, 0); s2.Apply(); captureCamera.targetTexture = null; RenderTexture.active = null; Destroy(rt2); png2 = s2.EncodeToPNG(); Destroy(s2); } if (_G.LOGO != null) logoBytes = _G.LOGO.EncodeToPNG(); if (_G.LOGO2 != null) logo2Bytes = _G.LOGO2.EncodeToPNG(); #if UNITY_EDITOR GeneratePDF_Editor(title, items, png1, png2, logoBytes, logo2Bytes); Debug.Log($"✅ PDF généré : {title}.pdf"); #elif UNITY_WEBGL string b64 = png1 != null ? Convert.ToBase64String(png1) : ""; GeneratePDF(title, SerializeItems(items), b64); #endif } // ───────────────────────────────────────────── // EDITOR — PdfSharpCore // ───────────────────────────────────────────── #if UNITY_EDITOR private void GeneratePDF_Editor( string title, List items, byte[] png1, byte[] png2, byte[] logoBytes, byte[] logo2Bytes) { string path = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Desktop), title + ".pdf"); var doc = new PdfDocument(); doc.Info.Title = title; // ── Fonts ───────────────────────────────── var fTitle = new XFont("Arial", 11, XFontStyle.Bold); var fSmall = new XFont("Arial", 8, XFontStyle.Regular); var fLabel = new XFont("Arial", 9, XFontStyle.Regular); var fTiny = new XFont("Arial", 7, XFontStyle.Regular); var fFooter = new XFont("Arial", 8, XFontStyle.Regular); // ── Couleurs header ─────────────────────── var barBrush = new XSolidBrush(ColorToXColor(_G.colorBar)); var textBrush = new XSolidBrush(ColorToXColor(_G.colorText)); // ── Brushes cartouche ───────────────────── var black = XBrushes.Black; var grayLbl = new XSolidBrush(XColor.FromArgb(100, 100, 100)); var grayFoot = new XSolidBrush(XColor.FromArgb(150, 150, 150)); var rowBg = new XSolidBrush(XColor.FromArgb(235, 235, 235)); var borderPen = new XPen(XColor.FromArgb(100, 100, 100), 0.75); // ── Valeurs cartouche ───────────────────── string projet = GetItem(items, "Projet"); string nom = GetItem(items, "Nom"); string adresse = GetItem(items, "Adresse"); string email = GetItem(items, "Email"); string cuisiniste = GetItem(items, "Cuisiniste"); string note = GetItem(items, "Note"); string numero = GetItem(items, "Numéro"); string date = DateTime.Now.ToShortDateString(); byte[][] shots = { png1, png2 }; // ── 2 pages ─────────────────────────────── double mg = 20; double hdrH = 30; // ← une seule fois ici ✅ double cartoH = 110; for (int p = 0; p < 2; p++) { var page = doc.AddPage(); page.Size = PageSize.Letter; page.Orientation = PageOrientation.Landscape; double W = page.Width.Point; // 792 double H = page.Height.Point; // 612 double cartoY = H - cartoH - mg; using (var gfx = XGraphics.FromPdfPage(page)) { // ── Header — une seule barre ────── gfx.DrawRectangle(barBrush, 0, 0, W, hdrH); // Titre centré gfx.DrawString(title, fTitle, textBrush, new XRect(0, 0, W, hdrH), XStringFormats.Center); // Numéro page à droite gfx.DrawString($"Page {p + 1}", fTitle, textBrush, new XRect(0, 0, W - 10, hdrH), XStringFormats.CenterRight); // ── Logo 1 par-dessus ───────────── double l1W = 0; if (logoBytes != null) { l1W = hdrH * 3; using (var ms = new MemoryStream(logoBytes)) { var img = XImage.FromStream(() => ms); //gfx.DrawImage(img, new XRect(0, 0, l1W, hdrH)); } } else { gfx.DrawString("U-Kitchen-iT", fSmall, textBrush, new XPoint(10, 19)); } // ── Logo 2 par-dessus ───────────── if (logo2Bytes != null) { double l2W = hdrH * 3; using (var ms = new MemoryStream(logo2Bytes)) { var img2 = XImage.FromStream(() => ms); //gfx.DrawImage(img2, new XRect(l1W, 0, l2W, hdrH)); } } // ── Image ───────────────────────── double imgY = hdrH + 8; // ← directement après header ✅ double imgH = cartoY - imgY - 8; double imgW = W - mg * 2; double ratio = 1280.0 / 750.0; double fitW = imgH * ratio; if (fitW > imgW) { fitW = imgW; imgH = fitW / ratio; } double imgX = (W - fitW) / 2; if (shots[p] != null) { using (var ms = new MemoryStream(shots[p])) { var img = XImage.FromStream(() => ms); gfx.DrawImage(img, imgX, imgY, fitW, imgH); } } // ── Cartouche bas ───────────────── double lW = W * 0.5; double rW = W - lW - mg * 2; double cx = mg; double cy = cartoY; double rH = cartoH / 4.0; double rx = mg + lW; double dW = rW * 0.5; double nW = rW * 0.5; gfx.DrawRectangle(borderPen, XBrushes.White, cx, cy, W - mg * 2, cartoH); // Ligne 1 : Projet | Date | Numéro Cell(gfx, borderPen, XBrushes.White, cx, cy, lW, rH); Label(gfx, fTiny, grayLbl, "Projet :", cx+4, cy, lW, rH); Value(gfx, fLabel, black, projet, cx+4, cy, lW, rH); Cell(gfx, borderPen, XBrushes.White, rx, cy, dW, rH); Label(gfx, fTiny, grayLbl, "Date :", rx+4, cy, dW, rH); Value(gfx, fLabel, black, date, rx+4, cy, dW, rH); Cell(gfx, borderPen, XBrushes.White, rx+dW, cy, nW, rH); Label(gfx, fTiny, grayLbl, "Numéro :", rx+dW+4, cy, nW, rH); Value(gfx, fLabel, black, numero, rx+dW+4, cy, nW, rH); // Ligne 2 : Nom | Cuisiniste Cell(gfx, borderPen, rowBg, cx, cy+rH, lW, rH); Label(gfx, fTiny, grayLbl, "Nom :", cx+4, cy+rH, lW, rH); Value(gfx, fLabel, black, nom, cx+4, cy+rH, lW, rH); Cell(gfx, borderPen, rowBg, rx, cy+rH, rW, rH); Label(gfx, fTiny, grayLbl, "Cuisiniste :", rx+4, cy+rH, rW, rH); Value(gfx, fLabel, black, cuisiniste, rx+4, cy+rH, rW, rH); // Ligne 3 : Adresse | Note (span 2 lignes) Cell(gfx, borderPen, XBrushes.White, cx, cy+rH*2, lW, rH); Label(gfx, fTiny, grayLbl, "Adresse :", cx+4, cy+rH*2, lW, rH); Value(gfx, fLabel, black, adresse, cx+4, cy+rH*2, lW, rH); Cell(gfx, borderPen, XBrushes.White, rx, cy+rH*2, rW, rH*2); Label(gfx, fTiny, grayLbl, "Note :", rx+4, cy+rH*2, rW, rH*2); Value(gfx, fLabel, black, note, rx+4, cy+rH*2, rW, rH*2); // Ligne 4 : Email Cell(gfx, borderPen, rowBg, cx, cy+rH*3, lW, rH); Label(gfx, fTiny, grayLbl, "Email :", cx+4, cy+rH*3, lW, rH); Value(gfx, fLabel, black, email, cx+4, cy+rH*3, lW, rH); } // ← gfx dispose ✅ } // ── Footer ──────────────────────────────── int total = doc.PageCount; for (int p = 0; p < total; p++) { var pg = doc.Pages[p]; using (var gfxP = XGraphics.FromPdfPage(pg)) { gfxP.DrawString( $"{p + 1} / {total}", fFooter, grayFoot, new XRect(0, pg.Height.Point - 15, pg.Width.Point, 15), XStringFormats.Center); } } // ── Sauvegarde + ouverture ───────────────── doc.Save(path); new System.Threading.Thread(() => { System.Diagnostics.Process.Start( new System.Diagnostics.ProcessStartInfo() { FileName = path, UseShellExecute = true }); }).Start(); } // ── Helpers cellule ─────────────────────────── private void Cell(XGraphics g, XPen pen, XBrush bg, double x, double y, double w, double h) => g.DrawRectangle(pen, bg, x, y, w, h); private void Label(XGraphics g, XFont f, XBrush b, string text, double x, double y, double w, double h) => g.DrawString(text, f, b, new XPoint(x, y + 9)); private void Value(XGraphics g, XFont f, XBrush b, string text, double x, double y, double w, double h) { if (string.IsNullOrEmpty(text)) return; g.DrawString(text, f, b, new XPoint(x + 20, y + (h / 2) + 3)); } // ── Unity Color → XColor ────────────────────── private XColor ColorToXColor(Color c) => XColor.FromArgb( (int)(c.a * 255), (int)(c.r * 255), (int)(c.g * 255), (int)(c.b * 255)); // ── Hex → XColor ────────────────────────────── private XColor HexToXColor(string hex) { try { hex = hex.Replace("#", "").Trim(); if (hex.Length == 6) return XColor.FromArgb( Convert.ToByte(hex.Substring(0, 2), 16), Convert.ToByte(hex.Substring(2, 2), 16), Convert.ToByte(hex.Substring(4, 2), 16)); } catch (Exception e) { Debug.LogWarning($"HexToXColor : {e.Message}"); } return XColor.FromArgb(30, 30, 30); } #endif // ── GetItem ─────────────────────────────────── private string GetItem(List items, string label) { var i = items.Find(x => x.label.Equals( label, StringComparison.OrdinalIgnoreCase)); return i != null ? i.value : ""; } // ── SerializeItems ──────────────────────────── private string SerializeItems(List items) { string json = "["; for (int i = 0; i < items.Count; i++) { string l = items[i].label.Replace("\"", "\\\""); string v = items[i].value.Replace("\"", "\\\""); json += $"{{\"label\":\"{l}\",\"value\":\"{v}\"}}"; if (i < items.Count - 1) json += ","; } return json + "]"; } // ── Test ────────────────────────────────────── // [ContextMenu("Test Export PDF")] // public void TestExport() // { // ExportPDF("Rapport Cuisine", new List // { // new PDFItem("Projet", "U-Kitchen"), // new PDFItem("Nom", "Jean Tremblay"), // new PDFItem("Adresse", "123 Rue Principale"), // new PDFItem("Email", "jean@email.com"), // new PDFItem("Cuisiniste", "Marie Dupont"), // new PDFItem("Note", "Vue principale"), // new PDFItem("Numéro", "2024-001"), // }); // } }