Compare commits

..

7 Commits

Author SHA1 Message Date
MarcinZiabek eb61b19abf 2022.12.6 release 2023-05-09 02:32:28 +02:00
Marcin Ziąbek e684b9ec6d Table: fixed zindex rendering issue (#553) 2023-05-09 01:47:27 +02:00
MarcinZiabek 6074ed0e7e Font fallback: fixed regression 2023-04-27 17:01:50 +02:00
MarcinZiabek a560c6c864 Release 2022.12.4 2023-04-22 10:01:53 +02:00
Marcin Ziąbek 327c412c7d Fix(#483): TextStyle.Fallback incorrectly inherits parent's and global properties (#532)
* Fixed applying text style for fallback styles

* Code refactoring

* FontStyle: making method arguments explicit

* Bug fixes + test

* Minor code refactoring
2023-04-22 09:57:07 +02:00
Marcin Ziąbek 4b5eeea896 Update README.md 2023-04-18 23:50:24 +02:00
MarcinZiabek ef89be9710 Improvement: updated the CreateNotMatchingFontException text to mention that glyph checking operation can be disabled with the Settings.CheckIfAllTextGlyphsAreAvailable setting 2023-04-17 02:15:19 +02:00
22 changed files with 257 additions and 286 deletions
+1 -2
View File
@@ -54,8 +54,7 @@ Choosing a project dependency could be difficult. We need to ensure stability an
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
<img src="https://user-images.githubusercontent.com/9263853/224511996-9dd27a99-bef8-4d5e-bc14-9f6cfe0f5191.png" width="800" />
<img src="https://user-images.githubusercontent.com/9263853/232912983-b20e441e-bd47-41df-afa4-f02481252d26.png" width="800" />
## Please share with the community
+31 -15
View File
@@ -1,12 +1,10 @@
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
@@ -66,24 +64,42 @@ namespace QuestPDF.Examples
});
}
[Test]
public void ImageResolutionScaling()
public void ReusingTheSameImageFileShouldBePossible()
{
var image = Image.FromFile("large-image.jpg");
var fileName = Path.GetTempFileName() + ".jpg";
Document
.Create(document =>
{
document.Page(page =>
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 =>
{
page.Size(210, 210);
page.Margin(50);
page.Content().Image(image);
container
.Padding(20)
.Column(column =>
{
column.Spacing(20);
column.Item().Image(fileName);
column.Item().Image(fileName);
column.Item().Image(fileName);
});
});
})
.GeneratePdf($"test.pdf");
}
finally
{
File.Delete(fileName);
}
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -32,12 +32,6 @@
<None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="large-image.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="large-image.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+52
View File
@@ -316,6 +316,58 @@ namespace QuestPDF.Examples
});
}
[Test]
public void Bug_RowSpanWorksIncorrectly()
{
// https://github.com/QuestPDF/QuestPDF/issues/552
RenderingTest
.Create()
.ProduceImages()
.PageSize(PageSizes.A5.Landscape())
.ShowResults()
.Render(container =>
{
container.Padding(20).Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1);
columns.RelativeColumn(2);
columns.RelativeColumn(2);
columns.RelativeColumn(2);
});
foreach (var i in Enumerable.Range(6, 9))
{
table.Cell().Element(CellStyleMainTable).Text($"{i:00}:00");
foreach (var j in Enumerable.Range(1, 3))
table.Cell().Element(CellStyleMainTable).Text("");
}
static IContainer CellStyleMainTable(IContainer container)
{
return container
.Border(0.5f).BorderColor(Colors.Blue.Lighten4)
.Background(Colors.Blue.Lighten5)
.PaddingVertical(5);
}
table.Cell().Row(1).RowSpan(3).Column(2).Element(BlockAccepted).Text("3 rows");
table.Cell().Row(1).RowSpan(6).Column(3).Element(BlockAccepted).Text("6 rows");
table.Cell().Row(3).RowSpan(5).Column(4).Element(BlockAccepted).Text("5 rows");
static IContainer BlockAccepted(IContainer container)
{
return container
.Border(1.5f).BorderColor(Colors.Green.Lighten2)
.Background(Colors.Green.Lighten4);
}
});
});
}
[Test]
public void TableHeader()
{
+35
View File
@@ -1004,5 +1004,40 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void FontFallback_Nested()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A5.Landscape());
page.DefaultTextStyle(x => x
.FontSize(24)
.Bold()
.FontFamily("Times New Roman")
.Fallback(y => y
.FontFamily("Microsoft YaHei")
.Underline()
.BackgroundColor(Colors.Red.Lighten2)));
page.Content().Text(text =>
{
text.Line("Default times new roman 中文文本 text.");
text.Line("Normal weight and green 中文文本 text.").NormalWeight().BackgroundColor(Colors.Green.Lighten2);
text.Line("Strikethrough without underline 中文文本 text.").Strikethrough().Underline(false);
text.Line("Lato italic 中文文本 text.").FontFamily("Lato").Italic();
});
});
});
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

