var generator = new PdfGenerator();
var document = await generator.CreateDocument(doc =>
{
doc.Page(page =>
{
page.Size(PageSize.A4);
page.Margin(24);
page.Header(header => header.Text(t =>
{
t.Alignment(TextAlignment.Center);
t.Span("Quarterly Report").Bold().FontColor(PdfColor.FromRgb(70, 70, 70));
}));
page.Footer(footer => footer.Text(t =>
{
t.Alignment(TextAlignment.Center);
t.Span("Page ");
t.CurrentPageNumber();
t.Span(" of ");
t.TotalPages();
}));
page.Content(container =>
{
container.Column(column =>
{
column.Spacing(16);
column.Item()
.Padding(16).Border(1, PdfColor.FromHex("#DDDDDD")).CornerRadius(8)
.Background(PdfColor.FromRgb(248, 248, 248))
.Shadow(new PdfBoxShadow(PdfColor.FromArgb(40, 0, 0, 0), 0, 3, 6))
.Text(t =>
{
t.Span("Built directly in C# - ").FontSize(14);
t.Span("no HTML or CSS strings involved.").FontSize(14).Italic();
});
column.Item().Table(table =>
{
table.Columns(columns =>
{
columns.RelativeColumn(2);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Padding(4).Text(t => t.Span("Region").Bold());
header.Cell().Padding(4).Text(t => t.Span("Q1").Bold());
header.Cell().Padding(4).Text(t => t.Span("Q2").Bold());
});
foreach (var (region, q1, q2) in new[] { ("North", "$42k", "$48k"), ("South", "$31k", "$35k"), ("West", "$27k", "$30k") })
{
table.Row(row =>
{
row.Cell().Padding(4).Text(region);
row.Cell().Padding(4).Text(q1);
row.Cell().Padding(4).Text(q2);
});
}
});
column.Item().LineHorizontal(1, PdfColor.FromHex("#BBBBBB"), dashed: true);
column.Item().UnorderedList(list =>
{
list.Item().Text("Revenue grew across every region.");
list.Item().Text("West opened its first retail location.");
}, PdfListMarkerType.Disc);
column.Item().ClampLines(2, "… [continued]").Text(
"A full year in review: every region grew its revenue year over year, led by a " +
"particularly strong second half, and the West region opened its first physical " +
"retail location - a milestone the team has been working toward since the start " +
"of the fiscal year.");
column.Item().Text(t =>
{
// PdfTextDirection.Auto detects each span's own base direction from its text (the
// same first-strong-character detection dir="auto" uses on the HTML side) - no
// need to know ahead of time which language a given run of content is in. This
// English span resolves to ltr; a Hebrew or Arabic span (given a font covering
// those glyphs) would resolve to rtl the same way.
t.Span("Auto-detected direction, per span: ").Direction(PdfTextDirection.Auto);
t.Span("this paragraph resolves left-to-right.").Direction(PdfTextDirection.Auto);
});
// Standalone SVG: author-supplied markup placed directly, and a real charting
// library's own SVG export - both go through the same Svg(string) overload.
column.Item().Row(row =>
{
row.Spacing(12);
row.Item().Grow().Svg(
"""
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="180" viewBox="0 0 100 60">
<circle cx="50" cy="30" r="26" fill="#FFDEE9" stroke="#B5FFFC" stroke-width="3"/>
<text x="50" y="34" font-size="10" text-anchor="middle" fill="#444">Hand-authored</text>
</svg>
""");
var scatterPlot = new Plot();
scatterPlot.Add.Scatter(
new double[] { 1, 2, 3, 4, 5, 6 },
new double[] { 12, 18, 15, 24, 21, 30 });
scatterPlot.Title("Weekly signups");
row.Item().Grow().Svg(scatterPlot.GetSvgXml(300, 180));
});
// A dynamic raster chart, generated at exactly the container's own resolved pixel
// size once layout knows it - the reason Image(Func<PdfSize,byte[]>) exists at all.
column.Item().Width(300).Height(160).Image(size =>
{
var barPlot = new Plot();
barPlot.Add.Bars(new double[] { 42, 31, 27 });
barPlot.Title("Q1 revenue by region ($k)");
// 2x the container's own point size for a crisp raster at normal PDF viewer zoom
// (a PDF point isn't a device pixel) - the fill-by-default width:100%/height:100%
// still scales this to the container's actual size regardless of pixel count.
return barPlot.GetImageBytes((int)size.Width * 2, (int)size.Height * 2, ImageFormat.Png);
});
});
});
});
});
var stream = new MemoryStream();
document.Save(stream);