Compare commits

..

6 Commits

Author SHA1 Message Date
Marcin Ziąbek e18b5170e6 Improved text run calculation 2021-05-05 11:21:53 +02:00
Marcin Ziąbek 5010b61565 Merge branch 'main' into richtext
# Conflicts:
#	QuestPDF/Elements/Stack.cs
2021-05-05 00:16:45 +02:00
Marcin Ziąbek f8d270bd2a Font measurement improvements 2021-05-04 15:33:10 +02:00
Marcin Ziąbek f884547d16 First steps with text shaper 2021-04-29 11:18:16 +02:00
Marcin Ziąbek 4a7f9385c4 Improved text positioning 2021-04-28 23:49:22 +02:00
Marcin Ziąbek f4181de3d2 Simple drawing text 2021-04-28 00:24:26 +02:00
76 changed files with 1192 additions and 1868 deletions
+237 -585
View File
@@ -1,6 +1,4 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -9,379 +7,215 @@ using SkiaSharp;
namespace QuestPDF.Examples
{
[TestFixture]
public class ElementExamples
public class ElementExamples : ExampleTestBase
{
[Test]
public void Placeholder()
//[ShowResult]
[ImageSize(200, 150)]
public void Placeholder(IContainer container)
{
RenderingTest
.Create()
.PageSize(200, 150)
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Placeholder();
});
container
.Background("#FFF")
.Padding(25)
.Placeholder();
}
[Test]
public void Decoration()
//[ShowResult]
[ImageSize(300, 300)]
public void Decoration(IContainer container)
{
RenderingTest
.Create()
.PageSize(300, 300)
.Render(container =>
container
.Background("#FFF")
.Padding(25)
.Decoration(decoration =>
{
container
.Background("#FFF")
.Padding(25)
.Decoration(decoration =>
{
decoration
.Header()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration
.Content()
.Background(Colors.Grey.Lighten3)
.Padding(10)
.ExtendVertical()
.Text(Helpers.Placeholders.LoremIpsum());
});
});
}
// [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)
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Stack(stack =>
{
stack.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
stack.Item().Row(row =>
{
row.ConstantColumn(100)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeColumn(2)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
});
});
});
}
[Test]
public void RowSpacing()
{
RenderingTest
.Create()
.PageSize(740, 200)
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Row(row =>
{
row.Spacing(20);
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
});
});
}
[Test]
public void Stack()
{
RenderingTest
.Create()
.PageSize(500, 360)
.Render(container =>
{
container
.Background("#FFF")
.Padding(15)
.Stack(stack =>
{
stack.Spacing(15);
stack.Item().Background(Colors.Grey.Medium).Height(50);
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
[Test]
public void Debug()
{
RenderingTest
.Create()
.PageSize(210, 210)
.Render(container =>
{
container
.Padding(25)
.Debug("Grid example", Colors.Blue.Medium)
.Grid(grid =>
{
grid.Columns(3);
grid.Spacing(5);
foreach (var _ in Enumerable.Range(0, 8))
grid.Item().Height(50).Placeholder();
});
});
}
[Test]
public void ElementEnd()
{
RenderingTest
.Create()
.PageSize(300, 200)
.Render(container =>
{
var text = "";
container
decoration
.Header()
.Background("#888")
.Padding(10)
.Element(x =>
{
if (string.IsNullOrWhiteSpace(text))
x.Height(10).Width(50).Background("#DDD");
else
x.Text(text);
});
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration
.Content()
.Background("#DDD")
.Padding(10)
.ExtendVertical()
.Text(Helpers.Placeholders.LoremIpsum());
});
}
[Test]
public void GridExample()
//[ShowResult]
[ImageSize(298, 421)]
public void Page(IContainer container)
{
RenderingTest
.Create()
.PageSize(400, 230)
.Render(container =>
container
.Background("#FFF")
.Padding(15)
.Page(page =>
{
var textStyle = TextStyle.Default.Size(14);
page.Header()
.Height(60)
.Background("#BBB")
.AlignCenter()
.AlignMiddle()
.Text("Header");
page.Content()
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.Text("Content");
page.Footer()
.Height(30)
.Background("#BBB")
.AlignCenter()
.AlignMiddle()
.Text("Footer");
});
}
//[ShowResult]
[ImageSize(740, 200)]
public void Row(IContainer container)
{
container
.Background("#FFF")
.Padding(20)
.Stack(stack =>
{
stack.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
stack.Item().Row(row =>
{
row.ConstantColumn(100)
.Background("#DDD")
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeColumn()
.Background("#BBB")
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeColumn(2)
.Background("#DDD")
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
});
});
}
//[ShowResult]
[ImageSize(500, 350)]
public void Column(IContainer container)
{
container
.Background("#FFF")
.Padding(15)
.Stack(column =>
{
column.Spacing(10);
column
.Item()
.Background("#999")
.Height(50);
column
.Item()
.Background("#BBB")
.Height(100);
column
.Item()
.Background("#DDD")
.Height(150);
});
}
[ShowResult]
[ImageSize(210, 210)]
public void Debug(IContainer container)
{
container
.Padding(25)
.Debug("Grid example", Colors.Blue.Medium)
.Grid(grid =>
{
grid.Columns(3);
grid.Spacing(5);
foreach (var _ in Enumerable.Range(0, 8))
grid.Item().Height(50).Placeholder();
});
}
//[ShowResult]
[ImageSize(300, 200)]
public void ElementEnd(IContainer container)
{
var text = "";
container
.Padding(15)
.AlignRight()
.Grid(grid =>
{
grid.VerticalSpacing(10);
grid.HorizontalSpacing(10);
grid.AlignCenter();
grid.Columns(10); // 12 by default
grid.Item(6).Background(Colors.Blue.Lighten1).Height(50);
grid.Item(4).Background(Colors.Blue.Lighten3).Height(50);
grid.Item(2).Background(Colors.Teal.Lighten1).Height(70);
grid.Item(3).Background(Colors.Teal.Lighten2).Height(70);
grid.Item(5).Background(Colors.Teal.Lighten3).Height(70);
grid.Item(2).Background(Colors.Green.Lighten1).Height(50);
grid.Item(2).Background(Colors.Green.Lighten2).Height(50);
grid.Item(2).Background(Colors.Green.Lighten3).Height(50);
});
container
.Padding(10)
.Element(x =>
{
if (string.IsNullOrWhiteSpace(text))
x.Height(10).Width(50).Background("#DDD");
else
x.Text(text);
});
}
[Test]
public void Canvas()
//[ShowResult]
[ImageSize(300, 200)]
public void GridExample(IContainer container)
{
RenderingTest
.Create()
.PageSize(300, 200)
.Render(container =>
var textStyle = TextStyle.Default.Size(14);
container
.Padding(20)
.AlignRight()
.Grid(grid =>
{
container
.Background("#FFF")
.Padding(25)
.Canvas((canvas, size) =>
{
using var paint = new SKPaint
{
Color = SKColors.Red,
StrokeWidth = 10,
IsStroke = true
};
// move origin to the center of the available space
canvas.Translate(size.Width / 2, size.Height / 2);
grid.VerticalSpacing(20);
grid.HorizontalSpacing(10);
grid.Columns(12);
grid.Item(8).Background("#DDD").Height(50).Padding(5).Text("This is a short text", textStyle);
grid.Item(4).Background("#BBB").Padding(5).Text("Showing how to...", textStyle);
grid.Item(2).Background("#999").Height(50);
grid.Item(4).Background("#AAA").Border(2).BorderColor("#666").Padding(5).Text("...generate", textStyle);
grid.Item(6).Background("#CCC").Padding(5).Text("simple grids", textStyle.Size(18).Bold());
grid.Item(8).Background("#DDD").Height(50);
});
}
//[ShowResult]
[ImageSize(300, 300)]
public void RandomColorMatrix(IContainer container)
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(5);
// draw a circle
canvas.DrawCircle(0, 0, 50, paint);
});
Enumerable
.Range(0, 25)
.Select(x => Placeholders.BackgroundColor())
.ToList()
.ForEach(x => grid.Item().Height(50).Background(x));
});
}
[Test]
public void LayersExample()
{
RenderingTest
.Create()
.PageSize(400, 250)
.Render(container =>
{
container
.Padding(25)
.Layers(layers =>
{
// layer below main content
layers
.Layer()
.Height(100)
.Width(100)
.Background(Colors.Grey.Lighten3);
layers
.PrimaryLayer()
.Padding(25)
.Stack(stack =>
{
stack.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7))
stack.Item().Text(Placeholders.Sentence());
});
// layer above the main content
layers
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
layers
.Layer()
.AlignBottom()
.PageNumber("Page {pdf:currentPage}", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
});
});
}
// [Test]
// public void EnsureSpace()
// {
// RenderingTest
// .Create()
// .PageSize(300, 400)
// .Render(container =>
// {
// container
// .Padding(50)
// .Page(page =>
// {
// page.Header().PageNumber("Page {pdf:currentPage}");
//
// page.Content().Height(300).Stack(content =>
// {
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
//
// content.Item().EnsureSpace(100).Stack(stack =>
// {
// stack.Spacing(10);
//
// foreach (var _ in Enumerable.Range(0, 4))
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
// });
// });
// });
// });
// }
[Test]
public void RandomColorMatrix()
{
RenderingTest
.Create()
.PageSize(300, 300)
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(5);
Enumerable
.Range(0, 25)
.Select(x => Placeholders.BackgroundColor())
.ToList()
.ForEach(x => grid.Item().Height(50).Background(x));
});
});
}
[Test]
public void DefinedColors()
//[ShowResult]
[ImageSize(450, 150)]
public void DefinedColors(IContainer container)
{
var colors = new[]
{
@@ -404,24 +238,19 @@ namespace QuestPDF.Examples
Colors.Green.Accent4,
};
RenderingTest
.Create()
.PageSize(450, 150)
.Render(container =>
container
.Padding(25)
.Height(100)
.Row(row =>
{
container
.Padding(25)
.Height(100)
.Row(row =>
{
foreach (var color in colors)
row.RelativeColumn().Background(color);
});
foreach (var color in colors)
row.RelativeColumn().Background(color);
});
}
[Test]
public void DefinedFonts()
//[ShowResult]
[ImageSize(500, 175)]
public void DefinedFonts(IContainer container)
{
var fonts = new[]
{
@@ -436,231 +265,54 @@ namespace QuestPDF.Examples
Fonts.ComicSans
};
RenderingTest
.Create()
.PageSize(500, 175)
.Render(container =>
container
.Padding(25)
.Grid(grid =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(3);
grid.Columns(3);
foreach (var font in fonts)
{
grid.Item()
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
}
});
foreach (var font in fonts)
{
grid.Item()
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
}
});
}
[Test]
public void Layers()
//[ShowResult]
[ImageSize(300, 300)]
public void Layers(IContainer container)
{
RenderingTest
.Create()
.PageSize(300, 300)
.Render(container =>
container
.Background("#FFF")
.Padding(25)
.Layers(layers =>
{
container
.Background("#FFF")
.Padding(25)
.Layers(layers =>
layers.Layer().Text("Something else");
layers.PrimaryLayer().Stack(stack =>
{
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
{
using var paint = new SKPaint
{
layers.Layer().Text("Something else");
layers.PrimaryLayer().Stack(stack =>
{
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
{
using var paint = new SKPaint
{
Color = SKColors.Red,
StrokeWidth = 5
};
Color = SKColors.Red,
StrokeWidth = 5
};
canvas.Translate(size.Width / 2, size.Height / 2);
canvas.DrawCircle(0, 0, 50, paint);
});
canvas.Translate(size.Width / 2, size.Height / 2);
canvas.DrawCircle(0, 0, 50, paint);
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
});
});
}
[Test]
public void Box()
{
RenderingTest
.Create()
.PageSize(300, 150)
.Render(container =>
{
container
.Background("#FFF")
.Padding(15)
.Border(4)
.BorderColor(Colors.Blue.Medium)
//.Box()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
});
}
[Test]
public void Scale()
{
RenderingTest
.Create()
.PageSize(400, 400)
.Render(container =>
{
var style = TextStyle.Default.Size(20);
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
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);
});
});
}
[Test]
public void Translate()
{
RenderingTest
.Create()
.PageSize(500, 300)
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Box()
.Background(Colors.Green.Lighten4)
.Padding(5)
.TranslateX(15)
.TranslateY(15)
.Text("Text outside of bounds");
});
}
[Test]
public void Rotate()
{
RenderingTest
.Create()
.PageSize(450, 450)
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
grid.Columns(2);
grid.Spacing(10);
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Background(Colors.Grey.Lighten3)
.Padding(10)
//.Box()
.Element(element =>
{
foreach (var x in Enumerable.Range(0, turns))
element = element.RotateRight();
return element;
})
.Box()
.Background(Colors.Grey.Lighten1)
.Text($"Rotated {turns * 90} degrees.", TextStyle.Default.Size(14));
}
});
});
}
[Test]
public void Flip()
{
RenderingTest
.Create()
.PageSize(450, 450)
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Grid(grid =>
{
grid.Columns(2);
grid.Spacing(10);
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(200)
.Height(200)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Element(element =>
{
if (turns == 1 || turns == 2)
element = element.FlipX();
if (turns == 2 || turns == 3)
element = element.FlipY();
return element;
})
.Text($"Flipped.", TextStyle.Default.Size(14));
}
});
});
}
[Test]
public void RotateInTable()
{
RenderingTest
.Create()
.PageSize(200, 200)
.Render(container =>
{
container
.Border(2)
.Row(row =>
{
row.ConstantColumn(25)
.Border(1)
.RotateLeft()
.AlignCenter()
.AlignMiddle()
.Text("Sample text");
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
});
}
}
@@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples.Engine
{
[TestFixture]
public class ExampleTestBase
{
private readonly Size DefaultImageSize = new Size(400, 300);
private const string ResultPath = "Result";
[SetUp]
public void Setup()
{
if (Directory.Exists(ResultPath))
Directory.Delete(ResultPath, true);
Directory.CreateDirectory(ResultPath);
}
[Test]
public void RunTest()
{
GetType()
.GetMethods()
.Where(IsExampleMethod)
.ToList()
.ForEach(HandleExample);
}
private bool IsExampleMethod(MethodInfo method)
{
var parameters = method.GetParameters();
return parameters.Length == 1 && parameters.First().ParameterType == typeof(IContainer);
}
private T GetAttribute<T>(MethodInfo methodInfo) where T : Attribute
{
return methodInfo.GetCustomAttributes().FirstOrDefault(y => y is T) as T;
}
private void HandleExample(MethodInfo methodInfo)
{
var size = GetAttribute<ImageSizeAttribute>(methodInfo)?.Size ?? DefaultImageSize;
var fileName = GetAttribute<FileNameAttribute>(methodInfo)?.FileName ?? methodInfo.Name;
var showResult = GetAttribute<ShowResultAttribute>(methodInfo) != null;
var shouldIgnore = GetAttribute<IgnoreAttribute>(methodInfo) != null;
if (shouldIgnore)
return;
var container = new Container();
methodInfo.Invoke(this, new object[] {container});
Func<int, string> fileNameSchema = i => $"{fileName.ToLower()}-${i}.png";
try
{
var document = new SimpleDocument(container, size);
document.GenerateImages(fileNameSchema);
}
catch (Exception e)
{
throw new Exception($"Cannot perform test {fileName}", e);
}
if (showResult)
Process.Start("explorer", fileNameSchema(0));
}
private byte[] RenderPage(Element element, Size size)
{
// scale the result so it is more readable
const float scalingFactor = 2;
var imageInfo = new SKImageInfo((int)(size.Width * scalingFactor), (int)(size.Height * scalingFactor));
using var surface = SKSurface.Create(imageInfo);
surface.Canvas.Scale(scalingFactor);
var canvas = new Drawing.Canvas(surface.Canvas);
element?.Draw(canvas, size);
surface.Canvas.Save();
return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
}
}
}
@@ -0,0 +1,14 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class FileNameAttribute : Attribute
{
public string FileName { get; }
public FileNameAttribute(string fileName)
{
FileName = fileName;
}
}
}
@@ -0,0 +1,9 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class IgnoreAttribute : Attribute
{
}
}
@@ -0,0 +1,19 @@
using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public class ImageSizeAttribute : Attribute
{
private int Width { get; }
private int Height { get; }
public Size Size => new Size(Width, Height);
public ImageSizeAttribute(int width, int height)
{
Width = width;
Height = height;
}
}
}
-51
View File
@@ -1,51 +0,0 @@
using System;
using System.Diagnostics;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public class RenderingTest
{
private string FileNamePrefix = "test";
private Size Size { get; set; }
private RenderingTest()
{
}
public static RenderingTest Create()
{
return new RenderingTest();
}
public RenderingTest FileName(string fileName)
{
FileNamePrefix = fileName;
return this;
}
public RenderingTest PageSize(int width, int height)
{
Size = new Size(width, height);
return this;
}
public void Render(Action<IContainer> content)
{
var container = new Container();
content(container);
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
var document = new SimpleDocument(container, Size);
document.GenerateImages(fileNameSchema);
Process.Start("explorer", fileNameSchema(0));
}
}
}
@@ -0,0 +1,9 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class ShowResultAttribute : Attribute
{
}
}
+5 -12
View File
@@ -1,5 +1,4 @@
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -8,8 +7,6 @@ namespace QuestPDF.Examples.Engine
{
public class SimpleDocument : IDocument
{
public const int ImageScalingFactor = 2;
private IContainer Container { get; }
private Size Size { get; }
@@ -23,18 +20,14 @@ namespace QuestPDF.Examples.Engine
{
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = 10
RasterDpi = PageSizes.PointsPerInch * 2,
Size = Size
};
}
public void Compose(IDocumentContainer container)
public void Compose(IContainer container)
{
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Element(Container as Container);
});
container.Background("#FFF").Element(Container.Child);
}
}
}
+13 -19
View File
@@ -1,4 +1,3 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -20,30 +19,25 @@ namespace QuestPDF.Examples
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
}
public class FrameExample
public class FrameExample: ExampleTestBase
{
[Test]
[ImageSize(550, 400)]
[ShowResult]
public void Frame(IContainer container)
{
RenderingTest
.Create()
.PageSize(550, 400)
.Render(container =>
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
{
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
}
}
+16 -22
View File
@@ -1,5 +1,4 @@
using System.Net;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@@ -28,32 +27,27 @@ namespace QuestPDF.Examples
}
}
public class LoremPicsumExample
public class LoremPicsumExample : ExampleTestBase
{
[Test]
public void LoremPicsum()
[ShowResult]
[ImageSize(350, 280)]
public void LoremPicsum(IContainer container)
{
RenderingTest
.Create()
.PageSize(350, 280)
.Render(container =>
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
{
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
{
column.Spacing(10);
column.Spacing(10);
column
.Item()
.Component(new LoremPicsum(true));
column
.Item()
.Component(new LoremPicsum(true));
column
.Item()
.AlignRight()
.Text("From Lorem Picsum");
});
column
.Item()
.AlignRight()
.Text("From Lorem Picsum");
});
}
}
+99 -128
View File
@@ -1,161 +1,132 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class Examples
public class Examples : ExampleTestBase
{
[Test]
public void Padding()
public void Padding(IContainer container)
{
RenderingTest
.Create()
.PageSize(200, 150)
.Render(container =>
{
container
.Background("#FDD")
.Padding(50)
container
.Background("#FDD")
.Padding(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.Background("#77F")
.PaddingHorizontal(50)
.Background("#444");
});
.Background("#444");
}
[Test]
public void Border()
public void Border(IContainer container)
{
RenderingTest
.Create()
.PageSize(200, 150)
.Render(container =>
{
container
.Background("#EEE")
.Padding(25)
container
.Background("#EEE")
.Padding(25)
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
}
public void Alignment(IContainer container)
{
container
.Stack(column =>
{
column
.Item()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#444");
column
.Item()
.Height(100)
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#222");
column
.Item()
.Height(100)
.Background("#BBB")
.AlignRight()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#000");
});
}
[Test]
public void Alignment()
public void Expand(IContainer container)
{
RenderingTest
.Create()
.PageSize(200, 150)
.Render(container =>
container
.Stack(column =>
{
container
.Stack(column =>
column
.Item()
.Height(150)
.Row(row =>
{
column
.Item()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
row.RelativeColumn()
.Extend()
.Background("FFF")
.Width(50)
.Height(50)
.Background("#444");
column
.Item()
.Height(100)
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Width(50)
.Height(50)
.Background("#222");
column
.Item()
.Height(100)
.Background("#BBB")
.AlignRight()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#000");
.ExtendHorizontal()
.Background("444");
});
});
}
[Test]
public void Expand()
{
RenderingTest
.Create()
.PageSize(200, 150)
.Render(container =>
{
container
.Stack(column =>
{
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("FFF")
.Height(50)
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.ExtendHorizontal()
.Background("444");
});
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("BBB")
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("BBB")
.ExtendVertical()
.Width(50)
.Background("444");
.ExtendVertical()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
row.RelativeColumn()
.Extend()
.Background("BBB")
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
});
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
});
});
}
+212
View File
@@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class TextExample : ExampleTestBase
{
[ShowResult]
[ImageSize(1400, 600)]
public void OldText(IContainer container)
{
var fonts = new[]
{
Fonts.Arial,
Fonts.Calibri,
Fonts.Cambria,
Fonts.Candara,
Fonts.ComicSans,
Fonts.Consolas,
Fonts.Corbel,
Fonts.Courier,
Fonts.CourierNew,
Fonts.Georgia,
Fonts.Impact,
Fonts.LucidaConsole,
Fonts.SegoeSD,
Fonts.SegoeUI,
Fonts.Tahoma,
Fonts.TimesNewRoman,
Fonts.TimesRoman,
Fonts.Trebuchet,
Fonts.Verdana,
};
container
.Padding(50)
.Grid(stack =>
{
stack.Spacing(10);
stack.Columns(2);
foreach (var font in fonts)
{
stack
.Element()
.Box()
.Background(Placeholders.BackgroundColor())
.Text($"Lorem ipsum dolor sit amet {font}", TextStyle.Default.Size(24).FontType(font));
}
});
}
//[ShowResult]
[ImageSize(1400, 800)]
public void Test(IContainer container)
{
List<TextElement> Lorem()
{
return new List<TextElement>
{
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "Podstawowy łaciński Tabela znaków Unicode"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "✔"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "🥛"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "🧀"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "❤🚵‍♀️"
},
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "Lorem ipsum "
},
new TextElement()
{
Style = TextStyle.Default.Size(24).BackgroundColor(Placeholders.BackgroundColor()),
Text = " dolor "
},
new TextElement()
{
Style = TextStyle.Default.Size(64).BackgroundColor(Placeholders.BackgroundColor()),
Text = " sijt "
},
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = " amet"
}
};
}
Func<List<TextElement>> Source = () => Split(RandomText());
container
.Padding(50)
.Box().Border(1).Stack(stack =>
{
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Lorem()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
});
}
List<TextElement> RandomText()
{
var sizes = new[] { 24, 32, 48, 64};
return Placeholders
.Sentence()
.Split(" ")
.Select(x => new TextElement
{
Text = $"{x} ",
Style = TextStyle
.Default
//.Size(sizes[Placeholders.Random.Next(0, 3)])
.Size(24)
.BackgroundColor(Placeholders.BackgroundColor())
//.LineHeight((float)Placeholders.Random.NextDouble() / 2 + 1f)
.LineHeight(1.2f)
})
.ToList();
}
List<TextElement> Split(List<TextElement> elements)
{
return elements
.SelectMany(x => x
.Text
.Select(y => new TextElement
{
Style = x.Style,
Text = y.ToString()
}))
.ToList();
}
}
}
@@ -18,34 +18,21 @@ namespace QuestPDF.ReportSample.Layouts
{
return new DocumentMetadata()
{
Title = Model.Title
Title = Model.Title,
Size = PageSizes.A4
};
}
public void Compose(IDocumentContainer container)
public void Compose(IContainer 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}");
// })
.PaddingVertical(40)
.PaddingHorizontal(50)
.Page(page =>
{
page.MarginVertical(40);
page.MarginHorizontal(50);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().PageNumber();
page.Footer().AlignCenter().PageNumber("Page {number}");
});
}
@@ -43,7 +43,6 @@ 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());
});
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.UnitTests
.For(x => new PageBreak())
.MeasureElement(new Size(400, 300))
.CheckMeasureResult(new PartialRender(Size.Zero))
.CheckMeasureResult(new PartialRender(400, 300))
.DrawElement(new Size(400, 300))
.CheckDrawResult()
+3 -3
View File
@@ -30,10 +30,10 @@ namespace QuestPDF.UnitTests
Child = child.Object
};
element.Draw(Size.Zero);
element.Draw(Size.Zero);
element.Draw(null, Size.Zero);
element.Draw(null, Size.Zero);
child.Verify(x => x.Draw(It.IsAny<Size>()), Times.Once);
child.Verify(x => x.Draw(It.IsAny<ICanvas>(), It.IsAny<Size>()), Times.Once);
}
[Test]
+1 -2
View File
@@ -10,12 +10,11 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawExternalLink(string url, Size size)
{
throw new NotImplementedException();
+1 -1
View File
@@ -11,6 +11,6 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<Size> DrawFunc { get; set; }
internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace);
internal override void Draw(Size availableSpace) => DrawFunc(availableSpace);
internal override void Draw(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace);
}
}
+1 -4
View File
@@ -205,8 +205,6 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(ISpacePlan expected)
{
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput);
Assert.AreEqual(expected.GetType(), actual.GetType());
@@ -225,8 +223,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
Element.Draw(Canvas, OperationInput);
return this;
}
+59
View File
@@ -0,0 +1,59 @@
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class Canvas : ICanvas
{
internal SKCanvas SkiaCanvas { get; }
public Canvas(SKCanvas skiaCanvas)
{
SkiaCanvas = skiaCanvas;
}
~Canvas()
{
SkiaCanvas.Dispose();
}
public void Translate(Position vector)
{
SkiaCanvas.Translate(vector.X, vector.Y);
}
public void DrawRectangle(Position vector, Size size, string color)
{
if (size.Width < Size.Epsilon || size.Height < Size.Epsilon)
return;
var paint = color.ColorToPaint();
SkiaCanvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
}
public void DrawText(string text, Position vector, TextStyle style)
{
SkiaCanvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
}
public void DrawImage(SKImage image, Position vector, Size size)
{
SkiaCanvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawExternalLink(string url, Size size)
{
SkiaCanvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawLocationLink(string locationName, Size size)
{
SkiaCanvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
}
public void DrawLocation(string locationName)
{
SkiaCanvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ namespace QuestPDF.Drawing
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,
-36
View File
@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
internal class DocumentContainer : IDocumentContainer
{
internal List<IComponent> Pages { get; set; } = new List<IComponent>();
internal Container Compose()
{
var container = new Container();
container
.Stack(stack =>
{
Pages
.SelectMany(x => new List<Action>()
{
() => stack.Item().PageBreak(),
() => stack.Item().Component(x)
})
.Skip(1)
.ToList()
.ForEach(x => x());
});
return container;
}
}
}
+84 -49
View File
@@ -1,10 +1,8 @@
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;
@@ -16,78 +14,115 @@ namespace QuestPDF.Drawing
{
internal static void GeneratePdf(Stream stream, IDocument document)
{
var content = ElementExtensions.Create(document.Compose);
var metadata = document.GetMetadata();
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document);
}
internal static ICollection<byte[]> GenerateImages(IDocument document)
{
var metadata = document.GetMetadata();
var canvas = new ImageCanvas(metadata);
RenderDocument(canvas, document);
return canvas.Images;
}
private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
where TCanvas : ICanvas, IRenderingCanvas
{
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
var metadata = document.GetMetadata();
var pageContext = new PageContext();
RenderPass(pageContext, new FreeCanvas(), content, metadata);
RenderPass(pageContext, canvas, content, metadata);
}
private static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata)
where TCanvas : ICanvas, IRenderingCanvas
{
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
canvas.BeginDocument();
var currentPage = 1;
using var pdf = SKDocument.CreatePdf(stream, MapMetadata(metadata));
var totalPages = 1;
while(true)
{
pageContext.SetPageNumber(currentPage);
var spacePlan = content.Measure(Size.Max) as Size;
var spacePlan = content.Measure(metadata.Size);
if (spacePlan == null)
break;
using var skiaCanvas = pdf.BeginPage(metadata.Size.Width, metadata.Size.Height);
var canvas = new Canvas(skiaCanvas);
try
{
canvas.BeginPage(spacePlan);
content.Draw(spacePlan);
content.Draw(canvas, metadata.Size);
}
catch (Exception exception)
{
canvas.EndDocument();
pdf.Close();
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
}
canvas.EndPage();
pdf.EndPage();
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
{
canvas.EndDocument();
pdf.Close();
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
if (spacePlan is FullRender)
break;
currentPage++;
totalPages++;
}
canvas.EndDocument();
pdf.Close();
}
internal static IEnumerable<byte[]> GenerateImages(IDocument document)
{
var content = ElementExtensions.Create(document.Compose);
var metadata = document.GetMetadata();
var totalPages = 1;
while (true)
{
var spacePlan = content.Measure(metadata.Size);
byte[] result;
try
{
result = RenderPage(content);
}
catch (Exception exception)
{
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
}
yield return result;
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
{
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
if (spacePlan is FullRender)
break;
totalPages++;
}
byte[] RenderPage(Element element)
{
// scale the result so it is more readable
var scalingFactor = metadata.RasterDpi / (float) PageSizes.PointsPerInch;
var imageInfo = new SKImageInfo((int) (metadata.Size.Width * scalingFactor), (int) (metadata.Size.Height * scalingFactor));
using var surface = SKSurface.Create(imageInfo);
surface.Canvas.Scale(scalingFactor);
var canvas = new Canvas(surface.Canvas);
element?.Draw(canvas, metadata.Size);
surface.Canvas.Save();
return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
}
}
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{
return new SKDocumentPdfMetadata
{
Title = metadata.Title,
Author = metadata.Author,
Subject = metadata.Subject,
Keywords = metadata.Keywords,
Creator = metadata.Creator,
Producer = metadata.Producer,
Creation = metadata.CreationDate,
Modified = metadata.ModifiedDate,
RasterDpi = metadata.RasterDpi,
EncodingQuality = metadata.ImageQuality,
PdfA = metadata.PdfA
};
}
}
}
+1
View File
@@ -6,6 +6,7 @@ namespace QuestPDF.Drawing
{
public class DocumentMetadata
{
public Size Size { get; set; } = PageSizes.A4;
public int ImageQuality { get; set; } = 101;
public int RasterDpi { get; set; } = 72;
public bool PdfA { get; set; }
-71
View File
@@ -1,71 +0,0 @@
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class FreeCanvas : ICanvas, IRenderingCanvas
{
#region IRenderingCanvas
public void BeginDocument()
{
}
public void EndDocument()
{
}
public void BeginPage(Size size)
{
}
public void EndPage()
{
}
#endregion
#region ICanvas
public void Translate(Position vector)
{
}
public void DrawRectangle(Position vector, Size size, string color)
{
}
public void DrawText(string text, Position position, TextStyle style)
{
}
public void DrawImage(SKImage image, Position position, Size size)
{
}
public void DrawExternalLink(string url, Size size)
{
}
public void DrawLocationLink(string locationName, Size size)
{
}
public void DrawLocation(string locationName)
{
}
#endregion
}
}
-53
View File
@@ -1,53 +0,0 @@
using System.Collections.Generic;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class ImageCanvas : SkiaCanvasBase
{
private DocumentMetadata Metadata { get; }
private SKSurface Surface { get; set; }
internal ICollection<byte[]> Images { get; } = new List<byte[]>();
public ImageCanvas(DocumentMetadata metadata)
{
Metadata = metadata;
}
public override void BeginDocument()
{
}
public override void EndDocument()
{
Canvas?.Dispose();
Surface?.Dispose();
}
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);
Canvas = Surface.Canvas;
Canvas.Scale(scalingFactor);
}
public override void EndPage()
{
Canvas.Save();
var image = Surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
Images.Add(image);
Canvas.Dispose();
Surface.Dispose();
}
}
}
-65
View File
@@ -1,65 +0,0 @@
using System.IO;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class PdfCanvas : SkiaCanvasBase
{
private SKDocument Document { get; }
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
{
Document = SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
}
~PdfCanvas()
{
Document.Dispose();
}
public override void BeginDocument()
{
}
public override void EndDocument()
{
Canvas.Dispose();
Document.Close();
Document.Dispose();
}
public override void BeginPage(Size size)
{
Canvas = Document.BeginPage(size.Width, size.Height);
}
public override void EndPage()
{
Document.EndPage();
Canvas.Dispose();
}
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{
return new SKDocumentPdfMetadata
{
Title = metadata.Title,
Author = metadata.Author,
Subject = metadata.Subject,
Keywords = metadata.Keywords,
Creator = metadata.Creator,
Producer = metadata.Producer,
Creation = metadata.CreationDate,
Modified = metadata.ModifiedDate,
RasterDpi = metadata.RasterDpi,
EncodingQuality = metadata.ImageQuality,
PdfA = metadata.PdfA
};
}
}
}
-58
View File
@@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal abstract class SkiaCanvasBase : ICanvas, IRenderingCanvas
{
internal SKCanvas Canvas { get; set; }
public abstract void BeginDocument();
public abstract void EndDocument();
public abstract void BeginPage(Size size);
public abstract void EndPage();
public void Translate(Position vector)
{
Canvas.Translate(vector.X, vector.Y);
}
public void DrawRectangle(Position vector, Size size, string color)
{
if (size.Width < Size.Epsilon || size.Height < Size.Epsilon)
return;
var paint = color.ColorToPaint();
Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
}
public void DrawText(string text, Position vector, TextStyle style)
{
Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
}
public void DrawImage(SKImage image, Position vector, Size size)
{
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawExternalLink(string url, Size size)
{
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawLocationLink(string locationName, Size size)
{
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
}
public void DrawLocation(string locationName)
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
}
}
+4 -4
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.Elements
public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top;
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
if (Child == null)
return;
@@ -21,9 +21,9 @@ namespace QuestPDF.Elements
var top = GetTopOffset(availableSpace, childSize);
var left = GetLeftOffset(availableSpace, childSize);
Canvas.Translate(new Position(left, top));
Child.Draw(childSize);
Canvas.Translate(new Position(-left, -top));
canvas.Translate(new Position(left, top));
Child.Draw(canvas, childSize);
canvas.Translate(new Position(-left, -top));
}
private float GetTopOffset(Size availableSpace, Size childSize)
+2 -2
View File
@@ -36,13 +36,13 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
if (Child == null)
return;
var size = GetTargetSize(availableSpace);
Child?.Draw(size);
Child?.Draw(canvas, size);
}
private Size GetTargetSize(Size availableSpace)
+3 -3
View File
@@ -7,10 +7,10 @@ namespace QuestPDF.Elements
{
public string Color { get; set; } = Colors.Black;
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Child?.Draw(availableSpace);
canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Child?.Draw(canvas, availableSpace);
}
}
}
+6 -6
View File
@@ -12,26 +12,26 @@ namespace QuestPDF.Elements
public float Bottom { get; set; }
public float Left { get; set; }
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Child?.Draw(availableSpace);
Child?.Draw(canvas, availableSpace);
Canvas.DrawRectangle(
canvas.DrawRectangle(
new Position(-Left/2, -Top/2),
new Size(availableSpace.Width + Left/2 + Right/2, Top),
Color);
Canvas.DrawRectangle(
canvas.DrawRectangle(
new Position(-Left/2, -Top/2),
new Size(Left, availableSpace.Height + Top/2 + Bottom/2),
Color);
Canvas.DrawRectangle(
canvas.DrawRectangle(
new Position(-Left/2, availableSpace.Height-Bottom/2),
new Size(availableSpace.Width + Left/2 + Right/2, Bottom),
Color);
Canvas.DrawRectangle(
canvas.DrawRectangle(
new Position(availableSpace.Width-Right/2, -Top/2),
new Size(Right, availableSpace.Height + Top/2 + Bottom/2),
Color);
+2 -2
View File
@@ -4,14 +4,14 @@ namespace QuestPDF.Elements
{
internal class Box : ContainerElement
{
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
Child?.Draw(targetSize);
Child?.Draw(canvas, targetSize);
}
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
if (Handler == null || skiaCanvas == null)
return;
+2 -2
View File
@@ -44,13 +44,13 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var available = new Size(
MathHelpers.Min(MaxWidth, availableSpace.Width),
MathHelpers.Min(MaxHeight, availableSpace.Height));
Child?.Draw(available);
Child?.Draw(canvas, available);
}
}
+6 -14
View File
@@ -15,15 +15,7 @@ namespace QuestPDF.Elements
{
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public DecorationType Type { get; set; }
internal override ISpacePlan Measure(Size availableSpace)
{
@@ -50,22 +42,22 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var decorationSize = DecorationElement?.Measure(availableSpace) as Size ?? Size.Zero;
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
Action drawDecoration = () => DecorationElement?.Draw(canvas, new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height));
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
first();
Canvas.Translate(new Position(0, translateHeight));
canvas.Translate(new Position(0, translateHeight));
second();
Canvas.Translate(new Position(0, -translateHeight));
canvas.Translate(new Position(0, -translateHeight));
}
}
+2 -3
View File
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace.Width, availableSpace.Height);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var imageData = Source?.Invoke(availableSpace);
@@ -26,8 +26,7 @@ namespace QuestPDF.Elements
InternalImage = SKImage.FromEncodedData(imageData)
};
imageElement.Initialize(PageContext, Canvas);
imageElement.Draw(availableSpace);
imageElement.Draw(canvas, availableSpace);
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace QuestPDF.Elements
return new FullRender(Size.Zero);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
}
+3 -3
View File
@@ -6,15 +6,15 @@ namespace QuestPDF.Elements
{
public string Url { get; set; } = "https://www.questpdf.com";
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
Canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(availableSpace);
canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(canvas, availableSpace);
}
}
}
+2 -2
View File
@@ -18,12 +18,12 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
if (InternalImage == null)
return;
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
}
}
}
+3 -3
View File
@@ -6,15 +6,15 @@ namespace QuestPDF.Elements
{
public string LocationName { get; set; }
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
Canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(availableSpace);
canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(canvas, availableSpace);
}
}
}
+4 -17
View File
@@ -2,27 +2,14 @@
namespace QuestPDF.Elements
{
internal class InternalLocation : ContainerElement, IStateResettable
internal class InternalLocation : ContainerElement
{
public string LocationName { get; set; }
private bool IsRendered { get; set; }
public void ResetState()
internal override void Draw(ICanvas canvas, Size availableSpace)
{
IsRendered = false;
}
internal override void Draw(Size availableSpace)
{
if (!IsRendered)
{
PageContext.SetLocationPage(LocationName);
Canvas.DrawLocation(LocationName);
IsRendered = true;
}
Child?.Draw(availableSpace);
canvas.DrawLocation(LocationName);
Child?.Draw(canvas, availableSpace);
}
}
}
+3 -10
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
@@ -15,12 +14,6 @@ namespace QuestPDF.Elements
{
public List<Layer> Children { get; set; } = new List<Layer>();
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Children
@@ -28,12 +21,12 @@ namespace QuestPDF.Elements
.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Children
.Where(x => x.Measure(availableSpace) is Size)
.ToList()
.ForEach(x => x.Draw(availableSpace));
.ForEach(x => x.Draw(canvas, availableSpace));
}
}
}
+4 -4
View File
@@ -39,16 +39,16 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
if (Child == null)
return;
var internalSpace = InternalSpace(availableSpace);
Canvas.Translate(new Position(Left, Top));
Child?.Draw(internalSpace);
Canvas.Translate(new Position(-Left, -Top));
canvas.Translate(new Position(Left, Top));
Child?.Draw(canvas, internalSpace);
canvas.Translate(new Position(-Left, -Top));
}
private Size InternalSpace(Size availableSpace)
+6 -28
View File
@@ -1,44 +1,22 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Page : IComponent
{
public Size MinSize { get; set; } = PageSizes.A4;
public Size MaxSize { get; set; } = PageSizes.A4;
public float MarginLeft { get; set; }
public float MarginRight { get; set; }
public float MarginTop { get; set; }
public float MarginBottom { get; set; }
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container)
{
container
.MinWidth(MinSize.Width)
.MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height)
.PaddingLeft(MarginLeft)
.PaddingRight(MarginRight)
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.Decoration(decoration =>
{
decoration.Header().Element(Header);
decoration.Content().Extend().Element(Content);
decoration.Footer().Element(Footer);
});
container.Decoration(decoration =>
{
decoration.Header().Element(Header);
decoration.Content().Extend().Element(Content);
decoration.Footer().Element(Footer);
});
}
}
}
+5 -11
View File
@@ -1,27 +1,21 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class PageBreak : Element, IStateResettable
internal class PageBreak : Element
{
private bool IsRendered { get; set; }
public void ResetState()
{
IsRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace)
{
if (IsRendered)
return new FullRender(Size.Zero);
return new PartialRender(Size.Zero);
return new PartialRender(availableSpace.Width, availableSpace.Height);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
IsRendered = true;
}
+17 -30
View File
@@ -1,5 +1,3 @@
using System;
using System.Text.RegularExpressions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
@@ -9,44 +7,33 @@ 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);
}
public TextStyle? TextStyle { get; set; }
private Text? TextElement { get; set; }
private int Number { get; set; } = 1;
internal override ISpacePlan Measure(Size availableSpace)
{
TextElement.Value = GetText();
InitializeTextElement();
TextElement.Value = TextFormat.Replace("{number}", Number.ToString());
return TextElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
TextElement.Value = GetText();
TextElement.Draw(availableSpace);
InitializeTextElement();
TextElement.Draw(canvas, availableSpace);
Number++;
}
private string GetText()
private void InitializeTextElement()
{
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;
TextElement ??= new Text()
{
Style = TextStyle
};
}
}
}
-59
View File
@@ -1,59 +0,0 @@
using System;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
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;
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);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+11 -28
View File
@@ -11,9 +11,9 @@ namespace QuestPDF.Elements
{
public float Width { get; set; } = 1;
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Child?.Draw(availableSpace);
Child?.Draw(canvas, availableSpace);
}
}
@@ -38,14 +38,6 @@ namespace QuestPDF.Elements
internal Element Left { get; set; }
internal Element Right { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height)) as Size;
@@ -69,16 +61,16 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
Left.Draw(new Size(leftWidth, availableSpace.Height));
Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
canvas.Translate(new Position(leftWidth, 0));
Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
canvas.Translate(new Position(-leftWidth, 0));
}
}
@@ -86,21 +78,15 @@ namespace QuestPDF.Elements
{
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
public float Spacing { get; set; } = 0;
internal override void HandleVisitor(Action<Element?> visit)
{
Children.ToList().ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Compose(availableSpace.Width).Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Compose(availableSpace.Width).Draw(availableSpace);
Compose(availableSpace.Width).Draw(canvas, availableSpace);
}
#region structure
@@ -109,10 +95,7 @@ namespace QuestPDF.Elements
{
var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth);
var tree = BuildTree(rowElements.ToArray());
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
return tree;
return BuildTree(rowElements.ToArray());
}
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
-60
View File
@@ -1,60 +0,0 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Scale : ContainerElement
{
public float ScaleX { get; set; } = 1;
public float ScaleY { get; set; } = 1;
internal override ISpacePlan Measure(Size availableSpace)
{
var targetSpace = new Size(
Math.Abs(availableSpace.Width / ScaleX),
Math.Abs(availableSpace.Height / ScaleY));
var measure = base.Measure(targetSpace) as Size;
if (measure == null)
return new Wrap();
var targetSize = new Size(
Math.Abs(measure.Width * ScaleX),
Math.Abs(measure.Height * ScaleY));
if (measure is PartialRender)
return new PartialRender(targetSize);
if (measure is FullRender)
return new FullRender(targetSize);
throw new ArgumentException();
}
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var targetSpace = new Size(
Math.Abs(availableSpace.Width / ScaleX),
Math.Abs(availableSpace.Height / ScaleY));
var currentMatrix = skiaCanvas.TotalMatrix;
if (ScaleX < 0)
skiaCanvas.Translate(availableSpace.Width, 0);
if (ScaleY < 0)
skiaCanvas.Translate(0, availableSpace.Height);
skiaCanvas.Scale(ScaleX, ScaleY);
Child?.Draw(targetSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+4 -9
View File
@@ -3,15 +3,10 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ShowOnce : ContainerElement, IStateResettable
internal class ShowOnce : ContainerElement
{
private bool IsRendered { get; set; }
public void ResetState()
{
IsRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace)
{
if (Child == null || IsRendered)
@@ -20,7 +15,7 @@ namespace QuestPDF.Elements
return Child.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
if (Child == null || IsRendered)
return;
@@ -28,7 +23,7 @@ namespace QuestPDF.Elements
if (Child.Measure(availableSpace) is FullRender)
IsRendered = true;
Child.Draw(availableSpace);
Child.Draw(canvas, availableSpace);
}
}
}
+6 -20
View File
@@ -10,26 +10,13 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class SimpleStack : Element, IStateResettable
internal class SimpleStack : Element
{
internal Element First { get; set; } = Empty.Instance;
internal Element Second { get; set; } = Empty.Instance;
internal bool IsFirstRendered { get; set; } = false;
internal override void HandleVisitor(Action<Element?> visit)
{
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public void ResetState()
{
IsFirstRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -57,7 +44,7 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -69,7 +56,7 @@ namespace QuestPDF.Elements
var firstSize = firstMeasurement as Size;
if (firstSize != null)
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
firstElement.Draw(canvas, new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
return;
@@ -81,9 +68,9 @@ namespace QuestPDF.Elements
if (secondMeasurement == null)
return;
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
canvas.Translate(new Position(0, firstHeight));
Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height));
canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement is FullRender)
IsFirstRendered = false;
@@ -110,7 +97,6 @@ namespace QuestPDF.Elements
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
+10 -5
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
@@ -24,7 +25,8 @@ namespace QuestPDF.Elements
.DefaultIfEmpty(0)
.Max();
var realHeight = lines.Count * LineHeight;
var paint = Style.ToPaint().FontMetrics;
var realHeight = -paint.Ascent + paint.Descent;
if (realHeight > availableSpace.Height + Size.Epsilon)
return new Wrap();
@@ -32,22 +34,25 @@ namespace QuestPDF.Elements
return new FullRender(realWidth, realHeight);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var paint = Style.ToPaint().FontMetrics;
var offeset = -paint.Ascent; // paint.Ascent - Style.Size;
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
Canvas.Translate(new Position(0, Style.Size));
canvas.Translate(new Position(0, offeset));
foreach (var line in lines)
{
Canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
canvas.DrawText(line, new Position(offsetLeft, offsetTop), Style);
offsetTop += LineHeight;
}
Canvas.Translate(new Position(0, -Style.Size));
canvas.Translate(new Position(0, -offeset));
float GetLeftOffset()
{
+51
View File
@@ -0,0 +1,51 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Elements
{
public struct TextMeasurement
{
public float Width { get; set; }
public SKRect Position { get; set; }
}
internal class TextElement
{
public TextStyle Style { get; set; }
public string Text { get; set; }
private static ConcurrentDictionary<string, TextMeasurement> Measurements = new ConcurrentDictionary<string, TextMeasurement>();
public TextMeasurement Measure()
{
return Measure(Text, Style);
}
public void Draw(ICanvas canvas)
{
(canvas as QuestPDF.Drawing.Canvas).SkiaCanvas.DrawText(Text, 0, 0, Style.ToPaint());
}
internal static TextMeasurement Measure(string text, TextStyle style)
{
var key = $"{text}_{style}";
return Measurements.GetOrAdd(key, x =>
{
var rect = new SKRect();
var width = style.ToPaint().MeasureText(text, ref rect);
return new TextMeasurement
{
Position = rect,
Width = width
};
});
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Elements
{
internal class TextRun : Element
{
public List<TextElement> Elements = new List<TextElement>();
internal override ISpacePlan Measure(Size availableSpace)
{
var measurements = Elements.Select(x => x.Measure()).ToList();
var metrics = Elements
.Select(x => x.Style.ToPaint())
.Select(x => x.FontMetrics)
.ToList();
var lineHeight = metrics.Max(x => -x.Ascent) + metrics.Max(x => x.Descent);
var width = measurements.Sum(x => x.Width);
return new FullRender(width, lineHeight);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var measurements = Elements
.Select(x => x.Measure())
.ToList();
var metrics = Elements
.Select(x => x.Style.ToPaint())
.Select(x => x.FontMetrics)
.ToList();
var offset = metrics.Max(x => -x.Ascent);
var lineHeight = metrics.Max(x => -x.Ascent) + metrics.Max(x => x.Descent);
using var typeface = SKTypeface.FromFamilyName("Helvetica");
using var shaper = new SKShaper(typeface);
var shaped = shaper.Shape("Podstawowy łaciński — ✔️ ❤️ ☆ Tabela znaków Unicode", Elements.First().Style.ToPaint());
foreach (var textElement in Elements)
{
var size = textElement.Measure();
canvas.DrawRectangle(new Position(0, 0), new Size(size.Width, lineHeight), textElement.Style.BackgroundColor);
canvas.Translate(new Position(0, offset));
textElement.Draw(canvas);
canvas.Translate(new Position(0, -offset));
canvas.Translate(new Position(size.Width, 0));
}
canvas.Translate(new Position(-measurements.Sum(x => x.Width), 0));
//
}
}
}
-23
View File
@@ -1,23 +0,0 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Translate : ContainerElement
{
public float TranslateX { get; set; } = 1;
public float TranslateY { get; set; } = 1;
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
skiaCanvas.Translate(TranslateX, TranslateY);
base.Draw(availableSpace);
skiaCanvas.Translate(-TranslateX, -TranslateY);
}
}
}
+2 -2
View File
@@ -41,7 +41,7 @@ namespace QuestPDF.Fluent
return handler(parent.Container()).Container();
}
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
{
element.Element(new PageNumber
{
@@ -152,7 +152,7 @@ namespace QuestPDF.Fluent
public static void Canvas(this IContainer element, DrawOnCanvas handler)
{
element.Element(new Canvas
element.Element(new Elements.Canvas
{
Handler = handler
});
+2 -57
View File
@@ -1,7 +1,5 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
@@ -10,56 +8,6 @@ namespace QuestPDF.Fluent
{
internal Page Page { get; } = new Page();
public void Size(PageSize pageSize)
{
Page.MinSize = pageSize;
Page.MaxSize = pageSize;
}
public void ContinuousSize(float width)
{
Page.MinSize = new PageSize(width, 0);
Page.MaxSize = new PageSize(width, Infrastructure.Size.Max.Height);
}
public void MarginLeft(float value)
{
Page.MarginLeft = value;
}
public void MarginRight(float value)
{
Page.MarginRight = value;
}
public void MarginTop(float value)
{
Page.MarginTop = value;
}
public void MarginBottom(float value)
{
Page.MarginBottom = value;
}
public void MarginVertical(float value)
{
MarginTop(value);
MarginBottom(value);
}
public void MarginHorizontal(float value)
{
MarginLeft(value);
MarginRight(value);
}
public void Margin(float value)
{
MarginVertical(value);
MarginHorizontal(value);
}
public IContainer Header()
{
var container = new Container();
@@ -84,14 +32,11 @@ namespace QuestPDF.Fluent
public static class PageExtensions
{
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
public static void Page(this IContainer document, Action<PageDescriptor> handler)
{
var descriptor = new PageDescriptor();
handler(descriptor);
(document as DocumentContainer).Pages.Add(descriptor.Page);
return document;
document.Component(descriptor.Page);
}
}
}
-27
View File
@@ -1,27 +0,0 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RotateExtensions
{
private static IContainer Rotate(this IContainer element, Action<Rotate> handler)
{
var scale = element as Rotate ?? new Rotate();
handler(scale);
return element.Element(scale);
}
public static IContainer RotateLeft(this IContainer element)
{
return element.Rotate(x => x.TurnCount--);
}
public static IContainer RotateRight(this IContainer element)
{
return element.Rotate(x => x.TurnCount++);
}
}
}
-47
View File
@@ -1,47 +0,0 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class ScaleExtensions
{
private static IContainer Scale(this IContainer element, Action<Scale> handler)
{
var scale = element as Scale ?? new Scale();
handler(scale);
return element.Element(scale);
}
public static IContainer Scale(this IContainer element, float value)
{
return element.ScaleX(value).ScaleY(value);
}
public static IContainer ScaleX(this IContainer element, float value)
{
return element.Scale(x => x.ScaleX = value);
}
public static IContainer FlipX(this IContainer element)
{
return element.ScaleX(-1);
}
public static IContainer ScaleY(this IContainer element, float value)
{
return element.Scale(x => x.ScaleY = value);
}
public static IContainer FlipY(this IContainer element)
{
return element.ScaleY(-1);
}
public static IContainer FlipOver(this IContainer element)
{
return element.FlipX().FlipY();
}
}
}
+5
View File
@@ -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);
-32
View File
@@ -1,32 +0,0 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class TranslateExtensions
{
private static IContainer Translate(this IContainer element, Action<Translate> handler)
{
var translate = element as Translate ?? new Translate();
handler(translate);
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);
}
public static IContainer TranslateY(this IContainer element, float value)
{
return element.Translate(x => x.TranslateY = value);
}
}
}
+36 -36
View File
@@ -15,46 +15,46 @@ namespace QuestPDF.Helpers
{
public const int PointsPerInch = 72;
public static PageSize A0 => new PageSize(2384.2f, 3370.8f);
public static PageSize A1 => new PageSize(1684, 2384.2f);
public static PageSize A2 => new PageSize(1190.7f, 1684);
public static PageSize A3 => new PageSize(842, 1190.7f);
public static PageSize A4 => new PageSize(595.4f, 842);
public static PageSize A5 => new PageSize(419.6f, 595.4f);
public static PageSize A6 => new PageSize(297.7f, 419.6f);
public static PageSize A7 => new PageSize(209.8f, 297.7f);
public static PageSize A8 => new PageSize(147.4f, 209.8f);
public static PageSize A9 => new PageSize(104.9f, 147.4f);
public static PageSize A10 => new PageSize(73.7f, 104.9f);
public static Size A0 => new PageSize(2384.2f, 3370.8f);
public static Size A1 => new PageSize(1684, 2384.2f);
public static Size A2 => new PageSize(1190.7f, 1684);
public static Size A3 => new PageSize(842, 1190.7f);
public static Size A4 => new PageSize(595.4f, 842);
public static Size A5 => new PageSize(419.6f, 595.4f);
public static Size A6 => new PageSize(297.7f, 419.6f);
public static Size A7 => new PageSize(209.8f, 297.7f);
public static Size A8 => new PageSize(147.4f, 209.8f);
public static Size A9 => new PageSize(104.9f, 147.4f);
public static Size A10 => new PageSize(73.7f, 104.9f);
public static PageSize B0 => new PageSize(2835, 4008.7f);
public static PageSize B1 => new PageSize(2004.3f, 2835);
public static PageSize B2 => new PageSize(1417.5f, 2004.3f);
public static PageSize B3 => new PageSize(1000.8f, 1417.5f);
public static PageSize B4 => new PageSize(708.8f, 1000.8f);
public static PageSize B5 => new PageSize(499, 708.8f);
public static PageSize B6 => new PageSize(354.4f, 499);
public static PageSize B7 => new PageSize(249.5f, 354.4f);
public static PageSize B8 => new PageSize(175.8f, 249.5f);
public static PageSize B9 => new PageSize(124.7f, 175.8f);
public static PageSize B10 => new PageSize(87.9f, 124.7f);
public static Size B0 => new PageSize(2835, 4008.7f);
public static Size B1 => new PageSize(2004.3f, 2835);
public static Size B2 => new PageSize(1417.5f, 2004.3f);
public static Size B3 => new PageSize(1000.8f, 1417.5f);
public static Size B4 => new PageSize(708.8f, 1000.8f);
public static Size B5 => new PageSize(499, 708.8f);
public static Size B6 => new PageSize(354.4f, 499);
public static Size B7 => new PageSize(249.5f, 354.4f);
public static Size B8 => new PageSize(175.8f, 249.5f);
public static Size B9 => new PageSize(124.7f, 175.8f);
public static Size B10 => new PageSize(87.9f, 124.7f);
public static PageSize Env10 => new PageSize(683.2f, 294.8f);
public static PageSize EnvC4 => new PageSize(649.2f, 918.5f);
public static PageSize EnvDL => new PageSize(311.9f, 623.7f);
public static Size Env10 => new PageSize(683.2f, 294.8f);
public static Size EnvC4 => new PageSize(649.2f, 918.5f);
public static Size EnvDL => new PageSize(311.9f, 623.7f);
public static PageSize Executive => new PageSize(522, 756);
public static PageSize Legal => new PageSize(612.4f, 1009.3f);
public static PageSize Letter => new PageSize(612.4f, 791);
public static Size Executive => new PageSize(522, 756);
public static Size Legal => new PageSize(612.4f, 1009.3f);
public static Size Letter => new PageSize(612.4f, 791);
public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
public static PageSize ARCH_C => new PageSize(1295.6f, 1729.3f);
public static PageSize ARCH_D => new PageSize(1729.3f, 2591.2f);
public static PageSize ARCH_E => new PageSize(2591.2f, 3455.9f);
public static PageSize ARCH_E1 => new PageSize(2160.3f, 3024.9f);
public static PageSize ARCH_E2 => new PageSize(1871.1f, 2735.8f);
public static PageSize ARCH_E3 => new PageSize(1944.8f, 2809.5f);
public static Size ARCH_A => new PageSize(649.2f, 864.7f);
public static Size ARCH_B => new PageSize(864.7f, 1295.6f);
public static Size ARCH_C => new PageSize(1295.6f, 1729.3f);
public static Size ARCH_D => new PageSize(1729.3f, 2591.2f);
public static Size ARCH_E => new PageSize(2591.2f, 3455.9f);
public static Size ARCH_E1 => new PageSize(2160.3f, 3024.9f);
public static Size ARCH_E2 => new PageSize(1871.1f, 2735.8f);
public static Size ARCH_E3 => new PageSize(1944.8f, 2809.5f);
}
public static class PageSizeExtensions
+20 -20
View File
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
{
public static class Placeholders
{
private static Random Random = new Random();
public static Random Random = new Random();
#region Word Cache
@@ -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()
+3 -10
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
@@ -14,20 +13,14 @@ namespace QuestPDF.Infrastructure
set => Child = value as Element;
}
internal override void HandleVisitor(Action<Element?> visit)
{
base.HandleVisitor(visit);
Child?.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Child?.Measure(availableSpace) ?? new FullRender(Size.Zero);
}
internal override void Draw(Size availableSpace)
internal override void Draw(ICanvas canvas, Size availableSpace)
{
Child?.Draw(availableSpace);
Child?.Draw(canvas, availableSpace);
}
}
}
+1 -17
View File
@@ -1,26 +1,10 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
internal abstract class Element : IElement
{
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
internal virtual void HandleVisitor(Action<Element?> visit)
{
visit(this);
}
internal void Initialize(IPageContext pageContext, ICanvas canvas)
{
PageContext = pageContext;
Canvas = canvas;
}
internal abstract ISpacePlan Measure(Size availableSpace);
internal abstract void Draw(Size availableSpace);
internal abstract void Draw(ICanvas canvas, Size availableSpace);
}
}
-1
View File
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using SkiaSharp;
namespace QuestPDF.Infrastructure
+1 -1
View File
@@ -5,6 +5,6 @@ namespace QuestPDF.Infrastructure
public interface IDocument
{
DocumentMetadata GetMetadata();
void Compose(IDocumentContainer container);
void Compose(IContainer container);
}
}
@@ -1,7 +0,0 @@
namespace QuestPDF.Infrastructure
{
public interface IDocumentContainer
{
}
}
-11
View File
@@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace QuestPDF.Infrastructure
{
public interface IPageContext
{
void SetLocationPage(string key);
int GetLocationPage(string key);
ICollection<string> GetRegisteredLocations();
}
}
@@ -1,11 +0,0 @@
namespace QuestPDF.Infrastructure
{
public interface IRenderingCanvas
{
void BeginDocument();
void EndDocument();
void BeginPage(Size size);
void EndPage();
}
}
@@ -1,7 +0,0 @@
namespace QuestPDF.Infrastructure
{
internal interface IStateResettable
{
void ResetState();
}
}
-45
View File
@@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
{
public class PageContext : IPageContext
{
public const string CurrentPageSlot = "currentPage";
public const string TotalPagesSlot = "totalPages";
private Dictionary<string, int> Locations { get; } = new Dictionary<string, int>();
private int PageNumber { get; set; }
internal void SetPageNumber(int number)
{
PageNumber = number;
Locations[CurrentPageSlot] = number;
if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number)
Locations[TotalPagesSlot] = number;
}
public void SetLocationPage(string key)
{
if (Locations.ContainsKey(key))
return;
Locations[key] = PageNumber;
}
public int GetLocationPage(string key)
{
if (!Locations.ContainsKey(key))
throw new ArgumentException($"The location '{key}' does not exists.");
return Locations[key];
}
public ICollection<string> GetRegisteredLocations()
{
return Locations.Keys;
}
}
}
-1
View File
@@ -8,7 +8,6 @@
public float Height { get; }
public static Size Zero => new Size(0, 0);
public static Size Max => new Size(14_400, 14_400);
public Size(float width, float height)
{
+4 -3
View File
@@ -5,18 +5,19 @@ 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 float LineHeight { get; set; } = 1f;
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
internal bool IsItalic { get; set; } = false;
public static TextStyle Default => new TextStyle();
public override string ToString()
{
return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}";
return $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}";
}
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
+2 -1
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.5.2</Version>
<Version>2021.5.1</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>
<LangVersion>8</LangVersion>
@@ -23,6 +23,7 @@
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
</ItemGroup>
<ItemGroup>