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:
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user