Compare commits

...

16 Commits

Author SHA1 Message Date
Marcin Ziąbek c2fb68562d 2021.9.0 2021-08-30 23:37:44 +02:00
Marcin Ziąbek 683ab362d8 Added support for registering font types based on the suggestion of brendonparker 2021-08-29 20:15:12 +02:00
Marcin Ziąbek 04af33fc31 Included examples for DSL and complex layouts 2021-08-23 23:51:42 +02:00
Marcin Ziąbek 7f94ae589a Updated release notes and code example 2021-08-03 01:43:48 +02:00
Marcin Ziąbek adfce6204e Improved testing environment by adding proper result files names, Added more code examples 2021-08-03 01:35:46 +02:00
Marcin Ziąbek 6d3c8d0708 Added elements: Rotate, Unconstrained 2021-08-01 21:09:16 +02:00
Marcin Ziąbek 3b8c300126 Merge pull request #17 from QuestPDF/2021.6
Merge 2021.6 for QuestPdf 2021.8
2021-08-01 16:58:44 +02:00
MarcinZiabek 239a042615 Fixed rendering for the "Rotate" element 2021-07-28 21:36:51 +02:00
Marcin Ziąbek 49fb6dadb3 Improved null safety. Fixed tests 2021-07-26 12:26:31 +02:00
Marcin Ziąbek c79a4a0c08 Fixed rendering images 2021-07-26 11:59:48 +02:00
Marcin Ziąbek 31191efd88 Multipass rendering. Total pages / location position support. Continuous pages. 2021-07-26 11:35:52 +02:00
Marcin Ziąbek 20fe49dd01 Update SECURITY.md 2021-07-18 18:10:55 +02:00
Marcin Ziąbek 7cdc1fcee2 Create SECURITY.md 2021-07-18 18:10:20 +02:00
Marcin Ziąbek 14f11232be New elements: rotate, scale, translate 2021-06-09 23:34:25 +02:00
Marcin Ziąbek c05c5e1597 Update readme.md
Updated code, added the support section
2021-05-15 16:09:01 +02:00
Marcin Ziąbek 07f1eb99b9 Fixed: stack should not apply spacing for empty items 2021-05-05 13:26:46 +02:00
78 changed files with 2183 additions and 848 deletions
+32
View File
@@ -0,0 +1,32 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class BarCode
{
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.FileName()
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,95 +0,0 @@
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();
}
}
}
@@ -1,14 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class FileNameAttribute : Attribute
{
public string FileName { get; }
public FileNameAttribute(string fileName)
{
FileName = fileName;
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class IgnoreAttribute : Attribute
{
}
}
@@ -1,19 +0,0 @@
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;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
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([CallerMemberName] string fileName = "test")
{
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));
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class ShowResultAttribute : Attribute
{
}
}
+12 -5
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -7,6 +8,8 @@ namespace QuestPDF.Examples.Engine
{
public class SimpleDocument : IDocument
{
public const int ImageScalingFactor = 2;
private IContainer Container { get; }
private Size Size { get; }
@@ -20,14 +23,18 @@ namespace QuestPDF.Examples.Engine
{
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * 2,
Size = Size
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = 10
};
}
public void Compose(IContainer container)
public void Compose(IDocumentContainer container)
{
container.Background("#FFF").Element(Container.Child);
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Background(Colors.White).Element(Container as Container);
});
}
}
}
+22 -15
View File
@@ -1,3 +1,4 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -11,33 +12,39 @@ namespace QuestPDF.Examples
{
return container
.Border(1)
.Background(dark ? "#EEE" : "#FFF")
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Padding(10);
}
public static IContainer LabelCell(this IContainer container) => container.Cell(true);
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
}
public class FrameExample: ExampleTestBase
public class FrameExample
{
[ImageSize(550, 400)]
[ShowResult]
[Test]
public void Frame(IContainer container)
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
RenderingTest
.Create()
.PageSize(550, 400)
.FileName()
.Render(container =>
{
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
row.RelativeColumn(2).LabelCell().Text(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
}
});
}
}
Binary file not shown.
+23 -16
View File
@@ -1,4 +1,5 @@
using System.Net;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@@ -27,27 +28,33 @@ namespace QuestPDF.Examples
}
}
public class LoremPicsumExample : ExampleTestBase
public class LoremPicsumExample
{
[ShowResult]
[ImageSize(350, 280)]
public void LoremPicsum(IContainer container)
[Test]
public void LoremPicsum()
{
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
RenderingTest
.Create()
.PageSize(350, 280)
.FileName()
.Render(container =>
{
column.Spacing(10);
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
{
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");
});
});
}
}
+139 -106
View File
@@ -1,132 +1,165 @@
using QuestPDF.Examples.Engine;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class Examples : ExampleTestBase
public class Examples
{
public void Padding(IContainer container)
[Test]
public void Padding()
{
container
.Background("#FDD")
.Padding(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.Background("#444");
}
public void Border(IContainer container)
{
container
.Background("#EEE")
.Padding(25)
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
}
public void Alignment(IContainer container)
{
container
.Stack(column =>
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
column
.Item()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
container
.Background("#FDD")
.Padding(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.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");
});
}
public void Expand(IContainer container)
[Test]
public void Border()
{
container
.Stack(column =>
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
column
.Item()
.Height(150)
.Row(row =>
container
.Background("#EEE")
.Padding(25)
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
});
}
[Test]
public void Alignment()
{
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
.Stack(column =>
{
row.RelativeColumn()
.Extend()
.Background("FFF")
column
.Item()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
.Height(50)
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.ExtendHorizontal()
.Background("444");
});
.Background("#444");
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("BBB")
column
.Item()
.Height(100)
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.ExtendVertical()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.Background("#222");
column
.Item()
.Height(100)
.Background("#BBB")
.AlignRight()
.AlignMiddle()
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
.Width(50)
.Height(50)
.Background("#000");
});
});
}
[Test]
public void Expand()
{
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.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")
.ExtendVertical()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
});
});
});
}
@@ -16,4 +16,10 @@
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -18,21 +18,23 @@ namespace QuestPDF.ReportSample.Layouts
{
return new DocumentMetadata()
{
Title = Model.Title,
Size = PageSizes.A4
Title = Model.Title
};
}
public void Compose(IContainer container)
public void Compose(IDocumentContainer container)
{
container
.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 {number}");
page.Footer().AlignCenter().PageNumber();
});
}
@@ -43,6 +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());
});
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.UnitTests
.For(x => new PageBreak())
.MeasureElement(new Size(400, 300))
.CheckMeasureResult(new PartialRender(400, 300))
.CheckMeasureResult(new PartialRender(Size.Zero))
.DrawElement(new Size(400, 300))
.CheckDrawResult()
+3 -3
View File
@@ -30,10 +30,10 @@ namespace QuestPDF.UnitTests
Child = child.Object
};
element.Draw(null, Size.Zero);
element.Draw(null, Size.Zero);
element.Draw(Size.Zero);
element.Draw(Size.Zero);
child.Verify(x => x.Draw(It.IsAny<ICanvas>(), It.IsAny<Size>()), Times.Once);
child.Verify(x => x.Draw(It.IsAny<Size>()), Times.Once);
}
[Test]
+2 -1
View File
@@ -10,11 +10,12 @@ 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(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace);
internal override void Draw(Size availableSpace) => DrawFunc(availableSpace);
}
}
+4 -1
View File
@@ -205,6 +205,8 @@ 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());
@@ -223,7 +225,8 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.Draw(Canvas, OperationInput);
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this;
}
-59
View File
@@ -1,59 +0,0 @@
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);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
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;
}
}
}
+49 -84
View File
@@ -1,8 +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;
@@ -14,115 +16,78 @@ 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();
using var pdf = SKDocument.CreatePdf(stream, MapMetadata(metadata));
var totalPages = 1;
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;
while(true)
{
var spacePlan = content.Measure(metadata.Size);
pageContext.SetPageNumber(currentPage);
var spacePlan = content.Measure(Size.Max) as Size;
using var skiaCanvas = pdf.BeginPage(metadata.Size.Width, metadata.Size.Height);
var canvas = new Canvas(skiaCanvas);
if (spacePlan == null)
break;
try
{
content.Draw(canvas, metadata.Size);
canvas.BeginPage(spacePlan);
content.Draw(spacePlan);
}
catch (Exception exception)
{
pdf.Close();
canvas.EndDocument();
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
}
pdf.EndPage();
canvas.EndPage();
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
{
pdf.Close();
canvas.EndDocument();
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
if (spacePlan is FullRender)
break;
totalPages++;
currentPage++;
}
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
};
canvas.EndDocument();
}
}
}
-1
View File
@@ -6,7 +6,6 @@ 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; }
@@ -1,15 +1,23 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.IO;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal static class CanvasCache
public static class FontManager
{
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
public static void RegisterFontType(string fontName, Stream stream)
{
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
@@ -29,12 +37,10 @@ namespace QuestPDF.Drawing
static SKPaint Convert(TextStyle style)
{
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
Typeface = GetTypeface(style),
TextSize = style.Size,
TextEncoding = SKTextEncoding.Utf32,
@@ -47,6 +53,17 @@ namespace QuestPDF.Drawing
}
};
}
static SKTypeface GetTypeface(TextStyle style)
{
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
}
internal static TextMeasurement BreakText(this TextStyle style, string text, float availableWidth)
+71
View File
@@ -0,0 +1,71 @@
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
}
}
+52
View File
@@ -0,0 +1,52 @@
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
@@ -0,0 +1,65 @@
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
@@ -0,0 +1,58 @@
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(ICanvas canvas, Size availableSpace)
internal override void Draw(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(canvas, childSize);
canvas.Translate(new Position(-left, -top));
Canvas.Translate(new Position(left, top));
Child.Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
if (Child == null)
return;
var size = GetTargetSize(availableSpace);
Child?.Draw(canvas, size);
Child?.Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Child?.Draw(canvas, availableSpace);
Canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Child?.Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Child?.Draw(canvas, availableSpace);
Child?.Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
Child?.Draw(canvas, targetSize);
Child?.Draw(targetSize);
}
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (canvas as Drawing.Canvas)?.SkiaCanvas;
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (Handler == null || skiaCanvas == null)
return;
+2 -2
View File
@@ -44,13 +44,13 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var available = new Size(
MathHelpers.Min(MaxWidth, availableSpace.Width),
MathHelpers.Min(MaxHeight, availableSpace.Height));
Child?.Draw(canvas, available);
Child?.Draw(available);
}
}
+14 -6
View File
@@ -15,7 +15,15 @@ namespace QuestPDF.Elements
{
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
public DecorationType Type { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override ISpacePlan Measure(Size availableSpace)
{
@@ -42,22 +50,22 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(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(canvas, new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(canvas, new Size (availableSpace.Width, contentSize.Height));
Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(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));
}
}
+3 -2
View File
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace.Width, availableSpace.Height);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var imageData = Source?.Invoke(availableSpace);
@@ -26,7 +26,8 @@ namespace QuestPDF.Elements
InternalImage = SKImage.FromEncodedData(imageData)
};
imageElement.Draw(canvas, availableSpace);
imageElement.Initialize(PageContext, Canvas);
imageElement.Draw(availableSpace);
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace QuestPDF.Elements
return new FullRender(Size.Zero);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(canvas, availableSpace);
Canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(availableSpace);
}
}
}
+2 -2
View File
@@ -18,12 +18,12 @@ namespace QuestPDF.Elements
return new FullRender(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(canvas, availableSpace);
Canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(availableSpace);
}
}
}
+17 -4
View File
@@ -2,14 +2,27 @@
namespace QuestPDF.Elements
{
internal class InternalLocation : ContainerElement
internal class InternalLocation : ContainerElement, IStateResettable
{
public string LocationName { get; set; }
private bool IsRendered { get; set; }
internal override void Draw(ICanvas canvas, Size availableSpace)
public void ResetState()
{
canvas.DrawLocation(LocationName);
Child?.Draw(canvas, availableSpace);
IsRendered = false;
}
internal override void Draw(Size availableSpace)
{
if (!IsRendered)
{
PageContext.SetLocationPage(LocationName);
Canvas.DrawLocation(LocationName);
IsRendered = true;
}
Child?.Draw(availableSpace);
}
}
}
+10 -3
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
@@ -14,6 +15,12 @@ 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
@@ -21,12 +28,12 @@ namespace QuestPDF.Elements
.Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Children
.Where(x => x.Measure(availableSpace) is Size)
.ToList()
.ForEach(x => x.Draw(canvas, availableSpace));
.ForEach(x => x.Draw(availableSpace));
}
}
}
+4 -4
View File
@@ -39,16 +39,16 @@ namespace QuestPDF.Elements
throw new NotSupportedException();
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
if (Child == null)
return;
var internalSpace = InternalSpace(availableSpace);
canvas.Translate(new Position(Left, Top));
Child?.Draw(canvas, internalSpace);
canvas.Translate(new Position(-Left, -Top));
Canvas.Translate(new Position(Left, Top));
Child?.Draw(internalSpace);
Canvas.Translate(new Position(-Left, -Top));
}
private Size InternalSpace(Size availableSpace)
+28 -6
View File
@@ -1,22 +1,44 @@
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.Decoration(decoration =>
{
decoration.Header().Element(Header);
decoration.Content().Extend().Element(Content);
decoration.Footer().Element(Footer);
});
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);
});
}
}
}
+11 -5
View File
@@ -1,21 +1,27 @@
using QuestPDF.Drawing.SpacePlan;
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class PageBreak : Element
internal class PageBreak : Element, IStateResettable
{
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(availableSpace.Width, availableSpace.Height);
return new PartialRender(Size.Zero);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
IsRendered = true;
}
+30 -17
View File
@@ -1,3 +1,5 @@
using System;
using System.Text.RegularExpressions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
@@ -7,33 +9,44 @@ namespace QuestPDF.Elements
internal class PageNumber : Element
{
public string TextFormat { get; set; } = "";
public TextStyle? TextStyle { get; set; }
private Text? TextElement { get; set; }
private int Number { get; set; } = 1;
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)
{
InitializeTextElement();
TextElement.Value = TextFormat.Replace("{number}", Number.ToString());
TextElement.Value = GetText();
return TextElement.Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
InitializeTextElement();
TextElement.Draw(canvas, availableSpace);
Number++;
TextElement.Value = GetText();
TextElement.Draw(availableSpace);
}
private void InitializeTextElement()
private string GetText()
{
TextElement ??= new Text()
{
Style = TextStyle
};
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;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Rotate : ContainerElement
{
public float Angle { get; set; } = 0;
internal override void Draw(Size availableSpace)
{
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (skiaCanvas == null)
return;
var currentMatrix = skiaCanvas.TotalMatrix;
skiaCanvas.RotateDegrees(Angle);
Child?.Draw(availableSpace);
skiaCanvas.SetMatrix(currentMatrix);
}
}
}
+28 -11
View File
@@ -11,9 +11,9 @@ namespace QuestPDF.Elements
{
public float Width { get; set; } = 1;
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Child?.Draw(canvas, availableSpace);
Child?.Draw(availableSpace);
}
}
@@ -38,6 +38,14 @@ 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;
@@ -61,16 +69,16 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = (leftMeasurement as Size)?.Width ?? 0;
Left.Draw(canvas, new Size(leftWidth, availableSpace.Height));
Left.Draw(new Size(leftWidth, availableSpace.Height));
canvas.Translate(new Position(leftWidth, 0));
Right.Draw(canvas, new Size(availableSpace.Width - leftWidth, availableSpace.Height));
canvas.Translate(new Position(-leftWidth, 0));
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
}
}
@@ -78,15 +86,21 @@ 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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Compose(availableSpace.Width).Draw(canvas, availableSpace);
Compose(availableSpace.Width).Draw(availableSpace);
}
#region structure
@@ -95,7 +109,10 @@ namespace QuestPDF.Elements
{
var elements = AddSpacing(Children, Spacing);
var rowElements = ReduceRows(elements, availableWidth);
return BuildTree(rowElements.ToArray());
var tree = BuildTree(rowElements.ToArray());
tree.HandleVisitor(x => x.Initialize(PageContext, Canvas));
return tree;
}
private static ICollection<Element> ReduceRows(ICollection<RowElement> elements, float availableWidth)
+60
View File
@@ -0,0 +1,60 @@
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);
}
}
}
+9 -4
View File
@@ -3,10 +3,15 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ShowOnce : ContainerElement
internal class ShowOnce : ContainerElement, IStateResettable
{
private bool IsRendered { get; set; }
public void ResetState()
{
IsRendered = false;
}
internal override ISpacePlan Measure(Size availableSpace)
{
if (Child == null || IsRendered)
@@ -15,7 +20,7 @@ namespace QuestPDF.Elements
return Child.Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
if (Child == null || IsRendered)
return;
@@ -23,7 +28,7 @@ namespace QuestPDF.Elements
if (Child.Measure(availableSpace) is FullRender)
IsRendered = true;
Child.Draw(canvas, availableSpace);
Child.Draw(availableSpace);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Linq;
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);
}
}
}
+20 -6
View File
@@ -10,13 +10,26 @@ using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class SimpleStack : Element
internal class SimpleStack : Element, IStateResettable
{
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;
@@ -44,7 +57,7 @@ namespace QuestPDF.Elements
return new FullRender(targetSize);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
@@ -56,7 +69,7 @@ namespace QuestPDF.Elements
var firstSize = firstMeasurement as Size;
if (firstSize != null)
firstElement.Draw(canvas, new Size(availableSpace.Width, firstSize.Height));
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement is Wrap || firstMeasurement is PartialRender)
return;
@@ -68,9 +81,9 @@ namespace QuestPDF.Elements
if (secondMeasurement == null)
return;
canvas.Translate(new Position(0, firstHeight));
Second.Draw(canvas, new Size(availableSpace.Width, secondMeasurement.Height));
canvas.Translate(new Position(0, -firstHeight));
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement is FullRender)
IsFirstRendered = false;
@@ -97,6 +110,7 @@ namespace QuestPDF.Elements
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
+4 -4
View File
@@ -32,22 +32,22 @@ namespace QuestPDF.Elements
return new FullRender(realWidth, realHeight);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
canvas.Translate(new Position(0, Style.Size));
Canvas.Translate(new Position(0, Style.Size));
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, -Style.Size));
float GetLeftOffset()
{
+23
View File
@@ -0,0 +1,23 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Translate : ContainerElement
{
public float TranslateX { get; set; } = 0;
public float TranslateY { get; set; } = 0;
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);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
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);
}
}
}
+8 -3
View File
@@ -41,7 +41,7 @@ namespace QuestPDF.Fluent
return handler(parent.Container()).Container();
}
public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
public static void PageNumber(this IContainer element, string textFormat = "{pdf:currentPage} / {pdf:totalPages}", TextStyle? style = null)
{
element.Element(new PageNumber
{
@@ -104,7 +104,7 @@ namespace QuestPDF.Fluent
style.Alignment = alignment.Horizontal;
}
element.Element(new Text()
element.Element(new Text
{
Value = text.ToString(),
Style = style
@@ -152,7 +152,7 @@ namespace QuestPDF.Fluent
public static void Canvas(this IContainer element, DrawOnCanvas handler)
{
element.Element(new Elements.Canvas
element.Element(new Canvas
{
Handler = handler
});
@@ -162,5 +162,10 @@ namespace QuestPDF.Fluent
{
return element.Element(new Box());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
}
}
}
+4
View File
@@ -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++;
}
+57 -2
View File
@@ -1,5 +1,7 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
@@ -8,6 +10,56 @@ 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();
@@ -32,11 +84,14 @@ namespace QuestPDF.Fluent
public static class PageExtensions
{
public static void Page(this IContainer document, Action<PageDescriptor> handler)
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
{
var descriptor = new PageDescriptor();
handler(descriptor);
document.Component(descriptor.Page);
(document as DocumentContainer).Pages.Add(descriptor.Page);
return document;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RotateExtensions
{
private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
{
var scale = element as SimpleRotate ?? new SimpleRotate();
handler(scale);
return element.Element(scale);
}
public static IContainer RotateLeft(this IContainer element)
{
return element.SimpleRotate(x => x.TurnCount--);
}
public static IContainer RotateRight(this IContainer element)
{
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
});
}
}
}
+47
View File
@@ -0,0 +1,47 @@
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.ScaleHorizontal(value).ScaleVertical(value);
}
public static IContainer ScaleHorizontal(this IContainer element, float value)
{
return element.Scale(x => x.ScaleX = value);
}
public static IContainer FlipHorizontal(this IContainer element)
{
return element.ScaleHorizontal(-1);
}
public static IContainer ScaleVertical(this IContainer element, float value)
{
return element.Scale(x => x.ScaleY = value);
}
public static IContainer FlipVertical(this IContainer element)
{
return element.ScaleVertical(-1);
}
public static IContainer FlipOver(this IContainer element)
{
return element.FlipHorizontal().FlipVertical();
}
}
}
+27
View File
@@ -0,0 +1,27 @@
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 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 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 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 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 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 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 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 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 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 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 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 class PageSizeExtensions
+10 -3
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing.SpacePlan;
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
namespace QuestPDF.Infrastructure
@@ -13,14 +14,20 @@ 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(ICanvas canvas, Size availableSpace)
internal override void Draw(Size availableSpace)
{
Child?.Draw(canvas, availableSpace);
Child?.Draw(availableSpace);
}
}
}
+17 -1
View File
@@ -1,10 +1,26 @@
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(ICanvas canvas, Size availableSpace);
internal abstract void Draw(Size availableSpace);
}
}
+1
View File
@@ -1,3 +1,4 @@
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(IContainer container);
void Compose(IDocumentContainer container);
}
}
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
public interface IDocumentContainer
{
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace QuestPDF.Infrastructure
{
public interface IPageContext
{
void SetLocationPage(string key);
int GetLocationPage(string key);
ICollection<string> GetRegisteredLocations();
}
}
@@ -0,0 +1,11 @@
namespace QuestPDF.Infrastructure
{
public interface IRenderingCanvas
{
void BeginDocument();
void EndDocument();
void BeginPage(Size size);
void EndPage();
}
}
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
internal interface IStateResettable
{
void ResetState();
}
}
+45
View File
@@ -0,0 +1,45 @@
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,6 +8,7 @@
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 -4
View File
@@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.5.1</Version>
<Version>2021.9.0</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>Added support for registering custom fonts from a stream.</PackageReleaseNotes>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
@@ -14,8 +14,8 @@
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>QuestPDF contributors</Copyright>
<PackageTags>PDF file export generate create render portable document format quest free</PackageTags>
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
<PackageTags>pdf file export generate generation tool create creation render portable document format quest html library converter free</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
+14
View File
@@ -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.
+23 -12
View File
@@ -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();