Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 9ce9ca85be |
@@ -0,0 +1,411 @@
|
|||||||
|
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
|
||||||
|
.Padding(20)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.ExtendHorizontal()
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
|
||||||
|
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
|
||||||
|
|
||||||
|
static Action<IContainer> HeaderCell(string label)
|
||||||
|
{
|
||||||
|
return container => container
|
||||||
|
.Border(1)
|
||||||
|
.BorderColor(Colors.Grey.Medium)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.PaddingHorizontal(10)
|
||||||
|
.PaddingVertical(5)
|
||||||
|
.Text(label)
|
||||||
|
.FontSize(18)
|
||||||
|
.SemiBold();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
|
||||||
|
|
||||||
|
table.Cell()
|
||||||
|
.Element(TestCell)
|
||||||
|
.ContentFromRightToLeft()
|
||||||
|
.Element(content);
|
||||||
|
|
||||||
|
static IContainer TestCell(IContainer container)
|
||||||
|
{
|
||||||
|
return container.Border(1).BorderColor(Colors.Grey.Medium).Padding(10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[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.ContentFromRightToLeft();
|
||||||
|
|
||||||
|
page.Content().Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(20);
|
||||||
|
|
||||||
|
column.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.Spacing(10);
|
||||||
|
|
||||||
|
row.AutoItem().AlignMiddle().Width(20).Height(20).Image(Placeholders.Image);
|
||||||
|
|
||||||
|
row.RelativeItem()
|
||||||
|
.Text("Document title")
|
||||||
|
.FontSize(24).FontColor(Colors.Blue.Accent1).SemiBold();
|
||||||
|
});
|
||||||
|
|
||||||
|
column.Item().Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var i in Enumerable.Range(0, 9))
|
||||||
|
{
|
||||||
|
var width = (i % 4 == 0) ? 2 : 1;
|
||||||
|
|
||||||
|
table
|
||||||
|
.Cell()
|
||||||
|
.ColumnSpan((uint)width)
|
||||||
|
.Background(i % 4 == 0 ? Colors.Grey.Lighten1 : Colors.Grey.Lighten2)
|
||||||
|
.Padding(5)
|
||||||
|
.AlignCenter()
|
||||||
|
.Text(i)
|
||||||
|
.FontSize(20);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[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().Background(Colors.Grey.Lighten3).Text("Default alignment").FontSize(13);
|
||||||
|
column.Item().Element(ContentWithAlignment(null));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Left alignment").FontSize(14);
|
||||||
|
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Center alignment").FontSize(14);
|
||||||
|
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Right alignment").FontSize(14);
|
||||||
|
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().Background(Colors.Grey.Lighten3).Text("Default alignment").FontSize(13);
|
||||||
|
column.Item().Element(ContentWithAlignment(null));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Left alignment").FontSize(14);
|
||||||
|
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Center alignment").FontSize(14);
|
||||||
|
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
|
||||||
|
|
||||||
|
column.Item().Background(Colors.Grey.Lighten3).Text("Right alignment").FontSize(14);
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Elements;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class ProcessRunningTime
|
||||||
|
{
|
||||||
|
public TimeSpan FluentTime { get; set; }
|
||||||
|
public TimeSpan GenerationTime { get; set; }
|
||||||
|
public float Size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenerationBenchmark
|
||||||
|
{
|
||||||
|
public const int TestSize = 4096;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BenchmarkAsync()
|
||||||
|
{
|
||||||
|
RunTest(() => Enumerable
|
||||||
|
.Range(0, TestSize)
|
||||||
|
.AsParallel() // difference
|
||||||
|
.Select(GenerateAndCollect)
|
||||||
|
.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BenchmarkSync()
|
||||||
|
{
|
||||||
|
RunTest(() => Enumerable
|
||||||
|
.Range(0, TestSize)
|
||||||
|
.Select(GenerateAndCollect)
|
||||||
|
.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
|
||||||
|
{
|
||||||
|
var totalFluentTime = TimeSpan.Zero;
|
||||||
|
var totalGenerationTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
var stopWatch = new Stopwatch();
|
||||||
|
|
||||||
|
stopWatch.Start();
|
||||||
|
var results = handler();
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
totalFluentTime += result.FluentTime;
|
||||||
|
totalGenerationTime += result.GenerationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Fluent: {totalFluentTime:g}");
|
||||||
|
Console.WriteLine($"Generation: {totalGenerationTime:g}");
|
||||||
|
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
|
||||||
|
{
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
|
var container = new Container();
|
||||||
|
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
column.Item().Text($"Attempts {attemptNumber}");
|
||||||
|
|
||||||
|
const int numberOfRows = 100;
|
||||||
|
const int numberOfColumns = 10;
|
||||||
|
|
||||||
|
for (var y = 0; y < numberOfRows; y++)
|
||||||
|
{
|
||||||
|
column.Item().Row(row =>
|
||||||
|
{
|
||||||
|
for (var x = 0; x < numberOfColumns; x++)
|
||||||
|
{
|
||||||
|
row.RelativeItem()
|
||||||
|
|
||||||
|
.Background(Colors.Red.Lighten5)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Lighten4)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Lighten3)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Lighten2)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Lighten1)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Medium)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Darken1)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Darken2)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Darken3)
|
||||||
|
.Padding(3)
|
||||||
|
|
||||||
|
.Background(Colors.Red.Darken4)
|
||||||
|
.Height(3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var fluentTime = stopwatch.Elapsed;
|
||||||
|
|
||||||
|
stopwatch.Reset();
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
|
var size = Document
|
||||||
|
.Create(x => x.Page(page => page.Content().Element(container)))
|
||||||
|
.GeneratePdf()
|
||||||
|
.Length;
|
||||||
|
|
||||||
|
var generationTime = stopwatch.Elapsed;
|
||||||
|
|
||||||
|
return new ProcessRunningTime
|
||||||
|
{
|
||||||
|
FluentTime = fluentTime,
|
||||||
|
GenerationTime = generationTime,
|
||||||
|
Size = size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Drawing.Exceptions;
|
using QuestPDF.Drawing.Exceptions;
|
||||||
using QuestPDF.Examples.Engine;
|
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]
|
[Test]
|
||||||
public void Exception()
|
public void Exception()
|
||||||
{
|
{
|
||||||
@@ -47,5 +63,43 @@ namespace QuestPDF.Examples
|
|||||||
.Render(page => page.Image("non_existent.png"));
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,5 +105,41 @@ namespace QuestPDF.Examples
|
|||||||
.Row(row => { });
|
.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,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@@ -604,7 +604,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
RenderingTest
|
RenderingTest
|
||||||
.Create()
|
.Create()
|
||||||
.PageSize(500, 100)
|
.PageSize(250, 100)
|
||||||
|
|
||||||
.ProduceImages()
|
.ProduceImages()
|
||||||
.ShowResults()
|
.ShowResults()
|
||||||
@@ -614,9 +614,9 @@ namespace QuestPDF.Examples
|
|||||||
.Padding(25)
|
.Padding(25)
|
||||||
.MinimalBox()
|
.MinimalBox()
|
||||||
.Background(Colors.Grey.Lighten2)
|
.Background(Colors.Grey.Lighten2)
|
||||||
.Text("ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا")
|
.Text("خوارزمية ترتيب")
|
||||||
.FontFamily(Fonts.Calibri)
|
.FontFamily(Fonts.Calibri)
|
||||||
.FontSize(20);
|
.FontSize(30);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,5 +657,73 @@ 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 TextDirectionality()
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<Authors>MarcinZiabek</Authors>
|
<Authors>MarcinZiabek</Authors>
|
||||||
<Company>CodeFlint</Company>
|
<Company>CodeFlint</Company>
|
||||||
<PackageId>QuestPDF.Previewer</PackageId>
|
<PackageId>QuestPDF.Previewer</PackageId>
|
||||||
<Version>2022.9.1</Version>
|
<Version>2022.11.0</Version>
|
||||||
<PackAsTool>true</PackAsTool>
|
<PackAsTool>true</PackAsTool>
|
||||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
<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>
|
<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>
|
||||||
|
|||||||
@@ -68,9 +68,11 @@ namespace QuestPDF.ReportSample.Layouts
|
|||||||
|
|
||||||
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
|
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
|
||||||
|
|
||||||
text.Span(" (").Style(lengthStyle);
|
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x =>
|
||||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
{
|
||||||
text.Span(")").Style(lengthStyle);
|
var formatted = x == 1 ? "1 page long" : $"{x} pages long";
|
||||||
|
return $" ({formatted})";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ namespace QuestPDF.UnitTests
|
|||||||
Ratio = 2f
|
Ratio = 2f
|
||||||
})
|
})
|
||||||
.DrawElement(new Size(500, 200))
|
.DrawElement(new Size(500, 200))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(400, 200))
|
.ExpectChildDraw(new Size(400, 200))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.CheckDrawResult();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +170,45 @@ namespace QuestPDF.UnitTests
|
|||||||
Ratio = 2f
|
Ratio = 2f
|
||||||
})
|
})
|
||||||
.DrawElement(new Size(400, 300))
|
.DrawElement(new Size(400, 300))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(400, 200))
|
.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();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ namespace QuestPDF.UnitTests
|
|||||||
})
|
})
|
||||||
.MeasureElement(new Size(400, 300))
|
.MeasureElement(new Size(400, 300))
|
||||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(200, 100))
|
.ExpectChildDraw(new Size(200, 100))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.CheckDrawResult();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +51,43 @@ namespace QuestPDF.UnitTests
|
|||||||
})
|
})
|
||||||
.MeasureElement(new Size(500, 400))
|
.MeasureElement(new Size(500, 400))
|
||||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(300, 200))
|
.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();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
|
|
||||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||||
{
|
{
|
||||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
Element.InjectDependencies(null, Canvas);
|
||||||
|
|
||||||
var actual = Element.Measure(OperationInput);
|
var actual = Element.Measure(OperationInput);
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
|
|
||||||
public TestPlan CheckDrawResult()
|
public TestPlan CheckDrawResult()
|
||||||
{
|
{
|
||||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
Element.InjectDependencies(null, Canvas);
|
||||||
Element.Draw(OperationInput);
|
Element.Draw(OperationInput);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -253,10 +253,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
availableSpace ??= new Size(400, 300);
|
availableSpace ??= new Size(400, 300);
|
||||||
|
|
||||||
var canvas = new FreeCanvas();
|
var canvas = new FreeCanvas();
|
||||||
value.VisitChildren(x => x.Initialize(null, canvas));
|
value.InjectDependencies(null, canvas);
|
||||||
var valueMeasure = value.Measure(availableSpace.Value);
|
var valueMeasure = value.Measure(availableSpace.Value);
|
||||||
|
|
||||||
expected.VisitChildren(x => x.Initialize(null, canvas));
|
expected.InjectDependencies(null, canvas);
|
||||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||||
|
|
||||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||||
@@ -267,11 +267,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
|||||||
availableSpace ??= new Size(400, 300);
|
availableSpace ??= new Size(400, 300);
|
||||||
|
|
||||||
var valueCanvas = new OperationRecordingCanvas();
|
var valueCanvas = new OperationRecordingCanvas();
|
||||||
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
value.InjectDependencies(null, valueCanvas);
|
||||||
value.Draw(availableSpace.Value);
|
value.Draw(availableSpace.Value);
|
||||||
|
|
||||||
var expectedCanvas = new OperationRecordingCanvas();
|
var expectedCanvas = new OperationRecordingCanvas();
|
||||||
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
expected.InjectDependencies(null, expectedCanvas);
|
||||||
expected.Draw(availableSpace.Value);
|
expected.Draw(availableSpace.Value);
|
||||||
|
|
||||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ namespace QuestPDF.UnitTests
|
|||||||
})
|
})
|
||||||
.DrawElement(new Size(900, 800))
|
.DrawElement(new Size(900, 800))
|
||||||
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(1200, 1600))
|
.ExpectChildDraw(new Size(1200, 1600))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.CheckDrawResult();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +93,43 @@ namespace QuestPDF.UnitTests
|
|||||||
})
|
})
|
||||||
.DrawElement(new Size(900, 800))
|
.DrawElement(new Size(900, 800))
|
||||||
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
||||||
|
.ExpectCanvasTranslate(0, 0)
|
||||||
.ExpectChildDraw(new Size(1600, 1000))
|
.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();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,8 @@ namespace QuestPDF.Drawing
|
|||||||
var container = new DocumentContainer();
|
var container = new DocumentContainer();
|
||||||
document.Compose(container);
|
document.Compose(container);
|
||||||
var content = container.Compose();
|
var content = container.Compose();
|
||||||
|
|
||||||
ApplyRepeatContent(content);
|
|
||||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||||
|
ApplyContentDirection(content, ContentDirection.LeftToRight);
|
||||||
|
|
||||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ namespace QuestPDF.Drawing
|
|||||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
||||||
where TCanvas : ICanvas, IRenderingCanvas
|
where TCanvas : ICanvas, IRenderingCanvas
|
||||||
{
|
{
|
||||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
InjectDependencies(content, pageContext, canvas);
|
||||||
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||||
|
|
||||||
canvas.BeginDocument();
|
canvas.BeginDocument();
|
||||||
@@ -142,6 +141,18 @@ namespace QuestPDF.Drawing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private static void ApplyCaching(Container content)
|
||||||
{
|
{
|
||||||
content.VisitChildren(x =>
|
content.VisitChildren(x =>
|
||||||
@@ -163,6 +174,24 @@ namespace QuestPDF.Drawing
|
|||||||
return debuggingState;
|
return debuggingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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)
|
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||||
{
|
{
|
||||||
if (content == null)
|
if (content == null)
|
||||||
@@ -194,23 +223,5 @@ namespace QuestPDF.Drawing
|
|||||||
foreach (var child in content.GetChildren())
|
foreach (var child in content.GetChildren())
|
||||||
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void ApplyRepeatContent(this Element? content, bool enabled = false)
|
|
||||||
{
|
|
||||||
if (content == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (content is RepeatContent repeatContent)
|
|
||||||
{
|
|
||||||
ApplyRepeatContent(repeatContent.Child, repeatContent.Repeat);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var child in content.GetChildren())
|
|
||||||
ApplyRepeatContent(child, enabled);
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
content.CreateProxy(y => y is IContent ? new ShowOnce { Child = y } : y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,12 @@ namespace QuestPDF.Drawing
|
|||||||
PopulateBufferWithText(buffer, text);
|
PopulateBufferWithText(buffer, text);
|
||||||
buffer.GuessSegmentProperties();
|
buffer.GuessSegmentProperties();
|
||||||
|
|
||||||
|
if (TextStyle.Direction == TextDirection.LeftToRight)
|
||||||
|
buffer.Direction = Direction.LeftToRight;
|
||||||
|
|
||||||
|
if (TextStyle.Direction == TextDirection.RightToLeft)
|
||||||
|
buffer.Direction = Direction.RightToLeft;
|
||||||
|
|
||||||
ShaperFont.Shape(buffer);
|
ShaperFont.Shape(buffer);
|
||||||
|
|
||||||
var length = buffer.Length;
|
var length = buffer.Length;
|
||||||
@@ -56,7 +62,7 @@ namespace QuestPDF.Drawing
|
|||||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TextShapingResult(glyphs);
|
return new TextShapingResult(buffer.Direction, glyphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopulateBufferWithText(Buffer buffer, string text)
|
void PopulateBufferWithText(Buffer buffer, string text)
|
||||||
@@ -92,29 +98,65 @@ namespace QuestPDF.Drawing
|
|||||||
|
|
||||||
internal class TextShapingResult
|
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;
|
Glyphs = glyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int BreakText(int startIndex, float maxWidth)
|
public int BreakText(int startIndex, float maxWidth)
|
||||||
{
|
{
|
||||||
var index = startIndex;
|
return Direction switch
|
||||||
maxWidth += Glyphs[startIndex].Position.X;
|
|
||||||
|
|
||||||
while (index < Glyphs.Length)
|
|
||||||
{
|
{
|
||||||
var glyph = Glyphs[index];
|
Direction.LeftToRight => BreakTextLeftToRight(),
|
||||||
|
Direction.RightToLeft => BreakTextRightToLeft(),
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
int BreakTextLeftToRight()
|
||||||
break;
|
{
|
||||||
|
var index = startIndex;
|
||||||
|
maxWidth += Glyphs[startIndex].Position.X;
|
||||||
|
|
||||||
index++;
|
while (index < Glyphs.Length)
|
||||||
|
{
|
||||||
|
var glyph = Glyphs[index];
|
||||||
|
|
||||||
|
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public float MeasureWidth(int startIndex, int endIndex)
|
||||||
@@ -122,10 +164,15 @@ namespace QuestPDF.Drawing
|
|||||||
if (Glyphs.Length == 0)
|
if (Glyphs.Length == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var start = Glyphs[startIndex];
|
var start = this[startIndex];
|
||||||
var end = Glyphs[endIndex];
|
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)
|
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
|
||||||
@@ -146,14 +193,18 @@ namespace QuestPDF.Drawing
|
|||||||
{
|
{
|
||||||
var runIndex = sourceIndex - startIndex;
|
var runIndex = sourceIndex - startIndex;
|
||||||
|
|
||||||
glyphSpan[runIndex] = Glyphs[sourceIndex].Codepoint;
|
glyphSpan[runIndex] = this[sourceIndex].Codepoint;
|
||||||
positionSpan[runIndex] = Glyphs[sourceIndex].Position;
|
positionSpan[runIndex] = this[sourceIndex].Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var firstVisualCharacterIndex = Direction == Direction.LeftToRight
|
||||||
|
? startIndex
|
||||||
|
: endIndex;
|
||||||
|
|
||||||
return new DrawTextCommand
|
return new DrawTextCommand
|
||||||
{
|
{
|
||||||
SkTextBlob = skTextBlobBuilder.Build(),
|
SkTextBlob = skTextBlobBuilder.Build(),
|
||||||
TextOffsetX = -Glyphs[startIndex].Position.X
|
TextOffsetX = -this[firstVisualCharacterIndex].Position.X
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
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 float Ratio { get; set; } = 1;
|
||||||
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
|
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
|
||||||
|
|
||||||
@@ -42,7 +44,14 @@ namespace QuestPDF.Elements
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var size = GetTargetSize(availableSpace);
|
var size = GetTargetSize(availableSpace);
|
||||||
|
|
||||||
|
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||||
|
? Position.Zero
|
||||||
|
: new Position(availableSpace.Width - size.Width, 0);
|
||||||
|
|
||||||
|
Canvas.Translate(offset);
|
||||||
base.Draw(size);
|
base.Draw(size);
|
||||||
|
Canvas.Translate(offset.Reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Size GetTargetSize(Size availableSpace)
|
private Size GetTargetSize(Size availableSpace)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
|
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
|
||||||
|
|
||||||
internal class Canvas : Element, ICacheable, IContent
|
internal class Canvas : Element, ICacheable
|
||||||
{
|
{
|
||||||
public DrawOnCanvas Handler { get; set; }
|
public DrawOnCanvas Handler { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
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? MinWidth { get; set; }
|
||||||
public float? MaxWidth { get; set; }
|
public float? MaxWidth { get; set; }
|
||||||
|
|
||||||
@@ -45,11 +47,17 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
var available = new Size(
|
var size = new Size(
|
||||||
Min(MaxWidth, availableSpace.Width),
|
Min(MaxWidth, availableSpace.Width),
|
||||||
Min(MaxHeight, availableSpace.Height));
|
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)
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,10 @@ namespace QuestPDF.Elements
|
|||||||
public Position Offset { get; set; }
|
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 Before { get; set; } = new Empty();
|
||||||
internal Element Content { get; set; } = new Empty();
|
internal Element Content { get; set; } = new Empty();
|
||||||
internal Element After { 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);
|
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);
|
command.Element.Draw(elementSize);
|
||||||
Canvas.Translate(command.Offset.Reverse());
|
Canvas.Translate(offset.Reverse());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class DynamicHost : Element, IStateResettable, IContent
|
internal class DynamicHost : Element, IStateResettable, IContentDirectionAware
|
||||||
{
|
{
|
||||||
private DynamicComponentProxy Child { get; }
|
private DynamicComponentProxy Child { get; }
|
||||||
private object InitialComponentState { get; set; }
|
private object InitialComponentState { get; set; }
|
||||||
|
|
||||||
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
|
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||||
|
public ContentDirection ContentDirection { get; set; }
|
||||||
|
|
||||||
public DynamicHost(DynamicComponentProxy child)
|
public DynamicHost(DynamicComponentProxy child)
|
||||||
{
|
{
|
||||||
@@ -51,12 +52,14 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
var context = new DynamicContext
|
var context = new DynamicContext
|
||||||
{
|
{
|
||||||
PageNumber = PageContext.CurrentPage,
|
|
||||||
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
|
|
||||||
PageContext = PageContext,
|
PageContext = PageContext,
|
||||||
Canvas = Canvas,
|
Canvas = Canvas,
|
||||||
TextStyle = TextStyle,
|
|
||||||
|
|
||||||
|
TextStyle = TextStyle,
|
||||||
|
ContentDirection = ContentDirection,
|
||||||
|
|
||||||
|
PageNumber = PageContext.CurrentPage,
|
||||||
|
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
|
||||||
AvailableSize = availableSize
|
AvailableSize = availableSize
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,7 +76,9 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
internal IPageContext PageContext { get; set; }
|
internal IPageContext PageContext { get; set; }
|
||||||
internal ICanvas Canvas { get; set; }
|
internal ICanvas Canvas { get; set; }
|
||||||
|
|
||||||
internal TextStyle TextStyle { get; set; }
|
internal TextStyle TextStyle { get; set; }
|
||||||
|
internal ContentDirection ContentDirection { get; set; }
|
||||||
|
|
||||||
public int PageNumber { get; internal set; }
|
public int PageNumber { get; internal set; }
|
||||||
public int TotalPages { get; internal set; }
|
public int TotalPages { get; internal set; }
|
||||||
@@ -85,7 +90,9 @@ namespace QuestPDF.Elements
|
|||||||
content(container);
|
content(container);
|
||||||
|
|
||||||
container.ApplyDefaultTextStyle(TextStyle);
|
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.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||||
|
|
||||||
container.Size = container.Measure(Size.Max);
|
container.Size = container.Measure(Size.Max);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using SkiaSharp;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class DynamicImage : Element, IContent
|
internal class DynamicImage : Element
|
||||||
{
|
{
|
||||||
public Func<Size, byte[]>? Source { get; set; }
|
public Func<Size, byte[]>? Source { get; set; }
|
||||||
|
|
||||||
@@ -24,13 +24,8 @@ namespace QuestPDF.Elements
|
|||||||
if (imageData == null)
|
if (imageData == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var imageElement = new Image
|
using var image = SKImage.FromEncodedData(imageData);
|
||||||
{
|
Canvas.DrawImage(image, Position.Zero, availableSpace);
|
||||||
InternalImage = SKImage.FromEncodedData(imageData)
|
|
||||||
};
|
|
||||||
|
|
||||||
imageElement.Initialize(PageContext, Canvas);
|
|
||||||
imageElement.Draw(availableSpace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ using SkiaSharp;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class Image : Element, ICacheable, IContent
|
internal class Image : Element, ICacheable
|
||||||
{
|
{
|
||||||
public SKImage? InternalImage { get; set; }
|
public SKImage? InternalImage { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,17 @@ namespace QuestPDF.Elements
|
|||||||
public SpacePlan Size { get; set; }
|
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>();
|
public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
|
||||||
private Queue<InlinedElement> ChildrenQueue { get; set; }
|
private Queue<InlinedElement> ChildrenQueue { get; set; }
|
||||||
|
|
||||||
internal float VerticalSpacing { get; set; }
|
internal float VerticalSpacing { get; set; }
|
||||||
internal float HorizontalSpacing { get; set; }
|
internal float HorizontalSpacing { get; set; }
|
||||||
|
|
||||||
internal InlinedAlignment ElementsAlignment { get; set; }
|
internal InlinedAlignment? ElementsAlignment { get; set; }
|
||||||
internal VerticalAlignment BaselineAlignment { get; set; }
|
internal VerticalAlignment BaselineAlignment { get; set; }
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
@@ -49,6 +51,8 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
|
SetDefaultAlignment();
|
||||||
|
|
||||||
if (!ChildrenQueue.Any())
|
if (!ChildrenQueue.Any())
|
||||||
return SpacePlan.FullRender(Size.Zero);
|
return SpacePlan.FullRender(Size.Zero);
|
||||||
|
|
||||||
@@ -81,6 +85,8 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
|
SetDefaultAlignment();
|
||||||
|
|
||||||
var lines = Compose(availableSpace);
|
var lines = Compose(availableSpace);
|
||||||
var topOffset = 0f;
|
var topOffset = 0f;
|
||||||
|
|
||||||
@@ -103,7 +109,6 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
var elementOffset = ElementOffset();
|
var elementOffset = ElementOffset();
|
||||||
var leftOffset = AlignOffset();
|
var leftOffset = AlignOffset();
|
||||||
Canvas.Translate(new Position(leftOffset, 0));
|
|
||||||
|
|
||||||
foreach (var measurement in lineMeasurements)
|
foreach (var measurement in lineMeasurements)
|
||||||
{
|
{
|
||||||
@@ -113,16 +118,17 @@ namespace QuestPDF.Elements
|
|||||||
if (size.Height == 0)
|
if (size.Height == 0)
|
||||||
size = new Size(size.Width, lineSize.Height);
|
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);
|
measurement.Element.Draw(size);
|
||||||
Canvas.Translate(new Position(0, -baselineOffset));
|
Canvas.Translate(offset.Reverse());
|
||||||
|
|
||||||
leftOffset += size.Width + elementOffset;
|
leftOffset += size.Width + elementOffset;
|
||||||
Canvas.Translate(new Position(size.Width + elementOffset, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas.Translate(new Position(-leftOffset, 0));
|
|
||||||
|
|
||||||
float ElementOffset()
|
float ElementOffset()
|
||||||
{
|
{
|
||||||
var difference = availableSpace.Width - lineSize.Width;
|
var difference = availableSpace.Width - lineSize.Width;
|
||||||
@@ -140,15 +146,15 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
float AlignOffset()
|
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
|
return ElementsAlignment switch
|
||||||
{
|
{
|
||||||
InlinedAlignment.Left => 0,
|
InlinedAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||||
InlinedAlignment.Justify => 0,
|
InlinedAlignment.Justify => 0,
|
||||||
InlinedAlignment.SpaceAround => elementOffset,
|
InlinedAlignment.SpaceAround => elementOffset,
|
||||||
InlinedAlignment.Center => difference / 2,
|
InlinedAlignment.Center => emptySpace / 2,
|
||||||
InlinedAlignment.Right => difference,
|
InlinedAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -167,6 +173,16 @@ namespace QuestPDF.Elements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetDefaultAlignment()
|
||||||
|
{
|
||||||
|
if (ElementsAlignment.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ElementsAlignment = ContentDirection == ContentDirection.LeftToRight
|
||||||
|
? InlinedAlignment.Left
|
||||||
|
: InlinedAlignment.Right;
|
||||||
|
}
|
||||||
|
|
||||||
Size GetLineSize(ICollection<InlinedMeasurement> measurements)
|
Size GetLineSize(ICollection<InlinedMeasurement> measurements)
|
||||||
{
|
{
|
||||||
var width = measurements.Sum(x => x.Size.Width);
|
var width = measurements.Sum(x => x.Size.Width);
|
||||||
@@ -212,7 +228,7 @@ namespace QuestPDF.Elements
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
var element = queue.Peek();
|
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)
|
if (size.Type == SpacePlanType.Wrap)
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements
|
|||||||
Horizontal
|
Horizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Line : Element, ILine, ICacheable, IContent
|
internal class Line : Element, ILine, ICacheable
|
||||||
{
|
{
|
||||||
public LineType Type { get; set; } = LineType.Vertical;
|
public LineType Type { get; set; } = LineType.Vertical;
|
||||||
public string Color { get; set; } = Colors.Black;
|
public string Color { get; set; } = Colors.Black;
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class MinimalBox : ContainerElement
|
internal class MinimalBox : ContainerElement, IContentDirectionAware
|
||||||
{
|
{
|
||||||
|
public ContentDirection ContentDirection { get; set; }
|
||||||
|
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
var targetSize = base.Measure(availableSpace);
|
var targetSize = base.Measure(availableSpace);
|
||||||
@@ -12,7 +14,13 @@ namespace QuestPDF.Elements
|
|||||||
if (targetSize.Type == SpacePlanType.Wrap)
|
if (targetSize.Type == SpacePlanType.Wrap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||||
|
? new Position(availableSpace.Width - targetSize.Width, 0)
|
||||||
|
: Position.Zero;
|
||||||
|
|
||||||
|
Canvas.Translate(translate);
|
||||||
base.Draw(targetSize);
|
base.Draw(targetSize);
|
||||||
|
Canvas.Translate(translate.Reverse());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,8 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
internal class Page : IComponent
|
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 MinSize { get; set; } = PageSizes.A4;
|
||||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||||
@@ -30,6 +31,7 @@ namespace QuestPDF.Elements
|
|||||||
public void Compose(IContainer container)
|
public void Compose(IContainer container)
|
||||||
{
|
{
|
||||||
container
|
container
|
||||||
|
.ContentDirection(ContentDirection)
|
||||||
.Background(BackgroundColor)
|
.Background(BackgroundColor)
|
||||||
.Layers(layers =>
|
.Layers(layers =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Text))
|
if (string.IsNullOrWhiteSpace(Text))
|
||||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||||
|
|
||||||
else
|
else
|
||||||
x.Text(Text).FontSize(14);
|
x.Text(Text).FontSize(14);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using QuestPDF.Infrastructure;
|
|
||||||
|
|
||||||
namespace QuestPDF.Elements
|
|
||||||
{
|
|
||||||
internal class RepeatContent : ContainerElement
|
|
||||||
{
|
|
||||||
public bool Repeat { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,8 +31,10 @@ namespace QuestPDF.Elements
|
|||||||
public Position Offset { get; set; }
|
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 List<RowItem> Items { get; } = new();
|
||||||
internal float Spacing { get; set; }
|
internal float Spacing { get; set; }
|
||||||
|
|
||||||
@@ -91,9 +93,13 @@ namespace QuestPDF.Elements
|
|||||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||||
continue;
|
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);
|
command.RowItem.Draw(command.Size);
|
||||||
Canvas.Translate(command.Offset.Reverse());
|
Canvas.Translate(offset.Reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Items.All(x => x.IsRendered))
|
if (Items.All(x => x.IsRendered))
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements.Table
|
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 ContentDirection ContentDirection { get; set; }
|
||||||
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
|
||||||
|
public List<TableColumnDefinition> Columns { get; set; } = new();
|
||||||
|
public List<TableCell> Cells { get; set; } = new();
|
||||||
public bool ExtendLastCellsToTableBottom { get; set; }
|
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||||
|
|
||||||
|
private bool CacheInitialized { get; set; }
|
||||||
private int StartingRowsCount { get; set; }
|
private int StartingRowsCount { get; set; }
|
||||||
private int RowsCount { get; set; }
|
private int RowsCount { get; set; }
|
||||||
private int CurrentRow { get; set; }
|
private int CurrentRow { get; set; }
|
||||||
@@ -24,16 +27,7 @@ namespace QuestPDF.Elements.Table
|
|||||||
// inner table: list of all cells that ends at the corresponding row
|
// inner table: list of all cells that ends at the corresponding row
|
||||||
private TableCell[][] CellsCache { get; set; }
|
private TableCell[][] CellsCache { get; set; }
|
||||||
private int MaxRow { 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()
|
internal override IEnumerable<Element?> GetChildren()
|
||||||
{
|
{
|
||||||
@@ -42,12 +36,27 @@ namespace QuestPDF.Elements.Table
|
|||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
|
Initialize();
|
||||||
|
|
||||||
foreach (var x in Cells)
|
foreach (var x in Cells)
|
||||||
x.IsRendered = false;
|
x.IsRendered = false;
|
||||||
|
|
||||||
CurrentRow = 1;
|
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()
|
private void BuildCache()
|
||||||
{
|
{
|
||||||
if (CellsCache != null)
|
if (CellsCache != null)
|
||||||
@@ -56,6 +65,7 @@ namespace QuestPDF.Elements.Table
|
|||||||
if (Cells.Count == 0)
|
if (Cells.Count == 0)
|
||||||
{
|
{
|
||||||
MaxRow = 0;
|
MaxRow = 0;
|
||||||
|
MaxRowSpan = 1;
|
||||||
CellsCache = Array.Empty<TableCell[]>();
|
CellsCache = Array.Empty<TableCell[]>();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -66,6 +76,7 @@ namespace QuestPDF.Elements.Table
|
|||||||
.ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
|
.ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
|
||||||
|
|
||||||
MaxRow = groups.Max(x => x.Key);
|
MaxRow = groups.Max(x => x.Key);
|
||||||
|
MaxRowSpan = Cells.Max(x => x.RowSpan);
|
||||||
|
|
||||||
CellsCache = Enumerable
|
CellsCache = Enumerable
|
||||||
.Range(0, MaxRow + 1)
|
.Range(0, MaxRow + 1)
|
||||||
@@ -109,9 +120,13 @@ namespace QuestPDF.Elements.Table
|
|||||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||||
continue;
|
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);
|
command.Cell.Draw(command.Size);
|
||||||
Canvas.Translate(command.Offset.Reverse());
|
Canvas.Translate(offset.Reverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
||||||
@@ -201,7 +216,7 @@ namespace QuestPDF.Elements.Table
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cell visibility optimizations
|
// cell visibility optimizations
|
||||||
if (cell.Row > maxRenderingRow)
|
if (cell.Row > maxRenderingRow + MaxRowSpan)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// calculate cell position / size
|
// calculate cell position / size
|
||||||
|
|||||||
@@ -121,7 +121,11 @@ namespace QuestPDF.Elements.Text
|
|||||||
{
|
{
|
||||||
foreach (var textBlockItem in textBlockItems)
|
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)
|
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
|
||||||
{
|
{
|
||||||
@@ -133,16 +137,22 @@ namespace QuestPDF.Elements.Text
|
|||||||
|
|
||||||
foreach (var textRun in textRuns)
|
foreach (var textRun in textRuns)
|
||||||
{
|
{
|
||||||
yield return new TextBlockSpan
|
var newElement = textBlockSpan switch
|
||||||
{
|
{
|
||||||
Text = textRun.Content,
|
TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url },
|
||||||
Style = textRun.Style
|
TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName },
|
||||||
|
TextBlockSpan => new TextBlockSpan()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
newElement.Text = textRun.Content;
|
||||||
|
newElement.Style = textRun.Style;
|
||||||
|
|
||||||
|
yield return newElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return textBlockItem;
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||||
{
|
{
|
||||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
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));
|
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)
|
public void Draw(TextDrawingRequest request)
|
||||||
{
|
{
|
||||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
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));
|
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||||
Element.Draw(new Size(request.TextSize.Width, -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 string Text { get; set; }
|
||||||
public TextStyle Style { get; set; } = TextStyle.Default;
|
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 ();
|
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
|
||||||
protected virtual bool EnableTextCache => true;
|
protected virtual bool EnableTextCache => true;
|
||||||
@@ -47,11 +47,11 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
// ignore leading spaces
|
// ignore leading spaces
|
||||||
if (!request.IsFirstElementInBlock && request.IsFirstElementInLine)
|
if (!request.IsFirstElementInBlock && request.IsFirstElementInLine)
|
||||||
{
|
{
|
||||||
while (startIndex < TextShapingResult.Glyphs.Length && Text[startIndex] == spaceCodepoint)
|
while (startIndex < TextShapingResult.Length && Text[startIndex] == spaceCodepoint)
|
||||||
startIndex++;
|
startIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextShapingResult.Glyphs.Length == 0 || startIndex == TextShapingResult.Glyphs.Length)
|
if (TextShapingResult.Length == 0 || startIndex == TextShapingResult.Length)
|
||||||
{
|
{
|
||||||
return new TextMeasurementResult
|
return new TextMeasurementResult
|
||||||
{
|
{
|
||||||
@@ -90,7 +90,7 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
EndIndex = wrappedText.Value.endIndex,
|
EndIndex = wrappedText.Value.endIndex,
|
||||||
NextIndex = wrappedText.Value.nextIndex,
|
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)
|
// textLength - length of the part of the text that fits in available width (creating a line)
|
||||||
|
|
||||||
// entire text fits, no need to wrap
|
// entire text fits, no need to wrap
|
||||||
if (endIndex == TextShapingResult.Glyphs.Length - 1)
|
if (endIndex == TextShapingResult.Length - 1)
|
||||||
return (endIndex, endIndex);
|
return (endIndex, endIndex);
|
||||||
|
|
||||||
// breaking anywhere
|
// breaking anywhere
|
||||||
@@ -110,7 +110,7 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
return (endIndex, endIndex + 1);
|
return (endIndex, endIndex + 1);
|
||||||
|
|
||||||
// current line ends at word, next character is space, perfect place to wrap
|
// 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);
|
return (endIndex, endIndex + 2);
|
||||||
|
|
||||||
// find last space within the available text to wrap
|
// find last space within the available text to wrap
|
||||||
@@ -118,7 +118,7 @@ namespace QuestPDF.Elements.Text.Items
|
|||||||
|
|
||||||
while (lastSpaceIndex >= startIndex)
|
while (lastSpaceIndex >= startIndex)
|
||||||
{
|
{
|
||||||
if (TextShapingResult.Glyphs[lastSpaceIndex].Codepoint == spaceCodepoint)
|
if (TextShapingResult[lastSpaceIndex].Codepoint == spaceCodepoint)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
lastSpaceIndex--;
|
lastSpaceIndex--;
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements.Text
|
namespace QuestPDF.Elements.Text
|
||||||
{
|
{
|
||||||
internal class TextBlock : Element, IStateResettable, IContent
|
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 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));
|
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
|
||||||
@@ -51,8 +53,20 @@ namespace QuestPDF.Elements.Text
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetDefaultAlignment()
|
||||||
|
{
|
||||||
|
if (Alignment.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Alignment = ContentDirection == ContentDirection.LeftToRight
|
||||||
|
? HorizontalAlignment.Left
|
||||||
|
: HorizontalAlignment.Right;
|
||||||
|
}
|
||||||
|
|
||||||
internal override SpacePlan Measure(Size availableSpace)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
|
SetDefaultAlignment();
|
||||||
|
|
||||||
if (!RenderingQueue.Any())
|
if (!RenderingQueue.Any())
|
||||||
return SpacePlan.FullRender(Size.Zero);
|
return SpacePlan.FullRender(Size.Zero);
|
||||||
|
|
||||||
@@ -80,22 +94,18 @@ namespace QuestPDF.Elements.Text
|
|||||||
|
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
|
SetDefaultAlignment();
|
||||||
|
|
||||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||||
|
|
||||||
if (!lines.Any())
|
if (!lines.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var heightOffset = 0f;
|
var topOffset = 0f;
|
||||||
var widthOffset = 0f;
|
|
||||||
|
|
||||||
foreach (var line in lines)
|
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)
|
foreach (var item in line.Elements)
|
||||||
{
|
{
|
||||||
@@ -111,21 +121,20 @@ namespace QuestPDF.Elements.Text
|
|||||||
TotalAscent = line.Ascent
|
TotalAscent = line.Ascent
|
||||||
};
|
};
|
||||||
|
|
||||||
item.Item.Draw(textDrawingRequest);
|
var canvasOffset = ContentDirection == ContentDirection.LeftToRight
|
||||||
|
? new Position(leftOffset, topOffset - line.Ascent)
|
||||||
|
: new Position(availableSpace.Width - leftOffset - item.Measurement.Width, topOffset - line.Ascent);
|
||||||
|
|
||||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
Canvas.Translate(canvasOffset);
|
||||||
widthOffset += item.Measurement.Width;
|
item.Item.Draw(textDrawingRequest);
|
||||||
|
Canvas.Translate(canvasOffset.Reverse());
|
||||||
|
|
||||||
|
leftOffset += item.Measurement.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
topOffset += line.LineHeight;
|
||||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
|
||||||
Canvas.Translate(new Position(0, line.LineHeight));
|
|
||||||
|
|
||||||
heightOffset += line.LineHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas.Translate(new Position(0, -heightOffset));
|
|
||||||
|
|
||||||
lines
|
lines
|
||||||
.SelectMany(x => x.Elements)
|
.SelectMany(x => x.Elements)
|
||||||
.GroupBy(x => x.Item)
|
.GroupBy(x => x.Item)
|
||||||
@@ -142,18 +151,15 @@ namespace QuestPDF.Elements.Text
|
|||||||
|
|
||||||
float GetAlignmentOffset(float lineWidth)
|
float GetAlignmentOffset(float lineWidth)
|
||||||
{
|
{
|
||||||
if (Alignment == HorizontalAlignment.Left)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var emptySpace = availableSpace.Width - lineWidth;
|
var emptySpace = availableSpace.Width - lineWidth;
|
||||||
|
|
||||||
if (Alignment == HorizontalAlignment.Right)
|
return Alignment switch
|
||||||
return emptySpace;
|
{
|
||||||
|
HorizontalAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||||
if (Alignment == HorizontalAlignment.Center)
|
HorizontalAlignment.Center => emptySpace / 2,
|
||||||
return emptySpace / 2;
|
HorizontalAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||||
|
_ => 0
|
||||||
throw new ArgumentException();
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
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)
|
internal override SpacePlan Measure(Size availableSpace)
|
||||||
{
|
{
|
||||||
var childSize = base.Measure(Size.Max);
|
var childSize = base.Measure(Size.Max);
|
||||||
@@ -25,7 +27,13 @@ namespace QuestPDF.Elements
|
|||||||
if (measurement.Type == SpacePlanType.Wrap)
|
if (measurement.Type == SpacePlanType.Wrap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||||
|
? new Position(-measurement.Width, 0)
|
||||||
|
: Position.Zero;
|
||||||
|
|
||||||
|
Canvas.Translate(translate);
|
||||||
base.Draw(measurement);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
Decoration.Before = container;
|
Decoration.Before = container;
|
||||||
return container.RepeatContent();
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Before(Action<IContainer> handler)
|
public void Before(Action<IContainer> handler)
|
||||||
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
Decoration.After = container;
|
Decoration.After = container;
|
||||||
return container.RepeatContent();
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void After(Action<IContainer> handler)
|
public void After(Action<IContainer> handler)
|
||||||
|
|||||||
@@ -195,13 +195,5 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
return element.Element(new ScaleToFit());
|
return element.Element(new ScaleToFit());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IContainer RepeatContent(this IContainer element, bool enabled = true)
|
|
||||||
{
|
|
||||||
return element.Element(new RepeatContent
|
|
||||||
{
|
|
||||||
Repeat = enabled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public static class GridExtensions
|
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)
|
public static void Grid(this IContainer element, Action<GridDescriptor> handler)
|
||||||
{
|
{
|
||||||
var descriptor = new GridDescriptor();
|
var descriptor = new GridDescriptor();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace QuestPDF.Fluent
|
|||||||
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
||||||
public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
|
public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
|
||||||
|
|
||||||
|
internal void Alignment(InlinedAlignment? alignment) => Inlined.ElementsAlignment = alignment;
|
||||||
public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
|
public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
|
||||||
public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
|
public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
|
||||||
public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
|
public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
internal Page Page { get; } = new Page();
|
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);
|
var pageSize = new PageSize(width, height, unit);
|
||||||
|
|
||||||
@@ -40,6 +42,10 @@ namespace QuestPDF.Fluent
|
|||||||
Page.MaxSize = pageSize;
|
Page.MaxSize = pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Margin
|
||||||
|
|
||||||
public void MarginLeft(float value, Unit unit = Unit.Point)
|
public void MarginLeft(float value, Unit unit = Unit.Point)
|
||||||
{
|
{
|
||||||
Page.MarginLeft = value.ToPoints(unit);
|
Page.MarginLeft = value.ToPoints(unit);
|
||||||
@@ -78,6 +84,10 @@ namespace QuestPDF.Fluent
|
|||||||
MarginHorizontal(value, unit);
|
MarginHorizontal(value, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
public void DefaultTextStyle(TextStyle textStyle)
|
public void DefaultTextStyle(TextStyle textStyle)
|
||||||
{
|
{
|
||||||
Page.DefaultTextStyle = textStyle;
|
Page.DefaultTextStyle = textStyle;
|
||||||
@@ -88,6 +98,16 @@ namespace QuestPDF.Fluent
|
|||||||
DefaultTextStyle(handler(TextStyle.Default));
|
DefaultTextStyle(handler(TextStyle.Default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ContentFromLeftToRight()
|
||||||
|
{
|
||||||
|
Page.ContentDirection = ContentDirection.LeftToRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContentFromRightToLeft()
|
||||||
|
{
|
||||||
|
Page.ContentDirection = ContentDirection.RightToLeft;
|
||||||
|
}
|
||||||
|
|
||||||
public void PageColor(string color)
|
public void PageColor(string color)
|
||||||
{
|
{
|
||||||
Page.BackgroundColor = color;
|
Page.BackgroundColor = color;
|
||||||
@@ -99,6 +119,10 @@ namespace QuestPDF.Fluent
|
|||||||
PageColor(color);
|
PageColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Slots
|
||||||
|
|
||||||
public IContainer Background()
|
public IContainer Background()
|
||||||
{
|
{
|
||||||
var container = new Container();
|
var container = new Container();
|
||||||
@@ -133,6 +157,8 @@ namespace QuestPDF.Fluent
|
|||||||
Page.Footer = container;
|
Page.Footer = container;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PageExtensions
|
public static class PageExtensions
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||||
private TextStyle? DefaultStyle { get; set; }
|
private TextStyle? DefaultStyle { get; set; }
|
||||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
internal HorizontalAlignment? Alignment { get; set; }
|
||||||
private float Spacing { get; set; } = 0f;
|
private float Spacing { get; set; } = 0f;
|
||||||
|
|
||||||
public void DefaultTextStyle(TextStyle style)
|
public void DefaultTextStyle(TextStyle style)
|
||||||
@@ -242,7 +242,7 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
internal void Compose(IContainer container)
|
internal void Compose(IContainer container)
|
||||||
{
|
{
|
||||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
TextBlocks.ToList().ForEach(x => x.Alignment ??= Alignment);
|
||||||
|
|
||||||
if (DefaultStyle != null)
|
if (DefaultStyle != null)
|
||||||
container = container.DefaultTextStyle(DefaultStyle);
|
container = container.DefaultTextStyle(DefaultStyle);
|
||||||
|
|||||||
@@ -164,5 +164,27 @@ namespace QuestPDF.Fluent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
#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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace QuestPDF.Infrastructure
|
||||||
|
{
|
||||||
|
internal enum ContentDirection
|
||||||
|
{
|
||||||
|
LeftToRight,
|
||||||
|
RightToLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,12 +15,6 @@ namespace QuestPDF.Infrastructure
|
|||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
|
|
||||||
{
|
|
||||||
PageContext = pageContext;
|
|
||||||
Canvas = canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual void CreateProxy(Func<Element?, Element?> create)
|
internal virtual void CreateProxy(Func<Element?, Element?> create)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace QuestPDF.Infrastructure
|
|
||||||
{
|
|
||||||
internal interface IContent
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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? HasStrikethrough { get; set; }
|
||||||
internal bool? HasUnderline { get; set; }
|
internal bool? HasUnderline { get; set; }
|
||||||
internal bool? WrapAnywhere { get; set; }
|
internal bool? WrapAnywhere { get; set; }
|
||||||
|
internal TextDirection? Direction { get; set; }
|
||||||
|
|
||||||
internal TextStyle? Fallback { get; set; }
|
internal TextStyle? Fallback { get; set; }
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ namespace QuestPDF.Infrastructure
|
|||||||
HasStrikethrough = false,
|
HasStrikethrough = false,
|
||||||
HasUnderline = false,
|
HasUnderline = false,
|
||||||
WrapAnywhere = false,
|
WrapAnywhere = false,
|
||||||
|
Direction = TextDirection.Auto,
|
||||||
Fallback = null
|
Fallback = null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace QuestPDF.Infrastructure
|
|||||||
HasStrikethrough,
|
HasStrikethrough,
|
||||||
HasUnderline,
|
HasUnderline,
|
||||||
WrapAnywhere,
|
WrapAnywhere,
|
||||||
Fallback
|
Fallback,
|
||||||
|
Direction
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class TextStyleManager
|
internal static class TextStyleManager
|
||||||
@@ -193,6 +194,19 @@ namespace QuestPDF.Infrastructure
|
|||||||
return origin with { Fallback = castedValue };
|
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.");
|
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.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
||||||
|
result = MutateStyle(result, TextStyleProperty.Direction, parent.Direction, overrideStyle);
|
||||||
|
|
||||||
if (applyFallback)
|
if (applyFallback)
|
||||||
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Previewer
|
|||||||
public event Action? OnPreviewerStopped;
|
public event Action? OnPreviewerStopped;
|
||||||
|
|
||||||
private const int RequiredPreviewerVersionMajor = 2022;
|
private const int RequiredPreviewerVersionMajor = 2022;
|
||||||
private const int RequiredPreviewerVersionMinor = 9;
|
private const int RequiredPreviewerVersionMinor = 11;
|
||||||
|
|
||||||
public PreviewerService(int port)
|
public PreviewerService(int port)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<Authors>MarcinZiabek</Authors>
|
<Authors>MarcinZiabek</Authors>
|
||||||
<Company>CodeFlint</Company>
|
<Company>CodeFlint</Company>
|
||||||
<PackageId>QuestPDF</PackageId>
|
<PackageId>QuestPDF</PackageId>
|
||||||
<Version>2022.9.0</Version>
|
<Version>2022.11.0-alpha1</Version>
|
||||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||||
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||||
<LangVersion>9</LangVersion>
|
<LangVersion>9</LangVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Install-Package QuestPDF
|
|||||||
dotnet add package QuestPDF
|
dotnet add package QuestPDF
|
||||||
|
|
||||||
// Package reference in .csproj file
|
// Package reference in .csproj file
|
||||||
<PackageReference Include="QuestPDF" Version="2022.3.0" />
|
<PackageReference Include="QuestPDF" Version="2022.11.0" />
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
2022.9.0
|
Added support for the right-to-left content direction.
|
||||||
- Implemented font-fallback algorithm,
|
Fixed: word-wrapping algorithm does not work correctly with right-to-left languages.
|
||||||
- Introduced new Settings API,
|
Fixed: Page.Size() API incorrectly uses Unit.Inch as default unit. Replaced with Unit.Point for consistency.
|
||||||
- Significantly reduced memory allocation cost for TextStyle objects,
|
|
||||||
- Implemented optional checking if all font glyphs are available,
|
|
||||||
- Minor text-rendering optimizations.
|
|
||||||
@@ -17,7 +17,7 @@ It offers a layouting engine designed with a full paging support in mind. The do
|
|||||||
|
|
||||||
Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements.
|
Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements.
|
||||||
|
|
||||||
## Please show the value
|
## Please help by giving a star
|
||||||
|
|
||||||
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
|
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ Special thanks to all companies that decided to sponsor QuestPDF development. Th
|
|||||||
| Company | Description |
|
| Company | Description |
|
||||||
|--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| <img src="Resources/jetbrains-logo.svg" width="100px"> | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!<br/>100$ / month |
|
| <img src="Resources/jetbrains-logo.svg" width="100px"> | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!<br/>100$ / month |
|
||||||
|
| <img src="https://avatars.githubusercontent.com/u/2712328?v=4" width="100px"> | [Mark Gould](https://github.com/markgould) supports this project. Thank you!<br/>100$ / month |
|
||||||
|
|
||||||
[](https://github.com/sponsors/QuestPDF)
|
[](https://github.com/sponsors/QuestPDF)
|
||||||
|
|
||||||
@@ -62,14 +63,14 @@ Install-Package QuestPDF
|
|||||||
dotnet add package QuestPDF
|
dotnet add package QuestPDF
|
||||||
|
|
||||||
// Package reference in .csproj file
|
// Package reference in .csproj file
|
||||||
<PackageReference Include="QuestPDF" Version="2022.6.0" />
|
<PackageReference Include="QuestPDF" Version="2022.9.0" />
|
||||||
```
|
```
|
||||||
|
|
||||||
[](https://www.nuget.org/packages/QuestPDF/)
|
[](https://www.nuget.org/packages/QuestPDF/)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
[](https://www.questpdf.com/getting-started.html)
|
[](https://www.questpdf.com/getting-started)
|
||||||
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||||
|
|
||||||
|
|
||||||
@@ -77,14 +78,14 @@ A short and easy to follow tutorial showing how to design an invoice document un
|
|||||||
A detailed description of behavior of all available components and how to use them with C# Fluent API.
|
A detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||||
|
|
||||||
|
|
||||||
[](https://www.questpdf.com/design-patterns.html)
|
[](https://www.questpdf.com/design-patterns)
|
||||||
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
||||||
|
|
||||||
## QuestPDF Previewer
|
## QuestPDF Previewer
|
||||||
|
|
||||||
The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task!
|
The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task!
|
||||||
|
|
||||||
[](https://www.questpdf.com/document-previewer.html)
|
[](https://www.questpdf.com/document-previewer)
|
||||||
|
|
||||||
|
|
||||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
||||||
|
|||||||
Reference in New Issue
Block a user