Compare commits

..

3 Commits

Author SHA1 Message Date
MarcinZiabek e53b586943 Implemented automated image scaling 2023-05-01 14:23:46 +02:00
Marcin Ziąbek 9637dff1dc Image scaling dpi (#540)
* DPI-based image scaling prototype (not ready for production)

* Added ImageDpi API

* Added support for image DPI scaling
2023-04-30 11:28:14 +02:00
MarcinZiabek 1c9bccbeb2 Feature: added support for shared/global images 2023-04-17 02:08:04 +02:00
60 changed files with 461 additions and 1235 deletions
-19
View File
@@ -1,19 +0,0 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
- 'releases/*'
jobs:
qodana:
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2023.1.0
View File
+21
View File
@@ -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.
-13
View File
@@ -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.
-23
View File
@@ -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.
+4 -21
View File
@@ -9,15 +9,14 @@
[![GitHub Repo stars](https://img.shields.io/github/stars/QuestPDF/QuestPDF?style=for-the-badge)](https://github.com/QuestPDF/QuestPDF/stargazers) [![GitHub Repo stars](https://img.shields.io/github/stars/QuestPDF/QuestPDF?style=for-the-badge)](https://github.com/QuestPDF/QuestPDF/stargazers)
[![Nuget version](https://img.shields.io/nuget/v/QuestPdf?style=for-the-badge)](https://www.nuget.org/packages/QuestPDF/) [![Nuget version](https://img.shields.io/nuget/v/QuestPdf?style=for-the-badge)](https://www.nuget.org/packages/QuestPDF/)
[![Nuget download](https://img.shields.io/nuget/dt/QuestPDF?style=for-the-badge)](https://www.nuget.org/packages/QuestPDF/) [![Nuget download](https://img.shields.io/nuget/dt/QuestPDF?style=for-the-badge)](https://www.nuget.org/packages/QuestPDF/)
[![QuestPDF License](https://img.shields.io/badge/LICENSE%20details-Community%20MIT%20and%20professional-green?style=for-the-badge)](https://www.questpdf.com/pricing.html#license) [![License](https://img.shields.io/github/license/QuestPDF/QuestPDF?style=for-the-badge)](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
<br /> <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. ### 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%"> <img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
<table> <table>
<tr> <tr>
<td>👨‍💻</td> <td>👨‍💻</td>
@@ -49,14 +48,13 @@
<br /> <br />
## Please help by giving a star ## 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/236193691-efb9cff8-e919-4826-9720-dcfb492980ae.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 ## Please share with the community
@@ -70,7 +68,6 @@ As an open-source project without funding, I cannot afford advertising QuestPDF
<br /> <br />
## Let's get started ## 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. 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 /> <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.
[![Library license details](https://img.shields.io/badge/%F0%9F%93%9C%0A%20read-license%20details-blue?style=for-the-badge)](https://www.questpdf.com/pricing.html#license)
[![Library pricing details](https://img.shields.io/badge/%F0%9F%92%8E%20read-pricing%20details-green?style=for-the-badge)](https://www.questpdf.com/pricing.html#tiers)
<br />
## QuestPDF on JetBrains OSS Power-Ups ## 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! 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"> <img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/jetbrains-oss-powerups-youtube.png?raw=true" width="600px">
</a> </a>
[![YouTube video about QuestPDF]( https://img.shields.io/badge/watch%20on-YouTube-red?logo=youtube&style=for-the-badge)](https://www.youtube.com/watch?v=-iYvZvpLX0g) [![YouTube video about QuestPDF]( https://img.shields.io/badge/watch%20on-YouTube-red?logo=youtube&style=for-the-badge)](https://www.youtube.com/watch?v=-iYvZvpLX0g)
View File
@@ -13,22 +13,25 @@ namespace QuestPDF.Examples.Engine
private Action<IDocumentContainer> Content { get; } private Action<IDocumentContainer> Content { get; }
private int MaxPages { get; } private int MaxPages { get; }
private bool ApplyCaching { get; }
private bool ApplyDebugging { get; }
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging) public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
{ {
Content = content; Content = content;
MaxPages = maxPages; MaxPages = maxPages;
ApplyCaching = applyCaching;
QuestPDF.Settings.EnableCaching = applyCaching; ApplyDebugging = applyDebugging;
QuestPDF.Settings.EnableDebugging = applyDebugging;
QuestPDF.Settings.DocumentLayoutExceptionThreshold = MaxPages;
} }
public DocumentSettings GetSettings() public DocumentMetadata GetMetadata()
{ {
return new DocumentSettings() return new DocumentMetadata()
{ {
ImageRasterDpi = PageSizes.PointsPerInch * ImageScalingFactor RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = MaxPages,
ApplyCaching = ApplyCaching,
ApplyDebugging = ApplyDebugging
}; };
} }
+14 -89
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
@@ -51,64 +52,6 @@ namespace QuestPDF.Examples
}); });
} }
[Test]
public void ScalingImageWithAlpha()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(page =>
{
page.Padding(25).Layers(layers =>
{
layers.Layer().Image(Placeholders.Image);
layers.PrimaryLayer().Padding(25).Image("multilingual.png");
});
});
}
[Test]
public void DpiSetting()
{
RenderingTest
.Create()
.PageSize(400, 600)
.ProduceImages()
.ShowResults()
.Render(page =>
{
page.Padding(10).Column(column =>
{
column.Spacing(10);
column.Item().Image("photo.jpg").WithRasterDpi(16);
column.Item().Image("photo.jpg").WithRasterDpi(72);
});
});
}
[Test]
public void CompressionSetting()
{
RenderingTest
.Create()
.PageSize(400, 600)
.ProduceImages()
.ShowResults()
.Render(page =>
{
page.Padding(10).Column(column =>
{
column.Spacing(10);
column.Item().Image("photo.jpg").WithCompressionQuality(ImageCompressionQuality.VeryLow).WithRasterDpi(72);
column.Item().Image("photo.jpg").WithCompressionQuality(ImageCompressionQuality.High).WithRasterDpi(72);
});
});
}
[Test] [Test]
public void Exception() public void Exception()
{ {
@@ -123,42 +66,24 @@ namespace QuestPDF.Examples
}); });
} }
[Test] [Test]
public void ReusingTheSameImageFileShouldBePossible() public void ImageResolutionScaling()
{ {
var fileName = Path.GetTempFileName() + ".jpg"; var image = Image.FromFile("large-image.jpg");
try Document
{ .Create(document =>
var image = Placeholders.Image(300, 100); {
document.Page(page =>
using var file = File.Create(fileName);
file.Write(image);
file.Dispose();
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{ {
container page.Size(210, 210);
.Padding(20) page.Margin(50);
.Column(column => page.Content().Image(image);
{
column.Spacing(20);
column.Item().Image(fileName);
column.Item().Image(fileName);
column.Item().Image(fileName);
});
}); });
} })
finally .GeneratePdf($"test.pdf");
{
File.Delete(fileName);
}
} }
} }
} }
-15
View File
@@ -1,15 +0,0 @@
using NUnit.Framework;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
[SetUpFixture]
public class LicenseSetup
{
[OneTimeSetUp]
public static void Setup()
{
QuestPDF.Settings.License = LicenseType.Community;
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
@@ -32,10 +32,10 @@
<None Update="pdf-icon.svg"> <None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="multilingual.png"> <None Update="large-image.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="photo.jpg"> <None Update="large-image.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
-52
View File
@@ -316,58 +316,6 @@ 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] [Test]
public void TableHeader() public void TableHeader()
{ {
-35
View File
@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

@@ -4,10 +4,11 @@
<Authors>MarcinZiabek</Authors> <Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company> <Company>CodeFlint</Company>
<PackageId>QuestPDF.Previewer</PackageId> <PackageId>QuestPDF.Previewer</PackageId>
<Version>2023.5.0</Version> <Version>2022.12.0</Version>
<PackAsTool>true</PackAsTool> <PackAsTool>true</PackAsTool>
<ToolCommandName>questpdf-previewer</ToolCommandName> <ToolCommandName>questpdf-previewer</ToolCommandName>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription> <PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>Initial release.</PackageReleaseNotes>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon> <PackageIcon>Logo.png</PackageIcon>
@@ -17,8 +18,10 @@
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl> <RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright> <Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>previewer pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags> <PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core previewer</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
@@ -37,12 +40,12 @@
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Avalonia" Version="0.10.19" /> <PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.19" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.19" /> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" /> <PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.19" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
<PackageReference Include="ReactiveUI" Version="18.4.44" /> <PackageReference Include="ReactiveUI" Version="18.4.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.3" /> <PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.3" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.3" /> <PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.3" />
+13 -11
View File
@@ -17,24 +17,26 @@ namespace QuestPDF.ReportSample
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
QuestPDF.Settings.License = LicenseType.Community;
var model = DataSource.GetReport(); var model = DataSource.GetReport();
Report = new StandardReport(model); Report = new StandardReport(model);
}
[Test]
public void GenerateAndShowPdf()
{
//ImagePlaceholder.Solid = true; //ImagePlaceholder.Solid = true;
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
Report.GeneratePdf(path);
Process.Start("explorer.exe", path);
} }
[Test] [Test]
public void GeneratePdfAndShow() public void GenerateAndShowXps()
{ {
Report.GeneratePdfAndShow(); var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.xps");
} Report.GenerateXps(path);
Process.Start("explorer.exe", path);
[Test]
public void GenerateXpsAndShow()
{
Report.GenerateXpsAndShow();
} }
} }
} }
+4 -13
View File
@@ -17,8 +17,6 @@ namespace QuestPDF.UnitTests
TestPlan TestPlan
.For(x => new DynamicImage .For(x => new DynamicImage
{ {
TargetDpi = DocumentSettings.DefaultRasterDpi,
CompressionQuality = ImageCompressionQuality.High,
Source = GenerateImage Source = GenerateImage
}) })
.MeasureElement(new Size(300, 200)) .MeasureElement(new Size(300, 200))
@@ -31,8 +29,6 @@ namespace QuestPDF.UnitTests
TestPlan TestPlan
.For(x => new DynamicImage .For(x => new DynamicImage
{ {
TargetDpi = DocumentSettings.DefaultRasterDpi,
CompressionQuality = ImageCompressionQuality.High,
Source = size => null Source = size => null
}) })
.DrawElement(new Size(300, 200)) .DrawElement(new Size(300, 200))
@@ -45,8 +41,6 @@ namespace QuestPDF.UnitTests
TestPlan TestPlan
.For(x => new DynamicImage .For(x => new DynamicImage
{ {
TargetDpi = DocumentSettings.DefaultRasterDpi,
CompressionQuality = ImageCompressionQuality.High,
Source = GenerateImage Source = GenerateImage
}) })
.DrawElement(new Size(300, 200)) .DrawElement(new Size(300, 200))
@@ -57,13 +51,11 @@ namespace QuestPDF.UnitTests
[Test] [Test]
public void Draw_PassesCorrectSizeToSource() public void Draw_PassesCorrectSizeToSource()
{ {
ImageSize passedSize = default; Size passedSize = default;
TestPlan TestPlan
.For(x => new DynamicImage .For(x => new DynamicImage
{ {
TargetDpi = DocumentSettings.DefaultRasterDpi * 3,
CompressionQuality = ImageCompressionQuality.High,
Source = size => Source = size =>
{ {
passedSize = size; passedSize = size;
@@ -74,13 +66,12 @@ namespace QuestPDF.UnitTests
.ExpectCanvasDrawImage(Position.Zero, new Size(400, 300)) .ExpectCanvasDrawImage(Position.Zero, new Size(400, 300))
.CheckDrawResult(); .CheckDrawResult();
passedSize.Width.Should().Be(1200); passedSize.Should().BeEquivalentTo(new Size(400, 300));
passedSize.Height.Should().Be(900);
} }
byte[] GenerateImage(ImageSize size) byte[] GenerateImage(Size size)
{ {
var image = GenerateImage(size.Width, size.Height); var image = GenerateImage((int) size.Width, (int) size.Height);
return image.Encode(SKEncodedImageFormat.Png, 100).ToArray(); return image.Encode(SKEncodedImageFormat.Png, 100).ToArray();
} }
+39 -50
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mime; using System.Net.Mime;
using FluentAssertions; using FluentAssertions;
@@ -11,8 +10,7 @@ using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine; using QuestPDF.UnitTests.TestEngine;
using SkiaSharp; using SkiaSharp;
using ImageElement = QuestPDF.Elements.Image; using Image = QuestPDF.Infrastructure.Image;
using DocumentImage = QuestPDF.Infrastructure.Image;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
@@ -23,9 +21,9 @@ namespace QuestPDF.UnitTests
public void Measure_TakesAvailableSpaceRegardlessOfSize() public void Measure_TakesAvailableSpaceRegardlessOfSize()
{ {
TestPlan TestPlan
.For(x => new ImageElement .For(x => new QuestPDF.Elements.Image
{ {
DocumentImage = GenerateDocumentImage(400, 300) DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
}) })
.MeasureElement(new Size(300, 200)) .MeasureElement(new Size(300, 200))
.CheckMeasureResult(SpacePlan.FullRender(300, 200)); .CheckMeasureResult(SpacePlan.FullRender(300, 200));
@@ -35,11 +33,9 @@ namespace QuestPDF.UnitTests
public void Draw_TakesAvailableSpaceRegardlessOfSize() public void Draw_TakesAvailableSpaceRegardlessOfSize()
{ {
TestPlan TestPlan
.For(x => new ImageElement .For(x => new QuestPDF.Elements.Image
{ {
CompressionQuality = ImageCompressionQuality.High, DocumentImage = Image.FromSkImage(GenerateImage(400, 300))
TargetDpi = DocumentSettings.DefaultRasterDpi,
DocumentImage = GenerateDocumentImage(400, 300)
}) })
.DrawElement(new Size(300, 200)) .DrawElement(new Size(300, 200))
.ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200)) .ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200))
@@ -49,7 +45,7 @@ namespace QuestPDF.UnitTests
[Test] [Test]
public void Fluent_RecognizesImageProportions() public void Fluent_RecognizesImageProportions()
{ {
var image = GenerateDocumentImage(600, 200); var image = GenerateImage(600, 200).Encode(SKEncodedImageFormat.Png, 100).ToArray();
TestPlan TestPlan
.For(x => .For(x =>
@@ -61,63 +57,60 @@ namespace QuestPDF.UnitTests
.MeasureElement(new Size(300, 200)) .MeasureElement(new Size(300, 200))
.CheckMeasureResult(SpacePlan.FullRender(300, 100));; .CheckMeasureResult(SpacePlan.FullRender(300, 100));;
} }
[Test] [Test]
public void UsingSharedImageShouldNotDrasticallyIncreaseDocumentSize() public void UsingSharedImageShouldNotDrasticallyIncreaseDocumentSize()
{ {
var photo = File.ReadAllBytes("Resources/photo.jpg"); var placeholderImage = Placeholders.Image(1000, 200);
var documentWithSingleImageSize = GetDocumentSize(container => var documentWithSingleImageSize = GetDocumentSize(container =>
{ {
container.Image(photo); container.Image(placeholderImage);
}); });
var documentWithMultipleImagesSize = GetDocumentSize(container => var documentWithMultipleImagesSize = GetDocumentSize(container =>
{ {
container.Column(column => container.Column(column =>
{ {
foreach (var i in Enumerable.Range(0, 10)) foreach (var i in Enumerable.Range(0, 100))
column.Item().Image(photo); column.Item().Image(placeholderImage);
}); });
}); });
var documentWithSingleImageUsedMultipleTimesSize = GetDocumentSize(container => var documentWithSingleImageUsedMultipleTimesSize = GetDocumentSize(container =>
{ {
container.Column(column => container.Column(column =>
{ {
var sharedImage = DocumentImage.FromBinaryData(photo).DisposeAfterDocumentGeneration(); var sharedImage = Image.FromBinaryData(placeholderImage).DisposeAfterDocumentGeneration();
foreach (var i in Enumerable.Range(0, 10)) foreach (var i in Enumerable.Range(0, 100))
column.Item().Image(sharedImage); column.Item().Image(sharedImage);
}); });
}); });
(documentWithMultipleImagesSize / (float)documentWithSingleImageSize).Should().BeInRange(9.9f, 10); (documentWithMultipleImagesSize / (float)documentWithSingleImageSize).Should().BeInRange(90, 100);
(documentWithSingleImageUsedMultipleTimesSize / (float)documentWithSingleImageSize).Should().BeInRange(1f, 1.05f); (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)
{
var imageInfo = new SKImageInfo(width, height);
using var surface = SKSurface.Create(imageInfo);
return surface.Snapshot();
} }
[Test]
public void ImageCompressionHasImpactOnDocumentSize()
{
var photo = File.ReadAllBytes("Resources/photo.jpg");
var veryLowCompressionSize = GetDocumentSize(container => container.Image(photo).WithCompressionQuality(ImageCompressionQuality.VeryLow));
var bestCompressionSize = GetDocumentSize(container => container.Image(photo).WithCompressionQuality(ImageCompressionQuality.Best));
(bestCompressionSize / (float)veryLowCompressionSize).Should().BeGreaterThan(25);
}
[Test]
public void TargetDpiHasImpactOnDocumentSize()
{
var photo = File.ReadAllBytes("Resources/photo.jpg");
var lowDpiSize = GetDocumentSize(container => container.Image(photo).WithRasterDpi(12));
var highDpiSize = GetDocumentSize(container => container.Image(photo).WithRasterDpi(144));
(highDpiSize / (float)lowDpiSize).Should().BeGreaterThan(40);
}
private static int GetDocumentSize(Action<IContainer> container) private static int GetDocumentSize(Action<IContainer> container)
{ {
return Document return Document
@@ -131,11 +124,7 @@ namespace QuestPDF.UnitTests
.GeneratePdf() .GeneratePdf()
.Length; .Length;
} }
DocumentImage GenerateDocumentImage(int width, int height) #endregion
{
var image = Placeholders.Image(width, height);
return DocumentImage.FromBinaryData(image);
}
} }
} }
-15
View File
@@ -1,15 +0,0 @@
using NUnit.Framework;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests
{
[SetUpFixture]
public class LicenseSetup
{
[OneTimeSetUp]
public static void Setup()
{
QuestPDF.Settings.License = LicenseType.Community;
}
}
}
@@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>9</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -27,10 +26,4 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="Resources\photo.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

@@ -237,8 +237,6 @@ namespace QuestPDF.UnitTests.TestEngine
Child = new DynamicImage Child = new DynamicImage
{ {
CompressionQuality = ImageCompressionQuality.Medium,
TargetDpi = DocumentSettings.DefaultRasterDpi,
Source = Placeholders.Image Source = Placeholders.Image
} }
}; };
@@ -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);
}
}
}
+39 -89
View File
@@ -10,6 +10,7 @@ using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using Image = QuestPDF.Elements.Image;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
@@ -17,23 +18,20 @@ namespace QuestPDF.Drawing
{ {
internal static void GeneratePdf(Stream stream, IDocument document) internal static void GeneratePdf(Stream stream, IDocument document)
{ {
ValidateLicense();
CheckIfStreamIsCompatible(stream); CheckIfStreamIsCompatible(stream);
var metadata = document.GetMetadata(); var metadata = document.GetMetadata();
var settings = document.GetSettings(); var canvas = new PdfCanvas(stream, metadata);
var canvas = new PdfCanvas(stream, metadata, settings); RenderDocument(canvas, document, metadata);
RenderDocument(canvas, document, settings);
} }
internal static void GenerateXps(Stream stream, IDocument document) internal static void GenerateXps(Stream stream, IDocument document)
{ {
ValidateLicense();
CheckIfStreamIsCompatible(stream); CheckIfStreamIsCompatible(stream);
var settings = document.GetSettings(); var metadata = document.GetMetadata();
var canvas = new XpsCanvas(stream, settings); var canvas = new XpsCanvas(stream, metadata);
RenderDocument(canvas, document, settings); RenderDocument(canvas, document, metadata);
} }
private static void CheckIfStreamIsCompatible(Stream stream) private static void CheckIfStreamIsCompatible(Stream stream)
@@ -47,61 +45,30 @@ namespace QuestPDF.Drawing
internal static ICollection<byte[]> GenerateImages(IDocument document) internal static ICollection<byte[]> GenerateImages(IDocument document)
{ {
ValidateLicense(); var metadata = document.GetMetadata();
var canvas = new ImageCanvas(metadata);
var settings = document.GetSettings(); RenderDocument(canvas, document, metadata);
var canvas = new ImageCanvas(settings);
RenderDocument(canvas, document, settings);
return canvas.Images; return canvas.Images;
} }
private static void ValidateLicense()
{
if (Settings.License.HasValue)
return;
var newParagraph = Environment.NewLine + Environment.NewLine;
var exceptionMessage =
$"QuestPDF is a modern open-source library. " +
$"We identify the importance of the library in your projects and therefore want to make sure you can safely and confidently continue the development. " +
$"Being a healthy and growing community is the primary goal that motivates us to pursue professionalism. {newParagraph}" +
$"We love and highly appreciate the .NET Community, and therefore the vast majority of users are welcome to use the library completely for free under the QuestPDF Community MIT license. {newParagraph}" +
$"However, 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 the number of software developers. {newParagraph}" +
$"If you still want to support library development, please consider purchasing the Professional License. {newParagraph}" +
$"For evaluation purposes, feel free to use the QuestPDF Community License in a non-production environment. {newParagraph}" +
$"Please refer to the QuestPDF License and Pricing webpage for more details. (https://www.questpdf.com/pricing.html) {newParagraph}" +
$"If you are an existing QuestPDF user and for any reason cannot update, you can stay with the 2022.12.X release with the extended quality support but without any new features, improvements, or optimizations. That release will always be available under the MIT license, free for commercial usage. {newParagraph}" +
$"The library does not require any license key. " +
$"We trust our users, and therefore the process is simple. " +
$"To disable license validation and turn off this exception, please configure an eligible license using the QuestPDF.Settings.License API, for example: {newParagraph}" +
$"\"QuestPDF.Settings.License = LicenseType.Community;\" {newParagraph}" +
$"Learn more on: https://www.questpdf.com/license-configuration.html {newParagraph}";
throw new Exception(exceptionMessage)
{
HelpLink = "https://www.questpdf.com/pricing.html"
};
}
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document) internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
{ {
var metadata = document.GetMetadata();
var canvas = new SkiaPictureCanvas(); var canvas = new SkiaPictureCanvas();
RenderDocument(canvas, document, DocumentSettings.Default); RenderDocument(canvas, document, metadata);
return canvas.Pictures; return canvas.Pictures;
} }
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentSettings settings) internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, DocumentMetadata metadata)
where TCanvas : ICanvas, IRenderingCanvas where TCanvas : ICanvas, IRenderingCanvas
{ {
var container = new DocumentContainer(); var container = new DocumentContainer();
document.Compose(container); document.Compose(container);
var content = container.Compose(); var content = container.Compose();
ApplyDefaultImageDpi(content, metadata.RasterDpi, metadata.ImageQuality);
ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default); ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
ApplyContentDirection(content, settings.ContentDirection); ApplyContentDirection(content, ContentDirection.LeftToRight);
ApplyDefaultImageConfiguration(content, settings.ImageRasterDpi, settings.ImageCompressionQuality);
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
@@ -209,6 +176,18 @@ namespace QuestPDF.Drawing
return debuggingState; 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) internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
{ {
@@ -227,40 +206,8 @@ namespace QuestPDF.Drawing
foreach (var child in content.GetChildren()) foreach (var child in content.GetChildren())
ApplyContentDirection(child, direction); ApplyContentDirection(child, direction);
} }
internal static void ApplyDefaultImageConfiguration(this Element? content, int imageRasterDpi, ImageCompressionQuality imageCompressionQuality)
{
content.VisitChildren(x =>
{
if (x is QuestPDF.Elements.Image image)
{
image.TargetDpi ??= imageRasterDpi;
image.CompressionQuality ??= imageCompressionQuality;
}
if (x is QuestPDF.Elements.DynamicImage dynamicImage) internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
dynamicImage.TargetDpi ??= imageRasterDpi;
dynamicImage.CompressionQuality ??= imageCompressionQuality;
}
if (x is DynamicHost dynamicHost)
{
dynamicHost.ImageTargetDpi ??= imageRasterDpi;
dynamicHost.ImageCompressionQuality ??= imageCompressionQuality;
}
if (x is TextBlock textBlock)
{
foreach (var textBlockElement in textBlock.Items.OfType<TextBlockElement>())
{
textBlockElement.Element.ApplyDefaultImageConfiguration(imageRasterDpi, imageCompressionQuality);
}
}
});
}
internal static void ApplyInheritedAndGlobalTexStyle(this Element? content, TextStyle documentDefaultTextStyle)
{ {
if (content == null) if (content == null)
return; return;
@@ -270,23 +217,26 @@ namespace QuestPDF.Drawing
foreach (var textBlockItem in textBlock.Items) foreach (var textBlockItem in textBlock.Items)
{ {
if (textBlockItem is TextBlockSpan textSpan) if (textBlockItem is TextBlockSpan textSpan)
textSpan.Style = textSpan.Style.ApplyInheritedStyle(documentDefaultTextStyle).ApplyGlobalStyle(); {
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
if (textBlockItem is TextBlockElement textElement) }
ApplyInheritedAndGlobalTexStyle(textElement.Element, documentDefaultTextStyle); else if (textBlockItem is TextBlockElement textElement)
{
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
} }
return; return;
} }
if (content is DynamicHost dynamicHost) if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle); dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
if (content is DefaultTextStyle defaultTextStyleElement) if (content is DefaultTextStyle defaultTextStyleElement)
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle); documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
foreach (var child in content.GetChildren()) foreach (var child in content.GetChildren())
ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle); ApplyDefaultTextStyle(child, documentDefaultTextStyle);
} }
} }
} }
@@ -1,9 +1,16 @@
using System; using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Infrastructure namespace QuestPDF.Drawing
{ {
public class DocumentMetadata public class DocumentMetadata
{ {
public const int DefaultPdfDpi = 72;
public int ImageQuality { get; set; } = 101;
public int RasterDpi { get; set; } = DefaultPdfDpi;
public bool PdfA { get; set; }
public string? Title { get; set; } public string? Title { get; set; }
public string? Author { get; set; } public string? Author { get; set; }
public string? Subject { get; set; } public string? Subject { get; set; }
@@ -14,10 +21,6 @@ namespace QuestPDF.Infrastructure
public DateTime CreationDate { get; set; } = DateTime.Now; public DateTime CreationDate { get; set; } = DateTime.Now;
public DateTime ModifiedDate { get; set; } = DateTime.Now; public DateTime ModifiedDate { get; set; } = DateTime.Now;
public static DocumentMetadata Default => new DocumentMetadata();
#region Deprecated properties
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")] [Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")]
public int DocumentLayoutExceptionThreshold public int DocumentLayoutExceptionThreshold
{ {
@@ -38,16 +41,7 @@ namespace QuestPDF.Infrastructure
get => Settings.EnableDebugging; get => Settings.EnableDebugging;
set => Settings.EnableDebugging = value; set => Settings.EnableDebugging = value;
} }
[Obsolete("This API has been moved since version 2023.5. Please use the QuestPDF.Infrastructure.DocumentSettings API.")] public static DocumentMetadata Default => new DocumentMetadata();
public int? ImageQuality { get; set; }
[Obsolete("This API has been moved since version 2023.5. Please use the QuestPDF.Infrastructure.DocumentSettings API.")]
public int? RasterDpi { get; set; }
[Obsolete("This API has been moved since version 2023.5. Please use the QuestPDF.Infrastructure.DocumentSettings API.")]
public bool? PdfA { get; set; }
#endregion
} }
} }
+4 -4
View File
@@ -7,14 +7,14 @@ namespace QuestPDF.Drawing
{ {
internal class ImageCanvas : SkiaCanvasBase internal class ImageCanvas : SkiaCanvasBase
{ {
private DocumentSettings Settings { get; } private DocumentMetadata Metadata { get; }
private SKSurface Surface { get; set; } private SKSurface Surface { get; set; }
internal ICollection<byte[]> Images { get; } = new List<byte[]>(); internal ICollection<byte[]> Images { get; } = new List<byte[]>();
public ImageCanvas(DocumentSettings settings) public ImageCanvas(DocumentMetadata metadata)
{ {
Settings = settings; Metadata = metadata;
} }
public override void BeginDocument() public override void BeginDocument()
@@ -30,7 +30,7 @@ namespace QuestPDF.Drawing
public override void BeginPage(Size size) public override void BeginPage(Size size)
{ {
var scalingFactor = Settings.ImageRasterDpi / (float) PageSizes.PointsPerInch; var scalingFactor = Metadata.RasterDpi / (float) PageSizes.PointsPerInch;
var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor)); var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor));
Surface = SKSurface.Create(imageInfo); Surface = SKSurface.Create(imageInfo);
+8 -9
View File
@@ -2,24 +2,23 @@
using System.IO; using System.IO;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
internal class PdfCanvas : SkiaDocumentCanvasBase internal class PdfCanvas : SkiaDocumentCanvasBase
{ {
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata, DocumentSettings documentSettings) public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(CreatePdf(stream, documentMetadata, documentSettings)) : base(CreatePdf(stream, documentMetadata))
{ {
} }
private static SKDocument CreatePdf(Stream stream, DocumentMetadata documentMetadata, DocumentSettings documentSettings) private static SKDocument CreatePdf(Stream stream, DocumentMetadata documentMetadata)
{ {
try try
{ {
return SKDocument.CreatePdf(stream, MapMetadata(documentMetadata, documentSettings)); return SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
} }
catch (TypeInitializationException exception) catch (TypeInitializationException exception)
{ {
@@ -27,7 +26,7 @@ namespace QuestPDF.Drawing
} }
} }
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata, DocumentSettings documentSettings) private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{ {
return new SKDocumentPdfMetadata return new SKDocumentPdfMetadata
{ {
@@ -41,9 +40,9 @@ namespace QuestPDF.Drawing
Creation = metadata.CreationDate, Creation = metadata.CreationDate,
Modified = metadata.ModifiedDate, Modified = metadata.ModifiedDate,
RasterDpi = documentSettings.ImageRasterDpi, RasterDpi = metadata.RasterDpi,
EncodingQuality = documentSettings.ImageCompressionQuality.ToQualityValue(), EncodingQuality = metadata.ImageQuality,
PdfA = documentSettings.PdfA PdfA = metadata.PdfA
}; };
} }
} }
+4 -5
View File
@@ -2,24 +2,23 @@
using System.IO; using System.IO;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp; using SkiaSharp;
namespace QuestPDF.Drawing namespace QuestPDF.Drawing
{ {
internal class XpsCanvas : SkiaDocumentCanvasBase internal class XpsCanvas : SkiaDocumentCanvasBase
{ {
public XpsCanvas(Stream stream, DocumentSettings documentSettings) public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(CreateXps(stream, documentSettings)) : base(CreateXps(stream, documentMetadata))
{ {
} }
private static SKDocument CreateXps(Stream stream, DocumentSettings documentSettings) private static SKDocument CreateXps(Stream stream, DocumentMetadata documentMetadata)
{ {
try try
{ {
return SKDocument.CreateXps(stream, documentSettings.ImageRasterDpi); return SKDocument.CreateXps(stream, documentMetadata.RasterDpi);
} }
catch (TypeInitializationException exception) catch (TypeInitializationException exception)
{ {
+1 -7
View File
@@ -8,14 +8,11 @@ namespace QuestPDF.Elements
{ {
public ContentDirection ContentDirection { get; set; } public ContentDirection ContentDirection { get; set; }
public float Ratio { get; set; } public float Ratio { get; set; } = 1;
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth; public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
if (Ratio == 0)
return SpacePlan.FullRender(0, 0);
if(Child == null) if(Child == null)
return SpacePlan.FullRender(0, 0); return SpacePlan.FullRender(0, 0);
@@ -59,9 +56,6 @@ namespace QuestPDF.Elements
private Size GetTargetSize(Size availableSpace) private Size GetTargetSize(Size availableSpace)
{ {
if (Ratio == 0)
return availableSpace;
var spaceRatio = availableSpace.Width / availableSpace.Height; var spaceRatio = availableSpace.Width / availableSpace.Height;
var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ; var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ;
+2 -12
View File
@@ -14,9 +14,6 @@ namespace QuestPDF.Elements
internal TextStyle TextStyle { get; set; } = TextStyle.Default; internal TextStyle TextStyle { get; set; } = TextStyle.Default;
public ContentDirection ContentDirection { get; set; } public ContentDirection ContentDirection { get; set; }
internal int? ImageTargetDpi { get; set; }
internal ImageCompressionQuality? ImageCompressionQuality { get; set; }
public DynamicHost(DynamicComponentProxy child) public DynamicHost(DynamicComponentProxy child)
{ {
Child = child; Child = child;
@@ -61,9 +58,6 @@ namespace QuestPDF.Elements
TextStyle = TextStyle, TextStyle = TextStyle,
ContentDirection = ContentDirection, ContentDirection = ContentDirection,
ImageTargetDpi = ImageTargetDpi.Value,
ImageCompressionQuality = ImageCompressionQuality.Value,
PageNumber = PageContext.CurrentPage, PageNumber = PageContext.CurrentPage,
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd, TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
AvailableSize = availableSize AvailableSize = availableSize
@@ -85,10 +79,7 @@ namespace QuestPDF.Elements
internal TextStyle TextStyle { get; set; } internal TextStyle TextStyle { get; set; }
internal ContentDirection ContentDirection { get; set; } internal ContentDirection ContentDirection { get; set; }
internal int ImageTargetDpi { get; set; }
internal ImageCompressionQuality ImageCompressionQuality { get; set; }
public int PageNumber { get; internal set; } public int PageNumber { get; internal set; }
public int TotalPages { get; internal set; } public int TotalPages { get; internal set; }
public Size AvailableSize { get; internal set; } public Size AvailableSize { get; internal set; }
@@ -98,9 +89,8 @@ namespace QuestPDF.Elements
var container = new DynamicElement(); var container = new DynamicElement();
content(container); content(container);
container.ApplyInheritedAndGlobalTexStyle(TextStyle); container.ApplyDefaultTextStyle(TextStyle);
container.ApplyContentDirection(ContentDirection); container.ApplyContentDirection(ContentDirection);
container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality);
container.InjectDependencies(PageContext, Canvas); container.InjectDependencies(PageContext, Canvas);
container.VisitChildren(x => (x as IStateResettable)?.ResetState()); container.VisitChildren(x => (x as IStateResettable)?.ResetState());
+5 -24
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Security;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -7,13 +6,9 @@ using SkiaSharp;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
public delegate byte[] GenerateDynamicImageDelegate(ImageSize size);
internal class DynamicImage : Element internal class DynamicImage : Element
{ {
internal int? TargetDpi { get; set; } public Func<Size, byte[]>? Source { get; set; }
internal ImageCompressionQuality? CompressionQuality { get; set; }
public GenerateDynamicImageDelegate? Source { get; set; }
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
@@ -24,27 +19,13 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace) internal override void Draw(Size availableSpace)
{ {
var targetResolution = GetTargetResolution(availableSpace, TargetDpi.Value); var imageData = Source?.Invoke(availableSpace);
var imageData = Source?.Invoke(targetResolution);
if (imageData == null) if (imageData == null)
return; return;
using var originalImage = SKImage.FromEncodedData(imageData); using var image = SKImage.FromEncodedData(imageData);
using var compressedImage = originalImage.CompressImage(CompressionQuality.Value); Canvas.DrawImage(image, Position.Zero, availableSpace);
var targetImage = Helpers.Helpers.GetImageWithSmallerSize(originalImage, compressedImage);
Canvas.DrawImage(targetImage, Position.Zero, availableSpace);
}
private static ImageSize GetTargetResolution(Size availableSize, int targetDpi)
{
var scalingFactor = targetDpi / (float)DocumentSettings.DefaultRasterDpi;
return new ImageSize(
(int)(availableSize.Width * scalingFactor),
(int)(availableSize.Height * scalingFactor)
);
} }
} }
} }
+1 -39
View File
@@ -9,10 +9,6 @@ namespace QuestPDF.Elements
{ {
public Infrastructure.Image? DocumentImage { get; set; } public Infrastructure.Image? DocumentImage { get; set; }
internal bool UseOriginalImage { get; set; }
internal int? TargetDpi { get; set; }
internal ImageCompressionQuality? CompressionQuality { get; set; }
~Image() ~Image()
{ {
if (DocumentImage is { IsDocumentScoped: true }) if (DocumentImage is { IsDocumentScoped: true })
@@ -31,41 +27,7 @@ namespace QuestPDF.Elements
if (DocumentImage == null) if (DocumentImage == null)
return; return;
var image = GetImageToDraw(availableSpace); Canvas.DrawImage(DocumentImage.GetVersionOfSize(availableSpace), Position.Zero, availableSpace);
Canvas.DrawImage(image, Position.Zero, availableSpace);
}
private SKImage GetImageToDraw(Size availableSpace)
{
var originalImage = DocumentImage.SkImage;
if (UseOriginalImage)
return originalImage;
var request = new GetImageVersionRequest
{
Resolution = GetTargetResolution(DocumentImage.Size, availableSpace, TargetDpi.Value),
CompressionQuality = CompressionQuality.Value
};
var targetImage = DocumentImage.GetVersionOfSize(request);
return Helpers.Helpers.GetImageWithSmallerSize(originalImage, targetImage);
}
private static ImageSize GetTargetResolution(ImageSize imageResolution, Size availableAreaSize, int targetDpi)
{
var scalingFactor = targetDpi / (float)DocumentSettings.DefaultRasterDpi;
var targetResolution = new ImageSize(
(int)(availableAreaSize.Width * scalingFactor),
(int)(availableAreaSize.Height * scalingFactor));
var isImageResolutionSmallerThanTarget = imageResolution.Width < targetResolution.Width || imageResolution.Height < targetResolution.Height;
if (isImageResolutionSmallerThanTarget)
return imageResolution;
return targetResolution;
} }
} }
} }
+1 -1
View File
@@ -112,7 +112,7 @@ namespace QuestPDF.Elements.Table
UpdateColumnsWidth(availableSpace.Width); UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace); var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands.OrderBy(x => x.Cell.ZIndex)) foreach (var command in renderingCommands)
{ {
if (command.Measurement.Type == SpacePlanType.FullRender) if (command.Measurement.Type == SpacePlanType.FullRender)
command.Cell.IsRendered = true; command.Cell.IsRendered = true;
@@ -8,8 +8,6 @@ namespace QuestPDF.Elements.Table
public int Column { get; set; } = 0; public int Column { get; set; } = 0;
public int ColumnSpan { get; set; } = 1; public int ColumnSpan { get; set; } = 1;
public int ZIndex { get; set; }
public bool IsRendered { get; set; } public bool IsRendered { get; set; }
} }
} }
@@ -16,13 +16,9 @@ namespace QuestPDF.Elements.Table
{ {
var cellsWindow = new List<TableCell>(); var cellsWindow = new List<TableCell>();
(int x, int y) currentLocation = (1, 1); (int x, int y) currentLocation = (1, 1);
var zIndex = 0;
foreach (var cell in cells) foreach (var cell in cells)
{ {
cell.ZIndex = zIndex;
zIndex++;
if (cellsWindow.Count > Math.Max(columnsCount, 16)) if (cellsWindow.Count > Math.Max(columnsCount, 16))
{ {
cellsWindow = cellsWindow cellsWindow = cellsWindow
@@ -105,9 +105,7 @@ namespace QuestPDF.Elements.Text
$"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. ");
$"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) static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -29,17 +28,6 @@ namespace QuestPDF.Fluent
DocumentGenerator.GeneratePdf(stream, document); DocumentGenerator.GeneratePdf(stream, document);
} }
private static int GenerateAndShowCounter = 0;
public static void GeneratePdfAndShow(this IDocument document)
{
GenerateAndShowCounter++;
var filePath = Path.Combine(Path.GetTempPath(), $"QuestPDF Document {GenerateAndShowCounter}.pdf");
document.GeneratePdf(filePath);
OpenFileUsingDefaultProgram(filePath);
}
#endregion #endregion
#region XPS #region XPS
@@ -62,13 +50,6 @@ namespace QuestPDF.Fluent
DocumentGenerator.GenerateXps(stream, document); DocumentGenerator.GenerateXps(stream, document);
} }
public static void GenerateXpsAndShow(this IDocument document)
{
var filePath = Path.Combine(Path.GetTempPath(), $"QuestPDF Document.xps");
document.GenerateXps(filePath);
OpenFileUsingDefaultProgram(filePath);
}
#endregion #endregion
#region Images #region Images
@@ -96,22 +77,5 @@ namespace QuestPDF.Fluent
} }
#endregion #endregion
#region Helpers
private static void OpenFileUsingDefaultProgram(string filePath)
{
var process = new Process
{
StartInfo = new ProcessStartInfo(filePath)
{
UseShellExecute = true
}
};
process.Start();
}
#endregion
} }
} }
+26 -157
View File
@@ -3,129 +3,30 @@ using System.IO;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
{ {
public class ImageDescriptor
{
private Elements.Image ImageElement { get; }
private AspectRatio AspectRatioElement { get; }
private float ImageAspectRatio { get; }
internal ImageDescriptor(Elements.Image imageElement, Elements.AspectRatio aspectRatioElement)
{
ImageElement = imageElement;
AspectRatioElement = aspectRatioElement;
var imageSize = ImageElement.DocumentImage.Size;
ImageAspectRatio = imageSize.Width / (float)imageSize.Height;
}
/// <summary>
/// When enabled, the library will not attempt to resize the image to fit the target DPI, nor save it with target image quality.
/// </summary>
public ImageDescriptor UseOriginalImage(bool value = true)
{
ImageElement.UseOriginalImage = value;
return this;
}
/// <summary>
/// The DPI (pixels-per-inch) at which images and features without native PDF support will be rasterized.
/// A larger DPI would create a PDF that reflects the original intent with better fidelity, but it can make for larger PDF files too, which would use more memory while rendering, and it would be slower to be processed or sent online or to printer.
/// When generating images, this parameter also controls the resolution of the generated content.
/// Default value is 144.
/// </summary>
public ImageDescriptor WithRasterDpi(int dpi)
{
ImageElement.TargetDpi = dpi;
return this;
}
/// <summary>
/// Encoding quality controls the trade-off between size and quality.
/// When the image is opaque, it will be encoded using the JPEG format with the selected quality setting.
/// When the image contains an alpha channel, it is always encoded using the PNG format and this option is ignored.
/// The default value is "very high quality".
/// </summary>
public ImageDescriptor WithCompressionQuality(ImageCompressionQuality quality)
{
ImageElement.CompressionQuality = quality;
return this;
}
#region Aspect Ratio
/// <summary>
/// The image scales to take the entire available width. Default.
/// </summary>
public ImageDescriptor FitWidth()
{
return SetAspectRatio(AspectRatioOption.FitWidth);
}
/// <summary>
/// The images scales to take the entire available height. Good in conjunction with constraining elements.
/// </summary>
public ImageDescriptor FitHeight()
{
return SetAspectRatio(AspectRatioOption.FitHeight);
}
/// <summary>
/// This is the combination of both of the FitWidth and the FitHeight options.
/// The element scales to occupy the entire available area while preserving its aspect ratio.
/// This means that sometimes it occupies the entire width and sometimes the entire height.
/// This is the safest option.
/// </summary>
public ImageDescriptor FitArea()
{
return SetAspectRatio(AspectRatioOption.FitArea);
}
/// <summary>
/// The image resizes itself to occupy the entire available space.
/// It does not preserve proportions.
/// The image may look incorrectly scaled, and is not desired in most of the cases.
/// </summary>
public ImageDescriptor FitUnproportionally()
{
AspectRatioElement.Ratio = 0;
return this;
}
private ImageDescriptor SetAspectRatio(AspectRatioOption option)
{
AspectRatioElement.Ratio = ImageAspectRatio;
AspectRatioElement.Option = option;
return this;
}
#endregion
}
public static class ImageExtensions public static class ImageExtensions
{ {
public static ImageDescriptor Image(this IContainer parent, byte[] imageData) public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth)
{ {
var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration(); var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration();
return parent.Image(image); parent.Image(image, scaling);
} }
public static ImageDescriptor Image(this IContainer parent, string filePath) public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth)
{ {
var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration(); var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration();
return parent.Image(image); parent.Image(image, scaling);
} }
public static ImageDescriptor Image(this IContainer parent, Stream fileStream) public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth)
{ {
var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration(); var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration();
return parent.Image(image); parent.Image(image, scaling);
} }
internal static ImageDescriptor Image(this IContainer parent, Infrastructure.Image image) public static void Image(this IContainer parent, Infrastructure.Image image, ImageScaling scaling = ImageScaling.FitWidth)
{ {
if (image == null) if (image == null)
throw new DocumentComposeException("Cannot load or decode provided image."); throw new DocumentComposeException("Cannot load or decode provided image.");
@@ -135,64 +36,32 @@ namespace QuestPDF.Fluent
DocumentImage = image DocumentImage = image
}; };
var aspectRationElement = new AspectRatio if (scaling != ImageScaling.Resize)
{ {
Child = imageElement var aspectRatio = image.Width / (float)image.Height;
}; parent = parent.AspectRatio(aspectRatio, Map(scaling));
}
parent.Element(aspectRationElement);
return new ImageDescriptor(imageElement, aspectRationElement).FitWidth(); parent.Element(imageElement);
static AspectRatioOption Map(ImageScaling scaling)
{
return scaling switch
{
ImageScaling.FitWidth => AspectRatioOption.FitWidth,
ImageScaling.FitHeight => AspectRatioOption.FitHeight,
ImageScaling.FitArea => AspectRatioOption.FitArea,
_ => throw new ArgumentOutOfRangeException()
};
}
} }
public static void Image(this IContainer element, GenerateDynamicImageDelegate dynamicImageSource) public static void Image(this IContainer element, Func<Size, byte[]> imageSource)
{ {
element.Element(new DynamicImage element.Element(new DynamicImage
{ {
Source = dynamicImageSource Source = imageSource
}); });
} }
#region Obsolete
[Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that takes the GenerateDynamicImageDelegate as an argument.")]
public static void Image(this IContainer element, Func<Size, byte[]> imageSource)
{
element.Image((ImageSize x) => imageSource(new Size(x.Width, x.Height)));
}
[Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")]
public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling)
{
parent.Image(imageData).ApplyScaling(scaling);
}
[Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")]
public static void Image(this IContainer parent, string filePath, ImageScaling scaling)
{
parent.Image(filePath).ApplyScaling(scaling);
}
[Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")]
public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling)
{
parent.Image(fileStream).ApplyScaling(scaling);
}
internal static void ApplyScaling(this ImageDescriptor descriptor, ImageScaling scaling)
{
if (scaling == ImageScaling.Resize)
descriptor.FitUnproportionally();
else if (scaling == ImageScaling.FitWidth)
descriptor.FitWidth();
else if (scaling == ImageScaling.FitHeight)
descriptor.FitHeight();
else if (scaling == ImageScaling.FitArea)
descriptor.FitArea();
}
#endregion
} }
} }
-8
View File
@@ -8,7 +8,6 @@ namespace QuestPDF.Fluent
{ {
private Action<IDocumentContainer> ContentSource { get; } private Action<IDocumentContainer> ContentSource { get; }
private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default; private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default;
private DocumentSettings Settings { get; set; } = DocumentSettings.Default;
private Document(Action<IDocumentContainer> contentSource) private Document(Action<IDocumentContainer> contentSource)
{ {
@@ -26,16 +25,9 @@ namespace QuestPDF.Fluent
return this; return this;
} }
public Document WithSettings(DocumentSettings settings)
{
Settings = settings ?? Settings;
return this;
}
#region IDocument #region IDocument
public DocumentMetadata GetMetadata() => Metadata; public DocumentMetadata GetMetadata() => Metadata;
public DocumentSettings GetSettings() => Settings;
public void Compose(IDocumentContainer container) => ContentSource(container); public void Compose(IDocumentContainer container) => ContentSource(container);
#endregion #endregion
-48
View File
@@ -6,7 +6,6 @@ using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using QuestPDF.Drawing; using QuestPDF.Drawing;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Helpers namespace QuestPDF.Helpers
{ {
@@ -61,52 +60,5 @@ namespace QuestPDF.Helpers
{ {
return size.Width < 0f || size.Height < 0f; return size.Width < 0f || size.Height < 0f;
} }
internal static int ToQualityValue(this ImageCompressionQuality quality)
{
return quality switch
{
ImageCompressionQuality.Best => 100,
ImageCompressionQuality.VeryHigh => 90,
ImageCompressionQuality.High => 75,
ImageCompressionQuality.Medium => 50,
ImageCompressionQuality.Low => 25,
ImageCompressionQuality.VeryLow => 10,
_ => throw new ArgumentOutOfRangeException(nameof(quality), quality, null)
};
}
internal static SKImage CompressImage(this SKImage image, ImageCompressionQuality compressionQuality)
{
var targetFormat = image.Info.IsOpaque
? SKEncodedImageFormat.Jpeg
: SKEncodedImageFormat.Png;
if (targetFormat == SKEncodedImageFormat.Png)
compressionQuality = ImageCompressionQuality.Best;
var data = image.Encode(targetFormat, compressionQuality.ToQualityValue());
return SKImage.FromEncodedData(data);
}
internal static SKImage ResizeAndCompressImage(this SKImage image, ImageSize targetResolution, ImageCompressionQuality compressionQuality)
{
if (image.Width == targetResolution.Width && image.Height == targetResolution.Height)
return CompressImage(image, compressionQuality);
var imageInfo = new SKImageInfo(targetResolution.Width, targetResolution.Height, image.Info.ColorType, image.Info.AlphaType, image.Info.ColorSpace);
using var resultImage = SKImage.Create(imageInfo);
image.ScalePixels(resultImage.PeekPixels(), SKFilterQuality.Medium);
return CompressImage(resultImage, compressionQuality);
}
internal static SKImage GetImageWithSmallerSize(SKImage one, SKImage second)
{
return one.EncodedData.Size < second.EncodedData.Size
? one
: second;
}
} }
} }
+3 -3
View File
@@ -213,10 +213,10 @@ namespace QuestPDF.Helpers
public static byte[] Image(int width, int height) public static byte[] Image(int width, int height)
{ {
return Image(new ImageSize(width, height)); return Image(new Size(width, height));
} }
public static byte[] Image(ImageSize size) public static byte[] Image(Size size)
{ {
// shuffle corner positions // shuffle corner positions
var targetPositions = new[] var targetPositions = new[]
@@ -239,7 +239,7 @@ namespace QuestPDF.Helpers
.ToArray(); .ToArray();
// create image with white background // create image with white background
var imageInfo = new SKImageInfo(size.Width, size.Height); var imageInfo = new SKImageInfo((int)size.Width, (int)size.Height);
using var surface = SKSurface.Create(imageInfo); using var surface = SKSurface.Create(imageInfo);
using var backgroundPaint = new SKPaint using var backgroundPaint = new SKPaint
@@ -1,6 +1,6 @@
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
public enum ContentDirection internal enum ContentDirection
{ {
LeftToRight, LeftToRight,
RightToLeft RightToLeft
@@ -1,34 +0,0 @@
namespace QuestPDF.Infrastructure
{
public class DocumentSettings
{
public const int DefaultRasterDpi = 72;
/// <summary>
/// Gets or sets a value indicating whether or not make the document PDF/A-2b conformant.
/// If true, include XMP metadata, a document UUID, and sRGB output intent information.
/// This adds length to the document and makes it non-reproducable, but are necessary features for PDF/A-2b conformance.
/// </summary>
public bool PdfA { get; set; } = false;
/// <summary>
/// Encoding quality controls the trade-off between size and quality.
/// When the image is opaque, it will be encoded using the JPEG format with the selected quality setting.
/// When the image contains an alpha channel, it is always encoded using the PNG format and this option is ignored.
/// The default value is "high quality".
/// </summary>
public ImageCompressionQuality ImageCompressionQuality { get; set; } = ImageCompressionQuality.VeryHigh;
/// <summary>
/// The DPI (pixels-per-inch) at which images and features without native PDF support will be rasterized.
/// A larger DPI would create a PDF that reflects the original intent with better fidelity, but it can make for larger PDF files too, which would use more memory while rendering, and it would be slower to be processed or sent online or to printer.
/// When generating images, this parameter also controls the resolution of the generated content.
/// Default value is 144.
/// </summary>
public int ImageRasterDpi { get; set; } = DefaultRasterDpi * 2;
public ContentDirection ContentDirection { get; set; } = ContentDirection.LeftToRight;
public static DocumentSettings Default => new DocumentSettings();
}
}
@@ -4,14 +4,7 @@ namespace QuestPDF.Infrastructure
{ {
public interface IDocument public interface IDocument
{ {
#if NETCOREAPP3_0_OR_GREATER
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public DocumentSettings GetSettings() => DocumentSettings.Default;
#else
DocumentMetadata GetMetadata(); DocumentMetadata GetMetadata();
DocumentSettings GetSettings();
#endif
void Compose(IDocumentContainer container); void Compose(IDocumentContainer container);
} }
} }
+120 -32
View File
@@ -1,56 +1,110 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers;
using SkiaSharp; using SkiaSharp;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
internal record GetImageVersionRequest
{
internal ImageSize Resolution { get; set; }
internal ImageCompressionQuality CompressionQuality { get; set; }
}
public class Image : IDisposable public class Image : IDisposable
{ {
internal SKImage SkImage { get; } private SKImage SkImage { get; }
internal ImageSize Size { 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; } internal bool IsDocumentScoped { get; set; }
internal LinkedList<(GetImageVersionRequest request, SKImage image)> ScaledImageCache { get; } = new(); 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) private Image(SKImage image)
{ {
SkImage = image; SkImage = image;
Size = new ImageSize(image.Width, image.Height); PerformScalingToTargetDpi = image.EncodedData.Size >= Settings.AdjustImageSizeThreshold;
} }
public void Dispose() public void Dispose()
{ {
SkImage.Dispose(); SkImage.Dispose();
ScaledImageCache?.ForEach(x => x.image.Dispose());
foreach (var cacheKey in ScaledImageCache)
cacheKey.image.Dispose();
}
#region Scaling Image
internal SKImage GetVersionOfSize(GetImageVersionRequest request)
{
foreach (var cacheKey in ScaledImageCache)
{
if (cacheKey.request == request)
return cacheKey.image;
}
var result = SkImage.ResizeAndCompressImage(request.Resolution, request.CompressionQuality);
ScaledImageCache.AddLast((request, result));
return result;
} }
#endregion 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 #region public constructors
@@ -84,10 +138,44 @@ namespace QuestPDF.Infrastructure
#endregion #endregion
internal Image DisposeAfterDocumentGeneration() #region configuration API
public Image DisposeAfterDocumentGeneration()
{ {
IsDocumentScoped = true; IsDocumentScoped = true;
return this; 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
} }
} }
@@ -1,35 +0,0 @@
namespace QuestPDF.Infrastructure
{
public enum ImageCompressionQuality
{
/// <summary>
/// JPEG format with compression set to 100 out of 100
/// </summary>
Best,
/// <summary>
/// JPEG format with compression set to 90 out of 100
/// </summary>
VeryHigh,
/// <summary>
/// JPEG format with compression set to 75 out of 100
/// </summary>
High,
/// <summary>
/// JPEG format with compression set to 50 out of 100
/// </summary>
Medium,
/// <summary>
/// JPEG format with compression set to 25 out of 100
/// </summary>
Low,
/// <summary>
/// JPEG format with compression set to 10 out of 100
/// </summary>
VeryLow
}
}
@@ -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
}
}
@@ -1,14 +0,0 @@
namespace QuestPDF.Infrastructure
{
public struct ImageSize
{
public readonly int Width;
public readonly int Height;
public ImageSize(int width, int height)
{
Width = width;
Height = height;
}
}
}
@@ -1,30 +0,0 @@
namespace QuestPDF.Infrastructure
{
public enum LicenseType
{
/// <summary>
/// <para>We love and highly appreciate the .NET Community and therefore the vast majority of users are welcome to use the library under the QuestPDF Community MIT License, without any limitations, even for commercial usage. Please kindly check if you are eligible to use this license.</para>
/// <para>License comparison: http://www.questpdf.com/pricing.html#license</para>
/// <para>License link: https://www.questpdf.com/license-community.html</para>
/// </summary>
Community,
/// <summary>
/// <para>You must purchase the QuestPDF Professional 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, <c>and there are up to 10 developers</c>.</para>
/// <para>If the number of developers is more than 10, you must purchase the QuestPDF Enterprise license.</para>
/// <para>Before making a license purchase, please evaluate the library in a non-production environment.</para>
/// <para>License comparison: http://www.questpdf.com/pricing.html#license</para>
/// <para>License link: https://www.questpdf.com/license-commercial.html</para>
/// </summary>
Professional,
/// <summary>
/// <para>You must purchase the QuestPDF Professional 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.</para>
/// <para>If there are less than 10 developers, you are eligible to use the cheaper QuestPDF Professional License.</para>
/// <para>Before making a license purchase, please evaluate the library in a non-production environment.</para>
/// <para>License comparison: http://www.questpdf.com/pricing.html#license</para>
/// <para>License link: https://www.questpdf.com/license-commercial.html</para>
/// </summary>
Enterprise
}
}
@@ -25,17 +25,16 @@ namespace QuestPDF.Infrastructure
internal static class TextStyleManager internal static class TextStyleManager
{ {
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new(); 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 origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new();
private static readonly ConcurrentDictionary<TextStyle, TextStyle> TextStyleApplyGlobalCache = new();
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new(); private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new();
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value) public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
{ {
var cacheKey = (origin, property, 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) if (overrideValue && value == null)
return origin; 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."); 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); 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) private static TextStyle ApplyFontFallback(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 var targetFallbackStyle = style
?.Fallback ?.Fallback
?.ApplyStyleProperties(style, overrideStyle: overrideStyle, overrideFontFamily: false, applyFallback: false) ?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
?.UpdateFontFallback(overrideStyle); ?.ApplyFontFallback();
return style.MutateStyle(TextStyleProperty.Fallback, targetFallbackStyle, overrideValue: true); return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
} }
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent) internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
{ {
var cacheKey = (style, parent); var cacheKey = (style, parent);
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; 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.Color, parent.Color, overrideStyle);
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, 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.Size, parent.Size, overrideStyle);
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle); result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
result = MutateStyle(result, TextStyleProperty.LetterSpacing, parent.LetterSpacing, overrideStyle); result = MutateStyle(result, TextStyleProperty.LetterSpacing, parent.LetterSpacing, overrideStyle);
@@ -20,11 +20,6 @@ namespace QuestPDF.Previewer
{ {
return DocumentMetadata.Default; return DocumentMetadata.Default;
} }
public DocumentSettings GetSettings()
{
return DocumentSettings.Default;
}
public void Compose(IDocumentContainer document) public void Compose(IDocumentContainer document)
{ {
@@ -18,8 +18,8 @@ namespace QuestPDF.Previewer
public event Action? OnPreviewerStopped; public event Action? OnPreviewerStopped;
private const int RequiredPreviewerVersionMajor = 2023; private const int RequiredPreviewerVersionMajor = 2022;
private const int RequiredPreviewerVersionMinor = 5; private const int RequiredPreviewerVersionMinor = 12;
public PreviewerService(int port) public PreviewerService(int port)
{ {
+27 -40
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>2023.5.0</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> <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> <PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion> <LangVersion>9</LangVersion>
@@ -12,17 +12,15 @@
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl> <PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl> <PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile> <PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<PackageLicenseFile>PackageLicense.md</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl> <RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright> <Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags> <PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks> <TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -39,43 +37,32 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" /> <EmbeddedResource Include="Resources\ImagePlaceholder.png" />
<None Include="Resources\PackageLicense.md" Pack="true" PackagePath="\" /> <EmbeddedResource Include="Resources\Logo.png" />
<None Remove="ImagePlaceholder.png" />
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
<EmbeddedResource Include="Resources\Logo.png" />
<None Remove="ImagePlaceholder.png" /> <None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
<EmbeddedResource Include="Resources\ImagePlaceholder.png" /> <None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
<None Remove="Resources\DefaultFont\Lato-Black.ttf" /> <None Remove="Resources\DefaultFont\Lato-Black.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" />
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" />
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-BlackItalic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-BlackItalic.ttf" /> <None Remove="Resources\DefaultFont\Lato-Bold.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Bold.ttf" />
<None Remove="Resources\DefaultFont\Lato-Bold.ttf" /> <None Remove="Resources\DefaultFont\Lato-BoldItalic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Bold.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-BoldItalic.ttf" />
<None Remove="Resources\DefaultFont\Lato-Italic.ttf" />
<None Remove="Resources\DefaultFont\Lato-BoldItalic.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-Italic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-BoldItalic.ttf" /> <None Remove="Resources\DefaultFont\Lato-Light.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Light.ttf" />
<None Remove="Resources\DefaultFont\Lato-Italic.ttf" /> <None Remove="Resources\DefaultFont\Lato-LightItalic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Italic.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-LightItalic.ttf" />
<None Remove="Resources\DefaultFont\Lato-Regular.ttf" />
<None Remove="Resources\DefaultFont\Lato-Light.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-Regular.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Light.ttf" /> <None Remove="Resources\DefaultFont\Lato-Thin.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Thin.ttf" />
<None Remove="Resources\DefaultFont\Lato-LightItalic.ttf" /> <None Remove="Resources\DefaultFont\Lato-ThinItalic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-LightItalic.ttf" /> <EmbeddedResource Include="Resources\DefaultFont\Lato-ThinItalic.ttf" />
<None Remove="Resources\DefaultFont\Lato-Regular.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Regular.ttf" />
<None Remove="Resources\DefaultFont\Lato-Thin.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-Thin.ttf" />
<None Remove="Resources\DefaultFont\Lato-ThinItalic.ttf" />
<EmbeddedResource Include="Resources\DefaultFont\Lato-ThinItalic.ttf" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,59 +0,0 @@
# QuestPDF License
## What license do you need?
Suppose 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. In that case, you must purchase the QuestPDF Professional or Enterprise License, depending on the number of software developers.
We love and highly appreciate the .NET Community. Therefore, most users are welcome to use the library entirely 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.
Please find all details regarding the QuestPDF license on the [official webpage](https://www.questpdf.com/pricing.html).
Being a healthy and growing community is the primary goal that motivates us to pursue professionalism. Thank you!
## 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.
## 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 right to distribute the Software (without royalty) as part of packaged commercial products.
### 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.
+22 -8
View File
@@ -1,8 +1,22 @@
Version 2023.5.0 Feature: implemented LetterSpacing property for the Text element
- Simplified development loop by introducing cross-platform methods: GeneratePdfAndShow() and GenerateXpsAndShow() Improvement: the Text element API accepts now only string values, objects are not automatically converted anymore
- New shared image API: the ability to define a single image resource that is used in multiple places in the document without increasing its size Fix: the Alignment element incorrectly limits size of its child when only one axis is set (horizontal or vertical)
- New DocumentSettings API: target image raster DPI - now, the library automatically resizes all images to achieve desired DPI (dots-per-inch) resolution. It allows for minimizing output file size Maintenance: Updated SkiaSharp dependency to 2.88.3
- New DocumentSettings API: target image compression quality - the ability to specify the balance between size and quality for images in the document. It allows for minimizing output file size
- Refactoring: moved the PdfA setting from the DocumentMetadata class to the DocumentSettings class In version 2022.12.1:
- Improved Image API by providing additional FluentA API methods Fixed: loading fonts from embedded resource via the FontManager.RegisterFontFromEmbeddedResource method
- Improvement: the GenerateImage element now provides the expected image resolution, abstracting away the physical area size and target image DPI 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:
Performance improvements in various areas
Text rendering stability improvements
Fixed: the Settings.CheckIfAllTextGlyphsAreAvailable setting does not work correctly
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
This release was possible thanks to enormous help of AntonyCorbett, thank you!
+7 -9
View File
@@ -1,15 +1,7 @@
using QuestPDF.Infrastructure; namespace QuestPDF
namespace QuestPDF
{ {
public static class Settings public static class Settings
{ {
/// <summary>
/// <para>Please kindly select license type that applies to your usage of the QuestPDF library.</para>
/// <para>For more details, please check the QuestPDF License and Pricing page: https://www.questpdf.com/pricing.html</para>
/// </summary>
public static LicenseType? License { get; set; }
/// <summary> /// <summary>
/// This value represents the maximum number of pages that the library produces. /// This value represents the maximum number of pages 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.
@@ -44,5 +36,11 @@ namespace QuestPDF
/// </summary> /// </summary>
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks> /// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached; public static bool 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;
} }
} }