From 8e6341d980e4e22a385bee8c3bbf753c314ed55c Mon Sep 17 00:00:00 2001 From: SmartRootsSrl Date: Mon, 8 Jun 2026 17:41:48 +0200 Subject: [PATCH] feat: add FundAnagraficaRenderer (layout C: title strip + 3 columns) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../Implementations/FundAnagraficaRenderer.cs | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 CertReports.Syncfusion/Services/Implementations/FundAnagraficaRenderer.cs diff --git a/CertReports.Syncfusion/Services/Implementations/FundAnagraficaRenderer.cs b/CertReports.Syncfusion/Services/Implementations/FundAnagraficaRenderer.cs new file mode 100644 index 0000000..89e21e8 --- /dev/null +++ b/CertReports.Syncfusion/Services/Implementations/FundAnagraficaRenderer.cs @@ -0,0 +1,266 @@ +using CertReports.Syncfusion.Helpers; +using CertReports.Syncfusion.Models; +using Syncfusion.Drawing; +using Syncfusion.Pdf; +using Syncfusion.Pdf.Graphics; + +namespace CertReports.Syncfusion.Services.Implementations; + +public class FundAnagraficaRenderer +{ + private const float PageW = 595f - 2 * PdfTheme.PageMargin; + private const float PageH = 842f - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight; + private const float ColGap = 8f; + private const float Col1W = 190f; + private const float Col2W = 125f; + private const float Col3W = PageW - Col1W - Col2W - 2 * ColGap; + + public PdfDocument Render(FundReportData data) + { + var doc = new PdfDocument(); + var page = PdfTheme.AddA4Page(doc); + var g = page.Graphics; + var info = data.Info; + float y = 0f; + + y = DrawTitle(g, info, y); + y += 6f; + y = DrawStrip(g, info, y); + y += 8f; + + float col1X = 0f; + float col2X = Col1W + ColGap; + float col3X = col2X + Col2W + ColGap; + + DrawAnagrafici(g, info, col1X, Col1W, y); + DrawEsg(g, info, col2X, Col2W, y); + DrawPerfGrid(g, info, col3X, Col3W, y); + + PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); + return doc; + } + + private float DrawTitle(PdfGraphics g, FundInfo info, float y) + { + const float h = 22f; + g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(0, y, PageW, h)); + var fmt = new PdfStringFormat + { + Alignment = PdfTextAlignment.Center, + LineAlignment = PdfVerticalAlignment.Middle + }; + var title = $"{info.Tipo ?? "Fondo"} — {info.Strumento} — {info.Isin}"; + g.DrawString(title, PdfTheme.Bold, + new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 255, 255, 255))), + new RectangleF(0, y, PageW, h), fmt); + return y + h; + } + + private float DrawStrip(PdfGraphics g, FundInfo info, float y) + { + const float h = 36f; + const float w1 = 100f; + const float w2 = 225f; + const float w3 = 150f; + const float gap = 8f; + + DrawStripBox(g, "RATING / RANK", + info.Rank.HasValue ? info.Rank.Value.ToString("F2") : "—", + 0f, y, w1, h); + DrawStripBox(g, "PREZZO", + info.Prezzo.HasValue ? $"{info.Prezzo.Value:F2} {info.Valuta}" : "—", + w1 + gap, y, w2, h); + DrawStripBox(g, "AGGIORNATO AL", + info.DataPrezzo.HasValue ? info.DataPrezzo.Value.ToString("dd/MM/yyyy") : "—", + w1 + gap + w2 + gap, y, w3, h); + + return y + h; + } + + private void DrawStripBox(PdfGraphics g, string label, string value, + float x, float y, float w, float h) + { + var bgBrush = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 232, 238, 248))); + var borderPen = new PdfPen(new PdfColor(PdfTheme.AccentBlue), 0.5f); + g.DrawRectangle(bgBrush, new RectangleF(x, y, w, h)); + g.DrawRectangle(borderPen, new RectangleF(x, y, w, h)); + g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(x, y, 3f, h)); + var grayBrush = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 136, 136, 136))); + var leftFmt = new PdfStringFormat { Alignment = PdfTextAlignment.Left }; + g.DrawString(label, PdfTheme.Small, grayBrush, + new RectangleF(x + 6f, y + 2f, w - 8f, 10f), leftFmt); + g.DrawString(value, PdfTheme.Bold, PdfTheme.AccentBlueBrush, + new RectangleF(x + 6f, y + 13f, w - 8f, h - 15f), leftFmt); + } + + private void DrawAnagrafici(PdfGraphics g, FundInfo info, float x, float w, float y) + { + y = DrawColHeader(g, "Dati Anagrafici", x, w, y); + + var items = new (string Label, string Value)[] + { + ("Società", info.Societa ?? "—"), + ("Categoria MS", info.CategoriaMorningstar ?? "—"), + ("Tipo", info.Tipo ?? "—"), + ("Valuta", info.Valuta ?? "—"), + ("Hedged", string.IsNullOrEmpty(info.Hedged) ? "—" : info.Hedged), + ("Benchmark", info.Benchmark ?? "—"), + ("Spese correnti", info.SpeseCorrenti.HasValue ? $"{info.SpeseCorrenti.Value:F2}%" : "—"), + ("Catalogo", info.Catalogo ?? "—"), + ("Proventi", info.Proventi ?? "—"), + ("Data lancio", info.DataLancio.HasValue ? info.DataLancio.Value.ToString("dd/MM/yyyy") : "—"), + ("Patrimonio", info.Patrimonio.HasValue ? $"{info.Patrimonio.Value:N0} EUR" : "—"), + }; + + DrawKvList(g, items, x, w, y); + } + + private void DrawEsg(PdfGraphics g, FundInfo info, float x, float w, float y) + { + y = DrawColHeader(g, "ESG Score", x, w, y); + + var scores = new (string Label, decimal? Value)[] + { + ("Sustainability", info.Sustainability), + ("Environmental", info.Environmental), + ("Social", info.Social), + ("Governance", info.Governance), + }; + + var greenBg = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 232, 245, 233))); + var greenPen = new PdfPen(new PdfColor(Color.FromArgb(255, 200, 230, 201)), 0.5f); + var grayLbl = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 85, 85, 85))); + var greenVal = new PdfSolidBrush(new PdfColor(PdfTheme.PositiveGreen)); + + foreach (var (label, val) in scores) + { + const float cardH = 36f; + g.DrawRectangle(greenBg, new RectangleF(x, y, w, cardH)); + g.DrawRectangle(greenPen, new RectangleF(x, y, w, cardH)); + g.DrawString(label, PdfTheme.Small, grayLbl, + new RectangleF(x + 5f, y + 2f, w - 8f, 12f)); + var valText = (val.HasValue && val.Value != 0m) ? val.Value.ToString("F2") : "—"; + g.DrawString(valText, PdfTheme.Bold, greenVal, + new RectangleF(x + 5f, y + 14f, w - 8f, 20f)); + y += cardH + 3f; + } + } + + private void DrawPerfGrid(PdfGraphics g, FundInfo info, float x, float w, float y) + { + y = DrawGridHeader(g, "PERFORMANCE · VOLATILITÀ · REND/RISK", x, w, y); + + var periods = new (string Label, decimal? Perf, decimal? Vol, decimal? Rr)[] + { + ("3 Mesi", info.P3M, info.V3M, info.R3M), + ("6 Mesi", info.P6M, info.V6M, info.R6M), + ("Da inizio anno", info.PYD, info.VYD, info.RYD), + ("1 Anno", info.P1Y, info.V1Y, info.R1Y), + ("3 Anni", info.P3Y, info.V3Y, info.R3Y), + ("5 Anni", info.P5Y, info.V5Y, info.R5Y), + }; + + const float cellGap = 3f; + float cellW = (w - 2 * cellGap) / 3f; + const float cellH = 50f; + + for (int i = 0; i < 6; i++) + { + int col = i % 3; + int row = i / 3; + float cx = x + col * (cellW + cellGap); + float cy = y + row * (cellH + cellGap); + var p = periods[i]; + DrawPerfCell(g, p.Label, p.Perf, p.Vol, p.Rr, cx, cy, cellW, cellH); + } + } + + private void DrawPerfCell(PdfGraphics g, string period, + decimal? perf, decimal? vol, decimal? rr, + float x, float y, float w, float h) + { + var cellBg = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 248, 250, 255))); + var cellPen = new PdfPen(new PdfColor(Color.FromArgb(255, 221, 227, 240)), 0.5f); + g.DrawRectangle(cellBg, new RectangleF(x, y, w, h)); + g.DrawRectangle(cellPen, new RectangleF(x, y, w, h)); + + var center = new PdfStringFormat + { + Alignment = PdfTextAlignment.Center, + LineAlignment = PdfVerticalAlignment.Top + }; + + g.DrawString(period, PdfTheme.SmallBold, PdfTheme.AccentBlueBrush, + new RectangleF(x, y + 2f, w, 12f), center); + + if (perf.HasValue) + { + var perfBrush = perf.Value < 0 + ? new PdfSolidBrush(new PdfColor(PdfTheme.NegativeRed)) + : new PdfSolidBrush(new PdfColor(PdfTheme.PositiveGreen)); + g.DrawString($"{perf.Value:F2}%", PdfTheme.TableBold, perfBrush, + new RectangleF(x, y + 14f, w, 12f), center); + } + else + g.DrawString("—", PdfTheme.TableFont, + new PdfSolidBrush(new PdfColor(PdfTheme.TextSecondary)), + new RectangleF(x, y + 14f, w, 12f), center); + + var volText = vol.HasValue ? $"Vol: {vol.Value:F2}%" : "Vol: —"; + g.DrawString(volText, PdfTheme.Small, + new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 85, 85, 85))), + new RectangleF(x, y + 26f, w, 12f), center); + + g.DrawLine(new PdfPen(new PdfColor(Color.FromArgb(255, 238, 238, 238)), 0.5f), + new PointF(x + 4f, y + h - 15f), new PointF(x + w - 4f, y + h - 15f)); + + var rrText = rr.HasValue ? $"R/R: {rr.Value:F2}" : "R/R: —"; + g.DrawString(rrText, PdfTheme.Small, + new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 136, 136, 136))), + new RectangleF(x, y + h - 14f, w, 12f), center); + } + + private float DrawColHeader(PdfGraphics g, string title, float x, float w, float y) + { + g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(x, y, 3f, 14f)); + g.DrawString(title, PdfTheme.Bold, new PdfSolidBrush(new PdfColor(PdfTheme.AccentBlue)), + new RectangleF(x + 6f, y, w - 6f, 14f)); + g.DrawLine(new PdfPen(new PdfColor(PdfTheme.AccentBlue), 0.5f), + new PointF(x, y + 15f), new PointF(x + w, y + 15f)); + return y + 18f; + } + + private float DrawGridHeader(PdfGraphics g, string title, float x, float w, float y) + { + const float h = 16f; + g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(x, y, w, h)); + var fmt = new PdfStringFormat + { + Alignment = PdfTextAlignment.Left, + LineAlignment = PdfVerticalAlignment.Middle + }; + g.DrawString(title, PdfTheme.SmallBold, + new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 255, 255, 255))), + new RectangleF(x + 4f, y, w - 6f, h), fmt); + return y + h + 4f; + } + + private void DrawKvList(PdfGraphics g, (string Label, string Value)[] items, + float x, float w, float y) + { + float rh = PdfTheme.RowHeight; + float labelW = w * 0.52f; + float valueW = w * 0.48f; + var labelBrush = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 85, 85, 85))); + var valueBrush = new PdfSolidBrush(new PdfColor(Color.FromArgb(255, 33, 33, 33))); + + foreach (var (label, value) in items) + { + g.DrawString(label + ":", PdfTheme.TableBold, labelBrush, + new RectangleF(x, y, labelW, rh)); + g.DrawString(value, PdfTheme.TableFont, valueBrush, + new RectangleF(x + labelW, y, valueW, rh)); + y += rh; + } + } +}