Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c679978be | |||
| 4fcd955238 | |||
| a9c06db2e0 | |||
| 2f55b8d386 | |||
| e4a5c471ee | |||
| 4e3d462e8d | |||
| 423ca330f7 | |||
| 57c2a8cb38 | |||
| 7c51649415 | |||
| 423c630a47 | |||
| 42c6c75862 | |||
| 559de4520a | |||
| 73e66c293b | |||
| 164bdd76d8 | |||
| d62cec4f66 | |||
| baa0feb5e6 | |||
| 57e68e293d | |||
| 320a6e221d | |||
| d92231f5d4 | |||
| 1060e4a44a | |||
| 983edd5f76 | |||
| 5315165cf5 | |||
| ee3d87010e | |||
| 3ac91c4971 | |||
| 8e450934ab | |||
| bf193eec23 | |||
| c9104540aa | |||
| af60d84abe | |||
| d56117647b | |||
| 117913693c | |||
| 2ba716a5ba | |||
| be2108f6fe | |||
| c08bb963a6 | |||
| 8ed8897fd1 | |||
| 2a0e91af68 | |||
| f647b79a0a | |||
| b1286bb536 | |||
| 43d1a55df0 | |||
| d1cd7b06db | |||
| 7bce01d6e6 | |||
| e3ff2960e1 | |||
| 5d9e6f327b | |||
| b895931eb9 | |||
| 86a35b7cd3 | |||
| 65990ebf59 | |||
| c348106b3f | |||
| 9d86e9efd3 | |||
| 9770575f9a | |||
| e6e6febf8b | |||
| 5cbacc7ea1 | |||
| 8214a8429d | |||
| 389b8ce304 | |||
| e1f7cff4aa | |||
| 6801425082 | |||
| ec31c9b063 | |||
| 6c8867e1b3 | |||
| 20d86cbfde | |||
| 16a164e5b8 | |||
| 261f087b46 | |||
| c876350b05 | |||
| 58f3932241 | |||
| 350abc2838 | |||
| 5d4c9a5dd2 | |||
| b31d097b61 | |||
| 9f6c091706 | |||
| af2229eabe | |||
| b86946b396 | |||
| e1ae97d3ca | |||
| a7ac747776 | |||
| fab0f5bfe1 | |||
| f5f90bd235 | |||
| 603dc13d0f | |||
| e277c6a8dc | |||
| 5ff9d03f20 |
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ContentDirectionExamples
|
||||
{
|
||||
private Action<IContainer> ContentDirectionTemplate(Action<IContainer> content)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container
|
||||
.MinimalBox()
|
||||
.ExtendHorizontal()
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(1);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
|
||||
header.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
|
||||
|
||||
static Action<IContainer> HeaderCell(string label)
|
||||
{
|
||||
return container => container
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.PaddingHorizontal(15)
|
||||
.PaddingVertical(5)
|
||||
.Text(label)
|
||||
.FontSize(18)
|
||||
.SemiBold();
|
||||
}
|
||||
});
|
||||
|
||||
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
|
||||
table.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
table.Cell().Element(TestCell).ContentFromRightToLeft().Element(content);
|
||||
|
||||
static IContainer TestCell(IContainer container)
|
||||
{
|
||||
return container.Padding(15);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Page()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A5);
|
||||
page.Margin(20);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
|
||||
page.ContentFromRightToLeft();
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item()
|
||||
.Text("مثال على الفاتورة") // example invoice
|
||||
.FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
|
||||
|
||||
column.Item().Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(100);
|
||||
});
|
||||
|
||||
table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
|
||||
table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
|
||||
table.Cell().Element(HeaderStyle).Text("سعر"); // price
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
"دورة البرمجة", // programming course
|
||||
"دورة تصميم الرسومات", // graphics design course
|
||||
"تحليل وتصميم الخوارزميات", // analysis and design of algorithms
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var price = Placeholders.Random.NextDouble() * 100;
|
||||
|
||||
table.Cell().Text(item);
|
||||
table.Cell().Text(Placeholders.Random.Next(1, 10));
|
||||
table.Cell().Text($"USD${price:F2}");
|
||||
}
|
||||
|
||||
static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Column()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(5);
|
||||
|
||||
column.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
column.Item().Height(50).Width(100).Background(Colors.Green.Lighten1);
|
||||
column.Item().Height(50).Width(150).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Row()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.Spacing(5);
|
||||
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
|
||||
row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Table()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Height(50).Background(Colors.Red.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Green.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Blue.Lighten1);
|
||||
table.Cell().ColumnSpan(2).Height(50).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Width(50).Height(50).Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unconstrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Unconstrained()
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Inlined()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(InlinedAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(5);
|
||||
|
||||
inlined.Alignment(alignment);
|
||||
|
||||
inlined.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
inlined.Item().Height(50).Width(75).Background(Colors.Green.Lighten1);
|
||||
inlined.Item().Height(50).Width(100).Background(Colors.Blue.Lighten1);
|
||||
inlined.Item().Height(50).Width(125).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Text()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(HorizontalAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Text(text =>
|
||||
{
|
||||
text.Alignment = alignment; // internal API
|
||||
|
||||
text.Span("Lorem ipsum").Bold().FontColor(Colors.Red.Medium);
|
||||
text.Element().Width(5);
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Decoration()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Decoration(decoration =>
|
||||
{
|
||||
decoration.Before().Background(Colors.Green.Lighten1).Padding(5).Text("Before").FontSize(16);
|
||||
decoration.Content().Background(Colors.Green.Lighten2).Padding(5).Text("Content").FontSize(16);
|
||||
decoration.After().Background(Colors.Green.Lighten3).Padding(5).Text("After").FontSize(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Dynamic(new SimpleDynamic());
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleDynamic : IDynamicComponent<int>
|
||||
{
|
||||
public int State { get; set; }
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.ConstantItem(50).Background(Colors.Red.Lighten2).Height(50);
|
||||
row.ConstantItem(75).Background(Colors.Green.Lighten2).Height(50);
|
||||
row.ConstantItem(100).Background(Colors.Blue.Lighten2).Height(50);
|
||||
});
|
||||
});
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
@@ -34,6 +35,21 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DynamicImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 350)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25)
|
||||
.Image(Placeholders.Image);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
@@ -47,5 +63,43 @@ namespace QuestPDF.Examples
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReusingTheSameImageFileShouldBePossible()
|
||||
{
|
||||
var fileName = Path.GetTempFileName() + ".jpg";
|
||||
|
||||
try
|
||||
{
|
||||
var image = Placeholders.Image(300, 100);
|
||||
|
||||
using var file = File.Create(fileName);
|
||||
file.Write(image);
|
||||
file.Dispose();
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
});
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RelativePaddingExamples
|
||||
{
|
||||
[Test]
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(250, 250)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Width(250)
|
||||
.Height(250)
|
||||
|
||||
.Padding(50)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
|
||||
.RelativePaddingLeft(0.1f)
|
||||
.RelativePaddingTop(0.2f)
|
||||
.RelativePaddingRight(0.3f)
|
||||
.RelativePaddingBottom(0.4f)
|
||||
|
||||
.Background(Colors.Grey.Darken2);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RelativePositionExamples
|
||||
{
|
||||
[Test]
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(500, 500)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(100)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.RelativePositionVertical(0.5f, -0.5f)
|
||||
.RelativePositionHorizontal(1f, -0.5f)
|
||||
.RelativeWidth(0.4f)
|
||||
.RelativeHeight(0.6f)
|
||||
.Background(Colors.Grey.Darken2);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RelativeSizeExamples
|
||||
{
|
||||
[Test]
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(600, 600)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Width(400)
|
||||
.Height(400)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Container()
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.RelativeWidth(0.25f)
|
||||
.RelativeHeight(0.5f)
|
||||
.Background(Colors.Grey.Darken2);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,5 +105,41 @@ namespace QuestPDF.Examples
|
||||
.Row(row => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RowElementForRelativeHeightDivision()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 400)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.AlignLeft()
|
||||
.RotateRight()
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(20);
|
||||
|
||||
row.RelativeItem(1).Element(Content);
|
||||
row.RelativeItem(2).Element(Content);
|
||||
row.RelativeItem(3).Element(Content);
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.RotateLeft()
|
||||
.Border(1)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.Padding(5)
|
||||
.Text(Placeholders.Label());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ShrinkExamples
|
||||
{
|
||||
[Test]
|
||||
public void Shrink_Without()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Border(2)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(20)
|
||||
.Text("This is test.")
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Shrink_Horizontal()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Border(2)
|
||||
.ShrinkHorizontal()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(20)
|
||||
.Text("This is test.")
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Shrink_Vertical()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Border(2)
|
||||
.ShrinkVertical()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(20)
|
||||
.Text("This is test.")
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Shrink_Both()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Border(2)
|
||||
.Shrink()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(20)
|
||||
.Text("This is test.")
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
@@ -604,7 +604,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 100)
|
||||
.PageSize(250, 100)
|
||||
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
@@ -614,9 +614,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text("ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا")
|
||||
.Text("خوارزمية ترتيب")
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(20);
|
||||
.FontSize(30);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -657,5 +657,134 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WordWrappingStability()
|
||||
{
|
||||
// instruction: check if any characters repeat when performing the word-wrapping algorithm
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "Lorem ipsum dolor sit amet consectetuer";
|
||||
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
foreach (var width in Enumerable.Range(25, 200))
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.MaxWidth(width)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Text(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AdvancedLanguagesSupport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(400, 400))
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||
|
||||
container
|
||||
.Padding(20)
|
||||
.ContentFromRightToLeft()
|
||||
.Text(text)
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(22);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WordWrappingWhenRightToLeft()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(1000, 500))
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||
|
||||
container
|
||||
.Padding(25)
|
||||
.ContentFromRightToLeft()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
foreach (var size in new[] { 36, 34, 32, 30, 15 })
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.MaxWidth(size * 25)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text(text)
|
||||
.FontSize(20)
|
||||
.FontFamily("Segoe UI");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ForcingTextDirection()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(new PageSize(1000, 500))
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri"))
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
var word = "الجوريتم";
|
||||
var definition = "algorithm in Arabic";
|
||||
|
||||
var text = $"{word} - {definition}";
|
||||
|
||||
// text direction is automatically detected using the first word
|
||||
column.Item().Text(text);
|
||||
|
||||
// it is possible to force specific content direction
|
||||
column.Item().Text(text).DirectionFromLeftToRight();
|
||||
column.Item().Text(text).DirectionFromRightToLeft();
|
||||
|
||||
// to combine text in various content directions, split it into segments
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.Span(word);
|
||||
text.Span(" - ");
|
||||
text.Span(definition);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
using QuestPDF.ReportSample;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
// var model = DataSource.GetReport();
|
||||
// var report = new StandardReport(model);
|
||||
// report.ShowInPreviewer();
|
||||
//
|
||||
// return;
|
||||
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hot Reload!")
|
||||
.SemiBold().FontSize(36).FontColor(Colors.Blue.Darken2);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Table(t =>
|
||||
{
|
||||
t.ColumnsDefinition(c =>
|
||||
{
|
||||
c.RelativeColumn();
|
||||
c.RelativeColumn(3);
|
||||
});
|
||||
|
||||
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Visual Studio");
|
||||
t.Cell().Border(1).Padding(5).Text("Start in debug mode with 'Hot Reload on Save' enabled.");
|
||||
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Command line");
|
||||
t.Cell().Border(1).Padding(5).Text("Run 'dotnet watch'.");
|
||||
});
|
||||
|
||||
x.Item().Text("Modify this line and the preview should show your changes instantly.");
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
})
|
||||
.ShowInPreviewer();
|
||||
@@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QuestPDF.ReportSample\QuestPDF.ReportSample.csproj" />
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,79 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class CommunicationService
|
||||
{
|
||||
public static CommunicationService Instance { get; } = new ();
|
||||
|
||||
public event Action<ICollection<PreviewPage>>? OnDocumentRefreshed;
|
||||
|
||||
private WebApplication? Application { get; set; }
|
||||
|
||||
private readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private CommunicationService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Task Start(int port)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Services.AddLogging(x => x.ClearProviders());
|
||||
builder.WebHost.UseKestrel(options => options.Limits.MaxRequestBodySize = null);
|
||||
Application = builder.Build();
|
||||
|
||||
Application.MapGet("ping", HandlePing);
|
||||
Application.MapGet("version", HandleVersion);
|
||||
Application.MapPost("update/preview", HandleUpdatePreview);
|
||||
|
||||
return Application.RunAsync($"http://localhost:{port}/");
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
await Application.StopAsync();
|
||||
await Application.DisposeAsync();
|
||||
}
|
||||
|
||||
private async Task<IResult> HandlePing()
|
||||
{
|
||||
return OnDocumentRefreshed == null
|
||||
? Results.StatusCode(StatusCodes.Status503ServiceUnavailable)
|
||||
: Results.Ok();
|
||||
}
|
||||
|
||||
private async Task<IResult> HandleVersion()
|
||||
{
|
||||
return Results.Json(GetType().Assembly.GetName().Version);
|
||||
}
|
||||
|
||||
private async Task<IResult> HandleUpdatePreview(HttpRequest request)
|
||||
{
|
||||
var command = JsonSerializer.Deserialize<DocumentSnapshot>(request.Form["command"], JsonSerializerOptions);
|
||||
|
||||
var pages = command
|
||||
.Pages
|
||||
.Select(page =>
|
||||
{
|
||||
using var stream = request.Form.Files[page.Id].OpenReadStream();
|
||||
var picture = SKPicture.Deserialize(stream);
|
||||
|
||||
return new PreviewPage(picture, page.Width, page.Height);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
Task.Run(() => OnDocumentRefreshed(pages));
|
||||
return Results.Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
public static void GeneratePdfFromDocumentSnapshots(string filePath, ICollection<PreviewPage> pages)
|
||||
{
|
||||
using var stream = File.Create(filePath);
|
||||
|
||||
using var document = SKDocument.CreatePdf(stream);
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
using var canvas = document.BeginPage(page.Width, page.Height);
|
||||
canvas.DrawPicture(page.Picture);
|
||||
document.EndPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.SceneGraph;
|
||||
using Avalonia.Skia;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class InteractiveCanvas : ICustomDrawOperation
|
||||
{
|
||||
public Rect Bounds { get; set; }
|
||||
public ICollection<PreviewPage> Pages { get; set; }
|
||||
|
||||
private float Width => (float)Bounds.Width;
|
||||
private float Height => (float)Bounds.Height;
|
||||
|
||||
public float Scale { get; private set; } = 1;
|
||||
public float TranslateX { get; set; }
|
||||
public float TranslateY { get; set; }
|
||||
|
||||
private const float MinScale = 0.1f;
|
||||
private const float MaxScale = 10f;
|
||||
|
||||
private const float PageSpacing = 25f;
|
||||
private const float SafeZone = 25f;
|
||||
|
||||
public float TotalPagesHeight => Pages.Sum(x => x.Height) + (Pages.Count - 1) * PageSpacing;
|
||||
public float TotalHeight => TotalPagesHeight + SafeZone * 2 / Scale;
|
||||
public float MaxWidth => Pages.Any() ? Pages.Max(x => x.Width) : 0;
|
||||
|
||||
public float MaxTranslateY => TotalHeight - Height / Scale;
|
||||
|
||||
public float ScrollPercentY
|
||||
{
|
||||
get
|
||||
{
|
||||
return TranslateY / MaxTranslateY;
|
||||
}
|
||||
set
|
||||
{
|
||||
TranslateY = value * MaxTranslateY;
|
||||
}
|
||||
}
|
||||
|
||||
public float ScrollViewportSizeY
|
||||
{
|
||||
get
|
||||
{
|
||||
var viewPortSize = Height / Scale / TotalHeight;
|
||||
return Math.Clamp(viewPortSize, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#region transformations
|
||||
|
||||
private void LimitScale()
|
||||
{
|
||||
Scale = Math.Max(Scale, MinScale);
|
||||
Scale = Math.Min(Scale, MaxScale);
|
||||
}
|
||||
|
||||
private void LimitTranslate()
|
||||
{
|
||||
if (TotalPagesHeight > Height / Scale)
|
||||
{
|
||||
TranslateY = Math.Min(TranslateY, MaxTranslateY);
|
||||
TranslateY = Math.Max(TranslateY, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateY = (TotalPagesHeight - Height / Scale) / 2;
|
||||
}
|
||||
|
||||
if (Width / Scale < MaxWidth)
|
||||
{
|
||||
var maxTranslateX = (Width / 2 - SafeZone) / Scale - MaxWidth / 2;
|
||||
|
||||
TranslateX = Math.Min(TranslateX, -maxTranslateX);
|
||||
TranslateX = Math.Max(TranslateX, maxTranslateX);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateX = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void TranslateWithCurrentScale(float x, float y)
|
||||
{
|
||||
TranslateX += x / Scale;
|
||||
TranslateY += y / Scale;
|
||||
|
||||
LimitTranslate();
|
||||
}
|
||||
|
||||
public void ZoomToPoint(float x, float y, float factor)
|
||||
{
|
||||
var oldScale = Scale;
|
||||
Scale *= factor;
|
||||
|
||||
LimitScale();
|
||||
|
||||
TranslateX -= x / Scale - x / oldScale;
|
||||
TranslateY -= y / Scale - y / oldScale;
|
||||
|
||||
LimitTranslate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region rendering
|
||||
|
||||
public void Render(IDrawingContextImpl context)
|
||||
{
|
||||
if (Pages.Count <= 0)
|
||||
return;
|
||||
|
||||
LimitScale();
|
||||
LimitTranslate();
|
||||
|
||||
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
|
||||
|
||||
if (canvas == null)
|
||||
throw new InvalidOperationException($"Context needs to be ISkiaDrawingContextImpl but got {nameof(context)}");
|
||||
|
||||
var originalMatrix = canvas.TotalMatrix;
|
||||
|
||||
canvas.Translate(Width / 2, 0);
|
||||
|
||||
canvas.Scale(Scale);
|
||||
canvas.Translate(TranslateX, -TranslateY + SafeZone / Scale);
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
canvas.Translate(-page.Width / 2f, 0);
|
||||
DrawBlankPage(canvas, page.Width, page.Height);
|
||||
canvas.DrawPicture(page.Picture);
|
||||
canvas.Translate(page.Width / 2f, page.Height + PageSpacing);
|
||||
}
|
||||
|
||||
canvas.SetMatrix(originalMatrix);
|
||||
DrawInnerGradient(canvas);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public bool Equals(ICustomDrawOperation? other) => false;
|
||||
public bool HitTest(Point p) => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region blank page
|
||||
|
||||
private static SKPaint BlankPagePaint = new SKPaint
|
||||
{
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
private static SKPaint BlankPageShadowPaint = new SKPaint
|
||||
{
|
||||
ImageFilter = SKImageFilter.CreateBlendMode(
|
||||
SKBlendMode.Overlay,
|
||||
SKImageFilter.CreateDropShadowOnly(0, 6, 6, 6, SKColors.Black.WithAlpha(64)),
|
||||
SKImageFilter.CreateDropShadowOnly(0, 10, 14, 14, SKColors.Black.WithAlpha(32)))
|
||||
};
|
||||
|
||||
private void DrawBlankPage(SKCanvas canvas, float width, float height)
|
||||
{
|
||||
canvas.DrawRect(0, 0, width, height, BlankPageShadowPaint);
|
||||
canvas.DrawRect(0, 0, width, height, BlankPagePaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region inner viewport gradient
|
||||
|
||||
private const int InnerGradientSize = (int)SafeZone;
|
||||
private static readonly SKColor InnerGradientColor = SKColor.Parse("#666");
|
||||
|
||||
private void DrawInnerGradient(SKCanvas canvas)
|
||||
{
|
||||
// gamma correction
|
||||
var colors = Enumerable
|
||||
.Range(0, InnerGradientSize)
|
||||
.Select(x => 1f - x / (float) InnerGradientSize)
|
||||
.Select(x => Math.Pow(x, 2f))
|
||||
.Select(x => (byte)(x * 255))
|
||||
.Select(x => InnerGradientColor.WithAlpha(x))
|
||||
.ToArray();
|
||||
|
||||
using var fogPaint = new SKPaint
|
||||
{
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(0, 0),
|
||||
new SKPoint(0, InnerGradientSize),
|
||||
colors,
|
||||
SKShaderTileMode.Clamp)
|
||||
};
|
||||
|
||||
canvas.DrawRect(0, 0, Width, InnerGradientSize, fogPaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
record PreviewPage(SKPicture Picture, float Width, float Height);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<Application x:Class="QuestPDF.Previewer.PreviewerApp"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="QuestPDF Document Preview">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark" />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@@ -1,27 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerApp : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new PreviewerWindow()
|
||||
{
|
||||
DataContext = new PreviewerWindowViewModel()
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
class PreviewerControl : Control
|
||||
{
|
||||
private InteractiveCanvas InteractiveCanvas { get; set; } = new ();
|
||||
|
||||
public static readonly StyledProperty<ObservableCollection<PreviewPage>> PagesProperty =
|
||||
AvaloniaProperty.Register<PreviewerControl, ObservableCollection<PreviewPage>>(nameof(Pages));
|
||||
|
||||
public ObservableCollection<PreviewPage>? Pages
|
||||
{
|
||||
get => GetValue(PagesProperty);
|
||||
set => SetValue(PagesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<float> CurrentScrollProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(CurrentScroll));
|
||||
|
||||
public float CurrentScroll
|
||||
{
|
||||
get => GetValue(CurrentScrollProperty);
|
||||
set => SetValue(CurrentScrollProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<float> ScrollViewportSizeProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(ScrollViewportSize));
|
||||
|
||||
public float ScrollViewportSize
|
||||
{
|
||||
get => GetValue(ScrollViewportSizeProperty);
|
||||
set => SetValue(ScrollViewportSizeProperty, value);
|
||||
}
|
||||
|
||||
public PreviewerControl()
|
||||
{
|
||||
PagesProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
InteractiveCanvas.Pages = x.NewValue.Value;
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
CurrentScrollProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
InteractiveCanvas.ScrollPercentY = x.NewValue.Value;
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
ClipToBounds = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||
{
|
||||
base.OnPointerWheelChanged(e);
|
||||
|
||||
if ((e.KeyModifiers & KeyModifiers.Control) != 0)
|
||||
{
|
||||
var scaleFactor = 1 + e.Delta.Y / 10f;
|
||||
var point = new Point(Bounds.Center.X, Bounds.Top) - e.GetPosition(this);
|
||||
|
||||
InteractiveCanvas.ZoomToPoint((float)point.X, -(float)point.Y, (float)scaleFactor);
|
||||
}
|
||||
|
||||
if (e.KeyModifiers == KeyModifiers.None)
|
||||
{
|
||||
var translation = (float)e.Delta.Y * 25;
|
||||
InteractiveCanvas.TranslateWithCurrentScale(0, -translation);
|
||||
}
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
private bool IsMousePressed { get; set; }
|
||||
private Vector MousePosition { get; set; }
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
|
||||
if (IsMousePressed)
|
||||
{
|
||||
var currentPosition = e.GetPosition(this);
|
||||
var translation = currentPosition - MousePosition;
|
||||
InteractiveCanvas.TranslateWithCurrentScale((float)translation.X, -(float)translation.Y);
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
MousePosition = e.GetPosition(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
IsMousePressed = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
base.OnPointerReleased(e);
|
||||
IsMousePressed = false;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
CurrentScroll = InteractiveCanvas.ScrollPercentY;
|
||||
ScrollViewportSize = InteractiveCanvas.ScrollViewportSizeY;
|
||||
|
||||
InteractiveCanvas.Bounds = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
|
||||
context.Custom(InteractiveCanvas);
|
||||
base.Render(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
internal class DocumentSnapshot
|
||||
{
|
||||
public ICollection<PageSnapshot> Pages { get; set; }
|
||||
|
||||
public class PageSnapshot
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
|
||||
public SKPicture Picture { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="QuestPDF.Previewer.PreviewerWindow"
|
||||
x:DataType="previewer:PreviewerWindowViewModel"
|
||||
x:CompileBindings="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ExtendClientAreaToDecorationsHint="true"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
Background="#666"
|
||||
Icon="/Resources/Logo.png"
|
||||
UseLayoutRounding="True"
|
||||
Title="QuestPDF Document Preview">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Button.actions">
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
<Setter Property="CornerRadius" Value="100"/>
|
||||
<Setter Property="Background" Value="#888"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#999"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Panel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextAlignment="Center" Text="QuestPDF Previewer" FontSize="14" Foreground="#DFFF" FontWeight="Regular" IsHitTestVisible="False" />
|
||||
|
||||
<previewer:PreviewerControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CurrentScroll="{Binding CurrentScroll, Mode=TwoWay}"
|
||||
ScrollViewportSize="{Binding ScrollViewportSize, Mode=OneWayToSource}"
|
||||
Pages="{Binding Pages, Mode=OneWay}" />
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Vertical" VerticalAlignment="Bottom" Spacing="16" Margin="32">
|
||||
<Button Classes="actions"
|
||||
Command="{Binding ShowPdfCommand, Mode=OneTime}"
|
||||
IsVisible="{Binding !!Pages.Count}"
|
||||
ToolTip.Tip="Generates PDF file and shows it in the default browser. Useful for testing compatibility and interactive links.">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H13C12.59,21.75 12.2,21.44 11.86,21.1C11.53,20.77 11.25,20.4 11,20H6V4H13V9H18V10.18C18.71,10.34 19.39,10.61 20,11V8L14,2M20.31,18.9C21.64,16.79 21,14 18.91,12.68C16.8,11.35 14,12 12.69,14.08C11.35,16.19 12,18.97 14.09,20.3C15.55,21.23 17.41,21.23 18.88,20.32L22,23.39L23.39,22L20.31,18.9M16.5,19A2.5,2.5 0 0,1 14,16.5A2.5,2.5 0 0,1 16.5,14A2.5,2.5 0 0,1 19,16.5A2.5,2.5 0 0,1 16.5,19Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
|
||||
<Button Classes="actions"
|
||||
Command="{Binding ShowDocumentationCommand, Mode=OneTime}"
|
||||
ToolTip.Tip="Opens official QuestPDF documentation">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M19 1L14 6V17L19 12.5V1M21 5V18.5C19.9 18.15 18.7 18 17.5 18C15.8 18 13.35 18.65 12 19.5V6C10.55 4.9 8.45 4.5 6.5 4.5C4.55 4.5 2.45 4.9 1 6V20.65C1 20.9 1.25 21.15 1.5 21.15C1.6 21.15 1.65 21.1 1.75 21.1C3.1 20.45 5.05 20 6.5 20C8.45 20 10.55 20.4 12 21.5C13.35 20.65 15.8 20 17.5 20C19.15 20 20.85 20.3 22.25 21.05C22.35 21.1 22.4 21.1 22.5 21.1C22.75 21.1 23 20.85 23 20.6V6C22.4 5.55 21.75 5.25 21 5M10 18.41C8.75 18.09 7.5 18 6.5 18C5.44 18 4.18 18.19 3 18.5V7.13C3.91 6.73 5.14 6.5 6.5 6.5C7.86 6.5 9.09 6.73 10 7.13V18.41Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
|
||||
<Button Classes="actions"
|
||||
Command="{Binding SponsorProjectCommand, Mode=OneTime}"
|
||||
ToolTip.Tip="Do you like QuestPDF? Please consider sponsoring the project. It really helps!">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M12,21.1L10.5,22.4C3.9,16.5 0.5,13.4 0.5,9.6C0.5,8.4 0.9,7.3 1.5,6.4C1.5,6.6 1.5,6.8 1.5,7C1.5,11.7 5.4,15.2 12,21.1M13.6,17C18.3,12.7 21.5,9.9 21.6,7C21.6,5 20.1,3.5 18.1,3.5C16.5,3.5 15,4.5 14.5,5.9H12.6C12,4.5 10.5,3.5 9,3.5C7,3.5 5.5,5 5.5,7C5.5,9.9 8.6,12.7 13.4,17L13.5,17.1M18,1.5C21.1,1.5 23.5,3.9 23.5,7C23.5,10.7 20.1,13.8 13.5,19.8C6.9,13.9 3.5,10.8 3.5,7C3.5,3.9 5.9,1.5 9,1.5C10.7,1.5 12.4,2.3 13.5,3.6C14.6,2.3 16.3,1.5 18,1.5Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollBar Grid.Row="1" Grid.Column="1"
|
||||
Orientation="Vertical"
|
||||
AllowAutoHide="False"
|
||||
Minimum="0" Maximum="1"
|
||||
IsVisible="{Binding VerticalScrollbarVisible, Mode=OneWay}"
|
||||
Value="{Binding CurrentScroll, Mode=TwoWay}"
|
||||
ViewportSize="{Binding ScrollViewportSize, Mode=OneWay}"></ScrollBar>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Window>
|
||||
@@ -1,23 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
class PreviewerWindow : Window
|
||||
{
|
||||
public PreviewerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using ReactiveUI;
|
||||
using Unit = System.Reactive.Unit;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerWindowViewModel : ReactiveObject
|
||||
{
|
||||
private ObservableCollection<PreviewPage> _pages = new();
|
||||
public ObservableCollection<PreviewPage> Pages
|
||||
{
|
||||
get => _pages;
|
||||
set => this.RaiseAndSetIfChanged(ref _pages, value);
|
||||
}
|
||||
|
||||
private float _currentScroll;
|
||||
public float CurrentScroll
|
||||
{
|
||||
get => _currentScroll;
|
||||
set => this.RaiseAndSetIfChanged(ref _currentScroll, value);
|
||||
}
|
||||
|
||||
private float _scrollViewportSize;
|
||||
public float ScrollViewportSize
|
||||
{
|
||||
get => _scrollViewportSize;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _scrollViewportSize, value);
|
||||
VerticalScrollbarVisible = value < 1;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _verticalScrollbarVisible;
|
||||
public bool VerticalScrollbarVisible
|
||||
{
|
||||
get => _verticalScrollbarVisible;
|
||||
private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _verticalScrollbarVisible, value));
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ShowPdfCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> ShowDocumentationCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> SponsorProjectCommand { get; }
|
||||
|
||||
public PreviewerWindowViewModel()
|
||||
{
|
||||
CommunicationService.Instance.OnDocumentRefreshed += HandleUpdatePreview;
|
||||
|
||||
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/api-reference/index.html"));
|
||||
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
|
||||
}
|
||||
|
||||
private void ShowPdf()
|
||||
{
|
||||
var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.pdf");
|
||||
Helpers.GeneratePdfFromDocumentSnapshots(filePath, Pages);
|
||||
|
||||
OpenLink(filePath);
|
||||
}
|
||||
|
||||
private void OpenLink(string path)
|
||||
{
|
||||
using var openBrowserProcess = new Process
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = path
|
||||
}
|
||||
};
|
||||
|
||||
openBrowserProcess.Start();
|
||||
}
|
||||
|
||||
private void HandleUpdatePreview(ICollection<PreviewPage> pages)
|
||||
{
|
||||
var oldPages = Pages;
|
||||
|
||||
Pages.Clear();
|
||||
Pages = new ObservableCollection<PreviewPage>(pages);
|
||||
|
||||
foreach (var page in oldPages)
|
||||
page.Picture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.ReactiveUI;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
var applicationPort = GetCommunicationPort();
|
||||
CommunicationService.Instance.Start(applicationPort);
|
||||
|
||||
if(Application.Current?.ApplicationLifetime is ClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new PreviewerWindow()
|
||||
{
|
||||
DataContext = new PreviewerWindowViewModel()
|
||||
};
|
||||
|
||||
desktop.MainWindow.Show();
|
||||
desktop.Start(Array.Empty<string>());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AppBuilder
|
||||
.Configure(() => new PreviewerApp())
|
||||
.UsePlatformDetect()
|
||||
.UseReactiveUI()
|
||||
.StartWithClassicDesktopLifetime(Array.Empty<string>());
|
||||
|
||||
static int GetCommunicationPort()
|
||||
{
|
||||
const int defaultApplicationPort = 12500;
|
||||
|
||||
var arguments = Environment.GetCommandLineArgs();
|
||||
|
||||
if (arguments.Length < 2)
|
||||
return defaultApplicationPort;
|
||||
|
||||
return int.TryParse(arguments[1], out var port) ? port : defaultApplicationPort;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF.Previewer</PackageId>
|
||||
<Version>2022.9.1</Version>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>Initial release.</PackageReleaseNotes>
|
||||
<LangVersion>10</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||
<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 previewer</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<OutputType>exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Resources\Logo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Logo.png" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.80.4" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,23 +0,0 @@
|
||||
Install nuget locally (in directory where nupkg file is located)
|
||||
|
||||
```
|
||||
dotnet tool install --global --add-source . QuestPDF.Previewer --global
|
||||
```
|
||||
|
||||
Run on default port
|
||||
|
||||
```
|
||||
questpdf-previewer
|
||||
```
|
||||
|
||||
Run on custom port
|
||||
|
||||
```
|
||||
questpdf-previewer 12500
|
||||
```
|
||||
|
||||
Remove nuget locally
|
||||
|
||||
```
|
||||
dotnet tool uninstall QuestPDF.Previewer --global
|
||||
```
|
||||
@@ -83,6 +83,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
.Range(0, model.PhotoCount)
|
||||
.ToList()
|
||||
.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>());
|
||||
|
||||
//grid.Item().AspectRatio(4 / 3f).Width(100000).Component<ImagePlaceholder>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,11 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
|
||||
|
||||
text.Span(" (").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
||||
text.Span(")").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x =>
|
||||
{
|
||||
var formatted = x == 1 ? "1 page long" : $"{x} pages long";
|
||||
return $" ({formatted})";
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
|
||||
Content = documentContainer.Compose();
|
||||
|
||||
PageContext = new PageContext();
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
|
||||
[Benchmark]
|
||||
public void GenerationTest()
|
||||
{
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, null);
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
@@ -24,11 +25,13 @@ namespace QuestPDF.ReportSample
|
||||
[Test]
|
||||
public void GenerateAndShowPdf()
|
||||
{
|
||||
Report.ShowInPreviewer();
|
||||
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
Report.GeneratePdf(path);
|
||||
Process.Start("explorer.exe", path);
|
||||
//
|
||||
// var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
// Report.GeneratePdf(path);
|
||||
// Process.Start("explorer.exe", path);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -50,8 +53,8 @@ namespace QuestPDF.ReportSample
|
||||
|
||||
var pageContext = new PageContext();
|
||||
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, null);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
|
||||
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,9 @@ namespace QuestPDF.UnitTests
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -168,7 +170,45 @@ namespace QuestPDF.UnitTests
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerWidth_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f,
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(500, 200))
|
||||
.ExpectCanvasTranslate(100, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(-100, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerHeight_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f,
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class BoxTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
|
||||
|
||||
[Test]
|
||||
public void Draw_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.Wrap())
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_PartialRender_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectCanvasTranslate(200, 0)
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.ExpectCanvasTranslate(-200, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FullRender_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(350, 200))
|
||||
.ExpectCanvasTranslate(150, 0)
|
||||
.ExpectChildDraw(new Size(350, 200))
|
||||
.ExpectCanvasTranslate(-150, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
public class PreviewerTests
|
||||
{
|
||||
[Test]
|
||||
public void SendError()
|
||||
{
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!")
|
||||
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Text(Placeholders.LoremIpsum());
|
||||
x.Item().Image(Placeholders.Image(200, 100));
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
//throw new Exception("Dupa!");
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
})
|
||||
.ShowInPreviewer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ShrinkTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<Shrink>();
|
||||
|
||||
[Test]
|
||||
public void Draw_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Shrink
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ShrinkVertical = true,
|
||||
ShrinkHorizontal = true
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.Wrap())
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Shrink
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ShrinkVertical = true,
|
||||
ShrinkHorizontal = true
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Shrink
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ShrinkVertical = true,
|
||||
ShrinkHorizontal = true
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
||||
.ExpectChildDraw(new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||
{
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.InjectDependencies(null, Canvas);
|
||||
|
||||
var actual = Element.Measure(OperationInput);
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckDrawResult()
|
||||
{
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.InjectDependencies(null, Canvas);
|
||||
Element.Draw(OperationInput);
|
||||
return this;
|
||||
}
|
||||
@@ -253,10 +253,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var canvas = new FreeCanvas();
|
||||
value.VisitChildren(x => x.Initialize(null, canvas));
|
||||
value.InjectDependencies(null, canvas);
|
||||
var valueMeasure = value.Measure(availableSpace.Value);
|
||||
|
||||
expected.VisitChildren(x => x.Initialize(null, canvas));
|
||||
expected.InjectDependencies(null, canvas);
|
||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||
|
||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||
@@ -267,11 +267,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var valueCanvas = new OperationRecordingCanvas();
|
||||
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
||||
value.InjectDependencies(null, valueCanvas);
|
||||
value.Draw(availableSpace.Value);
|
||||
|
||||
var expectedCanvas = new OperationRecordingCanvas();
|
||||
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
||||
expected.InjectDependencies(null, expectedCanvas);
|
||||
expected.Draw(availableSpace.Value);
|
||||
|
||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||
|
||||
@@ -77,7 +77,9 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(1200, 1600))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -91,7 +93,43 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(1600, 1000))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenChildPartiallyRenders_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Unconstrained
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
||||
.ExpectCanvasTranslate(-1200, 0)
|
||||
.ExpectChildDraw(new Size(1200, 1600))
|
||||
.ExpectCanvasTranslate(1200, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenChildFullyRenders_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Unconstrained
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
||||
.ExpectCanvasTranslate(-1600, 0)
|
||||
.ExpectChildDraw(new Size(1600, 1000))
|
||||
.ExpectCanvasTranslate(1600, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
|
||||
+5
-11
@@ -8,14 +8,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer.Examples", "QuestPDF.Previewer.Examples\QuestPDF.Previewer.Examples.csproj", "{CA413A39-038F-4A9F-B56F-0E5413B6B158}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{73123649-216A-47B4-BFB2-DADF13FBE75D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer", "QuestPDF.Previewer\QuestPDF.Previewer.csproj", "{B2FF6003-3A45-4A78-A85D-B86C7F01D054}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleApp", "SimpleApp\SimpleApp.csproj", "{B361D452-80A8-493A-BEA6-AB268E99153E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -39,13 +37,9 @@ Global
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CA413A39-038F-4A9F-B56F-0E5413B6B158}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2FF6003-3A45-4A78-A85D-B86C7F01D054}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -16,13 +16,14 @@ namespace QuestPDF.Drawing
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.DebugPointer("Document")
|
||||
.Column(column =>
|
||||
{
|
||||
Pages
|
||||
.SelectMany(x => new List<Action>()
|
||||
{
|
||||
() => column.Item().PageBreak(),
|
||||
() => column.Item().Component(x)
|
||||
() => column.Item().DebugPointer("Page").Component(x)
|
||||
})
|
||||
.Skip(1)
|
||||
.ToList()
|
||||
|
||||
@@ -10,6 +10,7 @@ using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer.Inspection;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
@@ -51,35 +52,48 @@ namespace QuestPDF.Drawing
|
||||
return canvas.Images;
|
||||
}
|
||||
|
||||
internal static ICollection<PreviewerPicture> GeneratePreviewerPictures(IDocument document)
|
||||
internal static DocumentPreviewResult GeneratePreviewerPictures(IDocument document)
|
||||
{
|
||||
var canvas = new SkiaPictureCanvas();
|
||||
RenderDocument(canvas, document);
|
||||
return canvas.Pictures;
|
||||
InspectionElement documentHierarchy = null;
|
||||
|
||||
RenderDocument(canvas, document, true, x => documentHierarchy = x);
|
||||
|
||||
return new DocumentPreviewResult
|
||||
{
|
||||
PageSnapshots = canvas.Pictures.ToList(),
|
||||
DocumentHierarchy = documentHierarchy
|
||||
};
|
||||
}
|
||||
|
||||
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
|
||||
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, bool applyInspection = false, Action<InspectionElement> inspectionResultHandler = null)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
var container = new DocumentContainer();
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||
|
||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||
ApplyContentDirection(content, ContentDirection.LeftToRight);
|
||||
|
||||
if (Settings.EnableCaching)
|
||||
ApplyCaching(content);
|
||||
|
||||
var pageContext = new PageContext();
|
||||
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, debuggingState);
|
||||
RenderPass(pageContext, new FreeCanvas(), content);
|
||||
|
||||
if (applyInspection)
|
||||
ApplyInspection(content);
|
||||
|
||||
RenderPass(pageContext, canvas, content);
|
||||
|
||||
if (applyInspection)
|
||||
inspectionResultHandler(content.ExtractDocumentHierarchy());
|
||||
}
|
||||
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||
InjectDependencies(content, pageContext, canvas);
|
||||
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
canvas.BeginDocument();
|
||||
@@ -89,7 +103,6 @@ namespace QuestPDF.Drawing
|
||||
while(true)
|
||||
{
|
||||
pageContext.SetPageNumber(currentPage);
|
||||
debuggingState?.Reset();
|
||||
|
||||
var spacePlan = content.Measure(Size.Max);
|
||||
|
||||
@@ -134,12 +147,29 @@ namespace QuestPDF.Drawing
|
||||
$"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.";
|
||||
|
||||
var elementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode.";
|
||||
content.RemoveExistingProxies();
|
||||
content.RemoveExistingContainers();
|
||||
content.ApplyLayoutDebugging();
|
||||
content.Measure(Size.Max);
|
||||
|
||||
var layoutTrace = content.CollectLayoutErrorTrace();
|
||||
|
||||
throw new DocumentLayoutException(message, elementTrace);
|
||||
throw new DocumentLayoutException(message, layoutTrace);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InjectDependencies(this Element content, IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
if (x == null)
|
||||
return;
|
||||
|
||||
x.PageContext = pageContext;
|
||||
x.Canvas = canvas;
|
||||
});
|
||||
}
|
||||
|
||||
private static void ApplyCaching(Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
@@ -149,16 +179,30 @@ namespace QuestPDF.Drawing
|
||||
});
|
||||
}
|
||||
|
||||
private static DebuggingState ApplyDebugging(Container content)
|
||||
private static void ApplyInspection(Container content)
|
||||
{
|
||||
var debuggingState = new DebuggingState();
|
||||
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
x.CreateProxy(y => y is ElementProxy or Container ? y : new InspectionProxy(y));
|
||||
});
|
||||
}
|
||||
|
||||
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
|
||||
return debuggingState;
|
||||
if (content is ContentDirectionSetter contentDirectionSetter)
|
||||
{
|
||||
ApplyContentDirection(contentDirectionSetter.Child, contentDirectionSetter.ContentDirection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content is IContentDirectionAware contentDirectionAware)
|
||||
contentDirectionAware.ContentDirection = direction;
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ApplyContentDirection(child, direction);
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace QuestPDF.Drawing
|
||||
set => Settings.EnableCaching = value;
|
||||
}
|
||||
|
||||
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableDebugging static property.")]
|
||||
[Obsolete("The new implementation for debugging layout issues does not introduce additional performance overhead. Therefore, this setting is no longer used since the 2023.1 release. Please remove this setter from your code.")]
|
||||
public bool ApplyDebugging
|
||||
{
|
||||
get => Settings.EnableDebugging;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentLayoutException : Exception
|
||||
{
|
||||
public string? ElementTrace { get; }
|
||||
internal LayoutErrorTrace? ElementTrace { get; }
|
||||
|
||||
internal DocumentLayoutException(string message, string? elementTrace = null) : base(message)
|
||||
internal DocumentLayoutException(string message, LayoutErrorTrace? elementTrace = null) : base(message)
|
||||
{
|
||||
ElementTrace = elementTrace;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace QuestPDF.Drawing.Proxy
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
|
||||
private bool IsClose(float x, float y)
|
||||
private static bool IsClose(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) < Size.Epsilon;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
public class DebugStackItem
|
||||
{
|
||||
public IElement Element { get; internal set; }
|
||||
public Size AvailableSpace { get; internal set; }
|
||||
public SpacePlan SpacePlan { get; internal set; }
|
||||
|
||||
public ICollection<DebugStackItem> Stack { get; internal set; } = new List<DebugStackItem>();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,34 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class DebuggingProxy : ElementProxy
|
||||
{
|
||||
private DebuggingState DebuggingState { get; }
|
||||
internal List<MeasureDetails> Measurements { get; } = new();
|
||||
|
||||
public DebuggingProxy(DebuggingState debuggingState, Element child)
|
||||
public DebuggingProxy(Element child)
|
||||
{
|
||||
DebuggingState = debuggingState;
|
||||
Child = child;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
DebuggingState.RegisterMeasure(Child, availableSpace);
|
||||
var spacePlan = base.Measure(availableSpace);
|
||||
DebuggingState.RegisterMeasureResult(Child, spacePlan);
|
||||
|
||||
var spacePlan = Child.Measure(availableSpace);
|
||||
Measurements.Add(new MeasureDetails(availableSpace, spacePlan));
|
||||
return spacePlan;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct MeasureDetails
|
||||
{
|
||||
public Size AvailableSpace { get; }
|
||||
public SpacePlan SpacePlan { get;}
|
||||
|
||||
public MeasureDetails(Size availableSpace, SpacePlan spacePlan)
|
||||
{
|
||||
AvailableSpace = availableSpace;
|
||||
SpacePlan = spacePlan;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class DebuggingState
|
||||
{
|
||||
private DebugStackItem Root { get; set; }
|
||||
private Stack<DebugStackItem> Stack { get; set; }
|
||||
|
||||
public DebuggingState()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Root = null;
|
||||
Stack = new Stack<DebugStackItem>();
|
||||
}
|
||||
|
||||
public void RegisterMeasure(IElement element, Size availableSpace)
|
||||
{
|
||||
if (element.GetType() == typeof(Container))
|
||||
return;
|
||||
|
||||
var item = new DebugStackItem
|
||||
{
|
||||
Element = element,
|
||||
AvailableSpace = availableSpace
|
||||
};
|
||||
|
||||
Root ??= item;
|
||||
|
||||
if (Stack.Any())
|
||||
Stack.Peek().Stack.Add(item);
|
||||
|
||||
Stack.Push(item);
|
||||
}
|
||||
|
||||
public void RegisterMeasureResult(IElement element, SpacePlan spacePlan)
|
||||
{
|
||||
if (element.GetType() == typeof(Container))
|
||||
return;
|
||||
|
||||
var item = Stack.Pop();
|
||||
|
||||
if (item.Element != element)
|
||||
throw new Exception();
|
||||
|
||||
item.SpacePlan = spacePlan;
|
||||
}
|
||||
|
||||
public string BuildTrace()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var nestingLevel = 0;
|
||||
|
||||
Traverse(Root);
|
||||
return builder.ToString();
|
||||
|
||||
void Traverse(DebugStackItem item)
|
||||
{
|
||||
var indent = new string(' ', nestingLevel * 4);
|
||||
var title = item.Element.GetType().Name;
|
||||
|
||||
if (item.Element is DebugPointer debugPointer)
|
||||
{
|
||||
title = debugPointer.Target;
|
||||
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
||||
}
|
||||
|
||||
if (item.SpacePlan.Type != SpacePlanType.FullRender)
|
||||
title = "🔥 " + title;
|
||||
|
||||
builder.AppendLine(indent + title);
|
||||
builder.AppendLine(indent + new string('-', title.Length));
|
||||
|
||||
builder.AppendLine(indent + "Available space: " + item.AvailableSpace);
|
||||
builder.AppendLine(indent + "Requested space: " + item.SpacePlan);
|
||||
|
||||
foreach (var configuration in GetElementConfiguration(item.Element))
|
||||
builder.AppendLine(indent + configuration);
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
nestingLevel++;
|
||||
item.Stack.ToList().ForEach(Traverse);
|
||||
nestingLevel--;
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetElementConfiguration(IElement element)
|
||||
{
|
||||
if (element is DebugPointer)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return element
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Select(x => new
|
||||
{
|
||||
Property = x.Name.PrettifyName(),
|
||||
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 TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
|
||||
string FormatValue(object value)
|
||||
{
|
||||
const int maxLength = 100;
|
||||
|
||||
var text = value?.ToString() ?? "-";
|
||||
|
||||
if (text.Length < maxLength)
|
||||
return text;
|
||||
|
||||
return text.AsSpan(0, maxLength).ToString() + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy;
|
||||
|
||||
internal static class Helpers
|
||||
{
|
||||
public static IReadOnlyCollection<DocumentElementProperty> GetElementConfiguration(this IElement element)
|
||||
{
|
||||
return element
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Select(x => new
|
||||
{
|
||||
Property = x.Name.PrettifyName(),
|
||||
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 TextStyle))
|
||||
.Select(x => new DocumentElementProperty
|
||||
{
|
||||
Label = x.Property,
|
||||
Value = FormatValue(x.Value)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
string FormatValue(object value)
|
||||
{
|
||||
const int maxLength = 100;
|
||||
|
||||
var text = value?.ToString() ?? "-";
|
||||
|
||||
if (text.Length < maxLength)
|
||||
return text;
|
||||
|
||||
return text.AsSpan(0, maxLength).ToString() + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer.Inspection;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class InspectionProxy : ElementProxy
|
||||
{
|
||||
public Dictionary<int, InspectionStateItem> Statistics { get; set; } = new();
|
||||
|
||||
public InspectionProxy(Element child)
|
||||
{
|
||||
Child = child;
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Canvas is SkiaCanvasBase canvas)
|
||||
{
|
||||
var matrix = canvas.Canvas.TotalMatrix;
|
||||
|
||||
var inspectionItem = new InspectionStateItem
|
||||
{
|
||||
Element = Child,
|
||||
Position = new Position(matrix.TransX, matrix.TransY),
|
||||
Size = availableSpace
|
||||
};
|
||||
|
||||
Statistics[PageContext.CurrentPage] = inspectionItem;
|
||||
}
|
||||
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal static class LayoutErrorAnalyzer
|
||||
{
|
||||
public static void ApplyLayoutDebugging(this Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(y));
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveExistingProxies(this Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => y is ElementProxy proxy ? proxy.Child : y);
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveExistingContainers(this Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => y is Container container ? container.Child : y);
|
||||
});
|
||||
}
|
||||
|
||||
public static LayoutErrorTrace CollectLayoutErrorTrace(this Container content)
|
||||
{
|
||||
var proxies = content.ExtractProxyOfType<DebuggingProxy>();
|
||||
return Map(proxies);
|
||||
|
||||
LayoutErrorTrace Map(TreeNode<DebuggingProxy> proxy)
|
||||
{
|
||||
var element = proxy.Value.Child;
|
||||
|
||||
return new LayoutErrorTrace
|
||||
{
|
||||
ElementType = element.GetType().Name,
|
||||
IsSingleChildContainer = element is ContainerElement,
|
||||
ElementProperties = element.GetElementConfiguration().ToList(),
|
||||
|
||||
Measurements = proxy
|
||||
.Value
|
||||
.Measurements
|
||||
.Select(x => new LayoutErrorMeasurement
|
||||
{
|
||||
AvailableSpace = new QuestPDF.Previewer.Size()
|
||||
{
|
||||
Width = x.AvailableSpace.Width,
|
||||
Height = x.AvailableSpace.Height
|
||||
},
|
||||
SpacePlan = new QuestPDF.Previewer.SpacePlan()
|
||||
{
|
||||
Width = x.SpacePlan.Width,
|
||||
Height = x.SpacePlan.Height,
|
||||
Type = x.SpacePlan.Type
|
||||
}
|
||||
})
|
||||
.ToList(),
|
||||
|
||||
Children = proxy.Children.Select(Map).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,12 @@ namespace QuestPDF.Drawing
|
||||
PopulateBufferWithText(buffer, text);
|
||||
buffer.GuessSegmentProperties();
|
||||
|
||||
if (TextStyle.Direction == TextDirection.LeftToRight)
|
||||
buffer.Direction = Direction.LeftToRight;
|
||||
|
||||
if (TextStyle.Direction == TextDirection.RightToLeft)
|
||||
buffer.Direction = Direction.RightToLeft;
|
||||
|
||||
ShaperFont.Shape(buffer);
|
||||
|
||||
var length = buffer.Length;
|
||||
@@ -56,7 +62,7 @@ namespace QuestPDF.Drawing
|
||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||
}
|
||||
|
||||
return new TextShapingResult(glyphs);
|
||||
return new TextShapingResult(buffer.Direction, glyphs);
|
||||
}
|
||||
|
||||
void PopulateBufferWithText(Buffer buffer, string text)
|
||||
@@ -92,29 +98,65 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal class TextShapingResult
|
||||
{
|
||||
public ShapedGlyph[] Glyphs { get; }
|
||||
private Direction Direction { get; }
|
||||
private ShapedGlyph[] Glyphs { get; }
|
||||
|
||||
public TextShapingResult(ShapedGlyph[] glyphs)
|
||||
public int Length => Glyphs.Length;
|
||||
|
||||
public ShapedGlyph this[int index] =>
|
||||
Direction == Direction.LeftToRight
|
||||
? Glyphs[index]
|
||||
: Glyphs[Glyphs.Length - 1 - index];
|
||||
|
||||
public TextShapingResult(Direction direction, ShapedGlyph[] glyphs)
|
||||
{
|
||||
Direction = direction;
|
||||
Glyphs = glyphs;
|
||||
}
|
||||
|
||||
public int BreakText(int startIndex, float maxWidth)
|
||||
{
|
||||
var index = startIndex;
|
||||
maxWidth += Glyphs[startIndex].Position.X;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
return Direction switch
|
||||
{
|
||||
var glyph = Glyphs[index];
|
||||
|
||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
|
||||
index++;
|
||||
}
|
||||
Direction.LeftToRight => BreakTextLeftToRight(),
|
||||
Direction.RightToLeft => BreakTextRightToLeft(),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
return index - 1;
|
||||
int BreakTextLeftToRight()
|
||||
{
|
||||
var index = startIndex;
|
||||
maxWidth += Glyphs[startIndex].Position.X;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
{
|
||||
var glyph = Glyphs[index];
|
||||
|
||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index - 1;
|
||||
}
|
||||
|
||||
int BreakTextRightToLeft()
|
||||
{
|
||||
var index = startIndex;
|
||||
|
||||
var startOffset = this[startIndex].Position.X + this[startIndex].Width;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
{
|
||||
if (startOffset - this[index].Position.X > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public float MeasureWidth(int startIndex, int endIndex)
|
||||
@@ -122,10 +164,15 @@ namespace QuestPDF.Drawing
|
||||
if (Glyphs.Length == 0)
|
||||
return 0;
|
||||
|
||||
var start = Glyphs[startIndex];
|
||||
var end = Glyphs[endIndex];
|
||||
var start = this[startIndex];
|
||||
var end = this[endIndex];
|
||||
|
||||
return end.Position.X - start.Position.X + end.Width;
|
||||
return Direction switch
|
||||
{
|
||||
Direction.LeftToRight => end.Position.X - start.Position.X + end.Width,
|
||||
Direction.RightToLeft => start.Position.X - end.Position.X + start.Width,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
|
||||
@@ -146,14 +193,18 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
var runIndex = sourceIndex - startIndex;
|
||||
|
||||
glyphSpan[runIndex] = Glyphs[sourceIndex].Codepoint;
|
||||
positionSpan[runIndex] = Glyphs[sourceIndex].Position;
|
||||
glyphSpan[runIndex] = this[sourceIndex].Codepoint;
|
||||
positionSpan[runIndex] = this[sourceIndex].Position;
|
||||
}
|
||||
|
||||
var firstVisualCharacterIndex = Direction == Direction.LeftToRight
|
||||
? startIndex
|
||||
: endIndex;
|
||||
|
||||
return new DrawTextCommand
|
||||
{
|
||||
SkTextBlob = skTextBlobBuilder.Build(),
|
||||
TextOffsetX = -Glyphs[startIndex].Position.X
|
||||
TextOffsetX = -this[firstVisualCharacterIndex].Position.X
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class AspectRatio : ContainerElement, ICacheable
|
||||
internal class AspectRatio : ContainerElement, ICacheable, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public float Ratio { get; set; } = 1;
|
||||
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
|
||||
|
||||
@@ -42,7 +44,14 @@ namespace QuestPDF.Elements
|
||||
return;
|
||||
|
||||
var size = GetTargetSize(availableSpace);
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? Position.Zero
|
||||
: new Position(availableSpace.Width - size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
base.Draw(size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
|
||||
private Size GetTargetSize(Size availableSpace)
|
||||
|
||||
@@ -6,7 +6,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ColumnItem : Container
|
||||
internal class ColumnItem : ContainerElement
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
}
|
||||
@@ -74,7 +74,7 @@ namespace QuestPDF.Elements
|
||||
command.ColumnItem.IsRendered = true;
|
||||
|
||||
var targetSize = new Size(availableSpace.Width, command.Size.Height);
|
||||
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.ColumnItem.Draw(targetSize);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
|
||||
@@ -5,8 +5,10 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Constrained : ContainerElement, ICacheable
|
||||
internal class Constrained : ContainerElement, ICacheable, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public float? MinWidth { get; set; }
|
||||
public float? MaxWidth { get; set; }
|
||||
|
||||
@@ -45,11 +47,17 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var available = new Size(
|
||||
var size = new Size(
|
||||
Min(MaxWidth, availableSpace.Width),
|
||||
Min(MaxHeight, availableSpace.Height));
|
||||
|
||||
Child?.Draw(available);
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? Position.Zero
|
||||
: new Position(availableSpace.Width - size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
base.Draw(size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
|
||||
private static float Min(float? x, float y)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ContentDirectionSetter : ContainerElement
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.ComponentModel;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DebugPointer : Container
|
||||
internal class DebugPointer : ContainerElement
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public bool Highlight { get; set; }
|
||||
|
||||
@@ -14,8 +14,10 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Decoration : Element, ICacheable
|
||||
internal class Decoration : Element, ICacheable, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal Element Before { get; set; } = new Empty();
|
||||
internal Element Content { get; set; } = new Empty();
|
||||
internal Element After { get; set; } = new Empty();
|
||||
@@ -64,9 +66,13 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
var elementSize = new Size(width, command.Measurement.Height);
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - width, command.Offset.Y);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
command.Element.Draw(elementSize);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DynamicHost : Element, IStateResettable
|
||||
internal class DynamicHost : Element, IStateResettable, IContentDirectionAware
|
||||
{
|
||||
private DynamicComponentProxy Child { get; }
|
||||
private object InitialComponentState { get; set; }
|
||||
|
||||
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public DynamicHost(DynamicComponentProxy child)
|
||||
{
|
||||
Child = child;
|
||||
@@ -51,12 +52,14 @@ namespace QuestPDF.Elements
|
||||
|
||||
var context = new DynamicContext
|
||||
{
|
||||
PageNumber = PageContext.CurrentPage,
|
||||
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
|
||||
PageContext = PageContext,
|
||||
Canvas = Canvas,
|
||||
TextStyle = TextStyle,
|
||||
|
||||
TextStyle = TextStyle,
|
||||
ContentDirection = ContentDirection,
|
||||
|
||||
PageNumber = PageContext.CurrentPage,
|
||||
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
|
||||
AvailableSize = availableSize
|
||||
};
|
||||
|
||||
@@ -73,7 +76,9 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal IPageContext PageContext { get; set; }
|
||||
internal ICanvas Canvas { get; set; }
|
||||
|
||||
internal TextStyle TextStyle { get; set; }
|
||||
internal ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public int PageNumber { get; internal set; }
|
||||
public int TotalPages { get; internal set; }
|
||||
@@ -85,7 +90,9 @@ namespace QuestPDF.Elements
|
||||
content(container);
|
||||
|
||||
container.ApplyDefaultTextStyle(TextStyle);
|
||||
container.VisitChildren(x => x?.Initialize(PageContext, Canvas));
|
||||
container.ApplyContentDirection(ContentDirection);
|
||||
|
||||
container.InjectDependencies(PageContext, Canvas);
|
||||
container.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
container.Size = container.Measure(Size.Max);
|
||||
|
||||
@@ -24,13 +24,8 @@ namespace QuestPDF.Elements
|
||||
if (imageData == null)
|
||||
return;
|
||||
|
||||
var imageElement = new Image
|
||||
{
|
||||
InternalImage = SKImage.FromEncodedData(imageData)
|
||||
};
|
||||
|
||||
imageElement.Initialize(PageContext, Canvas);
|
||||
imageElement.Draw(availableSpace);
|
||||
using var image = SKImage.FromEncodedData(imageData);
|
||||
Canvas.DrawImage(image, Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InlinedElement : Container
|
||||
internal class InlinedElement : ContainerElement
|
||||
{
|
||||
|
||||
}
|
||||
@@ -26,15 +26,17 @@ namespace QuestPDF.Elements
|
||||
public SpacePlan Size { get; set; }
|
||||
}
|
||||
|
||||
internal class Inlined : Element, IStateResettable
|
||||
internal class Inlined : Element, IStateResettable, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
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 InlinedAlignment? ElementsAlignment { get; set; }
|
||||
internal VerticalAlignment BaselineAlignment { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
@@ -47,8 +49,15 @@ namespace QuestPDF.Elements
|
||||
return Elements;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Elements.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
if (!ChildrenQueue.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
@@ -81,6 +90,8 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
var lines = Compose(availableSpace);
|
||||
var topOffset = 0f;
|
||||
|
||||
@@ -103,7 +114,6 @@ namespace QuestPDF.Elements
|
||||
|
||||
var elementOffset = ElementOffset();
|
||||
var leftOffset = AlignOffset();
|
||||
Canvas.Translate(new Position(leftOffset, 0));
|
||||
|
||||
foreach (var measurement in lineMeasurements)
|
||||
{
|
||||
@@ -113,15 +123,16 @@ namespace QuestPDF.Elements
|
||||
if (size.Height == 0)
|
||||
size = new Size(size.Width, lineSize.Height);
|
||||
|
||||
Canvas.Translate(new Position(0, baselineOffset));
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? new Position(leftOffset, baselineOffset)
|
||||
: new Position(availableSpace.Width - size.Width - leftOffset, baselineOffset);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
measurement.Element.Draw(size);
|
||||
Canvas.Translate(new Position(0, -baselineOffset));
|
||||
Canvas.Translate(offset.Reverse());
|
||||
|
||||
leftOffset += size.Width + elementOffset;
|
||||
Canvas.Translate(new Position(size.Width + elementOffset, 0));
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-leftOffset, 0));
|
||||
|
||||
float ElementOffset()
|
||||
{
|
||||
@@ -140,15 +151,15 @@ namespace QuestPDF.Elements
|
||||
|
||||
float AlignOffset()
|
||||
{
|
||||
var difference = availableSpace.Width - lineSize.Width - (lineMeasurements.Count - 1) * HorizontalSpacing;
|
||||
var emptySpace = availableSpace.Width - lineSize.Width - (lineMeasurements.Count - 1) * HorizontalSpacing;
|
||||
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.Left => 0,
|
||||
InlinedAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||
InlinedAlignment.Justify => 0,
|
||||
InlinedAlignment.SpaceAround => elementOffset,
|
||||
InlinedAlignment.Center => difference / 2,
|
||||
InlinedAlignment.Right => difference,
|
||||
InlinedAlignment.Center => emptySpace / 2,
|
||||
InlinedAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
@@ -167,6 +178,16 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultAlignment()
|
||||
{
|
||||
if (ElementsAlignment.HasValue)
|
||||
return;
|
||||
|
||||
ElementsAlignment = ContentDirection == ContentDirection.LeftToRight
|
||||
? InlinedAlignment.Left
|
||||
: InlinedAlignment.Right;
|
||||
}
|
||||
|
||||
Size GetLineSize(ICollection<InlinedMeasurement> measurements)
|
||||
{
|
||||
var width = measurements.Sum(x => x.Size.Width);
|
||||
@@ -212,7 +233,7 @@ namespace QuestPDF.Elements
|
||||
break;
|
||||
|
||||
var element = queue.Peek();
|
||||
var size = element.Measure(Size.Max);
|
||||
var size = element.Measure(new Size(availableSize.Width, Size.Max.Height));
|
||||
|
||||
if (size.Type == SpacePlanType.Wrap)
|
||||
break;
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace QuestPDF.Elements
|
||||
return Children;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Children.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Children
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class MinimalBox : ContainerElement, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = base.Measure(availableSpace);
|
||||
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||
? new Position(availableSpace.Width - targetSize.Width, 0)
|
||||
: Position.Zero;
|
||||
|
||||
Canvas.Translate(translate);
|
||||
base.Draw(targetSize);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Page : IComponent
|
||||
{
|
||||
public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
public TextStyle DefaultTextStyle { get; set; } = TextStyle.Default;
|
||||
|
||||
public Size MinSize { get; set; } = PageSizes.A4;
|
||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||
@@ -30,6 +31,7 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.ContentDirection(ContentDirection)
|
||||
.Background(BackgroundColor)
|
||||
.Layers(layers =>
|
||||
{
|
||||
@@ -40,6 +42,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.DebugPointer("Page primary layer")
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
@@ -62,9 +65,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.DebugPointer("Page content")
|
||||
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
|
||||
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
|
||||
.DebugPointer("Page content")
|
||||
.Element(Content);
|
||||
|
||||
decoration
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
|
||||
else
|
||||
x.Text(Text).FontSize(14);
|
||||
});
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RelativePadding : ContainerElement
|
||||
{
|
||||
public float Top { get; set; }
|
||||
public float Right { get; set; }
|
||||
public float Bottom { get; set; }
|
||||
public float Left { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return SpacePlan.FullRender(0, 0);
|
||||
|
||||
var internalSpace = InternalSpace(availableSpace);
|
||||
|
||||
if (internalSpace.Width < 0 || internalSpace.Height < 0)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var measure = base.Measure(internalSpace);
|
||||
|
||||
if (measure.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (measure.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(availableSpace);
|
||||
|
||||
if (measure.Type == SpacePlanType.FullRender)
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var internalOffset = InternalOffset(availableSpace);
|
||||
var internalSpace = InternalSpace(availableSpace);
|
||||
|
||||
Canvas.Translate(internalOffset);
|
||||
base.Draw(internalSpace);
|
||||
Canvas.Translate(internalOffset.Reverse());
|
||||
}
|
||||
|
||||
private Position InternalOffset(Size availableSpace)
|
||||
{
|
||||
return new Position(
|
||||
availableSpace.Width * Left,
|
||||
availableSpace.Height * Top);
|
||||
}
|
||||
|
||||
private Size InternalSpace(Size availableSpace)
|
||||
{
|
||||
return new Size(
|
||||
availableSpace.Width * (1f - Left - Right),
|
||||
availableSpace.Height * (1f - Top - Bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RelativePosition : ContainerElement
|
||||
{
|
||||
public float VerticalParent { get; set; }
|
||||
public float VerticalChild { get; set; }
|
||||
|
||||
public float HorizontalParent { get; set; }
|
||||
public float HorizontalChild { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var childSize = base.Measure(availableSpace);
|
||||
|
||||
if (childSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var left = availableSpace.Width * HorizontalParent + childSize.Width * HorizontalChild;
|
||||
var top = availableSpace.Height * VerticalParent + childSize.Height * VerticalChild;
|
||||
|
||||
Canvas.Translate(new Position(left, top));
|
||||
base.Draw(childSize);
|
||||
Canvas.Translate(new Position(-left, -top));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RelativeSize : ContainerElement
|
||||
{
|
||||
public float? WidthFactor { get; set; } = 1f;
|
||||
public float? HeightFactor { get; set; } = 1f;
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var internalSpace = new Size(
|
||||
availableSpace.Width * (WidthFactor ?? 1),
|
||||
availableSpace.Height * (HeightFactor ?? 1));
|
||||
|
||||
var childSpace = Child?.Measure(internalSpace) ?? SpacePlan.FullRender(0, 0);
|
||||
|
||||
if (childSpace.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var targetSpace = new Size(
|
||||
WidthFactor.HasValue ? internalSpace.Width : childSpace.Width,
|
||||
HeightFactor.HasValue ? internalSpace.Height : childSpace.Height);
|
||||
|
||||
if (childSpace.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(targetSpace);
|
||||
|
||||
if (childSpace.Type == SpacePlanType.FullRender)
|
||||
return SpacePlan.FullRender(targetSpace);
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
Relative
|
||||
}
|
||||
|
||||
internal class RowItem : Container
|
||||
internal class RowItem : ContainerElement
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public float Width { get; set; }
|
||||
@@ -31,8 +31,10 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Row : Element, ICacheable, IStateResettable
|
||||
internal class Row : Element, ICacheable, IStateResettable, IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal List<RowItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
|
||||
@@ -91,9 +93,13 @@ namespace QuestPDF.Elements
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - command.Offset.X - command.Size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
command.RowItem.Draw(command.Size);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
|
||||
if (Items.All(x => x.IsRendered))
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Shrink : ContainerElement
|
||||
{
|
||||
public bool ShrinkVertical { get; set; }
|
||||
public bool ShrinkHorizontal { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var childSize = base.Measure(availableSpace);
|
||||
|
||||
if (childSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var targetSize = new Size(
|
||||
ShrinkVertical ? childSize.Width : availableSpace.Width,
|
||||
ShrinkHorizontal ? childSize.Height : availableSpace.Height);
|
||||
|
||||
// TODO: adjust offset for RTL mode
|
||||
|
||||
base.Draw(targetSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,15 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class Table : Element, IStateResettable
|
||||
internal class Table : Element, IStateResettable, IContentDirectionAware
|
||||
{
|
||||
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
|
||||
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public List<TableColumnDefinition> Columns { get; set; } = new();
|
||||
public List<TableCell> Cells { get; set; } = new();
|
||||
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||
|
||||
private bool CacheInitialized { get; set; }
|
||||
private int StartingRowsCount { get; set; }
|
||||
private int RowsCount { get; set; }
|
||||
private int CurrentRow { get; set; }
|
||||
@@ -24,29 +27,40 @@ namespace QuestPDF.Elements.Table
|
||||
// inner table: list of all cells that ends at the corresponding row
|
||||
private TableCell[][] CellsCache { get; set; }
|
||||
private int MaxRow { get; set; }
|
||||
private int MaxRowSpan { get; set; }
|
||||
|
||||
internal override void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
|
||||
BuildCache();
|
||||
|
||||
base.Initialize(pageContext, canvas);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Cells;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Cells.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
foreach (var x in Cells)
|
||||
x.IsRendered = false;
|
||||
|
||||
CurrentRow = 1;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (CacheInitialized)
|
||||
return;
|
||||
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
|
||||
BuildCache();
|
||||
|
||||
CacheInitialized = true;
|
||||
}
|
||||
|
||||
private void BuildCache()
|
||||
{
|
||||
@@ -56,6 +70,7 @@ namespace QuestPDF.Elements.Table
|
||||
if (Cells.Count == 0)
|
||||
{
|
||||
MaxRow = 0;
|
||||
MaxRowSpan = 1;
|
||||
CellsCache = Array.Empty<TableCell[]>();
|
||||
|
||||
return;
|
||||
@@ -66,6 +81,7 @@ namespace QuestPDF.Elements.Table
|
||||
.ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
|
||||
|
||||
MaxRow = groups.Max(x => x.Key);
|
||||
MaxRowSpan = Cells.Max(x => x.RowSpan);
|
||||
|
||||
CellsCache = Enumerable
|
||||
.Range(0, MaxRow + 1)
|
||||
@@ -109,9 +125,13 @@ namespace QuestPDF.Elements.Table
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - command.Offset.X - command.Size.Width, command.Offset.Y);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
command.Cell.Draw(command.Size);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
|
||||
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
||||
@@ -158,7 +178,7 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
if (ExtendLastCellsToTableBottom)
|
||||
AdjustLastCellSizes(tableHeight, commands);
|
||||
|
||||
|
||||
return commands;
|
||||
|
||||
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
||||
@@ -199,9 +219,9 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
currentRow = cell.Row;
|
||||
}
|
||||
|
||||
|
||||
// cell visibility optimizations
|
||||
if (cell.Row > maxRenderingRow)
|
||||
if (cell.Row > maxRenderingRow + MaxRowSpan)
|
||||
break;
|
||||
|
||||
// calculate cell position / size
|
||||
@@ -218,14 +238,14 @@ namespace QuestPDF.Elements.Table
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class TableCell : Container, ITableCellContainer
|
||||
internal class TableCell : ContainerElement, ITableCellContainer
|
||||
{
|
||||
public int Row { get; set; } = 0;
|
||||
public int RowSpan { get; set; } = 1;
|
||||
|
||||
@@ -121,7 +121,11 @@ namespace QuestPDF.Elements.Text
|
||||
{
|
||||
foreach (var textBlockItem in textBlockItems)
|
||||
{
|
||||
if (textBlockItem is TextBlockSpan textBlockSpan and not TextBlockPageNumber)
|
||||
if (textBlockItem is TextBlockPageNumber or TextBlockElement)
|
||||
{
|
||||
yield return textBlockItem;
|
||||
}
|
||||
else if (textBlockItem is TextBlockSpan textBlockSpan)
|
||||
{
|
||||
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
|
||||
{
|
||||
@@ -130,19 +134,25 @@ namespace QuestPDF.Elements.Text
|
||||
}
|
||||
|
||||
var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style);
|
||||
|
||||
|
||||
foreach (var textRun in textRuns)
|
||||
{
|
||||
yield return new TextBlockSpan
|
||||
var newElement = textBlockSpan switch
|
||||
{
|
||||
Text = textRun.Content,
|
||||
Style = textRun.Style
|
||||
TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url },
|
||||
TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName },
|
||||
TextBlockSpan => new TextBlockSpan()
|
||||
};
|
||||
|
||||
newElement.Text = textRun.Content;
|
||||
newElement.Style = textRun.Style;
|
||||
|
||||
yield return newElement;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return textBlockItem;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ 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.InjectDependencies(request.PageContext, request.Canvas);
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
@@ -37,7 +37,7 @@ 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.InjectDependencies(request.PageContext, request.Canvas);
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextStyle Style { get; set; } = TextStyle.Default;
|
||||
public TextShapingResult? TextShapingResult { get; set; }
|
||||
private TextShapingResult? TextShapingResult { get; set; }
|
||||
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
|
||||
protected virtual bool EnableTextCache => true;
|
||||
@@ -47,11 +47,11 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// ignore leading spaces
|
||||
if (!request.IsFirstElementInBlock && request.IsFirstElementInLine)
|
||||
{
|
||||
while (startIndex < TextShapingResult.Glyphs.Length && Text[startIndex] == spaceCodepoint)
|
||||
while (startIndex < TextShapingResult.Length && Text[startIndex] == spaceCodepoint)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (TextShapingResult.Glyphs.Length == 0 || startIndex == TextShapingResult.Glyphs.Length)
|
||||
if (TextShapingResult.Length == 0 || startIndex == TextShapingResult.Length)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
@@ -90,7 +90,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
StartIndex = startIndex,
|
||||
EndIndex = wrappedText.Value.endIndex,
|
||||
NextIndex = wrappedText.Value.nextIndex,
|
||||
TotalIndex = TextShapingResult.Glyphs.Length - 1
|
||||
TotalIndex = TextShapingResult.Length - 1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// textLength - length of the part of the text that fits in available width (creating a line)
|
||||
|
||||
// entire text fits, no need to wrap
|
||||
if (endIndex == TextShapingResult.Glyphs.Length - 1)
|
||||
if (endIndex == TextShapingResult.Length - 1)
|
||||
return (endIndex, endIndex);
|
||||
|
||||
// breaking anywhere
|
||||
@@ -110,7 +110,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
return (endIndex, endIndex + 1);
|
||||
|
||||
// current line ends at word, next character is space, perfect place to wrap
|
||||
if (TextShapingResult.Glyphs[endIndex].Codepoint != spaceCodepoint && TextShapingResult.Glyphs[endIndex + 1].Codepoint == spaceCodepoint)
|
||||
if (TextShapingResult[endIndex].Codepoint != spaceCodepoint && TextShapingResult[endIndex + 1].Codepoint == spaceCodepoint)
|
||||
return (endIndex, endIndex + 2);
|
||||
|
||||
// find last space within the available text to wrap
|
||||
@@ -118,7 +118,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
while (lastSpaceIndex >= startIndex)
|
||||
{
|
||||
if (TextShapingResult.Glyphs[lastSpaceIndex].Codepoint == spaceCodepoint)
|
||||
if (TextShapingResult[lastSpaceIndex].Codepoint == spaceCodepoint)
|
||||
break;
|
||||
|
||||
lastSpaceIndex--;
|
||||
|
||||
@@ -8,9 +8,11 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
internal class TextBlock : Element, IStateResettable, IContentDirectionAware
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public HorizontalAlignment? Alignment { get; set; }
|
||||
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
|
||||
@@ -19,7 +21,7 @@ namespace QuestPDF.Elements.Text
|
||||
private int CurrentElementIndex { get; set; }
|
||||
|
||||
private bool FontFallbackApplied { get; set; } = false;
|
||||
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
ApplyFontFallback();
|
||||
@@ -50,9 +52,21 @@ namespace QuestPDF.Elements.Text
|
||||
FontFallbackApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultAlignment()
|
||||
{
|
||||
if (Alignment.HasValue)
|
||||
return;
|
||||
|
||||
Alignment = ContentDirection == ContentDirection.LeftToRight
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
@@ -80,23 +94,19 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return;
|
||||
|
||||
var heightOffset = 0f;
|
||||
var widthOffset = 0f;
|
||||
|
||||
var topOffset = 0f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
widthOffset = 0f;
|
||||
var leftOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
var alignmentOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
Canvas.Translate(new Position(alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(0, -line.Ascent));
|
||||
|
||||
foreach (var item in line.Elements)
|
||||
{
|
||||
var textDrawingRequest = new TextDrawingRequest
|
||||
@@ -111,21 +121,20 @@ namespace QuestPDF.Elements.Text
|
||||
TotalAscent = line.Ascent
|
||||
};
|
||||
|
||||
var canvasOffset = ContentDirection == ContentDirection.LeftToRight
|
||||
? new Position(leftOffset, topOffset - line.Ascent)
|
||||
: new Position(availableSpace.Width - leftOffset - item.Measurement.Width, topOffset - line.Ascent);
|
||||
|
||||
Canvas.Translate(canvasOffset);
|
||||
item.Item.Draw(textDrawingRequest);
|
||||
|
||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
||||
widthOffset += item.Measurement.Width;
|
||||
Canvas.Translate(canvasOffset.Reverse());
|
||||
|
||||
leftOffset += item.Measurement.Width;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
||||
Canvas.Translate(new Position(0, line.LineHeight));
|
||||
|
||||
heightOffset += line.LineHeight;
|
||||
topOffset += line.LineHeight;
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(0, -heightOffset));
|
||||
|
||||
|
||||
lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
@@ -142,18 +151,15 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
float GetAlignmentOffset(float lineWidth)
|
||||
{
|
||||
if (Alignment == HorizontalAlignment.Left)
|
||||
return 0;
|
||||
|
||||
var emptySpace = availableSpace.Width - lineWidth;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Right)
|
||||
return emptySpace;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
return emptySpace / 2;
|
||||
|
||||
throw new ArgumentException();
|
||||
return Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||
HorizontalAlignment.Center => emptySpace / 2,
|
||||
HorizontalAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Unconstrained : ContainerElement, ICacheable
|
||||
internal class Unconstrained : ContainerElement, IContentDirectionAware, ICacheable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var childSize = base.Measure(Size.Max);
|
||||
@@ -25,7 +27,13 @@ namespace QuestPDF.Elements
|
||||
if (measurement.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||
? new Position(-measurement.Width, 0)
|
||||
: Position.Zero;
|
||||
|
||||
Canvas.Translate(translate);
|
||||
base.Draw(measurement);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ContentDirectionExtensions
|
||||
{
|
||||
internal static IContainer ContentDirection(this IContainer element, ContentDirection direction)
|
||||
{
|
||||
return element.Element(new ContentDirectionSetter
|
||||
{
|
||||
ContentDirection = direction
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer ContentFromLeftToRight(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(Infrastructure.ContentDirection.LeftToRight);
|
||||
}
|
||||
|
||||
public static IContainer ContentFromRightToLeft(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(Infrastructure.ContentDirection.RightToLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,17 @@ namespace QuestPDF.Fluent
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.1. 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());
|
||||
}
|
||||
|
||||
public static IContainer Unconstrained(this IContainer element)
|
||||
{
|
||||
return element.Element(new Unconstrained());
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static class GridExtensions
|
||||
{
|
||||
[Obsolete("This element has been deprecated since version 2022.11. Please use the Table element, or the combination of the Row and Column elements.")]
|
||||
public static void Grid(this IContainer element, Action<GridDescriptor> handler)
|
||||
{
|
||||
var descriptor = new GridDescriptor();
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace QuestPDF.Fluent
|
||||
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
||||
public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
|
||||
|
||||
internal void Alignment(InlinedAlignment? alignment) => Inlined.ElementsAlignment = alignment;
|
||||
public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
|
||||
public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
|
||||
public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Page Page { get; } = new Page();
|
||||
|
||||
public void Size(float width, float height, Unit unit = Unit.Inch)
|
||||
#region Size
|
||||
|
||||
public void Size(float width, float height, Unit unit = Unit.Point)
|
||||
{
|
||||
var pageSize = new PageSize(width, height, unit);
|
||||
|
||||
@@ -39,6 +41,10 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Margin
|
||||
|
||||
public void MarginLeft(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
@@ -78,6 +84,10 @@ namespace QuestPDF.Fluent
|
||||
MarginHorizontal(value, unit);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public void DefaultTextStyle(TextStyle textStyle)
|
||||
{
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
@@ -88,6 +98,16 @@ namespace QuestPDF.Fluent
|
||||
DefaultTextStyle(handler(TextStyle.Default));
|
||||
}
|
||||
|
||||
public void ContentFromLeftToRight()
|
||||
{
|
||||
Page.ContentDirection = ContentDirection.LeftToRight;
|
||||
}
|
||||
|
||||
public void ContentFromRightToLeft()
|
||||
{
|
||||
Page.ContentDirection = ContentDirection.RightToLeft;
|
||||
}
|
||||
|
||||
public void PageColor(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
@@ -99,6 +119,10 @@ namespace QuestPDF.Fluent
|
||||
PageColor(color);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Slots
|
||||
|
||||
public IContainer Background()
|
||||
{
|
||||
var container = new Container();
|
||||
@@ -133,6 +157,8 @@ namespace QuestPDF.Fluent
|
||||
Page.Footer = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class PageExtensions
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RelativePaddingExtensions
|
||||
{
|
||||
private static IContainer RelativePadding(this IContainer element, Action<RelativePadding> handler)
|
||||
{
|
||||
var relativePadding = element as RelativePadding ?? new RelativePadding();
|
||||
handler(relativePadding);
|
||||
|
||||
return element.Element(relativePadding);
|
||||
}
|
||||
|
||||
public static IContainer RelativePaddingTop(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativePadding(x => x.Top += value);
|
||||
}
|
||||
|
||||
public static IContainer RelativePaddingBottom(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativePadding(x => x.Bottom += value);
|
||||
}
|
||||
|
||||
public static IContainer RelativePaddingLeft(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativePadding(x => x.Left += value);
|
||||
}
|
||||
|
||||
public static IContainer RelativePaddingRight(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativePadding(x => x.Right += value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RelativePositionExtensions
|
||||
{
|
||||
private static IContainer RelativePosition(this IContainer element, Action<RelativePosition> handler)
|
||||
{
|
||||
var relativePosition = element as RelativePosition ?? new RelativePosition();
|
||||
handler(relativePosition);
|
||||
|
||||
return element.Element(relativePosition);
|
||||
}
|
||||
|
||||
public static IContainer RelativePositionVertical(this IContainer element, float parentOffset, float childOffset)
|
||||
{
|
||||
return element.RelativePosition(x =>
|
||||
{
|
||||
x.VerticalParent = parentOffset;
|
||||
x.VerticalChild = childOffset;
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer RelativePositionHorizontal(this IContainer element, float parentOffset, float childOffset)
|
||||
{
|
||||
return element.RelativePosition(x =>
|
||||
{
|
||||
x.HorizontalParent = parentOffset;
|
||||
x.HorizontalChild = childOffset;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RelativeSizeExtensions
|
||||
{
|
||||
private static IContainer RelativeSize(this IContainer element, Action<RelativeSize> handler)
|
||||
{
|
||||
var relativeSize = element as RelativeSize ?? new RelativeSize();
|
||||
handler(relativeSize);
|
||||
|
||||
return element.Element(relativeSize);
|
||||
}
|
||||
|
||||
public static IContainer RelativeWidth(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativeSize(x => x.WidthFactor = value);
|
||||
}
|
||||
|
||||
public static IContainer RelativeHeight(this IContainer element, float value)
|
||||
{
|
||||
return element.RelativeSize(x => x.HeightFactor = value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ShrinkExtensions
|
||||
{
|
||||
private static IContainer Shrink(this IContainer element, Action<Shrink> handler)
|
||||
{
|
||||
var shrink = element as Shrink ?? new Shrink();
|
||||
handler(shrink);
|
||||
|
||||
return element.Element(shrink);
|
||||
}
|
||||
|
||||
public static IContainer Shrink(this IContainer element)
|
||||
{
|
||||
return element.ShrinkVertical().ShrinkHorizontal();
|
||||
}
|
||||
|
||||
public static IContainer ShrinkVertical(this IContainer element)
|
||||
{
|
||||
return element.Shrink(x => x.ShrinkVertical = true);
|
||||
}
|
||||
|
||||
public static IContainer ShrinkHorizontal(this IContainer element)
|
||||
{
|
||||
return element.Shrink(x => x.ShrinkHorizontal = true);
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.1. Please use the Shrink method.")]
|
||||
public static IContainer Box(this IContainer element)
|
||||
{
|
||||
return element.Element(new Shrink());
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.11. Please use the Shrink method.")]
|
||||
public static IContainer MinimalBox(this IContainer element)
|
||||
{
|
||||
return element.Element(new Shrink());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle? DefaultStyle { get; set; }
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
internal HorizontalAlignment? Alignment { get; set; }
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
@@ -62,7 +62,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
DefaultStyle = style(TextStyle.Default);
|
||||
}
|
||||
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
@@ -242,7 +242,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment ??= Alignment);
|
||||
|
||||
if (DefaultStyle != null)
|
||||
container = container.DefaultTextStyle(DefaultStyle);
|
||||
|
||||
@@ -164,5 +164,27 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Direction
|
||||
|
||||
public static T DirectionAuto<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionAuto());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T DirectionFromLeftToRight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionFromLeftToRight());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T DirectionFromRightToLeft<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionFromRightToLeft());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -165,5 +165,29 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Direction
|
||||
|
||||
private static TextStyle TextDirection(this TextStyle style, TextDirection textDirection)
|
||||
{
|
||||
return style.Mutate(TextStyleProperty.Direction, textDirection);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionAuto(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.Auto);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionFromLeftToRight(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.LeftToRight);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionFromRightToLeft(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.RightToLeft);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
public static class Placeholders
|
||||
{
|
||||
public static readonly Random Random = new Random(0);
|
||||
public static readonly Random Random = new Random();
|
||||
|
||||
#region Word Cache
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal enum ContentDirection
|
||||
{
|
||||
LeftToRight,
|
||||
RightToLeft
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,6 @@ namespace QuestPDF.Infrastructure
|
||||
yield break;
|
||||
}
|
||||
|
||||
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
PageContext = pageContext;
|
||||
Canvas = canvas;
|
||||
}
|
||||
|
||||
internal virtual void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
}
|
||||
|
||||
class Slot : Container, ISlot
|
||||
class Slot : ContainerElement, ISlot
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal interface IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal enum TextDirection
|
||||
{
|
||||
Auto,
|
||||
LeftToRight,
|
||||
RightToLeft
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace QuestPDF.Infrastructure
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
internal bool? WrapAnywhere { get; set; }
|
||||
internal TextDirection? Direction { get; set; }
|
||||
|
||||
internal TextStyle? Fallback { get; set; }
|
||||
|
||||
@@ -32,6 +33,7 @@ namespace QuestPDF.Infrastructure
|
||||
HasStrikethrough = false,
|
||||
HasUnderline = false,
|
||||
WrapAnywhere = false,
|
||||
Direction = TextDirection.Auto,
|
||||
Fallback = null
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace QuestPDF.Infrastructure
|
||||
HasStrikethrough,
|
||||
HasUnderline,
|
||||
WrapAnywhere,
|
||||
Fallback
|
||||
Fallback,
|
||||
Direction
|
||||
}
|
||||
|
||||
internal static class TextStyleManager
|
||||
@@ -192,6 +193,19 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
return origin with { Fallback = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.Direction)
|
||||
{
|
||||
if (!overrideValue && origin.Direction != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (TextDirection?)value;
|
||||
|
||||
if (origin.Direction == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { Direction = castedValue };
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
|
||||
}
|
||||
@@ -238,6 +252,7 @@ namespace QuestPDF.Infrastructure
|
||||
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.Direction, parent.Direction, overrideStyle);
|
||||
|
||||
if (applyFallback)
|
||||
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Previewer.Inspection;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
internal sealed class IncompatibleVersionApiRequest
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal sealed class NotifyPresenceApiRequest
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
public string LibraryVersion { get; set; }
|
||||
public bool IsDotnet6OrBeyond { get; set; }
|
||||
public bool IsDotnet3OrBeyond { get; set; }
|
||||
public bool IsExecutedInUnitTest { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class DocumentPreviewApiRequest
|
||||
{
|
||||
public ICollection<PageSnapshot> PageSnapshots { get; set; }
|
||||
public InspectionElement DocumentHierarchy { get; set; }
|
||||
|
||||
public class PageSnapshot
|
||||
{
|
||||
public string ResourceId { get; } = Guid.NewGuid().ToString("N");
|
||||
public int PageNumber { get; set; }
|
||||
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GenericErrorApiRequest
|
||||
{
|
||||
public GenericError? Error { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class LayoutErrorApiRequest
|
||||
{
|
||||
public LayoutErrorTrace? Trace { get; set; }
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
public class ExceptionDocument : IDocument
|
||||
{
|
||||
private Exception Exception { get; }
|
||||
|
||||
public ExceptionDocument(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
{
|
||||
return DocumentMetadata.Default;
|
||||
}
|
||||
|
||||
public void Compose(IDocumentContainer document)
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.ContinuousSize(PageSizes.A4.Width);
|
||||
page.Margin(50);
|
||||
page.DefaultTextStyle(x => x.FontSize(16));
|
||||
|
||||
page.Foreground().PaddingTop(5).Border(10).BorderColor(Colors.Red.Medium);
|
||||
|
||||
page.Header()
|
||||
.ShowOnce()
|
||||
.PaddingBottom(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(15);
|
||||
|
||||
row.AutoItem()
|
||||
.PaddingTop(15)
|
||||
.Width(48)
|
||||
.AspectRatio(1)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
const float iconSize = 24;
|
||||
using var iconPath = SKPath.ParseSvgPathData("M23,12L20.56,14.78L20.9,18.46L17.29,19.28L15.4,22.46L12,21L8.6,22.47L6.71,19.29L3.1,18.47L3.44,14.78L1,12L3.44,9.21L3.1,5.53L6.71,4.72L8.6,1.54L12,3L15.4,1.54L17.29,4.72L20.9,5.54L20.56,9.22L23,12M20.33,12L18.5,9.89L18.74,7.1L16,6.5L14.58,4.07L12,5.18L9.42,4.07L8,6.5L5.26,7.09L5.5,9.88L3.67,12L5.5,14.1L5.26,16.9L8,17.5L9.42,19.93L12,18.81L14.58,19.92L16,17.5L18.74,16.89L18.5,14.1L20.33,12M11,15H13V17H11V15M11,7H13V13H11V7");
|
||||
|
||||
using var paint = new SKPaint()
|
||||
{
|
||||
Color = SKColors.Red,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
canvas.Scale(size.Width / iconSize);
|
||||
canvas.DrawPath(iconPath, paint);
|
||||
});
|
||||
|
||||
row.RelativeItem()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Exception").FontSize(36).FontColor(Colors.Red.Medium).Bold();
|
||||
column.Item().PaddingTop(-10).Text("Don't panic! Just analyze what's happened...").FontSize(18).FontColor(Colors.Red.Medium).Bold();
|
||||
});
|
||||
});
|
||||
|
||||
page.Content().PaddingVertical(20).Column(column =>
|
||||
{
|
||||
var currentException = Exception;
|
||||
|
||||
while (currentException != null)
|
||||
{
|
||||
column.Item()
|
||||
.PaddingTop(25)
|
||||
.PaddingBottom(15)
|
||||
|
||||
.Padding(-10)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.FontSize(16));
|
||||
|
||||
text.Span(currentException.GetType().Name + ": ").Bold();
|
||||
text.Span(currentException.Message);
|
||||
});
|
||||
|
||||
foreach (var trace in currentException.StackTrace.Split('\n'))
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.PaddingVertical(5)
|
||||
.Text(trace.Trim())
|
||||
.FontSize(12);
|
||||
}
|
||||
|
||||
currentException = currentException.InnerException;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
internal static class Helpers
|
||||
{
|
||||
public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value, options);
|
||||
var jsonContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
return client.PostAsync(requestUri, jsonContent, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Previewer.Inspection;
|
||||
|
||||
public static class DocumentHierarchyProcessor
|
||||
{
|
||||
internal static InspectionElement ExtractDocumentHierarchy(this Container content)
|
||||
{
|
||||
content.RemoveExistingContainers();
|
||||
|
||||
var proxies = content.ExtractProxyOfType<InspectionProxy>();
|
||||
return Map(proxies);
|
||||
|
||||
InspectionElement Map(TreeNode<InspectionProxy> treeNode)
|
||||
{
|
||||
var proxy = treeNode.Value;
|
||||
var element = proxy.Child;
|
||||
|
||||
var locations = proxy
|
||||
.Statistics
|
||||
.Keys
|
||||
.Select(x =>
|
||||
{
|
||||
var statistics = proxy.Statistics[x];
|
||||
|
||||
return new InspectionElementLocation
|
||||
{
|
||||
PageNumber = x,
|
||||
Position = new MyPosition
|
||||
{
|
||||
Top = statistics.Position.Y,
|
||||
Left = statistics.Position.X
|
||||
},
|
||||
Size = new Size
|
||||
{
|
||||
Width = statistics.Size.Width,
|
||||
Height = statistics.Size.Height
|
||||
}
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new InspectionElement
|
||||
{
|
||||
ElementType = element.GetType().Name,
|
||||
IsSingleChildContainer = element is ContainerElement,
|
||||
Location = locations,
|
||||
Properties = element.GetElementConfiguration().ToList(),
|
||||
Children = treeNode.Children.Select(Map).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user