feat: redesign page 1 - hybrid style, 3-section layout, sottostanti on page 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 12:23:13 +01:00
parent 4a8cd0351c
commit 8419ac0a78

View File

@@ -9,159 +9,334 @@ using Syncfusion.Pdf.Grid;
namespace CertReports.Syncfusion.Services.Implementations; namespace CertReports.Syncfusion.Services.Implementations;
/// <summary> /// <summary>
/// Sezione 1: Dati anagrafici del certificato + sottostanti. /// Sezione 1 — Prima pagina del report: Anagrafica + Analisi + Sottostanti.
/// Dati da: rpt_Master_CFT_ISIN + rpt_Details_UL_ISIN /// Struttura identica al vecchio report DevExpress, stile "Ibrido elegante".
/// </summary> /// </summary>
public class AnagraficaSectionRenderer : IPdfSectionRenderer public class AnagraficaSectionRenderer : IPdfSectionRenderer
{ {
public string SectionName => "Anagrafica"; public string SectionName => "Anagrafica";
public int Order => 1; 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) public PdfDocument Render(CertificateReportData data)
{ {
var doc = new PdfDocument(); var doc = new PdfDocument();
var page = PdfTheme.AddA4Page(doc); var page = PdfTheme.AddA4Page(doc);
var g = page.Graphics; var g = page.Graphics;
float y = 0;
float w = page.GetClientSize().Width;
var info = data.Info; var info = data.Info;
float y = 0f;
// ── Titolo ───────────────────────────────────────────────────── // ── TITOLO ────────────────────────────────────────────────────
g.DrawString($"Scheda Prodotto {info.Isin}", PdfTheme.Title, PdfTheme.TextBrush, y = DrawTitle(g, info, PageW, y);
new RectangleF(0, y, w, 25));
y += 28;
if (!string.IsNullOrEmpty(info.Categoria)) // ── SEZIONE A: CARATTERISTICHE PRODOTTO ───────────────────────
{ y = DrawSectionLabel(g, "Caratteristiche Prodotto", y);
g.DrawString($"Tipologia: {info.Categoria}", PdfTheme.SectionTitleFont, y = DrawCaratteristiche(g, info, y);
new PdfSolidBrush(PdfTheme.SectionTitle), new RectangleF(0, y, w, 18)); y += SectionGap;
y += 22;
}
// ── Due colonne: Caratteristiche (sx) + Info emittente (dx) ──── // ── SEZIONE B: ANALISI ────────────────────────────────────────
float colW = (w - 20) / 2; y = DrawSectionLabel(g, "Analisi", y);
y = DrawAnalisi(g, info, y);
y += SectionGap;
// Colonna sinistra // ── SEZIONE C: SOTTOSTANTI ────────────────────────────────────
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 ────────────────────────────────────────
if (info.Sottostanti.Count > 0) if (info.Sottostanti.Count > 0)
{ {
// Se non c'è spazio nella pagina corrente, aggiungi nuova pagina // Se lo spazio rimanente è meno di 80pt, nuova pagina
if (y > page.GetClientSize().Height - 150) if (y > PageH - 80f)
{ {
PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding);
page = doc.Pages.Add(); page = doc.Pages.Add();
g = page.Graphics; 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; return doc;
} }
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// Sottostanti // TITOLO con Bid/Ask a destra
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
private float DrawSottostantiTable(PdfGraphics g, List<Sottostante> 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<Sottostante> sottostanti, float w, float y)
{
var grid = new PdfGrid(); 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", // 9 colonne
"Barr.CPN", "Buffer CPN", "Trigger AC", "Dist.AC" }; string[] headers = { "Nome", "Strike", "Last", "% Perf.", "Barr.K",
"Buffer K", "Trig.CPN", "Buf.CPN", "Trig.AC" };
foreach (var _ in headers) grid.Columns.Add(); foreach (var _ in headers) grid.Columns.Add();
@@ -170,18 +345,19 @@ public class AnagraficaSectionRenderer : IPdfSectionRenderer
for (int i = 0; i < headers.Length; i++) for (int i = 0; i < headers.Length; i++)
{ {
hr.Cells[i].Value = headers[i]; hr.Cells[i].Value = headers[i];
hr.Cells[i].Style.Font = PdfTheme.Header; hr.Cells[i].Style.Font = PdfTheme.TableBold;
hr.Cells[i].Style.BackgroundBrush = PdfTheme.HeaderBrush; hr.Cells[i].Style.BackgroundBrush = PdfTheme.TableHeaderBrush;
hr.Cells[i].Style.TextBrush = PdfTheme.HeaderTextBrush as PdfBrush; hr.Cells[i].Style.TextBrush = PdfTheme.HeaderTextBrush;
hr.Cells[i].StringFormat = new PdfStringFormat(PdfTextAlignment.Center); 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++) for (int i = 0; i < sottostanti.Count; i++)
{ {
var s = sottostanti[i]; var s = sottostanti[i];
var row = grid.Rows.Add(); var row = grid.Rows.Add();
row.Cells[0].Value = s.Nome; row.Cells[0].Value = s.Nome;
row.Cells[1].Value = s.Strike; row.Cells[1].Value = s.Strike;
row.Cells[2].Value = s.LastPrice; row.Cells[2].Value = s.LastPrice;
@@ -191,66 +367,52 @@ public class AnagraficaSectionRenderer : IPdfSectionRenderer
row.Cells[6].Value = s.CouponBarrier; row.Cells[6].Value = s.CouponBarrier;
row.Cells[7].Value = s.ULCouponBarrierBuffer; row.Cells[7].Value = s.ULCouponBarrierBuffer;
row.Cells[8].Value = s.TriggerAutocall; row.Cells[8].Value = s.TriggerAutocall;
row.Cells[9].Value = s.ULTriggerAutocallDistance;
foreach (var cell in row.Cells.OfType<PdfGridCell>()) for (int c = 0; c < headers.Length; c++)
{ {
cell.Style.Font = PdfTheme.Small; row.Cells[c].Style.Font = PdfTheme.TableFont;
cell.StringFormat = new PdfStringFormat(PdfTextAlignment.Right); 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) if (i % 2 == 1)
foreach (var cell in row.Cells.OfType<PdfGridCell>()) for (int c = 0; c < headers.Length; c++)
cell.Style.BackgroundBrush = PdfTheme.AlternateRowBrush; row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush;
} }
// Larghezze // Larghezze proporzionali (totale = PageW)
float[] cw = { 80, 52, 52, 48, 52, 48, 52, 48, 52, 48 }; float[] cw = { 90f, 52f, 52f, 46f, 52f, 46f, 52f, 46f, 46f };
float total = cw.Sum(); float total = cw.Sum();
float scale = width / total; float scale = w / total;
for (int i = 0; i < cw.Length; i++) for (int i = 0; i < cw.Length; i++)
grid.Columns[i].Width = cw[i] * scale; grid.Columns[i].Width = cw[i] * scale;
PdfTheme.ApplyThinBorders(grid); PdfTheme.ApplyThinBorders(grid);
grid.Draw(g, new PointF(0, y)); 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)); if (string.IsNullOrWhiteSpace(value)) return false;
g.DrawString(title, PdfTheme.Header, PdfTheme.HeaderTextBrush, var trimmed = value.TrimStart();
new RectangleF(x + PdfTheme.CellPadding, y + 3, width, PdfTheme.HeaderRowHeight)); return trimmed.StartsWith('-');
return y + PdfTheme.HeaderRowHeight + 2;
} }
private float DrawKV(PdfGraphics g, float x, float width, float y, Dictionary<string, string> data) private static void ColorPerformanceCell(PdfGridCell cell, string? value)
{ {
float labelW = width * 0.55f; if (IsNegativeValue(value))
float valueW = width * 0.45f; cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.NegativeRed);
else if (!string.IsNullOrWhiteSpace(value) && value != "-" && value != "—")
foreach (var kvp in data) cell.Style.TextBrush = new PdfSolidBrush(PdfTheme.PositiveGreen);
{
// 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;
} }
} }