feat: add DividendSectionRenderer with landscape two-level header table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 16:03:21 +01:00
parent 5b6400903d
commit 069e33f0ec

View File

@@ -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;
/// <summary>
/// Genera la pagina landscape con la tabella unificata Sottostanti + Dividendi.
/// Non implementa IPdfSectionRenderer — iniettato direttamente nell'orchestratore.
/// </summary>
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<int> 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));
}
}