From 8419ac0a784bfe0e47cfc1b5cefdfbc9c9e02739 Mon Sep 17 00:00:00 2001 From: SmartRootsSrl Date: Fri, 20 Mar 2026 12:23:13 +0100 Subject: [PATCH] feat: redesign page 1 - hybrid style, 3-section layout, sottostanti on page 1 Co-Authored-By: Claude Sonnet 4.6 --- .../AnagraficaSectionRenderer.cs | 496 ++++++++++++------ 1 file changed, 329 insertions(+), 167 deletions(-) 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); } }