diff --git a/CertReports.Syncfusion/Services/Implementations/DividendSectionRenderer.cs b/CertReports.Syncfusion/Services/Implementations/DividendSectionRenderer.cs new file mode 100644 index 0000000..122c0a7 --- /dev/null +++ b/CertReports.Syncfusion/Services/Implementations/DividendSectionRenderer.cs @@ -0,0 +1,223 @@ +using CertReports.Syncfusion.Helpers; +using CertReports.Syncfusion.Models; +using Syncfusion.Drawing; +using Syncfusion.Pdf; +using Syncfusion.Pdf.Graphics; +using Syncfusion.Pdf.Grid; + +namespace CertReports.Syncfusion.Services.Implementations; + +/// +/// Genera la pagina landscape con la tabella unificata Sottostanti + Dividendi. +/// Non implementa IPdfSectionRenderer — iniettato direttamente nell'orchestratore. +/// +public class DividendSectionRenderer +{ + // Larghezze colonne indicative (totale ~742pt) — scalate proporzionalmente a runtime + private static readonly float[] ColWidths = + [ + 90f, // 0 Nome + 50f, // 1 Strike + 50f, // 2 Last + 46f, // 3 % Perf. + 50f, // 4 Barr.Cap. + 46f, // 5 Buf.Cap. + 50f, // 6 Trig.CPN + 46f, // 7 Buf.CPN + 46f, // 8 Trig.AC + 54f, // 9 Data Stacco + 54f, // 10 Data Pag. + 40f, // 11 Importo + 40f, // 12 Rend. + 40f, // 13 Imp.Fut. + 40f, // 14 Rend.Fut. + ]; + + private static readonly string[] Col2Headers = + [ + "", "Strike", "Last", "% Perf.", + "Barr.Cap.", "Buf.Cap.", "Trig.CPN", "Buf.CPN", "Trig.AC", + "Data Stacco", "Data Pag.", "Importo", "Rend.", "Imp.Fut.", "Rend.Fut.", + ]; + + // Indici colonne soggette a colore performance (0-based) + private static readonly HashSet PerfCols = [3, 5, 7, 12, 14]; + + // Static brush e pen per header + private static readonly PdfSolidBrush DarkBlueBrush = new(Color.FromArgb(255, 10, 56, 128)); // #0A3880 + private static readonly PdfPen SeparatorPen = new(Color.FromArgb(255, 100, 181, 246), 1.5f); // #64B5F6 + + public PdfDocument Render(CertificateReportData data) + { + var doc = new PdfDocument(); + doc.PageSettings.Orientation = PdfPageOrientation.Landscape; + doc.PageSettings.Size = PdfPageSize.A4; + + var page = doc.Pages.Add(); + var g = page.Graphics; + var size = page.GetClientSize(); + + float pageWidth = size.Width; + float pageHeight = size.Height; + + float w = pageWidth - 2 * PdfTheme.PageMargin; + float h = pageHeight - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight; + float x0 = PdfTheme.PageMargin; + float y = PdfTheme.PageMargin; + + // ── Titolo ──────────────────────────────────────────────────── + g.DrawString("Sottostanti e Dividendi", PdfTheme.Title, PdfTheme.AccentBlueBrush, + new RectangleF(x0, y, w, 20f)); + y += 20f + 4f; + + // ── Linea separatrice ───────────────────────────────────────── + g.DrawLine(PdfTheme.AccentBluePen, new PointF(x0, y), new PointF(x0 + w, y)); + y += 6f; + + // ── Calcolo fattore scala su larghezza reale ────────────────── + float totalNominal = ColWidths.Sum(); + float scale = w / totalNominal; + float[] cw = ColWidths.Select(c => c * scale).ToArray(); + + // ── Disegno header a 2 livelli ──────────────────────────────── + float rh = PdfTheme.RowHeight; + + DrawHeader(g, x0, y, cw, rh); + + y += rh * 2; + + // ── Tabella dati ────────────────────────────────────────────── + DrawDataGrid(g, data, x0, y, cw); + + // ── Footer ──────────────────────────────────────────────────── + PdfTheme.DrawFooter(g, pageWidth, pageHeight, 1, data.ShowBranding); + + return doc; + } + + private static void DrawHeader(PdfGraphics g, float x0, float y, float[] cw, float rh) + { + var headerFont = PdfTheme.TableBold; + var whiteBrush = PdfBrushes.White; + var accentBrush = PdfTheme.AccentBlueBrush; + + // ── Riga 1: Gruppi ───────────────────────────────────────────── + float cx = x0; + + // "Nome" (col 0) — cella vuota + DrawHeaderCell(g, cx, y, cw[0], rh, "", accentBrush, whiteBrush, headerFont); + cx += cw[0]; + + // "SOTTOSTANTE" (cols 1-3) + float sottostanteW = cw[1] + cw[2] + cw[3]; + DrawHeaderCell(g, cx, y, sottostanteW, rh, "SOTTOSTANTE", accentBrush, whiteBrush, headerFont); + cx += sottostanteW; + + // "BARRIERE" (cols 4-8) + float barriereW = cw[4] + cw[5] + cw[6] + cw[7] + cw[8]; + DrawHeaderCell(g, cx, y, barriereW, rh, "BARRIERE", accentBrush, whiteBrush, headerFont); + cx += barriereW; + + // "DIVIDENDI" (cols 9-14) — blu scuro + float dividendiW = cw[9] + cw[10] + cw[11] + cw[12] + cw[13] + cw[14]; + DrawHeaderCell(g, cx, y, dividendiW, rh, "DIVIDENDI", darkBlueBrush, whiteBrush, headerFont); + + // ── Riga 2: Sottocolonne ─────────────────────────────────────── + cx = x0; + for (int i = 0; i < cw.Length; i++) + { + var bg = i >= 9 ? darkBlueBrush : accentBrush; + DrawHeaderCell(g, cx, y + rh, cw[i], rh, Col2Headers[i], bg, whiteBrush, headerFont); + cx += cw[i]; + } + + // ── Separatore verticale tra col 8 e col 9 ──────────────────── + float sepX = x0 + cw.Take(9).Sum(); + g.DrawLine(SeparatorPen, new PointF(sepX, y), new PointF(sepX, y + rh * 2)); + } + + private static void DrawHeaderCell(PdfGraphics g, float x, float y, float w, float h, + string text, PdfBrush bg, PdfBrush fg, PdfFont font) + { + g.DrawRectangle(bg, new RectangleF(x, y, w, h)); + if (!string.IsNullOrEmpty(text)) + { + var fmt = new PdfStringFormat + { + Alignment = PdfTextAlignment.Center, + LineAlignment = PdfVerticalAlignment.Middle, + }; + g.DrawString(text, font, fg, new RectangleF(x + 1, y, w - 2, h), fmt); + } + } + + private static void DrawDataGrid(PdfGraphics g, CertificateReportData data, + float x0, float y, float[] cw) + { + var grid = new PdfGrid(); + grid.Style.CellPadding = new PdfPaddings(2, 2, 2, 2); + grid.Style.Font = PdfTheme.TableFont; + + // 15 colonne + grid.Columns.Add(15); + for (int i = 0; i < 15; i++) + grid.Columns[i].Width = cw[i]; + + var sottostanti = data.Info.Sottostanti; + + for (int i = 0; i < sottostanti.Count; i++) + { + var s = sottostanti[i]; + var row = grid.Rows.Add(); + + row.Cells[0].Value = s.Nome; + row.Cells[1].Value = s.Strike; + row.Cells[2].Value = s.LastPrice; + row.Cells[3].Value = string.IsNullOrEmpty(s.Performance) ? "—" : s.Performance; + row.Cells[4].Value = string.IsNullOrEmpty(s.CapitalBarrier) ? "—" : s.CapitalBarrier; + row.Cells[5].Value = string.IsNullOrEmpty(s.ULCapitalBarrierBuffer) ? "—" : s.ULCapitalBarrierBuffer; + row.Cells[6].Value = string.IsNullOrEmpty(s.CouponBarrier) ? "—" : s.CouponBarrier; + row.Cells[7].Value = string.IsNullOrEmpty(s.ULCouponBarrierBuffer) ? "—" : s.ULCouponBarrierBuffer; + row.Cells[8].Value = string.IsNullOrEmpty(s.TriggerAutocall) ? "—" : s.TriggerAutocall; + row.Cells[9].Value = string.IsNullOrEmpty(s.DividendExDate) ? "—" : s.DividendExDate; + row.Cells[10].Value = string.IsNullOrEmpty(s.DividendPayDate) ? "—" : s.DividendPayDate; + row.Cells[11].Value = string.IsNullOrEmpty(s.DividendAmount) ? "—" : s.DividendAmount; + row.Cells[12].Value = string.IsNullOrEmpty(s.DividendYield) ? "—" : s.DividendYield; + row.Cells[13].Value = string.IsNullOrEmpty(s.DividendFutAmount) ? "—" : s.DividendFutAmount; + row.Cells[14].Value = string.IsNullOrEmpty(s.DividendFutYield) ? "—" : s.DividendFutYield; + + // Righe alternate + if (i % 2 == 1) + { + for (int c = 0; c < 15; c++) + row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush; + } + + // Colore performance: negativi rosso, positivi verde + foreach (int c in PerfCols) + { + var val = row.Cells[c].Value?.ToString(); + if (string.IsNullOrEmpty(val) || val == "—") continue; + + bool isNegative = val.TrimStart().StartsWith('-'); + row.Cells[c].Style.TextBrush = isNegative + ? PdfTheme.NegativeRedBrush + : PdfTheme.PositiveGreenBrush; + } + + // Allineamento: Nome a sinistra, tutto il resto centrato + row.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Left); + for (int c = 1; c < 15; c++) + { + row.Cells[c].StringFormat = new PdfStringFormat + { + Alignment = PdfTextAlignment.Center, + LineAlignment = PdfVerticalAlignment.Middle, + }; + } + } + + // Pagina singola: specifica garantisce max ~20 sottostanti per certificato + grid.Draw(g, new PointF(x0, y)); + } +}