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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
page.Margin(50);
|
page.Margin(50);
|
||||||
|
|
||||||
page.Content().PaddingVertical(10).Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
column.Item().Element(Title);
|
column.Item().Element(Title);
|
||||||
column.Item().PageBreak();
|
column.Item().PageBreak();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Elements.Text;
|
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
@@ -619,43 +618,5 @@ namespace QuestPDF.Examples
|
|||||||
.FontSize(20);
|
.FontSize(20);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void FontFallback()
|
|
||||||
{
|
|
||||||
RenderingTest
|
|
||||||
.Create()
|
|
||||||
.ProduceImages()
|
|
||||||
.ShowResults()
|
|
||||||
.RenderDocument(container =>
|
|
||||||
{
|
|
||||||
container.Page(page =>
|
|
||||||
{
|
|
||||||
page.Margin(50);
|
|
||||||
page.PageColor(Colors.White);
|
|
||||||
page.DefaultTextStyle(x => x
|
|
||||||
.Fallback(y => y.FontFamily("Segoe UI Emoji")
|
|
||||||
.Fallback(y => y.FontFamily("Microsoft YaHei"))));
|
|
||||||
|
|
||||||
page.Size(PageSizes.A4);
|
|
||||||
|
|
||||||
page.Content().Text(t =>
|
|
||||||
{
|
|
||||||
t.Line("This is normal text.");
|
|
||||||
t.EmptyLine();
|
|
||||||
|
|
||||||
t.Line("Following line should use font fallback:");
|
|
||||||
t.Line("中文文本");
|
|
||||||
t.EmptyLine();
|
|
||||||
|
|
||||||
t.Line("The following line contains a mix of known and unknown characters.");
|
|
||||||
t.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!");
|
|
||||||
t.EmptyLine();
|
|
||||||
|
|
||||||
t.Line("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<Authors>MarcinZiabek</Authors>
|
<Authors>MarcinZiabek</Authors>
|
||||||
<Company>CodeFlint</Company>
|
<Company>CodeFlint</Company>
|
||||||
<PackageId>QuestPDF.Previewer</PackageId>
|
<PackageId>QuestPDF.Previewer</PackageId>
|
||||||
<Version>2022.9.1</Version>
|
<Version>2022.8.0</Version>
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
||||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
<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>
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ namespace QuestPDF.Drawing
|
|||||||
var container = new DocumentContainer();
|
var container = new DocumentContainer();
|
||||||
document.Compose(container);
|
document.Compose(container);
|
||||||
var content = container.Compose();
|
var content = container.Compose();
|
||||||
|
|
||||||
ApplyRepeatContent(content);
|
|
||||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||||
|
|
||||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||||
@@ -76,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)
|
||||||
@@ -174,7 +174,7 @@ namespace QuestPDF.Drawing
|
|||||||
{
|
{
|
||||||
if (textBlockItem is TextBlockSpan textSpan)
|
if (textBlockItem is TextBlockSpan textSpan)
|
||||||
{
|
{
|
||||||
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
textSpan.Style = textSpan.Style.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
||||||
}
|
}
|
||||||
else if (textBlockItem is TextBlockElement textElement)
|
else if (textBlockItem is TextBlockElement textElement)
|
||||||
{
|
{
|
||||||
@@ -194,23 +194,5 @@ namespace QuestPDF.Drawing
|
|||||||
foreach (var child in content.GetChildren())
|
foreach (var child in content.GetChildren())
|
||||||
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ApplyRepeatContent(this Element? content, bool enabled = false)
|
|
||||||
{
|
|
||||||
if (content == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (content is RepeatContent repeatContent)
|
|
||||||
{
|
|
||||||
ApplyRepeatContent(repeatContent.Child, repeatContent.Repeat);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in content.GetChildren())
|
|
||||||
ApplyRepeatContent(child, enabled);
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
content.CreateProxy(y => y is IContent ? new ShowOnce { Child = y } : y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Drawing
|
||||||
|
{
|
||||||
|
internal class CircularBuffer
|
||||||
|
{
|
||||||
|
private const int BufferSize = 11_000;
|
||||||
|
|
||||||
|
private object[] Buffer = new object[BufferSize];
|
||||||
|
private int WriteIndex { get; set; } = 0;
|
||||||
|
private int ReadIndex { get; set; } = 0;
|
||||||
|
|
||||||
|
public T Get<T>() where T : class, new()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (ReadIndex == WriteIndex)
|
||||||
|
return new T();
|
||||||
|
|
||||||
|
var index = ReadIndex;
|
||||||
|
ReadIndex = (ReadIndex + 1) % BufferSize;
|
||||||
|
|
||||||
|
var result = Buffer[index] as T;
|
||||||
|
Buffer[index] = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Store(object value)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
Buffer[WriteIndex] = value;
|
||||||
|
WriteIndex = (WriteIndex + 1) % BufferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performance analysis:
|
||||||
|
// without: 115s
|
||||||
|
// ConcurrentQueue: 28s
|
||||||
|
// ConcurrentBag: 38s
|
||||||
|
// CircularBuffer: 30s
|
||||||
|
internal static class ElementCacheManager
|
||||||
|
{
|
||||||
|
private static ConcurrentDictionary<Type, ConcurrentQueue<object>> Cache { get; } = new();
|
||||||
|
|
||||||
|
public static T Get<T>() where T : class, new()
|
||||||
|
{
|
||||||
|
var buffer = Cache.GetOrAdd(typeof(T), _=> new ConcurrentQueue<object>());
|
||||||
|
return buffer.TryDequeue(out var result) ? result as T : new T();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Store<T>(T element)
|
||||||
|
{
|
||||||
|
var buffer = Cache.GetOrAdd(element.GetType(), _=> new ConcurrentQueue<object>());
|
||||||
|
buffer.Enqueue(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Collect(Element element)
|
||||||
|
{
|
||||||
|
foreach (var child in element.GetChildren())
|
||||||
|
Collect(child);
|
||||||
|
|
||||||
|
if (element is ICollectable collectable)
|
||||||
|
{
|
||||||
|
collectable.Collect();
|
||||||
|
Store(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,6 @@ namespace QuestPDF.Drawing.Exceptions
|
|||||||
{
|
{
|
||||||
public class DocumentDrawingException : Exception
|
public class DocumentDrawingException : Exception
|
||||||
{
|
{
|
||||||
internal DocumentDrawingException(string message) : base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
|
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace QuestPDF.Drawing
|
||||||
|
{
|
||||||
|
internal struct TextMeasurement
|
||||||
|
{
|
||||||
|
public int LineIndex { get; set; }
|
||||||
|
public float FragmentWidth { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,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);
|
||||||
}
|
}
|
||||||
@@ -75,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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
|
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
|
||||||
|
|
||||||
internal class Canvas : Element, ICacheable, IContent
|
internal class Canvas : Element, ICacheable
|
||||||
{
|
{
|
||||||
public DrawOnCanvas Handler { get; set; }
|
public DrawOnCanvas Handler { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class DynamicHost : Element, IStateResettable, IContent
|
internal class DynamicHost : Element, IStateResettable
|
||||||
{
|
{
|
||||||
private DynamicComponentProxy Child { get; }
|
private DynamicComponentProxy Child { get; }
|
||||||
private object InitialComponentState { get; set; }
|
private object InitialComponentState { get; set; }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using SkiaSharp;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class DynamicImage : Element, IContent
|
internal class DynamicImage : Element
|
||||||
{
|
{
|
||||||
public Func<Size, byte[]>? Source { get; set; }
|
public Func<Size, byte[]>? Source { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using SkiaSharp;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class Image : Element, ICacheable, IContent
|
internal class Image : Element, ICacheable
|
||||||
{
|
{
|
||||||
public SKImage? InternalImage { get; set; }
|
public SKImage? InternalImage { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements
|
|||||||
Horizontal
|
Horizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Line : Element, ILine, ICacheable, IContent
|
internal class Line : Element, ILine, ICacheable
|
||||||
{
|
{
|
||||||
public LineType Type { get; set; } = LineType.Vertical;
|
public LineType Type { get; set; } = LineType.Vertical;
|
||||||
public string Color { get; set; } = Colors.Black;
|
public string Color { get; set; } = Colors.Black;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using QuestPDF.Infrastructure;
|
|
||||||
|
|
||||||
namespace QuestPDF.Elements
|
|
||||||
{
|
|
||||||
internal class RepeatContent : ContainerElement
|
|
||||||
{
|
|
||||||
public bool Repeat { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Drawing.Exceptions;
|
|
||||||
using QuestPDF.Elements.Text.Items;
|
|
||||||
using QuestPDF.Fluent;
|
|
||||||
using QuestPDF.Infrastructure;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace QuestPDF.Elements.Text
|
|
||||||
{
|
|
||||||
internal static class FontFallback
|
|
||||||
{
|
|
||||||
public struct TextRun
|
|
||||||
{
|
|
||||||
public string Content { get; set; }
|
|
||||||
public TextStyle Style { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FallbackOption
|
|
||||||
{
|
|
||||||
public TextStyle Style { get; set; }
|
|
||||||
public SKFont Font { get; set; }
|
|
||||||
public SKTypeface Typeface { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SKFontManager FontManager => SKFontManager.Default;
|
|
||||||
|
|
||||||
public static IEnumerable<TextRun> SplitWithFontFallback(this string text, TextStyle textStyle)
|
|
||||||
{
|
|
||||||
var fallbackOptions = GetFallbackOptions(textStyle).ToArray();
|
|
||||||
|
|
||||||
var spanStartIndex = 0;
|
|
||||||
var spanFallbackOption = fallbackOptions[0];
|
|
||||||
|
|
||||||
for (var i = 0; i < text.Length; i += char.IsSurrogatePair(text, i) ? 2 : 1)
|
|
||||||
{
|
|
||||||
var codepoint = char.ConvertToUtf32(text, i);
|
|
||||||
var newFallbackOption = MatchFallbackOption(fallbackOptions, codepoint);
|
|
||||||
|
|
||||||
if (newFallbackOption == spanFallbackOption)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
yield return new TextRun
|
|
||||||
{
|
|
||||||
Content = text.Substring(spanStartIndex, i - spanStartIndex),
|
|
||||||
Style = spanFallbackOption.Style
|
|
||||||
};
|
|
||||||
|
|
||||||
spanStartIndex = i;
|
|
||||||
spanFallbackOption = newFallbackOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spanStartIndex > text.Length)
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
yield return new TextRun
|
|
||||||
{
|
|
||||||
Content = text.Substring(spanStartIndex, text.Length - spanStartIndex),
|
|
||||||
Style = spanFallbackOption.Style
|
|
||||||
};
|
|
||||||
|
|
||||||
static IEnumerable<FallbackOption> GetFallbackOptions(TextStyle? textStyle)
|
|
||||||
{
|
|
||||||
while (textStyle != null)
|
|
||||||
{
|
|
||||||
var font = textStyle.ToFont();
|
|
||||||
|
|
||||||
yield return new FallbackOption
|
|
||||||
{
|
|
||||||
Style = textStyle,
|
|
||||||
Font = font,
|
|
||||||
Typeface = font.Typeface
|
|
||||||
};
|
|
||||||
|
|
||||||
textStyle = textStyle.Fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static FallbackOption MatchFallbackOption(ICollection<FallbackOption> fallbackOptions, int codepoint)
|
|
||||||
{
|
|
||||||
foreach (var fallbackOption in fallbackOptions)
|
|
||||||
{
|
|
||||||
if (fallbackOption.Font.ContainsGlyph(codepoint))
|
|
||||||
return fallbackOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw CreateNotMatchingFontException(codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Exception CreateNotMatchingFontException(int codepoint)
|
|
||||||
{
|
|
||||||
var character = char.ConvertFromUtf32(codepoint);
|
|
||||||
var unicode = $"U-{codepoint:X4}";
|
|
||||||
|
|
||||||
var proposedFonts = FindFontsContainingGlyph(codepoint);
|
|
||||||
var proposedFontsFormatted = proposedFonts.Any() ? string.Join(", ", proposedFonts) : "no fonts available";
|
|
||||||
|
|
||||||
return new DocumentDrawingException(
|
|
||||||
$"Could not find an appropriate font fallback for glyph: {unicode} '{character}'. " +
|
|
||||||
$"Font families available on current environment that contain this glyph: {proposedFontsFormatted}. " +
|
|
||||||
$"Possible solutions: " +
|
|
||||||
$"1) Use one of the listed fonts as the primary font in your document. " +
|
|
||||||
$"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. ");
|
|
||||||
}
|
|
||||||
|
|
||||||
static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
|
|
||||||
{
|
|
||||||
var fontManager = SKFontManager.Default;
|
|
||||||
|
|
||||||
return fontManager
|
|
||||||
.GetFontFamilies()
|
|
||||||
.Select(fontManager.MatchFamily)
|
|
||||||
.Where(x => x.ContainsGlyph(codepoint))
|
|
||||||
.Select(x => x.FamilyName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<ITextBlockItem> ApplyFontFallback(this ICollection<ITextBlockItem> textBlockItems)
|
|
||||||
{
|
|
||||||
foreach (var textBlockItem in textBlockItems)
|
|
||||||
{
|
|
||||||
if (textBlockItem is TextBlockSpan textBlockSpan and not TextBlockPageNumber)
|
|
||||||
{
|
|
||||||
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
|
|
||||||
{
|
|
||||||
yield return textBlockSpan;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style);
|
|
||||||
|
|
||||||
foreach (var textRun in textRuns)
|
|
||||||
{
|
|
||||||
yield return new TextBlockSpan
|
|
||||||
{
|
|
||||||
Text = textRun.Content,
|
|
||||||
Style = textRun.Style
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return textBlockItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements.Text
|
namespace QuestPDF.Elements.Text
|
||||||
{
|
{
|
||||||
internal class TextBlock : Element, IStateResettable, IContent
|
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>();
|
||||||
@@ -18,11 +18,8 @@ namespace QuestPDF.Elements.Text
|
|||||||
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||||
private int CurrentElementIndex { get; set; }
|
private int CurrentElementIndex { get; set; }
|
||||||
|
|
||||||
private bool FontFallbackApplied { get; set; } = false;
|
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
ApplyFontFallback();
|
|
||||||
InitializeQueue();
|
InitializeQueue();
|
||||||
CurrentElementIndex = 0;
|
CurrentElementIndex = 0;
|
||||||
|
|
||||||
@@ -40,15 +37,11 @@ namespace QuestPDF.Elements.Text
|
|||||||
foreach (var item in Items)
|
foreach (var item in Items)
|
||||||
RenderingQueue.Enqueue(item);
|
RenderingQueue.Enqueue(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void ApplyFontFallback()
|
|
||||||
{
|
public void Collect()
|
||||||
if (FontFallbackApplied)
|
{
|
||||||
return;
|
Items.Clear();
|
||||||
|
|
||||||
Items = Items.ApplyFontFallback().ToList();
|
|
||||||
FontFallbackApplied = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
Decoration.Before = container;
|
Decoration.Before = container;
|
||||||
return container.RepeatContent();
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Before(Action<IContainer> handler)
|
public void Before(Action<IContainer> handler)
|
||||||
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
Decoration.After = container;
|
Decoration.After = container;
|
||||||
return container.RepeatContent();
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void After(Action<IContainer> handler)
|
public void After(Action<IContainer> handler)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -195,13 +196,5 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
return element.Element(new ScaleToFit());
|
return element.Element(new ScaleToFit());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IContainer RepeatContent(this IContainer element, bool enabled = true)
|
|
||||||
{
|
|
||||||
return element.Element(new RepeatContent
|
|
||||||
{
|
|
||||||
Repeat = enabled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,6 @@ namespace QuestPDF.Fluent
|
|||||||
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
|
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
|
||||||
{
|
{
|
||||||
AssignFormatFunction = assignFormatFunction;
|
AssignFormatFunction = assignFormatFunction;
|
||||||
AssignFormatFunction(x => x?.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
||||||
|
|||||||
@@ -15,17 +15,6 @@ namespace QuestPDF.Fluent
|
|||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Fallback<T>(this T descriptor, TextStyle? value = null) where T : TextSpanDescriptor
|
|
||||||
{
|
|
||||||
descriptor.TextStyle.Fallback = value;
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Fallback<T>(this T descriptor, Func<TextStyle, TextStyle> handler) where T : TextSpanDescriptor
|
|
||||||
{
|
|
||||||
return descriptor.Fallback(handler(TextStyle.Default));
|
|
||||||
}
|
|
||||||
|
|
||||||
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.MutateTextStyle(x => x.FontColor(value));
|
descriptor.MutateTextStyle(x => x.FontColor(value));
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Fluent
|
namespace QuestPDF.Fluent
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public static class TextStyleExtensions
|
public static class TextStyleExtensions
|
||||||
{
|
{
|
||||||
[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.")]
|
||||||
@@ -129,7 +131,6 @@ namespace QuestPDF.Fluent
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Position
|
#region Position
|
||||||
|
|
||||||
public static TextStyle NormalPosition(this TextStyle style)
|
public static TextStyle NormalPosition(this TextStyle style)
|
||||||
{
|
{
|
||||||
return style.Position(FontPosition.Normal);
|
return style.Position(FontPosition.Normal);
|
||||||
@@ -149,21 +150,6 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
|
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Fallback
|
|
||||||
|
|
||||||
public static TextStyle Fallback(this TextStyle style, TextStyle? value = null)
|
|
||||||
{
|
|
||||||
return style.Mutate(TextStyleProperty.Fallback, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TextStyle Fallback(this TextStyle style, Func<TextStyle, TextStyle> handler)
|
|
||||||
{
|
|
||||||
return style.Fallback(handler(TextStyle.Default));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace QuestPDF.Infrastructure
|
|
||||||
{
|
|
||||||
internal interface IContent
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,6 @@ namespace QuestPDF.Infrastructure
|
|||||||
internal bool? HasUnderline { get; set; }
|
internal bool? HasUnderline { get; set; }
|
||||||
internal bool? WrapAnywhere { get; set; }
|
internal bool? WrapAnywhere { get; set; }
|
||||||
|
|
||||||
internal TextStyle? Fallback { get; set; }
|
|
||||||
|
|
||||||
internal static TextStyle LibraryDefault { get; } = new()
|
internal static TextStyle LibraryDefault { get; } = new()
|
||||||
{
|
{
|
||||||
Color = Colors.Black,
|
Color = Colors.Black,
|
||||||
@@ -31,8 +29,7 @@ namespace QuestPDF.Infrastructure
|
|||||||
IsItalic = false,
|
IsItalic = false,
|
||||||
HasStrikethrough = false,
|
HasStrikethrough = false,
|
||||||
HasUnderline = false,
|
HasUnderline = false,
|
||||||
WrapAnywhere = false,
|
WrapAnywhere = false
|
||||||
Fallback = null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TextStyle Default { get; } = new();
|
public static TextStyle Default { get; } = new();
|
||||||
|
|||||||
@@ -16,15 +16,13 @@ namespace QuestPDF.Infrastructure
|
|||||||
IsItalic,
|
IsItalic,
|
||||||
HasStrikethrough,
|
HasStrikethrough,
|
||||||
HasUnderline,
|
HasUnderline,
|
||||||
WrapAnywhere,
|
WrapAnywhere
|
||||||
Fallback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class TextStyleManager
|
internal static class TextStyleManager
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
|
public static ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
|
||||||
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new();
|
public static ConcurrentDictionary<(TextStyle origin, TextStyle parent, bool overrideValue), TextStyle> TextStyleApplyCache = new();
|
||||||
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new();
|
|
||||||
|
|
||||||
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
|
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
|
||||||
{
|
{
|
||||||
@@ -32,7 +30,7 @@ namespace QuestPDF.Infrastructure
|
|||||||
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.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)
|
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object value, bool overrideValue = true)
|
||||||
{
|
{
|
||||||
if (overrideValue && value == null)
|
if (overrideValue && value == null)
|
||||||
return origin;
|
return origin;
|
||||||
@@ -179,69 +177,38 @@ namespace QuestPDF.Infrastructure
|
|||||||
|
|
||||||
return origin with { WrapAnywhere = castedValue };
|
return origin with { WrapAnywhere = castedValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property == TextStyleProperty.Fallback)
|
|
||||||
{
|
|
||||||
if (!overrideValue && origin.Fallback != null)
|
|
||||||
return origin;
|
|
||||||
|
|
||||||
var castedValue = (TextStyle?)value;
|
|
||||||
|
|
||||||
if (origin.Fallback == castedValue)
|
|
||||||
return origin;
|
|
||||||
|
|
||||||
return origin with { Fallback = castedValue };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
|
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)
|
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
|
||||||
{
|
{
|
||||||
var cacheKey = (style, parent);
|
var cacheKey = (style, parent, false);
|
||||||
return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
|
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
||||||
}
|
|
||||||
|
|
||||||
private static TextStyle ApplyFontFallback(this TextStyle style)
|
|
||||||
{
|
|
||||||
var targetFallbackStyle = style
|
|
||||||
?.Fallback
|
|
||||||
?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
|
|
||||||
?.ApplyFontFallback();
|
|
||||||
|
|
||||||
return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
||||||
{
|
{
|
||||||
var cacheKey = (style, parent);
|
var cacheKey = (style, parent, true);
|
||||||
|
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
||||||
return TextStyleOverrideCache.GetOrAdd(cacheKey, key =>
|
|
||||||
{
|
|
||||||
var result = ApplyStyle(key.origin, key.parent);
|
|
||||||
return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true)
|
private static TextStyle ApplyStyle(TextStyle style, TextStyle parent, bool overrideValue)
|
||||||
{
|
{
|
||||||
var result = style;
|
var result = style;
|
||||||
|
|
||||||
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideValue);
|
||||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideValue);
|
||||||
|
|
||||||
if (applyFallback)
|
|
||||||
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Previewer
|
|||||||
public event Action? OnPreviewerStopped;
|
public event Action? OnPreviewerStopped;
|
||||||
|
|
||||||
private const int RequiredPreviewerVersionMajor = 2022;
|
private const int RequiredPreviewerVersionMajor = 2022;
|
||||||
private const int RequiredPreviewerVersionMinor = 9;
|
private const int RequiredPreviewerVersionMinor = 8;
|
||||||
|
|
||||||
public PreviewerService(int port)
|
public PreviewerService(int port)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<Authors>MarcinZiabek</Authors>
|
<Authors>MarcinZiabek</Authors>
|
||||||
<Company>CodeFlint</Company>
|
<Company>CodeFlint</Company>
|
||||||
<PackageId>QuestPDF</PackageId>
|
<PackageId>QuestPDF</PackageId>
|
||||||
<Version>2022.9.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>
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
2022.9.0
|
2022.8.0:
|
||||||
- Implemented font-fallback algorithm,
|
|
||||||
- Introduced new Settings API,
|
- Improved library performance,
|
||||||
- Significantly reduced memory allocation cost for TextStyle objects,
|
- Breaking change: changed default font from Calibri to an open-source Lato,
|
||||||
- Implemented optional checking if all font glyphs are available,
|
- Default font files are included with the nuget package, making it safe to deploy on any environment,
|
||||||
- Minor text-rendering optimizations.
|
- Default font files are significantly smaller, so output document files should be smaller too (up to 20x reduction in size),
|
||||||
|
- When requested font is not available on the runtime environment, library provides list of available fonts,
|
||||||
|
- Fixed a rare layout overflow exception with the Inlined element,
|
||||||
|
- Fixed a memory leak connected to the HarfBuzz library.
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
public static class Settings
|
public static class Settings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This value represents the maximum length of the document that the library produces.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// To break the algorithm and save the environment, the library breaks the rendering process after reaching specified length of document.
|
||||||
@@ -35,6 +35,6 @@
|
|||||||
/// However, it provides hints that used fonts are not sufficient to produce correct results.
|
/// However, it provides hints that used fonts are not sufficient to produce correct results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
||||||
public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
public static bool CheckIfAllTextGlyphsAreAvailableInSpecifiedFont { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,8 +23,7 @@ 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/190931857-8ca52ec8-cc7d-4d12-9467-4442b3342fa1.png" width="700" />
|
<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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user