Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4743768e23 |
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,6 +74,8 @@ namespace QuestPDF.Drawing
|
|||||||
var pageContext = new PageContext();
|
var pageContext = new PageContext();
|
||||||
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
||||||
RenderPass(pageContext, canvas, content, debuggingState);
|
RenderPass(pageContext, canvas, content, debuggingState);
|
||||||
|
|
||||||
|
ElementCacheManager.Collect(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
@@ -124,5 +124,10 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Collect()
|
||||||
|
{
|
||||||
|
Items.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
internal class Container : ContainerElement
|
internal class Container : ContainerElement
|
||||||
{
|
{
|
||||||
internal Container()
|
public Container()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace QuestPDF.Infrastructure
|
||||||
|
{
|
||||||
|
public interface ICollectable
|
||||||
|
{
|
||||||
|
void Collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user