Compare commits

...

21 Commits

Author SHA1 Message Date
MarcinZiabek 4743768e23 Implemented simple object cache, and updated some fluent api invocations 2022-09-12 17:09:07 +02:00
MarcinZiabek 1ba01a1cf5 Fixed override value 2022-09-09 11:32:05 +02:00
MarcinZiabek d4448437ac Reduced creation of DefaultTextStyle objects 2022-09-08 17:29:52 +02:00
MarcinZiabek bc853f48fc Implemented TextStyleManager to reduce memory usage 2022-09-08 16:49:28 +02:00
MarcinZiabek 3ba09ea826 Optimization: do not apply column element when text contains only one paragraph 2022-09-07 13:41:46 +02:00
MarcinZiabek 92abd32aae Updated dependencies 2022-09-07 13:03:30 +02:00
MarcinZiabek e17867d1f3 Added checking if all text glyphs are available in specified font 2022-09-07 00:54:39 +02:00
Marcin Ziąbek bd71f30c78 Merge pull request #326 from Bebo-Maker/fix-documentation-link-in-previewer
Fix documentation link for previewer
2022-09-06 16:09:38 +02:00
MarcinZiabek 6a78a4ebdb Moved document rendering flags to QuestPDF.Settings class 2022-09-06 13:45:48 +02:00
Bennet Bo Fenner 9fc721d66b Fix documentation link for previewer 2022-08-22 13:23:44 +02:00
MarcinZiabek 7d62dead86 2022.8.2 Version type 2022-08-21 20:14:16 +02:00
MarcinZiabek 52ad0f5c24 2202.8.2 2022-08-20 18:22:06 +02:00
Marcin Ziąbek 53107073a6 Merge pull request #322 from emanueleguastella/main
Fix DocumentLayoutException when creating a document with more than one page
2022-08-20 18:14:08 +02:00
Emanuele Guastella c71bb3ea59 Fix DocumentLayoutException when creating a document with more than one page 2022-08-20 14:17:52 +02:00
Marcin Ziąbek 6cced3c143 Update readme.md 2022-08-19 14:45:26 +02:00
MarcinZiabek 425ea59cfe 2022.8.1 2022-08-19 14:40:49 +02:00
MarcinZiabek 4d326496c6 Optimization for the Column element: do not measure child when available height is negative 2022-08-17 15:14:38 +02:00
MarcinZiabek b7b6488d16 Updated stability of rendering elements when negative space 2022-08-17 15:13:06 +02:00
MarcinZiabek f0ba5fc32d Fixed: page breaking rendering does not work in very specific corner cases 2022-08-16 23:59:05 +02:00
MarcinZiabek 04da32e0e7 Stability improvements for text wrapping 2022-08-16 16:33:37 +02:00
MarcinZiabek 5bdb338996 Fixed: default text style does not always work 2022-08-16 16:25:39 +02:00
44 changed files with 808 additions and 295 deletions
+2 -2
View File
@@ -65,9 +65,9 @@ namespace QuestPDF.Examples.Engine
return this; return this;
} }
public RenderingTest ShowResults() public RenderingTest ShowResults(bool value = true)
{ {
ShowResult = true; ShowResult = value;
return this; return this;
} }
+4 -4
View File
@@ -6,11 +6,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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="microcharts" Version="0.9.5.9" />
<PackageReference Include="nunit" Version="3.13.2" /> <PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="SkiaSharp" Version="2.80.4" /> <PackageReference Include="SkiaSharp" Version="2.80.4" />
<PackageReference Include="Svg.Skia" Version="0.5.10" /> <PackageReference Include="Svg.Skia" Version="0.5.10" />
</ItemGroup> </ItemGroup>
+76 -30
View File
@@ -1,8 +1,13 @@
using System.Linq; using System;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples namespace QuestPDF.Examples
{ {
@@ -11,36 +16,77 @@ namespace QuestPDF.Examples
[Test] [Test]
public void Benchmark() public void Benchmark()
{ {
RenderingTest GenerateAndCollect();
.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();
});
foreach (var row in Enumerable.Range(0, numberOfRows)) var stopwatch = new Stopwatch();
foreach (var column in Enumerable.Range(0, numberOfColumns)) stopwatch.Start();
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
}); 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);
}
} }
} }
} }
@@ -49,7 +49,7 @@ namespace QuestPDF.Previewer
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview; CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
ShowPdfCommand = ReactiveCommand.Create(ShowPdf); 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")); SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
} }
+2 -2
View File
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
Content = documentContainer.Compose(); Content = documentContainer.Compose();
PageContext = new PageContext(); PageContext = new PageContext();
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null); DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
[Benchmark] [Benchmark]
public void GenerationTest() public void GenerationTest()
{ {
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null); DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
} }
} }
} }
+2 -3
View File
@@ -48,11 +48,10 @@ namespace QuestPDF.ReportSample
Report.Compose(container); Report.Compose(container);
var content = container.Compose(); var content = container.Compose();
var metadata = Report.GetMetadata();
var pageContext = new PageContext(); var pageContext = new PageContext();
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null); DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null); DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
} }
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.1.0" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="nunit" Version="3.13.2" /> <PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
+16 -21
View File
@@ -66,19 +66,19 @@ namespace QuestPDF.Drawing
var content = container.Compose(); var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
var metadata = document.GetMetadata(); var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
var pageContext = new PageContext();
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
if (metadata.ApplyCaching) if (Settings.EnableCaching)
ApplyCaching(content); ApplyCaching(content);
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState); var pageContext = new PageContext();
RenderPass(pageContext, canvas, content, metadata, debuggingState); 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 where TCanvas : ICanvas, IRenderingCanvas
{ {
content.VisitChildren(x => x?.Initialize(pageContext, canvas)); content.VisitChildren(x => x?.Initialize(pageContext, canvas));
@@ -114,7 +114,7 @@ namespace QuestPDF.Drawing
canvas.EndPage(); canvas.EndPage();
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold) if (currentPage >= Settings.DocumentLayoutExceptionThreshold)
{ {
canvas.EndDocument(); canvas.EndDocument();
ThrowLayoutException(); ThrowLayoutException();
@@ -131,8 +131,8 @@ namespace QuestPDF.Drawing
void ThrowLayoutException() void ThrowLayoutException()
{ {
var message = $"Composed layout generates infinite document. This may happen in two cases. " + 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. " + $"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(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " + $"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." + $"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."; $"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) if (textBlockItem is TextBlockSpan textSpan)
{ {
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle); textSpan.Style = textSpan.Style.ApplyGlobalStyle(TextStyle.LibraryDefault);
} }
else if (textBlockItem is TextBlockElement textElement) else if (textBlockItem is TextBlockElement textElement)
{ {
@@ -186,18 +186,13 @@ namespace QuestPDF.Drawing
} }
if (content is DynamicHost dynamicHost) if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle); dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement) if (content is DefaultTextStyle defaultTextStyleElement)
{ documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren()) foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle); ApplyDefaultTextStyle(child, documentDefaultTextStyle);
} }
} }
} }
+20 -7
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
@@ -18,14 +19,26 @@ namespace QuestPDF.Drawing
public DateTime CreationDate { get; set; } = DateTime.Now; public DateTime CreationDate { get; set; } = DateTime.Now;
public DateTime ModifiedDate { get; set; } = DateTime.Now; public DateTime ModifiedDate { get; set; } = DateTime.Now;
/// <summary> [Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")]
/// If the number of generated pages exceeds this threshold public int DocumentLayoutExceptionThreshold
/// (likely due to infinite layout), the exception is thrown. {
/// </summary> get => Settings.DocumentLayoutExceptionThreshold;
public int DocumentLayoutExceptionThreshold { get; set; } = 250; set => Settings.DocumentLayoutExceptionThreshold = value;
}
public bool ApplyCaching { 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 ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached; 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(); public static DocumentMetadata Default => new DocumentMetadata();
} }
+76
View File
@@ -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);
}
}
}
}
+13 -13
View File
@@ -14,13 +14,13 @@ namespace QuestPDF.Drawing
{ {
public static class FontManager public static class FontManager
{ {
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new(); private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new(); private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> FontPaints = new(); private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaints = new(); private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static ConcurrentDictionary<object, Font> ShaperFonts = new(); private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
private static ConcurrentDictionary<object, SKFont> Fonts = new(); private static readonly ConcurrentDictionary<TextStyle, SKFont> Fonts = new();
private static ConcurrentDictionary<object, TextShaper> TextShapers = new(); private static readonly ConcurrentDictionary<TextStyle, TextShaper> TextShapers = new();
static FontManager() static FontManager()
{ {
@@ -110,7 +110,7 @@ namespace QuestPDF.Drawing
internal static SKPaint ToPaint(this TextStyle style) 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) static SKPaint Convert(TextStyle style)
{ {
@@ -172,14 +172,14 @@ namespace QuestPDF.Drawing
internal static SKFontMetrics ToFontMetrics(this TextStyle style) 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) 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(); using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
@@ -200,12 +200,12 @@ namespace QuestPDF.Drawing
internal static TextShaper ToTextShaper(this TextStyle style) 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 ToFont(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());
} }
} }
} }
+30 -7
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using HarfBuzzSharp; using HarfBuzzSharp;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
@@ -9,14 +10,16 @@ namespace QuestPDF.Drawing
internal class TextShaper internal class TextShaper
{ {
public const int FontShapingScale = 512; 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(); TextStyle = textStyle;
Paint = style.ToPaint();
} }
public TextShapingResult Shape(string text) public TextShapingResult Shape(string text)
@@ -26,7 +29,7 @@ namespace QuestPDF.Drawing
PopulateBufferWithText(buffer, text); PopulateBufferWithText(buffer, text);
buffer.GuessSegmentProperties(); buffer.GuessSegmentProperties();
Font.Shape(buffer); ShaperFont.Shape(buffer);
var length = buffer.Length; var length = buffer.Length;
var glyphInfos = buffer.GlyphInfos; var glyphInfos = buffer.GlyphInfos;
@@ -52,6 +55,9 @@ namespace QuestPDF.Drawing
xOffset += glyphPositions[i].XAdvance * scaleX; xOffset += glyphPositions[i].XAdvance * scaleX;
yOffset += glyphPositions[i].YAdvance * scaleY; yOffset += glyphPositions[i].YAdvance * scaleY;
} }
if (Settings.CheckIfAllTextGlyphsAreAvailableInSpecifiedFont)
CheckIfAllGlyphsAreAvailable(glyphs, text);
return new TextShapingResult(glyphs); return new TextShapingResult(glyphs);
} }
@@ -72,6 +78,20 @@ namespace QuestPDF.Drawing
else else
throw new NotSupportedException("TextEncoding of type GlyphId is not supported."); 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 internal struct ShapedGlyph
@@ -129,6 +149,9 @@ namespace QuestPDF.Drawing
{ {
if (Glyphs.Length == 0) if (Glyphs.Length == 0)
return null; return null;
if (startIndex > endIndex)
return null;
using var skTextBlobBuilder = new SKTextBlobBuilder(); using var skTextBlobBuilder = new SKTextBlobBuilder();
+10
View File
@@ -41,5 +41,15 @@ namespace QuestPDF.Elements
{ {
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})"; 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;
}
} }
} }
+4 -1
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
@@ -12,7 +13,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
return SpacePlan.FullRender(availableSpace); return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
+12 -2
View File
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
public Position Offset { get; set; } 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 List<ColumnItem> Items { get; } = new();
internal float Spacing { get; set; } internal float Spacing { get; set; }
@@ -94,7 +94,12 @@ namespace QuestPDF.Elements
if (item.IsRendered) if (item.IsRendered)
continue; 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); var measurement = item.Measure(itemSpace);
if (measurement.Type == SpacePlanType.Wrap) if (measurement.Type == SpacePlanType.Wrap)
@@ -119,5 +124,10 @@ namespace QuestPDF.Elements
return commands; return commands;
} }
public void Collect()
{
Items.Clear();
}
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ namespace QuestPDF.Elements
{ {
internal class Container : ContainerElement internal class Container : ContainerElement
{ {
internal Container() public Container()
{ {
} }
+1 -1
View File
@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
private DynamicComponentProxy Child { get; } private DynamicComponentProxy Child { get; }
private object InitialComponentState { get; set; } private object InitialComponentState { get; set; }
internal TextStyle TextStyle { get; } = new(); internal TextStyle TextStyle { get; set; } = TextStyle.Default;
public DynamicHost(DynamicComponentProxy child) public DynamicHost(DynamicComponentProxy child)
{ {
+4 -1
View File
@@ -1,5 +1,6 @@
using System; using System;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
@@ -11,7 +12,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) 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) internal override void Draw(Size availableSpace)
+4 -1
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
@@ -9,7 +10,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) 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) internal override void Draw(Size availableSpace)
+4 -1
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
@@ -15,7 +16,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
return SpacePlan.FullRender(availableSpace); return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
+3
View File
@@ -23,6 +23,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
return Type switch return Type switch
{ {
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0), LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0),
+11 -1
View File
@@ -4,7 +4,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class Padding : ContainerElement, ICacheable internal class Padding : ContainerElement, ICacheable, ICollectable
{ {
public float Top { get; set; } public float Top { get; set; }
public float Right { get; set; } public float Right { get; set; }
@@ -62,5 +62,15 @@ namespace QuestPDF.Elements
{ {
return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})"; return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})";
} }
public override void Collect()
{
base.Collect();
Left = 0;
Right = 0;
Bottom = 0;
Top = 0;
}
} }
} }
+4
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
@@ -14,6 +15,9 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered) if (IsRendered)
return SpacePlan.FullRender(0, 0); return SpacePlan.FullRender(0, 0);
+6 -1
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Elements
public Position Offset { get; set; } 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 List<RowItem> Items { get; } = new();
internal float Spacing { get; set; } internal float Spacing { get; set; }
@@ -156,5 +156,10 @@ namespace QuestPDF.Elements
return renderingCommands; return renderingCommands;
} }
public void Collect()
{
Items.Clear();
}
} }
} }
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
internal class TextBlockSpan : ITextBlockItem internal class TextBlockSpan : ITextBlockItem
{ {
public string Text { get; set; } public string Text { get; set; }
public TextStyle Style { get; set; } = new(); public TextStyle Style { get; set; } = TextStyle.Default;
public TextShapingResult? TextShapingResult { get; set; } public TextShapingResult? TextShapingResult { get; set; }
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new (); private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
@@ -66,7 +66,7 @@ namespace QuestPDF.Elements.Text.Items
// start breaking text from requested position // start breaking text from requested position
var endIndex = TextShapingResult.BreakText(startIndex, request.AvailableWidth); var endIndex = TextShapingResult.BreakText(startIndex, request.AvailableWidth);
if (endIndex < 0) if (endIndex < startIndex)
return null; return null;
// break text only on spaces // break text only on spaces
+6 -1
View File
@@ -8,7 +8,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text namespace QuestPDF.Elements.Text
{ {
internal class TextBlock : Element, IStateResettable internal class TextBlock : Element, IStateResettable, ICollectable
{ {
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>(); public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
@@ -38,6 +38,11 @@ namespace QuestPDF.Elements.Text
RenderingQueue.Enqueue(item); RenderingQueue.Enqueue(item);
} }
} }
public void Collect()
{
Items.Clear();
}
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
+2 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
{ {
private static IContainer Border(this IContainer element, Action<Border> handler) 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); handler(border);
return element.Element(border); return element.Element(border);
+8 -10
View File
@@ -1,12 +1,14 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using Container = System.ComponentModel.Container;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
{ {
public class ColumnDescriptor public class ColumnDescriptor
{ {
internal Column Column { get; } = new(); internal Column Column { get; set; }
public void Spacing(float value, Unit unit = Unit.Point) public void Spacing(float value, Unit unit = Unit.Point)
{ {
@@ -15,14 +17,9 @@ namespace QuestPDF.Fluent
public IContainer Item() public IContainer Item()
{ {
var container = new Container(); var columnItem = ElementCacheManager.Get<ColumnItem>();
Column.Items.Add(columnItem);
Column.Items.Add(new ColumnItem return columnItem;
{
Child = container
});
return container;
} }
} }
@@ -36,7 +33,8 @@ namespace QuestPDF.Fluent
public static void Column(this IContainer element, Action<ColumnDescriptor> handler) 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); handler(descriptor);
element.Element(descriptor.Column); element.Element(descriptor.Column);
} }
+6 -5
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -52,10 +53,10 @@ namespace QuestPDF.Fluent
public static IContainer Background(this IContainer element, string color) public static IContainer Background(this IContainer element, string color)
{ {
return element.Element(new Background var background = ElementCacheManager.Get<Background>();
{ background.Color = color;
Color = color
}); return element.Element(background);
} }
public static void Placeholder(this IContainer element, string? text = null) public static void Placeholder(this IContainer element, string? text = null)
@@ -162,7 +163,7 @@ namespace QuestPDF.Fluent
public static IContainer MinimalBox(this IContainer element) 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) public static IContainer Unconstrained(this IContainer element)
+2 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
{ {
private static IContainer Padding(this IContainer element, Action<Padding> handler) 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); handler(padding);
return element.Element(padding); return element.Element(padding);
+11 -9
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -6,7 +7,7 @@ namespace QuestPDF.Fluent
{ {
public class RowDescriptor public class RowDescriptor
{ {
internal Row Row { get; } = new(); internal Row Row { get; set; }
public void Spacing(float value) public void Spacing(float value)
{ {
@@ -15,14 +16,12 @@ namespace QuestPDF.Fluent
private IContainer Item(RowItemType type, float size = 0) private IContainer Item(RowItemType type, float size = 0)
{ {
var element = new RowItem var rowItem = ElementCacheManager.Get<RowItem>();
{ rowItem.Type = type;
Type = type, rowItem.Size = size;
Size = size
};
Row.Items.Add(element); Row.Items.Add(rowItem);
return element; return rowItem;
} }
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")] [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) 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); handler(descriptor);
element.Element(descriptor.Row); element.Element(descriptor.Row);
ElementCacheManager.Store(descriptor);
} }
} }
} }
+44 -44
View File
@@ -12,11 +12,18 @@ namespace QuestPDF.Fluent
{ {
public class TextSpanDescriptor 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 public class TextPageNumberDescriptor : TextSpanDescriptor
{ {
internal PageNumberFormatter FormatFunction { get; private set; } = x => x?.ToString() ?? string.Empty; internal Action<PageNumberFormatter> AssignFormatFunction { get; }
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle) internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
{ {
AssignFormatFunction = assignFormatFunction;
} }
public TextPageNumberDescriptor Format(PageNumberFormatter formatter) public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
{ {
FormatFunction = formatter ?? FormatFunction; AssignFormatFunction(formatter);
return this; return this;
} }
} }
@@ -41,7 +48,7 @@ namespace QuestPDF.Fluent
public class TextDescriptor public class TextDescriptor
{ {
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>(); 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; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f; private float Spacing { get; set; } = 0f;
@@ -91,19 +98,15 @@ namespace QuestPDF.Fluent
public TextSpanDescriptor Span(string? text) public TextSpanDescriptor Span(string? text)
{ {
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (text == null) if (text == null)
return descriptor; return new TextSpanDescriptor(_ => { });
var items = text var items = text
.Replace("\r", string.Empty) .Replace("\r", string.Empty)
.Split(new[] { '\n' }, StringSplitOptions.None) .Split(new[] { '\n' }, StringSplitOptions.None)
.Select(x => new TextBlockSpan .Select(x => new TextBlockSpan
{ {
Text = x, Text = x
Style = style
}) })
.ToList(); .ToList();
@@ -118,7 +121,7 @@ namespace QuestPDF.Fluent
.ToList() .ToList()
.ForEach(TextBlocks.Add); .ForEach(TextBlocks.Add);
return descriptor; return new TextSpanDescriptor(x => items.ForEach(y => y.Style = x));
} }
public TextSpanDescriptor Line(string? text) public TextSpanDescriptor Line(string? text)
@@ -134,16 +137,10 @@ namespace QuestPDF.Fluent
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber) private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
{ {
var style = DefaultStyle.Clone(); var textBlockItem = new TextBlockPageNumber();
var descriptor = new TextPageNumberDescriptor(style); AddItemToLastTextBlock(textBlockItem);
AddItemToLastTextBlock(new TextBlockPageNumber return new TextPageNumberDescriptor(x => textBlockItem.Style = x, x => textBlockItem.Source = context => x(pageNumber(context)));
{
Source = context => descriptor.FormatFunction(pageNumber(context)),
Style = style
});
return descriptor;
} }
public TextPageNumberDescriptor CurrentPageNumber() public TextPageNumberDescriptor CurrentPageNumber()
@@ -187,20 +184,17 @@ namespace QuestPDF.Fluent
if (IsNullOrEmpty(sectionName)) if (IsNullOrEmpty(sectionName))
throw new ArgumentException("Section name cannot be null or empty", nameof(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)) if (IsNullOrEmpty(text))
return descriptor; return new TextSpanDescriptor(_ => { });
AddItemToLastTextBlock(new TextBlockSectionLink var textBlockItem = new TextBlockSectionLink
{ {
Style = style,
Text = text, Text = text,
SectionName = sectionName 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.")] [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)) if (IsNullOrEmpty(url))
throw new ArgumentException("Url cannot be null or empty", nameof(url)); throw new ArgumentException("Url cannot be null or empty", nameof(url));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text)) if (IsNullOrEmpty(text))
return descriptor; return new TextSpanDescriptor(_ => { });
AddItemToLastTextBlock(new TextBlockHyperlink var textBlockItem = new TextBlockHyperlink
{ {
Style = style,
Text = text, Text = text,
Url = url 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.")] [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) internal void Compose(IContainer container)
{ {
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment); 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); column.Spacing(Spacing);
foreach (var textBlock in TextBlocks) foreach (var textBlock in TextBlocks)
column.Item().Element(textBlock); column.Item().Element(textBlock);
}); });
} }
} }
+36 -34
View File
@@ -11,120 +11,124 @@ namespace QuestPDF.Fluent
if (style == null) if (style == null)
return descriptor; return descriptor;
descriptor.TextStyle.OverrideStyle(style); descriptor.MutateTextStyle(x => x.OverrideStyle(style));
return descriptor; return descriptor;
} }
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor 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; return descriptor;
} }
public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor 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; return descriptor;
} }
#region Weight #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 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 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 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 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 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 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 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 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 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 public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.ExtraBlack); descriptor.MutateTextStyle(x => x.ExtraBlack());
return descriptor;
} }
#endregion #endregion
@@ -132,24 +136,22 @@ namespace QuestPDF.Fluent
#region Position #region Position
public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor 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 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 public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Position(FontPosition.Superscript); descriptor.MutateTextStyle(x => x.Superscript());
}
private static T Position<T>(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontPosition = fontPosition;
return descriptor; return descriptor;
} }
#endregion #endregion
} }
} }
+13 -22
View File
@@ -4,16 +4,10 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
{ {
public static class TextStyleExtensions 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.")] [Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
public static TextStyle Color(this TextStyle style, string value) 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) 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) 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.")] [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) 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.")] [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) 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) 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) 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) 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) 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) 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 #region Weight
public static TextStyle Weight(this TextStyle style, FontWeight 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) public static TextStyle Thin(this TextStyle style)
@@ -154,10 +148,7 @@ namespace QuestPDF.Fluent
private static TextStyle Position(this TextStyle style, FontPosition fontPosition) private static TextStyle Position(this TextStyle style, FontPosition fontPosition)
{ {
if (style.FontPosition == fontPosition) return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
return style;
return style.Mutate(t => t.FontPosition = fontPosition);
} }
#endregion #endregion
} }
+6
View File
@@ -4,6 +4,7 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Helpers namespace QuestPDF.Helpers
@@ -54,5 +55,10 @@ namespace QuestPDF.Helpers
handler(element); handler(element);
} }
internal static bool IsNegative(this Size size)
{
return size.Width < 0f || size.Height < 0f;
}
} }
} }
+6 -1
View File
@@ -5,7 +5,7 @@ using QuestPDF.Elements;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
internal abstract class ContainerElement : Element, IContainer internal abstract class ContainerElement : Element, IContainer, ICollectable
{ {
internal Element? Child { get; set; } = Empty.Instance; internal Element? Child { get; set; } = Empty.Instance;
@@ -34,5 +34,10 @@ namespace QuestPDF.Infrastructure
{ {
Child?.Draw(availableSpace); Child?.Draw(availableSpace);
} }
public virtual void Collect()
{
Child = default;
}
} }
} }
+7
View File
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
public interface ICollectable
{
void Collect();
}
}
+3 -63
View File
@@ -3,10 +3,8 @@ using QuestPDF.Helpers;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
public class TextStyle public record TextStyle
{ {
internal bool HasGlobalStyleApplied { get; private set; }
internal string? Color { get; set; } internal string? Color { get; set; }
internal string? BackgroundColor { get; set; } internal string? BackgroundColor { get; set; }
internal string? FontFamily { get; set; } internal string? FontFamily { get; set; }
@@ -19,13 +17,7 @@ namespace QuestPDF.Infrastructure
internal bool? HasUnderline { get; set; } internal bool? HasUnderline { get; set; }
internal bool? WrapAnywhere { get; set; } internal bool? WrapAnywhere { get; set; }
internal object PaintKey { get; private set; } internal static TextStyle LibraryDefault { get; } = new()
internal object FontMetricsKey { get; private set; }
// REVIEW: Should this be a method call that news up a TextStyle,
// or can it be a static variable?
// (style mutations seem to create a clone anyway)
internal static readonly TextStyle LibraryDefault = new TextStyle
{ {
Color = Colors.Black, Color = Colors.Black,
BackgroundColor = Colors.Transparent, BackgroundColor = Colors.Transparent,
@@ -40,58 +32,6 @@ namespace QuestPDF.Infrastructure
WrapAnywhere = false WrapAnywhere = false
}; };
// REVIEW: Should this be a method call that news up a TextStyle, public static TextStyle Default { get; } = new();
// or can it be a static variable?
// (style mutations seem to create a clone anyway)
public static readonly 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;
}
} }
} }
+215
View File
@@ -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;
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<Authors>MarcinZiabek</Authors> <Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company> <Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId> <PackageId>QuestPDF</PackageId>
<Version>2022.8.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> <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> <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion> <LangVersion>9</LangVersion>
+78
View File
@@ -0,0 +1,78 @@
[![Dotnet](https://img.shields.io/badge/platform-.NET-blue)](https://www.nuget.org/packages/QuestPDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/QuestPDF/QuestPDF)](https://github.com/QuestPDF/QuestPDF/stargazers)
[![Nuget version](https://img.shields.io/nuget/v/QuestPdf)](https://www.nuget.org/packages/QuestPDF/)
[![Nuget download](https://img.shields.io/nuget/dt/QuestPDF)](https://www.nuget.org/packages/QuestPDF/)
[![License](https://img.shields.io/github/license/QuestPDF/QuestPDF)](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
[![Sponsor project](https://img.shields.io/badge/sponsor-project-red)](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
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](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.
[![API reference](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](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.
[![Patterns and Practices](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](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:
![invoice](https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/docs/public/minimal-example-shadow.png)
## 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).
![invoice](https://raw.githubusercontent.com/QuestPDF/QuestPDF-Documentation/main/docs/public/invoice-small.png)
+13
View File
@@ -1,3 +1,5 @@
2022.8.0:
- Improved library performance, - Improved library performance,
- Breaking change: changed default font from Calibri to an open-source Lato, - 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 included with the nuget package, making it safe to deploy on any environment,
@@ -5,3 +7,14 @@
- When requested font is not available on the runtime environment, library provides list of available fonts, - 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 rare layout overflow exception with the Inlined element,
- Fixed a memory leak connected to the HarfBuzz library. - Fixed a memory leak connected to the HarfBuzz library.
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.
+40
View File
@@ -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;
}
}
+2
View File
@@ -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! ⭐ ⭐ 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 ## 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! 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!