Compare commits

...

34 Commits

Author SHA1 Message Date
MarcinZiabek 09e642295f Repeat content experiment 2022-10-28 23:22:11 +02:00
MarcinZiabek 65990ebf59 Added the WordWrappingStability test 2022-10-21 12:05:45 +02:00
MarcinZiabek 9d86e9efd3 DynamicImage: simplified implementation 2022-10-19 20:08:41 +02:00
Marcin Ziąbek 5cbacc7ea1 Update readme.md 2022-10-19 19:15:25 +02:00
MarcinZiabek 8214a8429d 2022.9.1 2022-10-15 23:03:25 +02:00
MarcinZiabek 389b8ce304 Inlined: improved behaviour predictability 2022-10-15 22:49:50 +02:00
MarcinZiabek e1f7cff4aa Fix: performance regression of the Table element #320 2022-10-11 23:19:13 +02:00
MarcinZiabek 6801425082 Fix: cells with RowSpan are not always displayed properly #320 2022-10-11 22:39:31 +02:00
MarcinZiabek ec31c9b063 Fix: CheckIfAllTextGlyphsAreAvailable and Fallback break hyperlinks 2022-10-11 10:31:31 +02:00
MarcinZiabek 6c8867e1b3 Added test: ReusingTheSameImageFileShouldBePossible 2022-10-06 23:20:17 +02:00
Marcin Ziąbek 20d86cbfde Update readme.md 2022-10-05 22:49:03 +02:00
MarcinZiabek 16a164e5b8 Example: achieve proportional height division 2022-10-05 01:15:36 +02:00
Marcin Ziąbek 261f087b46 Update readme.md 2022-10-04 12:30:36 +02:00
Marcin Ziąbek c876350b05 Merge pull request #354 from CollinAlpert/fix_broken_links
Fix links in README
2022-10-03 22:10:19 +02:00
Collin Alpert 58f3932241 Update readme.md 2022-10-03 15:28:48 +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 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
37 changed files with 917 additions and 326 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
};
}
}
}
+55 -1
View File
@@ -1,4 +1,5 @@
using System.IO; using System;
using System.IO;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
@@ -34,6 +35,21 @@ namespace QuestPDF.Examples
}); });
} }
[Test]
public void DynamicImage()
{
RenderingTest
.Create()
.PageSize(450, 350)
.ProducePdf()
.ShowResults()
.Render(page =>
{
page.Padding(25)
.Image(Placeholders.Image);
});
}
[Test] [Test]
public void Exception() public void Exception()
{ {
@@ -47,5 +63,43 @@ namespace QuestPDF.Examples
.Render(page => page.Image("non_existent.png")); .Render(page => page.Image("non_existent.png"));
}); });
} }
[Test]
public void ReusingTheSameImageFileShouldBePossible()
{
var fileName = Path.GetTempFileName() + ".jpg";
try
{
var image = Placeholders.Image(300, 100);
using var file = File.Create(fileName);
file.Write(image);
file.Dispose();
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Column(column =>
{
column.Spacing(20);
column.Item().Image(fileName);
column.Item().Image(fileName);
column.Item().Image(fileName);
});
});
}
finally
{
File.Delete(fileName);
}
}
} }
} }
@@ -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 RepeatContentExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Decoration(decoration =>
{
decoration.Before().Text("Test").FontSize(22);
decoration.Content().Column(column =>
{
column.Spacing(20);
foreach (var _ in Enumerable.Range(0, 10))
column.Item().Background(Colors.Grey.Medium).ExtendHorizontal().Height(80);
});
});
});
}
}
}
+36
View File
@@ -105,5 +105,41 @@ namespace QuestPDF.Examples
.Row(row => { }); .Row(row => { });
}); });
} }
[Test]
public void RowElementForRelativeHeightDivision()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.MaxPages(100)
.PageSize(250, 400)
.Render(container =>
{
container
.Padding(25)
.AlignLeft()
.RotateRight()
.Row(row =>
{
row.Spacing(20);
row.RelativeItem(1).Element(Content);
row.RelativeItem(2).Element(Content);
row.RelativeItem(3).Element(Content);
void Content(IContainer container)
{
container
.RotateLeft()
.Border(1)
.Background(Placeholders.BackgroundColor())
.Padding(5)
.Text(Placeholders.Label());
}
});
});
}
} }
} }
+1 -1
View File
@@ -111,7 +111,7 @@ namespace QuestPDF.Examples
{ {
page.Margin(50); page.Margin(50);
page.Content().Column(column => page.Content().PaddingVertical(10).Column(column =>
{ {
column.Item().Element(Title); column.Item().Element(Title);
column.Item().PageBreak(); column.Item().PageBreak();
+32
View File
@@ -657,5 +657,37 @@ namespace QuestPDF.Examples
}); });
}); });
} }
[Test]
public void WordWrappingStability()
{
// instruction: check if any characters repeat when performing the word-wrapping algorithm
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(container =>
{
var text = "Lorem ipsum dolor sit amet consectetuer";
container
.Padding(20)
.Column(column =>
{
column.Spacing(10);
foreach (var width in Enumerable.Range(25, 200))
{
column
.Item()
.MaxWidth(width)
.Background(Colors.Grey.Lighten3)
.Text(text);
}
});
});
}
} }
} }
+1 -1
View File
@@ -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.8.0</Version> <Version>2022.9.1</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>
+54 -12
View File
@@ -65,13 +65,14 @@ namespace QuestPDF.Drawing
document.Compose(container); document.Compose(container);
var content = container.Compose(); var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
ApplyContentRepeatState(content, false);
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
if (Settings.EnableCaching) if (Settings.EnableCaching)
ApplyCaching(content); ApplyCaching(content);
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);
} }
@@ -81,6 +82,8 @@ namespace QuestPDF.Drawing
{ {
content.VisitChildren(x => x?.Initialize(pageContext, canvas)); content.VisitChildren(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState()); content.VisitChildren(x => (x as IStateResettable)?.ResetState());
ResetIsRenderedState(content);
canvas.BeginDocument(); canvas.BeginDocument();
@@ -160,6 +163,50 @@ namespace QuestPDF.Drawing
return debuggingState; return debuggingState;
} }
private static void ApplyContentRepeatState(Element? content, bool repeatContent)
{
if (content == null)
return;
if (content is IVisual visual)
visual.RepeatContent = repeatContent;
if (content is TextBlock textBlock)
{
foreach (var textBlockItem in textBlock.Items)
{
if (textBlockItem is TextBlockElement textElement)
{
ApplyContentRepeatState(textElement.Element, true);
}
}
return;
}
// TODO: apply RepeatState in dynamic content
//if (content is DynamicHost dynamicHost)
// dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
if (content is RepeatContentSetter repeatContentSetter)
repeatContent = repeatContentSetter.RepeatContent;
foreach (var child in content.GetChildren())
ApplyContentRepeatState(child, repeatContent);
}
private static void ResetIsRenderedState(Element? content)
{
if (content == null)
return;
if (content is IVisual visual)
visual.IsRendered = false;
foreach (var child in content.GetChildren())
ResetIsRenderedState(child);
}
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle) internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{ {
@@ -172,7 +219,7 @@ namespace QuestPDF.Drawing
{ {
if (textBlockItem is TextBlockSpan textSpan) if (textBlockItem is TextBlockSpan textSpan)
{ {
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle); textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
} }
else if (textBlockItem is TextBlockElement textElement) else if (textBlockItem is TextBlockElement textElement)
{ {
@@ -184,18 +231,13 @@ namespace QuestPDF.Drawing
} }
if (content is DynamicHost dynamicHost) if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle); dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement) if (content is DefaultTextStyle defaultTextStyleElement)
{ documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren()) foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle); ApplyDefaultTextStyle(child, documentDefaultTextStyle);
} }
} }
} }
+13 -13
View File
@@ -14,13 +14,13 @@ namespace QuestPDF.Drawing
{ {
public static class FontManager public static class FontManager
{ {
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new(); private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new(); private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> FontPaints = new(); private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaints = new(); private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static ConcurrentDictionary<object, Font> ShaperFonts = new(); private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
private static ConcurrentDictionary<object, SKFont> Fonts = new(); private static readonly ConcurrentDictionary<TextStyle, SKFont> Fonts = new();
private static ConcurrentDictionary<object, TextShaper> TextShapers = new(); private static readonly ConcurrentDictionary<TextStyle, TextShaper> TextShapers = new();
static FontManager() static FontManager()
{ {
@@ -110,7 +110,7 @@ namespace QuestPDF.Drawing
internal static SKPaint ToPaint(this TextStyle style) internal static SKPaint ToPaint(this TextStyle style)
{ {
return FontPaints.GetOrAdd(style.PaintKey, key => Convert(style)); return FontPaints.GetOrAdd(style, Convert);
static SKPaint Convert(TextStyle style) static SKPaint Convert(TextStyle style)
{ {
@@ -172,14 +172,14 @@ namespace QuestPDF.Drawing
internal static SKFontMetrics ToFontMetrics(this TextStyle style) internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{ {
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.NormalPosition().ToPaint().FontMetrics); return FontMetrics.GetOrAdd(style, key => key.NormalPosition().ToPaint().FontMetrics);
} }
internal static Font ToShaperFont(this TextStyle style) internal static Font ToShaperFont(this TextStyle style)
{ {
return ShaperFonts.GetOrAdd(style.PaintKey, _ => return ShaperFonts.GetOrAdd(style, key =>
{ {
var typeface = style.ToPaint().Typeface; var typeface = key.ToPaint().Typeface;
using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob(); using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
@@ -200,12 +200,12 @@ namespace QuestPDF.Drawing
internal static TextShaper ToTextShaper(this TextStyle style) internal static TextShaper ToTextShaper(this TextStyle style)
{ {
return TextShapers.GetOrAdd(style.PaintKey, _ => new TextShaper(style)); return TextShapers.GetOrAdd(style, key => new TextShaper(key));
} }
internal static SKFont ToFont(this TextStyle style) internal static SKFont ToFont(this TextStyle style)
{ {
return Fonts.GetOrAdd(style.PaintKey, _ => style.ToPaint().ToFont()); return Fonts.GetOrAdd(style, key => key.ToPaint().ToFont());
} }
} }
} }
-8
View File
@@ -1,8 +0,0 @@
namespace QuestPDF.Drawing
{
internal struct TextMeasurement
{
public int LineIndex { get; set; }
public float FragmentWidth { get; set; }
}
}
-17
View File
@@ -55,9 +55,6 @@ 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);
} }
@@ -78,20 +75,6 @@ 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
+13 -4
View File
@@ -7,19 +7,28 @@ namespace QuestPDF.Elements
{ {
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace); public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
internal class Canvas : Element, ICacheable internal class Canvas : Element, IVisual, ICacheable
{ {
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public DrawOnCanvas Handler { get; set; } public DrawOnCanvas Handler { get; set; }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
return availableSpace.IsNegative() if (availableSpace.IsNegative())
? SpacePlan.Wrap() return SpacePlan.Wrap();
: SpacePlan.FullRender(availableSpace);
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
IsRendered = true;
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas; var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (Handler == null || skiaCanvas == null) if (Handler == null || skiaCanvas == null)
+1 -1
View File
@@ -11,7 +11,7 @@ namespace QuestPDF.Elements
private DynamicComponentProxy Child { get; } private DynamicComponentProxy Child { get; }
private object InitialComponentState { get; set; } private object InitialComponentState { get; set; }
internal TextStyle TextStyle { get; } = new(); internal TextStyle TextStyle { get; set; } = TextStyle.Default;
public DynamicHost(DynamicComponentProxy child) public DynamicHost(DynamicComponentProxy child)
{ {
+18 -11
View File
@@ -6,31 +6,38 @@ using SkiaSharp;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class DynamicImage : Element internal class DynamicImage : Element, IVisual
{ {
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public Func<Size, byte[]>? Source { get; set; } public Func<Size, byte[]>? Source { get; set; }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
return availableSpace.IsNegative() if (availableSpace.IsNegative())
? SpacePlan.Wrap() return SpacePlan.Wrap();
: SpacePlan.FullRender(availableSpace);
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
IsRendered = true;
if (availableSpace.Width < Size.Epsilon || availableSpace.Height < Size.Epsilon)
return;
var imageData = Source?.Invoke(availableSpace); var imageData = Source?.Invoke(availableSpace);
if (imageData == null) if (imageData == null)
return; return;
var imageElement = new Image using var image = SKImage.FromEncodedData(imageData);
{ Canvas.DrawImage(image, Position.Zero, availableSpace);
InternalImage = SKImage.FromEncodedData(imageData)
};
imageElement.Initialize(PageContext, Canvas);
imageElement.Draw(availableSpace);
} }
} }
} }
+12 -4
View File
@@ -5,8 +5,11 @@ using SkiaSharp;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class Image : Element, ICacheable internal class Image : Element, IVisual, ICacheable
{ {
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public SKImage? InternalImage { get; set; } public SKImage? InternalImage { get; set; }
~Image() ~Image()
@@ -16,9 +19,13 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
return availableSpace.IsNegative() if (availableSpace.IsNegative())
? SpacePlan.Wrap() return SpacePlan.Wrap();
: SpacePlan.FullRender(availableSpace);
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
@@ -26,6 +33,7 @@ namespace QuestPDF.Elements
if (InternalImage == null) if (InternalImage == null)
return; return;
IsRendered = true;
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace); Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
} }
} }
+1 -1
View File
@@ -212,7 +212,7 @@ namespace QuestPDF.Elements
break; break;
var element = queue.Peek(); var element = queue.Peek();
var size = element.Measure(Size.Max); var size = element.Measure(new Size(availableSize.Width, Size.Max.Height));
if (size.Type == SpacePlanType.Wrap) if (size.Type == SpacePlanType.Wrap)
break; break;
+14 -6
View File
@@ -15,34 +15,42 @@ namespace QuestPDF.Elements
Horizontal Horizontal
} }
internal class Line : Element, ILine, ICacheable internal class Line : Element, ILine, IVisual, ICacheable
{ {
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
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;
public float Size { get; set; } = 1; public float Thickness { get; set; } = 1;
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
if (availableSpace.IsNegative()) if (availableSpace.IsNegative())
return SpacePlan.Wrap(); return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return Type switch return Type switch
{ {
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0), LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(Thickness, 0),
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size), LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(0, Thickness),
_ => SpacePlan.Wrap() _ => SpacePlan.Wrap()
}; };
} }
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
IsRendered = true;
if (Type == LineType.Vertical) if (Type == LineType.Vertical)
{ {
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color); Canvas.DrawRectangle(new Position(-Thickness/2, 0), new Size(Thickness, availableSpace.Height), Color);
} }
else if (Type == LineType.Horizontal) else if (Type == LineType.Horizontal)
{ {
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color); Canvas.DrawRectangle(new Position(0, -Thickness/2), new Size(availableSpace.Width, Thickness), Color);
} }
} }
} }
+9
View File
@@ -0,0 +1,9 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RepeatContentSetter : ContainerElement
{
public bool RepeatContent { get; set; }
}
}
+7 -4
View File
@@ -24,6 +24,7 @@ namespace QuestPDF.Elements.Table
// inner table: list of all cells that ends at the corresponding row // inner table: list of all cells that ends at the corresponding row
private TableCell[][] CellsCache { get; set; } private TableCell[][] CellsCache { get; set; }
private int MaxRow { get; set; } private int MaxRow { get; set; }
private int MaxRowSpan { get; set; }
internal override void Initialize(IPageContext pageContext, ICanvas canvas) internal override void Initialize(IPageContext pageContext, ICanvas canvas)
{ {
@@ -56,6 +57,7 @@ namespace QuestPDF.Elements.Table
if (Cells.Count == 0) if (Cells.Count == 0)
{ {
MaxRow = 0; MaxRow = 0;
MaxRowSpan = 1;
CellsCache = Array.Empty<TableCell[]>(); CellsCache = Array.Empty<TableCell[]>();
return; return;
@@ -66,6 +68,7 @@ namespace QuestPDF.Elements.Table
.ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray()); .ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
MaxRow = groups.Max(x => x.Key); MaxRow = groups.Max(x => x.Key);
MaxRowSpan = Cells.Max(x => x.RowSpan);
CellsCache = Enumerable CellsCache = Enumerable
.Range(0, MaxRow + 1) .Range(0, MaxRow + 1)
@@ -199,9 +202,9 @@ namespace QuestPDF.Elements.Table
currentRow = cell.Row; currentRow = cell.Row;
} }
// cell visibility optimizations // cell visibility optimizations
if (cell.Row > maxRenderingRow) if (cell.Row > maxRenderingRow + MaxRowSpan)
break; break;
// calculate cell position / size // calculate cell position / size
@@ -218,14 +221,14 @@ namespace QuestPDF.Elements.Table
{ {
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1); maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
} }
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row // corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
if (cellSize.Type == SpacePlanType.Wrap) if (cellSize.Type == SpacePlanType.Wrap)
{ {
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1); maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
continue; continue;
} }
// update position of the last row that cell occupies // update position of the last row that cell occupies
var bottomRow = cell.Row + cell.RowSpan - 1; var bottomRow = cell.Row + cell.RowSpan - 1;
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height); rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
+25 -12
View File
@@ -85,22 +85,26 @@ namespace QuestPDF.Elements.Text
if (fallbackOption.Font.ContainsGlyph(codepoint)) if (fallbackOption.Font.ContainsGlyph(codepoint))
return fallbackOption; return fallbackOption;
} }
throw CreateNotMatchingFontException(codepoint);
}
static Exception CreateNotMatchingFontException(int codepoint)
{
var character = char.ConvertFromUtf32(codepoint); var character = char.ConvertFromUtf32(codepoint);
var unicode = $"U-{codepoint:X4}"; var unicode = $"U-{codepoint:X4}";
var proposedFonts = FindFontsContainingGlyph(codepoint); var proposedFonts = FindFontsContainingGlyph(codepoint);
var proposedFontsFormatted = proposedFonts.Any() ? string.Join(", ", proposedFonts) : "no fonts available"; var proposedFontsFormatted = proposedFonts.Any() ? string.Join(", ", proposedFonts) : "no fonts available";
throw new DocumentDrawingException( return new DocumentDrawingException(
$"Could not find an appropriate font fallback for glyph: {unicode} '{character}'. " + $"Could not find an appropriate font fallback for glyph: {unicode} '{character}'. " +
$"Font families available on current environment that contain this glyph: {proposedFontsFormatted}. " + $"Font families available on current environment that contain this glyph: {proposedFontsFormatted}. " +
$"Possible solutions: " + $"Possible solutions: " +
$"1) Use one of the listed fonts as the primary font in your document. " + $"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. "); $"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. ");
} }
static IEnumerable<string> FindFontsContainingGlyph(int codepoint) static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
{ {
var fontManager = SKFontManager.Default; var fontManager = SKFontManager.Default;
@@ -117,29 +121,38 @@ namespace QuestPDF.Elements.Text
{ {
foreach (var textBlockItem in textBlockItems) foreach (var textBlockItem in textBlockItems)
{ {
if (textBlockItem is TextBlockSpan textBlockSpan) if (textBlockItem is TextBlockPageNumber or TextBlockElement)
{ {
// perform font-fallback operation only when any fallback is available yield return textBlockItem;
if (textBlockSpan.Style.Fallback == null) }
else if (textBlockItem is TextBlockSpan textBlockSpan)
{
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
{ {
yield return textBlockSpan; yield return textBlockSpan;
continue; continue;
} }
var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style); var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style);
foreach (var textRun in textRuns) foreach (var textRun in textRuns)
{ {
yield return new TextBlockSpan var newElement = textBlockSpan switch
{ {
Text = textRun.Content, TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url },
Style = textRun.Style TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName },
TextBlockSpan => new TextBlockSpan()
}; };
newElement.Text = textRun.Content;
newElement.Style = textRun.Style;
yield return newElement;
} }
} }
else else
{ {
yield return textBlockItem; throw new NotSupportedException();
} }
} }
} }
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
internal class TextBlockSpan : ITextBlockItem internal class TextBlockSpan : ITextBlockItem
{ {
public string Text { get; set; } public string Text { get; set; }
public TextStyle Style { get; set; } = new(); public TextStyle Style { get; set; } = TextStyle.Default;
private TextShapingResult? TextShapingResult { get; set; } private TextShapingResult? TextShapingResult { get; set; }
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new (); private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
+20 -4
View File
@@ -4,12 +4,16 @@ using System.Linq;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation; using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Elements.Text.Items; using QuestPDF.Elements.Text.Items;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text namespace QuestPDF.Elements.Text
{ {
internal class TextBlock : Element, IStateResettable internal class TextBlock : Element, IVisual, IStateResettable
{ {
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
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>();
@@ -19,7 +23,7 @@ namespace QuestPDF.Elements.Text
private int CurrentElementIndex { get; set; } private int CurrentElementIndex { get; set; }
private bool FontFallbackApplied { get; set; } = false; private bool FontFallbackApplied { get; set; } = false;
public void ResetState() public void ResetState()
{ {
ApplyFontFallback(); ApplyFontFallback();
@@ -53,6 +57,12 @@ namespace QuestPDF.Elements.Text
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
if (!RenderingQueue.Any()) if (!RenderingQueue.Any())
return SpacePlan.FullRender(Size.Zero); return SpacePlan.FullRender(Size.Zero);
@@ -80,6 +90,9 @@ namespace QuestPDF.Elements.Text
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
if (IsRendered && !RepeatContent)
return;
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList(); var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any()) if (!lines.Any())
@@ -136,10 +149,13 @@ namespace QuestPDF.Elements.Text
var lastElementMeasurement = lines.Last().Elements.Last().Measurement; var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex; CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
if (!RenderingQueue.Any()) if (!RenderingQueue.Any())
{
ResetState(); ResetState();
IsRendered = true;
}
float GetAlignmentOffset(float lineWidth) float GetAlignmentOffset(float lineWidth)
{ {
if (Alignment == HorizontalAlignment.Left) if (Alignment == HorizontalAlignment.Left)
+4 -8
View File
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
{ {
var container = new Container(); var container = new Container();
Decoration.Before = container; Decoration.Before = container;
return container; return container.RepeatContentWhenPaging();
} }
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; return container.RepeatContentWhenPaging();
} }
public void After(Action<IContainer> handler) public void After(Action<IContainer> handler)
@@ -49,9 +49,7 @@ namespace QuestPDF.Fluent
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")] [Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public IContainer Header() public IContainer Header()
{ {
var container = new Container(); return Before();
Decoration.Before = container;
return container;
} }
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")] [Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
@@ -63,9 +61,7 @@ namespace QuestPDF.Fluent
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public IContainer Footer() public IContainer Footer()
{ {
var container = new Container(); return After();
Decoration.After = container;
return container;
} }
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")] [Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
+1 -1
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
return container; return container;
} }
public IContainer Layer() => Layer(false); public IContainer Layer() => Layer(false).RepeatContentWhenPaging();
public IContainer PrimaryLayer() => Layer(true); public IContainer PrimaryLayer() => Layer(true);
internal void Validate() internal void Validate()
+6 -6
View File
@@ -6,11 +6,11 @@ namespace QuestPDF.Fluent
{ {
public static class LineExtensions public static class LineExtensions
{ {
private static ILine Line(this IContainer element, LineType type, float size) private static ILine Line(this IContainer element, LineType type, float thickness)
{ {
var line = new Line var line = new Line
{ {
Size = size, Thickness = thickness,
Type = type Type = type
}; };
@@ -18,14 +18,14 @@ namespace QuestPDF.Fluent
return line; return line;
} }
public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point) public static ILine LineVertical(this IContainer element, float thickness, Unit unit = Unit.Point)
{ {
return element.Line(LineType.Vertical, size.ToPoints(unit)); return element.Line(LineType.Vertical, thickness.ToPoints(unit));
} }
public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point) public static ILine LineHorizontal(this IContainer element, float thickness, Unit unit = Unit.Point)
{ {
return element.Line(LineType.Horizontal, size.ToPoints(unit)); return element.Line(LineType.Horizontal, thickness.ToPoints(unit));
} }
public static void LineColor(this ILine descriptor, string value) public static void LineColor(this ILine descriptor, string value)
@@ -0,0 +1,27 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RepeatContentExtensions
{
private static IContainer RepeatContent(this IContainer element, Action<RepeatContentSetter> handler)
{
var repeatContentSetter = element as RepeatContentSetter ?? new RepeatContentSetter();
handler(repeatContentSetter);
return element.Element(repeatContentSetter);
}
public static IContainer RepeatContentWhenPaging(this IContainer element)
{
return element.RepeatContent(x => x.RepeatContent = true);
}
public static IContainer DoNotRepeatContentWhenPaging(this IContainer element)
{
return element.RepeatContent(x => x.RepeatContent = false);
}
}
}
+37 -43
View File
@@ -12,11 +12,18 @@ namespace QuestPDF.Fluent
{ {
public class TextSpanDescriptor public class TextSpanDescriptor
{ {
internal TextStyle TextStyle { get; } internal TextStyle TextStyle = TextStyle.Default;
internal Action<TextStyle> AssignTextStyle { get; }
internal TextSpanDescriptor(TextStyle textStyle) internal TextSpanDescriptor(Action<TextStyle> assignTextStyle)
{ {
TextStyle = textStyle; AssignTextStyle = assignTextStyle;
}
internal void MutateTextStyle(Func<TextStyle, TextStyle> handler)
{
TextStyle = handler(TextStyle);
AssignTextStyle(TextStyle);
} }
} }
@@ -24,16 +31,17 @@ namespace QuestPDF.Fluent
public class TextPageNumberDescriptor : TextSpanDescriptor public class TextPageNumberDescriptor : TextSpanDescriptor
{ {
internal PageNumberFormatter FormatFunction { get; private set; } = x => x?.ToString() ?? string.Empty; internal Action<PageNumberFormatter> AssignFormatFunction { get; }
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle) internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
{ {
AssignFormatFunction = assignFormatFunction;
AssignFormatFunction(x => x?.ToString());
} }
public TextPageNumberDescriptor Format(PageNumberFormatter formatter) public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
{ {
FormatFunction = formatter ?? FormatFunction; AssignFormatFunction(formatter);
return this; return this;
} }
} }
@@ -41,7 +49,7 @@ namespace QuestPDF.Fluent
public class TextDescriptor public class TextDescriptor
{ {
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>(); private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
private TextStyle DefaultStyle { get; set; } = TextStyle.Default; private TextStyle? DefaultStyle { get; set; }
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f; private float Spacing { get; set; } = 0f;
@@ -91,19 +99,15 @@ namespace QuestPDF.Fluent
public TextSpanDescriptor Span(string? text) public TextSpanDescriptor Span(string? text)
{ {
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (text == null) if (text == null)
return descriptor; return new TextSpanDescriptor(_ => { });
var items = text var items = text
.Replace("\r", string.Empty) .Replace("\r", string.Empty)
.Split(new[] { '\n' }, StringSplitOptions.None) .Split(new[] { '\n' }, StringSplitOptions.None)
.Select(x => new TextBlockSpan .Select(x => new TextBlockSpan
{ {
Text = x, Text = x
Style = style
}) })
.ToList(); .ToList();
@@ -118,7 +122,7 @@ namespace QuestPDF.Fluent
.ToList() .ToList()
.ForEach(TextBlocks.Add); .ForEach(TextBlocks.Add);
return descriptor; return new TextSpanDescriptor(x => items.ForEach(y => y.Style = x));
} }
public TextSpanDescriptor Line(string? text) public TextSpanDescriptor Line(string? text)
@@ -134,16 +138,10 @@ namespace QuestPDF.Fluent
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber) private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
{ {
var style = DefaultStyle.Clone(); var textBlockItem = new TextBlockPageNumber();
var descriptor = new TextPageNumberDescriptor(style); AddItemToLastTextBlock(textBlockItem);
AddItemToLastTextBlock(new TextBlockPageNumber return new TextPageNumberDescriptor(x => textBlockItem.Style = x, x => textBlockItem.Source = context => x(pageNumber(context)));
{
Source = context => descriptor.FormatFunction(pageNumber(context)),
Style = style
});
return descriptor;
} }
public TextPageNumberDescriptor CurrentPageNumber() public TextPageNumberDescriptor CurrentPageNumber()
@@ -187,20 +185,17 @@ namespace QuestPDF.Fluent
if (IsNullOrEmpty(sectionName)) if (IsNullOrEmpty(sectionName))
throw new ArgumentException("Section name cannot be null or empty", nameof(sectionName)); throw new ArgumentException("Section name cannot be null or empty", nameof(sectionName));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text)) if (IsNullOrEmpty(text))
return descriptor; return new TextSpanDescriptor(_ => { });
AddItemToLastTextBlock(new TextBlockSectionLink var textBlockItem = new TextBlockSectionLink
{ {
Style = style,
Text = text, Text = text,
SectionName = sectionName SectionName = sectionName
}); };
return descriptor; AddItemToLastTextBlock(textBlockItem);
return new TextSpanDescriptor(x => textBlockItem.Style = x);
} }
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")] [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
@@ -214,20 +209,17 @@ namespace QuestPDF.Fluent
if (IsNullOrEmpty(url)) if (IsNullOrEmpty(url))
throw new ArgumentException("Url cannot be null or empty", nameof(url)); throw new ArgumentException("Url cannot be null or empty", nameof(url));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text)) if (IsNullOrEmpty(text))
return descriptor; return new TextSpanDescriptor(_ => { });
AddItemToLastTextBlock(new TextBlockHyperlink var textBlockItem = new TextBlockHyperlink
{ {
Style = style,
Text = text, Text = text,
Url = url Url = url
}); };
return descriptor; AddItemToLastTextBlock(textBlockItem);
return new TextSpanDescriptor(x => textBlockItem.Style = x);
} }
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")] [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
@@ -251,7 +243,9 @@ namespace QuestPDF.Fluent
internal void Compose(IContainer container) internal void Compose(IContainer container)
{ {
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment); TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
container = container.DefaultTextStyle(DefaultStyle);
if (DefaultStyle != null)
container = container.DefaultTextStyle(DefaultStyle);
if (TextBlocks.Count == 1) if (TextBlocks.Count == 1)
{ {
+36 -34
View File
@@ -11,7 +11,7 @@ namespace QuestPDF.Fluent
if (style == null) if (style == null)
return descriptor; return descriptor;
descriptor.TextStyle.OverrideStyle(style); descriptor.MutateTextStyle(x => x.OverrideStyle(style));
return descriptor; return descriptor;
} }
@@ -28,114 +28,118 @@ namespace QuestPDF.Fluent
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.Color = value; descriptor.MutateTextStyle(x => x.FontColor(value));
return descriptor; return descriptor;
} }
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.BackgroundColor = value; descriptor.MutateTextStyle(x => x.BackgroundColor(value));
return descriptor; return descriptor;
} }
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.FontFamily = value; descriptor.MutateTextStyle(x => x.FontFamily(value));
return descriptor; return descriptor;
} }
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.Size = value; descriptor.MutateTextStyle(x => x.FontSize(value));
return descriptor; return descriptor;
} }
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.LineHeight = value; descriptor.MutateTextStyle(x => x.LineHeight(value));
return descriptor; return descriptor;
} }
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.IsItalic = value; descriptor.MutateTextStyle(x => x.Italic(value));
return descriptor; return descriptor;
} }
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.HasStrikethrough = value; descriptor.MutateTextStyle(x => x.Strikethrough(value));
return descriptor; return descriptor;
} }
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.HasUnderline = value; descriptor.MutateTextStyle(x => x.Underline(value));
return descriptor; return descriptor;
} }
public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{ {
descriptor.TextStyle.WrapAnywhere = value; descriptor.MutateTextStyle(x => x.WrapAnywhere(value));
return descriptor; return descriptor;
} }
#region Weight #region Weight
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontWeight = weight;
return descriptor;
}
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Thin); descriptor.MutateTextStyle(x => x.Thin());
return descriptor;
} }
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.ExtraLight); descriptor.MutateTextStyle(x => x.ExtraLight());
return descriptor;
} }
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Light); descriptor.MutateTextStyle(x => x.Light());
return descriptor;
} }
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Normal); descriptor.MutateTextStyle(x => x.NormalWeight());
return descriptor;
} }
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Medium); descriptor.MutateTextStyle(x => x.Medium());
return descriptor;
} }
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.SemiBold); descriptor.MutateTextStyle(x => x.SemiBold());
return descriptor;
} }
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Bold); descriptor.MutateTextStyle(x => x.Bold());
return descriptor;
} }
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.ExtraBold); descriptor.MutateTextStyle(x => x.ExtraBold());
return descriptor;
} }
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.Black); descriptor.MutateTextStyle(x => x.Black());
return descriptor;
} }
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Weight(FontWeight.ExtraBlack); descriptor.MutateTextStyle(x => x.ExtraBlack());
return descriptor;
} }
#endregion #endregion
@@ -143,24 +147,22 @@ namespace QuestPDF.Fluent
#region Position #region Position
public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Position(FontPosition.Normal); descriptor.MutateTextStyle(x => x.NormalPosition());
return descriptor;
} }
public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Position(FontPosition.Subscript); descriptor.MutateTextStyle(x => x.Subscript());
return descriptor;
} }
public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
{ {
return descriptor.Position(FontPosition.Superscript); descriptor.MutateTextStyle(x => x.Superscript());
}
private static T Position<T>(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontPosition = fontPosition;
return descriptor; return descriptor;
} }
#endregion #endregion
} }
} }
+27 -32
View File
@@ -6,24 +6,6 @@ namespace QuestPDF.Fluent
{ {
public static class TextStyleExtensions public static class TextStyleExtensions
{ {
private static TextStyle Mutate(this TextStyle style, Action<TextStyle> handler)
{
style = style.Clone();
handler(style);
return style;
}
public static TextStyle Fallback(this TextStyle style, TextStyle? value = null)
{
return style.Mutate(x => x.Fallback = value);
}
public static TextStyle Fallback(this TextStyle style, Func<TextStyle, TextStyle> handler)
{
return style.Fallback(handler(TextStyle.Default));
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")] [Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
public static TextStyle Color(this TextStyle style, string value) public static TextStyle Color(this TextStyle style, string value)
{ {
@@ -32,12 +14,12 @@ namespace QuestPDF.Fluent
public static TextStyle FontColor(this TextStyle style, string value) public static TextStyle FontColor(this TextStyle style, string value)
{ {
return style.Mutate(x => x.Color = value); return style.Mutate(TextStyleProperty.Color, value);
} }
public static TextStyle BackgroundColor(this TextStyle style, string value) public static TextStyle BackgroundColor(this TextStyle style, string value)
{ {
return style.Mutate(x => x.BackgroundColor = value); return style.Mutate(TextStyleProperty.BackgroundColor, value);
} }
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")] [Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
@@ -48,7 +30,7 @@ namespace QuestPDF.Fluent
public static TextStyle FontFamily(this TextStyle style, string value) public static TextStyle FontFamily(this TextStyle style, string value)
{ {
return style.Mutate(x => x.FontFamily = value); return style.Mutate(TextStyleProperty.FontFamily, value);
} }
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")] [Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
@@ -59,39 +41,39 @@ namespace QuestPDF.Fluent
public static TextStyle FontSize(this TextStyle style, float value) public static TextStyle FontSize(this TextStyle style, float value)
{ {
return style.Mutate(x => x.Size = value); return style.Mutate(TextStyleProperty.Size, value);
} }
public static TextStyle LineHeight(this TextStyle style, float value) public static TextStyle LineHeight(this TextStyle style, float value)
{ {
return style.Mutate(x => x.LineHeight = value); return style.Mutate(TextStyleProperty.LineHeight, value);
} }
public static TextStyle Italic(this TextStyle style, bool value = true) public static TextStyle Italic(this TextStyle style, bool value = true)
{ {
return style.Mutate(x => x.IsItalic = value); return style.Mutate(TextStyleProperty.IsItalic, value);
} }
public static TextStyle Strikethrough(this TextStyle style, bool value = true) public static TextStyle Strikethrough(this TextStyle style, bool value = true)
{ {
return style.Mutate(x => x.HasStrikethrough = value); return style.Mutate(TextStyleProperty.HasStrikethrough, value);
} }
public static TextStyle Underline(this TextStyle style, bool value = true) public static TextStyle Underline(this TextStyle style, bool value = true)
{ {
return style.Mutate(x => x.HasUnderline = value); return style.Mutate(TextStyleProperty.HasUnderline, value);
} }
public static TextStyle WrapAnywhere(this TextStyle style, bool value = true) public static TextStyle WrapAnywhere(this TextStyle style, bool value = true)
{ {
return style.Mutate(x => x.WrapAnywhere = value); return style.Mutate(TextStyleProperty.WrapAnywhere, value);
} }
#region Weight #region Weight
public static TextStyle Weight(this TextStyle style, FontWeight weight) public static TextStyle Weight(this TextStyle style, FontWeight weight)
{ {
return style.Mutate(x => x.FontWeight = weight); return style.Mutate(TextStyleProperty.FontWeight, weight);
} }
public static TextStyle Thin(this TextStyle style) public static TextStyle Thin(this TextStyle style)
@@ -147,6 +129,7 @@ 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);
@@ -164,11 +147,23 @@ namespace QuestPDF.Fluent
private static TextStyle Position(this TextStyle style, FontPosition fontPosition) private static TextStyle Position(this TextStyle style, FontPosition fontPosition)
{ {
if (style.FontPosition == fontPosition) return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
return style;
return style.Mutate(t => t.FontPosition = fontPosition);
} }
#endregion
#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
} }
} }
+8
View File
@@ -0,0 +1,8 @@
namespace QuestPDF.Infrastructure
{
internal interface IVisual
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
}
}
+4 -72
View File
@@ -1,13 +1,10 @@
using System; using System;
using HarfBuzzSharp;
using QuestPDF.Helpers; using QuestPDF.Helpers;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
public class TextStyle public record TextStyle
{ {
internal bool HasGlobalStyleApplied { get; private set; }
internal string? Color { get; set; } internal string? Color { get; set; }
internal string? BackgroundColor { get; set; } internal string? BackgroundColor { get; set; }
internal string? FontFamily { get; set; } internal string? FontFamily { get; set; }
@@ -21,12 +18,8 @@ namespace QuestPDF.Infrastructure
internal bool? WrapAnywhere { get; set; } internal bool? WrapAnywhere { get; set; }
internal TextStyle? Fallback { get; set; } internal TextStyle? Fallback { get; set; }
// TODO: without cache, this may be an expensive operation internal static TextStyle LibraryDefault { get; } = new()
internal object PaintKey => (FontFamily, Size, FontWeight, FontPosition, IsItalic, Color);
internal object FontMetricsKey => (FontFamily, Size, FontWeight, IsItalic);
internal static readonly TextStyle LibraryDefault = new TextStyle
{ {
Color = Colors.Black, Color = Colors.Black,
BackgroundColor = Colors.Transparent, BackgroundColor = Colors.Transparent,
@@ -42,67 +35,6 @@ namespace QuestPDF.Infrastructure
Fallback = null Fallback = null
}; };
// it is important to create new instances for the DefaultTextStyle element to work correctly public static TextStyle Default { get; } = new();
public static TextStyle Default => new TextStyle();
internal void ApplyGlobalStyle(TextStyle globalStyle)
{
if (HasGlobalStyleApplied)
return;
HasGlobalStyleApplied = true;
ApplyParentStyle(globalStyle);
if (Fallback != null)
ApplyFallbackStyle(this);
}
internal void ApplyFallbackStyle(TextStyle parentStyle)
{
ApplyParentStyle(parentStyle, false);
Fallback?.ApplyFallbackStyle(this);
}
internal void ApplyParentStyle(TextStyle parentStyle, bool mapFallback = true)
{
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;
if (mapFallback)
Fallback ??= parentStyle.Fallback?.Clone();
}
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;
Fallback = parentStyle.Fallback?.Clone() ?? Fallback;
}
internal TextStyle Clone()
{
var clone = (TextStyle)MemberwiseClone();
clone.HasGlobalStyleApplied = false;
clone.Fallback = Fallback?.Clone();
return clone;
}
} }
} }
+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; public event Action? OnPreviewerStopped;
private const int RequiredPreviewerVersionMajor = 2022; private const int RequiredPreviewerVersionMajor = 2022;
private const int RequiredPreviewerVersionMinor = 8; private const int RequiredPreviewerVersionMinor = 9;
public PreviewerService(int port) public PreviewerService(int port)
{ {
+1 -1
View File
@@ -3,7 +3,7 @@
<Authors>MarcinZiabek</Authors> <Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company> <Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId> <PackageId>QuestPDF</PackageId>
<Version>2022.8.2</Version> <Version>2022.9.1</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>
+10 -19
View File
@@ -1,20 +1,11 @@
2022.8.0: 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.
- Improved library performance, 2022.9.1
- Breaking change: changed default font from Calibri to an open-source Lato, - Fixed: text hyperlinks do not work when the CheckIfAllTextGlyphsAreAvailable option or text fallback are used,
- Default font files are included with the nuget package, making it safe to deploy on any environment, - Fixed: cells with RowSpan (greater than 1) are not always displayed properly,
- Default font files are significantly smaller, so output document files should be smaller too (up to 20x reduction in size), - Improved predictability of the Inlined element when measuring its children.
- 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.
+2 -2
View File
@@ -3,7 +3,7 @@
public static class Settings public static class Settings
{ {
/// <summary> /// <summary>
/// This value represents the maximum lenght of the document that the library produces. /// 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. /// 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 CheckIfAllTextGlyphsAreAvailableInSpecifiedFont { get; set; } = System.Diagnostics.Debugger.IsAttached; public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
} }
} }
+8 -6
View File
@@ -17,13 +17,14 @@ It offers a layouting engine designed with a full paging support in mind. The do
Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements. Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements.
## Please show the value ## Please help by giving a star
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality. Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐ ⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
<img src="https://user-images.githubusercontent.com/9263853/184642026-27dd7567-a46a-45d4-9594-e6a70a7193e9.png" width="700" /> <img src="https://user-images.githubusercontent.com/9263853/190931857-8ca52ec8-cc7d-4d12-9467-4442b3342fa1.png" width="700" />
## Please share with the community ## Please share with the community
@@ -46,6 +47,7 @@ Special thanks to all companies that decided to sponsor QuestPDF development. Th
| Company | Description | | Company | Description |
|--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------| |--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
| <img src="Resources/jetbrains-logo.svg" width="100px"> | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!<br/>100$ / month | | <img src="Resources/jetbrains-logo.svg" width="100px"> | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!<br/>100$ / month |
| <img src="https://avatars.githubusercontent.com/u/2712328?v=4" width="100px"> | [Mark Gould](https://github.com/markgould) supports this project. Thank you!<br/>100$ / month |
[![Sponsor project](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F%20sponsor-QuestPDF-red)](https://github.com/sponsors/QuestPDF) [![Sponsor project](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F%20sponsor-QuestPDF-red)](https://github.com/sponsors/QuestPDF)
@@ -61,14 +63,14 @@ Install-Package QuestPDF
dotnet add package QuestPDF dotnet add package QuestPDF
// Package reference in .csproj file // Package reference in .csproj file
<PackageReference Include="QuestPDF" Version="2022.6.0" /> <PackageReference Include="QuestPDF" Version="2022.9.0" />
``` ```
[![Nuget version](https://img.shields.io/badge/package%20details-QuestPDF-blue?logo=nuget)](https://www.nuget.org/packages/QuestPDF/) [![Nuget version](https://img.shields.io/badge/package%20details-QuestPDF-blue?logo=nuget)](https://www.nuget.org/packages/QuestPDF/)
## Documentation ## Documentation
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started.html) [![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started)
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code. A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
@@ -76,14 +78,14 @@ A short and easy to follow tutorial showing how to design an invoice document un
A detailed description of behavior of all available components and how to use them with C# Fluent API. A detailed description of behavior of all available components and how to use them with C# Fluent API.
[![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns.html) [![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns)
Everything that may help you designing great reports and create reusable code that is easy to maintain. Everything that may help you designing great reports and create reusable code that is easy to maintain.
## QuestPDF Previewer ## QuestPDF Previewer
The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task! The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task!
[![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer.html) [![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer)
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%"> <img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">