Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc33056434 | |||
| d3e8386418 |
@@ -0,0 +1,33 @@
|
||||
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()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -8,25 +6,27 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Example()
|
||||
public void Barcode()
|
||||
{
|
||||
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 100)
|
||||
.ShowResults()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
using Microcharts;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class CanvasExamples
|
||||
{
|
||||
[Test]
|
||||
public void BorderRadius()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(175, 100)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
{
|
||||
DrawRoundedRectangle(Colors.White, false);
|
||||
DrawRoundedRectangle(Colors.Blue.Darken2, true);
|
||||
|
||||
void DrawRoundedRectangle(string color, bool isStroke)
|
||||
{
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse(color),
|
||||
IsStroke = isStroke,
|
||||
StrokeWidth = 2,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
|
||||
}
|
||||
});
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.PaddingVertical(10)
|
||||
.PaddingHorizontal(20)
|
||||
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Microcharts;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ChartExample
|
||||
{
|
||||
[Test]
|
||||
public void MicrochartChart()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new ChartEntry(212)
|
||||
{
|
||||
Label = "UWP",
|
||||
ValueLabel = "112",
|
||||
Color = SKColor.Parse("#2c3e50")
|
||||
},
|
||||
new ChartEntry(248)
|
||||
{
|
||||
Label = "Android",
|
||||
ValueLabel = "648",
|
||||
Color = SKColor.Parse("#77d065")
|
||||
},
|
||||
new ChartEntry(128)
|
||||
{
|
||||
Label = "iOS",
|
||||
ValueLabel = "428",
|
||||
Color = SKColor.Parse("#b455b6")
|
||||
},
|
||||
new ChartEntry(514)
|
||||
{
|
||||
Label = "Forms",
|
||||
ValueLabel = "214",
|
||||
Color = SKColor.Parse("#3498db")
|
||||
}
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 600)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack
|
||||
.Item()
|
||||
.PaddingBottom(10)
|
||||
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
|
||||
|
||||
stack
|
||||
.Item()
|
||||
.Border(1)
|
||||
.ExtendHorizontal()
|
||||
.Height(300)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
var chart = new BarChart
|
||||
{
|
||||
Entries = entries,
|
||||
|
||||
LabelOrientation = Orientation.Horizontal,
|
||||
ValueLabelOrientation = Orientation.Horizontal,
|
||||
|
||||
IsAnimated = false,
|
||||
};
|
||||
|
||||
chart.DrawContent(canvas, (int)size.Width, (int)size.Height);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ComplexLayoutBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void ComplexLayout()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => GenerateStructure(x, 16));
|
||||
}
|
||||
|
||||
private void GenerateStructure(IContainer container, int level)
|
||||
{
|
||||
if (level <= 0)
|
||||
{
|
||||
container.Background(Placeholders.BackgroundColor()).Height(10);
|
||||
return;
|
||||
}
|
||||
|
||||
level--;
|
||||
|
||||
if (level % 3 == 0)
|
||||
{
|
||||
container
|
||||
.Border(level / 4f)
|
||||
.BorderColor(Colors.Black)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Element(x => GenerateStructure(x, level));
|
||||
row.RelativeColumn().Element(x => GenerateStructure(x, level));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
container
|
||||
.Border(level / 4f)
|
||||
.BorderColor(Colors.Black)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(x => GenerateStructure(x, level));
|
||||
stack.Item().Element(x => GenerateStructure(x, level));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -11,66 +10,24 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Stack()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Stack(x =>
|
||||
{
|
||||
x.Item().Text("Test");
|
||||
x.Item().Width(150);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Simple()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(150)
|
||||
.Text("Test");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DebugPointer()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Width(150)
|
||||
.Text("Example");
|
||||
});
|
||||
});
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(15)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Spacing(15);
|
||||
|
||||
grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
|
||||
grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
|
||||
grid.Item().Background(Colors.Grey.Lighten2).Height(150);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DefaultTextStyleExample
|
||||
{
|
||||
[Test]
|
||||
public void DefaultTextStyle()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
// all text in this set of pages has size 20
|
||||
page.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A4);
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Content().Stack(stack =>
|
||||
{
|
||||
stack.Item().Text(Placeholders.Sentence());
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
// text in this block is additionally semibold
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold());
|
||||
|
||||
text.Line(Placeholders.Sentence());
|
||||
|
||||
// this text has size 20 but also semibold and red
|
||||
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DefaultTextStyleExamples
|
||||
{
|
||||
[Test]
|
||||
public void Placeholder()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(220, 270)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Text("Default style applies to all children", TextStyle.Default);
|
||||
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
|
||||
|
||||
stack.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||
{
|
||||
grid.Columns(4);
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 16))
|
||||
{
|
||||
grid.Item()
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Lighten1)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i, TextStyle.Default.Size(16 + i / 4));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DifferentHeaderOnFirstPageExample
|
||||
{
|
||||
[Test]
|
||||
public void Placeholder()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A6)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.Margin(30);
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Stack(stack =>
|
||||
{
|
||||
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
|
||||
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Content().PaddingVertical(10).Stack(stack =>
|
||||
{
|
||||
stack.Spacing(10);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 13))
|
||||
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(16));
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class OrderItem
|
||||
{
|
||||
public string ItemName { get; set; } = Placeholders.Label();
|
||||
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
|
||||
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
|
||||
}
|
||||
|
||||
public class OrdersTable : IDynamicComponent
|
||||
{
|
||||
private ICollection<OrderItem> Items { get; }
|
||||
private ICollection<OrderItem> ItemsLeft { get; set; }
|
||||
|
||||
public OrdersTable(ICollection<OrderItem> items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public void Compose(DynamicContext context, IDynamicContainer container)
|
||||
{
|
||||
if (context.Operation == DynamicLayoutOperation.Reset)
|
||||
{
|
||||
ItemsLeft = new List<OrderItem>(Items);
|
||||
return;
|
||||
}
|
||||
|
||||
var header = ComposeHeader(context);
|
||||
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
|
||||
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
|
||||
|
||||
var rows = GetItemsForPage(context, decorationHeight).ToList();
|
||||
var footer = ComposeFooter(context, rows.Select(x => x.Item));
|
||||
|
||||
if (ItemsLeft.Count > rows.Count)
|
||||
container.HasMoreContent();
|
||||
|
||||
if (context.Operation == DynamicLayoutOperation.Draw)
|
||||
ItemsLeft = ItemsLeft.Skip(rows.Count).ToList();
|
||||
|
||||
container.Box().Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Element(header);
|
||||
|
||||
decoration.Content().Box().Stack(stack =>
|
||||
{
|
||||
foreach (var row in rows)
|
||||
stack.Item().Element(row.Element);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(footer);
|
||||
});
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeHeader(DynamicContext context)
|
||||
{
|
||||
return context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
row.ConstantColumn(30).Text("#", textStyle);
|
||||
row.RelativeColumn().Text("Item name", textStyle);
|
||||
row.ConstantColumn(50).AlignRight().Text("Count", textStyle);
|
||||
row.ConstantColumn(50).AlignRight().Text("Price", textStyle);
|
||||
row.ConstantColumn(50).AlignRight().Text("Total", textStyle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
|
||||
{
|
||||
var total = items.Sum(x => x.Count * x.Price);
|
||||
|
||||
return context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.Padding(5)
|
||||
.AlignRight()
|
||||
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
|
||||
{
|
||||
var totalHeight = decorationHeight;
|
||||
var counter = Items.Count - ItemsLeft.Count + 1;
|
||||
|
||||
foreach (var orderItem in ItemsLeft)
|
||||
{
|
||||
var element = context.CreateElement(content =>
|
||||
{
|
||||
content
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(30).Text(counter++);
|
||||
row.RelativeColumn().Text(orderItem.ItemName);
|
||||
row.ConstantColumn(50).AlignRight().Text(orderItem.Count);
|
||||
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Price}$");
|
||||
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Count*orderItem.Price}$");
|
||||
});
|
||||
});
|
||||
|
||||
var elementHeight = element.Size.Height;
|
||||
|
||||
if (totalHeight + elementHeight > context.AvailableSize.Height)
|
||||
break;
|
||||
|
||||
totalHeight += elementHeight;
|
||||
yield return (orderItem, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicExamples
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
|
||||
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Darken2).Size(16));
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Dynamic(new OrdersTable(items));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -32,6 +33,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -61,6 +63,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -101,6 +104,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(740, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -122,6 +126,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -144,6 +149,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(210, 210)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -166,6 +172,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "";
|
||||
@@ -188,6 +195,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 230)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
var textStyle = TextStyle.Default.Size(14);
|
||||
@@ -222,6 +230,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -251,6 +260,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 250)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -326,6 +336,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -370,6 +381,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -402,6 +414,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -428,6 +441,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -467,6 +481,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -474,7 +489,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(15)
|
||||
.Border(4)
|
||||
.BorderColor(Colors.Blue.Medium)
|
||||
//.MinimalBox()
|
||||
//.Box()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(15)
|
||||
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
||||
@@ -487,6 +502,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 175)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -538,11 +554,12 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
|
||||
.Padding(25)
|
||||
|
||||
@@ -565,6 +582,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(650, 450)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -588,7 +606,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
@@ -603,6 +621,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -629,6 +648,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -660,6 +680,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -686,7 +707,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
@@ -701,6 +722,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 200)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -726,6 +748,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 350)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -762,6 +785,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 225)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -815,6 +839,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(600, 310)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples.Engine
|
||||
@@ -18,10 +17,7 @@ namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
private string FileNamePrefix = "test";
|
||||
private Size Size { get; set; }
|
||||
private int? MaxPagesThreshold { get; set; }
|
||||
private bool ShowResult { get; set; }
|
||||
private bool ApplyCaching { get; set; }
|
||||
private bool ApplyDebugging { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private RenderingTest()
|
||||
@@ -29,12 +25,12 @@ namespace QuestPDF.Examples.Engine
|
||||
|
||||
}
|
||||
|
||||
public static RenderingTest Create([CallerMemberName] string fileName = "test")
|
||||
public static RenderingTest Create()
|
||||
{
|
||||
return new RenderingTest().FileName(fileName);
|
||||
return new RenderingTest();
|
||||
}
|
||||
|
||||
public RenderingTest FileName(string fileName)
|
||||
public RenderingTest FileName([CallerMemberName] string fileName = "test")
|
||||
{
|
||||
FileNamePrefix = fileName;
|
||||
return this;
|
||||
@@ -69,46 +65,14 @@ namespace QuestPDF.Examples.Engine
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest MaxPages(int value)
|
||||
{
|
||||
MaxPagesThreshold = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest EnableCaching(bool value = true)
|
||||
{
|
||||
ApplyCaching = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest EnableDebugging(bool value = true)
|
||||
{
|
||||
ApplyDebugging = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Render(Action<IContainer> content)
|
||||
{
|
||||
RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(new PageSize(Size.Width, Size.Height));
|
||||
page.Content().Container().Background(Colors.White).Element(content);
|
||||
});
|
||||
});
|
||||
}
|
||||
var container = new Container();
|
||||
content(container);
|
||||
|
||||
public void RenderDocument(Action<IDocumentContainer> content)
|
||||
{
|
||||
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
|
||||
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(container, Size, maxPages);
|
||||
|
||||
Render(document);
|
||||
}
|
||||
|
||||
private void Render(IDocument document)
|
||||
{
|
||||
if (ResultType == RenderingTestResult.Images)
|
||||
{
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -11,17 +10,15 @@ namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
public const int ImageScalingFactor = 2;
|
||||
|
||||
private Action<IDocumentContainer> Content { get; }
|
||||
private IContainer Container { get; }
|
||||
private Size Size { get; }
|
||||
private int MaxPages { get; }
|
||||
private bool ApplyCaching { get; }
|
||||
private bool ApplyDebugging { get; }
|
||||
|
||||
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
|
||||
public SimpleDocument(IContainer container, Size size, int maxPages)
|
||||
{
|
||||
Content = content;
|
||||
Container = container;
|
||||
Size = size;
|
||||
MaxPages = maxPages;
|
||||
ApplyCaching = applyCaching;
|
||||
ApplyDebugging = applyDebugging;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
@@ -29,15 +26,17 @@ namespace QuestPDF.Examples.Engine
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = MaxPages,
|
||||
ApplyCaching = ApplyCaching,
|
||||
ApplyDebugging = ApplyDebugging
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
};
|
||||
}
|
||||
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
Content(container);
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(new PageSize(Size.Width, Size.Height));
|
||||
page.Content().Container().Background(Colors.White).Element(Container as Container);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class EnsureSpaceExample
|
||||
{
|
||||
[Test]
|
||||
public void EnsureSpaceWith()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
|
||||
|
||||
page.Content().Stack(stack =>
|
||||
{
|
||||
stack
|
||||
.Item()
|
||||
.ExtendHorizontal()
|
||||
.Height(75)
|
||||
.Background(Colors.Grey.Lighten2);
|
||||
|
||||
stack
|
||||
.Item()
|
||||
.EnsureSpace(100)
|
||||
.Text(Placeholders.LoremIpsum());
|
||||
});
|
||||
|
||||
page.Footer().Text(text =>
|
||||
{
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,12 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(dark ? Colors.Grey.Lighten3 : Colors.White)
|
||||
.Padding(5);
|
||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
||||
.Padding(10);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||
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 static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
@@ -30,6 +28,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 400)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -38,7 +37,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
for(var i = 1; i <= 4; i++)
|
||||
for(var i=1; i<=4; i++)
|
||||
{
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ImageExamples
|
||||
{
|
||||
[Test]
|
||||
public void LoadingImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25).Stack(stack =>
|
||||
{
|
||||
stack.Spacing(25);
|
||||
|
||||
stack.Item().Image("logo.png");
|
||||
|
||||
var binaryData = File.ReadAllBytes("logo.png");
|
||||
stack.Item().Image(binaryData);
|
||||
|
||||
using var stream = new FileStream("logo.png", FileMode.Open);
|
||||
stack.Item().Image(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
Assert.Throws<DocumentComposeException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A2)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class InlinedExamples
|
||||
{
|
||||
[Test]
|
||||
public void Inlined()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(800, 650)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.PaddingTop(25)
|
||||
//.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(25);
|
||||
|
||||
inlined.AlignSpaceAround();
|
||||
inlined.BaselineMiddle();
|
||||
|
||||
var random = new Random(123);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 50))
|
||||
{
|
||||
var width = random.Next(2, 7);
|
||||
var height = random.Next(2, 7);
|
||||
|
||||
var sizeText = $"{width}×{height}";
|
||||
|
||||
inlined
|
||||
.Item()
|
||||
.Border(1)
|
||||
.Width(width * 25)
|
||||
.Height(height * 25)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.Layer().Grid(grid =>
|
||||
{
|
||||
grid.Columns(width);
|
||||
Enumerable.Range(0, width * height).ToList().ForEach(x => grid.Item().Border(1).BorderColor(Colors.White).Width(25).Height(25));
|
||||
});
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(sizeText, TextStyle.Default.Size(15));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Inline_AlignLeft_BaselineBottom()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(800, 600)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Inlined(inlined =>
|
||||
{
|
||||
inlined.VerticalSpacing(50);
|
||||
inlined.HorizontalSpacing(25);
|
||||
inlined.AlignRight();
|
||||
inlined.BaselineMiddle();
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 100))
|
||||
inlined.Item().Element(RandomBlock);
|
||||
});
|
||||
});
|
||||
|
||||
void RandomBlock(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(Placeholders.Random.Next(1, 5) * 20)
|
||||
.Height(Placeholders.Random.Next(1, 5) * 20)
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Background(Placeholders.BackgroundColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 280)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -36,6 +37,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -60,6 +62,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
@@ -110,6 +113,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(200, 150)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
@@ -25,9 +23,6 @@
|
||||
<None Update="LibreBarcode39-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="logo.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RowExamples
|
||||
{
|
||||
[Test]
|
||||
public void ColumnTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(650, 300)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().LabelCell("Total width: 600px");
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).ValueCell("150px");
|
||||
row.ConstantColumn(100).ValueCell("100px");
|
||||
row.RelativeColumn(4).ValueCell("200px");
|
||||
row.RelativeColumn(3).ValueCell("150px");
|
||||
});
|
||||
|
||||
stack.Item().Row(row =>
|
||||
{
|
||||
row.ConstantColumn(100).ValueCell("100px");
|
||||
row.ConstantColumn(50).ValueCell("50px");
|
||||
row.Column(constantWidth: 100, relativeWidth: 4).ValueCell("350px");
|
||||
row.RelativeColumn(2).ValueCell("100px");
|
||||
row.RelativeColumn(1).ValueCell("50px");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability()
|
||||
{
|
||||
// up to version 2021.12, this code would always result with the infinite layout exception
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
{
|
||||
stack.Item().ShowOnce().Element(CreateBox).Text("X");
|
||||
stack.Item().Element(CreateBox).Text("1");
|
||||
stack.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(CreateBox).Text("1");
|
||||
stack.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
static IContainer CreateBox(IContainer container)
|
||||
{
|
||||
return container
|
||||
.ExtendHorizontal()
|
||||
.ExtendVertical()
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Border(1)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.ShowOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ShowOnceExample
|
||||
{
|
||||
[Test]
|
||||
public void ShowOnce()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Text("With show once", TextStyle.Default.SemiBold());
|
||||
|
||||
page.Content().PaddingVertical(5).Row(row =>
|
||||
{
|
||||
row.RelativeColumn()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.ShowOnce()
|
||||
.Text(Placeholders.Label());
|
||||
|
||||
row.RelativeColumn(2)
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Text(Placeholders.Paragraph());
|
||||
});
|
||||
|
||||
page.Footer().Text(text =>
|
||||
{
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class SkipOnceExample
|
||||
{
|
||||
[Test]
|
||||
public void SkipOnce()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Stack(stack =>
|
||||
{
|
||||
stack.Item().ShowOnce().Text("This header is visible on the first page.");
|
||||
stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
|
||||
});
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(10)
|
||||
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
|
||||
|
||||
page.Footer().Text(text =>
|
||||
{
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TableExamples
|
||||
{
|
||||
[Test]
|
||||
public void BasicPlacement()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 220)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
// by using custom 'Element' method, we can reuse visual configuration
|
||||
table.Cell().Row(1).Column(4).Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("C");
|
||||
table.Cell().Row(4).Column(1).Element(Block).Text("D");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultCellStyle()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 120)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.DefaultTextStyle(TextStyle.Default.Size(16))
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Row(1).Column(3).Element(Block).Text("C");
|
||||
table.Cell().Row(2).Column(4).Element(Block).Text("D");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ColumnsDefinition()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(320, 80)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(50);
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn(2);
|
||||
columns.RelativeColumn(3);
|
||||
});
|
||||
|
||||
table.Cell().ColumnSpan(4).LabelCell("Total width: 300px");
|
||||
table.Cell().ValueCell("50px");
|
||||
table.Cell().ValueCell("100px");
|
||||
table.Cell().ValueCell("100px");
|
||||
table.Cell().ValueCell("150px");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialAutoPlacement()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 220)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Element(Block).Text("C");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("E");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtendLastCellsToTableBottom()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.ExtendLastCellsToTableBottom();
|
||||
|
||||
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||
table.Cell().Row(3).Column(1).Element(Block).Text("B");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("C");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Overlapping()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(170, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
|
||||
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
|
||||
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Spans()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 220)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("2");
|
||||
table.Cell().Element(Block).Text("3");
|
||||
table.Cell().Element(Block).Text("4");
|
||||
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("6");
|
||||
table.Cell().RowSpan(2).Element(Block).Text("7");
|
||||
table.Cell().Element(Block).Text("8");
|
||||
table.Cell().Element(Block).Text("9");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(300, 300)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().RowSpan(4).Element(Block).Text("1");
|
||||
|
||||
table.Cell().RowSpan(2).Element(Block).Text("2");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("3");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("4");
|
||||
|
||||
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("6");
|
||||
table.Cell().RowSpan(1).Element(Block).Text("7");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TableHeader()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(500, 200)
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(container =>
|
||||
{
|
||||
var pageSizes = new List<(string name, double width, double height)>()
|
||||
{
|
||||
("Letter (ANSI A)", 8.5f, 11),
|
||||
("Legal", 8.5f, 14),
|
||||
("Ledger (ANSI B)", 11, 17),
|
||||
("Tabloid (ANSI B)", 17, 11),
|
||||
("ANSI C", 22, 17),
|
||||
("ANSI D", 34, 22),
|
||||
("ANSI E", 44, 34)
|
||||
};
|
||||
|
||||
const int inchesToPoints = 72;
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Lighten1)
|
||||
.Background(backgroundColor)
|
||||
.PaddingVertical(5)
|
||||
.PaddingHorizontal(10)
|
||||
.AlignCenter()
|
||||
.AlignMiddle();
|
||||
}
|
||||
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
|
||||
|
||||
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
|
||||
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
|
||||
|
||||
header.Cell().Element(CellStyle).Text("Width");
|
||||
header.Cell().Element(CellStyle).Text("Height");
|
||||
|
||||
header.Cell().Element(CellStyle).Text("Width");
|
||||
header.Cell().Element(CellStyle).Text("Height");
|
||||
|
||||
// you can extend already existing styles by creating additional methods
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
|
||||
});
|
||||
|
||||
foreach (var page in pageSizes)
|
||||
{
|
||||
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||
|
||||
// inches
|
||||
table.Cell().Element(CellStyle).Text(page.width);
|
||||
table.Cell().Element(CellStyle).Text(page.height);
|
||||
|
||||
// points
|
||||
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
|
||||
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
|
||||
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PerformanceText_TemperatureReport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.MaxPages(10_000)
|
||||
.EnableCaching()
|
||||
.EnableDebugging(false)
|
||||
.ShowResults()
|
||||
.Render(container => GeneratePerformanceStructure(container, 1000));
|
||||
}
|
||||
|
||||
public static void GeneratePerformanceStructure(IContainer container, int repeats)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
//.Background(Colors.Blue.Lighten2)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
//.Background(Colors.Red.Lighten2)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.ExtendLastCellsToTableBottom();
|
||||
|
||||
foreach (var i in Enumerable.Range(0, repeats))
|
||||
{
|
||||
table.Cell().RowSpan(3).LabelCell("Project");
|
||||
table.Cell().RowSpan(3).ShowEntire().ValueCell(Placeholders.Sentence());
|
||||
|
||||
table.Cell().LabelCell("Report number");
|
||||
table.Cell().ValueCell(i.ToString());
|
||||
|
||||
table.Cell().LabelCell("Date");
|
||||
table.Cell().ValueCell(Placeholders.ShortDate());
|
||||
|
||||
table.Cell().LabelCell("Inspector");
|
||||
table.Cell().ValueCell("Marcin Ziąbek");
|
||||
|
||||
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
|
||||
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("7:13");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("18:25");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Sunny");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Windy");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Mild");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Strong");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("17°C");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("32°C");
|
||||
|
||||
table.Cell().LabelCell("Remarks");
|
||||
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// this method uses a higher order function to define a custom and dynamic style
|
||||
static IContainer Block(IContainer container)
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.MinWidth(50)
|
||||
.MinHeight(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
@@ -42,7 +43,8 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 300)
|
||||
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -42,13 +42,14 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 300)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -70,13 +71,14 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 200)
|
||||
.FileName()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -101,6 +103,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
@@ -108,7 +111,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -131,6 +134,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
@@ -138,7 +142,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -160,6 +164,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
@@ -167,7 +172,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -223,6 +228,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
@@ -230,7 +236,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
@@ -7,6 +7,9 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static class DataSource
|
||||
{
|
||||
public static int SectionCounter { get; set; }
|
||||
public static int FieldCounter { get; set; }
|
||||
|
||||
public static ReportModel GetReport()
|
||||
{
|
||||
return new ReportModel
|
||||
@@ -86,6 +89,7 @@ namespace QuestPDF.ReportSample
|
||||
return new ReportSectionMap
|
||||
{
|
||||
Label = "Location",
|
||||
ImageSource = Placeholders.Image,
|
||||
Location = Helpers.RandomLocation()
|
||||
};
|
||||
}
|
||||
@@ -95,7 +99,10 @@ namespace QuestPDF.ReportSample
|
||||
return new ReportSectionPhotos
|
||||
{
|
||||
Label = "Photos",
|
||||
PhotoCount = Helpers.Random.Next(1, 15)
|
||||
Photos = Enumerable
|
||||
.Range(0, Helpers.Random.Next(1, 15))
|
||||
.Select(x => Placeholders.Image(400, 300))
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,9 +110,14 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
return new ReportPhoto()
|
||||
{
|
||||
PhotoData = Placeholders.Image(800, 600),
|
||||
|
||||
Comments = Placeholders.Sentence(),
|
||||
Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
|
||||
Location = Helpers.RandomLocation()
|
||||
Location = Helpers.RandomLocation(),
|
||||
|
||||
MapContextSource = x => Placeholders.Image(400, 300),
|
||||
MapDetailsSource = x => Placeholders.Image(400, 300)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
public class DifferentHeadersTemplate : IDocument
|
||||
{
|
||||
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
|
||||
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.Margin(40);
|
||||
|
||||
page.Size(PageSizes.A4);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
page.Footer().Element(ComposeFooter);
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
|
||||
{
|
||||
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||
});
|
||||
stack.Item().SkipOnce().Padding(5).Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeContent(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Item().PaddingVertical(80).Text("First");
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().PaddingVertical(80).Text("Second");
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().PaddingVertical(80).Text("Third");
|
||||
stack.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeFooter(IContainer container)
|
||||
{
|
||||
container.Background(Colors.Grey.Lighten3).Stack(stack =>
|
||||
{
|
||||
stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
row.RelativeColumn().AlignRight().Text("Footer for header");
|
||||
});
|
||||
|
||||
stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
row.RelativeColumn().AlignRight().Text("Footer for every page except header");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
public class ImagePlaceholder : IComponent
|
||||
{
|
||||
public static bool Solid { get; set; } = false;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
if (Solid)
|
||||
container.Background(Placeholders.Color());
|
||||
|
||||
else
|
||||
container.Image(Placeholders.Image);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
public class ImageTemplate : IComponent
|
||||
{
|
||||
private Func<Size, byte[]> Source { get; }
|
||||
private float AspectRatio { get; }
|
||||
|
||||
public ImageTemplate(byte[] source, float aspectRatio = 1.333333f) : this(_ => source, aspectRatio)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ImageTemplate(Func<Size, byte[]> source, float aspectRatio = 1.333333f)
|
||||
{
|
||||
Source = source;
|
||||
AspectRatio = aspectRatio;
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.AspectRatio(AspectRatio)
|
||||
.Background("#EEEEEE")
|
||||
.Image(Source(Size.Zero));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
container
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
row.RelativeColumn(2).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
|
||||
|
||||
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
|
||||
{
|
||||
stack.Spacing(7f);
|
||||
|
||||
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
|
||||
stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
grid.Columns(6);
|
||||
|
||||
grid.Item().LabelCell().Text("Date");
|
||||
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
|
||||
grid.Item().LabelCell().Text("Location");
|
||||
grid.Item(2).ValueCell().Text(Model.Location.Format());
|
||||
grid.Item().LabelCell().Text("Date", Typography.Normal);
|
||||
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
|
||||
grid.Item().LabelCell().Text("Location", Typography.Normal);
|
||||
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
|
||||
|
||||
grid.Item().LabelCell().Text("Comments");
|
||||
grid.Item(5).ValueCell().Text(Model.Comments);
|
||||
grid.Item().LabelCell().Text("Comments", Typography.Normal);
|
||||
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -31,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
stack.Item().EnsureSpace(25).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(150).LabelCell().Text(part.Label);
|
||||
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
|
||||
var frame = row.RelativeColumn().ValueCell();
|
||||
|
||||
if (part is ReportSectionText text)
|
||||
frame.ShowEntire().Text(text.Text);
|
||||
frame.ShowEntire().Text(text.Text, Typography.Normal);
|
||||
|
||||
if (part is ReportSectionMap map)
|
||||
frame.Element(x => MapElement(x, map));
|
||||
@@ -50,9 +49,9 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
void MapElement(IContainer container, ReportSectionMap model)
|
||||
{
|
||||
if (model.Location == null)
|
||||
if (model.ImageSource == null || model.Location == null)
|
||||
{
|
||||
container.Text("No location provided");
|
||||
container.Text("No location provided", Typography.Normal);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,14 +59,14 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
stack.Spacing(5);
|
||||
|
||||
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
|
||||
stack.Item().Text(model.Location.Format());
|
||||
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
|
||||
stack.Item().Text(model.Location.Format(), Typography.Normal);
|
||||
});
|
||||
}
|
||||
|
||||
void PhotosElement(IContainer container, ReportSectionPhotos model)
|
||||
{
|
||||
if (model.PhotoCount == 0)
|
||||
if (model.Photos.Count == 0)
|
||||
{
|
||||
container.Text("No photos", Typography.Normal);
|
||||
return;
|
||||
@@ -78,10 +77,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
grid.Spacing(5);
|
||||
grid.Columns(3);
|
||||
|
||||
Enumerable
|
||||
.Range(0, model.PhotoCount)
|
||||
.ToList()
|
||||
.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>());
|
||||
model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.DefaultTextStyle(Typography.Normal);
|
||||
|
||||
page.MarginVertical(40);
|
||||
page.MarginHorizontal(50);
|
||||
|
||||
@@ -39,6 +37,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(Typography.Normal);
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
@@ -55,7 +55,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
row.Spacing(50);
|
||||
|
||||
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
|
||||
row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
|
||||
row.ConstantColumn(150).ExternalLink("https://www.questpdf.com").Background(Colors.Grey.Lighten3);
|
||||
});
|
||||
|
||||
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
|
||||
@@ -69,8 +69,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
grid.Item().Text(text =>
|
||||
{
|
||||
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
|
||||
text.Span(field.Value);
|
||||
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
|
||||
text.Span(field.Value, Typography.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.InternalLink(locationName)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text($"{number}.");
|
||||
row.RelativeColumn().Text(locationName);
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
|
||||
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
|
||||
row.RelativeColumn().Text(locationName, Typography.Normal);
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,16 +44,22 @@ namespace QuestPDF.ReportSample
|
||||
|
||||
public class ReportSectionMap : ReportSectionElement
|
||||
{
|
||||
public Func<Size, byte[]> ImageSource { get; set; }
|
||||
public Location Location { get; set; }
|
||||
}
|
||||
|
||||
public class ReportSectionPhotos : ReportSectionElement
|
||||
{
|
||||
public int PhotoCount { get; set; }
|
||||
public List<byte[]> Photos { get; set; }
|
||||
}
|
||||
|
||||
public class ReportPhoto
|
||||
{
|
||||
{
|
||||
public byte[] PhotoData { get; set; }
|
||||
|
||||
public Func<Size, byte[]> MapDetailsSource { get; set; }
|
||||
public Func<Size, byte[]> MapContextSource { get; set; }
|
||||
|
||||
public Location Location { get; set; }
|
||||
|
||||
public DateTime? Date { get; set; }
|
||||
|
||||
@@ -11,13 +11,12 @@ using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
{
|
||||
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 256)]
|
||||
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 100)]
|
||||
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
|
||||
public class PerformanceTests
|
||||
{
|
||||
@@ -28,8 +27,6 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void Run()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var configuration = ManualConfig
|
||||
.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
|
||||
@@ -40,8 +37,6 @@ namespace QuestPDF.ReportSample
|
||||
[IterationSetup]
|
||||
public void GenerateReportData()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var model = DataSource.GetReport();
|
||||
var report = new StandardReport(model);
|
||||
Metadata = report.GetMetadata();
|
||||
@@ -56,7 +51,7 @@ namespace QuestPDF.ReportSample
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
Content.VisitChildren(x =>
|
||||
Content.HandleVisitor(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
|
||||
@@ -22,28 +22,17 @@ namespace QuestPDF.ReportSample
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GenerateAndShowPdf()
|
||||
public void GenerateAndShow()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
Report.GeneratePdf(path);
|
||||
Process.Start("explorer.exe", path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GenerateAndShowXps()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.xps");
|
||||
Report.GenerateXps(path);
|
||||
|
||||
Process.Start("explorer.exe", path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Profile()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var container = new DocumentContainer();
|
||||
Report.Compose(container);
|
||||
var content = container.Compose();
|
||||
|
||||
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
|
||||
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
|
||||
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ namespace QuestPDF.UnitTests
|
||||
public class BoxTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
|
||||
public void Measure() => SimpleContainerTests.Measure<Box>();
|
||||
|
||||
[Test]
|
||||
public void Draw_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace QuestPDF.UnitTests
|
||||
[TestFixture]
|
||||
public class ConstrainedTests
|
||||
{
|
||||
#region Height
|
||||
|
||||
[Test]
|
||||
public void Measure_MinHeight_ExpectWrap()
|
||||
{
|
||||
@@ -90,91 +88,5 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectChildMeasure(new Size(400, 100), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Width
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_ExpectWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100
|
||||
})
|
||||
.MeasureElement(new Size(50, 400))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_ExtendHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(200, 400))
|
||||
.ExpectChildMeasure(new Size(200, 400), SpacePlan.FullRender(50, 400))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(100, 400));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_PassHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(200, 400))
|
||||
.ExpectChildMeasure(new Size(200, 400), SpacePlan.FullRender(150, 400))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(150, 400));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxWidth_Empty()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 100
|
||||
})
|
||||
.MeasureElement(new Size(150, 400))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(0, 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxWidth_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(200, 400))
|
||||
.ExpectChildMeasure(new Size(100, 400), SpacePlan.PartialRender(75, 400))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(75, 400));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxWidth_ExpectWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(200, 400))
|
||||
.ExpectChildMeasure(new Size(100, 400), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectCanvasDrawImage(Position.Zero, new Size(400, 300))
|
||||
.CheckDrawResult();
|
||||
|
||||
passedSize.Should().BeEquivalentTo(new Size(400, 300));
|
||||
passedSize.Should().Be(new Size(400, 300));
|
||||
}
|
||||
|
||||
byte[] GenerateImage(Size size)
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||
{
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
|
||||
var actual = Element.Measure(OperationInput);
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckDrawResult()
|
||||
{
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
Element.Draw(OperationInput);
|
||||
return this;
|
||||
}
|
||||
@@ -271,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var canvas = new FreeCanvas();
|
||||
value.VisitChildren(x => x.Initialize(null, canvas));
|
||||
value.HandleVisitor(x => x.Initialize(null, canvas));
|
||||
var valueMeasure = value.Measure(availableSpace.Value);
|
||||
|
||||
expected.VisitChildren(x => x.Initialize(null, canvas));
|
||||
expected.HandleVisitor(x => x.Initialize(null, canvas));
|
||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||
|
||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||
@@ -285,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var valueCanvas = new OperationRecordingCanvas();
|
||||
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
||||
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
|
||||
value.Draw(availableSpace.Value);
|
||||
|
||||
var expectedCanvas = new OperationRecordingCanvas();
|
||||
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
||||
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
|
||||
expected.Draw(availableSpace.Value);
|
||||
|
||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
@@ -22,13 +17,6 @@ namespace QuestPDF.Drawing
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
internal static void GenerateXps(Stream stream, IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new XpsCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
@@ -44,15 +32,20 @@ namespace QuestPDF.Drawing
|
||||
var container = new DocumentContainer();
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var pageContext = new PageContext();
|
||||
|
||||
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
|
||||
|
||||
if (metadata.ApplyCaching)
|
||||
DebuggingState debuggingState = null;
|
||||
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
debuggingState = ApplyDebugging(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyCaching(content);
|
||||
}
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||
@@ -61,8 +54,8 @@ namespace QuestPDF.Drawing
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
|
||||
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
canvas.BeginDocument();
|
||||
|
||||
@@ -110,13 +103,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
void ThrowLayoutException()
|
||||
{
|
||||
var message = $"Composed layout generates infinite document. This may happen in two cases. " +
|
||||
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
|
||||
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
|
||||
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
|
||||
$"Please analyze your documents structure to detect this element and fix its size constraints.";
|
||||
|
||||
throw new DocumentLayoutException(message)
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.")
|
||||
{
|
||||
ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
|
||||
};
|
||||
@@ -125,7 +112,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
private static void ApplyCaching(Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
content.HandleVisitor(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
@@ -136,46 +123,12 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
var debuggingState = new DebuggingState();
|
||||
|
||||
content.VisitChildren(x =>
|
||||
content.HandleVisitor(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
});
|
||||
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
|
||||
if (content is TextBlock textBlock)
|
||||
{
|
||||
foreach (var textBlockItem in textBlock.Items)
|
||||
{
|
||||
if (textBlockItem is TextBlockSpan textSpan)
|
||||
{
|
||||
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
}
|
||||
else if (textBlockItem is TextBlockElement textElement)
|
||||
{
|
||||
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var targetTextStyle = documentDefaultTextStyle;
|
||||
|
||||
if (content is DefaultTextStyle defaultTextStyleElement)
|
||||
{
|
||||
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
|
||||
targetTextStyle = defaultTextStyleElement.TextStyle;
|
||||
}
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ApplyDefaultTextStyle(child, targetTextStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,7 @@ namespace QuestPDF.Drawing
|
||||
/// (likely due to infinite layout), the exception is thrown.
|
||||
/// </summary>
|
||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
|
||||
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal static SKPaint ToPaint(this TextStyle style)
|
||||
{
|
||||
return Paints.GetOrAdd(style.Key, key => Convert(style));
|
||||
return Paints.GetOrAdd(style.ToString(), key => Convert(style));
|
||||
|
||||
static SKPaint Convert(TextStyle style)
|
||||
{
|
||||
@@ -41,7 +41,7 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
Color = SKColor.Parse(style.Color),
|
||||
Typeface = GetTypeface(style),
|
||||
TextSize = (style.Size ?? 12),
|
||||
TextSize = style.Size,
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
};
|
||||
}
|
||||
@@ -51,16 +51,16 @@ namespace QuestPDF.Drawing
|
||||
if (Typefaces.TryGetValue(style.FontType, out var result))
|
||||
return result;
|
||||
|
||||
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
|
||||
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
|
||||
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 SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
|
||||
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,43 @@ using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class PdfCanvas : SkiaDocumentCanvasBase
|
||||
internal class PdfCanvas : SkiaCanvasBase
|
||||
{
|
||||
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
|
||||
: base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata)))
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace QuestPDF.Drawing.Proxy
|
||||
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
||||
}
|
||||
|
||||
if (item.SpacePlan.Type != SpacePlanType.FullRender)
|
||||
if (item.SpacePlan.Type == SpacePlanType.Wrap)
|
||||
title = "🔥 " + title;
|
||||
|
||||
builder.AppendLine(indent + title);
|
||||
@@ -109,7 +109,7 @@ namespace QuestPDF.Drawing.Proxy
|
||||
Value = x.GetValue(element)
|
||||
})
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is IEnumerable<IElement>))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class SkiaDocumentCanvasBase : SkiaCanvasBase
|
||||
{
|
||||
private SKDocument Document { get; }
|
||||
|
||||
public SkiaDocumentCanvasBase(SKDocument document)
|
||||
{
|
||||
Document = document;
|
||||
}
|
||||
|
||||
~SkiaDocumentCanvasBase()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Drawing
|
||||
if (Type == SpacePlanType.Wrap)
|
||||
return Type.ToString();
|
||||
|
||||
return $"{Type} (Width: {Width:N3}, Height: {Height:N3})";
|
||||
return $"{Type} | width {Width:N2} | height {Height:N2}";
|
||||
}
|
||||
|
||||
public static implicit operator Size(SpacePlan spacePlan)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class XpsCanvas : SkiaDocumentCanvasBase
|
||||
{
|
||||
public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
|
||||
: base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class MinimalBox : ContainerElement
|
||||
internal class Box : ContainerElement
|
||||
{
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
@@ -22,9 +22,9 @@ namespace QuestPDF.Elements
|
||||
if (Handler == null || skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var originalMatrix = skiaCanvas.TotalMatrix;
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
Handler.Invoke(skiaCanvas, availableSpace);
|
||||
skiaCanvas.SetMatrix(originalMatrix);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ namespace QuestPDF.Elements
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var available = new Size(
|
||||
Min(MaxWidth, availableSpace.Width),
|
||||
Min(MaxHeight, availableSpace.Height));
|
||||
LimitMin(availableSpace.Width, MaxWidth),
|
||||
LimitMin(availableSpace.Height, MaxHeight));
|
||||
|
||||
var measurement = base.Measure(available);
|
||||
|
||||
@@ -31,8 +31,8 @@ namespace QuestPDF.Elements
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var actualSize = new Size(
|
||||
Max(MinWidth, measurement.Width),
|
||||
Max(MinHeight, measurement.Height));
|
||||
LimitMax(measurement.Width, MinWidth),
|
||||
LimitMax(measurement.Height, MinHeight));
|
||||
|
||||
if (measurement.Type == SpacePlanType.FullRender)
|
||||
return SpacePlan.FullRender(actualSize);
|
||||
@@ -46,20 +46,20 @@ namespace QuestPDF.Elements
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var available = new Size(
|
||||
Min(MaxWidth, availableSpace.Width),
|
||||
Min(MaxHeight, availableSpace.Height));
|
||||
LimitMin(availableSpace.Width, MaxWidth),
|
||||
LimitMin(availableSpace.Height, MaxHeight));
|
||||
|
||||
Child?.Draw(available);
|
||||
base.Draw(available);
|
||||
}
|
||||
|
||||
private float LimitMin(float value, float? limit)
|
||||
{
|
||||
return limit.HasValue ? Math.Min(value, limit.Value) : value;
|
||||
}
|
||||
|
||||
private static float Min(float? x, float y)
|
||||
private float LimitMax(float value, float? limit)
|
||||
{
|
||||
return x.HasValue ? Math.Min(x.Value, y) : y;
|
||||
}
|
||||
|
||||
private static float Max(float? x, float y)
|
||||
{
|
||||
return x.HasValue ? Math.Max(x.Value, y) : y;
|
||||
return limit.HasValue ? Math.Max(value, limit.Value) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements
|
||||
.Layer()
|
||||
.ShowIf(!string.IsNullOrWhiteSpace(Text))
|
||||
.AlignCenter()
|
||||
.MinimalBox()
|
||||
.Box()
|
||||
.Background(Colors.White)
|
||||
.Padding(2)
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -18,10 +17,12 @@ namespace QuestPDF.Elements
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
public DecorationType Type { get; set; }
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
yield return DecorationElement;
|
||||
yield return ContentElement;
|
||||
DecorationElement?.HandleVisitor(visit);
|
||||
ContentElement?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element, Element> create)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DefaultTextStyle : ContainerElement
|
||||
{
|
||||
public TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DynamicHost : Element, IStateResettable
|
||||
{
|
||||
private IDynamicComponent Child { get; }
|
||||
|
||||
public DynamicHost(IDynamicComponent child)
|
||||
{
|
||||
Child = child;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
GetContent(Size.Zero, DynamicLayoutOperation.Reset);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var content = GetContent(availableSpace, DynamicLayoutOperation.Measure);
|
||||
var measurement = content.Measure(availableSpace);
|
||||
|
||||
if (measurement.Type != SpacePlanType.FullRender)
|
||||
throw new DocumentLayoutException("Dynamic component generated content that does not fit on a single page.");
|
||||
|
||||
return content.HasMoreContent
|
||||
? SpacePlan.PartialRender(measurement)
|
||||
: SpacePlan.FullRender(measurement);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
GetContent(availableSpace, DynamicLayoutOperation.Draw).Draw(availableSpace);
|
||||
}
|
||||
|
||||
DynamicContainer GetContent(Size availableSize, DynamicLayoutOperation operation)
|
||||
{
|
||||
var context = new DynamicContext
|
||||
{
|
||||
PageNumber = PageContext.GetLocationPage(Infrastructure.PageContext.CurrentPageSlot),
|
||||
PageContext = PageContext,
|
||||
Canvas = Canvas,
|
||||
|
||||
AvailableSize = availableSize,
|
||||
Operation = operation
|
||||
};
|
||||
|
||||
var container = new DynamicContainer();
|
||||
Child.Compose(context, container);
|
||||
|
||||
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
|
||||
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DynamicLayoutOperation
|
||||
{
|
||||
Reset,
|
||||
Measure,
|
||||
Draw
|
||||
}
|
||||
|
||||
public class DynamicContext
|
||||
{
|
||||
internal IPageContext PageContext { get; set; }
|
||||
internal ICanvas Canvas { get; set; }
|
||||
|
||||
public int PageNumber { get; internal set; }
|
||||
public Size AvailableSize { get; internal set; }
|
||||
public DynamicLayoutOperation Operation { get; internal set; }
|
||||
|
||||
public IDynamicElement CreateElement(Action<IContainer> content)
|
||||
{
|
||||
var container = new DynamicElement();
|
||||
content(container);
|
||||
|
||||
container.HandleVisitor(x => x?.Initialize(PageContext, Canvas));
|
||||
container.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
container.Size = container.Measure(AvailableSize);
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDynamicContainer : IContainer
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal class DynamicContainer : Container, IDynamicContainer
|
||||
{
|
||||
internal bool HasMoreContent { get; set; }
|
||||
}
|
||||
|
||||
public static class DynamicContainerExtensions
|
||||
{
|
||||
public static IDynamicContainer HasMoreContent(this IDynamicContainer container)
|
||||
{
|
||||
(container as DynamicContainer).HasMoreContent = true;
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDynamicElement : IElement
|
||||
{
|
||||
Size Size { get; }
|
||||
}
|
||||
|
||||
internal class DynamicElement : ContainerElement, IDynamicElement
|
||||
{
|
||||
public Size Size { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using static QuestPDF.Infrastructure.HorizontalAlignment;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace QuestPDF.Elements
|
||||
public Queue<GridElement> ChildrenQueue { get; set; } = new Queue<GridElement>();
|
||||
public int ColumnsCount { get; set; } = DefaultColumnsCount;
|
||||
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public HorizontalAlignment Alignment { get; set; } = Left;
|
||||
public float VerticalSpacing { get; set; } = 0;
|
||||
public float HorizontalSpacing { get; set; } = 0;
|
||||
|
||||
@@ -61,15 +62,15 @@ namespace QuestPDF.Elements
|
||||
var emptySpace = ColumnsCount - columnsWidth;
|
||||
var hasEmptySpace = emptySpace >= Size.Epsilon;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
if (Alignment == Center)
|
||||
emptySpace /= 2;
|
||||
|
||||
if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
|
||||
if (hasEmptySpace && Alignment != Left)
|
||||
row.RelativeColumn(emptySpace);
|
||||
|
||||
elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
|
||||
|
||||
if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
|
||||
if (hasEmptySpace && Alignment != Right)
|
||||
row.RelativeColumn(emptySpace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InlinedElement : Container
|
||||
{
|
||||
public SpacePlan? MeasureCache { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
// TODO: once element caching proxy is introduces, this can be removed
|
||||
|
||||
MeasureCache ??= Child.Measure(Size.Max);
|
||||
return MeasureCache.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum InlinedAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Justify,
|
||||
SpaceAround
|
||||
}
|
||||
|
||||
internal class Inlined : Element, IStateResettable
|
||||
{
|
||||
public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
|
||||
private Queue<InlinedElement> ChildrenQueue { get; set; }
|
||||
|
||||
internal float VerticalSpacing { get; set; }
|
||||
internal float HorizontalSpacing { get; set; }
|
||||
|
||||
internal InlinedAlignment ElementsAlignment { get; set; }
|
||||
internal VerticalAlignment BaselineAlignment { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
ChildrenQueue = new Queue<InlinedElement>(Elements);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Elements;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!ChildrenQueue.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
var lines = Compose(availableSpace);
|
||||
|
||||
if (!lines.Any())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var lineSizes = lines
|
||||
.Select(line =>
|
||||
{
|
||||
var size = GetLineSize(line);
|
||||
|
||||
var widthWithSpacing = size.Width + (line.Count - 1) * HorizontalSpacing;
|
||||
return new Size(widthWithSpacing, size.Height);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var width = lineSizes.Max(x => x.Width);
|
||||
var height = lineSizes.Sum(x => x.Height) + (lines.Count - 1) * VerticalSpacing;
|
||||
var targetSize = new Size(width, height);
|
||||
|
||||
var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count;
|
||||
|
||||
if (isPartiallyRendered)
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var lines = Compose(availableSpace);
|
||||
var topOffset = 0f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var height = line
|
||||
.Select(x => x.Measure(Size.Max))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.Max(x => x.Height);
|
||||
|
||||
DrawLine(line);
|
||||
|
||||
topOffset += height + VerticalSpacing;
|
||||
Canvas.Translate(new Position(0, height + VerticalSpacing));
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(0, -topOffset));
|
||||
lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue());
|
||||
|
||||
void DrawLine(ICollection<InlinedElement> elements)
|
||||
{
|
||||
var lineSize = GetLineSize(elements);
|
||||
|
||||
var elementOffset = ElementOffset();
|
||||
var leftOffset = AlignOffset();
|
||||
Canvas.Translate(new Position(leftOffset, 0));
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var size = element.Measure(Size.Max);
|
||||
var baselineOffset = BaselineOffset(size, lineSize.Height);
|
||||
|
||||
Canvas.Translate(new Position(0, baselineOffset));
|
||||
element.Draw(size);
|
||||
Canvas.Translate(new Position(0, -baselineOffset));
|
||||
|
||||
leftOffset += size.Width + elementOffset;
|
||||
Canvas.Translate(new Position(size.Width + elementOffset, 0));
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-leftOffset, 0));
|
||||
|
||||
float ElementOffset()
|
||||
{
|
||||
var difference = availableSpace.Width - lineSize.Width;
|
||||
|
||||
if (elements.Count == 1)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Justify)
|
||||
return difference / (elements.Count - 1);
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return difference / (elements.Count + 1);
|
||||
|
||||
return HorizontalSpacing;
|
||||
}
|
||||
|
||||
float AlignOffset()
|
||||
{
|
||||
if (ElementsAlignment == InlinedAlignment.Left)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Justify)
|
||||
return 0;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return elementOffset;
|
||||
|
||||
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Center)
|
||||
return difference / 2;
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.Right)
|
||||
return difference;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float BaselineOffset(Size elementSize, float lineHeight)
|
||||
{
|
||||
if (BaselineAlignment == VerticalAlignment.Top)
|
||||
return 0;
|
||||
|
||||
var difference = lineHeight - elementSize.Height;
|
||||
|
||||
if (BaselineAlignment == VerticalAlignment.Middle)
|
||||
return difference / 2;
|
||||
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Size GetLineSize(ICollection<InlinedElement> elements)
|
||||
{
|
||||
var sizes = elements
|
||||
.Select(x => x.Measure(Size.Max))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.ToList();
|
||||
|
||||
var width = sizes.Sum(x => x.Width);
|
||||
var height = sizes.Max(x => x.Height);
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
// list of lines, each line is a list of elements
|
||||
private ICollection<ICollection<InlinedElement>> Compose(Size availableSize)
|
||||
{
|
||||
var queue = new Queue<InlinedElement>(ChildrenQueue);
|
||||
var result = new List<ICollection<InlinedElement>>();
|
||||
|
||||
var topOffset = 0f;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var line = GetNextLine();
|
||||
|
||||
if (!line.Any())
|
||||
break;
|
||||
|
||||
var height = line
|
||||
.Select(x => x.Measure(availableSize))
|
||||
.Where(x => x.Type != SpacePlanType.Wrap)
|
||||
.Max(x => x.Height);
|
||||
|
||||
if (topOffset + height > availableSize.Height + Size.Epsilon)
|
||||
break;
|
||||
|
||||
topOffset += height + VerticalSpacing;
|
||||
result.Add(line);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
ICollection<InlinedElement> GetNextLine()
|
||||
{
|
||||
var result = new List<InlinedElement>();
|
||||
var leftOffset = GetInitialAlignmentOffset();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!queue.Any())
|
||||
break;
|
||||
|
||||
var element = queue.Peek();
|
||||
var size = element.Measure(Size.Max);
|
||||
|
||||
if (size.Type == SpacePlanType.Wrap)
|
||||
break;
|
||||
|
||||
if (leftOffset + size.Width > availableSize.Width + Size.Epsilon)
|
||||
break;
|
||||
|
||||
queue.Dequeue();
|
||||
leftOffset += size.Width + HorizontalSpacing;
|
||||
result.Add(element);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float GetInitialAlignmentOffset()
|
||||
{
|
||||
// this method makes sure that the spacing between elements is no lesser than configured
|
||||
|
||||
if (ElementsAlignment == InlinedAlignment.SpaceAround)
|
||||
return HorizontalSpacing * 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,12 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public List<Layer> Children { get; set; } = new List<Layer>();
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
return Children;
|
||||
Children.ForEach(x => x.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Children
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -8,8 +7,6 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Page : IComponent
|
||||
{
|
||||
public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
|
||||
|
||||
public Size MinSize { get; set; } = PageSizes.A4;
|
||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||
|
||||
@@ -18,8 +15,6 @@ namespace QuestPDF.Elements
|
||||
public float MarginTop { get; set; }
|
||||
public float MarginBottom { get; set; }
|
||||
|
||||
public string BackgroundColor { get; set; } = Colors.Transparent;
|
||||
|
||||
public Element Header { get; set; } = Empty.Instance;
|
||||
public Element Content { get; set; } = Empty.Instance;
|
||||
public Element Footer { get; set; } = Empty.Instance;
|
||||
@@ -27,21 +22,18 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.Background(BackgroundColor)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
|
||||
+53
-48
@@ -6,16 +6,9 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RowElement : Constrained
|
||||
internal abstract class RowElement : Constrained
|
||||
{
|
||||
public float ConstantSize { get; }
|
||||
public float RelativeSize { get; }
|
||||
|
||||
public RowElement(float constantSize, float relativeSize)
|
||||
{
|
||||
ConstantSize = constantSize;
|
||||
RelativeSize = relativeSize;
|
||||
}
|
||||
public float Size { get; protected set; }
|
||||
|
||||
public void SetWidth(float width)
|
||||
{
|
||||
@@ -24,59 +17,62 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
internal class BinaryRow : Element, ICacheable, IStateResettable
|
||||
internal class ConstantRowElement : RowElement
|
||||
{
|
||||
public ConstantRowElement(float size)
|
||||
{
|
||||
Size = size;
|
||||
SetWidth(size);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RelativeRowElement : RowElement
|
||||
{
|
||||
public RelativeRowElement(float size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BinaryRow : Element, ICacheable
|
||||
{
|
||||
internal Element Left { get; set; }
|
||||
internal Element Right { get; set; }
|
||||
|
||||
private bool IsLeftRendered { get; set; }
|
||||
private bool IsRightRendered { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
IsLeftRendered = false;
|
||||
IsRightRendered = false;
|
||||
Left.HandleVisitor(visit);
|
||||
Right.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
yield return Left;
|
||||
yield return Right;
|
||||
}
|
||||
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Left = create(Left);
|
||||
Right = create(Right);
|
||||
}
|
||||
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsLeftRendered = true;
|
||||
|
||||
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
|
||||
|
||||
if (rightMeasurement.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsRightRendered = true;
|
||||
|
||||
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
||||
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
||||
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
|
||||
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
|
||||
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
}
|
||||
|
||||
@@ -97,15 +93,16 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
|
||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
||||
private Element? RootElement { get; set; }
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
if (RootElement == null)
|
||||
ComposeTree();
|
||||
|
||||
yield return RootElement;
|
||||
|
||||
RootElement.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
@@ -124,23 +121,31 @@ namespace QuestPDF.Elements
|
||||
|
||||
private void ComposeTree()
|
||||
{
|
||||
Items = AddSpacing(Items, Spacing);
|
||||
Children = AddSpacing(Children, Spacing);
|
||||
|
||||
var elements = Items.Cast<Element>().ToArray();
|
||||
var elements = Children.Cast<Element>().ToArray();
|
||||
RootElement = BuildTree(elements);
|
||||
}
|
||||
|
||||
private void UpdateElementsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = Items.Sum(x => x.ConstantSize);
|
||||
var relativeWidth = Items.Sum(x => x.RelativeSize);
|
||||
var constantWidth = Children
|
||||
.Where(x => x is ConstantRowElement)
|
||||
.Cast<ConstantRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var relativeWidth = Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
||||
|
||||
foreach (var row in Items)
|
||||
{
|
||||
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
|
||||
}
|
||||
Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.ToList()
|
||||
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
@@ -149,7 +154,7 @@ namespace QuestPDF.Elements
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
|
||||
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
|
||||
.Skip(1)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class SkipOnce : ContainerElement, IStateResettable
|
||||
{
|
||||
private bool FirstPageWasSkipped { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
FirstPageWasSkipped = false;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (Child == null || !FirstPageWasSkipped)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
return Child.Measure(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
if (FirstPageWasSkipped)
|
||||
Child.Draw(availableSpace);
|
||||
|
||||
FirstPageWasSkipped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,14 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal bool IsFirstRendered { get; set; } = false;
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
yield return First;
|
||||
yield return Second;
|
||||
First?.HandleVisitor(visit);
|
||||
Second?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsFirstRendered = false;
|
||||
@@ -95,12 +97,12 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal class Stack : IComponent
|
||||
{
|
||||
public ICollection<Element> Items { get; } = new List<Element>();
|
||||
public ICollection<Element> Children { get; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
var elements = AddSpacing(Spacing, Items);
|
||||
var elements = AddSpacing(Spacing, Children);
|
||||
|
||||
container
|
||||
.PaddingBottom(-Spacing)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
/// <summary>
|
||||
/// This dictionary allows to access key that does not exist.
|
||||
/// Instead of throwing an exception, it returns a default value.
|
||||
/// </summary>
|
||||
internal class DynamicDictionary<TKey, TValue>
|
||||
{
|
||||
private TValue Default { get; }
|
||||
private IDictionary<TKey, TValue> Dictionary { get; } = new Dictionary<TKey, TValue>();
|
||||
|
||||
public DynamicDictionary()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DynamicDictionary(TValue defaultValue)
|
||||
{
|
||||
Default = defaultValue;
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => Dictionary.TryGetValue(key, out var value) ? value : Default;
|
||||
set => Dictionary[key] = value;
|
||||
}
|
||||
|
||||
public List<KeyValuePair<TKey, TValue>> Items => Dictionary.ToList();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
public interface ITableCellContainer : IContainer
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class Table : Element, IStateResettable
|
||||
{
|
||||
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
|
||||
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||
|
||||
// cache for efficient cell finding
|
||||
// index of first array - number of row
|
||||
// nested array - collection of all cells starting at given row
|
||||
private TableCell[][] OrderedCells { get; set; }
|
||||
|
||||
private int StartingRowsCount { get; set; }
|
||||
private int RowsCount { get; set; }
|
||||
private int CurrentRow { get; set; }
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Cells;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
if (StartingRowsCount == default)
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
|
||||
if (RowsCount == default)
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
|
||||
if (OrderedCells == default)
|
||||
{
|
||||
var groups = Cells
|
||||
.GroupBy(x => x.Row)
|
||||
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
|
||||
|
||||
OrderedCells = Enumerable
|
||||
.Range(0, RowsCount + 1)
|
||||
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
Cells.ForEach(x => x.IsRendered = false);
|
||||
CurrentRow = 1;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!Cells.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
UpdateColumnsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
if (!renderingCommands.Any())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var width = Columns.Sum(x => x.Width);
|
||||
var height = renderingCommands.Max(x => x.Offset.Y + x.Size.Height);
|
||||
var tableSize = new Size(width, height);
|
||||
|
||||
if (tableSize.Width > availableSpace.Width + Size.Epsilon)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
return FindLastRenderedRow(renderingCommands) == StartingRowsCount
|
||||
? SpacePlan.FullRender(tableSize)
|
||||
: SpacePlan.PartialRender(tableSize);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
UpdateColumnsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
if (command.Measurement.Type == SpacePlanType.FullRender)
|
||||
command.Cell.IsRendered = true;
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.Cell.Draw(command.Size);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
|
||||
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
||||
|
||||
var isFullRender = FindLastRenderedRow(renderingCommands) == StartingRowsCount;
|
||||
|
||||
if (isFullRender)
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private int FindLastRenderedRow(ICollection<TableCellRenderingCommand> commands)
|
||||
{
|
||||
return commands
|
||||
.GroupBy(x => x.Cell.Row)
|
||||
.Where(x => x.All(y => y.Measurement.Type == SpacePlanType.FullRender))
|
||||
.Select(x => x.Key)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
}
|
||||
|
||||
private void UpdateColumnsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = Columns.Sum(x => x.ConstantSize);
|
||||
var relativeWidth = Columns.Sum(x => x.RelativeSize);
|
||||
|
||||
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||
|
||||
foreach (var column in Columns)
|
||||
{
|
||||
column.Width = column.ConstantSize + column.RelativeSize * widthPerRelativeUnit;
|
||||
}
|
||||
}
|
||||
|
||||
private ICollection<TableCellRenderingCommand> PlanLayout(Size availableSpace)
|
||||
{
|
||||
var columnOffsets = GetColumnLeftOffsets(Columns);
|
||||
|
||||
var commands = GetRenderingCommands();
|
||||
|
||||
if (!commands.Any())
|
||||
return commands;
|
||||
|
||||
var tableHeight = commands.Max(cell => cell.Offset.Y + cell.Size.Height);
|
||||
|
||||
if (ExtendLastCellsToTableBottom)
|
||||
AdjustLastCellSizes(tableHeight, commands);
|
||||
|
||||
return commands;
|
||||
|
||||
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
||||
{
|
||||
var cellOffsets = new float[columns.Count + 1];
|
||||
cellOffsets[0] = 0;
|
||||
|
||||
foreach (var column in Enumerable.Range(1, cellOffsets.Length - 1))
|
||||
cellOffsets[column] = columns[column - 1].Width + cellOffsets[column - 1];
|
||||
|
||||
return cellOffsets;
|
||||
}
|
||||
|
||||
ICollection<TableCellRenderingCommand> GetRenderingCommands()
|
||||
{
|
||||
var rowBottomOffsets = new DynamicDictionary<int, float>();
|
||||
|
||||
var cellsToTry = Enumerable
|
||||
.Range(CurrentRow, RowsCount - CurrentRow + 1)
|
||||
.SelectMany(x => OrderedCells[x]);
|
||||
|
||||
var currentRow = CurrentRow;
|
||||
var maxRenderingRow = RowsCount;
|
||||
|
||||
var commands = new List<TableCellRenderingCommand>();
|
||||
|
||||
foreach (var cell in cellsToTry)
|
||||
{
|
||||
// update position of previous row
|
||||
if (cell.Row > currentRow)
|
||||
{
|
||||
rowBottomOffsets[currentRow] = Math.Max(rowBottomOffsets[currentRow], rowBottomOffsets[currentRow - 1]);
|
||||
|
||||
if (rowBottomOffsets[currentRow - 1] > availableSpace.Height + Size.Epsilon)
|
||||
break;
|
||||
|
||||
foreach (var row in Enumerable.Range(cell.Row, cell.Row - currentRow))
|
||||
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
|
||||
|
||||
currentRow = cell.Row;
|
||||
}
|
||||
|
||||
// cell visibility optimizations
|
||||
if (cell.Row > maxRenderingRow)
|
||||
break;
|
||||
|
||||
if (cell.IsRendered)
|
||||
continue;
|
||||
|
||||
// calculate cell position / size
|
||||
var topOffset = rowBottomOffsets[cell.Row - 1];
|
||||
|
||||
var availableWidth = GetCellWidth(cell);
|
||||
var availableHeight = availableSpace.Height - topOffset;
|
||||
var availableCellSize = new Size(availableWidth, availableHeight);
|
||||
|
||||
var cellSize = cell.Measure(availableCellSize);
|
||||
|
||||
// corner case: if cell within the row is not fully rendered, do not attempt to render next row
|
||||
if (cellSize.Type == SpacePlanType.PartialRender)
|
||||
{
|
||||
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
|
||||
}
|
||||
|
||||
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
|
||||
if (cellSize.Type == SpacePlanType.Wrap)
|
||||
{
|
||||
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// update position of the last row that cell occupies
|
||||
var bottomRow = cell.Row + cell.RowSpan - 1;
|
||||
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
|
||||
|
||||
// accept cell to be rendered
|
||||
commands.Add(new TableCellRenderingCommand()
|
||||
{
|
||||
Cell = cell,
|
||||
Measurement = cellSize,
|
||||
Size = new Size(availableWidth, cellSize.Height),
|
||||
Offset = new Position(columnOffsets[cell.Column - 1], topOffset)
|
||||
});
|
||||
}
|
||||
|
||||
if (!commands.Any())
|
||||
return commands;
|
||||
|
||||
var maxRow = commands.Select(x => x.Cell).Max(x => x.Row + x.RowSpan);
|
||||
|
||||
foreach (var row in Enumerable.Range(CurrentRow, maxRow - CurrentRow))
|
||||
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
|
||||
|
||||
AdjustCellSizes(commands, rowBottomOffsets);
|
||||
|
||||
// corner case: reject cell if other cells within the same row are rejected
|
||||
return commands.Where(x => x.Cell.Row <= maxRenderingRow).ToList();
|
||||
}
|
||||
|
||||
// corner sase: if two cells end up on the same row (a.Row + a.RowSpan = b.Row + b.RowSpan),
|
||||
// bottom edges of their bounding boxes should be at the same level
|
||||
static void AdjustCellSizes(ICollection<TableCellRenderingCommand> commands, DynamicDictionary<int, float> rowBottomOffsets)
|
||||
{
|
||||
foreach (var command in commands)
|
||||
{
|
||||
var lastRow = command.Cell.Row + command.Cell.RowSpan - 1;
|
||||
var height = rowBottomOffsets[lastRow] - command.Offset.Y;
|
||||
|
||||
command.Size = new Size(command.Size.Width, height);
|
||||
command.Offset = new Position(command.Offset.X, rowBottomOffsets[command.Cell.Row - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// corner sase: all cells, that are last ones in their respective columns, should take all remaining space
|
||||
static void AdjustLastCellSizes(float tableHeight, ICollection<TableCellRenderingCommand> commands)
|
||||
{
|
||||
var columnsCount = commands.Select(x => x.Cell).Max(x => x.Column + x.ColumnSpan - 1);
|
||||
|
||||
foreach (var column in Enumerable.Range(1, columnsCount))
|
||||
{
|
||||
var lastCellInColumn = commands
|
||||
.Where(x => x.Cell.Column <= column && column < x.Cell.Column + x.Cell.ColumnSpan)
|
||||
.OrderByDescending(x => x.Cell.Row + x.Cell.RowSpan)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (lastCellInColumn == null)
|
||||
continue;
|
||||
|
||||
lastCellInColumn.Size = new Size(lastCellInColumn.Size.Width, tableHeight - lastCellInColumn.Offset.Y);
|
||||
}
|
||||
}
|
||||
|
||||
float GetCellWidth(TableCell cell)
|
||||
{
|
||||
return columnOffsets[cell.Column + cell.ColumnSpan - 1] - columnOffsets[cell.Column - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class TableCell : Container, ITableCellContainer
|
||||
{
|
||||
public int Row { get; set; } = 0;
|
||||
public int RowSpan { get; set; } = 1;
|
||||
|
||||
public int Column { get; set; } = 0;
|
||||
public int ColumnSpan { get; set; } = 1;
|
||||
|
||||
public bool IsRendered { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class TableCellRenderingCommand
|
||||
{
|
||||
public TableCell Cell { get; set; }
|
||||
public SpacePlan Measurement { get; set; }
|
||||
public Size Size { get; set; }
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class TableColumnDefinition
|
||||
{
|
||||
public float ConstantSize { get; }
|
||||
public float RelativeSize { get; }
|
||||
|
||||
internal float Width { get; set; }
|
||||
|
||||
public TableColumnDefinition(float constantSize, float relativeSize)
|
||||
{
|
||||
ConstantSize = constantSize;
|
||||
RelativeSize = relativeSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Fluent;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
static class TableLayoutValidator
|
||||
{
|
||||
public static void PlanCellPositions(this Table table)
|
||||
{
|
||||
PlanCellPositions(table.Columns.Count, table.Cells);
|
||||
}
|
||||
|
||||
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)
|
||||
{
|
||||
var cellsWindow = new List<TableCell>();
|
||||
(int x, int y) currentLocation = (1, 1);
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cellsWindow.Count > Math.Max(columnsCount, 16))
|
||||
{
|
||||
cellsWindow = cellsWindow
|
||||
.Where(x => x.Row + x.RowSpan > currentLocation.y)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SetPartialLocation(cell);
|
||||
|
||||
if (cell.HasLocation())
|
||||
{
|
||||
cellsWindow.Add(cell);
|
||||
currentLocation = (cell.Column, cell.Row);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var location in GenerateCoordinates(columnsCount, currentLocation))
|
||||
{
|
||||
if (location.x + cell.ColumnSpan - 1 > columnsCount)
|
||||
continue;
|
||||
|
||||
cell.Column = location.x;
|
||||
cell.Row = location.y;
|
||||
|
||||
if (cell.CollidesWithAnyOf(cellsWindow))
|
||||
continue;
|
||||
|
||||
cellsWindow.Add(cell);
|
||||
currentLocation = (cell.Column, cell.Row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<(int x, int y)> GenerateCoordinates(int columnsCount, (int x, int y) startPosition)
|
||||
{
|
||||
if (startPosition.x > columnsCount)
|
||||
throw new ArgumentException();
|
||||
|
||||
foreach (var x in Enumerable.Range(startPosition.x, columnsCount - startPosition.x + 1))
|
||||
yield return (x, startPosition.y);
|
||||
|
||||
foreach (var y in Enumerable.Range(startPosition.y + 1, 1_000_000))
|
||||
foreach (var x in Enumerable.Range(1, columnsCount))
|
||||
yield return (x, y);
|
||||
}
|
||||
|
||||
private static bool CollidesWith(this TableCell cell, TableCell neighbour)
|
||||
{
|
||||
return cell.Column < neighbour.Column + neighbour.ColumnSpan &&
|
||||
cell.Column + cell.ColumnSpan > neighbour.Column &&
|
||||
cell.Row < neighbour.Row + neighbour.RowSpan &&
|
||||
cell.RowSpan + cell.Row > neighbour.Row;
|
||||
}
|
||||
|
||||
private static bool CollidesWithAnyOf(this TableCell cell, ICollection<TableCell> neighbours)
|
||||
{
|
||||
return neighbours.Any(cell.CollidesWith);
|
||||
}
|
||||
|
||||
private static void SetPartialLocation(this TableCell cell)
|
||||
{
|
||||
if (cell.Row == default && cell.Column == default)
|
||||
return;
|
||||
|
||||
if (cell.Row == default)
|
||||
cell.Row = 1;
|
||||
|
||||
if (cell.Column == default)
|
||||
cell.Column = 1;
|
||||
}
|
||||
|
||||
private static bool HasLocation(this TableCell cell)
|
||||
{
|
||||
return cell.Row != 0 && cell.Column != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Fluent;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
static class TableLayoutPlanner
|
||||
{
|
||||
public static void ValidateCellPositions(this Table table)
|
||||
{
|
||||
ValidateCellPositions(table.Columns.Count, table.Cells);
|
||||
}
|
||||
|
||||
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)
|
||||
{
|
||||
const string prefix = "Detected issue in table cells configuration.";
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Column < 1)
|
||||
throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}.");
|
||||
|
||||
if (cell.Row < 1)
|
||||
throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}.");
|
||||
|
||||
if (cell.Column > columnsCount)
|
||||
throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
|
||||
|
||||
if (cell.Column + cell.ColumnSpan - 1 > columnsCount)
|
||||
throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
|
||||
}
|
||||
|
||||
string GetCellDetails(TableCell cell)
|
||||
{
|
||||
return $"Row {cell.Row}, Column {cell.Column}, RowSpan {cell.RowSpan}, ColumnSpan {cell.ColumnSpan}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
@@ -11,8 +10,8 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
@@ -36,8 +35,8 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
|
||||
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockExternalLink : TextBlockSpan
|
||||
internal class TextBlockExternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return MeasureWithoutCache(request);
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,33 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockInternalLink : TextBlockSpan
|
||||
internal class TextBlockInternalLink : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string Text { get; set; }
|
||||
public string LocationName { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return MeasureWithoutCache(request);
|
||||
return GetItem().MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
GetItem().Draw(request);
|
||||
}
|
||||
|
||||
private TextBlockSpan GetItem()
|
||||
{
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = Text
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,31 +3,34 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : TextBlockSpan
|
||||
internal class TextBlockPageNumber : ITextBlockItem
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string SlotName { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
return MeasureWithoutCache(request);
|
||||
return GetItem(request.PageContext).MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
base.Draw(request);
|
||||
GetItem(request.PageContext).Draw(request);
|
||||
}
|
||||
|
||||
private void SetPageNumber(IPageContext context)
|
||||
private TextBlockSpan GetItem(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
Text = pageNumber.ToString();
|
||||
|
||||
return new TextBlockSpan
|
||||
{
|
||||
Style = Style,
|
||||
Text = pageNumber.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
|
||||
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
|
||||
|
||||
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var cacheKey = (request.StartIndex, request.AvailableWidth);
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
Width = 0,
|
||||
|
||||
LineHeight = Style.LineHeight ?? 1,
|
||||
LineHeight = Style.LineHeight,
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent
|
||||
};
|
||||
@@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent,
|
||||
|
||||
LineHeight = Style.LineHeight ?? 1,
|
||||
LineHeight = Style.LineHeight,
|
||||
|
||||
StartIndex = startIndex,
|
||||
EndIndex = endIndex,
|
||||
@@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void Draw(TextDrawingRequest request)
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
@@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
|
||||
if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
|
||||
if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
|
||||
@@ -11,16 +11,14 @@ namespace QuestPDF.Elements.Text
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
|
||||
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
|
||||
|
||||
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
private int CurrentElementIndex { get; set; }
|
||||
public Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
public int CurrentElementIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Items);
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Children);
|
||||
CurrentElementIndex = 0;
|
||||
}
|
||||
|
||||
@@ -32,7 +30,7 @@ namespace QuestPDF.Elements.Text
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return SpacePlan.Wrap();
|
||||
return SpacePlan.PartialRender(Size.Zero);
|
||||
|
||||
var width = lines.Max(x => x.Width);
|
||||
var height = lines.Sum(x => x.LineHeight);
|
||||
|
||||
@@ -41,6 +41,16 @@ namespace QuestPDF.Fluent
|
||||
return handler(parent.Container()).Container();
|
||||
}
|
||||
|
||||
public static void Dynamic<TDynamic>(this IContainer element) where TDynamic : IDynamicComponent, new()
|
||||
{
|
||||
element.Dynamic(new TDynamic());
|
||||
}
|
||||
|
||||
public static void Dynamic(this IContainer element, IDynamicComponent dynamicElement)
|
||||
{
|
||||
element.Element(new DynamicHost(dynamicElement));
|
||||
}
|
||||
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
|
||||
{
|
||||
return element.Element(new AspectRatio
|
||||
@@ -70,12 +80,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
return element.Element(new ShowOnce());
|
||||
}
|
||||
|
||||
public static IContainer SkipOnce(this IContainer element)
|
||||
{
|
||||
return element.Element(new SkipOnce());
|
||||
}
|
||||
|
||||
|
||||
public static IContainer ShowEntire(this IContainer element)
|
||||
{
|
||||
return element.Element(new ShowEntire());
|
||||
@@ -136,29 +141,14 @@ namespace QuestPDF.Fluent
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: deprecated Box method in QuestPDF 2022.1
|
||||
[Obsolete("This element has been renamed. Please use the MinimalBox method.")]
|
||||
public static IContainer Box(this IContainer element)
|
||||
{
|
||||
return element.Element(new MinimalBox());
|
||||
}
|
||||
|
||||
public static IContainer MinimalBox(this IContainer element)
|
||||
{
|
||||
return element.Element(new MinimalBox());
|
||||
return element.Element(new Box());
|
||||
}
|
||||
|
||||
public static IContainer Unconstrained(this IContainer element)
|
||||
{
|
||||
return element.Element(new Unconstrained());
|
||||
}
|
||||
|
||||
public static IContainer DefaultTextStyle(this IContainer element, TextStyle textStyle)
|
||||
{
|
||||
return element.Element(new DefaultTextStyle
|
||||
{
|
||||
TextStyle = textStyle
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class GenerateExtensions
|
||||
{
|
||||
#region PDF
|
||||
|
||||
public static byte[] GeneratePdf(this IDocument document)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
@@ -28,32 +26,6 @@ namespace QuestPDF.Fluent
|
||||
DocumentGenerator.GeneratePdf(stream, document);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static byte[] GenerateXps(this IDocument document)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
document.GenerateXps(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public static void GenerateXps(this IDocument document, string filePath)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Create);
|
||||
document.GenerateXps(stream);
|
||||
}
|
||||
|
||||
public static void GenerateXps(this IDocument document, Stream stream)
|
||||
{
|
||||
DocumentGenerator.GenerateXps(stream, document);
|
||||
}
|
||||
|
||||
#region XPS
|
||||
|
||||
#endregion
|
||||
|
||||
#region Images
|
||||
|
||||
public static IEnumerable<byte[]> GenerateImages(this IDocument document)
|
||||
{
|
||||
return DocumentGenerator.GenerateImages(document);
|
||||
@@ -75,7 +47,5 @@ namespace QuestPDF.Fluent
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
@@ -9,29 +7,12 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ImageExtensions
|
||||
{
|
||||
public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
public static void Image(this IContainer parent, byte[] data, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(imageData);
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(filePath);
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
var image = SKImage.FromEncodedData(fileStream);
|
||||
parent.Image(image, scaling);
|
||||
}
|
||||
|
||||
private static void Image(this IContainer parent, SKImage image, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
if (image == null)
|
||||
throw new DocumentComposeException("Cannot load or decode provided image.");
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var image = SKImage.FromEncodedData(data);
|
||||
var aspectRatio = image.Width / (float)image.Height;
|
||||
|
||||
var imageElement = new Image
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class InlinedDescriptor
|
||||
{
|
||||
internal Inlined Inlined { get; } = new Inlined();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
VerticalSpacing(value);
|
||||
HorizontalSpacing(value);
|
||||
}
|
||||
|
||||
public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value;
|
||||
public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value;
|
||||
|
||||
public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top;
|
||||
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
||||
public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
|
||||
|
||||
public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
|
||||
public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
|
||||
public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
|
||||
public void AlignJustify() => Inlined.ElementsAlignment = InlinedAlignment.Justify;
|
||||
public void AlignSpaceAround() => Inlined.ElementsAlignment = InlinedAlignment.SpaceAround;
|
||||
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new InlinedElement();
|
||||
Inlined.Elements.Add(container);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InlinedExtensions
|
||||
{
|
||||
public static void Inlined(this IContainer element, Action<InlinedDescriptor> handler)
|
||||
{
|
||||
var descriptor = new InlinedDescriptor();
|
||||
handler(descriptor);
|
||||
|
||||
element.Element(descriptor.Inlined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,16 +70,6 @@ namespace QuestPDF.Fluent
|
||||
MarginHorizontal(value);
|
||||
}
|
||||
|
||||
public void DefaultTextStyle(TextStyle textStyle)
|
||||
{
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
}
|
||||
|
||||
public void Background(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
}
|
||||
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
@@ -15,19 +15,17 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public IContainer ConstantColumn(float width)
|
||||
{
|
||||
return Column(constantWidth: width);
|
||||
var element = new ConstantRowElement(width);
|
||||
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
public IContainer RelativeColumn(float width = 1)
|
||||
{
|
||||
return Column(relativeWidth: width);
|
||||
}
|
||||
|
||||
public IContainer Column(float constantWidth = 0, float relativeWidth = 0)
|
||||
{
|
||||
var element = new RowElement(constantWidth, relativeWidth);
|
||||
var element = new RelativeRowElement(width);
|
||||
|
||||
Row.Items.Add(element);
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace QuestPDF.Fluent
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
Stack.Items.Add(container);
|
||||
Stack.Children.Add(container);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Table;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TableColumnsDefinitionDescriptor
|
||||
{
|
||||
internal List<TableColumnDefinition> Columns { get; } = new();
|
||||
|
||||
public void ConstantColumn(float width)
|
||||
{
|
||||
ComplexColumn(constantWidth: width);
|
||||
}
|
||||
|
||||
public void RelativeColumn(float width = 1)
|
||||
{
|
||||
ComplexColumn(relativeWidth: width);
|
||||
}
|
||||
|
||||
public void ComplexColumn(float constantWidth = 0, float relativeWidth = 0)
|
||||
{
|
||||
var columnDefinition = new TableColumnDefinition(constantWidth, relativeWidth);
|
||||
Columns.Add(columnDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
public class TableCellDescriptor
|
||||
{
|
||||
private ICollection<TableCell> Cells { get; }
|
||||
|
||||
internal TableCellDescriptor(ICollection<TableCell> cells)
|
||||
{
|
||||
Cells = cells;
|
||||
}
|
||||
|
||||
public ITableCellContainer Cell()
|
||||
{
|
||||
var cell = new TableCell();
|
||||
Cells.Add(cell);
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
public class TableDescriptor
|
||||
{
|
||||
internal List<TableColumnDefinition> Columns { get; private set; }
|
||||
|
||||
private Table HeaderTable { get; } = new();
|
||||
private Table ContentTable { get; } = new();
|
||||
private Table FooterTable { get; } = new();
|
||||
|
||||
public void ColumnsDefinition(Action<TableColumnsDefinitionDescriptor> handler)
|
||||
{
|
||||
var descriptor = new TableColumnsDefinitionDescriptor();
|
||||
handler(descriptor);
|
||||
|
||||
HeaderTable.Columns = descriptor.Columns;
|
||||
ContentTable.Columns = descriptor.Columns;
|
||||
FooterTable.Columns = descriptor.Columns;
|
||||
}
|
||||
|
||||
public void ExtendLastCellsToTableBottom()
|
||||
{
|
||||
ContentTable.ExtendLastCellsToTableBottom = true;
|
||||
}
|
||||
|
||||
public void Header(Action<TableCellDescriptor> handler)
|
||||
{
|
||||
var descriptor = new TableCellDescriptor(HeaderTable.Cells);
|
||||
handler(descriptor);
|
||||
}
|
||||
|
||||
public void Footer(Action<TableCellDescriptor> handler)
|
||||
{
|
||||
var descriptor = new TableCellDescriptor(FooterTable.Cells);
|
||||
handler(descriptor);
|
||||
}
|
||||
|
||||
public ITableCellContainer Cell()
|
||||
{
|
||||
var cell = new TableCell();
|
||||
ContentTable.Cells.Add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
internal IElement CreateElement()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
ConfigureTable(HeaderTable);
|
||||
ConfigureTable(ContentTable);
|
||||
ConfigureTable(FooterTable);
|
||||
|
||||
container
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Header().Element(HeaderTable);
|
||||
decoration.Content().Element(ContentTable);
|
||||
decoration.Footer().Element(FooterTable);
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private static void ConfigureTable(Table table)
|
||||
{
|
||||
if (!table.Columns.Any())
|
||||
throw new DocumentComposeException($"Table should have at least one column. Please call the '{nameof(ColumnsDefinition)}' method to define columns.");
|
||||
|
||||
table.PlanCellPositions();
|
||||
table.ValidateCellPositions();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TableExtensions
|
||||
{
|
||||
public static void Table(this IContainer element, Action<TableDescriptor> handler)
|
||||
{
|
||||
var descriptor = new TableDescriptor();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.CreateElement());
|
||||
}
|
||||
}
|
||||
|
||||
public static class TableCellExtensions
|
||||
{
|
||||
private static ITableCellContainer TableCell(this ITableCellContainer element, Action<TableCell> handler)
|
||||
{
|
||||
if (element is TableCell tableCell)
|
||||
handler(tableCell);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public static ITableCellContainer Column(this ITableCellContainer tableCellContainer, uint value)
|
||||
{
|
||||
return tableCellContainer.TableCell(x => x.Column = (int)value);
|
||||
}
|
||||
|
||||
public static ITableCellContainer ColumnSpan(this ITableCellContainer tableCellContainer, uint value)
|
||||
{
|
||||
return tableCellContainer.TableCell(x => x.ColumnSpan = (int)value);
|
||||
}
|
||||
|
||||
public static ITableCellContainer Row(this ITableCellContainer tableCellContainer, uint value)
|
||||
{
|
||||
return tableCellContainer.TableCell(x => x.Row = (int)value);
|
||||
}
|
||||
|
||||
public static ITableCellContainer RowSpan(this ITableCellContainer tableCellContainer, uint value)
|
||||
{
|
||||
return tableCellContainer.TableCell(x => x.RowSpan = (int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
@@ -14,7 +13,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
@@ -47,15 +46,12 @@ namespace QuestPDF.Fluent
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.Last().Items.Add(item);
|
||||
TextBlocks.Last().Children.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string? text, TextStyle? style = null)
|
||||
public void Span(string text, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
style ??= DefaultStyle;
|
||||
|
||||
var items = text
|
||||
.Replace("\r", string.Empty)
|
||||
@@ -73,18 +69,22 @@ namespace QuestPDF.Fluent
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Items = new List<ITextBlockItem> { x }
|
||||
Children = new List<ITextBlockItem> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void Line(string? text, TextStyle? style = null)
|
||||
public void Line(string text, TextStyle? style = null)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
Span(text + Environment.NewLine, style);
|
||||
}
|
||||
|
||||
|
||||
public void Line(string text)
|
||||
{
|
||||
Span(text + Environment.NewLine);
|
||||
}
|
||||
|
||||
public void EmptyLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
@@ -92,10 +92,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(slotName))
|
||||
throw new ArgumentException(nameof(slotName));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber()
|
||||
{
|
||||
@@ -116,21 +113,12 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
|
||||
PageNumber(locationName, style);
|
||||
}
|
||||
|
||||
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
|
||||
public void InternalLocation(string text, string locationName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockInternalLink
|
||||
{
|
||||
@@ -140,15 +128,9 @@ namespace QuestPDF.Fluent
|
||||
});
|
||||
}
|
||||
|
||||
public void ExternalLocation(string? text, string url, TextStyle? style = null)
|
||||
public void ExternalLocation(string text, string url, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(url))
|
||||
throw new ArgumentException(nameof(url));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
style ??= DefaultStyle;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockExternalLink
|
||||
{
|
||||
@@ -167,14 +149,14 @@ namespace QuestPDF.Fluent
|
||||
Element = container
|
||||
});
|
||||
|
||||
return container.AlignBottom().MinimalBox();
|
||||
return container.AlignBottom().Box();
|
||||
}
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
|
||||
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
@@ -188,18 +170,19 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static void Text(this IContainer element, Action<TextDescriptor> content)
|
||||
{
|
||||
var descriptor = new TextDescriptor();
|
||||
|
||||
var textBlock = new TextBlock();
|
||||
|
||||
if (element is Alignment alignment)
|
||||
descriptor.Alignment = alignment.Horizontal;
|
||||
textBlock.Alignment = alignment.Horizontal;
|
||||
|
||||
var descriptor = new TextDescriptor();
|
||||
content?.Invoke(descriptor);
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object? text, TextStyle? style = null)
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
{
|
||||
element.Text(x => x.Span(text?.ToString(), style));
|
||||
element.Text(x => x.Span(text.ToString(), style));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Helpers
|
||||
{
|
||||
@@ -43,13 +41,5 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
|
||||
}
|
||||
|
||||
internal static void VisitChildren(this Element? element, Action<Element?> handler)
|
||||
{
|
||||
foreach (var child in element.GetChildren().Where(x => x != null))
|
||||
VisitChildren(child, handler);
|
||||
|
||||
handler(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,12 @@ namespace QuestPDF.Helpers
|
||||
|
||||
public static class PageSizeExtensions
|
||||
{
|
||||
public static PageSize Portrait(this PageSize size)
|
||||
public static PageSize Portrait(this Size size)
|
||||
{
|
||||
return new PageSize(Math.Min(size.Width, size.Height), Math.Max(size.Width, size.Height));
|
||||
}
|
||||
|
||||
public static PageSize Landscape(this PageSize size)
|
||||
public static PageSize Landscape(this Size size)
|
||||
{
|
||||
return new PageSize(Math.Max(size.Width, size.Height), Math.Min(size.Width, size.Height));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
public static class Placeholders
|
||||
{
|
||||
public static readonly Random Random = new Random(3); // 3
|
||||
public static readonly Random Random = new Random();
|
||||
|
||||
#region Word Cache
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
|
||||
@@ -15,9 +14,10 @@ namespace QuestPDF.Infrastructure
|
||||
set => Child = value as Element;
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
yield return Child;
|
||||
Child?.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure
|
||||
internal IPageContext PageContext { get; set; }
|
||||
internal ICanvas Canvas { get; set; }
|
||||
|
||||
internal virtual IEnumerable<Element?> GetChildren()
|
||||
internal virtual void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
yield break;
|
||||
visit(this);
|
||||
}
|
||||
|
||||
internal void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Elements;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public interface IDynamicComponent
|
||||
{
|
||||
void Compose(DynamicContext context, IDynamicContainer container);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,5 @@
|
||||
{
|
||||
return new Position(-X, -Y);
|
||||
}
|
||||
|
||||
public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})";
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,12 @@
|
||||
public readonly struct Size
|
||||
{
|
||||
public const float Epsilon = 0.001f;
|
||||
public const float Infinity = float.PositiveInfinity;
|
||||
|
||||
public readonly float Width;
|
||||
public readonly float Height;
|
||||
|
||||
public static Size Zero { get; } = new Size(0, 0);
|
||||
public static Size Max { get; } = new Size(14_400, 14_400);
|
||||
public static Size Zero => new Size(0, 0);
|
||||
public static Size Max => new Size(14_400, 14_400);
|
||||
|
||||
public Size(float width, float height)
|
||||
{
|
||||
@@ -17,6 +16,6 @@
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})";
|
||||
public override string ToString() => $"{Width:N2} {Height:N2}";
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,29 @@
|
||||
using System;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class TextStyle
|
||||
{
|
||||
internal bool HasGlobalStyleApplied { get; private set; }
|
||||
|
||||
internal string? Color { get; set; }
|
||||
internal string? BackgroundColor { get; set; }
|
||||
internal string? FontType { get; set; }
|
||||
internal float? Size { get; set; }
|
||||
internal float? LineHeight { get; set; }
|
||||
internal FontWeight? FontWeight { get; set; }
|
||||
internal bool? IsItalic { get; set; }
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
|
||||
internal string? Key { get; private set; }
|
||||
|
||||
internal static TextStyle LibraryDefault => new TextStyle
|
||||
{
|
||||
Color = Colors.Black,
|
||||
BackgroundColor = Colors.Transparent,
|
||||
FontType = Fonts.Calibri,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
IsItalic = false,
|
||||
HasStrikethrough = false,
|
||||
HasUnderline = false
|
||||
};
|
||||
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 FontWeight FontWeight { get; set; } = FontWeight.Normal;
|
||||
internal bool IsItalic { get; set; } = false;
|
||||
internal bool HasStrikethrough { get; set; } = false;
|
||||
internal bool HasUnderline { get; set; } = false;
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
internal void ApplyGlobalStyle(TextStyle globalStyle)
|
||||
{
|
||||
if (HasGlobalStyleApplied)
|
||||
return;
|
||||
|
||||
HasGlobalStyleApplied = true;
|
||||
|
||||
ApplyParentStyle(globalStyle);
|
||||
Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
|
||||
}
|
||||
private string? KeyCache { get; set; }
|
||||
|
||||
internal void ApplyParentStyle(TextStyle parentStyle)
|
||||
public override string ToString()
|
||||
{
|
||||
Color ??= parentStyle.Color;
|
||||
BackgroundColor ??= parentStyle.BackgroundColor;
|
||||
FontType ??= parentStyle.FontType;
|
||||
Size ??= parentStyle.Size;
|
||||
LineHeight ??= parentStyle.LineHeight;
|
||||
FontWeight ??= parentStyle.FontWeight;
|
||||
IsItalic ??= parentStyle.IsItalic;
|
||||
HasStrikethrough ??= parentStyle.HasStrikethrough;
|
||||
HasUnderline ??= parentStyle.HasUnderline;
|
||||
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
|
||||
return KeyCache;
|
||||
}
|
||||
|
||||
internal TextStyle Clone()
|
||||
{
|
||||
var clone = (TextStyle)MemberwiseClone();
|
||||
clone.HasGlobalStyleApplied = false;
|
||||
return clone;
|
||||
}
|
||||
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.1.0-beta4</Version>
|
||||
<Version>2021.10.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>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||
<LangVersion>9</LangVersion>
|
||||
<PackageReleaseNotes>Enhanced text rendering capabilities. Improved rendering performance.</PackageReleaseNotes>
|
||||
<LangVersion>8</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
@@ -15,12 +15,10 @@
|
||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
|
||||
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
|
||||
<PackageTags>pdf file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,221 +1,27 @@
|
||||
## QuestPDF Overview
|
||||
## Rely on solid fundamentals
|
||||
|
||||
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.
|
||||
This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results.
|
||||
|
||||
## Features
|
||||
## Work with organized self-explanatory code
|
||||
|
||||
**Rely on solid fundamentals** - This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results.
|
||||
The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code.
|
||||
|
||||
**Work with organized self-explanatory code** - The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code.
|
||||
## Compose simple components into complex documents
|
||||
|
||||
**Compose simple components into complex documents** - Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here!
|
||||
Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here!
|
||||
|
||||
**Create and reuse components** - Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path.
|
||||
## Create and reuse components
|
||||
|
||||
**Prototype with ease** - We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data.
|
||||
Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path.
|
||||
|
||||
**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.
|
||||
## Prototype with ease
|
||||
|
||||
## Learning resources
|
||||
We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data.
|
||||
|
||||
**[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.
|
||||
## Enjoy fast PDF generation
|
||||
|
||||
**[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
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.
|
||||
|
||||
**[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
## Write less, achieve more
|
||||
|
||||
**[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain.
|
||||
|
||||
## Example invoice
|
||||
|
||||
Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices.
|
||||
|
||||
For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html).
|
||||
|
||||

|
||||
|
||||
Here you can find an example code showing how easy is to write and understand the fluent API.
|
||||
|
||||
|
||||
**General document structure** with header, content and footer:
|
||||
|
||||
```csharp
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
|
||||
page.Footer().AlignCenter().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The header area** consists of basic invoice information along with a logo placeholder.
|
||||
|
||||
```csharp
|
||||
void ComposeHeader(IContainer container)
|
||||
{
|
||||
var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium);
|
||||
|
||||
container.Row(row =>
|
||||
{
|
||||
{
|
||||
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle);
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.Span("Issue date: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{Model.IssueDate:d}");
|
||||
});
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.Span("Due date: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{Model.DueDate:d}");
|
||||
});
|
||||
});
|
||||
|
||||
row.ConstantColumn(100).Height(50).Placeholder();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
|
||||
|
||||
```csharp
|
||||
void ComposeContent(IContainer container)
|
||||
{
|
||||
container.PaddingVertical(40).Stack(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Component(new AddressComponent("From", Model.SellerAddress));
|
||||
row.ConstantColumn(50);
|
||||
row.RelativeColumn().Component(new AddressComponent("For", Model.CustomerAddress));
|
||||
});
|
||||
|
||||
column.Item().Element(ComposeTable);
|
||||
|
||||
var totalPrice = Model.Items.Sum(x => x.Price * x.Quantity);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.PaddingRight(5)
|
||||
.AlignRight()
|
||||
.Text($"Grand total: {totalPrice}$", TextStyle.Default.SemiBold());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Model.Comments))
|
||||
column.Item().PaddingTop(25).Element(ComposeComments);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The table and comments** codes are extracted into separate methods to increase clarity:
|
||||
|
||||
```csharp
|
||||
void ComposeTable(IContainer container)
|
||||
{
|
||||
var headerStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
container.Decoration(decoration =>
|
||||
{
|
||||
// header
|
||||
decoration.Header().BorderBottom(1).Padding(5).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text("#", headerStyle);
|
||||
row.RelativeColumn(3).Text("Product", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Unit price", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Quantity", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Total", headerStyle);
|
||||
});
|
||||
|
||||
// content
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(column =>
|
||||
{
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text(Model.Items.IndexOf(item) + 1);
|
||||
row.RelativeColumn(3).Text(item.Name);
|
||||
row.RelativeColumn().AlignRight().Text($"{item.Price}$");
|
||||
row.RelativeColumn().AlignRight().Text(item.Quantity);
|
||||
row.RelativeColumn().AlignRight().Text($"{item.Price * item.Quantity}$");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
void ComposeComments(IContainer container)
|
||||
{
|
||||
container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message =>
|
||||
{
|
||||
message.Spacing(5);
|
||||
message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold());
|
||||
message.Item().Text(Model.Comments);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The address details section** is implemented using components. This way the code can be easily reused for both seller and customer:
|
||||
|
||||
```csharp
|
||||
public class AddressComponent : IComponent
|
||||
{
|
||||
private string Title { get; }
|
||||
private Address Address { get; }
|
||||
|
||||
public AddressComponent(string title, Address address)
|
||||
{
|
||||
Title = title;
|
||||
Address = address;
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container.ShowEntire().Stack(column =>
|
||||
{
|
||||
column.Spacing(5);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.BorderBottom(1)
|
||||
.PaddingBottom(5)
|
||||
.Text(Title, TextStyle.Default.SemiBold());
|
||||
|
||||
column.Item().Text(Address.CompanyName);
|
||||
column.Item().Text(Address.Street);
|
||||
column.Item().Text($"{Address.City}, {Address.State}");
|
||||
column.Item().Text(Address.Email);
|
||||
column.Item().Text(Address.Phone);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices. [Learn more](https://www.questpdf.com/documentation/getting-started.html)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user