Compare commits

..

26 Commits

Author SHA1 Message Date
Marcin Ziąbek 2772b5b4a4 Size component cleanup 2021-09-12 20:25:50 +02:00
Marcin Ziąbek 3dfe7a59e0 Fixed space removal 2021-09-12 20:15:41 +02:00
Marcin Ziąbek df5c5524ee Fixed text alignment 2021-09-12 20:10:52 +02:00
Marcin Ziąbek 5975b19445 Improved book layout in benchmark 2021-09-07 13:34:04 +02:00
Marcin Ziąbek fb7743e828 Improved text rendering (space corner cases) 2021-09-07 13:16:34 +02:00
Marcin Ziąbek 3bb46ecc42 Fixed text rendering (spacing corner case) 2021-08-29 19:18:58 +02:00
Marcin Ziąbek 3112ee10cf Better new line handling 2021-08-27 20:25:20 +02:00
Marcin Ziąbek 899c138842 Code structure improvements. Text rendering: support for words longer than available space 2021-08-27 19:51:28 +02:00
Marcin Ziąbek 5177dcfd2b Code structure improvements 2021-08-27 12:40:06 +02:00
Marcin Ziąbek 4e6415aee2 Example report fixes 2021-08-27 11:40:33 +02:00
Marcin Ziąbek d9b3f016e8 Text: rendering custom elements inside text 2021-08-27 01:55:20 +02:00
Marcin Ziąbek b9cfaf3846 Text component: internal and external links 2021-08-27 01:38:26 +02:00
Marcin Ziąbek e3e7a22af8 Fix: the Stack may not show last an element in certain scenarios 2021-08-27 01:38:10 +02:00
Marcin Ziąbek c938ea32b0 Code cleanup 2021-08-27 01:10:30 +02:00
Marcin Ziąbek 69419c8440 Text Rendering Optimization: Caching Measurement Results 2021-08-27 00:29:04 +02:00
Marcin Ziąbek 23e8aa3491 Text rendering optimization: style key, font metrics caching 2021-08-26 23:36:43 +02:00
Marcin Ziąbek fd5d51e764 Implemented text rendering benchmark 2021-08-26 22:25:33 +02:00
Marcin Ziąbek dbc479db07 Improved text rendering capabilities 2021-08-25 03:40:16 +02:00
Marcin Ziąbek edc595d199 Initial text rendering improvements 2021-08-09 22:35:39 +02:00
Marcin Ziąbek 7f94ae589a Updated release notes and code example 2021-08-03 01:43:48 +02:00
Marcin Ziąbek adfce6204e Improved testing environment by adding proper result files names, Added more code examples 2021-08-03 01:35:46 +02:00
Marcin Ziąbek 6d3c8d0708 Added elements: Rotate, Unconstrained 2021-08-01 21:09:16 +02:00
Marcin Ziąbek 3b8c300126 Merge pull request #17 from QuestPDF/2021.6
Merge 2021.6 for QuestPdf 2021.8
2021-08-01 16:58:44 +02:00
Marcin Ziąbek 20fe49dd01 Update SECURITY.md 2021-07-18 18:10:55 +02:00
Marcin Ziąbek 7cdc1fcee2 Create SECURITY.md 2021-07-18 18:10:20 +02:00
Marcin Ziąbek c05c5e1597 Update readme.md
Updated code, added the support section
2021-05-15 16:09:01 +02:00
66 changed files with 10492 additions and 480 deletions
+32
View File
@@ -0,0 +1,32 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
[TestFixture]
public class BarcodeExamples
{
[Test]
public void Barcode()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
});
});
}
}
}
+186 -71
View File
@@ -1,4 +1,3 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
@@ -18,6 +17,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -33,6 +33,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -55,49 +56,14 @@ namespace QuestPDF.Examples
});
});
}
// [Test]
// public void Page()
// {
// RenderingTest
// .Create()
// .PageSize(298, 421)
// .Render(container =>
// {
// container
// .Background("#FFF")
// .Padding(15)
// .Page(page =>
// {
// page.Header()
// .Height(60)
// .Background(Colors.Grey.Lighten1)
// .AlignCenter()
// .AlignMiddle()
// .Text("Header");
//
// page.Content()
// .Background(Colors.Grey.Lighten2)
// .AlignCenter()
// .AlignMiddle()
// .Text("Content");
//
// page.Footer()
// .Height(30)
// .Background(Colors.Grey.Lighten1)
// .AlignCenter()
// .AlignMiddle()
// .Text("Footer");
// });
// });
// }
[Test]
public void Row()
{
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
@@ -138,6 +104,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
@@ -159,6 +126,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(500, 360)
.FileName()
.Render(container =>
{
container
@@ -181,6 +149,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(210, 210)
.FileName()
.Render(container =>
{
container
@@ -203,6 +172,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
var text = "";
@@ -225,6 +195,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(400, 230)
.FileName()
.Render(container =>
{
var textStyle = TextStyle.Default.Size(14);
@@ -259,6 +230,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
@@ -288,6 +260,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(400, 250)
.FileName()
.Render(container =>
{
container
@@ -322,7 +295,7 @@ namespace QuestPDF.Examples
layers
.Layer()
.AlignBottom()
.PageNumber("Page {pdf:currentPage}", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
});
});
}
@@ -363,6 +336,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -407,6 +381,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(450, 150)
.FileName()
.Render(container =>
{
container
@@ -439,6 +414,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(500, 175)
.FileName()
.Render(container =>
{
container
@@ -465,6 +441,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
@@ -504,6 +481,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(300, 150)
.FileName()
.Render(container =>
{
container
@@ -523,21 +501,49 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(400, 400)
.PageSize(300, 175)
.FileName()
.Render(container =>
{
var style = TextStyle.Default.Size(20);
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
.Background(Colors.White)
.Padding(10)
.Decoration(decoration =>
{
stack.Spacing(5);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(0.5f).Text("Smaller text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Text("Normal text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(2f).Text("Bigger text", style);
stack.Item().Background(Placeholders.BackgroundColor()).Scale(-1.5f).Text("Flipped text", style);
var headerFontStyle = TextStyle
.Default
.Size(20)
.Color(Colors.Blue.Darken2)
.SemiBold();
decoration
.Header()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
decoration
.Content()
.Stack(stack =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
foreach (var scale in scales)
{
var fontColor = scale <= 1f
? Colors.Red.Lighten4
: Colors.Green.Lighten4;
var fontStyle = TextStyle.Default.Size(16);
stack
.Item()
.Border(1)
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
}
});
});
});
}
@@ -547,31 +553,39 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.PageSize(500, 300)
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Box()
.Background(Colors.Green.Lighten4)
.Padding(5)
.Padding(25)
.Background(Colors.Green.Lighten3)
.TranslateX(15)
.TranslateY(15)
.Text("Text outside of bounds");
.Border(2)
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
});
}
[Test]
public void Rotate()
public void ConstrainedRotate()
{
RenderingTest
.Create()
.PageSize(450, 450)
.PageSize(350, 350)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
@@ -581,11 +595,10 @@ namespace QuestPDF.Examples
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Background(Colors.Grey.Lighten3)
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten2)
.Padding(10)
//.Box()
.Element(element =>
{
foreach (var x in Enumerable.Range(0, turns))
@@ -594,23 +607,83 @@ namespace QuestPDF.Examples
return element;
})
.Box()
.Background(Colors.Grey.Lighten1)
.Text($"Rotated {turns * 90} degrees.", TextStyle.Default.Size(14));
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
}
});
});
}
[Test]
public void FreeRotate()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Background(Colors.Grey.Lighten2)
.AlignCenter()
.AlignMiddle()
.Background(Colors.White)
.Rotate(30)
.Width(100)
.Height(100)
.Background(Colors.Blue.Medium);
});
}
[Test]
public void FreeRotateCenter()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Background(Colors.Grey.Lighten2)
.AlignCenter()
.AlignMiddle()
.Background(Colors.White)
.TranslateX(50)
.TranslateY(50)
.Rotate(30)
.TranslateX(-50)
.TranslateY(-50)
.Width(100)
.Height(100)
.Background(Colors.Blue.Medium);
});
}
[Test]
public void Flip()
{
RenderingTest
.Create()
.PageSize(450, 450)
.PageSize(350, 350)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
@@ -620,21 +693,24 @@ namespace QuestPDF.Examples
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Element(element =>
{
if (turns == 1 || turns == 2)
element = element.FlipX();
element = element.FlipHorizontal();
if (turns == 2 || turns == 3)
element = element.FlipY();
element = element.FlipVertical();
return element;
})
.Text($"Flipped.", TextStyle.Default.Size(14));
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
}
});
});
@@ -646,9 +722,11 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 200)
.FileName()
.Render(container =>
{
container
.Padding(10)
.Border(2)
.Row(row =>
{
@@ -663,5 +741,42 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void Unconstrained()
{
RenderingTest
.Create()
.PageSize(400, 350)
.FileName()
.Render(container =>
{
container
.Padding(25)
.PaddingLeft(75)
.Stack(stack =>
{
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
stack
.Item()
// creates an infinite space for its child
.Unconstrained()
// moves the child up and left
.TranslateX(-50)
.TranslateY(-50)
// limits the space for the child
.Width(100)
.Height(100)
.Background(Colors.Blue.Darken1);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
});
});
}
}
}
+53 -9
View File
@@ -1,17 +1,24 @@
using System;
using System.Diagnostics;
using QuestPDF.Drawing;
using System.Runtime.CompilerServices;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public enum RenderingTestResult
{
Pdf,
Images
}
public class RenderingTest
{
private string FileNamePrefix = "test";
private Size Size { get; set; }
private bool ShowResult { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private RenderingTest()
{
@@ -23,15 +30,38 @@ namespace QuestPDF.Examples.Engine
return new RenderingTest();
}
public RenderingTest FileName(string fileName)
public RenderingTest FileName([CallerMemberName] string fileName = "test")
{
FileNamePrefix = fileName;
return this;
}
public RenderingTest PageSize(Size size)
{
Size = size;
return this;
}
public RenderingTest PageSize(int width, int height)
{
Size = new Size(width, height);
return PageSize(new Size(width, height));
}
public RenderingTest ProducePdf()
{
ResultType = RenderingTestResult.Pdf;
return this;
}
public RenderingTest ProduceImages()
{
ResultType = RenderingTestResult.Images;
return this;
}
public RenderingTest ShowResults()
{
ShowResult = true;
return this;
}
@@ -39,13 +69,27 @@ namespace QuestPDF.Examples.Engine
{
var container = new Container();
content(container);
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
var document = new SimpleDocument(container, Size);
document.GenerateImages(fileNameSchema);
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
var document = new SimpleDocument(container, Size, maxPages);
Process.Start("explorer", fileNameSchema(0));
if (ResultType == RenderingTestResult.Images)
{
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult)
Process.Start("explorer", fileNameSchema(0));
}
if (ResultType == RenderingTestResult.Pdf)
{
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult)
Process.Start("explorer", fileName);
}
}
}
}
+5 -3
View File
@@ -12,11 +12,13 @@ namespace QuestPDF.Examples.Engine
private IContainer Container { get; }
private Size Size { get; }
private int MaxPages { get; }
public SimpleDocument(IContainer container, Size size)
public SimpleDocument(IContainer container, Size size, int maxPages)
{
Container = container;
Size = size;
MaxPages = maxPages;
}
public DocumentMetadata GetMetadata()
@@ -24,7 +26,7 @@ namespace QuestPDF.Examples.Engine
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = 10
DocumentLayoutExceptionThreshold = MaxPages
};
}
@@ -33,7 +35,7 @@ namespace QuestPDF.Examples.Engine
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Element(Container as Container);
page.Content().Container().Background(Colors.White).Element(Container as Container);
});
}
}
+1
View File
@@ -28,6 +28,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(550, 400)
.FileName()
.Render(container =>
{
container
+1
View File
@@ -36,6 +36,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(350, 280)
.FileName()
.Render(container =>
{
container
+6 -1
View File
@@ -13,6 +13,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -35,6 +36,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -48,7 +50,8 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
});
}
@@ -58,6 +61,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -108,6 +112,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
@@ -16,4 +16,10 @@
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="quo-vadis.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+228
View File
@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TextBenchmark
{
[Test]
public void Generate()
{
var chapters = GetBookChapters().ToList();
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(x => ComposeBook(x, chapters));
}
[Test]
public void Benchmark()
{
var chapters = GetBookChapters().ToList();
var results = PerformTest(16).ToList();
Console.WriteLine($"Min: {results.Min():F}");
Console.WriteLine($"Max: {results.Max():F}");
Console.WriteLine($"Avg: {results.Average():F}");
void GenerateDocument()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
{
foreach (var i in Enumerable.Range(0, attempts))
{
var timer = new Stopwatch();
timer.Start();
GenerateDocument();
timer.Stop();
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
yield return timer.ElapsedMilliseconds;
}
}
}
class BookChapter
{
public string Title { get; set; }
public string Content { get; set; }
}
private static IEnumerable<BookChapter> GetBookChapters()
{
var book = File.ReadAllLines("quo-vadis.txt");
var chapterPointers = book
.Select((line, index) => new
{
LineNumber = index,
Text = line
})
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
.Select(x => x.LineNumber)
.ToList();
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
{
var chapter = chapterPointers[index];
var title = book[chapter];
var lineFrom = chapterPointers[index];
var lineTo = chapterPointers[index + 1] - 1;
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x));
var content = string.Join(Environment.NewLine, lines);
yield return new BookChapter
{
Title = title,
Content = content
};
}
}
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
{
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
var normalStyle = TextStyle.Default.Size(14);
ComposePage(container);
void ComposePage(IContainer container)
{
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Stack(stack =>
{
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
Chapters(stack);
stack.Item().Element(Acknowledgements);
});
decoration.Footer().Element(Footer);
});
}
void Title(IContainer container)
{
container
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Stack(stack =>
{
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
});
}
void TableOfContents(IContainer container)
{
container.Stack(stack =>
{
SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters)
{
stack.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
});
}
});
}
void Chapters(StackDescriptor stack)
{
foreach (var chapter in chapters)
{
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Stack(stack =>
{
SectionTitle(stack, title);
stack.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
});
stack.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Stack(stack =>
{
SectionTitle(stack, "Podziękowania");
stack.Item().Text(text =>
{
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).Underlined());
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
}
void SectionTitle(StackDescriptor stack, string text)
{
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
{
container
.AlignCenter()
.Text(text =>
{
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
}
}
}
}
+122
View File
@@ -0,0 +1,122 @@
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 TextExamples
{
[Test]
public void TextElements()
{
RenderingTest
.Create()
.PageSize(500, 400)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span(Placeholders.LoremIpsum());
text.NewLine();
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).Underlined());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.NewLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.NewLine();
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.NewLine();
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.NewLine();
/*
text.Span("Let's start with bold text. ", TextStyle.Default.Bold().BackgroundColor(Colors.Grey.Lighten3).Size(16));
text.Span("Then something bigger. ", TextStyle.Default.Size(28).Color(Colors.DeepOrange.Darken2).BackgroundColor(Colors.Yellow.Lighten3).Underlined());
text.Span("And tiny \r\n teeny-tiny. ", TextStyle.Default.Size(6));
text.Span("Stroked text also works fine. ", TextStyle.Default.Size(14).Stroked().BackgroundColor(Colors.Grey.Lighten4));
text.Span("0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789", TextStyle.Default.Size(18));
text.NewLine();
text.NewLine();
text.NewLine();
text.Span("Is it time for lorem ipsum? ", TextStyle.Default.Size(12).Underlined().BackgroundColor(Colors.Grey.Lighten3));
text.Span(Placeholders.LoremIpsum(), TextStyle.Default.Size(12));
text.Span("Before element - ");
text.Element().PaddingBottom(-10).Background(Colors.Red.Lighten4).Height(20).PaddingHorizontal(5).AlignMiddle().Text("Text inside text", TextStyle.Default.Size(8));
text.Span(" - end of element.");
text.NewLine();
// text.Span("And now some colors: ", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
//
// foreach (var i in Enumerable.Range(1, 100))
// {
// text.Span($"{i}: {Placeholders.Sentence()} ", TextStyle.Default.Size(12 + i / 5).LineHeight(2.75f - i / 50f).Color(Placeholders.Color()).BackgroundColor(Placeholders.BackgroundColor()));
// }*/
});
});
}
[Test]
public void PageNumber()
{
RenderingTest
.Create()
.PageSize(500, 400)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
});
});
}
}
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -18,8 +18,8 @@ namespace QuestPDF.ReportSample
HeaderFields = HeaderFields(),
LogoData = Helpers.GetImage("Logo.png"),
Sections = Enumerable.Range(0, 50).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 30).Select(x => GetReportPhotos()).ToList()
Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
};
List<ReportHeaderField> HeaderFields()
@@ -28,13 +28,13 @@ namespace QuestPDF.ReportSample.Layouts
{
foreach (var part in Model.Parts)
{
stack.Item().Row(row =>
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.Text(text.Text, Typography.Normal);
frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
+15 -16
View File
@@ -25,17 +25,6 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IDocumentContainer container)
{
container
// .Page(page =>
// {
// page.MarginVertical(40);
// page.MarginHorizontal(50);
//
// page.ContinuousSize(PageSizes.A4.Width);
//
// page.Header().Element(ComposeHeader);
// page.Content().Element(ComposeContent);
// page.Footer().AlignCenter().PageNumber("Page A {number}");
// })
.Page(page =>
{
page.MarginVertical(40);
@@ -45,7 +34,15 @@ namespace QuestPDF.ReportSample.Layouts
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
}
@@ -70,10 +67,10 @@ namespace QuestPDF.ReportSample.Layouts
foreach (var field in Model.HeaderFields)
{
grid.Item().Stack(row =>
{
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
row.Item().Text(field.Value, Typography.Normal);
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value, Typography.Normal);
});
}
});
@@ -88,6 +85,8 @@ namespace QuestPDF.ReportSample.Layouts
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
stack.Item().PageBreak();
foreach (var section in Model.Sections)
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
@@ -43,7 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
{
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().PageNumber($"Page {{pdf:{locationName}}}", Typography.Normal.AlignRight());
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
});
}
}
+3 -3
View File
@@ -28,7 +28,7 @@ namespace QuestPDF.ReportSample
// target document length should be around 100 pages
// test size
const int testSize = 100;
const int testSize = 10;
const decimal performanceTarget = 1; // documents per second
// create report models
@@ -57,8 +57,8 @@ namespace QuestPDF.ReportSample
Console.WriteLine($"Time per document: {performance:N} ms");
Console.WriteLine($"Documents per second: {speed:N} d/s");
if (speed < performanceTarget)
throw new Exception("Rendering algorithm is too slow.");
//if (speed < performanceTarget)
// throw new Exception("Rendering algorithm is too slow.");
}
}
}
+1 -1
View File
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
{
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.25f).AlignLeft();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
}
}
-1
View File
@@ -1,5 +1,4 @@
using FluentAssertions;
using FluentAssertions.Equivalency;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
+3 -2
View File
@@ -4,6 +4,7 @@ using System.Text.Json;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations;
@@ -245,9 +246,9 @@ namespace QuestPDF.UnitTests.TestEngine
public static Element CreateUniqueElement()
{
return new Text
return new DynamicImage
{
Value = Guid.NewGuid().ToString("N")
Source = Placeholders.Image
};
}
}
+5 -19
View File
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -7,7 +6,8 @@ namespace QuestPDF.Drawing
{
internal static class CanvasCache
{
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
internal static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
internal static SKPaint ColorToPaint(this string color)
@@ -36,28 +36,14 @@ namespace QuestPDF.Drawing
Color = SKColor.Parse(style.Color),
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32,
TextAlign = style.Alignment switch
{
HorizontalAlignment.Left => SKTextAlign.Left,
HorizontalAlignment.Center => SKTextAlign.Center,
HorizontalAlignment.Right => SKTextAlign.Right,
_ => SKTextAlign.Left
}
TextEncoding = SKTextEncoding.Utf32
};
}
}
internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
var index = (int)style.ToPaint().BreakText(text, availableWidth, out var width);
return new TextMeasurement()
{
LineIndex = index,
FragmentWidth = width
};
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
}
}
}
-1
View File
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
-4
View File
@@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
-2
View File
@@ -1,6 +1,4 @@
using System;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
-1
View File
@@ -31,7 +31,6 @@ namespace QuestPDF.Drawing
public override void BeginPage(Size size)
{
var scalingFactor = Metadata.RasterDpi / (float) PageSizes.PointsPerInch;
var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor));
Surface = SKSurface.Create(imageInfo);
-3
View File
@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using SkiaSharp;
+13
View File
@@ -0,0 +1,13 @@
namespace QuestPDF.Drawing.SpacePlan
{
internal class TextRender : FullRender
{
public float Ascent { get; set; }
public float Descent { get; set; }
public TextRender(float width, float height) : base(width, height)
{
}
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
-52
View File
@@ -1,52 +0,0 @@
using System;
using System.Text.RegularExpressions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements
{
internal class PageNumber : Element
{
public string TextFormat { get; set; } = "";
private Text TextElement { get; set; } = new Text();
public TextStyle? TextStyle
{
get => TextElement?.Style;
set => TextElement.Style = value;
}
internal override void HandleVisitor(Action<Element?> visit)
{
TextElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
TextElement.Value = GetText();
return TextElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
TextElement.Value = GetText();
TextElement.Draw(availableSpace);
}
private string GetText()
{
var result = TextFormat;
// replace known locations
foreach (var location in PageContext.GetRegisteredLocations())
result = result.Replace($"{{pdf:{location}}}", PageContext.GetLocationPage(location).ToString());
// placeholder unknown locations
result = Regex.Replace(result, @"{pdf:[ \w]+}", "123");
return result;
}
}
}
+3 -4
View File
@@ -18,16 +18,15 @@ namespace QuestPDF.Elements
{
container
.Background(Colors.Grey.Lighten2)
.Padding(5)
.AlignMiddle()
.AlignCenter()
.Padding(5)
.MaxHeight(32)
.Element(x =>
{
if (string.IsNullOrWhiteSpace(Text))
x.Image(ImageData, ImageScaling.FitArea);
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
else
x.Text(Text, TextStyle.Default.Size(14).SemiBold());
x.Text(Text, TextStyle.Default.Size(14));
});
}
}
+4 -40
View File
@@ -1,57 +1,21 @@
using System;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Rotate : ContainerElement
{
public int TurnCount { get; set; }
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
internal override ISpacePlan Measure(Size availableSpace)
{
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
return base.Measure(availableSpace);
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
var childSpace = base.Measure(availableSpace) as Size;
public float Angle { get; set; } = 0;
if (childSpace == null)
return new Wrap();
var targetSpace = new Size(childSpace.Height, childSpace.Width);
if (childSpace is FullRender)
return new FullRender(targetSpace);
if (childSpace is PartialRender)
return new PartialRender(targetSpace);
throw new ArgumentException();
}
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
skiaCanvas.Translate(availableSpace.Width, 0);
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.RotateRadians(NormalizedTurnCount * (float) Math.PI / 2f);
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
skiaCanvas.RotateDegrees(Angle);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
-1
View File
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
+58
View File
@@ -0,0 +1,58 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SimpleRotate : ContainerElement
{
public int TurnCount { get; set; }
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
internal override ISpacePlan Measure(Size availableSpace)
{
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
return base.Measure(availableSpace);
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
var childSpace = base.Measure(availableSpace) as Size;
if (childSpace == null)
return new Wrap();
var targetSpace = new Size(childSpace.Height, childSpace.Width);
if (childSpace is FullRender)
return new FullRender(targetSpace);
if (childSpace is PartialRender)
return new PartialRender(targetSpace);
throw new ArgumentException();
}
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
skiaCanvas.Translate(availableSpace.Width, 0);
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
-1
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
-117
View File
@@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements
{
internal class Text : Element
{
public string? Value { get; set; }
public TextStyle? Style { get; set; } = new TextStyle();
private float LineHeight => Style.Size * Style.LineHeight;
internal override ISpacePlan Measure(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var realWidth = lines
.Select(line => Style.BreakText(line, availableSpace.Width).FragmentWidth)
.DefaultIfEmpty(0)
.Max();
var realHeight = lines.Count * LineHeight;
if (realHeight > availableSpace.Height + Size.Epsilon)
return new Wrap();
return new FullRender(realWidth, realHeight);
}
internal override void Draw(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
Canvas.Translate(new Position(0, Style.Size));
foreach (var line in lines)
{
Canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
offsetTop += LineHeight;
}
Canvas.Translate(new Position(0, -Style.Size));
float GetLeftOffset()
{
return Style.Alignment switch
{
HorizontalAlignment.Left => 0,
HorizontalAlignment.Center => availableSpace.Width / 2,
HorizontalAlignment.Right => availableSpace.Width,
_ => throw new NotSupportedException()
};
}
}
#region Word Wrap
private List<string> BreakLines(float maxWidth)
{
var lines = new List<string> ();
var remainingText = Value.Trim();
while(true)
{
if (string.IsNullOrEmpty(remainingText))
break;
var breakPoint = BreakLinePoint(remainingText, maxWidth);
if (breakPoint == 0)
break;
var lastLine = remainingText.Substring(0, breakPoint).Trim();
lines.Add(lastLine);
remainingText = remainingText.Substring(breakPoint).Trim();
}
return lines;
}
private int BreakLinePoint(string text, float width)
{
var index = 0;
var lengthBreak = Style.BreakText(text, width).LineIndex;
while (index <= text.Length)
{
var next = text.IndexOfAny (new [] { ' ', '\n' }, index);
if (next <= 0)
return index == 0 || lengthBreak == text.Length ? lengthBreak : index;
if (next > lengthBreak)
return index;
if (text[next] == '\n')
return next;
index = next + 1;
}
return index;
}
#endregion
}
}
@@ -0,0 +1,16 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextDrawingRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public float TotalAscent { get; set; }
public Size TextSize { get; set; }
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements.Text.Items;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextLine
{
public ICollection<TextLineElement> Elements { get; set; }
public float TextHeight => Elements.Max(x => x.Measurement.Height);
public float LineHeight => Elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
public float Ascent => Elements.Min(x => x.Measurement.Ascent) - (LineHeight - TextHeight) / 2;
public float Descent => Elements.Max(x => x.Measurement.Descent) + (LineHeight - TextHeight) / 2;
public float Width => Elements.Sum(x => x.Measurement.Width);
}
}
@@ -0,0 +1,10 @@
using QuestPDF.Elements.Text.Calculation;
namespace QuestPDF.Elements.Text.Items
{
internal class TextLineElement
{
public ITextBlockItem Item { get; set; }
public TextMeasurementResult Measurement { get; set; }
}
}
@@ -0,0 +1,14 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextMeasurementRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public float AvailableWidth { get; set; }
public bool IsFirstLineElement { get; set; }
}
}
@@ -0,0 +1,22 @@
using System;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextMeasurementResult
{
public float Width { get; set; }
public float Height => Math.Abs(Descent) + Math.Abs(Ascent);
public float Ascent { get; set; }
public float Descent { get; set; }
public float LineHeight { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int NextIndex { get; set; }
public int TotalIndex { get; set; }
public bool IsLast => EndIndex == TotalIndex;
}
}
@@ -0,0 +1,10 @@
using QuestPDF.Elements.Text.Calculation;
namespace QuestPDF.Elements.Text.Items
{
internal interface ITextBlockItem
{
TextMeasurementResult? Measure(TextMeasurementRequest request);
void Draw(TextDrawingRequest request);
}
}
@@ -0,0 +1,48 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockElement : ITextBlockItem
{
public Element Element { get; set; } = Empty.Instance;
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
if (measurement is Wrap || measurement is PartialRender)
return null;
var elementSize = measurement as Size;
return new TextMeasurementResult
{
Width = elementSize.Width,
Ascent = -elementSize.Height,
Descent = 0,
LineHeight = 1,
StartIndex = 0,
EndIndex = 0,
TotalIndex = 0
};
}
public void Draw(TextDrawingRequest request)
{
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
request.Canvas.Translate(new Position(0, request.TotalAscent));
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
}
}
}
@@ -0,0 +1,35 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockExternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string Url { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -0,0 +1,35 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockInternalLink : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string LocationName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
}
}
}
@@ -0,0 +1,36 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockPageNumber : ITextBlockItem
{
public TextStyle Style { get; set; } = new TextStyle();
public string SlotName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem(request.PageContext).MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
{
GetItem(request.PageContext).Draw(request);
}
private TextBlockSpan GetItem(IPageContext context)
{
var pageNumberPlaceholder = 123;
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
? context.GetLocationPage(SlotName)
: pageNumberPlaceholder;
return new TextBlockSpan
{
Style = Style,
Text = pageNumber.ToString()
};
}
}
}
@@ -0,0 +1,128 @@
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockSpan : ITextBlockItem
{
public string Text { get; set; }
public TextStyle Style { get; set; } = new TextStyle();
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
var cacheKey = (request.StartIndex, request.AvailableWidth);
if (!MeasureCache.ContainsKey(cacheKey))
MeasureCache[cacheKey] = MeasureWithoutCache(request);
return MeasureCache[cacheKey];
}
internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request)
{
const char space = ' ';
var paint = Style.ToPaint();
var fontMetrics = Style.ToFontMetrics();
var startIndex = request.StartIndex;
if (request.IsFirstLineElement)
{
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
startIndex++;
}
if (Text.Length == 0)
{
return new TextMeasurementResult
{
Width = 0,
LineHeight = Style.LineHeight,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent
};
}
// start breaking text from requested position
var text = Text.Substring(startIndex);
var breakingIndex = (int)paint.BreakText(text, request.AvailableWidth);
if (breakingIndex <= 0)
return null;
// break text only on spaces
if (breakingIndex < text.Length)
{
var lastSpaceIndex = text.Substring(0, breakingIndex).LastIndexOf(space) - 1;
if (lastSpaceIndex <= 0)
{
if (!request.IsFirstLineElement)
return null;
}
else
{
breakingIndex = lastSpaceIndex + 1;
}
}
text = text.Substring(0, breakingIndex);
var endIndex = startIndex + breakingIndex;
var nextIndex = endIndex;
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
nextIndex++;
// measure final text
var width = paint.MeasureText(text);
return new TextMeasurementResult
{
Width = width,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent,
LineHeight = Style.LineHeight,
StartIndex = startIndex,
EndIndex = endIndex,
NextIndex = nextIndex,
TotalIndex = Text.Length
};
}
public void Draw(TextDrawingRequest request)
{
var fontMetrics = Style.ToFontMetrics();
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
// draw stroke
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
void DrawLine(float offset, float thickness)
{
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
}
}
}
}
+202
View File
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text
{
internal class TextBlock : Element, IStateResettable
{
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
public Queue<ITextBlockItem> RenderingQueue { get; set; }
public int CurrentElementIndex { get; set; }
public void ResetState()
{
RenderingQueue = new Queue<ITextBlockItem>(Children);
CurrentElementIndex = 0;
}
internal override ISpacePlan Measure(Size availableSpace)
{
if (!RenderingQueue.Any())
return new FullRender(Size.Zero);
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height);
if (!lines.Any())
return new PartialRender(Size.Zero);
var width = lines.Max(x => x.Width);
var height = lines.Sum(x => x.LineHeight);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return new Wrap();
var fullyRenderedItemsCount = lines
.SelectMany(x => x.Elements)
.GroupBy(x => x.Item)
.Count(x => x.Any(y => y.Measurement.IsLast));
if (fullyRenderedItemsCount == RenderingQueue.Count)
return new FullRender(width, height);
return new PartialRender(width, height);
}
internal override void Draw(Size availableSpace)
{
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any())
return;
var heightOffset = 0f;
var widthOffset = 0f;
foreach (var line in lines)
{
widthOffset = 0f;
var alignmentOffset = GetAlignmentOffset(line.Width);
Canvas.Translate(new Position(alignmentOffset, 0));
Canvas.Translate(new Position(0, -line.Ascent));
foreach (var item in line.Elements)
{
var textDrawingRequest = new TextDrawingRequest
{
Canvas = Canvas,
PageContext = PageContext,
StartIndex = item.Measurement.StartIndex,
EndIndex = item.Measurement.EndIndex,
TextSize = new Size(item.Measurement.Width, line.LineHeight),
TotalAscent = line.Ascent
};
item.Item.Draw(textDrawingRequest);
Canvas.Translate(new Position(item.Measurement.Width, 0));
widthOffset += item.Measurement.Width;
}
Canvas.Translate(new Position(-alignmentOffset, 0));
Canvas.Translate(new Position(-line.Width, line.Ascent));
Canvas.Translate(new Position(0, line.LineHeight));
heightOffset += line.LineHeight;
}
Canvas.Translate(new Position(0, -heightOffset));
lines
.SelectMany(x => x.Elements)
.GroupBy(x => x.Item)
.Where(x => x.Any(y => y.Measurement.IsLast))
.Select(x => x.Key)
.ToList()
.ForEach(x => RenderingQueue.Dequeue());
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
if (!RenderingQueue.Any())
ResetState();
float GetAlignmentOffset(float lineWidth)
{
if (Alignment == HorizontalAlignment.Left)
return 0;
var emptySpace = availableSpace.Width - lineWidth;
if (Alignment == HorizontalAlignment.Right)
return emptySpace;
if (Alignment == HorizontalAlignment.Center)
return emptySpace / 2;
throw new ArgumentException();
}
}
public IEnumerable<TextLine> DivideTextItemsIntoLines(float availableWidth, float availableHeight)
{
var queue = new Queue<ITextBlockItem>(RenderingQueue);
var currentItemIndex = CurrentElementIndex;
var currentHeight = 0f;
while (queue.Any())
{
var line = GetNextLine();
if (!line.Elements.Any())
yield break;
if (currentHeight + line.LineHeight > availableHeight + Size.Epsilon)
yield break;
currentHeight += line.LineHeight;
yield return line;
}
TextLine GetNextLine()
{
var currentWidth = 0f;
var currentLineElements = new List<TextLineElement>();
while (true)
{
if (!queue.Any())
break;
var currentElement = queue.Peek();
var measurementRequest = new TextMeasurementRequest
{
Canvas = Canvas,
PageContext = PageContext,
StartIndex = currentItemIndex,
AvailableWidth = availableWidth - currentWidth,
IsFirstLineElement = !currentLineElements.Any()
};
var measurementResponse = currentElement.Measure(measurementRequest);
if (measurementResponse == null)
break;
currentLineElements.Add(new TextLineElement
{
Item = currentElement,
Measurement = measurementResponse
});
currentWidth += measurementResponse.Width;
currentItemIndex = measurementResponse.NextIndex;
if (!measurementResponse.IsLast)
break;
currentItemIndex = 0;
queue.Dequeue();
}
return new TextLine
{
Elements = currentLineElements
};
}
}
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ namespace QuestPDF.Elements
{
internal class Translate : ContainerElement
{
public float TranslateX { get; set; } = 1;
public float TranslateY { get; set; } = 1;
public float TranslateX { get; set; } = 0;
public float TranslateY { get; set; } = 0;
internal override void Draw(Size availableSpace)
{
+31
View File
@@ -0,0 +1,31 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Unconstrained : ContainerElement
{
internal override ISpacePlan Measure(Size availableSpace)
{
var childSize = Child?.Measure(Size.Max) ?? new FullRender(Size.Zero);
if (childSize is PartialRender)
return new PartialRender(Size.Zero);
if (childSize is FullRender)
return new FullRender(Size.Zero);
return childSize;
}
internal override void Draw(Size availableSpace)
{
var measurement = Child?.Measure(Size.Max) as Size;
if (measurement == null)
return;
Child?.Draw(measurement);
}
}
}
+5 -27
View File
@@ -40,15 +40,6 @@ namespace QuestPDF.Fluent
{
return handler(parent.Container()).Container();
}
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
{
element.Element(new PageNumber
{
TextFormat = textFormat,
TextStyle = style ?? TextStyle.Default
});
}
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
{
@@ -92,25 +83,7 @@ namespace QuestPDF.Fluent
MinHeight = minHeight
});
}
public static void Text(this IContainer element, object text, TextStyle? style = null)
{
text ??= string.Empty;
style ??= TextStyle.Default;
if (element is Alignment alignment)
{
style = style.Clone();
style.Alignment = alignment.Horizontal;
}
element.Element(new Text()
{
Value = text.ToString(),
Style = style
});
}
public static void PageBreak(this IContainer element)
{
element.Element(new PageBreak());
@@ -162,5 +135,10 @@ namespace QuestPDF.Fluent
{
return element.Element(new Box());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
}
}
}
+4
View File
@@ -39,6 +39,10 @@ namespace QuestPDF.Fluent
foreach (var imageData in document.GenerateImages())
{
var path = filePath(index);
if (File.Exists(path))
File.Delete(path);
File.WriteAllBytes(path, imageData);
index++;
}
+13 -4
View File
@@ -6,9 +6,9 @@ namespace QuestPDF.Fluent
{
public static class RotateExtensions
{
private static IContainer Rotate(this IContainer element, Action<Rotate> handler)
private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
{
var scale = element as Rotate ?? new Rotate();
var scale = element as SimpleRotate ?? new SimpleRotate();
handler(scale);
return element.Element(scale);
@@ -16,12 +16,21 @@ namespace QuestPDF.Fluent
public static IContainer RotateLeft(this IContainer element)
{
return element.Rotate(x => x.TurnCount--);
return element.SimpleRotate(x => x.TurnCount--);
}
public static IContainer RotateRight(this IContainer element)
{
return element.Rotate(x => x.TurnCount++);
return element.SimpleRotate(x => x.TurnCount++);
}
/// <param name="angle">In degrees</param>
public static IContainer Rotate(this IContainer element, float angle)
{
return element.Element(new Rotate
{
Angle = angle
});
}
}
}
-2
View File
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
+8 -8
View File
@@ -16,32 +16,32 @@ namespace QuestPDF.Fluent
public static IContainer Scale(this IContainer element, float value)
{
return element.ScaleX(value).ScaleY(value);
return element.ScaleHorizontal(value).ScaleVertical(value);
}
public static IContainer ScaleX(this IContainer element, float value)
public static IContainer ScaleHorizontal(this IContainer element, float value)
{
return element.Scale(x => x.ScaleX = value);
}
public static IContainer FlipX(this IContainer element)
public static IContainer FlipHorizontal(this IContainer element)
{
return element.ScaleX(-1);
return element.ScaleHorizontal(-1);
}
public static IContainer ScaleY(this IContainer element, float value)
public static IContainer ScaleVertical(this IContainer element, float value)
{
return element.Scale(x => x.ScaleY = value);
}
public static IContainer FlipY(this IContainer element)
public static IContainer FlipVertical(this IContainer element)
{
return element.ScaleY(-1);
return element.ScaleVertical(-1);
}
public static IContainer FlipOver(this IContainer element)
{
return element.FlipX().FlipY();
return element.FlipHorizontal().FlipVertical();
}
}
}
-2
View File
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
+176
View File
@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class TextDescriptor
{
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f;
public void DefaultTextStyle(TextStyle style)
{
DefaultStyle = style;
}
public void AlignLeft()
{
Alignment = HorizontalAlignment.Left;
}
public void AlignCenter()
{
Alignment = HorizontalAlignment.Center;
}
public void AlignRight()
{
Alignment = HorizontalAlignment.Right;
}
public void ParagraphSpacing(float value)
{
Spacing = value;
}
private void AddItemToLastTextBlock(ITextBlockItem item)
{
if (!TextBlocks.Any())
TextBlocks.Add(new TextBlock());
TextBlocks.Last().Children.Add(item);
}
public void Span(string text, TextStyle? style = null)
{
style ??= DefaultStyle;
var items = text
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select(x => new TextBlockSpan
{
Text = x,
Style = style
})
.ToList();
AddItemToLastTextBlock(items.First());
items
.Skip(1)
.Select(x => new TextBlock
{
Children = new List<ITextBlockItem> { x }
})
.ToList()
.ForEach(TextBlocks.Add);
}
public void NewLine()
{
Span(Environment.NewLine);
}
private void PageNumber(string slotName, TextStyle? style = null)
{
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockPageNumber()
{
Style = style,
SlotName = slotName
});
}
public void CurrentPageNumber(TextStyle? style = null)
{
PageNumber(PageContext.CurrentPageSlot, style);
}
public void TotalPages(TextStyle? style = null)
{
PageNumber(PageContext.TotalPagesSlot, style);
}
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
{
PageNumber(locationName, style);
}
public void InternalLocation(string text, string locationName, TextStyle? style = null)
{
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockInternalLink
{
Style = style,
Text = text,
LocationName = locationName
});
}
public void ExternalLocation(string text, string url, TextStyle? style = null)
{
style ??= DefaultStyle;
AddItemToLastTextBlock(new TextBlockExternalLink
{
Style = style,
Text = text,
Url = url
});
}
public IContainer Element()
{
var container = new Container();
AddItemToLastTextBlock(new TextBlockElement
{
Element = container
});
return container.Box();
}
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
container.Stack(stack =>
{
stack.Spacing(Spacing);
foreach (var textBlock in TextBlocks)
stack.Item().Element(textBlock);
});
}
}
public static class TextExtensions
{
public static void Text(this IContainer element, Action<TextDescriptor> content)
{
var textBlock = new TextBlock();
if (element is Alignment alignment)
textBlock.Alignment = alignment.Horizontal;
var descriptor = new TextDescriptor();
content?.Invoke(descriptor);
descriptor.Compose(element);
}
public static void Text(this IContainer element, object text, TextStyle? style = null)
{
element.Text(x => x.Span(text.ToString(), style));
}
}
}
+15 -24
View File
@@ -18,6 +18,11 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.Color = value);
}
public static TextStyle BackgroundColor(this TextStyle style, string value)
{
return style.Mutate(x => x.BackgroundColor = value);
}
public static TextStyle FontType(this TextStyle style, string value)
{
return style.Mutate(x => x.FontType = value);
@@ -37,31 +42,17 @@ namespace QuestPDF.Fluent
{
return style.Mutate(x => x.IsItalic = value);
}
public static TextStyle Stroked(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.IsStroked = value);
}
public static TextStyle Underlined(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.IsUnderlined = value);
}
#region Alignmnet
public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value)
{
return style.Mutate(x => x.Alignment = value);
}
public static TextStyle AlignLeft(this TextStyle style)
{
return style.Alignment(HorizontalAlignment.Left);
}
public static TextStyle AlignCenter(this TextStyle style)
{
return style.Alignment(HorizontalAlignment.Center);
}
public static TextStyle AlignRight(this TextStyle style)
{
return style.Alignment(HorizontalAlignment.Right);
}
#endregion
#region Weight
public static TextStyle Weight(this TextStyle style, FontWeight weight)
+1 -6
View File
@@ -13,12 +13,7 @@ namespace QuestPDF.Fluent
return element.Element(translate);
}
public static IContainer Translate(this IContainer element, float value)
{
return element.TranslateX(value).TranslateY(value);
}
public static IContainer TranslateX(this IContainer element, float value)
{
return element.Translate(x => x.TranslateX = value);
+19 -19
View File
@@ -155,25 +155,25 @@ namespace QuestPDF.Helpers
private static readonly string[] BackgroundColors =
{
Colors.Red.Lighten2,
Colors.Pink.Lighten2,
Colors.Purple.Lighten2,
Colors.DeepPurple.Lighten2,
Colors.Indigo.Lighten2,
Colors.Blue.Lighten2,
Colors.LightBlue.Lighten2,
Colors.Cyan.Lighten2,
Colors.Teal.Lighten2,
Colors.Green.Lighten2,
Colors.LightGreen.Lighten2,
Colors.Lime.Lighten2,
Colors.Yellow.Lighten2,
Colors.Amber.Lighten2,
Colors.Orange.Lighten2,
Colors.DeepOrange.Lighten2,
Colors.Brown.Lighten2,
Colors.Grey.Lighten2,
Colors.BlueGrey.Lighten2
Colors.Red.Lighten3,
Colors.Pink.Lighten3,
Colors.Purple.Lighten3,
Colors.DeepPurple.Lighten3,
Colors.Indigo.Lighten3,
Colors.Blue.Lighten3,
Colors.LightBlue.Lighten3,
Colors.Cyan.Lighten3,
Colors.Teal.Lighten3,
Colors.Green.Lighten3,
Colors.LightGreen.Lighten3,
Colors.Lime.Lighten3,
Colors.Yellow.Lighten3,
Colors.Amber.Lighten3,
Colors.Orange.Lighten3,
Colors.DeepOrange.Lighten3,
Colors.Brown.Lighten3,
Colors.Grey.Lighten3,
Colors.BlueGrey.Lighten3
};
public static string BackgroundColor()
-1
View File
@@ -1,6 +1,5 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
-1
View File
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using SkiaSharp;
namespace QuestPDF.Infrastructure
-1
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
-8
View File
@@ -30,13 +30,5 @@
if (obj.GetType() != this.GetType()) return false;
return Equals((Size) obj);
}
public override int GetHashCode()
{
unchecked
{
return (Width.GetHashCode() * 397) ^ Height.GetHashCode();
}
}
}
}
+7 -2
View File
@@ -5,18 +5,23 @@ namespace QuestPDF.Infrastructure
public class TextStyle
{
internal string Color { get; set; } = Colors.Black;
internal string BackgroundColor { get; set; } = Colors.Transparent;
internal string FontType { get; set; } = "Calibri";
internal float Size { get; set; } = 12;
internal float LineHeight { get; set; } = 1.2f;
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
internal bool IsItalic { get; set; } = false;
internal bool IsStroked { get; set; } = false;
internal bool IsUnderlined { get; set; } = false;
public static TextStyle Default => new TextStyle();
private string? KeyCache { get; set; }
public override string ToString()
{
return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}";
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{IsStroked}|{IsUnderlined}";
return KeyCache;
}
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
+2 -2
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.5.2</Version>
<Version>2021.8</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>New elements: Box, Grid, Canvas, EnsureSpance and Layers. Redesigned the Debug element. Added material design colors. Added list of basic fonts. Added spacing property to the Row element. Fluent API improvements and increased stability.</PackageReleaseNotes>
<PackageReleaseNotes>New elements: Unconstrained, Scale, ScaleHorizontal, ScaleVertical, FlipOver, FlipHorizontal, FlipVertical, Rotate, RotateLeft, RotateRight, TranslateX, TranslateY. Added support for more placeholders in the PageNumber element (current page, total number of pages, page number of any predefined location). Added support for documents with various page sizes. Various API improvements.</PackageReleaseNotes>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
+14
View File
@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2021.5.2 | :white_check_mark: |
| Older | :x: |
## Reporting a Vulnerability
Please report any code vulnerabilities using GitHub Issues. We are trying to respond within 1 day and analyze reported situation as soon as possible.
Please collaborate with us to find best solutation available.
Once the vulnerability is recognized and fixed, the issue is closed with appropraite message.
+23 -12
View File
@@ -16,6 +16,15 @@
6) **Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
## Support QuestPDF
All great frameworks and libraries started from zero. Please help us to make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. It can be as easy as:
- Giving this repository a star ⭐ so more people will know about it,
- Observing 🤩 the library to know about each new realease,
- Trying our sample project to see how easy it is to create an invoice 📊,
- Sharing your thoughts 💬 with us and your colleagues,
- Simply using it 👨‍💻 and suggesting new features,
- Creating new features 🆕 for everybody.
## Installation
@@ -25,7 +34,6 @@ The library is available as a nuget package. You can install it as any other nug
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/nuget.svg" width="200px">
</a>
## Documentation
1. [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.
@@ -46,16 +54,19 @@ For tutorial, documentation and API reference, please visit [the QuestPDF docume
Here you can find an example code showing how easy is to write and understand the fluent API:
```csharp
public void Compose(IContainer container)
{
public void Compose(IDocumentContainer container)
{
container
.PaddingHorizontal(50)
.PaddingVertical(50)
.Page(page =>
{
page.Header(ComposeHeader);
page.Content(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
page.MarginVertical(60);
page.MarginHorizontal(40);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
});
}
@@ -63,11 +74,11 @@ void ComposeHeader(IContainer container)
{
container.Row(row =>
{
row.RelativeColumn().Stack(column =>
row.RelativeColumn().Stack(stack =>
{
column.Element().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
column.Element().Text($"Issue date: {Model.IssueDate:d}");
column.Element().Text($"Due date: {Model.DueDate:d}");
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
stack.Item().Text($"Due date: {Model.DueDate:d}");
});
row.ConstantColumn(100).Height(50).Placeholder();