Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e53b586943 | |||
| 9637dff1dc | |||
| 1c9bccbeb2 |
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 QuestPDF
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,13 +0,0 @@
|
||||
# QuestPDF Community MIT License
|
||||
|
||||
|
||||
## License Permissions
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
## Copyright
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
## Limitation Of Liability
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,29 +0,0 @@
|
||||
# QuestPDF Professional And Enterprise Use License
|
||||
|
||||
|
||||
## Do No Harm
|
||||
By downloading or using the Software, the Licensee agrees not to utilize the software in a manner which is disparaging to QuestPDF, and not to rent, lease or otherwise transfer rights to the Software.
|
||||
|
||||
|
||||
## License Permissions
|
||||
Grants the use of the Software by a specified number of developers to create and deploy closed-source software for unlimited end user organizations ("The Organization") in multiple locations. This license covers unlimited applications or projects. The Software may be deployed upon any number of machines for the end-use of The Organization. This license also intrinsically covers for development, staging and production servers for each project.
|
||||
|
||||
Grants the use of the Software by a specified number of developers to create and deploy closed-source software for unlimited end user organizations ("The Organization") in multiple locations. This license covers unlimited applications or projects. The Software may be deployed upon any number of machines for the end-use of The Organization. This license also intrinsically covers for development, staging and production servers for each project.
|
||||
|
||||
|
||||
## License Fees
|
||||
A. If you wish to use the Software in a production environment, you may download and use the Software for one year upon payment of the appropriate license fee as indicated on the pricing page in accordance with the terms and conditions of this Agreement.
|
||||
|
||||
B. If you wish to use the Software in a non-production environment, you may download and access the source and/or binaries at no charge solely for testing and evaluation purposes and in accordance with all license limitations and restrictions set forth in this Agreement.
|
||||
|
||||
|
||||
## Ownership
|
||||
QuestPDF shall at all times retain ownership of the QuestPDF Software library and all subsequent copies.
|
||||
|
||||
|
||||
## Copyright
|
||||
Title, ownership rights, and intellectual property rights in and to the Software shall remain with QuestPDF. The Software is protected by the international copyright laws. Title, ownership rights, and intellectual property rights in and to the content accessed through the Software is the property of the applicable content owner and may be protected by applicable copyright or other law. This License gives you no rights to such content.
|
||||
|
||||
|
||||
## Limitation Of Liability
|
||||
THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. QUESTPDF AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL QUESTPDF OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF QUESTPDF HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
@@ -1,23 +0,0 @@
|
||||
# What License Do You Need?
|
||||
|
||||
## General License Overview
|
||||
|
||||
Open-source projects are an important foundation for the software development ecosystem. Modern applications are depending more and more upon the work of enthusiast and experts. Open-source is not anymore just sharing knowledge and passion with everybody. Over last years, maintaining and developing those projects have became a highly responsible task.
|
||||
|
||||
We identify the importance of the QuestPDF library in your projects, and therefore want to make sure you can safely and confidently continue the development. We strongly believe that a dual-licensing model is the best solution for all users: providing the best development experience and feature set for everybody and every project, but also offering stability for every professional and enterprise product.
|
||||
|
||||
Being a healthy and growing community is our primary goal that motivates us to pursue professionalism.
|
||||
|
||||
[Read more about exact terms](https://www.questpdf.com/pricing.html)
|
||||
|
||||
|
||||
## QuestPDF Professional Or Enterprise Use License
|
||||
|
||||
If you are consuming the QuestPDF library as a Direct Package Dependency for usage in a Closed Source software in the capacity of a for-profit company/individual with more than 1M USD annual gross revenue, you must purchase the `QuestPDF Professional or Enterprise` License, depending on number of software developers. [Read more](https://www.questpdf.com/license-commercial.html)
|
||||
|
||||
|
||||
## QuestPDF Community MIT License
|
||||
|
||||
We love and highly appreciate the .NET Community and therefore the vast majority of users are welcome to use the library completely for free. If you do not meet the criteria described above, you are eligible to use the `QuestPDF Community MIT` License, without any limitations, even for commercial usage. [Read more](https://www.questpdf.com/license-community.html)
|
||||
|
||||
If you still want to support library development, please consider purchasing the Professional License.
|
||||
@@ -9,15 +9,14 @@
|
||||
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.questpdf.com/pricing.html#license)
|
||||
[](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
### QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API.
|
||||
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>👨💻</td>
|
||||
@@ -49,14 +48,13 @@
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/9263853/232912983-b20e441e-bd47-41df-afa4-f02481252d26.png" width="800" />
|
||||
<img src="https://user-images.githubusercontent.com/9263853/224511996-9dd27a99-bef8-4d5e-bc14-9f6cfe0f5191.png" width="800" />
|
||||
|
||||
|
||||
## Please share with the community
|
||||
@@ -70,7 +68,6 @@ As an open-source project without funding, I cannot afford advertising QuestPDF
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
## Let's get started
|
||||
|
||||
Begin exploring the QuestPDF library today. You are 250 lines of C# code away from creating a fully functional PDF invoice implementation.
|
||||
@@ -83,21 +80,6 @@ Read the Getting Started tutorial to familiarize yourself with general library a
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
## Library License
|
||||
|
||||
If you are consuming the QuestPDF library as a Direct Package Dependency for usage in a Closed Source software in the capacity of a for-profit company/individual with more than 1M USD annual gross revenue, you must purchase the `QuestPDF Professional or Enterprise` License, depending on number of software developers.
|
||||
|
||||
We love and highly appreciate the .NET Community and therefore the vast majority of users are welcome to use the library completely for free. If you do not meet the criteria described above, you are eligible to use the `QuestPDF Community MIT` License, without any limitations, even for commercial usage.
|
||||
|
||||
If you still want to support library development, please consider purchasing the Professional License.
|
||||
|
||||
[](https://www.questpdf.com/pricing.html#license)
|
||||
[](https://www.questpdf.com/pricing.html#tiers)
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
## QuestPDF on JetBrains OSS Power-Ups
|
||||
|
||||
QuestPDF was presented on one of the episodes of OSS Power-Ups hosted by JetBrains. Huge thanks for Matthias Koch and entire JetBrains team for giving me a chance to show QuestPDF. You are the best!
|
||||
@@ -106,4 +88,5 @@ QuestPDF was presented on one of the episodes of OSS Power-Ups hosted by JetBrai
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/jetbrains-oss-powerups-youtube.png?raw=true" width="600px">
|
||||
</a>
|
||||
|
||||
|
||||
[](https://www.youtube.com/watch?v=-iYvZvpLX0g)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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
|
||||
{
|
||||
@@ -64,42 +66,24 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void ReusingTheSameImageFileShouldBePossible()
|
||||
public void ImageResolutionScaling()
|
||||
{
|
||||
var fileName = Path.GetTempFileName() + ".jpg";
|
||||
var image = Image.FromFile("large-image.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 =>
|
||||
Document
|
||||
.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
});
|
||||
page.Size(210, 210);
|
||||
page.Margin(50);
|
||||
page.Content().Image(image);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
})
|
||||
.GeneratePdf($"test.pdf");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
<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>
|
||||
|
||||
@@ -1004,40 +1004,5 @@ 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.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -1,10 +1,16 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using FluentAssertions;
|
||||
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
|
||||
{
|
||||
@@ -15,9 +21,9 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
.For(x => new QuestPDF.Elements.Image
|
||||
{
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(300, 200));
|
||||
@@ -27,9 +33,9 @@ namespace QuestPDF.UnitTests
|
||||
public void Draw_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
.For(x => new QuestPDF.Elements.Image
|
||||
{
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
|
||||
})
|
||||
.DrawElement(new Size(300, 200))
|
||||
.ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200))
|
||||
@@ -51,6 +57,52 @@ namespace QuestPDF.UnitTests
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(300, 100));;
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImageShouldNotBeScaledAboveItsNativeResolution()
|
||||
{
|
||||
var image = Placeholders.Image(200, 200);
|
||||
|
||||
var documentSizeWithScaledDownImage = GetDocumentSize(container => container.Width(100).Height(100).Image(Image.FromBinaryData(image)));
|
||||
//var documentSizeWithNormalImage = GetDocumentSize(container => container.Width(200).Height(200).Image(image));
|
||||
//var documentSizeWithScaledUpImage = GetDocumentSize(container => container.Width(400).Height(400).Image(image));
|
||||
}
|
||||
|
||||
#region helpers
|
||||
|
||||
SKImage GenerateImage(int width, int height)
|
||||
{
|
||||
@@ -58,5 +110,21 @@ namespace QuestPDF.UnitTests
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
return surface.Snapshot();
|
||||
}
|
||||
|
||||
private static int GetDocumentSize(Action<IContainer> container)
|
||||
{
|
||||
return Document
|
||||
.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Content().Element(container);
|
||||
});
|
||||
})
|
||||
.GeneratePdf()
|
||||
.Length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Image = QuestPDF.Elements.Image;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
@@ -21,7 +22,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new PdfCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
RenderDocument(canvas, document, metadata);
|
||||
}
|
||||
|
||||
internal static void GenerateXps(Stream stream, IDocument document)
|
||||
@@ -30,7 +31,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new XpsCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
RenderDocument(canvas, document, metadata);
|
||||
}
|
||||
|
||||
private static void CheckIfStreamIsCompatible(Stream stream)
|
||||
@@ -46,25 +47,27 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new ImageCanvas(metadata);
|
||||
RenderDocument(canvas, document);
|
||||
RenderDocument(canvas, document, metadata);
|
||||
|
||||
return canvas.Images;
|
||||
}
|
||||
|
||||
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new SkiaPictureCanvas();
|
||||
RenderDocument(canvas, document);
|
||||
RenderDocument(canvas, document, metadata);
|
||||
return canvas.Pictures;
|
||||
}
|
||||
|
||||
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
|
||||
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentMetadata metadata)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
var container = new DocumentContainer();
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default);
|
||||
ApplyDefaultImageDpi(content, metadata.RasterDpi, metadata.ImageQuality);
|
||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||
ApplyContentDirection(content, ContentDirection.LeftToRight);
|
||||
|
||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||
@@ -173,6 +176,18 @@ namespace QuestPDF.Drawing
|
||||
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultImageDpi(this Element? content, int targetDpi, int targetImageQuality)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
if (x is not Image { DocumentImage: { } image })
|
||||
return;
|
||||
|
||||
image.TargetDpi ??= targetDpi;
|
||||
image.ImageQuality ??= targetImageQuality;
|
||||
});
|
||||
}
|
||||
|
||||
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
|
||||
{
|
||||
@@ -192,7 +207,7 @@ namespace QuestPDF.Drawing
|
||||
ApplyContentDirection(child, direction);
|
||||
}
|
||||
|
||||
internal static void ApplyInheritedAndGlobalTexStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
@@ -202,23 +217,26 @@ namespace QuestPDF.Drawing
|
||||
foreach (var textBlockItem in textBlock.Items)
|
||||
{
|
||||
if (textBlockItem is TextBlockSpan textSpan)
|
||||
textSpan.Style = textSpan.Style.ApplyInheritedStyle(documentDefaultTextStyle).ApplyGlobalStyle();
|
||||
|
||||
if (textBlockItem is TextBlockElement textElement)
|
||||
ApplyInheritedAndGlobalTexStyle(textElement.Element, documentDefaultTextStyle);
|
||||
{
|
||||
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
}
|
||||
else if (textBlockItem is TextBlockElement textElement)
|
||||
{
|
||||
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (content is DynamicHost dynamicHost)
|
||||
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
|
||||
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
if (content is DefaultTextStyle defaultTextStyleElement)
|
||||
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle);
|
||||
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle);
|
||||
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
public class DocumentMetadata
|
||||
{
|
||||
public const int DefaultPdfDpi = 72;
|
||||
|
||||
public int ImageQuality { get; set; } = 101;
|
||||
public int RasterDpi { get; set; } = 72;
|
||||
public int RasterDpi { get; set; } = DefaultPdfDpi;
|
||||
public bool PdfA { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace QuestPDF.Elements
|
||||
var container = new DynamicElement();
|
||||
content(container);
|
||||
|
||||
container.ApplyInheritedAndGlobalTexStyle(TextStyle);
|
||||
container.ApplyDefaultTextStyle(TextStyle);
|
||||
container.ApplyContentDirection(ContentDirection);
|
||||
|
||||
container.InjectDependencies(PageContext, Canvas);
|
||||
|
||||
@@ -7,11 +7,12 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Image : Element, ICacheable
|
||||
{
|
||||
public SKImage? InternalImage { get; set; }
|
||||
public Infrastructure.Image? DocumentImage { get; set; }
|
||||
|
||||
~Image()
|
||||
{
|
||||
InternalImage?.Dispose();
|
||||
if (DocumentImage is { IsDocumentScoped: true })
|
||||
DocumentImage?.Dispose();
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
@@ -23,10 +24,10 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (InternalImage == null)
|
||||
if (DocumentImage == null)
|
||||
return;
|
||||
|
||||
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
Canvas.DrawImage(DocumentImage.GetVersionOfSize(availableSpace), Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,7 @@ 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. " +
|
||||
$"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.");
|
||||
$"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. ");
|
||||
}
|
||||
|
||||
static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.IO;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
@@ -11,30 +10,30 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(imageData);
|
||||
var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration();
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(filePath);
|
||||
var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration();
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(fileStream);
|
||||
var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration();
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
private static void Image(this IContainer parent, SKImage image, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
public static void Image(this IContainer parent, Infrastructure.Image image, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
if (image == null)
|
||||
throw new DocumentComposeException("Cannot load or decode provided image.");
|
||||
|
||||
var imageElement = new Image
|
||||
var imageElement = new QuestPDF.Elements.Image
|
||||
{
|
||||
InternalImage = image
|
||||
DocumentImage = image
|
||||
};
|
||||
|
||||
if (scaling != ImageScaling.Resize)
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
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 int? ImageQuality { get; set; }
|
||||
internal bool PerformScalingToTargetDpi { get; set; }
|
||||
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;
|
||||
PerformScalingToTargetDpi = image.EncodedData.Size >= Settings.AdjustImageSizeThreshold;
|
||||
}
|
||||
|
||||
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, ImageQuality);
|
||||
ScaledImageCache.Add((targetResolution, scaledImage));
|
||||
|
||||
if (SkImage.EncodedData.Size < scaledImage.EncodedData.Size)
|
||||
return SkImage;
|
||||
|
||||
return scaledImage;
|
||||
|
||||
|
||||
|
||||
static SKImage ScaleImage(SKImage originalImage, Size targetSize, int? imageQuality)
|
||||
{
|
||||
var imageInfo = new SKImageInfo((int)targetSize.Width, (int)targetSize.Height);
|
||||
using var target = SKImage.Create(imageInfo);
|
||||
originalImage.ScalePixels(target.PeekPixels(), SKFilterQuality.High);
|
||||
|
||||
var codes = SKCodec.Create(target.EncodedData);
|
||||
|
||||
var targetFormat = imageQuality > 100 ? SKEncodedImageFormat.Png : SKEncodedImageFormat.Jpeg;
|
||||
var targetQuality = Math.Max(imageQuality, 100);
|
||||
var data = target.Encode(targetFormat, targetQuality);
|
||||
|
||||
return SKImage.FromEncodedData(data);
|
||||
}
|
||||
|
||||
static (SKEncodedImageFormat format, int quality) GetTargetImageFormat(SKImage originalImage, int? imageQuality)
|
||||
{
|
||||
if (imageQuality.HasValue)
|
||||
{
|
||||
var format = imageQuality > 100
|
||||
? SKEncodedImageFormat.Png
|
||||
: SKEncodedImageFormat.Jpeg;
|
||||
|
||||
var quality = Math.Max(imageQuality.Value, 100);
|
||||
|
||||
return (format, quality);
|
||||
}
|
||||
|
||||
var codec = SKCodec.Create(originalImage.EncodedData);
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Values from 1 to 100 correspond to the JPEG format, where 1 is lowest and 100 is highest quality.
|
||||
/// Value 101 correspond to the PNG format with a lossless compression and alpha channel support.
|
||||
/// </summary>
|
||||
/// <param name="quality"></param>
|
||||
/// <returns></returns>
|
||||
public Image WithQuality(int quality)
|
||||
{
|
||||
ImageQuality = quality;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Image WithQuality(ImageQuality quality)
|
||||
{
|
||||
ImageQuality = (int)quality;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Image WithTargetDpi(int dpi = DocumentMetadata.DefaultPdfDpi)
|
||||
{
|
||||
TargetDpi = dpi;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Image ScaleToTargetDpi(bool value = true)
|
||||
{
|
||||
PerformScalingToTargetDpi = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public enum ImageQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// PNG format with alpha support
|
||||
/// </summary>
|
||||
Lossless = 101,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 100 out of 100
|
||||
/// </summary>
|
||||
Max = 100,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 90 out of 100
|
||||
/// </summary>
|
||||
VeryHigh = 90,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 80 out of 100
|
||||
/// </summary>
|
||||
High = 80,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 60 out of 100
|
||||
/// </summary>
|
||||
Medium = 60,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 40 out of 100
|
||||
/// </summary>
|
||||
Low = 40,
|
||||
|
||||
/// <summary>
|
||||
/// JPEG format with compression set to 20 out of 100
|
||||
/// </summary>
|
||||
VeryLow = 20
|
||||
}
|
||||
}
|
||||
@@ -25,17 +25,16 @@ 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> TextStyleApplyInheritedCache = new();
|
||||
private static readonly ConcurrentDictionary<TextStyle, TextStyle> TextStyleApplyGlobalCache = 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, overrideValue: true));
|
||||
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value));
|
||||
}
|
||||
|
||||
private static TextStyle MutateStyle(this TextStyle origin, TextStyleProperty property, object? value, bool overrideValue)
|
||||
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true)
|
||||
{
|
||||
if (overrideValue && value == null)
|
||||
return origin;
|
||||
@@ -225,42 +224,40 @@ namespace QuestPDF.Infrastructure
|
||||
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
|
||||
}
|
||||
|
||||
internal static TextStyle ApplyInheritedStyle(this TextStyle style, TextStyle parent)
|
||||
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
|
||||
{
|
||||
var cacheKey = (style, parent);
|
||||
return TextStyleApplyInheritedCache.GetOrAdd(cacheKey, key => key.origin.ApplyStyleProperties(key.parent, overrideStyle: false, overrideFontFamily: false, applyFallback: true).UpdateFontFallback(overrideStyle: true));
|
||||
return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
|
||||
}
|
||||
|
||||
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)
|
||||
private static TextStyle ApplyFontFallback(this TextStyle style)
|
||||
{
|
||||
var targetFallbackStyle = style
|
||||
?.Fallback
|
||||
?.ApplyStyleProperties(style, overrideStyle: overrideStyle, overrideFontFamily: false, applyFallback: false)
|
||||
?.UpdateFontFallback(overrideStyle);
|
||||
?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
|
||||
?.ApplyFontFallback();
|
||||
|
||||
return style.MutateStyle(TextStyleProperty.Fallback, targetFallbackStyle, overrideValue: true);
|
||||
return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
|
||||
}
|
||||
|
||||
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
||||
{
|
||||
var cacheKey = (style, parent);
|
||||
return TextStyleOverrideCache.GetOrAdd(cacheKey, key => ApplyStyleProperties(key.origin, key.parent, overrideStyle: true, overrideFontFamily: true, applyFallback: true));
|
||||
|
||||
return TextStyleOverrideCache.GetOrAdd(cacheKey, key =>
|
||||
{
|
||||
var result = ApplyStyle(key.origin, key.parent);
|
||||
return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback);
|
||||
});
|
||||
}
|
||||
|
||||
private static TextStyle ApplyStyleProperties(this TextStyle style, TextStyle parent, bool overrideStyle, bool overrideFontFamily, bool applyFallback)
|
||||
private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.12.5</Version>
|
||||
<Version>2022.12.3</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>
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
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
|
||||
|
||||
This release was possible thanks to enormous help of AntonyCorbett, thank you!
|
||||
|
||||
Version 2022.12.1:
|
||||
In 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
|
||||
|
||||
Version 2022.12.2:
|
||||
In version 2022.12.2:
|
||||
Performance improvements in various areas
|
||||
Text rendering stability improvements
|
||||
Fixed: the Settings.CheckIfAllTextGlyphsAreAvailable setting does not work correctly
|
||||
|
||||
Version 2022.12.3:
|
||||
In 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
|
||||
|
||||
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
|
||||
This release was possible thanks to enormous help of AntonyCorbett, thank you!
|
||||
|
||||
@@ -36,5 +36,11 @@
|
||||
/// </summary>
|
||||
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
||||
public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The file size threshold in bytes that is used to determine if the image should be automatically scaled to physical dimensions.
|
||||
/// </summary>
|
||||
public static int AdjustImageSizeThreshold { get; set; } = 32 * 1024;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user