Compare commits
224 Commits
rtl
..
shared-images
| Author | SHA1 | Date | |
|---|---|---|---|
| e53b586943 | |||
| 9637dff1dc | |||
| 1c9bccbeb2 | |||
| 176566801b | |||
| 22a696fab9 | |||
| 9809faccb6 | |||
| 6ad465b9fa | |||
| e6c7befac4 | |||
| be3e4b59ec | |||
| e751bfd2ff | |||
| b05f76f848 | |||
| b5d7dbb346 | |||
| 0ab73f4f2b | |||
| 458b379208 | |||
| c3c0d7631c | |||
| a3ea8bceb6 | |||
| 455d187897 | |||
| 65e5d36ded | |||
| ad5e8ab27d | |||
| a17197addc | |||
| a1e7cbbeb3 | |||
| c00ac5e9c7 | |||
| 0e0a8d5d69 | |||
| bc341da6dd | |||
| 6096771b51 | |||
| 4f097d0fed | |||
| 24beea80c7 | |||
| e1e5778ff0 | |||
| 8679391609 | |||
| c1b60287ca | |||
| 011e1b7ce9 | |||
| 5fd72cbb4b | |||
| 7284998657 | |||
| 30bd7420e2 | |||
| bd48688670 | |||
| 68e3efde59 | |||
| 805fd55d94 | |||
| b3a3deb354 | |||
| 68e4a10e81 | |||
| e15a21ab0e | |||
| f851221718 | |||
| 91c93e68b5 | |||
| 3dcc35be97 | |||
| 3f32c6e2c5 | |||
| f1f609ccc1 | |||
| 5c07485cc1 | |||
| 42684ec180 | |||
| 08f9f706ce | |||
| 1f199423b0 | |||
| d8e640fcf3 | |||
| fb02d97a10 | |||
| d5f8a884d6 | |||
| 5de5be6429 | |||
| 96d57b241e | |||
| 920e61fc5e | |||
| 1cc606261c | |||
| 0a6c021fe9 | |||
| b65ee32daf | |||
| e496917d84 | |||
| a5004b986c | |||
| 84d7fcf41f | |||
| 641cc6dee2 | |||
| 9dbed14559 | |||
| 6323a8dba2 | |||
| 356dbb020d | |||
| bad6be1a28 | |||
| 27c7383437 | |||
| ecac19ee46 | |||
| fd37070d3b | |||
| 3b433f3efb | |||
| b3d6a39a98 | |||
| 8d1a449e69 | |||
| 7f0be69ffc | |||
| 8553131b99 | |||
| 1bf9198a51 | |||
| 563b782f6a | |||
| edf8db878a | |||
| 5fd6488068 | |||
| d6a7c3703d | |||
| 5844d3bdba | |||
| 011d44304a | |||
| a087be8afe | |||
| 483cbb6d46 | |||
| ec76cbd696 | |||
| a83d8c43fe | |||
| 5f25e37f7f | |||
| cafd27a60d | |||
| 5082bbbdf2 | |||
| d2ba21821f | |||
| 9bef7d64d5 | |||
| ca4352addd | |||
| 2906315fa7 | |||
| 6f036388b9 | |||
| 9cd4fd740e | |||
| c97acada76 | |||
| 19ff9062ad | |||
| e4f8243c9b | |||
| 1060e4a44a | |||
| 983edd5f76 | |||
| 5315165cf5 | |||
| ee3d87010e | |||
| 3ac91c4971 | |||
| 8e450934ab | |||
| bf193eec23 | |||
| c9104540aa | |||
| af60d84abe | |||
| 4345ce857f | |||
| 823572bf25 | |||
| e6bd63b3ec | |||
| bbf3d71e7e | |||
| d56117647b | |||
| 117913693c | |||
| 2ba716a5ba | |||
| be2108f6fe | |||
| c08bb963a6 | |||
| 8ed8897fd1 | |||
| 2a0e91af68 | |||
| f647b79a0a | |||
| b1286bb536 | |||
| 43d1a55df0 | |||
| d1cd7b06db | |||
| 7bce01d6e6 | |||
| e3ff2960e1 | |||
| 5d9e6f327b | |||
| b895931eb9 | |||
| 86a35b7cd3 | |||
| 65990ebf59 | |||
| c348106b3f | |||
| 9d86e9efd3 | |||
| 9770575f9a | |||
| e6e6febf8b | |||
| 5cbacc7ea1 | |||
| 8214a8429d | |||
| 389b8ce304 | |||
| e1f7cff4aa | |||
| 6801425082 | |||
| ec31c9b063 | |||
| 8a57dceb9d | |||
| 6c8867e1b3 | |||
| 20d86cbfde | |||
| 16a164e5b8 | |||
| 261f087b46 | |||
| c876350b05 | |||
| 58f3932241 | |||
| 350abc2838 | |||
| 5d4c9a5dd2 | |||
| b31d097b61 | |||
| 9f6c091706 | |||
| af2229eabe | |||
| b86946b396 | |||
| e1ae97d3ca | |||
| a7ac747776 | |||
| fab0f5bfe1 | |||
| f5f90bd235 | |||
| 603dc13d0f | |||
| e277c6a8dc | |||
| 5ff9d03f20 | |||
| 9ce9ca85be | |||
| 553c8ab719 | |||
| e768e9b06d | |||
| fd913a777c | |||
| ed9e6daec5 | |||
| ee6249a658 | |||
| b307304f46 | |||
| f028f82e11 | |||
| 719a3385f6 | |||
| 2adff11400 | |||
| 34fed6d547 | |||
| db2df75624 | |||
| 6b535752df | |||
| 556f87ff25 | |||
| fbebbd85eb | |||
| 4650c2a4ea | |||
| 5390fc3f1b | |||
| 1ba01a1cf5 | |||
| d4448437ac | |||
| bc853f48fc | |||
| 0468dd5f02 | |||
| 3ba09ea826 | |||
| 92abd32aae | |||
| e17867d1f3 | |||
| bd71f30c78 | |||
| 6a78a4ebdb | |||
| 9fc721d66b | |||
| 7d62dead86 | |||
| 52ad0f5c24 | |||
| 53107073a6 | |||
| c71bb3ea59 | |||
| 6cced3c143 | |||
| 425ea59cfe | |||
| 4d326496c6 | |||
| b7b6488d16 | |||
| f0ba5fc32d | |||
| 04da32e0e7 | |||
| 5bdb338996 | |||
| 06b30f2ad6 | |||
| d39f68a974 | |||
| db9d8b5fcf | |||
| a31a588b24 | |||
| cb2a3c6363 | |||
| e775f62fab | |||
| 056e3f244c | |||
| cd3f7f5a25 | |||
| a5805f6e43 | |||
| abe7b2f14c | |||
| 3e485a572c | |||
| fd0bb5b089 | |||
| cc51b62b73 | |||
| 8e16b5ad4f | |||
| 71e2743409 | |||
| ba9d3aa04b | |||
| 2a0b133185 | |||
| 6058dac524 | |||
| 25b8f62245 | |||
| 2c31f3d5b5 | |||
| f7cd89ccf6 | |||
| 6914a9063a | |||
| b87e8d5d4e | |||
| d740d67a64 | |||
| a14e9ca61f | |||
| 1047854f77 | |||
| c5fbf7263c | |||
| fae2205ff8 | |||
| 1cbe4b1dc8 |
@@ -0,0 +1,54 @@
|
||||
name: Build And Create Nuget Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '3 0 * * 0'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ${{ matrix.environment }}
|
||||
strategy:
|
||||
matrix:
|
||||
environment:
|
||||
- macos-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
UNIT_TEST_PROJECT: QuestPDF.UnitTests/QuestPDF.UnitTests.csproj
|
||||
|
||||
steps:
|
||||
- name: 📝 Fetch Sources 📝
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: ⚙ Setup .NET 6.0 SDK ⚙
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: 🔄 Restore Nuget Packages 🔄
|
||||
shell: bash
|
||||
run: dotnet restore
|
||||
working-directory: ./Source
|
||||
|
||||
- name: 🛠 Build Solution 🛠
|
||||
shell: bash
|
||||
run: dotnet build -c Release --no-restore
|
||||
working-directory: ./Source
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ matrix.environment == 'windows-latest' }}
|
||||
with:
|
||||
name: Build Package
|
||||
path: |
|
||||
**/*.nupkg
|
||||
**/*.snupkg
|
||||
!.nuget
|
||||
@@ -1,47 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextShapingTests
|
||||
{
|
||||
[Test]
|
||||
public void ShapeText()
|
||||
{
|
||||
using var textPaint = new SKPaint
|
||||
{
|
||||
Color = SKColors.Black,
|
||||
Typeface = SKTypeface.CreateDefault(),
|
||||
IsAntialias = true,
|
||||
TextSize = 20
|
||||
};
|
||||
|
||||
using var backgroundPaint = new SKPaint
|
||||
{
|
||||
Color = SKColors.LightGray
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 250)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
//var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio ipsum, aliquam a neque a, lacinia vehicula lectus.";
|
||||
//var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور";
|
||||
|
||||
var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
var arabic = "ينا الألم. في بعض (5000) الأحيان ونظراً للالتزامات التي يفرضها علينا";
|
||||
|
||||
container
|
||||
.Padding(25)
|
||||
.Text(arabic)
|
||||
.FontSize(25);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Microsoft.VisualStudio.TestPlatform.TestHost;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
{
|
||||
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 256)]
|
||||
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
|
||||
public class PerformanceTests
|
||||
{
|
||||
private PageContext PageContext { get; set; }
|
||||
private DocumentMetadata Metadata { get; set; }
|
||||
private Container Content { get; set; }
|
||||
|
||||
[Test]
|
||||
public void Run()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var configuration = ManualConfig
|
||||
.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
|
||||
|
||||
BenchmarkRunner.Run<PerformanceTests>(configuration);
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void GenerateReportData()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var model = DataSource.GetReport();
|
||||
var report = new StandardReport(model);
|
||||
Metadata = report.GetMetadata();
|
||||
|
||||
var documentContainer = new DocumentContainer();
|
||||
report.Compose(documentContainer);
|
||||
Content = documentContainer.Compose();
|
||||
|
||||
PageContext = new PageContext();
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
Content.VisitChildren(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
});
|
||||
|
||||
sw.Stop();
|
||||
Console.WriteLine($"Creating cache took: {sw.ElapsedMilliseconds}");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void GenerationTest()
|
||||
{
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ImageTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
{
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
{
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
})
|
||||
.DrawElement(new Size(300, 200))
|
||||
.ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Fluent_RecognizesImageProportions()
|
||||
{
|
||||
var image = GenerateImage(600, 200).Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
|
||||
TestPlan
|
||||
.For(x =>
|
||||
{
|
||||
var container = new Container();
|
||||
container.Image(image);
|
||||
return container;
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(300, 100));;
|
||||
}
|
||||
|
||||
SKImage GenerateImage(int width, int height)
|
||||
{
|
||||
var imageInfo = new SKImageInfo(width, height);
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
return surface.Snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public class DocumentMetadata
|
||||
{
|
||||
public int ImageQuality { get; set; } = 101;
|
||||
public int RasterDpi { get; set; } = 72;
|
||||
public bool PdfA { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Author { get; set; }
|
||||
public string? Subject { get; set; }
|
||||
public string? Keywords { get; set; }
|
||||
public string? Creator { get; set; }
|
||||
public string? Producer { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; } = DateTime.Now;
|
||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// If the number of generated pages exceeds this threshold
|
||||
/// (likely due to infinite layout), the exception is thrown.
|
||||
/// </summary>
|
||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
|
||||
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class InitializationException : Exception
|
||||
{
|
||||
internal InitializationException(string documentType, Exception innerException) : base(CreateMessage(documentType), innerException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static string CreateMessage(string documentType)
|
||||
{
|
||||
return $"Cannot create the {documentType} document using the SkiaSharp library. " +
|
||||
$"This exception usually means that, on your operating system where you run the application, SkiaSharp requires installing additional dependencies. " +
|
||||
$"Such dependencies are available as additional nuget packages, for example SkiaSharp.NativeAssets.Linux. " +
|
||||
$"Please refer to the SkiaSharp documentation for more details.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class DirectionProxy : ContainerElement
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal struct TextMeasurement
|
||||
{
|
||||
public int LineIndex { get; set; }
|
||||
public float FragmentWidth { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
using System;
|
||||
using HarfBuzzSharp;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
using Buffer = HarfBuzzSharp.Buffer;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class TextShaper
|
||||
{
|
||||
public const int FontShapingScale = 512;
|
||||
|
||||
private Font Font { get; }
|
||||
private SKPaint Paint { get; }
|
||||
|
||||
public TextShaper(TextStyle style)
|
||||
{
|
||||
Font = style.ToShaperFont();
|
||||
Paint = style.ToPaint();
|
||||
}
|
||||
|
||||
public TextShapingResult Shape(string text)
|
||||
{
|
||||
var buffer = new Buffer();
|
||||
|
||||
PopulateBufferWithText(buffer, text);
|
||||
buffer.GuessSegmentProperties();
|
||||
//buffer.Direction = Direction.RightToLeft;
|
||||
|
||||
Font.Shape(buffer);
|
||||
|
||||
var length = buffer.Length;
|
||||
var glyphInfos = buffer.GlyphInfos;
|
||||
var glyphPositions = buffer.GlyphPositions;
|
||||
|
||||
var scaleY = Paint.TextSize / FontShapingScale;
|
||||
var scaleX = scaleY * Paint.TextScaleX;
|
||||
|
||||
var xOffset = 0f;
|
||||
var yOffset = 0f;
|
||||
|
||||
var glyphs = new ShapedGlyph[length];
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
glyphs[i] = new ShapedGlyph
|
||||
{
|
||||
Codepoint = (ushort)glyphInfos[i].Codepoint,
|
||||
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
|
||||
Width = glyphPositions[i].XAdvance * scaleX
|
||||
};
|
||||
|
||||
xOffset += glyphPositions[i].XAdvance * scaleX;
|
||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||
}
|
||||
|
||||
return new TextShapingResult(glyphs);
|
||||
}
|
||||
|
||||
void PopulateBufferWithText(Buffer buffer, string text)
|
||||
{
|
||||
var encoding = Paint.TextEncoding;
|
||||
|
||||
if (encoding == SKTextEncoding.Utf8)
|
||||
buffer.AddUtf8(text);
|
||||
|
||||
else if (encoding == SKTextEncoding.Utf16)
|
||||
buffer.AddUtf16(text);
|
||||
|
||||
else if (encoding == SKTextEncoding.Utf32)
|
||||
buffer.AddUtf32(text);
|
||||
|
||||
else
|
||||
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ShapedGlyph
|
||||
{
|
||||
public ushort Codepoint;
|
||||
public SKPoint Position;
|
||||
public float Width;
|
||||
}
|
||||
|
||||
internal struct DrawTextCommand
|
||||
{
|
||||
public SKTextBlob SkTextBlob;
|
||||
public float TextOffsetX;
|
||||
}
|
||||
|
||||
internal class TextShapingResult
|
||||
{
|
||||
public ShapedGlyph[] Glyphs { get; }
|
||||
|
||||
public TextShapingResult(ShapedGlyph[] glyphs)
|
||||
{
|
||||
Glyphs = glyphs;
|
||||
}
|
||||
|
||||
public int BreakText(int startIndex, float maxWidth)
|
||||
{
|
||||
var index = startIndex;
|
||||
maxWidth += Glyphs[startIndex].Position.X;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
{
|
||||
var glyph = Glyphs[index];
|
||||
|
||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index - 1;
|
||||
}
|
||||
|
||||
public float MeasureWidth(int startIndex, int endIndex)
|
||||
{
|
||||
if (Glyphs.Length == 0)
|
||||
return 0;
|
||||
|
||||
var start = Glyphs[startIndex];
|
||||
var end = Glyphs[endIndex];
|
||||
|
||||
return end.Position.X - start.Position.X + end.Width;
|
||||
}
|
||||
|
||||
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
|
||||
{
|
||||
if (Glyphs.Length == 0)
|
||||
return null;
|
||||
|
||||
using var skTextBlobBuilder = new SKTextBlobBuilder();
|
||||
|
||||
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(textStyle.FoFont(), endIndex - startIndex + 1);
|
||||
var glyphSpan = positionedRunBuffer.GetGlyphSpan();
|
||||
var positionSpan = positionedRunBuffer.GetPositionSpan();
|
||||
|
||||
for (var sourceIndex = startIndex; sourceIndex <= endIndex; sourceIndex++)
|
||||
{
|
||||
var runIndex = sourceIndex - startIndex;
|
||||
|
||||
glyphSpan[runIndex] = Glyphs[sourceIndex].Codepoint;
|
||||
positionSpan[runIndex] = Glyphs[sourceIndex].Position;
|
||||
}
|
||||
|
||||
return new DrawTextCommand
|
||||
{
|
||||
SkTextBlob = skTextBlobBuilder.Build(),
|
||||
TextOffsetX = -Glyphs[startIndex].Position.X
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ContentDirection : ContainerElement
|
||||
{
|
||||
public ContentDirectionType Direction { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Image : Element, ICacheable
|
||||
{
|
||||
public SKImage? InternalImage { get; set; }
|
||||
|
||||
~Image()
|
||||
{
|
||||
InternalImage?.Dispose();
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (InternalImage == null)
|
||||
return;
|
||||
|
||||
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class MinimalBox : ContainerElement
|
||||
{
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = base.Measure(availableSpace);
|
||||
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
base.Draw(targetSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ContentDirectionExtensions
|
||||
{
|
||||
internal static IContainer ContentDirection(this IContainer element, ContentDirectionType contentDirectionType)
|
||||
{
|
||||
var contentDirection = new ContentDirection
|
||||
{
|
||||
Direction = contentDirectionType
|
||||
};
|
||||
|
||||
return element.Element(contentDirection);
|
||||
}
|
||||
|
||||
public static IContainer ContentDirectionLeftToRight(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(ContentDirectionType.LeftToRight);
|
||||
}
|
||||
|
||||
public static IContainer ContentDirectionRightToLeft(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(ContentDirectionType.RightToLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class TextSpanDescriptorExtensions
|
||||
{
|
||||
public static T Style<T>(this T descriptor, TextStyle style) where T : TextSpanDescriptor
|
||||
{
|
||||
if (style == null)
|
||||
return descriptor;
|
||||
|
||||
descriptor.TextStyle.OverrideStyle(style);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Color = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.BackgroundColor = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontFamily = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Size = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.LineHeight = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.IsItalic = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasStrikethrough = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasUnderline = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.WrapAnywhere = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontWeight = weight;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Thin);
|
||||
}
|
||||
|
||||
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraLight);
|
||||
}
|
||||
|
||||
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Light);
|
||||
}
|
||||
|
||||
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Normal);
|
||||
}
|
||||
|
||||
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Medium);
|
||||
}
|
||||
|
||||
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.SemiBold);
|
||||
}
|
||||
|
||||
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Bold);
|
||||
}
|
||||
|
||||
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBold);
|
||||
}
|
||||
|
||||
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Black);
|
||||
}
|
||||
|
||||
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBlack);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Position
|
||||
public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Normal);
|
||||
}
|
||||
|
||||
public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Subscript);
|
||||
}
|
||||
|
||||
public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Position(FontPosition.Superscript);
|
||||
}
|
||||
|
||||
private static T Position<T>(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontPosition = fontPosition;
|
||||
return descriptor;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class TextStyle
|
||||
{
|
||||
internal bool HasGlobalStyleApplied { get; private set; }
|
||||
|
||||
internal string? Color { get; set; }
|
||||
internal string? BackgroundColor { get; set; }
|
||||
internal string? FontFamily { get; set; }
|
||||
internal float? Size { get; set; }
|
||||
internal float? LineHeight { get; set; }
|
||||
internal FontWeight? FontWeight { get; set; }
|
||||
internal FontPosition? FontPosition { get; set; }
|
||||
internal bool? IsItalic { get; set; }
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
internal bool? WrapAnywhere { get; set; }
|
||||
|
||||
internal object PaintKey { get; private set; }
|
||||
internal object FontMetricsKey { get; private set; }
|
||||
|
||||
internal static TextStyle LibraryDefault => new TextStyle
|
||||
{
|
||||
Color = Colors.Black,
|
||||
BackgroundColor = Colors.Transparent,
|
||||
FontFamily = Fonts.Calibri,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
FontPosition = Infrastructure.FontPosition.Normal,
|
||||
IsItalic = false,
|
||||
HasStrikethrough = false,
|
||||
HasUnderline = false,
|
||||
WrapAnywhere = false
|
||||
};
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
internal void ApplyGlobalStyle(TextStyle globalStyle)
|
||||
{
|
||||
if (HasGlobalStyleApplied)
|
||||
return;
|
||||
|
||||
HasGlobalStyleApplied = true;
|
||||
|
||||
ApplyParentStyle(globalStyle);
|
||||
PaintKey ??= (FontFamily, Size, FontWeight, FontPosition, IsItalic, Color);
|
||||
FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
|
||||
}
|
||||
|
||||
internal void ApplyParentStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color ??= parentStyle.Color;
|
||||
BackgroundColor ??= parentStyle.BackgroundColor;
|
||||
FontFamily ??= parentStyle.FontFamily;
|
||||
Size ??= parentStyle.Size;
|
||||
LineHeight ??= parentStyle.LineHeight;
|
||||
FontWeight ??= parentStyle.FontWeight;
|
||||
FontPosition ??= parentStyle.FontPosition;
|
||||
IsItalic ??= parentStyle.IsItalic;
|
||||
HasStrikethrough ??= parentStyle.HasStrikethrough;
|
||||
HasUnderline ??= parentStyle.HasUnderline;
|
||||
WrapAnywhere ??= parentStyle.WrapAnywhere;
|
||||
}
|
||||
|
||||
internal void OverrideStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color = parentStyle.Color ?? Color;
|
||||
BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor;
|
||||
FontFamily = parentStyle.FontFamily ?? FontFamily;
|
||||
Size = parentStyle.Size ?? Size;
|
||||
LineHeight = parentStyle.LineHeight ?? LineHeight;
|
||||
FontWeight = parentStyle.FontWeight ?? FontWeight;
|
||||
FontPosition = parentStyle.FontPosition ?? FontPosition;
|
||||
IsItalic = parentStyle.IsItalic ?? IsItalic;
|
||||
HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
|
||||
HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
|
||||
WrapAnywhere = parentStyle.WrapAnywhere ?? WrapAnywhere;
|
||||
}
|
||||
|
||||
internal TextStyle Clone()
|
||||
{
|
||||
var clone = (TextStyle)MemberwiseClone();
|
||||
clone.HasGlobalStyleApplied = false;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,4 +0,0 @@
|
||||
Implemented support for the text shaping algorithm that fixes rendering more advanced languages such as Arabic.
|
||||
Improved exception message when SkiaSharp throws the TypeInitializationException (when additional dependencies are needed).
|
||||
Fixed: a rare case when the Row.AutoItem does not calculate properly the width of its content.
|
||||
Fixed: the QuestPDF Previewer does not work with content-rich documents.
|
||||
@@ -0,0 +1,92 @@
|
||||
<a href="https://www.questpdf.com/" target="_blank">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="400">
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.questpdf.com)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
|
||||
|
||||
<br />
|
||||
|
||||
### QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API.
|
||||
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>👨💻</td>
|
||||
<td>Design PDF documents using C# and employ a code-only approach. Utilize your version control system to its fullest potential.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🧱</td>
|
||||
<td>Compose PDF document with a range of powerful and predictable structural elements, such as text, image, border, table, and many more.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>⚙️</td>
|
||||
<td>Utilize a comprehensive layout engine, specifically designed for PDF document generation and paging support.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>📖</td>
|
||||
<td>Write code using concise and easy-to-understand C# Fluent API. Utilize IntelliSense to quickly discover available options.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🔗</td>
|
||||
<td>Don't be limited to any proprietary scripting language or format. Follow your experience and leverage all modern C# features.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>⌛</td>
|
||||
<td>Save time thanks to a hot-reload capability, allowing real-time PDF document preview without code recompilation.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<br />
|
||||
|
||||
## Please help by giving a star
|
||||
|
||||
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
|
||||
|
||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/9263853/224511996-9dd27a99-bef8-4d5e-bc14-9f6cfe0f5191.png" width="800" />
|
||||
|
||||
|
||||
## Please share with the community
|
||||
|
||||
As an open-source project without funding, I cannot afford advertising QuestPDF in a typical way. Instead, the library relies on community interactions. Please consider sharing a post about QuestPDF and the value it provides. It really does help!
|
||||
|
||||
[](https://reddit.com/submit?url=https://github.com/QuestPDF/QuestPDF&title=Check%20out%20QuestPDF%20%F0%9F%8E%8A%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation%20%F0%9F%9A%80)
|
||||
[](https://twitter.com/share?url=https://github.com/QuestPDF/QuestPDF&text=Check%20out%20QuestPDF%20%F0%9F%8E%8A%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation%20%F0%9F%9A%80%20%23dotnet%20%23csharp%20%23questpdf)
|
||||
[](https://news.ycombinator.com/submitlink?u=https://github.com/QuestPDF/QuestPDF&t=QuestPDF%20-%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation)
|
||||
[](https://www.facebook.com/sharer/sharer.php?u=https://github.com/QuestPDF/QuestPDF)
|
||||
|
||||
<br />
|
||||
|
||||
## Let's get started
|
||||
|
||||
Begin exploring the QuestPDF library today. You are 250 lines of C# code away from creating a fully functional PDF invoice implementation.
|
||||
|
||||
Read the Getting Started tutorial to familiarize yourself with general library architecture, important layout structures as well as to better understand helpful patterns and practices. Easily start designing your PDF documents, reports, invoices and even more.
|
||||
|
||||
[](https://www.questpdf.com/getting-started.html)
|
||||
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/invoice-small.png?raw=true" width="400px">
|
||||
|
||||
<br />
|
||||
|
||||
## QuestPDF on JetBrains OSS Power-Ups
|
||||
|
||||
QuestPDF was presented on one of the episodes of OSS Power-Ups hosted by JetBrains. Huge thanks for Matthias Koch and entire JetBrains team for giving me a chance to show QuestPDF. You are the best!
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=-iYvZvpLX0g">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/jetbrains-oss-powerups-youtube.png?raw=true" width="600px">
|
||||
</a>
|
||||
|
||||
|
||||
[](https://www.youtube.com/watch?v=-iYvZvpLX0g)
|
||||
@@ -1 +0,0 @@
|
||||
<svg height="180" viewBox="0 0 180 180" width="180" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="32.64" x2="82.77" y1="61.16" y2="85.54"><stop offset=".21" stop-color="#fe2857"/><stop offset="1" stop-color="#293896"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="17.38" x2="82.95" y1="69.86" y2="21.23"><stop offset="0" stop-color="#fe2857"/><stop offset=".01" stop-color="#fe2857"/><stop offset=".86" stop-color="#ff318c"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="74.17" x2="160.27" y1="21.58" y2="99.76"><stop offset=".02" stop-color="#ff318c"/><stop offset=".21" stop-color="#fe2857"/><stop offset=".86" stop-color="#fdb60d"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="155.46" x2="55.07" y1="89.8" y2="158.9"><stop offset=".01" stop-color="#fdb60d"/><stop offset=".86" stop-color="#fcf84a"/></linearGradient><path d="m81.56 83.71-41.35-35a15 15 0 1 0 -14.47 25.7h.15l.39.12 52.16 15.89a3.53 3.53 0 0 0 1.18.21 3.73 3.73 0 0 0 1.93-6.91z" fill="url(#a)"/><path d="m89.85 25.93a10.89 10.89 0 0 0 -16.85-9.18l-50.5 30.66a15 15 0 1 0 17.9 24l45.27-36.89.36-.3a10.93 10.93 0 0 0 3.82-8.29z" fill="url(#b)"/><path d="m163.29 92-76.62-73.79a10.91 10.91 0 1 0 -14.81 16l.14.12 81.4 68.58a7.36 7.36 0 0 0 12.09-5.65 7.39 7.39 0 0 0 -2.2-5.26z" fill="url(#c)"/><path d="m165.5 97.29a7.35 7.35 0 0 0 -11.67-6l-92.71 45.3a15 15 0 1 0 15.48 25.59l85.73-58.84a7.35 7.35 0 0 0 3.17-6.05z" fill="url(#d)"/><path d="m60 60h60v60h-60z"/><g fill="#fff"><path d="m66.53 108.75h22.5v3.75h-22.5z"/><path d="m65.59 75.47 1.67-1.58a1.88 1.88 0 0 0 1.47.87c.64 0 1.06-.45 1.06-1.32v-5.92h2.58v5.94a3.44 3.44 0 0 1 -.92 2.63 3.52 3.52 0 0 1 -2.57 1 3.84 3.84 0 0 1 -3.29-1.62z"/><path d="m73.53 67.52h7.53v2.19h-5v1.43h4.49v2h-4.45v1.49h5v2.2h-7.6z"/><path d="m84.73 69.79h-2.8v-2.27h8.21v2.27h-2.81v7.09h-2.6z"/><path d="m66.63 80.58h4.42a3.47 3.47 0 0 1 2.55.83 2.09 2.09 0 0 1 .61 1.52 2.18 2.18 0 0 1 -1.45 2.09 2.27 2.27 0 0 1 1.86 2.29c0 1.69-1.31 2.69-3.55 2.69h-4.44zm5 2.89c0-.52-.42-.8-1.18-.8h-1.29v1.64h1.25c.78 0 1.24-.27 1.24-.81zm-.9 2.66h-1.57v1.73h1.62c.8 0 1.24-.31 1.24-.86-.02-.53-.4-.87-1.27-.87z"/><path d="m75.45 80.58h4.15a4.14 4.14 0 0 1 3.05 1 2.92 2.92 0 0 1 .83 2.18 3 3 0 0 1 -1.93 2.89l2.24 3.35h-3l-1.89-2.84h-.87v2.84h-2.6zm4 4.5c.87 0 1.4-.43 1.4-1.12 0-.75-.55-1.13-1.41-1.13h-1.39v2.27z"/><path d="m87.09 80.51h2.5l4 9.44h-2.79l-.67-1.69h-3.63l-.67 1.74h-2.71zm2.28 5.73-1.05-2.65-1.06 2.65z"/><path d="m94 80.55h2.6v9.37h-2.6z"/><path d="m97.56 80.55h2.44l3.37 5v-5h2.57v9.37h-2.27l-3.53-5.14v5.14h-2.58z"/><path d="m106.37 88.53 1.44-1.73a4.86 4.86 0 0 0 3 1.13c.71 0 1.08-.25 1.08-.65 0-.41-.3-.61-1.59-.91-2-.46-3.53-1-3.53-2.93 0-1.74 1.38-3 3.63-3a5.88 5.88 0 0 1 3.85 1.25l-1.25 1.78a4.56 4.56 0 0 0 -2.62-.92c-.63 0-.94.25-.94.6 0 .43.32.62 1.63.91 2.15.47 3.48 1.17 3.48 2.92 0 1.91-1.51 3-3.78 3a6.56 6.56 0 0 1 -4.4-1.45z"/></g><path d="m0 0h180v180h-180z" fill="none"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,29 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class AlignmentExamples
|
||||
{
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Border(1)
|
||||
.AlignBottom()
|
||||
.Background(Colors.Grey.Lighten1)
|
||||
.Text("Test");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ContentDirectionExamples
|
||||
{
|
||||
private Action<IContainer> ContentDirectionTemplate(Action<IContainer> content)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container
|
||||
.MinimalBox()
|
||||
.ExtendHorizontal()
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(1);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
|
||||
header.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
|
||||
|
||||
static Action<IContainer> HeaderCell(string label)
|
||||
{
|
||||
return container => container
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.PaddingHorizontal(15)
|
||||
.PaddingVertical(5)
|
||||
.Text(label)
|
||||
.FontSize(18)
|
||||
.SemiBold();
|
||||
}
|
||||
});
|
||||
|
||||
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
|
||||
table.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
table.Cell().Element(TestCell).ContentFromRightToLeft().Element(content);
|
||||
|
||||
static IContainer TestCell(IContainer container)
|
||||
{
|
||||
return container.Padding(15);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Page()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A5);
|
||||
page.Margin(20);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
|
||||
page.ContentFromRightToLeft();
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item()
|
||||
.Text("مثال على الفاتورة") // example invoice
|
||||
.FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
|
||||
|
||||
column.Item().Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(100);
|
||||
});
|
||||
|
||||
table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
|
||||
table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
|
||||
table.Cell().Element(HeaderStyle).Text("سعر"); // price
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
"دورة البرمجة", // programming course
|
||||
"دورة تصميم الرسومات", // graphics design course
|
||||
"تحليل وتصميم الخوارزميات", // analysis and design of algorithms
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var price = Placeholders.Random.NextDouble() * 100;
|
||||
|
||||
table.Cell().Text(item);
|
||||
table.Cell().Text(Placeholders.Random.Next(1, 10));
|
||||
table.Cell().Text($"USD${price:F2}");
|
||||
}
|
||||
|
||||
static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Column()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(5);
|
||||
|
||||
column.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
column.Item().Height(50).Width(100).Background(Colors.Green.Lighten1);
|
||||
column.Item().Height(50).Width(150).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Row()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.Spacing(5);
|
||||
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
|
||||
row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Table()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Height(50).Background(Colors.Red.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Green.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Blue.Lighten1);
|
||||
table.Cell().ColumnSpan(2).Height(50).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Width(50).Height(50).Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unconstrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Unconstrained()
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Inlined()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(InlinedAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(5);
|
||||
|
||||
inlined.Alignment(alignment);
|
||||
|
||||
inlined.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
inlined.Item().Height(50).Width(75).Background(Colors.Green.Lighten1);
|
||||
inlined.Item().Height(50).Width(100).Background(Colors.Blue.Lighten1);
|
||||
inlined.Item().Height(50).Width(125).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Text()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(HorizontalAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Text(text =>
|
||||
{
|
||||
text.Alignment = alignment; // internal API
|
||||
|
||||
text.Span("Lorem ipsum").Bold().FontColor(Colors.Red.Medium);
|
||||
text.Element().Width(5);
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Decoration()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Decoration(decoration =>
|
||||
{
|
||||
decoration.Before().Background(Colors.Green.Lighten1).Padding(5).Text("Before").FontSize(16);
|
||||
decoration.Content().Background(Colors.Green.Lighten2).Padding(5).Text("Content").FontSize(16);
|
||||
decoration.After().Background(Colors.Green.Lighten3).Padding(5).Text("After").FontSize(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Dynamic(new SimpleDynamic());
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleDynamic : IDynamicComponent<int>
|
||||
{
|
||||
public int State { get; set; }
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.ConstantItem(50).Background(Colors.Red.Lighten2).Height(50);
|
||||
row.ConstantItem(75).Background(Colors.Green.Lighten2).Height(50);
|
||||
row.ConstantItem(100).Background(Colors.Blue.Lighten2).Height(50);
|
||||
});
|
||||
});
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
@@ -43,7 +44,7 @@ namespace QuestPDF.Examples
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i)
|
||||
.Text(i.ToString(CultureInfo.InvariantCulture))
|
||||
.FontSize(16 + i / 4);
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
@@ -49,15 +50,15 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container.MinimalBox().Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Element(header);
|
||||
decoration.Before().Element(header);
|
||||
|
||||
decoration.Content().Box().Stack(stack =>
|
||||
decoration.Content().Column(column =>
|
||||
{
|
||||
foreach (var row in rows)
|
||||
stack.Item().Element(row.Element);
|
||||
column.Item().Element(row.Element);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(footer);
|
||||
decoration.After().Element(footer);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,15 +83,14 @@ namespace QuestPDF.Examples
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Padding(5)
|
||||
.DefaultTextStyle(TextStyle.Default.SemiBold())
|
||||
.Row(row =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
row.ConstantItem(30).Text("#", textStyle);
|
||||
row.RelativeItem().Text("Item name", textStyle);
|
||||
row.ConstantItem(50).AlignRight().Text("Count", textStyle);
|
||||
row.ConstantItem(50).AlignRight().Text("Price", textStyle);
|
||||
row.ConstantItem(50).AlignRight().Text("Total", textStyle);
|
||||
row.ConstantItem(30).Text("#");
|
||||
row.RelativeItem().Text("Item name");
|
||||
row.ConstantItem(50).AlignRight().Text("Count");
|
||||
row.ConstantItem(50).AlignRight().Text("Price");
|
||||
row.ConstantItem(50).AlignRight().Text("Total");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -105,7 +105,8 @@ namespace QuestPDF.Examples
|
||||
.Width(context.AvailableSize.Width)
|
||||
.Padding(5)
|
||||
.AlignRight()
|
||||
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
|
||||
.DefaultTextStyle(TextStyle.Default.FontSize(14).SemiBold())
|
||||
.Text($"Subtotal: {total}$");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,9 +127,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantItem(30).Text(index + 1);
|
||||
row.ConstantItem(30).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
row.RelativeItem().Text(item.ItemName);
|
||||
row.ConstantItem(50).AlignRight().Text(item.Count);
|
||||
row.ConstantItem(50).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
});
|
||||
@@ -164,7 +165,7 @@ namespace QuestPDF.Examples
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text(text =>
|
||||
{
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
@@ -96,9 +97,9 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
var item = Items[index];
|
||||
|
||||
table.Cell().Element(Style).Text(index + 1);
|
||||
table.Cell().Element(Style).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).Text(item.ItemName);
|
||||
table.Cell().Element(Style).AlignRight().Text(item.Count);
|
||||
table.Cell().Element(Style).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ProcessRunningTime
|
||||
{
|
||||
public TimeSpan FluentTime { get; set; }
|
||||
public TimeSpan GenerationTime { get; set; }
|
||||
public float Size { get; set; }
|
||||
}
|
||||
|
||||
public class GenerationBenchmark
|
||||
{
|
||||
public const int TestSize = 4096;
|
||||
|
||||
[Test]
|
||||
public void BenchmarkAsync()
|
||||
{
|
||||
RunTest(() => Enumerable
|
||||
.Range(0, TestSize)
|
||||
.AsParallel() // difference
|
||||
.Select(GenerateAndCollect)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BenchmarkSync()
|
||||
{
|
||||
RunTest(() => Enumerable
|
||||
.Range(0, TestSize)
|
||||
.Select(GenerateAndCollect)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
|
||||
{
|
||||
var totalFluentTime = TimeSpan.Zero;
|
||||
var totalGenerationTime = TimeSpan.Zero;
|
||||
|
||||
var stopWatch = new Stopwatch();
|
||||
|
||||
stopWatch.Start();
|
||||
var results = handler();
|
||||
stopWatch.Stop();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
totalFluentTime += result.FluentTime;
|
||||
totalGenerationTime += result.GenerationTime;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Fluent: {totalFluentTime:g}");
|
||||
Console.WriteLine($"Generation: {totalGenerationTime:g}");
|
||||
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
|
||||
}
|
||||
|
||||
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text($"Attempts {attemptNumber}");
|
||||
|
||||
const int numberOfRows = 100;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
for (var y = 0; y < numberOfRows; y++)
|
||||
{
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
for (var x = 0; x < numberOfColumns; x++)
|
||||
{
|
||||
row.RelativeItem()
|
||||
|
||||
.Background(Colors.Red.Lighten5)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten4)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Medium)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken4)
|
||||
.Height(3);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var fluentTime = stopwatch.Elapsed;
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
var size = Document
|
||||
.Create(x => x.Page(page => page.Content().Element(container)))
|
||||
.GeneratePdf()
|
||||
.Length;
|
||||
|
||||
var generationTime = stopwatch.Elapsed;
|
||||
|
||||
return new ProcessRunningTime
|
||||
{
|
||||
FluentTime = fluentTime,
|
||||
GenerationTime = generationTime,
|
||||
Size = size
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
@@ -34,6 +37,21 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DynamicImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 350)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25)
|
||||
.Image(Placeholders.Image);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
@@ -47,5 +65,25 @@ namespace QuestPDF.Examples
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void ImageResolutionScaling()
|
||||
{
|
||||
var image = Image.FromFile("large-image.jpg");
|
||||
|
||||
Document
|
||||
.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(210, 210);
|
||||
page.Margin(50);
|
||||
page.Content().Image(image);
|
||||
});
|
||||
})
|
||||
.GeneratePdf($"test.pdf");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,5 +120,42 @@ namespace QuestPDF.Examples
|
||||
.Background(Placeholders.BackgroundColor());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RepeatingInlinedInHeader_Test()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header()
|
||||
.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(10);
|
||||
|
||||
foreach (var i in Enumerable.Range(5, 5))
|
||||
inlined.Item().Width(i * 10).Height(20).Background(Colors.Red.Medium);
|
||||
});
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
|
||||
foreach (var i in Enumerable.Range(10, 20))
|
||||
column.Item().Width(i * 10).Height(50).Background(Colors.Grey.Lighten2);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,12 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
var url = "https://picsum.photos/300/200";
|
||||
|
||||
if(Greyscale)
|
||||
if (Greyscale)
|
||||
url += "?grayscale";
|
||||
|
||||
using var client = new WebClient();
|
||||
client.Headers.Add("user-agent", "QuestPDF/1.0 Unit Testing");
|
||||
|
||||
var response = client.DownloadData(url);
|
||||
container.Image(response);
|
||||
}
|
||||
@@ -59,4 +61,4 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -32,6 +32,12 @@
|
||||
<None Update="pdf-icon.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="large-image.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="large-image.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -105,5 +105,41 @@ namespace QuestPDF.Examples
|
||||
.Row(row => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RowElementForRelativeHeightDivision()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 400)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.AlignLeft()
|
||||
.RotateRight()
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(20);
|
||||
|
||||
row.RelativeItem(1).Element(Content);
|
||||
row.RelativeItem(2).Element(Content);
|
||||
row.RelativeItem(3).Element(Content);
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.RotateLeft()
|
||||
.Border(1)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.Padding(5)
|
||||
.Text(Placeholders.Label());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
@@ -299,17 +300,18 @@ namespace QuestPDF.Examples
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().RowSpan(4).Element(Block).Text("1");
|
||||
var image = Placeholders.Image(400, 300);
|
||||
|
||||
table.Cell().RowSpan(2).Element(Block).Text("2");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("3");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("4");
|
||||
|
||||
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("6");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("7");
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -389,12 +391,12 @@ namespace QuestPDF.Examples
|
||||
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||
|
||||
// inches
|
||||
table.Cell().Element(CellStyle).Text(page.width);
|
||||
table.Cell().Element(CellStyle).Text(page.height);
|
||||
table.Cell().Element(CellStyle).Text(page.width.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text(page.height.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// points
|
||||
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
|
||||
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
|
||||
table.Cell().Element(CellStyle).Text((page.width * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text((page.height * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(50);
|
||||
|
||||
page.Content().Column(column =>
|
||||
page.Content().PaddingVertical(10).Column(column =>
|
||||
{
|
||||
column.Item().Element(Title);
|
||||
column.Item().PageBreak();
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
@@ -121,7 +123,132 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void LetterSpacing()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 700)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
var letterSpacing = new[] { -0.1f, 0f, 0.2f };
|
||||
var paragraph = Placeholders.Sentence().ToUpper();
|
||||
|
||||
foreach (var spacing in letterSpacing)
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Column(nestedColumn =>
|
||||
{
|
||||
nestedColumn.Item()
|
||||
.Text(paragraph)
|
||||
.FontSize(16)
|
||||
.LetterSpacing(spacing);
|
||||
|
||||
nestedColumn.Item()
|
||||
.Text($"Letter spacing of {spacing} em")
|
||||
.FontSize(10)
|
||||
.Italic()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LetterSpacing_Arabic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 700)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Column(column =>
|
||||
{
|
||||
var letterSpacing = new[] { -0.1f, 0f, 0.2f };
|
||||
var paragraph = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا";
|
||||
foreach (var spacing in letterSpacing)
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Column(nestedColumn =>
|
||||
{
|
||||
nestedColumn.Item()
|
||||
.Text(paragraph)
|
||||
.FontSize(16)
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.LetterSpacing(spacing);
|
||||
|
||||
nestedColumn.Item()
|
||||
.Text($"Letter spacing of {spacing} em")
|
||||
.FontSize(10)
|
||||
.Italic()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void LetterSpacing_Unicode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 700)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Column(column =>
|
||||
{
|
||||
var letterSpacing = new[] { 0f, 0.5f };
|
||||
|
||||
|
||||
|
||||
var paragraph = "Ţ̴̡̧̤̮̺̤̗͎̱̹͙͎͖͂̿̓́̉̊̀̍͜h̵̞̘͇̾̎̏̅į̵̹̖͔͉̰̎̉̄̐̏͑͂̅̃̃͘͝s̷͓͉̭̭̯̬̥̻̰̩̦̑̀̀͌́̒̍̒̌̇͛̀͛́̎ ̷̡̡̟͕̳̺̝̼͇͔̬̟̖͍̈́̽͜͝͝i̶͔͚̟̊̐͛́͛̄̌ṡ̸̡̤̪͙͍̥͙̟̼̝̰̥͈̿̓̄̿̓͠ ̶̢̦̙͍̯̖̱̰̯͕͔͎̯̝̎͑t̸͖̲̱̼̎͐̎̉̾̎̾̌̅̔̏͘ȩ̶̝̫̙͓̙̣̔̀̌̔̋̂̑̈́̏̀̈͘̕͜͝s̸̫̝̮̻̼͐̅̄̎̎̑͝ț̷̨̢̨̻͈̮̞̆͗̓͊̃̌͂̑̉̕̕͜͝͝";
|
||||
|
||||
|
||||
|
||||
foreach (var spacing in letterSpacing)
|
||||
{
|
||||
column.Item()
|
||||
.Text($"Letter spacing of {spacing} em")
|
||||
.FontSize(10)
|
||||
.Italic()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
|
||||
column.Item()
|
||||
.PaddingVertical(50)
|
||||
.Text(paragraph)
|
||||
.FontSize(16)
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.LetterSpacing(spacing);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SuperscriptSubscript_Simple()
|
||||
{
|
||||
@@ -522,7 +649,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Text(null);
|
||||
column.Item().Text((string) null);
|
||||
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
@@ -554,7 +681,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Text(null);
|
||||
column.Item().Text((string) null);
|
||||
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
@@ -567,5 +694,315 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextShaping_Unicode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(600, 100)
|
||||
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(35)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
|
||||
|
||||
text.Span("Complex Unicode structure: ");
|
||||
|
||||
|
||||
text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontFamily(Fonts.Calibri).FontColor(Colors.Red.Medium);
|
||||
|
||||
|
||||
text.Span(".");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextShaping_Arabic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(250, 100)
|
||||
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text("خوارزمية ترتيب")
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(30);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontFallback()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x
|
||||
.Fallback(y => y.FontFamily("Segoe UI Emoji")
|
||||
.Fallback(y => y.FontFamily("Microsoft YaHei"))));
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Content().Text(t =>
|
||||
{
|
||||
t.Line("This is normal text.");
|
||||
t.EmptyLine();
|
||||
|
||||
t.Line("Following line should use font fallback:");
|
||||
t.Line("中文文本");
|
||||
t.EmptyLine();
|
||||
|
||||
t.Line("The following line contains a mix of known and unknown characters.");
|
||||
t.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!");
|
||||
t.EmptyLine();
|
||||
|
||||
t.Line("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WordWrappingStability()
|
||||
{
|
||||
// instruction: check if any characters repeat when performing the word-wrapping algorithm
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "Lorem ipsum dolor sit amet consectetuer";
|
||||
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
foreach (var width in Enumerable.Range(25, 200))
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.MaxWidth(width)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Text(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AdvancedLanguagesSupport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(400, 400))
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||
|
||||
container
|
||||
.Padding(20)
|
||||
.ContentFromRightToLeft()
|
||||
.Text(text)
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(22);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WordWrappingWhenRightToLeft()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(1000, 500))
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||
|
||||
container
|
||||
.Padding(25)
|
||||
.ContentFromRightToLeft()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
foreach (var size in new[] { 36, 34, 32, 30, 15 })
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.MaxWidth(size * 25)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text(text)
|
||||
.FontSize(20)
|
||||
.FontFamily("Segoe UI");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ForcingTextDirection()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(1000, 500))
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri"))
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
var word = "الجوريتم";
|
||||
var definition = "algorithm in Arabic";
|
||||
|
||||
var text = $"{word} - {definition}";
|
||||
|
||||
// text direction is automatically detected using the first word
|
||||
column.Item().Text(text);
|
||||
|
||||
// it is possible to force specific content direction
|
||||
column.Item().Text(text).DirectionFromLeftToRight();
|
||||
column.Item().Text(text).DirectionFromRightToLeft();
|
||||
|
||||
// to combine text in various content directions, split it into segments
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.Span(word);
|
||||
text.Span(" - ");
|
||||
text.Span(definition);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DetectSpanPositionExample()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(650, 800))
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var fontSize = 20;
|
||||
|
||||
var paint = new SKPaint
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
TextSize = fontSize
|
||||
};
|
||||
|
||||
var fontMetrics = paint.FontMetrics;
|
||||
|
||||
var start = 0f;
|
||||
var end = 0f;
|
||||
|
||||
// corner case: what if text is paged? clamp start and end?
|
||||
|
||||
container
|
||||
.Padding(25)
|
||||
.DefaultTextStyle(x => x.FontSize(fontSize).FontFamily("Calibri"))
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.PrimaryLayer().Text(text =>
|
||||
{
|
||||
text.Span(Placeholders.Paragraph());
|
||||
text.Span(" - ");
|
||||
|
||||
// record start
|
||||
text.Element().Width(0).Height(0)
|
||||
.Canvas((canvas, size) => start = canvas.TotalMatrix.TransY / canvas.TotalMatrix.ScaleY);
|
||||
|
||||
text.Span(Placeholders.LoremIpsum()).BackgroundColor(Colors.Red.Lighten4);
|
||||
|
||||
// record end
|
||||
text.Element().Width(0).Height(0)
|
||||
.Canvas((canvas, size) => end = canvas.TotalMatrix.TransY / canvas.TotalMatrix.ScaleY);
|
||||
|
||||
text.Span(" - ");
|
||||
text.Span(Placeholders.Paragraph());
|
||||
});
|
||||
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
{
|
||||
canvas.Save();
|
||||
|
||||
canvas.Translate(-canvas.TotalMatrix.TransX / canvas.TotalMatrix.ScaleX, -canvas.TotalMatrix.TransY / canvas.TotalMatrix.ScaleY);
|
||||
canvas.DrawRect(10, start + fontMetrics.Ascent, 5, end - start + (fontMetrics.Bottom - fontMetrics.Ascent), paint);
|
||||
|
||||
canvas.Restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InconsistentLineHeightWhenUsingNewLineTest()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.FontSize(16));
|
||||
|
||||
text.Line(Placeholders.Paragraph());
|
||||
text.Line("");
|
||||
text.Line(Placeholders.Paragraph());
|
||||
|
||||
text.Line(Placeholders.Label()).FontSize(48);
|
||||
|
||||
text.Line(Placeholders.Paragraph());
|
||||
text.Line("");
|
||||
text.Line(Placeholders.Paragraph());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,6 +1,7 @@
|
||||
<Application x:Class="QuestPDF.Previewer.PreviewerApp"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="QuestPDF Document Preview">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark" />
|
||||
</Application.Styles>
|
||||
@@ -49,7 +49,7 @@ namespace QuestPDF.Previewer
|
||||
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
|
||||
|
||||
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/documentation/api-reference.html"));
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/api-reference/index.html"));
|
||||
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF.Previewer</PackageId>
|
||||
<Version>2022.6.0</Version>
|
||||
<Version>2022.12.0</Version>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
@@ -13,6 +13,7 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
@@ -33,22 +34,22 @@
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Logo.png" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
|
||||
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
||||
<PackageReference Include="ReactiveUI" Version="18.4.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.3" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,4 +1,4 @@
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
@@ -7,34 +7,19 @@
|
||||
|
||||
QuestPDF is an open-source .NET library for PDF documents generation.
|
||||
|
||||
It offers a layout engine designed with a full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows. All of it done with predictable and discoverable Fluent API.
|
||||
|
||||
## Installation
|
||||
|
||||
The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/).
|
||||
|
||||
```xml
|
||||
// Package Manager
|
||||
Install-Package QuestPDF
|
||||
|
||||
// .NET CLI
|
||||
dotnet add package QuestPDF
|
||||
|
||||
// Package reference in .csproj file
|
||||
<PackageReference Include="QuestPDF" Version="2022.3.0" />
|
||||
```
|
||||
It offers a layout engine designed with a full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.
|
||||
|
||||
## Documentation
|
||||
|
||||
[](https://www.questpdf.com/documentation/getting-started.html)
|
||||
[](https://www.questpdf.com/getting-started.html)
|
||||
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/documentation/api-reference.html)
|
||||
[](https://www.questpdf.com/api-reference/index.html)
|
||||
A detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
|
||||
|
||||
[](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)
|
||||
[](https://www.questpdf.com/design-patterns.html)
|
||||
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
||||
|
||||
## Simplicity is the key
|
||||
@@ -84,10 +69,10 @@ Document.Create(container =>
|
||||
|
||||
And compare it to the produced PDF file:
|
||||
|
||||

|
||||

|
||||
|
||||
## Are you ready for more?
|
||||
|
||||
The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice).
|
||||
|
||||

|
||||

|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.ReportSample
|
||||
public static string FormatAsRomanNumeral(this int number)
|
||||
{
|
||||
if (number < 0 || number > 3999)
|
||||
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
|
||||
throw new ArgumentOutOfRangeException(nameof(number), "Number should be in range from 1 to 3999");
|
||||
|
||||
return RomanNumeralCache.GetOrAdd(number, x =>
|
||||
{
|
||||
@@ -33,11 +33,10 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
page.MarginHorizontal(50);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
page.ContentDirectionRightToLeft();
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
|
||||
@@ -68,9 +68,11 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
|
||||
|
||||
text.Span(" (").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
||||
text.Span(")").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x =>
|
||||
{
|
||||
var formatted = x == 1 ? "1 page long" : $"{x} pages long";
|
||||
return $" ({formatted})";
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -10,9 +10,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 993 B |