Compare commits

...

5 Commits

Author SHA1 Message Date
MarcinZiabek 1b9b164583 DefaultTextStyle API improvements 2022-03-12 16:11:03 +01:00
MarcinZiabek 2c14f91ebe Added support for background and foreground in page API 2022-03-11 18:04:30 +01:00
MarcinZiabek b533a32b61 Text API naming adjustments 2022-03-11 13:23:36 +01:00
MarcinZiabek 262bd3c182 Improved backwards compatibility 2022-03-09 22:13:51 +01:00
MarcinZiabek fcff989ab2 Improved text API 2022-03-08 17:27:30 +01:00
34 changed files with 590 additions and 336 deletions
+4 -2
View File
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
[Test]
public void Example()
{
FontManager.RegisterFontType(File.OpenRead("LibreBarcode39-Regular.ttf"));
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("Libre Barcode 39").Size(64));
.Text("*QuestPDF*")
.FontFamily("Libre Barcode 39")
.FontSize(64);
});
}
}
+4 -1
View File
@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
.Text("Sample text")
.FontSize(16)
.FontColor(Colors.Blue.Darken2)
.SemiBold();
});
});
}
+4 -1
View File
@@ -57,7 +57,10 @@ namespace QuestPDF.Examples
column
.Item()
.PaddingBottom(10)
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
.Text("Chart example")
.FontSize(20)
.SemiBold()
.FontColor(Colors.Blue.Medium);
column
.Item()
+1 -1
View File
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
{
foreach (var index in Enumerable.Range(1, 100))
column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
});
page.Footer().Text("Footer");
+1 -1
View File
@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.Sentence());
// this text has size 20 but also semibold and red
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
});
});
});
@@ -26,8 +26,8 @@ namespace QuestPDF.Examples
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Column(column =>
{
column.Item().Text("Default style applies to all children", TextStyle.Default);
column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().Text("Default style applies to all children");
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
column.Item().PaddingTop(10).Border(1).Grid(grid =>
{
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i, TextStyle.Default.Size(16 + i / 4));
.Text(i)
.FontSize(16 + i / 4);
}
});
});
+23 -11
View File
@@ -43,7 +43,9 @@ namespace QuestPDF.Examples
.Before()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
.Text("Notes")
.FontSize(16)
.FontColor("#FFF");
decoration
.Content()
@@ -280,12 +282,15 @@ namespace QuestPDF.Examples
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
.Text("Watermark")
.FontSize(48)
.Bold()
.FontColor(Colors.Green.Lighten3);
layers
.Layer()
.AlignBottom()
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
.Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
});
});
}
@@ -416,7 +421,9 @@ namespace QuestPDF.Examples
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
.Text(font)
.FontFamily(font)
.FontSize(16);
}
});
});
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
});
});
}
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
//.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
.Text("Test of the \n box element").FontSize(20);
});
}
@@ -503,7 +510,8 @@ namespace QuestPDF.Examples
decoration
.Before()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
.Text("Example: scale component")
.Style(headerFontStyle);
decoration
.Content()
@@ -525,7 +533,8 @@ namespace QuestPDF.Examples
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
.Text($"Content with {scale} scale.")
.Style(fontStyle);
}
});
});
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
.Text("Moved text")
.FontSize(25);
});
}
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
.Text($"Rotated {turns * 90}°")
.FontSize(16);
}
});
});
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
.Text($"Flipped {turns}")
.FontSize(16);
}
});
});
+1 -1
View File
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Header().Text("With ensure space").SemiBold();
page.Content().Column(column =>
{
+2 -2
View File
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
.Padding(5);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
}
public class FrameExample
+2 -1
View File
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.AlignCenter()
.AlignMiddle()
.Text(sizeText, TextStyle.Default.Size(15));
.Text(sizeText)
.FontSize(15);
});
}
});
+57 -6
View File
@@ -4,8 +4,6 @@ using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using ShimSkiaSharp;
using SKTypeface = SkiaSharp.SKTypeface;
namespace QuestPDF.Examples
{
@@ -19,26 +17,79 @@ namespace QuestPDF.Examples
{
container.Page(page =>
{
page.Size(PageSizes.A5);
page.Margin(1.5f, Unit.Centimetre);
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Header()
.Text("Hello PDF!", TextStyle.Default.SemiBold().Size(20));
.Text("Hello PDF!").SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(10);
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
Process.Start("explorer.exe", "hello.pdf");
}
[Test]
public void MinimalApi2()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Header()
.Text("Hello PDF!")
.SemiBold()
.FontSize(36)
.FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
});
}
}
}
+2 -1
View File
@@ -50,7 +50,8 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
.Text("Sample text")
.FontFamily("Segoe UI emoji");
});
}
@@ -0,0 +1,49 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class PageBackgroundForeground
{
[Test]
public void Test()
{
RenderingTest
.Create()
.PageSize(550, 400)
.ProducePdf()
.ShowResults()
.RenderDocument(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Inch);
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
page.Foreground()
.AlignMiddle()
.AlignCenter()
.Text("Watermark")
.FontSize(64)
.FontColor(Colors.Blue.Lighten3);
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24);
page.Content().PaddingVertical(25).Column(column =>
{
column.Spacing(25);
foreach (var i in Enumerable.Range(0, 100))
column.Item().Background(Colors.Grey.Lighten2).Height(75);
});
});
});
}
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With show once", TextStyle.Default.SemiBold());
page.Header().Text("With show once").SemiBold();
page.Content().PaddingVertical(5).Row(row =>
{
+2 -1
View File
@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
page.Content()
.PaddingVertical(10)
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
.Text(Placeholders.Paragraphs())
.FontColor(Colors.Grey.Medium);
page.Footer().Text(text =>
{
+7 -7
View File
@@ -141,8 +141,8 @@ namespace QuestPDF.Examples
.AlignBottom()
.Column(column =>
{
column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
});
}
@@ -156,8 +156,8 @@ namespace QuestPDF.Examples
{
column.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeItem().Text(chapter.Title, normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
row.RelativeItem().Text(chapter.Title).Style(normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
});
}
});
@@ -180,7 +180,7 @@ namespace QuestPDF.Examples
column.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
text.Span(content).Style(normalStyle);
});
column.Item().PageBreak();
@@ -198,7 +198,7 @@ namespace QuestPDF.Examples
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
@@ -206,7 +206,7 @@ namespace QuestPDF.Examples
void SectionTitle(ColumnDescriptor column, string text)
{
column.Item().Location(text).Text(text, subtitleStyle);
column.Item().Location(text).Text(text).Style(subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
+12 -10
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
text.Span("underlined red text.").FontSize(20).FontColor(Colors.Red.Medium).Underline();
});
});
}
@@ -57,7 +57,7 @@ namespace QuestPDF.Examples
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Span($"Paragraph {i}: ").SemiBold();
text.Line(Placeholders.Paragraph());
}
});
@@ -120,7 +120,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.LoremIpsum());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
});
});
}
@@ -173,6 +173,8 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(x => x.Bold());
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
@@ -182,9 +184,9 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.EmptyLine();
@@ -210,7 +212,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Span(Placeholders.Paragraphs()).Italic();
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
@@ -241,9 +243,9 @@ namespace QuestPDF.Examples
text.ParagraphSpacing(10);
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
@@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts
{
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
column.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
});
});
@@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
decoration
.Before()
.PaddingBottom(5)
.Text(Model.Title, Typography.Headline);
.Text(Model.Title)
.Style(Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
{
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.PhotoCount == 0)
{
container.Text("No photos", Typography.Normal);
container.Text("No photos").Style(Typography.Normal);
return;
}
@@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber(format: x => x?.FormatAsRomanNumeral() ?? "-----");
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
text.Span(" / ");
text.TotalPages(format: x => x?.FormatAsRomanNumeral() ?? "-----");
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
});
});
}
@@ -54,7 +54,7 @@ namespace QuestPDF.ReportSample.Layouts
{
row.Spacing(50);
row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
});
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span($"{field.Label}: ").SemiBold();
text.Span(field.Value);
});
}
@@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
decoration
.Before()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
.Text("Table of contents")
.Style(Typography.Headline);
decoration.Content().Column(column =>
{
@@ -53,9 +54,9 @@ namespace QuestPDF.ReportSample.Layouts
var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
text.Span(" (", lengthStyle);
text.TotalPagesWithinSection(locationName, lengthStyle, x => x == 1 ? "1 page long" : $"{x} pages long");
text.Span(")", lengthStyle);
text.Span(" (").Style(lengthStyle);
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
text.Span(")").Style(lengthStyle);
});
});
}
+1 -1
View File
@@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font");
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
},
DrawImageFunc = (image, position, size) =>
+5 -5
View File
@@ -30,7 +30,7 @@ namespace QuestPDF.Drawing
}
}
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFontType(Stream stream) overload.")]
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
public static void RegisterFontType(string fontName, Stream stream)
{
using var fontData = SKData.Create(stream);
@@ -38,7 +38,7 @@ namespace QuestPDF.Drawing
RegisterFontType(fontData, customName: fontName);
}
public static void RegisterFontType(Stream stream)
public static void RegisterFont(Stream stream)
{
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
@@ -78,16 +78,16 @@ namespace QuestPDF.Drawing
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
if (StyleSets.TryGetValue(style.FontType, out var fontStyleSet))
if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
return fontStyleSet.Match(fontStyle);
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontType, fontStyle);
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
if (fontFromDefaultSource != null)
return fontFromDefaultSource;
throw new ArgumentException(
$"The typeface '{style.FontType}' could not be found. " +
$"The typeface '{style.FontFamily}' could not be found. " +
$"Please consider the following options: " +
$"1) install the font on your operating system or execution environment. " +
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
+4 -1
View File
@@ -30,7 +30,10 @@ namespace QuestPDF.Elements
.MinimalBox()
.Background(Colors.White)
.Padding(2)
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
.Text(Text)
.FontColor(Color)
.FontFamily(Fonts.Consolas)
.FontSize(8);
});
}
}
+48 -30
View File
@@ -20,6 +20,9 @@ namespace QuestPDF.Elements
public string BackgroundColor { get; set; } = Colors.Transparent;
public Element Background { get; set; } = Empty.Instance;
public Element Foreground { get; set; } = Empty.Instance;
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
@@ -27,39 +30,54 @@ namespace QuestPDF.Elements
public void Compose(IContainer container)
{
container
.MinWidth(MinSize.Width)
.MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height)
.Background(BackgroundColor)
.PaddingLeft(MarginLeft)
.PaddingRight(MarginRight)
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
.Layers(layers =>
{
decoration
.Before()
.DebugPointer("Page header")
.Element(Header);
layers
.Layer()
.DebugPointer("Page background layer")
.Element(Background);
decoration
.Content()
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
.DebugPointer("Page content")
.Element(Content);
layers
.PrimaryLayer()
.MinWidth(MinSize.Width)
.MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height)
.Background(BackgroundColor)
.PaddingLeft(MarginLeft)
.PaddingRight(MarginRight)
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
{
decoration
.Before()
.DebugPointer("Page header")
.Element(Header);
decoration
.Content()
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
.DebugPointer("Page content")
.Element(Content);
decoration
.After()
.DebugPointer("Page footer")
.Element(Footer);
});
decoration
.After()
.DebugPointer("Page footer")
.Element(Footer);
layers
.Layer()
.DebugPointer("Page foreground layer")
.Element(Foreground);
});
bool IsClose(float x, float y)
+1 -1
View File
@@ -26,7 +26,7 @@ namespace QuestPDF.Elements
if (string.IsNullOrWhiteSpace(Text))
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
else
x.Text(Text, TextStyle.Default.Size(14));
x.Text(Text).FontSize(14);
});
}
}
+9 -1
View File
@@ -177,7 +177,15 @@ namespace QuestPDF.Fluent
TextStyle = textStyle
});
}
public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
{
return element.Element(new DefaultTextStyle
{
TextStyle = handler(TextStyle.Default)
});
}
public static IContainer StopPaging(this IContainer element)
{
return element.Element(new StopPaging());
+19
View File
@@ -83,11 +83,30 @@ namespace QuestPDF.Fluent
Page.DefaultTextStyle = textStyle;
}
public void DefaultTextStyle(Func<TextStyle, TextStyle> handler)
{
DefaultTextStyle(handler(TextStyle.Default));
}
public void Background(string color)
{
Page.BackgroundColor = color;
}
public IContainer Background()
{
var container = new Container();
Page.Background = container;
return container;
}
public IContainer Foreground()
{
var container = new Container();
Page.Foreground = container;
return container;
}
public IContainer Header()
{
var container = new Container();
+96 -44
View File
@@ -10,6 +10,34 @@ using static System.String;
namespace QuestPDF.Fluent
{
public class TextSpanDescriptor
{
internal TextStyle TextStyle { get; }
internal TextSpanDescriptor(TextStyle textStyle)
{
TextStyle = textStyle;
}
}
public delegate string PageNumberFormatter(int? pageNumber);
public class TextPageNumberDescriptor : TextSpanDescriptor
{
internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString();
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
{
}
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
{
FormatFunction = formatter ?? FormatFunction;
return this;
}
}
public class TextDescriptor
{
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
@@ -22,6 +50,11 @@ namespace QuestPDF.Fluent
DefaultStyle = style;
}
public void DefaultTextStyle(Func<TextStyle, TextStyle> style)
{
DefaultStyle = style(TextStyle.Default);
}
public void AlignLeft()
{
Alignment = HorizontalAlignment.Left;
@@ -50,12 +83,15 @@ namespace QuestPDF.Fluent
TextBlocks.Last().Items.Add(item);
}
public void Span(string? text, TextStyle? style = null)
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
public void Span(string? text, TextStyle style)
{
if (IsNullOrEmpty(text))
return;
style ??= TextStyle.Default;
Span(text).Style(style);
}
public TextSpanDescriptor Span(string? text)
{
var style = DefaultStyle.Clone();
var items = text
.Replace("\r", string.Empty)
@@ -77,78 +113,81 @@ namespace QuestPDF.Fluent
})
.ToList()
.ForEach(TextBlocks.Add);
return new TextSpanDescriptor(style);
}
public void Line(string? text, TextStyle? style = null)
public TextSpanDescriptor Line(string? text)
{
text ??= string.Empty;
Span(text + Environment.NewLine, style);
return Span(text + Environment.NewLine);
}
public void EmptyLine()
public TextSpanDescriptor EmptyLine()
{
Span(Environment.NewLine);
return Span(Environment.NewLine);
}
private static Func<int?, string> DefaultTextFormat = x => (x ?? 123).ToString();
private void PageNumber(Func<IPageContext, int?> pageNumber, TextStyle? style, Func<int?, string>? format)
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
{
style ??= TextStyle.Default;
format ??= DefaultTextFormat;
var style = DefaultStyle.Clone();
var descriptor = new TextPageNumberDescriptor(DefaultStyle);
AddItemToLastTextBlock(new TextBlockPageNumber()
AddItemToLastTextBlock(new TextBlockPageNumber
{
Source = context => format(pageNumber(context)),
Source = context => descriptor.FormatFunction(pageNumber(context)),
Style = style
});
return descriptor;
}
public void CurrentPageNumber(TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor CurrentPageNumber()
{
PageNumber(x => x.CurrentPage, style, format);
return PageNumber(x => x.CurrentPage);
}
public void TotalPages(TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor TotalPages()
{
PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length, style, format);
return PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")]
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
{
BeginPageNumberOfSection(locationName);
BeginPageNumberOfSection(locationName).Style(style);
}
public void BeginPageNumberOfSection(string locationName, TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor BeginPageNumberOfSection(string locationName)
{
PageNumber(x => x.GetLocation(locationName)?.PageStart, style, format);
return PageNumber(x => x.GetLocation(locationName)?.PageStart);
}
public void EndPageNumberOfSection(string locationName, TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor EndPageNumberOfSection(string locationName)
{
PageNumber(x => x.GetLocation(locationName)?.PageEnd, style, format);
return PageNumber(x => x.GetLocation(locationName)?.PageEnd);
}
public void PageNumberWithinSection(string locationName, TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor PageNumberWithinSection(string locationName)
{
PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd, style, format);
return PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd);
}
public void TotalPagesWithinSection(string locationName, TextStyle? style = null, Func<int?, string>? format = null)
public TextPageNumberDescriptor TotalPagesWithinSection(string locationName)
{
PageNumber(x => x.GetLocation(locationName)?.Length, style, format);
return PageNumber(x => x.GetLocation(locationName)?.Length);
}
public void SectionLink(string? text, string sectionName, TextStyle? style = null)
public TextSpanDescriptor SectionLink(string? text, string sectionName)
{
if (IsNullOrEmpty(text))
return;
if (IsNullOrEmpty(sectionName))
throw new ArgumentException(nameof(sectionName));
style ??= TextStyle.Default;
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text))
return descriptor;
AddItemToLastTextBlock(new TextBlockSectionlLink
{
@@ -156,23 +195,26 @@ namespace QuestPDF.Fluent
Text = text,
SectionName = sectionName
});
return descriptor;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
{
SectionLink(text, locationName, style);
SectionLink(text, locationName).Style(style);
}
public void Hyperlink(string? text, string url, TextStyle? style = null)
public TextSpanDescriptor Hyperlink(string? text, string url)
{
if (IsNullOrEmpty(text))
return;
if (IsNullOrEmpty(url))
throw new ArgumentException(nameof(url));
style ??= TextStyle.Default;
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text))
return descriptor;
AddItemToLastTextBlock(new TextBlockHyperlink
{
@@ -180,12 +222,14 @@ namespace QuestPDF.Fluent
Text = text,
Url = url
});
return descriptor;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
public void ExternalLocation(string? text, string url, TextStyle? style = null)
{
Hyperlink(text, url, style);
Hyperlink(text, url).Style(style);
}
public IContainer Element()
@@ -227,9 +271,17 @@ namespace QuestPDF.Fluent
descriptor.Compose(element);
}
public static void Text(this IContainer element, object? text, TextStyle? style = null)
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
public static void Text(this IContainer element, object? text, TextStyle style)
{
element.Text(x => x.Span(text?.ToString(), style));
element.Text(text).Style(style);
}
public static TextSpanDescriptor Text(this IContainer element, object? text)
{
var descriptor = (TextSpanDescriptor) null;
element.Text(x => descriptor = x.Span(text?.ToString()));
return descriptor;
}
}
}
@@ -0,0 +1,126 @@
using System;
using System.Runtime.CompilerServices;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class TextSpanDescriptorExtensions
{
public static T Style<T>(this T descriptor, TextStyle style) where T : TextSpanDescriptor
{
if (style == null)
return descriptor;
descriptor.TextStyle.OverrideStyle(style);
return descriptor;
}
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.Color = value;
return descriptor;
}
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.BackgroundColor = value;
return descriptor;
}
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontFamily = value;
return descriptor;
}
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.Size = value;
return descriptor;
}
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.LineHeight = value;
return descriptor;
}
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.IsItalic = value;
return descriptor;
}
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasStrikethrough = value;
return descriptor;
}
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasUnderline = value;
return descriptor;
}
#region Weight
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontWeight = weight;
return descriptor;
}
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Thin);
}
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraLight);
}
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Light);
}
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Normal);
}
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Medium);
}
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.SemiBold);
}
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Bold);
}
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBold);
}
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Black);
}
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBlack);
}
#endregion
}
}
+19 -1
View File
@@ -13,7 +13,13 @@ namespace QuestPDF.Fluent
return style;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
public static TextStyle Color(this TextStyle style, string value)
{
return style.FontColor(value);
}
public static TextStyle FontColor(this TextStyle style, string value)
{
return style.Mutate(x => x.Color = value);
}
@@ -23,12 +29,24 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.BackgroundColor = value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
public static TextStyle FontType(this TextStyle style, string value)
{
return style.Mutate(x => x.FontType = value);
return style.FontFamily(value);
}
public static TextStyle FontFamily(this TextStyle style, string value)
{
return style.Mutate(x => x.FontFamily = value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
public static TextStyle Size(this TextStyle style, float value)
{
return style.FontSize(value);
}
public static TextStyle FontSize(this TextStyle style, float value)
{
return style.Mutate(x => x.Size = value);
}
+18 -5
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Infrastructure
internal string? Color { get; set; }
internal string? BackgroundColor { get; set; }
internal string? FontType { get; set; }
internal string? FontFamily { get; set; }
internal float? Size { get; set; }
internal float? LineHeight { get; set; }
internal FontWeight? FontWeight { get; set; }
@@ -24,7 +24,7 @@ namespace QuestPDF.Infrastructure
{
Color = Colors.Black,
BackgroundColor = Colors.Transparent,
FontType = Fonts.Calibri,
FontFamily = Fonts.Calibri,
Size = 12,
LineHeight = 1.2f,
FontWeight = Infrastructure.FontWeight.Normal,
@@ -43,15 +43,15 @@ namespace QuestPDF.Infrastructure
HasGlobalStyleApplied = true;
ApplyParentStyle(globalStyle);
PaintKey ??= (FontType, Size, FontWeight, IsItalic, Color);
FontMetricsKey ??= (FontType, Size, FontWeight, IsItalic);
PaintKey ??= (FontFamily, Size, FontWeight, IsItalic, Color);
FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
}
internal void ApplyParentStyle(TextStyle parentStyle)
{
Color ??= parentStyle.Color;
BackgroundColor ??= parentStyle.BackgroundColor;
FontType ??= parentStyle.FontType;
FontFamily ??= parentStyle.FontFamily;
Size ??= parentStyle.Size;
LineHeight ??= parentStyle.LineHeight;
FontWeight ??= parentStyle.FontWeight;
@@ -60,6 +60,19 @@ namespace QuestPDF.Infrastructure
HasUnderline ??= parentStyle.HasUnderline;
}
internal void OverrideStyle(TextStyle parentStyle)
{
Color = parentStyle.Color ?? Color;
BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor;
FontFamily = parentStyle.FontFamily ?? FontFamily;
Size = parentStyle.Size ?? Size;
LineHeight = parentStyle.LineHeight ?? LineHeight;
FontWeight = parentStyle.FontWeight ?? FontWeight;
IsItalic = parentStyle.IsItalic ?? IsItalic;
HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
}
internal TextStyle Clone()
{
var clone = (TextStyle)MemberwiseClone();
+1 -1
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.2.3</Version>
<Version>2022.3.0</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
+53 -184
View File
@@ -8,215 +8,84 @@ I have designed this layouting engine with full paging support in mind. The docu
## Support QuestPDF
All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. Please give it a start ⭐ and share with your colleagues 💬👨‍💻.
All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice for generating PDF documents.
- ⭐ Give this repository a star,
- 💬 Share it with your team members.
## Installation
The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/).
```
```c#
// Package Manager
Install-Package QuestPDF
// .NET CLI
dotnet add package QuestPDF
// Package reference in .csproj file
<PackageReference Include="QuestPDF" Version="2022.2.5" />
```
## Documentation
**[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
**[🚀 Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
**[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
**[📖 API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
**[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
**[️ Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and create reusable code that is easy to maintain.
**[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain.
## Simplicity is the key
## Example invoice
Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices.
For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html).
<a href="https://github.com/QuestPDF/example-invoice">
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
</a>
Here you can find an example code showing how easy is to write and understand the fluent API.
**General document structure** with header, content and footer:
How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
```csharp
public void Compose(IDocumentContainer container)
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
// code in your main method
Document.Create(container =>
{
container
.Page(page =>
{
page.Margin(50);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(x =>
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Header()
.Text("Hello PDF!", TextStyle.Default.SemiBold().Size(36).Color(Colors.Blue.Medium));
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
});
}
```
**The header area** consists of basic invoice information along with a logo placeholder.
```csharp
void ComposeHeader(IContainer container)
{
var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium);
container.Row(row =>
{
row.RelativeColumn().Stack(stack =>
{
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle);
stack.Item().Text(text =>
{
text.Span("Issue date: ", TextStyle.Default.SemiBold());
text.Span($"{Model.IssueDate:d}");
});
stack.Item().Text(text =>
{
text.Span("Due date: ", TextStyle.Default.SemiBold());
text.Span($"{Model.DueDate:d}");
});
});
row.ConstantColumn(100).Height(50).Placeholder();
});
}
})
.GeneratePdf("hello.pdf");
```
Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
And compare it to the produced PDF file:
```csharp
void ComposeContent(IContainer container)
{
container.PaddingVertical(40).Stack(column =>
{
column.Spacing(20);
column.Item().Row(row =>
{
row.RelativeColumn().Component(new AddressComponent("From", Model.SellerAddress));
row.ConstantColumn(50);
row.RelativeColumn().Component(new AddressComponent("For", Model.CustomerAddress));
});
column.Item().Element(ComposeTable);
var totalPrice = Model.Items.Sum(x => x.Price * x.Quantity);
column
.Item()
.PaddingRight(5)
.AlignRight()
.Text($"Grand total: {totalPrice}$", TextStyle.Default.SemiBold());
## Are you ready for more?
if (!string.IsNullOrWhiteSpace(Model.Comments))
column.Item().PaddingTop(25).Element(ComposeComments);
});
}
```
The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice).
**The table and comments** codes are extracted into separate methods to increase clarity:
```csharp
void ComposeTable(IContainer container)
{
var headerStyle = TextStyle.Default.SemiBold();
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(25);
columns.RelativeColumn(3);
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Header(header =>
{
header.Cell().Text("#", headerStyle);
header.Cell().Text("Product", headerStyle);
header.Cell().AlignRight().Text("Unit price", headerStyle);
header.Cell().AlignRight().Text("Quantity", headerStyle);
header.Cell().AlignRight().Text("Total", headerStyle);
header.Cell().ColumnSpan(5)
.PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
});
foreach (var item in Model.Items)
{
table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1);
table.Cell().Element(CellStyle).Text(item.Name);
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$");
table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity);
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$");
static IContainer CellStyle(IContainer container)
{
container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
}
}
});
}
```
```csharp
void ComposeComments(IContainer container)
{
container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message =>
{
message.Spacing(5);
message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold());
message.Item().Text(Model.Comments);
});
}
```
**The address details section** is implemented using components. This way the code can be easily reused for both seller and customer:
```csharp
public class AddressComponent : IComponent
{
private string Title { get; }
private Address Address { get; }
public AddressComponent(string title, Address address)
{
Title = title;
Address = address;
}
public void Compose(IContainer container)
{
container.ShowEntire().Stack(column =>
{
column.Spacing(5);
column
.Item()
.BorderBottom(1)
.PaddingBottom(5)
.Text(Title, TextStyle.Default.SemiBold());
column.Item().Text(Address.CompanyName);
column.Item().Text(Address.Street);
column.Item().Text($"{Address.City}, {Address.State}");
column.Item().Text(Address.Email);
column.Item().Text(Address.Phone);
});
}
}
```
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">