Compare commits

..

33 Commits

Author SHA1 Message Date
MarcinZiabek 5cc0397acb Added more examples for the Shrink element 2022-10-19 23:20:19 +02:00
MarcinZiabek 73448f483c Extended and renamed the MinimalBox element 2022-10-19 21:37:10 +02:00
MarcinZiabek 7b7fac07b3 Relative padding implementation 2022-10-05 00:58:00 +02:00
MarcinZiabek 759655059d Renaming 2022-10-04 22:47:18 +02:00
MarcinZiabek fa3d912e41 RelativeSize, RelativePosition 2022-10-04 22:26:25 +02:00
MarcinZiabek 9ce9ca85be Added GenerationBenchmark to test async performance 2022-09-22 22:37:15 +02:00
MarcinZiabek 553c8ab719 2022.9.1 2022-09-19 12:05:44 +02:00
Marcin Ziąbek e768e9b06d Update readme.md 2022-09-19 00:58:55 +02:00
Marcin Ziąbek fd913a777c Update readme.md 2022-09-19 00:57:32 +02:00
MarcinZiabek ed9e6daec5 2022.9.0 2022-09-19 00:45:14 +02:00
Marcin Ziąbek ee6249a658 Merge pull request #341 from QuestPDF/2022.9
2022.9 Release
2022-09-19 00:43:29 +02:00
MarcinZiabek b307304f46 Fixed typo 2022-09-18 20:15:41 +02:00
MarcinZiabek f028f82e11 Removed unsued file 2022-09-17 20:42:14 +02:00
MarcinZiabek 719a3385f6 Updated Previewer to 2022.9 2022-09-16 19:52:10 +02:00
MarcinZiabek 2adff11400 2022.9.0-alpha1 2022-09-16 19:46:32 +02:00
MarcinZiabek 34fed6d547 Settings.CheckIfAllTextGlyphsAreAvailable improvements 2022-09-16 16:08:02 +02:00
MarcinZiabek db2df75624 Text example: fixed visuals 2022-09-15 22:19:28 +02:00
MarcinZiabek 6b535752df Fixed formatting page numbers 2022-09-15 22:12:06 +02:00
MarcinZiabek 556f87ff25 Settings renaming 2022-09-15 21:20:25 +02:00
MarcinZiabek fbebbd85eb Fixed build, adapted fallback process for cached TextStyle 2022-09-15 15:09:30 +02:00
MarcinZiabek 4650c2a4ea Merge branch 'font-fallback' into 2022.9
# Conflicts:
#	QuestPDF/Elements/Text/Items/TextBlockSpan.cs
#	QuestPDF/Fluent/TextStyleExtensions.cs
#	QuestPDF/Infrastructure/TextStyle.cs
2022-09-14 23:28:50 +02:00
MarcinZiabek 5390fc3f1b Implemented font-fallback as required configuration 2022-09-10 23:27:27 +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 0468dd5f02 Font-fallback implementation 2022-09-08 13:23:59 +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
45 changed files with 1418 additions and 334 deletions
+146
View File
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class ProcessRunningTime
{
public TimeSpan FluentTime { get; set; }
public TimeSpan GenerationTime { get; set; }
public float Size { get; set; }
}
public class GenerationBenchmark
{
public const int TestSize = 4096;
[Test]
public void BenchmarkAsync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.AsParallel() // difference
.Select(GenerateAndCollect)
.ToList());
}
[Test]
public void BenchmarkSync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.Select(GenerateAndCollect)
.ToList());
}
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
{
var totalFluentTime = TimeSpan.Zero;
var totalGenerationTime = TimeSpan.Zero;
var stopWatch = new Stopwatch();
stopWatch.Start();
var results = handler();
stopWatch.Stop();
foreach (var result in results)
{
totalFluentTime += result.FluentTime;
totalGenerationTime += result.GenerationTime;
}
Console.WriteLine($"Fluent: {totalFluentTime:g}");
Console.WriteLine($"Generation: {totalGenerationTime:g}");
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
}
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var container = new Container();
container
.Padding(10)
.MinimalBox()
.Border(1)
.Column(column =>
{
column.Item().Text($"Attempts {attemptNumber}");
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);
}
});
}
});
var fluentTime = stopwatch.Elapsed;
stopwatch.Reset();
stopwatch.Start();
var size = Document
.Create(x => x.Page(page => page.Content().Element(container)))
.GeneratePdf()
.Length;
var generationTime = stopwatch.Elapsed;
return new ProcessRunningTime
{
FluentTime = fluentTime,
GenerationTime = generationTime,
Size = size
};
}
}
}
+4 -4
View File
@@ -6,11 +6,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="microcharts" Version="0.9.5.9" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="SkiaSharp" Version="2.80.4" />
<PackageReference Include="Svg.Skia" Version="0.5.10" />
</ItemGroup>
@@ -0,0 +1,36 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class RelativePaddingExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(250, 250)
.ShowResults()
.Render(container =>
{
container
.Width(250)
.Height(250)
.Padding(50)
.Background(Colors.Grey.Lighten2)
.RelativePaddingLeft(0.1f)
.RelativePaddingTop(0.2f)
.RelativePaddingRight(0.3f)
.RelativePaddingBottom(0.4f)
.Background(Colors.Grey.Darken2);
});
}
}
}
@@ -0,0 +1,32 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class RelativePositionExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(500, 500)
.ShowResults()
.Render(container =>
{
container
.Padding(100)
.Background(Colors.Grey.Lighten2)
.RelativePositionVertical(0.5f, -0.5f)
.RelativePositionHorizontal(1f, -0.5f)
.RelativeWidth(0.4f)
.RelativeHeight(0.6f)
.Background(Colors.Grey.Darken2);
});
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class RelativeSizeExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(600, 600)
.ShowResults()
.Render(container =>
{
container
.AlignMiddle()
.AlignCenter()
.Width(400)
.Height(400)
.Background(Colors.Grey.Lighten2)
.AlignMiddle()
.AlignCenter()
.Container()
.AlignMiddle()
.AlignCenter()
.RelativeWidth(0.25f)
.RelativeHeight(0.5f)
.Background(Colors.Grey.Darken2);
});
}
}
}
+93
View File
@@ -0,0 +1,93 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class ShrinkExamples
{
[Test]
public void Shrink_Without()
{
RenderingTest
.Create()
.PageSize(300, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Border(2)
.Background(Colors.Grey.Lighten2)
.Padding(20)
.Text("This is test.")
.FontSize(20);
});
}
[Test]
public void Shrink_Horizontal()
{
RenderingTest
.Create()
.PageSize(300, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Border(2)
.ShrinkHorizontal()
.Background(Colors.Grey.Lighten2)
.Padding(20)
.Text("This is test.")
.FontSize(20);
});
}
[Test]
public void Shrink_Vertical()
{
RenderingTest
.Create()
.PageSize(300, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Border(2)
.ShrinkVertical()
.Background(Colors.Grey.Lighten2)
.Padding(20)
.Text("This is test.")
.FontSize(20);
});
}
[Test]
public void Shrink_Both()
{
RenderingTest
.Create()
.PageSize(300, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Border(2)
.Shrink()
.Background(Colors.Grey.Lighten2)
.Padding(20)
.Text("This is test.")
.FontSize(20);
});
}
}
}
+1 -1
View File
@@ -111,7 +111,7 @@ namespace QuestPDF.Examples
{
page.Margin(50);
page.Content().Column(column =>
page.Content().PaddingVertical(10).Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
+39
View File
@@ -2,6 +2,7 @@
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuestPDF.Elements.Text;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -618,5 +619,43 @@ namespace QuestPDF.Examples
.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: 😊😅🥳👍❤😍👌");
});
});
});
}
}
}
@@ -49,7 +49,7 @@ namespace QuestPDF.Previewer
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/documentation/api-reference.html"));
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/api-reference/index.html"));
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
}
+1 -1
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF.Previewer</PackageId>
<Version>2022.8.0</Version>
<Version>2022.9.1</Version>
<PackAsTool>true</PackAsTool>
<ToolCommandName>questpdf-previewer</ToolCommandName>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
+2 -2
View File
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
Content = documentContainer.Compose();
PageContext = new PageContext();
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
var sw = new Stopwatch();
sw.Start();
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
[Benchmark]
public void GenerationTest()
{
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
}
}
}
+2 -3
View File
@@ -48,11 +48,10 @@ namespace QuestPDF.ReportSample
Report.Compose(container);
var content = container.Compose();
var metadata = Report.GetMetadata();
var pageContext = new PageContext();
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
@@ -7,18 +7,20 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class BoxTests
public class ShrinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
public void Measure() => SimpleContainerTests.Measure<Shrink>();
[Test]
public void Draw_Wrap()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Shrink
{
Child = x.CreateChild()
Child = x.CreateChild(),
ShrinkVertical = true,
ShrinkHorizontal = true
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.Wrap())
@@ -29,9 +31,11 @@ namespace QuestPDF.UnitTests
public void Measure_PartialRender()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Shrink
{
Child = x.CreateChild()
Child = x.CreateChild(),
ShrinkVertical = true,
ShrinkHorizontal = true
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
@@ -43,9 +47,11 @@ namespace QuestPDF.UnitTests
public void Measure_FullRender()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Shrink
{
Child = x.CreateChild()
Child = x.CreateChild(),
ShrinkVertical = true,
ShrinkHorizontal = true
})
.MeasureElement(new Size(500, 400))
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
+14 -21
View File
@@ -66,19 +66,17 @@ namespace QuestPDF.Drawing
var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
var metadata = document.GetMetadata();
var pageContext = new PageContext();
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
if (metadata.ApplyCaching)
if (Settings.EnableCaching)
ApplyCaching(content);
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
RenderPass(pageContext, canvas, content, metadata, debuggingState);
var pageContext = new PageContext();
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
RenderPass(pageContext, canvas, content, debuggingState);
}
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas
{
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
@@ -114,7 +112,7 @@ namespace QuestPDF.Drawing
canvas.EndPage();
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
if (currentPage >= Settings.DocumentLayoutExceptionThreshold)
{
canvas.EndDocument();
ThrowLayoutException();
@@ -131,8 +129,8 @@ namespace QuestPDF.Drawing
void ThrowLayoutException()
{
var message = $"Composed layout generates infinite document. This may happen in two cases. " +
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
$"1) Your document and its layout configuration is correct but the content takes more than {Settings.DocumentLayoutExceptionThreshold} pages. " +
$"In this case, please increase the value {nameof(QuestPDF)}.{nameof(Settings)}.{nameof(Settings.DocumentLayoutExceptionThreshold)} static property. " +
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
$"Please analyze your documents structure to detect this element and fix its size constraints.";
@@ -174,7 +172,7 @@ namespace QuestPDF.Drawing
{
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
}
else if (textBlockItem is TextBlockElement textElement)
{
@@ -186,18 +184,13 @@ namespace QuestPDF.Drawing
}
if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
var targetTextStyle = documentDefaultTextStyle;
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
if (content is DefaultTextStyle defaultTextStyleElement)
{
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle);
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
}
}
}
+20 -7
View File
@@ -1,4 +1,5 @@
using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
@@ -18,14 +19,26 @@ namespace QuestPDF.Drawing
public DateTime CreationDate { get; set; } = DateTime.Now;
public DateTime ModifiedDate { get; set; } = DateTime.Now;
/// <summary>
/// If the number of generated pages exceeds this threshold
/// (likely due to infinite layout), the exception is thrown.
/// </summary>
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")]
public int DocumentLayoutExceptionThreshold
{
get => Settings.DocumentLayoutExceptionThreshold;
set => Settings.DocumentLayoutExceptionThreshold = value;
}
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableCaching static property.")]
public bool ApplyCaching
{
get => Settings.EnableCaching;
set => Settings.EnableCaching = value;
}
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableDebugging static property.")]
public bool ApplyDebugging
{
get => Settings.EnableDebugging;
set => Settings.EnableDebugging = value;
}
public static DocumentMetadata Default => new DocumentMetadata();
}
@@ -4,6 +4,11 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentDrawingException : Exception
{
internal DocumentDrawingException(string message) : base(message)
{
}
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
{
+13 -13
View File
@@ -14,13 +14,13 @@ namespace QuestPDF.Drawing
{
public static class FontManager
{
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> FontPaints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static ConcurrentDictionary<object, Font> ShaperFonts = new();
private static ConcurrentDictionary<object, SKFont> Fonts = new();
private static ConcurrentDictionary<object, TextShaper> TextShapers = new();
private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
private static readonly ConcurrentDictionary<TextStyle, SKFont> Fonts = new();
private static readonly ConcurrentDictionary<TextStyle, TextShaper> TextShapers = new();
static FontManager()
{
@@ -110,7 +110,7 @@ namespace QuestPDF.Drawing
internal static SKPaint ToPaint(this TextStyle style)
{
return FontPaints.GetOrAdd(style.PaintKey, key => Convert(style));
return FontPaints.GetOrAdd(style, Convert);
static SKPaint Convert(TextStyle style)
{
@@ -172,14 +172,14 @@ namespace QuestPDF.Drawing
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.NormalPosition().ToPaint().FontMetrics);
return FontMetrics.GetOrAdd(style, key => key.NormalPosition().ToPaint().FontMetrics);
}
internal static Font ToShaperFont(this TextStyle style)
{
return ShaperFonts.GetOrAdd(style.PaintKey, _ =>
return ShaperFonts.GetOrAdd(style, key =>
{
var typeface = style.ToPaint().Typeface;
var typeface = key.ToPaint().Typeface;
using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
@@ -200,12 +200,12 @@ namespace QuestPDF.Drawing
internal static TextShaper ToTextShaper(this TextStyle style)
{
return TextShapers.GetOrAdd(style.PaintKey, _ => new TextShaper(style));
return TextShapers.GetOrAdd(style, key => new TextShaper(key));
}
internal static SKFont ToFont(this TextStyle style)
{
return Fonts.GetOrAdd(style.PaintKey, _ => style.ToPaint().ToFont());
return Fonts.GetOrAdd(style, key => key.ToPaint().ToFont());
}
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace QuestPDF.Drawing
{
internal struct TextMeasurement
{
public int LineIndex { get; set; }
public float FragmentWidth { get; set; }
}
}
+10 -7
View File
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using HarfBuzzSharp;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -9,14 +10,16 @@ namespace QuestPDF.Drawing
internal class TextShaper
{
public const int FontShapingScale = 512;
private Font Font { get; }
private SKPaint Paint { get; }
public TextShaper(TextStyle style)
private TextStyle TextStyle { get; }
private SKFont Font => TextStyle.ToFont();
private Font ShaperFont => TextStyle.ToShaperFont();
private SKPaint Paint => TextStyle.ToPaint();
public TextShaper(TextStyle textStyle)
{
Font = style.ToShaperFont();
Paint = style.ToPaint();
TextStyle = textStyle;
}
public TextShapingResult Shape(string text)
@@ -26,7 +29,7 @@ namespace QuestPDF.Drawing
PopulateBufferWithText(buffer, text);
buffer.GuessSegmentProperties();
Font.Shape(buffer);
ShaperFont.Shape(buffer);
var length = buffer.Length;
var glyphInfos = buffer.GlyphInfos;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
private DynamicComponentProxy Child { get; }
private object InitialComponentState { get; set; }
internal TextStyle TextStyle { get; } = new();
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
public DynamicHost(DynamicComponentProxy child)
{
-18
View File
@@ -1,18 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class MinimalBox : ContainerElement
{
internal override void Draw(Size availableSpace)
{
var targetSize = base.Measure(availableSpace);
if (targetSize.Type == SpacePlanType.Wrap)
return;
base.Draw(targetSize);
}
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RelativePadding : ContainerElement
{
public float Top { get; set; }
public float Right { get; set; }
public float Bottom { get; set; }
public float Left { get; set; }
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(0, 0);
var internalSpace = InternalSpace(availableSpace);
if (internalSpace.Width < 0 || internalSpace.Height < 0)
return SpacePlan.Wrap();
var measure = base.Measure(internalSpace);
if (measure.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
if (measure.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(availableSpace);
if (measure.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(availableSpace);
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
{
if (Child == null)
return;
var internalOffset = InternalOffset(availableSpace);
var internalSpace = InternalSpace(availableSpace);
Canvas.Translate(internalOffset);
base.Draw(internalSpace);
Canvas.Translate(internalOffset.Reverse());
}
private Position InternalOffset(Size availableSpace)
{
return new Position(
availableSpace.Width * Left,
availableSpace.Height * Top);
}
private Size InternalSpace(Size availableSpace)
{
return new Size(
availableSpace.Width * (1f - Left - Right),
availableSpace.Height * (1f - Top - Bottom));
}
}
}
+33
View File
@@ -0,0 +1,33 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RelativePosition : ContainerElement
{
public float VerticalParent { get; set; }
public float VerticalChild { get; set; }
public float HorizontalParent { get; set; }
public float HorizontalChild { get; set; }
internal override void Draw(Size availableSpace)
{
if (Child == null)
return;
var childSize = base.Measure(availableSpace);
if (childSize.Type == SpacePlanType.Wrap)
return;
var left = availableSpace.Width * HorizontalParent + childSize.Width * HorizontalChild;
var top = availableSpace.Height * VerticalParent + childSize.Height * VerticalChild;
Canvas.Translate(new Position(left, top));
base.Draw(childSize);
Canvas.Translate(new Position(-left, -top));
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RelativeSize : ContainerElement
{
public float? WidthFactor { get; set; } = 1f;
public float? HeightFactor { get; set; } = 1f;
internal override SpacePlan Measure(Size availableSpace)
{
var internalSpace = new Size(
availableSpace.Width * (WidthFactor ?? 1),
availableSpace.Height * (HeightFactor ?? 1));
var childSpace = Child?.Measure(internalSpace) ?? SpacePlan.FullRender(0, 0);
if (childSpace.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
var targetSpace = new Size(
WidthFactor.HasValue ? internalSpace.Width : childSpace.Width,
HeightFactor.HasValue ? internalSpace.Height : childSpace.Height);
if (childSpace.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSpace);
if (childSpace.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(targetSpace);
throw new ArgumentException();
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Shrink : ContainerElement
{
public bool ShrinkVertical { get; set; }
public bool ShrinkHorizontal { get; set; }
internal override void Draw(Size availableSpace)
{
var childSize = base.Measure(availableSpace);
if (childSize.Type == SpacePlanType.Wrap)
return;
var targetSize = new Size(
ShrinkVertical ? childSize.Width : availableSpace.Width,
ShrinkHorizontal ? childSize.Height : availableSpace.Height);
// TODO: adjust offset for RTL mode
base.Draw(targetSize);
}
}
}
@@ -19,6 +19,5 @@ namespace QuestPDF.Elements.Text.Calculation
public int TotalIndex { get; set; }
public bool IsLast => EndIndex == TotalIndex;
public bool IsNewLine { get; set; }
}
}
+150
View File
@@ -0,0 +1,150 @@
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;
}
}
}
}
}
+6 -26
View File
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
internal class TextBlockSpan : ITextBlockItem
{
public string Text { get; set; }
public TextStyle Style { get; set; } = new();
public TextStyle Style { get; set; } = TextStyle.Default;
public TextShapingResult? TextShapingResult { get; set; }
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
@@ -39,34 +39,18 @@ namespace QuestPDF.Elements.Text.Items
var paint = Style.ToPaint();
var fontMetrics = Style.ToFontMetrics();
var spaceCodepoint = paint.ToFont().Typeface.GetGlyphs(" ")[0];
// if the element is the first one within the line,
// ignore leading spaces and new lines
var spaceCodepoint = paint.ToFont().Typeface.GetGlyph(' ');
var newLineCodepoint = paint.ToFont().Typeface.GetGlyph('\n');
var returnCodepoint = paint.ToFont().Typeface.GetGlyph('\r');
var startIndex = request.StartIndex;
// if the element is the first one within the line,
// ignore leading spaces
if (!request.IsFirstElementInBlock && request.IsFirstElementInLine)
{
while (startIndex < TextShapingResult.Glyphs.Length && (Text[startIndex] == spaceCodepoint || Text[startIndex] == newLineCodepoint || Text[startIndex] == returnCodepoint))
while (startIndex < TextShapingResult.Glyphs.Length && Text[startIndex] == spaceCodepoint)
startIndex++;
}
// calculate max index (new new line)
var newLineIndex = startIndex;
while (newLineIndex < TextShapingResult.Glyphs.Length)
{
var glyphCodepoint = TextShapingResult.Glyphs[newLineIndex].Codepoint;
if (glyphCodepoint == newLineCodepoint || glyphCodepoint == returnCodepoint)
break;
newLineIndex++;
}
if (TextShapingResult.Glyphs.Length == 0 || startIndex == TextShapingResult.Glyphs.Length)
{
return new TextMeasurementResult
@@ -84,8 +68,6 @@ namespace QuestPDF.Elements.Text.Items
if (endIndex < startIndex)
return null;
endIndex = Math.Min(endIndex, newLineIndex);
// break text only on spaces
var wrappedText = WrapText(startIndex, endIndex, request.IsFirstElementInLine);
@@ -108,9 +90,7 @@ namespace QuestPDF.Elements.Text.Items
StartIndex = startIndex,
EndIndex = wrappedText.Value.endIndex,
NextIndex = wrappedText.Value.nextIndex,
TotalIndex = TextShapingResult.Glyphs.Length - 1,
IsNewLine = endIndex == newLineIndex
TotalIndex = TextShapingResult.Glyphs.Length - 1
};
}
+13 -8
View File
@@ -11,16 +11,18 @@ namespace QuestPDF.Elements.Text
internal class TextBlock : Element, IStateResettable
{
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new();
public float ParagraphSpacing { get; set; } = 0;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
private Queue<ITextBlockItem> RenderingQueue { get; set; }
private int CurrentElementIndex { get; set; }
private bool FontFallbackApplied { get; set; } = false;
public void ResetState()
{
ApplyFontFallback();
InitializeQueue();
CurrentElementIndex = 0;
@@ -38,6 +40,15 @@ namespace QuestPDF.Elements.Text
foreach (var item in Items)
RenderingQueue.Enqueue(item);
}
void ApplyFontFallback()
{
if (FontFallbackApplied)
return;
Items = Items.ApplyFontFallback().ToList();
FontFallbackApplied = true;
}
}
internal override SpacePlan Measure(Size availableSpace)
@@ -111,12 +122,6 @@ namespace QuestPDF.Elements.Text
Canvas.Translate(new Position(0, line.LineHeight));
heightOffset += line.LineHeight;
if (line.Elements.Last().Measurement.IsNewLine)
{
heightOffset += ParagraphSpacing;
Canvas.Translate(new Position(0, ParagraphSpacing));
}
}
Canvas.Translate(new Position(0, -heightOffset));
-11
View File
@@ -154,17 +154,6 @@ namespace QuestPDF.Fluent
});
}
[Obsolete("This element has been renamed since version 2022.1. Please use the MinimalBox method.")]
public static IContainer Box(this IContainer element)
{
return element.Element(new MinimalBox());
}
public static IContainer MinimalBox(this IContainer element)
{
return element.Element(new MinimalBox());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
@@ -0,0 +1,37 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RelativePaddingExtensions
{
private static IContainer RelativePadding(this IContainer element, Action<RelativePadding> handler)
{
var relativePadding = element as RelativePadding ?? new RelativePadding();
handler(relativePadding);
return element.Element(relativePadding);
}
public static IContainer RelativePaddingTop(this IContainer element, float value)
{
return element.RelativePadding(x => x.Top += value);
}
public static IContainer RelativePaddingBottom(this IContainer element, float value)
{
return element.RelativePadding(x => x.Bottom += value);
}
public static IContainer RelativePaddingLeft(this IContainer element, float value)
{
return element.RelativePadding(x => x.Left += value);
}
public static IContainer RelativePaddingRight(this IContainer element, float value)
{
return element.RelativePadding(x => x.Right += value);
}
}
}
@@ -0,0 +1,35 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RelativePositionExtensions
{
private static IContainer RelativePosition(this IContainer element, Action<RelativePosition> handler)
{
var relativePosition = element as RelativePosition ?? new RelativePosition();
handler(relativePosition);
return element.Element(relativePosition);
}
public static IContainer RelativePositionVertical(this IContainer element, float parentOffset, float childOffset)
{
return element.RelativePosition(x =>
{
x.VerticalParent = parentOffset;
x.VerticalChild = childOffset;
});
}
public static IContainer RelativePositionHorizontal(this IContainer element, float parentOffset, float childOffset)
{
return element.RelativePosition(x =>
{
x.HorizontalParent = parentOffset;
x.HorizontalChild = childOffset;
});
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RelativeSizeExtensions
{
private static IContainer RelativeSize(this IContainer element, Action<RelativeSize> handler)
{
var relativeSize = element as RelativeSize ?? new RelativeSize();
handler(relativeSize);
return element.Element(relativeSize);
}
public static IContainer RelativeWidth(this IContainer element, float value)
{
return element.RelativeSize(x => x.WidthFactor = value);
}
public static IContainer RelativeHeight(this IContainer element, float value)
{
return element.RelativeSize(x => x.HeightFactor = value);
}
}
}
+48
View File
@@ -0,0 +1,48 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class ShrinkExtensions
{
private static IContainer Shrink(this IContainer element, Action<Shrink> handler)
{
var shrink = element as Shrink ?? new Shrink();
handler(shrink);
return element.Element(shrink);
}
public static IContainer Shrink(this IContainer element)
{
return element.ShrinkVertical().ShrinkHorizontal();
}
public static IContainer ShrinkVertical(this IContainer element)
{
return element.Shrink(x => x.ShrinkVertical = true);
}
public static IContainer ShrinkHorizontal(this IContainer element)
{
return element.Shrink(x => x.ShrinkHorizontal = true);
}
#region Obsolete
[Obsolete("This element has been renamed since version 2022.1. Please use the Shrink method.")]
public static IContainer Box(this IContainer element)
{
return element.Element(new Shrink());
}
[Obsolete("This element has been renamed since version 2022.11. Please use the Shrink method.")]
public static IContainer MinimalBox(this IContainer element)
{
return element.Element(new Shrink());
}
#endregion
}
}
+89 -51
View File
@@ -12,11 +12,18 @@ namespace QuestPDF.Fluent
{
public class TextSpanDescriptor
{
internal TextStyle TextStyle { get; }
internal TextStyle TextStyle = TextStyle.Default;
internal Action<TextStyle> AssignTextStyle { get; }
internal TextSpanDescriptor(TextStyle textStyle)
internal TextSpanDescriptor(Action<TextStyle> assignTextStyle)
{
TextStyle = textStyle;
AssignTextStyle = assignTextStyle;
}
internal void MutateTextStyle(Func<TextStyle, TextStyle> handler)
{
TextStyle = handler(TextStyle);
AssignTextStyle(TextStyle);
}
}
@@ -24,25 +31,27 @@ namespace QuestPDF.Fluent
public class TextPageNumberDescriptor : TextSpanDescriptor
{
internal PageNumberFormatter FormatFunction { get; private set; } = x => x?.ToString() ?? string.Empty;
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
internal Action<PageNumberFormatter> AssignFormatFunction { get; }
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
{
AssignFormatFunction = assignFormatFunction;
AssignFormatFunction(x => x?.ToString());
}
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
{
FormatFunction = formatter ?? FormatFunction;
AssignFormatFunction(formatter);
return this;
}
}
public class TextDescriptor
{
internal TextBlock TextBlock = new();
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
private TextStyle? DefaultStyle { get; set; }
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f;
public void DefaultTextStyle(TextStyle style)
{
@@ -71,9 +80,17 @@ namespace QuestPDF.Fluent
public void ParagraphSpacing(float value, Unit unit = Unit.Point)
{
TextBlock.ParagraphSpacing = value.ToPoints(unit);
Spacing = value.ToPoints(unit);
}
private void AddItemToLastTextBlock(ITextBlockItem item)
{
if (!TextBlocks.Any())
TextBlocks.Add(new TextBlock());
TextBlocks.Last().Items.Add(item);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
public void Span(string? text, TextStyle style)
{
@@ -82,19 +99,30 @@ namespace QuestPDF.Fluent
public TextSpanDescriptor Span(string? text)
{
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (text == null)
return descriptor;
TextBlock.Items.Add(new TextBlockSpan
{
Text = text,
Style = style
});
return descriptor;
return new TextSpanDescriptor(_ => { });
var items = text
.Replace("\r", string.Empty)
.Split(new[] { '\n' }, StringSplitOptions.None)
.Select(x => new TextBlockSpan
{
Text = x
})
.ToList();
AddItemToLastTextBlock(items.First());
items
.Skip(1)
.Select(x => new TextBlock
{
Items = new List<ITextBlockItem> { x }
})
.ToList()
.ForEach(TextBlocks.Add);
return new TextSpanDescriptor(x => items.ForEach(y => y.Style = x));
}
public TextSpanDescriptor Line(string? text)
@@ -110,16 +138,10 @@ namespace QuestPDF.Fluent
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
{
var style = DefaultStyle.Clone();
var descriptor = new TextPageNumberDescriptor(style);
var textBlockItem = new TextBlockPageNumber();
AddItemToLastTextBlock(textBlockItem);
TextBlock.Items.Add(new TextBlockPageNumber
{
Source = context => descriptor.FormatFunction(pageNumber(context)),
Style = style
});
return descriptor;
return new TextPageNumberDescriptor(x => textBlockItem.Style = x, x => textBlockItem.Source = context => x(pageNumber(context)));
}
public TextPageNumberDescriptor CurrentPageNumber()
@@ -163,20 +185,17 @@ namespace QuestPDF.Fluent
if (IsNullOrEmpty(sectionName))
throw new ArgumentException("Section name cannot be null or empty", nameof(sectionName));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text))
return descriptor;
TextBlock.Items.Add(new TextBlockSectionLink
return new TextSpanDescriptor(_ => { });
var textBlockItem = new TextBlockSectionLink
{
Style = style,
Text = text,
SectionName = sectionName
});
};
return descriptor;
AddItemToLastTextBlock(textBlockItem);
return new TextSpanDescriptor(x => textBlockItem.Style = x);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
@@ -190,20 +209,17 @@ namespace QuestPDF.Fluent
if (IsNullOrEmpty(url))
throw new ArgumentException("Url cannot be null or empty", nameof(url));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text))
return descriptor;
return new TextSpanDescriptor(_ => { });
TextBlock.Items.Add(new TextBlockHyperlink
var textBlockItem = new TextBlockHyperlink
{
Style = style,
Text = text,
Url = url
});
};
return descriptor;
AddItemToLastTextBlock(textBlockItem);
return new TextSpanDescriptor(x => textBlockItem.Style = x);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
@@ -216,13 +232,35 @@ namespace QuestPDF.Fluent
{
var container = new Container();
TextBlock.Items.Add(new TextBlockElement
AddItemToLastTextBlock(new TextBlockElement
{
Element = container
});
return container.AlignBottom().MinimalBox();
}
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
if (DefaultStyle != null)
container = container.DefaultTextStyle(DefaultStyle);
if (TextBlocks.Count == 1)
{
container.Element(TextBlocks.First());
return;
}
container.Column(column =>
{
column.Spacing(Spacing);
foreach (var textBlock in TextBlocks)
column.Item().Element(textBlock);
});
}
}
public static class TextExtensions
@@ -235,7 +273,7 @@ namespace QuestPDF.Fluent
descriptor.Alignment = alignment.Horizontal;
content?.Invoke(descriptor);
element.Element(descriptor.TextBlock);
descriptor.Compose(element);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
+47 -34
View File
@@ -11,120 +11,135 @@ namespace QuestPDF.Fluent
if (style == null)
return descriptor;
descriptor.TextStyle.OverrideStyle(style);
descriptor.MutateTextStyle(x => x.OverrideStyle(style));
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
{
descriptor.TextStyle.Color = value;
descriptor.MutateTextStyle(x => x.FontColor(value));
return descriptor;
}
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.BackgroundColor = value;
descriptor.MutateTextStyle(x => x.BackgroundColor(value));
return descriptor;
}
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontFamily = value;
descriptor.MutateTextStyle(x => x.FontFamily(value));
return descriptor;
}
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.Size = value;
descriptor.MutateTextStyle(x => x.FontSize(value));
return descriptor;
}
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.LineHeight = value;
descriptor.MutateTextStyle(x => x.LineHeight(value));
return descriptor;
}
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.IsItalic = value;
descriptor.MutateTextStyle(x => x.Italic(value));
return descriptor;
}
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasStrikethrough = value;
descriptor.MutateTextStyle(x => x.Strikethrough(value));
return descriptor;
}
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasUnderline = value;
descriptor.MutateTextStyle(x => x.Underline(value));
return descriptor;
}
public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.WrapAnywhere = value;
descriptor.MutateTextStyle(x => x.WrapAnywhere(value));
return descriptor;
}
#region Weight
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontWeight = weight;
return descriptor;
}
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Thin);
descriptor.MutateTextStyle(x => x.Thin());
return descriptor;
}
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraLight);
descriptor.MutateTextStyle(x => x.ExtraLight());
return descriptor;
}
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Light);
descriptor.MutateTextStyle(x => x.Light());
return descriptor;
}
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Normal);
descriptor.MutateTextStyle(x => x.NormalWeight());
return descriptor;
}
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Medium);
descriptor.MutateTextStyle(x => x.Medium());
return descriptor;
}
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.SemiBold);
descriptor.MutateTextStyle(x => x.SemiBold());
return descriptor;
}
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Bold);
descriptor.MutateTextStyle(x => x.Bold());
return descriptor;
}
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBold);
descriptor.MutateTextStyle(x => x.ExtraBold());
return descriptor;
}
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Black);
descriptor.MutateTextStyle(x => x.Black());
return descriptor;
}
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBlack);
descriptor.MutateTextStyle(x => x.ExtraBlack());
return descriptor;
}
#endregion
@@ -132,24 +147,22 @@ namespace QuestPDF.Fluent
#region Position
public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Position(FontPosition.Normal);
descriptor.MutateTextStyle(x => x.NormalPosition());
return descriptor;
}
public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Position(FontPosition.Subscript);
descriptor.MutateTextStyle(x => x.Subscript());
return descriptor;
}
public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Position(FontPosition.Superscript);
}
private static T Position<T>(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontPosition = fontPosition;
descriptor.MutateTextStyle(x => x.Superscript());
return descriptor;
}
#endregion
}
}
+27 -22
View File
@@ -6,14 +6,6 @@ namespace QuestPDF.Fluent
{
public static class TextStyleExtensions
{
private static TextStyle Mutate(this TextStyle style, Action<TextStyle> handler)
{
style = style.Clone();
handler(style);
return style;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
public static TextStyle Color(this TextStyle style, string value)
{
@@ -22,12 +14,12 @@ namespace QuestPDF.Fluent
public static TextStyle FontColor(this TextStyle style, string value)
{
return style.Mutate(x => x.Color = value);
return style.Mutate(TextStyleProperty.Color, value);
}
public static TextStyle BackgroundColor(this TextStyle style, string value)
{
return style.Mutate(x => x.BackgroundColor = value);
return style.Mutate(TextStyleProperty.BackgroundColor, value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
@@ -38,7 +30,7 @@ namespace QuestPDF.Fluent
public static TextStyle FontFamily(this TextStyle style, string value)
{
return style.Mutate(x => x.FontFamily = value);
return style.Mutate(TextStyleProperty.FontFamily, value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
@@ -49,39 +41,39 @@ namespace QuestPDF.Fluent
public static TextStyle FontSize(this TextStyle style, float value)
{
return style.Mutate(x => x.Size = value);
return style.Mutate(TextStyleProperty.Size, value);
}
public static TextStyle LineHeight(this TextStyle style, float value)
{
return style.Mutate(x => x.LineHeight = value);
return style.Mutate(TextStyleProperty.LineHeight, value);
}
public static TextStyle Italic(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.IsItalic = value);
return style.Mutate(TextStyleProperty.IsItalic, value);
}
public static TextStyle Strikethrough(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.HasStrikethrough = value);
return style.Mutate(TextStyleProperty.HasStrikethrough, value);
}
public static TextStyle Underline(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.HasUnderline = value);
return style.Mutate(TextStyleProperty.HasUnderline, value);
}
public static TextStyle WrapAnywhere(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.WrapAnywhere = value);
return style.Mutate(TextStyleProperty.WrapAnywhere, value);
}
#region Weight
public static TextStyle Weight(this TextStyle style, FontWeight weight)
{
return style.Mutate(x => x.FontWeight = weight);
return style.Mutate(TextStyleProperty.FontWeight, weight);
}
public static TextStyle Thin(this TextStyle style)
@@ -137,6 +129,7 @@ namespace QuestPDF.Fluent
#endregion
#region Position
public static TextStyle NormalPosition(this TextStyle style)
{
return style.Position(FontPosition.Normal);
@@ -154,11 +147,23 @@ namespace QuestPDF.Fluent
private static TextStyle Position(this TextStyle style, FontPosition fontPosition)
{
if (style.FontPosition == fontPosition)
return style;
return style.Mutate(t => t.FontPosition = fontPosition);
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
}
#endregion
#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
}
}
+7 -62
View File
@@ -3,10 +3,8 @@ using QuestPDF.Helpers;
namespace QuestPDF.Infrastructure
{
public class TextStyle
public record TextStyle
{
internal bool HasGlobalStyleApplied { get; private set; }
internal string? Color { get; set; }
internal string? BackgroundColor { get; set; }
internal string? FontFamily { get; set; }
@@ -19,13 +17,9 @@ namespace QuestPDF.Infrastructure
internal bool? HasUnderline { get; set; }
internal bool? WrapAnywhere { get; set; }
internal object PaintKey { get; private set; }
internal object FontMetricsKey { get; private set; }
// 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
internal TextStyle? Fallback { get; set; }
internal static TextStyle LibraryDefault { get; } = new()
{
Color = Colors.Black,
BackgroundColor = Colors.Transparent,
@@ -37,59 +31,10 @@ namespace QuestPDF.Infrastructure
IsItalic = false,
HasStrikethrough = false,
HasUnderline = false,
WrapAnywhere = false
WrapAnywhere = false,
Fallback = null
};
// it is important to create new instances for the DefaultTextStyle element to work correctly
public static TextStyle Default => new TextStyle();
internal void ApplyGlobalStyle(TextStyle globalStyle)
{
if (HasGlobalStyleApplied)
return;
HasGlobalStyleApplied = true;
ApplyParentStyle(globalStyle);
PaintKey ??= (FontFamily, Size, FontWeight, FontPosition, IsItalic, Color);
FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
}
internal void ApplyParentStyle(TextStyle parentStyle)
{
Color ??= parentStyle.Color;
BackgroundColor ??= parentStyle.BackgroundColor;
FontFamily ??= parentStyle.FontFamily;
Size ??= parentStyle.Size;
LineHeight ??= parentStyle.LineHeight;
FontWeight ??= parentStyle.FontWeight;
FontPosition ??= parentStyle.FontPosition;
IsItalic ??= parentStyle.IsItalic;
HasStrikethrough ??= parentStyle.HasStrikethrough;
HasUnderline ??= parentStyle.HasUnderline;
WrapAnywhere ??= parentStyle.WrapAnywhere;
}
internal void OverrideStyle(TextStyle parentStyle)
{
Color = parentStyle.Color ?? Color;
BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor;
FontFamily = parentStyle.FontFamily ?? FontFamily;
Size = parentStyle.Size ?? Size;
LineHeight = parentStyle.LineHeight ?? LineHeight;
FontWeight = parentStyle.FontWeight ?? FontWeight;
FontPosition = parentStyle.FontPosition ?? FontPosition;
IsItalic = parentStyle.IsItalic ?? IsItalic;
HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
WrapAnywhere = parentStyle.WrapAnywhere ?? WrapAnywhere;
}
internal TextStyle Clone()
{
var clone = (TextStyle)MemberwiseClone();
clone.HasGlobalStyleApplied = false;
return clone;
}
public static TextStyle Default { get; } = new();
}
}
+248
View File
@@ -0,0 +1,248 @@
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,
Fallback
}
internal static class TextStyleManager
{
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new();
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = 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 };
}
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.");
}
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
{
var cacheKey = (style, parent);
return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
}
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)
{
var cacheKey = (style, parent);
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)
{
var result = style;
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle);
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle);
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle);
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle);
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle);
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle);
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
if (applyFallback)
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
return result;
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ namespace QuestPDF.Previewer
public event Action? OnPreviewerStopped;
private const int RequiredPreviewerVersionMajor = 2022;
private const int RequiredPreviewerVersionMinor = 8;
private const int RequiredPreviewerVersionMinor = 9;
public PreviewerService(int port)
{
+1 -1
View File
@@ -3,7 +3,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2202.8.2</Version>
<Version>2022.9.0</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
+6 -20
View File
@@ -1,20 +1,6 @@
2022.8.0:
- Improved library performance,
- Breaking change: changed default font from Calibri to an open-source Lato,
- Default font files are included with the nuget package, making it safe to deploy on any environment,
- Default font files are significantly smaller, so output document files should be smaller too (up to 20x reduction in size),
- When requested font is not available on the runtime environment, library provides list of available fonts,
- Fixed a rare layout overflow exception with the Inlined element,
- Fixed a memory leak connected to the HarfBuzz library.
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
2202.8.2
- Fixed: the Column element incorrectly renders zero-height elements.
2022.9.0
- Implemented font-fallback algorithm,
- Introduced new Settings API,
- Significantly reduced memory allocation cost for TextStyle objects,
- Implemented optional checking if all font glyphs are available,
- Minor text-rendering optimizations.
+40
View File
@@ -0,0 +1,40 @@
namespace QuestPDF
{
public static class Settings
{
/// <summary>
/// This value represents the maximum length 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 CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
}
}
+2 -1
View File
@@ -23,7 +23,8 @@ Choosing a project dependency could be difficult. We need to ensure stability an
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
<img src="https://user-images.githubusercontent.com/9263853/184642026-27dd7567-a46a-45d4-9594-e6a70a7193e9.png" width="700" />
<img src="https://user-images.githubusercontent.com/9263853/190931857-8ca52ec8-cc7d-4d12-9467-4442b3342fa1.png" width="700" />
## Please share with the community