Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4743768e23 | |||
| 1ba01a1cf5 | |||
| d4448437ac | |||
| bc853f48fc | |||
| 3ba09ea826 | |||
| 92abd32aae | |||
| e17867d1f3 | |||
| bd71f30c78 | |||
| 6a78a4ebdb | |||
| 9fc721d66b | |||
| 7d62dead86 | |||
| 52ad0f5c24 | |||
| 53107073a6 | |||
| c71bb3ea59 | |||
| 6cced3c143 | |||
| 425ea59cfe | |||
| 4d326496c6 | |||
| b7b6488d16 | |||
| f0ba5fc32d | |||
| 04da32e0e7 | |||
| 5bdb338996 | |||
| 06b30f2ad6 | |||
| d39f68a974 | |||
| db9d8b5fcf | |||
| a31a588b24 | |||
| cb2a3c6363 | |||
| e775f62fab | |||
| 056e3f244c | |||
| cd3f7f5a25 | |||
| a5805f6e43 | |||
| abe7b2f14c | |||
| 3e485a572c | |||
| fd0bb5b089 | |||
| cc51b62b73 | |||
| 8e16b5ad4f | |||
| 71e2743409 | |||
| ba9d3aa04b | |||
| 2a0b133185 | |||
| 6058dac524 | |||
| 25b8f62245 | |||
| 2c31f3d5b5 | |||
| f7cd89ccf6 | |||
| 6914a9063a | |||
| b87e8d5d4e | |||
| d740d67a64 | |||
| 1047854f77 | |||
| c5fbf7263c | |||
| fae2205ff8 | |||
| 1cbe4b1dc8 |
@@ -65,9 +65,9 @@ namespace QuestPDF.Examples.Engine
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ShowResults()
|
||||
public RenderingTest ShowResults(bool value = true)
|
||||
{
|
||||
ShowResult = true;
|
||||
ShowResult = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
@@ -11,36 +16,77 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.MaxPages(10_000)
|
||||
.EnableCaching(true)
|
||||
.EnableDebugging(false)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
const int numberOfRows = 100_000;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
foreach (var _ in Enumerable.Range(0, numberOfColumns))
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
GenerateAndCollect();
|
||||
|
||||
foreach (var row in Enumerable.Range(0, numberOfRows))
|
||||
foreach (var column in Enumerable.Range(0, numberOfColumns))
|
||||
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
|
||||
});
|
||||
});
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 10000))
|
||||
{
|
||||
GenerateAndCollect();
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
Console.WriteLine($"Execution time: {stopwatch.Elapsed:g}");
|
||||
|
||||
void GenerateAndCollect()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Column(column =>
|
||||
{
|
||||
const int numberOfRows = 100;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
for (var y = 0; y < numberOfRows; y++)
|
||||
{
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
for (var x = 0; x < numberOfColumns; x++)
|
||||
{
|
||||
row.RelativeItem()
|
||||
|
||||
.Background(Colors.Red.Lighten5)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten4)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Medium)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken4)
|
||||
.Height(3);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ElementCacheManager.Collect(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +590,7 @@ namespace QuestPDF.Examples
|
||||
text.Span("Complex Unicode structure: ");
|
||||
|
||||
|
||||
text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontColor(Colors.Red.Medium);
|
||||
text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontFamily(Fonts.Calibri).FontColor(Colors.Red.Medium);
|
||||
|
||||
|
||||
text.Span(".");
|
||||
@@ -614,6 +614,7 @@ namespace QuestPDF.Examples
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text("ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا")
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Application x:Class="QuestPDF.Previewer.PreviewerApp"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="QuestPDF Document Preview">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark" />
|
||||
</Application.Styles>
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace QuestPDF.Previewer
|
||||
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
|
||||
|
||||
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/documentation/api-reference.html"));
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/api-reference/index.html"));
|
||||
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF.Previewer</PackageId>
|
||||
<Version>2022.6.0</Version>
|
||||
<Version>2022.8.0</Version>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
||||
<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>
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
|
||||
Content = documentContainer.Compose();
|
||||
|
||||
PageContext = new PageContext();
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
|
||||
[Benchmark]
|
||||
public void GenerationTest()
|
||||
{
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,10 @@ namespace QuestPDF.ReportSample
|
||||
Report.Compose(container);
|
||||
var content = container.Compose();
|
||||
|
||||
var metadata = Report.GetMetadata();
|
||||
var pageContext = new PageContext();
|
||||
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static class Typography
|
||||
{
|
||||
public static TextStyle Title => TextStyle.Default.FontFamily(Fonts.Calibri).FontColor(Colors.Blue.Darken3).FontSize(26).Black();
|
||||
public static TextStyle Headline => TextStyle.Default.FontFamily(Fonts.Calibri).FontColor(Colors.Blue.Medium).FontSize(16).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontFamily(Fonts.Verdana).FontColor(Colors.Black).FontSize(10).LineHeight(1.2f);
|
||||
public static TextStyle Title => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Blue.Darken3).FontSize(26).Black();
|
||||
public static TextStyle Headline => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Blue.Medium).FontSize(16).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Black).FontSize(10).LineHeight(1.2f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
|
||||
@@ -66,19 +66,19 @@ namespace QuestPDF.Drawing
|
||||
var content = container.Compose();
|
||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var pageContext = new PageContext();
|
||||
|
||||
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
|
||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||
|
||||
if (metadata.ApplyCaching)
|
||||
if (Settings.EnableCaching)
|
||||
ApplyCaching(content);
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||
var pageContext = new PageContext();
|
||||
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, debuggingState);
|
||||
|
||||
ElementCacheManager.Collect(content);
|
||||
}
|
||||
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||
@@ -114,7 +114,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
canvas.EndPage();
|
||||
|
||||
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
|
||||
if (currentPage >= Settings.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
ThrowLayoutException();
|
||||
@@ -131,8 +131,8 @@ namespace QuestPDF.Drawing
|
||||
void ThrowLayoutException()
|
||||
{
|
||||
var message = $"Composed layout generates infinite document. This may happen in two cases. " +
|
||||
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
|
||||
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
|
||||
$"1) Your document and its layout configuration is correct but the content takes more than {Settings.DocumentLayoutExceptionThreshold} pages. " +
|
||||
$"In this case, please increase the value {nameof(QuestPDF)}.{nameof(Settings)}.{nameof(Settings.DocumentLayoutExceptionThreshold)} static property. " +
|
||||
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
|
||||
$"Please analyze your documents structure to detect this element and fix its size constraints.";
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
if (textBlockItem is TextBlockSpan textSpan)
|
||||
{
|
||||
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
textSpan.Style = textSpan.Style.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
||||
}
|
||||
else if (textBlockItem is TextBlockElement textElement)
|
||||
{
|
||||
@@ -186,18 +186,13 @@ namespace QuestPDF.Drawing
|
||||
}
|
||||
|
||||
if (content is DynamicHost dynamicHost)
|
||||
dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
var targetTextStyle = documentDefaultTextStyle;
|
||||
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
if (content is DefaultTextStyle defaultTextStyleElement)
|
||||
{
|
||||
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
|
||||
targetTextStyle = defaultTextStyleElement.TextStyle;
|
||||
}
|
||||
|
||||
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ApplyDefaultTextStyle(child, targetTextStyle);
|
||||
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
@@ -18,14 +19,26 @@ namespace QuestPDF.Drawing
|
||||
public DateTime CreationDate { get; set; } = DateTime.Now;
|
||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// If the number of generated pages exceeds this threshold
|
||||
/// (likely due to infinite layout), the exception is thrown.
|
||||
/// </summary>
|
||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")]
|
||||
public int DocumentLayoutExceptionThreshold
|
||||
{
|
||||
get => Settings.DocumentLayoutExceptionThreshold;
|
||||
set => Settings.DocumentLayoutExceptionThreshold = value;
|
||||
}
|
||||
|
||||
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableCaching static property.")]
|
||||
public bool ApplyCaching
|
||||
{
|
||||
get => Settings.EnableCaching;
|
||||
set => Settings.EnableCaching = value;
|
||||
}
|
||||
|
||||
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableDebugging static property.")]
|
||||
public bool ApplyDebugging
|
||||
{
|
||||
get => Settings.EnableDebugging;
|
||||
set => Settings.EnableDebugging = value;
|
||||
}
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class CircularBuffer
|
||||
{
|
||||
private const int BufferSize = 11_000;
|
||||
|
||||
private object[] Buffer = new object[BufferSize];
|
||||
private int WriteIndex { get; set; } = 0;
|
||||
private int ReadIndex { get; set; } = 0;
|
||||
|
||||
public T Get<T>() where T : class, new()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (ReadIndex == WriteIndex)
|
||||
return new T();
|
||||
|
||||
var index = ReadIndex;
|
||||
ReadIndex = (ReadIndex + 1) % BufferSize;
|
||||
|
||||
var result = Buffer[index] as T;
|
||||
Buffer[index] = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void Store(object value)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Buffer[WriteIndex] = value;
|
||||
WriteIndex = (WriteIndex + 1) % BufferSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// performance analysis:
|
||||
// without: 115s
|
||||
// ConcurrentQueue: 28s
|
||||
// ConcurrentBag: 38s
|
||||
// CircularBuffer: 30s
|
||||
internal static class ElementCacheManager
|
||||
{
|
||||
private static ConcurrentDictionary<Type, ConcurrentQueue<object>> Cache { get; } = new();
|
||||
|
||||
public static T Get<T>() where T : class, new()
|
||||
{
|
||||
var buffer = Cache.GetOrAdd(typeof(T), _=> new ConcurrentQueue<object>());
|
||||
return buffer.TryDequeue(out var result) ? result as T : new T();
|
||||
}
|
||||
|
||||
public static void Store<T>(T element)
|
||||
{
|
||||
var buffer = Cache.GetOrAdd(element.GetType(), _=> new ConcurrentQueue<object>());
|
||||
buffer.Enqueue(element);
|
||||
}
|
||||
|
||||
public static void Collect(Element element)
|
||||
{
|
||||
foreach (var child in element.GetChildren())
|
||||
Collect(child);
|
||||
|
||||
if (element is ICollectable collectable)
|
||||
{
|
||||
collectable.Collect();
|
||||
Store(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,33 @@ namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class InitializationException : Exception
|
||||
{
|
||||
internal InitializationException(string documentType, Exception innerException) : base(CreateMessage(documentType), innerException)
|
||||
internal InitializationException(string documentType, Exception innerException) : base(CreateMessage(documentType, innerException.Message), innerException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static string CreateMessage(string documentType)
|
||||
private static string CreateMessage(string documentType, string innerExceptionMessage)
|
||||
{
|
||||
return $"Cannot create the {documentType} document using the SkiaSharp library. " +
|
||||
$"This exception usually means that, on your operating system where you run the application, SkiaSharp requires installing additional dependencies. " +
|
||||
$"Such dependencies are available as additional nuget packages, for example SkiaSharp.NativeAssets.Linux. " +
|
||||
$"Please refer to the SkiaSharp documentation for more details.";
|
||||
var (libraryName, nugetConvention) = GetLibraryName();
|
||||
|
||||
return $"Cannot create the {documentType} document using the {libraryName} library. " +
|
||||
$"This exception usually means that, on your operating system where you run the application, {libraryName} requires installing additional dependencies. " +
|
||||
$"Such dependencies are available as additional nuget packages, for example {nugetConvention}.Linux.NoDependencies. " +
|
||||
$"Some operating systems may require installing multiple nugets, e.g. MacOS may need both {nugetConvention}.macOS.NoDependencies and {nugetConvention}.Linux.NoDependencies." +
|
||||
$"Please refer to the {libraryName} documentation for more details. " +
|
||||
$"Also, please consult the inner exception that has been originally thrown by the dependency library.";
|
||||
|
||||
(string GetLibraryName, string nugetConvention) GetLibraryName()
|
||||
{
|
||||
if (innerExceptionMessage.Contains("libSkiaSharp"))
|
||||
return ("SkiaSharp", "SkiaSharp.NativeAssets");
|
||||
|
||||
if (innerExceptionMessage.Contains("libHarfBuzzSharp"))
|
||||
return ("HarfBuzzSharp", "HarfBuzzSharp.NativeAssets");
|
||||
|
||||
// default
|
||||
return ("SkiaSharp-related", "*.NativeAssets");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarfBuzzSharp;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -12,14 +14,19 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
public static class FontManager
|
||||
{
|
||||
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
|
||||
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
|
||||
private static ConcurrentDictionary<object, SKPaint> FontPaints = new();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaints = new();
|
||||
private static ConcurrentDictionary<object, Font> ShaperFonts = new();
|
||||
private static ConcurrentDictionary<object, SKFont> Fonts = new();
|
||||
private static ConcurrentDictionary<object, TextShaper> TextShapers = new();
|
||||
private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
|
||||
private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, SKFont> Fonts = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, TextShaper> TextShapers = new();
|
||||
|
||||
static FontManager()
|
||||
{
|
||||
RegisterLibraryDefaultFonts();
|
||||
}
|
||||
|
||||
private static void RegisterFontType(SKData fontData, string? customName = null)
|
||||
{
|
||||
foreach (var index in Enumerable.Range(0, 256))
|
||||
@@ -36,8 +43,13 @@ namespace QuestPDF.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
|
||||
[Obsolete("Since version 2022.8 this method has been renamed. Please use the RegisterFontWithCustomName method.")]
|
||||
public static void RegisterFontType(string fontName, Stream stream)
|
||||
{
|
||||
RegisterFontWithCustomName(fontName, stream);
|
||||
}
|
||||
|
||||
public static void RegisterFontWithCustomName(string fontName, Stream stream)
|
||||
{
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
@@ -49,6 +61,38 @@ namespace QuestPDF.Drawing
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
}
|
||||
|
||||
public static void RegisterFontFromEmbeddedResource(string pathName)
|
||||
{
|
||||
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(pathName);
|
||||
RegisterFont(stream);
|
||||
}
|
||||
|
||||
private static void RegisterLibraryDefaultFonts()
|
||||
{
|
||||
var fontFileNames = new[]
|
||||
{
|
||||
"Lato-Black.ttf",
|
||||
"Lato-BlackItalic.ttf",
|
||||
|
||||
"Lato-Bold.ttf",
|
||||
"Lato-BoldItalic.ttf",
|
||||
|
||||
"Lato-Regular.ttf",
|
||||
"Lato-Italic.ttf",
|
||||
|
||||
"Lato-Light.ttf",
|
||||
"Lato-LightItalic.ttf",
|
||||
|
||||
"Lato-Thin.ttf",
|
||||
"Lato-ThinItalic.ttf"
|
||||
};
|
||||
|
||||
fontFileNames
|
||||
.Select(x => $"QuestPDF.Resources.DefaultFont.{x}")
|
||||
.ToList()
|
||||
.ForEach(RegisterFontFromEmbeddedResource);
|
||||
}
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
{
|
||||
@@ -66,7 +110,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal static SKPaint ToPaint(this TextStyle style)
|
||||
{
|
||||
return FontPaints.GetOrAdd(style.PaintKey, key => Convert(style));
|
||||
return FontPaints.GetOrAdd(style, Convert);
|
||||
|
||||
static SKPaint Convert(TextStyle style)
|
||||
{
|
||||
@@ -103,12 +147,15 @@ namespace QuestPDF.Drawing
|
||||
|
||||
if (fontFromDefaultSource != null)
|
||||
return fontFromDefaultSource;
|
||||
|
||||
var availableFontNames = string.Join(", ", SKFontManager.Default.GetFontFamilies());
|
||||
|
||||
throw new ArgumentException(
|
||||
$"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.");
|
||||
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method. " +
|
||||
$"Available font family names: [{availableFontNames}]");
|
||||
}
|
||||
|
||||
static float GetTextScale(TextStyle style)
|
||||
@@ -125,14 +172,14 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.NormalPosition().ToPaint().FontMetrics);
|
||||
return FontMetrics.GetOrAdd(style, key => key.NormalPosition().ToPaint().FontMetrics);
|
||||
}
|
||||
|
||||
internal static Font ToShaperFont(this TextStyle style)
|
||||
{
|
||||
return ShaperFonts.GetOrAdd(style.PaintKey, _ =>
|
||||
return ShaperFonts.GetOrAdd(style, key =>
|
||||
{
|
||||
var typeface = style.ToPaint().Typeface;
|
||||
var typeface = key.ToPaint().Typeface;
|
||||
|
||||
using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
|
||||
|
||||
@@ -153,12 +200,12 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal static TextShaper ToTextShaper(this TextStyle style)
|
||||
{
|
||||
return TextShapers.GetOrAdd(style.PaintKey, _ => new TextShaper(style));
|
||||
return TextShapers.GetOrAdd(style, key => new TextShaper(key));
|
||||
}
|
||||
|
||||
internal static SKFont FoFont(this TextStyle style)
|
||||
internal static SKFont ToFont(this TextStyle style)
|
||||
{
|
||||
return Fonts.GetOrAdd(style.PaintKey, _ => style.ToPaint().ToFont());
|
||||
return Fonts.GetOrAdd(style, key => key.ToPaint().ToFont());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarfBuzzSharp;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
@@ -9,24 +10,26 @@ namespace QuestPDF.Drawing
|
||||
internal class TextShaper
|
||||
{
|
||||
public const int FontShapingScale = 512;
|
||||
|
||||
private Font Font { get; }
|
||||
private SKPaint Paint { get; }
|
||||
|
||||
public TextShaper(TextStyle style)
|
||||
private TextStyle TextStyle { get; }
|
||||
|
||||
private SKFont Font => TextStyle.ToFont();
|
||||
private Font ShaperFont => TextStyle.ToShaperFont();
|
||||
private SKPaint Paint => TextStyle.ToPaint();
|
||||
|
||||
public TextShaper(TextStyle textStyle)
|
||||
{
|
||||
Font = style.ToShaperFont();
|
||||
Paint = style.ToPaint();
|
||||
TextStyle = textStyle;
|
||||
}
|
||||
|
||||
public TextShapingResult Shape(string text)
|
||||
{
|
||||
var buffer = new Buffer();
|
||||
using var buffer = new Buffer();
|
||||
|
||||
PopulateBufferWithText(buffer, text);
|
||||
buffer.GuessSegmentProperties();
|
||||
|
||||
Font.Shape(buffer);
|
||||
ShaperFont.Shape(buffer);
|
||||
|
||||
var length = buffer.Length;
|
||||
var glyphInfos = buffer.GlyphInfos;
|
||||
@@ -52,6 +55,9 @@ namespace QuestPDF.Drawing
|
||||
xOffset += glyphPositions[i].XAdvance * scaleX;
|
||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||
}
|
||||
|
||||
if (Settings.CheckIfAllTextGlyphsAreAvailableInSpecifiedFont)
|
||||
CheckIfAllGlyphsAreAvailable(glyphs, text);
|
||||
|
||||
return new TextShapingResult(glyphs);
|
||||
}
|
||||
@@ -72,6 +78,20 @@ namespace QuestPDF.Drawing
|
||||
else
|
||||
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
|
||||
}
|
||||
|
||||
void CheckIfAllGlyphsAreAvailable(ShapedGlyph[] glyphs, string originalText)
|
||||
{
|
||||
var containsMissingGlyphs = glyphs.Any(x => x.Codepoint == default);
|
||||
|
||||
if (!containsMissingGlyphs)
|
||||
return;
|
||||
|
||||
throw new ArgumentException(
|
||||
$"Detected missing font glyphs while rendering text. " +
|
||||
$"This means that the document contains text with characters not present in the assigned font. " +
|
||||
$"Such characters are replaced by placeholders, usually visible as empty rectangles. " +
|
||||
$"Font family used: {TextStyle.FontFamily}. Issue detected in text: '{originalText}'");
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ShapedGlyph
|
||||
@@ -129,10 +149,13 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
if (Glyphs.Length == 0)
|
||||
return null;
|
||||
|
||||
if (startIndex > endIndex)
|
||||
return null;
|
||||
|
||||
using var skTextBlobBuilder = new SKTextBlobBuilder();
|
||||
|
||||
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(textStyle.FoFont(), endIndex - startIndex + 1);
|
||||
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(textStyle.ToFont(), endIndex - startIndex + 1);
|
||||
var glyphSpan = positionedRunBuffer.GetGlyphSpan();
|
||||
var positionSpan = positionedRunBuffer.GetPositionSpan();
|
||||
|
||||
|
||||
@@ -41,5 +41,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})";
|
||||
}
|
||||
|
||||
public override void Collect()
|
||||
{
|
||||
base.Collect();
|
||||
|
||||
Left = 0;
|
||||
Right = 0;
|
||||
Bottom = 0;
|
||||
Top = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -12,7 +13,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Column : Element, ICacheable, IStateResettable
|
||||
internal class Column : Element, ICacheable, IStateResettable, ICollectable
|
||||
{
|
||||
internal List<ColumnItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
@@ -94,7 +94,12 @@ namespace QuestPDF.Elements
|
||||
if (item.IsRendered)
|
||||
continue;
|
||||
|
||||
var itemSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
|
||||
var availableHeight = availableSpace.Height - topOffset;
|
||||
|
||||
if (availableHeight < 0)
|
||||
break;
|
||||
|
||||
var itemSpace = new Size(availableSpace.Width, availableHeight);
|
||||
var measurement = item.Measure(itemSpace);
|
||||
|
||||
if (measurement.Type == SpacePlanType.Wrap)
|
||||
@@ -119,5 +124,10 @@ namespace QuestPDF.Elements
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Container : ContainerElement
|
||||
{
|
||||
internal Container()
|
||||
public Container()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
|
||||
private DynamicComponentProxy Child { get; }
|
||||
private object InitialComponentState { get; set; }
|
||||
|
||||
internal TextStyle TextStyle { get; } = new();
|
||||
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||
|
||||
public DynamicHost(DynamicComponentProxy child)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -11,7 +12,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return SpacePlan.FullRender(availableSpace.Width, availableSpace.Height);
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
@@ -9,7 +10,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return SpacePlan.FullRender(0, 0);
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(0, 0);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -15,7 +16,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
|
||||
@@ -19,6 +19,12 @@ namespace QuestPDF.Elements
|
||||
Justify,
|
||||
SpaceAround
|
||||
}
|
||||
|
||||
internal struct InlinedMeasurement
|
||||
{
|
||||
public Element Element { get; set; }
|
||||
public SpacePlan Size { get; set; }
|
||||
}
|
||||
|
||||
internal class Inlined : Element, IStateResettable
|
||||
{
|
||||
@@ -80,10 +86,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var height = line
|
||||
.Select(x => x.Measure(Size.Max))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.Max(x => x.Height);
|
||||
var height = line.Max(x => x.Size.Height);
|
||||
|
||||
DrawLine(line);
|
||||
|
||||
@@ -94,24 +97,24 @@ namespace QuestPDF.Elements
|
||||
Canvas.Translate(new Position(0, -topOffset));
|
||||
lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue());
|
||||
|
||||
void DrawLine(ICollection<InlinedElement> elements)
|
||||
void DrawLine(ICollection<InlinedMeasurement> lineMeasurements)
|
||||
{
|
||||
var lineSize = GetLineSize(elements);
|
||||
var lineSize = GetLineSize(lineMeasurements);
|
||||
|
||||
var elementOffset = ElementOffset();
|
||||
var leftOffset = AlignOffset();
|
||||
Canvas.Translate(new Position(leftOffset, 0));
|
||||
|
||||
foreach (var element in elements)
|
||||
foreach (var measurement in lineMeasurements)
|
||||
{
|
||||
var size = (Size)element.Measure(Size.Max);
|
||||
var size = (Size)measurement.Size;
|
||||
var baselineOffset = BaselineOffset(size, lineSize.Height);
|
||||
|
||||
if (size.Height == 0)
|
||||
size = new Size(size.Width, lineSize.Height);
|
||||
|
||||
Canvas.Translate(new Position(0, baselineOffset));
|
||||
element.Draw(size);
|
||||
measurement.Element.Draw(size);
|
||||
Canvas.Translate(new Position(0, -baselineOffset));
|
||||
|
||||
leftOffset += size.Width + elementOffset;
|
||||
@@ -124,20 +127,20 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
var difference = availableSpace.Width - lineSize.Width;
|
||||
|
||||
if (elements.Count == 1)
|
||||
if (lineMeasurements.Count == 1)
|
||||
return 0;
|
||||
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.Justify => difference / (elements.Count - 1),
|
||||
InlinedAlignment.SpaceAround => difference / (elements.Count + 1),
|
||||
InlinedAlignment.Justify => difference / (lineMeasurements.Count - 1),
|
||||
InlinedAlignment.SpaceAround => difference / (lineMeasurements.Count + 1),
|
||||
_ => HorizontalSpacing
|
||||
};
|
||||
}
|
||||
|
||||
float AlignOffset()
|
||||
{
|
||||
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
|
||||
var difference = availableSpace.Width - lineSize.Width - (lineMeasurements.Count - 1) * HorizontalSpacing;
|
||||
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
@@ -164,24 +167,19 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
Size GetLineSize(ICollection<InlinedElement> elements)
|
||||
Size GetLineSize(ICollection<InlinedMeasurement> measurements)
|
||||
{
|
||||
var sizes = elements
|
||||
.Select(x => x.Measure(Size.Max))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.ToList();
|
||||
|
||||
var width = sizes.Sum(x => x.Width);
|
||||
var height = sizes.Max(x => x.Height);
|
||||
var width = measurements.Sum(x => x.Size.Width);
|
||||
var height = measurements.Max(x => x.Size.Height);
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
// list of lines, each line is a list of elements
|
||||
private ICollection<ICollection<InlinedElement>> Compose(Size availableSize)
|
||||
private ICollection<ICollection<InlinedMeasurement>> Compose(Size availableSize)
|
||||
{
|
||||
var queue = new Queue<InlinedElement>(ChildrenQueue);
|
||||
var result = new List<ICollection<InlinedElement>>();
|
||||
var result = new List<ICollection<InlinedMeasurement>>();
|
||||
|
||||
var topOffset = 0f;
|
||||
|
||||
@@ -192,10 +190,7 @@ namespace QuestPDF.Elements
|
||||
if (!line.Any())
|
||||
break;
|
||||
|
||||
var height = line
|
||||
.Select(x => x.Measure(availableSize))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.Max(x => x.Height);
|
||||
var height = line.Max(x => x.Size.Height);
|
||||
|
||||
if (topOffset + height > availableSize.Height + Size.Epsilon)
|
||||
break;
|
||||
@@ -206,9 +201,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
return result;
|
||||
|
||||
ICollection<InlinedElement> GetNextLine()
|
||||
ICollection<InlinedMeasurement> GetNextLine()
|
||||
{
|
||||
var result = new List<InlinedElement>();
|
||||
var result = new List<InlinedMeasurement>();
|
||||
var leftOffset = GetInitialAlignmentOffset();
|
||||
|
||||
while (true)
|
||||
@@ -227,7 +222,12 @@ namespace QuestPDF.Elements
|
||||
|
||||
queue.Dequeue();
|
||||
leftOffset += size.Width + HorizontalSpacing;
|
||||
result.Add(element);
|
||||
|
||||
result.Add(new InlinedMeasurement
|
||||
{
|
||||
Element = element,
|
||||
Size = size
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
return Type switch
|
||||
{
|
||||
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0),
|
||||
|
||||
@@ -4,7 +4,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Padding : ContainerElement, ICacheable
|
||||
internal class Padding : ContainerElement, ICacheable, ICollectable
|
||||
{
|
||||
public float Top { get; set; }
|
||||
public float Right { get; set; }
|
||||
@@ -62,5 +62,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})";
|
||||
}
|
||||
|
||||
public override void Collect()
|
||||
{
|
||||
base.Collect();
|
||||
|
||||
Left = 0;
|
||||
Right = 0;
|
||||
Bottom = 0;
|
||||
Top = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
@@ -14,6 +15,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered)
|
||||
return SpacePlan.FullRender(0, 0);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Row : Element, ICacheable, IStateResettable
|
||||
internal class Row : Element, ICacheable, IStateResettable, ICollectable
|
||||
{
|
||||
internal List<RowItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
@@ -156,5 +156,10 @@ namespace QuestPDF.Elements
|
||||
|
||||
return renderingCommands;
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,9 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Cells.ForEach(x => x.IsRendered = false);
|
||||
foreach (var x in Cells)
|
||||
x.IsRendered = false;
|
||||
|
||||
CurrentRow = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
internal class TextBlockSpan : ITextBlockItem
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextStyle Style { get; set; } = new();
|
||||
public TextStyle Style { get; set; } = TextStyle.Default;
|
||||
public TextShapingResult? TextShapingResult { get; set; }
|
||||
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
|
||||
@@ -66,7 +66,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// start breaking text from requested position
|
||||
var endIndex = TextShapingResult.BreakText(startIndex, request.AvailableWidth);
|
||||
|
||||
if (endIndex < 0)
|
||||
if (endIndex < startIndex)
|
||||
return null;
|
||||
|
||||
// break text only on spaces
|
||||
@@ -125,7 +125,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
}
|
||||
|
||||
// text contains space that can be used to wrap
|
||||
if (lastSpaceIndex >= startIndex)
|
||||
if (lastSpaceIndex > 1 && lastSpaceIndex >= startIndex)
|
||||
return (lastSpaceIndex - 1, lastSpaceIndex + 1);
|
||||
|
||||
// there is no available space to wrap text
|
||||
|
||||
@@ -8,20 +8,40 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
internal class TextBlock : Element, IStateResettable, ICollectable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
|
||||
|
||||
|
||||
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
private int CurrentElementIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Items);
|
||||
InitializeQueue();
|
||||
CurrentElementIndex = 0;
|
||||
|
||||
void InitializeQueue()
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (RenderingQueue == null)
|
||||
{
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Items);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderingQueue.Clear();
|
||||
|
||||
foreach (var item in Items)
|
||||
RenderingQueue.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
@@ -108,7 +128,7 @@ namespace QuestPDF.Elements.Text
|
||||
.ForEach(x => RenderingQueue.Dequeue());
|
||||
|
||||
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
|
||||
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.EndIndex;
|
||||
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
ResetState();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
private static IContainer Border(this IContainer element, Action<Border> handler)
|
||||
{
|
||||
var border = element as Border ?? new Border();
|
||||
var border = element as Border ?? ElementCacheManager.Get<Border>();
|
||||
handler(border);
|
||||
|
||||
return element.Element(border);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Container = System.ComponentModel.Container;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class ColumnDescriptor
|
||||
{
|
||||
internal Column Column { get; } = new();
|
||||
internal Column Column { get; set; }
|
||||
|
||||
public void Spacing(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
@@ -15,14 +17,9 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
Column.Items.Add(new ColumnItem
|
||||
{
|
||||
Child = container
|
||||
});
|
||||
|
||||
return container;
|
||||
var columnItem = ElementCacheManager.Get<ColumnItem>();
|
||||
Column.Items.Add(columnItem);
|
||||
return columnItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +33,8 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
|
||||
{
|
||||
var descriptor = new ColumnDescriptor();
|
||||
var descriptor = ElementCacheManager.Get<ColumnDescriptor>();
|
||||
descriptor.Column = ElementCacheManager.Get<Column>();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.Column);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -52,10 +53,10 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer Background(this IContainer element, string color)
|
||||
{
|
||||
return element.Element(new Background
|
||||
{
|
||||
Color = color
|
||||
});
|
||||
var background = ElementCacheManager.Get<Background>();
|
||||
background.Color = color;
|
||||
|
||||
return element.Element(background);
|
||||
}
|
||||
|
||||
public static void Placeholder(this IContainer element, string? text = null)
|
||||
@@ -162,7 +163,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer MinimalBox(this IContainer element)
|
||||
{
|
||||
return element.Element(new MinimalBox());
|
||||
return element.Element(ElementCacheManager.Get<MinimalBox>());
|
||||
}
|
||||
|
||||
public static IContainer Unconstrained(this IContainer element)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
private static IContainer Padding(this IContainer element, Action<Padding> handler)
|
||||
{
|
||||
var padding = element as Padding ?? new Padding();
|
||||
var padding = element as Padding ?? ElementCacheManager.Get<Padding>();
|
||||
handler(padding);
|
||||
|
||||
return element.Element(padding);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -6,7 +7,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public class RowDescriptor
|
||||
{
|
||||
internal Row Row { get; } = new();
|
||||
internal Row Row { get; set; }
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
@@ -15,14 +16,12 @@ namespace QuestPDF.Fluent
|
||||
|
||||
private IContainer Item(RowItemType type, float size = 0)
|
||||
{
|
||||
var element = new RowItem
|
||||
{
|
||||
Type = type,
|
||||
Size = size
|
||||
};
|
||||
var rowItem = ElementCacheManager.Get<RowItem>();
|
||||
rowItem.Type = type;
|
||||
rowItem.Size = size;
|
||||
|
||||
Row.Items.Add(element);
|
||||
return element;
|
||||
Row.Items.Add(rowItem);
|
||||
return rowItem;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
|
||||
@@ -57,9 +56,12 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static void Row(this IContainer element, Action<RowDescriptor> handler)
|
||||
{
|
||||
var descriptor = new RowDescriptor();
|
||||
var descriptor = ElementCacheManager.Get<RowDescriptor>();
|
||||
descriptor.Row = ElementCacheManager.Get<Row>();
|
||||
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.Row);
|
||||
ElementCacheManager.Store(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,18 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextSpanDescriptor
|
||||
{
|
||||
internal TextStyle TextStyle { get; }
|
||||
internal TextStyle TextStyle = TextStyle.Default;
|
||||
internal Action<TextStyle> AssignTextStyle { get; }
|
||||
|
||||
internal TextSpanDescriptor(TextStyle textStyle)
|
||||
internal TextSpanDescriptor(Action<TextStyle> assignTextStyle)
|
||||
{
|
||||
TextStyle = textStyle;
|
||||
AssignTextStyle = assignTextStyle;
|
||||
}
|
||||
|
||||
internal void MutateTextStyle(Func<TextStyle, TextStyle> handler)
|
||||
{
|
||||
TextStyle = handler(TextStyle);
|
||||
AssignTextStyle(TextStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +31,16 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public class TextPageNumberDescriptor : TextSpanDescriptor
|
||||
{
|
||||
internal PageNumberFormatter FormatFunction { get; private set; } = x => x?.ToString() ?? string.Empty;
|
||||
|
||||
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
|
||||
internal Action<PageNumberFormatter> AssignFormatFunction { get; }
|
||||
|
||||
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
|
||||
{
|
||||
|
||||
AssignFormatFunction = assignFormatFunction;
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
||||
{
|
||||
FormatFunction = formatter ?? FormatFunction;
|
||||
AssignFormatFunction(formatter);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +48,7 @@ namespace QuestPDF.Fluent
|
||||
public class TextDescriptor
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
|
||||
private TextStyle? DefaultStyle { get; set; }
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
@@ -91,19 +98,15 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public TextSpanDescriptor Span(string? text)
|
||||
{
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextSpanDescriptor(style);
|
||||
|
||||
if (text == null)
|
||||
return descriptor;
|
||||
return new TextSpanDescriptor(_ => { });
|
||||
|
||||
var items = text
|
||||
.Replace("\r", string.Empty)
|
||||
.Split(new[] { '\n' }, StringSplitOptions.None)
|
||||
.Select(x => new TextBlockSpan
|
||||
{
|
||||
Text = x,
|
||||
Style = style
|
||||
Text = x
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@@ -118,7 +121,7 @@ namespace QuestPDF.Fluent
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
|
||||
return descriptor;
|
||||
return new TextSpanDescriptor(x => items.ForEach(y => y.Style = x));
|
||||
}
|
||||
|
||||
public TextSpanDescriptor Line(string? text)
|
||||
@@ -134,16 +137,10 @@ namespace QuestPDF.Fluent
|
||||
|
||||
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
|
||||
{
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextPageNumberDescriptor(style);
|
||||
var textBlockItem = new TextBlockPageNumber();
|
||||
AddItemToLastTextBlock(textBlockItem);
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber
|
||||
{
|
||||
Source = context => descriptor.FormatFunction(pageNumber(context)),
|
||||
Style = style
|
||||
});
|
||||
|
||||
return descriptor;
|
||||
return new TextPageNumberDescriptor(x => textBlockItem.Style = x, x => textBlockItem.Source = context => x(pageNumber(context)));
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor CurrentPageNumber()
|
||||
@@ -187,20 +184,17 @@ namespace QuestPDF.Fluent
|
||||
if (IsNullOrEmpty(sectionName))
|
||||
throw new ArgumentException("Section name cannot be null or empty", nameof(sectionName));
|
||||
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextSpanDescriptor(style);
|
||||
|
||||
if (IsNullOrEmpty(text))
|
||||
return descriptor;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockSectionLink
|
||||
return new TextSpanDescriptor(_ => { });
|
||||
|
||||
var textBlockItem = new TextBlockSectionLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
SectionName = sectionName
|
||||
});
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
AddItemToLastTextBlock(textBlockItem);
|
||||
return new TextSpanDescriptor(x => textBlockItem.Style = x);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
|
||||
@@ -214,20 +208,17 @@ namespace QuestPDF.Fluent
|
||||
if (IsNullOrEmpty(url))
|
||||
throw new ArgumentException("Url cannot be null or empty", nameof(url));
|
||||
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextSpanDescriptor(style);
|
||||
|
||||
if (IsNullOrEmpty(text))
|
||||
return descriptor;
|
||||
return new TextSpanDescriptor(_ => { });
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockHyperlink
|
||||
var textBlockItem = new TextBlockHyperlink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
Url = url
|
||||
});
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
AddItemToLastTextBlock(textBlockItem);
|
||||
return new TextSpanDescriptor(x => textBlockItem.Style = x);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
|
||||
@@ -251,14 +242,23 @@ namespace QuestPDF.Fluent
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
if (DefaultStyle != null)
|
||||
container = container.DefaultTextStyle(DefaultStyle);
|
||||
|
||||
container.DefaultTextStyle(DefaultStyle).Column(column =>
|
||||
if (TextBlocks.Count == 1)
|
||||
{
|
||||
container.Element(TextBlocks.First());
|
||||
return;
|
||||
}
|
||||
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(Spacing);
|
||||
|
||||
foreach (var textBlock in TextBlocks)
|
||||
column.Item().Element(textBlock);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,120 +11,124 @@ namespace QuestPDF.Fluent
|
||||
if (style == null)
|
||||
return descriptor;
|
||||
|
||||
descriptor.TextStyle.OverrideStyle(style);
|
||||
descriptor.MutateTextStyle(x => x.OverrideStyle(style));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Color = value;
|
||||
descriptor.MutateTextStyle(x => x.FontColor(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.BackgroundColor = value;
|
||||
descriptor.MutateTextStyle(x => x.BackgroundColor(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontFamily = value;
|
||||
descriptor.MutateTextStyle(x => x.FontFamily(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Size = value;
|
||||
descriptor.MutateTextStyle(x => x.FontSize(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.LineHeight = value;
|
||||
descriptor.MutateTextStyle(x => x.LineHeight(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.IsItalic = value;
|
||||
descriptor.MutateTextStyle(x => x.Italic(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasStrikethrough = value;
|
||||
descriptor.MutateTextStyle(x => x.Strikethrough(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasUnderline = value;
|
||||
descriptor.MutateTextStyle(x => x.Underline(value));
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.WrapAnywhere = value;
|
||||
descriptor.MutateTextStyle(x => x.WrapAnywhere(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);
|
||||
descriptor.MutateTextStyle(x => x.Thin());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraLight);
|
||||
descriptor.MutateTextStyle(x => x.ExtraLight());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Light);
|
||||
descriptor.MutateTextStyle(x => x.Light());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Normal);
|
||||
descriptor.MutateTextStyle(x => x.NormalWeight());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Medium);
|
||||
descriptor.MutateTextStyle(x => x.Medium());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.SemiBold);
|
||||
descriptor.MutateTextStyle(x => x.SemiBold());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Bold);
|
||||
descriptor.MutateTextStyle(x => x.Bold());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBold);
|
||||
descriptor.MutateTextStyle(x => x.ExtraBold());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Black);
|
||||
descriptor.MutateTextStyle(x => x.Black());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBlack);
|
||||
descriptor.MutateTextStyle(x => x.ExtraBlack());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -132,24 +136,22 @@ namespace QuestPDF.Fluent
|
||||
#region Position
|
||||
public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Normal);
|
||||
descriptor.MutateTextStyle(x => x.NormalPosition());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Subscript);
|
||||
descriptor.MutateTextStyle(x => x.Subscript());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Superscript);
|
||||
}
|
||||
|
||||
private static T Position<T>(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontPosition = fontPosition;
|
||||
descriptor.MutateTextStyle(x => x.Superscript());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,10 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
|
||||
|
||||
public static class TextStyleExtensions
|
||||
{
|
||||
private static TextStyle Mutate(this TextStyle style, Action<TextStyle> handler)
|
||||
{
|
||||
style = style.Clone();
|
||||
|
||||
handler(style);
|
||||
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)
|
||||
{
|
||||
@@ -22,12 +16,12 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static TextStyle FontColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.Color = value);
|
||||
return style.Mutate(TextStyleProperty.Color, value);
|
||||
}
|
||||
|
||||
public static TextStyle BackgroundColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.BackgroundColor = value);
|
||||
return style.Mutate(TextStyleProperty.BackgroundColor, value);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
|
||||
@@ -38,7 +32,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static TextStyle FontFamily(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontFamily = value);
|
||||
return style.Mutate(TextStyleProperty.FontFamily, value);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
|
||||
@@ -49,39 +43,39 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static TextStyle FontSize(this TextStyle style, float value)
|
||||
{
|
||||
return style.Mutate(x => x.Size = value);
|
||||
return style.Mutate(TextStyleProperty.Size, value);
|
||||
}
|
||||
|
||||
public static TextStyle LineHeight(this TextStyle style, float value)
|
||||
{
|
||||
return style.Mutate(x => x.LineHeight = value);
|
||||
return style.Mutate(TextStyleProperty.LineHeight, value);
|
||||
}
|
||||
|
||||
public static TextStyle Italic(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsItalic = value);
|
||||
return style.Mutate(TextStyleProperty.IsItalic, value);
|
||||
}
|
||||
|
||||
public static TextStyle Strikethrough(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.HasStrikethrough = value);
|
||||
return style.Mutate(TextStyleProperty.HasStrikethrough, value);
|
||||
}
|
||||
|
||||
public static TextStyle Underline(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.HasUnderline = value);
|
||||
return style.Mutate(TextStyleProperty.HasUnderline, value);
|
||||
}
|
||||
|
||||
public static TextStyle WrapAnywhere(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.WrapAnywhere = value);
|
||||
return style.Mutate(TextStyleProperty.WrapAnywhere, value);
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
public static TextStyle Weight(this TextStyle style, FontWeight weight)
|
||||
{
|
||||
return style.Mutate(x => x.FontWeight = weight);
|
||||
return style.Mutate(TextStyleProperty.FontWeight, weight);
|
||||
}
|
||||
|
||||
public static TextStyle Thin(this TextStyle style)
|
||||
@@ -154,10 +148,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
private static TextStyle Position(this TextStyle style, FontPosition fontPosition)
|
||||
{
|
||||
if (style.FontPosition == fontPosition)
|
||||
return style;
|
||||
|
||||
return style.Mutate(t => t.FontPosition = fontPosition);
|
||||
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
public const string CourierNew = "Courier New";
|
||||
public const string Georgia = "Georgia";
|
||||
public const string Impact = "Impact";
|
||||
public const string Lato = "Lato";
|
||||
public const string LucidaConsole = "Lucida Console";
|
||||
public const string SegoeSD = "Segoe SD";
|
||||
public const string SegoeUI = "Segoe UI";
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Helpers
|
||||
@@ -41,15 +42,23 @@ namespace QuestPDF.Helpers
|
||||
|
||||
internal static string PrettifyName(this string text)
|
||||
{
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
internal static void VisitChildren(this Element? element, Action<Element?> handler)
|
||||
{
|
||||
foreach (var child in element.GetChildren().Where(x => x != null))
|
||||
VisitChildren(child, handler);
|
||||
if (element == null)
|
||||
return;
|
||||
|
||||
foreach (var child in element.GetChildren())
|
||||
VisitChildren(child, handler);
|
||||
|
||||
handler(element);
|
||||
}
|
||||
|
||||
internal static bool IsNegative(this Size size)
|
||||
{
|
||||
return size.Width < 0f || size.Height < 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using QuestPDF.Elements;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal abstract class ContainerElement : Element, IContainer
|
||||
internal abstract class ContainerElement : Element, IContainer, ICollectable
|
||||
{
|
||||
internal Element? Child { get; set; } = Empty.Instance;
|
||||
|
||||
@@ -34,5 +34,10 @@ namespace QuestPDF.Infrastructure
|
||||
{
|
||||
Child?.Draw(availableSpace);
|
||||
}
|
||||
|
||||
public virtual void Collect()
|
||||
{
|
||||
Child = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public interface ICollectable
|
||||
{
|
||||
void Collect();
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class TextStyle
|
||||
public record TextStyle
|
||||
{
|
||||
internal bool HasGlobalStyleApplied { get; private set; }
|
||||
|
||||
internal string? Color { get; set; }
|
||||
internal string? BackgroundColor { get; set; }
|
||||
internal string? FontFamily { get; set; }
|
||||
@@ -19,14 +17,11 @@ namespace QuestPDF.Infrastructure
|
||||
internal bool? HasUnderline { get; set; }
|
||||
internal bool? WrapAnywhere { get; set; }
|
||||
|
||||
internal object PaintKey { get; private set; }
|
||||
internal object FontMetricsKey { get; private set; }
|
||||
|
||||
internal static TextStyle LibraryDefault => new TextStyle
|
||||
internal static TextStyle LibraryDefault { get; } = new()
|
||||
{
|
||||
Color = Colors.Black,
|
||||
BackgroundColor = Colors.Transparent,
|
||||
FontFamily = Fonts.Calibri,
|
||||
FontFamily = Fonts.Lato,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
@@ -37,55 +32,6 @@ namespace QuestPDF.Infrastructure
|
||||
WrapAnywhere = false
|
||||
};
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
internal void ApplyGlobalStyle(TextStyle globalStyle)
|
||||
{
|
||||
if (HasGlobalStyleApplied)
|
||||
return;
|
||||
|
||||
HasGlobalStyleApplied = true;
|
||||
|
||||
ApplyParentStyle(globalStyle);
|
||||
PaintKey ??= (FontFamily, Size, FontWeight, FontPosition, IsItalic, Color);
|
||||
FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
|
||||
}
|
||||
|
||||
internal void ApplyParentStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color ??= parentStyle.Color;
|
||||
BackgroundColor ??= parentStyle.BackgroundColor;
|
||||
FontFamily ??= parentStyle.FontFamily;
|
||||
Size ??= parentStyle.Size;
|
||||
LineHeight ??= parentStyle.LineHeight;
|
||||
FontWeight ??= parentStyle.FontWeight;
|
||||
FontPosition ??= parentStyle.FontPosition;
|
||||
IsItalic ??= parentStyle.IsItalic;
|
||||
HasStrikethrough ??= parentStyle.HasStrikethrough;
|
||||
HasUnderline ??= parentStyle.HasUnderline;
|
||||
WrapAnywhere ??= parentStyle.WrapAnywhere;
|
||||
}
|
||||
|
||||
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;
|
||||
FontPosition = parentStyle.FontPosition ?? FontPosition;
|
||||
IsItalic = parentStyle.IsItalic ?? IsItalic;
|
||||
HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
|
||||
HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
|
||||
WrapAnywhere = parentStyle.WrapAnywhere ?? WrapAnywhere;
|
||||
}
|
||||
|
||||
internal TextStyle Clone()
|
||||
{
|
||||
var clone = (TextStyle)MemberwiseClone();
|
||||
clone.HasGlobalStyleApplied = false;
|
||||
return clone;
|
||||
}
|
||||
public static TextStyle Default { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using QuestPDF.Fluent;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal enum TextStyleProperty
|
||||
{
|
||||
Color,
|
||||
BackgroundColor,
|
||||
FontFamily,
|
||||
Size,
|
||||
LineHeight,
|
||||
FontWeight,
|
||||
FontPosition,
|
||||
IsItalic,
|
||||
HasStrikethrough,
|
||||
HasUnderline,
|
||||
WrapAnywhere
|
||||
}
|
||||
|
||||
internal static class TextStyleManager
|
||||
{
|
||||
public static ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
|
||||
public static ConcurrentDictionary<(TextStyle origin, TextStyle parent, bool overrideValue), TextStyle> TextStyleApplyCache = new();
|
||||
|
||||
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
|
||||
{
|
||||
var cacheKey = (origin, property, value);
|
||||
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value));
|
||||
}
|
||||
|
||||
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object value, bool overrideValue = true)
|
||||
{
|
||||
if (overrideValue && value == null)
|
||||
return origin;
|
||||
|
||||
if (property == TextStyleProperty.Color)
|
||||
{
|
||||
if (!overrideValue && origin.Color != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (string?)value;
|
||||
|
||||
if (origin.Color == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { Color = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.BackgroundColor)
|
||||
{
|
||||
if (!overrideValue && origin.BackgroundColor != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (string?)value;
|
||||
|
||||
if (origin.BackgroundColor == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { BackgroundColor = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.FontFamily)
|
||||
{
|
||||
if (!overrideValue && origin.FontFamily != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (string?)value;
|
||||
|
||||
if (origin.FontFamily == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { FontFamily = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.Size)
|
||||
{
|
||||
if (!overrideValue && origin.Size != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (float?)value;
|
||||
|
||||
if (origin.Size == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { Size = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.LineHeight)
|
||||
{
|
||||
if (!overrideValue && origin.LineHeight != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (float?)value;
|
||||
|
||||
if (origin.LineHeight == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { LineHeight = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.FontWeight)
|
||||
{
|
||||
if (!overrideValue && origin.FontWeight != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (FontWeight?)value;
|
||||
|
||||
if (origin.FontWeight == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { FontWeight = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.FontPosition)
|
||||
{
|
||||
if (!overrideValue && origin.FontPosition != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (FontPosition?)value;
|
||||
|
||||
if (origin.FontPosition == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { FontPosition = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.IsItalic)
|
||||
{
|
||||
if (!overrideValue && origin.IsItalic != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (bool?)value;
|
||||
|
||||
if (origin.IsItalic == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { IsItalic = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.HasStrikethrough)
|
||||
{
|
||||
if (!overrideValue && origin.HasStrikethrough != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (bool?)value;
|
||||
|
||||
if (origin.HasStrikethrough == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { HasStrikethrough = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.HasUnderline)
|
||||
{
|
||||
if (!overrideValue && origin.HasUnderline != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (bool?)value;
|
||||
|
||||
if (origin.HasUnderline == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { HasUnderline = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.WrapAnywhere)
|
||||
{
|
||||
if (!overrideValue && origin.WrapAnywhere != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (bool?)value;
|
||||
|
||||
if (origin.WrapAnywhere == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { WrapAnywhere = castedValue };
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
|
||||
}
|
||||
|
||||
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
|
||||
{
|
||||
var cacheKey = (style, parent, false);
|
||||
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
||||
}
|
||||
|
||||
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
||||
{
|
||||
var cacheKey = (style, parent, true);
|
||||
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
||||
}
|
||||
|
||||
private static TextStyle ApplyStyle(TextStyle style, TextStyle parent, bool overrideValue)
|
||||
{
|
||||
var result = style;
|
||||
|
||||
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideValue);
|
||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideValue);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ namespace QuestPDF.Previewer
|
||||
private HttpClient HttpClient { get; }
|
||||
|
||||
public event Action? OnPreviewerStopped;
|
||||
|
||||
private const int RequiredPreviewerVersionMajor = 2022;
|
||||
private const int RequiredPreviewerVersionMinor = 8;
|
||||
|
||||
public PreviewerService(int port)
|
||||
{
|
||||
@@ -94,12 +97,12 @@ namespace QuestPDF.Previewer
|
||||
|
||||
private void CheckVersionCompatibility(Version version)
|
||||
{
|
||||
if (version.Major == 2022 && version.Minor == 6)
|
||||
if (version.Major == RequiredPreviewerVersionMajor && version.Minor == RequiredPreviewerVersionMinor)
|
||||
return;
|
||||
|
||||
throw new Exception($"Previewer version is not compatible. Possible solutions: " +
|
||||
$"1) Update the QuestPDF library to newer version. " +
|
||||
$"2) Update the QuestPDF previewer tool using the following command: 'dotnet tool update --global QuestPDF.Previewer --version 2022.5'");
|
||||
$"2) Update the QuestPDF previewer tool using the following command: 'dotnet tool update --global QuestPDF.Previewer --version {RequiredPreviewerVersionMajor}.{RequiredPreviewerVersionMinor}'");
|
||||
}
|
||||
|
||||
private async Task WaitForConnection()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.6.0</Version>
|
||||
<Version>2022.8.2</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>
|
||||
@@ -44,5 +44,25 @@
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Remove="Resources\DefaultFont\Lato-Black.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-BlackItalic.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-Bold.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Bold.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-BoldItalic.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-BoldItalic.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-Italic.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Italic.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-Light.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Light.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-LightItalic.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-LightItalic.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-Regular.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Regular.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-Thin.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Thin.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-ThinItalic.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-ThinItalic.ttf" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,78 @@
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
|
||||
[](https://github.com/sponsors/QuestPDF)
|
||||
|
||||
QuestPDF is an open-source .NET library for PDF documents generation.
|
||||
|
||||
It offers a layout engine designed with a full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.
|
||||
|
||||
## Documentation
|
||||
|
||||
[](https://www.questpdf.com/getting-started.html)
|
||||
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/api-reference/index.html)
|
||||
A detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/design-patterns.html)
|
||||
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
||||
|
||||
## Simplicity is the key
|
||||
|
||||
How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
|
||||
|
||||
```#
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
// code in your main method
|
||||
Document.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.Background(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(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();
|
||||
});
|
||||
});
|
||||
})
|
||||
.GeneratePdf("hello.pdf");
|
||||
```
|
||||
|
||||
And compare it to the produced PDF file:
|
||||
|
||||

|
||||
|
||||
## Are you ready for more?
|
||||
|
||||
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).
|
||||
|
||||

|
||||
@@ -1,9 +1,20 @@
|
||||
Integrated the text-shaping algorithm. This change significantly improves the Unicode compatibility. Also, it extends support for more advanced languages (e.g. Arabic) that:
|
||||
1) Combine multiple text characters and display them as a single visual glyph.
|
||||
2) Are displayed in the right-to-left order.
|
||||
2022.8.0:
|
||||
|
||||
Improved the exception message when SkiaSharp throws the TypeInitializationException. On some operating systems, SkiaSharp requires additional dependencies installed as nuget packages. This change should help developers determine how to choose and install them correctly.
|
||||
- Improved library performance,
|
||||
- Breaking change: changed default font from Calibri to an open-source Lato,
|
||||
- Default font files are included with the nuget package, making it safe to deploy on any environment,
|
||||
- Default font files are significantly smaller, so output document files should be smaller too (up to 20x reduction in size),
|
||||
- When requested font is not available on the runtime environment, library provides list of available fonts,
|
||||
- Fixed a rare layout overflow exception with the Inlined element,
|
||||
- Fixed a memory leak connected to the HarfBuzz library.
|
||||
|
||||
Fixed: a rare case when the Row.AutoItem() does not correctly calculate the width of its content.
|
||||
|
||||
Fixed: the QuestPDF Previewer does not work with content-rich documents.
|
||||
2022.8.1:
|
||||
- Fixed: default text style does not always work
|
||||
- Fixed: page breaking rendering does not work in very specific corner cases
|
||||
- Stability improvements for text wrapping
|
||||
- Updated stability of rendering elements in negative space
|
||||
- Optimization for the Column element: do not measure child when available height is negative
|
||||
|
||||
2022.8.2
|
||||
- Fixed: the Column element incorrectly renders zero-height elements.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace QuestPDF
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// This value represents the maximum lenght of the document that the library produces.
|
||||
/// This is useful when layout constraints are too strong, e.g. one element does not fit in another.
|
||||
/// In such cases, the library would produce document of infinite length, consuming all available resources.
|
||||
/// To break the algorithm and save the environment, the library breaks the rendering process after reaching specified length of document.
|
||||
/// If your content requires generating longer documents, please assign the most reasonable value.
|
||||
/// </summary>
|
||||
public static int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
|
||||
/// <summary>
|
||||
/// This flag generates additional document elements to cache layout calculation results.
|
||||
/// In the vast majority of cases, this significantly improves performance, while slightly increasing memory consumption.
|
||||
/// </summary>
|
||||
/// <remarks>By default, this flag is enabled only when the debugger is NOT attached.</remarks>
|
||||
public static bool EnableCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
/// <summary>
|
||||
/// This flag generates additional document elements to improve layout debugging experience.
|
||||
/// When the DocumentLayoutException is thrown, the library is able to provide additional execution context.
|
||||
/// It includes layout calculation results and path to the problematic area.
|
||||
/// </summary>
|
||||
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
||||
public static bool EnableDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
/// <summary>
|
||||
/// This flag enables checking the font glyph availability.
|
||||
/// If your text contains glyphs that are not present in the specified font,
|
||||
/// 1) when this flag is enabled: the DocumentDrawingException is thrown. OR
|
||||
/// 2) when this flag is disabled: placeholder characters are visible in the produced PDF file.
|
||||
/// Enabling this flag may slightly decrease document generation performance.
|
||||
/// However, it provides hints that used fonts are not sufficient to produce correct results.
|
||||
/// </summary>
|
||||
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
||||
public static bool CheckIfAllTextGlyphsAreAvailableInSpecifiedFont { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.300",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<a href="https://www.questpdf.com/" target="_blank">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="300">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="400">
|
||||
</a>
|
||||
|
||||
---
|
||||
@@ -23,6 +23,8 @@ Choosing a project dependency could be difficult. We need to ensure stability an
|
||||
|
||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/9263853/184642026-27dd7567-a46a-45d4-9594-e6a70a7193e9.png" width="700" />
|
||||
|
||||
## Please share with the community
|
||||
|
||||
As an open-source project without funding, I cannot afford advertising QuestPDF in a typical way. Instead, the library relies on community interactions. Please consider sharing a post about QuestPDF and the value it provides. It really does help!
|
||||
@@ -66,28 +68,25 @@ dotnet add package QuestPDF
|
||||
|
||||
## Documentation
|
||||
|
||||
[](https://www.questpdf.com/documentation/getting-started.html)
|
||||
[](https://www.questpdf.com/getting-started.html)
|
||||
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/documentation/api-reference.html)
|
||||
[](https://www.questpdf.com/api-reference/index.html)
|
||||
A detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)
|
||||
[](https://www.questpdf.com/design-patterns.html)
|
||||
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
||||
|
||||
## QuestPDF Previewer
|
||||
|
||||
The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task!
|
||||
|
||||
[](https://www.questpdf.com/documentation/document-previewer.html)
|
||||
|
||||
<video src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/a6f54912ee761af14dfbe1f96aa70d7fcf7ff94f/images/previewer/video.mp4?raw=true"></video>
|
||||
[](https://www.questpdf.com/document-previewer.html)
|
||||
|
||||
|
||||
|
||||
<img src="https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/images/previewer/animation.gif" width="100%">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
||||
|
||||
## Simplicity is the key
|
||||
|
||||
@@ -136,13 +135,13 @@ Document.Create(container =>
|
||||
|
||||
And compare it to the produced PDF file:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/images/minimal-api.png" width="250px">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/minimal-example-shadow.png?raw=true" width="250px">
|
||||
|
||||
## Are you ready for more?
|
||||
|
||||
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 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/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).
|
||||
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="400px">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/invoice-small.png?raw=true" width="400px">
|
||||
|
||||
|
||||
## QuestPDF on JetBrains OSS Power-Ups
|
||||
@@ -150,7 +149,7 @@ The Fluent API of QuestPDF scales really well. It is easy to create and maintain
|
||||
QuestPDF was presented on one of the episodes of OSS Power-Ups hosted by JetBrains. Huge thanks for Matthias Koch and entire JetBrains team for giving me a chance to show QuestPDF. You are the best!
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=-iYvZvpLX0g">
|
||||
<img src="https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/images/jetbrains-oss-powerups-youtube.png" width="600px">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/jetbrains-oss-powerups-youtube.png?raw=true" width="600px">
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user