+5 -59
View File
@@ -1,16 +1,10 @@
using System;
using System.Linq;
using System.Net.Mime;
using FluentAssertions;
using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
using SkiaSharp;
using Image = QuestPDF.Infrastructure.Image;
namespace QuestPDF.UnitTests
{
@@ -21,9 +15,9 @@ namespace QuestPDF.UnitTests
public void Measure_TakesAvailableSpaceRegardlessOfSize()
{
TestPlan
.For(x => new QuestPDF.Elements.Image
.For(x => new Image
{
DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
InternalImage = GenerateImage(400, 300)
})
.MeasureElement(new Size(300, 200))
.CheckMeasureResult(SpacePlan.FullRender(300, 200));
@@ -33,9 +27,9 @@ namespace QuestPDF.UnitTests
public void Draw_TakesAvailableSpaceRegardlessOfSize()
{
TestPlan
.For(x => new QuestPDF.Elements.Image
.For(x => new Image
{
DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
InternalImage = GenerateImage(400, 300)
})
.DrawElement(new Size(300, 200))
.ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200))
@@ -64,53 +58,5 @@ namespace QuestPDF.UnitTests
using var surface = SKSurface.Create(imageInfo);
return surface.Snapshot();
}
[Test]
public void UsingSharedImageShouldNotDrasticallyIncreaseDocumentSize()
{
var placeholderImage = Placeholders.Image(1000, 200);
var documentWithSingleImageSize = GetDocumentSize(container =>
{
container.Image(placeholderImage);
});
var documentWithMultipleImagesSize = GetDocumentSize(container =>
{
container.Column(column =>
{
foreach (var i in Enumerable.Range(0, 100))
column.Item().Image(placeholderImage);
});
});
var documentWithSingleImageUsedMultipleTimesSize = GetDocumentSize(container =>
{
container.Column(column =>
{
var sharedImage = Image.FromBinaryData(placeholderImage).DisposeAfterDocumentGeneration();
foreach (var i in Enumerable.Range(0, 100))
column.Item().Image(sharedImage);
});
});
(documentWithMultipleImagesSize / (float)documentWithSingleImageSize).Should().BeInRange(90, 100);
(documentWithSingleImageUsedMultipleTimesSize / (float)documentWithSingleImageSize).Should().BeInRange(1f, 1.5f);
}
private static int GetDocumentSize(Action<IContainer> container)
{
return Document
.Create(document =>
{
document.Page(page =>
{
page.Content().Element(container);
});
})
.GeneratePdf()
.Length;
}
}
}
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>
@@ -0,0 +1,59 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class TextStyleTests
{
[Test]
public void ApplyInheritedAndGlobalStyle()
{
// arrange
var defaultTextStyle = TextStyle
.Default
.FontSize(20)
.FontFamily("Arial")
.BackgroundColor(Colors.Green.Lighten2)
.Fallback(y => y
.FontFamily("Microsoft YaHei")
.Underline()
.NormalWeight()
.BackgroundColor(Colors.Blue.Lighten2));
var spanTextStyle = TextStyle
.Default
.FontFamily("Times New Roman")
.Bold()
.Strikethrough()
.BackgroundColor(Colors.Red.Lighten2);
// act
var targetStyle = spanTextStyle.ApplyInheritedStyle(defaultTextStyle).ApplyGlobalStyle();
// assert
var expectedStyle = TextStyle.LibraryDefault with
{
Size = 20,
FontFamily = "Times New Roman",
FontWeight = FontWeight.Bold,
BackgroundColor = Colors.Red.Lighten2,
HasStrikethrough = true,
Fallback = TextStyle.LibraryDefault with
{
Size = 20,
FontFamily = "Microsoft YaHei",
FontWeight = FontWeight.Bold,
BackgroundColor = Colors.Red.Lighten2,
HasUnderline = true,
HasStrikethrough = true
}
};
targetStyle.Should().BeEquivalentTo(expectedStyle);
}
}
}
+14 -29
View File
@@ -10,7 +10,6 @@ using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using Image = QuestPDF.Elements.Image;
namespace QuestPDF.Drawing
{
@@ -22,7 +21,7 @@ namespace QuestPDF.Drawing
var metadata = document.GetMetadata();
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document, metadata);
RenderDocument(canvas, document);
}
internal static void GenerateXps(Stream stream, IDocument document)
@@ -31,7 +30,7 @@ namespace QuestPDF.Drawing
var metadata = document.GetMetadata();
var canvas = new XpsCanvas(stream, metadata);
RenderDocument(canvas, document, metadata);
RenderDocument(canvas, document);
}
private static void CheckIfStreamIsCompatible(Stream stream)
@@ -47,27 +46,25 @@ namespace QuestPDF.Drawing
{
var metadata = document.GetMetadata();
var canvas = new ImageCanvas(metadata);
RenderDocument(canvas, document, metadata);
RenderDocument(canvas, document);
return canvas.Images;
}
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
{
var metadata = document.GetMetadata();
var canvas = new SkiaPictureCanvas();
RenderDocument(canvas, document, metadata);
RenderDocument(canvas, document);
return canvas.Pictures;
}
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentMetadata metadata)
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
where TCanvas : ICanvas, IRenderingCanvas
{
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
ApplyDefaultImageDpi(content, metadata.RasterDpi);
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default);
ApplyContentDirection(content, ContentDirection.LeftToRight);
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
@@ -176,15 +173,6 @@ namespace QuestPDF.Drawing
return debuggingState;
}
internal static void ApplyDefaultImageDpi(this Element? content, int targetDpi)
{
content.VisitChildren(x =>
{
if (x is Image { DocumentImage: { } image })
image.TargetDpi ??= targetDpi;
});
}
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
{
@@ -204,7 +192,7 @@ namespace QuestPDF.Drawing
ApplyContentDirection(child, direction);
}
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
internal static void ApplyInheritedAndGlobalTexStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
if (content == null)
return;
@@ -214,26 +202,23 @@ namespace QuestPDF.Drawing
foreach (var textBlockItem in textBlock.Items)
{
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
}
else if (textBlockItem is TextBlockElement textElement)
{
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
textSpan.Style = textSpan.Style.ApplyInheritedStyle(documentDefaultTextStyle).ApplyGlobalStyle();
if (textBlockItem is TextBlockElement textElement)
ApplyInheritedAndGlobalTexStyle(textElement.Element, documentDefaultTextStyle);
}
return;
}
if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
if (content is DefaultTextStyle defaultTextStyleElement)
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
}
}
}
+1 -3
View File
@@ -5,10 +5,8 @@ namespace QuestPDF.Drawing
{
public class DocumentMetadata
{
public const int DefaultPdfDpi = 72;
public int ImageQuality { get; set; } = 101;
public int RasterDpi { get; set; } = DefaultPdfDpi;
public int RasterDpi { get; set; } = 72;
public bool PdfA { get; set; }
public string? Title { get; set; }
+1 -1
View File
@@ -89,7 +89,7 @@ namespace QuestPDF.Elements
var container = new DynamicElement();
content(container);
container.ApplyDefaultTextStyle(TextStyle);
container.ApplyInheritedAndGlobalTexStyle(TextStyle);
container.ApplyContentDirection(ContentDirection);
container.InjectDependencies(PageContext, Canvas);
+4 -5
View File
@@ -7,12 +7,11 @@ namespace QuestPDF.Elements
{
internal class Image : Element, ICacheable
{
public Infrastructure.Image? DocumentImage { get; set; }
public SKImage? InternalImage { get; set; }
~Image()
{
if (DocumentImage is { IsDocumentScoped: true })
DocumentImage?.Dispose();
InternalImage?.Dispose();
}
internal override SpacePlan Measure(Size availableSpace)
@@ -24,10 +23,10 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
if (DocumentImage == null)
if (InternalImage == null)
return;
Canvas.DrawImage(DocumentImage.GetVersionOfSize(availableSpace), Position.Zero, availableSpace);
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
}
}
}
+1 -1
View File
@@ -112,7 +112,7 @@ namespace QuestPDF.Elements.Table
UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
foreach (var command in renderingCommands.OrderBy(x => x.Cell.ZIndex))
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.Cell.IsRendered = true;
@@ -8,6 +8,8 @@ namespace QuestPDF.Elements.Table
public int Column { get; set; } = 0;
public int ColumnSpan { get; set; } = 1;
public int ZIndex { get; set; }
public bool IsRendered { get; set; }
}
}
@@ -16,9 +16,13 @@ namespace QuestPDF.Elements.Table
{
var cellsWindow = new List<TableCell>();
(int x, int y) currentLocation = (1, 1);
var zIndex = 0;
foreach (var cell in cells)
{
cell.ZIndex = zIndex;
zIndex++;
if (cellsWindow.Count > Math.Max(columnsCount, 16))
{
cellsWindow = cellsWindow
@@ -105,7 +105,9 @@ namespace QuestPDF.Elements.Text
$"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. ");
$"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. " +
$"You can disable this check by setting the 'Settings.CheckIfAllTextGlyphsAreAvailable' option to 'false'. " +
$"However, this may result with text glyphs being incorrectly rendered without any warning.");
}
static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
+7 -6
View File
@@ -3,6 +3,7 @@ using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Fluent
{
@@ -10,30 +11,30 @@ namespace QuestPDF.Fluent
{
public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth)
{
var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration();
var image = SKImage.FromEncodedData(imageData);
parent.Image(image, scaling);
}
public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth)
{
var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration();
var image = SKImage.FromEncodedData(filePath);
parent.Image(image, scaling);
}
public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth)
{
var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration();
var image = SKImage.FromEncodedData(fileStream);
parent.Image(image, scaling);
}
public static void Image(this IContainer parent, Infrastructure.Image image, ImageScaling scaling = ImageScaling.FitWidth)
private static void Image(this IContainer parent, SKImage image, ImageScaling scaling = ImageScaling.FitWidth)
{
if (image == null)
throw new DocumentComposeException("Cannot load or decode provided image.");
var imageElement = new QuestPDF.Elements.Image
var imageElement = new Image
{
DocumentImage = image
InternalImage = image
};
if (scaling != ImageScaling.Resize)
-133
View File
@@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions;
using SkiaSharp;
namespace QuestPDF.Infrastructure
{
public class Image : IDisposable
{
private SKImage SkImage { get; }
internal List<(Size size, SKImage image)>? ScaledImageCache { get; set; }
internal int? TargetDpi { get; set; }
internal bool PerformScalingToTargetDpi { get; set; } = true;
internal bool IsDocumentScoped { get; set; }
public int Width => SkImage.Width;
public int Height => SkImage.Height;
private const float ImageSizeSimilarityToleranceMax = 1.1f;
private const float ImageSizeSimilarityToleranceMin = 1 / ImageSizeSimilarityToleranceMax;
private Image(SKImage image)
{
SkImage = image;
}
public void Dispose()
{
SkImage.Dispose();
ScaledImageCache?.ForEach(x => x.image.Dispose());
}
internal SKImage GetVersionOfSize(Size size)
{
if (!PerformScalingToTargetDpi)
return SkImage;
var scalingFactor = TargetDpi.Value / (float)DocumentMetadata.DefaultPdfDpi;
var targetResolution = new Size(size.Width * scalingFactor, size.Height * scalingFactor);
if (targetResolution.Width > Width || targetResolution.Height > Height)
return SkImage;
ScaledImageCache ??= new List<(Size size, SKImage image)>();
foreach (var imageCache in ScaledImageCache)
{
if (HasSimilarSize(imageCache.size, targetResolution))
return imageCache.image;
}
var scaledImage = ScaleImage(SkImage, targetResolution);
ScaledImageCache.Add((targetResolution, scaledImage));
return scaledImage;
static SKImage ScaleImage(SKImage originalImage, Size targetSize)
{
var imageInfo = new SKImageInfo((int)targetSize.Width, (int)targetSize.Height);
var target = SKImage.Create(imageInfo);
originalImage.ScalePixels(target.PeekPixels(), SKFilterQuality.High);
return target;
}
static bool HasSimilarSize(Size a, Size b)
{
var widthRatio = a.Width / b.Width;
var heightRatio = a.Height / b.Height;
return widthRatio is > ImageSizeSimilarityToleranceMin and < ImageSizeSimilarityToleranceMax &&
heightRatio is > ImageSizeSimilarityToleranceMin and < ImageSizeSimilarityToleranceMax;
}
}
#region public constructors
internal static Image FromSkImage(SKImage image)
{
return CreateImage(image);
}
public static Image FromBinaryData(byte[] imageData)
{
return CreateImage(SKImage.FromEncodedData(imageData));
}
public static Image FromFile(string filePath)
{
return CreateImage(SKImage.FromEncodedData(filePath));
}
public static Image FromStream(Stream fileStream)
{
return CreateImage(SKImage.FromEncodedData(fileStream));
}
private static Image CreateImage(SKImage? image)
{
if (image == null)
throw new DocumentComposeException("Cannot load or decode provided image.");
return new Image(image);
}
#endregion
#region configuration API
public Image DisposeAfterDocumentGeneration()
{
IsDocumentScoped = true;
return this;
}
public Image WithTargetDpi(int dpi = DocumentMetadata.DefaultPdfDpi)
{
TargetDpi = dpi;
return this;
}
public Image ScaleToTargetDpi(bool value = true)
{
PerformScalingToTargetDpi = value;
return this;
}
#endregion
}
}
@@ -25,16 +25,17 @@ namespace QuestPDF.Infrastructure
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> TextStyleApplyInheritedCache = new();
private static readonly ConcurrentDictionary<TextStyle, 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));
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value, overrideValue: true));
}
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true)
private static TextStyle MutateStyle(this TextStyle origin, TextStyleProperty property, object? value, bool overrideValue)
{
if (overrideValue && value == null)
return origin;
@@ -224,40 +225,42 @@ namespace QuestPDF.Infrastructure
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
}
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
internal static TextStyle ApplyInheritedStyle(this TextStyle style, TextStyle parent)
{
var cacheKey = (style, parent);
return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
return TextStyleApplyInheritedCache.GetOrAdd(cacheKey, key => key.origin.ApplyStyleProperties(key.parent, overrideStyle: false, overrideFontFamily: false, applyFallback: true).UpdateFontFallback(overrideStyle: true));
}
private static TextStyle ApplyFontFallback(this TextStyle style)
internal static TextStyle ApplyGlobalStyle(this TextStyle style)
{
return TextStyleApplyGlobalCache.GetOrAdd(style, key => key.ApplyStyleProperties(TextStyle.LibraryDefault, overrideStyle: false, overrideFontFamily: false, applyFallback: true).UpdateFontFallback(overrideStyle: false));
}
private static TextStyle UpdateFontFallback(this TextStyle style, bool overrideStyle)
{
var targetFallbackStyle = style
?.Fallback
?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
?.ApplyFontFallback();
?.ApplyStyleProperties(style, overrideStyle: overrideStyle, overrideFontFamily: false, applyFallback: false)
?.UpdateFontFallback(overrideStyle);
return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
return style.MutateStyle(TextStyleProperty.Fallback, targetFallbackStyle, overrideValue: true);
}
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);
});
return TextStyleOverrideCache.GetOrAdd(cacheKey, key => ApplyStyleProperties(key.origin, key.parent, overrideStyle: true, overrideFontFamily: true, applyFallback: true));
}
private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true)
private static TextStyle ApplyStyleProperties(this TextStyle style, TextStyle parent, bool overrideStyle, bool overrideFontFamily, bool applyFallback)
{
var result = style;
if (string.IsNullOrWhiteSpace(result.FontFamily) || overrideFontFamily)
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
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.LetterSpacing, parent.LetterSpacing, overrideStyle);
+1 -1
View File
@@ -3,7 +3,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.12.3</Version>
<Version>2022.12.6</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. Easily generate PDF reports, invoices, exports, etc.</PackageDescription>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
+12 -4
View File
@@ -1,22 +1,30 @@
Version 2022.12.0:
Feature: implemented LetterSpacing property for the Text element
Improvement: the Text element API accepts now only string values, objects are not automatically converted anymore
Fix: the Alignment element incorrectly limits size of its child when only one axis is set (horizontal or vertical)
Maintenance: Updated SkiaSharp dependency to 2.88.3
In version 2022.12.1:
This release was possible thanks to enormous help of AntonyCorbett, thank you!
Version 2022.12.1:
Fixed: loading fonts from embedded resource via the FontManager.RegisterFontFromEmbeddedResource method
Fixed: better layout calculation stability for the Column element
Improvement: exposed missing API method for the Dynamic component, enabling applying more advanced optimizations
Improvement: better API documentation for the Settings.DocumentLayoutExceptionThreshold property
In version 2022.12.2:
Version 2022.12.2:
Performance improvements in various areas
Text rendering stability improvements
Fixed: the Settings.CheckIfAllTextGlyphsAreAvailable setting does not work correctly
In version 2022.12.3:
Version 2022.12.3:
Fix: inconsistent text height when using multiple lines with different TextStyles
Improvement: added validation for color arguments
Fix: the inlined element is shown only once in the header but should be repeated on each page
This release was possible thanks to enormous help of AntonyCorbett, thank you!
Version 2022.12.4:
Fix: the TextStyle.Fallback property incorrectly inherits parent's and global properties
Improvement: updated the CreateNotMatchingFontException message to mention that the glyph checking operation can be disabled with the Settings.CheckIfAllTextGlyphsAreAvailable setting
Version 2022.12.6:
Fix: fixed the rendering order of table cells in certain scenarios