Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2772b5b4a4 | |||
| 3dfe7a59e0 | |||
| df5c5524ee | |||
| 5975b19445 | |||
| fb7743e828 | |||
| 3bb46ecc42 | |||
| 3112ee10cf | |||
| 899c138842 | |||
| 5177dcfd2b | |||
| 4e6415aee2 | |||
| d9b3f016e8 | |||
| b9cfaf3846 | |||
| e3e7a22af8 | |||
| c938ea32b0 | |||
| 69419c8440 | |||
| 23e8aa3491 | |||
| fd5d51e764 | |||
| dbc479db07 | |||
| edc595d199 | |||
| 7f94ae589a | |||
| adfce6204e | |||
| 6d3c8d0708 | |||
| 3b8c300126 | |||
| 20fe49dd01 | |||
| 7cdc1fcee2 | |||
| c05c5e1597 |
@@ -0,0 +1,32 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Barcode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
@@ -18,6 +17,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -33,6 +33,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -55,49 +56,14 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// [Test]
|
||||
// public void Page()
|
||||
// {
|
||||
// RenderingTest
|
||||
// .Create()
|
||||
// .PageSize(298, 421)
|
||||
// .Render(container =>
|
||||
// {
|
||||
// container
|
||||
// .Background("#FFF")
|
||||
// .Padding(15)
|
||||
// .Page(page =>
|
||||
// {
|
||||
// page.Header()
|
||||
// .Height(60)
|
||||
// .Background(Colors.Grey.Lighten1)
|
||||
// .AlignCenter()
|
||||
// .AlignMiddle()
|
||||
// .Text("Header");
|
||||
//
|
||||
// page.Content()
|
||||
// .Background(Colors.Grey.Lighten2)
|
||||
// .AlignCenter()
|
||||
// .AlignMiddle()
|
||||
// .Text("Content");
|
||||
//
|
||||
// page.Footer()
|
||||
// .Height(30)
|
||||
// .Background(Colors.Grey.Lighten1)
|
||||
// .AlignCenter()
|
||||
// .AlignMiddle()
|
||||
// .Text("Footer");
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
[Test]
|
||||
public void Row()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -138,6 +104,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -159,6 +126,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -181,6 +149,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(210, 210)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -203,6 +172,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "";
|
||||
@@ -225,6 +195,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 230)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.Size(14);
|
||||
@@ -259,6 +230,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -288,6 +260,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 250)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -322,7 +295,7 @@ namespace QuestPDF.Examples
|
||||
layers
|
||||
.Layer()
|
||||
.AlignBottom()
|
||||
.PageNumber("Page {pdf:currentPage}", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
|
||||
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -363,6 +336,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -407,6 +381,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -439,6 +414,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -465,6 +441,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -504,6 +481,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -523,21 +501,49 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 400)
|
||||
.PageSize(300, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var style = TextStyle.Default.Size(20);
|
||||
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
stack.Item().Background(Placeholders.BackgroundColor()).Scale(0.5f).Text("Smaller text", style);
|
||||
stack.Item().Background(Placeholders.BackgroundColor()).Text("Normal text", style);
|
||||
stack.Item().Background(Placeholders.BackgroundColor()).Scale(2f).Text("Bigger text", style);
|
||||
stack.Item().Background(Placeholders.BackgroundColor()).Scale(-1.5f).Text("Flipped text", style);
|
||||
var headerFontStyle = TextStyle
|
||||
.Default
|
||||
.Size(20)
|
||||
.Color(Colors.Blue.Darken2)
|
||||
.SemiBold();
|
||||
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(10)
|
||||
.Text("Example: scale component", headerFontStyle);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
|
||||
|
||||
foreach (var scale in scales)
|
||||
{
|
||||
var fontColor = scale <= 1f
|
||||
? Colors.Red.Lighten4
|
||||
: Colors.Green.Lighten4;
|
||||
|
||||
var fontStyle = TextStyle.Default.Size(16);
|
||||
|
||||
stack
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Background(fontColor)
|
||||
.Scale(scale)
|
||||
.Padding(5)
|
||||
.Text($"Content with {scale} scale.", fontStyle);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -547,31 +553,39 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 300)
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Box()
|
||||
.Background(Colors.Green.Lighten4)
|
||||
.Padding(5)
|
||||
|
||||
.Padding(25)
|
||||
|
||||
.Background(Colors.Green.Lighten3)
|
||||
|
||||
.TranslateX(15)
|
||||
.TranslateY(15)
|
||||
.Text("Text outside of bounds");
|
||||
|
||||
.Border(2)
|
||||
.BorderColor(Colors.Green.Darken1)
|
||||
|
||||
.Padding(50)
|
||||
.Text("Moved text", TextStyle.Default.Size(25));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Rotate()
|
||||
public void ConstrainedRotate()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 450)
|
||||
.PageSize(350, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.Grid(grid =>
|
||||
{
|
||||
@@ -581,11 +595,10 @@ namespace QuestPDF.Examples
|
||||
foreach (var turns in Enumerable.Range(0, 4))
|
||||
{
|
||||
grid.Item()
|
||||
.Width(200)
|
||||
.Height(200)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(10)
|
||||
//.Box()
|
||||
.Element(element =>
|
||||
{
|
||||
foreach (var x in Enumerable.Range(0, turns))
|
||||
@@ -594,23 +607,83 @@ namespace QuestPDF.Examples
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.Background(Colors.Grey.Lighten1)
|
||||
.Text($"Rotated {turns * 90} degrees.", TextStyle.Default.Size(14));
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FreeRotate()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
|
||||
.Background(Colors.White)
|
||||
|
||||
.Rotate(30)
|
||||
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Blue.Medium);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FreeRotateCenter()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Background(Colors.White)
|
||||
|
||||
.TranslateX(50)
|
||||
.TranslateY(50)
|
||||
|
||||
.Rotate(30)
|
||||
|
||||
.TranslateX(-50)
|
||||
.TranslateY(-50)
|
||||
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Blue.Medium);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Flip()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 450)
|
||||
.PageSize(350, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(20)
|
||||
.Grid(grid =>
|
||||
{
|
||||
@@ -620,21 +693,24 @@ namespace QuestPDF.Examples
|
||||
foreach (var turns in Enumerable.Range(0, 4))
|
||||
{
|
||||
grid.Item()
|
||||
.Width(200)
|
||||
.Height(200)
|
||||
.Width(150)
|
||||
.Height(150)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Element(element =>
|
||||
{
|
||||
if (turns == 1 || turns == 2)
|
||||
element = element.FlipX();
|
||||
element = element.FlipHorizontal();
|
||||
|
||||
if (turns == 2 || turns == 3)
|
||||
element = element.FlipY();
|
||||
element = element.FlipVertical();
|
||||
|
||||
return element;
|
||||
})
|
||||
.Text($"Flipped.", TextStyle.Default.Size(14));
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -646,9 +722,11 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Border(2)
|
||||
.Row(row =>
|
||||
{
|
||||
@@ -663,5 +741,42 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unconstrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.PaddingLeft(75)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
|
||||
|
||||
stack
|
||||
.Item()
|
||||
|
||||
// creates an infinite space for its child
|
||||
.Unconstrained()
|
||||
|
||||
// moves the child up and left
|
||||
.TranslateX(-50)
|
||||
.TranslateY(-50)
|
||||
|
||||
// limits the space for the child
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
|
||||
.Background(Colors.Blue.Darken1);
|
||||
|
||||
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using QuestPDF.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public enum RenderingTestResult
|
||||
{
|
||||
Pdf,
|
||||
Images
|
||||
}
|
||||
|
||||
public class RenderingTest
|
||||
{
|
||||
private string FileNamePrefix = "test";
|
||||
private Size Size { get; set; }
|
||||
private bool ShowResult { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private RenderingTest()
|
||||
{
|
||||
@@ -23,15 +30,38 @@ namespace QuestPDF.Examples.Engine
|
||||
return new RenderingTest();
|
||||
}
|
||||
|
||||
public RenderingTest FileName(string fileName)
|
||||
public RenderingTest FileName([CallerMemberName] string fileName = "test")
|
||||
{
|
||||
FileNamePrefix = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest PageSize(Size size)
|
||||
{
|
||||
Size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest PageSize(int width, int height)
|
||||
{
|
||||
Size = new Size(width, height);
|
||||
return PageSize(new Size(width, height));
|
||||
}
|
||||
|
||||
public RenderingTest ProducePdf()
|
||||
{
|
||||
ResultType = RenderingTestResult.Pdf;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ProduceImages()
|
||||
{
|
||||
ResultType = RenderingTestResult.Images;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ShowResults()
|
||||
{
|
||||
ShowResult = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -39,13 +69,27 @@ namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
var container = new Container();
|
||||
content(container);
|
||||
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
|
||||
var document = new SimpleDocument(container, Size);
|
||||
document.GenerateImages(fileNameSchema);
|
||||
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(container, Size, maxPages);
|
||||
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
if (ResultType == RenderingTestResult.Images)
|
||||
{
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
document.GenerateImages(fileNameSchema);
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
if (ResultType == RenderingTestResult.Pdf)
|
||||
{
|
||||
var fileName = $"{FileNamePrefix}.pdf";
|
||||
document.GeneratePdf(fileName);
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,13 @@ namespace QuestPDF.Examples.Engine
|
||||
|
||||
private IContainer Container { get; }
|
||||
private Size Size { get; }
|
||||
private int MaxPages { get; }
|
||||
|
||||
public SimpleDocument(IContainer container, Size size)
|
||||
public SimpleDocument(IContainer container, Size size, int maxPages)
|
||||
{
|
||||
Container = container;
|
||||
Size = size;
|
||||
MaxPages = maxPages;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
@@ -24,7 +26,7 @@ namespace QuestPDF.Examples.Engine
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = 10
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +35,7 @@ namespace QuestPDF.Examples.Engine
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(new PageSize(Size.Width, Size.Height));
|
||||
page.Content().Container().Element(Container as Container);
|
||||
page.Content().Container().Background(Colors.White).Element(Container as Container);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 400)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 280)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -35,6 +36,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -48,7 +50,8 @@ namespace QuestPDF.Examples
|
||||
|
||||
.Background("FFF")
|
||||
.Padding(5)
|
||||
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
|
||||
.AlignCenter()
|
||||
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,6 +61,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -108,6 +112,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -16,4 +16,10 @@
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="quo-vadis.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void Generate()
|
||||
{
|
||||
var chapters = GetBookChapters().ToList();
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
var chapters = GetBookChapters().ToList();
|
||||
|
||||
var results = PerformTest(16).ToList();
|
||||
|
||||
Console.WriteLine($"Min: {results.Min():F}");
|
||||
Console.WriteLine($"Max: {results.Max():F}");
|
||||
Console.WriteLine($"Avg: {results.Average():F}");
|
||||
|
||||
void GenerateDocument()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
IEnumerable<float> PerformTest(int attempts)
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, attempts))
|
||||
{
|
||||
var timer = new Stopwatch();
|
||||
|
||||
timer.Start();
|
||||
GenerateDocument();
|
||||
timer.Stop();
|
||||
|
||||
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
|
||||
yield return timer.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BookChapter
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
private static IEnumerable<BookChapter> GetBookChapters()
|
||||
{
|
||||
var book = File.ReadAllLines("quo-vadis.txt");
|
||||
|
||||
var chapterPointers = book
|
||||
.Select((line, index) => new
|
||||
{
|
||||
LineNumber = index,
|
||||
Text = line
|
||||
})
|
||||
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
|
||||
.Select(x => x.LineNumber)
|
||||
.ToList();
|
||||
|
||||
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
|
||||
{
|
||||
var chapter = chapterPointers[index];
|
||||
|
||||
var title = book[chapter];
|
||||
|
||||
var lineFrom = chapterPointers[index];
|
||||
var lineTo = chapterPointers[index + 1] - 1;
|
||||
|
||||
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x));
|
||||
var content = string.Join(Environment.NewLine, lines);
|
||||
|
||||
yield return new BookChapter
|
||||
{
|
||||
Title = title,
|
||||
Content = content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
|
||||
{
|
||||
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
|
||||
var normalStyle = TextStyle.Default.Size(14);
|
||||
|
||||
ComposePage(container);
|
||||
|
||||
void ComposePage(IContainer container)
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(Title);
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Element(TableOfContents);
|
||||
stack.Item().PageBreak();
|
||||
|
||||
Chapters(stack);
|
||||
|
||||
stack.Item().Element(Acknowledgements);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
|
||||
void Title(IContainer container)
|
||||
{
|
||||
container
|
||||
.Extend()
|
||||
.PaddingBottom(200)
|
||||
.AlignBottom()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
|
||||
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
|
||||
});
|
||||
}
|
||||
|
||||
void TableOfContents(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, "Spis treści");
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().InternalLink(chapter.Title).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(chapter.Title, normalStyle);
|
||||
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Chapters(StackDescriptor stack)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
|
||||
}
|
||||
}
|
||||
|
||||
void Chapter(IContainer container, string title, string content)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, title);
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(5);
|
||||
text.Span(content, normalStyle);
|
||||
});
|
||||
|
||||
stack.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
void Acknowledgements(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
SectionTitle(stack, "Podziękowania");
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(normalStyle);
|
||||
|
||||
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
|
||||
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underlined());
|
||||
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SectionTitle(StackDescriptor stack, string text)
|
||||
{
|
||||
stack.Item().Location(text).Text(text, subtitleStyle);
|
||||
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
}
|
||||
|
||||
void Footer(IContainer container)
|
||||
{
|
||||
container
|
||||
.AlignCenter()
|
||||
.Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextExamples
|
||||
{
|
||||
[Test]
|
||||
public void TextElements()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 400)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.Span("This text is a normal text, ");
|
||||
text.Span("this is a bold text, ", TextStyle.Default.Bold());
|
||||
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underlined());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.Span("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Span(".");
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
|
||||
text.NewLine();
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.NewLine();
|
||||
|
||||
/*
|
||||
text.Span("Let's start with bold text. ", TextStyle.Default.Bold().BackgroundColor(Colors.Grey.Lighten3).Size(16));
|
||||
text.Span("Then something bigger. ", TextStyle.Default.Size(28).Color(Colors.DeepOrange.Darken2).BackgroundColor(Colors.Yellow.Lighten3).Underlined());
|
||||
text.Span("And tiny \r\n teeny-tiny. ", TextStyle.Default.Size(6));
|
||||
text.Span("Stroked text also works fine. ", TextStyle.Default.Size(14).Stroked().BackgroundColor(Colors.Grey.Lighten4));
|
||||
|
||||
text.Span("0123456789-0123456789-0123456789-0123456789-0123456789-0123456789-0123456789", TextStyle.Default.Size(18));
|
||||
|
||||
text.NewLine();
|
||||
text.NewLine();
|
||||
text.NewLine();
|
||||
text.Span("Is it time for lorem ipsum? ", TextStyle.Default.Size(12).Underlined().BackgroundColor(Colors.Grey.Lighten3));
|
||||
text.Span(Placeholders.LoremIpsum(), TextStyle.Default.Size(12));
|
||||
|
||||
text.Span("Before element - ");
|
||||
text.Element().PaddingBottom(-10).Background(Colors.Red.Lighten4).Height(20).PaddingHorizontal(5).AlignMiddle().Text("Text inside text", TextStyle.Default.Size(8));
|
||||
text.Span(" - end of element.");
|
||||
|
||||
text.NewLine();
|
||||
// text.Span("And now some colors: ", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
|
||||
//
|
||||
// foreach (var i in Enumerable.Range(1, 100))
|
||||
// {
|
||||
// text.Span($"{i}: {Placeholders.Sentence()} ", TextStyle.Default.Size(12 + i / 5).LineHeight(2.75f - i / 50f).Color(Placeholders.Color()).BackgroundColor(Placeholders.BackgroundColor()));
|
||||
// }*/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PageNumber()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 400)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -18,8 +18,8 @@ namespace QuestPDF.ReportSample
|
||||
HeaderFields = HeaderFields(),
|
||||
|
||||
LogoData = Helpers.GetImage("Logo.png"),
|
||||
Sections = Enumerable.Range(0, 50).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(0, 30).Select(x => GetReportPhotos()).ToList()
|
||||
Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
|
||||
};
|
||||
|
||||
List<ReportHeaderField> HeaderFields()
|
||||
|
||||
@@ -28,13 +28,13 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
foreach (var part in Model.Parts)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
stack.Item().EnsureSpace(25).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
|
||||
var frame = row.RelativeColumn().ValueCell();
|
||||
|
||||
if (part is ReportSectionText text)
|
||||
frame.Text(text.Text, Typography.Normal);
|
||||
frame.ShowEntire().Text(text.Text, Typography.Normal);
|
||||
|
||||
if (part is ReportSectionMap map)
|
||||
frame.Element(x => MapElement(x, map));
|
||||
|
||||
@@ -25,17 +25,6 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
// .Page(page =>
|
||||
// {
|
||||
// page.MarginVertical(40);
|
||||
// page.MarginHorizontal(50);
|
||||
//
|
||||
// page.ContinuousSize(PageSizes.A4.Width);
|
||||
//
|
||||
// page.Header().Element(ComposeHeader);
|
||||
// page.Content().Element(ComposeContent);
|
||||
// page.Footer().AlignCenter().PageNumber("Page A {number}");
|
||||
// })
|
||||
.Page(page =>
|
||||
{
|
||||
page.MarginVertical(40);
|
||||
@@ -45,7 +34,15 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber();
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(Typography.Normal);
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,10 +67,10 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
foreach (var field in Model.HeaderFields)
|
||||
{
|
||||
grid.Item().Stack(row =>
|
||||
{
|
||||
row.Item().AlignLeft().Text(field.Label, Typography.Normal.SemiBold());
|
||||
row.Item().Text(field.Value, Typography.Normal);
|
||||
grid.Item().Text(text =>
|
||||
{
|
||||
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
|
||||
text.Span(field.Value, Typography.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -88,6 +85,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
|
||||
|
||||
stack.Item().PageBreak();
|
||||
|
||||
foreach (var section in Model.Sections)
|
||||
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
|
||||
row.RelativeColumn().Text(locationName, Typography.Normal);
|
||||
row.ConstantColumn(150).AlignRight().PageNumber($"Page {{pdf:{locationName}}}", Typography.Normal.AlignRight());
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace QuestPDF.ReportSample
|
||||
// target document length should be around 100 pages
|
||||
|
||||
// test size
|
||||
const int testSize = 100;
|
||||
const int testSize = 10;
|
||||
const decimal performanceTarget = 1; // documents per second
|
||||
|
||||
// create report models
|
||||
@@ -57,8 +57,8 @@ namespace QuestPDF.ReportSample
|
||||
Console.WriteLine($"Time per document: {performance:N} ms");
|
||||
Console.WriteLine($"Documents per second: {speed:N} d/s");
|
||||
|
||||
if (speed < performanceTarget)
|
||||
throw new Exception("Rendering algorithm is too slow.");
|
||||
//if (speed < performanceTarget)
|
||||
// throw new Exception("Rendering algorithm is too slow.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
|
||||
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.25f).AlignLeft();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Equivalency;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text.Json;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine.Operations;
|
||||
|
||||
@@ -245,9 +246,9 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public static Element CreateUniqueElement()
|
||||
{
|
||||
return new Text
|
||||
return new DynamicImage
|
||||
{
|
||||
Value = Guid.NewGuid().ToString("N")
|
||||
Source = Placeholders.Image
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -7,7 +6,8 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal static class CanvasCache
|
||||
{
|
||||
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
internal static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
@@ -36,28 +36,14 @@ namespace QuestPDF.Drawing
|
||||
Color = SKColor.Parse(style.Color),
|
||||
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
|
||||
TextSize = style.Size,
|
||||
TextEncoding = SKTextEncoding.Utf32,
|
||||
|
||||
TextAlign = style.Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => SKTextAlign.Left,
|
||||
HorizontalAlignment.Center => SKTextAlign.Center,
|
||||
HorizontalAlignment.Right => SKTextAlign.Right,
|
||||
_ => SKTextAlign.Left
|
||||
}
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
var index = (int)style.ToPaint().BreakText(text, availableWidth, out var width);
|
||||
|
||||
return new TextMeasurement()
|
||||
{
|
||||
LineIndex = index,
|
||||
FragmentWidth = width
|
||||
};
|
||||
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace QuestPDF.Drawing
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
var scalingFactor = Metadata.RasterDpi / (float) PageSizes.PointsPerInch;
|
||||
|
||||
var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor));
|
||||
|
||||
Surface = SKSurface.Create(imageInfo);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace QuestPDF.Drawing.SpacePlan
|
||||
{
|
||||
internal class TextRender : FullRender
|
||||
{
|
||||
public float Ascent { get; set; }
|
||||
public float Descent { get; set; }
|
||||
|
||||
public TextRender(float width, float height) : base(width, height)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class PageNumber : Element
|
||||
{
|
||||
public string TextFormat { get; set; } = "";
|
||||
private Text TextElement { get; set; } = new Text();
|
||||
|
||||
public TextStyle? TextStyle
|
||||
{
|
||||
get => TextElement?.Style;
|
||||
set => TextElement.Style = value;
|
||||
}
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
TextElement?.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
TextElement.Value = GetText();
|
||||
return TextElement.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
TextElement.Value = GetText();
|
||||
TextElement.Draw(availableSpace);
|
||||
}
|
||||
|
||||
private string GetText()
|
||||
{
|
||||
var result = TextFormat;
|
||||
|
||||
// replace known locations
|
||||
foreach (var location in PageContext.GetRegisteredLocations())
|
||||
result = result.Replace($"{{pdf:{location}}}", PageContext.GetLocationPage(location).ToString());
|
||||
|
||||
// placeholder unknown locations
|
||||
result = Regex.Replace(result, @"{pdf:[ \w]+}", "123");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Padding(5)
|
||||
.MaxHeight(32)
|
||||
.Element(x =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.Image(ImageData, ImageScaling.FitArea);
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
else
|
||||
x.Text(Text, TextStyle.Default.Size(14).SemiBold());
|
||||
x.Text(Text, TextStyle.Default.Size(14));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,21 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Rotate : ContainerElement
|
||||
{
|
||||
public int TurnCount { get; set; }
|
||||
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
|
||||
return base.Measure(availableSpace);
|
||||
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
var childSpace = base.Measure(availableSpace) as Size;
|
||||
public float Angle { get; set; } = 0;
|
||||
|
||||
if (childSpace == null)
|
||||
return new Wrap();
|
||||
|
||||
var targetSpace = new Size(childSpace.Height, childSpace.Width);
|
||||
|
||||
if (childSpace is FullRender)
|
||||
return new FullRender(targetSpace);
|
||||
|
||||
if (childSpace is PartialRender)
|
||||
return new PartialRender(targetSpace);
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
|
||||
skiaCanvas.Translate(availableSpace.Width, 0);
|
||||
|
||||
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
|
||||
skiaCanvas.Translate(0, availableSpace.Height);
|
||||
|
||||
skiaCanvas.RotateRadians(NormalizedTurnCount * (float) Math.PI / 2f);
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
|
||||
skiaCanvas.RotateDegrees(Angle);
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class SimpleRotate : ContainerElement
|
||||
{
|
||||
public int TurnCount { get; set; }
|
||||
public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2)
|
||||
return base.Measure(availableSpace);
|
||||
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
var childSpace = base.Measure(availableSpace) as Size;
|
||||
|
||||
if (childSpace == null)
|
||||
return new Wrap();
|
||||
|
||||
var targetSpace = new Size(childSpace.Height, childSpace.Width);
|
||||
|
||||
if (childSpace is FullRender)
|
||||
return new FullRender(targetSpace);
|
||||
|
||||
if (childSpace is PartialRender)
|
||||
return new PartialRender(targetSpace);
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 2)
|
||||
skiaCanvas.Translate(availableSpace.Width, 0);
|
||||
|
||||
if (NormalizedTurnCount == 2 || NormalizedTurnCount == 3)
|
||||
skiaCanvas.Translate(0, availableSpace.Height);
|
||||
|
||||
skiaCanvas.RotateDegrees(NormalizedTurnCount * 90);
|
||||
|
||||
if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3)
|
||||
availableSpace = new Size(availableSpace.Height, availableSpace.Width);
|
||||
|
||||
Child?.Draw(availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Fluent;
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Text : Element
|
||||
{
|
||||
public string? Value { get; set; }
|
||||
public TextStyle? Style { get; set; } = new TextStyle();
|
||||
|
||||
private float LineHeight => Style.Size * Style.LineHeight;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var lines = BreakLines(availableSpace.Width);
|
||||
|
||||
var realWidth = lines
|
||||
.Select(line => Style.BreakText(line, availableSpace.Width).FragmentWidth)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
var realHeight = lines.Count * LineHeight;
|
||||
|
||||
if (realHeight > availableSpace.Height + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
return new FullRender(realWidth, realHeight);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var lines = BreakLines(availableSpace.Width);
|
||||
|
||||
var offsetTop = 0f;
|
||||
var offsetLeft = GetLeftOffset();
|
||||
|
||||
Canvas.Translate(new Position(0, Style.Size));
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
Canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
|
||||
offsetTop += LineHeight;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(0, -Style.Size));
|
||||
|
||||
float GetLeftOffset()
|
||||
{
|
||||
return Style.Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => 0,
|
||||
HorizontalAlignment.Center => availableSpace.Width / 2,
|
||||
HorizontalAlignment.Right => availableSpace.Width,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#region Word Wrap
|
||||
|
||||
private List<string> BreakLines(float maxWidth)
|
||||
{
|
||||
var lines = new List<string> ();
|
||||
|
||||
var remainingText = Value.Trim();
|
||||
|
||||
while(true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(remainingText))
|
||||
break;
|
||||
|
||||
var breakPoint = BreakLinePoint(remainingText, maxWidth);
|
||||
|
||||
if (breakPoint == 0)
|
||||
break;
|
||||
|
||||
var lastLine = remainingText.Substring(0, breakPoint).Trim();
|
||||
lines.Add(lastLine);
|
||||
|
||||
remainingText = remainingText.Substring(breakPoint).Trim();
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private int BreakLinePoint(string text, float width)
|
||||
{
|
||||
var index = 0;
|
||||
var lengthBreak = Style.BreakText(text, width).LineIndex;
|
||||
|
||||
while (index <= text.Length)
|
||||
{
|
||||
var next = text.IndexOfAny (new [] { ' ', '\n' }, index);
|
||||
|
||||
if (next <= 0)
|
||||
return index == 0 || lengthBreak == text.Length ? lengthBreak : index;
|
||||
|
||||
if (next > lengthBreak)
|
||||
return index;
|
||||
|
||||
if (text[next] == '\n')
|
||||
return next;
|
||||
|
||||
index = next + 1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextDrawingRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
public float TotalAscent { get; set; }
|
||||
public Size TextSize { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextLine
|
||||
{
|
||||
public ICollection<TextLineElement> Elements { get; set; }
|
||||
|
||||
public float TextHeight => Elements.Max(x => x.Measurement.Height);
|
||||
public float LineHeight => Elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
|
||||
|
||||
public float Ascent => Elements.Min(x => x.Measurement.Ascent) - (LineHeight - TextHeight) / 2;
|
||||
public float Descent => Elements.Max(x => x.Measurement.Descent) + (LineHeight - TextHeight) / 2;
|
||||
|
||||
public float Width => Elements.Sum(x => x.Measurement.Width);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextLineElement
|
||||
{
|
||||
public ITextBlockItem Item { get; set; }
|
||||
public TextMeasurementResult Measurement { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextMeasurementRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public float AvailableWidth { get; set; }
|
||||
public bool IsFirstLineElement { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Calculation
|
||||
{
|
||||
internal class TextMeasurementResult
|
||||
{
|
||||
public float Width { get; set; }
|
||||
public float Height => Math.Abs(Descent) + Math.Abs(Ascent);
|
||||
|
||||
public float Ascent { get; set; }
|
||||
public float Descent { get; set; }
|
||||
|
||||
public float LineHeight { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
public int NextIndex { get; set; }
|
||||
public int TotalIndex { get; set; }
|
||||
|
||||
public bool IsLast => EndIndex == TotalIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal interface ITextBlockItem
|
||||
{
|
||||
TextMeasurementResult? Measure(TextMeasurementRequest request);
|
||||
void Draw(TextDrawingRequest request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockElement : ITextBlockItem
|
||||
{
|
||||
public Element Element { get; set; } = Empty.Instance;
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
if (measurement is Wrap || measurement is PartialRender)
|
||||
return null;
|
||||
|
||||
var elementSize = measurement as Size;
|
||||
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = elementSize.Width,
|
||||
|
||||
Ascent = -elementSize.Height,
|
||||
Descent = 0,
|
||||
|
||||
LineHeight = 1,
|
||||
|
||||
StartIndex = 0,
|
||||
EndIndex = 0,
|
||||
TotalIndex = 0
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockExternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockInternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string LocationName { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string SlotName { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem(request.PageContext).MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
GetItem(request.PageContext).Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = pageNumber.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockSpan : ITextBlockItem
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
|
||||
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var cacheKey = (request.StartIndex, request.AvailableWidth);
|
||||
|
||||
if (!MeasureCache.ContainsKey(cacheKey))
|
||||
MeasureCache[cacheKey] = MeasureWithoutCache(request);
|
||||
|
||||
return MeasureCache[cacheKey];
|
||||
}
|
||||
|
||||
internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request)
|
||||
{
|
||||
const char space = ' ';
|
||||
|
||||
var paint = Style.ToPaint();
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var startIndex = request.StartIndex;
|
||||
|
||||
if (request.IsFirstLineElement)
|
||||
{
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (Text.Length == 0)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = 0,
|
||||
|
||||
LineHeight = Style.LineHeight,
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent
|
||||
};
|
||||
}
|
||||
|
||||
// start breaking text from requested position
|
||||
var text = Text.Substring(startIndex);
|
||||
|
||||
var breakingIndex = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
|
||||
if (breakingIndex <= 0)
|
||||
return null;
|
||||
|
||||
// break text only on spaces
|
||||
if (breakingIndex < text.Length)
|
||||
{
|
||||
var lastSpaceIndex = text.Substring(0, breakingIndex).LastIndexOf(space) - 1;
|
||||
|
||||
if (lastSpaceIndex <= 0)
|
||||
{
|
||||
if (!request.IsFirstLineElement)
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
breakingIndex = lastSpaceIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Substring(0, breakingIndex);
|
||||
|
||||
var endIndex = startIndex + breakingIndex;
|
||||
var nextIndex = endIndex;
|
||||
|
||||
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
|
||||
nextIndex++;
|
||||
|
||||
// measure final text
|
||||
var width = paint.MeasureText(text);
|
||||
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = width,
|
||||
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent,
|
||||
|
||||
LineHeight = Style.LineHeight,
|
||||
|
||||
StartIndex = startIndex,
|
||||
EndIndex = endIndex,
|
||||
NextIndex = nextIndex,
|
||||
TotalIndex = Text.Length
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
|
||||
|
||||
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
public int CurrentElementIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Children);
|
||||
CurrentElementIndex = 0;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!RenderingQueue.Any())
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height);
|
||||
|
||||
if (!lines.Any())
|
||||
return new PartialRender(Size.Zero);
|
||||
|
||||
var width = lines.Max(x => x.Width);
|
||||
var height = lines.Sum(x => x.LineHeight);
|
||||
|
||||
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
var fullyRenderedItemsCount = lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
.Count(x => x.Any(y => y.Measurement.IsLast));
|
||||
|
||||
if (fullyRenderedItemsCount == RenderingQueue.Count)
|
||||
return new FullRender(width, height);
|
||||
|
||||
return new PartialRender(width, height);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return;
|
||||
|
||||
var heightOffset = 0f;
|
||||
var widthOffset = 0f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
widthOffset = 0f;
|
||||
|
||||
var alignmentOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
Canvas.Translate(new Position(alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(0, -line.Ascent));
|
||||
|
||||
foreach (var item in line.Elements)
|
||||
{
|
||||
var textDrawingRequest = new TextDrawingRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = item.Measurement.StartIndex,
|
||||
EndIndex = item.Measurement.EndIndex,
|
||||
|
||||
TextSize = new Size(item.Measurement.Width, line.LineHeight),
|
||||
TotalAscent = line.Ascent
|
||||
};
|
||||
|
||||
item.Item.Draw(textDrawingRequest);
|
||||
|
||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
||||
widthOffset += item.Measurement.Width;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
||||
Canvas.Translate(new Position(0, line.LineHeight));
|
||||
|
||||
heightOffset += line.LineHeight;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(0, -heightOffset));
|
||||
|
||||
lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
.Where(x => x.Any(y => y.Measurement.IsLast))
|
||||
.Select(x => x.Key)
|
||||
.ToList()
|
||||
.ForEach(x => RenderingQueue.Dequeue());
|
||||
|
||||
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
|
||||
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
ResetState();
|
||||
|
||||
float GetAlignmentOffset(float lineWidth)
|
||||
{
|
||||
if (Alignment == HorizontalAlignment.Left)
|
||||
return 0;
|
||||
|
||||
var emptySpace = availableSpace.Width - lineWidth;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Right)
|
||||
return emptySpace;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
return emptySpace / 2;
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TextLine> DivideTextItemsIntoLines(float availableWidth, float availableHeight)
|
||||
{
|
||||
var queue = new Queue<ITextBlockItem>(RenderingQueue);
|
||||
var currentItemIndex = CurrentElementIndex;
|
||||
var currentHeight = 0f;
|
||||
|
||||
while (queue.Any())
|
||||
{
|
||||
var line = GetNextLine();
|
||||
|
||||
if (!line.Elements.Any())
|
||||
yield break;
|
||||
|
||||
if (currentHeight + line.LineHeight > availableHeight + Size.Epsilon)
|
||||
yield break;
|
||||
|
||||
currentHeight += line.LineHeight;
|
||||
yield return line;
|
||||
}
|
||||
|
||||
TextLine GetNextLine()
|
||||
{
|
||||
var currentWidth = 0f;
|
||||
|
||||
var currentLineElements = new List<TextLineElement>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!queue.Any())
|
||||
break;
|
||||
|
||||
var currentElement = queue.Peek();
|
||||
|
||||
var measurementRequest = new TextMeasurementRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = currentItemIndex,
|
||||
AvailableWidth = availableWidth - currentWidth,
|
||||
IsFirstLineElement = !currentLineElements.Any()
|
||||
};
|
||||
|
||||
var measurementResponse = currentElement.Measure(measurementRequest);
|
||||
|
||||
if (measurementResponse == null)
|
||||
break;
|
||||
|
||||
currentLineElements.Add(new TextLineElement
|
||||
{
|
||||
Item = currentElement,
|
||||
Measurement = measurementResponse
|
||||
});
|
||||
|
||||
currentWidth += measurementResponse.Width;
|
||||
currentItemIndex = measurementResponse.NextIndex;
|
||||
|
||||
if (!measurementResponse.IsLast)
|
||||
break;
|
||||
|
||||
currentItemIndex = 0;
|
||||
queue.Dequeue();
|
||||
}
|
||||
|
||||
return new TextLine
|
||||
{
|
||||
Elements = currentLineElements
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Translate : ContainerElement
|
||||
{
|
||||
public float TranslateX { get; set; } = 1;
|
||||
public float TranslateY { get; set; } = 1;
|
||||
public float TranslateX { get; set; } = 0;
|
||||
public float TranslateY { get; set; } = 0;
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Unconstrained : ContainerElement
|
||||
{
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var childSize = Child?.Measure(Size.Max) ?? new FullRender(Size.Zero);
|
||||
|
||||
if (childSize is PartialRender)
|
||||
return new PartialRender(Size.Zero);
|
||||
|
||||
if (childSize is FullRender)
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
return childSize;
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var measurement = Child?.Measure(Size.Max) as Size;
|
||||
|
||||
if (measurement == null)
|
||||
return;
|
||||
|
||||
Child?.Draw(measurement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,15 +40,6 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return handler(parent.Container()).Container();
|
||||
}
|
||||
|
||||
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
|
||||
{
|
||||
element.Element(new PageNumber
|
||||
{
|
||||
TextFormat = textFormat,
|
||||
TextStyle = style ?? TextStyle.Default
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
|
||||
{
|
||||
@@ -92,25 +83,7 @@ namespace QuestPDF.Fluent
|
||||
MinHeight = minHeight
|
||||
});
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
if (element is Alignment alignment)
|
||||
{
|
||||
style = style.Clone();
|
||||
style.Alignment = alignment.Horizontal;
|
||||
}
|
||||
|
||||
element.Element(new Text()
|
||||
{
|
||||
Value = text.ToString(),
|
||||
Style = style
|
||||
});
|
||||
}
|
||||
|
||||
public static void PageBreak(this IContainer element)
|
||||
{
|
||||
element.Element(new PageBreak());
|
||||
@@ -162,5 +135,10 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return element.Element(new Box());
|
||||
}
|
||||
|
||||
public static IContainer Unconstrained(this IContainer element)
|
||||
{
|
||||
return element.Element(new Unconstrained());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ namespace QuestPDF.Fluent
|
||||
foreach (var imageData in document.GenerateImages())
|
||||
{
|
||||
var path = filePath(index);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
File.WriteAllBytes(path, imageData);
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RotateExtensions
|
||||
{
|
||||
private static IContainer Rotate(this IContainer element, Action<Rotate> handler)
|
||||
private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
|
||||
{
|
||||
var scale = element as Rotate ?? new Rotate();
|
||||
var scale = element as SimpleRotate ?? new SimpleRotate();
|
||||
handler(scale);
|
||||
|
||||
return element.Element(scale);
|
||||
@@ -16,12 +16,21 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer RotateLeft(this IContainer element)
|
||||
{
|
||||
return element.Rotate(x => x.TurnCount--);
|
||||
return element.SimpleRotate(x => x.TurnCount--);
|
||||
}
|
||||
|
||||
public static IContainer RotateRight(this IContainer element)
|
||||
{
|
||||
return element.Rotate(x => x.TurnCount++);
|
||||
return element.SimpleRotate(x => x.TurnCount++);
|
||||
}
|
||||
|
||||
/// <param name="angle">In degrees</param>
|
||||
public static IContainer Rotate(this IContainer element, float angle)
|
||||
{
|
||||
return element.Element(new Rotate
|
||||
{
|
||||
Angle = angle
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
|
||||
@@ -16,32 +16,32 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IContainer Scale(this IContainer element, float value)
|
||||
{
|
||||
return element.ScaleX(value).ScaleY(value);
|
||||
return element.ScaleHorizontal(value).ScaleVertical(value);
|
||||
}
|
||||
|
||||
public static IContainer ScaleX(this IContainer element, float value)
|
||||
public static IContainer ScaleHorizontal(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleX = value);
|
||||
}
|
||||
|
||||
public static IContainer FlipX(this IContainer element)
|
||||
public static IContainer FlipHorizontal(this IContainer element)
|
||||
{
|
||||
return element.ScaleX(-1);
|
||||
return element.ScaleHorizontal(-1);
|
||||
}
|
||||
|
||||
public static IContainer ScaleY(this IContainer element, float value)
|
||||
public static IContainer ScaleVertical(this IContainer element, float value)
|
||||
{
|
||||
return element.Scale(x => x.ScaleY = value);
|
||||
}
|
||||
|
||||
public static IContainer FlipY(this IContainer element)
|
||||
public static IContainer FlipVertical(this IContainer element)
|
||||
{
|
||||
return element.ScaleY(-1);
|
||||
return element.ScaleVertical(-1);
|
||||
}
|
||||
|
||||
public static IContainer FlipOver(this IContainer element)
|
||||
{
|
||||
return element.FlipX().FlipY();
|
||||
return element.FlipHorizontal().FlipVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextDescriptor
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
|
||||
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
{
|
||||
DefaultStyle = style;
|
||||
}
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
public void AlignCenter()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
public void AlignRight()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
public void ParagraphSpacing(float value)
|
||||
{
|
||||
Spacing = value;
|
||||
}
|
||||
|
||||
private void AddItemToLastTextBlock(ITextBlockItem item)
|
||||
{
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.Last().Children.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string text, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
var items = text
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
|
||||
.Select(x => new TextBlockSpan
|
||||
{
|
||||
Text = x,
|
||||
Style = style
|
||||
})
|
||||
.ToList();
|
||||
|
||||
AddItemToLastTextBlock(items.First());
|
||||
|
||||
items
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Children = new List<ITextBlockItem> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void NewLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber()
|
||||
{
|
||||
Style = style,
|
||||
SlotName = slotName
|
||||
});
|
||||
}
|
||||
|
||||
public void CurrentPageNumber(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.CurrentPageSlot, style);
|
||||
}
|
||||
|
||||
public void TotalPages(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.TotalPagesSlot, style);
|
||||
}
|
||||
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
PageNumber(locationName, style);
|
||||
}
|
||||
|
||||
public void InternalLocation(string text, string locationName, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockInternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
LocationName = locationName
|
||||
});
|
||||
}
|
||||
|
||||
public void ExternalLocation(string text, string url, TextStyle? style = null)
|
||||
{
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockExternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
public IContainer Element()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockElement
|
||||
{
|
||||
Element = container
|
||||
});
|
||||
|
||||
return container.Box();
|
||||
}
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
foreach (var textBlock in TextBlocks)
|
||||
stack.Item().Element(textBlock);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextExtensions
|
||||
{
|
||||
public static void Text(this IContainer element, Action<TextDescriptor> content)
|
||||
{
|
||||
var textBlock = new TextBlock();
|
||||
|
||||
if (element is Alignment alignment)
|
||||
textBlock.Alignment = alignment.Horizontal;
|
||||
|
||||
var descriptor = new TextDescriptor();
|
||||
content?.Invoke(descriptor);
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
{
|
||||
element.Text(x => x.Span(text.ToString(), style));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ namespace QuestPDF.Fluent
|
||||
return style.Mutate(x => x.Color = value);
|
||||
}
|
||||
|
||||
public static TextStyle BackgroundColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.BackgroundColor = value);
|
||||
}
|
||||
|
||||
public static TextStyle FontType(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontType = value);
|
||||
@@ -37,31 +42,17 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return style.Mutate(x => x.IsItalic = value);
|
||||
}
|
||||
|
||||
public static TextStyle Stroked(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsStroked = value);
|
||||
}
|
||||
|
||||
public static TextStyle Underlined(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsUnderlined = value);
|
||||
}
|
||||
|
||||
#region Alignmnet
|
||||
|
||||
public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value)
|
||||
{
|
||||
return style.Mutate(x => x.Alignment = value);
|
||||
}
|
||||
|
||||
public static TextStyle AlignLeft(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Left);
|
||||
}
|
||||
|
||||
public static TextStyle AlignCenter(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Center);
|
||||
}
|
||||
|
||||
public static TextStyle AlignRight(this TextStyle style)
|
||||
{
|
||||
return style.Alignment(HorizontalAlignment.Right);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Weight
|
||||
|
||||
public static TextStyle Weight(this TextStyle style, FontWeight weight)
|
||||
|
||||
@@ -13,12 +13,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
return element.Element(translate);
|
||||
}
|
||||
|
||||
public static IContainer Translate(this IContainer element, float value)
|
||||
{
|
||||
return element.TranslateX(value).TranslateY(value);
|
||||
}
|
||||
|
||||
|
||||
public static IContainer TranslateX(this IContainer element, float value)
|
||||
{
|
||||
return element.Translate(x => x.TranslateX = value);
|
||||
|
||||
@@ -155,25 +155,25 @@ namespace QuestPDF.Helpers
|
||||
|
||||
private static readonly string[] BackgroundColors =
|
||||
{
|
||||
Colors.Red.Lighten2,
|
||||
Colors.Pink.Lighten2,
|
||||
Colors.Purple.Lighten2,
|
||||
Colors.DeepPurple.Lighten2,
|
||||
Colors.Indigo.Lighten2,
|
||||
Colors.Blue.Lighten2,
|
||||
Colors.LightBlue.Lighten2,
|
||||
Colors.Cyan.Lighten2,
|
||||
Colors.Teal.Lighten2,
|
||||
Colors.Green.Lighten2,
|
||||
Colors.LightGreen.Lighten2,
|
||||
Colors.Lime.Lighten2,
|
||||
Colors.Yellow.Lighten2,
|
||||
Colors.Amber.Lighten2,
|
||||
Colors.Orange.Lighten2,
|
||||
Colors.DeepOrange.Lighten2,
|
||||
Colors.Brown.Lighten2,
|
||||
Colors.Grey.Lighten2,
|
||||
Colors.BlueGrey.Lighten2
|
||||
Colors.Red.Lighten3,
|
||||
Colors.Pink.Lighten3,
|
||||
Colors.Purple.Lighten3,
|
||||
Colors.DeepPurple.Lighten3,
|
||||
Colors.Indigo.Lighten3,
|
||||
Colors.Blue.Lighten3,
|
||||
Colors.LightBlue.Lighten3,
|
||||
Colors.Cyan.Lighten3,
|
||||
Colors.Teal.Lighten3,
|
||||
Colors.Green.Lighten3,
|
||||
Colors.LightGreen.Lighten3,
|
||||
Colors.Lime.Lighten3,
|
||||
Colors.Yellow.Lighten3,
|
||||
Colors.Amber.Lighten3,
|
||||
Colors.Orange.Lighten3,
|
||||
Colors.DeepOrange.Lighten3,
|
||||
Colors.Brown.Lighten3,
|
||||
Colors.Grey.Lighten3,
|
||||
Colors.BlueGrey.Lighten3
|
||||
};
|
||||
|
||||
public static string BackgroundColor()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Elements;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
|
||||
@@ -30,13 +30,5 @@
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((Size) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Width.GetHashCode() * 397) ^ Height.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,23 @@ namespace QuestPDF.Infrastructure
|
||||
public class TextStyle
|
||||
{
|
||||
internal string Color { get; set; } = Colors.Black;
|
||||
internal string BackgroundColor { get; set; } = Colors.Transparent;
|
||||
internal string FontType { get; set; } = "Calibri";
|
||||
internal float Size { get; set; } = 12;
|
||||
internal float LineHeight { get; set; } = 1.2f;
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
|
||||
internal bool IsItalic { get; set; } = false;
|
||||
internal bool IsStroked { get; set; } = false;
|
||||
internal bool IsUnderlined { get; set; } = false;
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
private string? KeyCache { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}";
|
||||
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{IsStroked}|{IsUnderlined}";
|
||||
return KeyCache;
|
||||
}
|
||||
|
||||
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2021.5.2</Version>
|
||||
<Version>2021.8</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>New elements: Box, Grid, Canvas, EnsureSpance and Layers. Redesigned the Debug element. Added material design colors. Added list of basic fonts. Added spacing property to the Row element. Fluent API improvements and increased stability.</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>New elements: Unconstrained, Scale, ScaleHorizontal, ScaleVertical, FlipOver, FlipHorizontal, FlipVertical, Rotate, RotateLeft, RotateRight, TranslateX, TranslateY. Added support for more placeholders in the PageNumber element (current page, total number of pages, page number of any predefined location). Added support for documents with various page sizes. Various API improvements.</PackageReleaseNotes>
|
||||
<LangVersion>8</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2021.5.2 | :white_check_mark: |
|
||||
| Older | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any code vulnerabilities using GitHub Issues. We are trying to respond within 1 day and analyze reported situation as soon as possible.
|
||||
Please collaborate with us to find best solutation available.
|
||||
Once the vulnerability is recognized and fixed, the issue is closed with appropraite message.
|
||||
@@ -16,6 +16,15 @@
|
||||
|
||||
6) **Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
|
||||
|
||||
## Support QuestPDF
|
||||
|
||||
All great frameworks and libraries started from zero. Please help us to make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. It can be as easy as:
|
||||
- Giving this repository a star ⭐ so more people will know about it,
|
||||
- Observing 🤩 the library to know about each new realease,
|
||||
- Trying our sample project to see how easy it is to create an invoice 📊,
|
||||
- Sharing your thoughts 💬 with us and your colleagues,
|
||||
- Simply using it 👨💻 and suggesting new features,
|
||||
- Creating new features 🆕 for everybody.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -25,7 +34,6 @@ The library is available as a nuget package. You can install it as any other nug
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/nuget.svg" width="200px">
|
||||
</a>
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
1. [Release notes and roadmap](https://www.questpdf.com/documentation/releases.html) - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
|
||||
@@ -46,16 +54,19 @@ For tutorial, documentation and API reference, please visit [the QuestPDF docume
|
||||
Here you can find an example code showing how easy is to write and understand the fluent API:
|
||||
|
||||
```csharp
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
.PaddingHorizontal(50)
|
||||
.PaddingVertical(50)
|
||||
.Page(page =>
|
||||
{
|
||||
page.Header(ComposeHeader);
|
||||
page.Content(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber("Page {number}");
|
||||
page.MarginVertical(60);
|
||||
page.MarginHorizontal(40);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,11 +74,11 @@ void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Stack(column =>
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
{
|
||||
column.Element().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
|
||||
column.Element().Text($"Issue date: {Model.IssueDate:d}");
|
||||
column.Element().Text($"Due date: {Model.DueDate:d}");
|
||||
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
|
||||
stack.Item().Text($"Issue date: {Model.IssueDate:d}");
|
||||
stack.Item().Text($"Due date: {Model.DueDate:d}");
|
||||
});
|
||||
|
||||
row.ConstantColumn(100).Height(50).Placeholder();
|
||||
|
||||
Reference in New Issue
Block a user