using System; using System.Linq; using System.Text; using NUnit.Framework; using QuestPDF.Elements.Text; using QuestPDF.Examples.Engine; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using SkiaSharp; namespace QuestPDF.Examples { public class TextExamples { [Test] public void SimpleText() { RenderingTest .Create() .PageSize(500, 100) .ProduceImages() .ShowResults() .Render(container => { container .Padding(5) .MinimalBox() .Border(1) .Padding(10) .Text(Placeholders.Paragraph()); }); } [Test] public void SimpleTextBlock() { RenderingTest .Create() .PageSize(600, 300) .ProduceImages() .ShowResults() .Render(container => { container .Padding(5) .MinimalBox() .Border(1) .MaxWidth(300) .Padding(10) .Text(text => { text.DefaultTextStyle(TextStyle.Default.FontSize(20)); text.Span("This is a normal text, followed by an "); text.Span("underlined red text").FontColor(Colors.Red.Medium).Underline(); text.Span("."); }); }); } [Test] public void TextWeight() { RenderingTest .Create() .PageSize(500, 500) .ProduceImages() .ShowResults() .Render(container => { container .Padding(20) .MinimalBox() .Border(1) .Padding(20) .Text(text => { text.DefaultTextStyle(x => x.FontFamily(Fonts.Calibri).FontSize(20)); text.Line("Thin").Thin(); text.Line("ExtraLight").ExtraLight(); text.Line("Light").Light(); text.Line("NormalWeight").NormalWeight(); text.Line("Medium").Medium(); text.Line("SemiBold").SemiBold(); text.Line("Bold").Bold(); text.Line("ExtraBold").ExtraBold(); text.Line("Black").Black(); text.Line("ExtraBlack").ExtraBlack(); }); }); } [Test] public void LineHeight() { RenderingTest .Create() .PageSize(500, 700) .ProduceImages() .ShowResults() .Render(container => { container .Padding(20) .Column(column => { var lineHeights = new[] { 0.8f, 1f, 1.5f }; var paragraph = Placeholders.Paragraph(); foreach (var lineHeight in lineHeights) { column .Item() .Border(1) .Padding(10) .Text(paragraph) .FontSize(16) .LineHeight(lineHeight); } }); }); } [Test] public void LetterSpacing() { RenderingTest .Create() .PageSize(500, 700) .ProduceImages() .ShowResults() .Render(container => { container .Padding(20) .Column(column => { var letterSpacing = new[] { -0.1f, 0f, 0.2f }; var paragraph = Placeholders.Sentence().ToUpper(); foreach (var spacing in letterSpacing) { column .Item() .Border(1) .Padding(10) .Column(nestedColumn => { nestedColumn.Item() .Text(paragraph) .FontSize(16) .LetterSpacing(spacing); nestedColumn.Item() .Text($"Letter spacing of {spacing} em") .FontSize(10) .Italic() .FontColor(Colors.Blue.Medium); }); } }); }); } [Test] public void LetterSpacing_Arabic() { RenderingTest .Create() .PageSize(500, 700) .ProduceImages() .ShowResults() .Render(container => { container .Padding(50) .Column(column => { var letterSpacing = new[] { -0.1f, 0f, 0.2f }; var paragraph = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا"; foreach (var spacing in letterSpacing) { column .Item() .Border(1) .Padding(10) .Column(nestedColumn => { nestedColumn.Item() .Text(paragraph) .FontSize(16) .FontFamily(Fonts.Calibri) .LetterSpacing(spacing); nestedColumn.Item() .Text($"Letter spacing of {spacing} em") .FontSize(10) .Italic() .FontColor(Colors.Blue.Medium); }); } }); }); } [Test] public void LetterSpacing_Unicode() { RenderingTest .Create() .PageSize(500, 700) .ProduceImages() .ShowResults() .Render(container => { container .Padding(50) .Column(column => { var letterSpacing = new[] { 0f, 0.5f }; var paragraph = "Ţ̴̡̧̤̮̺̤̗͎̱̹͙͎͖͂̿̓́̉̊̀̍͜h̵̞̘͇̾̎̏̅į̵̹̖͔͉̰̎̉̄̐̏͑͂̅̃̃͘͝s̷͓͉̭̭̯̬̥̻̰̩̦̑̀̀͌́̒̍̒̌̇͛̀͛́̎ ̷̡̡̟͕̳̺̝̼͇͔̬̟̖͍̈́̽͜͝͝i̶͔͚̟̊̐͛́͛̄̌ṡ̸̡̤̪͙͍̥͙̟̼̝̰̥͈̿̓̄̿̓͠ ̶̢̦̙͍̯̖̱̰̯͕͔͎̯̝̎͑t̸͖̲̱̼̎͐̎̉̾̎̾̌̅̔̏͘ȩ̶̝̫̙͓̙̣̔̀̌̔̋̂̑̈́̏̀̈͘̕͜͝s̸̫̝̮̻̼͐̅̄̎̎̑͝ț̷̨̢̨̻͈̮̞̆͗̓͊̃̌͂̑̉̕̕͜͝͝"; foreach (var spacing in letterSpacing) { column.Item() .Text($"Letter spacing of {spacing} em") .FontSize(10) .Italic() .FontColor(Colors.Blue.Medium); column.Item() .PaddingVertical(50) .Text(paragraph) .FontSize(16) .FontFamily(Fonts.Calibri) .LetterSpacing(spacing); } }); }); } [Test] public void SuperscriptSubscript_Simple() { RenderingTest .Create() .PageSize(500, 500) .ProduceImages() .ShowResults() .Render(container => { container .Padding(20) .MinimalBox() .Border(1) .Padding(20) .Text(text => { text.DefaultTextStyle(x => x.FontSize(20)); text.ParagraphSpacing(10); var highlight = TextStyle.Default.BackgroundColor(Colors.Green.Lighten3); text.Span("E=mc").Style(highlight); text.Span("2").Superscript().Style(highlight); text.Span(" is the equation of mass–energy equivalence."); text.EmptyLine(); text.Span("H").Style(highlight); text.Span("2").Subscript().Style(highlight); text.Span("O").Style(highlight); text.Span(" is the chemical formula for water."); }); }); } [Test] public void SuperscriptSubscript_Effects() { RenderingTest .Create() .PageSize(800, 400) .ProduceImages() .ShowResults() .Render(container => { container .Padding(25) .DefaultTextStyle(x => x.FontSize(30)) .Column(column => { column.Spacing(25); column.Item().Text(text => { text.DefaultTextStyle(x => x.Underline()); text.Span("Underline of the superscript (E = mc"); text.Span("2").Superscript(); text.Span(") should be at the same height as for normal text."); }); column.Item().Text(text => { text.DefaultTextStyle(x => x.Underline()); text.Span("Underline of the subscript(H"); text.Span("2").Subscript(); text.Span("O) should be slightly lower than a normal text."); }); column.Item().Text(text => { text.DefaultTextStyle(x => x.Strikethrough()); text.Span("Strikethrough of both superscript (E=mc"); text.Span("2").Superscript(); text.Span(") and subscript(H"); text.Span("2").Subscript(); text.Span("O) should be visible in the middle of the text."); }); }); }); } [Test] public void ParagraphSpacing() { RenderingTest .Create() .PageSize(500, 500) .ProduceImages() .ShowResults() .Render(container => { container .Padding(5) .MinimalBox() .Border(1) .Padding(10) .Text(text => { text.Line(Placeholders.Paragraph()); }); }); } [Test] public void CustomElement() { RenderingTest .Create() .PageSize(500, 200) .ProduceImages() .ShowResults() .Render(container => { container .Padding(5) .MinimalBox() .Border(1) .Padding(10) .Text(text => { text.DefaultTextStyle(TextStyle.Default.FontSize(25)); text.Span("This is a random image aligned to the middle of the baseline: "); text.Element(TextInjectedElementAlignment.Middle) .Height(24) .Width(48) .Image(Placeholders.Image); text.Span("."); }); }); } [Test] public void TextElements() { RenderingTest .Create() .PageSize(PageSizes.A4) .ProducePdf() .ShowResults() .Render(container => { container .Padding(20) .Padding(10) .MinimalBox() .Border(1) .Padding(5) .Padding(10) .Text(text => { text.DefaultTextStyle(TextStyle.Default); text.AlignLeft(); text.ParagraphSpacing(10); 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? <").Underline(); }); }); } [Test] public void Textcolumn() { RenderingTest .Create() .PageSize(PageSizes.A4) .ProducePdf() .ShowResults() .Render(container => { container .Padding(20) .Padding(10) .MinimalBox() .Border(1) .Padding(5) .Padding(10) .Text(text => { text.DefaultTextStyle(TextStyle.Default); text.AlignLeft(); text.ParagraphSpacing(10); foreach (var i in Enumerable.Range(1, 100)) text.Line($"{i}: {Placeholders.Paragraph()}"); }); }); } [Test] public void SpaceIssue() { RenderingTest .Create() .PageSize(PageSizes.A4) .ProducePdf() .ShowResults() .Render(container => { container .Padding(20) .Padding(10) .MinimalBox() .Border(1) .Padding(5) .Padding(10) .Text(text => { text.DefaultTextStyle(x => x.Bold()); text.DefaultTextStyle(TextStyle.Default); text.AlignLeft(); text.ParagraphSpacing(10); text.Span(Placeholders.LoremIpsum()); text.EmptyLine(); text.Span("This text is a normal text, "); 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(); text.Span("The new text element also supports injecting custom content between words: "); text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image); text.Span("."); text.EmptyLine(); text.Span("This is page number "); text.CurrentPageNumber(); text.Span(" out of "); text.TotalPages(); text.EmptyLine(); text.Hyperlink("Please visit QuestPDF website", "https://www.questpdf.com"); text.EmptyLine(); text.Span(Placeholders.Paragraphs()); text.EmptyLine(); text.Span(Placeholders.Paragraphs()).Italic(); text.Line("This is target text that does not show up. " + Placeholders.Paragraph()); }); }); } [Test] public void HugeList() { RenderingTest .Create() .PageSize(PageSizes.A4) .ProducePdf() .ShowResults() .Render(container => { container .Padding(20) .Padding(10) .MinimalBox() .Border(1) .Padding(5) .Padding(10) .Text(text => { text.DefaultTextStyle(TextStyle.Default.FontSize(20).BackgroundColor(Colors.Red.Lighten4)); text.AlignLeft(); text.ParagraphSpacing(10); text.Span("This text is a normal text, "); 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(-4).Height(16).Width(32).Image(Placeholders.Image); text.Span("."); text.EmptyLine(); foreach (var i in Enumerable.Range(1, 100)) { text.Line($"{i}: {Placeholders.Paragraph()}"); text.Hyperlink("Please visit QuestPDF website. ", "https://www.questpdf.com"); text.Span("This is page number "); text.CurrentPageNumber(); text.Span(" out of "); text.TotalPages(); text.EmptyLine(); } }); }); } [Test] public void MeasureIssueWhenSpaceAtLineEnd() { // issue 135 RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A4); page.Content().Text("This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ").FontSize(11).BackgroundColor(Colors.Red.Lighten3); }); }); } [Test] public void EmptyText() { // issue 135 RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A4); page.Content().Text(" ").FontSize(11).BackgroundColor(Colors.Red.Lighten3); }); }); } [Test] public void Whitespaces() { // issue 135 RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A4); page.Content().Text(" x ").FontSize(11).BackgroundColor(Colors.Red.Lighten3); }); }); } [Test] public void DrawingNullTextShouldNotThrowException() { RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A4); page.Content().Column(column => { column.Item().Text((string) null); column.Item().Text(text => { text.Span(null); text.Line(null); text.Hyperlink(null, "http://www.questpdf.com"); text.TotalPages().Format(x => null); }); }); }); }); } [Test] public void BreakingLongWord() { RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A4); page.Content().Column(column => { column.Item().Text((string) null); column.Item().Text(text => { text.DefaultTextStyle(x => x.BackgroundColor(Colors.Red.Lighten3).FontSize(24)); text.Span(" " + Placeholders.LoremIpsum()); text.Span(" 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 ").WrapAnywhere(); }); }); }); }); } [Test] public void TextShaping_Unicode() { RenderingTest .Create() .PageSize(600, 100) .ProduceImages() .ShowResults() .Render(container => { container .Padding(35) .MinimalBox() .Background(Colors.Grey.Lighten2) .Text(text => { text.DefaultTextStyle(TextStyle.Default.FontSize(20)); text.Span("Complex Unicode structure: "); text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontFamily(Fonts.Calibri).FontColor(Colors.Red.Medium); text.Span("."); }); }); } [Test] public void TextShaping_Arabic() { RenderingTest .Create() .PageSize(250, 100) .ProduceImages() .ShowResults() .Render(container => { container .Padding(25) .MinimalBox() .Background(Colors.Grey.Lighten2) .Text("خوارزمية ترتيب") .FontFamily(Fonts.Calibri) .FontSize(30); }); } [Test] public void FontFallback() { RenderingTest .Create() .ProducePdf() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.DefaultTextStyle(x => x .Fallback(y => y.FontFamily("Segoe UI Emoji") .Fallback(y => y.FontFamily("Microsoft YaHei")))); page.Size(PageSizes.A4); page.Content().Text(t => { t.Line("This is normal text."); t.EmptyLine(); t.Line("Following line should use font fallback:"); t.Line("中文文本"); t.EmptyLine(); t.Line("The following line contains a mix of known and unknown characters."); t.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!"); t.EmptyLine(); t.Line("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌"); }); }); }); } [Test] public void WordWrappingStability() { // instruction: check if any characters repeat when performing the word-wrapping algorithm RenderingTest .Create() .PageSize(PageSizes.A4) .ProducePdf() .ShowResults() .Render(container => { var text = "Lorem ipsum dolor sit amet consectetuer"; container .Padding(20) .Column(column => { column.Spacing(10); foreach (var width in Enumerable.Range(25, 200)) { column .Item() .MaxWidth(width) .Background(Colors.Grey.Lighten3) .Text(text); } }); }); } [Test] public void AdvancedLanguagesSupport() { RenderingTest .Create() .PageSize(new PageSize(400, 400)) .ProduceImages() .ShowResults() .Render(container => { var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد."; container .Padding(20) .ContentFromRightToLeft() .Text(text) .FontFamily(Fonts.Calibri) .FontSize(22); }); } [Test] public void WordWrappingWhenRightToLeft() { RenderingTest .Create() .PageSize(new PageSize(1000, 500)) .ProducePdf() .ShowResults() .Render(container => { var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد."; container .Padding(25) .ContentFromRightToLeft() .Column(column => { column.Spacing(20); foreach (var size in new[] { 36, 34, 32, 30, 15 }) { column .Item() .ShowEntire() .MaxWidth(size * 25) .Background(Colors.Grey.Lighten3) .MinimalBox() .Background(Colors.Grey.Lighten2) .Text(text) .FontSize(20) .FontFamily("Segoe UI"); } }); }); } [Test] public void ForcingTextDirection() { RenderingTest .Create() .PageSize(new PageSize(1000, 500)) .ProduceImages() .ShowResults() .Render(container => { container .Padding(10) .DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri")) .Column(column => { column.Spacing(10); var word = "الجوريتم"; var definition = "algorithm in Arabic"; var text = $"{word} - {definition}"; // text direction is automatically detected using the first word column.Item().Text(text); // it is possible to force specific content direction column.Item().Text(text).DirectionFromLeftToRight(); column.Item().Text(text).DirectionFromRightToLeft(); // to combine text in various content directions, split it into segments column.Item().Text(text => { text.Span(word); text.Span(" - "); text.Span(definition); }); }); }); } [Test] public void InconsistentLineHeightWhenUsingNewLineTest() { RenderingTest .Create() .PageSize(PageSizes.A4) .ProduceImages() .ShowResults() .Render(container => { container .Padding(20) .Background(Colors.Grey.Lighten4) .Text(text => { text.DefaultTextStyle(x => x.FontSize(16)); text.Line(Placeholders.Paragraph()); text.Line(""); text.Line(Placeholders.Paragraph()); text.Line(Placeholders.Label()).FontSize(48); text.Line(Placeholders.Paragraph()); text.Line(""); text.Line(Placeholders.Paragraph()); }); }); } [Test] public void FontFallback_Nested() { RenderingTest .Create() .ProduceImages() .ShowResults() .RenderDocument(container => { container.Page(page => { page.Margin(50); page.PageColor(Colors.White); page.Size(PageSizes.A5.Landscape()); page.DefaultTextStyle(x => x .FontSize(24) .Bold() .FontFamily("Times New Roman")); page.Content().Text(text => { text.Line("Default times new roman 中文文本 text."); text.Line("Normal weight and green 中文文本 text.").NormalWeight().BackgroundColor(Colors.Green.Lighten2); text.Line("Strikethrough without underline 中文文本 text.").Strikethrough().Underline(false); text.Line("Lato italic 中文文本 text.").FontFamily("Lato").Italic(); }); }); }); } [Test] public void TextShouldInheritAlignment() { RenderingTest .Create() .ProduceImages() .ShowResults() .PageSize(PageSizes.A4) .Render(container => { var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; container.Padding(20).Width(400).Column(column => { column.Spacing(20); column.Item().Background(Colors.Grey.Lighten3).AlignLeft().Text(text); column.Item().Background(Colors.Grey.Lighten3).AlignCenter().Text(text); column.Item().Background(Colors.Grey.Lighten3).AlignRight().Text(text); }); }); } [Test] public void LongPageableText() { var longText = string.Join("\n", Enumerable.Range(0, 100).Select(_ => Placeholders.Paragraph())); RenderingTest .Create() .ProducePdf() .ShowResults() .PageSize(PageSizes.A4) .Render(container => { container.Padding(25).Background(Colors.Grey.Lighten3).AlignLeft().Text(longText); }); } [Test] public void TextAlignment() { RenderingTest .Create() .ProducePdf() .ShowResults() .PageSize(PageSizes.A4) .Render(container => { container.Padding(25).Column(column => { column.Spacing(25); foreach (var contentDirection in Enum.GetValues()) { column.Item().Text(contentDirection.ToString()).FontSize(20).Bold(); foreach (var horizontalAlignment in Enum.GetValues()) foreach (var textHorizontalAlignment in Enum.GetValues()) column.Item().Element(GenerateTextCase(contentDirection, horizontalAlignment, textHorizontalAlignment)); } }); Action GenerateTextCase(ContentDirection contentDirection, HorizontalAlignment horizontalAlignment, TextHorizontalAlignment textAlignment) { return container => { container .Element(element => { return contentDirection switch { ContentDirection.LeftToRight => element.ContentFromLeftToRight(), ContentDirection.RightToLeft => element.ContentFromRightToLeft(), _ => throw new Exception() }; }) .Element(element => { return horizontalAlignment switch { HorizontalAlignment.Left => element.AlignLeft(), HorizontalAlignment.Center => element.AlignCenter(), HorizontalAlignment.Right => element.AlignRight(), _ => throw new Exception() }; }) .Background(Colors.Grey.Lighten3) .Width(200) .Padding(10) .Text(text => { if (textAlignment == TextHorizontalAlignment.Left) text.AlignLeft(); else if (textAlignment == TextHorizontalAlignment.Center) text.AlignCenter(); else if (textAlignment == TextHorizontalAlignment.Right) text.AlignRight(); else if (textAlignment == TextHorizontalAlignment.Justify) text.Justify(); else if (textAlignment == TextHorizontalAlignment.Start) text.AlignStart(); else if (textAlignment == TextHorizontalAlignment.End) text.AlignEnd(); text.Span($"{horizontalAlignment.ToString()} - {textAlignment.ToString()}"); }); }; } }); } } }