diff --git a/CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs b/CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs
index bb69c58..7749702 100644
--- a/CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs
+++ b/CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs
@@ -9,159 +9,334 @@ using Syncfusion.Pdf.Grid;
namespace CertReports.Syncfusion.Services.Implementations;
///
-/// Sezione 1: Dati anagrafici del certificato + sottostanti.
-/// Dati da: rpt_Master_CFT_ISIN + rpt_Details_UL_ISIN
+/// Sezione 1 — Prima pagina del report: Anagrafica + Analisi + Sottostanti.
+/// Struttura identica al vecchio report DevExpress, stile "Ibrido elegante".
///
public class AnagraficaSectionRenderer : IPdfSectionRenderer
{
public string SectionName => "Anagrafica";
public int Order => 1;
+ // Larghezza area utile (A4 595pt - 2*36 margini)
+ private const float PageW = 595f - 2 * PdfTheme.PageMargin;
+ // Altezza area utile (A4 842pt - 2*36 margini - footer)
+ private const float PageH = 842f - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight;
+ private const float ColGap = 10f;
+ private const float ColW = (PageW - ColGap) / 2f;
+ private const float SectionGap = 10f;
+
public PdfDocument Render(CertificateReportData data)
{
var doc = new PdfDocument();
var page = PdfTheme.AddA4Page(doc);
var g = page.Graphics;
- float y = 0;
- float w = page.GetClientSize().Width;
var info = data.Info;
+ float y = 0f;
- // ── Titolo ─────────────────────────────────────────────────────
- g.DrawString($"Scheda Prodotto {info.Isin}", PdfTheme.Title, PdfTheme.TextBrush,
- new RectangleF(0, y, w, 25));
- y += 28;
+ // ── TITOLO ────────────────────────────────────────────────────
+ y = DrawTitle(g, info, PageW, y);
- if (!string.IsNullOrEmpty(info.Categoria))
- {
- g.DrawString($"Tipologia: {info.Categoria}", PdfTheme.SectionTitleFont,
- new PdfSolidBrush(PdfTheme.SectionTitle), new RectangleF(0, y, w, 18));
- y += 22;
- }
+ // ── SEZIONE A: CARATTERISTICHE PRODOTTO ───────────────────────
+ y = DrawSectionLabel(g, "Caratteristiche Prodotto", y);
+ y = DrawCaratteristiche(g, info, y);
+ y += SectionGap;
- // ── Due colonne: Caratteristiche (sx) + Info emittente (dx) ────
- float colW = (w - 20) / 2;
+ // ── SEZIONE B: ANALISI ────────────────────────────────────────
+ y = DrawSectionLabel(g, "Analisi", y);
+ y = DrawAnalisi(g, info, y);
+ y += SectionGap;
- // Colonna sinistra
- float ly = DrawSectionHeader(g, "Caratteristiche Prodotto", 0, colW, y);
- ly = DrawKV(g, 0, colW, ly, new()
- {
- ["Cedola Annua"] = info.NominalAnnualYield,
- ["Valore Nominale"] = info.NominalValue?.ToString("F0") ?? "-",
- ["Prezzo Emissione"] = info.PrezzoEmissione?.ToString("F0") ?? "-",
- ["Memoria"] = info.Memory,
- ["Frequenza Cedole"] = info.FrequenzaCedole,
- ["Tipo Barriera"] = info.BarrierType,
- ["Tipo Basket"] = info.BasketType,
- ["Livello Barriera"] = info.LivelloBarriera,
- ["Direzione"] = info.Direzione,
- ["Airbag"] = info.Airbag,
- ["Leva"] = info.Leva,
- });
-
- // Colonna destra
- float rx = colW + 20;
- float ry = DrawSectionHeader(g, "Informazioni", rx, colW, y);
- ry = DrawKV(g, rx, colW, ry, new()
- {
- ["Emittente"] = info.Emittente,
- ["ISIN"] = info.Isin,
- ["Mercato"] = info.Mercato,
- ["Valuta"] = info.Valuta,
- ["Data Emissione"] = info.DataEmissione,
- ["Data Scadenza"] = info.Scadenza,
- ["Prossimo Autocall"] = info.NextAutocallDate,
- ["Data Rimborso"] = info.DataRimborso,
- ["Nome"] = info.Nome,
- });
-
- y = Math.Max(ly, ry) + 12;
-
- // ── Prezzi ─────────────────────────────────────────────────────
- ly = DrawSectionHeader(g, "Prezzi", 0, colW, y);
- ly = DrawKV(g, 0, colW, ly, new()
- {
- ["Bid"] = info.Bid,
- ["Ask"] = info.Ask,
- ["Data/Ora Prezzo"] = info.LastPriceDate,
- ["Valore Intrinseco"] = info.IntrinsicValue,
- ["Premio"] = info.Premium,
- ["RTS"] = info.RTS,
- ["VaR 95%"] = info.Var95,
- });
-
- // ── Rendimenti ─────────────────────────────────────────────────
- ry = DrawSectionHeader(g, "Rendimenti e Protezioni", rx, colW, y);
- ry = DrawKV(g, rx, colW, ry, new()
- {
- ["Rend. Capitale a Scadenza"] = info.CapitalReturnAtMaturity,
- ["Rend. Annuo Capitale Scad."] = info.CapitalAnnualReturnAtMaturity,
- ["Rend. Autocall"] = info.AutocallReturn,
- ["Rend. Annuo Autocall"] = info.AutocallAnnualReturn,
- ["Distanza Autocall"] = info.TriggerAutocallDistance,
- ["IRR"] = info.IRR,
- ["Rendimento Totale"] = info.RendimentoTotale,
- ["Rendimento Attuale"] = info.RendimentoAttuale,
- ["Protezione Capitale"] = info.BufferKProt,
- ["Protezione Coupon"] = info.BufferCPNProt,
- });
-
- y = Math.Max(ly, ry) + 12;
-
- // ── Cedole e Valori ────────────────────────────────────────────
- ly = DrawSectionHeader(g, "Cedole", 0, colW, y);
- ly = DrawKV(g, 0, colW, ly, new()
- {
- ["Cedola Annua"] = info.NominalAnnualYield,
- ["Coupon Yield"] = info.CouponYield,
- ["Potential Coupon Yield"] = info.PotentialCouponYield,
- ["Minimum Yield"] = info.MinimumYield,
- ["Cedole Pagate"] = info.CpnPagati?.ToString("F2") ?? "-",
- ["Cedole da Pagare"] = info.CpnDaPagare?.ToString("F2") ?? "-",
- ["Cedole in Memoria"] = info.CpnInMemoria?.ToString("F2") ?? "-",
- });
-
- ry = DrawSectionHeader(g, "Valori", rx, colW, y);
- ry = DrawKV(g, rx, colW, ry, new()
- {
- ["Valore Capitale"] = info.CapitalValue,
- ["Valore Autocall"] = info.AutocallValue,
- ["Valore Rimborso"] = info.ValoreRimborso,
- ["Fattore Airbag"] = info.FattoreAirbag,
- ["Trigger OneStar"] = info.TriggerOneStar,
- });
-
- y = Math.Max(ly, ry) + 15;
-
- // ── Tabella Sottostanti ────────────────────────────────────────
+ // ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
if (info.Sottostanti.Count > 0)
{
- // Se non c'è spazio nella pagina corrente, aggiungi nuova pagina
- if (y > page.GetClientSize().Height - 150)
+ // Se lo spazio rimanente è meno di 80pt, nuova pagina
+ if (y > PageH - 80f)
{
+ PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
page = doc.Pages.Add();
g = page.Graphics;
- y = 0;
+ y = 0f;
}
- y = DrawSottostantiTable(g, info.Sottostanti, w, y);
+ y = DrawSectionLabel(g, "Sottostanti", y);
+ DrawSottostanti(g, info.Sottostanti, PageW, y);
}
+ PdfTheme.DrawFooter(g, PageW, PageH, doc.Pages.Count, data.ShowBranding);
+
return doc;
}
- // ═══════════════════════════════════════════════════════════════════
- // Sottostanti
- // ═══════════════════════════════════════════════════════════════════
+ // ═══════════════════════════════════════════════════════════════
+ // TITOLO con Bid/Ask a destra
+ // ═══════════════════════════════════════════════════════════════
- private float DrawSottostantiTable(PdfGraphics g, List sottostanti, float width, float y)
+ private float DrawTitle(PdfGraphics g, CertificateInfo info, float w, float y)
{
- y = DrawSectionHeader(g, "Analisi Sottostanti", 0, width, y);
+ // ISIN + titolo a sinistra
+ g.DrawString($"Scheda Prodotto {info.Isin}",
+ PdfTheme.SectionTitleFont,
+ new PdfSolidBrush(PdfTheme.AccentBlue),
+ new RectangleF(0, y, w * 0.65f, 20f));
+ // Bid/Ask a destra
+ if (!string.IsNullOrEmpty(info.Bid) && !string.IsNullOrEmpty(info.Ask))
+ {
+ string bidAsk = $"{info.Bid} BID {info.Ask} ASK";
+ g.DrawString(info.LastPriceDate, PdfTheme.Small,
+ new PdfSolidBrush(PdfTheme.TextSecondary),
+ new RectangleF(w * 0.65f, y, w * 0.35f, 10f),
+ new PdfStringFormat(PdfTextAlignment.Right));
+ g.DrawString(bidAsk, PdfTheme.Bold,
+ new PdfSolidBrush(PdfTheme.AccentBlue),
+ new RectangleF(w * 0.65f, y + 10f, w * 0.35f, 12f),
+ new PdfStringFormat(PdfTextAlignment.Right));
+ }
+
+ y += 22f;
+
+ // Tipologia sotto il titolo
+ if (!string.IsNullOrEmpty(info.Categoria))
+ {
+ g.DrawString($"Tipologia: {info.Categoria}", PdfTheme.Regular,
+ new PdfSolidBrush(PdfTheme.TextSecondary),
+ new RectangleF(0, y, w, 12f));
+ y += 13f;
+ }
+
+ // Linea separatrice blu
+ g.DrawLine(PdfTheme.AccentBluePen, 0, y + 2f, w, y + 2f);
+ y += 8f;
+
+ return y;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // INTESTAZIONE DI SEZIONE (accent line laterale + testo blu)
+ // ═══════════════════════════════════════════════════════════════
+
+ private float DrawSectionLabel(PdfGraphics g, string title, float y)
+ {
+ // Accent line verticale sinistra
+ g.DrawRectangle(PdfTheme.AccentBlueBrush, new RectangleF(0, y, 3f, 14f));
+ g.DrawString(title, PdfTheme.Bold,
+ new PdfSolidBrush(PdfTheme.AccentBlue),
+ new RectangleF(6f, y, PageW, 14f));
+ return y + 16f;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // SEZIONE A: CARATTERISTICHE PRODOTTO
+ // Sinistra: tabella emittente | Destra: cedole + rendimento totale
+ // ═══════════════════════════════════════════════════════════════
+
+ private float DrawCaratteristiche(PdfGraphics g, CertificateInfo info, float y)
+ {
+ // ── Colonna sinistra: tabella emittente ──
+ float leftY = DrawEmittenteTable(g, info, 0, ColW, y);
+
+ // ── Colonna destra: cedole + rendimento totale ──
+ float rightX = ColW + ColGap;
+ float rightY = DrawCedoleTable(g, info, rightX, ColW, y);
+
+ return Math.Max(leftY, rightY);
+ }
+
+ private float DrawEmittenteTable(PdfGraphics g, CertificateInfo info, float x, float w, float y)
+ {
+ float rh = PdfTheme.RowHeight;
+ float pad = PdfTheme.CellPadding;
+
+ // Header blu
+ g.DrawRectangle(PdfTheme.TableHeaderBrush, new RectangleF(x, y, w, rh));
+ string headerText = string.IsNullOrEmpty(info.Emittente)
+ ? "EMITTENTE"
+ : $"EMITTENTE {info.Emittente.ToUpper()}";
+ g.DrawString(headerText, PdfTheme.TableBold,
+ PdfTheme.HeaderTextBrush,
+ new RectangleF(x + pad, y + 3f, w - pad * 2, rh));
+ y += rh;
+
+ // Righe dati
+ var rows = new[]
+ {
+ ("ISIN", info.Isin),
+ ("Mercato", info.Mercato),
+ ("Valuta", info.Valuta),
+ ("Data Emissione", info.DataEmissione),
+ ("Data Scadenza", info.Scadenza),
+ ("Prossimo Autocall", info.NextAutocallDate),
+ };
+
+ for (int i = 0; i < rows.Length; i++)
+ {
+ var (label, value) = rows[i];
+ if (string.IsNullOrWhiteSpace(value)) continue;
+
+ bool alt = i % 2 == 1;
+ if (alt)
+ g.DrawRectangle(new PdfSolidBrush(PdfTheme.TableAltRow),
+ new RectangleF(x, y, w, rh));
+
+ g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y);
+
+ g.DrawString(label, PdfTheme.TableFont,
+ new PdfSolidBrush(PdfTheme.TextSecondary),
+ new RectangleF(x + pad, y + 2f, w * 0.5f, rh));
+
+ var valueBrush = (label == "ISIN" || label == "Prossimo Autocall")
+ ? new PdfSolidBrush(PdfTheme.AccentBlue)
+ : new PdfSolidBrush(PdfTheme.TextPrimary);
+
+ g.DrawString(value, PdfTheme.TableBold, valueBrush,
+ new RectangleF(x + w * 0.5f, y + 2f, w * 0.5f - pad, rh),
+ new PdfStringFormat(PdfTextAlignment.Right));
+
+ y += rh;
+ }
+
+ // Bottom border line
+ g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y);
+
+ return y + 2f;
+ }
+
+ private float DrawCedoleTable(PdfGraphics g, CertificateInfo info, float x, float w, float y)
+ {
+ float rh = PdfTheme.RowHeight;
+ float pad = PdfTheme.CellPadding;
+
+ // Header blu
+ g.DrawRectangle(PdfTheme.TableHeaderBrush, new RectangleF(x, y, w, rh));
+ g.DrawString("CEDOLE E RENDIMENTO", PdfTheme.TableBold,
+ PdfTheme.HeaderTextBrush,
+ new RectangleF(x + pad, y + 3f, w - pad * 2, rh));
+ y += rh;
+
+ var rows = new (string Label, string Value, bool IsNegative)[]
+ {
+ ("Importo Cedole Pagate", info.CpnPagati?.ToString("N2") ?? "-", false),
+ ("Importo Cedole da Pagare", info.CpnDaPagare?.ToString("N2") ?? "-", false),
+ ("Importo Cedole in Memoria", info.CpnInMemoria?.ToString("N2") ?? "-", false),
+ ("Rendimento Totale", info.RendimentoAttuale, IsNegativeValue(info.RendimentoAttuale)),
+ };
+
+ for (int i = 0; i < rows.Length; i++)
+ {
+ var (label, value, isNeg) = rows[i];
+ bool alt = i % 2 == 1;
+ if (alt)
+ g.DrawRectangle(new PdfSolidBrush(PdfTheme.TableAltRow),
+ new RectangleF(x, y, w, rh));
+
+ g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y);
+
+ g.DrawString(label, PdfTheme.TableFont,
+ new PdfSolidBrush(PdfTheme.TextSecondary),
+ new RectangleF(x + pad, y + 2f, w * 0.65f, rh));
+
+ var valueBrush = isNeg
+ ? new PdfSolidBrush(PdfTheme.NegativeRed)
+ : new PdfSolidBrush(PdfTheme.TextPrimary);
+
+ var valueFont = label == "Rendimento Totale" ? PdfTheme.TableBold : PdfTheme.TableFont;
+
+ g.DrawString(value, valueFont, valueBrush,
+ new RectangleF(x + w * 0.65f, y + 2f, w * 0.35f - pad, rh),
+ new PdfStringFormat(PdfTextAlignment.Right));
+
+ y += rh;
+ }
+
+ g.DrawLine(PdfTheme.TableBorderPen, x, y, x + w, y);
+
+ return y + 2f;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // SEZIONE B: ANALISI
+ // Sinistra: caratteristiche prodotto | Destra: rendimenti
+ // ═══════════════════════════════════════════════════════════════
+
+ private float DrawAnalisi(PdfGraphics g, CertificateInfo info, float y)
+ {
+ var leftItems = new (string Label, string Value)[]
+ {
+ ("Importo Cedola (p.a.)", info.NominalAnnualYield),
+ ("Frequenza Cedola", info.FrequenzaCedole),
+ ("Valore Nominale", info.NominalValue?.ToString("N0") ?? "-"),
+ ("Prezzo Emissione", info.PrezzoEmissione?.ToString("N0") ?? "-"),
+ ("Barriera Capitale", info.LivelloBarriera),
+ ("Tipo Barriera", info.BarrierType),
+ ("Tipo Basket", info.BasketType),
+ ("Leva", string.IsNullOrWhiteSpace(info.Leva) ? "—" : info.Leva),
+ };
+
+ var rightItems = new (string Label, string Value)[]
+ {
+ ("Rend. Capitale a Scadenza", info.CapitalReturnAtMaturity),
+ ("IRR", info.IRR),
+ ("Protezione Capitale", info.BufferKProt),
+ ("Protezione Coupon", info.BufferCPNProt),
+ ("Valore Autocall", info.AutocallValue),
+ ("Distanza Autocall", info.TriggerAutocallDistance),
+ ("Rendimento Autocall", info.AutocallReturn),
+ ("Fattore Airbag", string.IsNullOrWhiteSpace(info.FattoreAirbag) ? "—" : info.FattoreAirbag),
+ ("Trigger OneStar", string.IsNullOrWhiteSpace(info.TriggerOneStar) ? "—" : info.TriggerOneStar),
+ };
+
+ float leftY = DrawKVList(g, leftItems, 0, ColW, y);
+ float rightY = DrawKVList(g, rightItems, ColW + ColGap, ColW, y);
+
+ return Math.Max(leftY, rightY);
+ }
+
+ private float DrawKVList(PdfGraphics g, (string Label, string Value)[] items, float x, float w, float y)
+ {
+ float rh = PdfTheme.RowHeight;
+ float pad = PdfTheme.CellPadding;
+ float labelW = w * 0.58f;
+ float valueW = w * 0.42f;
+
+ foreach (var (label, value) in items)
+ {
+ if (string.IsNullOrWhiteSpace(value)) continue;
+
+ g.DrawString(label, PdfTheme.TableFont,
+ new PdfSolidBrush(PdfTheme.TextSecondary),
+ new RectangleF(x + pad, y + 1f, labelW, rh));
+
+ bool isNeg = IsNegativeValue(value);
+ bool isKey = label is "Importo Cedola (p.a.)" or "Barriera Capitale";
+
+ var brush = isNeg ? new PdfSolidBrush(PdfTheme.NegativeRed)
+ : isKey ? new PdfSolidBrush(PdfTheme.AccentBlue)
+ : new PdfSolidBrush(PdfTheme.TextPrimary);
+
+ g.DrawString(value, PdfTheme.TableBold, brush,
+ new RectangleF(x + labelW, y + 1f, valueW - pad, rh),
+ new PdfStringFormat(PdfTextAlignment.Right));
+
+ g.DrawLine(new PdfPen(PdfTheme.SeparatorLine, 0.3f),
+ x, y + rh, x + w, y + rh);
+
+ y += rh;
+ }
+ return y;
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // SEZIONE C: SOTTOSTANTI (9 colonne, font 7.5pt)
+ // ═══════════════════════════════════════════════════════════════
+
+ private void DrawSottostanti(PdfGraphics g, List sottostanti, float w, float y)
+ {
var grid = new PdfGrid();
- grid.Style.CellPadding = new PdfPaddings(3, 3, 2, 2);
+ grid.Style.CellPadding = new PdfPaddings(2, 2, 2, 2);
- string[] headers = { "Nome", "Strike", "Last", "% Perf.", "Barr.K", "Buffer K",
- "Barr.CPN", "Buffer CPN", "Trigger AC", "Dist.AC" };
+ // 9 colonne
+ string[] headers = { "Nome", "Strike", "Last", "% Perf.", "Barr.K",
+ "Buffer K", "Trig.CPN", "Buf.CPN", "Trig.AC" };
foreach (var _ in headers) grid.Columns.Add();
@@ -170,18 +345,19 @@ public class AnagraficaSectionRenderer : IPdfSectionRenderer
for (int i = 0; i < headers.Length; i++)
{
hr.Cells[i].Value = headers[i];
- hr.Cells[i].Style.Font = PdfTheme.Header;
- hr.Cells[i].Style.BackgroundBrush = PdfTheme.HeaderBrush;
- hr.Cells[i].Style.TextBrush = PdfTheme.HeaderTextBrush as PdfBrush;
- hr.Cells[i].StringFormat = new PdfStringFormat(PdfTextAlignment.Center);
+ hr.Cells[i].Style.Font = PdfTheme.TableBold;
+ hr.Cells[i].Style.BackgroundBrush = PdfTheme.TableHeaderBrush;
+ hr.Cells[i].Style.TextBrush = PdfTheme.HeaderTextBrush;
+ hr.Cells[i].StringFormat = new PdfStringFormat(
+ i == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Center);
}
- hr.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Left);
- // Righe
+ // Righe dati
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;
@@ -191,66 +367,52 @@ public class AnagraficaSectionRenderer : IPdfSectionRenderer
row.Cells[6].Value = s.CouponBarrier;
row.Cells[7].Value = s.ULCouponBarrierBuffer;
row.Cells[8].Value = s.TriggerAutocall;
- row.Cells[9].Value = s.ULTriggerAutocallDistance;
- foreach (var cell in row.Cells.OfType())
+ for (int c = 0; c < headers.Length; c++)
{
- cell.Style.Font = PdfTheme.Small;
- cell.StringFormat = new PdfStringFormat(PdfTextAlignment.Right);
+ row.Cells[c].Style.Font = PdfTheme.TableFont;
+ row.Cells[c].StringFormat = new PdfStringFormat(
+ c == 0 ? PdfTextAlignment.Left : PdfTextAlignment.Right);
}
- row.Cells[0].StringFormat = new PdfStringFormat(PdfTextAlignment.Left);
+ // Colori performance e buffer negativi
+ ColorPerformanceCell(row.Cells[3], s.Performance);
+ ColorPerformanceCell(row.Cells[5], s.ULCapitalBarrierBuffer);
+ ColorPerformanceCell(row.Cells[7], s.ULCouponBarrierBuffer);
+
+ // Righe alternate
if (i % 2 == 1)
- foreach (var cell in row.Cells.OfType())
- cell.Style.BackgroundBrush = PdfTheme.AlternateRowBrush;
+ for (int c = 0; c < headers.Length; c++)
+ row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush;
}
- // Larghezze
- float[] cw = { 80, 52, 52, 48, 52, 48, 52, 48, 52, 48 };
+ // Larghezze proporzionali (totale = PageW)
+ float[] cw = { 90f, 52f, 52f, 46f, 52f, 46f, 52f, 46f, 46f };
float total = cw.Sum();
- float scale = width / total;
+ float scale = w / total;
for (int i = 0; i < cw.Length; i++)
grid.Columns[i].Width = cw[i] * scale;
PdfTheme.ApplyThinBorders(grid);
-
grid.Draw(g, new PointF(0, y));
- return y + (grid.Rows.Count + 1) * PdfTheme.RowHeight + 5;
}
- // ═══════════════════════════════════════════════════════════════════
- // Helper di disegno
- // ═══════════════════════════════════════════════════════════════════
+ // ═══════════════════════════════════════════════════════════════
+ // Helper
+ // ═══════════════════════════════════════════════════════════════
- private float DrawSectionHeader(PdfGraphics g, string title, float x, float width, float y)
+ private static bool IsNegativeValue(string? value)
{
- g.DrawRectangle(PdfTheme.HeaderBrush, new RectangleF(x, y, width, PdfTheme.HeaderRowHeight));
- g.DrawString(title, PdfTheme.Header, PdfTheme.HeaderTextBrush,
- new RectangleF(x + PdfTheme.CellPadding, y + 3, width, PdfTheme.HeaderRowHeight));
- return y + PdfTheme.HeaderRowHeight + 2;
+ if (string.IsNullOrWhiteSpace(value)) return false;
+ var trimmed = value.TrimStart();
+ return trimmed.StartsWith('-');
}
- private float DrawKV(PdfGraphics g, float x, float width, float y, Dictionary data)
+ private static void ColorPerformanceCell(PdfGridCell cell, string? value)
{
- float labelW = width * 0.55f;
- float valueW = width * 0.45f;
-
- foreach (var kvp in data)
- {
- // Salta campi vuoti per compattare il layout
- if (string.IsNullOrWhiteSpace(kvp.Value) || kvp.Value == "-") continue;
-
- g.DrawString(kvp.Key, PdfTheme.Small, PdfTheme.TextSecondaryBrush,
- new RectangleF(x + PdfTheme.CellPadding, y, labelW, PdfTheme.RowHeight));
- g.DrawString(kvp.Value, PdfTheme.SmallBold, PdfTheme.TextBrush,
- new RectangleF(x + labelW, y, valueW - PdfTheme.CellPadding, PdfTheme.RowHeight),
- new PdfStringFormat(PdfTextAlignment.Right));
-
- g.DrawLine(new PdfPen(PdfTheme.BorderColor, 0.3f),
- x, y + PdfTheme.RowHeight, x + width, y + PdfTheme.RowHeight);
-
- y += PdfTheme.RowHeight;
- }
- return y;
+ if (IsNegativeValue(value))
+ cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.NegativeRed);
+ else if (!string.IsNullOrWhiteSpace(value) && value != "-" && value != "—")
+ cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.PositiveGreen);
}
}